Merge 2 lists of dictionaries by property value in Ansible - ansible

I'm trying to merge 2 lists of dictionary objects by one of the properties, but I can't seem to find the correct syntax/filters to do so
default_settings:
- { name: 'setting1', value: 'default value1' }
- { name: 'setting2', value: 'default value2' }
overrides:
- { name: 'setting1', value: 'overridden value' }
- { name: 'setting3', value: 'new value3' }
This should result in:
result:
- { name: 'setting1', value: 'overridden value' }
- { name: 'setting2', value: 'default value2' }
- { name: 'setting3', value: 'new value3' }
I've tried my luck with simply adding them, using combine filter, but those things either only work on dictionaries or lists, but not lists of dictionaries.

Of course after 2 hours of trying and searching and eventually making this post, I managed to get it working in a final little thing I tested. So for those who have the same problem, you can use the groupby filter, combined with a mapped last and mapped combine.
---
- hosts: localhost
connection: local
gather_facts: no
vars:
default_settings:
- { name: 'setting1', value: 'default value1' }
- { name: 'setting2', value: 'default value2' }
overrides:
- { name: 'setting1', value: 'overridden value' }
- { name: 'setting3', value: 'new value3' }
tasks:
- debug:
msg: "{{ item.name }} = {{ item.value }}"
loop: "{{ (default_settings + overrides) | groupby('name') | map('last') | map('combine') }}"
TASK [debug] *********************************************************************************************************************************************************************************************************************************************************************
ok: [localhost] => (item={'name': 'setting1', 'value': 'overridden value'}) => {
"msg": "setting1 = overridden value"
}
ok: [localhost] => (item={'name': 'setting2', 'value': 'default value2'}) => {
"msg": "setting2 = default value2"
}
ok: [localhost] => (item={'name': 'setting3', 'value': 'new value3'}) => {
"msg": "setting3 = new value3"
}

The easiest way is to convert them to dictionaries and combine them:
- hosts: localhost
gather_facts: no
vars:
default_settings:
- { name: 'setting1', value: 'default value1' }
- { name: 'setting2', value: 'default value2' }
overrides:
- { name: 'setting1', value: 'overridden value' }
- { name: 'setting3', value: 'new value3' }
combined_settings: "{{ [default_settings, overrides] | map('items2dict', key_name='name') | combine }}"
tasks:
- name: now you have a dict
debug:
msg: "{{ combined_settings }}"
- name: which you can convert back to a list to iterate over
debug:
msg: "{{ item.key }}: {{ item.value }}"
loop: "{{ combined_settings | dict2items }}"
TASK [now you have a dict] ********************************************************
ok: [localhost] => {
"msg": {
"setting1": "overridden value",
"setting2": "default value2",
"setting3": "new value3"
}
}
TASK [which you can convert back to a list to iterate over] ***********************
ok: [localhost] => (item={'key': 'setting1', 'value': 'overridden value'}) => {
"msg": "setting1: overridden value"
}
ok: [localhost] => (item={'key': 'setting2', 'value': 'default value2'}) => {
"msg": "setting2: default value2"
}
ok: [localhost] => (item={'key': 'setting3', 'value': 'new value3'}) => {
"msg": "setting3: new value3"
}

Related

ansible: how to parse output of an Oracle script (JSON_OBJECT)

There is an Oracle script that runs this query:
select json_object (name, value) from v$parameter where lower(name) in ...
output of this script:
{"name":"processes","value":"1000"}
{"name":"sessions","value":"1522"}
{"name":"sga_target","value":"3221225472"}
{"name":"control_file_record_keep_time","value":"7"}
{"name":"db_block_size","value":"8192"}
{"name":"compatible","value":"19.0.0.0.0"}
...
I register stdout_lines during sql-script execution and then parse line by line:
- name: print fetched database parameters
debug:
msg: "{{ db_params_out.stdout_lines }}"
- name: create list of newdb parameters from fetched result
debug:
msg: "{{ item }}"
with_list: "{{ db_params_out.stdout_lines }}"
when: db_params_out.stdout_lines | length > 0
Output looks good to me:
TASK [db_standby_preinstall : print fetched database parameters] *********************************************************************************************
ok: [localhost -> ol79_1] => {
"msg": [
"",
"{\"name\":\"processes\",\"value\":\"1000\"}",
"{\"name\":\"sessions\",\"value\":\"1522\"}",
"{\"name\":\"sga_target\",\"value\":\"3221225472\"}",
"{\"name\":\"control_file_record_keep_time\",\"value\":\"7\"}",
"{\"name\":\"db_block_size\",\"value\":\"8192\"}",
"{\"name\":\"compatible\",\"value\":\"19.0.0.0.0\"}",
...
TASK [db_standby_preinstall : create list of newdb parameters from fetched result] ***************************************************************************
ok: [localhost -> ol79_1] => (item=) => {
"msg": ""
}
ok: [localhost -> ol79_1] => (item={'name': 'processes', 'value': '1000'}) => {
"msg": {
"name": "processes",
"value": "1000"
}
}
ok: [localhost -> ol79_1] => (item={'name': 'sessions', 'value': '1522'}) => {
"msg": {
"name": "sessions",
"value": "1522"
}
}
ok: [localhost -> ol79_1] => (item={'name': 'sga_target', 'value': '3221225472'}) => {
"msg": {
"name": "sga_target",
"value": "3221225472"
}
}
ok: [localhost -> ol79_1] => (item={'name': 'control_file_record_keep_time', 'value': '7'}) => {
"msg": {
"name": "control_file_record_keep_time",
"value": "7"
}
}
ok: [localhost -> ol79_1] => (item={'name': 'db_block_size', 'value': '8192'}) => {
"msg": {
"name": "db_block_size",
"value": "8192"
}
}
ok: [localhost -> ol79_1] => (item={'name': 'compatible', 'value': '19.0.0.0.0'}) => {
"msg": {
"name": "compatible",
"value": "19.0.0.0.0"
but I cannot address key/value of a fetched item, ansible fails with The error was: 'ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'name' (or 'value') if i try to use item.name or item.value instead of item.
P.S. I can modify Oracle script and provide output in any format. I need to parse output and assign values to corresponding variables:
newdb_processes:1000
newdb_sessions:1522
etc...
Issue is solved. The problem was in the first line of stdout_lines which is empty but still passes via filter
when: db_params_out.stdout_lines | length > 0
I've removed this line but now I use "reject" filter in loop:
with_list: "{{ db_params_out.stdout_lines | reject('match', '^$') }}"
And it works now, no more empty output lines.
Thanks a lot user larsks who pointed me to a test-case with correct data to prove that his solution works properly.
You can pass each line through the from_json filter to get a structured object, like this:
- hosts: localhost
gather_facts: false
vars:
db_params_out:
stdout_lines:
- '{"name":"processes","value":"1000"}'
- '{"name":"sessions","value":"1522"}'
- '{"name":"sga_target","value":"3221225472"}'
- '{"name":"control_file_record_keep_time","value":"7"}'
- '{"name":"db_block_size","value":"8192"}'
- '{"name":"compatible","value":"19.0.0.0.0"}'
tasks:
- name: create list of newdb parameters from fetched result
debug:
msg: "{{ (item|from_json).name }}"
with_list: "{{ db_params_out.stdout_lines }}"
when: db_params_out.stdout_lines | length > 0
The above playbook outputs:
TASK [create list of newdb parameters from fetched result] **********************************************
ok: [localhost] => (item={'name': 'processes', 'value': '1000'}) => {
"msg": "processes"
}
ok: [localhost] => (item={'name': 'sessions', 'value': '1522'}) => {
"msg": "sessions"
}
ok: [localhost] => (item={'name': 'sga_target', 'value': '3221225472'}) => {
"msg": "sga_target"
}
ok: [localhost] => (item={'name': 'control_file_record_keep_time', 'value': '7'}) => {
"msg": "control_file_record_keep_time"
}
ok: [localhost] => (item={'name': 'db_block_size', 'value': '8192'}) => {
"msg": "db_block_size"
}
ok: [localhost] => (item={'name': 'compatible', 'value': '19.0.0.0.0'}) => {
"msg": "compatible"
}

Parsing dictionary from VMware module

I'm using ansible community.vmware.vmware_datacenter_info.I only want to get the name of my several datacenter in vCenter.
Here is my play:
---
- hosts: localhost
gather_facts: False
tasks:
- name: Gather information about all datacenters
community.vmware.vmware_datacenter_info:
hostname: "{{ vCenter }}"
username: "{{ vCenter_username }}"
password: "{{ vCenter_password }}"
validate_certs: False
delegate_to: localhost
register: result
Here is the output of my variable result:
TASK [Print the gateway for each host when defined] ********************************
ok: [localhost] => {
"msg": {
"changed": false,
"datacenter_info": [
{
"config_status": "gray",
"moid": "datacenter-271",
"name": "Sample_DC_1",
"overall_status": "gray"
},
{
"config_status": "gray",
"moid": "datacenter-276",
"name": "Sample_DC_2",
"overall_status": "gray"
}
],
"failed": false
}
}
What I would like to get is only the name for both datacenter: Sample_DC_1 and Simple_DC_2 so I can use them in another play.
What I tried:
- name: Display datacenter name
debug:
msg:
- "{{ item.value.name }}"
loop: "{{ lookup('dict', result) }}"
Result:
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'bool object' has no attribute 'name
if I only try to display "Key: {{ item.key }}" & " Value: {{ item.value }}" I got this output:
"msg": [
"Key : datacenter_info Value : [{'name': 'Sample_DC_1', 'moid': 'datacenter-271', 'config_status': 'gray', 'overall_status': 'gray'}, {'name': 'Sample_DC_2', 'moid': 'datacenter-276', 'config_status': 'gray', 'overall_status': 'gray'}]"
]
How can I register my both datacenter's name in a var so I can re-use them later in the playbook?
Q: "Get the names of both datacenter."
A: Map the attribute
dc_names: "{{ result.datacenter_info|map(attribute='name')|list }}"
Example of a complete playbook for testing
- hosts: localhost
vars:
result:
changed: false
datacenter_info:
- config_status: gray
moid: datacenter-271
name: Sample_DC_1
overall_status: gray
- config_status: gray
moid: datacenter-276
name: Sample_DC_2
overall_status: gray
failed: false
dc_names: "{{ result.datacenter_info|map(attribute='name')|list }}"
tasks:
- debug:
var: dc_names
gives
dc_names:
- Sample_DC_1
- Sample_DC_2

Use two with_items loops in one Task

I did quite some research on this question. Though i did not find the answer to solve my problem.
I want to delete a directorys content with ansibe with deleting the directory itself. I want to do this for multiple directorys.
In theory i want to do something like this:
- name: Delete dir on Prod/Stag
file:
state: "{{ item.1 }}"
path: "{{ /path/ }}{{ item.2 }}/"
with_items.1:
- absent
- directory
with_items.2:
- test1
- test2
- test3
- test4
Sadly this does not work.
This is what i have right now.
- name: Delete dir
file:
state: absent
path: "{{ path }}{{ item }}/"
with_items:
- test1
- test2
- test3
- test4
Is there a way to make this code shorter by creating two loops?
You want with_nested:
- debug:
msg: "state: {{ item.0 }}; path: {{ item.1 }}"
with_nested:
- [ absent, directory ]
- [ sys, wifi, reco-properties, threshold-prod ]
Results in:
TASK [debug] *******************************************************************
ok: [localhost] => (item=[u'absent', u'sys']) => {
"msg": "state: absent; path: sys"
}
ok: [localhost] => (item=[u'absent', u'wifi']) => {
"msg": "state: absent; path: wifi"
}
ok: [localhost] => (item=[u'absent', u'reco-properties']) => {
"msg": "state: absent; path: reco-properties"
}
ok: [localhost] => (item=[u'absent', u'threshold-prod']) => {
"msg": "state: absent; path: threshold-prod"
}
ok: [localhost] => (item=[u'directory', u'sys']) => {
"msg": "state: directory; path: sys"
}
ok: [localhost] => (item=[u'directory', u'wifi']) => {
"msg": "state: directory; path: wifi"
}
ok: [localhost] => (item=[u'directory', u'reco-properties']) => {
"msg": "state: directory; path: reco-properties"
}
ok: [localhost] => (item=[u'directory', u'threshold-prod']) => {
"msg": "state: directory; path: threshold-prod"
}
https://docs.ansible.com/ansible/2.4/playbooks_loops.html#nested-loops

Setting multiple values in sysctl with Ansible

I have a playbook with several tasks setting values to sysctl. Instead of having a task for each setting, how can I set all the values with one task, using the sysctl module?
Playbook snippet:
- name: Set tcp_keepalive_probes in sysctl
become: yes
sysctl:
name: net.ipv4.tcp_keepalive_probes
value: 3
state: present
reload: yes
- name: Set tcp_keepalive_intvl in sysctl
become: yes
sysctl:
name: net.ipv4.tcp_keepalive_intvl
value: 10
state: present
reload: yes
- name: Set rmem_default in sysctl
become: yes
sysctl:
name: net.core.rmem_default
value: 16777216
state: present
reload: yes
define all the variables in a var file:
e.g.
sysctl:
- name: test
value: test
...
...
playbook:
- hosts: "{{ }}"
tasks:
- name: update sysctl param
sysctl:
name: "{{ item.name }}"
value: "{{ item.value }}"
state: present
reload: yes
with_items:
- "{{ hosts }}"
Simple solution: define variable as a dict
Example playbook:
---
- hosts: all
gather_facts: false
become: true
vars:
ansible_python_interpreter: /usr/bin/python3
sysctl_config:
net.ipv4.ip_forward: 1
net.ipv4.conf.all.forwarding: 1
net.ipv6.conf.all.forwarding: 1
tasks:
- name: Change various sysctl-settings
sysctl:
name: '{{ item.key }}'
value: '{{ item.value }}'
sysctl_set: yes
state: present
reload: yes
ignoreerrors: yes
with_dict: '{{ sysctl_config }}'
Output:
TASK [Change various sysctl-settings] **********************************************************************************************************************************************************************
changed: [10.10.10.10] => (item={'key': 'net.ipv4.ip_forward', 'value': 1}) => {
"ansible_loop_var": "item",
"changed": true,
"item": {
"key": "net.ipv4.ip_forward",
"value": 1
}
}
changed: [10.10.10.10] => (item={'key': 'net.ipv4.conf.all.forwarding', 'value': 1}) => {
"ansible_loop_var": "item",
"changed": true,
"item": {
"key": "net.ipv4.conf.all.forwarding",
"value": 1
}
}
changed: [10.10.10.10] => (item={'key': 'net.ipv6.conf.all.forwarding', 'value': 1}) => {
"ansible_loop_var": "item",
"changed": true,
"item": {
"key": "net.ipv6.conf.all.forwarding",
"value": 1
}
}

Loops inside inventory files?

Is it possible to use loops in the inventory files? For example in one inv file I got:
---
ISPs:
- name: ISP1
- name: ISP2
networks:
- name: network1
- name: network2
- name: network3
and then I want to create something like this in this same inventory:
from ISP1 to network1 permit
from ISP1 to network2 permit
from ISP1 to network3 permit
from ISP2 to network1 permit
from ISP2 to network2 permit
from ISP2 to network3 permit
but I think that using loops I could do it faster instead of copy-paste and write down all of the possible combinations. Is it even possible?
example:
from {{ item[0] }} to {{ item[1] }} permit
with_nested:
- [ 'ISP1', 'ISP2' ]
- [ 'network1', 'network2', 'network3' ]
will create something like this:
from ISP1 to network1 permit
from ISP1 to network2 permit
from ISP1 to network3 permit
from ISP2 to network1 permit
from ISP2 to network2 permit
from ISP2 to network3 permit
Thanks for any answer!
here is 2 variants,
see https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html#with-nested-with-cartesian
---
- hosts: localhost
connection: local
gather_facts: false
vars:
ISPs:
- name: ISP1
- name: ISP2
networks:
- name: network1
- name: network2
- name: network3
tasks:
- name: Using with_nested
debug:
msg: '{{ item.0.name }} - {{item.1.name }} '
with_nested:
- '{{ ISPs }}'
- '{{ networks }}'
- name: Using loop
debug:
msg: '{{ item.0.name }} - {{item.1.name }} '
loop: "{{ ISPs | product(networks) | list }}"
Output:
PLAY [localhost] *******************************************************************************************************************************************************************************************
TASK [Using with_nested] ***********************************************************************************************************************************************************************************
ok: [localhost] => (item=[{u'name': u'ISP1'}, {u'name': u'network1'}]) => {
"msg": "ISP1 - network1 "
}
ok: [localhost] => (item=[{u'name': u'ISP1'}, {u'name': u'network2'}]) => {
"msg": "ISP1 - network2 "
}
ok: [localhost] => (item=[{u'name': u'ISP1'}, {u'name': u'network3'}]) => {
"msg": "ISP1 - network3 "
}
ok: [localhost] => (item=[{u'name': u'ISP2'}, {u'name': u'network1'}]) => {
"msg": "ISP2 - network1 "
}
ok: [localhost] => (item=[{u'name': u'ISP2'}, {u'name': u'network2'}]) => {
"msg": "ISP2 - network2 "
}
ok: [localhost] => (item=[{u'name': u'ISP2'}, {u'name': u'network3'}]) => {
"msg": "ISP2 - network3 "
}
TASK [Using loop] ******************************************************************************************************************************************************************************************
ok: [localhost] => (item=[{u'name': u'ISP1'}, {u'name': u'network1'}]) => {
"msg": "ISP1 - network1 "
}
ok: [localhost] => (item=[{u'name': u'ISP1'}, {u'name': u'network2'}]) => {
"msg": "ISP1 - network2 "
}
ok: [localhost] => (item=[{u'name': u'ISP1'}, {u'name': u'network3'}]) => {
"msg": "ISP1 - network3 "
}
ok: [localhost] => (item=[{u'name': u'ISP2'}, {u'name': u'network1'}]) => {
"msg": "ISP2 - network1 "
}
ok: [localhost] => (item=[{u'name': u'ISP2'}, {u'name': u'network2'}]) => {
"msg": "ISP2 - network2 "
}
ok: [localhost] => (item=[{u'name': u'ISP2'}, {u'name': u'network3'}]) => {
"msg": "ISP2 - network3 "
}
PLAY RECAP *************************************************************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0
It is possible to do, but applying login inside ansible code will be messy, So better idea is to invoke any bash or python script in ansible & pass these two list as an argument to that script & return the expected array from the script at the end & store this value to fact using set_fact
Example:
tasks:
- set_fact: isp_networks="{{ lookup('pipe','python script.py ' + {{ISPs}} + ' ' + {{networks}}) }}"
- debug: var={{isp_networks}}

Resources