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

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

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

前半パートの内容は
前準備とCodeCommitへpushした情報から更新種別を仕分けをする処理（言語：python）までとなっております。
***
**因みに**
CodeCommit・CodePipeline・CodeBuild（通称：Code三兄弟）を用いた方法は下記サイトが参考になります。
[【aws】CodeCommitにpushするだけでlambda関数をデプロイするパイプラインを作る【CodePipeline】](https://ishidalog.com/?p=475)
***
　　

## 利用環境
- Windows 10 


## 利用するサービス
＜AWSサービス＞
　・　CodeCommit
　・　Lambda
　・　S3

＜その他＞
　・　Git
　・　VSCode
　　

## やりたいこと
**As-Is**
　ユーザーは①、②の作業をしている。
　①Lambda関数の更新
　②CodeCommit上のソースコード更新(push)
　![AsIs_image(codecommit to lambda).drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/c3908c6e-c47d-8e4e-5d23-de2fe088583a.png)

**To-Be**
　ユーザーは①の作業のみ行う。（②、③は今回作成するLambda functionが自動で実行する。）
　①CodeCommit上のソースコード更新（push)

　*②pushをトリガーにLambda functionを起動、S3のコミットIDを参照*
　*③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)



　　

## 前準備
- 権限関係
　今回使うAWSの３つのサービスに対するフルアクセスのポリシーをアタッチしました。
　・　CodeCommit フルアクセス
　・　Lambda フルアクセス
　・　S3 フルアクセス
![IAM①PNG.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/d335ad41-03b5-9820-565b-2b7305b708cc.png)
　　

- Lambdaで使用する実行ロール作成
　今回はfor_codecommit_to_lambdaというロールを作成し、下記３つのフルアクセスのポリシーをアタッチしました。
　・　CodeCommit フルアクセス
　・　Lambda フルアクセス
　・　S3 フルアクセス
![role①PNG.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/619e8c5b-c8c5-f778-7ae0-46cdd01f1d35.png)
![role②PNG.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/fd990c2f-faf4-de68-4009-1fecc17f5cb2.png)
　　 

- CodeCommitにリポジトリ作成
今回はrep_codecommit_to_lambdaというリポジトリを作成しております。
![codecommit0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/ef9a2c20-8bbb-44aa-b219-098b82c22ca4.png)
　　
　
- gitのインストール＆認証情報の設定
　gitのインストールは下記サイトが参考になります。
　[Git for Windows インストール （日本語訳あり）](https://qiita.com/HyunwookPark/items/d399f6959fc922a15ee1)
　
　CodeCommit の Git 認証情報の設定は下記サイトが参考になります。
　[CodeCommitのGitリポジトリへの接続方法](https://dev.classmethod.jp/articles/using-codecommit-git-repository/)
　　　　

- VSCodeのインストール＆VSCodeのGit操作
　VSCodeのインストールは下記サイトが参考になります。
　[Visual Studio Code (Windows版) のインストール](https://qiita.com/psychoroid/items/7d85ae6bade4a67aedb1)

　　　VSCodeのGit操作は下記サイトが参考になります。
　　　[VSCodeでのGitの基本操作まとめ](https://qiita.com/y-tsutsu/items/2ba96b16b220fb5913be)
　　

- S3に前回のコミットID情報を格納
S3の任意の場所に前回のコミットID情報が載っているテキストファイルを格納しておきます。
![S3①.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/5379931e-9faa-e9db-5b22-2d9c3f02147d.png)


コミットIDはCodeCommitの対象のリポジトリのコミットからコピーできます。
![codecommit①.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/ae12bc78-3311-bb60-5b55-7bc41d837c19.png)


## Lambda function（関数）を作成する
下記内容で関数を新規作成する。
![lambda_function①PNG.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/5ebc1801-ca38-7ea9-b295-90c3b0b05d9b.png)
![lambda_function②PNG.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/6b96d57a-9468-59ca-9abb-6af000afdde2.png)
　　
トリガーを設定する。
![lambda_function③-１.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/923580e7-e176-cda1-5ddf-d026b0469252.png)
![lambda_function③.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/b88bb8ee-5e72-229d-2d32-167397d04ef5.png)
![lambda_function④.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777457/90ebd887-185f-6c7c-b2f7-7ba1329dcb86.png)


続いて、コーディングです。
今回の全体のソースコードは下記です。
ソースコードの内容の説明については下の方に記載します。
```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))

############更新種別仕分け作業#############  
```

　　
ソースコードの内容
＜準備作業＞
今回のLambda functionで使用するboto3のクライアント・リソースの準備をしております。
```
    # boto3準備
    s3 = boto3.resource('s3')
    codecommit = boto3.client('codecommit')
    lambda_ = boto3.client('lambda')
```
　　

S3関係の変数を定義しております。
```
    ##変数定義
    # S3関係
    bucket_name = 'zeal-test-codecommit' 　※任意のバケット
    object_key_name = 'zeal-test-codecommit/before-commitid/commit_id.txt'　※前回のコミットIDを記載したテキストファイルを格納したS3のパス
    bucket = s3.Bucket(bucket_name)
    obj = bucket.Object(object_key_name)
```
なぜ前回のコミットIDが必要かというと、この後出てくるcodecommitのget_differencesという処理が、
前回のpushの内容（前回のコミットID）と今回のpushの内容を比較して今回のpushの内容が新規作成・修正・削除を判断しているためです。

そのためS3に前回のコミットIDを記したテキストファイルを格納しております。
　　

S3のテキストファイルから前回のコミットIDを取得し変数定義、
トリガーのイベント情報から今回のコミットID、リポジトリ名を取得し変数定義
```    
    # 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を更新しております。
```   
    #S3のコミットIDを更新
    obj.put(Body=commit_id)
``` 

　　
＜更新種別仕分け作業＞
前回のコミットIDと今回のコミットID使用して更新がファイルを特定しております。

``` 
    # 変更があったファイルを特定
    differences = codecommit.get_differences(repositoryName=rep,afterCommitSpecifier=commit_id,beforeCommitSpecifier=before_id)['differences']
    print('変更内容詳細：' +  json.dumps(differences))
``` 
　　
更新種別仕分けに必要なLambda関数リストを作成しております。
``` 
    # 反映前の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()
``` 
　　
更新があったファイルに対して更新種別（新規作成・修正・削除）を判断して、更新種別ごとにリスト（target_A・target_M・target_D）に格納しております。
``` 
    # 変更対象のフォルダを特定し、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':　※予期せぬエラーが発生した場合に削除しないよう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))
``` 



## おわりに
今回は前準備とCodeCommitへpushした情報から更新種別を仕分けをする処理について紹介しました。
次回は仕分けした更新種別を基にそれぞれ新規作成、修正、削除を実行の処理を行います。


## 参考URLまとめ
・Code三兄弟
[【aws】CodeCommitにpushするだけでlambda関数をデプロイするパイプラインを作る【CodePipeline】](https://ishidalog.com/?p=475)

・Git
[Git for Windows インストール （日本語訳あり）](https://qiita.com/HyunwookPark/items/d399f6959fc922a15ee1)
[CodeCommitのGitリポジトリへの接続方法](https://dev.classmethod.jp/articles/using-codecommit-git-repository/)

・VScode
[Visual Studio Code (Windows版) のインストール](https://qiita.com/psychoroid/items/7d85ae6bade4a67aedb1)
[VSCodeでのGitの基本操作まとめ](https://qiita.com/y-tsutsu/items/2ba96b16b220fb5913be)

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