基本的にassetsファイル系はクラウドなりCDNに上げてるはずで、dockerイメージの中にそんなものを含めたくはないわけですよ。
しかし、未だにsprocketsという世界から抜け出ることはできてないわけで、デプロイまでのどこかでassets precompileをしなければならない。
で、基本的にイメージビルド時にやるかデプロイ時にやるか、になるのですが今のところ私の環境ではデプロイ時にやってます。
イメージをビルドする時はassetの事は考えない。
そして、deployをする時にデプロイ前にフックを挟んでコンテナでrakeを動かし、assets:precompile
及びassets:sync
(asset_sync gemを使っている場合)を行う。
しかし、rake実行コンテナと、アプリケーションが起動するコンテナは別々のため、manifestファイルをどこかに保持しておく必要がある。
なので、コンテナ内から環境変数でビルドした時のコミットハッシュを読める様にしておいて、それとRAILS_ENVの組み合わせをキーにしたsuffixを付けてS3上にmanifest.jsonを上げてしまう。
ここまでをrakeで行う。
起動時には、アプリ起動前にフックを仕込んでS3からmanifestファイルをDLしてpublic/assets/manifest.json
に配置する処理を挟む。
(その他にS3上にKMSで暗号化して保持している秘密鍵的なもの等もDLしている)
これで、基本的にはそんなに問題無くassetが扱える。
しかし、一つ厄介な野郎が居る。roadieだ。メールのビューにCSSを当ててくれるgemなのですが、こいつはその性質上、S3に上げてそこ見れば良いや、というわけにはいかない。メール内にインラインでCSSを仕込まねばならない。
幸いにして、roadieにはAssetProvider
という仕組みがありCSSの探索プロセスを差し替えられる様になっている。
roadie-railsのデフォルトはFileSystemProvider
とAssetPipelineProvider
だ。
public/assets
の下を探して、無かったらsprocketsから探すという感じの動きをする。
このFileSystemProvider
の動作を拡張すればいい。
ファイルが無かったらS3上のbucketからファイル取得をしてローカルに保存する処理を挟めばちゃんと動くはずだ。
で、雑に書いたのが以下のS3Provider
だ。
require 'aws-sdk'
require 'fileutils'
require 'zlib'
module Roadie
# Override builtin Provider
class S3Provider < FilesystemProvider
def initialize(path = Dir.pwd, bucket: nil, aws_access_key_id: nil, aws_secret_access_key: nil, region: nil)
super(path)
@bucket = bucket
raise "Need bucket" unless @bucket
@options = {access_key_id: aws_access_key_id, secret_access_key: aws_secret_access_key, region: region}.reject do |_, v|
v.nil?
end
end
# @return [Stylesheet, nil]
def find_stylesheet(name)
file_path = build_file_path(name)
if File.exist? file_path
Stylesheet.new file_path, File.read(file_path)
else
find_stylesheet_from_s3(name, file_path)
end
end
# @raise InsecurePathError
# @return [Stylesheet]
def find_stylesheet!(name)
file_path = build_file_path(name)
if File.exist? file_path
Stylesheet.new file_path, File.read(file_path)
else
stylesheet = find_stylesheet_from_s3(name, file_path)
unless stylesheet
basename = File.basename file_path
raise CssNotFound.new(basename, %{#{file_path} does not exist. (Original name was "#{name}")}, self)
end
stylesheet
end
end
private
def s3_client
@s3_client ||= Aws::S3::Client.new(@options)
end
def find_stylesheet_from_s3(name, file_path)
key = name[0] == "/" ? name[1..-1] : name
obj = s3_client.get_object(bucket: @bucket, key: key)
if obj.content_encoding == "gzip"
body = Zlib::GzipReader.new(s3_client.get_object(bucket: @bucket, key: key).body).read
else
body = s3_client.get_object(bucket: @bucket, key: key).body.read
end
FileUtils.mkdir_p(File.dirname(file_path))
File.open(file_path, "w") { |f| f.write(body) }
Stylesheet.new file_path, body
rescue
@s3_client = nil
nil
end
end
end
一応、無事動いた。良かった。