条件选择

常常来说,一个play的结果经常取决于一个变量的值,事件(从远端系统得到事件),或者之前任务的结果.在有些情况下,这些变量的值也会取决于其他变量.进而,可以建立多余的组基于这些主机是否符合某些条件来操控主机,Ansible 提供了很多不同选项,来控制执行流.让我们详细看看这些都是啥.

When 语句

有时候用户有可能需要某一个主机越过某一个特定的步骤.这个过程就可以简单的像在某一个特定版本的系统上少装了一个包一样或者像在一个满了的文件系统上执行清理操作一样.这些操作在Ansible上,若使用when语句都异常简单.When语句包含Jinja2表达式(参见:doc:playbooks_variables).实际上真的很简单:

  1. tasks:
  2. - name: "shutdown Debian flavored systems"
  3. command: /sbin/shutdown -t now
  4. when: ansible_os_family == "Debian"

一系列的Jinja2 “过滤器” 也可以在when语句中使用, 但有些是Ansible中独有的.比如我们想忽略某一错误,通过执行成功与否来做决定,我们可以像这样:

  1. tasks:
  2. - command: /bin/false
  3. register: result
  4. ignore_errors: True
  5. - command: /bin/something
  6. when: result|failed
  7. - command: /bin/something_else
  8. when: result|success
  9. - command: /bin/still/something_else
  10. when: result|skipped

我知道,在这里讨论’register’语句命令,有点过于超前,我们将会在这议长靠后一点的地方接触这个.

友情提示,如果想查看哪些事件在某个特定系统中时允许的,可以执行以下命令:

  1. ansible hostname.example.com -m setup

提示: 有些时候你得到一个返回参数的值是一个字符串,并且你还想使用数学操作来比较它,那么你可以执行一下操作:

  1. tasks:
  2. - shell: echo "only on Red Hat 6, derivatives, and later"
  3. when: ansible_os_family == "RedHat" and ansible_lsb.major_release|int >= 6

Note

以上事例需要目标主机上安装lsb_release软件包,来返回ansible_lsb.major_release 事件.

在playbooks 和 inventory中定义的变量都可以使用. 下面一个例子,就是基于布尔值来决定一个任务是否被执行:

  1. vars:
  2. epic: true

一个条件选择执行也许看起来像这样:

  1. tasks:
  2. - shell: echo "This certainly is epic!"
  3. when: epic

或者像这样:

  1. tasks:
  2. - shell: echo "This certainly isn't epic!"
  3. when: not epic

如果一个变量不存在,你可以使用Jinja2的defined命令跳过或略过.例如:

  1. tasks:
  2. - shell: echo "I've got '{{ foo }}' and am not afraid to use it!"
  3. when: foo is defined
  4.  
  5. - fail: msg="Bailing out. this play requires 'bar'"
  6. when: bar is not defined

这个机制在选择引入变量文件时有时候特别有用,详情如下.

note当同时使用whenhewith_items (详见:doc:playbooks_loops), notewhen语句对于不同项目将会单独处理.这个源于原初设计:

  1. tasks:
  2. - command: echo {{ item }}
  3. with_items: [ 0, 2, 4, 6, 8, 10 ]
  4. when: item > 5

加载客户事件

加载客户自己的事件,事实上也很简单,将在:doc:developing_modules 详细介绍.只要调用客户自己的事件,进而把所有的模块放在任务列表顶端,变量的返回值今后就可以访问了:

  1. tasks:
  2. - name: gather site specific fact data
  3. action: site_facts
  4. - command: /usr/bin/thingy
  5. when: my_custom_fact_just_retrieved_from_the_remote_system == '1234'

在roles 和 includes 上面应用’when’语句

note,如果你的很多任务都共享同样的条件语句的话,可以在选择语句后面添加inlcudes语句,参见下面事例.这个特性并不适用于playbook的inclues,只有task 的 includes适用.所有的task都会被检验,选择会应用到所有的task上面:

  1. - include: tasks/sometasks.yml
  2. when: "'reticulating splines' in output"

或者应用于role:

  1. - hosts: webservers
  2. roles:
  3. - { role: debian_stock_config, when: ansible_os_family == 'Debian' }

在系统中使用这个方法但是并不能匹配某些标准时,你会发现在Ansible中,有很多默认’skipped’的结果.详情参见:doc:modules 文档中的 ‘group_by’ 模块, 你会找到更加赏心悦目的方法来解决这个问题.

条件导入

Note

这是一个很高级但是却被经常使用的话题.当然你也可以跳过这一节.

基于某个特定标准,又是你也许在一个playbook中你想以不同的方式做同一件事.在不同平台或操作系统上使用痛一个playbook就是一个很好的例子.

举个例子,名字叫做Apache的包,在CentOS 和 Debian系统中也许不同,但是这个问题可以一些简单的语法就可以被Ansible Playbook解决:

  1. ---
  2. - hosts: all
  3. remote_user: root
  4. vars_files:
  5. - "vars/common.yml"
  6. - [ "vars/{{ ansible_os_family }}.yml", "vars/os_defaults.yml" ]
  7. tasks:
  8. - name: make sure apache is running
  9. service: name={{ apache }} state=running

Note

‘ansible_os_family’ 已经被导入到为vars_files定义的文件名列表中了.

提醒一下,很多的不同的YAML文件只是包含键和值:

  1. ---
  2. # for vars/CentOS.yml
  3. apache: httpd
  4. somethingelse: 42

这个具体事怎么工作的呢? 如果操作系统是’CentOS’, Ansible导入的第一个文件将是’vars/CentOS.yml’,紧接着是’/var/os_defaults.yml’,如果这个文件不存在.而且在列表中没有找到,就会报错.在Debian,最先查看的将是’vars/Debian.yml’而不是’vars/CentOS.yml’, 如果没找到,则寻找默认文件’vars/os_defaults.yml’很简单.如果使用这个条件性导入特性,你需要在运行playbook之前安装facter 或者 ohai.当然如果你喜欢,你也可以把这个事情推给Ansible来做:

  1. # for facter
  2. ansible -m yum -a "pkg=facter state=present"
  3. ansible -m yum -a "pkg=ruby-json state=present"
  4.  
  5. # for ohai
  6. ansible -m yum -a "pkg=ohai state=present"

Ansible 中的设置方式———— 从任务中把参数分开,这样可避免代码中有太多丑陋嵌套if等复杂语句.这样可以使得配置条目更加的流畅的赏心悦目———— 特别是因为这样可以尽量减少决定点

基于变量选择文件和模版

Note

这是一个经常用到的高级话题.也可以跳过这章.

有时候,你想要复制一个配置文件,或者一个基于参数的模版.下面的结构选载选第一个宿主给予的变量文件,这些可以比把很多if选择放在模版里要简单的多.下面的例子展示怎样根据不同的系统,例如CentOS,Debian制作一个配置文件的模版:

  1. - name: template a file
  2. template: src={{ item }} dest=/etc/myapp/foo.conf
  3. with_first_found:
  4. - files:
  5. - {{ ansible_distribution }}.conf
  6. - default.conf
  7. paths:
  8. - search_location_one/somedir/
  9. - /opt/other_location/somedir/

注册变量

经常在playbook中,存储某个命令的结果在变量中,以备日后访问是很有用的.这样使用命令模块可以在许多方面除去写站(site)特异事件,据哥例子你可以检测某一个特定程序是否存在

这个 ‘register’ 关键词决定了把结果存储在哪个变量中.结果参数可以用在模版中,动作条目,或者 when 语句. 像这样(这是一个浅显的例子):

  1. - name: test play
  2. hosts: all
  3.  
  4. tasks:
  5.  
  6. - shell: cat /etc/motd
  7. register: motd_contents
  8.  
  9. - shell: echo "motd contains the word hi"
  10. when: motd_contents.stdout.find('hi') != -1

就像上面展示的那样,这个注册后的参数的内容为字符串’stdout’是可以访问.这个注册了以后的结果,如果像上面展示的,可以转化为一个list(或者已经是一个list),就可以在任务中的”with_items”中使用.“stdout_lines”在对象中已经可以访问了,当然如果你喜欢也可以调用 “home_dirs.stdout.split()” , 也可以用其它字段切割:

  1. - name: registered variable usage as a with_items list
  2. hosts: all
  3.  
  4. tasks:
  5.  
  6. - name: retrieve the list of home directories
  7. command: ls /home
  8. register: home_dirs
  9.  
  10. - name: add home dirs to the backup spooler
  11. file: path=/mnt/bkspool/{{ item }} src=/home/{{ item }} state=link
  12. with_items: home_dirs.stdout_lines
  13. # same as with_items: home_dirs.stdout.split()

See also