Chapter 7. Using Task Control

This chapter covers the following subjects:

Using Loops and Items

Using when to Run Tasks Conditionally

Using Handlers

• Dealing with Failures

The following RHCE exam objectives are covered in this chapter:

• Create Ansible plays and playbooks

• Use conditionals to control play execution

• Configure error handling

• Create playbooks to configure systems to a specified state

“Do I Know This Already?” Quiz

The “Do I Know This Already?” quiz allows you to assess whether you should read this entire chapter thoroughly or jump to the “Exam Preparation Tasks” section. If you are in doubt about your answers to these questions or your own assessment of your knowledge of the topics, read the entire chapter. Table 7-1 lists the major headings in this chapter and their corresponding “Do I Know This Already?” quiz questions. You can find the answers in Appendix A, “Answers to the ’Do I Know This Already?’ Quizzes and Review Questions.

Table 7-1 “Do I Know This Already?” Section-to-Question Mapping

Image

1. Which of the following should you use to iterate through a variable that contains a list of items as its value?

a. with_items

b. with_value

c. item

d. loop

2. Which statement about using variables in when conditional statements is true?

a. Loops cannot be used in when statements.

b. When used in a when statement, the variable name is written without curly brackets.

c. When using variables in a when statement, you always need to write them between curly brackets as well as double quotes.

d. When using variables in a when statement, you need to address them using a %% sign.

3. Which of the following shows the proper test to verify whether a Boolean variable is true?

a. variable is defined

b. variable

c. variable = “true”

d. variable == 1

4. Which of the following shows correct syntax for a string test used to check whether variable has a specific value?

a. variable = value

b. variable == value

c. variable = “value”

d. variable == “value”

5. Which of the following shows correct syntax for a test that checks whether a variable has a specific numeric value?

a. key == “n”

b. key = “n”

c. key == n

d. key = n

6. What can you use in a playbook to show a prompt while a user executes the playbook, asking the user to provide a specific value for a variable?

a. prompt

b. ask_vars

c. vars_prompt

d. prompt_vars

7. Which can be used in a playbook to activate a handler upon successful execution of a task?

a. notify

b. alert

c. handler

d. call

8. While working with handlers, different requirements apply. Which of the following is not one of them?

a. If one task in the play fails, no handlers will run.

b. Handlers will run only after all tasks in the play have been processed.

c. Handlers will run only if the task results in an ok or a changed status.

d. Handlers will run in the order specified in the handlers section.

9. Which of the following can be used to ensure that handlers will also run if any task finishes with an error?

a. force_handlers

b. ignore_errors

c. run_always

d. ignore_all

10. Which module should be used to generate a specific error message if a specific failure occurs?

a. failed_when

b. failed

c. fail

d. failure

Foundation Topics

Using Loops and Items

Some modules enable you to provide a list that needs to be processed. Many modules don’t, and in these cases, it makes sense to use a loop mechanism to iterate over a list of items. Take, for instance, the yum module. While specifying the names of packages, you can use a list of packages. If, however, you want to do something similar for the service module, you find out that this is not possible. That is where loops come in. In this section you learn all there is to know about using loops.

Working with Loops

To help you understand working with loops, Listing 7-1 shows a simple playbook that installs software packages using the yum module and then ensures that services installed from these packages are started using the service module.

Listing 7-1 Using loop

---
- name: install and start services
  hosts: ansible1
  tasks:
  - name: install packages
    yum:
      name:
      - vsftpd
      - httpd
      - samba
      state: latest
  - name: start the services
    service:
      name: "{{ item }}"
      state: started
      enabled: yes
    loop:
    - vsftpd
    - httpd
    - smb

In Listing 7-1, you can see that a loop is defined at the same level as the service module. The loop has a list of services in the list (array) statement that you have seen before. Items in the loop can be accessed by using the system internal variable item. At no place in the playbook is there a definition of the variable item; the loop takes care of this.

In Listing 7-1 you can also see that a different approach is used for the yum module. The name in the yum module does support a list by default, so in this case there is no further need to use loop and item.

When considering whether to use a loop, you should first investigate whether a module offers support for providing lists as values to the keys that are used. If this is the case, just provide a list, as all items in the list can be considered in one run of the module. If not, define the list using loop and provide “{{ item }}” as the value to the key. Notice that when using loop, the module is activated again on each iteration.

Using Loops on Variables

Although it’s possible to define a loop within a task, it’s not the most elegant way. To create a flexible environment where static code is separated from dynamic site-specific parameters, it’s a much better idea to define loops outside the static code, in variables. When you define loops within a variable, all the normal rules for working with variables apply: The variables can be defined in the play header, using an include file, or as host/hostgroup variables. In Listing 7-2 you can see how the example from Listing 7-1 has been rewritten to include the loop from a variable.

Listing 7-2 Providing the Loop by a Variable

---
- name: install and start services
  hosts: ansible1
  vars:
    services:
    - vsftpd
    - httpd
    - smb
  tasks:
  - name: install packages
    yum:
      name:
      - vsftpd
      - httpd
      - samba
      state: latest
  - name: start the services
    service:
      name: "{{ item }}"
      state: started
      enabled: yes
    loop: "{{ services }}"

Using Loops on Multivalued Variables

An item can be a simple list, but it can also be presented as a multivalued variable, as long as the multivalued variable is presented as a list. Consider the sample playbook in Listing 7-4, which uses variables that are imported from the file vars/users-list shown in Listing 7-3.

Listing 7-3 Variables File

users:
  - username: linda
    homedir: /home/linda
    shell: /bin/bash
    groups: wheel
  - username: lisa
    homedir: /home/lisa
    shell: /bin/bash
    groups: users
  - username: anna
    homedir: /home/anna
    shell: /bin/bash
    groups: users

Listing 7-4 Using Multivalued Variables

---
- name: create users using a loop from a list
  hosts: ansible1
  vars_files: vars/users-list
  tasks:
  - name: create users
    user:
      name: "{{ item.username }}"
      state: present
      groups: "{{ item.groups }}"
      shell: "{{ item.shell }}"
    loop: "{{ users }}"

Working with multivalued variables is possible, but the variables in that case must be presented as a list; using dictionaries is not supported. The only way to loop over dictionaries is to use the dict2items filter. Use of filters is not included in the RHCE topics and for that reason is not explained further here. You can look up “Iterating over a dictionary” in the Ansible documentation for more information. Listing 7-5 shows the output of the command ansible-playbook listing74.yaml.

Listing 7-5 Working with Multivalued Variables Output

[ansible@control ~]$ ansible-playbook listing74.yaml

PLAY [create users using a loop from a list] *******************************************

TASK [Gathering Facts] *****************************************************************
ok: [ansible1]

TASK [create users] ********************************************************************
changed: [ansible1] => (item={’username’: ’linda’, ’homedir’: ’/home/linda’, ’shell’: ’/bin/bash’, ’groups’: ’wheel’})
changed: [ansible1] => (item={’username’: ’lisa’, ’homedir’: ’/home/lisa’, ’shell’: ’/bin/bash’, ’groups’: ’users’})
changed: [ansible1] => (item={’username’: ’anna’, ’homedir’: ’/home/anna’, ’shell’: ’/bin/bash’, ’groups’: ’users’})

PLAY RECAP *****************************************************************************
ansible1                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Understanding with_items

Since Ansible 2.5, using loop has been the command way to iterate over the values in a list. In earlier versions of Ansible, the with_keyword statement was used instead. In this approach, the keyword is replaced with the name of an Ansible look-up plug-in, but the rest of the syntax is very common. Table 7-2 provides an overview of some of the most common options. Notice that the syntax was still supported at the time this book was written but will be deprecated in a future version of Ansible.

Table 7-2 with_keyword Options Overview

Image

In Listing 7-6 you can see how the playbook from Listing 7-2 is rewritten to use with_items. Notice that it has only one change: loop has been changed to with_items.

Listing 7-6 Using with_items

---
- name: install and start services
  hosts: ansible1
  vars:
    services:
    - vsftpd
    - httpd
    - smb
  tasks:
  - name: install packages
    yum:
      name:
      - vsftpd
      - httpd
      - samba
      state: latest
  - name: start the services
    service:
      name: "{{ item }}"
      state: started
      enabled: yes
    with_items: "{{ services }}"

In Exercise 7-1 you practice working with loop.

Exercise 7-1 Working with loop

1. Use your editor to define a variables file with the name vars/packages and the following contents:

packages:
- name: httpd
  state: absent
- name: vsftpd
  state: installed
- name: mysql-server
  state: latest

2. Use your editor to define a playbook with the name exercise71.yaml and create the play header:

- name: manage packages using a loop from a list
  hosts: ansible1
  vars_files: vars/packages
  tasks:

3. Continue the playbook by adding the yum task that will manage the packages, using the packages variable as defined in the vars/packages variable include file:

- name: manage packages using a loop from a list
  hosts: ansible1
  vars_files: vars/packages
  tasks:
  - name: install packages
    yum:
      name: "{{ item.name }}"
      state: "{{ item.state }}"
    loop: "{{ packages }}"

4. Run the playbook using ansible-playbook exercise71.yaml, and observe the results. In the results you should see which packages it is trying to manage and in which state it is trying to get the packages.

Using when to Run Tasks Conditionally

In Ansible, you can use a when statement to run tasks conditionally. Multiple tests can be done using when; for instance, you can test whether a variable has a specific value, whether a file exists, whether a minimal amount of memory is available, and more.

Working with when

Let’s get started with a simple playbook to explore the workings of when. In the sample playbook in Listing 7-7, you can see how a when statement is used to install the right software package for the Apache web server, based on the Linux distribution that was found in the Ansible facts. Notice that when used in when statements, the variable that is evaluated is not placed between double curly braces.

Listing 7-7 Using when for Conditional Software Installation

---
- name: conditional install
  hosts: all
  tasks:
  - name: install apache on Red Hat and family
    yum:
      name: httpd
      state: latest
    when: ansible_facts[’os_family’] == "RedHat"
  - name: install apache on Ubuntu and family
    apt:
      name: apache2
      state: latest
    when: ansible_facts[’os_family’] == "Debian"

Notice the use of when: Because it is not a part of any properties of the modules on which it is used, the when statement must be indented at the same level as the module itself. In the example in Listing 7-7, a string test is used to check whether the value of the Ansible fact ansible_os_family (which in the playbook is written as the equivalent ansible_facts[’os_family’]) is equal to the string RedHat. Because this is a string test, the string itself must be between double quotes. Without the double quotes, it would be considered an integer test. A string test is just one of the many conditional tests that can be executed. Table 7-3 provides an overview with some other examples of conditional tests.

Listing 7-8 shows the output of the playbook in Listing 7-7. Because no hosts use a Linux distribution from the Debian family, the second task reports a status of skipped.

Listing 7-8 Conditional Playbook Result

[ansible@control ~]$ ansible-playbook listing77.yaml

PLAY [conditional install] *************************************************************

TASK [Gathering Facts] *****************************************************************
ok: [ansible2]
ok: [ansible1]

TASK [install apache on Red Hat and family] ********************************************
ok: [ansible1]
changed: [ansible2]

TASK [install apache on Ubuntu and family] *********************************************
skipping: [ansible1]
skipping: [ansible2]

PLAY RECAP *****************************************************************************
ansible1                   : ok=2    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0
ansible2                   : ok=2    changed=1    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

Using Conditional Test Statements

When working with when, you use conditional test statements. In the example in Listing 7-7 you saw a string test, but many other tests are also available. Table 7-3 gives an overview of some common conditional tests that you can perform with the when statement.

Image

Table 7-3 Conditional Tests Overview

Image


Exam tip

Conditional tests are important, but you won’t find a nice table like Table 7-3 in the Ansible documentation. Look for “Tests” in the Ansible documentation, and use the item that is found in Templating (Jinja2). This section contains useful examples that might help you during the exam.


Image

When referring to variables in when statements, you don’t have to use curly brackets because items in a when statement are considered to be variables by default. So you can write when: text == “hello” instead of when: “{{ text }}” == “hello”.

As you can see in Table 7-3, there are roughly four types of when conditional tests:

• Checks related to variable existence

• Boolean checks

• String comparisons

• Integer comparisons

The first type of test checks whether a variable exists or is a part of another variable, such as a list. This is a useful test to figure out if, for instance, a specific Ansible fact has been set. An example is shown in Listing 7-9 where the sample playbook checks for the existence of a specific disk device, using variable is defined and variable is not defined. When you run this playbook, you’ll notice that all failing tests result in the message “skipping.”

Listing 7-9 Using when to Check Whether a Variable Exists

---
- name: check for existence of devices
  hosts: all
  tasks:
  - name: check if /dev/sda exists
    debug:
      msg: a disk device /dev/sda exists
    when: ansible_facts[’devices’][’sda’] is defined
  - name: check if /dev/sdb exists
    debug:
      msg: a disk device /dev/sdb exists
    when: ansible_facts[’devices’][’sdb’] is defined
  - name: dummy test, intended to fail
    debug:
      msg: failing
    when: dummy is defined
  - name: check if /dev/sdc does not exist
    debug:
      msg: there is no /dev/sdc device
    when: ansible_facts[’devices’][’sdc’] is not defined

Closely related to the is defined check is the check that finds whether the first variable value is present in the second variable’s list. This scenario is demonstrated in Listing 7-10, which executes the debug task if the variable my_answer is in supported_packages. Notice that in this listing vars_prompt is used. This stops the playbook, asks the user for input, and stores the input in a variable with the name my_answer.

Listing 7-10 Checking Whether a Variable Occurs in a List

---
- name: test if variable is in another variables list
  hosts: all
  vars_prompt:
  - name: my_answer
    prompt: which package do you want to install
  vars:
    supported_packages:
    - httpd
    - nginx
  tasks:
  - name: something
    debug:
      msg: you are trying to install a supported package
    when: my_answer in supported_packages

The next type of check is the Boolean check. This check works on variables that have a Boolean value, but these variables are not very common. The most important thing to know about this type of check is that it should not be defined with the check for existence. Boolean checks are used to check the Boolean value of a variable; is defined is used to check whether a variable is defined.

The last types of checks are string comparisons and integer comparisons. You already saw a string test in Listing 7-7. In an integer comparison, you check whether a variable has a specific value. You can, for instance, check if more than 1 GB of disk space is available. When doing checks on available disk space and available memory, carefully look at the expected value. Memory is shown in megabytes, by default, whereas disk space is expressed in bytes. Listing 7-11 shows an example that will install vsftpd if more than 50 MB of memory is available.

Listing 7-11 Using an Integer Check

---
- name: conditionals test
  hosts: all
  tasks:
  - name: install vsftpd if sufficient memory available
    package:
      name: vsftpd
      state: latest
    when: ansible_facts[’memory_mb’][’real’][’free’] > 50

Testing Multiple Conditions

Apart from doing single evaluations, when statements can also be used to evaluate multiple conditions. To do so, you can group the conditions with parentheses and combine them with and and or keywords. Listing 7-12 shows an example where and is used and that runs the task only if both conditions are true. Alternatively, consider using or to allow a task to run if one of the conditions is true.

Listing 7-12 Combining Multiple Conditions

---
- name: testing multiple conditions
  hosts: all
  tasks:
  - name: showing output
    debug:
      msg: using CentOS 8.1
    when: ansible_facts[’distribution_version’] == "8.1" and ansible_facts[’distribution’] == "CentOS"

Apart from the simple and statement in Listing 7-12, you can make more complex statements by grouping conditions together in parentheses. Listing 7-13 shows an example. Note that in this example the when statement starts with a > sign because the statement is wrapped over the next five lines for readability. The > sign makes sure that all the values provided to the when statement are interpreted as one line and not as five.

Listing 7-13 Combining Complex Statements

---
- name: using multiple conditions
  hosts: all
  tasks:
  - package:
      name: httpd
      state: removed
    when: >
      ( ansible_facts[’distribution’] == "RedHat" and
        ansible_facts[’memfree_mb’] < 512 )
      or
      ( ansible_facts[’distribution’] == "CentOS" and
        ansible_facts[’memfree_mb’] < 256 )

Combining loop and when

Although you use when to execute a task only if a specific condition is true, you can use loop to iterate over a list of items. To unleash the full power of Ansible playbooks, you can use a combination. The playbook in Listing 7-14 performs a kernel update only if /boot is on a dedicated mount point and at least 200 MB is available in the mount.

Listing 7-14 Combining loop and when

---
- name: conditionals test
  hosts: all
  tasks:
  - name: update the kernel if sufficient space is available in /boot
    package:
      name: kernel
      state: latest
    loop: "{{ ansible_facts[’mounts’] }}"
    when: item.mount == "/boot" and item.size_available > 200000000

Because the sample playbook from Listing 7-14 loops over all the mounts that were found, it’s interesting to observe its output. In Listing 7-15 you can see how the task first skips the / mount and next performs the task on the /boot mount as it meets the conditions. Later in Exercise 7-2 you practice working with when.

Listing 7-15 Listing 7-14 Task Result

[ansible@control ~]$ ansible-playbook listing714.yaml

PLAY [conditionals test] ***************************************************************

TASK [Gathering Facts] *****************************************************************
ok: [ansible2]
ok: [ansible1]

TASK [update kernel if sufficient space in /boot] **************************************
skipping: [ansible1] => (item={’mount’: ’/’, ’device’: ’/dev/mapper/cl-root’, ’fstype’: ’xfs’, ’options’: ’rw,seclabel,relatime,attr2,inode64,noquota’, ’size_total’: 18238930944, ’size_available’: 13722013696, ’block_size’: 4096, ’block_total’: 4452864, ’block_available’: 3350101, ’block_used’: 1102763, ’inode_total’: 8910848, ’inode_available’: 8790863, ’inode_used’: 119985, ’uuid’: ’ef0bb39c-5a29-4c0a-9152-7dd3fd5254c2’})
skipping: [ansible2] => (item={’mount’: ’/’, ’device’: ’/dev/mapper/cl-root’, ’fstype’: ’xfs’, ’options’: ’rw,seclabel,relatime,attr2,inode64,noquota’, ’size_total’: 18238930944, ’size_available’: 16635084800, ’block_size’: 4096, ’block_total’: 4452864, ’block_available’: 4061300, ’block_used’: 391564, ’inode_total’: 8910848, ’inode_available’: 8877221, ’inode_used’: 33627, ’uuid’: ’acdeb1af-c439-4030-b9ba-c21d4d4fb0a8’})
changed: [ansible2] => (item={’mount’: ’/boot’, ’device’: ’/dev/sda1’, ’fstype’: ’ext4’, ’options’: ’rw,seclabel,relatime’, ’size_total’: 1023303680, ’size_available’: 811139072, ’block_size’: 4096, ’block_total’: 249830, ’block_available’: 198032, ’block_used’: 51798, ’inode_total’: 65536, ’inode_available’: 65227, ’inode_used’: 309, ’uuid’: ’cc870ab6-1e0e-4d27-9df3-9e5961d9fa62’})
changed: [ansible1] => (item={’mount’: ’/boot’, ’device’: ’/dev/sda1’, ’fstype’: ’ext4’, ’options’: ’rw,seclabel,relatime’, ’size_total’: 1023303680, ’size_available’: 803180544, ’block_size’: 4096, ’block_total’: 249830, ’block_available’: 196089, ’block_used’: 53741, ’inode_total’: 65536, ’inode_available’: 65227, ’inode_used’: 309, ’uuid’: ’7acd65d6-115f-499f-a02f-90364a18b9fc’})

PLAY RECAP *****************************************************************************
ansible1                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
ansible2                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Combining loop and register

In Chapter 6, “Working with Variables and Facts,” you learned that the results of a command can be stored in a variable when you use register. Using register, you store the result of a command in a multivalued variable, and based on this result, another conditional task can run. In the example in Listing 7-16, you can see how this statement is used in a playbook.

Listing 7-16 Combining register and loop

---
- name: test register
  hosts: all
  tasks:
    - shell: cat /etc/passwd
      register: passwd_contents
    - debug:
        msg: passwd contains user lisa
      when: passwd_contents.stdout.find(’lisa’) != -1

You might notice that in this playbook, the when statement makes use of some particular items that we haven’t seen before. To start with, it refers to passwd_contents.stdout.find, but passwd_contents.stdout does not contain any item with the name find. The construction that is used here is variable.find, which enables a task to search a specific string in a variable. To do so, the find function in Python is used—after all, Ansible is written in Python. When the Python find function does not find a string, it returns a value of −1. If the requested string is found, the find function returns an integer that returns the position where the string was found. For instance, if the string lisa is found in /etc/passwd, it returns an unexpected value like 2604, which is the position in the file, expressed as a byte offset from the beginning, where the string is found for the first time.

Because of the behavior of the Python find function, variable.find needs not to be equal to −1 to have the task succeed. So don’t write passwd_contents.stdout.find(’lisa’) = 0 (because it is not a Boolean), but instead write passwd_contents.stdout.find(’lisa’) != -1. In Exercise 7-2 you practice working with conditionals using register.

Image

Note that when using register, you might want to define a task that runs a command that will fail, just to capture the return code of that command, after which the playbook should continue. If that is the case, you must ensure that ignore_errors: yes is used in the task definition. The default behavior is that if a task fails, execution of the playbook is aborted, and no other tasks run.

Exercise 7-2 Using when

1. Use your editor to create a new file with the name exercise72.yaml. Start writing the play header as follows:

---
- name: restart sshd service if httpd is running
  hosts: ansible1
  tasks:

2. Add the first task, which checks whether the httpd service is running, using command output that will be registered. Notice the use of ignore_errors: yes. This line makes sure that if the service is not running, the play is still executed further.

---
- name: restart sshd service if httpd is running
  hosts: ansible1
  tasks:
  - name: get httpd service status
    command: systemctl is-active httpd
    ignore_errors: yes
    register: result

3. Add a debug task that shows the output of the command so that you can analyze what is currently in the registered variable:

---
- name: restart sshd service if httpd is running
  hosts: ansible1
  tasks:
  - name: get httpd service status
    command: systemctl is-active httpd
    ignore_errors: yes
    register: result
  - name: show result variable contents
    debug:
      msg: printing contents of the registered variable {{ result }}

4. Complete the playbook by including the service task, which is started only if the value stored in result.rc (which is the return code of the command that was registered) contains a 0. This is the case if the previous command executed successfully.

---
- name: restart sshd service if httpd is running
  hosts: ansible1
  tasks:
  - name: get httpd service status
    command: systemctl is-active httpd
    ignore_errors: yes
    register: result
  - name: show result variable contents
    debug:
      msg: printing contents of the registered variable {{ result }}
  - name: restart sshd service
    service:
      name: sshd
      state: restarted
    when: result.rc == 0

5. Use an ad hoc command to make sure the httpd service is installed: ansible ansible1 -m yum -a “name=httpd state=latest”.

6. Use an ad hoc command to make sure the httpd service is stopped: ansible ansible1 -m service -a “name=httpd state=stopped”.

7. Run the playbook using ansible-playbook exercise72.yaml and analyze the result. You should see that the playbook skips the service task.

8. Type ansible ansible1 -m service -a “name=httpd state=started” and run the playbook again, using ansible-playbook exercise72.yaml. Playbook execution at this point should be successful.

Using Handlers

Playbooks contain lists of tasks. Tasks are always executed in order, and if one task fails, none of the following tasks are executed. In some cases, you might need to manage execution dependencies in a more specific way. That is when handlers can come in handy. A handler is a task that is triggered and is executed by a successful task.

Working with Handlers

To work with handlers, you should define a notify statement at the level where the task is defined. The notify statement should list the name of the handler that is to be executed, and the handlers are listed at the end of the play. Make sure the name of the handler matches the name of the item that is called in the notify statement, because that is what the handler is looking for.

The playbook in Listing 7-17 shows how to work with handlers. It is a multiplay playbook, where the first play is used to define the file index.html on localhost. Next, this file is used in the second play to set up the web server.

The handler is triggered from the task where the copy module is used to copy the index.html file. If this task is successful, the notify statement calls the handler. Notice that handlers can be specified as a list, so one task can call multiple handlers. Also notice that in Listing 7-17, a second task is defined, which is intended to fail.

Listing 7-17 Working with Handlers

---
- name: create file on localhost
  hosts: localhost
  tasks:
  - name: create index.html on localhost
    copy:
      content: "welcome to the webserver"
      dest: /tmp/index.html

- name: set up web server
  hosts: all
  tasks:
    - name: install httpd
      yum:
        name: httpd
        state: latest
    - name: copy index.html
      copy:
        src: /tmp/index.html
        dest: /var/www/html/index.html
      notify:
        - restart_web
    - name: copy nothing - intended to fail
      copy:
        src: /tmp/nothing
        dest: /var/www/html/nothing.html
  handlers:
    - name: restart_web
      service:
        name: httpd
        state: restarted

Listing 7-18 shows the result of the command ansible-playbook listing717.yaml.

Listing 7-18 ansible-playbook listing717.yaml Command Result

[ansible@control ~]$ ansible-playbook listing717.yaml

PLAY [create file on localhost] ********************************************************

TASK [Gathering Facts] *****************************************************************
ok: [localhost]

TASK [create index.html on localhost] **************************************************
changed: [localhost]

PLAY [set up web server] ***************************************************************

TASK [Gathering Facts] *****************************************************************
ok: [ansible2]
ok: [ansible1]

TASK [install httpd] *******************************************************************
changed: [ansible2]
changed: [ansible1]

TASK [copy index.html] *****************************************************************
changed: [ansible2]
changed: [ansible1]

TASK [copy nothing - intended to fail] *************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: If you are using a module and expect the file to exist on the remote, see the remote_src option
fatal: [ansible2]: FAILED! => {"changed": false, "msg": "Could not find or access ’/tmp/nothing’ on the Ansible Controller.
If you are using a module and expect the file to exist on the remote, see the remote_src option"}
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: If you are using a module and expect the file to exist on the remote, see the remote_src option
fatal: [ansible1]: FAILED! => {"changed": false, "msg": "Could not find or access ’/tmp/nothing’ on the Ansible Controller.
If you are using a module and expect the file to exist on the remote, see the remote_src option"}

RUNNING HANDLER [restart_web] **********************************************************

PLAY RECAP *****************************************************************************
ansible1                   : ok=3    changed=2    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0
ansible2                   : ok=3    changed=2    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0
localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

As you can see in the command result in Listing 7-18, all tasks up to copy index.html run successfully. However, the task copy nothing fails, which is why the handler does not run. The solution seems easy: the handler doesn’t run because the task that copies the file /tmp/nothing fails as the source file doesn’t exist. So the solution seems simple: create the source file using touch /tmp/nothing on the control host and run the task again. Listing 7-19 shows the result of this approach.

Listing 7-19 Running playbook listing717.yaml Again

[ansible@control ~]$ ansible-playbook listing717.yaml

PLAY [create file on localhost] ********************************************************

TASK [Gathering Facts] *****************************************************************
ok: [localhost]

TASK [create index.html on localhost] **************************************************
ok: [localhost]

PLAY [set up web server] ***************************************************************

TASK [Gathering Facts] *****************************************************************
ok: [ansible2]
ok: [ansible1]

TASK [install httpd] *******************************************************************
ok: [ansible2]
ok: [ansible1]

TASK [copy index.html] *****************************************************************
ok: [ansible2]
ok: [ansible1]

TASK [copy nothing - intended to fail] *************************************************
changed: [ansible2]
changed: [ansible1]

PLAY RECAP *****************************************************************************
ansible1                   : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
ansible2                   : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

As you can see, despite what you might have expected, after creating the source file and running the playbook again, the handler still doesn’t run. The reason is that handlers run only if the task that triggers them gives a changed status, and that doesn’t happen in Listing 7-19 because the task already executed successfully while running the playbook in Listing 7-18. To see the handler being triggered successfully, you must run an ad hoc command to remove the /var/www/html/index.html file on the managed hosts and run the playbook again. Listing 7-20 shows the result.

Listing 7-20 Successfully Running Listing 7-17

[ansible@control ~]$ ansible ansible2 -m file -a "name=/var/www/html/index.html state=absent"
ansible2 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": true,
    "path": "/var/www/html/index.html",
    "state": "absent"
}
[ansible@control ~]$ ansible-playbook listing717.yaml

PLAY [create file on localhost] ********************************************************

TASK [Gathering Facts] *****************************************************************
ok: [localhost]

TASK [create index.html on localhost] **************************************************
ok: [localhost]

PLAY [set up web server] ***************************************************************

TASK [Gathering Facts] *****************************************************************
ok: [ansible2]
ok: [ansible1]

TASK [install httpd] *******************************************************************
ok: [ansible2]
ok: [ansible1]

TASK [copy index.html] *****************************************************************
changed: [ansible2]
ok: [ansible1]

TASK [copy nothing - intended to fail] *************************************************
ok: [ansible2]
ok: [ansible1]

RUNNING HANDLER [restart_web] **********************************************************
changed: [ansible2]

PLAY RECAP *****************************************************************************
ansible1                   : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
ansible2                   : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Understanding Handler Execution and Exceptions

When a task fails, none of the following tasks run. How does that make handlers different? A handler runs only on the success of a task, but the next task in the list also runs only if the previous task was successful. What, then, is so special about handlers?

The difference is in the nature of the handler. Handlers are meant to perform an extra action when a task makes a change to a host. So in the design of the playbook, the handler should be considered an extension to the regular task. They are a conditional task that runs only upon the success of a previous task.

If a handler is triggered and a task that is later in the play fails, the handler will not be executed on the node where the subsequent task has failed. There are two solutions to prevent this. To start with, you can use force_handlers: true in the play header to ensure that the handler will run anyway. You also can use the more generic ignore_errors: true statement in the play header to accomplish the same thing. Because force_handlers: true is more specific, using that option is preferred if you just need to make sure that your handlers will run.

When you work with handlers, there are a few specifics to be aware of:

Image

• Handlers are specified in a handlers section at the end of the play.

• Handlers run in the order they occur in the handlers section and not in the order as triggered.

• Handlers run only if the task calling them generates a changed status.

• Handlers by default will not run if any task in the same play fails, unless force_handlers or ignore_errors are used.

• Handlers run only after all tasks in the play where the handler is activated have been processed. You might want to define multiple plays to avoid this behavior.

Exercise 7-3 Working with Handlers

1. Open a playbook with the name exercise73.yaml.

2. Define the play header:

---
- name: update the kernel
  hosts: all
  force_handlers: true
  tasks:

3. Add a task that updates the current kernel:

---
- name: update the kernel
  hosts: all
  force_handlers: true
  tasks:
  - name: update kernel
    yum:
      name: kernel
      state: latest
    notify: reboot_server

4. Add a handler that reboots the server in case the kernel was successfully updated:

---
- name: update the kernel
  hosts: all
  force_handlers: true
  tasks:
  - name: update kernel
    yum:
      name: kernel
      state: latest
    notify: reboot_server
  handlers:
  - name: reboot_server
    command: reboot

5. Run the playbook using ansible-playbook exercise73.yaml and observe its result. Notice that the handler runs only if the kernel was updated. If the kernel already was at the latest version, nothing has changed and the handler does not run. Also notice that it wasn’t really necessary to use force_handlers in the play header, but by using it anyway, at least you now know where to use it.

Dealing with Failures

When working with playbooks, you can get unexpected results. To deal with these situations, you need to understand normal playbook operations. Based on your understanding of the expected result, you can handle a situation if something doesn’t go as expected.

Understanding Task Execution

Tasks in Ansible playbooks are executed in the order they are specified. If a task in the playbook fails to execute on a host, the task generates an error and the play does not further execute on that specific host. This also goes for handlers: if any task that follows the task that triggers a handler fails, the handlers do not run. In both of these cases, it is important to know that the tasks that have run successfully still generate their result. Because this can give an unexpected result, it is important to always restore the original situation if that happens.

In some cases you might want the entire playbook to stop executing on all hosts when a failing task is encountered. If that is the case, you can use any_errors_fatal in the play header or on a block (blocks are explained later in this chapter).

Managing Task Errors

Generically, tasks can generate three different types of results. Table 7-4 gives an overview.

Table 7-4 Tasks Result Overview

Image

As you saw before, if a task fails, all execution stops. This outcome can be prevented by using ignore_errors and force_handlers. If you specify ignore_errors: yes in a task, the playbook continues, even after processing the failing task. Likewise, force_handlers can be used to ensure that handlers will be executed, even if a failing task was encountered. Listing 7-21 shows an example of a playbook that uses ignore_errors.

Listing 7-21 Example with ignore_errors

---
- name: restart sshd only if crond is running
  hosts: all
  tasks:
    - name: get the crond server status
      command: /usr/bin/systemctl is-active crond
      ignore_errors: yes
      register: result
    - name: restart sshd based on crond status
      service:
        name: sshd
        state: restarted
      when: result.rc == 0

The essence of the playbook in Listing 7-21 is that the sshd service needs to be restarted, based on the result of the current status of the crond service. To find the current status of the crond service, you use the command systemctl is-active crond, and the result of that command is registered. To allow the playbook to continue, even if the service currently is not running, you include ignore_errors: yes in the task definition. This allows the result of the command module to be recorded using register. Next, the sshd service is restarted based on the value of the registered command result.

In Listing 7-17 you learned how to work with handlers. Also in this playbook, you saw how the handlers aren’t triggered if any task in the play will fail. You can easily fix this issue by including force_handlers in the play header. Listing 7-22 shows the modified playbook where this approach is applied.

Listing 7-22 Forcing Handlers to Run

---
- name: create file on localhost
  hosts: localhost
  tasks:
  - name: create index.html on localhost
    copy:
      content: "welcome to the webserver"
      dest: /tmp/index.html

- name: set up web server
  hosts: all
  force_handlers: yes
  tasks:
    - name: install httpd
      yum:
        name: httpd
        state: latest
    - name: copy index.html
      copy:
        src: /tmp/index.html
        dest: /var/www/html/index.html
      notify:
        - restart_web
    - name: copy nothing - intended to fail
      copy:
        src: /tmp/nothing
        dest: /var/www/html/nothing.html
  handlers:
    - name: restart_web
      service:
        name: httpd
        state: restarted

As you can see, regardless of the fact that the copy nothing task fails, the handler is executed anyway because of the use of force_handlers.

Specifying Task Failure Conditions

If a task successfully runs a command, according to Ansible it has run successfully, even if the command output itself indicates a failure. In that case it makes sense to set a failure condition anyway. You can do so by using the failed_when conditional. Notice that failed_when is a true conditional, so it must be used to evaluate some expression. Listing 7-23 shows a sample script where this conditional is demonstrated.

Listing 7-23 Using failed_when

---
- name: demonstrating failed_when
  hosts: all
  tasks:
  - name: run a script
    command: echo hello world
    ignore_errors: yes
    register: command_result
    failed_when: "’world’ in command_result.stdout"
  - name: see if we get here
    debug:
      msg: second task executed

In Listing 7-23, the command module is used to run a simple command. As it runs the echo command, the command itself would be considered successful. However, register is used to capture command output, and failed_when is used to define the command as failed when the text “world” occurs in the stdout of the command. That means the command generates a failed status in all cases. At the same time, the ignore_errors: yes statement enables the task to fail, after which the playbook still continues. As a result, after showing the failure on the first task, the second task does get executed, as you can see in Listing 7-24.

Listing 7-24 Result of Running ansible-playbook listing723yaml

[ansible@control ~]$ ansible-playbook listing723.yaml

PLAY [demonstrating failed_when] *******************************************************

TASK [Gathering Facts] *****************************************************************
ok: [ansible1]
ok: [ansible2]

TASK [run a script] ********************************************************************
fatal: [ansible2]: FAILED! => {"changed": true, "cmd": ["echo", "hello", "world"], "delta": "0:00:00.004303", "end": "2020-04-06 03:44:56.748552", "failed_when_result": true, "rc": 0, "start": "2020-04-06 03:44:56.744249", "stderr": "", "stderr_lines": [], "stdout": "hello world", "stdout_lines": ["hello world"]}
...ignoring
fatal: [ansible1]: FAILED! => {"changed": true, "cmd": ["echo", "hello", "world"], "delta": "0:00:00.004261", "end": "2020-04-06 03:44:56.770166", "failed_when_result": true, "rc": 0, "start": "2020-04-06 03:44:56.765905", "stderr": "", "stderr_lines": [], "stdout": "hello world", "stdout_lines": ["hello world"]}
...ignoring

TASK [see if we get here] **************************************************************
ok: [ansible1] => {
    "msg": "second task executed"
}
ok: [ansible2] => {
    "msg": "second task executed"
}

PLAY RECAP *****************************************************************************
ansible1                   : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=1
ansible2                   : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=1

Alternatively, you can use the fail module to specify when a task fails. Using this module makes sense only if when is used to define the exact condition when a failure should occur. Listing 7-25 shows how the playbook from Listing 7-23 can be rewritten using the fail module.

Listing 7-25 Using the fail Module

---
- name: demonstrating the fail module
  hosts: all
  ignore_errors: yes
  tasks:
  - name: run a script
    command: echo hello world
    register: command_result
  - name: report a failure
    fail:
      msg: the command has failed
    when: "’world’ in command_result.stdout"
  - name: see if we get here
    debug:
      msg: second task executed

Notice that in the rewritten playbook in Listing 7-25, the ignore_errors statement has moved from the task definition to the play header. Without this move, the message “second task executed” would never be shown because the fail module always generates a failure message. The main advantage of using the fail module instead of using failed_when is that the fail module can easily be used to set a clear failure message, which is not possible when using failed_when.

Managing Changed Status

In Ansible, there are commands that change something and commands that don’t. Some commands, however, are not very obvious in reporting their status. Run the playbook in Listing 7-26, for example.

Listing 7-26 Sample Playbook Contents

---
- name: demonstrate changed status
  hosts: all
  tasks:
  - name: check local time
    command: date
    register: command_result

  - name: print local time
    debug:
      var: command_result.stdout

As you can see in Listing 7-27, this playbook reports a changed status, even if nothing really was changed!

Listing 7-27 Result of Running ansible-playbook listing726.yaml

[ansible@control ~]$ ansible-playbook listing726.yaml

PLAY [demonstrate changed status] ******************************************************

TASK [Gathering Facts] *****************************************************************
ok: [ansible2]
ok: [ansible1]

TASK [check local time] ****************************************************************
changed: [ansible2]
changed: [ansible1]

TASK [print local time] ****************************************************************
ok: [ansible1] => {
    "command_result.stdout": "Mon Apr  6 04:11:26 EDT 2020"
}
ok: [ansible2] => {
    "command_result.stdout": "Mon Apr  6 04:11:26 EDT 2020"
}

PLAY RECAP *****************************************************************************
ansible1                   : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
ansible2                   : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

In some cases, managing the changed status can be useful in avoiding unexpected results while running a playbook. Listing 7-28 shows how the sample playbook from Listing 7-26 can be changed accordingly, using changed_when, and Listing 7-29 shows the result of running the playbook in Listing 7-28. If you set changed_when to false, the playbook reports only an ok or failed status and never reports a changed status.

Listing 7-28 Using changed_when

---
- name: demonstrate changed status
  hosts: all
  tasks:
  - name: check local time
    command: date
    register: command_result
    changed_when: false

  - name: print local time
    debug:
      var: command_result.stdout

Listing 7-29 Result of Running ansible-playbook listing728.yaml

[ansible@control ~]$ ansible-playbook listing728.yaml

PLAY [demonstrate changed status] ******************************************************

TASK [Gathering Facts] *****************************************************************
ok: [ansible2]
ok: [ansible1]

TASK [check local time] ****************************************************************
ok: [ansible2]
ok: [ansible1]

TASK [print local time] ****************************************************************
ok: [ansible1] => {
    "command_result.stdout": "Mon Apr  6 04:15:26 EDT 2020"
}
ok: [ansible2] => {
    "command_result.stdout": "Mon Apr  6 04:15:26 EDT 2020"
}

PLAY RECAP *******************************************************************
ansible1                   : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
ansible2                   : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Using Blocks

When you are working with conditional statements, blocks can be very useful. A block is a group of tasks to which a when statement can be applied. As a result, if a single condition is true, multiple tasks can be executed. To do so, between the tasks: statement in the play header and the actual tasks that run the specific modules, you can insert a block: statement. Listing 7-30 shows an example.

Listing 7-30 Using Blocks

- name: simple block example
  hosts: all
  tasks:
  - name: setting up http
    block:
    - name: installing http
      yum:
        name: httpd
        state: present
    - name: restart httpd
      service:
        name: httpd
        state: started
    when: ansible_distribution == "CentOS"

To understand the sample playbook in Listing 7-30, notice that the when statement is applied at the same level as the block definition. When you define it this way, the tasks in the block are executed only if the when statement is true.

Using Blocks with rescue and always Statements

Listing 7-30 shows how a block is used to group different tasks together. Blocks can be used for simple error handling as well, in such a way that if any task that is defined in the block statement fails, the tasks that are defined in the rescue section are executed. Besides that, an always section can be used to define tasks that should always run, regardless of the success or failure of the tasks in the block. Listing 7-31 shows an example.


Exam tip

On the RHCE exam, you definitely need to work out a playbook that handles complex conditional statements. Make sure you fully understand how to use blocks, because they provide an excellent way to do this.


Listing 7-31 Using Blocks, rescue, and always

---
- name: using blocks
  hosts: all
  tasks:
  - name: intended to be successful
    block:
    - name: remove a file
      shell:
        cmd: rm /var/www/html/index.html
    - name: printing status
      debug:
        msg: block task was operated
    rescue:
    - name: create a file
      shell:
        cmd: touch /tmp/rescuefile
    - name: printing rescue status
      debug:
        msg: rescue task was operated
    always:
    - name: always write a message to logs
      shell:
        cmd: logger hello
    - name: always printing this message
      debug:
        msg: this message is always printed

In Listing 7-32 you can see the output of the command ansible-playbook listing731yaml.

Listing 7-32 Output of Command ansible-playbook listing731.yaml

[ansible@control ~]$ ansible-playbook listing731.yaml

PLAY [using blocks] ********************************************************************

TASK [Gathering Facts] *****************************************************************
ok: [ansible2]
ok: [ansible1]

TASK [remove a file] *******************************************************************
[WARNING]: Consider using the file module with state=absent rather than running ’rm’.
If you need to use command because file is insufficient you can add ’warn: false’ to
this command task or set ’command_warnings=False’ in ansible.cfg to get rid of this
message.
changed: [ansible2]
changed: [ansible1]

TASK [printing status] *****************************************************************
ok: [ansible1] => {
    "msg": "block task was operated"
}
ok: [ansible2] => {
    "msg": "block task was operated"
}

TASK [always write a message to logs] **************************************************
changed: [ansible2]
changed: [ansible1]

TASK [always printing this message] ****************************************************
ok: [ansible1] => {
    "msg": "this message is always printed"
}
ok: [ansible2] => {
    "msg": "this message is always printed"
}

PLAY RECAP *****************************************************************************
ansible1                   : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
ansible2                   : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

As you can see in the output in Listing 7-32, the tasks in the block were successfully executed, and for that reason, the tasks in the rescue section were all skipped, and the tasks in the always section were also executed successfully. As a result of the code in this specific playbook, the next time that the same playbook is used, it will not be able to run the tasks in the block statement (as the file was already removed in the previous run) and, for that reason, run the tasks in the rescue statement as well as the tasks in always. You can see this in the output in Listing 7-33.

Listing 7-33 Tasks in rescue Are Executed If Tasks in block Are Failing

[ansible@control ~]$ ansible-playbook listing731.yaml

PLAY [using blocks] ********************************************************************

TASK [Gathering Facts] *****************************************************************
ok: [ansible2]
ok: [ansible1]

TASK [remove a file] *******************************************************************
[WARNING]: Consider using the file module with state=absent rather than running ’rm’.
If you need to use command because file is insufficient you can add ’warn: false’ to
this command task or set ’command_warnings=False’ in ansible.cfg to get rid of this
message.
fatal: [ansible2]: FAILED! => {"changed": true, "cmd": "rm /var/www/html/index.html", "delta": "0:00:00.003018", "end": "2020-04-06 05:16:29.810703", "msg": "non-zero return code", "rc": 1, "start": "2020-04-06 05:16:29.807685", "stderr": "rm: cannot remove ’/var/www/html/index.html’: No such file or directory", "stderr_lines": ["rm: cannot remove ’/var/www/html/index.html’: No such file or directory"], "stdout": "", "stdout_lines": []}
fatal: [ansible1]: FAILED! => {"changed": true, "cmd": "rm /var/www/html/index.html", "delta": "0:00:00.012466", "end": "2020-04-06 05:16:29.836735", "msg": "non-zero return code", "rc": 1, "start": "2020-04-06 05:16:29.824269", "stderr": "rm: cannot remove ’/var/www/html/index.html’: No such file or directory", "stderr_lines": ["rm: cannot remove ’/var/www/html/index.html’: No such file or directory"], "stdout": "", "stdout_lines": []}

TASK [create a file] *******************************************************************
[WARNING]: Consider using the file module with state=touch rather than running ’touch’.
If you need to use command because file is insufficient you can add ’warn: false’ to
this command task or set ’command_warnings=False’ in ansible.cfg to get rid of this
message.
changed: [ansible2]
changed: [ansible1]

TASK [printing rescue status] **********************************************************
ok: [ansible1] => {
    "msg": "rescue task was operated"
}
ok: [ansible2] => {
    "msg": "rescue task was operated"
}

TASK [always write a message to logs] **************************************************
changed: [ansible2]
changed: [ansible1]

TASK [always printing this message] ****************************************************
ok: [ansible1] => {
    "msg": "this message is always printed"
}
ok: [ansible2] => {
    "msg": "this message is always printed"
}

PLAY RECAP *****************************************************************************
ansible1                   : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=1    ignored=0
ansible2                   : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=1    ignored=0

As you can see in Listing 7-33, the tasks in the block statement have failed, which is why the tasks in the rescue statement are executed instead.

In the output of Listing 7-32 as well as Listing 7-33, a warning is shown because the command module is used to run a command that can also be issued using the file module. This is just a warning in which Ansible informs you that a better solution is available. This relates to one of the basic principles while working with Ansible: Don’t use the command module if a specific module is available to do the same thing. As indicated, you can set command_warnings=False in ansible.cfg to avoid seeing this message. Or you can rewrite the task that uses the shell module to remove a file (which is not a very Ansible way of doing things). Better use the file module and set its argument state to absent to remove the file.


Note

Blocks are useful, but one thing is inconvenient: you cannot use a block on a loop. If you need to iterate over a list of values, think of using a different solution.


Summary

In this lesson you learned about using conditionals in Ansible. In the first part you explored how to work with loop, allowing iteration over a list of items. Next, you learned how to define conditional tasks using when. After that, we explored the workings of handlers, which allow for conditional task execution. In the last part of this chapter, you read how to manage error handling in Ansible playbook, in which using blocks plays an important role.

Exam Preparation Tasks

As mentioned in the section “How to Use This Book” in the Introduction, you have a couple of choices for exam preparation: the exercises here, Chapter 16, “Final Preparation,” and the exam simulation questions on the companion website.

Review All Key Topics

Review the most important topics in this chapter, noted with the Key Topic icon in the outer margin of the page. Table 7-5 lists a reference of these key topics and the page numbers on which each is found.

Image

Table 7-5 Key Topics for Chapter 7

Image

Memory Tables

Print a copy of Appendix D, “Memory Tables” (found on the companion website), or at least the section for this chapter, and complete the tables and lists from memory. Appendix E, “Memory Tables Answer Key,” also on the companion website, includes completed tables and lists to check your work.

Define Key Terms

Define the following key terms from this chapter, and check your answers in the glossary:

block

failure condition

handler

item

lookup plug-in

when

Review Questions

1. If a loop is used on the contents of the variable “{{ services }}”, what is the name of the specific variable that should be used while iterating over the different values?

2. What should you do to loop over the values in a dictionary?

3. Which statement should you use to run a task only if a specific condition is true?

4. What do you need to include in your playbook to have it execute a task only if the variable myvar exists?

5. How do you write a when statement that tests whether the variable myvar has the string value “myvalue”?

6. Which conditional test should you use to verify that mypackage is a value in the list mypackages?

7. How would you write a test that checks whether var1 has the value value1 and var2 has the value value2, or var3 has the value value3 and var4 has the value value4?

8. What can you do to check whether the output of a command, as registered in the variable cmd_out using register, contains the text “error”?

9. How can you make sure that a play continues, even if a specific task has resulted in an error?

10. How can you stop execution of a complete playbook if any task generates an error?

End-of-Chapter Lab

Now that we’re at the end of this chapter, let’s do a lab. In this lab, you install and set up an Apache web server.

Lab 7-1

Write a playbook that meets the following requirements. Use multiple plays in a way that makes sense.

• Write a first play that installs the httpd and mod_ssl packages on host ansible2.

• Use variable inclusion to define the package names in a separate file.

• Use a conditional to loop over the list of packages to be installed.

• Install the packages only if the current operating system is CentOS or RedHat (but not Fedora) version 8.0 or later. If that is not the case, the playbook should fail with the error message “Host hostname does not meet minimal requirements,” where hostname is replaced with the current host name.

• On the Ansible control host, create a file /tmp/index.html. This file must have the contents “welcome to my webserver”.

• If the file /tmp/index.html is successfully copied to /var/www/html, the web server process must be restarted. If copying the package fails, the playbook should show an error message.

• The firewall must be opened for the http as well as the https services.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset