In a playbook,
you might want to execute different tasks depending on the value of a
fact or a variable or the result of a previous task.
You may want the value of some variables to depend
on the value of other variables.
You can do all of these things with conditionals.
Ansible uses Jinja2 tests and filters in conditionals.
Basic conditions are used with the when clause,
so the simplest conditional statement applies to a single task.
You create the task, and then you add a when statement that applies to the task.
Ansible then evaluates the task for all the hosts
when running the task or playbook.
For example, if you're installing MySQL on multiple machines,
some of which may have SELinux enabled,
you might have a task to configure SELinux to allow MySQL to run.
You would only want the task to run the machines that have SELinux enabled,
so sometimes you want to execute or skip a task based on a fact.
When you're using conditions based on facts,
you can, for example, install a certain package,
but only when the operating system is a particular version.
You can, for example, configure a firewall and host with an internal IP address,
and you can also perform cleanup tasks only when a file system is getting full.
You can also create conditionals based on variables defined
in playbooks or even in the inventory.
So we have playbook variables, registered variables,
and facts that all can be used with conditionals and ensure that
tasks are only run if specific conditions are true.
So there are several ways Ansible can be configured
for conditional task execution.
We have, for example,
handlers for conditional task execution are used
when a task has changed something.
Then, as already mentioned, we have the when statement.
That is a pretty powerful statement.
The when statement allows you to run tasks when specific conditions are true.
You can also use the register in combination with the when statements.
So a handler is a task only executed when triggered by
a task when something has changed.
A handler is executed after all tasks in the play,
so you need to organize the contents of your playbook accordingly.
If any of the task fails when the task calls a handler,
the handler will not be executed.
We can use the force handler to change this,
and we'll see how this works in an up and coming demo.
So keep in mind that handlers are operational when something has changed.
We have the force handler that allows you to force handlers
to be started even if the task is failing,
but we need to have a change first.
So simply put, a handler is a special type of task,
and it's called only if something's changed.
Each task section in the playbook is handled separately.
Any handler notified into pre tasks or tasks or post tasks
is executed at the end of each section.
As a result, it is possible to execute one handler several times in one play.
Blocks are also very useful and create logical groups of tasks.
Blocks also offer ways to handle task errors similar to
exception handling in many programming languages.
Grouping tasks with blocks handles errors with blocks too.
So a block is a logical group of tasks,
and it's used to control how tasks are executed.
So blocks are also used in error conditional handling,
so you have to use the blocks to define the main task to run,
and then we have the rescue to define tasks that run.
If tasks defined in the block fail,
you can use always to define tasks that will run regardless of
the success or failure of blocks or rescue.
Next, let's talk about loops.
So, what happens if a single task needs to run against a list of data,
for example,
creating several user accounts or directories or change
something that's a bit more complex?
With all programming languages, we have loops,
and we have loops in Ansible too that provide an easier way to execute
repetitive tasks with fewer lines of code in the playbook.
So an example of commonly used loops include changing ownership on
several files and directories with the file module,
creating multiple users, and we'll do this in the demo with the user module,
or repeating a polling step until a certain result is reached.
So in this final section, let's have a look at managing failures,
and Ansible looks at the exit status of a task to
determine whether it's failed or not.
When any task fails,
we know that Ansible aborts the rest of the playbook on the
host and continues with the next host.
We can change this default when we want to.
For example, we can use ignore errors in the task to ignore any failures.
We also have the force_handlers that we previously spoke about.
This can force a handler that has been triggered to run even if a task fails,
but remember, there needs to be a change.
We also have the failed_when.
That allows you to specify what to look for in a command
output to recognize a specific failure.
So you may have a playbook.
That is used to clean up a load of resources for you,
and for this reason, you want the playbook to ignore every error.
In this case, when using the failed module,
the failing task must have the ignore errors set to yes.