Ansibleで変数のmergeをするには?
Ansibleでは変数のFilterにJinja2を使用します。
時として辞書を含む変数を綺麗にdeep mergeしたい時ってないですか?
今日はAnsibleで変数のdeep mergeを実現させる方法を2つ紹介します。
何が違うかというと、Ansible 2.0以上 とそれ以下のバージョンでそれぞれ方法が変わってきます。
Ansible version ">=2.0" の場合
Ansible 2.0から新たに combine filter が実装されました。
http://docs.ansible.com/ansible/playbooks_filters.html#combining-hashes-dictionaries
- hosts: localhost
gather_facts: no
vars:
dict:
foo:
bar: 1
dict2:
foo:
baz: 2
qux: 2
# combining hashes/dictionaries (new in version 2.0)
dict_combine: "{{ dict | combine(dict2, recursive=True) }}"
tasks:
- debug:
var: dict_combine
上記を実行すると下記の結果が得られると思います。
$ ansible-playbook -i localhost, test.yml
PLAY [localhost] ***************************************************************
TASK [debug] *******************************************************************
ok: [localhost] => {
"dict_combine": {
"foo": {
"bar": 1,
"baz": 2
},
"qux": 2
}
}
非常に便利ですね。
きちんとdict->fooの中身がmergeされています。
ちなみに「recursive」を省略すると単なる update mergeになります。
$ ansible-playbook -i localhost, test.yml
PLAY [localhost] ***************************************************************
TASK [debug] *******************************************************************
ok: [localhost] => {
"dict_combine": {
"foo": {
"baz": 2
},
"qux": 2
}
}
ちなみにrecursiveを省略したcombine相当の機能はAnsible 2.0以前のバージョンでもupdate()を使うことで下記のように実現することができました。
- hosts: localhost
gather_facts: no
vars:
dict:
foo:
bar: 1
dict2:
foo:
baz: 2
qux: 2
# **[Note]**
# jinja2 'do' tag need expression-statement extension
# please set below to [default] section in ansible.cfg
# jinja2_extensions=jinja2.ext.do
dict_update: |
{% do dict.update(dict2) %}
{{ dict }}
tasks:
- debug:
var: dict_update
Ansible version "<2.0" の場合
色々な理由でAnsible 1.X台を使わざるを得ない場合、combineが使えずに多少不便です。
無論2.0で実装されたcombineのソース部分を取り込めばよいのですが、せっかくなので独自にFilter pluginを作ってみたいと思います。
http://docs.ansible.com/ansible/developing_plugins.html#filter-plugins
pluginを使うにはansible.cfgにパス(filter_plugins=)を通すか、ansible本体のプラグインディレクトリに直接カスタムファイルを置くことでansible実行時に読み込んでくれます。
from copy import deepcopy
def dict_merge(a, b):
if not isinstance(b, dict):
return b
result = deepcopy(a)
for k, v in b.iteritems():
if k in result and isinstance(result[k], dict):
result[k] = dict_merge(result[k], v)
else:
result[k] = deepcopy(v)
return result
class FilterModule(object):
def filters(self):
return {'dict_merge': dict_merge}
これでプラグインの作成は終了です。思ったより簡単ですね。
ではテストしてみます。
- hosts: localhost
gather_facts: no
vars:
dict:
foo:
bar: 1
dict2:
foo:
baz: 2
qux: 2
# custom filter plugin
dict_merged: "{{ dict | dict_merge(dict2) }}"
tasks:
- debug:
var: dict_merged
実行結果は下記の通りです。
$ ansible-playbook -i localhost, test.yml
PLAY [localhost] ***************************************************************
TASK [debug] *******************************************************************
ok: [localhost] => {
"dict_merged": {
"foo": {
"bar": 1,
"baz": 2
},
"qux": 2
}
}
combine同様の結果を得ることが出来ました。
もちろん更に深い階層のdictもmergeしてくれます。
1.X 系でサクッとdeep mergeを使いたい場合はこういう手もあるので、検討されてみてはいかがでしょうか。
その他
Filterプラグインの他にもAnsibleには独自に作ることのできるプラグインがいくつかあります。
http://docs.ansible.com/ansible/developing_plugins.html
Lookupやcallbacksプラグインは比較的情報も多く色んな事をしている方の情報がありますね。
Let's enjoy your Ansible life!