测试
测试 在 Jinja 中是一种评估模板表达式并返回 True 或 False 的方法。Jinja 附带了许多这样的测试。请参阅官方 Jinja 模板文档中的 内置测试。
测试和过滤器之间的主要区别在于,Jinja 测试用于比较,而过滤器用于数据操作,它们在 jinja 中有不同的应用。测试也可以用在列表处理过滤器中,比如 map()
和 select()
,用于选择列表中的项目。
与所有模板化一样,测试始终在 Ansible 控制节点上执行,而不是在任务的目标上执行,因为它们测试的是本地数据。
除了这些 Jinja2 测试之外,Ansible 还提供了一些额外的测试,用户可以轻松创建自己的测试。
测试语法
测试语法 不同于 过滤器语法 (variable | filter
)。历史上,Ansible 将测试注册为 jinja 测试和 jinja 过滤器,允许使用过滤器语法引用它们。
从 Ansible 2.5 开始,将 jinja 测试用作过滤器将生成弃用警告。从 Ansible 2.9+ 开始,必须使用 jinja 测试语法。
使用 jinja 测试的语法如下
variable is test_name
例如
result is failed
测试字符串
要将字符串与子字符串或正则表达式进行匹配,请使用 match
、search
或 regex
测试
vars:
url: "https://example.com/users/foo/resources/bar"
tasks:
- debug:
msg: "matched pattern 1"
when: url is match("https://example.com/users/.*/resources")
- debug:
msg: "matched pattern 2"
when: url is search("users/.*/resources/.*")
- debug:
msg: "matched pattern 3"
when: url is search("users")
- debug:
msg: "matched pattern 4"
when: url is regex("example\.com/\w+/foo")
match
如果在字符串开头找到模式,则成功,而 search
如果在字符串中的任何位置找到模式,则成功。默认情况下,regex
的工作方式类似于 search
,但 regex
可以通过传递 match_type
关键字参数来配置以执行其他测试。特别是,match_type
确定用于执行搜索的 re
方法。完整列表可以在相关的 Python 文档 此处 找到。
所有字符串测试还都接受可选的 ignorecase
和 multiline
参数。这些参数分别对应于 Python 的 re
库中的 re.I
和 re.M
。
保险库
版本 2.10 中的新增功能。
可以使用 vault_encrypted
测试来测试变量是否为内联单个保险库加密值。
vars:
variable: !vault |
$ANSIBLE_VAULT;1.2;AES256;dev
61323931353866666336306139373937316366366138656131323863373866376666353364373761
3539633234313836346435323766306164626134376564330a373530313635343535343133316133
36643666306434616266376434363239346433643238336464643566386135356334303736353136
6565633133366366360a326566323363363936613664616364623437336130623133343530333739
3039
tasks:
- debug:
msg: '{{ (variable is vault_encrypted) | ternary("Vault encrypted", "Not vault encrypted") }}'
测试真值性
版本 2.10 中的新增功能。
从 Ansible 2.10 开始,你现在可以执行 Python 类似的真值性和假值性检查。
- debug:
msg: "Truthy"
when: value is truthy
vars:
value: "some string"
- debug:
msg: "Falsy"
when: value is falsy
vars:
value: ""
此外,truthy
和 falsy
测试接受一个名为 convert_bool
的可选参数,该参数将尝试将布尔指示器转换为实际的布尔值。
- debug:
msg: "Truthy"
when: value is truthy(convert_bool=True)
vars:
value: "yes"
- debug:
msg: "Falsy"
when: value is falsy(convert_bool=True)
vars:
value: "off"
比较版本
版本 1.6 中的新增功能。
注意
在 2.5 中,version_compare
被重命名为 version
要比较版本号,例如检查 ansible_facts['distribution_version']
版本是否大于或等于‘12.04’,可以使用 version
测试。
version
测试还可以用来评估 ansible_facts['distribution_version']
{{ ansible_facts['distribution_version'] is version('12.04', '>=') }}
如果 ansible_facts['distribution_version']
大于或等于 12.04,则此测试返回 True,否则返回 False。
version
测试接受以下运算符
<, lt, <=, le, >, gt, >=, ge, ==, =, eq, !=, <>, ne
此测试还接受第三个参数,strict
,它定义是否应该使用由 ansible.module_utils.compat.version.StrictVersion
定义的严格版本解析。默认值为 False
(使用 ansible.module_utils.compat.version.LooseVersion
),True
启用严格的版本解析
{{ sample_version_var is version('1.0', operator='lt', strict=True) }}
从 Ansible 2.11 开始,version
测试接受一个 version_type
参数,该参数与 strict
相互排斥,并接受以下值
loose, strict, semver, semantic, pep440
松散
此类型对应于 Python
distutils.version.LooseVersion
类。所有版本格式对于此类型都是有效的。比较规则简单且可预测,但可能并不总是给出预期的结果。严格
此类型对应于 Python
distutils.version.StrictVersion
类。版本号由两个或三个用点分隔的数字组成,末尾可选地带有“预发布”标记。预发布标记由单个字母‘a’或‘b’后跟一个数字组成。如果两个版本号的数字部分相等,则带预发布标记的版本将始终被视为比不带预发布标记的版本更早(更小)。semver
/semantic
此类型实现了用于版本比较的 语义版本 方案。
pep440
此类型实现了 Python PEP-440 版本控制规则,用于版本比较。在版本 2.14 中添加。
使用 version_type
比较语义版本可以通过以下方式实现
{{ sample_semver_var is version('2.0.0-rc.1+build.123', 'lt', version_type='semver') }}
在 Ansible 2.14 中,添加了 version_type
的 pep440
选项,此类型的规则在 PEP-440 中定义。以下示例展示了这种类型如何将预发布版本区分开来,使其小于通用版本。
{{ '2.14.0rc1' is version('2.14.0', 'lt', version_type='pep440') }}
在剧本或角色中使用 version
时,不要使用 {{ }}
,如 FAQ 中所述
vars:
my_version: 1.2.3
tasks:
- debug:
msg: "my_version is higher than 1.0.0"
when: my_version is version('1.0.0', '>')
集合论测试
版本 2.1 中的新增功能。
注意
在 2.5 中,issubset
和 issuperset
被重命名为 subset
和 superset
要查看列表是否包含或被另一个列表包含,可以使用‘subset’和‘superset’
vars:
a: [1,2,3,4,5]
b: [2,3]
tasks:
- debug:
msg: "A includes B"
when: a is superset(b)
- debug:
msg: "B is included in A"
when: b is subset(a)
测试列表是否包含值
版本 2.8 中的新增功能。
Ansible 包含一个 contains
测试,其工作原理类似,但与 Jinja2 提供的 in
测试相反。 contains
测试旨在与 select
、reject
、selectattr
和 rejectattr
过滤器一起使用。
vars:
lacp_groups:
- master: lacp0
network: 10.65.100.0/24
gateway: 10.65.100.1
dns4:
- 10.65.100.10
- 10.65.100.11
interfaces:
- em1
- em2
- master: lacp1
network: 10.65.120.0/24
gateway: 10.65.120.1
dns4:
- 10.65.100.10
- 10.65.100.11
interfaces:
- em3
- em4
tasks:
- debug:
msg: "{{ (lacp_groups|selectattr('interfaces', 'contains', 'em1')|first).master }}"
测试列表值是否为 True
版本 2.4 中新增。
您可以使用 any 和 all 来检查列表中的任何或所有元素是否为真。
vars:
mylist:
- 1
- "{{ 3 == 3 }}"
- True
myotherlist:
- False
- True
tasks:
- debug:
msg: "all are true!"
when: mylist is all
- debug:
msg: "at least one is true"
when: myotherlist is any
测试路径
注意
在 2.5 中,以下测试被重命名以移除 is_
前缀。
以下测试可以提供有关控制节点上路径的信息。
- debug:
msg: "path is a directory"
when: mypath is directory
- debug:
msg: "path is a file"
when: mypath is file
- debug:
msg: "path is a symlink"
when: mypath is link
- debug:
msg: "path already exists"
when: mypath is exists
- debug:
msg: "path is {{ (mypath is abs)|ternary('absolute','relative')}}"
- debug:
msg: "path is the same file as path2"
when: mypath is same_file(path2)
- debug:
msg: "path is a mount"
when: mypath is mount
- debug:
msg: "path is a directory"
when: mypath is directory
vars:
mypath: /my/path
- debug:
msg: "path is a file"
when: "'/my/path' is file"
测试大小格式
human_readable
和 human_to_bytes
函数允许您测试您的剧本,以确保您在任务中使用正确的尺寸格式,并确保您向计算机提供字节格式,向人们提供人类可读的格式。
人类可读
断言给定的字符串是否为人类可读的。
例如
- name: "Human Readable"
assert:
that:
- '"1.00 Bytes" == 1|human_readable'
- '"1.00 bits" == 1|human_readable(isbits=True)'
- '"10.00 KB" == 10240|human_readable'
- '"97.66 MB" == 102400000|human_readable'
- '"0.10 GB" == 102400000|human_readable(unit="G")'
- '"0.10 Gb" == 102400000|human_readable(isbits=True, unit="G")'
这将导致
{ "changed": false, "msg": "All assertions passed" }
人类到字节
返回以字节格式表示的给定字符串。
例如
- name: "Human to Bytes"
assert:
that:
- "{{'0'|human_to_bytes}} == 0"
- "{{'0.1'|human_to_bytes}} == 0"
- "{{'0.9'|human_to_bytes}} == 1"
- "{{'1'|human_to_bytes}} == 1"
- "{{'10.00 KB'|human_to_bytes}} == 10240"
- "{{ '11 MB'|human_to_bytes}} == 11534336"
- "{{ '1.1 GB'|human_to_bytes}} == 1181116006"
- "{{'10.00 Kb'|human_to_bytes(isbits=True)}} == 10240"
这将导致
{ "changed": false, "msg": "All assertions passed" }
测试任务结果
以下任务说明了旨在检查任务状态的测试。
tasks:
- shell: /usr/bin/foo
register: result
ignore_errors: True
- debug:
msg: "it failed"
when: result is failed
# in most cases you'll want a handler, but if you want to do something right now, this is nice
- debug:
msg: "it changed"
when: result is changed
- debug:
msg: "it succeeded in Ansible >= 2.1"
when: result is succeeded
- debug:
msg: "it succeeded"
when: result is success
- debug:
msg: "it was skipped"
when: result is skipped
注意
从 2.1 开始,您还可以使用 success、failure、change 和 skip,以确保语法匹配,对于那些需要对此严格的人来说。
类型测试
当试图确定类型时,您可能很想使用 type_debug
过滤器并将该过滤器与该类型的字符串名称进行比较,但是,您应该使用类型测试比较,例如
tasks:
- name: "String interpretation"
vars:
a_string: "A string"
a_dictionary: {"a": "dictionary"}
a_list: ["a", "list"]
assert:
that:
# Note that a string is classed as also being "iterable" and "sequence", but not "mapping"
- a_string is string and a_string is iterable and a_string is sequence and a_string is not mapping
# Note that a dictionary is classed as not being a "string", but is "iterable", "sequence" and "mapping"
- a_dictionary is not string and a_dictionary is iterable and a_dictionary is mapping
# Note that a list is classed as not being a "string" or "mapping" but is "iterable" and "sequence"
- a_list is not string and a_list is not mapping and a_list is iterable
- name: "Number interpretation"
vars:
a_float: 1.01
a_float_as_string: "1.01"
an_integer: 1
an_integer_as_string: "1"
assert:
that:
# Both a_float and an_integer are "number", but each has their own type as well
- a_float is number and a_float is float
- an_integer is number and an_integer is integer
# Both a_float_as_string and an_integer_as_string are not numbers
- a_float_as_string is not number and a_float_as_string is string
- an_integer_as_string is not number and a_float_as_string is string
# a_float or a_float_as_string when cast to a float and then to a string should match the same value cast only to a string
- a_float | float | string == a_float | string
- a_float_as_string | float | string == a_float_as_string | string
# Likewise an_integer and an_integer_as_string when cast to an integer and then to a string should match the same value cast only to an integer
- an_integer | int | string == an_integer | string
- an_integer_as_string | int | string == an_integer_as_string | string
# However, a_float or a_float_as_string cast as an integer and then a string does not match the same value cast to a string
- a_float | int | string != a_float | string
- a_float_as_string | int | string != a_float_as_string | string
# Again, Likewise an_integer and an_integer_as_string cast as a float and then a string does not match the same value cast to a string
- an_integer | float | string != an_integer | string
- an_integer_as_string | float | string != an_integer_as_string | string
- name: "Native Boolean interpretation"
loop:
- yes
- true
- True
- TRUE
- no
- No
- NO
- false
- False
- FALSE
assert:
that:
# Note that while other values may be cast to boolean values, these are the only ones that are natively considered boolean
# Note also that `yes` is the only case-sensitive variant of these values.
- item is boolean