Ansible 2.0 parsing Json and store in another variable - json

My Task is
- name: task name
shell: some command --format json
register: result
The output will be in below json format
[
{
"mac_address": "x.x.x.x.x.x",
"fixed_ips": "{\"subnet_id\": \"s-s-s-s\",\"ip_address\": \"172.*.*.*\"}",
"id": "1",
"name": ""
},
{
"mac_address": "x.x.x.x",
"fixed_ips": "{\"subnet_id\": \"s-s-s-s\", \"ip_address\": \"192.*.*.*\"}",
"id": "2",
"name": ""
}
]
What i want to do is,get only the ip address that starts with 192 and store it into a variable so that i can use it in another task.

If it is a list of dicts, use selectattr:
"{{ (result.stdout | from_json | selectattr('ip_address','match','^192') | list | fist).ip_address }}"
I assume that you need only one element, so I use first to get single element from the list.
Updated expression due to modifications of input data:
- set_fact:
ip_address: "{{ ( result.stdout |
from_json |
map(attribute='fixed_ips') |
map('from_json') |
selectattr('ip_address','match','^192') |
list |
first
).ip_address }}"
fixed_ips is a string, so you need to apply from_json with map to convert every item to be able to apply selectattr filter.

Related

Parsing JSON output from Juniper switch using Ansible

I have this JSON output from Juniper switch where I need to get the remote system name associated with lldp_local_parent_interface_name having the value ae0.
So, I only know the value ae0 and I need to get the remote system name A_B_C_D in order to start targeting this host to execute some other tasks.
The data, inside the file juniper-data.json looks like this:
{
"output": {
"lldp_neighbors_information": [{
"lldp_neighbor_information": [{
"lldp_local_parent_interface_name": [{
"data": "ae0"
}],
"lldp_local_port_id": [{
"data": "xe_0/2/0"
}],
"lldp_remote_chassis_id": [{
"data": "00:00:00:00:00:00:00"
}],
"lldp_remote_chassis_id_subtype": [{
"data": "Mac address"
}],
"lldp_remote_port_description": [{
"data": "xe_1/0/1"
}],
"lldp_remote_system_name": [{
"data": "A_B_C_D"
}]
},
{
"lldp_local_parent_interface_name": [{
"data": "_"
}],
"lldp_local_port_id": [{
"data": "ge_0/0/23"
}],
"lldp_remote_chassis_id": [{
"data": "xx:xx:xx:xx:xx:xx"
}],
"lldp_remote_chassis_id_subtype": [{
"data": "Mac address"
}],
"lldp_remote_port_description": [{
"data": "bond0"
}]
}
]
}]
}
}
Here are my tasks:
- name: load data to var
set_fact:
remote_sys_name: "{{ lookup('file', 'juniper-data.json') | from_json }}"
- name: view loaded data
debug:
var: item
loop: "{{ remote_sys_name | community.general.json_query('output.lldp_neighbors_information[0].lldp_neighbor_information[*].[?lldp_local_parent_interface_name[0].data=='ae0'].lldp_remote_system_name[0].data') }}"
Expected results
"item": "A_B_C_D"
What I got is
fatal: [localhost]: FAILED! => {"msg": "template error while templating string: expected token ',', got 'ae0'. String: {{ remote_sys_name | community.general.json_query('output.lldp_neighbors_information[0].lldp_neighbor_information[*].[?lldp_local_parent_interface_name[0].data=='ae0'].lldp_remote_system_name[0].data') }}"}
If you do not care about the exact hierarchy of nodes and their names beside lldp_neighbor_information, you can use a cohort of object projections and flatten projection to simplify it:
*.*[][].*[][?
lldp_local_parent_interface_name[0].data == 'ae0'
][].lldp_remote_system_name[].data
As for your current attempt, the issue lies in the fact that your condition is wrongly located in this bit:
lldp_neighbor_information[*].[?lldp_local_parent_interface_name[0].data=='ae0']
The condition should actual replace the star:
lldp_neighbor_information[?lldp_local_parent_interface_name[0].data=='ae0']
Ending up with the query:
output
.lldp_neighbors_information[0]
.lldp_neighbor_information[?
lldp_local_parent_interface_name[0].data=='ae0'
]
.lldp_remote_system_name[0]
.data
Given the task:
- debug:
var: item
loop: >-
{{
lookup('file', 'juniper-data.json')
| from_json
| json_query('
output
.lldp_neighbors_information[0]
.lldp_neighbor_information[?
lldp_local_parent_interface_name[0].data==`ae0`
]
.lldp_remote_system_name[0]
.data
')
}}
This yields:
ok: [localhost] => (item=A_B_C_D) =>
ansible_loop_var: item
item: A_B_C_D
Bonus: If know you before hand that you will only have one element, you can also end your query with | [0] to stop the projection and only get the first element.
This way, you can get rid of the loop:
- debug:
var: remote_sys_name
vars:
remote_sys_name: >-
{{
lookup('file', 'juniper-data.json')
| from_json
| json_query('
output
.lldp_neighbors_information[0]
.lldp_neighbor_information[?
lldp_local_parent_interface_name[0].data==`ae0`
]
.lldp_remote_system_name[0]
.data | [0]
')
}}
Does yields the name right away:
ok: [localhost] =>
remote_sys_name: A_B_C_D
Bonus bis: if your interface name is in a variable, here would be the task. Mind that it is in a variable local to the task here, but you might define it in any place your requirements call for.
- debug:
var: remote_sys_name
vars:
_interface_name: ae0
remote_sys_name: >-
{{
lookup('file', 'juniper-data.json')
| from_json
| json_query('
output
.lldp_neighbors_information[0]
.lldp_neighbor_information[?
lldp_local_parent_interface_name[0]
.data==`' ~ _interface_name ~ '`
]
.lldp_remote_system_name[0]
.data | [0]
')
}}

ansible json-query trying to select "id" by content file wildcard

Very new to JSON.
I'm trying to extract 2 variables from this json file.
It has many files and id's but I only want the file & id if it contains es7.x86_64
When done my desired variables would be:
id=13140
file=NessusAgent-8.3.0-es7.x86_64.rpm
{
"banners": [],
"containsRequiredAuth": true,
"created_at": "2017-10-13T00:53:32.137Z",
"description": "Download Nessus Agents for use with Tenable.io and Nessus Manager",
"documentation_link": null,
"downloads": [
{
"created_at": "2021-06-29T19:06:41.776Z",
"description": "Red Hat ES 7 (64-bit) / CentOS 7 / Oracle Linux 7 (including Unbreakable Enterprise Kernel)",
"file": "NessusAgent-8.3.0-es7.x86_64.rpm",
"id": 13140,
"meta_data": {
"md5": "f67a2bdd2a7180f66b75f319439d56d5",
"product": "Nessus Agents - 8.3.0",
"product_notes": null,
"product_release_date": "06/29/2021",
"product_type": "default",
"release_date": "06/03/2021",
"sha256": "8a6452086ce0a7193e0f24b1f2adbff3aa6bd0f4ac519384e8453bb68bae0460",
"version": "8.3.0"
},
"name": "NessusAgent-8.3.0-es7.x86_64.rpm",
"page_id": 61,
"publish": true,
"required_auth": false,
"size": 16375828,
"sort_order": null,
"type": "download",
"updated_at": "2021-06-29T19:08:47.628Z"
},
My utterly failed attempt to assign file & id variables that have es7.x86_64.
- name: Convert agent_tempfile to json and register result
shell: python -m json.tool "{{ agent_tempfile }}"
register: result
- name: Extract file & id for es7.x86_64 rpm's
set_fact:
agent_id: "{{ result | json_query('downloads[*es7.x86_64*].id') | first }}"
agent_file: "{{ result | json_query('downloads[*es7.x86_64*].file') | first }}"
I have a feeling I'm going to be doing a lot more of these types of queries soon. Can some one also direct me to a good guide that details parsing specific values from JSON output? The stuff I've found so far just lists arrays but I really want to know how to pull specific data out.
First, there are some great tools out there for playing with JMESPath syntax (the syntax used by the json_query filter). The examples in the JMESPath tutorial are all "live": you can paste your own data into the text fields, and then experiment with filters and check the result.
The jpterm command is a terminal tool for experimenting with JMESPath queries. This is my personal favorite.
To look for items that contain a specific substring (like es7.x86_64), you can use the contains operator, like this:
json_query("downloads[?contains(name, 'es7.x86_64')]")
To make this work for your code, we first need to deal with the fact
that the result of your first task is going to be a string, rather
than a dictionary. We'll need to pass the standard output through the
from_json filter.
We can also avoid having two almost identical json_query expression
by moving the bulk of the expression into a task-local variable.
This gives us something like:
- hosts: localhost
gather_facts: false
tasks:
- command: cat data.json
register: result
- set_fact:
agent_id: "{{ selected[0].id }}"
agent_file: "{{ selected[0].file }}"
vars:
selected: >-
{{
result.stdout |
from_json |
json_query("downloads[?contains(name, 'es7.x86_64')]")
}}
- debug:
msg:
- "ID: {{ agent_id }}"
- "FILE: {{ agent_file }}"
When that task runs, the value of selected will be something like:
[
{
"file": "NessusAgent-8.3.0-es7.x86_64.rpm",
"id": 13140,
"name": "NessusAgent-8.3.0-es7.x86_64.rpm",
"page_id": 61,
"publish": true,
"required_auth": false,
"size": 16375828,
"sort_order": null,
"type": "download",
"updated_at": "2021-06-29T19:08:47.628Z"
}
]
This assumes you're only expecting a single result, so we can just ask
for selected[0] to get at that dictionary, and then it's a simple
matter of getting at the .id and .file attributes.
Running the above playbook produces:
TASK [debug] *********************************************************************************
ok: [localhost] => {
"msg": [
"ID: 13140",
"FILE: NessusAgent-8.3.0-es7.x86_64.rpm"
]
}

ansible dictionary key value pairs

I am new to Ansible/json and I am trying to parse the following json:
{
"resultCPU": {
"changed": false,
"msg": "All items completed",
"results": [
{
"ansible_facts": {
"CPU": "6",
"VM": "tigger"
},
{
"ansible_facts": {
"CPU": "4",
"VM": "pooh"
},
I need to set the value of the items in this json so that pooh=4 and tigger=6. I will need to refer to both these values later (advice on how to do that would help as well).
I have tried using cpuvm "{{ resultCPU.results |selectattr('VM') |map(attribute='CPU')|list }}" but it complains "'dict object' has no attribute VM".
What am I doing wrong?
Your selectattr ignored the ansible_facts between results: [] and the VM you tried to map. Thankfully, the attribute kwarg understands dot notation
- debug:
msg: >-
VMs are {{ resultCPU.results | map(attribute="ansible_facts.VM") | list }}
CPUs are {{ resultCPU.results | map(attribute="ansible_facts.CPU") | list }}
The together filter may interest you, too
and as for "I will need to refer to both these values later (advice on how to do that would help as well)." that is usually what set_fact: is used for, but without more specifics about what you have tried and what shape you are expecting, it's hard to give a more specific answer

Ansible extract var from JSON

I am using the URI module and I get a JSON response back. This is my playbook so far.
.. some playbook ..
register: output
- debug: msg="{{ output }}"
- name: get job id
set_fact:
job_id: "{{ output.json.results }}"
- debug: msg="{{ job_id }}"
Here’s the output of job_id from the debug above:
ok: [localhost] => {
"msg": [
{
"approval_state": "pending_approval",
"created_on": "2018-12-18T22:48:40Z",
"description": "Provision from [tpl] to [test]",
"href": "https://foo.ca/api/provision_requests/1000000000143",
"id": "1000000000143",
"message": "VM Provisioning - Request Created",
"options": {
"addr_mode": [
"dhcp",
"DHCP"
],
"auto_approve": false,
"cluster_filter": [
null,
null
],
"cores_per_socket": [
2,
"2"
],
I want to extract the “id” in the 5th line of the JSON output above. Any ideas?
I tried output.json.results.id but that errors out with no object id found
If you look at the JSON that has been produced, you actually have a list of dictionaries, albeit in this particular case, you have a list containing a single dictionary. So given the exact example:
- debug:
msg: "{{ job_id[0].id }}"
will extract the id field.
If you want to extract the id fields from every dictionary in the list (assuming multiple dictionaries are possible), you could instead do:
- debug:
msg: "{{ job_id | map(attribute='id') | list }}"
which will produce a simple list containing the contents of the id field in each dictionary.

Ansible unicode object has no attribute value json parsing

My Task is
- name: task name
shell: openstack floating ip create provider --format json
register: result
The output will be in below json format
{
"router_id": null,
"status": "DOWN",
"description": "",
"created_at": "2017-05-24T10:49:15Z",
"updated_at": "2017-05-24T10:49:15Z",
"floating_network_id": "923-cc77237b08e7",
"headers": "",
"fixed_ip_address": null,
"floating_ip_address": "192.*.*.*",
"revision_number": 1,
"project_id": "2709ad381fcf41c5bce673c916fded10",
"port_id": null,
"id": "c5d187elg-d269-4eae-b6ae-7d258f04983"
}
What i want to do is,get only the floating_ip_address and store it into a variable so that i can use it in another task.
Im using the below code for doing this,
- set_fact:
address: "{{ (result.stdout | from_json | selectattr('floating_ip_address') | list | first).floating_ip_address }}"
But Im getting an error
"ERROR! 'unicode object' has no attribute 'floating_ip_address'"
What is correct format to get only the ip address?
If floating_ip_address is not a list, but a simple key as in your input, for example:
- set_fact:
address: "{{ (result.stdout | from_json)['floating_ip_address'] }}"