概要
たまにCI/CDでやりたくなるAnsibleのvars上書き芸。
こんな感じの優先順位で上書きされますよね。(下に行くほど優先順位が高い)
command line values (eg “-u user”)
role defaults [1]
inventory file or script group vars [2]
inventory group_vars/all [3]
playbook group_vars/all [3]
inventory group_vars/* [3]
playbook group_vars/* [3]
inventory file or script host vars [2]
inventory host_vars/* [3]
playbook host_vars/* [3]
host facts / cached set_facts [4]
play vars
play vars_prompt
play vars_files
role vars (defined in role/vars/main.yml)
block vars (only for tasks in block)
task vars (only for the task)
include_vars
set_facts / registered vars
role (and include_role) params
include params
extra vars (always win precedence)
参考: Using Variables — Ansible Documentation
例えば… ansible.cfg
で hash_behaviour=merge
としておけば…
roles/hoge_role/defaults/main.yml
とかで下記みたいに変数を定義して…
dict:
a: "aaa"
b: "bbb"
c: "ccc"
hosts/host_group_a/group_vars/all.yml
で下記のように定義すれば。
dict:
a: "override"
こんな結果が得られます。
dict:
a: "override"
b: "bbb"
c: "ccc"
このノリで set_fact
を触ったらハマった。
やりたかったこと
roles/hoge_role/defaults/main.yml
とかで下記みたいに変数を定義。
dict:
a: "aaa"
b: "bbb"
c: "ccc"
hosts/host_group_a/group_vars/all.yml
で下記のように定義した上で…
dict:
a: "override"
更に b
と c
についても上書きする。この上書きは特定の --extra-vars
で指定した変数から値を持ってくる。
なおかつこれらの変数は指定されないこともある…
などというトリッキーなことをしたいとする。
ハマった方法
- name: "Load B"
set_fact:
dict:
b: "{{ B }}"
when: B is defined
- name: "Load C"
set_fact:
dict:
c: "{{ C }}"
when: C is defined
とかってして、 --extra-vars "B=hoge"
とか --extra-vars "C=fuga"
とか --extra-vars "B=hoge C=fuga"
としたかった。
で、 --extra-vars "B=hoge C=fuga"
の時にうまく行かない。こんな感じになるわけですね。
dict:
a: "override"
b: "bbb"
c: "fuga"
本当に必要としていた結果はこれですよね。
dict:
a: "override"
b: "hoge"
c: "fuga"
どうやら、set_factのmargeには癖があるらしい。
どうするのが良いんだろうね
こんな感じ?釈然としない。
- name: "Load B"
set_fact:
dict:
b: "{{ B }}"
when:
- B is defined
- not C is defined
- name: "Load C"
set_fact:
dict:
c: "{{ C }}"
when:
- not B is defined
- C is defined
- name: "Load B and C"
set_fact:
dict:
b: "{{ B }}"
c: "{{ C }}"
when:
- B is defined
- C is defined
追記: 寄せられたもう少しスマートな方法
@hiroyuki_onodera さんよりコメント頂いた方法。
- name: Load B,C
set_fact:
dict:
b: "{{ B|default(omit) }}"
c: "{{ C|default(omit) }}"
なるほど。 default
フィルターってのがあるんですね。こちょこちょいじらない場合はこれがスマートそう。
まとめ
あんまりトリッキーなことはやめておきましょう。
ドキュメント流し読みしかしてないけど、どこかにこの挙動って書いてあったっけ…?