title: Correct CloudFormation For Route53 RecordSet to API Gateway
body: <p>I'm trying to deploy a CloudFormation template (through AWS CLI) that contains DynamoDB and some Lambdas served through API Gateway. The following is the template:</p>
  
  <pre class="lang-py prettyprint-override"><code>Resources:
    UTableArticle:
      Type: AWS::DynamoDB::Table
      Properties:
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1
        TableName: !Sub ${AWS::StackName}-Article
      UpdateReplacePolicy: Retain
      DeletionPolicy: Retain
    UIAMRoleFunctionServiceRoleArticle:
      Type: AWS::IAM::Role
      Properties:
        AssumeRolePolicyDocument:
          Statement:
            - Action: sts:AssumeRole
              Effect: Allow
              Principal:
                Service: lambda.amazonaws.com
          Version: "2012-10-17"
        ManagedPolicyArns:
          - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
  
    UIAMRoleFunctionServiceRolePolicyArticle:
      Type: AWS::IAM::Policy
      Properties:
        PolicyDocument:
          Statement:
            - Action:
                - dynamodb:BatchGetItem
                - dynamodb:GetRecords
                - dynamodb:GetShardIterator
                - dynamodb:Query
                - dynamodb:GetItem
                - dynamodb:Scan
                - dynamodb:BatchWriteItem
                - dynamodb:PutItem
                - dynamodb:UpdateItem
                - dynamodb:DeleteItem
              Effect: Allow
              Resource:
                - !GetAtt [ UTableArticle, Arn ]
                - !Ref AWS::NoValue
          Version: "2012-10-17"
        PolicyName: UIAMRoleFunctionServiceRolePolicyArticle
        Roles:
          - !Ref UIAMRoleFunctionServiceRoleArticle
  
    BFunctionSaveArticle:
      Type: AWS::Lambda::Function
      Properties:
        Code:
          S3Bucket: !Ref ArtefactRepositoryBucket
          S3Key: !Join [ '', [!Ref ArtefactRepositoryKeyPrefix, '.zip' ] ]
        Handler: !Ref 'SaveArticleHandler'
        Role: !GetAtt [ UIAMRoleFunctionServiceRoleArticle, Arn ]
        Runtime: java11
        Environment:
          Variables:
            TABLE_NAME: !Ref UTableArticle
            PRIMARY_KEY: id
      DependsOn:
        - UIAMRoleFunctionServiceRolePolicyArticle
        - UIAMRoleFunctionServiceRoleArticle
    BFunctionGetArticle:
      Type: AWS::Lambda::Function
      Properties:
        Code:
          S3Bucket: !Ref ArtefactRepositoryBucket
          S3Key: !Join [ '', [!Ref ArtefactRepositoryKeyPrefix, '.zip' ] ]
        Handler: !Ref 'GetArticleHandler'
        Role: !GetAtt [ UIAMRoleFunctionServiceRoleArticle, Arn ]
        Runtime: java11
        Environment:
          Variables:
            TABLE_NAME: !Ref UTableArticle
            PRIMARY_KEY: id
      DependsOn:
        - UIAMRoleFunctionServiceRolePolicyArticle
        - UIAMRoleFunctionServiceRoleArticle
    BFunctionListArticles:
      Type: AWS::Lambda::Function
      Properties:
        Code:
          S3Bucket: !Ref ArtefactRepositoryBucket
          S3Key: !Join [ '', [!Ref ArtefactRepositoryKeyPrefix, '.zip' ] ]
        Handler: !Ref 'ListArticlesHandler'
        Role: !GetAtt [ UIAMRoleFunctionServiceRoleArticle, Arn ]
        Runtime: java11
        Environment:
          Variables:
            TABLE_NAME: !Ref UTableArticle
            PRIMARY_KEY: id
      DependsOn:
        - UIAMRoleFunctionServiceRolePolicyArticle
        - UIAMRoleFunctionServiceRoleArticle
  
    BFunctionGWPermissionGetArticle:
      Type: AWS::Lambda::Permission
      DependsOn:
        - BlogRestApi
        - BFunctionListArticles
      Properties:
        Action: lambda:InvokeFunction
        FunctionName: !GetAtt [ BFunctionListArticles, Arn ]
        Principal: apigateway.amazonaws.com
        SourceArn: !Join ['', ['arn:', !Ref 'AWS::Partition', ':execute-api:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':', !Ref BlogRestApi, '/*/GET/article'] ]
    BFunctionGWPermissionPostArticle:
      Type: AWS::Lambda::Permission
      DependsOn:
        - BlogRestApi
        - BFunctionSaveArticle
      Properties:
        Action: lambda:InvokeFunction
        FunctionName: !GetAtt [ BFunctionSaveArticle, Arn ]
        Principal: apigateway.amazonaws.com
        SourceArn: !Join ['', ['arn:', !Ref 'AWS::Partition', ':execute-api:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':', !Ref BlogRestApi, '/*/POST/article'] ]
    BFunctionGWPermissionGetIdArticle:
      Type: AWS::Lambda::Permission
      DependsOn:
        - BlogRestApi
        - BFunctionGetArticle
      Properties:
        Action: lambda:InvokeFunction
        FunctionName: !GetAtt [ BFunctionGetArticle, Arn ]
        Principal: apigateway.amazonaws.com
        SourceArn: !Join ['', ['arn:', !Ref 'AWS::Partition', ':execute-api:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':', !Ref BlogRestApi, '/*/GET/article/{id}'] ]
    BFunctionGWPermissionPatchIdArticle:
      Type: AWS::Lambda::Permission
      DependsOn:
        - BlogRestApi
        - BFunctionSaveArticle
      Properties:
        Action: lambda:InvokeFunction
        FunctionName: !GetAtt [ BFunctionSaveArticle, Arn ]
        Principal: apigateway.amazonaws.com
        SourceArn: !Join ['', ['arn:', !Ref 'AWS::Partition', ':execute-api:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':', !Ref BlogRestApi, '/*/PATCH/article/{id}'] ]
    BlogRestApi:
      Type: AWS::ApiGateway::RestApi
      Properties:
        Name: Article
    UAGDeploymentArticle:
      Type: AWS::ApiGateway::Deployment
      Properties:
        RestApiId: !Ref BlogRestApi
        Description: Automatically created by the RestApi construct
      DependsOn:
        - UAGMethodArticleIdGet
        - UAGMethodArticleIdPatch
        - UAGResourceArticleId
        - UAGMethodArticleGet
        - UAGMethodArticlePost
        - UAGResourceArticle
    BAGDeploymentStageProdArticle:
      Type: AWS::ApiGateway::Stage
      Properties:
        RestApiId: !Ref BlogRestApi
        DeploymentId: !Ref UAGDeploymentArticle
        StageName: prod
    UAIMRoleCWPushArticle:
      Type: AWS::IAM::Role
      Properties:
        AssumeRolePolicyDocument:
          Statement:
            - Action: sts:AssumeRole
              Effect: Allow
              Principal:
                Service: !Join ["", ['apigateway.', !Ref "AWS::URLSuffix"] ]
          Version: "2012-10-17"
        ManagedPolicyArns:
          - !Join ['', ['arn:', !Ref 'AWS::Partition', ':iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs'] ]
    UAGAccountArticle:
      Type: AWS::ApiGateway::Account
      Properties:
        CloudWatchRoleArn: !GetAtt [ UAIMRoleCWPushArticle, Arn ]
      DependsOn:
        - BlogRestApi
    UAGResourceArticle:
      Type: AWS::ApiGateway::Resource
      Properties:
        ParentId: !GetAtt [ BlogRestApi, RootResourceId ]
        PathPart: article
        RestApiId: !Ref BlogRestApi
    UAGMethodArticleGet:
      Type: AWS::ApiGateway::Method
      Properties:
        HttpMethod: GET
        ResourceId: !Ref UAGResourceArticle
        RestApiId: !Ref BlogRestApi
        AuthorizationType: NONE
        Integration:
          IntegrationHttpMethod: POST
          Type: AWS_PROXY
          Uri: !Join [ "", ['arn:', !Ref 'AWS::Partition', ':apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt [ BFunctionListArticles, Arn ], '/invocations' ] ]
    UAGMethodArticlePost:
      Type: AWS::ApiGateway::Method
      Properties:
        HttpMethod: POST
        ResourceId: !Ref UAGResourceArticle
        RestApiId: !Ref BlogRestApi
        AuthorizationType: NONE
        Integration:
          IntegrationHttpMethod: POST
          Type: AWS_PROXY
          Uri: !Join [ "", ['arn:', !Ref 'AWS::Partition', ':apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt [ BFunctionSaveArticle, Arn ], '/invocations' ] ]
    UAGResourceArticleId:
      Type: AWS::ApiGateway::Resource
      Properties:
        ParentId: !Ref UAGResourceArticle
        PathPart: "{id}"
        RestApiId: !Ref BlogRestApi
    UAGMethodArticleIdGet:
      Type: AWS::ApiGateway::Method
      Properties:
        HttpMethod: GET
        ResourceId: !Ref UAGResourceArticleId
        RestApiId: !Ref BlogRestApi
        AuthorizationType: NONE
        Integration:
          IntegrationHttpMethod: POST
          Type: AWS_PROXY
          Uri: !Join [ "", ['arn:', !Ref 'AWS::Partition', ':apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt [ BFunctionGetArticle, Arn ], '/invocations' ] ]
    UAGMethodArticleIdPatch:
      Type: AWS::ApiGateway::Method
      Properties:
        HttpMethod: PATCH
        ResourceId: !Ref UAGResourceArticleId
        RestApiId: !Ref BlogRestApi
        AuthorizationType: NONE
        Integration:
          IntegrationHttpMethod: POST
          Type: AWS_PROXY
          Uri: !Join [ "", ['arn:', !Ref 'AWS::Partition', ':apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt [ BFunctionSaveArticle, Arn ], '/invocations' ] ]
    # apparently the below already exists??
    BlogAPIDomainName:
      Type: AWS::ApiGateway::DomainName
      Properties:
        DomainName: blogapi.zenithwebfoundry.com
        EndpointConfiguration:
          Types:
            - EDGE
        CertificateArn: 'arn:aws:acm:us-east-1:499908792600:certificate/2983bc14-28d4-43ab-b1da-fe9618a926d1'
        SecurityPolicy: TLS_1_0
    BlogAPIHostedZone:
      Type: AWS::Route53::HostedZone
      Properties:
        Name: !Ref BlogAPIDomainName
    BlogAPIBasePathMapping:
      Type: AWS::ApiGateway::BasePathMapping
      Properties:
        DomainName: !Ref BlogAPIDomainName
        RestApiId: !Ref BlogRestApi
        Stage: 'prod'
    Route53RecordSetGroup:
      Type: AWS::Route53::RecordSetGroup
      Properties:
        HostedZoneId: Z18TN67OCTIBE0 # zenithwebfoundry.com. HostedZoneId
        RecordSets:
          - Name: blog.zenithwebfoundry.com.
            Type: A
            TTL: '300'
            ResourceRecords:
              - 52.64.238.177
          - Name: blogapi.zenithwebfoundry.com
            Type: A
            AliasTarget:
              HostedZoneId: Z2FDTNDATAQYW2
              DNSName: !Ref BlogAPIDomainName
  
    AssetsBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: !Join [ ".", [ !Ref 'AWS::StackName', 'assets' ] ]
        CorsConfiguration:
          CorsRules:
            - AllowedHeaders: ['*']
              AllowedMethods: [GET,PUT,POST,DELETE,HEAD]
              AllowedOrigins: ['http://localhost*']
        PublicAccessBlockConfiguration:
          BlockPublicAcls: true
          BlockPublicPolicy: true
          IgnorePublicAcls: true
          RestrictPublicBuckets: true
    WebBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: !Join [ ".", [ !Ref 'AWS::StackName', 'web' ] ]
        PublicAccessBlockConfiguration:
          BlockPublicAcls: true
          BlockPublicPolicy: true
          IgnorePublicAcls: true
          RestrictPublicBuckets: true
        WebsiteConfiguration:
          IndexDocument: 'index.html'
          ErrorDocument: 'index.html'
  
  
  Parameters:
    ArtefactRepositoryBucket:
      Type: String
      Description: 'S3 bucket for the blog artefact zip'
    ArtefactRepositoryKeyPrefix:
      Type: String
      Description: 'S3 key prefix for the blog artefact zip'
    CodeVersion:
      Type: String
      Description: 'Asset version (either just major.minor.patch, or  major.minor.patch-SNAPSHOT)'
    SaveArticleHandler:
      Type: String
      Default: 'com.zenithwebfoundry.blog.api.SaveArticleHandler'
    GetArticleHandler:
      Type: String
      Default: 'com.zenithwebfoundry.blog.api.GetArticleHandler'
    ListArticlesHandler:
      Type: String
      Default: 'com.zenithwebfoundry.blog.api.ListArticlesHandler'
    ParamDnsDomain:
      Description: "Public DNS Zone Name"
      Type: String
      Default: 'zenithwebfoundry.com'
  
  Outputs:
    ArticleEndpoint:
      Value: !Join ["", ['https://', !Ref BlogRestApi, '.execute-api.ap-southeast-2.', !Ref 'AWS::URLSuffix', '/', !Ref BAGDeploymentStageProdArticle, '/'] ]
  
  </code></pre>
  
  <p>The part that I'm having difficulty with is the <code>AWS::Route53::RecordSetGroup</code> in particular the entry for <code>blogapi.zenithwebfoundry.com</code>. Essentially, stack creation fails at the RecordSetGroup with the error:</p>
  
  <pre><code>[RRSet with DNS name blogapi.zenithwebfoundry.com., type A contains an alias target that contains a hosted zone 108086391059049046 that is an invalid alias target., Tried to create an alias that targets z2fdtndataqyw2., type A in zone Z08366712O4E48ODE3XHX, but the alias target name does not lie within the target zone]
  </code></pre>
  
  <p>I have tried various permutations, including trying to use the HostedZone I set up here:</p>
  
  <pre><code>  BlogAPIHostedZone:
      Type: AWS::Route53::HostedZone
      Properties:
        Name: !Ref BlogAPIDomainName
  </code></pre>
  
  <p>Hoping that this would provide a valid reference, but, it failed with a different error, essentially saying that the record didn't exist.</p>
  
  <p>So I'm wondering how do you set up a record set with an alias that points to API Gateway. I have seen lots of AWS doco, but it seems to steer off to Regional API Gateway endpoints (which I think is a bit of a special case), or purely console-style instructions, which creates a HostedZoneId prior to the RecordSetGroup (allowing you to hard code the value). But how do you do it through a CloudFormation template.</p>
  
  <p>PS: no serverless.com answers please - this is purely about AWS CloudFormation and deploying stacks through the AWS CLI. I am only interested in answers that pertain to that.</p>
  
  <p><strong>EDIT</strong>
  Following a suggestion, I changed:</p>
  
  <pre><code>DNSName: !Ref BlogAPIDomainName
  </code></pre>
  
  <p>to:</p>
  
  <pre><code>DNSName: !GetAtt [BlogAPIDomainName, DistributionDomainName]
  </code></pre>
  
  <p>The deployment actually completed. So thank you.</p>