AWS CloudFormation(CFn)でAmazon Managed Blockchainのリソースを管理しようとテンプレート作成してた際にハマったエラーです。
エラー原因
JSONに含まれる日付がdatetime.datetime()
となっているため発生します。cfnresponse.send
でのjson.dumps
実行時にエラーとなります。
解決策
JSONに含まれる日付をdatetime.datetime()
から文字列に変換して対応します。
下記が参考になりました。
[Python] dateやdatetimeをjson.dumpでエラーなく出力する – YoheiM .NET
https://www.yoheim.net/blog.php?q=20170703
エラー再現と対応
前提
- AWSアカウントがある
- AWS CLIが利用できる
- AWS Lambda、CFnの作成権限がある
エラーとなるテンプレート定義
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 import datetime def handler(event, context): response = {} if event['RequestType'] == 'Create': response = { "Id": "hoge", "Datetime": datetime.datetime.today() } cfnresponse.send(event, context, cfnresponse.SUCCESS, response) 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.Datetime
Lambda関数を抜粋してみるとcfnresponse.send
のパラメータdata
のJSONdatetime.datetime.today()
で設定して値がdatetime.datetime()
となるようにします。
cfn-template.yaml_抜粋
import cfnresponse import datetime def handler(event, context): response = {} if event['RequestType'] == 'Create': response = { "Id": "hoge", "Datetime": datetime.datetime.today() } cfnresponse.send(event, context, cfnresponse.SUCCESS, response)
スタック作成して確認する
aws cloudformation create-stack \ --stack-name cfn-json-error \ --template-body file://cfn-template.yaml \ --capabilities CAPABILITY_IAM { "StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-json-error/2371e5d0-97e6-11e9-8372-0abba895ce2c" } # エラーでロールバックするまでしばらくかかります... aws cloudformation describe-stacks \ --stack-name cfn-json-error { "Stacks": [ { "StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-json-error/2371e5d0-97e6-11e9-8372-0abba895ce2c", "StackName": "cfn-json-error", "CreationTime": "2019-06-26T07:43:50.455Z", "DeletionTime": "2019-06-26T08:44:33.971Z", "RollbackConfiguration": {}, "StackStatus": "ROLLBACK_COMPLETE", "DisableRollback": false, "NotificationARNs": [], "Capabilities": [ "CAPABILITY_IAM" ], "Tags": [], "EnableTerminationProtection": false, "DriftInformation": { "StackDriftStatus": "NOT_CHECKED" } } ] }
Lambda関数のエラーログを一部抜粋します。
[ERROR] TypeError: Object of type datetime is not JSON serializable Traceback (most recent call last): File "/var/task/index.py", line 11, in handler cfnresponse.send(event, context, cfnresponse.SUCCESS, data) File "/var/task/cfnresponse.py", line 29, in send json_responseBody = json.dumps(responseBody) File "/var/lang/lib/python3.7/json/__init__.py", line 231, in dumps return _default_encoder.encode(obj) File "/var/lang/lib/python3.7/json/encoder.py", line 199, in encode chunks = self.iterencode(o, _one_shot=True) File "/var/lang/lib/python3.7/json/encoder.py", line 257, in iterencode return _iterencode(o, 0) File "/var/lang/lib/python3.7/json/encoder.py", line 179, in default raise TypeError(f'Object of type {o.__class__.__name__} '
エラー対応する
テンプレート定義
エラー対応したテンプレート定義です。
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 import json from datetime import date, datetime def json_serial(obj): if isinstance(obj, (datetime, date)): return obj.isoformat() raise TypeError ('Type %s not serializable' % type(obj)) def handler(event, context): response = {} if event['RequestType'] == 'Create': response = { "Id": "hoge", "Datetime": datetime.today() } response = json.loads(json.dumps(response, default=json_serial)) cfnresponse.send(event, context, cfnresponse.SUCCESS, response) 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.Datetime
ポイント
json.dumps
のdefault
を指定して日付を文字列に変換する
実装そのままです。json.dumps
でJSONを文字列変換する際にdefault
に指定したjson_serial
メソッドで日付を文字列に変換します。cfnresponse.send
にはJSONを渡す必要があるのでjson.loads
で文字列から再度JSONに戻しています。
cfn-template.yaml_対応版_抜粋
import cfnresponse import json from datetime import date, datetime def json_serial(obj): if isinstance(obj, (datetime, date)): return obj.isoformat() raise TypeError ('Type %s not serializable' % type(obj)) def handler(event, context): response = {} if event['RequestType'] == 'Create': response = { "Id": "hoge", "Datetime": datetime.today() } response = json.loads(json.dumps(response, default=json_serial)) cfnresponse.send(event, context, cfnresponse.SUCCESS, response)
再度スタック作成して確認する
aws cloudformation delete-stack \ --stack-name cfn-json-error > aws cloudformation create-stack \ --stack-name cfn-json-error \ --template-body file://cfn-template.yaml \ --capabilities CAPABILITY_IAM { "StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-json-error/fead9f00-97ef-11e9-b4fc-0e16aabfe77c" } > aws cloudformation describe-stacks \ --stack-name cfn-json-error { "Stacks": [ { "StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-json-error/fead9f00-97ef-11e9-b4fc-0e16aabfe77c", "StackName": "cfn-json-error", "CreationTime": "2019-06-26T08:54:23.736Z", "RollbackConfiguration": {}, "StackStatus": "CREATE_COMPLETE", "DisableRollback": false, "NotificationARNs": [], "Capabilities": [ "CAPABILITY_IAM" ], "Outputs": [ { "OutputKey": "hoge", "OutputValue": "2019-06-26T08:54:48.738374" } ], "Tags": [], "EnableTerminationProtection": false, "DriftInformation": { "StackDriftStatus": "NOT_CHECKED" } } ] }
無事に日付が文字列に変換されてスタック作成できたのが確認できました。
参考
[Python] dateやdatetimeをjson.dumpでエラーなく出力する – YoheiM .NET
https://www.yoheim.net/blog.php?q=20170703