AWS CloudFormationn(CFn)のLambda-Backedカスタムリソースを利用していてレスポンスとしてネストされたJSONを返そうとしたらハマったので調べてみました。

ハマった内容

Lambda-Backedカスタムリソースで

import cfnresponse
def handler(event, context):
  data = {
    "hoge": "hoge",
    "foo": {
      "hoge": "foohoge"
    }
  }
  cfnresponse.send(event, context, cfnresponse.SUCCESS, data)

のようにCFnへレスポンスを返してCFnテンプレートで

Outputs:
  hoge:
    Value: !GetAtt CustomResource.hoge
  foo:
    Value: !GetAtt CustomResource.foo.hoge

と、カスタムリソースの値を参照すると!GetAtt CustomResource.foo.hogeでエラーになりました。
エラー内容はCustomResource attribute error: Vendor response doesn't contain foo.hoge key

解決策

Lambda-BackedカスタムリソースでJSONを返す場合はネストせずフラットなJSONを返しましょう。

以下検証内容となります。

検証内容

前提

  • AWSアカウントがある
  • AWS CLIが利用できる
  • AWS Lambda、CloudFormationの作成権限がある

エラーにならないケース

テンプレート

Lambda関数ではネストされたJSONを返しますが、!GetAtt ではネストされた値を参照しないパターンです。
これでスタック作成してもエラーになりません。

cfn-template.yaml

Resources:
  CustomResource:
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !GetAtt CustomResourceFunction.Arn

  CustomResourceFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Role: !GetAtt FunctionExecutionRole.Arn
      Code:
        ZipFile: !Sub |
          import cfnresponse
          def handler(event, context):
            data = {
              "hoge": "hoge",
              "foo": {
                "hoge": "foohoge"
              }
            }
            cfnresponse.send(event, context, cfnresponse.SUCCESS, data)
      Runtime: python3.7

  FunctionExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: "/"
      Policies:
      - PolicyName: root
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:PutLogEvents
            Resource: "arn:aws:logs:*:*:*"

Outputs:
  hoge:
    Value: !GetAtt CustomResource.hoge

スタック作成して確認する

> aws cloudformation create-stack \
  --stack-name cfn-response-test \
  --template-body file://cfn-template.yaml \
  --capabilities CAPABILITY_IAM

{
    "StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-response-test/55d8cd90-97b4-11e9-aa0c-128a65397f5a"
}


> aws cloudformation describe-stacks \
  --stack-name cfn-response-test

{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-response-test/55d8cd90-97b4-11e9-aa0c-128a65397f5a",
            "StackName": "cfn-response-test",
            "CreationTime": "2019-06-26T01:47:20.177Z",
            "RollbackConfiguration": {},
            "StackStatus": "CREATE_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Capabilities": [
                "CAPABILITY_IAM"
            ],
            "Outputs": [
                {
                    "OutputKey": "hoge",
                    "OutputValue": "hoge"
                }
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}

正常に作成できました。Lambda関数のログを確認しておきます。

> aws cloudformation list-stack-resources \
  --stack-name cfn-response-test

{
    "StackResourceSummaries": [
        {
            "LogicalResourceId": "CustomResource",
            "PhysicalResourceId": "2019/06/26/[$LATEST]f518c197bb8541f0b651a151bb201560",
            "ResourceType": "Custom::CustomResource",
            "LastUpdatedTimestamp": "2019-06-26T01:47:45.532Z",
            "ResourceStatus": "CREATE_COMPLETE",
            "DriftInformation": {
                "StackResourceDriftStatus": "NOT_CHECKED"
            }
        },
        {
            "LogicalResourceId": "CustomResourceFunction",
            "PhysicalResourceId": "cfn-response-test-CustomResourceFunction-HI0ITG58NF7G",
            "ResourceType": "AWS::Lambda::Function",
            "LastUpdatedTimestamp": "2019-06-26T01:47:39.855Z",
            "ResourceStatus": "CREATE_COMPLETE",
            "DriftInformation": {
                "StackResourceDriftStatus": "NOT_CHECKED"
            }
        },
        (略)
    ]
}


> aws logs get-log-events \
  --log-group-name /aws/lambda/cfn-response-test-CustomResourceFunction-HI0ITG58NF7G \
  --log-stream-name '2019/06/26/[$LATEST]f518c197bb8541f0b651a151bb201560' \
  --output=text \
  --query "events[*].message"

START RequestId: db73aff3-d14a-4ff1-92cc-6335d23279aa Version: $LATEST
        https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/(略)
        Response body:
        {"Status": "SUCCESS", "Reason": "See the details in CloudWatch Log Stream: 2019/06/26/[$LATEST]f518c197bb8541f0b651a151bb201560", "PhysicalResourceId": "2019/06/26/[$LATEST]f518c197bb8541f0b651a151bb201560", "StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-response-test/55d8cd90-97b4-11e9-aa0c-128a65397f5a", "RequestId": "c734ee9f-7963-4626-bf6e-40d7e24a99ab", "LogicalResourceId": "CustomResource", "NoEcho": false, "Data": {"hoge": "hoge", "foo": {"hoge": "foohoge"}}}
        Status code: OK
        END RequestId: db73aff3-d14a-4ff1-92cc-6335d23279aa
        REPORT RequestId: db73aff3-d14a-4ff1-92cc-6335d23279aa  Duration: 310.53 ms     Billed Duration: 400 ms         Memory Size: 128 MB   Max Memory Used: 24 MB

レスポンス内容のDataでネストされたJSONを返していることが確認できます。

エラーにならないケース

Outputsの定義を変更して確認します。

テンプレート(抜粋)

cfn-template.yaml_抜粋

Outputs:
  hoge:
    Value: !GetAtt CustomResource.hoge
  foo:
    Value: !GetAtt CustomResource.foo.hoge

スタック更新して確認する

> aws cloudformation update-stack \
  --stack-name cfn-response-test \
  --template-body file://cfn-template.yaml \
  --capabilities CAPABILITY_IAM

{
    "StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-response-test/55d8cd90-97b4-11e9-aa0c-128a65397f5a"
}


> aws cloudformation describe-stacks \
  --stack-name cfn-response-test

{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-response-test/55d8cd90-97b4-11e9-aa0c-128a65397f5a",
            "StackName": "cfn-response-test",
            "CreationTime": "2019-06-26T01:47:20.177Z",
            "LastUpdatedTime": "2019-06-26T01:50:44.408Z",
            "RollbackConfiguration": {},
            "StackStatus": "UPDATE_ROLLBACK_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Capabilities": [
                "CAPABILITY_IAM"
            ],
            "Outputs": [
                {
                    "OutputKey": "hoge",
                    "OutputValue": "hoge"
                }
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}

UPDATE_ROLLBACK_COMPLETEとなり更新に失敗しました。
イベントを取得してエラー内容を確認します。

> aws cloudformation describe-stack-events \
  --stack-name cfn-response-test

{
    "StackEvents": [
        {
            "StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-response-test/55d8cd90-97b4-11e9-aa0c-128a65397f5a",
            "EventId": "e01c4f90-97b4-11e9-bb14-12f81177eb92",
            "StackName": "cfn-response-test",
            "LogicalResourceId": "cfn-response-test",
            "PhysicalResourceId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-response-test/55d8cd90-97b4-11e9-aa0c-128a65397f5a",
            "ResourceType": "AWS::CloudFormation::Stack",
            "Timestamp": "2019-06-26T01:51:12.126Z",
            "ResourceStatus": "UPDATE_ROLLBACK_COMPLETE"
        },
        {
            "StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-response-test/55d8cd90-97b4-11e9-aa0c-128a65397f5a",
            "EventId": "df9dd0c0-97b4-11e9-9cbb-0eaa79b17470",
            "StackName": "cfn-response-test",
            "LogicalResourceId": "cfn-response-test",
            "PhysicalResourceId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-response-test/55d8cd90-97b4-11e9-aa0c-128a65397f5a",
            "ResourceType": "AWS::CloudFormation::Stack",
            "Timestamp": "2019-06-26T01:51:11.295Z",
            "ResourceStatus": "UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS"
        },
        {
            "StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-response-test/55d8cd90-97b4-11e9-aa0c-128a65397f5a",
            "EventId": "d1776100-97b4-11e9-a0b3-0a20b68b404c",
            "StackName": "cfn-response-test",
            "LogicalResourceId": "cfn-response-test",
            "PhysicalResourceId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-response-test/55d8cd90-97b4-11e9-aa0c-128a65397f5a",
            "ResourceType": "AWS::CloudFormation::Stack",
            "Timestamp": "2019-06-26T01:50:47.544Z",
            "ResourceStatus": "UPDATE_ROLLBACK_IN_PROGRESS",
            "ResourceStatusReason": "CustomResource attribute error: Vendor response doesn't contain foo.hoge key in object arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-response-test/55d8cd90-97b4-11e9-aa0c-128a65397f5a|CustomResource|c734ee9f-7963-4626-bf6e-40d7e24a99ab in S3 bucket cloudformation-custom-resource-storage-useast1"
        },
        (略)
    ]
}

CustomResource attribute error: Vendor response doesn't contain foo.hoge keyとエラー内容が確認できます。

参照方法を変えてみる

!GetAtt CustomResource.foo.hoge としているのがダメなのかなと考えて参照方法を変更してみました。

テンプレート(抜粋)

cfn-template.yaml_抜粋

Outputs:
  hoge:
    Value: !GetAtt CustomResource.hoge
  foo:
    Value: !GetAtt CustomResource.foo['hoge']

スタック更新して確認する

> aws cloudformation update-stack \
  --stack-name cfn-response-test \
  --template-body file://cfn-template.yaml \
  --capabilities CAPABILITY_IAM

{
    "StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-response-test/55d8cd90-97b4-11e9-aa0c-128a65397f5a"
}


> aws cloudformation describe-stacks \
  --stack-name cfn-response-test

{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-response-test/55d8cd90-97b4-11e9-aa0c-128a65397f5a",
            "StackName": "cfn-response-test",
            "CreationTime": "2019-06-26T01:47:20.177Z",
            "LastUpdatedTime": "2019-06-26T01:54:45.226Z",
            "RollbackConfiguration": {},
            "StackStatus": "UPDATE_ROLLBACK_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Capabilities": [
                "CAPABILITY_IAM"
            ],
            "Outputs": [
                {
                    "OutputKey": "hoge",
                    "OutputValue": "hoge"
                }
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}


> aws cloudformation describe-stack-events \
  --stack-name cfn-response-test

{
    "StackEvents": [
        {
            "StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-response-test/55d8cd90-97b4-11e9-aa0c-128a65397f5a",
            "EventId": "6f1d0b80-97b5-11e9-b714-0a2e8057061e",
            "StackName": "cfn-response-test",
            "LogicalResourceId": "cfn-response-test",
            "PhysicalResourceId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-response-test/55d8cd90-97b4-11e9-aa0c-128a65397f5a",
            "ResourceType": "AWS::CloudFormation::Stack",
            "Timestamp": "2019-06-26T01:55:12.038Z",
            "ResourceStatus": "UPDATE_ROLLBACK_COMPLETE"
        },
        {
            "StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-response-test/55d8cd90-97b4-11e9-aa0c-128a65397f5a",
            "EventId": "6eb6f6b0-97b5-11e9-aaf5-0a437bda7b86",
            "StackName": "cfn-response-test",
            "LogicalResourceId": "cfn-response-test",
            "PhysicalResourceId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-response-test/55d8cd90-97b4-11e9-aa0c-128a65397f5a",
            "ResourceType": "AWS::CloudFormation::Stack",
            "Timestamp": "2019-06-26T01:55:11.375Z",
            "ResourceStatus": "UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS"
        },
        {
            "StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-response-test/55d8cd90-97b4-11e9-aa0c-128a65397f5a",
            "EventId": "609a23e0-97b5-11e9-a31e-0e366af4fd90",
            "StackName": "cfn-response-test",
            "LogicalResourceId": "cfn-response-test",
            "PhysicalResourceId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-response-test/55d8cd90-97b4-11e9-aa0c-128a65397f5a",
            "ResourceType": "AWS::CloudFormation::Stack",
            "Timestamp": "2019-06-26T01:54:47.692Z",
            "ResourceStatus": "UPDATE_ROLLBACK_IN_PROGRESS",
            "ResourceStatusReason": "CustomResource attribute error: Vendor response doesn't contain foo['hoge'] key in object arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-response-test/55d8cd90-97b4-11e9-aa0c-128a65397f5a|CustomResource|c734ee9f-7963-4626-bf6e-40d7e24a99ab in S3 bucket cloudformation-custom-resource-storage-useast1"
        },
        (略)
    ]
}

CustomResource attribute error: Vendor response doesn't contain foo['hoge'] keyとエラーになりました。

まとめ

Lambda-BackedカスタムリソースでネストされたJSONを返しても!GetAttでは参照できないことがわかりました。ドキュメントにはDataの型がType: JSON objectと定義されているのですが制限があるみたいです。。。

カスタムリソースの応答オブジェクト – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/crpg-ref-responses.html

参考

カスタムリソースの応答オブジェクト – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/crpg-ref-responses.html

元記事はこちら

AWS CloudFormationのLambda-BackedカスタムリソースでネストされたJSONを返しても参照できない