Add object to a dictionary in variables - ansible

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.

Related

Extract IP addresses from a text file with other values

Let's say you have a variable file on your localhost. We can call it values.txt, and its contents are:
isjwidywiudywdiuwqoq10.110.195.108xxsxww/ed/swqqwfdfwef8.8.8.8
You want to extract only the IP address values from all of this junk. I have made multiple attempts at this using the file lookup and the ipaddr filter. What I'm doing looks like this:
- name: extract IPs only
debug:
msg: "{{ query('file', 'values.txt') | ipaddr }}"
However, this does not work! How can I go about doing this to where I get only the IP's?
You can do this with a regular expression filter. To extract all the ip addresses from a string, it sounds like you want to find everything that matches [0-9]+\.[0-9]+\.[0-9]+\.[0-9]). To extract all matches of an expression from a string, Ansible provides us with the regex_findall filter.
For example:
- hosts: localhost
gather_facts: false
tasks:
- debug:
msg: >-
{{ "isjwidywiudywdiuwqoq10.110.195.108xxsxww/ed/swqqwfdfwef8.8.8.8" | regex_findall("[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+") }}
The above playbook will output:
TASK [debug] ******************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": [
"10.110.195.108",
"8.8.8.8"
]
}
Here's one example
# // Ansible playbook to Extract IP addresses from a text file
- hosts: localhost
gather_facts: false
tasks:
- name: Extract IP addresses from a text file
set_fact:
ips: "{{ lookup('file', '/tmp/tmpdir/test.txt') | regex_findall('(?<!\\d)(?:\\d{1,3}\\.){3}\\d{1,3}(?!\\d)') }}"
register: result
- debug:
msg: "{{ result }}"
Sample run
╰─ cat test.txt
hello
12.14.34.45asdfadsf234234
asdfasldfkj23.34.45.23alsdjfwerk
fafn aslkdfj vahsdifadk 23.43.34.232 asldkvvnasdfads
╰─ ansible-playbook test.yaml
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [localhost] ********************************************************************************************************************************************************************************
TASK [Extract IP addresses from a text file] ****************************************************************************************************************************************************
ok: [localhost]
TASK [debug] ************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": {
"ansible_facts": {
"ips": [
"12.14.34.45",
"23.34.45.23",
"23.43.34.232"
]
},
"changed": false,
"failed": false
}
}
PLAY RECAP **************************************************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

How to pass URLs as a variable to an ansible playbook

I created a playbook which clears cache, I'm trying to pass a url to a variable, and when I execute the playbook I get an empty array for that parameter.
In my playbook I have a vars module with this variable (environment), it gets defined when you pass in a variable to the ansible-playbook command
vars:
environment: "{{testenv}}"
-e testenv=https://www.test1.com
When I execute the playbook I get this error.
Do I need to format the url in someway?
fatal: [localhost]: FAILED! => {"changed": false, "msg": "unknown url type: '[]/content/clear_cache?
Your issue is coming from the fact that environment is a reserved variable, as pointed in the second row of this table in the documentation:
Valid variable names
 Not valid
 foo
 *foo, Python keywords such as async and lambda
 foo_env
 playbook keywords such as environment
 foo_port
 foo-port, foo port, foo.port
 foo5, _foo
 5foo, 12
Source: https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#creating-valid-variable-names
So, you just need to change your variable name to something else and it will work.
Given the playbook:
- hosts: all
gather_facts: no
tasks:
- debug:
msg: "{{ _environment }}"
vars:
_environment: "{{ testenv }}"
When run:
$ ansible-playbook play.yml -e testenv=https://www.test1.com
PLAY [all] **********************************************************************************************************
TASK [debug] ********************************************************************************************************
ok: [localhost] => {
"msg": "https://www.test1.com"
}
PLAY RECAP **********************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=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

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: iterating and pairing inventory items with variables

This is directly related to the following link question, and the answer from larsks, that I tried, but it does not work: "how to loop through inventory and assign value in ansible"
I was trying to do the same, and I tested to rename 2 VMs controlled by Ansible, but I get errors like the next one when I try to run the Playbook (ansible-playbook -i hosts test_iterate_win.yml -vvv), I would say that it is literally taking by name 'System.Object[]' instead of, for example, wCloud2:
failed: [oldVM2] (item=[u'oldVM2', u'wCloud2']) => {
"ansible_loop_var": "item",
"changed": false,
"item": [
"oldVM2",
"wCloud2"
],
"msg": "Failed to rename computer to 'System.Object[]': Skip computer 'oldVM2' with new name 'System.Object[]' because the new name is not valid. The new computer name entered is not properly formatted. Standard names may contain letters (a-z, A-Z), numbers (0-9), and hyphens (-), but no spaces or periods (.). The name may not consist entirely of digits, and may not be longer than 63 characters.",
"old_name": "oldVM2",
"reboot_required": false
}
In my inventory file:
[windows]
oldVM1 ansible_host=192.168.122.6
oldVM2 ansible_host=192.168.122.139
My Playbook:
---
- hosts: windows
gather_facts: false
vars:
hostnames:
- wCloud1
- wCloud2
tasks:
- name: change hostname
win_hostname:
name: "{{ item }}"
loop: "{{ groups.windows|zip(hostnames)|list }}"
What I am doing wrong?
TL;DR;
I would say, you are making this super complex for yourself for nothing, when there could be a simple solution about it.
Your task could be easily resolved by just using host variables in your inventory:
[windows]
oldVM1 ansible_host=192.168.122.6 newName=wCloud1
oldVM2 ansible_host=192.168.122.139 newName=wCloud2
Then your playbook is as easy as:
---
- hosts: windows
gather_facts: false
tasks:
- name: change hostname
win_hostname:
name: "{{ newName }}"
Now, the reason why your attempt didn't work, is actually coming from a bit of a misconception you have on task(s) run on multiple hosts by Ansible, I would say.
Namely, when Ansible have a task (or a set of tasks) to run on multiple hosts, one and only one task need to be defined.
Based on the said task, when the inventory hosts is actually a group of hosts, the task will be run on the host 1, then the host 2, ... until the host n, before it goes to run the next task (if any).
Nota: still, don't take this for granted, there has been know issues where Ansible would not follow the order of definition of the hosts in the inventory (see: https://github.com/ansible/ansible/issues/34861), so really it could end up being host 2, host n, host 1.
Consider this playbook with the above inventory:
---
- hosts: windows
gather_facts: false
tasks:
- name: change hostname
debug:
msg: '{{ newName }}'
- name: another task
debug:
msg: 'some example'
The output of it will be
$ansible-playbook test.yml
PLAY [windows] *****************************************************************
TASK [change hostname] *********************************************************
ok: [oldVM1] => {
"msg": "wCloud1"
}
ok: [oldVM2] => {
"msg": "wCloud2"
}
TASK [another task] ************************************************************
ok: [oldVM1] => {
"msg": "some example"
}
ok: [oldVM2] => {
"msg": "some example"
}
PLAY RECAP *********************************************************************
oldVM1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
oldVM2 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Where you can clearly see that the first task (named, change hostname) is processed on all hosts before Ansible could move forward to other tasks in the play.
And really what your System.Object[] error means is that you are trying to feed an object (namely a list) in the win_hostname module's name, that tries to convert it as string somehow and fails badly, because, here is the list that your item variable contains:
[
"oldVM1",
"wCloud1"
]

Resources