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]
Related
I need to check if a file named deploy.db exists. If it does not exist, I need to perform a set of tasks for which I am using a block.
Below is how I run the playbook
ansible-playbook test.yml \
-e Layer=APP \
-e BASEPATH="/logs" \
-e Filenames="file1,file2,file3"
Here is the playbook test.yml:
---
- name: "Play 1"
hosts: localhost
gather_facts: false
tasks:
- name: Construct
debug:
msg: "Run"
- block:
- stat: path="{{ BASEPATH }}/deploy.db"
register: currdb
- file: path="{{ BASEPATH }}/deploy.db" state=touch recurse=no
when: currdb.stat.exists == False
- shell: "echo done>>{{ BASEPATH }}/deploy.db"
when: currdb.stat.exists == False
when: Layer == 'APP'
with_items:
- "{{ Filenames.split(',') }}"
I am getting the below error running the playbook:
ERROR! 'with_items' is not a valid attribute for a Block
The error appears to be in '/app/test.yml': line 9, column 6, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- block:
^ here
After researching a bit, I understand that neither with_items nor loop is supported by a block and the solution is to include a tasks file.
I am, however, not sure how to get that to work. Can you suggest what tweaks I need in order to make my playbook work?
Considering I am on the latest version of Ansible, are there other solutions?
TL;DR
'with_items' is not a valid attribute for a Block
The error message says it all: you cannot loop over a block.
If you need to loop over a set of tasks, put them in a separate file and use include_tasks
Implementation (and some good practice...)
Below is an implementation based on your example illustrating the solution.
Since your question and code lacks some precision and since I pointed out some bad practices, please note that:
I fixed the looped code to effectively use the filenames you loop on (I inferred it was supposed to the deploy.db file). Note the use of loop_control to disambiguate the variable name in the included file (i.e. db_filename).
I made the code idempotent as much as possible by using the ansible module copy in place of shell and dropped the touch phase.
I transformed the var names to all lowercase and underscore separator.
To make sure the copy task works on all occasion, I replaced the removed tasks with a single making sure the basepath dir exists.
I added a unique filter after filenames.split(',') as well as a trim filter on each value to remove possible duplicates and eventual spaces added by error in the coma separated list.
I used not keyword and bool filter (for extra security) rather than a bare compare to a boolean False value.
Here is the included file create_db_each.yml
---
- name: Check if file exists
stat:
path: "{{ basepath }}/{{ db_filename }}"
register: currdb
- name: Create the file with "done" line if not present
copy:
content: "done"
dest: "{{ basepath }}/{{ db_filename }}"
when: not currdb.stat.exists | bool
used in the following create_db.yml playbook
---
- name: "Create my dbs"
hosts: localhost
gather_facts: false
tasks:
- name: Make sure the base directory exists
file:
path: "{{ basepath }}"
state: directory
- name: load each db
include_tasks: "create_db_each.yml"
when: layer == 'APP'
loop: "{{ filenames.split(',') | unique | map('trim') }}"
loop_control:
loop_var: db_filename
which gives
notes:
first run only, run it again on your side to witness it reports OK everywhere
see the filenames parameter value to illustrate the use of unique and trim
$ ansible-playbook -e basepath=/tmp/my/base/path -e "filenames='a.bla, b.toto, c , z.txt,a.bla'" -e layer=APP create_db.yml
PLAY [Create my dbs] ************************************************
TASK [Make sure the base directory exists] **************************
changed: [localhost]
TASK [load each db] *************************************************
included: /home/olcla/Sources/ZZ_tests/ansitests/create_db_each.yml for localhost => (item=a.bla)
included: /home/olcla/Sources/ZZ_tests/ansitests/create_db_each.yml for localhost => (item=b.toto)
included: /home/olcla/Sources/ZZ_tests/ansitests/create_db_each.yml for localhost => (item=c)
included: /home/olcla/Sources/ZZ_tests/ansitests/create_db_each.yml for localhost => (item=z.txt)
TASK [Check if file exists] *****************************************
ok: [localhost]
TASK [Create the file with "done" line if not present] **************
changed: [localhost]
TASK [Check if file exists] *****************************************
ok: [localhost]
TASK [Create the file with "done" line if not present] **************
changed: [localhost]
TASK [Check if file exists] *****************************************
ok: [localhost]
TASK [Create the file with "done" line if not present] **************
changed: [localhost]
TASK [Check if file exists] *****************************************
ok: [localhost]
TASK [Create the file with "done" line if not present] **************
changed: [localhost]
PLAY RECAP **********************************************************
localhost: ok=13 changed=5 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
$ tree /tmp/my/base/path/
/tmp/my/base/path/
├── a.bla
├── b.toto
├── c
└── z.txt
$ for f in /tmp/my/base/path/*; do cat $f; echo; done
done
done
done
done
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?
I am creating playbook where i am taking input from user of which software version need to download as input and passing into tasks/main.yml
ansible-playbook -i inventory.txt --extra-vars 'git_version=2.7.1'
tasks/main.yml :
---
- name: git
include: ../../deploy/templates/git/git.yml
when: git_version == "1.7.4" or
git_version == "2.7.1"
- name: ant
include: ../../deploy/templates/ant/ant.yml
when: ant_version == "1.10.3" or
ant_version == "1.10.4"
git.yml :
- name: Download Software
get_url:
url: "/Linux/git/{{ git_version }}/git-{{ git_version }}.tar.gz"
dest: "{{ Tools }}"
mode: 0755
So when i run playbook i am getting below error as syntax problem my requirement is take user input of which software he need to download and using multiple when condition with different include /import_task option to download software
TASK [Gathering Facts] ***************************************************************************************************
ok: []
TASK [rtt : Download Software] ********************************************************************************************
skipping: []
TASK [rtt : Unarchive software] *******************************************************************************************
skipping: []
TASK [rtt : Delete the tar file] ******************************************************************************************
skipping: []
TASK [rtt : Download Software] ********************************************************************************************
To the best of my knowledge, Ansible doesn't allow you to have when: conditions for undefined variables, with one exception:
when: variable_name is defined
For your purposes, it may be simpler to just set empty defaults, e.g. create defaults/main.yml with the following content:
---
ant_version: ''
git_version: ''
This allows the role to execute and tasks are only run when user's override the default variables (e.g. when user adds --extra-vars 'git_version=2.7.1' to playbook command).
I need to check if a file named deploy.db exists. If it does not exist, I need to perform a set of tasks for which I am using a block.
Below is how I run the playbook
ansible-playbook test.yml \
-e Layer=APP \
-e BASEPATH="/logs" \
-e Filenames="file1,file2,file3"
Here is the playbook test.yml:
---
- name: "Play 1"
hosts: localhost
gather_facts: false
tasks:
- name: Construct
debug:
msg: "Run"
- block:
- stat: path="{{ BASEPATH }}/deploy.db"
register: currdb
- file: path="{{ BASEPATH }}/deploy.db" state=touch recurse=no
when: currdb.stat.exists == False
- shell: "echo done>>{{ BASEPATH }}/deploy.db"
when: currdb.stat.exists == False
when: Layer == 'APP'
with_items:
- "{{ Filenames.split(',') }}"
I am getting the below error running the playbook:
ERROR! 'with_items' is not a valid attribute for a Block
The error appears to be in '/app/test.yml': line 9, column 6, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- block:
^ here
After researching a bit, I understand that neither with_items nor loop is supported by a block and the solution is to include a tasks file.
I am, however, not sure how to get that to work. Can you suggest what tweaks I need in order to make my playbook work?
Considering I am on the latest version of Ansible, are there other solutions?
TL;DR
'with_items' is not a valid attribute for a Block
The error message says it all: you cannot loop over a block.
If you need to loop over a set of tasks, put them in a separate file and use include_tasks
Implementation (and some good practice...)
Below is an implementation based on your example illustrating the solution.
Since your question and code lacks some precision and since I pointed out some bad practices, please note that:
I fixed the looped code to effectively use the filenames you loop on (I inferred it was supposed to the deploy.db file). Note the use of loop_control to disambiguate the variable name in the included file (i.e. db_filename).
I made the code idempotent as much as possible by using the ansible module copy in place of shell and dropped the touch phase.
I transformed the var names to all lowercase and underscore separator.
To make sure the copy task works on all occasion, I replaced the removed tasks with a single making sure the basepath dir exists.
I added a unique filter after filenames.split(',') as well as a trim filter on each value to remove possible duplicates and eventual spaces added by error in the coma separated list.
I used not keyword and bool filter (for extra security) rather than a bare compare to a boolean False value.
Here is the included file create_db_each.yml
---
- name: Check if file exists
stat:
path: "{{ basepath }}/{{ db_filename }}"
register: currdb
- name: Create the file with "done" line if not present
copy:
content: "done"
dest: "{{ basepath }}/{{ db_filename }}"
when: not currdb.stat.exists | bool
used in the following create_db.yml playbook
---
- name: "Create my dbs"
hosts: localhost
gather_facts: false
tasks:
- name: Make sure the base directory exists
file:
path: "{{ basepath }}"
state: directory
- name: load each db
include_tasks: "create_db_each.yml"
when: layer == 'APP'
loop: "{{ filenames.split(',') | unique | map('trim') }}"
loop_control:
loop_var: db_filename
which gives
notes:
first run only, run it again on your side to witness it reports OK everywhere
see the filenames parameter value to illustrate the use of unique and trim
$ ansible-playbook -e basepath=/tmp/my/base/path -e "filenames='a.bla, b.toto, c , z.txt,a.bla'" -e layer=APP create_db.yml
PLAY [Create my dbs] ************************************************
TASK [Make sure the base directory exists] **************************
changed: [localhost]
TASK [load each db] *************************************************
included: /home/olcla/Sources/ZZ_tests/ansitests/create_db_each.yml for localhost => (item=a.bla)
included: /home/olcla/Sources/ZZ_tests/ansitests/create_db_each.yml for localhost => (item=b.toto)
included: /home/olcla/Sources/ZZ_tests/ansitests/create_db_each.yml for localhost => (item=c)
included: /home/olcla/Sources/ZZ_tests/ansitests/create_db_each.yml for localhost => (item=z.txt)
TASK [Check if file exists] *****************************************
ok: [localhost]
TASK [Create the file with "done" line if not present] **************
changed: [localhost]
TASK [Check if file exists] *****************************************
ok: [localhost]
TASK [Create the file with "done" line if not present] **************
changed: [localhost]
TASK [Check if file exists] *****************************************
ok: [localhost]
TASK [Create the file with "done" line if not present] **************
changed: [localhost]
TASK [Check if file exists] *****************************************
ok: [localhost]
TASK [Create the file with "done" line if not present] **************
changed: [localhost]
PLAY RECAP **********************************************************
localhost: ok=13 changed=5 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
$ tree /tmp/my/base/path/
/tmp/my/base/path/
├── a.bla
├── b.toto
├── c
└── z.txt
$ for f in /tmp/my/base/path/*; do cat $f; echo; done
done
done
done
done
I wonder if anyone found a solution that would avoid displaying any warnings if an inventory group is undefined or empty.
I just want to make a section of the playbook run if a group exists and is not empty, skipping without warnings if not.
Please read https://github.com/ansible/ansible/issues/35255#issuecomment-388455001 and test alternatives because I spend a good amount of time trying to find a workaround for this issue.
So far I was not able to find any way to avoid the warnings when group is not defined.
I'm slightly unsure if I'm answering the right question, but here goes. I'm interpreting "if a group exists and is not empty" to mean "the currently executing host belongs to a certain group".
If you meant to ask something like "can I find out from the current host if any other hosts belong to a group that the current host does not belong to," or "can I run a playbook without errors when the hosts defined for some groups are unreachable," then I'm afraid this doesn't answer your question :)
But running a task based on whether or not the current host belongs to a group can be done with one of Ansible's default vars, groups group_names.
The following playbook contains two tasks, one to run a debug task when the current host belongs to the group existent, and one to run a debug task when the current host belongs to the group nonexistent. As the output shows, the first task runs and the second does not.
hosts.yml
[existent]
localhost ansible_connection=local
playbook.yml
- hosts: all
gather_facts: true
tasks:
- name: This command will run.
debug:
msg: "The group `existent_1` exists!"
when:
- "'existent_1' in groups"
- name: This command will not run.
debug:
msg: "The group `existent_1` exists and this host is in it!"
when:
- "'existent_1' in groups"
- "'existent_1' in group_names"
- name: This command will run.
debug:
msg: "The group `existent_2` exists and this host is in it!"
when:
- "'existent_2' in groups"
- "'existent_2' in group_names"
- name: This command will not run.
debug:
msg: "The group `nonexistent` exists!"
when:
- "'nonexistent' in groups"
- "'nonexistent' in group_names"
Output
➜ ansible-playbook -i hosts.yml playbook.yml
PLAY [all] *********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [localhost]
TASK [This command will run.] **************************************************
ok: [localhost] =>
msg: The group `existent_1` exists!
TASK [This command will not run.] **********************************************
skipping: [localhost]
TASK [This command will run.] **************************************************
ok: [localhost] =>
msg: The group `existent_2` exists and this host is in it!
TASK [This command will not run.] **********************************************
skipping: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0
Not sure if this will suppress the warnings or not, but it should accomplish the first part ("make a section of the playbook run if a group exists and is not empty"):
- hosts: all
gather_facts: true
tasks:
- name: "This command will only run if {{ group_to_test }} is a non-empty group that exists"
debug:
msg: The group 'existent' exists and contains hosts!
when: group_to_test in groups and groups[group_to_test]
Give 'er a test and let me know if she works to suppress the warnings!
Obviously group_to_test must be replaced with a constant string or set as a variable/fact/default.