PageRenderTime 29ms CodeModel.GetById 16ms app.highlight 9ms RepoModel.GetById 1ms app.codeStats 0ms

/docsite/rst/playbooks_conditionals.rst

https://github.com/ajanthanm/ansible
ReStructuredText | 257 lines | 186 code | 71 blank | 0 comment | 0 complexity | 4b330c32f02665c8da032e257753e696 MD5 | raw file
  1Conditionals
  2============
  3
  4.. contents:: Topics
  5
  6
  7Often the result of a play may depend on the value of a variable, fact (something learned about the remote system), 
  8or previous task result.  In some cases, the values of variables may depend on other variables.  
  9Further, additional groups can be created to manage hosts based on
 10whether the hosts match other criteria.   There are many options to control execution flow in Ansible.
 11
 12Let's dig into what they are.
 13
 14The When Statement
 15``````````````````
 16
 17Sometimes you will want to skip a particular step on a particular host.  This could be something
 18as simple as not installing a certain package if the operating system is a particular version,
 19or it could be something like performing some cleanup steps if a filesystem is getting full.
 20
 21This is easy to do in Ansible, with the `when` clause, which contains a Jinja2 expression (see :doc:`playbooks_variables`).
 22It's actually pretty simple::
 23
 24    tasks:
 25      - name: "shutdown Debian flavored systems"
 26        command: /sbin/shutdown -t now
 27        when: ansible_os_family == "Debian"
 28
 29A number of Jinja2 "filters" can also be used in when statements, some of which are unique
 30and provided by Ansible.  Suppose we want to ignore the error of one statement and then
 31decide to do something conditionally based on success or failure::
 32
 33    tasks:
 34      - command: /bin/false
 35        register: result
 36        ignore_errors: True
 37      - command: /bin/something
 38        when: result|failed
 39      - command: /bin/something_else
 40        when: result|success
 41      - command: /bin/still/something_else
 42        when: result|skipped
 43
 44Note that was a little bit of foreshadowing on the 'register' statement.  We'll get to it a bit later in this chapter.
 45
 46As a reminder, to see what facts are available on a particular system, you can do::
 47
 48    ansible hostname.example.com -m setup
 49
 50Tip: Sometimes you'll get back a variable that's a string and you'll want to do a math operation comparison on it.  You can do this like so::
 51
 52    tasks:
 53      - shell: echo "only on Red Hat 6, derivatives, and later"
 54        when: ansible_os_family == "RedHat" and ansible_lsb.major_release|int >= 6
 55
 56.. note:: the above example requires the lsb_release package on the target host in order to return the ansible_lsb.major_release fact.
 57
 58Variables defined in the playbooks or inventory can also be used.  An example may be the execution of a task based on a variable's boolean value::
 59
 60    vars:
 61      epic: true
 62
 63Then a conditional execution might look like::
 64
 65    tasks:
 66        - shell: echo "This certainly is epic!"
 67          when: epic
 68
 69or::
 70 
 71    tasks:
 72        - shell: echo "This certainly isn't epic!"
 73          when: not epic
 74
 75If a required variable has not been set, you can skip or fail using Jinja2's
 76`defined` test. For example::
 77
 78    tasks:
 79        - shell: echo "I've got '{{ foo }}' and am not afraid to use it!"
 80          when: foo is defined
 81
 82        - fail: msg="Bailing out. this play requires 'bar'"
 83          when: bar is not defined
 84
 85This is especially useful in combination with the conditional import of vars
 86files (see below).
 87
 88Note that when combining `when` with `with_items` (see :doc:`playbooks_loops`), be aware that the `when` statement is processed separately for each item. This is by design::
 89
 90    tasks:
 91        - command: echo {{ item }}
 92          with_items: [ 0, 2, 4, 6, 8, 10 ]
 93          when: item > 5
 94
 95Loading in Custom Facts
 96```````````````````````
 97
 98It's also easy to provide your own facts if you want, which is covered in :doc:`developing_modules`.  To run them, just
 99make a call to your own custom fact gathering module at the top of your list of tasks, and variables returned
100there will be accessible to future tasks::
101
102    tasks:
103        - name: gather site specific fact data
104          action: site_facts
105        - command: /usr/bin/thingy
106          when: my_custom_fact_just_retrieved_from_the_remote_system == '1234'
107                   
108Applying 'when' to roles and includes
109`````````````````````````````````````
110
111Note that if you have several tasks that all share the same conditional statement, you can affix the conditional
112to a task include statement as below.  Note this does not work with playbook includes, just task includes.  All the tasks
113get evaluated, but the conditional is applied to each and every task::
114
115    - include: tasks/sometasks.yml
116      when: "'reticulating splines' in output"
117
118Or with a role::
119
120    - hosts: webservers
121      roles:
122         - { role: debian_stock_config, when: ansible_os_family == 'Debian' }
123
124You will note a lot of 'skipped' output by default in Ansible when using this approach on systems that don't match the criteria.
125Read up on the 'group_by' module in the :doc:`modules` docs for a more streamlined way to accomplish the same thing.
126
127Conditional Imports
128```````````````````
129
130.. note:: This is an advanced topic that is infrequently used.  You can probably skip this section.
131
132Sometimes you will want to do certain things differently in a playbook based on certain criteria.
133Having one playbook that works on multiple platforms and OS versions is a good example.
134
135As an example, the name of the Apache package may be different between CentOS and Debian,
136but it is easily handled with a minimum of syntax in an Ansible Playbook::
137
138    ---
139    - hosts: all
140      remote_user: root
141      vars_files:
142        - "vars/common.yml"
143        - [ "vars/{{ ansible_os_family }}.yml", "vars/os_defaults.yml" ]
144      tasks:
145      - name: make sure apache is running
146        service: name={{ apache }} state=running
147
148.. note::
149   The variable 'ansible_os_family' is being interpolated into
150   the list of filenames being defined for vars_files.
151
152As a reminder, the various YAML files contain just keys and values::
153
154    ---
155    # for vars/CentOS.yml
156    apache: httpd
157    somethingelse: 42
158
159How does this work?  If the operating system was 'CentOS', the first file Ansible would try to import
160would be 'vars/CentOS.yml', followed by '/vars/os_defaults.yml' if that file
161did not exist.   If no files in the list were found, an error would be raised.
162On Debian, it would instead first look towards 'vars/Debian.yml' instead of 'vars/CentOS.yml', before
163falling back on 'vars/os_defaults.yml'. Pretty simple.
164
165To use this conditional import feature, you'll need facter or ohai installed prior to running the playbook, but
166you can of course push this out with Ansible if you like::
167
168    # for facter
169    ansible -m yum -a "pkg=facter ensure=installed"
170    ansible -m yum -a "pkg=ruby-json ensure=installed"
171
172    # for ohai
173    ansible -m yum -a "pkg=ohai ensure=installed"
174
175Ansible's approach to configuration -- separating variables from tasks, keeps your playbooks
176from turning into arbitrary code with ugly nested ifs, conditionals, and so on - and results
177in more streamlined & auditable configuration rules -- especially because there are a
178minimum of decision points to track.
179
180Selecting Files And Templates Based On Variables
181````````````````````````````````````````````````
182
183.. note:: This is an advanced topic that is infrequently used.  You can probably skip this section.
184
185Sometimes a configuration file you want to copy, or a template you will use may depend on a variable.
186The following construct selects the first available file appropriate for the variables of a given host, which is often much cleaner than putting a lot of if conditionals in a template.
187
188The following example shows how to template out a configuration file that was very different between, say, CentOS and Debian::
189
190    - name: template a file
191      template: src={{ item }} dest=/etc/myapp/foo.conf
192      with_first_found:
193        - files: 
194           - {{ ansible_distribution }}.conf
195           - default.conf
196          paths:
197           - search_location_one/somedir/
198           - /opt/other_location/somedir/
199
200Register Variables
201``````````````````
202
203Often in a playbook it may be useful to store the result of a given command in a variable and access
204it later.  Use of the command module in this way can in many ways eliminate the need to write site specific facts, for
205instance, you could test for the existence of a particular program.
206
207The 'register' keyword decides what variable to save a result in.  The resulting variables can be used in templates, action lines, or *when* statements.  It looks like this (in an obviously trivial example)::
208
209    - name: test play
210      hosts: all
211
212      tasks:
213
214          - shell: cat /etc/motd
215            register: motd_contents
216
217          - shell: echo "motd contains the word hi"
218            when: motd_contents.stdout.find('hi') != -1
219
220As shown previously, the registered variable's string contents are accessible with the 'stdout' value.
221The registered result can be used in the "with_items" of a task if it is converted into
222a list (or already is a list) as shown below.  "stdout_lines" is already available on the object as
223well though you could also call "home_dirs.stdout.split()" if you wanted, and could split by other
224fields::
225
226    - name: registered variable usage as a with_items list
227      hosts: all
228
229      tasks:
230
231          - name: retrieve the list of home directories
232            command: ls /home
233            register: home_dirs
234
235          - name: add home dirs to the backup spooler
236            file: path=/mnt/bkspool/{{ item }} src=/home/{{ item }} state=link
237            with_items: home_dirs.stdout_lines
238            # same as with_items: home_dirs.stdout.split()
239
240
241.. seealso::
242
243   :doc:`playbooks`
244       An introduction to playbooks
245   :doc:`playbooks_roles`
246       Playbook organization by roles
247   :doc:`playbooks_best_practices`
248       Best practices in playbooks
249   :doc:`playbooks_conditionals`
250       Conditional statements in playbooks
251   :doc:`playbooks_variables`
252       All about variables
253   `User Mailing List <http://groups.google.com/group/ansible-devel>`_
254       Have a question?  Stop by the google group!
255   `irc.freenode.net <http://irc.freenode.net>`_
256       #ansible IRC chat channel
257