Boto3でLambda@EdgeをCloudFrontにデプロイする
アカウントのLambdaがいくつかNode.js 10のランタイムを使っていて、Node.js 12以上への更新してねーとのこと。
まあこれ自体は単にランタイムを更新すればいいんですが、問題はLambda@Edgeです。
CloudFrontディストリビューション紐づけたLambda@Edge関数は、CloudFrontのコンソールから一つ一つBehaviorを選んで更新するか、Lambdaのコンソールからディストリビューションを選んで関数単位にデプロイするかでバージョンを上げれます。
ところが、複数のBehaviorに複数のNode.js 10のLambda@Edge関数がくっついていたためコンソールからだとと更新が中々しんどい事態になってました。
というわけでBoto3でスクリプトを組んで更新してみました。
まずは、Node.js 10の時の関数バージョンで紐づいちゃってる一覧をとります。
import boto3
cf = boto3.client('cloudfront')
distributions = cf.list_distributions()
if distributions['DistributionList']['Quantity'] > 0:
headers = [
"ID",
"Domain Name",
"Comment",
"State",
"Cache Behavior(Path Pattern)",
"Lambda Function",
]
print("\t".join(headers))
while True:
for distribution in distributions['DistributionList']['Items']:
row = [
distribution['Id'],
distribution['DomainName'],
distribution['Comment'],
distribution['Status'],
]
defaultCacheBehavior = distribution['DefaultCacheBehavior']
row.append('Default')
if defaultCacheBehavior['LambdaFunctionAssociations']['Quantity'] > 0:
for function in defaultCacheBehavior['LambdaFunctionAssociations']['Items']:
row.append(function['LambdaFunctionARN'])
print("\t".join(row))
cacheBehavior = distribution['CacheBehaviors']
if cacheBehavior['Quantity'] > 0:
for behavior in cacheBehavior['Items']:
row = ['','','','']
"""
row = [
distribution['Id'],
distribution['DomainName'],
distribution['Comment'],
distribution['Status'],
]
"""
row.append(behavior['PathPattern'])
if behavior['LambdaFunctionAssociations']['Quantity'] > 0:
for function in behavior['LambdaFunctionAssociations']['Items']:
row.append(function['LambdaFunctionARN'])
print("\t".join(row))
# Max 100
if 'NextMarker' in distributions['DistributionList']:
nextMarker = distributions['DistributionList']['NextMarker']
distributions = cf.list_distributions(Marker=nextMarker)
else:
break
else:
print("No CloudFront Distributions.")
$ python list_distributions.py > list_distributions.tsv
$ cat list_distributions.tsv
ID Domain Name Comment State Cache Behavior(Path Pattern) Lambda Function
XXXXXXXXXXXXXX xxxxxxxxxxxxx.cloudfront.net Deployed Default arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:foo-func:2
YYYYYYYYYYYYYY yyyyyyyyyyyyy.cloudfront.net Deployed Default arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:foo-func:2
/login arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:bar-func:3
~~~~~
PHPでやった時も思いましたがCloudFrontの設定情報はたくさんあってネストしてるので処理がめんどくさいです…。
では、本題のディストリビューションに紐づくLambda@Edgeの更新です。
import boto3
import json
import copy
from dictdiffer import diff
cf = boto3.client('cloudfront')
# 対象の環境に合わせてBehaviorのパスに紐づくLambda@EdgeのARNを調整
path_map_function_arn = {
'default': 'arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:foo-func:3',
'/login': 'arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:bar-func:4',
}
def update_distribution(id, dryrun):
try:
distribution_config = cf.get_distribution_config(
Id=id
)
distribution_config_old = copy.deepcopy(distribution_config)
defaultCacheBehavior = distribution_config['DistributionConfig']['DefaultCacheBehavior']
if defaultCacheBehavior['LambdaFunctionAssociations']['Quantity'] > 0:
for idx, function in enumerate(defaultCacheBehavior['LambdaFunctionAssociations']['Items']):
defaultCacheBehavior['LambdaFunctionAssociations']['Items'][idx]['LambdaFunctionARN'] \
= path_map_function_arn['default']
distribution_config['DistributionConfig']['DefaultCacheBehavior'] = defaultCacheBehavior
cacheBehavior = distribution_config['DistributionConfig']['CacheBehaviors']
if cacheBehavior['Quantity'] > 0:
for idx1, behavior in enumerate(cacheBehavior['Items']):
if behavior['PathPattern'] in path_map_function_arn:
if behavior['LambdaFunctionAssociations']['Quantity'] > 0:
for idx2, function in enumerate(behavior['LambdaFunctionAssociations']['Items']):
cacheBehavior['Items'][idx1]['LambdaFunctionAssociations']['Items'][idx2]['LambdaFunctionARN'] \
= path_map_function_arn[behavior['PathPattern']]
distribution_config['DistributionConfig']['CacheBehaviors'] = cacheBehavior
# 差分を確認
print(json.dumps(list(diff(distribution_config_old, distribution_config)), indent=2))
if not dryrun:
cf.update_distribution(
DistributionConfig=distribution_config["DistributionConfig"],
Id=id,
IfMatch=distribution_config["ETag"],
)
else:
print('Dry run update.')
except cf.exceptions.NoSuchDistribution as e:
print("This ID is not found.")
if __name__ == '__main__':
id = input('Please input distribution id:')
dryrun = input('Dry run y/n?:') == 'y'
update_distribution(id, dryrun)
$ python update_distribution.py
Please input distribution id:YYYYYYYYYYYYYY
Dry run y/n?:y
[
[
"change",
[
"DistributionConfig",
"DefaultCacheBehavior",
"LambdaFunctionAssociations",
"Items",
0,
"LambdaFunctionARN"
],
[
"arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:foo-func:2",
"arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:foo-func:3"
]
],
[
"change",
[
"DistributionConfig",
"CacheBehaviors",
"Items",
0,
"LambdaFunctionAssociations",
"Items",
0,
"LambdaFunctionARN"
],
[
"arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:bar-func:3",
"arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:bar-func:4"
]
]
]
Dry run update.
変えちゃいけないとこ変わったら嫌なので変更後の設定のDIFFをdictdifferで表示できるようにしています。
一覧取得と組み合わせれば一括更新もできるだろうけど確認しながら確実にやりたいので。
あんまりかっこいい感じに作れなかったけど、必要十分なことはできたのでヨシ!
ディスカッション
コメント一覧
まだ、コメントがありません