使用Ansible条件语句
控制结构指的是对程序执行流有影响的任何东西。控制结构主要有以下两种类型:
有条件的
迭代
有时,我们需要根据变量的值、平台的类型甚至其他命令的结果有条件地执行代码。有时我们还需要迭代多个对象,例如列表哈希或者多级变量。
在本节中,我们将介绍以下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.yml
playbook我定义了一个条件,在第一个任务中使用'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"