slack のチャンネル名がうまく設定できないのを調査した話

こんにちは、最近開発環境をふっ飛ばした masm11 です。

で、再構築しまして、ふと、開発環境から slack に通知が飛んでないな、ということに 気づいたわけです。

Webhook URL の設定を忘れていたのはよくある笑い話として、 設定しても飛ばないのです。

これはもしや……

おもむろに Rails console を起動します。

$ bin/rails c

> puts Const::Slack.dev_channel

'#alert-dev'

やっぱり。チャンネル名に ' (single quote) が含まれています。

弊社では、dotenv-rails gem を使用して .env.development を読み込み、 その値を Const クラスに設定して使用しています。

.env.development には以下のように指定しています。

slack_dev_channel='#alert-dev'

この ' が値として含まれてしまってるんですね。

「もしや」というのは、以前もあったんです。 その時にはろくに調べず .env.development から ' を削除しました。 私は Vagrant の中で docker-compose を使っているのですが、 開発者によっては Mac で Docker Desktop を使っていて、 そちらでは問題ないそうでした。

ここであったが百年目(?)、今度こそ調べてみました。

Dotenv

とにかくコードを追いかけてみます。

dotenv-rails-2.8.1/lib/dotenv/rails.rb:

    def load
      Dotenv.load(*dotenv_files)
    end

Dotenv.load を呼び出してます。 Dotenv.load は↓これです。

dotenv-2.8.1/lib/dotenv.rb:

  def load(*filenames)
    with(*filenames) do |f|
      ignoring_nonexistent_files do
        env = Environment.new(f, true)
        instrument("dotenv.load", env: env) { env.apply }
      end
    end
  end

Environment.new は何をしているのでしょう?

dotenv-2.8.1/lib/dotenv/environment.rb:

    def initialize(filename, is_load = false)
      @filename = filename
      load(is_load)
    end

load してますね。そのまんまです。

ちなみに env.apply の方はというと、

dotenv-2.8.1/lib/dotenv/environment.rb:

    def apply
      each { |k, v| ENV[k] ||= v }
    end

なるほど、ENV になければセットしています。

つまり、.env.development を読み込んで env に集め、 それを env.apply で ENV に適用しているわけです。

先程の def initialize に戻って、そこから load を呼び出してますね。

    def load(is_load = false)
      update Parser.call(read, is_load)
    end

Parser を呼び出してますね。

そして Parser は dotenv-2.8.1/lib/dotenv/parser.rb にあります。

class Parser を見ると、行の正規表現が定義してあります。 長いので、' に関係するところ周辺のみ引用します。

      (?:\s*=\s*?|:\s+?)    # separator
      (                     # optional value begin
        \s*'(?:\\'|[^'])*'  #   single quoted value    ←←←
        |                   #   or
        \s*"(?:\\"|[^"])*"  #   double quoted value
        |                   #   or
        [^\#\r\n]+          #   unquoted value
      )?                    # value end
      \s*                   # trailing whitespace

「←←←」は私が付けました。ここが ' のある場合の定義です。 ' があって、\' または '以外が0個以上あって、最後に '

あれ? 合ってますね……

ちょっと puts で覗いてみましょうか。 覗く場所は、一番最初に挙げた load メソッドです。

    def load
      puts 'before load'
      puts ENV.inspect
      Dotenv.load(*dotenv_files)
      puts 'after load'
      puts ENV.inspect
    end

結果に驚愕しました。 before load の時点で既に値があります。しかも ' を含んでいます。 上で見た通り、env.apply は、既に ENV に値がある場合はそれを上書きしませんので、 after load でも同じ値です。

コンテナ PID=1 を調べる

さて、環境変数に値をセットしているのは、dotenv-rails より前であることが判りました。 次はどこを調べてみましょうか。

コンテナの PID=1 のプロセスとかどうでしょう?

実行中のプロセスの環境変数は、/proc/PID/environ を使えば調べられることは、 以前の記事に書きました。

blog.ingage.jp

docker-compose exec app /bin/bash

でコンテナに入って、

less /proc/1/environ

して、目を皿のようにして slack_dev_channel を探します。

slack_dev_channel='#alert-dev'

が見つかりました………

コンテナの設定を確認する

さて、コンテナの PID=1 に既に設定されているということは、 設定されている箇所はもっと前です。

docker-compose.yml? いやいや、今探しているのは .env.development です。 .env はそれはそれで存在していて、ちゃんと機能しています。そんなはずは…

しかし、ありました。

    env_file:
      - .env.development

おぅ... これか...

情報を探す

Docker Desktop で大丈夫という情報があるのはあるのですが、 もう docker-compose が何かやっているとしか考えられません。

必死に情報を探します。

ありました!!

https://github.com/docker/compose/issues/8388

docker-compose v1 では ' は普通の文字であること、 それはドキュメントに書いてあること、 docker-compose v2 では ' を quote 文字として扱うように修正されたこと、 ドキュメントに反映されていないこと、 等が書いてあります。

ところで、docker-compose v2 って何でしょう? 私の手元で使っているのは v1 のようです。 最近再構築中にインストールしたものなので、十分新しいと思います。 v2 なんてあるのでしょうか?

docker compose (- なし) のことかもしれませんね。 試しに docker compose を使ったら、 before load の ENV に ' が付いていませんでした。

まとめ

docker compose を使いましょう。