ansible playbook multiple when condition with include option - ansible

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).

Related

How to loop multiple tasks in Ansible? [duplicate]

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

Is there any way to validate whether a file is passed as extra vars in ansible-playbook

I have an ansible playbook which requires a JSON file to be passed as a --extra-vars (or -e ) from the command line. I would like to know if there is any way that I can add a validation task in my playbook to confirm that such a file is passed.
I have checked hostvars and did not find any parameter that stores such value or name of the file. It directly loaded the variables present inside the file.
I tried echo !! (to fetch the command history) using command and shell modules which didn't work either.
Sample command
ansible-playbook test.yml -e#input_vars.json
I want a task that will fail the execution if the JSON file is not passed in --extra-vars. Is it possible?
Even if it is not possible to find out where and when a variable was defined, it might be possible to check the content of the CLI option(s).
Whereby for the CLI options --tags, --skip-tags and --limit the Special Variables ansible_run_tags, ansible_skip_tags and ansible_limit are available, to access the command line argument --extra-vars used to invoke Ansible one would need to drop a tiny Custom Action Plugin.
After that a sample playbook
---
- hosts: test
become: false
gather_facts: true
tasks:
- name: Gather Arguments Values
get_argv:
- name: Show values
debug:
msg: "{{ ansible_facts.argv }}"
called via
ansible-playbook --extra-vars="test=test" extraVars.yml
results into an output of
TASK [Show values] *************
ok: [test.example.com] =>
msg:
- /usr/bin/ansible-playbook
- --extra-vars=test=test
- extraVars.yml
... if there is any way that I can add a validation task in my playbook to confirm that such a file is passed ... I want a task that will fail the execution if the JSON file is not passed in --extra-vars ...
You would need than just to include a check of the Ansible facts and Conditionals based on ansible_facts.
Further Documentation
Action plugins
Developing plugins - Action plugins
Action plugins included with Ansible Core
Python sys - System-specific parameters and functions
The above approach gives also the opportunity to check for multiple extra vars files like in the problem What is the variable order/precedence in case of multiple 'extra_vars' files?
ansible-playbook extraVars.yml --extra-vars="test=test" -e="test=TEST"
resulting into an output of
TASK [Show values] *************
ok: [test.example.com] =>
msg:
- /usr/bin/ansible-playbook
- extraVars.yml
- --extra-vars=test=test
- -e=test=TEST
TASK [Show content of var 'test'] *************
ok: [test.example.com] =>
msg: TEST
There is no method, function, lookup plugin, special variable, etc. to find out where a variable comes from. Without knowing the run-string you also can't tell whether extra variables are used or not.
The simplest method to determine whether extra variables are used might be based on the fact that extra vars are the highest precedence. For example, test if extra vars overrode a variable
shell> cat input_vars.yml
extra_vars: true
var1: foo
var2: bar
Declare the variable extra_vars also in the playbook
shell> cat test.yml
- hosts: localhost
vars:
extra_vars: false
tasks:
- assert:
that: extra_vars|bool
fail_msg: Extra vars missing. End of play.
- debug:
msg: |
var1: {{ var1 }}
var2: {{ var2 }}
The playbook fails if you run it without extra vars
shell> ansible-playbook test.yml
PLAY [localhost] *****************************************************************************
TASK [assert] ********************************************************************************
fatal: [localhost]: FAILED! => changed=false
assertion: extra_vars|bool
evaluated_to: false
msg: Extra vars missing. End of play.
The playbook will continue if extra vars are used
shell> ansible-playbook test.yml -e#input_vars.yml
PLAY [localhost] *****************************************************************************
TASK [assert] ********************************************************************************
ok: [localhost] => changed=false
msg: All assertions passed
TASK [debug] *********************************************************************************
ok: [localhost] =>
msg: |-
var1: foo
var2: bar

Merging multiple tasks to reduce the output

We are trying to get lesser output while executing a playbook on multiple OS flavours. But unable to find a solution hence posting is here for a better answer.
As we get multiple task executed, is it possible to merge into one. We are collecting the output in a file & then will veryfy the same with different tags.
- name: verify hostname
block:
- name: read hostname [PRE]
shell: hostname
register: hostname
- name: set fact [hostname]
set_fact:
results_pre: "{{ results_pre | combine({'hostname': hostname.stdout.replace(\"'\", '\"')|quote }) }}"
- name: write hostname
copy:
dest: "{{ remote_logs_path }}/{{ ansible_ssh_host }}/pre/hostname"
content: "{{ hostname.stdout }}"
tags:
- pre
Current output
TASK [role : read hostname [PRE]] ***************************************************************************
changed: [ip]
TASK [role : set fact [hostname]] ***************************************************************************
ok: [ip]
TASK [role : write hostname] ********************************************************************************
changed: [ip]
Required Output
TASK [role : Hostname Collected] ********************************************************************************
changed: [ip]
Generally, it's a bad idea to parse Ansible output. You may get some runtime warnings or unexpected additional lines.
If you really want to stick to Ansible output, there are a so-called callback plugins, you may try to implement your own if you want.
If you need some report from Ansible playbook, the common pattern is to have a separate task, which reports into a file (usually, on a controller host, using delegate: localhost).
Finally, if you want to check for idempotence, Molecule provides this feature.

Issue looping on block containing a set of tasks in Ansible

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

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]

Resources