aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.59k stars 3.89k forks source link

apigateway.BasePathMapping: How to set mapping correctly with custom domain? #20518

Closed xwnor closed 2 years ago

xwnor commented 2 years ago

Describe the issue

Hi,

I'd like to set a custom domain and stage mapping for my apigateway.RestApi. The console path is: API Gateway -> Custom domain names -> API mappings.

Say the default API endpoint is: https://abcdefg.execute-api.ap-northeast-1.amazonaws.com/dev (stage is dev in this case). I want to have the example.com domain to map to https://abcdefg.execute-api.ap-northeast-1.amazonaws.com/dev

So that: https://abcdefg.execute-api.ap-northeast-1.amazonaws.com/dev and https://example.com return the same result.

What I did

const domainNameString = 'example.com'

const apiCertificate = new acm.DnsValidatedCertificate(this, 'ApiCertificate', {
    domainName: domainNameString,
    hostedZone,
});

const api = new apigateway.RestApi(this, 'PublicGateway', {
    restApiName: 'name',
    deploy: false,
    endpointConfiguration: {
        types: [apigateway.EndpointType.REGIONAL],
    },
    domainName: {
        domainName: domainNameString,
        certificate: apiCertificate,
        securityPolicy: apigateway.SecurityPolicy.TLS_1_2,
    }
});

new route53.ARecord(this, 'AliasRecord', {
    recordName: domainNameString,
    zone: hostedZone,
    target: route53.RecordTarget.fromAlias(new route53Targets.ApiGateway(api))
});

new apigateway.CfnBasePathMapping(this, 'BasePathMapping', {
    domainName: domainNameString,
    restApiId: api.restApiId,
    stage: 'dev'
});

What I got

Resource handler returned message: "Base path already exists for this domain name (Service: ApiGateway, Status Code: 409, Request ID: )" (RequestToken: , HandlerErrorCode: AlreadyExists)

Could you have a look? Looking forward to your reply.

Links

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.BasePathMapping.html

otaviomacedo commented 2 years ago

When you create a RestApi passing in a domainName property, it internally creates a base path mapping on your behalf. Then you are trying to add another base path mapping yourself (new apigateway.CfnBasePathMapping(this, 'BasePathMapping', {...}). Since only one is allowed, the deployment fails.

Try this:

const api = new apigateway.RestApi(this, 'PublicGateway', {
  restApiName: 'name',
  deploy: false,
  endpointConfiguration: {
      types: [apigateway.EndpointType.REGIONAL],
  }, 
  // No domain name is passed in
});

// Instead, define it separately...
const domainName = new apigateway.DomainName(this, 'domain-name', {
  domainName: 'example.com',
  certificate: apiCertificate,
  securityPolicy: apigateway.SecurityPolicy.TLS_1_2,
});

// (Notice that this is an L2 construct)
new apigateway.BasePathMapping(this, 'BasePathMapping', {
  domainName: domainName, // ... and pass it in here
  restApi: restApi,
  stage: 'dev',
});
xwnor commented 2 years ago

Thanks for the explanation.

github-actions[bot] commented 2 years ago

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see. If you need more assistance, please either tag a team member or open a new issue that references this one. If you wish to keep having a conversation with other community members under this issue feel free to do so.

Aubtin commented 2 years ago

@otaviomacedo The following code which is what you posted above does not appear to work. It can be reproduced by copying the below into a new CDK project and running cdk synth:

const domainNameString = 'example.com';
const zoneId = 'ABC12345678';

const hostedZone = aws_route53.HostedZone.fromHostedZoneAttributes(this, "domainHostedZone", {
  zoneName: domainNameString,
  hostedZoneId: zoneId
});

const apiCertificate = new aws_certificatemanager.DnsValidatedCertificate(this, 'ApiCertificate', {
  domainName: domainNameString,
  hostedZone
});

const api = new aws_apigateway.RestApi(this, 'PublicGateway', {
  restApiName: 'name',
  deploy: false,
  endpointConfiguration: {
    types: [aws_apigateway.EndpointType.REGIONAL],
  },
  // No domain name is passed in
});

// Instead, define it separately...
const domainName = new aws_apigateway.DomainName(this, 'domain-name', {
  domainName: domainNameString,
  certificate: apiCertificate,
  securityPolicy: aws_apigateway.SecurityPolicy.TLS_1_2,
});

const deployment = new aws_apigateway.Deployment(this, "apiDeployment", { api });

const apiStage = new aws_apigateway.Stage(this, "prod", { deployment });

new aws_route53.ARecord(this, "ARecord", {
  zone: hostedZone,
  target: aws_route53.RecordTarget.fromAlias(new aws_route53_targets.ApiGateway(api))
});

// (Notice that this is an L2 construct)
new aws_apigateway.BasePathMapping(this, 'BasePathMapping', {
  domainName: domainName, // ... and pass it in here
  restApi: api,
  stage: apiStage,
});

cdk synth error:

Error: API does not define a default domain name

EDIT: The error is from the route53 record. Changing the target from ApiGateway to ApiGatewayDomain fixes the above code snippet.

BwL1289 commented 5 months ago

Just to add: make sure that deploy is set to false in RestApi (as it is above), and do not use the add_domain_name method as it automatically creates a mappingto the RestApi under the hood and you will get the Resource handler returned message: "Base path already exists for this domain name (Service: ApiGateway, Status Code: 409, Request ID: )" (RequestToken: , HandlerErrorCode: AlreadyExists) error still.