Carpe Diem

備忘録

YAMLのような設定ファイルで環境変数を扱う

背景

christina04.hatenablog.com

ではconfig.yamlを読み込ませてサーバを起動するのですが、その中にSlackのAPIトークンを入れる箇所がありました。

config.yaml自体はConfigMapで渡しているのですが、中に記述されているAPIトークンは環境変数でSecretで管理できないかなぁと思ったのがきっかけです。

環境

  • go 1.18.2

対応方法

os.ExpandEnv()を使うと文字列に環境変数の値を反映できます。
なのでYAMLを読み込んだ後にos.ExpandEnv()を実行すればOKです。

os.ExpandEnvの挙動

以下のように環境変数を文字列に展開することができます。

func main() {
    os.Setenv("ID", "gopher")
    os.Setenv("PASSWORD", "qwertyuiop")

    fmt.Println(os.ExpandEnv("{ID: $ID, PW: ${PASSWORD}}"))
}

https://go.dev/play/p/fs5Xz9IpTcQ

{ID: gopher, PW: qwertyuiop}

環境変数の記法

環境変数の記法としては

  • $VAR
  • ${VAR}

のどちらの記法も対応しています。

YAMLファイルの場合

では実際にyamlファイルを扱うときの書き方です。

.yamlの修正

元々ベタ書きだった箇所を

token: "some_token"
channel: "#general"

以下のように環境変数の記法に書き換えます。

token: ${API_TOKEN}
channel: "#general"

goの実装

yamlファイルを読み込んだあとでos.ExpandEnv()を実行します。

package main

import (
    "flag"
    "fmt"
    "io/ioutil"
    "log"
    "os"

    "gopkg.in/yaml.v2"
)

func main() {
    var fp string
    flag.StringVar(&fp, "c", "./config.yaml", "set yaml file path")
    flag.Parse()

    b, err := ioutil.ReadFile(fp)
    if err != nil {
        log.Fatal(err)
    }
    expaneded := os.ExpandEnv(string(b))  // here
    cfg := Config{}
    if err := yaml.Unmarshal([]byte(expaneded), &cfg); err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%#v\n", cfg)
}

type Config struct {
    Token   string `yaml:"token"`
    Channel string `yaml:"channel"`
}

動作確認

環境変数を渡さないと空のままですが、

$ go run main.go
main.Config{Token:"", Channel:"#general"}

環境変数を設定すると反映されます。

$ API_TOKEN=foobar go run main.go
main.Config{Token:"foobar", Channel:"general"}

その他

docker-composeも対応してる

Environment variables in Compose | Docker Documentation

にあるように環境変数対応しています。

YAMLファイル内で$記号を使いたい時は?

docker-composeでは$$を使えばエスケープできるとありますが、標準パッケージのos.ExpandEnv()はそれをサポートしていません。

回避策としては以下のように別の環境変数の値として$を定義し、置換していく実装があります。

func main() {
    os.Setenv("ID", "gopher")
    os.Setenv("PASSWORD", "qwertyuiop")

    fmt.Println(expandEnv("{ID: $ID, PW: ${PASSWORD}, Money: $$100}"))
}

func expandEnv(s string) string {
    os.Setenv("ESCAPE_DOLLAR", "$")
    return os.ExpandEnv(strings.Replace(s, "$$", "${ESCAPE_DOLLAR}", -1))
}

https://go.dev/play/p/EO3bnWm-Eu9

{ID: gopher, PW: qwertyuiop, Money: $100}

まとめ

yamljsonを使った設定ファイルは多いのでos.ExpandEnv()を追加するだけで環境変数の注入が行えるのは非常に便利ですね。

参考