LoginSignup
32
15

More than 5 years have passed since last update.

Dockerコンテナ上でのassets precompileの扱い 2017

Posted at

基本的に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のデフォルトはFileSystemProviderAssetPipelineProviderだ。
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

一応、無事動いた。良かった。

32
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
15