I want to extract values from a nested dictionary. The dictionary is
"customers": {
"customer_1": [
{
"c1_cluster_1": [
{
"primary": [
"c1_server_1"
]
},
{
"secondaries": [
"c1_server_2",
"c1_server_3"
]
},
{
"tiebreakers": [
"c1_server_4",
"c1_server_5"
]
}
]
},
{
"c1_cluster_2": [
{
"primary": [
"c1_server_1"
]
},
{
"secondaries": [
"c1_server_2",
"c1_server_3"
]
},
{
"tiebreakers": [
"c1_server_4",
"c1_server_5"
]
}
]
},
{
"c1_cluster_3": [
{
"primary": [
"c1_server_1"
]
},
{
"secondaries": [
"c1_server_2",
"c1_server_3"
]
},
{
"tiebreakers": [
"c1_server_4",
"c1_server_5"
]
}
]
}
],
"customer_2": [
{
"c2_cluster_1": [
{
"primary": [
"c2_server_1"
]
},
{
"secondaries": [
"c2_server_2",
"c2_server_3"
]
},
{
"tiebreakers": [
"c2_server_4",
"c2_server_5"
]
}
]
},
{
"c2_cluster_2": [
{
"primary": [
"c2_server_1"
]
},
{
"secondaries": [
"c2_server_2",
"c2_server_3"
]
},
{
"tiebreakers": [
"c2_server_4",
"c2_server_5"
]
}
]
}
]
}
I need to have a list of unique server names per customer, the Cluster number or server role are not needed. End result would be
customer_servers: [
customer_1: [
"c1_server_1",
"c1_server_2",
"c1_server_3",
"c1_server_4",
"c1_server_5"
],
customer_2: [
"c2_server_1",
"c2_server_2",
"c2_server_3",
"c2_server_4",
"c2_server_5"
]
]
I've tried the following
- name: use Jinja to extract only the customer names and server names from customers
ansible.builtin.set_fact:
cust_servers: |
{% for cust in customers %}
{{ cust.key }}:
{% for serv in cust.value %}
{% for k, v in serv.items() %}
- {{v}}
{% endfor %}
{% endfor %}
{% endfor %}
- name: Convert cust_servers to a dictionary
ansible.builtin.set_fact:
cust_servers_dict: "{{cust_servers|from_yaml}}"
I've also attempted to break down the components of customers using debug but, have failed to get to the level that I wanted
- name: print the customer dictionary component parts
debug:
msg:
- "ClusterName is {{customers[0].key}} and it's value is"
- "{{customers[0].value}}
- " ----------------------------------------------------- "
- "{{customers[0].value[0].values()}}
My intention with this second attempt was to loop through the customer list but it became apparent that i might need multiple loops with this approach and i was probably over complicating it.
For example,
customer_servers_keys: "{{ customers.keys()|list }}"
customer_servers_vals: "{{ customers.values()|
map('ansible.utils.to_paths')|
map('dict2items')|
map('map', attribute='value')|
map('unique')|list }}"
customer_servers: "{{ dict(customer_servers_keys|
zip(customer_servers_vals)) }}"
gives the expected result
customer_servers:
customer_1: [c1_server_1, c1_server_2, c1_server_3, c1_server_4, c1_server_5]
customer_2: [c2_server_1, c2_server_2, c2_server_3, c2_server_4, c2_server_5]
Example of a complete playbook for testing
- hosts: localhost
vars:
customers:
customer_1:
- c1_cluster_1:
- primary: [c1_server_1]
- secondaries: [c1_server_2, c1_server_3]
- tiebreakers: [c1_server_4, c1_server_5]
- c1_cluster_2:
- primary: [c1_server_1]
- secondaries: [c1_server_2, c1_server_3]
- tiebreakers: [c1_server_4, c1_server_5]
- c1_cluster_3:
- primary: [c1_server_1]
- secondaries: [c1_server_2, c1_server_3]
- tiebreakers: [c1_server_4, c1_server_5]
customer_2:
- c2_cluster_1:
- primary: [c2_server_1]
- secondaries: [c2_server_2, c2_server_3]
- tiebreakers: [c2_server_4, c2_server_5]
- c2_cluster_2:
- primary: [c2_server_1]
- secondaries: [c2_server_2, c2_server_3]
- tiebreakers: [c2_server_4, c2_server_5]
# Expected result
# customer_servers:
# - customer_1: [c1_server_1, c1_server_2, c1_server_3, c1_server_4, c1_server_5]
# - customer_2: [c2_server_1, c2_server_2, c2_server_3, c2_server_4, c2_server_5]
customer_servers_keys: "{{ customers.keys()|list }}"
customer_servers_vals: "{{ customers.values()|
map('ansible.utils.to_paths')|
map('dict2items')|
map('map', attribute='value')|
map('unique')|list }}"
customer_servers: "{{ dict(customer_servers_keys|
zip(customer_servers_vals)) }}"
tasks:
- debug:
var: customer_servers|to_yaml
Related
I am producing from VMware datastores collection a list of volumes and their associated tags
I formated them in a JSON type output to be able to feed another system later. The output is working but for the tags section I would like to keep only the name and category_name not the other properties.
This is my playbook :
---
- name: Fetching Datastores
hosts: localhost
tasks:
- name: Gather info from datacenter about specific datastore
community.vmware.vmware_datastore_info:
hostname: 'myesxiHost'
username: 'administrator'
password: 'Password'
validate_certs: False
show_tag: true
delegate_to: localhost
register: ds_info
- set_fact:
ds_info_eph: "{{ ds_info_eph|default([]) + [ {
'name': item.name,
'type': item.type,
'capacity': item.capacity,
'tag': item.tags
} ] }}"
loop: "{{ ds_info.datastores }}"
- name: Output info
debug:
msg: "{{ ds_info_eph }}"
This playbook give me the following output :
ok: [localhost] => {
"msg": [
{
"capacity": 499826819072,
"name": "ESX01_SSD4",
"tag": [
{
"category_id": "urn:vmomi:InventoryServiceCategory:017ad796-b931-4f5b-bd4a-8fee7adaf124:GLOBAL",
"category_name": "Tier",
"description": "",
"id": "urn:vmomi:InventoryServiceTag:6112eeac-ed81-48d4-b7d9-81084b8a415a:GLOBAL",
"name": "Gold"
}
],
"type": "VMFS"
},
{
"capacity": 499826819072,
"name": "ESX01_SSD3",
"tag": [
{
"category_id": "urn:vmomi:InventoryServiceCategory:017ad796-b931-4f5b-bd4a-8fee7adaf124:GLOBAL",
"category_name": "Tier",
"description": "",
"id": "urn:vmomi:InventoryServiceTag:6112eeac-ed81-48d4-b7d9-81084b8a415a:GLOBAL",
"name": "Gold"
}
],
"type": "VMFS"
},
{
"capacity": 499826819072,
"name": "ESX01_SSD5",
"tag": [
{
"category_id": "urn:vmomi:InventoryServiceCategory:017ad796-b931-4f5b-bd4a-8fee7adaf124:GLOBAL",
"category_name": "Tier",
"description": "",
"id": "urn:vmomi:InventoryServiceTag:6112eeac-ed81-48d4-b7d9-81084b8a415a:GLOBAL",
"name": "Gold"
}
],
"type": "VMFS"
},
{
"capacity": 239981297664,
"name": "ESX01_SSD2",
"tag": [
{
"category_id": "urn:vmomi:InventoryServiceCategory:017ad796-b931-4f5b-bd4a-8fee7adaf124:GLOBAL",
"category_name": "Tier",
"description": "",
"id": "urn:vmomi:InventoryServiceTag:6112eeac-ed81-48d4-b7d9-81084b8a415a:GLOBAL",
"name": "Gold"
}
],
"type": "VMFS"
},
{
"capacity": 248034361344,
"name": "ESX01_SSD1",
"tag": [
{
"category_id": "urn:vmomi:InventoryServiceCategory:017ad796-b931-4f5b-bd4a-8fee7adaf124:GLOBAL",
"category_name": "Tier",
"description": "",
"id": "urn:vmomi:InventoryServiceTag:6112eeac-ed81-48d4-b7d9-81084b8a415a:GLOBAL",
"name": "Gold"
}
],
"type": "VMFS"
}
]
}
Instead I would like to have this output :
ok: [localhost] => {
"msg": [
{
"capacity": 499826819072,
"name": "ESX01_SSD4",
"tag": [
{
"category_name": "Tier",
"name": "Gold"
}
],
"type": "VMFS"
},
{
"capacity": 499826819072,
"name": "ESX01_SSD3",
"tag": [
{
"category_name": "Tier",
"name": "Gold"
}
],
"type": "VMFS"
},
{
"capacity": 499826819072,
"name": "ESX01_SSD5",
"tag": [
{
"category_name": "Tier",
"name": "Gold"
}
],
"type": "VMFS"
},
{
"capacity": 239981297664,
"name": "ESX01_SSD2",
"tag": [
{
"category_name": "Tier",
"name": "Gold"
}
],
"type": "VMFS"
},
{
"capacity": 248034361344,
"name": "ESX01_SSD1",
"tag": [
{
"category_name": "Tier",
"name": "Gold"
}
],
"type": "VMFS"
}
]
}
But on the tag section I tried to do in the playbook the following to get only the name and not all the tag item list : 'tag': item.tags.name but I'v got the following error fatal: [localhost]: FAILED! => {"msg": "Unexpected templating type error occurred on ({{ ds_info_eph|default([]) + [ { 'name': item.name, 'type': item.type, 'capacity': item.capacity, 'tag': item.tags.name } ] }}): can only concatenate str (not \"list\") to str"}
My understanding is somehow in that set_fac I have to loop again to get only the tag name and tag category_name but I didn't figure out how to.
Thank you for your help !
Select the attributes from the tag lists, e.g.
- set_fact:
_tags: "{{ ds_info_eph|
map(attribute='tag')|
map('items2dict', 'category_name', 'name')}}"
gives
_tags:
- Tier: Gold
- Tier: Gold
- Tier: Gold
- Tier: Gold
- Tier: Gold
Combine the dictionaries
- set_fact:
result: "{{ result|d([]) + [item.0|combine({'tag': item.1})] }}"
with_together:
- "{{ ds_info_eph }}"
- "{{ _tags|map('dict2items', 'category_name', 'name') }}"
gives
result:
- capacity: 499826819072
name: ESX01_SSD4
tag:
category_name: Tier
name: Gold
type: VMFS
- capacity: 499826819072
name: ESX01_SSD3
tag:
category_name: Tier
name: Gold
type: VMFS
- capacity: 499826819072
name: ESX01_SSD5
tag:
category_name: Tier
name: Gold
type: VMFS
- capacity: 239981297664
name: ESX01_SSD2
tag:
category_name: Tier
name: Gold
type: VMFS
- capacity: 248034361344
name: ESX01_SSD1
tag:
category_name: Tier
name: Gold
type: VMFS
I have json file (I can't change what is in json file)
{
"application": {
...,
"VS": [
{
"LTM": "server1",
"ltm_vs": "VS_1",
"address": "IP",
"port": "80",
"link": "/Common/link"
},
{
"LTM": "server1",
"ltm_vs": "VS_2",
"address": "IP",
"port": "8081",
"link": "/Common/link"
},
{
"LTM": "server1",
"ltm_vs": "VS_3",
"address": "IP",
"port": "443",
"link": "/Common/link"
}
]
}
}
I need to take that file to create list with only some information.
So presently I import the json file like that:
...
tasks:
- name: Include application information
include_vars:
file: path/file.json
...
and was create a variables list application.VS I need to map attribute LTM to server and ltm_vs to virtual_server and create list:
VS_lst:
- { server: "server1", virtual_server: "VS_1" }
- { server: "server1", virtual_server: "VS_2" }
- { server: "server1", virtual_server: "VS_3" }
I have try with that:
- name: Add variables
set_fact:
VS_lst: >-
{{ VS_lst +[{
'server': "{{ item.LTM }}",
'virtual_server': "{{ item.ltm_vs }}"
}]
}}
with_items: "{{ application.VS }}"
- debug:
msg: "{{VS_lst}}"
the output:
TASK [Add variables] *******************************************************************************
ok: [GTM_Server] => (item={'LTM': 'server1', 'ltm_vs': 'VS_1', 'address': 'IP', 'port': '80', 'link': '/Common/link'})
ok: [GTM_Server] => (item={'LTM': 'server1', 'ltm_vs': 'VS_2', 'address': 'IP', 'port': '8081', 'link': '/Common/link'})
ok: [GTM_Server] => (item={'LTM': 'server1', 'ltm_vs': 'VS_3', 'address': 'IP', 'port': '443', 'link': '/Common/link'})
TASK [debug] ***************************************************************************************
ok: [GTM_Server] => {
"msg": [
{
"server": "server1",
"virtual_server": "VS_2"
},
{
"server": "{{ item.LTM }}",
"virtual_server": "{{ item.ltm_vs }}"
},
{
"server": "{{ item.LTM }}",
"virtual_server": "{{ item.ltm_vs }}"
}
]
}
This is the fixed version
- name: Add variables
set_fact:
VS_lst: "{{ VS_lst|default([]) +
[{'server': item.LTM,
'virtual_server': item.ltm_vs}] }}"
loop: "{{ application.VS }}"
There are also other options. For example, it's possible to use json_query
- set_fact:
VS_lst: "{{ application.VS|
json_query('[].{server: LTM,
virtual_server: ltm_vs}') }}"
- debug:
var: VS_lst
give
"VS_lst": [
{
"server": "server1",
"virtual_server": "VS_1"
},
{
"server": "server1",
"virtual_server": "VS_2"
},
{
"server": "server1",
"virtual_server": "VS_3"
}
]
Without json_query, this task "creates a list of items base on another list of items" and gives the same result
- set_fact:
VS_lst: "{{ VS_lst|default([]) +
[dict(dictionary_keys|
zip(selected_params|
map('extract', item)))] }}"
loop: "{{ application.VS }}"
vars:
selected_params: ['LTM','ltm_vs']
dictionary_keys: ['server', 'virtual_server']
I'm trying to add an existing list to an existing dict in Ansible.
I have a dict "jbossvars" containing the following (ansible debug)
"jbossvars": {
"environments": {
"TEST_ENV": {
"key1": "value1",
"key2": "value2"
},
"TEST_ENV2": {
"key1": "value1",
"key2": "value2"
}
}
}
and a list "env_homes" containing the following (ansible debug)
"env_homes": [
"/opt/redhat/jboss-7.2.0/TEST_ENV",
"/opt/redhat/jboss-7.2.0/TEST_ENV2"
]
which I want to combine to a new dictionary "new_dict"
"jbossvars": {
"environments": {
"TEST_ENV": {
"key1": "value1",
"key2": "value2",
"key3": "/opt/redhat/jboss-7.2.0/TEST_ENV"
},
"TEST_ENV2": {
"key1": "value1",
"key2": "value2",
"key3": "/opt/redhat/jboss-7.2.0/TEST_ENV2"
}
}
}
The following play does not give me the desired situation:
- name: Create dict to append
set_fact:
env_homes: "{{ {'TEST_ENV': [ jbossvars.environments.TEST_ENV ] + env_homes} }}"
- name: Insert created dict into existing dict and save it into a new variable newdict
set_fact:
newdict: "{{ jbossvars.environments|combine(env_homes) }}"
- debug: var: newdict
To get the result
TEST_ENV: { "a": 1, "b": [ 2, "x1", "x2" ] }
The play below
vars:
TEST_ENV:
a: 1
b: 2
add_this:
c: [ x1, x2 ]
tasks:
- set_fact:
add_this: "{{ {'b': [ TEST_ENV.b ] + add_this.c} }}"
- set_fact:
TEST_ENV: "{{ TEST_ENV|combine(add_this) }}"
- debug:
var: TEST_ENV
gives
"TEST_ENV": {
"a": 1,
"b": [
2,
"x1",
"x2"
]
}
How to check if an object of a list of dictionaries is present in another list of dictionaries (with Ansible?)
You could try jinja filters selectattr - I had an issue with using it, so I did revert to a simplified but ugly solution - build a filtered list and compare filtered attributes only (list-to-list).
I do not like it, but it works.
Let me know if you know other way.
playbook:
- name: find existing system_crontabs #would generate a list of dict
find:
path: /var/spool/cron/crontabs/
register: system_side_crontabs
become: True
- name: create lists of system_cron_names and repo_cron_names
set_fact:
system_cron_names: "[]"
repo_cron_names: "[]"
- name: build list of system_cron_names
set_fact:
system_cron_names: "{{ system_cron_names }} + [ '{{ item.path |basename }}' ]"
with_items: "{{ system_side_crontabs.files }}"
- name: build lists of repo_cron_names
set_fact:
repo_cron_names: "{{ repo_cron_names }} + [ '{{ item.user }}' ]"
with_items: "{{ crontabs }}"
- name: assert check if an object of system_crontab is defined in repo_crontab
assert:
that: "{{ [item] |intersect(repo_cron_names) | length }} == 1"
with_items: "{{ system_cron_names }}"
hosts_vars/prd-inner-mgmt202 #a list of dictionaries
crontabs:
- user: root
crontab_rules: |
11 1 * * * find /home/ansible/.ansible/tmp/ -atime +10 -delete
a result of find
ok: [prd-inner-mgmt202] => {
"changed": false,
"examined": 1,
"files": [ ### List of dictionary
{
"path": "/var/spool/cron/crontabs/root",
},
{
"path": "/var/spool/cron/crontabs/another_file",
}
],
"invocation": {
"module_args": {
"age": null,
"age_stamp": "mtime",
"contains": null,
"file_type": "file",
"follow": false,
"get_checksum": false,
"hidden": false,
"path": "/var/spool/cron/crontabs/",
"paths": [
"/var/spool/cron/crontabs/"
],
"patterns": [
"*"
],
"recurse": false,
"size": null,
"use_regex": false
},
"module_name": "find"
},
"matched": 1,
"msg": ""
}
generate a list of strings that are easy to compare
TASK [mid_crontab : build list of system_cron_names] **************************
"ansible_facts": {
"system_cron_names": [
"root",
]
},
"changed": false,
"invocation": {
"module_args": {
"system_cron_names": [
"root"
]
},
"module_name": "set_fact"
},
generate another list of strings
TASK [mid_crontab : build list of repo_cron_names] *****************************
ok: [prd-inner-mgmt202] => (item={u'crontab_rules': u'11 1 * * * find /home/ansible/.ansible/tmp/ -atime +10 -delete\n', u'user': u'root'}) => {
"ansible_facts": {
"repo_cron_names": [
"root"
]
},
"changed": false,
"invocation": {
"module_args": {
"repo_cron_names": [
"root",
"other"
]
},
"module_name": "set_fact"
},
"item": {
"crontab_rules": "11 1 * * * find /home/ansible/.ansible/tmp/ -atime +10 -delete\n",
"user": "root"
}
}
Assert the required check, use intersect jinja filter. In my case a system defined object (cron record) should exist in my repository - so the list should have 1 element.
TASK [mid_crontab : assert check if system_crontab is defined in repo_crontab] *
ok: [prd-inner-mgmt202] => (item=root) => {
"changed": false,
"invocation": {
"module_args": {
"that": "1 == 1"
},
"module_name": "assert"
},
"item": "root",
"msg": "All assertions passed"
}
Seems you want to reduce original dict to list of strings (names) and compare the difference:
---
- hosts: localhost
gather_facts: no
vars:
crontabs:
- user: root
crontab_rules: xxx
tasks:
- find:
path: /tmp/test
register: myfiles
- assert:
that: sys_crons_violation | count == 0
msg: "This crons are not defined in repo : {{ sys_crons_violation | join(', ') }}"
vars:
sys_crons: "{{ myfiles.files | map(attribute='path') | map('basename') | list }}"
repo_crons: "{{ crontabs | map(attribute='user') | list }}"
sys_crons_violation: "{{ sys_crons | difference(repo_crons) }}"
result:
TASK [find] ************************
TASK [assert] **********************
fatal: [localhost]: FAILED! => {
"assertion": "sys_crons_violation | count == 0",
"changed": false,
"evaluated_to": false,
"msg": "This crons are not defined in repo : another_file"
}
basically I need to filter out Date - SEVERITY - JAVACLASSNAME - ERROR MESSAGE.
This is working for me..But its just half done.
(?[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2},[0-9]{3}) %{WORD:Severity}(?:%{GREEDYDATA:msg})
It doesnt show Javaclass..!
Here is the output I get
{
"Timestamp": [
[
"2015-03-03 03:12:16,978"
]
],
"Severity": [
[
"INFO"
]
],
"Error_Message": [
[
" [http-bio-16006-exec-71] [XYZ.ABC.JLM.app.task.ERT] [app:/saas reqid:23121221 jsid:* aid:* uid: org: vorg: un:] - Received to update queued for monitorId=54213213JBNJBSJBSJBS, worklow=8u298u2189u312, session=21684216814321"
]
]
}
LOGLINE
2015-03-03 03:12:16,978 INFO [http-bio-16006-exec-71] [XYZ.ABC.JLM.app.task.ERT] [app:/saas reqid:23121221 jsid:* aid:* uid: org: vorg: un:] - Received to update queued for monitorId=54213213JBNJBSJBSJBS, worklow=8u298u2189u312, session=21684216814321
This should work:
filter {
grok {
match => [
"message",
"%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:severity} \[(?<threadname>[^\]]+)\] \[(?<classname>[^\]]+)\] %{GREEDYDATA:message}"
]
overwrite => ["message"]
}
}