How to read multiline argument in Ansible - 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'] }}"

Related

Dynamically generate bmc hostnames from static inventory?

I have an ansible inventory that looks something like:
all:
children:
nodes:
vars:
bmc_user: root
bmc_pass: secret
hosts:
node1.example.com:
bmc_addr: node1-bmc.example.com
node2.example.com:
bmc_addr: node2-bmc.example.com
For running something like ipmitool, I can simply delegate the task to localhost, like this:
- hosts: all
gather_facts: false
tasks:
- delegate_to: localhost
command: >-
ipmitool -I lanplus -H {{ bmc_addr }} -U {{ bmc_user }} -P {{ bmc_pass }} power off
Occasionally, I need to interact with the bmc using ssh. Thsi is
tricky, because typically this requires password authentication. I
want to write something like this:
- hosts: all
gather_facts: false
tasks:
- delegate_to: "{{ bmc_addr }}"
command: >-
racadm set iDRAC.IPMILan.Enabled Enable
...but this won't work, because the credentials required to access the
bmc are not the same as the credentials necessary to access the host.
I would like to be able to do something like this in my inventory:
all:
children:
nodes:
vars:
bmc_user: root
bmc_pass: secret
hosts:
node1.example.com:
bmc_addr: node1-bmc.example.com
node2.example.com:
bmc_addr: node2-bmc.example.com
nodes_bmc:
vars:
ansible_user: root
ansible_ssh_pass: secret
hosts:
node1-bmc.example.com:
node2-bmc.example.com:
...but I would like the hosts in the nodes_bmc group to be populated
dynamically from the bmc_addr values in the nodes group, rather
than having to specify them manually.
I've been puzzling over this for a bit, and the only solution I've
come up with is to write some tooling that reads the ansible inventory
and then generates a new static inventory containing the necessary
entries. That seems clunky, and requires ensuring that the "generate
bmc inventory" tool is run whenever the main inventory is modified.
Let's try the add_host module:
- name: add hosts
hosts: all
gather_facts: no
tasks:
- add_host:
hostname: "{{ hostvars[item].bmc_addr }}"
groups:
- nodes_bmc
loop: "{{ play_hosts }}"
Now, you need a new play so you have all the hosts in the play.
- name: Show all hosts
hosts: all
gather_facts: no
tasks:
- debug:
var: play_hosts
run_once: yes
Running with your inventory (but w/o any hosts in the node_bmc group):
$ ansible-playbook -i hosts.yml ./add_hosts.yml
PLAY [add hosts] ***********************************************************************************************************
TASK [add_host] ************************************************************************************************************
ok: [node1.example.com] => (item=node1.example.com)
ok: [node1.example.com] => (item=node2.example.com)
PLAY [Show all hosts] ******************************************************************************************************
TASK [debug] ***************************************************************************************************************
ok: [node1-bmc.example.com] => {
"play_hosts": [
"node1-bmc.example.com",
"node2-bmc.example.com",
"node1.example.com",
"node2.example.com"
]
}
PLAY RECAP *****************************************************************************************************************
node1-bmc.example.com : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node1.example.com : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Now, here's a totally different way.
You will have two file in your inventory:
The one you currently have (but w/o the hosts: section under nodes_bmc)
A generate_bmc.sh file, which must be executable: chmod +x generate_bmc.sh
That file will simply be this:
#!/bin/bash
# Figure out where we are
here=$( dirname $0 )
# Write header
cat << 'end_header'
{
"nodes_bmc": {
"hosts": [
end_header
# Get hosts from inventory file
hosts=$( ansible-inventory --list -i ${here}/hosts.yml | grep '"bmc_addr":' | cut -d: -f2 | sed 's/,$//' )
# Or, if you have jq installed:
# hosts=$( ansible-inventory --list -i ${here}/hosts.yml | jq "._meta.hostvars[].bmc_addr" )
last=$( echo $hosts | sed 's/.* //' )
# Write hosts
for host in ${hosts}
do
echo -n " "$host
[ "${host}" != "${last}" ] && echo ","
done
echo
# Close output
cat << 'end_footer'
]
}
}
end_footer
In your ansible.cfg file, have the line:
inventory = /the/directory/that/has/those/two/files
You can test this with ansible-inventory --list.

Avoid unused and undefined variable in playbook

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

Ansible import_playbook fails with variable undefined error

I am trying to conditionally import a playbook by dynamically generating the name of the playbook to import.
I have the following 2 files:
root#ubuntu:~/test# ls -ltr
total 8
-rw-r--r-- 1 root root 117 sij 2 12:07 child_playbook_1.yaml
-rw-r--r-- 1 root root 210 sij 2 12:11 start.yaml
root#ubuntu:~/test#
start.yaml:
---
- name: main playbook
hosts: localhost
tasks:
- set_fact:
var: "child_playbook"
- debug:
msg: "{{ var }}"
- import_playbook: "{{ var + '_1.yaml' }}"
when: var is defined
child_playbook_1.yaml:
---
- name: child_playbook
hosts: localhost
tasks:
- debug:
msg: "Message from the child playbook"
When I execute start.yaml, I get this as output:
root#ubuntu:~/test# ansible-playbook start.yaml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
ERROR! 'var' is undefined
root#ubuntu:~/test#
Why is var not seen and why do I get this message?
How to overcome this?
To put things into a bit more perspective, the 'var' variable in start.yaml is being dynamically read which means that the name of the playbook to be imported will also be dynamic.
var is a fact defined for localhost (i.e. the only host in the play) in the first play. So it does not exist "globally", only for the given host.
Moreover, imports are made statically at time of playbook parsing (by opposition to includes which are dynamic but to not exist for playbooks).
One could then try to use the var defined for localhost by referencing hostvars['localhost'].var but:
set_fact has not yet run since it is an import.
Moreover hostvars is not yet defined at time of import.
Lastly, you are miss-interpreting how a when clause is actually working. Basically, the condition is passed to all the tasks contained in your included/imported object. So the imported object must exist. If you use an undefined var to get its name, it will always fire an error.
The only solution I currently see to pass a variable playbook name is to use an extra var on the command line. If you want to be able to 'skip' the import when no variable is defined, you could default to an empty playbook.
Here is a dummy empty.yml playbook
---
- name: Dummy empty playbook
hosts: localhost
gather_facts: false
For my tests, I used the same child playbook as in your question except I disabled facts gathering not needed here:
---
- name: child_playbook
hosts: localhost
gather_facts: false
tasks:
- debug: msg="Message from the child playbook"
The new main playbook start.yml looks like this:
---
- name: Play written in start.yml directly
hosts: localhost
gather_facts: false
tasks:
- debug:
msg: "Message from start playbook"
- import_playbook: "{{ var is defined | ternary( var | default('') + '_1.yml', 'empty.yml') }}"
Note than even though we use var is defined as a condition, var will still be interpreted in the ternary filter in all cases (defined or not). So we have to use a default value to make sure we don't have an error when we don't pass a value.
We can now call the playbook with or without an extra var. Here is the result:
$ ansible-playbook start.yml
PLAY [Play written in start.yml directly] *************************************************************************************************************************************************************************
TASK [debug] ******************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Message from main playbook"
}
PLAY [Dummy empty playbook] ***************************************************************************************************************************************************************************************
PLAY RECAP ********************************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
$ ansible-playbook start.yml -e var=child_playbook
PLAY [Play written in start.yml directly] *************************************************************************************************************************************************************************
TASK [debug] ******************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Message from main playbook"
}
PLAY [child_playbook] *********************************************************************************************************************************************************************************************
TASK [debug] ******************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Message from the child playbook"
}
PLAY RECAP ********************************************************************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

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

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