[TOC]

条件判断

描述:在高级语言中条件判断是不可或缺的,同样我们也可以在ansible中条件判断的用户;
注意:绝大多数语言中都使用”if”作为条件判断的关键字,而在ansible中条件判断的关键字是"when"

then 关键字

描述:在ansible中采用使用when关键字指明条件;我们可以使用如下运算符。

#比较运算符
== :比较两个对象是否相等,相等为真
!= :比较两个对象是否不等,不等为真
> :比较两个值的大小,如果左边的值大于右边的值,则为真
< :比较两个值的大小,如果左边的值小于右边的值,则为真
>= :比较两个值的大小,如果左边的值大于右边的值或左右相等,则为真
<= :比较两个值的大小,如果左边的值小于右边的值或左右相等,则为真

#比较运算符
and :逻辑与,当左边与右边同时为真,则返回真
or :逻辑或,当左边与右边有任意一个为真,则返回真
not :取反,对一个操作体取反
( ) :组合,将一组操作体包装在一起,形成一个较大的操作体


# 判断路径和文件
# 注:如下tests的判断均针对于ansible主机中的路径,与目标主机无关
file : 判断路径是否是一个文件,如果路径是一个文件则返回真
directory :判断路径是否是一个目录,如果路径是一个目录则返回真
link :判断路径是否是一个软链接,如果路径是一个软链接则返回真
mount:判断路径是否是一个挂载点,如果路径是一个挂载点则返回真
exists:判断路径是否存在,如果路径存在则返回真
"is exists" 可以在路径存在时返回真
"is not exists" 表示对应路径不存在时返回真
"not 变量 is exists" 表示对应路径不存在时返回真


#判断变量关键字
defined :判断变量是否已经定义,已经定义则返回真
undefind :判断变量是否已经定义,未定义则返回真
none :判断变量值是否为空,如果变量已经定义但是变量值为空则返回真


#判断执行结果关键字
success 或 succeeded:通过任务的返回信息判断任务的执行状态,任务执行成功则返回真
failure 或 failed:通过任务的返回信息判断任务的执行状态,任务执行失败则返回真
change 或 changed:通过任务的返回信息判断任务的执行状态,任务执行状态为changed则返回真
skip 或 skipped:通过任务的返回信息判断任务的执行状态,当任务没有满足条件而被跳过执行时则返回真

#判断字符串关键字
string:判断对象是否是一个字符串,是字符串则返回真
lower:判断包含字母的字符串中的字母是否是纯小写,字符串中的字母全部为小写则返回真
upper:判断包含字母的字符串中的字母是否是纯大写,字符串中的字母全部为大写则返回真

#判断整除的关键字
number:判断对象是否是一个数字,是数字则返回真
even :判断数值是否是偶数,是偶数则返回真
odd :判断数值是否是奇数,是奇数则返回真
divisibleby(num) :判断是否可以整除指定的数值,如果除以指定的值以后余数为0,则返回真

#other关键字
version('版本号', '比较操作符'):可以用于对比两个版本号的大小,或者与指定的版本号进行对比
version支持的比较操作符如下
大于:>, gt
大于等于:>=, ge
小于:<, lt
小于等于:<=, le
等于: ==, =, eq
不等于:!=, <>, ne

#判断包含非包含关键字
subset:判断一个list是不是另一个list的子集,是另一个list的子集时返回真
superset : 判断一个list是不是另一个list的父集,是另一个list的父集时返回真
#注:2.5版本中上述两个tests从issubset和issuperset更名为subset和superset

总结:ansible使用jinja2模板引擎,这些运算符其实都是jinja2的运算符,在ansible中也可以直接使用jinja2的这些运算符。


比如:ansible_distribution就是facts信息中的一个key,通过ansible_distribution可以获取到目标主机系统的发行版]

ansible local -m setup -a "filter=ansible_distribution"
local | SUCCESS => {
"ansible_facts": {
"ansible_distribution": "CentOS",
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false
}

基础示例:

# cat > then.yml<<END
---
- hosts: local
remote_user: root
tasks:
- name: "Demo 1-Method 1"
debug:
msg: "Method1-System release is Centos7"
when: ansible_distribution == "CentOS" and ansible_distribution_major_version == "7" #方式1
- name: "Demo 1-Method 2"
debug:
msg: "Method2-System release is Centos7"
when:
- ansible_distribution == "CentOS" #方式2
- ansible_distribution_major_version == "7"
- name: "Demo 3"
debug:
msg: "{{ item }}"
with_items:
- 1
- 2
- 3
- 4
when: item > 1 and ( item == 3 or item ==5) #列表中的所有条件同时成立时,对应的任务才会执行
- name: "Demo 4"
debug:
msg: "System release is not centos"
when: not ansible_distribution == "CentOS" #关键点
END

执行结果:

#TASK [Demo 1-Method 1]
ok: [local] => {"msg": "Method1-System release is Centos7"}

#TASK [Demo 1-Method 2]
ok: [local] => {"msg": "Method2-System release is Centos7"}

#TASK [Demo 3]
skipping: [local] => (item=1)
skipping: [local] => (item=2)
ok: [local] => (item=3) => {"msg": 3}
skipping: [local] => (item=4)


在linux中我们可以使用test命令进行一些常用的判断操作;比如使用test命令判断”/testdir”是否存在

  • 如果”/testdir”存在则返回true,如果”/testdir”不存在则返回false,而在linux中命令的返回值为0表示true,返回值为非0表示false
    #--- shell
    # test -e /testdir
    # echo $?
    0

    #--- shell 脚本
    #!/bin/bash
    if test -e /testdir; then
    echo "testdir exist"
    fi

在ansible中也有运算标识符与”test -e”命令的作用是相同的,通过exsts可以判断ansible主机中的对应路径是否存在(注意:是ansible控制主机中的路径,与目标主机没有关系)

# cat >whenIsExists.yml<<END
---
- hosts: local
remote_user: root
gather_facts: no
vars:
testpath1: /tmp/login.txt
testpath2: /var/log
tasks:
- name: "Demo then file/directory"
debug:
msg: "testpath1 file"
when: testpath1 is file
- debug:
msg: "testpath2 directory"
when: testpath2 is directory
- debug:
msg: "link"
when: testpath2 is link
- debug:
msg: "link"
when: testpath2 is link
- debug:
msg: "mount"
when: testpath1 is mount
- debug:
msg: "file {{ testpath1 }} not exist"
when: testpath1 is not exists
- debug:
msg: "file {{ testpath1 }} is exist"
when: testpath1 is exists
END

执行结果:

#TASK [Demo then file/directory] 
ok: [local] => {"msg": "testpath1 file"}

#TASK [debug]
ok: [local] => {"msg": "testpath2 directory"}

#TASK [debug]
skipping: [local]

#TASK [debug]
ok: [local] => {"msg": "file /tmp/login.txt is exist"}



其他的一些值得学习的test案例:

# cat > whenOther.yml<<END
---
- hosts: local
remote_user: root
gather_facts: no
vars:
testvar: "test"
testvar1:
testpath: /bin/bash
teststr: "thisisstringdemo"
testnum: 1024
a:
- 2
- 5
b: [1,2,3,4,5]
ver: 7.4.1708
tasks:
- name: "Demo then defined"
debug:
msg: "Variable is defined"
when: testvar is defined #关键点
- name: "Demo then undefined or none"
debug:
msg: "Variable is undefined or is none"
when: (testvar2 is undefined) or (testvar1 is none) #关键点
#分割线----------------------------------------
- name: "Demo then success|failure|change|skip"
shell: "cat /testdir/abc"
when: testvar == "test"
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
#分割线----------------------------------------
- name: "Demo then String"
debug:
msg: "String = {{teststr}} ,but is lower"
when: (teststr is lower) and (teststr is string)
#分割线----------------------------------------
- name: "Demo then number"
debug:
msg: "Number = {{testnum}} ,but is odd" #odd奇数 / even偶数
when: (testnum is number) and (testnum is even)
- debug:
msg: "Can be divided exactly by 2" #是否能被设置数整除
when: testnum is divisibleby(2)
#分割线----------------------------------------
- name: "Demo then super set"
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)
#分割线----------------------------------------
- name: "Demo then version"
debug:
msg: "This message can be displayed when the ver is greater than 7.3"
when: (ver is version(7.3,"gt")) and (ver is version(7.3,">"))
END

执行结果:

local: ok=11 changed=1 unreachable=0  failed=0 skipped=2 rescued=0 ignored=1

#TASK [Demo then defined]
ok: [local] => {"msg": "Variable is defined"}
#TASK [Demo then undefined or none]
ok: [local] => {"msg": "Variable is undefined or is none"}

#TASK [Demo then success|failure|change|skip]
fatal: [local]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": true, "cmd": "cat /testdir/abc", "delta": "0:00:00.002826", "end": "2019-08-06 10:49:00.620395", "msg": "non-zero return code", "rc": 1, "start": "2019-08-06 10:49:00.617569", "stderr": "cat: /testdir/abc: No such file or directory", "stderr_lines": ["cat: /testdir/abc: No such file or directory"], "stdout": "", "stdout_lines": []}
...ignoring

#TASK [debug-success]
skipping: [local]
#TASK [debug]
ok: [local] => {"msg": "failed"}
#TASK [debug]
ok: [local] => { "msg": "changed"}
#TASK [debug-skip]
skipping: [local]

#TASK [Demo then String]
ok: [local] => {"msg": "String = thisisstringdemo ,but is lower"}

#TASK [Demo then number]
ok: [local] => {"msg": "Number = 1024 ,but is odd"}
TASK [debug]
ok: [local] => { "msg": "Can be divided exactly by 2"}

#TASK [Demo then super set]
ok: [local] => {"msg": "A is a subset of B"}
#TASK [debug]
ok: [local] => {"msg": "B is the parent set of A"}

#TASK [Demo then version]
# ok: [local] => {"msg": "This message can be displayed when the ver is greater than 7.3"}

总结:

  • 在when关键字中引用变量时,变量名不需要加双大括号{},


错误处理

ignore_errors 关键字

描述:”ignore_errors”表示即使当前task执行报错,ansible也会忽略这个错误继续执行playbook;

当我们调用shell模块运行命令时,通常需要获取到shell模块的返回信息以便之后的模块能够根据返回信息的值判断之后进行怎样的操作:
比如:下面shell模块在远程主机test70中执行命令 “ls /testabc”,我们将shell模块的返回值注册到了变量returnmsg,然后通过returnmsg获取到了命令执行的返回码,如果返回码为0则证明命令完全正常执行,如果返回码不为0则证明命令执行时出错了;

# cat > ignore_error.yml<<END
---
- hosts: local
remote_user: root
tasks:
- name: task1
shell: "ls /testabc"
ignore_errors: true #关键点,表示shell模块执行报错后,ansible会忽略报错,继续执行之后的task。
register: returnmsg
- name: task2
debug:
msg: "Command execution successful"
when: returnmsg.rc == 0 #关键点
- name: task3
debug:
msg: "Command execution failed"
when: returnmsg.rc != 0 #关键点
END
```
执行结果:
```bash
#TASK [task1]
fatal: [local]: FAILED! => {"changed": true, "cmd": "ls /testabc", "delta": "0:00:00.004237", "end": "2019-08-06 09:27:49.566974", "msg": "non-zero return code", "rc": 2, "start": "2019-08-06 09:27:49.562737", "stderr": "ls: cannot access /testabc: No such file or directory", "stderr_lines": ["ls: cannot access /testabc: No such file or directory"], "stdout": "", "stdout_lines": []}
...ignoring

#TASK [task2]
skipping: [local] #会跳过错误

#TASK [task3]
ok: [local] => { "msg": "Command execution failed" }

#PLAY RECAP
local : ok=3 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=1


block 关键字

描述:我们使用”when”关键字对条件进行判断,如果条件成立则执行对应的任务,但是when当条件成立时我们只能执行一个任务
如果我们想要在条件成立时,执行多个任务,该怎么办呢?我们可以借助\”block\”解决这个小问题。

  • 在ansible中,可以使用\”block\”关键字将多个任务整合成一个"块"将被当做一个整体,我们可以对这个”块”添加判断条件,当条件成立时则执行这个块中的所有任务;
  • 其实block除了能够与when结合在一起使用,还有一个很有用的功能就是"错误处理"功能, 当然我们可以借助failed也可以实现类似的功能;

block实际应用案例:

#cat >block.yml<<END
---
- hosts: local
remote_user: root
tasks:
- debug:
msg: "task1 not in block"
- name: "Demo block"
block: #当when对应的条件成立,则执行block中的两个任务 #关键点
- debug:
msg: "task2 in block1"
- debug:
msg: "task3 in block1"
when: 2 > 1
- name: "Demo ingnore erros"
shell: 'ls /ttt'
register: return_value
ignore_errors: true #关键点
- debug:
msg: "I cought an error" #如果条件成立,代表shell任务执行出错,则执行debug任务
when: return_value is failed #关键点采用filed处理错误选择
- name: "Demo block error"
block:
- debug:
msg: "shell error!"
- debug:
msg: "shell execute error!"
when: return_value is failed #关键点采用filed处理错误选择
END


#执行结果:
ok: [local] => {"msg": "task1 not in block"}
ok: [local] => {"msg": "task2 in block1"}
ok: [local] => {"msg": "task3 in block1"}

fatal: [local]: FAILED! => {"changed": true, "cmd": "ls /ttt", "delta": "0:00:00.002747", "end": "2019-08-06 11:31:53.282766", "msg": "non-zero return code", "rc": 2, "start": "2019-08-06 11:31:53.280019", "stderr": "ls: cannot access /ttt: No such file or directory", "stderr_lines": ["ls: cannot access /ttt: No such file or directory"], "stdout": "", "stdout_lines": []}
...ignoring

ok: [local] => {"msg": "I cought an error"}
ok: [local] => {"msg": "shell error!"}
ok: [local] => {"msg": "shell execute error!"}


resuce 关键字

描述:我们会使用block和rescue结合,完成"错误捕捉,报出异常"的功能;rescue关键字字面意思为”救援”与block关键字对齐,表示当block中的任务执行失败时,会执行rescue中的任务进行补救,
注意:当block中的任务出错时会执行rescue中的任务,当block中的任务顺利执行时则不会执行rescue中的任务。


always 关键

描述:除了上面使用到的block与rescue关键字,其实我们还能够加入always关键字,以后无论block中的任务执行成功还是失败,always中的任务都会被执行

实际案例:

block中有多个任务和rescue中也有多个任务,故意执行”/bin/false”命令模拟任务出错的情况,在block代码块中命令执行失败时候就会在执行rescue中的任务时,会先输出 ‘I caught an error’,然后又在rescue中使用’/bin/false’模拟出错的情况,出错后之后的debug任务不会被执行,直接执行always中的任务(无论前面是否执行);

# cat > resuceAlways.yml<<END
---
- hosts: local
remote_user: root
gather_facts: no
tasks:
- block:
- shell: 'ls /opt'
- shell: 'ls /testdir' #关键点 执行错误跳入 rescue 任务中
- shell: 'ls /c'
rescue: #关键点 block执行错误时候执行
- debug:
msg: 'I caught an error'
- command: /bin/false #关键点
- debug:
msg: 'I also never execute'
always: #关键点 字面意思总是执行
- debug:
msg: "This always executes"
END

执行结果:

#TASK [shell]
changed: [local]

#TASK [shell]
fatal: [local]: FAILED! => {"changed": true, "cmd": "ls /testdir", "delta": "0:00:00.002757", "end": "2019-08-06 11:44:47.542015", "msg": "non-zero return code", "rc": 2, "start": "2019-08-06 11:44:47.539258", "stderr": "ls: cannot access /testdir: No such file or directory", "stderr_lines": ["ls: cannot access /testdir: No such file or directory"], "stdout": "", "stdout_lines": []}

#TASK [debug]
ok: [local] => {"msg": "I caught an error"}

#TASK [command]
fatal: [local]: FAILED! => {"changed": true, "cmd": ["/bin/false"], "delta": "0:00:00.026379", "end": "2019-08-06 11:44:47.788466", "msg": "non-zero return code", "rc": 1, "start": "2019-08-06 11:44:47.762087", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}

#TASK [debug]
ok: [local] => {"msg": "This always executes"}


fail 模块

应用场景:当脚本执行到某个阶段时需要对某个条件进行判断,如果条件成立,则立即终止脚本的运行,(只需要在条件成立时调用\”exit\”命令即可终止脚本的运行)

那么在编写playbook时,如果有类似的需求我们该怎么办呢?
答: 借助fail模块;

我们知道在执行playbook时,如果playbook中的任何一个任务执行失败,playbook都会停止运行,除非这个任务设置了”ignore_errors: true”,在任务没有设置”ignore_errors: yes”的情况下,任务执行失败后,playbook就会自动终止;而fail模块天生就是一个用来"执行失败"的模块,当fail模块执行后playbook就会认为有任务失败了,从而终止运行,实现我们想要的中断效果

基础示例:

# cat > fail.yml<<END
---
- hosts: local
remote_user: root
gather_facts: no
tasks:
- debug:
msg: "1"
- debug:
msg: "2"
- fail: #关键点
msg: "Interrupt running playbook"
- debug:
msg: "3"
- debug:
msg: "4"
END

#执行结果
ok: [local] => {"msg": "1"}
ok: [local] => {"msg": "2"}

#TASK [fail]
fatal: [local]: FAILED! => {"changed": false, "msg": "Interrupt running playbook"}

常常将fail模块通常与when结合使用,比如如果之前模块执行后的标准输出信息中包含字符串’error’,则认为中断剧本的条件成立,就立即调用fail模块以终断playbook

# cat >whenfail.yml<<END
---
- hosts: local
remote_user: root
gather_facts: no
tasks:
- shell: "echo 'This is a string for testing--error'"
register: return_value #关键点
- fail: #关键点
msg: "1.Interrupt running playbook"
when: "'error' in return_value.stdout" #并对fail模块添加了判断条件 表示shell模块执行后的标注输出信息中如果包含'error'字符串,则条件成立
- debug: #关键点 由于遇到了fail便不会被执行
msg: "I never execute,Because the playbook has stopped"
END

## 执行结果
#TASK [shell]
changed: [local]

#TASK [fail]
fatal: [local]: FAILED! => {"changed": false, "msg": "1.Interrupt running playbook"}

补充知识点:

  • 1.使用’in’或者’not in’进行条件判断时,’in’关键字的用法与python中’in’的用法相同
    #两种语法是正确
    when: ' "successful" not in return_value.stdout '
    when: " 'successful' not in return_value.stdout "


failed_when 关键字

描述:完成类似功能即失败选择,failed_when’的作用就是当对应的条件成立时将对应任务的执行状态设置为失败,以停止playbook的运行;

但是需要注意的时’ failed_when’虽然会将任务的执行状态设置为失败,但是并不代表任务真的失败了,failed_when’将shell模块的执行状态设置为失败而已,所以'failed_when'并不会影响shell模块的执行过程,只会在条件成立时影响shell模块最终的执行状态,以便停止playbook的运行。

实际案例:

# cat >failed_when.yml<<END
---
- hosts: local
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' #表示\"error\"字符串如果存在于shell模块执行后的标准输出中,则条件成立,当条件成立后,shell模块的执行状态将会被设置为失败
- debug: #关键点 - 由于失败则debug模块不会被执行
msg: "I never execute,Because the playbook has stopped"
END


## 执行结果
#TASK [debug]
ok: [local] => {
"msg": "I execute normally"
}

#TASK [shell]
fatal: [local]: FAILED! => {"changed": true, "cmd": "echo 'This is a string for testing error'", "delta": "0:00:00.002401", "end": "2019-08-06 12:52:57.933101", "failed_when_result": true, "rc": 0, "start": "2019-08-06 12:52:57.930700", "stderr": "", "stderr_lines": [], "stdout": "This is a string for testing error", "stdout_lines": ["This is a string for testing error"]}


changed_when 关键字

描述:理解了’ failed_when’关键字以后,顺势理解’changed_when’关键字就容易多了。

  • ‘failed_when’关键字的作用是在条件成立时,将对应任务的执行状态设置为失败
  • ‘changed_when’除了能够在条件成立时将任务的执行状态设置为\”changed\”,还能让对应的任务永远不能是changed状态

我们知道debug模块在正常执行的情况下只能是”ok”状态,这里使用fialed_when设置成为”changed状态”,只有任务作出了实际的操作时(执行后状态为changed),才会真正的执行对应的handlers而在某些时候,如果想要通过任务执行后的返回值将任务的最终执行状态判定为changed,则可以使用’changed_when’关键字,以便条件成立时可以执行对应的handlers,

当将’changed_when’直接设置为false时,对应任务的状态将不会被设置为’changed’,如果任务原本的执行状态为’changed’,最终则会被设置为’ok’

基础案例:

# cat>changed_when.yml<<END
---
- hosts: local
remote_user: root
gather_facts: no
tasks:
- debug:
msg: "test message"
changed_when: 2 > 1 #关键点
- name: "shell VS"
shell: "ls /opt"
- shell: "ls /opt" #hell模块的执行状态最终为'ok'
changed_when: false
END

执行结果:

#TASK [debug] 
changed: [local] => {"msg": "test message"}

#TASK [shell VS]
changed: [local]

#TASK [shell]
ok: [local]