Ansible: merge dictionaries appending values - data-structures

How can I get a dictionary with values from input separated with a comma? There can be a different number and order of input parameters. What I've tried just gives the error below
- set_fact:
input:
- port: 1234
protocol: TCP
messages: 888-999
file: s3://somepath/file.xsl
- protocol: TLS
port: 5678
path: s3://somepath/mycertificate.crt
messages: 345, 467, 888
file: s3://somepath/file2.xsl
- set_fact:
final_dict:
finalFile: item | map(attribute='file')| join(',')
finalFilter: item | map(attribute='messages')| join(',')
finalPath: item | map(attribute='path')| join(',')
finalProtocol: item | map(attribute='protocol')| join(',')
finalPort: item | map(attribute='port')| join(',')
loop: "{{ input }}"
The error message is as follows:
>"msg": "The task includes an option with an undefined variable. The error was: 'str object' has no attribute 'file'

You do have three issues here:
if you intend to use map, then you need to do it on a list, so, you should have expressions like
var: input | map(attribute='file')
And not act on the item of a loop.
you are missing the {{ ... }} expression delimiters in your final_dict, e.g.:
finalFile: "{{ input | map(attribute='file') | join(',') }}"
and not
finalFile: input | map(attribute='file') | join(',')
Because you do have some undefined keys in your list of dictionaries input, you want to use the default value of map:
finalPath: "{{ input | map(attribute='path', default='') | join(',') }}"
(optionally) if you are going to use input only in that context, make it a variable block. You can define it at the level of the task, play, inventory, ...
Given these three remarks, those two tasks:
- set_fact:
final_dict:
finalFile: "{{ input | map(attribute='file') | join(',') }}"
finalFilter: "{{ input | map(attribute='messages') | join(',') }}"
finalPath: "{{ input | map(attribute='path', default='') | join(',') }}"
finalProtocol: "{{ input | map(attribute='protocol') | join(',') }}"
finalPort: "{{ input | map(attribute='port') | join(',') }}"
vars:
input:
- port: 1234
protocol: TCP
messages: 888-999
file: s3://somepath/file.xsl
- protocol: TLS
port: 5678
path: s3://somepath/mycertificate.crt
messages: 345, 467, 888
file: s3://somepath/file2.xsl
- debug:
var: final_dict
Would yield:
ok: [localhost] =>
final_dict:
finalFile: s3://somepath/file.xsl,s3://somepath/file2.xsl
finalFilter: 888-999,345, 467, 888
finalPath: ',s3://somepath/mycertificate.crt'
finalPort: 1234,5678
finalProtocol: TCP,TLS

Related

Create var based on list in dict

Imagine this dict on 4 different hosts.
# on host 1
my_dict:
ip: 10.0.0.111
roles:
- name: something
observer: false
# on host 2
my_dict:
ip: 10.0.0.112
roles:
- name: something
observer: false
# on host 3
my_dict:
ip: 10.0.0.113
roles:
- name: something
observer: true
# on host 4
my_dict:
ip: 10.0.0.114
roles:
- name: whatever
When Ansible runs for all 4 hosts I want it to build a variable for each host having the roles name 'something'. The desired output is:
10.0.0.111 10.0.0.112 10.0.0.113:observer
There are 2 requirements:
when my_dict.roles.name == 'something' it must add the ip to the var
but when my_dict.roles.observer , it must add the ip + ':observer'
I eventually want to use the var in a Jinja template, so to me, the var can be either set via an Ansible task or as a jinja template.
This doesn't work:
- name: set fact for ip
debug:
msg: >-
{{ ansible_play_hosts |
map('extract', hostvars, ['my_dict', 'ip'] ) |
join(' ') }}
when: ???
You could create two lists:
one with what should be postfixed to the IPs with the condition based on the observer property
the other one with the IPs
And then zip them back together.
Given:
- debug:
msg: >-
{{
_ips | zip(_is_observer) | map('join') | join(' ')
}}
vars:
_hosts: >-
{{
hostvars
| dict2items
| selectattr('key', 'in', ansible_play_hosts)
| selectattr('value.my_dict.roles.0.name', '==', 'something')
}}
_is_observer: >-
{{
_hosts
| map(attribute='value.my_dict.roles.0.observer')
| map('replace', false, '')
| map('replace', true, ':observer')
}}
_ips: >-
{{
_hosts
| map(attribute='value.my_dict.ip')
}}
This yields:
TASK [debug] *************************************************************
ok: [localhost] =>
msg: 10.0.0.111 10.0.0.112 10.0.0.113:observer

Get Odd or Even Index Value from Variable in Ansible Playbook

I need to get odd or even index value from a variable list:
For example:
- hosts: myhost
vars:
- var1: ["test1","test2","test3","test4","test5"]
- odd_var: []
- even_var: []
I need odd_var to be ["test1","test3","test5"] and even_var to be ["test2","test4"] and also concatenate each variable string of odd_var and even_var to be one string like:
odd_string: "test1,test3,test5"
even_string: "test2,test4"
What should i do to achieve this?
I have tried :
- name: test
set_fact:
odd_list: "{{ odd_list | default([]) + [item] }}"
loop: "{{ var1 }}"
when: "{{ lookup('ansible.utils.index_of', var1, 'eq', item) is even }}
it works but i wonder if i can get more eficient way to do this
Since you said "index value", I'm going to take you at your word and base it on position in the list, not the numbers contained in the strings.
- hosts: localhost
vars:
var1:
- test1
- test2
- test3
- test4
- test5
odd_var: "{{ var1[::2] | join(',') }}"
even_var: "{{ var1[1::2] | join(',') }}"
tasks:
- debug:
msg: "{{ odd_var }} / {{ even_var }}"
Output:
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": "test1,test3,test5 / test2,test4"
}
Using regex, checking the last character.
- var1: ["test1","test2","test3","test4","test5"]
- even: "{{ var1 | select('search','.*[02468]$') | join(',') }}"
- odd: "{{ var1 | select('search','.*[13579]$') | join(',') }}"

Ansible returns only one item from a dictionary

I'm trying to get a list of IPs from an specific Service, and ansible returns only one item from the loop.
I have tried many things and is always the same result.
Need help.
- name: "Amazon IPs"
include_vars:
file: /home/user1/ansible/AWS/ip-ranges.json
name: amazon
# - set_fact:
# # # test: "{{ (variable.stdout | from_json).prefixes | map(attribute='ip_prefix') | list }}"
# amazonipv4: "{{ item }}"
# # amazonipv6: "{{ amazon.ipv6_prefixes | map(attribute='ipv6_prefix') | list }}"
# loop: "{{amazon.prefixes | map(attribute='ip_prefix') | list }}"
# # when: '"AMAZON" in item.service'
- set_fact:
test3: "{{item.ip_prefix}}"
loop: "{{amazon.prefixes | list }}"
when: '"AMAZON" in item.service'
- debug:
var: test3
I expect to get a list based on the service, but I only get one item.
example:
TASK [debug] ***********************************************
ok: [localhost] => {
"test3": "54.190.198.32/28"
in each iteration in the set_fact loop, you are setting the value, not pushing to a list. you need to change your syntax to:
- set_fact:
test3: "{{ test3 | default([]) + [item.ip_prefix] }}"
hope it helps.

Multiple facts retrieved in set_fact task causes playbook run to issue error

Multiple facts retrieved using set_fact in playbook, causes error
I have tried separate set_fact tasks, 1 per fact retrieved. Also got error.
Seems to occur when I have 3 facts defined under set_fact i.e when I include mountsize_tmp. No error when I have the first 2 facts only.
Does a variable name used in set_fact need to be defined in the var variables section?
Error is:
The offending line appears to be: set_fact: ^ here exception type: exception: No first item, sequence was empty.
- set_fact:
alto_seal: "{{ ansible_local.alto_bootstrap.seal }}" # local fact
mountsize: "{{ ansible_mounts | selectattr('mount', 'equalto', '/abc') | map(attribute='size_total') | first }}" # ansible fact
mountsize_tmp: "{{ ansible_mounts | selectattr('mount', 'equalto', '/tmp') | map(attribute='size_total') | first }}"
- debug:
msg: "{{ 'Alto start mountsize ' ~ mountsize }}"
- debug:
msg: "{{'Seal ' ~ alto_seal }}"
Expect values of a file mount a size and a fact from a custom fact file to be displayed in 2 rows
To avoid your exception you can manage that by:
Defining default values:
- set_fact:
alto_seal: "{{ ansible_local.alto_bootstrap.seal }}" # local fact
- set_fact:
mountsize: "<DEFAULT_VALUE_OF_YOUR_CHOICE>"
mountsize_tmp: "<DEFAULT_VALUE_OF_YOUR_CHOICE>"
- set_fact:
mountsize: "{{ ansible_mounts | selectattr('mount', 'equalto', '/abc') | map(attribute='size_total') | first }}" # ansible fact
when: ansible_mounts | selectattr('mount', 'equalto', '/abc') | map(attribute='size_total') | list != []
- set_fact:
mountsize_tmp: "{{ ansible_mounts | selectattr('mount', 'equalto', '/tmp') | map(attribute='size_total') | first }}"
when: ansible_mounts | selectattr('mount', 'equalto', '/tmp') | map(attribute='size_total') | list != []
or
Sending a specific message:
- set_fact:
alto_seal: "{{ ansible_local.alto_bootstrap.seal }}" # local fact
- set_fact:
mountsize: "{{ ansible_mounts | selectattr('mount', 'equalto', '/abc') | map(attribute='size_total') | first }}" # ansible fact
msg: "{{ 'Alto start mountsize ' ~ mountsize }}"
when: ansible_mounts | selectattr('mount', 'equalto', '/abc') | map(attribute='size_total') | list != []
- set_fact:
msg: "Alto start mountsize cannot be calculated"
when: ansible_mounts | selectattr('mount', 'equalto', '/abc') | map(attribute='size_total') | list == []
- debug:
msg: "{{ msg }}"

Ansible get first element from list

Suppose I have the following vars_file:
mappings:
- primary: 1.1.1.1
secondary: 2.2.2.2
- primary: 12.12.12.12
secondary: 11.11.11.11
and hosts file
1.1.1.1
12.12.12.12
5.5.5.5
and the following playbook task
- name: Extract secondary from list
debug:
msg: "{{ (mappings | selectattr('primary', 'search', inventory_hostname) | list | first | default({'secondary':None})).secondary }}"
The current task works and will give empty string when no match are found, but I would like to know if there is a better way/cleaner way of doing it without passing a dictionary to the default constructor.
An option would be to use json_query
- debug:
msg: "{{ mappings | json_query(\"[?primary=='\" + inventory_hostname + \"'].secondary\") }}"
, but selectattr works too
- debug:
msg: "{{ mappings | selectattr('primary', 'equalto', inventory_hostname) | map(attribute='secondary') | list }}"

Resources