一、【说在前面】
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
的行为,比如是在尾部追加还是头部追加,冲突是以原数据为准还是以新数据为准。-
这里是关于
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
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,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 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]]