一个专注于IT互联网运维的技术博客

Ansible条件判断详解

2019.05.25

1、when 条件判断

绝大多数语言中,都使用"if"作为条件判断的关键字,而在ansible中,条件判断的关键字是"when",一个简单的"when"条件判断:

tasks:
  - name: "shut down Debian flavored systems"
    command: /sbin/shutdown -t now
    when: ansible_facts['os_family'] == "Debian"
    # note that all variables can be used directly in conditionals without double curly braces
  • 使用 when 时,变量名不需要双大括号“{{}}”。
  • 在上述两个示例中,我们使用了 "==" 和 ">" 两个比较运算符,在 Ansible 中,我们可以使用jinja2的比较运算符,如下:

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

可以使用小括号控制条件的优先级:

tasks:
  - name: "shut down CentOS 6 and Debian 7 systems"
    command: /sbin/shutdown -t now
    when: (ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_major_version'] == "6") or
          (ansible_facts['distribution'] == "Debian" and ansible_facts['distribution_major_version'] == "7")
  • Ansible 使用的逻辑运算符有:

    and :逻辑与,当左边与右边同时为真,则返回真
    or  :逻辑或,当左边与右边有任意一个为真,则返回真
    not :取反,对一个操作体取反
    

逻辑与也可以使用列表表示:

tasks:
  - name: "shut down CentOS 6 systems"
    command: /sbin/shutdown -t now
    when:
      - ansible_facts['distribution'] == "CentOS"
      - ansible_facts['distribution_major_version'] == "6"

2、条件判断与 tests

Ansible 可以使用 jinja2 的 tests 和 filters,如下:

tasks:
  - command: /bin/false
    register: result
    ignore_errors: True

  - command: /bin/something
    when: result is failed

  # In older versions of ansible use ``success``, now both are valid but succeeded uses the correct tense.
  - command: /bin/something_else
    when: result is succeeded

  - command: /bin/still/something_else
    when: result is skipped

可以使用 debug 模块打印可用的 facts:

- debug: var=ansible_facts

可以做算术运算:

tasks:
  - shell: echo "only on Red Hat 6, derivatives, and later"
    when: ansible_facts['os_family'] == "RedHat" and ansible_facts['lsb']['major_release']|int >= 6
  • 输出“lsb major_release”需要 lsb_release 软件包。

使用变量:

vars:
  epic: true
tasks:
    - shell: echo "This certainly is epic!"
      when: epic
tasks:
    - shell: echo "This certainly isn't epic!"
      when: not epic

使用 jinja2 的 tests:defined:

tasks:
    - shell: echo "I've got '{{ foo }}' and am not afraid to use it!"
      when: foo is defined

    - fail: msg="Bailing out. this play requires 'bar'"
      when: bar is undefined

判断变量的 tests

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

判断执行结果的 tests

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

判断路径的 tests

注:如下tests的判断均针对于ansible主机中的路径,与目标主机无关
file : 判断路径是否是一个文件,如果路径是一个文件则返回真
directory :判断路径是否是一个目录,如果路径是一个目录则返回真
link :判断路径是否是一个软链接,如果路径是一个软链接则返回真
mount:判断路径是否是一个挂载点,如果路径是一个挂载点则返回真
exists:判断路径是否存在,如果路径存在则返回真

判断字符串的 tests

lower:判断包含字母的字符串中的字母是否是纯小写,字符串中的字母全部为小写则返回真
upper:判断包含字母的字符串中的字母是否是纯大写,字符串中的字母全部为大写则返回真

判断整除的 tests

even :判断数值是否是偶数,是偶数则返回真
odd :判断数值是否是奇数,是奇数则返回真
divisibleby(num) :判断是否可以整除指定的数值,如果除以指定的值以后余数为0,则返回真

其他一些 tests

version:可以用于对比两个版本号的大小,或者与指定的版本号进行对比,使用语法为 version('版本号', '比较操作符')
subset:判断一个list是不是另一个list的子集,是另一个list的子集时返回真
superset : 判断一个list是不是另一个list的父集,是另一个list的父集时返回真
number:判断对象是否是一个数字,是数字则返回真

3、条件判断和 register

调用shell模块运行命令时,通常需要获取到shell模块的返回信息,以便之后的模块能够根据返回信息的值判断之后进行怎样的操作,这时可以使用 register 关键字。register 关键字决定保存结果的变量,结果变量可用于模板,操作行或when语句。例如:

- name: test play
  hosts: all
  tasks:
      - shell: cat /etc/motd
        register: motd_contents
      - shell: echo "motd contains the word hi"
        when: motd_contents.stdout.find('hi') != -1
  • 如前所示,可以使用'stdout'值访问已注册变量的字符串内容。如果将注册结果转换为列表(或已经是列表),则可以在任务的循环中使用该注册结果,如下所示。在这个对象中可以用“stdout_lines”,也可以调用“home_dirs.stdout.split()”,并且可以通过其他字段拆分:

    - name: registered variable usage as a loop list
    hosts: all
    tasks:
    - name: retrieve the list of home directories
      command: ls /home
      register: home_dirs
    - name: add home dirs to the backup spooler
      file:
        path: /mnt/bkspool/{{ item }}
        src: /home/{{ item }}
        state: link
      loop: "{{ home_dirs.stdout_lines }}"
      # same as loop: "{{ home_dirs.stdout.split() }}"
    
  • 可以使用'stdout'值访问已注册变量的字符串内容,也可以检查已注册变量的字符串内容是否空白:

    - name: check registered variable for emptiness
    hosts: all
    tasks:
      - name: list contents of directory
        command: ls mydir
        register: contents
      - name: check contents for emptiness
        debug:
          msg: "Directory is empty"
        when: contents.stdout == ""
    
  • 如果 mydir 目录不存在,ls mydir语句报错,playbook 执行停止,这时可以使用 ignore_errors 关键字。ignore_errors: true表示即使当前 task 执行报错,Ansible 也会忽略这个错误,继续执行 playbook:

    - name: check registered variable
    hosts: all
    tasks:
      - name: list contents of directory
        command: ls mydir
        register: contents
        ignore_errors: true
      - name: mydir exists
        debug:
          msg: "Command execution successful"
        when: contents.rc == 0
      - name: mydir does not exist
        debug:
          msg: "Command execution failed"
        when: contents.rc != 0
    

4、条件判断与block

block 允许逻辑分组任务和错误处理,可以在 block 中应用大多数可应用于单个任务(循环除外)的任务,这使得设置任务共有的数据或指令变得更加容易。

block 用于分组任务

tasks:
   - name: Install, configure, and start Apache
     block:
     - name: install httpd and memcached
       yum:
         name: "{{ item }}"
         state: present
       loop:
         - httpd
         - memcached
     - name: apply the foo config template
       template:
         src: templates/src.j2
         dest: /etc/foo.conf
     - name: start service bar and enable it
       service:
         name: bar
         state: started
         enabled: True
     when: ansible_facts['distribution'] == 'CentOS'
     become: true
     become_user: root
     ignore_errors: yes

block 用于错误处理

block 还引入了以类似于大多数编程语言中的异常的方式处理错误的能力,block 仅处理 tasks 的“失败”,不包括错误的任务定义或主机无法访问。

 tasks:
 - name: Handle the error
   block:
     - debug:
         msg: 'I execute normally'
     - name: i force a failure
       command: /bin/false
     - debug:
         msg: 'I never execute, due to the above task failing, :-('
   rescue:
     - debug:
         msg: 'I caught an error, can do stuff here to fix it, :-)'
  • 除了 block 关键字,还有另外一个关键字 rescue,rescue 关键字与 block 关键字同级,rescue 的字面意思为"救援",表示当 block 中的任务执行失败时,会执行 rescue 中的任务进行补救,这将“恢复”运行任务的失败状态,并且 playbook 将继续,就像它已成功一样。
  • rescue 关键字可用的变量:

    ansible_failed_task:The task that returned ‘failed’ and triggered the rescue. For example, to get the name use ansible_failed_task.name.
    
    ansible_failed_result:The captured return result of the failed task that triggered the rescue. This would equate to having used this var in the register keyword.
    

可以使用 always 关键字,加入 always 关键字以后,无论 block 中的任务执行成功还是失败,always 中的任务都会被执行,示例如下:

 - name: Always do X
   block:
     - debug:
         msg: 'I execute normally'
     - name: i force a failure
       command: /bin/false
     - debug:
         msg: 'I never execute :-('
   always:
     - debug:
         msg: "This always executes, :-)"

一个复杂的错误处理:

- name: Attempt and graceful roll back demo
  block:
    - debug:
        msg: 'I execute normally'
    - name: i force a failure
      command: /bin/false
    - debug:
        msg: 'I never execute, due to the above task failing, :-('
  rescue:
    - debug:
        msg: 'I caught an error'
    - name: i force a failure in middle of recovery! >:-)
      command: /bin/false
    - debug:
        msg: 'I also never execute :-('
  always:
    - debug:
        msg: "This always executes"

还可以在 block 中执行 handlers:

tasks:
   - name: Attempt and graceful roll back demo
     block:
       - debug:
           msg: 'I execute normally'
         changed_when: yes
         notify: run me even after an error
       - command: /bin/false
     rescue:
       - name: make sure all handlers run
         meta: flush_handlers
 handlers:
    - name: run me even after an error
      debug:
        msg: 'This handler runs even on error'

5、条件判断与错误处理

fail模块:

- 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"
  • 我们使用shell模块故意输出了一个包含'error'字符串的文本,并且将shell模块执行后的返回值注册到了变量' return_value'中,在之后调用了fail模块,并对fail模块添加了判断条件,对应的条件为 "'error' in return_value.stdout",这个条件表示shell模块执行后的标注输出信息中如果包含'error'字符串,则条件成立,其中,'in'关键字的用法与python中'in'的用法相同,可以使用'in'关键字判断一个字符串是否存在于另一个字符串中,也可以用于判断一个特定的值是否存在于列表中,由于shell标准输出的信息中的确包含error字符串,所以fail模块对应的条件成立,最终调用fail模块,playbook终止运行。不过需要注意的是,当使用"in"或者"not in"进行条件判断时,整个条件需要用引号引起,并且,需要判断的字符串也需要使用引号引起,所以,使用'in'或者'not in'进行条件判断时,如下两种语法是正确的:

    when: ' "successful" not in return_value.stdout '
    when: " 'successful' not in return_value.stdout "
    

借助'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'关键字以后,顺势理解'changed_when'关键字就容易多了:

  • ' failed_when'关键字的作用是在条件成立时,将对应任务的执行状态设置为失败
  • 'changed_when'关键字的作用是在条件成立时,将对应任务的执行状态设置为changed

    - hosts: test70
      remote_user: root
      tasks:
      - debug:
          msg: "test message"
        changed_when: 2 > 1
    
  • 我们知道,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'。

参考文档

Ansible Docs » Conditionals

ansible笔记(29):条件判断与错误处理

发表评论