cloudpack あら便利カレンダー 2019 の記事となります。誕生秘話 はこちら。
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の作成権限がある
エラーとなるテンプレート定義
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()
となるようにします。
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__} '
エラー対応する
テンプレート定義
エラー対応したテンプレート定義です。
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に戻しています。
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