使用Ansible条件语句

时间:2020-01-09 10:34:02  来源:igfitidea点击:

控制结构指的是对程序执行流有影响的任何东西。控制结构主要有以下两种类型:

  • 有条件的

  • 迭代

有时,我们需要根据变量的值、平台的类型甚至其他命令的结果有条件地执行代码。有时我们还需要迭代多个对象,例如列表哈希或者多级变量。

在本节中,我们将介绍以下3种不同的条件语句

  • when

  • failed_when

  • changed_when

使用when语句

when键的值是不带花括号的Jinja2表达式。

只有当Jinja2表达式的计算结果为true时,才会执行该任务。这个Jinja2表达式可以计算变量的值。它可以包含Jinja2过滤器,也可以包含逻辑运算符和分组。

我们将使用上一个练习中的现有剧本,首先了解"when"条件是如何工作的:

--
 - name: when conditional operator
   hosts: localhost
   gather_facts: false
   vars:
     x: 10
     y: 15
     list: [10,20,30]
   tasks:
     - debug:
         msg:
          - "The value of x is {{ x }} and the value of y is {{ y }}"
          - "Our list contains: {{ list }}"
       when: x == y
     - debug:
         msg: "x is present in the list"
       when: x in list

说明:

你应该注意"when"语句的缩进。它应该具有与模块名相同的缩进(不带连字符)。在我的例子中,我将when语句的空格数与debug模块的空格数相同,否则会出现语法错误

在这个 conditional-operator.ymlplaybook我定义了一个条件,在第一个任务中使用'debug'模块使用'when'。因此,如果条件为TRUE,即'x==y',则只执行第一个任务,否则将跳过第二个任务。我添加了另一个条件,即'x在列表中,则只执行第二个任务,否则将跳过第二个任务

让我们执行剧本

[ansible@controller ~]$ ansible-playbook conditional-operator.yml
PLAY [when conditional operator] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *****
TASK [debug] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *****
skipping: [localhost]
TASK [debug] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *****
ok: [localhost] => {
    "msg": "x is present in the list"
}
PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

因此,我们跳过了第一个任务,因为"x"不等于"y",而执行了第二个任务,因为"x"是"list"的一部分

示例1:安装HTTPD

现在让我们举一些实际的例子:

--
 - name: Installing HTTPD
   hosts: server2
   become: true
   gather_facts: false
   vars:
     pkg: httpd
   tasks:
     - shell: rpm -q httpd | head -n 1
       register: result
     - set_fact:
        result: "{{ result.stdout }}"
     - debug:
        msg: "Is the {{ pkg }} installed: {{ result }}"
     - yum:
        name: "{{ pkg }}"
        state: latest
       when: result | regex_search("not installed")

我们将使用此剧本install-httpd.yml在"server2"上安装"httpd"包,但有一个条件,即只有在包"未安装"的情况下,playbook才应安装该包。如果已经安装了pkg,则"不执行任何操作"

我在这个剧本里写了4个任务

  • 检查是否使用"shell"模块安装了"httpd"包,并将输出注册到"result"变量中

  • 使用"set_fact"将输出存储到"result"变量中,因为我们在运行时执行某些操作,并存储无法使用"vars"的变量(这将在"Ansible Variables and Data Types"一章中介绍)

  • 使用"debug"模块获取变量的输出,以确保它得到正确的输出

  • 使用"yum"模块根据"when"条件安装"pkg"。我们使用"regex_search"在"result"变量中搜索字符串"not installed"

让我们执行这个剧本。因为我使用的是rpm命令,ansible抛出警告,但是因为我们使用的是rpm命令只是为了获取包列表,所以可以忽略警告。

[ansible@controller ~]$ ansible-playbook install-httpd.yml
PLAY [Installing HTTPD] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **
TASK [shell] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *****
[WARNING]: Consider using the yum, dnf or zypper module rather than running 'rpm'.  If you need to use command
because yum, dnf or zypper is insufficient you can add 'warn: false' to this command task or set
'command_warnings=False' in ansible.cfg to get rid of this message.
changed: [server2]
TASK [set_fact] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **
ok: [server2]
TASK [debug] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *****
ok: [server2] => {
    "msg": "Is the httpd installed: httpd-2.4.37-21.module_el8.2.0+494+1df74eae.x86_64"
}
TASK [yum] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***
skipping: [server2]
PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***
server2                    : ok=3    changed=1    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

根据"debug"模块的输出,由于已经安装了"httpd",我们的TASK4被"跳过",因此没有执行任何安装。现在我已经从server2中删除了httpd包,让我们重新运行这个剧本:

[ansible@controller ~]$ ansible-playbook install-httpd.yml
PLAY [Installing HTTPD] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **
TASK [shell] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *****
[WARNING]: Consider using the yum, dnf or zypper module rather than running 'rpm'.  If you need to use command
because yum, dnf or zypper is insufficient you can add 'warn: false' to this command task or set
'command_warnings=False' in ansible.cfg to get rid of this message.
changed: [server2]
TASK [set_fact] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **
ok: [server2]
TASK [debug] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *****
ok: [server2] => {
    "msg": "Is the httpd installed: package httpd is not installed"
}
TASK [yum] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***
changed: [server2]
PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***
server2                    : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

所以这次"httpd"包成功安装了

使用失败的_when语句

如果我们以上一个示例playbook为例,我们使用它来安装"httpd"包。我已经写了4个任务来实现我们的议程,虽然我们可以删除'debug'任务,但是还有另一种方法来处理这样的用例。

我们可以使用"failed_when"条件根据条件检查故意使脚本失败。我用"failed_check"条件更新了相同的剧本,还添加了warn:false以避免使用"rpm"命令时出现警告

--
 - name: Installing HTTPD
   hosts: server2
   become: true
   gather_facts: false
   vars:
     pkg: httpd
   tasks:
     - shell: rpm -q httpd | head -n 1
       args:
         warn: false
       register: result
       failed_when: "'not installed' not in result.stdout"
     - yum:
        name: "{{ pkg }}"
        state: present

现在在这个playbook的shell任务中,我们查询"httpd"包并检查"stdout"。如果"stdout"包含"not installed",则表示未安装"httpd"包,因此playbook可以继续安装"pkg",但如果未找到,则表示"httpd"包已处于安装状态,因此请将playbook标记为失败并退出,而不执行任何其他任务。

让我们来执行这个剧本:

[ansible@controller ~]$ ansible-playbook install-httpd.yml
PLAY [Installing HTTPD] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **
TASK [shell] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *****
fatal: [server2]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"}, "changed": true, "cmd": "rpm -q httpd | head -n 1", "delta": "0:00:00.015037", "end": "2017-09-25 07:39:33.713691", "failed_when_result": true, "rc": 0, "start": "2017-09-25 07:39:33.698654", "stderr": "", "stderr_lines": [], "stdout": "httpd-2.4.37-21.module_el8.2.0+494+1df74eae.x86_64", "stdout_lines": ["httpd-2.4.37-21.module_el8.2.0+494+1df74eae.x86_64"]}
PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***
server2                    : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

因此playbook被标记为失败,因为"httpd"包已经安装在"server2"上。我们可以将"-vvv"与ansible playbook命令一起使用,以获得详细的输出,我们可以对任务的"输出"有更多的了解。

打印失败消息

在上面的示例中,我们确实根据条件匹配使playbook失败,但是控制台上没有输出,因此操作员可能会混淆playbook是否有BUG或者失败是故意的。因此,我们可以使用fail module而不是'failed_when',以便在控制台上打印消息:

我已经用"fail"模块更新了我们的playbook,并添加了一个"debug"模块来检查playbook是到达那个阶段还是在失败阶段退出。

--
 - name: Installing HTTPD
   hosts: server2
   become: true
   gather_facts: false
   vars:
     pkg: httpd
   tasks:
     - shell: rpm -q httpd | head -n 1
       args:
         warn: false
       register: result
     - fail:
         msg: "Failed because {{ pkg }} is already installed"
       when: "'not installed' not in result.stdout"
     - debug:
        msg: do we get here
     - yum:
        name: "{{ pkg }}"
        state: present

让我们来执行这个剧本:

[ansible@controller ~]$ ansible-playbook install-httpd.yml
PLAY [Installing HTTPD] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **
TASK [shell] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *****
changed: [server2]
TASK [fail] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **
fatal: [server2]: FAILED! => {"changed": false, "msg": "Failed because httpd is already installed"}
PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***
server2                    : ok=1    changed=1    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

因此,playbook已经退出了我们的自定义消息,而playbook的其余部分从未被调用。

使用更改的_when语句

我们将继续使用现有的"安装"-httpd.yml"剧本文件。如果我们观察上一个示例输出的"SUMMARY":

PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***
server2                    : ok=1    changed=1    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

它显示'changed=1',即使我们知道'nothing was changed'。playbook刚刚查询了一个"httpd"包,由于它已经处于安装状态,playbook被标记为失败,它退出了,没有执行任何进一步的任务。

有可能某个模块认为它更改了目标主机的状态,即当它报告"changed"状态时,但在这种情况下,由于我们知道作为此模块的一部分,没有任何更改,因此可以使用"changed:false"抑制该输出

以下是我们行动手册的更新版本:

--
 - name: Installing HTTPD
   hosts: server2
   become: true
   gather_facts: false
   vars:
     pkg: httpd
   tasks:
     - shell: rpm -q httpd | head -n 1
       args:
         warn: false
       register: result
       changed_when: false
     - fail:
         msg: "Failed because {{ pkg }} is already installed"
       when: "'not installed' not in result.stdout"
     - debug:
        msg: do we get here
     - yum:
        name: "{{ pkg }}"
        state: present

在这个剧本中,我为shell模块任务添加了changed:false。让我们来表演这个剧本:

[ansible@controller ~]$ ansible-playbook install-httpd.yml
PLAY [Installing HTTPD] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **
TASK [shell] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *****
ok: [server2]
TASK [fail] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **
fatal: [server2]: FAILED! => {"changed": false, "msg": "Failed because httpd is already installed"}
PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***
server2                    : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

因此,playbook执行被标记为预期的失败,并且"changed=0",因此在远程主机上没有做任何更改。

让我手动从server2中删除httpd包,看看playbook是否成功

[ansible@controller ~]$ ansible-playbook install-httpd.yml
PLAY [Installing HTTPD] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **
TASK [shell] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *****
ok: [server2]
TASK [fail] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **
skipping: [server2]
TASK [debug] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *****
ok: [server2] => {
    "msg": "do we get here"
}
TASK [yum] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***
changed: [server2]
PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***
server2                    : ok=3    changed=1    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

所以我们剩下的任务也会被执行,安装了"httpd",其中"changed=1"