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")
}
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]]
}