データをアップロードするとファイル名チェックをするAWS Lambda

# はじめに


* AWS S3にアップロードされたデータが、ファイル命名規則に沿っているかチェックします。
    * チェック結果はSlackに通知
* 規則に沿っている場合、後工程に繋げることもできます(ex.機械学習モデルのの推論呼び出し)。
    * 今回はその前段階です。
    * AWS上での機械学習モデルの学習や、推論は以下の記事を参照ください。
        * [Amazon SageMakerで猫と犬の検出と種類の特定をしてみた](https://dev.classmethod.jp/machine-learning/sagemaker-cat-dog-detection/)

# 前提

* AWSのアカウントは作成済み
* SlackのWorkspaceに所属している

# 手順

* Step.1 Slackの設定
* Step.2 データをアップロードするS3の作成
* Step.3 Lambda関数の作成
* Step.4 Lambda関数の修正


# Step.1 : Slackの設定

* 参考：[AWS Lambdaで作るSlack bot (Incoming Webhook)](https://qiita.com/yokoc1322/items/553ad147b82277b2beca)

Incoming Webhookは、アプリケーションからSlackへの通知が行える機能です。
この設定を終えると、URLが提供され、アプリケーション(今回はlambda)がそのURLにPOSTを送ることで通知が飛ぶようになります。

## 1.1. Incoming Webhookを登録

[SlackApp](https://my.slack.com/apps)から登録します。
```Incoming Webhook``` を検索し、```Slackに追加``` ボタンを押下します。

![slack_1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531226/92465d59-dafb-1054-4138-e921b6d71ee5.png)


## 1.2. チャンネル選択

```チャンネルを選択``` から、通知を送るチャンネルを選択します。
本記事では```#lambda-post``` というチャンネルを選択しました。
最後に```Incoming Webhookインテグレーションの追加``` を押下します。

![slack_2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531226/3994596d-5a66-18e3-d2e4-8d2598a1871e.png)


## 1.3. Webhook URLを記録

設定後の画面に```Webhook URL``` の項目があるので、コピーして記録しておきましょう。

![slack_3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531226/a257ec49-59ca-e47f-0329-1444ea5a82d7.png)

## 1.4. カスタマイズ

さらに、この画面では、投稿する際のslack上の名前やアイコンがカスタマイズできます。
本記事では、```filename_checker``` という名前を付けました。
最後に設定を保存します。

![slack_4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531226/ea1d93fb-3877-902e-e349-59f1e38714a4.png)


# Step.2 データをアップロードするS3の作成

S3はクラウド型のオブジェクトストレージサービスです。
バージョニング機能、ログ記録、アクセス権限などを持ちます。
この設定を終えると、オブジェクトを置くバケットが作成されます。

## 2.1. S3へのアクセス

AWSコンソールにログインをし、S3にアクセスします。

![S3_1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531226/8b8d95b2-e205-22fa-deb7-ec23a35e0095.png)


## 2.2. バケットの作成

* S3にて```バケットを作成する``` を押下します。

![S3_2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531226/8c08a0ab-23fd-159a-2240-874a03d0f01b.png)


* 作成画面でバケット名とリージョンを指定します。
本記事では```origin-data``` と```アジアパシフィック(東京)``` を指定しています。

![S3_3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531226/6cc87925-5ee7-2d4d-b214-2c965dd6b88f.png)


* 左下の ```作成``` ボタンを押下します。

# Step.3 lambdaの作成

* 参考：[S3 へのデータアップロードをトリガーにデータを推論し、推論結果を S3 に保存する AWS Lambda 関数の作成](https://qiita.com/NendaiHiroki/items/9a1b32cdd5eceaf463e7)

Lambdaはサーバーレスコンピューティングのサービスで、「何かのイベントをトリガーにして決められた処理を行う」という特徴があります。
この設定を終えると、Step.2で作成したS3にオブジェクトが作成された場合に、ログ出力(デフォルト)がされるようになります。
具体的な処理内容を追加するのはStep.4で行います。

## 3.1. Lambdaへのアクセス

AWSコンソールにログインをし、Lambdaにアクセスします。

![lambda_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531226/20f9a580-a3e5-ddec-f8f1-a4d756b186ed.png)

## 3.2. 関数の作成

* 左メニューの一覧から```関数``` を選択し、右上の```関数の作成``` を押下します。

![lambda_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531226/4292af8c-d732-53b3-c289-f9a9bd84a0e6.png)


* ```一から作成``` を選択し、関数名を入力、ランタイムを選択します。
本記事では、関数名は ```s3-trigger-slack``` 、ランタイムは ```Python3.7``` を選択しています。

![lambda_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531226/6327019b-c4b0-64cc-6706-216cd2fe6804.png)


* さらに ```実行ロールの選択または作成``` を展開し、実行ロールが```基本的な Lambda アクセス権限で新しいロールを作成``` であることを確認し、```関数の作成``` を押下します。

![lambda_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531226/da8c8100-5749-c829-80b6-6401b8880438.png)


## 3.3. トリガーの追加

続いて、S3にオブジェクトが追加された際に、作成した関数が起動するように設定を行います。

* 作成後の画面から、```トリガーを追加``` を選択します。

![lambda_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531226/64b4c734-cd5a-6095-b92e-54b5b4123b38.png)

* トリガーの設定で ```S3``` を選択します。

![lambda_6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531226/c1942c8d-44c7-5192-ff99-eb9245d4843e.png)

* バケットはStep.2で作成したものを選択し、イベントタイプは```すべてのオブジェクト作成イベント``` を選択します。サフィックスは、対象とする拡張子を入力すると良いです。本記事では ```.jpg``` を入力しています。

![lambda_7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531226/f87490ed-4dbe-f10c-01c7-3d4f19d2a2fa.png)

* 最後に```追加``` ボタンを押下します。

# Step.4 Lambdaの修正

* 追加する機能は以下の2点です
    * ファイル命名規則のチェック
    * チェック結果のメッセージング

## 4.1. ファイル命名規則のチェック

* 本記事では、「**英字_数字.jpg**」というファイル命名規則を満たす必要があるとしています。
それ以外の規則を適用させる場合には、下記リンクを参考に、適宜、正規表現を書き換えましょう。
* 参考：[初心者歓迎！手と目で覚える正規表現入門・その１「さまざまな形式の電話番号を検索しよう」](https://qiita.com/jnchito/items/893c887fbf19e17d3ff9)

### 関数コードへのアクセス

関数の ```Designer``` から $\lambda$マークのアイコンがある箱をクリックすると、ページ下部に ```関数コード``` が現れます。

![lambda_python_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531226/df08c29d-cba7-e2c4-1a68-1a6cab917661.png)


### コードの変更

コードを以下のように変更し、ページ右上の ```保存``` ボタンを押下します。

```{python}
import json
import re

# ファイル命名規則
FILENAME_PATTERN = "[A-Za-z]+_[0-9]+.jpg"
FILANAME_PATTERN_EXPLAIN = "英字_数字.jpg (英字と数字の文字数は自由)"

def lambda_handler(event, context):
    # アップロードファイルの情報を抽出
    bucket   = event['Records'][0]['s3']['bucket']['name']
    filename = event['Records'][0]['s3']['object']['key']
    
    # ログ出力
    print("buckect : " + bucket + ", filaname = " + filename )
    
    # ファイル名チェック
    filename_check = re.match(FILENAME_PATTERN, filename)
    
    if filename_check:
        ## ファイル命名規則に合致した場合
        print("log : filename -> OK")
    else :
        ## ファイル命名規則に合致しなかった場合
        print("log : filename -> NG")
        
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }
```


## 4.2. チェック結果のメッセージング

* 参考：[AWS Lambdaで作るSlack bot (Incoming Webhook)](https://qiita.com/yokoc1322/items/553ad147b82277b2beca)

メッセージング機能は以下の関数が担います。
**※参考リンク内のコードを、メッセージ(文字列)を引数に取るように変更したものです。**
4.1.で作成したif-else内でこの関数を投稿したい内容(message)を引数に呼び出すことでSlackにチェック結果を通知することが出来ます。

```{python}
import urllib.request

def post_slack(message):
    # データ作成
    send_data = {
        "username": "filename-checker",
        "text": message,
    }
    send_text = "payload=" + json.dumps(send_data)
    # URL : Step.1で記録したWebhookのURL 
    request = urllib.request.Request(
        "https://hooks.slack.com/services/~~~~~~~~~~~~~~~~~~", 
        data=send_text.encode("utf-8"), 
        method="POST"
    )
    with urllib.request.urlopen(request) as response:
        response_body = response.read().decode("utf-8")```
```

## 4.3. Lambda関数(全体)

以上を踏まえると、Lambda内の関数は以下のようになります。

```lambda_function.py
import json
import re
import urllib.request

# ファイル命名規則
FILENAME_PATTERN = "[A-Za-z]+_[0-9]+.jpg"
FILANAME_PATTERN_EXPLAIN = "英字_数字.jpg (英字と数字の文字数は自由)"

def lambda_handler(event, context):
    # アップロードファイルの情報を抽出
    bucket   = event['Records'][0]['s3']['bucket']['name']
    filename = event['Records'][0]['s3']['object']['key']
    
    # ログ出力
    print("buckect : " + bucket + ", filaname = " + filename )
    
    # ファイル名チェック
    filename_check = re.match(FILENAME_PATTERN, filename)
    
    if filename_check:
        ## ファイル命名規則に合致した場合
        print("log : filename -> OK")
        
        # slackに投稿
        message = filename + """がアップロードされました。
        -> ファイル命名規則に沿っています。
        """
        post_slack(message)
        
    else :
        ## ファイル命名規則に合致しなかった場合
        print("log : filename -> NG")
        
        # slackに投稿
        message = filename + """がアップロードされました。
        -> ファイル命名規則に沿っていません。
        -> ファイル命名規則は""" + FILANAME_PATTERN_EXPLAIN + """です。"""
        post_slack(message)
    
    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

def post_slack(message):
    # データ作成
    send_data = {
        "username": "filename-checker",
        "text": message,
    }
    send_text = "payload=" + json.dumps(send_data)
    # URL : Step.1で記録したWebhookのURL 
    request = urllib.request.Request(
        "https://hooks.slack.com/services/~~~~~~~~~~~~~~~~~~", 
        data=send_text.encode("utf-8"), 
        method="POST"
    )
    with urllib.request.urlopen(request) as response:
        response_body = response.read().decode("utf-8")
```

# 動作チェック


以下がS3に"OK_001.jpg"と"NG_aaa.jpg"をアップした際のSlack画面です。

![result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531226/6cc8f850-5738-f975-7a70-be308cf59b03.png)


**えらい**

# おわりに

ちゃんとググったら情報が出てくるから、今の時代はえらい