jc parserをAnsibleで使ってみる。
2021.07.06追記: community.generalコレクションのフィルタのドキュメントがいつのまにか追加されてた -> community.general Filter Guide — Ansible Documentation
jc parserについて
jc parserは、Ansibleで使用できるFilterの一つ。
jcで用意されているparserを指定することで、様々なLinuxコマンドの出力などを自動でparseし、構造化データに変換することができる。
Ansible 2.10だとcommunity.general collectionにFilterとして含まれる。
Community.General Plugin Index
ただ、Ansible DocumentationサイトではFilterのリファレンス(Filterのリスト)が無いため、2021.02.09時点でドキュメントはちょっと見当たらず。。 → 2021.07.06 現在、フィルターガイドというドキュメントが(6月頃に?)新設されていて、community.general コレクションで提供されるフィルタープラグインはここにまとめられている。
ソースはplugins/filter/jc.pyで確認でき、コメントを見ればだいたい使い方はわかるはず。
また、jcそのものについては、jc parser本体のリポジトリに詳細がある
使えるparserの種類
これはjcのGitHubのリポジトリのREADMEのParsersの章を見るのが早い。
ここの一覧はCLIツールとしてのjcで使用するparserのリストになっているが、Ansibleでこれらのparser名をFilterの引数に指定して使う。
簡単な使い方
環境
確認した環境は以下の通り。
- Ansible: 2.10.5
- community.general: 1.3.5
- jc: 1.14.1
準備
jcパッケージが必要なのでpipでインストールしておく。
$ pip install jc
ps
例えばps auxの結果を構造化データに変換したい場合。
- name: jc parser vars: result: "{{ lookup('pipe', 'ps aux') }}" debug: msg: "{{ result | community.general.jc('ps') }}"
この内容のplaybookを実行すると、実行結果は以下の通り。
TASK [jc parser] **************************************************************
ok: [localhost] => {
"msg": [
{
"command": "/usr/lib/systemd/systemd --switched-root --system --deserialize 22",
"cpu_percent": 0.0,
"mem_percent": 0.0,
"pid": 1,
"rss": 6048,
"start": "2020",
"stat": "Ss",
"time": "9:40",
"tty": null,
"user": "root",
"vsz": 194036
},
{
"command": "[kthreadd]",
"cpu_percent": 0.0,
"mem_percent": 0.0,
"pid": 2,
"rss": 0,
"start": "2020",
"stat": "S",
"time": "0:00",
"tty": null,
"user": "root",
"vsz": 0
},
[...]
ちなみに、ps -efの結果でもちゃんとparseされる。
ただし、ps auxf見れる階層情報についてはparse対象外のため、プロセス名(command)の部分に階層表示のための|や\\_が入り込む。
free
- name: jc parser free vars: free: "{{ lookup('pipe', 'free') }}" debug: msg: "{{ free | community.general.jc('free') }}"
TASK [jc parser free] *********************************************************
ok: [localhost] => {
"msg": [
{
"available": 3237832,
"buff_cache": 3034204,
"free": 528604,
"shared": 27256,
"total": 7990060,
"type": "Mem",
"used": 4427252
},
{
"free": 3655420,
"total": 3670012,
"type": "Swap",
"used": 14592
}
]
}
freeは人の目に優しいhuman optionを付けてると(おそらく数値のみを拾ってるっぽくて)parseできない。
(オプション確認してhumanのhだとわかったけど、ずっとhugeのhだと思ってた…)
$ free -h
total used free shared buff/cache available
Mem: 7.6G 4.2G 584M 26M 2.9G 3.2G
Swap: 3.5G 14M 3.5G
こうなる状態で、以下のplaybookを実行。
- name: jc parser free vars: free: "{{ lookup('pipe', 'free -h') }}" debug: msg: "{{ free | community.general.jc('free') }}"
結果は以下の通り。
TASK [jc parser free] *********************************************************
ok: [localhost] => {
"msg": [
{
"available": null,
"buff_cache": null,
"free": null,
"shared": null,
"total": null,
"type": "Mem",
"used": null
},
{
"free": null,
"total": null,
"type": "Swap",
"used": null
}
]
}
他にもiptablesやrouteなど、OSの設定系を構造化データとして取得することで加工・再利用しやすくなると思う。
ssはあるのに、ipは一覧に無さげなのは何故だろう。(なのでTTP使ったparseを試した)
2021.07.03 追記:
「ipは一覧に無さげなのは何故だろう」めっちゃ亀レスなんですが、たぶんなんですけど ip は標準で json 出力できるから対象外にしてるのかなと思います!
— よこち(yokochi) (@akira6592) 2021年7月2日
なるほど、ip -j aをfrom_jsonフィルタに突っ込んだりすれば簡単にオブジェクトに出来るから、jc parserが無くても大丈夫ですね。情報ありがとうございます!
ini
個人的に面白いと思ったのがiniのフィルタ。
[ansible] command = ansible filetype = yaml name = playbook [kubernetes] command = kubectl filetype = yaml name = manifest
こんな形式のテキストデータに対して、
- name: jc parser ini vars: ini: "{{ lookup('file', 'jc-sample.ini') }}" debug: msg: "{{ ini | community.general.jc('ini') }}"
iniを指定してフィルタをかますと、
ok: [localhost] => {
"msg": {
"ansible": {
"command": "ansible",
"filetype": "yaml",
"name": "playbook"
},
"kubernetes": {
"command": "kubectl",
"filetype": "yaml",
"name": "manifest"
}
}
}
このように、セクション名をキーにとしたディクショナリ型に変換してくれる。
kv (key / value)
セクション名の無い単なるkey=value形式のファイルの場合。
command = ansible filetype = yaml name = playbook
kvというparserもあるが、実はiniでもparseできる。
デリミタは=でなく:でもOK
command: ansible filetype: yaml name: playbook
ソース確認するとkvの中身は実はiniをコールしているだけ。
サンプル
まとめ
jc parserに対応している書式のテキストデータを扱う場合は簡単に処理できるのでオススメです。