数据操作

在很多情况下,您需要对变量执行复杂的操作。虽然不建议将 Ansible 作为数据处理/操作工具,但您可以结合现有的 Jinja2 模板以及许多新增的 Ansible 过滤器、查找和测试来执行一些非常复杂的转换。

让我们从快速定义每种类型的插件开始
  • 查找:主要用于查询“外部数据”,在 Ansible 中,这些是使用 with_<lookup> 结构进行循环的主要部分,但它们可以独立使用以返回数据进行处理。由于前面提到的在循环中的主要功能,它们通常返回列表。与 lookupquery Jinja2 运算符一起使用。

  • 过滤器:用于更改/转换数据,与 | Jinja2 运算符一起使用。

  • 测试:用于验证数据,与 is Jinja2 运算符一起使用。

循环和列表推导式

大多数编程语言都有循环(forwhile 等)和列表推导式来对列表(包括对象列表)进行转换。Jinja2 有几个提供此功能的过滤器:mapselectrejectselectattrrejectattr

  • map:这是一个基本的 for 循环,它只允许您使用“attribute”关键字更改列表中的每个项目,您可以根据列表元素的属性进行转换。

  • select/reject:这是一个带有条件的 for 循环,它允许您创建列表的子集,该子集基于条件的结果进行匹配(或不匹配)。

  • selectattr/rejectattr:与上述非常相似,但它使用列表元素的特定属性作为条件语句。

使用循环创建指数退避。

- name: try wait_for_connection up to 10 times with exponential delay
  ansible.builtin.wait_for_connection:
    delay: '{{ item | int }}'
    timeout: 1
  loop: '{{ range(1, 11) | map("pow", 2) }}'
  loop_control:
    extended: true
  ignore_errors: "{{ not ansible_loop.last }}"
  register: result
  when: result is not defined or result is failed

从字典中提取与列表元素匹配的键

Python 等效代码为

chains = [1, 2]
for chain in chains:
    for config in chains_config[chain]['configs']:
        print(config['type'])

在 Ansible 中有几种方法可以做到这一点,这只是一个例子

从字典列表中提取匹配键的方法
 tasks:
   - name: Show extracted list of keys from a list of dictionaries
     ansible.builtin.debug:
       msg: "{{ chains | map('extract', chains_config) | map(attribute='configs') | flatten | map(attribute='type') | flatten }}"
     vars:
       chains: [1, 2]
       chains_config:
           1:
               foo: bar
               configs:
                   - type: routed
                     version: 0.1
                   - type: bridged
                     version: 0.2
           2:
               foo: baz
               configs:
                   - type: routed
                     version: 1.0
                   - type: bridged
                     version: 1.1
调试任务的结果,一个包含提取键的列表
   ok: [localhost] => {
       "msg": [
           "routed",
           "bridged",
           "routed",
           "bridged"
       ]
   }
获取每个主机上不同的变量的唯一值列表
   vars:
       unique_value_list: "{{ groups['all'] | map ('extract', hostvars, 'varname') | list | unique}}"

查找挂载点

在这种情况下,我们希望找到给定路径在我们的机器上的挂载点,因为我们已经收集了挂载事实,所以我们可以使用以下方法

使用 selectattr 将挂载点筛选到列表中,然后我可以对其进行排序并选择最后一个
  - hosts: all
    gather_facts: True
    vars:
       path: /var/lib/cache
    tasks:
    - name: The mount point for {{path}}, found using the Ansible mount facts, [-1] is the same as the 'last' filter
      ansible.builtin.debug:
       msg: "{{(ansible_facts.mounts | selectattr('mount', 'in', path) | list | sort(attribute='mount'))[-1]['mount']}}"

从列表中省略元素

特殊的 omit 变量仅适用于模块选项,但我们仍然可以以其他方式将其用作标识符来定制元素列表

在提供模块选项时进行内联列表筛选
   - name: Enable a list of Windows features, by name
     ansible.builtin.set_fact:
       win_feature_list: "{{ namestuff | reject('equalto', omit) | list }}"
     vars:
       namestuff:
         - "{{ (fs_installed_smb_v1 | default(False)) | ternary(omit, 'FS-SMB1') }}"
         - "foo"
         - "bar"

另一种方法是在一开始就避免将元素添加到列表中,因此您可以直接使用它

在循环中使用 set_fact 有条件地递增列表
   - name: Build unique list with some items conditionally omitted
     ansible.builtin.set_fact:
        namestuff: ' {{ (namestuff | default([])) | union([item]) }}'
     when: item != omit
     loop:
         - "{{ (fs_installed_smb_v1 | default(False)) | ternary(omit, 'FS-SMB1') }}"
         - "foo"
         - "bar"

合并来自相同字典列表的值

结合上面示例中的正向和负向过滤器,您可以获得“存在时值”和“不存在时回退”。

使用 selectattr 和 rejectattr 根据需要获取 ansible_host 或 inventory_hostname
   - hosts: localhost
     tasks:
       - name: Check hosts in inventory that respond to ssh port
         wait_for:
           host: "{{ item }}"
           port: 22
         loop: '{{ has_ah + no_ah }}'
         vars:
           has_ah: '{{ hostvars|dictsort|selectattr("1.ansible_host", "defined")|map(attribute="1.ansible_host")|list }}'
           no_ah: '{{ hostvars|dictsort|rejectattr("1.ansible_host", "defined")|map(attribute="0")|list }}'

基于变量的自定义文件通配符

此示例使用 Python 参数列表解包 基于变量创建自定义文件通配符列表。

使用基于变量的列表进行文件通配符。
  - hosts: all
    vars:
      mygroups:
        - prod
        - web
    tasks:
      - name: Copy a glob of files based on a list of groups
        copy:
          src: "{{ item }}"
          dest: "/tmp/{{ item }}"
        loop: '{{ q("fileglob", *globlist) }}'
        vars:
          globlist: '{{ mygroups | map("regex_replace", "^(.*)$", "files/\1/*.conf") | list }}'

复杂类型转换

Jinja 提供了用于简单数据类型转换的过滤器(intbool 等),但是当您想要转换数据结构时,事情就不那么容易了。您可以使用上面所示的循环和列表推导式来帮助您,还可以将其他过滤器和查找链接起来以实现更复杂的转换。

从列表创建字典

在大多数语言中,从键值对列表创建字典(也称为映射/关联数组/哈希等)很容易。在 Ansible 中,有几种方法可以做到这一点,最适合您的方法可能取决于您的数据来源。

此示例生成 {"a": "b", "c": "d"}

通过假设列表为 [键,值,键,值,…] 来实现简单的列表到字典的转换
 vars:
     single_list: [ 'a', 'b', 'c', 'd' ]
     mydict: "{{ dict(single_list[::2] | zip_longest(single_list[1::2])) }}"
当我们有键值对列表时,它更简单:
 vars:
     list_of_pairs: [ ['a', 'b'], ['c', 'd'] ]
     mydict: "{{ dict(list_of_pairs) }}"

两者最终都是相同的东西,zip_longestsingle_list 转换为 list_of_pairs 生成器。

稍微复杂一些,使用 set_factloop 从两个列表创建/更新具有键值对的字典

使用 set_fact 从一组列表创建字典
    - name: Uses 'combine' to update the dictionary and 'zip' to make pairs of both lists
      ansible.builtin.set_fact:
        mydict: "{{ mydict | default({}) | combine({item[0]: item[1]}) }}"
      loop: "{{ (keys | zip(values)) | list }}"
      vars:
        keys:
          - foo
          - var
          - bar
        values:
          - a
          - b
          - c

这将生成 {"foo": "a", "var": "b", "bar": "c"}

您甚至可以将这些简单的示例与其他过滤器和查找结合起来,通过将模式与变量名称匹配来动态创建字典

使用“vars”从一组列表定义字典,而无需任务
   vars:
       xyz_stuff: 1234
       xyz_morestuff: 567
       myvarnames: "{{ q('varnames', '^xyz_') }}"
       mydict: "{{ dict(myvarnames|map('regex_replace', '^xyz_', '')|list | zip(q('vars', *myvarnames))) }}"

快速解释一下,因为这两行代码有很多内容需要解释

  • varnames 查找返回与“以 xyz_ 开头”匹配的变量列表。

  • 然后将上一步中的列表提供给 vars 查找以获取值列表。* 用于“取消引用列表”(python 中的一种用法,在 Jinja 中有效),否则它会将列表作为单个参数。

  • 这两个列表都传递给 zip 过滤器,将其配对成一个统一的列表(键,值,键2,值2,…)。

  • 然后,dict 函数采用此“键值对列表”来创建字典。

一个使用事实查找满足条件 X 的主机数据的示例

vars:
  uptime_of_host_most_recently_rebooted: "{{ansible_play_hosts_all | map('extract', hostvars, 'ansible_uptime_seconds') | sort | first}}"

一个显示主机正常运行时间(以天/小时/分钟/秒为单位)的示例(假设已收集事实)。

- name: Show the uptime in days/hours/minutes/seconds
  ansible.builtin.debug:
   msg: Uptime {{ now().replace(microsecond=0) - now().fromtimestamp(now(fmt='%s') | int - ansible_uptime_seconds) }}

另请参阅

使用过滤器操作数据

Ansible 内置的 Jinja2 过滤器

测试

Ansible 内置的 Jinja2 测试

Jinja2 文档

Jinja2 文档,包括核心过滤器和测试的列表