omuriceman's blog

iOS / AWS / Firebase / JavaScript / Deep Learning を中心とした技術を面白く紹介します!

AWS Amplify入門① / Storageについての解説

f:id:omuriceman:20190403220250p:plain

最近プロジェクトで扱ったAWS Amplifyについてブログにまとめていきたいと思います。その中でも第1弾は画像やテキストを保存できるStorageの紹介から始めます。 誤字脱字や認識の間違いなどありましたらコメントいただけますと幸いです。

AWS Amplifyとは

以前DevOpsのセミナーに参加した際に「DevOpsとはつまりなんなんですか?」と質問をしたところ発表者から「私もわからなくなってきました」と言われたことを印象深く覚えております。
Amplifyとは何か。も、その質問と同様に実態を説明するのは少し悩むところがあります。しかし私なりにまとめると以下のような回答になります。

  • FirebaseのようなものをAWSで実現するためのサービス
  • Webサービス開発に必要な、認証/ストレージ/データベース/解析/配信(CDN)などをサーバーレスですぐに展開できるためのサービス
  • AWSの各種マネージドサービスをまとめてフロント側ですぐに組み込めるように提供してくれるサービス

サーバーレス環境での構築になりますので、EC2などと違い環境の構築やサーバーの管理などは必要ありません。node.jsやnpmなどが入って入れば10分もしないでWebアプリケーションを作ることができます。脆弱性対応もAWSがやってくれますし、DBへのアクセスが増えた場合でもDynamoDBの管理画面ポチポチするだけですぐに対応が可能です。最近ではAmazon DynamoDB On-Demand という機能も追加されて、スケールも自動でやってくれるようになってきています。

Storageについて

今回紹介するStorageとはどういったサービスなのでしょうか。解説をしたいと思います。

機能の概要

Storageはユーザーがアップロードする、テキスト/画像/動画などのファイルを保管しておくファイルストレージ機能を提供します。AWSを触ったことがある人はご存知かと思いますが、裏側ではS3が使われております。S3を使ったことがある人はこんな経験ありませんか?

  • S3にアップロードやダウンロードできるようになるまでに管理画面の操作が面倒
  • S3のバケットの適切な権限の設定ができているか不安

そのような人のためにAmplifyがセットアップを肩代わりしてくれて、すぐにプロジェクトに組み込むことができるようになります。

ファイルアクセスレベル(ファイルの操作権限について)

Storage機能には3つのファイルアクセスレベルが存在します。

  1. public / 全てのユーザーがお互いのファイルの読み取りやアップロードが可能です。
  2. protected / 全てのユーザー同士でファイルの読み取りが可能ですが書き込みはユーザーごとのフォルダに制限されています。
  3. private / 個々のユーザーごとのフォルダのみ読み取りと書き込みができます。

言葉だけではわかりづらいかと思いますので図にまとめてみました。

f:id:omuriceman:20190403122349p:plain
ファイルアクセスレベル
publicに関しては全員が同じフォルダ上でデータをアップするので、ファイル名が同じだと上書きされてしまいますのでご注意ください。 protectedとprivateのアクセスレベルではファイルがアップロードされると、アップロードしたユーザーに紐づいているCognito Identity IDがフォルダのパス名に付与されます。

例:
protected/{user_identity_id}/hoge.png
private/{user_identity_id}/hoge.png

また1つのプロジェクトの中で3つのアクセスレベルを組み合わせることも可能です。

  • 自分だけが見れる画像や動画をアップロードする。→private
  • 自分のプロフィール画像などをアップして他のユーザーに閲覧してもらう→protected

このような使い分けができるようになるでしょう。

環境構築をする

使い方については構築を進めながら確認していきましょう。

環境構築前の下準備

入門初回ですのでAmplifyのセットアップから始めたいと思います。 node.jsのバージョンは8.x以上。npmのバージョンは5.x以上にしておいてください。 また今回の解説はReact.jsフレームワークを利用した解説となります。 今回の実装までの手順については公式ページにも記載されております。

https://aws-amplify.github.io/docs/

英語やらページがまたがっているやらでややわかりづらいので、 こちらのブログではできるだけ解説を挟んでいきたいと思います。

React.jsのセットアップ

$ npm install -g create-react-app
$ create-react-app myapp && cd myapp
$ npm start

Amplifyを導入する前のReact.js用の環境構築になります。 一行目ではReact.jsの雛形を作れるコマンドをインストールしています。 二行目ではmyappという名前で雛形を作成し、完了後myappディレクトリに移動します。 この「myapp」については自由に好きな名前をつけてもらっていいでしょう。 三行目ではローカル環境でwebサーバーを起動しています。

この一連の動作で以下のような画面が表示されたら次のステップにいきましょう。

f:id:omuriceman:20190403135542p:plain
create-react-app初期画面

Amplifyのインストール

Storage機能を先ほど作ったReact.jsにすぐに組み込めるように、Amplifyの環境を作成していきます。 まずはAmplify自身をnpmでインストールする必要があります。そしてReact上でAmplifyを簡単に扱えるためのUIも 公式で配布されていますので、同時にインストールします。

$ npm install --save aws-amplify
$ npm install --save aws-amplify-react

Amplifyのセットアップ

インストールが終わると、Amplifyのcliコマンドを実行できるようになりますのでセットアップを開始します。

$ amplify init

ここでいくつか対話形式の質問を受けますので簡単にご紹介します。

使用しているエディターについて

? Choose your default editor: (Use arrow keys)
❯ Sublime Text 
  Visual Studio Code 
  Atom Editor 
  IDEA 14 CE 
  Vim (via Terminal, Mac OS only) 
  Emacs (via Terminal, Mac OS only) 
  None 

作成するアプリの種類について

? Choose the type of app that you're building (Use arrow keys)
  android 
  ios 
❯ javascript 

使用するjavascriptフレームワークについて

? What javascript framework are you using (Use arrow keys)
❯ react 
  react-native 
  angular 
  ionic 
  vue 
  none 

パスの指定や各種コマンドの指定について。 特に指定がなければここはエンターキーを押すだけでいいと思います。

? Source Directory Path:  (src) 
? Distribution Directory Path: (build) 
? Build Command:  (npm run-script build) 
? Start Command: (npm run-script start) 

AWSプロフィール使用の有無について

? Do you want to use an AWS profile? (Y/n) 

Yを選択するといままで追加したaws profile一覧がでてきますので、 このプロジェクトで使用したいprofileを選びましょう。 nを選択するとその場でaccessKeyIdとsecretAccessKeyを入れることになります。

この後、プロジェクトのセットアップが開始されます。

⠸ Initializing project in the cloud...

裏側ではAWSのCloudFormation*1というサービスが動いていて、 セットアップを行います。 この段階でCloudFormationは3つセットアップしてくれます。

  1. Webサービスで認証したユーザー向けのIAM Role(Auth Role)
  2. Webサービスで認証していないユーザー向けのIAM Role(UnAuth Role)
  3. 今後WebサービスをデプロイするためのS3バケット

3はあまり気にしないでいただいて、1と2は今後Authなどの認証機能をサービスに組み込んだ際に、 ログインしているユーザーとそうでないユーザーで扱えるリソースの範囲を変えることができるようになります。 複雑なサービスを作る過程で触れる必要があるので覚えておくといいかと思います。

この段階でCloudFormationにエラーが出る場合、大抵のケースでprofileに与えられているIAMの権限が影響しています。ご注意ください。

Storageのセットアップ

さてここまできたら次はStorage機能を組み込んで行くことにしましょう。

$ amplify add storage

こちらでも質問を受けます。

? Please select from one of the below mentioned services (Use arrow keys)
❯ Content (Images, audio, video, etc.) 
  NoSQL Database 

利用するサービスについてですが今回は画像や動画などを利用するため、Contentを選択します。 後者のNoSQL DatabaseについてはAmplifyで触ったことがないため必要があれば調査します。
Auth機能入れる必要があるんだけどいれていいですか〜?

? You need to add auth (Amazon Cognito) to your project in order to add storage for user files. Do you want t
o add auth now? (Y/n) 

Authの設定はどうしますか〜?

Using service: Cognito, provided by: awscloudformation
 The current configured provider is Amazon Cognito. 
 Do you want to use the default authentication and security configuration? (Use arrow keys)
❯ Yes, use the default configuration. 
  No, I will set up my own configuration. 
  I want to learn more. 

今回はStorageの説明なのでデフォルトを選択していいです。

  • バケット名どうしますか〜?
  • 認証ユーザーと未認証ユーザーのファイルへのアクセス権限どうしますか〜?
? Please provide a friendly name for your resource that will be used to label this category in the project: (...) 
? Please provide bucket name: (...) 
? Who should have access: (Use arrow keys)
❯ Auth users only 
  Auth and guest users 
? What kind of access do you want for Authenticated users (Use arrow keys)
  read 
  write 
❯ read/write 
? What kind of access do you want for Guest users (Use arrow keys)
❯ read 
  write 
  read/write 

Storageを構築するための設定ファイルの準備が完了しました。 この設定情報はまだローカルにしかないのでAmplifyコマンドを通じてAWSクラウド側に送ります。

$ amplify push

先ほどStorageだけでなくAuthの設定もしましたので、確認画面には以下のように表示されているはずです。

| Category | Resource name   | Operation | Provider plugin   |
| -------- | --------------- | --------- | ----------------- |
| Auth     | cognitod9aca3ff | Create    | awscloudformation |
| Storage  | s30366fc59      | Create    | awscloudformation |
? Are you sure you want to continue? true

この後またCloudFormationがStorageとAuth用の環境構築を始めます。今回のCloudFormationは何をしているのでしょうか。

  • S3の画像や動画などがアップされるのバケットを作成します。
  • Auth用のUserPool(ユーザー認証を担当)とIdentityPool(AWSリソースへの認可を担当)を作成します。
  • 認証ユーザーと未認証ユーザー用のIAM_Roleを追加します。上記の対話時に回答をした「Authenticated users」と「Guest users」向けのS3へのアクセス権限がそのまま反映されます。

また先程紹介したCognito Identity IDはIdentityPoolで発行されるユーザーのIDになります。 解説するとなかなか長いこのAmplifyですがようやく準備が整いました。

Storageの基本機能の紹介

設定ファイルを読み込む

まずは環境構築時にaws-exports.jsというファイルが出力されていますので、使用するファイルでインポートしてみましょう。 今回はデモですのでApp.jsというファイルにインポートする前提で進めます。

import Amplify, { Storage } from 'aws-amplify';
import awsmobile from './aws-exports';
Amplify.configure(awsmobile);

aws-exports.jsファイルを読み込まなくても設定をすることは可能です。 その(マニュアルセットアップ)場合はそれぞれのパラメーターを自分で用意した文字列に置き換えます。

import Amplify from 'aws-amplify';

Amplify.configure({
    Auth: {
        identityPoolId: 'XX-XXXX-X:XXXXXXXX-XXXX-1234-abcd-1234567890ab', //REQUIRED - Amazon Cognito Identity Pool ID
        region: 'XX-XXXX-X', // REQUIRED - Amazon Cognito Region
        userPoolId: 'XX-XXXX-X_abcd1234', //OPTIONAL - Amazon Cognito User Pool ID
        userPoolWebClientId: 'XX-XXXX-X_abcd1234', //OPTIONAL - Amazon Cognito Web Client ID
    },
    Storage: {
        AWSS3: {
            bucket: '', //REQUIRED -  Amazon S3 bucket
            region: 'XX-XXXX-X', //OPTIONAL -  Amazon service region
        }
    }
});

マニュアルセットアップの場合はS3のバケットも自分で作成していると思います。Storage機能から読み込み/書き込みできるようにバケットのポリシーとCORSポリシーを設定してください。

設定方法については長くなるので割愛いたしますが下記のリンクをご参照ください。

https://aws-amplify.github.io/docs/js/storage#using-amazon-s3

https://aws-amplify.github.io/docs/js/storage#amazon-s3-bucket-cors-policy-setup

Put(ファイルのアップロード)

はじめに一番シンプルなアップロードの例です。

Storage.put('test.txt', 'Hello')
    .then (result => console.log(result)) // {key: "test.txt"}
    .catch(err => console.log(err));

この場合putイベントの引数にファイルアクセスレベルについてパラメーターを渡していないため、public配下にデータがアップロードされます。 ではprotectedやprivateの場合はどのようにすればよいでしょうか。

Storage.put('test.txt', 'Protected Content', {
    level: 'protected',
    contentType: 'text/plain'
})
.then (result => console.log(result))
.catch(err => console.log(err));

このようにしてlevelのvalueに対してprotectedかprivateを明示することで対応ができます。
続いて動画などの大容量データをアップロードする場合に進捗率を取り、プログレスバーなどに反映したい場合はどうすればいいでしょうか。

Storage.put('test.txt', 'File content', {
    progressCallback(progress) {
        console.log(`Uploaded: ${progress.loaded}/${progress.total}`);
  },
});

ここで計算された%をReactのstateなどに渡してViewを更新するとよいでしょう。

Get(ファイルのダウンロード)

一番シンプルなダウンロードの例はこちらです。

Storage.get('test.txt')
    .then(result => console.log(result))
    .catch(err => console.log(err));

オプションを指定しない場合ファイルレベルはpublicからデータを取得しようとします。 では、ファイルアクセスレベルを変更したい場合はどのようにすればいいでしょうか。 ご想像に容易いですが基本的にはPutと同じように引数にパラメーターを渡します。

Storage.get('test.txt', { level: 'protected' })
    .then(result => console.log(result))
    .catch(err => console.log(err));

Storageの説明時に図で書きましたが、protectedファイルレベルの場合他のユーザーフォルダに置かれているデータを取得することもできます。

Storage.get('test.txt', { 
    level: 'protected', 
    identityId: 'xxxxxxx' // the identityId of that user
})
.then(result => console.log(result))
.catch(err => console.log(err));

Remove(ファイルの削除)

削除については特別に解説を入れる必要はありません。Put/Getと同じ書き方です。

Storage.remove('test.txt', {level: 'protected'})
    .then(result => console.log(result))
    .catch(err => console.log(err));

List keys(フォルダ配下のファイル一覧取得)

特定のフォルダにアップロードされたファイル一覧を取得する場合はどのようにしたらいいでしょうか。

Storage.list('photos/', { 
    level: 'protected', 
    identityId: 'xxxxxxx' // the identityId of that user
})
.then(result => console.log(result))
.catch(err => console.log(err));

これでprotected/{user_identity_id}/photos/ の配下に置かれているデータ一覧を取得できるようになります。

Tracking Events

アップロードとダウンロードをトラッキングしたい場合は以下のようにします。

Storage.get('welcome.png', { track: true });

全てのストレージに対して一斉にトラッキングを仕込むことも可能です。

Storage.configure({ track: true })

またトラッキングイベントはAmazonPinpointに送られます。

UI Components for Reactを利用する

先程までの機能はUIは自分で実装する必要がありましたが、Amplifyではそれらをさらに簡易に扱えるようにUIを提供しています。デザインにこだわらない場合は利用してみるのがいいでしょう。

Picker

Pickerはファイルを選択するためのコンポーネントです。PhotoPickerとTextPickerの2種類が存在します。

PhotoPicker

Content-Typeが「image/*」のファイルを選択できるようになります。

<PhotoPicker preview onLoad={dataURL => console.log(dataURL)} />

このようにコンポートを呼び出すと、ドラッグ&ドロップもできて画像のプレビューも表示してくれるようになります。なかなかドラッグ&ドロップは実装が難しいんですよね。

f:id:omuriceman:20190403191841p:plain:w280
PhotoPicker

PhotoPickerとStorageを利用して画像をアップロードするサンプルを作成いたしました。

import React, { Component } from 'react';
import { PhotoPicker } from 'aws-amplify-react';
import Amplify,{ Storage } from 'aws-amplify';
import awsmobile from './aws-exports';
Amplify.configure(awsmobile);

class App extends Component {
  render() {
    return (
        <PhotoPicker preview onPick={data =>{ 
          const { file } = data;
          Storage.put(file.name, file,{
            contentType: file.type
          })
          .then (result => console.log(result)) 
          .catch(err => console.log(err));

        }
      } onLoad={dataURL => console.log(dataURL)} />

      );
  }
}

export default App;

認証機能はまだ付いていませんので、Guest Userもアップロードできる権限にしておいてください。amplify update storageで後からでも権限周りの変更が可能です。

TextPicker

Content-Typeが「text/*」のファイルを選択できるようになっています。 こちらもなんとすごいことに、previewをつけるとアップロードしたテキストを取得して表示してくれるようになります。

S3Image

このComponentを利用することで、データを取得して描画してを一括で対応してくれます。

import { S3Image } from 'aws-amplify-react';

render() {
    return <S3Image imgKey={key} />
}

S3Text

S3Imageと似たような感じです。「textKey」なのに注意してください。

import { S3Text } from 'aws-amplify-react';

render() {
    return <S3Text textKey={key} />
}

S3Album

S3Albumを利用すると、指定したフォルダ配下の画像やテキストを全て表示します。

import { S3Album } from 'aws-amplify-react';

render() {
    return <S3Album path={path} />

下記のようにpickerというオプションをつけると、指定したフォルダ配下の画像/テキストを表示しつつアップロードも可能になります。アップロード先はpathで指定している箇所と同じです。

<S3Album path={path} picker />

こんな時どうするの?

さて、ここまでで一通りStorageの実装は終わりです。 ここから少しだけ私の困った問題を解決しましたので次使う人のお役に立てれば…と思います。

特定のデータだけ別のバケットにアップロードしたい

最初に設定したバケットに基本的にはデータをあげていくのですが、どうしてもすでに存在しているバケットに特定のデータだけアップしたい。その場合はこのようにします。

Storage.put(fileName,fileData,{
 level: '...',
 contentType: '...',
 bucket:bucketName←これが重要!
});

コードのみ抜粋していますが、アップロードするバケット側には上記のリンクで紹介した各種ポリシーの設定などが必要です。

WYSIWYGなどの画像のアップロードにStorage機能を使いたい

ぐぐぐ。。これは実装が難しいです。私は諦めてしまいました。 Storage.getで取得できる画像のパスはPre-Signed URLといって、一定の時間しかファイルの閲覧ができません。とはいえ、アクセスするたびに毎回新しいPre-Signed URLが発行されるので基本的にStorage.get関数を呼べる限りは画像の閲覧に支障はありません。ただ、WYSISYGではHTMLを保存してそのまま呼び出すわけですのでそのHTMLに含まれる画像のパスは少し前にアップされたPre-Signed URLになりますので、しばらくすると見れなくなってしまいます。

この場合の解決策として色々ありますがスマートな解決策はないです。

  • S3のjavascript SDKを使いStorage機能を使わずにアップロード/ダウンロードする
  • バケットのポリシーを緩くして画像のパスを直接参照できるようにする

などなど。結論WYSIWYGではStorage機能は使わないのがいいでしょう。

まとめ

  • AmplifyはWebサービスを開発するのにとても便利
  • StorageはS3の面倒なあれやこれやの設定を色々やってくれて便利
  • Storageを使うために必要になると思われるUIはAmplifyで用意されている
  • 少しつまづいたらgithubを見てみると公式サイトに書いてない関数/パラメーターなどが載ってる場合がある

github.com

Amplifyの機能についてこちらの書籍でも紹介しております!

techbookfest.org

僕はAmplifyのAPI(AppSync/Graphql)の機能についてWebサービスの開発を行いながら紹介しております。4月14日に開催される技術書典にお越しの際は是非お立ち寄りくださいませ😀

*1:AWSの各種サービスの環境構築を自動設定してくれるサービスです