Task Control
-
Handlers
-
Using Loops and Items
-
Using when to Run Tasks Conditionally
Handlers
Using Loops and Items
Using when to Run Tasks Conditionally
---
- 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: restartedAll 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.
Create the source file using touch /tmp/nothing on the control host and run the task again.
After creating the source file and running the playbook again, the handler still doesn’t run.
Handlers run only if the task that triggers them gives a changed status.
Run an ad hoc command to remove the /var/www/html/index.html file on the managed hosts and run the playbook again:
ansible ansible2 -m file -a "name=/var/www/html/index.html state=absent"
Run the playbook again and you’ll see the handler runs.
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.
Two methods to get Handlers to run even if a subsequent task fails:
force_handlers: true (More specific and preferred)
ignore_errors: true
• 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.
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_server4. 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: reboot5. Run the playbook using ansible-playbook exercise73.yaml andobserve 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.
any_errors_fatal
Generically, tasks can generate three different types of results. ok
ignore_errors: yes
force_handlers. If
---
- 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 ---
- 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: restartedfailed_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 executedfail 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 executedIn Ansible, there are commands that change something and commands that don’t. Some commands, however, are not very obvious in reporting their status.
---
- name: demonstrate changed status
hosts: all
tasks:
- name: check local time
command: date
register: command_result
- name: print local time
debug:
var: command_result.stdoutReports a changed status, even if nothing really was changed!
Managing the changed status can be useful in avoiding unexpected results while running a playbook.
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---
- name: simple block example
hosts: all
tasks:
- name: setting up http
block:
- name: installing http
yum:
name: httpd
state: present
- name: restart httpod
service:
name: httpd
state: started
when: ansible_distribution == "CentOS"- 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 printedcommand_warnings=False
Setting in ansible.cfg to avoid seeing command module warning message.
you cannot use a block on a loop.
If you need to iterate over a list of values, think of using a different solution.
Install software packages using the yum module and then ensures that services installed from these packages are started using the service module:
---
- 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
- smbA loop is defined at the same level as the service module.
The loop has a list of services in a list (array) statement
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.
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.
When using loop, the module is activated again on each iteration.
Include the loop from 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 }}"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.
Use variables that are imported from the file vars/users-list:
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: usersUse the list in a playbook:
---
- 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 }}"With_keyword Options Overview with_items
Loop over a list using with_keyword:
---
- 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 }}"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: latest2. 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.
Install the right software package for the Apache web server, based on the Linux distribution that was found in the Ansible facts. Notice that
---
- 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"not a part of any properties of the modules on which it is used
must be indented at the same level as the module itself.
For a string test, the string itself must be between double quotes.
Without the double quotes, it would be considered an integer test.
Common conditional tests that you can perform with the when statement:
Variable exists
variable is defined Variable does not exist
variable is not defined First variable is present in list mentioned as second
ansible_distribution in distributions Variable is true, 1 or yes
variable Variable is false, 0 or no
not variable Equal (string)
key == “value” Equal (numeric)
key == value Less than
key < value Less than or equal to
key <= value Greater than
key > value Greater than or equal to
key >= value Not equal to
key != value
Look for “Tests” in the Ansible documentation, and use the item that is found in Templating (Jinja2).
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”.
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.
Checks for the existence of a specific disk device, using variable is defined and variable is not defined. All failing tests result in the message “skipping.”
---
- 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 ---
- 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_packagesBoolean check
string comparisons and integer comparisons
---
- 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 ---
- 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" ---
- 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 ) ---
- 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 ---
- 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’) != -1passwd_contents.stdout.find,
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: result3. 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 == 05. 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.