Ansible Filter滤波器的使用(一)

一、【说在前面】

Ansible Filter一般被称为滤波器或者叫过滤器。

这个东西初次听到以为是什么科学计算的东西,但是想来ansible不太可能有什么滤波操作,所以这个东西本质是一个数值筛选器,内置函数,本质是一个为了做区别化的工具,比如根据不同的机器名做不同操作,根据预先设定的值做区别化对待。

这篇文章介绍一下ansibe常用的过滤器是怎么用的,有什么作用,官网文档更详细,但看起来例子比较单薄,本文本质是笔者回顾Ansible的学习笔记,希望这篇文章能发挥一些补充作用。

官网:Using filters to manipulate data — Ansible Documentation

二、【常用过滤器介绍】

1. 默认过滤器 (default filter):

这个过滤器用于为变量指定默认值,或者叫缺省值,这种方法可以为剧本添加某个默认动作。这里以debug打印数值为例。

---
- hosts: localhost
  gather_facts: no
  vars:
    # 模拟一个有时有值,有时没有值的变量
    my_variable_present: "I have a value"
    my_variable_absent: null  # 或者可以将这个变量设置为 ""

  tasks:
    - name: Set default value if variable is absent
      set_fact:
        # 使用 default 过滤器,如果变量不存在或者为空,将其设置为默认值 "Default Value"
        my_variable_present: "{{ my_variable_present | default('Default Value') }}"
        my_variable_absent: "{{ my_variable_absent | default('Default Value') }}"

    - debug:
        var: my_variable_present
    - debug:
        var: my_variable_absent

2. 省略参数 (omit parameter):

这个功能结合默认过滤器用于省略模块参数。

---
# 定义主机为 localhost,这表示剧本将在本地主机上执行。
- hosts: localhost

  # 不收集主机的事实信息,因为此剧本只操作变量而不需要主机信息。
  gather_facts: no

  # 定义变量部分,这里创建了两个变量,一个有值,一个没有值。
  vars:
    file_path_present: "/path/to/existing/file.txt"
    file_path_absent: null  # 或者可以将这个变量设置为 ""

  # 任务部分,包含两个任务。
  tasks:

    # 第一个任务名称,用于创建一个文件,如果变量 file_path_present 存在。
    - name: Create a file if path is provided
      file:
        # 使用 default 过滤器和 omit 变量,以便在 file_path_present 存在时,设置 path 参数。
        path: "{{ file_path_present | default(omit) }}"
        state: touch
      when: file_path_present is defined

    # 第二个任务名称,用于创建一个文件,如果变量 file_path_absent 不存在。
    - name: Create a file with omitted path
      file:
        # 使用 default 过滤器和 omit 变量,以便在 file_path_absent 不存在时,省略 path 参数。
        path: "{{ file_path_absent | default(omit) }}"
        state: touch
      when: file_path_absent | default(omit) is omit

3. 集合或列表过滤器 (set theory or list filters):

这些过滤器用于操作列表变量。有常见的什么集合、去重操作之类。

---
- hosts: localhost
  gather_facts: no
  vars:
    list1: [1, 2, 5, 1, 3, 4, 10]
    list2: [1, 2, 3, 4, 5, 11, 99]
  tasks:
    - name: Get a unique set from list1
      debug:
        var: list1 | unique  # 做一个unique操作,去重用
    - name: Get the union of list1 and list2
      debug:
        var: list1 | union(list2) # 这是做list1和list2的并集
# list1: [1, 2, 5, 1, 3, 4, 10]
# list2: [1, 2, 3, 4, 5, 11, 99]
# => [1, 2, 5, 1, 3, 4, 10, 11, 99]


    - name: Get the intersection of list1 and list2
      debug:
        var: list1 | intersect(list2) # 求一个交集
# list1: [1, 2, 5, 3, 4, 10]
# list2: [1, 2, 3, 4, 5, 11, 99]
# => [1, 2, 5, 3, 4]


    - name: Get the difference of list1 and list2
      debug:
        var: list1 | difference(list2) # 这是拿出list1中有,list2没有的值,也即差集
# list1: [1, 2, 5, 1, 3, 4, 10]
# list2: [1, 2, 3, 4, 5, 11, 99]
# => [10]

    - name: Get the symmetric difference of list1 and list2
      debug:
        var: list1 | symmetric_difference(list2) # 对称差集,没有同时存在的值
# list1: [1, 2, 5, 1, 3, 4, 10]
# list2: [1, 2, 3, 4, 5, 11, 99]
# => [10, 11, 99]

4. 三元表达式(ternary): 

本质是一个快速判断的工具,用于快速判断一个表达式,用法是:

{{ condition | ternary(true_value, false_value) }}

---
- hosts: localhost
  gather_facts: no
  vars:
    status: "needs_restart"
    enabled: true
    some_value: null

  tasks:

    # 第一个任务:检查状态并确定动作
    - name: Check the status and determine action
      debug:
        msg: |
          # 打印状态值和相应的动作
          Status is "{{ status }}". Action: {{
          (status == 'needs_restart') | ternary('Restart', 'Continue') }}

    # 第二个任务:检查是否已启用并配置
    - name: Check if enabled and configure
      debug:
        msg: |
          # 打印是否已启用和相应的配置
          Enabled: "{{ enabled }}". Configuration: {{
          enabled | ternary('No Shutdown', 'Shutdown') }}

    # 第三个任务:检查一个值并处理空值
    - name: Check a value and handle null
      debug:
        msg: |
          # 打印某个值和根据是否为null选择的结果
          Some Value: "{{ some_value }}". Result: {{
          some_value | ternary('Not Null', 'Null') }}

5. 发现数据类型(Discovering the data type)

这是 Ansible 2.3 版本中引入的功能。如果您不确定一个变量的底层 Python 数据类型是什么,您可以使用 ansible.builtin.type_debug 过滤器来显示它。这在调试过程中非常有用

---
- hosts: localhost
  gather_facts: no
  vars:
    # 定义一个变量,包含不同数据类型的值
    myvar_string: "This is a string"
    myvar_integer: 42
    myvar_list: [1, 2, 3]
    myvar_dict: {"key": "value"}
  
  tasks:
    - name: Display the data type of variables
      debug:
        # 使用 type_debug 过滤器显示变量的数据类型
        msg: "myvar_string: {{ myvar_string | type_debug }}, myvar_integer: {{ myvar_integer | type_debug }}, myvar_list: {{ myvar_list | type_debug }}, myvar_dict: {{ myvar_dict | type_debug }}"

6. 字典过滤器 (dictionary filters):

包括 dict2items 过滤器,它将字典转换为项目列表,以及 items2dict 过滤器,反之亦然。

---
- hosts: localhost
  gather_facts: no
  vars:
    # 定义一个字典变量
    my_dict:
      users: /etc/passwd
      groups: /etc/group
  
  tasks:
    - name: Transform dictionary into a list of items
      debug:
        # 使用 dict2items 过滤器将字典转换为项列表
        msg: "{{ my_dict | dict2items }}"
# 会把这个字典平铺成一个列表。会变成
# - file: users
#   path: /etc/passwd
# - file: groups
#   path: /etc/group

7. 生成yaml或json(Formatting data: YAML and JSON):

这个过滤器一共就是六个语句:to_yaml, to_json, to_nice_yaml, to_nice_json;以及from_json, from_yaml

nice不nice取决于可读性的好坏,并且这四种都可以设置每行缩进和每行长度限制

---
- hosts: localhost
  gather_facts: no
  vars:
    # 定义一个字典变量
    my_dict:
      name: "John"
      age: 30
      city: "New York"
      hobbies:
        - Reading
        - Hiking
        - Cooking
  
  tasks:

    # 第一个任务:将字典转换为JSON格式并显示
    - name: Convert to JSON format
      debug:
        msg: |
          # 使用 to_json 过滤器将 my_dict 转换为JSON格式的字符串
          JSON Format:
          {{ my_dict | to_json(indent=8, width=1337) }} # 设置每行缩进8,宽度限制1337
    
    # 第二个任务:将字典转换为YAML格式并显示
    - name: Convert to YAML format
      debug:
        msg: |
          # 使用 to_yaml 过滤器将 my_dict 转换为YAML格式的字符串
          YAML Format:
          {{ my_dict | to_yaml }}
    
    # 第三个任务:将字典转换为格式化良好的JSON格式并显示
    - name: Convert to Nice JSON format
      debug:
        msg: |
          # 使用 to_nice_json 过滤器将 my_dict 转换为格式化良好的JSON格式的字符串
          Nice JSON Format:
          {{ my_dict | to_nice_json }}
    
    # 第四个任务:将字典转换为格式化良好的YAML格式并显示
    - name: Convert to Nice YAML format
      debug:
        msg: |
          # 使用 to_nice_yaml 过滤器将 my_dict 转换为格式化良好的YAML格式的字符串
          Nice YAML Format:
          {{ my_dict | to_nice_yaml }}

# 在写一个使用from_json的例子:
tasks:
  - name: Register JSON output as a variable
    ansible.builtin.shell: cat /some/path/to/file.json
    register: result

  - name: Set a variable
    ansible.builtin.set_fact:
      myvar: "{{ result.stdout | from_json }}"

8. Zip 和 Zip_Longest 过滤器(Combining items from multiple lists: zip and zip_longest):

这些过滤器用于合并多个列表的元素,可以选择填充间隙,跟python内置方法zip差不多。

---
- hosts: localhost
  gather_facts: no
  vars:
    # 定义多个列表
    list1: [1, 2, 3, 4, 5]
    list2: ['a', 'b', 'c', 'd']
    list3: ['x', 'y']

  tasks:

    # 使用 zip 过滤器将两个列表组合成一个新列表
    - name: Combine two lists using zip
      debug:
        msg: |
          # 使用 zip 过滤器将 list1 和 list2 组合成新列表,生成长度与短的列表一致
          Combined List:
          {{ list1 | zip(list2) | list }}
          # [[1, "a"], [2, "b"], [3, "c"], [4, "d"]]
    
    # 使用 zip_longest 过滤器将三个列表组合成一个新列表,并用 'X' 填充不足的元素
    - name: Combine three lists using zip_longest
      debug:
        msg: |
          # 使用 zip_longest 过滤器将 list1、list2 和 list3 组合成新列表,用 'X' 填充不足的元素
          Combined List with Fillvalue 'X':
          {{ list1 | zip_longest(list2, list3, fillvalue='X') | list }}
          # [[1, "a", "x"], [2, "b", "y"], [3, "c", "X"], [4, "d", "X"], [5, "X", "X"]]
    

9. 子元素过滤器 (Combining objects and subelements):

过滤器产生一个对象和该对象的子元素值的乘积,适用于处理嵌套数据结构。

---
- name: Manage SSH Authorized Keys
  hosts: your_target_host
  vars:
    users:
      - name: alice
        authorized_keys:
          - /path/to/alice/key1.pub
          - /path/to/alice/key2.pub
        groups:
          - admin
          - developer
      - name: bob
        authorized_keys:
          - /path/to/bob/key.pub
        groups:
          - developer

  tasks:
    - name: Set authorized SSH keys for each user and group
      ansible.posix.authorized_key:
        user: "{{ item.0.name }}" # 这里对应第零个元素,正好是name
        key: "{{ lookup('file', item.1) }}" 
# 这里索引第一个元素,正好是公钥路径,然后ansible将loop写在后方,
# 会把这个过程重复,剧本将使用ansible.posix.authorized_key模块
# 来确保这些密钥被添加到相应用户的~/.ssh/authorized_keys文件中
        state: present
      loop: "{{ users | subelements('authorized_keys') }}"

10. 组合过滤器(Combining):

这个可以用来组合字典、列表、yaml,本质是数值的混合和替换,已有的值会被替换,没有的值会被混合。主要关注其两个参数recursive and list_merge

  • recursive 是一个布尔值,翻译为递归,默认为 False。它确定是否应递归合并嵌套的哈希。请注意:它不依赖于 ansible.cfg 中的 hash_behaviour 设置。

这个是什么意思呢,也就是说如果我们把这个参数置TRUE,那么对于具有多层级的数据,会对每一层做处理,而设置为FALSE只会做单纯的替换,这里举一个例子

# 对于这个数据,它们的结构是一样的,
# 我们做combine的时候这个递归recursive会影响合并后的结果
default:
  a:
    x: default_value
    y: default_value
  b:
    - 1
    - 2
patch:
  a:
    y: patch_value
    z: patch_value
  b:
    - 3
    - 4

# 如果 recursive = TRUE
# {{ default | combine(patch, recursive=True) }}
{
  'a': {
    'x': 'default_value',
    'y': 'patch_value',
    'z': 'patch_value'
  },
  'b': [1, 2, 3, 4]
}

# 如果 recursive = FALSE
# {{ default | combine(patch, recursive=False) }}
{
  'a': {
    'y': 'patch_value',
    'z': 'patch_value'
  },
  'b': [3, 4]
}
  • list_merge 是一个字符串,可以填为 'replace'(默认)、'keep'、'append'、'prepend'、'append_rp' 或 'prepend_rp'。它会决定当要合并的哈希包含数组/列表时 ansible.builtin.combine 的行为,比如是在尾部追加还是头部追加,冲突是以原数据为准还是以新数据为准。

    • 这里是关于 prependappendkeepappend_rpprepend_rplist_merge 参数中的区别:

    • prepend: 当使用 list_merge='prepend' 时,右边哈希中的数组元素将前置到左边哈希中的数组。换句话说,右边的元素会放在左边的元素之前。

    • append: 当使用 list_merge='append' 时,右边哈希中的数组元素将附加到左边哈希中的数组。右边的元素会放在左边的元素之后。

    • keep: 当使用 list_merge='keep' 时,左边哈希中的数组将被保留,右边哈希中的数组将被忽略,不会有合并或更改。

    • append_rp("rp" 意为 "remove present"):当使用 list_merge='append_rp' 时,右边哈希中的数组元素将附加到左边哈希中的数组。但是,左边哈希中与右边哈希中相对应数组中的元素将被删除。

    • prepend_rp:与 append_rp 类似,但右边哈希中的数组元素会前置到左边哈希中的数组,同时删除左边哈希中与右边哈希中相对应数组中的元素。

然后介绍一下这个方法对常见数据的处理结果

### 例子1
{{ {'a':1, 'b':2} | combine({'b':3}) }}
# {'a':1, 'b':3} 结果会更新b的值

### 例子2 说明keep、append、prepend
# 原数据default
a:
  - default
# 新数据patch
a:
  - patch


{{ default | combine(patch, list_merge='keep') }}   # 这里数据会还是defalt
# 答案,keep是保留原状,如果有撞上了的数据,以原来的为主
#a:
#  - default

{{ default | combine(patch, list_merge='append') }}
# 答案,可以看到插入了尾部
#a:
#  - default
#  - patch

{{ default | combine(patch, list_merge='prepend') }}
# 答案可以看到头部插入了
# a:
#  - patch
#  - default

### 例子3
# 原数据
default:
  a:
    - 1
    - 1
    - 2
    - 3
# 新数据
patch:
  a:
    - 3
    - 4
    - 5
    - 5

{{ default | combine(patch, list_merge='append_rp') }}
# 答案,可以看到分支在尾部进行拼接,冲突数据不会二次追加
# a:
#   - 1
#   - 1
#   - 2
#   - 3
#   - 4
#   - 5
#   - 5

{{ default | combine(patch, list_merge='prepend_rp') }}
# 答案,分支数据在头部拼接,冲突数据不会二次追加
# a:
#   - 3
#   - 4
#   - 5
#   - 5
#   - 1
#   - 1
#   - 2

11. 提取器(extract):

它用于从数组或哈希表中选择值。这个过滤器在Ansible的2.1版本中引入。本质是通过映射做数据的提取,这么说会比较抽象,举个例子看看

# 例子1 
{{ [0, 2] | map('extract', ['x', 'y', 'z']) | list }}
# 这个命令会返回['x', 'z'],刚好是【0,2】这个索引对应到后面的值

# 例子2 
{{ ['x', 'y'] | map('extract', {'x': 42, 'y': 31}) | list }}
# 与例1类似,会返回x和y的值,也即[42, 31]

# 例子3 三参数
{{ groups['x'] | map('extract', hostvars, 'ec2_ip_address') | list }}
# extract 过滤器还可以接受第三个参数,用于执行递归查找
# 我们首先从Ansible的 groups 中选择了名为 'x' 的主机组的列表,
# 然后使用 extract 过滤器执行两次查找。
# 首先,它在 hostvars 中查找了这些主机的信息,
# 然后查找了 ec2_ip_address 键的值(ec2是aws对虚拟机的称呼,elastic comp cloud)
# 最终的结果是一个包含了主机组 'x' 中主机的IP地址列表
# 比如这个例子
x:
  hosts:
    host1:
      ec2_ip_address: '192.168.1.101'
    host2:
      ec2_ip_address: '192.168.1.102'
    host3:
      ec2_ip_address: '192.168.1.103'

# 最后会返回['192.168.1.101', '192.168.1.102', '192.168.1.103']


# 例子4 递归查找
{{ ['a'] | map('extract', b, ['x', 'y']) | list }}
# 比如以这个数据为例子
b:
  a:
    x:
      y: 'value_to_extract'
# 他会逐层搜索,直到找到名为a的数据,也即第二行
# 然后接下来,我们再次使用 extract 过滤器,这次从子哈希表中查找键 'x' 下的值。
# 这将返回一个包含键 'y' 的子哈希表。
# 最后,我们再次使用 extract 过滤器,这次从子哈希表中查找键 'y' 下的值,即 'value_to_extract'

# 因此,最终的结果是 ['value_to_extract'],
# 这是从嵌套的数据结构 b 中提取出来的值。
# 这个操作允许你从深层嵌套的数据结构中选择特定的值,
# 而不必手动递归访问每一层。它可以在处理复杂的数据结构时非常有用。

12. 排列、组合、笛卡尔积(permutations,combinationsproducts

这个和python的itertoolbox库是一个功能,输出排列组合和笛卡尔积

# 例子1
# 排列,第一个是返回所有排列
# 第二个是返回所有长度为 3 的排列
- name: Give me the largest permutations (order matters)
  ansible.builtin.debug:
    msg: "{{ [1,2,3,4,5] | ansible.builtin.permutations | list }}"

- name: Give me permutations of sets of three
  ansible.builtin.debug:
    msg: "{{ [1,2,3,4,5] | ansible.builtin.permutations(3) | list }}"

# 例子2
# 这个例子是给出所有长度为 2 的组合

- name: Give me combinations for sets of two
  ansible.builtin.debug:
    msg: "{{ [1,2,3,4,5] | ansible.builtin.combinations(2) | list }}"

# 例子3
# 这个是给出笛卡尔积,笛卡尔积就是所有位置相乘
# 这个例子会返回两个url
- name: Generate multiple hostnames
  ansible.builtin.debug:
    msg: "{{ ['foo', 'bar'] | product(['com']) | map('join', '.') | join(',') }}"
This would result in:

# 返回 { "msg": "foo.com,bar.com" }

13. Json Query过滤器

这个跟日常经常用到的JQ是一样的,用于切出一个巨大的JSON中我们需要的数据,语句逻辑同样也是JMESPath

# 以这个JSON数据为例
{
    "domain": {
        "cluster": [
            {
                "name": "cluster1"
            },
            {
                "name": "cluster2"
            }
        ],
        "server": [
            {
                "name": "server11",
                "cluster": "cluster1",
                "port": "8080"
            },
            {
                "name": "server12",
                "cluster": "cluster1",
                "port": "8090"
            },
            {
                "name": "server21",
                "cluster": "cluster2",
                "port": "9080"
            },
            {
                "name": "server22",
                "cluster": "cluster2",
                "port": "9090"
            }
        ],
        "library": [
            {
                "name": "lib1",
                "target": "cluster1"
            },
            {
                "name": "lib2",
                "target": "cluster2"
            }
        ]
    }
}


# 例子1 拿到所有集群名
- name: Display all cluster names
  ansible.builtin.debug:
    var: item
  loop: "{{ domain_definition | community.general.json_query('domain.cluster[*].name') }}"

# 例子2 拿到所有服务名
- name: Display all server names
  ansible.builtin.debug:
    var: item
  loop: "{{ domain_definition | community.general.json_query('domain.server[*].name') }}"

# 例子3 提取集群“cluster 1”的端口号
- name: Display all ports from cluster1
  ansible.builtin.debug:
    var: item
  loop: "{{ domain_definition | community.general.json_query(server_name_cluster1_query) }}"
  vars:
    server_name_cluster1_query: "domain.server[?cluster=='cluster1'].port"

# 例子4 显示集群1的所有端口号,并且写到一行用‘,’分割
- name: Display all ports from cluster1 as a string
  ansible.builtin.debug:
    msg: "{{ domain_definition | community.general.json_query('domain.server[?cluster==`cluster1`].port') | join(', ') }}"


# 例子5
生成一个字典,分别是name和port的hashmap:
- name: Display all server ports and names from cluster1
  ansible.builtin.debug:
    var: item
  loop: "{{ domain_definition | community.general.json_query(server_name_cluster1_query) }}"
  vars:
    server_name_cluster1_query: "domain.server[?cluster=='cluster1'].{name: name, port: port}"

# 例子6 从集群名开头为server1的机器提取端口号,本质是多了一层字符串处理
- name: Display ports from all clusters with the name starting with 'server1'
  ansible.builtin.debug:
    msg: "{{ domain_definition | to_json | from_json | community.general.json_query(server_name_query) }}"
  vars:
    server_name_query: "domain.server[?starts_with(name,'server1')].port"


# 例子7 从集群名包含server1的机器中提取端口号
- name: Display ports from all clusters with the name containing 'server1'
  ansible.builtin.debug:
    msg: "{{ domain_definition | to_json | from_json | community.general.json_query(server_name_query) }}"
  vars:
    server_name_query: "domain.server[?contains(name,'server1')].port"

另外,一般用Ansible的JQ时,配合`` to_json | from_json `` 过滤器一起使用。

14. 随机过滤器 (random filters):

包括生成随机 MAC 地址或数字的过滤器,提供诸如为随机数字指定范围或步长的功能。跟python的random库一个作用

# 例子1 需要版本大于version 2.6,根据一个前缀生成一串mac地址
"{{ '52:54:00' | community.general.random_mac }}"
# => '52:54:00:ef:1c:03'
Note that if anything is wrong with the prefix string, the filter will issue an error.

# 例子2 需要版本大于version 2.9 根据一个随机数种子生成一个mac
# 随机数种子的好处是可以生成随机但幂等的随机数,也即只要拿到种子就可以控制随机数
"{{ '52:54:00' | community.general.random_mac(seed=inventory_hostname) }}"


# 例子3 在选定范围生成一个数
"{{ ['a','b','c'] | random }}"
# => 'c'

# 例子4 生成一个范围中的随机数,下面这个是0~60随机生成一个,区间是左闭右开
# 生成一个计划任务,比较实用
"{{ 60 | random }} * * * * root /script/from/cron"
# => '21 * * * * root /script/from/cron'

# 例子5 带有步进的随机数生成,步数为10
# 你可以注意到,这个例子中是101,所以包含了100这个数
{{ 101 | random(step=10) }}
# => 70

# 例子6 生成1到101之间(不包括101),每10为一个步长的随机数。
# 第二个表达式是更明确地指定了起始值为1,步长为10。所以它们都会生成诸如1, 11, 21, ... 91这样步长为10的随机数。注意这里区间左闭右开,101是不包括在内的。
{{ 101 | random(1, 10) }}
# => 31
{{ 101 | random(start=1, step=10) }}
# => 51

# 例子7 根据随机数种子生成一个随机数
"{{ 60 | random(seed=inventory_hostname) }} * * * * root /script/from/cron"

15. 列表操作过滤器(managing list variables)

提供一些极值查找,列表平铺等操作

# 例1 最小值
{{ list1 | min }}

# 例2 2.11版本中,添加了一个筛选,这里只比较key = val,其他的忽略
{{ [{'val': 1}, {'val': 2, 'other': 'something'}] | min(attribute='val') }}

# 例3 最大值
{{ [3, 4, 2] | max }}

# 例4 待筛选的最大值选取
{{ [{'val': 1}, {'val': 2}] | max(attribute='val') }}

# 例5 2.5版本中新增了一个平铺设置,比较好理解,跟python中的itertools.chain一个效果
{{ [3, [4, 2] ] | flatten }}
# => [3, 4, 2]

# 例6 带层数的平铺,这里是一层,效果比较好理解
{{ [3, [4, [2]] ] | flatten(levels=1) }}
# => [3, 4, [2]]

# 例7 2.11中新增了一个参数skip_nulls,这里不会跳过空值
{{ [3, None, [4, [2]] ] | flatten(levels=1, skip_nulls=False) }}
# => [3, None, 4, [2]]

你可能感兴趣的:(技术积累,服务器,linux,python,ansible)