経験は何よりも饒舌

10年後に真価を発揮するかもしれないブログ 

mackerel-agent configtest によるチェック強化の実装について

Mackerel Advent Calendar 2022 15日目です。

mackerel-agent configtestによるチェック機能を強化したので実装を軽く紹介します。
github.com
mackerel.io

何が変わったのか

次のようなmackerel-agent-sample.confを作成し、mackerel-agent configtestを実行する。

apikey = "abcdefg"
podfile = "/path/to/pidfile"

[foo]
command = "bar"
 
[plugin.checks.incorrect1]
command = "test command"
action = { command = "test command", user = "test user", en = { TEST_KEY = "VALUE_1" } }

[plugins.checks.incorrect2]
command = "test command"

[plugin.check.incorrect3]
command = "test command"

強化前は、Syntax OKとなる。

$ mackerel-agent configtest -conf /usr/local/etc/mackerel-agent-sample.conf
/usr/local/etc/mackerel-agent-sample.conf Syntax OK

強化後は、各設定項目typoで無効になっていたら警告を出してくれる。

$ mackerel-agent configtest -conf /usr/local/etc/mackerel-agent-sample.conf
[WARNING] foo is unexpected key. Did you mean root ?
[WARNING] plugin.check.incorrect3 is unexpected key. Did you mean plugin.checks.incorrect3 ?
[WARNING] plugin.checks.incorrect1.action.en is unexpected key. Did you mean plugin.checks.incorrect1.action.env ?
[WARNING] plugins is unexpected key. Did you mean plugin ?
[WARNING] podfile is unexpected key. Did you mean pidfile ?

実装

候補の列挙

候補とは、Did you mean ******のことで、各設定項目に該当する。
コードではconfig/config.goConfig構造体に列挙されている。

type Config struct {
	Apibase       string
        ...
        DisplayName   string        `toml:"display_name"`
        ...
        HostStatus    HostStatus    `toml:"host_status" conf:"parent"`
        ...
        HostIDStorage   HostIDStorage   `conf:"ignore"`
        ...
}
  • Apibaseのように、タグがついていない場合、フィールド名の先頭を小文字にして追加する。
  • DisplayNameのように、tomlタグがついている場合、その値を追加する。
  • HostStatusのように、フィールドの型が構造体の場合、parentタグをつけて候補を再帰的に取得する(ここだとon_start, on_stop)。
  • HostIDStorageのように、設定ファイルに書き込まれることがない場合、ignoreタグをつけてスキップする。

実装: mackerel-agent/validate.go at 5a2668daac02a5e2a406d22ddd23af6cca32e7be · mackerelio/mackerel-agent · GitHub

typoキーの検出

typoキーは、BurntSushi/toml#MetaData.Undecodedを用いて検出できる。

上記の設定ファイルの例では、次のように検出できている。ソートしているのは後の処理のため。

mackerel-agent/validate.go at 5a2668daac02a5e2a406d22ddd23af6cca32e7be · mackerelio/mackerel-agent · GitHub

func ValidateConfigFile(file string) ([]UnexpectedKey, error) {
    config := &Config{}
    md, err := toml.DecodeFile(file, config)
    ...
    undecodedKeys := md.Undecoded()
    sort.Slice(undecodedKeys, func(i, j int) bool {
	return undecodedKeys[i].String() < undecodedKeys[j].String()
    })

    // [foo foo.command
    //  plugin.checks.incorrect1.action.en plugin.checks.incorrect1.action.en.TEST_KEY
    //  plugins.checks.incorrect2 plugins.checks.incorrect2.command
    //  podfile]
    fmt.Printf("%+v", undecodedKeys)
typoキーの判定
  • fooの場合、fooは候補として列挙されていないので、typoキーはfoo
  • plugin.checks.incorrect1.action.enの場合、pluginが候補として列挙されているので、typoキーはen
  • plugins.checks.incorrect2の場合、pluginsは候補として列挙されていないので、typoキーはplugins
  • podfileの場合、podfileは候補として列挙されていないので、typoキーはpodfile
  • foo.commandは、fooが既に判定されているのでスキップ。
  • plugin.checks.incorrect1.action.en.TEST_KEYは、plugin.checks.incorrect1.action.enが既に判定されているのでスキップ。
  • plugins.checks.incorrect2.commandは、plugins.checks.incorrect2が既に判定されているのでスキップ。

スキップしないとContents of suggestion when executing configtest may not be correct · Issue #829 · mackerelio/mackerel-agent · GitHubのような不具合が生じてしまう。

実装: mackerel-agent/validate.go at 5a2668daac02a5e2a406d22ddd23af6cca32e7be · mackerelio/mackerel-agent · GitHub
Config構造体にparentタグがあるかで判定するより、候補として列挙されているかどうかで判定する方が良かったというPR:
refactor(config/validate): use `candidates` instead of `parentConfKeys` by wafuwafu13 · Pull Request #839 · mackerelio/mackerel-agent · GitHub

plugin.typo.fooの検出

プラグインの設定項目は、checks, metrics, metadataでないといけないが、Config構造体ではstringであれば良いので、MetaData.Undecodedでは検出できない。
上記の設定ファイルの例だと、plugin.check.incorrect3が検出されていない。

type Config struct {
   ...
   Plugin map[string]map[string]*PluginConfig `conf:"parent"`
   ....
}

そのため、config.Pluginを別途調べる必要がある。

実装: mackerel-agent/validate.go at 5a2668daac02a5e2a406d22ddd23af6cca32e7be · mackerelio/mackerel-agent · GitHub

結果の表示

TOMLの形式がおかしいなど致命的なエラーは赤、typoの警告は黄で表示する。
\x1b[31m *** \x1b[0mのように文字列で指定するとWindowsでうまく表示できないのでfatih/colorを使用した。

展望

今はtypoキーをソートして表示しているけど、余裕があれば設定ファイルの行数を取得してその順番通りにtypoの行数も表示したい。