Search code examples
amazon-web-servicesaws-cloudformationaws-appsync

Is it possible to reference a CloudFormation resource name in an AppSync resolver?


I have a few DynamoDB table resources that I'm provisioning in my CloudFormation template (written in YAML), and elsewhere in the template I'm provisioning an AppSync endpoint.

I don't specify the names of the tables and let CloudFormation generate names for them. Whenever I need to specify the table name, I use

!Ref TableName

I want to use a DynamoDB Batch resolver in AppSync, which requires me to list the names of the tables, however "!Ref TableName" doesn't get converted to the name of the table when I update the stack. The resolver just ends up with "!Ref TableName" as the table name.

Is there a way I can get the name of the table generated by CloudFormation into the AppSync templating language?

Below is my CloudFormation template, trimmed for relevance:

conversationsTable:
Type: "AWS::DynamoDB::Table"
Properties:
  AttributeDefinitions:
    -
      AttributeName: "id"
      AttributeType: "S"
  KeySchema:
    -
      AttributeName: "id"
      KeyType: "HASH"
  ProvisionedThroughput:
    ReadCapacityUnits: "1"
    WriteCapacityUnits: "1"

userConversationsTable:
Type: "AWS::DynamoDB::Table"
Properties:
  AttributeDefinitions:
    -
      AttributeName: "userId"
      AttributeType: "S"
    -
      AttributeName: "conversationId"
      AttributeType: "S"
  KeySchema:
    -
      AttributeName: "userId"
      KeyType: "HASH"
    -
      AttributeName: "conversationId"
      KeyType: "RANGE"
  ProvisionedThroughput:
    ReadCapacityUnits: "1"
    WriteCapacityUnits: "1"

...

createConversationMutationResolver:
Type: "AWS::AppSync::Resolver"
Properties:
  ApiId: !GetAtt chatQLApi.ApiId
  TypeName: "Mutation"
  FieldName: "createConversation"
  DataSourceName: !GetAtt conversationsTableDataSource.Name
  RequestMappingTemplate: |
    {
      "version" : "2018-05-29",
      "operation" : "BatchPutItem",
      "tables": {
        !Ref conversationsTable : $util.toJson($convoList),
        !Ref userConversationsTable : $util.toJson($users)
      }
    }
  ResponseMappingTemplate: |
    #if($context.error)
      $util.appendError($context.error.message, $context.error.message)
    #end
    {
      "conversation": $util.toJson("${context.result.data}!Ref conversationTable")
      "userConversations": $util.toJson("${context.result.data}!Ref userConversationsTable")
    }

Solution

  • After reading the CloudFormation documentation on intrinsic functions, I was able to achieve my goal using the Sub and Join functions.

    In the RequestMapping template, I use Fn::Sub to set two VTL variables to the names of the tables, then I use Fn::Join to join the string with substitutions to the rest of the template string.

    In the ResponseMapping template, I put placeholders in the template code for everything that needs the table names, and sub those with Fn::Sub. Then to append the table names to the context object path, I use Fn::Join to build that path entirely using the CloudFormation template, and put it in the substitution map used by Fn::Sub.

    What follows is my template above with the changes

    createConversationMutationResolver:
    Type: "AWS::AppSync::Resolver"
    Properties:
      ApiId: !GetAtt chatQLApi.ApiId
      TypeName: "Mutation"
      FieldName: "createConversation"
      DataSourceName: !GetAtt conversationsTableDataSource.Name
      RequestMappingTemplate:
        !Join
          - ''
          - - !Sub
               - |
                 #set($conversationTable = "${conversationsTable}")
                 #set($userConversationTable = "${userConversationsTable}")
               - { conversationTable: !Ref conversationsTable, userConversationTable: !Ref userConversationsTable }
            - |
              {
                "version" : "2018-05-29",
                "operation" : "BatchPutItem",
                "tables": {
                  "${conversationTable}" : $util.toJson($convoList),
                  "${userConversationTable}" : $util.toJson($users)
                }
              }
      ResponseMappingTemplate:
        !Sub
          - |
            #if($context.error)
              $util.appendError($context.error.message, $context.error.message)
            #end
            {
              "${conversation}": $util.toJson(${conversationContext})
              "${userConversations}": $util.toJson(${userConversationContext})
            }
          - {
              conversation: !Ref conversationsTable,
              userConversations: !Ref userConversationsTable,
              conversationContext: !Join [ '.', ["$context.result.data", !Ref conversationsTable]],
              userConversationContext: !Join [ '.', ["$context.result.data", !Ref userConversationsTable]]
            }