一、【说在前面】
Ansible Filter一般被称为滤波器或者叫过滤器。
这个东西初次听到以为是什么科学计算的东西,但是想来ansible不太可能有什么滤波操作,所以这个东西本质是一个数值筛选器,内置函数,本质是一个为了做区别化的工具,比如根据不同的机器名做不同操作,根据预先设定的值做区别化对待。
这篇文章介绍一下ansibe常用的过滤器是怎么用的,有什么作用,官网文档更详细,但看起来例子比较单薄,本文本质是笔者回顾Ansible的学习笔记,希望这篇文章能发挥一些补充作用。
官网:Using filters to manipulate data — Ansible Documentation
二、【常用过滤器介绍】
1. 默认过滤器 (default filter):
这个过滤器用于为变量指定默认值,或者叫缺省值,这种方法可以为剧本添加某个默认动作。这里以debug打印数值为例。
---
- hosts: localhostgather_facts: novars:# 模拟一个有时有值,有时没有值的变量my_variable_present: "I have a value"my_variable_absent: null # 或者可以将这个变量设置为 ""tasks:- name: Set default value if variable is absentset_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 providedfile:# 使用 default 过滤器和 omit 变量,以便在 file_path_present 存在时,设置 path 参数。path: "{{ file_path_present | default(omit) }}"state: touchwhen: file_path_present is defined# 第二个任务名称,用于创建一个文件,如果变量 file_path_absent 不存在。- name: Create a file with omitted pathfile:# 使用 default 过滤器和 omit 变量,以便在 file_path_absent 不存在时,省略 path 参数。path: "{{ file_path_absent | default(omit) }}"state: touchwhen: file_path_absent | default(omit) is omit
3. 集合或列表过滤器 (set theory or list filters):
这些过滤器用于操作列表变量。有常见的什么集合、去重操作之类。
---
- hosts: localhostgather_facts: novars:list1: [1, 2, 5, 1, 3, 4, 10]list2: [1, 2, 3, 4, 5, 11, 99]tasks:- name: Get a unique set from list1debug:var: list1 | unique # 做一个unique操作,去重用- name: Get the union of list1 and list2debug: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 list2debug: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 list2debug: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 list2debug: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: localhostgather_facts: novars:status: "needs_restart"enabled: truesome_value: nulltasks:# 第一个任务:检查状态并确定动作- name: Check the status and determine actiondebug:msg: |# 打印状态值和相应的动作Status is "{{ status }}". Action: {{(status == 'needs_restart') | ternary('Restart', 'Continue') }}# 第二个任务:检查是否已启用并配置- name: Check if enabled and configuredebug:msg: |# 打印是否已启用和相应的配置Enabled: "{{ enabled }}". Configuration: {{enabled | ternary('No Shutdown', 'Shutdown') }}# 第三个任务:检查一个值并处理空值- name: Check a value and handle nulldebug: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: localhostgather_facts: novars:# 定义一个变量,包含不同数据类型的值myvar_string: "This is a string"myvar_integer: 42myvar_list: [1, 2, 3]myvar_dict: {"key": "value"}tasks:- name: Display the data type of variablesdebug:# 使用 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: localhostgather_facts: novars:# 定义一个字典变量my_dict:users: /etc/passwdgroups: /etc/grouptasks:- name: Transform dictionary into a list of itemsdebug:# 使用 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: localhostgather_facts: novars:# 定义一个字典变量my_dict:name: "John"age: 30city: "New York"hobbies:- Reading- Hiking- Cookingtasks:# 第一个任务:将字典转换为JSON格式并显示- name: Convert to JSON formatdebug:msg: |# 使用 to_json 过滤器将 my_dict 转换为JSON格式的字符串JSON Format:{{ my_dict | to_json(indent=8, width=1337) }} # 设置每行缩进8,宽度限制1337# 第二个任务:将字典转换为YAML格式并显示- name: Convert to YAML formatdebug:msg: |# 使用 to_yaml 过滤器将 my_dict 转换为YAML格式的字符串YAML Format:{{ my_dict | to_yaml }}# 第三个任务:将字典转换为格式化良好的JSON格式并显示- name: Convert to Nice JSON formatdebug:msg: |# 使用 to_nice_json 过滤器将 my_dict 转换为格式化良好的JSON格式的字符串Nice JSON Format:{{ my_dict | to_nice_json }}# 第四个任务:将字典转换为格式化良好的YAML格式并显示- name: Convert to Nice YAML formatdebug:msg: |# 使用 to_nice_yaml 过滤器将 my_dict 转换为格式化良好的YAML格式的字符串Nice YAML Format:{{ my_dict | to_nice_yaml }}# 在写一个使用from_json的例子:
tasks:- name: Register JSON output as a variableansible.builtin.shell: cat /some/path/to/file.jsonregister: result- name: Set a variableansible.builtin.set_fact:myvar: "{{ result.stdout | from_json }}"
8. Zip 和 Zip_Longest 过滤器(Combining items from multiple lists: zip and zip_longest):
这些过滤器用于合并多个列表的元素,可以选择填充间隙,跟python内置方法zip差不多。
---
- hosts: localhostgather_facts: novars:# 定义多个列表list1: [1, 2, 3, 4, 5]list2: ['a', 'b', 'c', 'd']list3: ['x', 'y']tasks:# 使用 zip 过滤器将两个列表组合成一个新列表- name: Combine two lists using zipdebug: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_longestdebug: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 Keyshosts: your_target_hostvars:users:- name: aliceauthorized_keys:- /path/to/alice/key1.pub- /path/to/alice/key2.pubgroups:- admin- developer- name: bobauthorized_keys:- /path/to/bob/key.pubgroups:- developertasks:- name: Set authorized SSH keys for each user and groupansible.posix.authorized_key:user: "{{ item.0.name }}" # 这里对应第零个元素,正好是namekey: "{{ lookup('file', item.1) }}"
# 这里索引第一个元素,正好是公钥路径,然后ansible将loop写在后方,
# 会把这个过程重复,剧本将使用ansible.posix.authorized_key模块
# 来确保这些密钥被添加到相应用户的~/.ssh/authorized_keys文件中state: presentloop: "{{ users | subelements('authorized_keys') }}"
8. 组合过滤器(Combining):
这个可以用来组合字典、列表、yaml,本质是数值的混合和替换,已有的值会被替换,没有的值会被混合。主要关注其两个参数recursive
and list_merge
-
recursive
是一个布尔值,翻译为递归,默认为 False。它确定是否应递归合并嵌套的哈希。请注意:它不依赖于 ansible.cfg 中的hash_behaviour
设置。
这个是什么意思呢,也就是说如果我们把这个参数置TRUE,那么对于具有多层级的数据,会对每一层做处理,而设置为FALSE只会做单纯的替换,这里举一个例子
# 对于这个数据,它们的结构是一样的,
# 我们做combine的时候这个递归recursive会影响合并后的结果
default:a:x: default_valuey: default_valueb:- 1- 2
patch:a:y: patch_valuez: patch_valueb:- 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
的行为,比如是在尾部追加还是头部追加,冲突是以原数据为准还是以新数据为准。-
这里是关于
prepend
、append
、keep
、append_rp
和prepend_rp
在list_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
9. 提取器(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 中提取出来的值。
# 这个操作允许你从深层嵌套的数据结构中选择特定的值,
# 而不必手动递归访问每一层。它可以在处理复杂的数据结构时非常有用。
10. 排列、组合、笛卡尔积(permutations,combinations,products)
这个和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 threeansible.builtin.debug:msg: "{{ [1,2,3,4,5] | ansible.builtin.permutations(3) | list }}"# 例子2
# 这个例子是给出所有长度为 2 的组合- name: Give me combinations for sets of twoansible.builtin.debug:msg: "{{ [1,2,3,4,5] | ansible.builtin.combinations(2) | list }}"# 例子3
# 这个是给出笛卡尔积,笛卡尔积就是所有位置相乘
# 这个例子会返回两个url
- name: Generate multiple hostnamesansible.builtin.debug:msg: "{{ ['foo', 'bar'] | product(['com']) | map('join', '.') | join(',') }}"
This would result in:# 返回 { "msg": "foo.com,bar.com" }
11. 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 namesansible.builtin.debug:var: itemloop: "{{ domain_definition | community.general.json_query('domain.cluster[*].name') }}"# 例子2 拿到所有服务名
- name: Display all server namesansible.builtin.debug:var: itemloop: "{{ domain_definition | community.general.json_query('domain.server[*].name') }}"# 例子3 提取集群“cluster 1”的端口号
- name: Display all ports from cluster1ansible.builtin.debug:var: itemloop: "{{ 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 stringansible.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 cluster1ansible.builtin.debug:var: itemloop: "{{ 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 `` 过滤器一起使用。
12. 随机过滤器 (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"
13. 数学过滤器 (math filters):
用于进行数学运算,如加减乘除、对数、幂等。