Check if any task in block changed? - ansible

Is it possible to execute a task in Ansible only if at least one task in a specific block was changed?
So, something like this:
- name: Tasks block
block:
- name: Task 1
# ...
- name: Task 2
# ...
- name: Task 3
# ...
- name: Task 4
# ...
- name: Task 5
# ...
- name: Conditional task
when: block_result.changed
# ...
I know it is possible by registering a variable for each task in the block, then check every single one of them.
But is it possible to be done on the level of block? This is especially possible to avoid defining a lot of variables in case I have many tasks in one block.
My main goal is to evaluate the whole changed/not changed after the whole block is finished, so that the conditional task is executed only once if needed.

Your use case seems like the perfect fit for a handler, which are meant to run a task only once, even if multiple tasks are notifying them.
A typical use case, as presented in the documentation is when you are configuring a daemon and its configuration file(s), even if multiple tasks are changing some components, you want to be as less disruptive as possible and restart the daemon only once:
Notifying the same handler multiple times will result in executing the handler only once regardless of how many tasks notify it. For example, if multiple tasks update a configuration file and notify a handler to restart Apache, Ansible only bounces Apache once to avoid unnecessary restarts.
Source: https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_handlers.html#notifying-handlers
If you need to trigger it at this exact location in your playbook, you will also need a meta: flush_handlers task, in order to trigger the handlers right away, and not at the end of all the tasks.
tasks:
- block:
- name: Task 1
shell: "true"
changed_when: false
- name: Task 2
shell: "true"
changed_when: true
- name: Task 3
shell: "true"
changed_when: false
notify:
- Conditional task
- name: Flush handlers
meta: flush_handlers
- debug:
msg: I run after the handler
handlers:
- name: Conditional task
debug:
msg: Running this conditional task now
Output of a play with the above code:
TASK [Task 1] *****************************************************
ok: [localhost]
TASK [Task 2] *****************************************************
changed: [localhost]
TASK [Task 3] *****************************************************
ok: [localhost]
TASK [Flush handlers] *********************************************
RUNNING HANDLER [Conditional task] ********************************
ok: [localhost] =>
msg: Running this conditional task now
TASK [debug] ******************************************************
ok: [localhost] =>
msg: I run after the handler

Related

Rerunning same Ansible task a few times

I am trying to implement a task that runs in a loop, for example, ten times.
Currently, the task runs only once.
I am trying something like the following:
my_role/tasks/main.yaml
---
- set_fact:
counter: 0
- name: "Iteration"
import_task: my_task.yaml
until: counter <= 10
...
my_role/tasks/my_task.yaml
---
- name: "task 1"
...
- name: "task 2"
...
- set_fact:
counter=={{ counter | int + 1 }}
...
I expected "task 1" and "task 2" to run ten times.
I would be happy to get some ideas on how to implement such loop.
You should use include_tasks module and loop over it.
my_role/tasks/main.yaml
---
- name: "Iteration"
ansible.builtin.include_tasks:
file: my_task.yaml
with_sequence: start=1 end=10
...
my_role/tasks/my_task.yaml
---
- name: "task 1"
...
- name: "task 2"
...
...
Ansible is an automation tool that is commonly used to configure and manage servers, networks, and other IT infrastructure. One of the key features of Ansible is its idempotence, which means that running the same task multiple times on the same system should produce the same end result, regardless of how many times the task is run.
Here is a plan on how to rerun the same Ansible task multiple times:
Create or open an existing Ansible playbook. A playbook is a YAML file that contains one or more plays, which are a list of tasks that are executed on a specific set of hosts.
Define the task that you want to rerun in the playbook using the Ansible modules. These modules are pre-built scripts that can perform specific actions on the remote systems, such as installing software, creating users, and configuring services.
Run the playbook using the ansible-playbook command. You can run the playbook on one or more specific hosts by specifying the -l option, or on all the hosts defined in your inventory file by using the -i option.
Ansible uses the idempotence feature to make sure that the same task is executed only once on the same host. If the task has already been executed and the state of the host hasn't changed, Ansible will not execute the task again.
If you want to force the task to rerun even if the state of the host hasn't changed, you can use the --force-handlers or --force-rerun options when running the ansible-playbook command.
To check the results of the tasks, you can check the log files generated by Ansible or the output of the ansible-playbook command.
Here is an example of how to run the same task multiple times using ansible-playbook command:
ansible-playbook playbook.yml --extra-vars "var1=value1 var2=value2"
You can run this command as many times as you want and Ansible will make sure that the task is executed only once, unless you use the --force-handlers or --force-rerun options.
Please note that if the task is modifying a state of the host, rerunning the task will have an effect and will change the state of the host.
How to implement task(s) that runs in a loop?
To do so, you may have a look into Loops and a minimal example like
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- name: Example loop
debug:
msg: "{{ item }}"
loop: "{{ range(1, 11) | list }}"
register: result
resulting into an output of
TASK [Example loop] **********
ok: [localhost] => (item=1) =>
msg: 1
ok: [localhost] => (item=2) =>
msg: 2
ok: [localhost] => (item=3) =>
msg: 3
ok: [localhost] => (item=4) =>
msg: 4
ok: [localhost] => (item=5) =>
msg: 5
ok: [localhost] => (item=6) =>
msg: 6
ok: [localhost] => (item=7) =>
msg: 7
ok: [localhost] => (item=8) =>
msg: 8
ok: [localhost] => (item=9) =>
msg: 9
ok: [localhost] => (item=10) =>
msg: 10
whereby it is possible to replace the current single task debug against an import_task for a task file.
Further Documentation
Extended loop variables
What's the difference between include_tasks and import_tasks?

Run play book with multiple conditions

I have written a playbook which run handlers if the task was successful.Now I want to use some type conditions that if the above task fail then run different handler. Just like simple IF else statement works.
Current PLAYBOOK
tasks:
- name: checking file format
command: named-checkzone example.com /var/named/example.com
notify: service
handlers:
- name: "service reload"
command: rndc reload example.com
listen: "service"
Now I want to omit file name in configuration file if the main tasks fails
Q: "1) Run handler if the task was successful. 2) If the task fails then run another handler."
A: The simple way is to notify a handler when a task is changed. When a task fails the status is failed not changed and no handler is notified.
In this particular case, you don't care whether the command succeeded or failed. The handler shall always be notified. This can be achieved by an explicit combination of ignore_errors, failed_when, and changed_when.
Notify both handlers service success and service fail. The conditions in the handlers will decide which handler will run. For example the playbook
shell> cat playbook.yml
- hosts: localhost
tasks:
- command: "{{ cmd }}"
register: named_checkzone_result
ignore_errors: true
failed_when: false
changed_when: true
notify:
- service success
- service fail
handlers:
- name: service success
debug:
msg: Service success
when: named_checkzone_result.rc == 0
- name: service fail
debug:
msg: Service fail
when: named_checkzone_result.rc == 1
gives (abridged) if the command succeeds
shell> ansible-playbook playbook.yml -e "cmd=true"
TASK [command] ****
changed: [localhost]
RUNNING HANDLER [service success] ****
ok: [localhost] =>
msg: Service success
RUNNING HANDLER [service fail] ****
skipping: [localhost]
gives (abridged) if the command fails
shell> ansible-playbook playbook.yml -e "cmd=false"
TASK [command] ****
changed: [localhost]
RUNNING HANDLER [service success] ****
skipping: [localhost]
RUNNING HANDLER [service fail] ****
ok: [localhost] =>
msg: Service fail

Wait for handler for every item in with_lines - Ansible

Ansible Version: ansible 2.4.2.0
I want to start VM sequentially depends on the role(master/backup). Multiple VM IDs are stored in 2 files master & backup. The controller flow should like below
Iterate VM IDs one by one from a file
For every iteration, the handler should notified. i.e the iteration should WAIT for handler complete
Iteration should not move foreword if handler is failed(or in WAITING state).
For reference, you see the below playbook
- name: Performs Power Actions VMs
hosts: localhost
vars:
- status: "{% if action=='stop' %}SHUTOFF{% else %}ACTIVE{% endif %}" # For Checking VM status
tasks:
- name: Staring Master VM
shell: |
echo {{ item }} > /tmp/current
echo "RUN nova start {{ item }} HERE!!!"
when: action == "start"
with_lines: cat ./master
notify: "Poll VM power status"
- name: Starting Backup VM
shell: |
echo {{ item }} > /tmp/current
echo "RUN nova start {{ item }} HERE!!!"
when: action == "start"
with_lines: cat ./backup
notify: "Poll VM power status"
handlers:
- name: Poll VM power status
shell: openstack server show -c status --format value `cat /tmp/current`
register: cmd_out
until: cmd_out.stdout == status
retries: 5
delay: 10
For above playbook, what I see is the handlers is notified after entire iteration is complete.
PLAY [Performs Power Actions on ESC VMs] **********************************************************************************************
TASK [Stopping Backup VM] *********************************************************************************************************
skipping: [localhost] => (item=Test)
TASK [Stopping Master VM] *********************************************************************************************************
skipping: [localhost] => (item=Test)
TASK [Staring Master VM] **********************************************************************************************************
changed: [localhost] => (item=Test)
TASK [Starting Backup VM] *********************************************************************************************************
changed: [localhost] => (item=Test)
TASK [Removing tmp files] *************************************************************************************************************
changed: [localhost] => (item=./master)
changed: [localhost] => (item=./backup)
RUNNING HANDLER [Poll VM power status] ********************************************************************************************
FAILED - RETRYING: Poll ESC VM power status (5 retries left).
^C [ERROR]: User interrupted execution
Is there any better approach to solve this problem? or Any suggestion how to fit block in this playbook to solve?
PS: The dummy command in tasks RUN nova start {{ item }} HERE!!! doesn't wait. That's why I have to check the status manually.
By default, handlers are run at the end of a play.
However, you can force the already notified handlers to run at a given time in your play by using the meta module.
- name: force running of all notified handlers now
meta: flush_handlers
In your case, you just have to add it in between your two vm start tasks
Edit: This will actually work in between your two tasks but not for each iteration in a single task so it is not really answering your full requirement.
An other approach (to be developed) would be to include your check command directly in your task that should not return until conditions are met.
Have you considered exploring the galaxy of openstack related modules? They might solve your current problems as well.

ansible playbook run a task only once

I need to run a task ,set a flag and the second time the play runs run the task only if the flag is not set
Play at a later stage
- name: Dump all databases
mysql_db:
state: dump
name: all
target: /root/mysql_all.sql
when: ansible_local.mysql.replication.setup is not defined
- name: create directory for ansible custom facts
file: state=directory recurse=yes path=/etc/ansible/facts.d
- name: install custom fact stating mysql is setup
template:
src: mysql.fact.j2
dest: /etc/ansible/facts.d/mysql.fact
The problem is that the fist time this play runs its throwing an error.
FAILED! => {"failed": true, "msg": "The conditional check 'ansible_local.mysql.replication.setup is not defined' failed. The error was: error while evaluating conditional (ansible_local.mysql.replication.setup is not defined): 'ansible_local' is undefined
What is the best way to run a task only in the first run and skip for subsequent runs.
you should make a task prior that registers if /root/mysql_all.sql exists then add it to your when clause.
Example:
- name: check if dump exists
stat:
path: /root/mysql_all.sql
register: mysqldump
- name: Dump all databases
mysql_db:
state: dump
name: all
target: /root/mysql_all.sql
when:
- ansible_local.mysql.replication.setup is not defined
- mysqldump.stat.exists == true
- name: create directory for ansible custom facts
file: state=directory recurse=yes path=/etc/ansible/facts.d
- name: install custom fact stating mysql is setup
template:
src: mysql.fact.j2
dest: /etc/ansible/facts.d/mysql.fact
One option is to utilize the fact cache. When enabled, you can set cacheable facts in your play and check for them.
Fact caching always takes place. There are various fact cache plug-ins available of which the memory plug-in is the default, and json file and redis cache are the most popular. You can only set one plug-in. Refer to
https://docs.ansible.com/ansible/latest/plugins/cache.html
When you want to explore with the json file you can set the environment variables as follows:
export ANSIBLE_CACHE_PLUGIN=jsonfile
export ANSIBLE_CACHE_PLUGIN_CONNECTION="~/ansiblefactcache"
In your play book you can check for facts and set them as cacheable, Refer to
https://docs.ansible.com/ansible/latest/collections/ansible/builtin/set_fact_module.html
A small example playbook.yaml:
- name: Example Fact Cache Playbook
hosts: all
gather_facts: false # the default is true; it gathers various host facts
tasks:
- name: Runs when examplefact equals something
debug:
msg: "Runs when examplefact equals something"
when: ansible_facts['examplefact'] is defined and ansible_facts['examplefact'] == "something"
- name: Does not run when examplefact equals something
debug:
msg: "does not run when examplefact equals something"
when: ansible_facts['examplefact'] is not defined or ansible_facts['examplefact'] != "something"
- name: Set the examplefact to something
set_fact:
examplefact: "something"
cacheable: true
Note the usage of the cacheable instruction. When true the fact goes into the cache.
After having run this small playbook you will notice the creation of a localhost file in your home's subfolder ansiblefactcache which contains your cached facts.
Also note the usage of the gather_facts instruction. The default being true will scan your machine for various details such as environment variables, network details, etc. All are cached. You can play with it and see the localhost file being populated with it.
You can also try to edit the localhost file yourself or even delete it and run the play again.
I used the following inventory file inventory.yaml:
all:
hosts:
localhost:
ansible_connection: local
And I run ansible as follows:
ansible-playbook playbook.yaml -i inventory.yaml -vvv
First run yields the following:
PLAY [Example Fact Cache Playbook] ********************************************************************************************************************************************************************************
TASK [Runs when examplefact equals something] *********************************************************************************************************************************************************************
skipping: [localhost]
TASK [Does not run when examplefact equals something] *************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "does not run when examplefact equals something"
}
TASK [Set the examplefact to something] ***************************************************************************************************************************************************************************
ok: [localhost]
Second run yields the following:
PLAY [Example Fact Cache Playbook] ********************************************************************************************************************************************************************************
TASK [Runs when examplefact equals something] *********************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Runs when examplefact equals something"
}
TASK [Does not run when examplefact equals something] *************************************************************************************************************************************************************
skipping: [localhost]
TASK [Set the examplefact to something] ***************************************************************************************************************************************************************************
ok: [localhost]

How to filter tasks from include statement

Currently, I would pass a parameter to include and use that variable in another file to check conditionally which block to execute.
eg,
- include: tasks/common.yml param=dns
tasks/common.yml:
---
- block:
- name: do something interesting
when: param == "dns"
- block:
- name: do another thing
when: param == "ip"
This method works however in my output all the tasks that don't match the condition are displayed as skipping. Since I have dozens of tasks and try to reuse some of them I see a lot of skipping tasks.
Is there a better way doing this? Using ansible 2.2
There is no support for that in the playbook or in ansible parameters. One way is to configure your ansible to not display skipped tasks.
See: stdout_callback
New in version 2.0.
This setting allows you to override the default stdout callback for
ansible-playbook:
stdout_callback = skippy
Edit your ansible config file (usually /etc/ansible/ansible.cfg) and add this line under defaults section
stdout_callback = skippy
which tells ansible not to display skipped tasks.
Before setting to skippy
TASK [Run scripts] *************************************************************
changed: [localhost] => (item=1)
skipping: [localhost] => (item=2)
After setting to skippy
TASK [Run scripts] *************************************************************
changed: [localhost] => (item=1)

Resources