参考:ansible笔记(29):条件判断与错误处理-朱双印博客
一、条件判断when
绝大多数语言中,都使用”if”作为条件判断的关键字,而在ansible中,条件判断的关键字是”when”,我们来看一个简单的示例,如下:
---
- hosts: test70
remote_user: root
tasks:
- debug:
msg: "System release is centos"
when: ansible_distribution == "CentOS"
如上例所示,使用when关键字指明条件,条件是ansible_distribution的值是CentOS,细心如你一定已经发现了,ansible_distribution就是facts信息中的一个key,通过ansible_distribution可以获取到目标主机系统的发行版,在之前的文章中,如果我们需要获取到facts中的key的值,都是通过引用变量的方式获取的,即”{{ key }}”,但是,在使用when关键字时,我们并没有为ansible_distribution添加”{{ }}”,没错,在when关键字中引用变量时,变量名不需要加”{{ }}”, 条件满足就执行这个模块。我们可以使用when关键字为任务指定条件,条件成立,则执行任务,条件不成立,则不执行任务
示例2:
---
- hosts: test70
remote_user: root
gather_facts: no
tasks:
- debug:
msg: "{{ item }}"
with_items:
- 1
- 2
- 3
when: item > 1
上例表示当item的值大于1时,才会调用debug模块输出对应的信息
在上述两个示例中,我们使用了 “==” 和 “>” 两个比较运算符,在ansible中,我们可以使用如下比较运算符。
== :比较两个对象是否相等,相等为真
!= :比较两个对象是否不等,不等为真
> :比较两个值的大小,如果左边的值大于右边的值,则为真
< :比较两个值的大小,如果左边的值小于右边的值,则为真
>= :比较两个值的大小,如果左边的值大于右边的值或左右相等,则为真
<= :比较两个值的大小,如果左边的值小于右边的值或左右相等,则为真
我们总结的这些运算符其实都是jinja2的运算符,ansible使用jinja2模板引擎,在ansible中也可以直接使用jinja2的这些运算符。
说完了比较运算符,再来说说逻辑运算符,可用的逻辑运算符如下
and :逻辑与,当左边与右边同时为真,则返回真
or :逻辑或,当左边与右边有任意一个为真,则返回真
not :取反,对一个操作体取反
( ) :组合,将一组操作体包装在一起,形成一个较大的操作体
我们来看一些关于逻辑运算符的示例,如下:
---
- hosts: test70
remote_user: root
tasks:
- debug:
msg: "System release is centos7"
when: ansible_distribution == "CentOS" and ansible_distribution_major_version == "7"
其实,当我们需要使用”逻辑与”时,除了使用”and”这种写法,还能够使用另一种”列表”的写法,示例如下(列表中同时成立才执行):
---
- hosts: test70
remote_user: root
tasks:
- debug:
msg: "System release is centos7"
when:
- ansible_distribution == "CentOS"
- ansible_distribution_major_version == "7"
当我们调用shell模块运行命令时,通常需要获取到shell模块的返回信息,以便之后的模块能够根据返回信息的值判断之后进行怎样的操作,示例如下,如下示例存在一个问题:
---
- hosts: test70
remote_user: root
tasks:
- name: task1
shell: "ls /testabc"
register: returnmsg
- name: task2
debug:
msg: "Command execution successful"
when: returnmsg.rc == 0
- name: task3
debug:
msg: "Command execution failed"
when: returnmsg.rc != 0
我们的想法是如果执行不成功,那就debug会输出Command execution failed"。但是ansible特性是执行失败就直接结束了。并不会执行后面的task2和task3
很简单,通过”ignore_errors”关键字即可实现这种效果,”ignore_errors”表示即使当前task执行报错,ansible也会忽略这个错误,继续执行playbook,示例如下:
---
- hosts: test70
remote_user: root
tasks:
- name: task1
shell: "ls /testabc"
register: returnmsg
ignore_errors: true
- name: task2
debug:
msg: "Command execution successful"
when: returnmsg.rc == 0
- name: task3
debug:
msg: "Command execution failed"
when: returnmsg.rc != 0
二、条件判断与tests
在linux中,我们可以使用test命令进行一些常用的判断操作,比如,使用test命令判断”/testdir”是否存在,示例如下
# test -e /testdir
# echo $?
0
其实,在ansible中,也有类似的用法,只不过ansible没有使用linux的test命令,而是使用了jinja2的tests,借助tests,我们可以进行一些判断操作,tests会将判断后的布尔值返回,如果条件成立,则返回true,如果条件不成立,tests会返回false,示例如下:
---
- hosts: test70
remote_user: root
gather_facts: no
vars:
testpath: /testdir
tasks:
- debug:
msg: "file exist"
when: testpath is exists
如上例所示,我们定义了一个testpath变量,这个变量的值是”/testdir”路径,我通过when判断”/testdir”路径是否存在,没错,就是这么简单,”is exists”中的”exists”就是tests的一种,它与”test -e”命令的作用是相同的,通过”exists”可以判断ansible主机中的对应路径是否存在(注意:是ansible控制主机中的路径,与目标主机没有关系),当对应的路径存在于ansible控制节点时,”is exists”为真,是不是很简单?
“is exists”可以在路径存在时返回真,但是有时,我们想要在路径不存在时返回真,我们该怎么办呢?我们可以使用”is not exists”,”is not exists”表示对应路径不存在时返回真
判断变量的一些tests
defined :判断变量是否已经定义,已经定义则返回真
undefind :判断变量是否已经定义,未定义则返回真
none :判断变量值是否为空,如果变量已经定义,但是变量值为空,则返回真
判断执行结果的一些tests
success 或 succeeded:通过任务的返回信息判断任务的执行状态,任务执行成功则返回真
failure 或 failed:通过任务的返回信息判断任务的执行状态,任务执行失败则返回真
change 或 changed:通过任务的返回信息判断任务的执行状态,任务执行状态为changed则返回真
skip 或 skipped:通过任务的返回信息判断任务的执行状态,当任务没有满足条件,而被跳过执行时,则返回真
示例:
---
- hosts: test70
remote_user: root
gather_facts: no
vars:
doshell: "yes"
tasks:
- shell: "cat /testdir/abc"
when: doshell == "yes"
register: returnmsg
ignore_errors: true
- debug:
msg: "success"
when: returnmsg is success
- debug:
msg: "failed"
when: returnmsg is failure
- debug:
msg: "changed"
when: returnmsg is change
- debug:
msg: "skip"
when: returnmsg is skip
判断路径的一些tests
注:如下tests的判断均针对于ansible主机中的路径,与目标主机无关
file : 判断路径是否是一个文件,如果路径是一个文件则返回真
directory :判断路径是否是一个目录,如果路径是一个目录则返回真
link :判断路径是否是一个软链接,如果路径是一个软链接则返回真
mount:判断路径是否是一个挂载点,如果路径是一个挂载点则返回真
exists:判断路径是否存在,如果路径存在则返回真
示例:
---
- hosts: test70
remote_user: root
gather_facts: no
vars:
testpath1: "/testdir/test"
testpath2: "/testdir/"
testpath3: "/testdir/testsoftlink"
testpath4: "/testdir/testhardlink"
testpath5: "/boot"
tasks:
- debug:
msg: "file"
when: testpath1 is file
- debug:
msg: "directory"
when: testpath2 is directory
- debug:
msg: "link"
when: testpath3 is link
- debug:
msg: "link"
when: testpath4 is link
- debug:
msg: "mount"
when: testpath5 is mount
- debug:
msg: "exists"
when: testpath1 is exists
判断字符串的一些tests
lower:判断包含字母的字符串中的字母是否是纯小写,字符串中的字母全部为小写则返回真
upper:判断包含字母的字符串中的字母是否是纯大写,字符串中的字母全部为大写则返回真
示例:
---
- hosts: test70
remote_user: root
gather_facts: no
vars:
str1: "abc"
str2: "ABC"
tasks:
- debug:
msg: "This string is all lowercase"
when: str1 is lower
- debug:
msg: "This string is all uppercase"
when: str2 is upper
判断整除的一些tests
even :判断数值是否是偶数,是偶数则返回真
odd :判断数值是否是奇数,是奇数则返回真
divisibleby(num) :判断是否可以整除指定的数值,如果除以指定的值以后余数为0,则返回真
其他的一些testst
version:可以用于对比两个版本号的大小,或者与指定的版本号进行对比,使用语法为 version(‘版本号’, ‘比较操作符’)
示例:
---
- hosts: test70
remote_user: root
vars:
ver: 7.4.1708
ver1: 7.4.1707
tasks:
- debug:
msg: "This message can be displayed when the ver is greater than ver1"
when: ver is version(ver1,">")
- debug:
msg: "system version {{ansible_distribution_version}} greater than 7.3"
when: ansible_distribution_version is version("7.3","gt")
上例中有两个task
第一个task中,当ver的版本号大于ver1时,返回真,条件成立,debug模块输出”This message can be displayed when the ver is greater than ver1″
第二个task中,当facts中的ansible_distribution_version的值大于7.3时,返回真,条件成立,debug模块输出对应信息
细心如你一定发现了,”>”与”gt”都表示”大于”,当使用version时,支持多种风格的比较操作符,你可以根据自己的使用习惯进行选择,version支持的比较操作符如下
大于:>, gt
大于等于:>=, ge
小于:<, lt
小于等于:<=, le
等于: ==, =, eq
不等于:!=, <>, ne
subset:判断一个list是不是另一个list的子集,是另一个list的子集时返回真
superset : 判断一个list是不是另一个list的父集,是另一个list的父集时返回真
注:2.5版本中上述两个tests从issubset和issuperset更名为subset和superset
示例如下
---
- hosts: test70
remote_user: root
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)
string:判断对象是否是一个字符串,是字符串则返回真
---
- hosts: test70
remote_user: root
gather_facts: no
vars:
testvar1: 1
testvar2: "1"
testvar3: a
tasks:
- debug:
msg: "This variable is a string"
when: testvar1 is string
- debug:
msg: "This variable is a string"
when: testvar2 is string
- debug:
msg: "This variable is a string"
when: testvar3 is string
上例playbook中只有testvar2和testvar3会被判断成字符串,testvar1不会
number:判断对象是否是一个数字,是数字则返回真
示例如下
---
- hosts: test70
remote_user: root
gather_facts: no
vars:
testvar1: 1
testvar2: "1"
testvar3: 00.20
tasks:
- debug:
msg: "This variable is number"
when: testvar1 is number
- debug:
msg: "This variable is a number"
when: testvar2 is number
- debug:
msg: "This variable is a number"
when: testvar3 is number
上例playbook中只有testvar1和testvar3会被判断成数字,testvar2不会
三、block
前文中,我们使用”when”关键字对条件进行判断,如果条件成立,则执行对应的任务,但是,细心如你一定已经发现了,当条件成立时,我们只能执行一个任务,如果我们想要在条件成立时,执行三个任务,该怎么办呢?难道我们要在这三个任务的每个任务中都加入相同的条件判断么?这种方法也太麻烦了,显然应该有更好的方法,没错,我们可以借助”block”解决这个小问题。
在ansible中,可以使用”block”关键字将多个任务整合成一个”块”,这个”块”将被当做一个整体,我们可以对这个”块”添加判断条件,当条件成立时,则执行这个块中的所有任务,我们来看一个小示例,如下:
---
- hosts: test70
remote_user: root
tasks:
- debug:
msg: "task1 not in block"
- block:
- debug:
msg: "task2 in block1"
- debug:
msg: "task3 in block1"
when: 2 > 1
task2与task3在一个block中
block除了整合多个task与when连用外,还可以支持错误处理。rescue
---
- hosts: test70
remote_user: root
tasks:
- block:
- shell: 'ls /ooo'
rescue:
- debug:
msg: 'I caught an error'
block中有多个任务时,这种优势就比较明显了,我们来看一个小示例,如下(任何一个出错就报错):
---
- hosts: test70
remote_user: root
tasks:
- block:
- shell: 'ls /opt'
- shell: 'ls /testdir'
- shell: 'ls /c'
rescue:
- debug:
msg: 'I caught an error'
你一定已经理解了,我们来扩展一下,上例中只使用到了block与rescue关键字,其实,我们还能够再加入always关键字,加入always关键字以后,无论block中的任务执行成功还是失败,always中的任务都会被执行,示例如下:
---
- hosts: test70
remote_user: root
tasks:
- block:
- debug:
msg: 'I execute normally'
- command: /bin/false
- debug:
msg: 'I never execute, due to the above task failing'
rescue:
- debug:
msg: 'I caught an error'
- command: /bin/false
- debug:
msg: 'I also never execute'
always:
- debug:
msg: "This always executes"
如上例所示,block中有多个任务,rescue中也有多个任务,上例中故意执行”/bin/false”命令,模拟任务出错的情况,当block中的’/bin/false’执行后,其后的debug任务将不会被执行,因为’/bin/false’模拟出错,出错后直接执行rescue中的任务,在执行rescue中的任务时,会先输出 ‘I caught an error’,然后又在rescue中使用’/bin/false’模拟出错的情况,出错后之后的debug任务不会被执行,直接执行always中的任务,always中的任务一定会被执行,无论block中的任务是否出错
四、fail模块
在编写shell脚本时,有可能会有这样的需求,当脚本执行到某个阶段时,需要对某个条件进行判断,如果条件成立,则立即终止脚本的运行,在shell脚本中实现这个需求很简单,只需要在条件成立时调用”exit”命令即可终止脚本的运行, 那么在编写playbook时,如果有类似的需求,我们该怎么办呢?
想要在playbook中按照我们的意愿中断剧本的执行,其实也很简单,我们只需要借助一个模块即可完成,这个模块就是”fail”模块。
我们知道,在执行playbook时,如果playbook中的任何一个任务执行失败,playbook都会停止运行,除非这个任务设置了”ignore_errors: true”,在任务没有设置”ignore_errors: yes”的情况下,任务执行失败后,playbook就会自动终止,而fail模块天生就是一个用来”执行失败”的模块,当fail模块执行后,playbook就会认为有任务失败了,从而终止运行,实现我们想要的中断效果,来看一个小示例:
---
- hosts: all
remote_user: root
tasks:
- debug:
msg: "1"
- debug:
msg: "2"
- fail:
- debug:
msg: "3"
- debug:
msg: "4"
执行结果:
[root@ansible-master ~]# ansible-playbook if.yml
PLAY [all] **************************************************************************************
TASK [Gathering Facts] **************************************************************************
ok: [192.168.174.145]
ok: [192.168.174.144]
TASK [debug] ************************************************************************************
ok: [192.168.174.144] => {
"msg": "1"
}
ok: [192.168.174.145] => {
"msg": "1"
}
TASK [debug] ************************************************************************************
ok: [192.168.174.144] => {
"msg": "2"
}
ok: [192.168.174.145] => {
"msg": "2"
}
TASK [fail] *************************************************************************************
fatal: [192.168.174.144]: FAILED! => {"changed": false, "msg": "Failed as requested from task"}
fatal: [192.168.174.145]: FAILED! => {"changed": false, "msg": "Failed as requested from task"}
PLAY RECAP **************************************************************************************
192.168.174.144 : ok=3 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
192.168.174.145 : ok=3 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
[root@ansible-master ~]#
从上图可以看出,当前两个debug模块输出了对应的信息后,playbook报错了,之后的debug模块并未被调用,实现了中断剧本运行的效果,当执行fail模块时,fail模块默认的输出信息为’Failed as requested from task’,我们可以通过fail模块的msg参数自定义报错的信息,示例如下:
---
- hosts: test70
remote_user: root
tasks:
- debug:
msg: "1"
- fail:
msg: "Interrupt running playbook"
- debug:
msg: "2"
当然,上述示例只是为了初步介绍fail模块的用法,我们通常并不会毫无理由的想要去中断playbook,通常需要对某些条件进行判断,如果条件满足,则中断剧本,所以,fail模块通常与when结合使用,比如,如果之前模块执行后的标准输出信息中包含字符串’error’,则认为中断剧本的条件成立,就立即调用fail模块,以终断playbook,示例如下:
---
- hosts: test70
remote_user: root
tasks:
- shell: "echo 'This is a string for testing--error'"
register: return_value
- fail:
msg: "Conditions established,Interrupt running playbook"
when: "'error' in return_value.stdout"
- debug:
msg: "I never execute,Because the playbook has stopped"
或者:
when: ' "successful" not in return_value.stdout ' when: " 'successful' not in return_value.stdout "
五、failed_when
看完上述示例,你一定已经明白了怎样在条件满足时终止playbook,其实,还有另一种方法可以实现类似的效果,我们可以借助’failed_when’关键字来完成类似功能,’failed_when’的作用就是,当对应的条件成立时,将对应任务的执行状态设置为失败,这样说可能不是特别容易理解,不如先来看一个小示例,如下:
---
- hosts: test70
remote_user: root
tasks:
- debug:
msg: "I execute normally"
- shell: "echo 'This is a string for testing error'"
register: return_value
failed_when: ' "error" in return_value.stdout'
- debug:
msg: "I never execute,Because the playbook has stopped"
但是需要注意的时,’ failed_when’虽然会将任务的执行状态设置为失败,但是并不代表任务真的失败了,就以上例来说,上例的shell模块的确是完全正常的执行了,只不过在执行之后,’ failed_when’对应的条件成立了,’ failed_when’将shell模块的执行状态设置为失败而已,所以,’ failed_when’并不会影响shell模块的执行过程,只会在条件成立时影响shell模块最终的执行状态,以便停止playbook的运行。
六、changed_when
理解了’ failed_when’关键字以后,顺势理解’changed_when’关键字就容易多了。
‘ failed_when’关键字的作用是在条件成立时,将对应任务的执行状态设置为失败
‘changed_when’关键字的作用是在条件成立时,将对应任务的执行状态设置为changed
示例:
---
- hosts: test70
remote_user: root
tasks:
- debug:
msg: "test message"
changed_when: 2 > 1
执行结果:
[root@ansible-master ~]# ansible-playbook if.yml
PLAY [all] **************************************************************************************
TASK [Gathering Facts] **************************************************************************
ok: [192.168.174.144]
ok: [192.168.174.145]
TASK [debug] ************************************************************************************
changed: [192.168.174.144] => {
"msg": "test message"
}
changed: [192.168.174.145] => {
"msg": "test message"
}
PLAY RECAP **************************************************************************************
192.168.174.144 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
192.168.174.145 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[root@ansible-master ~]#
我们知道,debug模块在正常执行的情况下只能是”ok”状态,上例中,我们使用’changed_when’关键字将debug模块的执行后的状态定义为了”changed”
总结过handlers的用法,我们知道,只有任务作出了实际的操作时(执行后状态为changed),才会真正的执行对应的handlers,而在某些时候,如果想要通过任务执行后的返回值将任务的最终执行状态判定为changed,则可以使用’changed_when’关键字,以便条件成立时,可以执行对应的handlers,其实,’changed_when’除了能够在条件成立时将任务的执行状态设置为”changed”,还能让对应的任务永远不能是changed状态,示例如下:
---
- hosts: test70
remote_user: root
tasks:
- shell: "ls /opt"
changed_when: false
当将’changed_when’直接设置为false时,对应任务的状态将不会被设置为’changed’,如果任务原本的执行状态为’changed’,最终则会被设置为’ok’,所以,上例playbook执行后,shell模块的执行状态最终为’ok’