はじめに
2020/12/12 に、YouTube Live で「つまずき Ansible 【Part21】ansible.utils collection を試す」という配信をしました。
実際に作業しながら(ときには)エラーと戦って進めるシリーズです。
今回は、先日 1.0.0 になった、ansible.utils collection のモジュールを触ってみます。
ネストされた変数の比較やマージに便利そうだったので、興味をそそりました。
同じような機能でも、フィルター、ルックアッププラグイン、モジュールのように複数の利用形態があるものもあります。
やったことをふりかえります。
- 環境
- ansible 2.10.0
- ansible-base 2.10.3
- ansible.utils collection 1.0.1
動画
■ やったこと
各ドキュメントの Example の載ってる例を参考にして、いろいろ試しました。
準備
2.9 でも 2.10 系での標準では含まれていない collection なので、予め以下のコマンドでインストールしておきます。
ansible-galaxy collection install ansible.utils
インストールしてないとこんなメッセージが表示されました。
[WARNING]: an unexpected error occurred during Jinja2 environment setup: unable to locate collection ansible.utils fatal: [localhsot]: FAILED! => {"msg": "template error while templating string: unable to locate collection ansible.utils. String: {{ a|ansible.utils.get_path(path) }}"}
get_path
フィルター
ネストされた変数に対して、ドット区切りのパスを指定することで、そのパスに該当する値を抽出します。
--- - hosts: all gather_facts: false tasks: - ansible.builtin.set_fact: a: b: c: d: - 0 - 1 e: - True - False - name: Retrieve a value deep inside a using a path ansible.builtin.debug: msg: "{{ a | ansible.utils.get_path(path) }}" vars: path: b.c.d[0]
実行結果(抜粋)
TASK [ansible.builtin.set_fact] ************************************************* ok: [localhsot] TASK [Retrieve a value deep inside a using a path] ****************************** ok: [localhsot] => { "changed": false, "msg": "0" }
もしかしたら、
msg: "{{ a.b.c.d[0] }}"
の動きと変わらないので、特に有り難みがないかも知れませんね。。
to_paths
フィルター
ネストされた変数内で、条件にマッチした値のパスを取得します。
--- - hosts: all gather_facts: false tasks: - ansible.builtin.set_fact: a: b: c: d: - 0 - 1 e: - True - False - ansible.builtin.debug: msg: "{{ a|ansible.utils.to_paths(prepend='a') }}"
実行結果(抜粋)
TASK [ansible.builtin.set_fact] *********************************** ok: [localhsot] TASK [ansible.builtin.debug] ************************************** ok: [localhsot] => { "changed": false, "msg": { "a.b.c.d[0]": 0, "a.b.c.d[1]": 1, "a.b.c.e[0]": true, "a.b.c.e[1]": false } }
prepend='a'
を指定すると、a
からのパスの取得になります。
この表示形式は、後述の fact_diff
と組み合わせでも活用できます。
index_of
フィルター
条件にマッチした値のインデックス(0オリジン)を取得します。
--- - hosts: all gather_facts: false tasks: - set_fact: data: - 1 # index 0 - 2 # index 1 - 3 # index 2 - name: Find the index of 2 ansible.builtin.debug: msg: "{{ data | ansible.utils.index_of('eq', 2) }}" # 2 の値 - name: 2 ansible.builtin.debug: msg: "{{ data | ansible.utils.index_of('ge', 2) }}" # 2以上の値
実行結果(抜粋)
TASK [Find the index of 2] ********************************* ok: [localhsot] => { "changed": false, "msg": "1" } TASK [ansible.builtin.debug] ******************************* ok: [localhsot] => { "changed": false, "msg": [ 1, 2 ] }
条件に使用した、eq
や ge
は色々変更できそうです。
あとからツイート拝見したのですが、
条件にマッチするものがなかったら、空のリストが返りそうですね。#tekunabe
— xotaki (@xotaki) 2020年12月12日
気になってたしたらやっぱり空のリストがかえってきました。
TASK [Find the index of 9999] ***** ok: [localhsoaaat] => { "changed": false, "msg": [] }
update_fact
モジュール
ネストされた変数に対して、パスを指定した特定の部分のみを更新できます。
--- - hosts: all gather_facts: false tasks: # Update an exisitng fact, dot or bracket notation - name: Set a fact set_fact: a: b: c: - 1 # -> 10 - 2 # -> 20 - name: Update the fact ansible.utils.update_fact: updates: - path: a.b.c.0 value: 10 - path: "a['b']['c'][1]" value: 20 register: updated - name: debug: msg: "{{ updated }}"
実行結果(抜粋)
TASK [Set a fact] ******************************** ok: [localhsot] TASK [Update the fact] *************************** changed: [localhsot] TASK [debug] ************************************* ok: [localhsot] => { "msg": { "a": { "b": { "c": [ 10, 20 ] } }, "changed": true, "failed": false } }
もとの変数はそのままで、変更後の値は regsiter
で受け取る必要があります。非破壊的な動作ですね。
fact_diff
モジュール
今回一番便利だと思ったものです。2つのネストされた変数で異なる値の部分を diff 表示します。
前述の to_paths
フィルターと組合わると、表示方法も変更できます。
--- - hosts: all gather_facts: false tasks: - set_fact: before: a: b: c: d: - 0 - 1 after: a: b: c: d: - 2 - 3 - name: diff1 ansible.utils.fact_diff: before: "{{ before }}" after: "{{ after }}" - name: diff2 ansible.utils.fact_diff: before: "{{ before | ansible.utils.to_paths }}" after: "{{ after | ansible.utils.to_paths }}"
実行結果(抜粋)
TASK [set_fact] ***************************************** ok: [localhsot] TASK [diff1] ******************************************** --- before +++ after @@ -3,8 +3,8 @@ "b": { "c": { "d": [ - 0, - 1 + 2, + 3 ] } } changed: [localhsot] TASK [diff2] ******************************************** --- before +++ after @@ -1,4 +1,4 @@ { - "a.b.c.d[0]": 0, - "a.b.c.d[1]": 1 + "a.b.c.d[0]": 2, + "a.b.c.d[1]": 3 } changed: [localhsot]
ツイートいただいて、changed
になってることに気が付きました。
fact_diff、こういうやつでchangedになるってのが意外
— tatematsu_san (@tk4_jj) 2020年12月12日
#tekunabe
差分がない場合は、以下のように ok になりました。
PLAY [all] ********************************** TASK [set_fact] ***************************** ok: [localhsot] TASK [diff1] ******************************** ok: [localhsot] TASK [diff2] ******************************** ok: [localhsot]
validate
モジュール
サンプルの準備ができずためせませんでした。
JSON Schema などの定義を利用して妥当性のチェックができるようです。
[2020/12/12 23:20 追記] あとでテストにあるファイル oper_status_up.json や、show_interfaceを参考にて試せました。
環境としては、まず pip install jsonschema
が必要です。
--- - hosts: all gather_facts: false tasks: - name: set facts for data and criteria set_fact: data: "{{ lookup('file', './files/show_interface.json')}}" criteria: "{{ lookup('file', './files/oper_status_up.json') }}" - name: validate data in with jsonschema engine (by passing task vars as configurable plugin options) ansible.utils.validate: data: "{{ data }}" criteria: "{{ criteria }}" engine: ansible.utils.jsonschema vars: ansible_jsonschema_draft: draft7
実行結果: NGケース(抜粋)(見やすさのため callback plugin を yaml に変更)
TASK [validate data in with jsonschema engine (by passing task vars as configurable plugin options)] ****** fatal: [localhost]: FAILED! => changed=false errors: - data_path: GigabitEthernet0/0/0/0.oper_status expected: up found: down json_path: $.GigabitEthernet0/0/0/0.oper_status message: '''down'' does not match ''up''' relative_schema: pattern: up type: string schema_path: patternProperties.^.*.properties.oper_status.pattern validator: pattern msg: |- Validation errors were found. At 'patternProperties.^.*.properties.oper_status.pattern' 'down' does not match 'up'.
上記は、 oper_status_up.json の
// 略 "oper_status": { "type": "string", "pattern": "up" } // 略
といいう定義に対して、show_interface の "GigabitEthernet0/0/0/0"
が "oper_status": "down"
だったため、 validation 失敗しました。
実行結果: OKケース(抜粋)
show_interface の "GigabitEthernet0/0/0/0"
を "oper_status": "up"
に修正したところ以下のように ok となりました。
TASK [set facts for data and criteria] ******************************************************************** ok: [localhost] TASK [validate data in with jsonschema engine (by passing task vars as configurable plugin options)] ****** ok: [localhost]
cli_parse
モジュール
ansbile.netcommon
collectionの ansbile.netcommon.cli_parse
モジュールと同じようにみえますが、こちらにもある経緯などはわかりませせん・・。
[2020/12/14 追記] ansible.utils.cli_parse
モジュールを追加する Pull requesut には、ansible.netcommon
collection から移動、とありました。現状はまだ ansible.netcommon.cli_parse
はあるようですが、そのうちなくなるかもしれません。
ansbile.netcommon.cli_parse
モジュール は Part 15 で触れました。
tekunabe.hatenablog.jp
おわりに
地味なのものが多いですが、変数の操作を標準機能で賄おうとして Playbook が複雑になってしまいそうなときは、この collection を思い出すと良いかなと思いました。
Part22 にむけて
以下のネタを検討中です。気が向いたものをやります。