测试

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

所有字符串测试还都接受可选的 ignorecasemultiline 参数。这些参数分别对应于 Python 的 re 库中的 re.Ire.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: ""

此外,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 测试接受一个 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_typepep440 选项,此类型的规则在 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 中,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 来检查列表中的任何或所有元素是否为真。

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 函数允许您测试您的剧本,以确保您在任务中使用正确的尺寸格式,并确保您向计算机提供字节格式,向人们提供人类可读的格式。

人类可读

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

例如

- 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 剧本

剧本简介

条件

剧本中的条件语句

使用变量

关于变量的一切

循环

剧本中的循环

角色

按角色组织剧本

一般提示

剧本的提示和技巧

交流

有问题?需要帮助?想分享您的想法?请访问 Ansible 交流指南