本記事は「Ansible Advent Calendar 2022」の5日目のエントリとなります。
ちょっと前にやらかしたインベントリファイルの行中コメントについて、詳しく調べてみた。
playbookのYAMLに埋め込まれてる変数定義をインベントリファイルに単に移すと動く変数と動かない変数があってプチハマりしたけど、ini形式インベントリに
— z a k i 🌈🌉 (@zaki_hmkc) 2022年11月2日
[all:vars]
foobar = bazqux # コメント
って書かれていると、値は「bazqux # コメント」になるんだなーーーーーーああああーーーーーくっそ
結論から言うと、ini形式インベントリファイルのグループ変数定義部分の行コメントは、行の先頭のみしか認識せず、行中の行コメント文字はコメントとしては認識されない。
ちなみに急いでる人は「動作概要」だけ見ればOKです。
動作概要
[server] host1 #ansible_user=nya # サーバー1 host2 ansible_user=wan # サーバー2 [server:vars] # サーバーの設定 dst_addr=172.29.0.25 # サーバーアドレス dst_port=25 # 接続先ポート
つい書いてしまいがちなこんな記述をすると、上でいうとdst_addr
の値は「172.29.0.25
」ではなく「172.29.0.25 # サーバーアドレス
」になってしまう。インベントリファイル的には書式エラーではないため、Ansibleを実行しこの変数を参照するタスクが実行され(上の変数の用途的に)接続エラーになるまで問題が発覚しづらい。
# サーバーの設定
と書かれているコメント行はコメントとして有効。
なお、ホストの定義部分であれば、ホスト変数部分である「キー=値」の書式のparse処理が別途あるため、その際にコメント処理される。
前述の内容であれば、host2
の「ansible_user=wan
」は有効で、「host1
のansible_user=nya
」は無視される。
インベントリ変数がどう認識されるか確認するには
ansible-inventory
コマンドを使うと簡単。
ansible-inventory -i <インベントリファイル> --list --vars
ini形式インベントリファイルの読み込み実装を調査
ここから本編(?)です。
ini形式のインベントリファイルの変数部分をparseしているのは以下の_parse_variable_definition()
メソッド内。(例によってprintデバッグで発掘)
lib/ansible/plugins/inventory/ini.pyのL.280-L.282
if '=' in line: (k, v) = [e.strip() for e in line.split("=", 1)] return (k, self._parse_value(v))
ここの処理で、キーバリューの記述の=
をsplit
しているが、特に行末のコメント処理は行っていない。
return
で呼び出している_parse_value()
についても、リストや辞書形式の記述をオブジェクト化するのみでコメント処理は行っていない。
ではこの_parse_variable_definition()
の呼び出し元はというと、同じクラスの_parse()
メソッドから。
このメソッドは、以下のようにインベントリファイルの「コメント(;
か#
)で始まる行」を除いた箇所を行ごと処理している。strip()
は、行頭と行末のスペースを除いている。
lib/ansible/plugins/inventory/ini.pyのL.162-L.165
line = line.strip() # Skip empty lines and comments if not line or line[0] in self._COMMENT_MARKERS: continue
で、処理対象の行の定義がhost
かchildren
かvar
部分かを判断しながら処理しており、変数の場合の処理で前述の_parse_variable_definition()
を呼び出し、変数セットをインスタンスに保持している。
lib/ansible/plugins/inventory/ini.pyのL.220-L.222
elif state == 'vars': (k, v) = self._parse_variable_definition(line) self.inventory.set_variable(groupname, k, v)
なお、使用可能なコメント文字は、以下の通り;
と#
の2文字。どちらも同様に動作する。
lib/ansible/plugins/inventory/ini.pyのL.93
_COMMENT_MARKERS = frozenset((u';', u'#'))
ということで、変数部分のコメント処理は、行頭の場合のみが有効だと実装からも確認できる。
iniファイルを処理するその他の実装
WindowsのGetPrivateProfileString()
WindowsではAPI関数のGetPrivateProfileString()
を使ってiniファイルを読み込める。
作ったアプリはこちら。
[read]押下でiniから設定値を読み込み、[write]押下でフィールド入力中のテキストをiniへ書き込む、というもの。
このアプリを使って、コメント(;
)を行中にいれて設定文字列をiniファイルへ書き込んで、
[read]押下するとこの通り、コメント以降も含めて読み込まれる。
コメント文字を#
にしても動作は同じ。
分かりづらかったのでCLIで
で、わざわざGUIアプリを作ってみたけど、ぶっちゃけ文章と図で状態の変化を見せるのには向いてないことに気付いた(笑)ので、ソース修正してGUI無しのmain()
のみのCLI版も実装。
動作概要はこの通りで、実行時に引数があればiniへ書き込み、なければiniから読み込む、というもの。
zaki@wensley% ./cli.exe foobar zaki@wensley% ./cli.exe ini string: [foobar] zaki@wensley% cat config.ini [Section] config=foobar
行中コメントを書き込んでみると、以下のようにコメント文字以降も設定値として有効な文字列として読み込まれる。
zaki@wensley% ./cli.exe "foobar ; comment" zaki@wensley% ./cli.exe ini string: [foobar ; comment] zaki@wensley% cat config.ini [Section] config=foobar ; comment zaki@wensley% zaki@wensley% ./cli.exe "foobar # comment" zaki@wensley% cat config.ini [Section] config=foobar # comment zaki@wensley% ./cli.exe ini string: [foobar # comment]
やはり作業とログはCLIに限るな。
PythonのConfigParser
Pythonでは標準で使えるini形式の設定ファイルを扱うConfigParserクラスがある。
(Ansibleでもこれ使ってるのかと思ってたけど、自前で実装しているのね)
このクラスを使ったiniファイル操作も、Windowsと同様にデフォルトでは行中のコメントは認識されない。
import configparser ini = configparser.ConfigParser() ini.read('sample.ini') interpreter = ini.get('defaults', 'interpreter_python') ssh_config = ini.get('ssh_connection', 'ssh_args') print(interpreter) print(ssh_config)
こんなソースに対して、下記のようにansible.cfg
っぽい内容のdefaults
セクションのinterpreter_python
と、ssh_connection
セクションのssh_args
が定義された設定ファイルを読み込むと、これまでの例と同様にコメントを含めて読み込まれる。
(master %>) [zaki@cloud-dev2 configparser]$ cat sample.ini [defaults] stdout_callback = yaml interpreter_python = /usr/bin/python3 # ver3 [ssh_connection] ssh_args = -o UserKnownHostsFile=/dev/null ; ssh (master %>) [zaki@cloud-dev2 configparser]$ (master %>) [zaki@cloud-dev2 configparser]$ (master %>) [zaki@cloud-dev2 configparser]$ python sample.py /usr/bin/python3 # ver3 -o UserKnownHostsFile=/dev/null ; ssh
ただしConfigParserクラスにはinline_comment_prefixes
オプションが用意されており、以下のようにコメント文字を指定することで行中のコメントを有効にする(コメントとして処理を無視させる)ことが可能。
configparser.ConfigParser(inline_comment_prefixes=(';', '#'))
インスタンス生成時にオプション指定しておくと、行中コメントを含むiniファイルの扱いは以下の通りで、行中コメントは無視される。
(master %>) [zaki@cloud-dev2 configparser]$ python sample.py /usr/bin/python3 -o UserKnownHostsFile=/dev/null
余談だが古いコード(Python2系)でインスタンス生成時にSafeConfigParser
を使ってると、3.12で削除される警告が表示されるので要修正。
DeprecationWarning: The SafeConfigParser class has been renamed to ConfigParser in Python 3.2. This alias will be removed in Python 3.12. Use ConfigParser directly instead.
環境
確認に使ったCのソースコードはこちら
まとめ
ということで、Ansibleのini形式インベントリファイルに限らずiniファイルのコメントは、行頭以外には使わないようにしましょう。