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.go
のConfig構造体に列挙されている。
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
タグをつけてスキップする。
typoキーの検出
typoキーは、BurntSushi/toml#MetaData.Undecodedを用いて検出できる。
上記の設定ファイルの例では、次のように検出できている。ソートしているのは後の処理のため。
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
を別途調べる必要がある。
候補の判定
agext/levenshteinで、レーベンシュタイン距離を用いて判定する。
Terraformでも使われている。
結果の表示
TOMLの形式がおかしいなど致命的なエラーは赤、typoの警告は黄で表示する。
\x1b[31m *** \x1b[0m
のように文字列で指定するとWindowsでうまく表示できないのでfatih/colorを使用した。