在shell当中,我们可使用test命令来进行一些常用的判断操作,如下:
| |
| test -e /test |
| |
| |
| test -d /testdir |
事实上,在ansible中也有类似的用法,只不过ansible没有使用linux的test命令,而是jinja2模板的tests。
下面是一个简单示例:
| |
| - 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。接下来我们详细说一说。
- 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 |
- 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 |
- file:判断指定路径是否为一个文件,是则为真
- directory:判断指定路径是否为一个目录,是则为真
- link:判断指定路径是否为一个软链接,是则为真
- mount:判断指定路径是否为一个挂载点,是则为真
- exists:判断指定路径是否存在,存在则为真
特别注意:关于路径的所有判断均是判断主控端上的路径,而非被控端上的路径
示例:
| - hosts: web01 |
| gather_facts: no |
| vars: |
| testpath1: "/testdir/test" |
| yyy: "/testdir" |
| tasks: |
| - debug: |
| msg: "file" |
| when: testpath1 is file |
| - debug: |
| msg: "directory" |
| when: yyy is directory |
- 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 |
- 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、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") |
| ver1 is version(ver2,">") 使用了 version 测试插件来比较 ver1 和 ver2,并且只有在 ver1 大于 ver2 时才会输出 msg 信息。根据定义的变量值,ver1 是 1.2,ver2 是 1.3,所以这个条件为假,不会输出调试信息 |
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 |
当我们要使用同一个条件判断执行多个任务的时候,采用之前的方法,意味着我们要在每一个任务下面都写一下这条重复的when语句
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使用when,但不能直接对block使用loop
block除了能和when一起使用之外,还能作错误处理。这个时候就需要用到rescue关键字:
| - hosts: test |
| tasks: |
| - block: |
| - shell: 'ls /testdir' |
| rescue: |
| - debug: |
| msg: '/testdir is not exists' |
| |
| - name: 后续的任务 |
| debug: |
| msg: "可以正常执行" |
在上面的例子中,
1、当block中的任务执行失败时,则运行rescue中的任务。
2、如果block中的任务正常执行,则rescue的任务就不会被执行。3、如果block中有多个任务,则任何一个任务执行失败,都会执行rescue。
4、block中可以定义多个任务,同样rescue当中也可以定义多个任务。
只有在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除了可以将多个任务组合到一起,还有错误处理的功能。接下来我们继续说一说错误处理。
在shell中,可能会有这样的需求:当脚本执行至某个阶段时,需要对某个条件进行判断,如果条件成立,则立即终止脚本的运行。在shell中,可以直接调用”exit”即可执行退出。
事实上,在playbook中也有类似的模块可以做这件事。即fail模块。Ansible 的 fail 模块可以在特定条件下停止在某个主机上的执行,并继续在其它主机上执行(即某个主机在满足条件时触发了fail,则该主机将不再参与后续的task任务的执行)。通常与条件语句组合使用,当满足条件时,终止当前play的运行。fail的选项只有一个:
示例
| # 使用fail模块中断playbook输出 |
| - hosts: web_group |
| tasks: |
| - name: 任务1 |
| debug: |
| msg: 第一个任务执行 |
| |
| - fail: |
| msg: "下面的when判断满足,则会终止当前主机所有后续task的执行" |
| # web01主机会满足下面的条件,web02主机不满足于是会skip跳过 |
| when: "'web01' in ansible_fqdn" |
| #ignore_errors: True |
| |
| - name: 后续的任务 |
| debug: |
| msg: "web01不会执行该task,web02会正常执行该任务" |
| |
| |
| 执行输出结果如下(我们的主机清单web_group组里有web01与web02两台机器) |
| [root@m01 ~]# ansible-playbook test.yaml |
| |
| PLAY [web_group] **************************************************************************************** |
| |
| TASK [Gathering Facts] ********************************************************************************** |
| |
| |
| |
| ok: [web01] |
| ok: [web02] |
| |
| TASK [任务1] ********************************************************************************************** |
| ok: [web01] => { |
| "msg": "第一个任务执行" |
| } |
| ok: [web02] => { |
| "msg": "第一个任务执行" |
| } |
| |
| TASK [fail] ********************************************************************************************* |
| fatal: [web01]: FAILED! => {"changed": false, "msg": "下面的when判断满足,则会终止整个playbook的执行"} |
| skipping: [web02] |
| |
| TASK [后续的任务] ******************************************************************************************** |
| ok: [web02] => { |
| "msg": "web01不会执行该task,web02会正常执行该任务" |
| } |
| |
| PLAY RECAP ********************************************************************************************** |
| web01 : ok=2 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 |
| web02 : ok=3 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 |
| |
| |
| |
| |
事实上,当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与其msg写在一行,如下:
| |
| - hosts: web_group |
| tasks: |
| - name: 任务1 |
| debug: |
| msg: 第一个任务执行 |
| |
| - fail: msg="下面的when判断满足,则会终止整个playbook的执行" |
| when: "'web01' in ansible_fqdn" |
| |
| |
| - name: 后续的任务 |
| debug: |
| msg: "web01不会执行该task,web02会正常执行该任务" |
一个任务执行有三种状态
1、当任务在远程主机上成功执行,状态发生更改时,会返回changed状态响应
2、状态未发生更改时,会返回OK状态响应
3、当任务被跳过时,会返回skipped状态响应
针对changed状态,我们可以通过changed_when来手动更改。示例如下:
| - shell: /usr/bin/billybass --mode="take me to the river" |
| register: bass_result |
| changed_when: "bass_result.rc != 2" |
| |
| |
| - shell: wall 'beep' |
| changed_when: False |
对于当满足某某条件时就失败的逻辑,可以使用fail模块加when指令来实现,也可使用更为直接的assert模块进行断言。
例如:
| |
| - hosts: web_group |
| gather_facts: no |
| tasks: |
| - name: Check conditions |
| assert: |
| that: |
| - 100 > 20 |
| - 200 >= 200 |
| fail_msg: "断言失败啦啦啦啦" |
| success_msg: "断言成功啦阿啦啦啦" |
其中that参数接收一个列表,用于定义一个或多个条件,如果条件全为true,则任务成功,只要有一个条件为false,则任务失败。fail_msg(或其别名参数msg)定义任务失败时的信息,success_msg定义任务成功时的信息。
如果想让某个失败的任务直接导致整个play的失败,可在play级别使用any_errors_fatal指令。
| |
| - hosts: web_group |
| gather_facts: no |
| any_errors_fatal: true |
| tasks: |
| - name: 任务1 |
| debug: |
| msg: 第一个任务执行 |
| |
| - fail: msg="下面的when判断满足,则会终止整个playbook的执行" |
| when: "'web01' in ansible_fqdn" |
| |
| |
| - name: 后续的任务 |
| debug: |
| msg: "web01不会执行该task,web02会正常执行该任务" |
| |
将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 ************* |
| ......... |
| |
对比
| |
| 并继续在其它主机上执行。 |
| |
| 那么整个剧本将在所有主机上停止执行,包括后续的任务和剧本,这无疑是一个更严格的错误处理方式。 |
。。。
| |
| - hosts: web01 |
| |
| tasks: |
| - name: 任务1,循环列表,并判断大于5时才会执行command定制的echo命令,其余的都会skip跳过 |
| command: echo {{ item }} |
| loop: [ 0, 2, 4, 6, 8, 10 ] |
| when: item > 5 |
| |
| |
| - name: install mariadb-server if enough space on root |
| yum: |
| name: mariadb-server |
| state: present |
| loop: "{{ ansible_mounts }}" |
| when: item.mount == "/" and item.size_available > 300000000 |