Layerを含むLambdaをboto3で構築する

Lambdaを自動構築するプログラムを作成する機会がありました。
boto3でやっている記事を探したのですが、文献が多くなく、
特に最近機能が追加されたLayer周りの記事はほぼないので、自分で書いてしまおうと。
最終的には公式ドキュメント読めば済むと思いますが、自分のためを含めて記事にします。

#前提
使用言語はPython2.7[^1]（おそらくPython3.6でも動きますが、未確認）
[^1]: Python2.7はサポート終了が間近にせまっているので、新規開発する方は私を見習わずに3系を使ってください。
boto3のバージョンは1.9.97を使用
lambdaを手で構築したことがあり、ある程度の仕組みや用語がわかる（例：ハンドラやroleなどがわかる）

#本題
##まずはLambdaの作成
lambdaに適応するコードはZIP圧縮して任意の場所に用意しておきます。
（今回の例では　zip/file/path　というpathに用意）
lambdaを実行するためのroleは事前に作成して用意しておきます。
Timeout、MemorySize、は最大で用意します（未指定でも可。その場合初期値）
環境変数にも任意の値を入れます。（未指定でも可。その場合、環境変数はなし）

```Python
region = "us-east-1"
access_key = "xxxxxxxx"
secret_key = "yyyyyyyy"
lambda_ = boto3.client('lambda', region_name=region, aws_access_key_id=access_key, aws_secret_access_key=secret_key)
iam = boto3.client("iam", region_name=region, aws_access_key_id=access_key, aws_secret_access_key=secret_key)

role = iam.get_role(RoleName="role-name")["Role"]["Arn"]
zip_file = open("zip/file/path", "rb").read()

lambda_.create_function(
	FunctionName="test_func", #関数名
	Runtime="python2.7", #ランタイム
	Role=role, #実行ロール
	Handler="lambda_function.lambda_handler", #ハンドラ
	Code={"ZipFile": zip_file}, #ソースコード（ZIPファイルアップロード）
	Timeout=60*15, #タイムアウト（秒数）
	MemorySize=3008, #メモリ（MB）
	Environment={ "Variables":{'AAA':'aaa', 'BBB':'bbb'} }, #環境変数
)
```
ZIPファイルでは上げられない大きさのファイル（10MB以上）を上げる場合は、コードをZIP圧縮してS3にアップします。そして、create_functionのCodeに指定するDictを　{'S3Bucket':'bucket_name','S3Key': 'file_key'}　にするとS3にあげたコードを適応することができます。

##作ったLambda関数にトリガを設定する
boto3のLambdaクラスのドキュメントを見ても、それらしきメソッドが見つからなかったのですが、
どうやらトリガを引きたいもののリソースをいじるようです。
下記の例はS3にファイルが入ったときに実行するトリガを設定した場合

```Python
#トリガを付けるためにS3からの実行権の付与
self.lambda_.add_permission(
	FunctionName="test_func",
	StatementId='1', #権限の固有ID。以降権限を書き換えるときはここで指定した値で更新する。
	Action='lambda:InvokeFunction',
	Principal='s3.amazonaws.com',
	SourceArn="arn:aws:s3:::bucketname",
)
        
#トリガの設定
FunctionArn = create_function_ret['FunctionArn'] #create_functionの戻り値
s3Resource.BucketNotification(bucketname).put(
	NotificationConfiguration={'LambdaFunctionConfigurations': [{
		'LambdaFunctionArn': FunctionArn,
		'Events': ['s3:ObjectCreated:*'],
		'Filter': { 'Key': { 'FilterRules':[{'Name':'prefix', 'Value':'lambda_trigger/'}] } }
	},]}
)
```
Lambda関数のArnはcreate_function()を実行したときのReturnにFunctionArnというフィールドで格納されているので、関数作成と一連で行うのであれば、そこから持ってくるのが楽。

##登録したLambda関数の設定値を変更

ソースコードの修正とその他の設定の修正は別メソッドを使用することになるので注意。
なお、FunctionName以外は省略可なので、更新する部分のみ値を渡すのでOK

```Python
#ソースコードの変更
lambda_.update_function_code(
	FunctionName="test_func", #関数名
	ZipFile=open("zip/file/path", "rb").read(),
)

#その他の設定の変更
lambda_.update_function_configuration(
	FunctionName="test_func", #関数名
	Runtime="python2.7", #ランタイム
	Role=role, #実行ロール
	Handler="lambda_function.lambda_handler", #ハンドラ
	Timeout=60*15, #タイムアウト（秒数）
	MemorySize=3008, #メモリ（MB）
	Environment={ "Variables":{'AAA':'aaa', 'BBB':'bbb'} }, #環境変数
)
```

##Layerを登録する
今回はあらかじめ手で登録しておくという設計にしたので、boto3での構築は未実施。
以下の手順をやる場合はあらかじめ、手動でLayerを登録しておいてください。


##Lambda関数にLayerを付ける

create_functionのLayersパラメータを指定するだけでLayerを付けることができます。

```Python
#Lambda関数を新規登録する場合
res = lambda_.create_function(
	FunctionName="test_func", #関数名
	Runtime="python2.7", #ランタイム
	Role=role, #実行ロール
	Handler="lambda_function.lambda_handler", #ハンドラ
	Code={"ZipFile": zip_file}, #ソースコード（ZIPファイルアップロード）
	Layers=['arn',], # <=NEW! 上記で登録したLayerのArnを調べて入れる
)

#作成済みのLambda関数の設定を変更する場合
lambda_.update_function_configuration(
	FunctionName="test_func", #関数名
	Layers=['arn',],
)
```

##別アカウントのLayerを付ける
別アカウントのLayerを紐づけるためには、そのLayerにアクセス権限を付ける必要がある。
なお、私がはまったことですが、別リージョンのLayerはつけられないので注意。

```Python
aws_id = '123456789012'

#Layerが登録されているアカウントの情報
layer_region = "us-east-1"
layer_access_key = "aaaaaaaa"
layer_secret_key = "bbbbbbbb"
layer_lambda = boto3.client('lambda', region_name=region, aws_access_key_id=access_key, aws_secret_access_key=secret_key)

#当該のアクセス権の削除を試みる（なぜこんなこと行うかは後述）
try:
	layer_lambda.remove_layer_version_permission(
		LayerName='common', #Layerの名前
		VersionNumber=1, #Layerのバージョン
		StatementId=’1’, #削除する権限の固有ID。
	)
except ClientError as e:
	#もしルールがない場合は下記のメッセージでエラーが出るので、無視する。
	if e.response['Error']['Code'] == 'ResourceNotFoundException': pass
	else: raise

#アクセス権を新規付け直し
layer_lambda.add_layer_version_permission(
	LayerName='common', #Layerの名前
	VersionNumber=1, #Layerのバージョン
	StatementId='1', #権限の固有ID。権限を削除するときはここで指定した値で更新する。
	Action='lambda:GetLayerVersion',
	Principal=aws_id, #このLayerに許可するAWSアカウント
)

#以下、前述の方法でLayerを付けられるようになる
```
権限は、既に登録されているか確認したり、上書きすることができない。（boto3で行う方法があったら教えてください）
そのため、複数回登録してしまう可能性が有ったり、設定を変更したりする場合は、一度消してから再登録する。
手元で試した限りでは、LayerをLambdaに紐づけた後は権限を外してもLayerが外れることも動作しないことも起こらなかったので、この方法で問題ないし、紐づけ後に毎回消してしまっても問題ないかもしれない。


#まとめ
lambda周りのboto3（特にLayer周り）は正直あまり使いやすいとは言えませんでしたが、上記を使って基本的な事はできるようになりました。
使いやすさに関しては次版以降のアップデートで強化されていくことに期待しましょう。

#参考
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lambda.html