ansible的条件判断复杂用法

一、条件判断与tests

在shell当中,我们可使用test命令来进行一些常用的判断操作,如下:

# 判断/test文件是否存在
test -e /test

# 判断/testdir是否存在且为一个目录
test -d /testdir

事实上,在ansible中也有类似的用法,只不过ansible没有使用linux的test命令,而是jinja2模板的tests。

下面是一个简单示例:

# 通过条件语句判断testpath的路径是否存在
- hosts: test
  vars:
    testpath: /testdir
  tasks:
    - debug:
        msg: "file exist"
      when: testpath is exists

上面的示例中,我们使用了is exists用于路径存在时返回真,也可以使用is not exists用于路径不存在时返回真。也可以在整个条件表达式的前面使用not以取反:

- hosts: test
  vars:
    testpath: /testdir1
  tasks:
    - debug:
        msg: "file not exist"
      when: not testpath is exists

在ansible中,除了能够使用exists这种tests之外,还有一些别的tests。接下来我们详细说一说。

1.1 判断变量

  • defined:判断变量是否已定义,已定义则返回真
  • undefined:判断变量是否未定义,未定义则返回真
  • none:判断变量的值是否为空,如果变量已定义且值为空,则返回真

示例:

- hosts: test
  gather_facts: no
  vars:
    testvar: "test"
    testvar1:
  tasks:
    - debug:
        msg: "testvar is defined"
      when: testvar is defined
    - debug:
        msg: "testvar2 is undefined"
      when: testvar2 is undefined
    - debug:
        msg: "testvar1 is none"
      when: testvar1 is none

1.2 判断执行结果

  • sucess或succeeded:通过任务执行结果返回的信息判断任务的执行状态,任务执行成功则返回true
  • failure或failed:任务执行失败则返回true
  • change或changed:任务执行状态为changed则返回true
  • skip或skipped:任务被跳过则返回true

示例:

- hosts: test
  gather_facts: no
  vars:
    doshell: true
  tasks:
    - shell: 'cat /testdir/aaa'
      when: doshell
      register: result
      ignore_errors: true
    - debug:
        msg: "success"
      when: result is success

    - debug:
        msg: "failed"
      when: result is failure

    - debug:
        msg: "changed"
      when: result is change

    - debug:
        msg: "skip"
      when: result is skip

1.3 判断路径

  • file:判断指定路径是否为一个文件,是则为真
  • directory:判断指定路径是否为一个目录,是则为真
  • link:判断指定路径是否为一个软链接,是则为真
  • mount:判断指定路径是否为一个挂载点,是则为真
  • exists:判断指定路径是否存在,存在则为真

特别注意:关于路径的所有判断均是判断主控端上的路径,而非被控端上的路径

示例:

- hosts: test
  gather_facts: no
  vars:
    testpath1: "/testdir/test"
    testpath2: "/testdir"
  tasks:
    - debug:
        msg: "file"
      when: testpath1 is file
    - debug:
        msg: "directory"
      when: testpath2 is directory

1.4 判断字符串

  • lower:判断字符串中的所有字母是否都是小写,是则为真
  • upper:判断字符串中的所有字母是否都是大写,是则为真
- hosts: test
  gather_facts: no
  vars: 
    str1: "abc"
    str2: "ABC"
  tasks:
    - debug:
        msg: "str1 is all lowercase"
      when: str1 is lower
    - debug:
        msg: "str2 is all uppercase"
      when: str2 is upper

1.5 判断整除

  • even:判断数值是否为偶数,是则为真
  • odd:判断数值是否为奇数,是则为真
  • divisibleby(num):判断是否可以整除指定的数值,是则为真

示例:

- hosts: test
  gather_facts: no
  vars: 
    num1: 6
    num2: 8 
    num3: 15
  tasks:
    - debug: 
        msg: "num1 is an even number"
      when: num1 is even
    - debug:
        msg: "num2 is an odd number"
      when: num2 is odd
    - debug:
        msg: "num3 can be divided exactly by"
      when: num3 is divisibleby(3)

1.6 其他tests

1、version

# 可用于对比两个版本号的大小,或者与指定的版本号进行对比,
# 使用语法为version("版本号","比较操作符")
- hosts: test
  vars:
    ver1: 1.2
    ver2: 1.3
  tasks:
    - debug:
        msg: "ver1 is greater than ver2"
      when: ver1 is version(ver2,">")
    - debug:
        msg: "system version {{ ansible_distribution_version }} greater than 7.3"
      when: ansible_distribution_version is version("7.3","gt")

version中使用的比较运算符说明:

  • 大于: >, gt
  • 大于等于: >=, ge
  • 小于: <, lt
  • 小于等于: <=, le
  • 等于: =, ==, eq
  • 不等于: !=, <>, ne

2、subset 判断一个list是不是另一个list的子集

3、superset 判断一个list是不是另一个list的父集"

- hosts: test
  gather_facts: no
  vars:
    a:
      - 2
      - 5
    b: [1,2,3,4,5]
  tasks:
    - debug:
        msg: "A is a subset of B"
      when: a is subset(b)
    - debug:
        msg: "B is the parent set of A"
      when: b is superset(a)

4、in 判断一个字符串是否存在于另一个字符串中,也可用于判断某个特定的值是否存在于列表中

- hosts: test
  vars:
    supported_distros:
      - RedHat
      - CentOS
  tasks:
    - debug:
        msg: "{{ ansible_distribution }} in supported_distros"
      when: ansible_distribution in supported_distros

5、string 判断对象是否为一个字符串,是则为真

6、number 判断对象是否为一个数字,是则为真

- hosts: test
  gather_facts: no
  vars:
    var1: 1
    var2: "1"
    var3: a
  tasks:
    - debug:
        msg: "var1 is a number"
      when: var1 is number
    - debug:
        msg: "var2 is a string"
      when: var2 is string
    - debug:
        msg: "var3 is a string"
      when: var3 is string

二、条件判断与block

2.1 block

我们在前面使用when做条件判断时,如果条件成立则执行对应的任务。但这就面临一个问题,当我们要使用同一个条件判断执行多个任务的时候,就意味着我们要在某一个任务下面都写一下when语句,而且判断条件完全一样。这种方式不仅麻烦而且显得low。Ansible提供了一种更好的方式来解决这个问题,即block。在ansible中,使用block将多个任务进行组合,当作一个整体。我们可以对这一个整体做条件判断,当条件成立时,则执行块中的所有任务:

- hosts: test
  tasks:
    - debug:
        msg: "task1 not in block"
    - block:
        - debug:
            msg: "task2 in block1"
        - debug:
            msg: "task3 in block1"
      when: 2 > 1

下面是一个稍微有用点儿的例子:

- hosts: test
  tasks:
    - name: set /etc/resolv.conf
      template: 
        src: resolv.conf.j2 
        dest: /etc/resolv.conf 
        owner: root 
        group: root 
        mode: 0644
    - block:
        - name: ensure /etc/resolvconf/resolv.conf.d/base file for ubuntu 16.04
          template: 
            src: resolv.conf.j2
            dest: /etc/resolvconf/resolv.conf.d/base

        - name: config dns for ubuntu 16.04
          template: 
            src: resolv.conf.j2
            dest: /etc/resolv.conf
      when: ansible_distribution == "Ubuntu" and ansible_distribution_major_version == "16" 

使用block注意事项:
1、可以为block定义name(ansible 2.3增加的特性)
2、可以直接对block使用when,但不能直接对block使用loop

2.2 rescue

block除了能和when一起使用之外,还能作错误处理。这个时候就需要用到rescue关键字:

- hosts: test
  tasks:
    - block:
        - shell: 'ls /testdir'
      rescue:
        - debug:
            msg: '/testdir is not exists'

在上面的例子中,当block中的任务执行失败时,则运行rescue中的任务。如果block中的任务正常执行,则rescue的任务就不会被执行。如果block中有多个任务,则任何一个任务执行失败,都会执行rescue。block中可以定义多个任务,同样rescue当中也可以定义多个任务。#

2.3 always

当block执行失败时,rescue中的任务才会被执行;而无论block执行成功还是失败,always中的任务都会被执行:

- hosts: test
  tasks:
    - block:
        - shell: 'ls /testdir'
      rescue:
        - debug:
            msg: '/testdir is not exists'
      always:
        - debug:
            msg: 'This task always executes'

三、条件判断与错误处理

在上面讲block的使用方法的时候,我们说block除了可以将多个任务组合到一起,还有错误处理的功能。接下来我们继续说一说错误处理。

3.1 fail模块

在shell中,可能会有这样的需求:当脚本执行至某个阶段时,需要对某个条件进行判断,如果条件成立,则立即终止脚本的运行。在shell中,可以直接调用"exit"即可执行退出。事实上,在playbook中也有类似的模块可以做这件事。即fail模块。Ansible 的 fail 模块可以在特定条件下停止在某个主机上的执行,并继续在其它主机上执行。通常与条件语句组合使用,当满足条件时,终止当前play的运行。选项只有一个:

  • msg:终止前打印出信息

示例

# 使用fail模块中断playbook输出
- hosts: test
  tasks:
    - shell: echo "Just a test--error" 
      register: result
    - fail:
        msg: "Conditions established,Interrupt running playbook"
      when: "'error' in result.stdout"
    - debug:
        msg: "Inever execute,Because the playbook has stopped"

3.2 failed_when

事实上,当fail和when组合使用的时候,还有一个更简单的写法,即failed_when,当满足某个条件时,ansible主动触发失败。

# 如果在command_result存在错误输出,且错误输出中,包含了`FAILED`字串,即返回失败状态:
- name: this command prints FAILED when it fails
  command: /usr/bin/example-command -x -y -z
  register: command_result
  failed_when: "'FAILED' in command_result.stderr"

也可以直接通过fail模块和when条件语句,写成如下:

- name: this command prints FAILED when it fails
  command: /usr/bin/example-command -x -y -z
  register: command_result
  ignore_errors: True

- name: fail the play if the previous command did not succeed
  fail: msg="the command failed"
  when: " command_result.stderr and 'FAILED' in command_result.stderr"

ansible一旦执行返回失败,后续操作就会中止,所以failed_when通常可以用于满足某种条件时主动中止playbook运行的一种方式。ansible默认处理错误的机制是遇到错误就停止执行。但有些时候,有些错误是计划之中的。我们希望忽略这些错误,以让playbook继续往下执行。这个时候就可以使用ignore_errors忽略错误,从而让playbook继续往下执行

3.3 changed_when

当我们控制一些远程主机执行某些任务时,当任务在远程主机上成功执行,状态发生更改时,会返回changed状态响应,状态未发生更改时,会返回OK状态响应,当任务被跳过时,会返回skipped状态响应。我们可以通过changed_when来手动更改changed响应状态。示例如下:

- shell: /usr/bin/billybass --mode="take me to the river"
register: bass_result
changed_when: "bass_result.rc != 2"    #只有该条task执行以后,bass_result.rc的值不为2时,才会返回changed状态

# this will never report 'changed' status
- shell: wall 'beep'
  changed_when: False    #当changed_when为false时,该条task在执行以后,永远不会返回changed状态

3.4 断言:assert模块

对于当满足某某条件时就失败的逻辑,可以使用fail模块加when指令来实现,也可使用更为直接的assert模块进行断言。

例如:

---
- hosts: localhost
  gather_facts: no
  tasks:
    - assert:
        that:
          - 100 > 20
          - 200 > 200
      fail_msg: "oh, not me"
      success_msg: "oh, it's me"

其中that参数接收一个列表,用于定义一个或多个条件,如果条件全为true,则任务成功,只要有一个条件为false,则任务失败。fail_msg(或其别名参数msg)定义任务失败时的信息,success_msg定义任务成功时的信息。# 5.5 any_errors

3.5 any_errors_fatal

如果想让某个失败的任务直接导致整个play的失败,可在play级别使用any_errors_fatal指令。

---
- hosts: nginx
  gather_facts: no
  any_errors_fatal: true
  tasks:
    - fail:
        msg: "oh, not me"
      when: inventory_hostname == groups['nginx'][0]
    - debug:
        msg: "hello"

- hosts: localhost
  gather_facts: no
  tasks:
    - debug:
        msg: "HELLO WORLD"

将any_errors_fatal设置为true后,nginx组第一个节点只要一开始执行fail任务,整个playbook中所有后续任务都将不再执行,就连其它play也一样不执行。注意观察playbook的执行结果,它将提示"NO MORE HOSTS LEFT":

........
TASK [fail] *********************
fatal: [192.168.200.42]: FAILED! => {"changed": false, "msg": "oh, not me"}
skipping: [192.168.200.43]
skipping: [192.168.200.44]

NO MORE HOSTS LEFT **************

PLAY RECAP *************
.........

对比

# 1、fail 模块:Ansible 的 fail 模块可以在特定条件下停止在某个主机上的执行,
并继续在其它主机上执行。
# 2、any_errors_fatal:这个参数是对整个剧本的设置,如果某个任务失败并触发了any_errors_fatal,
那么整个剧本将在所有主机上停止执行,包括后续的任务和剧本,这无疑是一个更严格的错误处理方式。

3.6 max_fail_percentage

。。。

四、在循环语句中使用条件语句

# 只打印大于5的值
tasks:
    - command: echo {{ item }}
      loop: [ 0, 2, 4, 6, 8, 10 ]
      when: item > 5
# 确保将mariadb-server安装到根分区且根分区的可用空间要大于300M
- name: install mariadb-server if enough space on root
  yum: 
    name: mariadb-server
    state;拉特st
  loop: "{{ ansible_mounts }}"
  when: item.mount == "/" and item.size_available > 300000000
上一篇
下一篇
Copyright © 2022 Egon的技术星球 egonlin.com 版权所有 帮助IT小伙伴学到真正的技术