ツナワタリマイライフ

日常ネタから技術ネタ、音楽ネタまで何でも書きます。

Active StorageでGoogle Cloud Storageに画像を保存する

はじめに

ホビープロジェクトでモバイル向けのシンプルなTODOアプリを作ることになりました。TODOといえばWebアプリサンプルのド定番だけど、本当に自分にあった使えるものを作ってきっちり公開まではやろうと思ってます。それは置いておいて、サーバサイドをRailsで書きました。

TODOには写真が添付できると良い、というのが要件にあったので、Active Storageを使うことにします。せっかくRails 5.2を使っているので!

Active Storageとは

最高のガイドがあるので基本的にこれを読めばいいです。

railsguides.jp

ただ、調べてもS3にやってみた、という例が多く、今回は気が向いてGoogle Cloud Storageに保存することにしたのでそちらを記事に残しておこうかな、というのが本記事の主旨です。

ちなみに気が向いた理由ですが、アプリの認証をGoogleでやっていて、すでにプロジェクトがあったからです。

Active Storageを使う準備

参考になりました。ありがとうございます。【Rails 5.2】 Active Storageの使い方 - Qiita

Gemfileに必要なドライバをインストールしておきましょう。

gem "google-cloud-storage", "~> 1.3", require: false

bundle

そしてActive Storage用のテーブルを生成するためのmigrationを行う必要があります。これは内部的に使われているようで、実際は触らずに使えます。

$ rails active_storage:install
$ rails db:migrate

画像を持たせるmodelにhas_one_attached :imageを追加。今回はTask modelに持たせました。key名はimageでもphotoでもお好きなものを。

app/models/task.rb

class Task < ApplicationRecord
  belongs_to :user
  belongs_to :tag
  has_one_attached :image

# (skip)
end

デフォルトでconfig/storage.ymlはdiskになっているので、development環境でこの時点でファイルのアップロードができます。templateはerbでやりました。form_forで受けて画像を添付したいリソースのcreate actionを行うときに、file fieldでファイルをアップロードして、先ほどmodelで入力したkey名をparamにのせてあげます。これだけでできます。すごい。

<%= form_for @task,:url => {:action => :create} do |f| %>
  <p>
  <%= f.label :name %>
  <%= f.text_field :name %>
  </p>

  <%= f.file_field :image %>

  <%= f.submit %>

(root)/storage以下にファイルがアップロードされるでしょう。config/storage.ymlにすでにこのように書いてあるからですね。

  5 local:
  6   service: Disk
  7   root: <%= Rails.root.join("storage") %>

Google Cloud Storageを使う準備

さて、Rails guideを見るにconfig/storage.ymlに以下を埋める必要がありそうです。

google:
  service: GCS
  keyfile: {
    type: "service_account",
    project_id: "",
    private_key_id: "",
    private_key: "",
    client_email: "",
    client_id: "",
    auth_uri: "https://accounts.google.com/o/oauth2/auth",
    token_uri: "https://accounts.google.com/o/oauth2/token",
    auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs",
    client_x509_cert_url: ""
  }
  project: ""
  bucket: ""

service_accountを作ってproject作ってバケット作ればいけそうな雰囲気がしますね。

Service Account作る

プロジェクトは適当に作ってある前提で、サービスアカウントはIAMと管理->サービスアカウント

ユニークな名前をいれましょう。

f:id:take_she12:20181111174935p:plain

作成されたら権限を付与します。閲覧だけでは足りないので編集権限をつけます。

f:id:take_she12:20181111174944p:plain

Active Storageがアクセスするためのkeyを作成します。

f:id:take_she12:20181111174954p:plain

作成するとダウンロードされます。

APIを有効にする

Google Cloud Storage JSON APIを有効にしてください。しないとアクセス時に弾かれます。あと、有効になるまで数分かかります。

https://console.cloud.google.com/apis/api/storage-api.googleapis.com/overview?project=simple-todo-kamoshare

ちなみに認証情報を作成する、とありますが多分不要です。この目的に適したサービスアカウントがすでに存在します、と言われるので。

f:id:take_she12:20181111175449p:plain

バケットに権限を与える

バケットは普通にぼちぽち作っていただいて、バケットに権限を与えないと弾かれます。サービスアカウントが常に権限もってるじゃん、って思うんですけどね。

ストレージ->ブラウザでバケットの右側の3点ボックスから権限を編集

f:id:take_she12:20181111175819p:plain

で、ここがびっくりなんですが、ストレージ管理者の権限が必要です。強すぎでは、、、と思うんですが、サービスアカウント実態にも確かにこれ相当のものを持たせてるから仕方ないかなと無理やり納得しています。

f:id:take_she12:20181111175939p:plain

Storage.ymlにダウンロードしたkey情報を反映

なんと、ダウンロードしたkeyを見ると中身はそのままstorage.ymlに載せるべき内容がのっています。このままいれればいいですね。

しかし、中身にはprivate_keyが入っており、localで動かすならまだしもコミットはできないので適切に秘匿しなければなりません。

Encrypted Credential

参考になりました。ありがとうございます。secrets.ymlや環境変数をRails 5.2のEncrypted Credentialsに移行する - Qiita

editorで開きます。

$ EDITOR=vim bin/rails credentials:edit
  1 # aws:
  2 #   access_key_id: 123
  3 #   secret_access_key: 345
  4
  5 # Used as the base secret for all MessageVerifiers in Rails, including the one protecting co    okies.
  6 secret_key_base: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  7
  8 private_key_id: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  9 private_key: xxxxxxxxxxxxxxxxxxxxxxxxx

こんな感じで追加してあげます。config/master.keyは秘密鍵になるので絶対に漏らさない/なくさないようにしましょう。(.gitignoreで無視されるようになっています)

最終的なconfig/storage.ymlはこんな感じになります。

google:
  service: GCS
  keyfile: {
    type: "service_account",
    project_id: "simple-todo-project",
    private_key_id: <%= Rails.application.credentials.private_key_id %>,
    private_key: <%= Rails.application.credentials.private_key %>,
    client_email: "active-storage-sample@simple-todo-project.iam.gserviceaccount.com",
    client_id: "1111111111111111111111",
    auth_uri: "https://accounts.google.com/o/oauth2/auth",
    token_uri: "https://oauth2.googleapis.com/token",
    auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs",
    client_x509_cert_url: "https://www.googleapis.com/robot/v1/metadata/x509/active-storage-sample%40simple-todo-project.iam.gserviceaccount.com"
  }
  project: "simple-todo-project"
  bucket: "simple-todo-project"

生成されたconfig/credentials.yml.encはコミットしても大丈夫です。

最後にdevelopmentでもgoogle cloud storageを使うように変更しましょう。

config/environments/development.rb

-  config.active_storage.service = :local
+  config.active_storage.service = :google

おわりに

Active Storage、model側にカラムを追加することなく画像を持たせることができてとても便利だと思いました。ちゃんとリソースを削除すれば画像も削除されます。

GCPのプロジェクト、サービスアカウント、APIに使い慣れてる人ならわかるとは思いますが、はじめて使うひとの参考になれば幸いです。