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を使用した。
![[改訂新版]プログラマのための文字コード技術入門 (WEB+DB PRESS plusシリーズ) [改訂新版]プログラマのための文字コード技術入門 (WEB+DB PRESS plusシリーズ)](https://m.media-amazon.com/images/I/51vqn-2eVKL._SL500_.jpg)















