v-crn Code Log

主に備忘録

Heroku上のSolidus製ECサイトからAmazon S3に画像をアップロードする

Solidusで構築したECサイトの画像用StorageとしてAmazon S3を導入する方法を紹介します。

前提

Gemインストール

2019年7月現在、Solidusは画像添付機能をpaperclipというGemに依存しています。

ちなみにインストール済みのGemが依存しているGemは以下のコマンドで確認できます。

$ gem dependency Gem名

例:

$ gem dependency paperclip
Gem paperclip-5.3.0
  activemodel (>= 4.2.0)
  activerecord (>= 4.2.0, development)
  activesupport (>= 4.2.0)
  appraisal (>= 0, development)
  aruba (~> 0.9.0, development)
  aws-sdk (>= 2.3.0, < 3.0, development)
  bourne (>= 0, development)
  bundler (>= 0, development)
  capybara (>= 0, development)
  cucumber-expressions (= 4.0.3, development)
  cucumber-rails (>= 0, development)
  fakeweb (>= 0, development)
  fog-aws (>= 0, development)
  fog-local (>= 0, development)
  generator_spec (>= 0, development)
  launchy (>= 0, development)
  mime-types (>= 0)
  mimemagic (~> 0.3.0)
  mocha (>= 0, development)
  nokogiri (>= 0, development)
  railties (>= 0, development)
  rake (>= 0, development)
  rspec (~> 3.0, development)
  shoulda (>= 0, development)
  terrapin (~> 0.6.0)
  timecop (>= 0, development)

paperclipで扱う画像データをS3にアップロードするために今回はaws-sdk(version 3.0未満)とfog-awsというGemを利用します。

# Gemfile
gem 'aws-sdk', '< 3.0'
gem 'fog-aws'
$ bundle update

paperclip初期設定ファイルの作成

/config/initializersにpaperclip.rbを新規作成します。

$ touch config/initializers/paperclip.rb

paperclip.rbを次のように編集します。*2

# paperclip.rb
if ENV['S3_ACCESS_KEY_ID']
  Paperclip::Attachment.default_options.merge!(
    storage: :fog,
    fog_credentials: {
      provider: 'AWS',
      aws_access_key_id: ENV['S3_ACCESS_KEY_ID'],
      aws_secret_access_key: ENV['S3_SECRET_ACCESS_KEY'],
            region: ENV['S3_REGION'],
            host: ENV['S3_HOST']
        },

        # S3バケットの指定
        fog_directory: ENV["S3_BUCKET"]

        #---アップロードするデータを一般公開しない場合の設定---
        # fog_public設定をfalseにすることでS3のURLへの直接アクセスを禁止できる
        # config.fog_public = false

        # S3の認証URLの有効期限(秒)の設定
        # config.fog_authenticated_url_expiration = 60
        #----------------------------------------------
  )

  Spree::Image.attachment_definitions[:attachment].delete(:url)
    Spree::Image.attachment_definitions[:attachment].delete(:path)
end

Amazon S3バケットを用意する

バケットの作成

公式の手順に従ってバケットを作成してください。

docs.aws.amazon.com

開発環境でもS3をストレージとして使う場合は、本番環境と合わせて2つのバケットを作成します。

このステップでの要点をあげると、以下のようなものがあります。

  • バケット名は既存のバケット名と重ならないようにする
  • 日本での利用を想定している場合、リージョンは「アジアパシフィック(東京)」を選択(最寄りのリージョンを設定するのはデータレイテンシーを減らすため)
  • 「既存のバケットから設定をコピー」できる(ただしバケットポリシーを除く)

バケットポリシー

対象となるバケットを選択し、「アクセス権限」 -> 「バケットポリシー」をクリックします。

今回S3に保存するデータは、サイトを訪れたユーザーの誰もが見ることができる商品画像です。また、画像をストレージに追加したり削除したりはできますが、アップロードした画像を編集することはありません。、それらを踏まえ、バケットポリシーで一般公開と読み取り専用の設定を行います。

バケットポリシーの空欄に次の内容を記述してください。ただし、「bucketname」は対象のバケット名に置き換えてください。

{
    "Version": "2012-10-17",
    "Id": "PublicRead",
    "Statement": [
        {
            "Sid": "ReadAccess",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::bucketname/*"
        }
    ]
}

ブロックパブリックアクセス (バケット設定)

パブリックポリシーを不正に変更できないように「アクセス権限」 -> 「ブロックパブリックアクセス」で以下の項目をオンにしておきましょう。

「新しいパブリックバケットポリシーを介して許可されたバケットとオブジェクトへのパブリックアクセスをブロックする」

今後バケットポリシーを変更する際はこの設定をオフにしてから変更してください。

アクセス・コントロール・リスト

「アクセス権限」 -> アクセス・コントロール・リスト -> パブリックアクセス -> Everyoneをクリックし、「オブジェクトの一覧」を有効化します。

IAMユーザーの作成

公式の手順に従ってIAMユーザーを作成しましょう。

AWS アカウントでの IAM ユーザーの作成 - AWS Identity and Access Management

ここで想定するIAMユーザーはECサイトの管理者です。 管理者には画像をS3にアップロードしたり、削除したりできる権限を与えたいので、アクセス権限はReadOnlyではなくFullAccessを設定します。

要点

  • ユーザー名は任意
  • AWS アクセスの種類:「プログラムによるアクセス」を有効化
  • アクセス権限:AmazonS3FullAccess
  • シークレットアクセスキーはユーザー作成直後のcsvダウンロード画面以外では確認できないので注意

環境変数の設定

環境変数の設定方法には.bash_profileに記述する方法とdotenvというgemを使う方法の主に2種類があります。 個人的にシステム側の環境設定を汚したくないので、今回はdotenvを利用してみます。

なお、dotenvは本来開発環境での環境変数の読み込みのためにつくられた背景から、基本的には本番環境の環境変数設定に利用することを勧めていません。その方針に従い、development環境とtest環境のみにdotenvを使用することとします。

Gemインストール

# Gemfile
gem 'dotenv-rails', groups: [:development, :test]
$ bundle

環境変数の設定

本番環境以外の環境変数設定

プロジェクトのルートディレクトリに環境変数設定ファイルを作成します。

$ touch .env.development
$ touch .env.test

このファイルの内容にはパスワードが含まれているため、絶対に外部に流出させたくありません。誤ってリポジトリに上げて一般公開してしまうことがないよう.gitignoreに環境設定ファイルを無視する記述をしておきましょう。

# .gitignore
.env*
# ファイル名が「.env」で始まるファイルをステージングできなくする

もし一度でもgit addコマンド等でステージングしてgitの追跡対象に加えていると.gitignoreの設定が反映されません。その場合、次のコマンドを使ってキャッシュを削除します。

$ git rm --cached 無視したいファイル名

git statusコマンドで無視するファイルの名前が表示されなくなればOKです。

$ git status

では次に作成したファイルに環境変数を定義します。環境変数名はpaperclip.rbに記述したそれぞれの変数と対応するようにします。

.env.development

S3_BUCKET=
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_REGION=ap-northeast-1
S3_HOST=s3-ap-northeast-1.amazonaws.com

「=」の右側にバケット名、アクセスキーID、シークレットアクセスキーを書いてください。引用符は不要です。.env.testについても同様に記述してください。なお、上で設定しているS3_REGION_NAMEとS3_HOST_NAMEは東京リージョンのものです。

ファイルの編集が終わったら環境変数にアクセスできることをRailsコンソール上で確認しましょう。

$ RAILS_ENV=development rails c
Loading development environment (Rails 5.2.3)
>> ENV['S3_ACCESS_KEY_ID']
=> "S3ACCESSKEYIDDEV"

本番環境(Heroku)での環境変数設定

次のコマンドを利用して、本番環境用S3バケット情報をコンソールから設定します。

$ heroku config:set S3_BUCKET=bucket_name
$ heroku config:set S3_ACCESS_KEY_ID=access_key_id
$ heroku config:set S3_SECRET_ACCESS_KEY=secret_access_key
$ heroku config:set S3_REGION=ap-northeast-1
$ heroku config:set S3_HOST=s3-ap-northeast-1.amazonaws.com

設定情報はheroku configで確認できます。

動作確認

開発環境での動作確認

まずDBをリセットします。

$ bundle exec rails db:reset
Dropped database 'db/development.sqlite3'
Dropped database 'db/test.sqlite3'
Created database 'db/development.sqlite3'
Created database 'db/test.sqlite3'

...

Loading seed file: stores
Loading seed file: store_credit
Loading seed file: countries
Loading seed file: return_reasons
Loading seed file: states
Loading seed file: stock_locations
Loading seed file: zones
Loading seed file: refund_reasons
Loading seed file: roles
Loading seed file: shipping_categories
Create the admin user (press enter for defaults).
Email [admin@example.com]: 
Password [test123]: 
Done!

途中、管理者アカウントの作成があることに注意です。

続いて商品サンプルデータを再インストールします。

$ bundle exec rails g spree:install
...
Admin user has already been previously created.
Would you like to create a new admin user? (yes/no)
no
No admin user created.
...
**************************************************
Solidus has been installed successfully. You're all ready to go!
 
Enjoy!

サーバーを起動してhttp://localhost:3000/にアクセスします。

$ bundle exec rails s

無事、商品画像が表示されたでしょうか?

問題なさそうであればブラウザのDeveloper ToolsでページのHTMLを解析してみましょう。

f:id:v-crn:20190702062524p:plain

上図では商品画像を示すimgタグのsrc属性に画像URLが格納されています。

https://solidus-ec-site-development.s3.amazonaws.com/spree/images/attachments/000/000/021/small/ror_tote.jpeg?1562014586

https://solidus-ec-site-development.s3.amazonaws.com/spree/images/attachments/000/000/021/small/ror_tote.jpeg?1562014586

このURLは次の形式で構成されています。

https://バケット名.s3.amazonaws.com/プロジェクトのpublicフォルダ下から画像保存先までのパス

あとは管理画面にログインしたり、商品を登録したり、カートに入れた商品を会計したりして挙動を確かめてみてください。

本番環境(Heroku)での動作確認

以下のコマンドを順に打ち込んで確認していきます。 ログインとherokuアプリの作成が済んでいる場合、上2つをスキップしてください。

$ heroku login
$ heroku create アプリ名(任意)
------
$ heroku pg:reset DATABASE
$ bundle exec rake assets:precompile
$ git push heroku master
$ heroku addons:create heroku-postgresql
$ heroku run rails db:migrate
$ heroku run rails g spree:install
$ heroku restart

無事デプロイできたら、開発環境での動作確認と同様にしてサイトの挙動を確かめてみましょう。

*1:ImageMagickには多くの脆弱性が報告されているため使用に際しては十分注意が必要です。

*2:host:の項目がない状態のpaperclip.rbでS3への画像アップロードに成功している旨の記事が多く見られましたが、私の環境ではうまくいきませんでした。バージョンアップで要求が変更されたのか、何か他のミスがあったのか...。