Avoid unused and undefined variable in playbook - ansible

I have following data in a variable file
data: [
{
service: "ServiceName",
var2: "file/path/value",
var3: "{{ check_var }}",
}, {
service: "ServiceName",
var2: "file/path/value",
var3: "{{ check_var }}",
}
]
I have two playbooks which require the same data. However one playbook does not require var3.
- debug: msg="{{ item['service'] }} uses - {{ item['var2'] }}"
with_items: "{{ data }}"
This gives error - "'check_var' is undefined".
TRIED:
I don't want to populate the playbook with bad standards and use
when: check_var is undefined
Or use false dummy data in playbook's vars atrribute. Is there any way around this while maintaining standards. Also the actual data is quite huge so I don't want to repeat it twice for each playbook.

In Ansible data has to be assigned to hosts not to playbooks.
You have to create two host groups. Those hosts, who needs just two variables go into the first group. And those hosts which need 3 variables go into both groups. You can include the hosts of the first group in the second group.
Then you create two group var files. In the first you put 2 variables and in the second the 3rd variable.
By this each host gets the right amount of information. Playbook 1 uses 3 variables and playbook 2 uses just 2 variables.
Update: Minimal example
$ diff -N -r none .
diff -N -r none/check_var.yaml ./check_var.yaml
0a1,4
> ---
> - hosts: localhost
> tasks:
> - debug: var=check_var
diff -N -r none/group_vars/myhosts.yaml ./group_vars/myhosts.yaml
0a1
> check_var: "Hello World!"
diff -N -r none/inventory ./inventory
0a1,2
> [myhosts]
> localhost
$ ansible-playbook -i inventory check_var.yaml
PLAY [localhost] ***************************************************************************
TASK [Gathering Facts] *********************************************************************
ok: [localhost]
TASK [debug] *******************************************************************************
ok: [localhost] => {
"check_var": "Hello World!"
}
PLAY RECAP *********************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0

Related

How to print a list of items in a multiline string output in ansible

I have a list in ansible and want to print the contents in a block, if I do loop like below:
- test_list
- one
- two
- three
- debug:
msg: "{{ item }}"
loop: "{{ test_list }}"
it will produce output something like:
{ msg: "one" }
{ msg: "two" }
{ msg: "three" }
here I have multiple msg values, I want output like:
msg:"one
two
three"
where list items are broken won into multiple lines. Can anyone direct me to the correct path or provide a hint.
Any help would be appreciated.
You can achieve the desired result by the following steps :
Set callback plugin to debug using the following command:
export ANSIBLE_STDOUT_CALLBACK=debug
Replace loop with for loop on jinja2:
Example:
The desired output could be obtained using the following playbook:
---
- name: Sample playbook
connection: local
gather_facts: no
hosts: localhost
vars:
test_list:
- one
- two
- three
tasks:
- debug:
msg: "{% for item in test_list %}{{ item + '\n'}}{% endfor %}"
The above playbook would result in:
PLAY [Sample playbook] ***************************************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************
ok: [localhost]
TASK [debug] *************************************************************************************************************************
ok: [localhost] => {}
MSG:
one
two
three
PLAY RECAP ***************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0
if you need starting/ending double quotes:
msg: "\"{% for item in test_list %}{{ item + '\n'}}{% endfor %}\""

How to read multiline argument in Ansible

I pass a multiline variable called host_list from Jenkins to ansible which contains the list of hosts.
I need to read each host line by line and add it to ansible's add_host module.
Below is how my multiline argument looks like.
ansible-playbook /app/upgrade_tomcat.yml -i /tmp/inventory1775725953939119720.ini -t validate -f 5 -e tomcat_home=/app/tomcat -e host_list='10.9.9.19
10.9.55.16
10.9.44.26
' -e USER=user1
I tried the below but it does not work.
---
- name: "Find the details here"
hosts: localhost
tasks:
- add_host: name={{ item }}
groups=dest_nodes
ansible_user={{ USER }}
with_items: "{{ host_list.split('\n') }}"
I even tried the following:
host_list.splitlines()
host_list.split( )
But none of them works.
Requesting suggestions.
Warning: this is absolutely ugly and should be replaced by any other correct way to acheive your result (non exhaustively including: defining your host list in a group in your inventory, including a file containing a definition of your list in yaml/json, passing var as extra var directly in yaml/json....)
After this warning, here is a working solution with your current situation. Simply quote values correctly.
The command
ansible-playbook playbook.yml -e 'my_list="toto
pipo
bingo"'
The playbook
---
- name: Passing abolutely ugly vars on command line
hosts: localhost
gather_facts: false
tasks:
- name: Split ugly extra var
debug:
msg: "{{ my_list.splitlines() }}"
The result:
PLAY [Passing abolutely ugly vars on command line] ***********************************************************
TASK [Split ugly extra var] **************************************************************************************
ok: [localhost] => {
"msg": [
"toto",
"pipo",
"bingo"
]
}
PLAY RECAP ***************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
You can pass as an array in extra-vars as below
-e '{"host_list": [10.9.9.19,10.9.55.16,10.9.44.26]}'
But it is always good to add them in the inventory as a group and use the group name in playbook
Inventory:
[host_list]
10.9.9.19
10.9.55.16
10.9.44.26
Use below to loop through each host
with_items: "{{ groups['host_list'] }}"

ansible set variables conditionally in group_vars/all

i am trying to set global variables that will be valid to all playbooks
i am trying to set this global variable in group_vars/all file
i want to set one variable which can have multiple values depending to the conditions
i have tried to use when conditions:
kdump_nfs: 'nfs1'
when: ansible_local.default_gateway.site == "site1"
kdump_nfs: 'nfs2'
when: ansible_local.default_gateway.site == "site2"
but getting :
[WARNING]: While constructing a mapping from /group_vars/all, line 1, column 1, found a duplicate dict key (kdump_nfs). Using last defined value only.
how can i set the variable "kdump_nfs" to get a deferent value depending of the condition
Setting a changing variable at group vars will not be a good option. You can use the extra vars to pass the variable value that will be more dynamic.
For eg.
--extra-vars "var_name=${var_value}"
$ cat group_vars/all/main.yml
my_variable: "{% if condition1 %}foo{% else %}bar{% endif %}"
$ cat site.yml
- hosts: all
gather_facts: false
tasks:
- name: site.yml --> Print debug variable
debug:
msg: "{{ my_variable }}"
$ ansible-playbook --connection=local --inventory 127.0.0.1, site.yml --extra-vars "condition1=True"
PLAY [all] ********************************************************************************************************************************************************************************************************
TASK [site.yml --> Print debug variable] **************************************************************************************************************************************************************************
ok: [127.0.0.1] => {
"msg": "foo"
}
PLAY RECAP ********************************************************************************************************************************************************************************************************
127.0.0.1 : ok=2 changed=0 unreachable=0 failed=0

Ansible increment variable globally for all hosts

I have two servers in my inventory (hosts)
[server]
10.23.12.33
10.23.12.40
and playbook (play.yml)
---
- hosts: all
roles:
web
Inside web role in vars directory i have main.yml
---
file_number : 0
Inside web role in tasks directory i have main.yml
---
- name: Increment variable
set_fact: file_number={{ file_number | int + 1 }}
- name: create file
command: 'touch file{{ file_number }}'
Now i expect that in first machine i will have file1 and in second machine i will have file2 but in both machines i have file1
So this variable is local for every machine, how could i make it global for all machines.
My file structure is:
hosts
play.yml
roles/
web/
tasks/
main.yml
vars/
main.yml
Now i expect that in first machine i will have file1 and in second machine i will have file2 but in both machines i have file1
You need to keep in mind that variables in Ansible aren't global. Variables (aka 'facts') are applied uniquely to each host, so file_number for host1 is different than file_number for host2. Here's an example based loosely on what you posted:
roles/test/vars/main.yml:
---
file_number: 0
roles/test/tasks/main.yml:
---
- name: Increment variable
set_fact: file_number={{ file_number | int + 1 }}
- name: debug
debug: msg="file_number is {{ file_number }} on host {{ inventory_hostname }}"
Now suppose you have just two hosts defined, and you run this role multiple times in a playbook that looks like this:
---
- hosts: all
roles:
- { role: test }
- hosts: host1
roles:
- { role: test }
- hosts: all
roles:
- { role: test }
So in the first play the role is applied to both host1 & host2. In the second play it's only run against host1, and in the third play it's again run against both host1 & host2. The output of this playbook is:
PLAY [all] ********************************************************************
TASK: [test | Increment variable] *********************************************
ok: [host1]
ok: [host2]
TASK: [test | debug] **********************************************************
ok: [host1] => {
"msg": "file_number is 1 on host host1"
}
ok: [host2] => {
"msg": "file_number is 1 on host host2"
}
PLAY [host1] **************************************************
TASK: [test | Increment variable] *********************************************
ok: [host1]
TASK: [test | debug] **********************************************************
ok: [host1] => {
"msg": "file_number is 2 on host host1"
}
PLAY [all] ********************************************************************
TASK: [test | Increment variable] *********************************************
ok: [host1]
ok: [host2]
TASK: [test | debug] **********************************************************
ok: [host1] => {
"msg": "file_number is 3 on host host1"
}
ok: [host2] => {
"msg": "file_number is 2 on host host2"
}
So as you can see, the value of file_number is different for host1 and host2 since the role that increments the value ran against host1 more times than it did host2.
Unfortunately there really isn't a clean way making a variable global within Ansible. The entire nature of Ansible's ability to run tasks in parallel against large numbers of hosts makes something like this very tricky. Unless you're extremely careful with global variables in a parallel environment you can easily trigger a race condition, which will likely result in unpredictable (inconsistent) results.
I haven't found a solution with ansible although i made work around using shell to make global variable for all hosts.
Create temporary file in /tmp in localhost and place in it the starting count
Read the file for every host and increment the number inside the file
I created the file and initialized it in the playbook (play.yml)
- name: Manage localhost working area
hosts: 127.0.0.1
connection: local
tasks:
- name: Create localhost tmp file
file: path={{ item.path }} state={{ item.state }}
with_items:
- { path: '/tmp/file_num', state: 'absent' }
- { path: '/tmp/file_num', state: 'touch' }
- name: Managing tmp files
lineinfile: dest=/tmp/file_num line='0'
Then in web role in main.tml task i read the file and increment it.
- name: Get file number
local_action: shell file=$((`cat /tmp/file_num` + 1)); echo $file | tee /tmp/file_num
register: file_num
- name: Set file name
command: 'touch file{{ file_num.stdout }}'
Now i have in first host file1 and in second host file2
You can use Matt Martz's solution from here.
Basically your task would be like:
- name: Set file name
command: 'touch file{{ play_hosts.index(inventory_hostname) }}'
And you can remove all that code for maintaining global var and external file.

Add object to a dictionary in variables

I'd like to add objects to a list in variables like this
system_user:
- user1
system_users: "{{ system_users | union(system_user) }}"
It fails with a recursion error:
AnsibleError: recursive loop detected in template string
Is there any way to solve this? I want to create a definition file for each user in group_vars/all/ and then loop through them in a playbook. I don't want to redefine the list for every new user.
PS: There's a workaround: create variables with user names, like system_user_otto20 but it's not elegant at all.
There is a similar opened issue: https://github.com/ansible/ansible/issues/17906
I suggest you not to use undefined variables in template strings to define them.
As another workaround you could use hash_behaviour=merge with following definitions:
group_vars/all.yml
system_users:
user1:
user2:
book1.yml
- hosts: localhost
gather_facts: false
vars:
system_users:
user3:
user4:
tasks:
- debug: msg="{{ system_users | unique }}"
Running a playbook:
$ ansible-playbook book1.yml
[WARNING]: Host file not found: /etc/ansible/hosts
[WARNING]: provided hosts list is empty, only localhost is available
PLAY [localhost] ***************************************************************
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": [
"user4",
"user2",
"user3",
"user1"
]
}
PLAY RECAP *********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0
unique is used to convert dictionary to unsorted list.

Resources