

# サーバーレスファイル処理アプリケーションを作成する
<a name="file-processing-app"></a>

Lambda の最も一般的なユースケースの 1 つは、ファイル処理タスクを実行することです。例えば、Lambda 関数を使用して、HTML ファイルまたは画像から PDF ファイルを自動的に作成したり、ユーザーが画像をアップロードしたときにサムネイルを作成したりできます。

この例では、PDF ファイルが Amazon Simple Storage Service (Amazon S3) バケットにアップロードされると自動的に暗号化されるアプリケーションを作成します。このアプリを実装するには、以下のリソースを作成する必要があります。
+ ユーザーが PDF ファイルをアップロードするための S3 バケット
+ アップロードされたファイルを読み取り、暗号化されたパスワードで保護されたバージョンのファイルを作成する Python の Lambda 関数
+ Lambda が暗号化されたファイルに保存するための 2 番目の S3 バケット

また、AWS Identity and Access Management (IAM) ポリシーを作成して、S3 バケットで読み取りおよび書き込みオペレーションを実行するアクセス許可を Lambda 関数に付与します。

![\[\]](http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/images/ExampleApps/file_process_resources.png)


**ヒント**  
Lambda を初めて使用する場合は、このサンプルアプリを作成する前にチュートリアル [最初の Lambda 関数を作成する](getting-started.md) から始めることをお勧めします。

AWS マネジメントコンソール または AWS Command Line Interface (AWS CLI) を使用してリソースを作成して設定することで、アプリケーションを手動でデプロイできます。AWS Serverless Application Model (AWS SAM) を使用してアプリケーションをデプロイすることもできます。AWS SAM は、Infrastructure as Code (IaC) ツールです。IaC では、リソースを手動で作成するのではなく、コードに定義して自動的にデプロイします。

このサンプルアプリケーションをデプロイする前に、IaC で Lambda を使用する方法の詳細については、「[Lambda と Infrastructure as code (IaC) の使用](foundation-iac.md)」を参照してください。

## Lambda 関数のソースコードファイルを作成する
<a name="file-processing-app-download"></a>

プロジェクトディレクトリに次のファイルを作成します。
+ `lambda_function.py` - ファイル暗号化を実行する Lambda 関数の Python 関数コード
+ `requirements.txt` - Python 関数コードに必要な依存関係を定義するマニフェストファイル

以下のセクションを展開してコードを表示し、各ファイルの役割の詳細を確認してください。ローカルマシンにファイルを作成するには、以下のコードをコピーして貼り付けるか、[aws-lambda-developer-guide GitHub レポジトリ](https://github.com/awsdocs/aws-lambda-developer-guide/tree/main/sample-apps/file-processing-python)からファイルをダウンロードします。

### Python 関数コード
<a name="file-processing-app-function-code"></a>

次のコードをコピーし、`lambda_function.py` という名前のファイルに貼り付けます。

```
from pypdf import PdfReader, PdfWriter
import uuid
import os
from urllib.parse import unquote_plus
import boto3

# Create the S3 client to download and upload objects from S3
s3_client = boto3.client('s3')

def lambda_handler(event, context):
    # Iterate over the S3 event object and get the key for all uploaded files
    for record in event['Records']:
        bucket = record['s3']['bucket']['name']
        key = unquote_plus(record['s3']['object']['key']) # Decode the S3 object key to remove any URL-encoded characters
        download_path = f'/tmp/{uuid.uuid4()}.pdf' # Create a path in the Lambda tmp directory to save the file to 
        upload_path = f'/tmp/converted-{uuid.uuid4()}.pdf' # Create another path to save the encrypted file to
        
        # If the file is a PDF, encrypt it and upload it to the destination S3 bucket
        if key.lower().endswith('.pdf'):
            s3_client.download_file(bucket, key, download_path)
            encrypt_pdf(download_path, upload_path)
            encrypted_key = add_encrypted_suffix(key)
            s3_client.upload_file(upload_path, f'{bucket}-encrypted', encrypted_key)

# Define the function to encrypt the PDF file with a password
def encrypt_pdf(file_path, encrypted_file_path):
    reader = PdfReader(file_path)
    writer = PdfWriter()
    
    for page in reader.pages:
        writer.add_page(page)

    # Add a password to the new PDF
    writer.encrypt("my-secret-password")

    # Save the new PDF to a file
    with open(encrypted_file_path, "wb") as file:
        writer.write(file)

# Define a function to add a suffix to the original filename after encryption
def add_encrypted_suffix(original_key):
    filename, extension = original_key.rsplit('.', 1)
    return f'{filename}_encrypted.{extension}'
```

**注記**  
このサンプルコードでは、暗号化されたファイル (`my-secret-password`) のパスワードが関数コードにハードコードされます。本番環境のアプリケーションでは、関数コードにパスワードなどの機密情報を含めないでください。代わりに、[AWS Secrets Manager シークレットを作成](https://docs.aws.amazon.com/secretsmanager/latest/userguide/create_secret.html)し、[AWS Parameters and Secrets Lambda 拡張機能を使用](with-secrets-manager.md)して Lambda 関数の認証情報を取得します。

Python 関数コードには 3 つの関数が含まれています。1 つは関数が呼び出されたときに Lambda が実行する[ハンドラー関数](python-handler.md)、他の 2 つは PDF 暗号化を実行するためにハンドラーが呼び出す `add_encrypted_suffix` と `encrypt_pdf` という名前のそれぞれ独立した関数です。

関数が Amazon S3 によって呼び出されると、Lambda は呼び出しの原因となったイベントの詳細を含む JSON 形式の*イベント*引数を関数に渡します。この場合、情報には S3 バケットの名前と、アップロードされたファイルのオブジェクトキーが含まれます。Amazon S3 のイベントオブジェクトの形式の詳細については、「[Lambda を使用した Amazon S3 イベント通知の処理](with-s3.md)」を参照してください。

次に、関数は AWS SDK for Python (Boto3) を使用して、イベントオブジェクトで指定された PDF ファイルをローカル一時ストレージディレクトリにダウンロードしてから、[https://pypi.org/project/pypdf/](https://pypi.org/project/pypdf/) ライブラリを使用して暗号化します。

最後に、この関数は Boto3 SDK を使用して暗号化されたファイルを S3 送信先バケットに保存します。

### `requirements.txt` マニフェストファイル
<a name="file-processing-app-dependencies"></a>

次のコードをコピーし、`requirements.txt` という名前のファイルに貼り付けます。

```
boto3
pypdf
```

この例では、関数コードには、標準の Python ライブラリに含まれていない依存関係が 2 つしかありません。SDK for Python (Boto3) と、関数が PDF 暗号化の実行に使用する `pypdf` パッケージです。

**注記**  
SDK for Python (Boto3) のバージョンは Lambda ランタイムの一部として含まれているため、Boto3 を関数のデプロイパッケージに追加せずにコードが実行されます。ただし、関数の依存関係を完全に制御し、バージョン不一致による問題を回避するには、Python のベストプラクティスとして、関数のデプロイパッケージにすべての関数の依存関係を含めることをお勧めします。詳細については、「[Python でのランタイム依存関係](python-package.md#python-package-dependencies)」を参照してください。

## アプリをデプロイする
<a name="file-processing-app-deploy"></a>

このサンプルアプリケーションのリソースは、手動または AWS SAM を使用して作成およびデプロイできます。実稼働環境では、AWS SAM のような IaC ツールを使用して、手動プロセスを使用せずにサーバーレスアプリケーション全体を迅速かつ繰り返しデプロイすることをお勧めします。

### リソースを手動でデプロイする
<a name="file-processing-app-deploy-manual"></a>

アプリを手動でデプロイするには:
+ ソースと送信先の Amazon S3 バケットを作成する
+ PDF ファイルを暗号化し、暗号化されたバージョンを S3 バケットに保存する Lambda 関数を作成する
+ オブジェクトがソースバケットにアップロードされたときに、関数を呼び出す Lambda トリガーを設定する

開始する前に、ビルドマシンに [Python](https://www.python.org/downloads/) がインストールされていることを確認してください。

#### 2 つの S3 バケットを作成する
<a name="file-processing-app-deploy-manual-create-buckets"></a>

まず、2 つの S3 バケットを作成します。1 つ目のバケットは、PDF ファイルをアップロードするソース バケットです。2 つ目のバケットは、関数を呼び出すときに暗号化されたファイルを保存するために Lambda が使用するバケットです。

------
#### [ Console ]

**S3 バケットを作成するには (コンソール)**

1. Amazon S3 コンソールの [[汎用バケット]](https://console.aws.amazon.com/s3/buckets) ページを開きます。

1. 住まいの地域に最も近い AWS リージョン を選択してください。画面上部にあるドロップダウンリストを使用して、リージョンを変更できます。  
![\[\]](http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/images/console_region_select.png)

1. **[バケットを作成する]** を選択します。

1. **[全般設定]** で、次の操作を行います。

   1. **[バケットタイプ]** で、**[汎用]** が選択されていることを確認してください。

   1. **[バケット名]** には、Amazon S3 [バケットの命名規則](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html)を満たすグローバルに一意な名前を入力します。バケット名は、小文字、数字、ドット (.)、およびハイフン (-) のみで構成できます。

1. 他のすべてのオプションはデフォルト設定値のままにしておき、**[バケットの作成]** を選択します。

1. ステップ 1 ～ 4 を繰り返して、送信先のバケットを作成します。**[バケット名]** には `amzn-s3-demo-bucket-encrypted` と入力します。`amzn-s3-demo-bucket` は先ほど作成したソース元バケットの名前です。

------
#### [ AWS CLI ]

開始する前に、ビルドマシンに [AWS CLI がインストールされている](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)ことを確認してください。

**Amazon S3 バケットを作成する方法 (AWS CLI)**

1. 次の CLI コマンドを実行して、ソース元のバケットを作成します。バケットに付ける名前は、グローバルに一意で、Amazon S3 [バケットの命名規則](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html)に従ったものである必要があります。名前には、小文字、数字、ドット (.)、およびハイフン (-) のみを使用できます。`region` および `LocationConstraint` については、お住まいの地域に最も近い [AWS リージョン](https://docs.aws.amazon.com/general/latest/gr/lambda-service.html) を選択してください。

   ```
   aws s3api create-bucket --bucket amzn-s3-demo-bucket --region us-east-2 \
   --create-bucket-configuration LocationConstraint=us-east-2
   ```

   チュートリアルの後半では、ソース元バケットと同じ AWS リージョン で Lambda 関数を作成する必要があるため、選択したリージョンを書き留めておいてください。

1. 次のコマンドを実行して、送信先のバケットを作成します。バケット名には `amzn-s3-demo-bucket-encrypted` を使用する必要があります。`amzn-s3-demo-bucket` はステップ 1 で作成したソース元バケットの名前です。`region` および `LocationConstraint` については、ソース元バケットを作成するときに使用したものと同じ AWS リージョン を選択してください。

   ```
   aws s3api create-bucket --bucket amzn-s3-demo-bucket-encrypted --region us-east-2 \
   --create-bucket-configuration LocationConstraint=us-east-2
   ```

------

#### 実行ロールを作成する
<a name="file-processing-app-deploy-manual-create-execution-role"></a>

実行ロールとは、AWS のサービス とリソースにアクセスする許可を Lambda 関数に付与する IAM ロールです。関数に Amazon S3 への読み取りおよび書き込みアクセスを許可するには、[AWS 管理ポリシー](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html#aws-managed-policies) `AmazonS3FullAccess` をアタッチします。

------
#### [ Console ]

**実行ロールを作成して `AmazonS3FullAccess` 管理ポリシーをアタッチするには (コンソール)**

1. IAM コンソールの [[ロール]](https://console.aws.amazon.com/iam/home/roles) ページを開きます。

1. [**ロールの作成**] を選択してください。

1. **[信頼されたエンティティタイプ]** で **[AWS サービス]** を選択し、**[ユースケース]** で **[Lambda]** を選択します。

1. [**次へ**] を選択します。

1. 次の手順を実行して `AmazonS3FullAccess` 管理ポリシーを追加します。

   1. **[許可ポリシー]** で、検索ボックスに **AmazonS3FullAccess** と入力します。

   1. ポリシーの横にあるチェックボックスを選択します。

   1. [**次へ**] を選択します。

1. **[ロールの詳細]** の **[ロール名]** には **LambdaS3Role** を入力します。

1. **[ロールの作成]** を選択します。

------
#### [ AWS CLI ]

**実行ロールを作成して `AmazonS3FullAccess` 管理ポリシー (AWS CLI) をアタッチするには**

1. 次の JSON を `trust-policy.json` という名のファイルに保存します。この信頼ポリシーは、AWS Security Token Service (AWS STS) `AssumeRole` アクションを呼び出すサービスプリンシパルの `lambda.amazonaws.com` アクセス許可を付与することで、Lambda がロールのアクセス許可を使用できるようにします。  
****  

   ```
   {
     "Version":"2012-10-17",		 	 	 
     "Statement": [
       {
         "Effect": "Allow",
         "Principal": {
           "Service": "lambda.amazonaws.com"
         },
         "Action": "sts:AssumeRole"
       }
     ]
   }
   ```

1. JSON 信頼ポリシードキュメントを保存したディレクトリから、次の CLI コマンドを実行して実行ロールを作成します。

   ```
   aws iam create-role --role-name LambdaS3Role --assume-role-policy-document file://trust-policy.json
   ```

1. `AmazonS3FullAccess` 管理ポリシーをアタッチするには、次の CLI コマンドを実行します。

   ```
   aws iam attach-role-policy --role-name LambdaS3Role --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess
   ```

------

#### 関数デプロイパッケージを作成する
<a name="file-processing-app-deploy-manual-create-function-package"></a>

関数を作成するには、関数コードおよびその依存関係を含む*デプロイパッケージ*を作成します。このアプリケーションでは、関数コードは PDF 暗号化に別のライブラリを使用します。

**デプロイパッケージを作成するには**

1. 以前に GitHub から作成またはダウンロードした `lambda_function.py` および `requirements.txt` ファイルを含むプロジェクトディレクトリに移動し、`package` という名前の新しいディレクトリを作成します。

1. 次のコマンドを実行して、 `package` ディレクトリの `requirements.txt` ファイルで指定された依存関係をインストールします。

   ```
   pip install -r requirements.txt --target ./package/
   ```

1. アプリケーション コードとその依存関係を含む .zip ファイルを作成します。Linux または MacOS では、コマンドラインインターフェイスから次のコマンドを実行します。

   ```
   cd package
   zip -r ../lambda_function.zip .
   cd ..
   zip lambda_function.zip lambda_function.py
   ```

    Windows では、任意の zip ツールを使用して、`lambda_function.zip` ファイルを作成します。`lambda_function.py` ファイルと依存関係が含まれるフォルダは、.zip ファイルのルートにインストールする必要があります。

また、Python 仮想環境を使用してデプロイパッケージを作成することもできます。「[Python Lambda 関数で .zip ファイルアーカイブを使用する](python-package.md)」を参照してください。

#### Lambda 関数を作成する
<a name="file-processing-app-deploy-manual-createfunction"></a>

これで、前のステップで作成したデプロイパッケージを使用して Lambda 関数をデプロイします。

------
#### [ Console ]

**関数を作成するには (コンソール)**

コンソールを使用して Lambda 関数を作成するには、まず「Hello world」コードが含まれるベーシックな関数を作成します。次に、前のステップで作成した .zip ファイルをアップロードして、、このコードを自身で作成した関数コードへと置き換えます。

サイズの大きい PDF ファイルを暗号化するときに関数がタイムアウトしないようにするには、関数のメモリとタイムアウトの設定が必要です。また、関数のログ形式を JSON に設定します。提供されたテストスクリプトを使用するときは、CloudWatch Logs から関数の呼び出しステータスを読み取って呼び出しが成功したことを確認するために、JSON 形式のログを設定する必要があります。

1. Lambda コンソールの [[関数]](https://console.aws.amazon.com/lambda/home#/functions) ページを開きます。

1. S3 バケットを作成したときと同じ AWS リージョン で操作していることを確認してください。画面上部にあるドロップダウンリストを使用して、リージョンを変更できます。  
![\[\]](http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/images/console_region_select.png)

1. **関数の作成** を選択します。

1. **Author from scratch** を選択します。

1. **基本的な情報** で、以下の作業を行います。

   1. **[関数名]** に「`EncryptPDF`」と入力します。

   1. **ランタイム** には、**[Python 3.12]** を選択します。

   1. **[アーキテクチャ]** で **[x86\$164]** を選択します。

1. 次の手順を実行して、前のステップで作成した実行ロールをアタッチします。

   1. **[デフォルト実行ロールの変更]** セクションを展開します。

   1. **[既存のロールを使用]** を選択します。

   1. **[既存のロール]** で、ロール (`LambdaS3Role`) を選択します。

1. [**関数の作成**] を選択してください。

**関数コードをアップロードする方法 (コンソール)**

1. **[コードソース]** ペインで、**[アップロード元]** をクリックします。

1. **[.zip ファイル]** をクリックします。

1. **アップロード** を選択します。

1. ファイルセレクターで .zip ファイルを選択し、**[開く]** を選択します。

1. **[保存]** を選択します。

**関数のメモリとタイムアウトを設定するには (コンソール）**

1. 関数の **[設定]** タブを選択します。

1. **[一般設定]** ペインで、**[編集]** を選択します。

1. **[メモリ]** を 256 MB に設定し、**[タイムアウト]**を 15 秒に設定します。

1. **[保存]** を選択します。

**ログ形式を設定するには (コンソール）**

1. 関数の **[設定]** タブを選択します。

1. **[モニタリングおよび運用ツール]** を選択します。

1. **[ログ記録設定]** ペインで、**[編集]** を選択します。

1. **[ログ記録設定]** で、**[JSON]**を選択します。

1. **[保存]** を選択します。

------
#### [ AWS CLI ]

**関数を作成する方法 (AWS CLI)**
+ `lambda_function.zip` ファイルを含むディレクトリから次のコマンドを実行します。`region` パラメータの場合は、`us-east-2` を S3 バケットを作成したリージョンに置き換えます。

  ```
  aws lambda create-function --function-name EncryptPDF \
  --zip-file fileb://lambda_function.zip --handler lambda_function.lambda_handler \
  --runtime python3.12 --timeout 15 --memory-size 256 \
  --role arn:aws:iam::123456789012:role/LambdaS3Role --region us-east-2 \
  --logging-config LogFormat=JSON
  ```

------

#### 関数を呼び出すように Amazon S3 トリガーを設定する
<a name="file-processing-app-deploy-manual-configure-s3-trigger"></a>

ファイルをソース元のバケットにアップロードするときに Lambda 関数が実行されるようにするには、関数のトリガーを設定する必要があります。Amazon S3 トリガーは、コンソールまたは AWS CLI を使用して設定できます。

**重要**  
この手順では、オブジェクトが S3 バケット内に作成されるたびに、関数を呼び出すようにバケットを設定します。この設定は、ソース元バケットのみで行うようにしてください。Lambda 関数が自身を呼び出した同じバケットにオブジェクトを作成する場合、関数が[連続的にループして呼び出される](https://serverlessland.com/content/service/lambda/guides/aws-lambda-operator-guide/recursive-runaway)可能性があります。その結果、予期しない請求がお客様の AWS アカウント に請求される可能性があります。

------
#### [ Console ]

**Amazon S3 トリガーを設定する方法 (コンソール)**

1. Lambda コンソールの [[関数]](https://console.aws.amazon.com/lambda/home#/functions) ページを開き、関数を選択します (`EncryptPDF`)。

1. **[トリガーを追加]** を選択します。

1. **[S3]** を選択します。

1. **[バケット]** で、ソース元のバケットを選択します。

1. **[イベントタイプ]** で、**[すべてのオブジェクト作成イベント]** を選択します。

1. **[再帰呼び出し]** でチェックボックスをオンにすると、入力と出力に同じ S3 バケットを使用することは推奨されないことを認識できます。Lambda の再帰呼び出しパターンについて詳しくは、Serverless Land の「[Lambda 関数が暴走する原因となる再帰パターン](https://serverlessland.com/content/service/lambda/guides/aws-lambda-operator-guide/recursive-runaway)」を参照してください。

1. **[Add]** (追加) を選択します。

   Lambda コンソールを使用してトリガーを作成すると、Lambda は[リソースベースのポリシー](https://docs.aws.amazon.com/lambda/latest/dg/access-control-resource-based.html)を自動的に作成し、選択したサービスに関数を呼び出すアクセス許可を付与します。

------
#### [ AWS CLI ]

**Amazon S3 トリガーを設定する方法 (AWS CLI)**

1. ファイルを追加するときに Amazon S3 のソースバケットが関数を呼び出すことを許可する [[リソースベースのポリシー]](https://docs.aws.amazon.com/lambda/latest/dg/access-control-resource-based.html) を関数に追加します。リソースベースのポリシーステートメントが、他の AWS のサービス に関数を呼び出す権限を付与します。Amazon S3 に関数を呼び出す権限を付与するには、次の CLI コマンドを実行します。`source-account` パラメータは必ず自分自身の AWS アカウント ID に置き換えて、自分自身のソース元バケット名を使用するようにしてください。

   ```
   aws lambda add-permission --function-name EncryptPDF \
   --principal s3.amazonaws.com --statement-id s3invoke --action "lambda:InvokeFunction" \
   --source-arn arn:aws:s3:::amzn-s3-demo-bucket \
   --source-account 123456789012
   ```

   このコマンドで定義するポリシーにより、Amazon S3 はソース元バケットでアクションが発生した場合にのみ、関数を呼び出すことができるようになります。
**注記**  
S3 バケットの名前はグローバルに一意ですが、リソースベースのポリシーを使用する場合には、バケットがアカウントに属していなければならないことを指定するのがベストプラクティスです。これは、バケットを削除したときに、別の AWS アカウント が同じ Amazon リソースネーム (ARN) でバケットを作成する可能性があるからです。

1. 次の JSON を `notification.json` という名のファイルに保存します。この JSON をソースバケットに適用すると、新しいオブジェクトが追加されるたびに Lambda 関数に通知を送信するようにバケットが設定されます。Lambda 関数 ARN の AWS アカウント 番号と AWS リージョン を、自分自身のアカウント番号とリージョンへと置き換えます。

   ```
   {
   "LambdaFunctionConfigurations": [
       {
         "Id": "EncryptPDFEventConfiguration",
         "LambdaFunctionArn": "arn:aws:lambda:us-east-2:123456789012:function:EncryptPDF",
         "Events": [ "s3:ObjectCreated:Put" ]
       }
     ]
   }
   ```

1. 次の CLI コマンドを実行して、JSON ファイル内に作成した通知設定をソース元のバケットに適用します。`amzn-s3-demo-bucket` を自分自身のソース元バケットの名前へと置き換えます。

   ```
   aws s3api put-bucket-notification-configuration --bucket amzn-s3-demo-bucket \
   --notification-configuration file://notification.json
   ```

   `put-bucket-notification-configuration` コマンドと `notification-configuration` オプションの詳細については、「*AWS CLI コマンドリファレンス*」の「[put-bucket-notification-configuration](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/put-bucket-notification-configuration.html)」を参照してください。

------

### AWS SAM を使用してリソースをデプロイする
<a name="file-processing-app-deploy-sam"></a>

開始する前に、[Docker](https://docs.docker.com/get-docker/) と [AWS SAMCLI の最新バージョン](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html)がビルドマシンにインストールされていることを確認してください。

1. プロジェクトディレクトリで、次のコードをコピーして `template.yaml` という名前のファイルに貼り付けます。プレースホルダーバケット名を置き換えます。
   + ソースバケットの場合は、`amzn-s3-demo-bucket` を [S3 バケットの命名規則](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html)に準拠した任意の名前に置き換えます。
   + レプリケート先バケットの場合は、`amzn-s3-demo-bucket-encrypted` を `<source-bucket-name>-encrypted` に置き換えます。この `<source-bucket>` はレプリケート元バケットに選択した名前です。

   ```
   AWSTemplateFormatVersion: '2010-09-09'
   Transform: AWS::Serverless-2016-10-31
   
   Resources:
     EncryptPDFFunction:
       Type: AWS::Serverless::Function
       Properties:
         FunctionName: EncryptPDF
         Architectures: [x86_64]
         CodeUri: ./
         Handler: lambda_function.lambda_handler
         Runtime: python3.12
         Timeout: 15
         MemorySize: 256
         LoggingConfig:
           LogFormat: JSON
         Policies:
           - AmazonS3FullAccess
         Events:
           S3Event:
             Type: S3
             Properties:
               Bucket: !Ref PDFSourceBucket
               Events: s3:ObjectCreated:*
   
     PDFSourceBucket:
       Type: AWS::S3::Bucket
       Properties:
         BucketName: amzn-s3-demo-bucket
   
     EncryptedPDFBucket:
       Type: AWS::S3::Bucket
       Properties:
         BucketName: amzn-s3-demo-bucket-encrypted
   ```

   AWS SAM テンプレートは、アプリケーション用に作成するリソースを定義します。この例では、テンプレートは `AWS::Serverless::Function` タイプを使用して Lambda 関数を定義し、`AWS::S3::Bucket` タイプを使用して 2 つの S3 バケットを定義します。テンプレートで指定されたバケット名はプレースホルダーです。AWS SAM を使用してアプリケーションをデプロイする前に、テンプレートを編集して、[S3 バケットの命名規則](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html)を満たすグローバルに一意の名前でバケットの名前を変更する必要があります。このステップについては、「[AWS SAM を使用してリソースをデプロイする](#file-processing-app-deploy-sam)」で詳しく説明します。

   Lambda 関数リソースの定義は、`S3Event` イベントプロパティを使用して関数のトリガーを設定します。このトリガーにより、ソースバケットにオブジェクトが作成されるたびに関数が呼び出されます。

   関数定義は、関数の[実行ロール](lambda-intro-execution-role.md)にアタッチする AWS Identity and Access Management (IAM) ポリシーも指定します。[AWS 管理ポリシー](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html#aws-managed-policies) `AmazonS3FullAccess` は、Amazon S3 へのオブジェクトの読み取りと書き込みに必要なアクセス許可を関数に付与します。

1. `template.yaml`、`lambda_function.py`、および `requirements.txt` ファイルを保存したディレクトリで、次のコマンドを実行します。

   ```
   sam build --use-container
   ```

   このコマンドによって、アプリケーションのビルドアーティファクトが収集され、それらをデプロイする場所を適切な形式で配置します。`--use-container` オプションを指定すると、Lambda のような Docker コンテナ内に関数がビルドされます。ここではこれを使用するため、ビルドを実行するためにローカルマシンに Python 3.12 をインストールする必要はありません。

   ビルドプロセス中に、AWS SAM はテンプレートの `CodeUri` プロパティで指定した場所で Lambda 関数コードを検索します。この場合、現在のディレクトリをロケーション (`./`) として指定しました。

   `requirements.txt` ファイルが存在する場合、AWS SAM はそれを使用して指定された依存関係を収集します。デフォルトでは、AWS SAM は関数コードと依存関係を含む .zip デプロイパッケージを作成します。[PackageType](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-packagetype) プロパティを使用して、関数をコンテナイメージとしてデプロイすることもできます。

1. アプリケーションをデプロイし、AWS SAM テンプレートで指定された Lambda リソースと Amazon S3 リソースを作成するには、次のコマンドを実行します。

   ```
   sam deploy --guided
   ```

   `--guided` フラグを使用すると、AWS SAM にデプロイプロセスの手順が示されます。このデプロイでは、Enter キーを押してデフォルトのオプションをそのまま使用してください。

デプロイプロセス中に、AWS SAM でお使いの AWS アカウントに次のリソースが作成されます。
+ `sam-app` という名前の CloudFormation[ スタック](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-whatis-concepts.html#cfn-concepts-stacks)
+ `EncryptPDF` という名前の Lambda 関数
+ `template.yaml` AWS SAM テンプレートファイルを編集したときに選択した名前の 2 つの S3 バケット
+ 名前形式 `sam-app-EncryptPDFFunctionRole-2qGaapHFWOQ8` の関数の IAM 実行ロール

AWS SAM がリソースの作成を完了すると、次のメッセージが表示されます。

```
Successfully created/updated stack - sam-app in us-east-2
```

## アプリケーションをテストする
<a name="file-processing-app-test"></a>

アプリをテストするには、PDF ファイルをソースバケットにアップロードし、Lambda が送信先バケットに暗号化されたバージョンのファイルを作成することを確認します。この例では、コンソールまたは AWS CLI を使用して手動でテストするか、提供されたテストスクリプトを使用してテストできます。

本番環境のアプリケーションでは、ユニットテストなどの従来のテスト方法や手法を使用して、Lambda 関数コードが正しく機能していることを確認できます。ベストプラクティスは、実際のクラウドベースのリソースとの統合テストを実行する、提供されているテストスクリプトのようなテストを実行することです。クラウド環境における統合テストでは、インフラストラクチャが適切にデプロイされ、イベントが期待どおりに異なるサービス間で流れることを確認します。詳細については[サーバーレス関数とアプリケーションをテストする方法](testing-guide.md)を参照してください。

### アプリケーションの手動テスト
<a name="file-processing-app-test-manual"></a>

Amazon S3 ソースバケットに PDF ファイルを追加することで、関数を手動でテストできます。ファイルをソースバケットに追加すると、Lambda 関数が自動的に呼び出され、ファイルの暗号化されたバージョンが送信先のバケットに保存されます。

------
#### [ Console ]

**ファイルをアップロードしてアプリケーションをテストするには (コンソール）**

1. PDF ファイルを S3 バケットにアップロードするには、次の手順を実行します。

   1. Amazon S3 コンソールの[[バケット]](https://console.aws.amazon.com/s3/buckets) ページを開き、ソース元のバケットを選択します。

   1. **アップロード** を選択します。

   1. **[ファイルを追加]** を選択し、ファイルセレクターを使用してアップロードする PDF ファイルを選択します。

   1. **[開く]**、**[アップロード]** の順に選択します。

1. 次の手順を実行して、Lambda が PDF ファイルの暗号化されたバージョンを送信先のバケットに保存したことを確認します。

   1. Amazon S3 コンソールの [[バケット]](https://console.aws.amazon.com/s3/buckets) ページに戻り、送信先バケットを選択します。

   1. **[オブジェクト]** ペインに、名前形式 `filename_encrypted.pdf` (`filename.pdf` はソースバケットにアップロードしたファイルの名前) のファイルが表示されます。暗号化された PDF をダウンロードするには、ファイルを選択し、**[ダウンロード]** を選択します。

   1. ダウンロードしたファイルを、Lambda 関数で保護されたパスワード (`my-secret-password`) で開くことができることを確認します。

------
#### [ AWS CLI ]

**ファイルをアップロードしてアプリケーションをテストするには (AWS CLI）**

1. アップロードする PDF ファイルが含まれるディレクトリから、次の CLI コマンドを実行します。`--bucket` パラメータをソース元バケットの名前に置き換えます。`--key` および `--body` パラメータには、テストファイルのファイル名を使用します。

   ```
   aws s3api put-object --bucket amzn-s3-demo-bucket --key test.pdf --body ./test.pdf
   ```

1. 関数によってファイルの暗号化されたバージョンが作成され、それが送信先の S3 バケットに保存されたことを確認します。`amzn-s3-demo-bucket-encrypted` を自分自身の送信先のバケットの名前へと置き換えて、次の CLI コマンドを実行します。

   ```
   aws s3api list-objects-v2 --bucket amzn-s3-demo-bucket-encrypted
   ```

   関数が正常に実行されると、以下に類似した出力が表示されます。送信先のバケットには、名前形式 `<your_test_file>_encrypted.pdf` のファイルが含まれている必要があります。`<your_test_file>` はアップロードしたファイル名です。

   ```
   {
       "Contents": [
           {
               "Key": "test_encrypted.pdf",
               "LastModified": "2023-06-07T00:15:50+00:00",
               "ETag": "\"7781a43e765a8301713f533d70968a1e\"",
               "Size": 2763,
               "StorageClass": "STANDARD"
           }
       ]
   }
   ```

1. Lambda が送信先のバケットに保存したファイルをダウンロードするには、次の CLI コマンドを実行します。`--bucket` パラメータを送信先のバケットの名前に置き換えます。`--key` パラメータには、ファイル名 `<your_test_file>_encrypted.pdf` を使用します。`<your_test_file>` はアップロードしたテストファイルの名前です。

   ```
   aws s3api get-object --bucket amzn-s3-demo-bucket-encrypted --key test_encrypted.pdf my_encrypted_file.pdf
   ```

   このコマンドは、ファイルを現在のディレクトリにダウンロードし、`my_encrypted_file.pdf` として保存します。

1. ダウンロードしたファイルを、Lambda 関数で保護されたパスワード (`my-secret-password`) で開くことができることを確認します。

------

### 自動スクリプトを使用したアプリケーションのテスト
<a name="file-processing-app-test-auto"></a>

プロジェクトディレクトリに次のファイルを作成します。
+ `test_pdf_encrypt.py` - アプリケーションの自動テストに使用できるテストスクリプト
+ `pytest.ini` - テストスクリプトの設定ファイル

以下のセクションを展開してコードを表示し、各ファイルの役割の詳細を確認してください。

#### 自動テストスクリプト
<a name="file-processing-app-test-script"></a>

次のコードをコピーし、`test_pdf_encrypt.py` という名前のファイルに貼り付けます。プレースホルダーバケット名は必ず置き換えてください。
+ `test_source_bucket_available` 関数で、`amzn-s3-demo-bucket` をソースバケットの名前に置き換えます。
+ `test_encrypted_file_in_bucket` 関数で、`amzn-s3-demo-bucket-encrypted` を `source-bucket-encrypted` に置き換えます。`source-bucket>` はソースバケットの名前です。
+ `cleanup` 関数で、`amzn-s3-demo-bucket` をソースバケットの名前に置き換え、`amzn-s3-demo-bucket-encrypted` を送信先バケットの名前に置き換えます。

```
import boto3
import json
import pytest
import time
import os

@pytest.fixture
def lambda_client():
    return boto3.client('lambda')
    
@pytest.fixture
def s3_client():
    return boto3.client('s3')

@pytest.fixture
def logs_client():
    return boto3.client('logs')

@pytest.fixture(scope='session')
def cleanup():
    # Create a new S3 client for cleanup
    s3_client = boto3.client('s3')

    yield
    # Cleanup code will be executed after all tests have finished

    # Delete test.pdf from the source bucket
    source_bucket = 'amzn-s3-demo-bucket'
    source_file_key = 'test.pdf'
    s3_client.delete_object(Bucket=source_bucket, Key=source_file_key)
    print(f"\nDeleted {source_file_key} from {source_bucket}")

    # Delete test_encrypted.pdf from the destination bucket
    destination_bucket = 'amzn-s3-demo-bucket-encrypted'
    destination_file_key = 'test_encrypted.pdf'
    s3_client.delete_object(Bucket=destination_bucket, Key=destination_file_key)
    print(f"Deleted {destination_file_key} from {destination_bucket}")
        

@pytest.mark.order(1)
def test_source_bucket_available(s3_client):
    s3_bucket_name = 'amzn-s3-demo-bucket'
    file_name = 'test.pdf'
    file_path = os.path.join(os.path.dirname(__file__), file_name)

    file_uploaded = False
    try:
        s3_client.upload_file(file_path, s3_bucket_name, file_name)
        file_uploaded = True
    except:
        print("Error: couldn't upload file")

    assert file_uploaded, "Could not upload file to S3 bucket"

    

@pytest.mark.order(2)
def test_lambda_invoked(logs_client):

    # Wait for a few seconds to make sure the logs are available
    time.sleep(5)

    # Get the latest log stream for the specified log group
    log_streams = logs_client.describe_log_streams(
        logGroupName='/aws/lambda/EncryptPDF',
        orderBy='LastEventTime',
        descending=True,
        limit=1
    )

    latest_log_stream_name = log_streams['logStreams'][0]['logStreamName']

    # Retrieve the log events from the latest log stream
    log_events = logs_client.get_log_events(
        logGroupName='/aws/lambda/EncryptPDF',
        logStreamName=latest_log_stream_name
    )

    success_found = False
    for event in log_events['events']:
        message = json.loads(event['message'])
        status = message.get('record', {}).get('status')
        if status == 'success':
            success_found = True
            break

    assert success_found, "Lambda function execution did not report 'success' status in logs."

@pytest.mark.order(3)
def test_encrypted_file_in_bucket(s3_client):
    # Specify the destination S3 bucket and the expected converted file key
    destination_bucket = 'amzn-s3-demo-bucket-encrypted'
    converted_file_key = 'test_encrypted.pdf'

    try:
        # Attempt to retrieve the metadata of the converted file from the destination S3 bucket
        s3_client.head_object(Bucket=destination_bucket, Key=converted_file_key)
    except s3_client.exceptions.ClientError as e:
        # If the file is not found, the test will fail
        pytest.fail(f"Converted file '{converted_file_key}' not found in the destination bucket: {str(e)}")

def test_cleanup(cleanup):
    # This test uses the cleanup fixture and will be executed last
    pass
```

自動テストスクリプトは、3 つのテスト関数を実行して、アプリの正しいオペレーションを確認します。
+ テスト `test_source_bucket_available` では、テスト PDF ファイルをバケットにアップロードすることで、ソースバケットが正常に作成されたことを確認します。
+ テスト `test_lambda_invoked` は、関数の最新の CloudWatch Logs ログストリームを調べて、テストファイルをアップロードしたときに Lambda 関数が実行され、成功が報告されていることを確認します。
+ テスト `test_encrypted_file_in_bucket` では、レプリケート先バケットに暗号化された `test_encrypted.pdf` ファイルが含まれていることを確認します。

これらのテストがすべて実行されると、スクリプトは追加のクリーンアップステップを実行して、送信元バケットと送信先バケットの両方から `test.pdf` および `test_encrypted.pdf` ファイルを削除します。

AWS SAM テンプレートと同様に、このファイルで指定されたバケット名はプレースホルダーです。テストを実行する前に、このファイルをアプリケーションの実際のバケット名で編集する必要があります。このステップについては、「[自動スクリプトを使用したアプリケーションのテスト](#file-processing-app-test-auto)」で詳しく説明します。

#### テストスクリプト設定ファイル
<a name="file-processing-app-test-config"></a>

次のコードをコピーし、`pytest.ini` という名前のファイルに貼り付けます。

```
[pytest]
markers =
    order: specify test execution order
```

これは、`test_pdf_encrypt.py` スクリプト内のテストを実行する順序を指定するために必要です。

テストを実行するには、次の手順を実行します。

1. `pytest` モジュールがローカル環境にインストールされていることを確認します。`pytest` は以下のコマンドを実行することでインストールできます。

   ```
   pip install pytest
   ```

1. `test.pdf` という名前の PDF ファイルを、`test_pdf_encrypt.py` ファイルと `pytest.ini` ファイルがあるディレクトリに保存します。

1. ターミナルまたはシェルプログラムを開き、テストファイルがあるディレクトリから次のコマンドを実行します。

   ```
   pytest -s -v
   ```

   テストが完了すると、次のようになります。

   ```
   ============================================================== test session starts =========================================================
   platform linux -- Python 3.12.2, pytest-7.2.2, pluggy-1.0.0 -- /usr/bin/python3
   cachedir: .pytest_cache
   hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('/home/pdf_encrypt_app/.hypothesis/examples')
   Test order randomisation NOT enabled. Enable with --random-order or --random-order-bucket=<bucket_type>
   rootdir: /home/pdf_encrypt_app, configfile: pytest.ini
   plugins: anyio-3.7.1, hypothesis-6.70.0, localserver-0.7.1, random-order-1.1.0
   collected 4 items
   
   test_pdf_encrypt.py::test_source_bucket_available PASSED
   test_pdf_encrypt.py::test_lambda_invoked PASSED
   test_pdf_encrypt.py::test_encrypted_file_in_bucket PASSED
   test_pdf_encrypt.py::test_cleanup PASSED
   Deleted test.pdf from amzn-s3-demo-bucket
   Deleted test_encrypted.pdf from amzn-s3-demo-bucket-encrypted
   
   
   =============================================================== 4 passed in 7.32s ==========================================================
   ```

## 次のステップ
<a name="file-processing-app-next-steps"></a>

これで、サンプルアプリケーションを作成できました。このコードをベースにして、他の種類のファイル処理アプリケーションを作成することができます。`lambda_function.py` ファイル内のコードを変更して、ユースケースのファイル処理ロジックを実装します。

一般的なファイル処理のユースケースの多くには、画像処理が含まれます。Python で画像処理を行う場合、[Pillow](https://pypi.org/project/pillow/) のような一般的な画像処理ライブラリは、通常 C 言語または C\$1\$1 コンポーネントを含んでいます 関数のデプロイパッケージが Lambda 実行環境と互換性があることを確認するには、正しいソースディストリビューションバイナリを使用することが重要です。

AWS SAM を使用してリソースをデプロイする場合、デプロイパッケージに適切なソースディストリビューションを含めるための追加の手順を実行する必要があります。AWS SAM はビルドマシンとは異なるプラットフォームの依存関係をインストールしないため、ビルドマシンが Lambda 実行環境とは異なるオペレーティングシステムまたはアーキテクチャを使用している場合、`requirements.txt` ファイルに正しいソースディストリビューション (`.whl` ファイル) を指定しても機能しません。代わりに、次のいずれかの操作を行います。
+ `sam build` を実行するときは、 `--use-container` オプションを使用します。このオプションを指定すると、AWS SAM は Lambda 実行環境と互換性のあるコンテナベースイメージをダウンロードし、そのイメージを使用して関数のデプロイパッケージを Docker コンテナに構築します。詳細については、「[指定されたコンテナ内における Lambda 関数の構築](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/using-sam-cli-build.html#using-sam-cli-build-options-container)」を参照してください。
+ 正しいソースディストリビューションバイナリを使用して関数の .zip デプロイパッケージを自分で構築し、AWS SAM テンプレートの `CodeUri` として指定したディレクトリに .zip ファイルを保存します。バイナリディストリビューションを使用して Python 用の .zip デプロイパッケージを構築する方法の詳細については、「[依存関係を含めて .zip デプロイパッケージを作成する](python-package.md#python-package-create-dependencies)」および「[ネイティブライブラリとともに .zip デプロイパッケージを作成する](python-package.md#python-package-native-libraries)」を参照してください。