AWS + Rails で動画配信サイトを作る （２） 開発環境から S3 へのアクセス

# 1. この記事で扱う内容
AWSの各種サービスとRailsを使用して動画配信（VOD）サイトを構築する手順を、複数回に分けて紹介しています。　全体の概要については 「[（１）概要](https://qiita.com/mas_yam/items/5d6e4973fbdfeb9b7489)」 を参照してください。


このページでは、Rails アプリに aws-sdk を導入して S3 上のファイルを操作する方法について紹介します。

記載した内容は、私が開発中に試行錯誤した結果であって、使用する環境や目的によっては必ずしも最善ではないかもしれませんので、その点をご注意ください。

## 1-1. システム全体の構成
詳しくは 「[（１）概要](https://qiita.com/mas_yam/items/5d6e4973fbdfeb9b7489)」 を参照してください。

## 1-2. この記事で使用する環境と前提条件
**環境**

- CentOS 6.5
- Rails 5.2
- ruby 2.4.1
- Gem
 - aws-sdk-rails 2.0.1
 - aws-sdk-s3 1.23.0

**前提**

- AWS のアカウントを取得済みで、以下の知識がある
 - IAM ユーザー, ポリシー, ロールの作成方法を知っている、または教えてもらえば作成できる
 - Amazon S3 でバケットを作成する方法
- Ruby, Rails で独自にアプリを開発できる程度の知識がある

# 2. 開発環境からS3へアクセスする方法
Railsアプリから S3 へアクセスするには、AWS が公開している aws-sdk という gem を使用します（参照:[https://github.com/aws/aws-sdk-rails](https://github.com/aws/aws-sdk-rails)）。


aws-sdk を使用して AWS の各種サービスにアクセスするにはユーザー認証が必要になりますが、次の二つの環境ではその方法が異なります。  

- AWS Cloud9 （Amazon EC2）を使用する場合
- AWS Cloud9 （Amazon EC2）を使用しない場合

## 2-1. Cloud9 を使用する場合
開発環境として AWS のサービスである Cloud9 や EC2 を使用している場合は、S3 へのアクセスに必要な一連の認証がすべて自動で行われます（一時認証情報管理：AWS Managed Temporary Credentials）（参照：[AWS Managed Temporary Credentials](https://docs.aws.amazon.com/ja_jp/cloud9/latest/user-guide/auth-and-access-control.html#auth-and-access-control-temporary-managed-credentials)）。

一時認証情報管理により、Cloud9 を使用しているユーザーの権限を使用してS3へアクセスするため、コード内にアクセスキーやシークレット情報を記載する必要はありません。

一時認証情報管理を使用する手順は以下の通りです。

### （１）S3 アクセス用のポリシーの作成
1. IAM 管理者権限のあるアカウントで AWS にログインし、IAM ダッシュボードを開きます。
2. IAM ダッシュボードの左メニューから「ポリシー」を選択し、「ポリシーの作成」ボタンを押します。
3. 「ポリシーの作成」画面が表示されるので、「サービス欄」で「サービスの選択」を押して表示されたテキストボックスに「S3」と入力します。その下に「S3」が表示されるので、クリックしてサービスを選択します。  
![fig1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/b7c58fd9-bec5-978e-dbf3-40f837ef2ee8.png)
4. 「アクション」欄で「リスト」を全て、「読み込み」を全て選択し、「書き込み」で以下の項目を選択します。「アクセス権限の管理」は選択しません。 
![fig2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/b6198c9e-69af-743b-9398-a4b72b4e3b96.png)
5. 「リソース」欄で、「すべてのリソース」を選択します（操作の対象を特定のバケットもしくはオブジェクトのみに限定したい場合は、ここで「指定」を選んで、バケットもしくはオブジェクトのARNを指定してください）。
6. 項目の入力が完了したら、画面下部にある「ポリシーの確認」ボタンを押します。
7. 「ポリシーの確認」画面が表示されますので、「名前」にポリシーの名前を、「説明」にそのポリシーの説明を入力します。
8. 画面下部の「ポリシーの作成」ボタンを押して、ポリシーを作成します。

### （２）一時認証情報管理に使用するロールの作成
1. IAM 管理者権限のあるアカウントで AWS にログインし、IAM ダッシュボードを開きます。
2. IAM ダッシュボードの左メニューから「ロール」を選択し、「ロールの作成」ボタンを押します。
3. 「信頼されたエンティティの種類を選択」画面が表示されるので、「AWS サービス」から「 EC2 」を選択し、「次のステップ」ボタンを押します（Cloud9 はサーバーとして EC2 インスタンスを使用しているので、ロールの適用先として EC2 を選択します。）。  
![fig3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/5e46bfc1-adc7-7619-031b-1983ee6f359d.png)
4. 「Attach アクセス権限ポリシー」画面で、検索ボックスに（１）で作成したポリシーの名前を入れて、表示されたポリシーにチェックを入れて「次のステップ」ボタンを押します。
5. 「タグの追加」画面が表示されるので、何か情報を付加したい場合は、ここでキーと値を入力します。
6. 「確認」画面が表示されるので、「ロール名」にこのロールの名前を、「ロールの説明」にこのロールの説明を入れます。設定内容に間違いがなければ、「ロールの作成」ボタンを押してロールを作成します。

### （３）EC2 インスタンスにロールを適用
1. EC2 管理者権限のあるアカウントで AWS にログインし、 EC2 のコンソールを開きます。
2. EC2 コンソールの左側メニューからインスタンスを開き、該当の EC2 インスタンスを選択し、「アクション」＞「インスタンスの設定」＞「IAMロールの割り当て」を選びます。  
![fig4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/5965110d-6b36-35a0-4fcd-783a611eed13.png)
3. 「IAM ロールの割り当て」画面が表示されるので、「IAM ロール」で（２）で作成したロールを選び、「適用」ボタンを押します。

### （４）Rails に S3 を操作するためのコードを記載する
Rails から S3 を操作するには、AWS が用意している以下の GEM を使用します。

- aws-sdk-rails ([https://github.com/aws/aws-sdk-rails](https://github.com/aws/aws-sdk-rails))
- aws-sdk-s3 ([https://github.com/aws/aws-sdk-ruby/tree/master/gems/aws-sdk-s3](https://github.com/aws/aws-sdk-ruby/tree/master/gems/aws-sdk-s3))

AWS SDK の各種クラス・メソッドの詳細については、AWS が公開している以下のドキュメントで確認できます。
[https://docs.aws.amazon.com/sdk-for-ruby/v3/api/index.html](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/index.html)

S3 へのアクセスには、AWS SDK に含まれる S3.Resource クラスを使用します。　このクラスは S3 を操作するためのインターフェースをまとめたもので、用意されたメソッドを使用してバケットの一覧取得やファイルの作成などを行うことができます。

1.　Gemfile に以下を記載して、bundle install します。

```ruby:Gemfile
gem 'aws-sdk-rails'
gem 'aws-sdk-s3', '~> 1'
```
```
$> bundle install
```

2.　必要なコントローラに以下のコードを記載して、S3.Resource インスタンスを生成します。

```ruby
def xxxxx
  s3 = get_s3_resource  # S3.Resourse インスタンスを作成
end

private
def get_s3_resource
  Aws::S3::Resource.new(region: <バケットのリージョン>)
end
```

## 2-2. Cloud9 を使用しない場合
開発環境として Cloud9 や EC2 などの AWS サービスを使用していない場合は、S3 へのアクセスに認証情報（アクセスキーとシークレットキー）が必要になります。

S3 の操作に必要な権限のみを持った IAM ユーザーを作成し、その IAM ユーザーのアクセスキーとシークレットキーを使用します。

**<font color="red">【注意】</font>**

- 第三者がアクセスキーとシークレットキーを入手した場合は、その権限で許される操作はすべて実行できるようになりますので、絶対に第三者に知られないように管理してください。
- 特に、ソースコードに直接記載するようなことはしないでください。

### （１）必要な権限を持ったIAMポリシーを作成
ここでは、プログラムから S3 を操作するために必要最低限の以下の権限のみを持つポリシーを作成します。

- オブジェクトの取得
- オブジェクトの作成
- オブジェクトの削除
- オブジェクトのACL設定

<ol>
<li>IAMダッシュボードの左のメニューから「ポリシー」を選び、表示された画面の左上にある「ポリシーの作成」ボタンを押します。</li>
<li>「ポリシーの作成」画面で、以下の事項を入力し、右下にある「ポリシーの確認」ボタンを押します。
<ul>
<li>サービス ： S3</li>
<li>アクション
  <ul>
   <li>リスト ：　なし</li>
   <li>読み込み ： GetObject</li>
   <li>書込み ： PutObject, DeleteObject</li>
   <li>アクセス権限の管理 ： PutObjectAcl</li>
  </ul>
</li>
<li>リソース ： すべてのリソース</li>
</ul>
</li>
<li>「ポリシーの確認」画面でポリシーの名前（必須）と説明（任意）を入力し、右下にある「ポリシーの作成」ボタンを押します。</li>
</ol>

### （２）新しい IAM ユーザーを作成し、アクセスキーとシークレットアクセスキーを取得する
ここで作成する IAM ユーザーは、プログラムからのみ使用することを目的としており、AWS マネジメントコンソールでのログイン（ブラウザにパスワードを入力してログインする方法）に使用しないものとします。

1. IAM ダッシュボードの左のメニューから「ユーザー」を選び、表示された画面の左上にある「ユーザーを追加」ボタンを押します。
1. 「ユーザー詳細の設定」画面でユーザー名とアクセスの種類を入力して、「次のステップ：アクセス権限」ボタンを押します。  
![fig5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/37fbbd71-5d93-e484-fe45-20d1b9baa20c.png)
1. 「アクセス許可の設定」画面で「既存のポリシーを直接アタッチ」を選択し、（１）で作成したポリシーにチェックを入れて、「次のステップ：タグ」ボタンを押します。
1. 「タグの追加」画面で、必要ならばタグを設定し、「次のステップ：確認」ボタンを押します。
1. 「確認」画面で設定した内容を確認し、間違いなければ「ユーザーの作成」ボタンを押します。
1. ユーザーが作成されるとアクセスキーとシークレットアクセスキーが発行されるので、「.csvのダウンロード」ボタンを押して、キー情報を掲載したファイルをローカルに保存します（※認証情報のダウンロードはこの画面でしか行えませんので、忘れずに保存してください）。  
![fig6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/7a25d4c3-595a-99ef-e359-0b5bc5db185a.png)

### （３）Rails に S3 を操作するためのコードを記載する
操作方法は Cloud9 を使用している場合と同じですが、S3.Resource インスタンスを生成する際に（２）で取得したアクセスキーとシークレットアクセスキーを指定する必要があります。

アクセスキーとシークレットアクセスキーを直接コードに書き込むと、そのままコードを公開してしまう危険性があるので、環境変数にデータを保存して管理します。
　
<ol>
<li>Cloud9 を使用する場合と同様、Gemfile に以下を記載して、bundle install します。

```ruby:Gemfile
gem 'aws-sdk-rails'
gem 'aws-sdk-s3', '~> 1'
```
```
$> bundle install
```
</li>
<li>環境変数に（２）で取得したアクセスキーとシークレットアクセスキーを登録します。ホームディレクトリにある「.bash_profile」にコマンドを記載しておけば、以降ログイン時に自動的に環境変数が設定されるようになります。

```Bash:~/.bash_profile
export AWS_ACCESS_KEY=[アクセスキー]
export AWS_SECRET_KEY=[シークレットアクセスキー]
```
</li>
<li>プログラムで 1. の環境変数を読み込み、S3.Resource のコンストラクタで指定します。

```ruby
def xxxxx
 s3 = get_s3_resource
end

private
def get_s3_resource
  region = <バケットのリージョン>
  Aws::S3::Resource.new(
    region: region,
    credentials: Aws::Credentials.new(
        ENV['AWS_ACCESS_KEY'],
        ENV['AWS_SECRET_KEY']
    )
  )
end
```
</li>
</ol>

# 3. S3 ファイルの操作
　2. で生成した S3.Resource インスタンスを使用して、S3 にファイルをアップロードする方法と、S3 上のファイルを削除する方法を紹介します。

## 3-1. S3へファイルをアップロードする方法
作業の流れは以下の通りです。

- S3バケットの作成
- Rails でファイルアップロード用のアクション、モデル、ビューを作る


**①　バケットの作成**
アップロードした動画ファイルを保存するためのバケットを S3 に作成します。
ここでは、簡単のために「bucket-name」という名前でバケットを作成したものとします。

**②　アクション、モデル、ビューの作成**
Rails でファイルアップロード用のアクション、モデル、ビューを作ります。

1.　一時保存用ディレクトリの作成
ファイルは一度ローカル（Railsアプリが稼働しているサーバー）に保存してから、それをS3に転送するので、ファイルを一時保存するディレクトリを用意します（ここでは、「./tmp/s3」とします）。

```bash
# 一時保存用ディレクトリの作成
$> mkdir tmp/s3
```
2.　モデルの作成
アップロード後のファイルのキー（S3バケット上のパス）を保存するモデルを作成します（ここではモデル名を s3file とし、キーを保持するカラムを keyとします）

```bash
# urlを保存するカラムfileを持つモデル 
$> rails g model s3file key:string
$> rails db:migrate
```

3.　ビューの作成
View に form_for を使用してファイルアップロードのフォームを作成します。

```ruby:app/views/s3files/new.html.erb
<%= form_for @s3file do |f|%>
  <div class="form-group">
    <%= f.label :key, 'ファイル'%>
    <%= f.file_field :key, class: 'form-control' %>
  </div>
  <%= f.submit 'アップロード', class: 'btn btn-primary'%>
<% end %>
```

4.　コントローラ①
コントローラに S3 アクセス用の設定を記載します。

```ruby:app/controllers/s3files_controller.rb 
class S3filesController < ApplicationController
  def initialize
    super
    @region = 'region-name'  # ①で作成したバケットのリージョン
    @bucketname = 'bucket-name'  # ①で作成したバケットの名前
    @s3 = get_s3_resource # S3リソースオブジェクトの生成
  end
  
  private
  def get_s3_resource
    # Cloud9(EC2)使用の場合
    Aws::S3::Resource.new(region: @region)

    # Cloud9(EC2)以外のサーバーを使用している場合
    Aws::S3::Resource.new(
      region: @region,
      credentials: Aws::Credentials.new(
          # 2-2.(3).2. で登録したアクセスキーとシークレットキー
          ENV['AWS_ACCESS_KEY'],  
          ENV['AWS_SECRET_KEY']
       )
    )
  end
end
```

5.　コントローラ②
４．と同じコントローラにファイルアップロード用のアクションを追加します。

```ruby:app/controllers/s3files_controller.rb
def new
  @s3file = S3file.new()
end

def create
  # ポストされたfileデータを取得
  file = s3file_params[:key]
  filename = file.original_filename

  #  一時保存用のパスにファイルを保存 
  file_path = "tmp/s3/#{filename}"
  File.binwrite(file_path, file.read)
  
  # バケット名を指定
  bucket = @s3.bucket(@bucketname)
  
  # バケットに保存する際の一意の識別名（ファイル名）を指定 
  key = filename
  # S3にアップロードする際に特定のディレクトリに入れたい場合は、filename の先頭に
  # ディレクトリ名を追加してください
  # 例：key = "dir1/dir2/#{filename}"）
  object = bucket.object(key)

  # upload_fileメソッドを使って、S3上のバケットにファイルをアップロードする 
  # 　第一引数 = 一時保存してあるファイルのパス 
  # 　第二引数 = オプション(aclはアクセス権) 
  object.upload_file(file_path, acl:'public-read')
  
  # アップロードしたファイルのキーをDBに保存 
  s3file = S3file.new(key: key)
  s3file.save
  
  redirect_to root_path
end
```

※ upload_file メソッドを使うと、大きいファイルは自動的に multipart upload API を使用してアップロードされます（[https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#upload_file-instance_method](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#upload_file-instance_method)）


## 3-2. S3 のファイルを削除する方法
S3.Object クラスの delete メソッドを使うと、S3 のバケットに保存してあるファイル（オブジェクト）を削除することができます。

参照：Aws::S3::Object の delete メソッド ([https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#delete-instance_method](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#delete-instance_method))

3-1.② の手順 1 ～ 4 までを実施済みとして 、コントローラに以下の削除用アクションを追加します。
（S3file テーブルの削除するデータのIDが、ビューからリクエストパラメータとして送信されるものとします）

```ruby:app/controllers/s3files_controller.rb
def destroy
  s3file = S3file.find(params[:id]) #テーブルからデータを取り出す
  key = s3file.key  # キー（ファイル名）取得 
  
  bucket = @s3.bucket(@backetname) # バケット指定
  object = bucket.object(key)  # キー指定
  object.delete  # オブジェクト（ファイル）削除
  
  s3file.destroy
  redirect_to s3files_path, flash: {notice: "ファイル [#{key}] を削除しました"}
end
```

<br/>

次回は、S3にアップロードしたMP4形式の動画ファイルを自動的にHLS形式に変換する方法について紹介します。

<br/>

# 目次
本記事では、システム構築の手順を以下のように数回に分けて紹介します。

（１）[概要](https://qiita.com/mas_yam/items/5d6e4973fbdfeb9b7489)
動画配信システム全体の構成を紹介します
（２）開発環境から S3 へのアクセス
このページ
（３）[S3 上の動画ファイルを Lambdaで自動的に動画変換する方法](https://qiita.com/mas_yam/items/788d08b7143108d4a3b1)
S3 に動画ファイルを保存すると、自動的に Elastic Transcoder による動画変換を実行する方法について紹介します。
（４）[S3 上の動画ファイルを CloudFront で配信する方法](https://qiita.com/mas_yam/items/7116de3c795b4dc8c015)
S3 に保存した動画ファイルを CloudFront 経由で配信する方法について紹介します。