测试

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

测试字符串

要将字符串与子字符串或正则表达式匹配,请使用 matchsearchregex 测试

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,但也可以通过传递 match_type 关键字参数来配置 regex 来执行其他测试。 特别是,match_type 确定用于执行搜索的 re 方法。 可以在相关的 Python 文档此处找到完整列表。

所有字符串测试还采用可选的 ignorecasemultiline 参数。 它们分别对应于 Python re 库中的 re.Ire.M

Vault

2.10 版本新增。

您可以使用 vault_encrypted 测试来测试变量是否是内联的单个 vault 加密值。

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: ""

此外,truthyfalsy 测试接受一个名为 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 测试接受与 strict 互斥的 version_type 参数,并接受以下值

loose, strict, semver, semantic, pep440
loose

此类型对应于 Python distutils.version.LooseVersion 类。所有版本格式对此类型都有效。比较规则简单且可预测,但可能并不总是给出预期的结果。

strict

此类型对应于 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_typepep440 选项,此类型的规则在 PEP-440 中定义。以下示例展示了此类型如何区分预发布版本和正式发布版本,将预发布版本视为小于正式发布版本。

{{ '2.14.0rc1' is version('2.14.0', 'lt', version_type='pep440') }}

在 playbook 或 role 中使用 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 版本中,issubsetissuperset 被重命名为 subsetsuperset

要查看一个列表是否包含另一个列表或被另一个列表包含,可以使用 ‘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 测试旨在与 selectrejectselectattrrejectattr 过滤器一起使用。

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 版本新增。

您可以使用 anyall 来检查列表中是否任何或所有元素为 true。

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_readablehuman_to_bytes 函数可以让你测试你的 playbook,以确保你在任务中使用了正确的大小格式,并向计算机提供字节格式,向人们提供人类可读的格式。

人类可读

断言给定的字符串是否是人类可读的。

例如

- 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

另请参阅

Ansible Playbook

playbook 简介

条件

playbook 中的条件语句

使用变量

关于变量的一切

循环

在 playbook 中循环

角色

按角色组织 playbook

一般提示

playbook 的技巧和窍门

沟通

有问题?需要帮助?想分享你的想法?请访问 Ansible 通信指南