[小提示] 有两种方法可以正确地在Ansible中深度合并嵌套字典变量

在Ansible中如何合并变量?

在Ansible中,可以使用Jinja2作为变量过滤器。

有时候您是否希望将包含字典的变量进行漂亮的深度合并呢?
今天我将介绍两种在Ansible中实现变量深度合并的方法。它们之间的区别是,对于Ansible 2.0以上和更低版本,方法各有不同。

如果Ansible版本大于等于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的内容已经完整地合并了。
顺便说一下,如果省略了“recursive”,则只会进行简单的更新合并。

$ ansible-playbook -i localhost, test.yml

PLAY [localhost] ***************************************************************

TASK [debug] *******************************************************************
ok: [localhost] => {
    "dict_combine": {
        "foo": {
            "baz": 2
        },
        "qux": 2
    }
}

顺便提一下,在Ansible 2.0以前的版本中,可以通过使用update()来实现类似于省略了递归的combine功能,如下所示。

- 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版本”<2.0″的情况

如果由于各种原因不得不使用Ansible 1.X版本,那么无法使用combine会有些不便。
当然,可以通过引入2.0版本中实现的combine源代码部分来解决,但我想趁机自己尝试开发一个独立的Filter插件。
http://docs.ansible.com/ansible/developing_plugins.html#filter-plugins

要使用插件,需要通過在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函数,我们可以获得类似的结果。
当然,它还可以帮助我们合并更深层次的字典。
如果您想在X系统中快速使用深度合并,不妨考虑一下这种方法。

其他

除了Filter插件外,Ansible还有一些可以自定义的插件。请参考http://docs.ansible.com/ansible/developing_plugins.html。

查找和回调插件是相对而言非常丰富且具有多种功能的插件。

让我们享受你的Ansible生活吧!

广告
将在 10 秒后关闭
bannerAds