My data is in json file as shown below:
vmdata.json
{
"VMDetails":
[
{
"name": "Owner1",
"vms": [ "vm10", "vm11", "vm12", "vm13" ]
},
{
"name": "Owner2",
"vms": [ "vm20", "vm21", "vm22", "vm23" ]
},
{
"name": "Owner3",
"vms": [ "vm30", "vm31", "vm32", "vm33" ]
}
]
}
I need this json data converted to list of key-value pairs and later used for tagging the VMs on vcenter.
Owner1: vm11
Owner1: vm12
Owner1: vm13
Owner1: vm14
Owner2: vm21
Owner2: vm22
Owner2: vm23
Owner2: vm24
Owner3: vm31
Owner3: vm32
Owner3: vm33
Owner3: vm34
I have assigned the content from the data file to variable using :
vms_tobe_tagged: "{{ lookup ('file', 'vmtags.json')| from_json}}"
I query and get the list of owners using this and it works well:
- set_fact:
Owner: "{{ vms_tobe_tagged| json_query('OwnerDetails[*].name') }}"
- name: Test loop
debug:
msg: "{{ Owner }}"
Is it possible to generate the data using this?
loop: "{{ ['alice', 'bob'] |product(['clientdb', 'employeedb', 'providerdb'])|list }}"
Q: "I need this json data converted to a list of key-value pairs ..."
Owner1: vm11
Owner1: vm12
Owner1: vm13
Owner1: vm14
Owner2: vm21
...
Is it possible to generate the data using 'product' filter?
A: Yes. With the product filter it's possible to create a list that comprises all products. The list of the key-value pairs can be created in the next loop. For example the tasks below
- set_fact:
owner_list: "{{ owner_list|
default([]) +
[item.name]|product(item.vms)|list }}"
loop: "{{ vms_tobe_tagged.VMDetails }}"
- set_fact:
owner: "{{ owner|
default([]) +
[{item.0: item.1}] }}"
loop: "{{ owner_list }}"
- debug:
var: owner
give
"owner": [
{
"Owner1": "vm10"
},
{
"Owner1": "vm11"
},
{
"Owner1": "vm12"
},
{
"Owner1": "vm13"
},
{
"Owner2": "vm20"
...
This helped me. I used json_query, with_subelements to iterate.
- set_fact:
VMOwnerdetails: "{{ vms_tobe_tagged| json_query('VMDetails[*]') }}"
- name: Verbose updates
with_subelements:
- "{{VMOwnerdetails}}"
- vms
debug:
msg: "{{ item.0.name }} : {{ item.1 }}"
Related
Please help me figure out what I'm wrong with. I'm getting a JSON from Ansible and filtering it, after which I want to save the output and reuse it. But, unfortunately, I get an error that this attribute does not exist. Where did I go wrong?
playbook code:
var:
query_general: "body.results[].{display_name: display_name, subnets: subnets[]}"
- name: parsing query
set_fact:
myvar: "{{ results | json_query(query_general) }}"
register: output
- name: qwe
set_fact:
scndjson: "{{ output.myvar[].display_name }}"
- name: print
debug:
msg: "{{ scndjson }}"
I tried the json_query second case as well, but that didn't work either.
in register:output i have:
[
{
"display_name": "1test",
"subnets": [
{
"gateway_address": "0.0.0.0/25",
"network": "0.0.0.0/25"
}
]
},
{
"display_name": "test",
"subnets": [
{
"gateway_address": "0.0.0.1/25",
"network": "0.0.0.1/25"
}
]
}
]
error:
The task includes an option with an undefined variable.
it can be: output, display_name, etc
UPD:
I corrected the yaml, there are no errors, but the data is not displayed.
tasks:
- name:
nsxt_rest:
hostname: anyhost
username: anyuser
password: anypass
validate_certs: false
method: get
path: /policy/api/v1/infra/segments
register: nsx_results
- debug:
var: nsx_query_general
vars:
nsx_query_general: "{{ nsx_results | json_query('body.results[].{display_name: display_name, subnets: subnets[]}') }}"
register: output
- debug:
var: secondjson
vars:
secondjson: "{{ output|json_query('[].display_name') }}"
Output from nsx_query_general:
{
"nsx_query_general": [
{
"display_name": "test",
"subnets": [
{
"gateway_address": "0.0.0.0/25",
"network": "0.0.0.0/25"
}
]
},
{
"display_name": "1test",
"subnets": [
{
"gateway_address": "0.0.0.1/25",
"network": "0.0.0.1/25"
}
]
}]}
Output from secondjson:
{
"secondjson": "",
"_ansible_verbose_always": true,
"_ansible_no_log": false,
"changed": false
}
Given the registered variable output
output:
- display_name: 1test
subnets:
- gateway_address: 0.0.0.0/25
network: 0.0.0.0/25
- display_name: test
subnets:
- gateway_address: 0.0.0.1/25
network: 0.0.0.1/25
Either use json_query
scndjson: "{{ output|json_query('[].display_name') }}"
, or map attribute
scndjson: "{{ output|map(attribute='display_name')|list }}"
Both declarations create the list
scndjson: [1test, test]
Example of a complete playbook
- hosts: localhost
vars:
output:
- display_name: 1test
subnets:
- gateway_address: 0.0.0.0/25
network: 0.0.0.0/25
- display_name: test
subnets:
- gateway_address: 0.0.0.1/25
network: 0.0.0.1/25
tasks:
- debug:
var: scndjson
vars:
scndjson: "{{ output|json_query('[].display_name') }}"
- debug:
var: scndjson
vars:
scndjson: "{{ output|map(attribute='display_name')|list }}"
I have in ansible the following code:
---
- name: "Retrieve ID for group {{onboarding.repo.subGroup}}"
uri:
method: GET
return_content: yes
validate_certs: "{{gitlab_validate_certs}}"
url: "{{ gitlab_api_url }}/api/v4/groups?search={{onboarding.repo.subGroup}}"
headers:
Private-Token: "{{ gitlab_api_token }}"
register: uri_response
- debug:
msg: "{{ uri_response['content'] }}"
- set_fact:
groupID: "{{ uri_response['content'] | from_json | json_query([*].[?name=='{{onboarding.repo.subGroup}}'].id) }}"
The get method retrieve a json list like the following one:
[
{
"id":1,
"name":"name1"
},
{
"id":2,
"name":"name2"
},
...
]
With the set_fact module I am trying to get the id on an item that correspond to a specific name. I have tried many json_query syntax (for instance .[?name=='{{onboarding.repo.subGroup}}'].id or [?name=='{{onboarding.repo.subGroup}}'].id), without success. I saw many different examples online, but they were different because the list was the value of a key, like the following:
key:
[
{...},
{...},
...
]
And I saw different examples working with a query syntax like key.[?name=='{{onboarding.repo.subGroup}}'].id
I cannot find a way to adapt that query to my case. Can anyone help me on finding the right syntax?
Regards,
Giorgio
Q: "Get the id on an item that corresponds to a specific name."
A: Convert the list to a dictionary
content: "{{ uri_response.content|
items2dict(key_name='name', value_name='id') }}"
gives
content:
name1: 1
name2: 2
Use the dictionary
- debug:
msg: "{{ content[item]|default('undef') }}"
loop:
- name1
- name2
- name3
gives (abridged)
msg: '1'
msg: '2'
msg: undef
Example of a complete playbook
- hosts: localhost
vars:
uri_response:
content:
[
{
"id":1,
"name":"name1"
},
{
"id":2,
"name":"name2"
}
]
content: "{{ uri_response.content|items2dict(key_name='name', value_name='id') }}"
tasks:
- debug:
msg: "{{ content[item]|default('undef') }}"
loop:
- name1
- name2
- name3
I am using an Ansible module (vmware_guest_info) with this code:
- name: Get status of VM post-change
vmware_guest_info:
datacenter: "xxxx"
hostname: "{{ result_item.vcenter }}"
username: "{{ xxxx }}"
password: "{{ ansible_password }}"
name: "{{ result_item.vmname }}"
schema: "vsphere"
validate_certs: no
properties: ["config.hardware.memoryMB", "config.hardware.numCPU", "config.hardware.numCoresPerSocket"]
register: postvmname_vminfo
loop: "{{ result.list }}"
loop_control:
loop_var: result_item
It produces the following JSON:
{
"results": [
{
"ansible_loop_var": "result_item",
"changed": false,
"failed": false,
"instance": {
"config": {
"hardware": {
"memoryMB": 4096,
"numCPU": 6,
"numCoresPerSocket": 1
}
}
},
The command that produces the json is looping though an excel file with these contents:
vmname vcenter
Server1 Vcenter1
Server2 Vcenter1
I need to create a dictionary between vmname and numCPU. How can I do this?
I have tried this:
- set_fact:
resultCPU: "{{ instance.config.hardware.numCPU }}"
with_items:
- "{{ postvmname_vminfo.results | json_query(query) }}"
vars:
query: "[?invocation.module_args.name=='{{ result_item.vmname }}']"
loop: "{{ result.list }}"
loop_control:
loop_var: result_item
I get item is undefined from the above. I believe the issue is with my query. I think what I really need to do is use zip to create a dictionary between the servers(vmname) in my Excel file and the value of numCPU.
Does anyone have any suggestions?
I have a json file with server information and the applications related.
Something like this:
{
"Apps": [
{
"AppOwner": "Me",
"AppNm": "Vacations"
}
],
"Hostnm": "some_server",
"Environment": "Prod",
"OSnm": "Windows Server 2008",
"OSManu": "Microsoft",
"SerialNum": "VMware-42 14 e1 37 7a 63 9b 0e-43 07 15 46 64 9c 3c 12"
}
I want to create a list with all the applications and the servers related
For example
{
"Vacations": [server1, server2]
},
{
"other_app": [server2, server3]
},
For that, I have been trying the following.
- set_fact:
apps: "{{ apps }} + {{ item.Apps | map(attribute='AppNm') | list}}"
loop: "{{ server_changes }}"
- set_fact:
apps_revised: "{{apps | unique}}"
- set_fact:
apps_server: "{{ apps_revised | default([]) + [{item : []}] }}"
with_items: "{{ apps_revised }}"
The first part of the code, will obtain all the applications with the duplicates from the json.
The second part of the code, will clean the apps with unique.
The last part its going to create a list of application with a nested list (of servers).
The problem is that I don't understand how to navigate the nested server_changes application list and then add the server to my new list.
Probably there is an easier way to do it, so any help will be appreciated.
Thanks
Given a test data
server_changes:
- "Apps":
- {"AppOwner": "Me", "AppNm": "Vacations"}
"Hostnm": "server1"
- "Apps":
- {"AppOwner": "Me", "AppNm": "Vacations"}
- {"AppOwner": "Me", "AppNm": "other_app"}
"Hostnm": "server2"
- "Apps":
- {"AppOwner": "Me", "AppNm": "other_app"}
"Hostnm": "server3"
Let's simplify the data. For example
- set_fact:
my_server_changes: "{{ my_server_changes|default([]) +
[{'hostname': item.Hostnm,
'applications': item.Apps|
json_query('[].AppNm')}] }}"
loop: "{{ server_changes }}"
- debug:
var: my_server_changes
gives
"my_server_changes": [
{
"applications": [
"Vacations"
],
"hostname": "server1"
},
{
"applications": [
"Vacations",
"other_app"
],
"hostname": "server2"
},
{
"applications": [
"other_app"
],
"hostname": "server3"
}
]
Create the list of the applications
- set_fact:
my_apps: "{{ server_changes|json_query('[].Apps[].AppNm')|unique }}"
- debug:
var: my_apps
gives
"my_apps": [
"Vacations",
"other_app"
]
Then loop the list of the applications, select records that contains the item and combine the dictionary
- set_fact:
apps_server: "{{ apps_server|default({})|
combine({item: my_server_changes|json_query(query)}) }}"
loop: "{{ my_apps }}"
vars:
query: "[?applications.contains(#, '{{ item }}')].hostname"
- debug:
var: apps_server
gives
"apps_server": {
"Vacations": [
"server1",
"server2"
],
"other_app": [
"server2",
"server3"
]
}
I have a variable (via set_fact) containing a json string:
{
"PolicyVersion": {
"CreateDate": "2017-08-07T02:48:05Z",
"Document": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Resource": [
"arn:aws:iam::123456789123:role/Root_Update_svcacct",
"arn:aws:iam::123456789123:role/Root_Delete_svcacct",
"arn:aws:iam::123456789123:role/Root_Create_svcacct",
"arn:aws:iam::123456789123:role/Root_Full_svcacct",
"arn:aws:iam::987654321987:role/Member1_Create_svcacct",
"arn:aws:iam::987654321987:role/Member1_Update_svcacct",
"arn:aws:iam::987654321987:role/Member1_Delete_svcacct",
"arn:aws:iam::987654321987:role/Member1_Full_svcacct"
]
}
],
"Version": "2012-10-17"
},
"IsDefaultVersion": true,
"VersionId": "v2"
}
}
What is the best way to insert more elements in the "Resource" array?
"arn:aws:iam::001122334455:role/Member1_Create_svcacct",
"arn:aws:iam::001122334455:role/Member1_Update_svcacct",
"arn:aws:iam::001122334455:role/Member1_Delete_svcacct",
"arn:aws:iam::001122334455:role/Member1_Full_svcacct"
I am exploring dumping the variable to a file and inserting the block I want with external shell tools, which does not seem to be elegant.
I don't know about the best way, but one option is to write a simple library module to handle the mechanics of the update for you. You could use the jsonpointer module as a way of locating the data you wish to modify, and then return the modified object to ansible. A starting point might look like:
#!/usr/bin/python
from ansible.module_utils.basic import AnsibleModule
import json
try:
import jsonpointer
except ImportError:
jsonpointer = None
def main():
module = AnsibleModule(
argument_spec=dict(
data=dict(required=True, type='dict'),
pointer=dict(required=True),
action=dict(required=True,
choices=['append', 'extend', 'update']),
update=dict(type='dict'),
extend=dict(type='list'),
append=dict(),
),
supports_check_mode=True,
)
if jsonpointer is None:
module.fail_json(msg='jsonpointer module is not available')
action = module.params['action']
data = module.params['data']
pointer = module.params['pointer']
if isinstance(data, str):
data = json.loads(str)
try:
res = jsonpointer.resolve_pointer(data, pointer)
except jsonpointer.JsonPointerException as err:
module.fail_json(msg=str(err))
if action == 'append':
res.append(module.params['append'])
if action == 'extend':
res.extend(module.params['extend'])
elif action == 'update':
res.update(module.params['update'])
module.exit_json(changed=True,
result=data)
if __name__ == '__main__':
main()
If you drop this into, e.g., library/json_modify.py, you can use it in a playbook like this:
- hosts: localhost
gather_facts: false
vars:
myvar: {
"PolicyVersion": {
"CreateDate": "2017-08-07T02:48:05Z",
"Document": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Resource": [
"arn:aws:iam::123456789123:role/Root_Update_svcacct",
"arn:aws:iam::123456789123:role/Root_Delete_svcacct",
"arn:aws:iam::123456789123:role/Root_Create_svcacct",
"arn:aws:iam::123456789123:role/Root_Full_svcacct",
"arn:aws:iam::987654321987:role/Member1_Create_svcacct",
"arn:aws:iam::987654321987:role/Member1_Update_svcacct",
"arn:aws:iam::987654321987:role/Member1_Delete_svcacct",
"arn:aws:iam::987654321987:role/Member1_Full_svcacct"
]
}
],
"Version": "2012-10-17"
},
"IsDefaultVersion": true,
"VersionId": "v2"
}
}
tasks:
- json_modify:
data: "{{ myvar }}"
pointer: "/PolicyVersion/Document/Statement/0/Resource"
action: extend
extend:
- "arn:aws:iam::001122334455:role/Member1_Create_svcacct"
- "arn:aws:iam::001122334455:role/Member1_Update_svcacct"
- "arn:aws:iam::001122334455:role/Member1_Delete_svcacct"
- "arn:aws:iam::001122334455:role/Member1_Full_svcacct"
register: result
- debug:
var: result.result
The result of running this playbook and the proposed module is:
TASK [debug] *******************************************************************
ok: [localhost] => {
"result.result": {
"PolicyVersion": {
"CreateDate": "2017-08-07T02:48:05Z",
"Document": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Resource": [
"arn:aws:iam::123456789123:role/Root_Update_svcacct",
"arn:aws:iam::123456789123:role/Root_Delete_svcacct",
"arn:aws:iam::123456789123:role/Root_Create_svcacct",
"arn:aws:iam::123456789123:role/Root_Full_svcacct",
"arn:aws:iam::987654321987:role/Member1_Create_svcacct",
"arn:aws:iam::987654321987:role/Member1_Update_svcacct",
"arn:aws:iam::987654321987:role/Member1_Delete_svcacct",
"arn:aws:iam::987654321987:role/Member1_Full_svcacct",
"arn:aws:iam::001122334455:role/Member1_Create_svcacct",
"arn:aws:iam::001122334455:role/Member1_Update_svcacct",
"arn:aws:iam::001122334455:role/Member1_Delete_svcacct",
"arn:aws:iam::001122334455:role/Member1_Full_svcacct"
]
}
],
"Version": "2012-10-17"
},
"IsDefaultVersion": true,
"VersionId": "v2"
}
}
}
Actually Ansible is natively able to read JSON files.
see this question:
add-a-new-key-value-to-a-json-file-using-ansible
This is a little bit old. I know but I felt it could be useful to many people so I'll post here my solution as "the best way to modify a json" that I could personally find.
First of all the JSON. In my use-case I had a bunch of aws snapshots that were encrypted with the wrong kms key and I had to recreate the AMI with the correct key.
So I had to :
get data of the old snapshots ( like size device_name etc.)
create the new snaps with the different key
re-register a new ami with the correct block_device_mapping
Here's the code
- name: get ami
amazon.aws.ec2_ami_info:
image_ids: ami-<id>
region: "{{ region }}"
register: ami
- name: save snapshot ids and device_name and volume_size
set_fact:
snapshot_ids: "{{ ami | json_query('images[].block_device_mappings[].ebs.snapshot_id') }}"
device_name: "{{ ami | json_query('images[].block_device_mappings[].device_name') }}"
volume_size: "{{ ami | json_query('images[].block_device_mappings[].ebs.volume_size') }}"
basically each of the above is a list of each of the 3 things (device_name, snap_id, volume_size) that I need (but it could be extended)
then:
- name: get kms arn
aws_kms_info:
filters:
alias: "{{ kms_keys.alias }}"
region: "{{ region }}"
register: aws_kms_facts_out
- debug:
var: aws_kms_facts_out
- set_fact:
kms_arn: "{{ aws_kms_facts_out['keys'][0].key_arn }}"
- name: copy snap with new encryption key
community.aws.ec2_snapshot_copy:
source_region: "{{ region }}"
region: "{{ region }}"
source_snapshot_id: "{{ item }}"
encrypted: yes
kms_key_id: "{{ kms_arn }}"
loop: "{{ snapshot_ids }}"
register: new_snapshots
and then here's the catch
- set_fact:
new_snapshot_ids: "{{ new_snapshots| json_query('snapshots[].snapshot_id') }}"
- name: creating the block_device_mappings structure (still a list of objects)
ansible.builtin.debug:
msg: '{
"device_name": "{{ item.2 }}",
"delete_on_termination": "true",
"snapshot_id": "{{ item.0 }}",
"volume_size": {{ item.1 }},
"volume_type": "gp2"
}'
loop: "{{ new_snapshot_ids|zip_longest(volume_size,device_name)|list }}"
register: block_device_mappings
- set_fact:
bdm: "{{ block_device_mappings | json_query('results[].msg') }}"
finally
- name: create new ami from newly created snapshots
amazon.aws.ec2_ami:
region: "{{ region }}"
name: "{{ instance.name }}-{{ ansible_date_time.date }}"
state: present
architecture: x86_64
virtualization_type: hvm
root_device_name: /dev/sda1
device_mapping: "{{ bdm }}"
This is how you can do it without requiring any additional trick.
Of course this is declined to my particular use case but you can adapt it to any circumstance, that do not require a complete disassemble and reassemble of the Json itself