Amplify FuctionからStorageのファイルを操作する

## はじめに

この記事はAmplify Functionから、Amplify Storageのファイルを操作する方法を共有するためのものです。

### 対象とする読者

-   AWSを使ったことがある
-   Amplifyの[Getting started](https://docs.amplify.aws/start/q/integration/js)を完了した
-   Amplifyに含まれている個別のサービスを使ったことがない

### 対象とする環境

この記事では、JavaScriptベースのWebアプリケーションを対象とします。モバイルアプリケーションは対象としません。

必須となるnpmモジュールは以下の通りです。

_▼package.json_

```json
"dependencies": {
  "aws-amplify": "^3.3.6"
}
```

#### パッケージのバージョンを確認してください

この記事では2020/12/04時点でのAmplifyを前提にしています。Amplifyは活発に開発が行われているため、導入手順が大きく変更されている場合があります。記事を読む前に、お手元の環境を確認してください。

## 先に結論だけ

FunctionにStorageへのアクセス権限を設定すれば、FunctionのLambda関数内からS3ストレージとして読み書きできます。しかしAmplifyが設定するアクセス権限に制限があるため、一部の命令は実行できません。

## Amplifyの各サービスについての解説

まず、今回の記事で対象となる各サービスを解説します。

### Amplify

[Amplify](https://aws.amazon.com/jp/amplify/)とは、モバイル/Webアプリケーションの開発に必要なAWSサービスのパッケージです。アプリケーションに必要となるAPI/ユーザー認証/ホスティング/ユーザー分析…といった機能がパッケージされています。

#### Amplify Function

[Amplify Function](https://docs.amplify.aws/cli/function)はサーバーレスでプログラムを実行するサービスです。AWS EC2のように仮想マシンが立ち上がっているわけではありません。何らかのトリガーによって呼び出された時だけ、事前に設定したプログラムが動きます。

Functionサービスは[AWS Lambda](https://aws.amazon.com/jp/lambda/)で構成されています。

#### Amplify Storage

[Amplify Storage](https://docs.amplify.aws/lib/storage/getting-started/q/platform/js)はアプリケーションに統合されたオンラインストレージです。データベースには向かないメディアファイルを格納できます。たとえばユーザーがアップロードした写真を任意のユーザーにシェアするといった使い方ができます。

Storageサービスは[Amazon S3](https://aws.amazon.com/jp/s3/)で構成されています。

## 手順

Amplify FunctionとStorageを連携させる手順を解説します。`amplify init`コマンドを実行し、空のAmplifyアプリケーションが作成されている時点から手順を開始します。


### Storage : 新規Storageを作成する

`amplify add storage`コマンドで新規ストレージを追加します。サービスの種類はコンテンツを選びます。

```shell
% amplify add storage      
? Please select from one of the below mentioned services: Content (Images, audio, video, etc.)
? You need to add auth (Amazon Cognito) to your project in order to add storage for user files. Do you want to add auth now? Yes
```

StorageにはAuthサービスが必須なので、まだセットアップされていない場合はAmplifyから追加を求められます。

```shell
Using service: Cognito, provided by: awscloudformation
 
 The current configured provider is Amazon Cognito. 
 
 Do you want to use the default authentication and security configuration? Default configuration
 Warning: you will not be able to edit these selections. 
 How do you want users to be able to sign in? Email
 Do you want to configure advanced settings? No, I am done.
Successfully added auth resource <Auth名> locally

Some next steps:
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud
```

Authのセットアップが完了すると、そのままStorageの作成が続きます。

```shell
? Please provide a friendly name for your resource that will be used to label this category in the project: <ストレージ名>
? Please provide bucket name: <S3バケット名>
? Who should have access: Auth users only
? What kind of access do you want for Authenticated users? create/update, read, delete
? Do you want to add a Lambda Trigger for your S3 Bucket? No
Successfully added resource <ストレージ名> locally

If a user is part of a user pool group, run "amplify update storage" to enable IAM group policies for CRUD operations
Some next steps:
"amplify push" builds all of your local backend resources and provisions them in the cloud
"amplify publish" builds all of your local backend and front-end resources (if you added hosting category) and provisions them in the cloud
```

### Function : 新規Functionを作成する

まず`amplify add function`コマンドで、Functionを追加します。

_▼ログ_

```shell
$ amplify add function
Scanning for plugins...
Plugin scan successful
? Select which capability you want to add: Lambda function (serverless function)
? Provide an AWS Lambda function name: <任意のFunction名>
? Choose the runtime that you want to use: NodeJS
? Choose the function template that you want to use: Hello World

Available advanced settings:
- Resource access permissions
- Scheduled recurring invocation
- Lambda layers configuration

? Do you want to configure advanced settings? Yes
? Do you want to access other resources in this project from your Lambda function? Yes
? Select the category storage
? Storage has 5 resources in this project. Select the one you would like your Lambda to access 
❯◉ <ストレージ名>
^^^^^^^^^^^^^^^^^^^
```

`? Do you want to configure advanced settings?`(詳細設定をしますか？)で`Yes`を選択します。アクセス権限の追加について聞かれますので、操作したいバケットへのアクセスを許可します。

❗下線で強調した部分は**複数選択可能なチェックボックス**です。スペースキーで選択、リターンで決定という操作方法です。なにもチェックを行わないままリターンを押すと操作がキャンセルされたと解釈して処理が進みます。ご注意ください。

Functionのindex.jsにS3バケット名を格納した環境変数が追加されます。`STORAGE_<ストレージ名>_BUCKETNAME`がバケット名です。

_▼amplify/backend/function/"Function名"/src/index.js_

```js
/* Amplify Params - DO NOT EDIT
	ENV
	REGION
	STORAGE_<ストレージ名>_BUCKETNAME
Amplify Params - DO NOT EDIT */
```

既存のFunctionにStorageへのアクセス権限を追加する場合は`amplify update function`コマンドを使います。Function一覧が表示されますので、その中から権限を追加するFunctionを選択してください。

### Function : 関数を実装する

Functionを実装します。

_▼amplify/backend/function/"Function名"/src/index.js_

```js
/* Amplify Params - DO NOT EDIT
	ENV
	REGION
	STORAGE_<ストレージ名>_BUCKETNAME
Amplify Params - DO NOT EDIT */

const AWS = require("aws-sdk");
const s3 = new AWS.S3();

/**
 * Storageにオブジェクトを追加する。
 * @return {Promise<*>}
 */
const putObjectToStorage = async () => {
    const testObj = {
        test: "test string",
    };
    return await s3
        .putObject({
            Bucket: process.env.STORAGE_<ストレージ名>_BUCKETNAME,
            Key: "file.json",
            Body: JSON.stringify(testObj),
            ContentType: "application/json",
        })
        .promise();
};

exports.handler = async (event) => {
    const result = await putObjectToStorage();
    const response = {
        statusCode: 200,
        body: JSON.stringify(result),
    };
    return response;
};
```

レスポンスに、生成されたS3オブジェクトのエンティティタグが出力されれば成功です。

## Amplify Functionでは実行できない操作

Amplifyによって生成されるLambda用IAMロールは以下のようになります。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:ListBucket",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::<バケット名>/*"
            ],
            "Effect": "Allow"
        }
    ]
}
```

このロールで許可されない処理は実行できません。

### ACL操作

Functionに割り当てられる権限には、[ACL](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html)の操作権限がありません。そのため[PutObjectAcl](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectAcl.html)のようなACL操作を伴う処理はできません。

### ListObjects

2020/12/13現在、FunctionからStorageバケットのオブジェクトリストを取得できません。Functionへの権限割り当てに問題があるようです。
参考 : [Issue #5528](https://github.com/aws-amplify/amplify-cli/issues/5528)

以上、ありがとうございました。