Ansible set_fact array and populate it from in loop - ansible

I want to create an array and insert value from the the array IP_TO_DNS to reversed IP address.
The idea is to restructure the IP address given in the argument to be matchable later in my code.
Code
- name: create array reversed
set_fact: reversed_ip=[]
- name: set convert ips from cli to matchable reversed ip
set_fact: reversed_ip='{{ item | regex_replace('^(?P<first_range>\d{1,3})\.(?P<second_range>\d{1,3})\.(?P<third_range>\d{1,3})\.', 'named.\\g<third_range>.\\g<second_range>.\\g<first_range>')}}'
with_items: '{{IP_TO_DNS}}'
- name: Match first block of results in path name
debug: var=item
with_items: '{{reversed_ip}}'
Output
TASK [dns : set convert ips from cli to matchable reversed ip] *****************
ok: [10.1.10.5] => (item=10.1.10.1)
ok: [10.1.10.5] => (item=10.1.10.2)
ok: [10.1.10.5] => (item=10.1.10.3)
TASK [dns : Match first block of results in path name] *************************
ok: [10.1.10.5] => (item=named.10.1.103) => {
"item": "named.10.1.103"
}
It look like my variable is not set as an array and only the first value is populate.
Any ideas ?

This is the one of the ways which I used
vars:
my_new_list: []
tasks:
- name: Get list of elements from list_vars
set_fact:
my_new_list: "{{ my_new_list + [item] }}"
with_items: "{{ list_vars }}"

You are setting the same fact three times and it gets overwritten.
You should register the output:
- name: set convert ips from cli to matchable reversed ip
set_fact: reversed_ip='{{ item | regex_replace('^(?P<first_range>\d{1,3})\.(?P<second_range>\d{1,3})\.(?P<third_range>\d{1,3})\.', 'named.\\g<third_range>.\\g<second_range>.\\g<first_range>')}}'
with_items: '{{IP_TO_DNS}}'
register: reversed_ip_results_list
- name: Match first block of results in path name
debug: var=item.ansible_facts.reversed_ip
with_items: '{{reversed_ip_results_list.results}}'
or if you want a list:
- debug: msg="{{ reversed_ip_results_list.results | map(attribute='ansible_facts.reversed_ip') | list }}"

You can assign the default of reversed_ip as a list and append the item to the list.
- name: set convert ips from cli to matchable reversed ip
set_fact: reversed_ip='{{ reversed_ip |default([]) + [item | regex_replace('^(?P<first_range>\d{1,3})\.(?P<second_range>\d{1,3})\.(?P<third_range>\d{1,3})\.', 'named.\\g<third_range>.\\g<second_range>.\\g<first_range>')] }}'
with_items: "{{ IP_TO_DNS }}"
- name: Match first block of results in path name
debug: var=item
with_items: '{{reversed_ip}}'

Related

Jinja2 expression for split , replace and join

In Ansible playbooks, We have the variable 'dns_name: xyz.abc.pqr.*.com' where as we have one template file called es_config.yml there the value of cname should be (cname: .abc.pqr..com)
How can we write jinja2 expression for this ?
dns_name: xyz.abc.com (Or) xyz.abc.pqr.***.com
cname: *.abc.com (Or) .abc.pqr.**.com (We have to use variable of dns_name)
Playbook
- hosts: elastic-search-servers
gather_facts: true
vars:
es_admin_hostname: test.develop123.com
tasks:
- name: split string
set_fact:
cname: "{{ es_admin_hostname.split('.') }} | first | replace('*')"
- name: debug
debug:
msg: "{{ cname[1:] }} is dns name"
Required Output
*.develop123.com
just change your split by regex_replaces:
- name: split string
set_fact:
cname: "{{ es_admin_hostname| regex_replace('^[^\\.]+', '*') }}"
- name: debug
debug:
msg: "{{ cname }} is dns name"
result:
ok: [localhost] => {
"msg": "*.develop123.com is dns name"
}
'^[^\\.]+' means trap all chars from beginning of string until i meet a dot and replace them by * (the \\ is needed because . is special char)

Run an Ansible task to all hosts if at least one of the hosts has a variable

I want to run a task to all hosts if at least one of the hosts has a variable "new_node".
For example i have inventory
[all]
host1.example.net
host2.example.net
host3.example.net new_node=True
And if in one of the hosts has variable "new_node=True"
then run this task on all hosts
---
- hosts: all
tasks:
- name: Create file yep at all hosts
file:
path: /tmp/yep
state: file
What condition or filter should i apply? Any ideas. Thanks
extract the variables, select true items and evaluate the test is any, e.g.
- set_fact:
semaphore: "{{ ansible_play_hosts_all|
map('extract', hostvars, 'new_node')|
select is any }}"
run_once: true
gives true if any variable new_node is true and gives false otherwise
semaphore: true
Then use the variable semaphore in the conditions e.g.
when: semaphore|bool
Filter select explained
Quoting from select
"If no test is specified, each object will be evaluated as a boolean."
Given the inventory
shell> cat hosts
host1.example.net
host2.example.net new_node=False
host3.example.net new_node=True
The task extracts the variables
- debug:
msg: "{{ ansible_play_hosts_all|
map('extract', hostvars, 'new_node')|
list }}"
msg: '[Undefined, False, True]'
We don't have to care about Undefined because select() evaluates Undefined to False
- debug:
msg: "{{ ansible_play_hosts_all|
map('extract', hostvars, 'new_node')|
select|list }}"
msg:
- true
You can test the evaluation of Undefined to False separately, e.g.
- debug:
msg: "{{ ansible_play_hosts_all|
map('extract', hostvars, 'new_node')|
map('bool')|
list }}"
gives
msg:
- false
- false
- true
If no variable is defined
shell> cat hosts
host1.example.net
host2.example.net
host3.example.net
select returns an empty list
msg: '[Undefined, Undefined, Undefined]'
msg: []
You can create a variable, that will be set to true when one of the hosts in [all] group has the variable new_node defined. Then this variable can be used to conditionally run the task.
Example:
tasks:
- set_fact:
run_on_all: true
when: hostvars[item]['new_node']|default(false)
with_items: "{{ groups['all'] }}"
- file:
path: /tmp/yep
state: touch
when: run_on_all|default(false)
The first task, sets the variable run_on_all to true if any one of the hosts has the variable new_node=True. Then the next task will execute if the previous task set the run_on_all variable to true. I am using the default() filter to avoid the chances of the variables being "undefined".
You did not actually specify what should happen if this property was not present, so I will assume you would simply want to abort the play in that case.
Simplest solution that I could think of is to set the playbook to run against the group [all], and check if this property is present on any of the hosts. If it is not present on any hosts, you abort the play.
Example
---
- hosts: all
tasks:
- name: Check if the hostvar 'new_node' is present on any hosts
set_fact:
has_new_node: "{{ groups['all'] | map('extract', hostvars, 'new_node') | list | select('defined') | length | bool }}"
delegate_to: localhost
- name: Abort play if not 'has_new_node'
meta: end_play
when: not has_new_node
- debug:
msg: new_node found

Create a new list after setting fact

I have a playbook that looks like this
---
- hosts: localhost
gather_facts: false
connection: local
vars:
addresses: ['10.10.10.0/28', '10.11.11.0/28']
list_address: []
tasks:
- name: set empty list for cidr
set_fact:
ips_and_masks: []
- name: set up address fact
set_fact:
address: "{{ item.split('/')[0] }} {{ item | ipaddr('netmask') }}"
loop: "{{ item.addresses }}"
- name: create new list
set_fact:
list_address: "{{ list_address + [address] }}"
- name: debug new list
debug:
msg: "Addresses: {{ list_address }}"
I'm trying to get the output to be
"Address": [
"10.10.10.0 255.255.255.240",
"10.11.11.0 255.255.255.240"
]
But the second iteration of the loop overwrites the first so I end up with
"Address": [
"10.11.11.0 255.255.255.240"
]
Is there a way to append it rather than overwrite it?
You don't need address fact at all. Change your task to this:
- name: set up address fact
set_fact:
list_address: "{{ (list_address | default([])) + [ (item.split('/')[0]) + (item | ipaddr('netmask')) ] }}"
loop: "{{ addresses }}"
Then, you can remove create new list task.
You can remove list_address var declaration as well, because it's been initialized in set_fact (as #Zeitounator says in comments)

Ansible: Skip loop when list is undefined

Example playbook -
---
- hosts: localhost
vars:
lesson:
name: Physics
students:
- Bob
- Joe
tasks:
- name: Display student names
debug:
msg: '{{ item }}'
loop: "{{ lesson.students }}"
when: item | default("")
The above playbook works well to output the student names.
However, if the input changes (as per below) such that no student names have been defined, then an error occurs. Is there a simple way to have the playbook skip this task if the list is undefined as per the input below? I realize it would work if the input specifies students: [], but as this input is coming from simple users, they're not going to know this. Much Thanks!
vars:
lesson:
name: Physics
students:
Error: fatal: [localhost]: FAILED! =>
msg: 'Invalid data passed to ''loop'', it requires a list, got this instead: . Hint: If you passed a list/dict of just one element, try adding wantlist=True to your lookup invocation or use q/query instead of lookup.
Update - I've tried the below variations but still get the same error -
---
- hosts: localhost
vars:
lesson:
name: Physics
students:
tasks:
- name: Display student names variation 1
debug:
msg: '{{ item }}'
loop: "{{ lesson.students }}"
when: lesson.students is iterable
- name: Display student names variation 2
debug:
msg: '{{ item }}'
loop: "{{ lesson.students }}"
when: lesson.students is not none
- name: Display student names variation 3
debug:
msg: '{{ item }}'
loop: "{{ lesson.students }}"
when: ( item | default("") ) or ( item is not none )
The real problem is that loop requires a list, even if it is an empty list.
If your var is undefined/None/empty string, it exists but is not a list and your when condition will never get evaluated because loop will fire an error before it is ever reached.
You have to default your var to an empty list in such cases, which will lead to a 0 size loop equivalent to skipping the task.
Since your var is defined but None you need to use the second optional parameter to default so that empty/false values are replaced as well
Note: I used the short alias d to default in my below examples
- name: Display student names
debug:
msg: '{{ item }}'
loop: "{{ lesson.students | d([], true) }}"
A good practice here that would have nipped that error in the bud would be to have a coherent data declaration by either:
not declaring the key at all and use a simple default i.e.
# ... #
vars:
lesson:
name: Physics
# ... #
loop: "{{ lesson.students | d([]) }}"
declare an empty list for the key rather than a None value i.e.
# ... #
vars:
lesson:
name: Physics
students: []
# ... #
loop: "{{ lesson.students }}"
My first proposition is the safest in this case anyway and will work in for all the above vars declarations.
There is a difference between an undefined variable, and variable having None value.
When you set variable name, but leave the right hand side empty. The variable is defined, but it is set to NoneType.
So your when: condition should have additional check for NoneType:
- hosts: localhost
vars:
lesson:
name: Physics
students:
tasks:
- name: Display student names
debug:
msg: '{{ item }}'
loop: "{{ lesson.students }}"
when: ( item | default("") ) or ( item is not none )
This will give:
skipping: [localhost] => (item=None)

Ansible: Convert two lists into key, value dict

I have 2 lists as set_fact and want to create a dict
I am running ansible 2.8
I have list1 as below
"inventory_devices": [
"device0",
"device1"
]
and list2 as below
"inventory_ips": [
"10.1.1.1",
"10.1.1.2"
]
I want to get an output shows like
"inventory_dict": [
"device0": "10.1.1.1",
"device1": "10.1.1.2"
]
Thanks.
You can do it entirely with jinja2 using the zip filter built into ansible.
To get a list combining the elements of other lists use zip
- name: give me list combo of two lists
debug:
msg: "{{ [1,2,3,4,5] | zip(['a','b','c','d','e','f']) | list }}"
...
Similarly to the output of the items2dict filter mentioned above, these filters can be
used to contruct a dict:
{{ dict(keys_list | zip(values_list)) }}
The zip filter sequentially combines items from pairs of lists and the dict construct creates a dictionary from a list of pairs.
inventory_dict: "{{ dict(inventory_devices | zip(inventory_ips)) }}"
here is the task to do it, populate combined var in the PB below:
---
- hosts: localhost
gather_facts: false
vars:
inventory_devices:
- device0
- device1
inventory_ips:
- 10.1.1.1
- 10.1.1.2
tasks:
- name: populate combined var
set_fact:
combined_var: "{{ combined_var|default({}) | combine({ item.0: item.1 }) }}"
loop: "{{ query('together', inventory_devices, inventory_ips) }}"
- name: print combined var
debug:
var: combined_var
result:
TASK [print combined var] **********************************************************************************************************************************************************************************************
ok: [localhost] => {
"combined_var": {
"device0": "10.1.1.1",
"device1": "10.1.1.2"
}
}
hope it helps

Resources