CodeCommitへpushと同時にLambda関数に反映　後編

## はじめに
株式会社ジールの@suguru_y_zdh です。
AWSを使ったデータ分析基盤関係の業務に従事しております。
その中で得たノウハウをQiitaをとおして発信していきたいと思います。

今回の記事は
CodeCommitでバージョン管理しているLambda関数のソースコードを、CodeCommit・Lambda・S3を使用して、CodeCommitへpushすると同時にその内容をLambda関数へ反映する方法を紹介する記事の後半パートです。

前半パートの内容は下記に記載します。
[【AWS】CodeCommitへpushと同時にLambda関数に反映　前編](https://qiita.com/suguru_y_zdh/items/8e16d4ab33fbb2a2fc09)


## Lambda function（関数）を作成する
さっそく、前回の続きからコーディングします。
（利用するサービス、前準備は前回と同様のため割愛します。）

### ソースコード全体
全体のソースコードは下記です。
ソースコードの内容の説明については下の方に記載します。

```py
import json
import boto3
import zipfile 　#追加ライブラリ

def lambda_handler(event, context):
    print('イベント：' + json.dumps(event))

'''
前半パート
'''

    # boto3準備
    s3 = boto3.resource('s3')
    codecommit = boto3.client('codecommit')
    lambda_ = boto3.client('lambda')
    
    ##変数定義
    # S3関係
    bucket_name = 'zeal-test-codecommit'
    object_key_name = 'zeal-test-codecommit/before-commitid/commit_id.txt'
    bucket = s3.Bucket(bucket_name)
    obj = bucket.Object(object_key_name)
    
    # S3から前回のコミットID取得を読み取り,表示
    response = obj.get() 
    before_id = response['Body'].read().decode('cp932')
    print('前回コミットID：' +  str(before_id))

 # レポジトリ名を取得
    rep = event['Records'][0]['eventSourceARN'].split(":")[5]
    # 今回のコミットID取得,表示
    commit_id = event['Records'][0]['codecommit']['references'][0]['commit']
    print('今回コミットID：' +  str(commit_id))

    #S3のコミットIDを更新
    obj.put(Body=commit_id)

   
    # 変更があったファイルを特定
    differences = codecommit.get_differences(repositoryName=rep,afterCommitSpecifier=commit_id,beforeCommitSpecifier=before_id)['differences']
    print('変更内容詳細：' +  json.dumps(differences))


    # 反映前のlambda関数全量をリスト化
    bef_func = []
        
    def function_list(max_items=50, next_marker=None):
        if next_marker:
            r = lambda_.list_functions(MaxItems=max_items, Marker=next_marker)
        else:
            r = lambda_.list_functions(MaxItems=max_items)
        
        for functions in r['Functions']:
            bef_func.append(functions['FunctionName'])
    
        if 'NextMarker' in r:
            return function_list(max_items=max_items, next_marker=r['NextMarker'])
        else:
            return
    
    function_list()
 

    # 変更対象のフォルダを特定し、changetype毎に格納
    targets_A = []
    targets_M = []
    targets_D = []

    for n in range(len(differences)):
        #　変更後の情報が無い場合
        if 'afterBlob' not in differences[n].keys():
            target = differences[n]['beforeBlob']['path'].split("/")[0]
            
            # codecommit（リモートリポジトリ上）にターゲットの関数が無ければtarget_D、有ればtarget_Mへ格納
            try :
                codecommit.get_folder(repositoryName=rep,folderPath=target)
            except Exception as e:
                if e.response['Error']['Code'] == 'FolderDoesNotExistException':
                    targets_D.append(target) 
            else:
                targets_M .append(target)
            
        #　変更前の情報が無い場合
        elif 'beforeBlob' not in differences[n].keys():
            target = differences[n]['afterBlob']['path'].split("/")[0]
            
            # ターゲットの関数が反映前のlambda関数全量リストに無ければtarget_A、有ればtarget_Mへ
            if target not in bef_func:
                targets_A .append(target)               
            else:
                targets_M .append(target)

        #　それ以外の場合、target_Mへ
        else:
            target = differences[n]['afterBlob']['path'].split("/")[0]
            targets_M .append(target)


    # targetA,M,D毎で重複した関数を削除し、それぞれtarget_list_A,M,Dに格納
    def unique_target(target_list):
        unique_targets = []
        for judge_target in target_list:
            if judge_target not in unique_targets:
                unique_targets.append(judge_target)
        return unique_targets
    
    target_list_A = unique_target(targets_A)
    target_list_M = unique_target(targets_M)
    target_list_D = unique_target(targets_D)
    
    print('新規関数：' +  str(target_list_A))
    print('修正関数：' +  str(target_list_M))
    print('削除関数：' +  str(target_list_D))

'''
後半パート
'''

############関数内のフォルダの階層構造確認##############

    # 関数内のフォルダの階層構造確認
    def listFiles(folder):
        ccf = codecommit.get_folder(repositoryName=rep,folderPath=folder)
        files =[]
    
        for i in range(len(ccf['files'])):
            files.append(ccf['files'][i]['absolutePath'])
    
        for f in range(len(ccf['subFolders'])):
            files2 = listFiles(ccf['subFolders'][f]['absolutePath'])
            files += files2
    
        return files

############Lambda関数内の階層構造確認##############

############更新種別ごとの処理##############

    # target_list_Aの関数の階層構造をzip化し、関数を新規作成
    if len(target_list_A) != 0:
        for i in target_list_A:
            fn = listFiles(i)
            for n in range(len(fn)):
                ccgf = codecommit.get_file(repositoryName=rep,filePath=fn[n])
                with zipfile.ZipFile('/tmp/' + i + '.zip','a') as zip:
                    with zip.open(fn[n][(len(i)+1):],'w') as f:
                        f.write(ccgf['fileContent'])
            with open('/tmp/' + i + '.zip', 'rb') as f:
                zip_data = f.read()
        
            response = lambda_.create_function(FunctionName=i,
                                                Runtime='python3.7',
                                                Role='arn:aws:iam::XXXXXXXXXX:role/for_codecommit_to_lambda',
                                                Handler='lambda_function.lambda_handler',
                                                Code={
                                                    'ZipFile':zip_data 
                                                })


    # target_list_Mの関数の階層構造をzip化し、関数を更新  
    if len(target_list_M) != 0:
      for i in target_list_M:
        fn = listFiles(i)
        for n in range(len(fn)):
            ccgf = codecommit.get_file(repositoryName=rep,filePath=fn[n])
            with zipfile.ZipFile('/tmp/' + i + '.zip','a') as zip:
                with zip.open(fn[n][(len(i)+1):],'w') as f:
                    f.write(ccgf['fileContent'])
        with open('/tmp/' + i + '.zip', 'rb') as f:
            zip_data = f.read()
    
        respose = lambda_.update_function_code(FunctionName=i,ZipFile=zip_data)


    # target_list_Dの関数を削除
    if len(target_list_D) != 0:
        for i in target_list_D:
            response = lambda_.delete_function(FunctionName=i)
############更新種別ごとの処理##############
```
　
### ソースコードの説明
#### ＜Lambda関数内の階層構造確認＞
Lambda関数は下図のように階層構造となっているため、その階層構造（ファイルパス）をリスト化する関数を定義しています。こちらの処理は下記記事を参考にさせていただきました。
[Lambda関数をCodeCommitで管理する](https://qiita.com/afukuma/items/a08648763d60045667bf)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/e7646162-09ac-cd03-ef3b-473eea90e824.png)
```
    # 関数内のフォルダの階層構造確認
    def listFiles(folder):
        ccf = codecommit.get_folder(repositoryName=rep,folderPath=folder)
        files =[]
    
        for i in range(len(ccf['files'])):
            files.append(ccf['files'][i]['absolutePath'])
    
        for f in range(len(ccf['subFolders'])):
            files2 = listFiles(ccf['subFolders'][f]['absolutePath'])
            files += files2
    
        return files
```
　
#### ＜更新種別ごとの処理（新規作成）＞
更新種別が新規作成のLambda関数の場合、
先ほど定義したLambda関数内の階層構造（ファイルパス）をリスト化する関数を用いて、各ファイルの情報をzip化し、Lambda関数を新規作成（create_function）しています。
今回はテスト用なので新規作成する際の設定項目を最小限にしてますが、タイムアウト時間、メモリサイズ、レイヤーの追加など細かくに設定することができます。詳細は公式ドキュメントでご確認ください。
[Lambda — Boto3 Docs 1.26.53(create_function)](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lambda.html#Lambda.Client.create_function)

```
# target_list_Aの関数の階層構造をzip化し、関数を新規作成
    if len(target_list_A) != 0:
        for i in target_list_A:
            fn = listFiles(i)
            for n in range(len(fn)):
                ccgf = codecommit.get_file(repositoryName=rep,filePath=fn[n])
                with zipfile.ZipFile('/tmp/' + i + '.zip','a') as zip:
                    with zip.open(fn[n][(len(i)+1):],'w') as f:
                        f.write(ccgf['fileContent'])
            with open('/tmp/' + i + '.zip', 'rb') as f:
                zip_data = f.read()
        
            response = lambda_.create_function(FunctionName=i,
                                                Runtime='python3.7',
                                                Role='arn:aws:iam::XXXXXXXXXX:role/for_codecommit_to_lambda',  ※前準備で作成したロールを設定
                                                Handler='lambda_function.lambda_handler',
                                                Code={
                                                    'ZipFile':zip_data 
                                                })
```
　
#### ＜更新種別ごとの処理（更新）＞
更新種別が修正のLambda関数の場合、
先ほど同様各ファイルの情報をzip化し、Lambda関数を更新（update_function）します。

```
    # target_list_Mの関数の階層構造をzip化し、関数を更新  
    if len(target_list_M) != 0:
      for i in target_list_M:
        fn = listFiles(i)
        for n in range(len(fn)):
            ccgf = codecommit.get_file(repositoryName=rep,filePath=fn[n])
            with zipfile.ZipFile('/tmp/' + i + '.zip','a') as zip:
                with zip.open(fn[n][(len(i)+1):],'w') as f:
                    f.write(ccgf['fileContent'])
        with open('/tmp/' + i + '.zip', 'rb') as f:
            zip_data = f.read()
    
        respose = lambda_.update_function_code(FunctionName=i,ZipFile=zip_data)
```
　
#### ＜更新種別ごとの処理（削除）＞
更新種別が削除のLambda関数の場合、
Lambda関数を削除（delete_function）します。

```
    # target_list_Dの関数を削除
    if len(target_list_D) != 0:
        for i in target_list_D:
            response = lambda_.delete_function(FunctionName=i)
```

## 動作確認
続いてCodeCommitへテスト用のLambda関数を新規作成し動作を確認です。

動作確認のために、まず、ローカル環境からCodeCommitのリモートリポジトリへpushするためのローカルリポジトリが必要ですのでそちらを作成します。
git用語でいうとcloneがこの作業にあたります。

### ＜clone作業＞
任意の場所に任意の名前でフォルダを作成し、VSCodeで開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/726007c7-f45f-8a19-a250-2269d3782719.png)

続いてリソース管理を選択肢、リポジトリを初期化します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/171d16ac-ed30-1b8f-15d5-053aa4997146.png)

CodeCommitでクローン用URLをコピー（認証方式によってコピーするURLは変わります）
※認証方式についてはこちらの記事を参考にしてください。
　[CodeCommitのGitリポジトリへの接続方法](https://dev.classmethod.jp/articles/using-codecommit-git-repository/)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/268f0c4d-7d72-1fb0-5724-38a743354fc7.png)

リソース管理からクローンを選択し、URLを貼り付けます。複製するフォルダを選択する画面になるので作成した任意のフォルダを選択します。これでclone作業は完了です。
※初回のみ認証方式を設定する際に発行するアクセスキーを求められる可能性があります。
  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/0d1ac7f1-6b40-ac08-e293-df086fa8e778.png)

### ＜push作業＞
エクスプローラーからLambdaに追加する関数名のフォルダを作成し、フォルダの中にlambda_function.pyという名前のpythonファイルを作成します。次に作成したpythonファイルに任意のコードを記載します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/64331e47-f1e2-4d65-ba96-a02d1c0a2cda.png)

リソース管理から変更内容をステージングし、メッセージ欄に任意のメッセージを記載、コミット＆プッシュを実施します。これでpush作業完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/efe94e02-2497-793c-6174-980f9d9c0cd4.png)

### ＜動作確認＞
先ほどpushした内容のlambda関数が作成されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/2562a420-f00d-1722-41e7-359c45860d9b.png)


更新種別が新規作成の処理の中で指定した設定項目も反映されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/47f5592c-042f-1ef6-537c-508d6a31f31c.png)




## おわりに
前回仕分けした更新種別をもとにそれぞれ新規作成、修正、削除を実行する処理の紹介と動作確認が完了しました。当初のやりたいこと（下図）が実現できました。今回のCodeCommitへpushと同時にLambda関数に反映の記事は以上でございます。こちらの記事が皆様の参考になれば幸いでございます。


　![ToBe_image(codecommit to lambda).drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/e770ae70-d677-347b-a8a1-11e3f109d0a5.png)

## 参考URLまとめ
・前半パートの記事
[　【AWS】CodeCommitへpushと同時にLambda関数に反映　前編](https://qiita.com/suguru_y_zdh/items/8e16d4ab33fbb2a2fc09)

・Lambda関数のフォルダ内の階層構造をzip化し更新処理をする
[　Lambda関数をCodeCommitで管理する](https://qiita.com/afukuma/items/a08648763d60045667bf)

・Git
[　CodeCommitのGitリポジトリへの接続方法](https://dev.classmethod.jp/articles/using-codecommit-git-repository/)

#
株式会社ジールでは、初期費用が不要で運用・保守の手間もかからず、ノーコード・ローコードですぐに手元データを分析可能なオールインワン型データ活用プラットフォーム「ZEUSCloud」を提供しております。
ご興味がある方は是非下記のリンクをご覧ください：
https://www.zdh.co.jp/products-services/cloud-data/zeuscloud/