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に対応している書式のテキストデータを扱う場合は簡単に処理できるのでオススメです。