I have an issue about removing the array brackets in the output. I need the output without the array brackets so that i can pass the result in the rest api code. I have an example listed here below. I would like to group each set with the name and nicknames associated with that name. I was able to group the nicknames for each host but the result has brackets which I am trying to remove.
name: Set Global Vars
hosts: localhost
gather_facts: false
vars:
msg: [
{
"name": "abcdsto1",
"portnames": [
{
"nickname": "abcdsto1p1",
"port": "4353000e1ec2ec9a",
"type": "fc"
},
{
"nickname": "abcdsto1p2",
"port": "4353000e1ec2ec9b",
"type": "fc"
},
{
"nickname": "abcdsto1p3",
"port": "4353000e1ec2ec3a",
"type": "fc"
},
{
"nickname": "abcdsto1p4",
"port": "4353000e1ec2ec3b",
"type": "fc"
}
]
},
{
"name": "abcdsto2",
"portnames": [
{
"nickname": "abcdsto2p1",
"port": "4353000e1ec2ec06",
"type": "fc"
},
{
"nickname": "abcdsto2p2",
"port": "4353000e1ec2ec07",
"type": "fc"
},
{
"nickname": "abcdsto2p3",
"port": "4353000e1ec2ec64",
"type": "fc"
},
{
"nickname": "abcdsto2p4",
"port": "4353000e1ec2ec65",
"type": "fc"
}
]
},
{
"name": "abcdsto3",
"portnames": [
{
"nickname": "abcdsto3p1",
"port": "4353000e1ec43f20",
"type": "fc"
},
{
"nickname": "abcdsto3p2",
"port": "4353000e1ec43f21",
"type": "fc"
},
{
"nickname": "abcdsto3p3",
"port": "4353000e1ec43f30",
"type": "fc"
},
{
"nickname": "abcdsto3p4",
"port": "4353000e1ec43f31",
"type": "fc"
}
]
}
]
tasks:
- name: print json file
debug:
msg: "{{ item.portnames | community.general.json_query('[*].nickname') }}"
loop: "{{ msg }}"
TASK [print json file] ***************************************************************************************************************************************************************************
ok: [localhost] => (item={'name': 'abcdsto1', 'portnames': [{'nickname': 'abcdsto1p1', 'port': '4353000e1ec2ec9a', 'type': 'fc'}, {'nickname': 'abcdsto1p2', 'port': '4353000e1ec2ec9b', 'type': 'fc'}, {'nickname': 'abcdsto1p3', 'port': '4353000e1ec2ec3a', 'type': 'fc'}, {'nickname': 'abcdsto1p4', 'port': '4353000e1ec2ec3b', 'type': 'fc'}]}) => {
"msg": [
"abcdsto1p1",
"abcdsto1p2",
"abcdsto1p3",
"abcdsto1p4"
]
}
ok: [localhost] => (item={'name': 'abcdsto2', 'portnames': [{'nickname': 'abcdsto2p1', 'port': '4353000e1ec2ec06', 'type': 'fc'}, {'nickname': 'abcdsto2p2', 'port': '4353000e1ec2ec07', 'type': 'fc'}, {'nickname': 'abcdsto2p3', 'port': '4353000e1ec2ec64', 'type': 'fc'}, {'nickname': 'abcdsto2p4', 'port': '4353000e1ec2ec65', 'type': 'fc'}]}) => {
"msg": [
"abcdsto2p1",
"abcdsto2p2",
"abcdsto2p3",
"abcdsto2p4"
]
}
ok: [localhost] => (item={'name': 'abcdsto3', 'portnames': [{'nickname': 'abcdsto3p1', 'port': '4353000e1ec43f20', 'type': 'fc'}, {'nickname': 'abcdsto3p2', 'port': '4353000e1ec43f21', 'type': 'fc'}, {'nickname': 'abcdsto3p3', 'port': '4353000e1ec43f30', 'type': 'fc'}, {'nickname': 'abcdsto3p4', 'port': '4353000e1ec43f31', 'type': 'fc'}]}) => {
"msg": [
"abcdsto3p1",
"abcdsto3p2",
"abcdsto3p3",
"abcdsto3p4"
]
}
But I need the result to be
msg: abcdsto1p1,abcdsto1p2,abcdsto1p3,abcdsto1p4
msg: abcdsto2p1,abcdsto2p2,abcdsto2p3,abcdsto2p4
msg: abcdsto3p1,abcdsto3p2,abcdsto3p3,abcdsto3p4
Thanks for the help
I have faced this same issue. The join filter will work here to extract it from being pack into an array.
Before the join this is what I was using
{%- set _ = nodes.append(hostvars[server]['node_id']
this is what was being returned, even though it was coming form a valid list item.
- - 63dbc56c-8abb-a9e1-b604-71b4a7cf698e
If you then try to strip the extra hyphen using replace() filter, it will pack it into an array
| replace('-', '')
- '[''63dbc56c-8abb-a9e1-b604-71b4a7cf698e'']'
When filtering with join() it, it essentially gets casted to a string. If you have only one item in the array, then its just one string in your list.
{%- set _ = nodes.append(hostvars[server]['node_id'] | join(',') ) -%}\
The list data comes back cleaner, as expected
- 63dbc56c-8abb-a9e1-b604-71b4a7cf698e
just the same, the following will work on multiple elements inside array brackets
msg: "{{ item.portnames | community.general.json_query('[*].nickname') | join(',') }}"
will get you
msg: abcdsto1p1,abcdsto1p2,abcdsto1p3,abcdsto1p4
...
Thats a couple use cases where we want the value returned as a string only. We can filter with join()
Also thanks to #β.εηοιτ.βε for the tip.
Related
How to read/display the following response (e.g: name, srcinft, dstinf) from FortiGate Firewall using Ansible. Or is there any way I can read this JSON output from file and display the fields i want.
{
"changed": false,
"meta": {
"http_method": "GET",
"size": 2,
"matched_count": 2,
"next_idx": 1,
"revision": "ac9c4e1d722b74695dee4fb3ce4fcd12",
"results": [
{
"policyid": 1,
"q_origin_key": 1,
"status": "enable",
"name": "test-policy01",
"uuid": "c4de3298-97ce-51ed-ccba-cafc556ba9e0",
"uuid-idx": 14729,
"srcintf": [
{
"name": "port2",
"q_origin_key": "port2"
}
],
"dstintf": [
{
"name": "port1",
"q_origin_key": "port1"
}
],
"action": "accept",
"ztna-status": "disable",
"srcaddr": [
{
"name": "all",
"q_origin_key": "all"
}
],
"dstaddr": [
{
"name": "all",
"q_origin_key": "all"
}
],
"policy-expiry": "disable",
"policy-expiry-date": "0000-00-00 00:00:00",
"service": [
{
"name": "ALL",
"q_origin_key": "ALL"
}
],
"tos": "0x00",
"sgt-check": "disable",
"sgt": []
},
{
"policyid": 2,
"q_origin_key": 2,
"status": "enable",
"name": "test-policy-02",
"uuid": "534b6c9c-97d1-51ed-7aa8-7544c628c7ea",
"uuid-idx": 14730,
"srcintf": [
{
"name": "port1",
"q_origin_key": "port1"
}
],
"dstintf": [
{
"name": "port2",
"q_origin_key": "port2"
}
],
"action": "accept",
"nat64": "disable",
"nat46": "disable",
"ztna-status": "disable",
"srcaddr": [
{
"name": "all",
"q_origin_key": "all"
}
],
"dstaddr": [
{
"name": "login.microsoft.com",
"q_origin_key": "login.microsoft.com"
}
],
"srcaddr6": [],
"reputation-direction6": "destination",
"policy-expiry-date": "0000-00-00 00:00:00",
"service": [
{
"name": "ALL_ICMP6",
"q_origin_key": "ALL_ICMP6"
}
],
"tos": "0x00",
"webcache": "disable",
"webcache-https": "disable",
"sgt-check": "disable",
"sgt": []
}
],
"vdom": "root",
"path": "firewall",
"name": "policy",
"version": "v7.2.3",
"build": 1262
},
"invocation": {
"module_args": {
"vdom": "root",
"access_token": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
"selector": "firewall_policy",
"selectors": null
}
},
"_ansible_no_log": false
}
Expected result:
result:
test-policy-02:
dstintf:
- name: port2
q_origin_key: port2
srcintf:
- name: port1
q_origin_key: port1
test-policy01:
dstintf:
- name: port1
q_origin_key: port1
srcintf:
- name: port2
q_origin_key: port2
Unfortunately there is absolute no description or any further information.
However, regarding
How to read/display the following response (e.g: name, srcinft, dstinf) from FortiGate Firewall using Ansible.
you may have a look into the following simple and lazy approach with loop
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- name: Include vars of stuff.yaml into the 'stuff' variable (2.2).
ansible.builtin.include_vars:
file: stuff.json
name: stuff
- name: Show list of dict
debug:
msg: "{{ stuff.meta.results }}"
- name: Print key:value
debug:
msg:
- "name: {{ item.name }}"
- "{{ item.srcintf }}"
- "{{ item.dstintf }}"
loop_control:
label: "policyid: {{ item.policyid }}"
loop: "{{ stuff.meta.results }}"
resulting into an output of
TASK [Print key:value] *****************
ok: [localhost] => (item=policyid: 1) =>
msg:
- 'name: test-policy01'
- - name: port2
q_origin_key: port2
- - name: port1
q_origin_key: port1
ok: [localhost] => (item=policyid: 2) =>
msg:
- 'name: test-policy-02'
- - name: port1
q_origin_key: port1
- - name: port2
q_origin_key: port2
Further Documentation
include_vars module – Load variables from files, dynamically within a task
Loops
Or is there any way I can read this JSON output from file and display the fields I want?
To get familiar with data structure, respective JSON response you've provided in your example, you could start with something like a JSONPathFinder. It will result into an path of
x.meta.results[0].name
x.meta.results[0].srcintf
x.meta.results[0].dstintf
x.meta.results[1].name
x.meta.results[1].srcintf
x.meta.results[1].dstintf
for the provided keys.
It is also possible to use jq on CLI
jq keys stuff.json
[
"_ansible_no_log",
"changed",
"invocation",
"meta"
]
and just proceed further with
jq '.meta.results' stuff.json
jq '.meta.results[0]' stuff.json
jq '.meta.results[1]' stuff.json
Further Q&A
How to get key names from JSON using jq?
Updated with suggestions from larsks.
With the following structure
"intf_output_ios": {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"failed": false,
"gathered": [
{
"name": "GigabitEthernet0/0"
},
{
"mode": "trunk",
"name": "GigabitEthernet0/1",
"trunk": {
"allowed_vlans": [
"10",
"20",
"30",
"99",
"100"
],
"encapsulation": "dot1q"
}
},
{
"mode": "trunk",
"name": "GigabitEthernet0/2",
"trunk": {
"allowed_vlans": [
"10",
"20",
"30",
"99",
"100"
],
"encapsulation": "dot1q"
}
},
{
"access": {
"vlan": 30
},
"mode": "access",
"name": "GigabitEthernet0/3"
},
{
"name": "GigabitEthernet1/0"
},
{
"name": "GigabitEthernet1/1"
},
{
"name": "GigabitEthernet1/2"
},
{
"name": "GigabitEthernet1/3"
},
{
"name": "GigabitEthernet2/0"
},
{
"name": "GigabitEthernet2/1"
},
{
"name": "GigabitEthernet2/2"
},
{
"name": "GigabitEthernet2/3"
},
{
"name": "GigabitEthernet3/0"
},
{
"name": "GigabitEthernet3/1"
},
{
"name": "GigabitEthernet3/2"
},
{
"access": {
"vlan": 99
},
"mode": "access",
"name": "GigabitEthernet3/3"
}
]
}
To print only the ports in VLAN 30 use the following?
- name: "P901T6: Set fact to include only access ports - IOS"
set_fact:
access_ports_ios_2: "{{ intf_output_ios | json_query(query) }}"
vars:
query: >-
gathered[?access.vlan==`30`]
- name: "P901T7: Dump list of access ports - IOS"
debug:
var=access_ports_ios_2
NOTE: It is important to use 30 (with backticks) and not '30'
I have gone through https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html#managing-list-variables without really understanding how to fix this. If someone has some good link that would be very useful
With a structure like
ok: [access01] => {
"access_ports_ios": [
{
"access": {
"vlan": 30
},
"mode": "access",
"name": "GigabitEthernet0/3"
},
{
"access": {
"vlan": 99
},
"mode": "access",
"name": "GigabitEthernet3/3"
}
]
}
To get ports in vlan 30 use:
- debug:
var: access_ports_ios|json_query(query)
vars:
query: >-
[?access.vlan==`30`]
Note:
If you want to use a variable for vlan instead of hard-coding it. I had to do as follows:
- name: Debug 4
debug:
var: access_ports_ios|json_query('[?access.vlan==`{{ src_vlan | int}}`]')
You're asking for gathered.access, but gathered is a list and does not have an access attribute. You want "all items from gathered for which access.vlan is 30 (and note that the value of access.vlan is an integer, not a string):
- debug:
var: intf_output_ios|json_query(query)
vars:
query: >-
gathered[?access.vlan==`30`]
Which given you example input produces:
TASK [debug] *******************************************************************
ok: [localhost] => {
"intf_output_ios|json_query(query)": [
{
"access": {
"vlan": 30
},
"mode": "access",
"name": "GigabitEthernet0/3"
}
]
}
I'm going to reiterate advice I often give for json_query questions: use something like jpterm or the JMESPath website to test JMESPath expressions against your actual data. This makes it much easier to figure out where an expression might be going wrong.
I am using following JSON file:
sample.json:
{
"lldp_output['gathered']": [
{
"mode": "trunk",
"name": "GigabitEthernet0/0",
"trunk": {
"encapsulation": "dot1q"
}
},
{
"access": {
"vlan": 12
},
"mode": "access",
"name": "GigabitEthernet0/1"
},
{
"name": "GigabitEthernet0/2"
}
]
}
And the playbook:
---
- hosts: localhost
gather_facts: no
vars:
tmpdata: "{{ lookup('file','sample.json') | from_json }}"
tasks:
- name: Take 4
debug:
msg: "{{ tmpdata | community.general.json_query(lldp_output['gathered']) }}"
I get the following error:
TASK [Take 4] ********************************************************************************************
task path: /root/scripts/atest.yml:18
fatal: [localhost]: FAILED! => {
"msg": "Error in jmespath.search in json_query filter plugin:\n'lldp_output' is undefined"
}
How do I query the JSON shown so I get a list of all ports that have mode: trunk
When I run in a playbook:
---
- name: Find trunk ports
hosts: ios
tasks:
- name: Collect interface output
cisco.ios.ios_l2_interfaces:
config:
state: gathered
register:
"intf_output"
- debug:
var=intf_output
- name: Take 4
debug:
msg: "{{ intf_output | json_query(query) }}"
vars:
query: >-
"lldp_output['gathered']"[?mode=='trunk']
The structure returned is like following:
{
"intf_output": {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"failed": false,
"gathered": [
{
"name": "GigabitEthernet0/0"
},
{
"mode": "trunk",
"name": "GigabitEthernet0/1",
"trunk": {
"allowed_vlans": [
"10",
"20",
"30",
"100"
],
"encapsulation": "dot1q"
}
},
{
"mode": "trunk",
"name": "GigabitEthernet0/2",
"trunk": {
"allowed_vlans": [
"10",
"20",
"30",
"100"
],
"encapsulation": "dot1q"
}
},
{
"mode": "trunk",
"name": "GigabitEthernet0/3",
"trunk": {
"allowed_vlans": [
"10",
"20",
"30",
"100"
],
"encapsulation": "dot1q"
}
},
{
"mode": "trunk",
"name": "GigabitEthernet1/0",
"trunk": {
"allowed_vlans": [
"10",
"20",
"30",
"100"
],
"encapsulation": "dot1q"
}
},
{
"mode": "trunk",
"name": "GigabitEthernet1/1",
"trunk": {
"allowed_vlans": [
"10",
"20",
"30",
"100"
],
"encapsulation": "dot1q"
}
},
{
"name": "GigabitEthernet1/2"
},
{
"name": "GigabitEthernet1/3"
},
{
"name": "GigabitEthernet2/0"
},
{
"name": "GigabitEthernet2/1"
},
{
"name": "GigabitEthernet2/2"
},
{
"name": "GigabitEthernet2/3"
},
{
"name": "GigabitEthernet3/0"
},
{
"name": "GigabitEthernet3/1"
},
{
"name": "GigabitEthernet3/2"
},
{
"name": "GigabitEthernet3/3"
}
]
}
}
For each host I run against the playbook against.
The argument to json_query must be a string. Because you haven't quoted your argument, Ansible is looking for a variable named lldp_output. But you've got additonal problems, since you're trying to access a key named lldp_output['gathered'], but [ is a syntactically significant character in JSON (and JMESPath queries), so that needs to be escaped as well.
In order to avoid all sorts of quote escaping contortions, we can place the query itself into a variable, so that we have:
- hosts: localhost
vars:
tmpdata: "{{ lookup('file','sample.json') | from_json }}"
tasks:
- name: Take 4
debug:
msg: "{{ tmpdata | json_query(query) }}"
vars:
query: >-
"lldp_output['gathered']"
Note that we are using the >- block quote operator, which means that the value of query is the literal string "lldp_output['gathered']", including the outer quotes.
That playbook outputs:
TASK [Take 4] *********************************************************************************
ok: [localhost] => {
"msg": [
{
"mode": "trunk",
"name": "GigabitEthernet0/0",
"trunk": {
"encapsulation": "dot1q"
}
},
{
"access": {
"vlan": 12
},
"mode": "access",
"name": "GigabitEthernet0/1"
},
{
"name": "GigabitEthernet0/2"
}
]
}
To get just those systems with mode equal to trunk, just add that
criteria to your query:
- hosts: localhost
vars:
tmpdata: "{{ lookup('file','sample.json') | from_json }}"
tasks:
- name: Take 4
debug:
msg: "{{ tmpdata | json_query(query) }}"
vars:
query: >-
"lldp_output['gathered']"[?mode=='trunk']
This will output:
TASK [Take 4] *********************************************************************************
ok: [localhost] => {
"msg": [
{
"mode": "trunk",
"name": "GigabitEthernet0/0",
"trunk": {
"encapsulation": "dot1q"
}
}
]
}
Update
Given the data you've shown in your updated question, things are much
simpler, because you don't have the weird quoting you had in the
original question. With intf_output defined as shown, you can
write:
tasks:
- name: Take 4
debug:
msg: "{{ intf_output | json_query(query) }}"
vars:
query: >-
gathered[?mode=='trunk']
My JSON File
{
"msg": {
"changed": false,
"failed": false,
"ontap_info": {
"ontap_version": "160",
"volume_info": {
"testvol1:test1": {
"encrypt": "false",
"encryption_state": "none",
"encryption_type": "none",
"key_id": null,
"volume_antivirus_attributes": {
"on_access_policy": "default"
},
},
"testvol2:test2": {
"encrypt": "false",
"encryption_state": "none",
"encryption_type": "none",
"key_id": null,
"volume_antivirus_attributes": {
"on_access_policy": "default"
},
},
},
},
},
}
My Task
- name: Volume Aggr Info
raw: |
VOLUME_NAME="{{ ontap_volume_info | json_query( info ) | json_query( "'item'" ) | json_query( volumename ) }}"
delegate_to: localhost
loop: "{{ ontap_volume_info.ontap_info.volume_info.keys() | list }}"
I have to loop through the key which contains the colon, for example "testvol2:test2".
I tried multiple solutions and nothing is working. When I try to display the VOLUME_NAME variable, it is displaying an empty string.
Q: "Loop through the key which contains the colon, for example, "testvol2:test2"
A: Each of the keys must be unique. YAML places no further restrictions on the nodes.
Given a valid JSON in the variable my_data. The task below does the job
- debug:
msg: "{{ my_data.ontap_info.volume_info[item] }}"
loop: "{{ my_data.ontap_info.volume_info.keys()|list }}"
should give
ok: [localhost] => (item=testvol1:test1) => {
"msg": {
"encrypt": "false",
"encryption_state": "none",
"encryption_type": "none",
"key_id": null,
"volume_antivirus_attributes": {
"on_access_policy": "default"
}
}
}
ok: [localhost] => (item=testvol2:test2) => {
"msg": {
"encrypt": "false",
"encryption_state": "none",
"encryption_type": "none",
"key_id": null,
"volume_antivirus_attributes": {
"on_access_policy": "default"
}
}
}
I am running an ansible playbook which outputs my information in JSON. This array has a loop that has multiple identical keys with different values. Please see below, I am interested in the "intf_id":
"stdout": [
{
"TABLE_cdp_neighbor_brief_info": {
"ROW_cdp_neighbor_brief_info": [
{
"capability": [
"switch",
"IGMP_cnd_filtering"
],
"device_id": "osw1J15",
"ifindex": "83886080",
"intf_id": "mgmt0",
"platform_id": "cisco WS-C2960X-48TS-LL",
"port_id": "GigabitEthernet0/45",
"ttl": "160"
},
{
"capability": [
"router",
"switch",
"Supports-STP-Dispute"
],
"device_id": "spine01",
"ifindex": "436232192",
"intf_id": "Ethernet1/49",
"platform_id": "N9K-C9508",
"port_id": "Ethernet1/11",
"ttl": "159"
},
{
"capability": [
"router",
"switch",
"Supports-STP-Dispute"
],
"device_id": "spine02",
"ifindex": "436232704",
"intf_id": "Ethernet1/50",
"platform_id": "N9K-C9508",
"port_id": "Ethernet1/11",
"ttl": "127"
},
{
"capability": [
"router",
"switch",
"IGMP_cnd_filtering",
"Supports-STP-Dispute"
],
"device_id": "leaf1J1402",
"ifindex": "436234240",
"intf_id": "Ethernet1/53",
"platform_id": "N9K-C93180YC-EX",
"port_id": "Ethernet1/53",
"ttl": "175"
},
{
"capability": [
"router",
"switch",
"IGMP_cnd_filtering",
"Supports-STP-Dispute"
],
"device_id": "leaf1J1402",
"ifindex": "436234752",
"intf_id": "Ethernet1/54",
"platform_id": "N9K-C93180YC-EX",
"port_id": "Ethernet1/54",
"ttl": "175"
}
]
},
"neigh_count": "5"
}
]
Currently I can access the first iteration of "intf_id" with :
- debug: msg="{{ list.stdout[0].TABLE_cdp_neighbor_brief_info.ROW_cdp_neighbor_brief_info[0].intf_id }}"
I feel like I'm close, but cannot figure out how to get each iteration of "intf_id". I can get subsequent ones by changing ROW_cdp_neighbor_brief_info[0] to [1] or [2], etc. I need to be able to access each key, without knowing how many keys there will be. Each value to the keys also need to be individually callable by subsequent tasks.
1) How do I get ansible to debug each iteration?
2) Depending on the answer to 1, how can I assign a particualr variable to each value? (ie. interface1, interface2, interface3...)
ansible-playbook json_query.yml
tasks:
- name:
debug:
msg: "{{ item }}"
loop: "{{ stdout | json_query('TABLE_cdp_neighbor_brief_info.ROW_cdp_neighbor_brief_info[*].intf_id') }}"
(output abridged)
"msg": "mgmt0"
"msg": "Ethernet1/49"
"msg": "Ethernet1/50"
"msg": "Ethernet1/53"
"msg": "Ethernet1/54"
You can use the filter json_query with jmespath syntax to loop over the various elements intf_id this way
---
- hosts: localhost
gather_facts: no
tasks:
- name: populate router data from json
set_fact:
data: "{{ lookup('file','so-router-info.json') }}"
- name: looping over interfaces
debug:
var: item
loop: "{{ data | json_query('TABLE_cdp_neighbor_brief_info.ROW_cdp_neighbor_brief_info[*].intf_id') }}"
it gives this output
…
TASK [looping over interfaces] ********************************************************************************************************
ok: [localhost] => (item=mgmt0) => {
"item": "mgmt0"
}
ok: [localhost] => (item=Ethernet1/49) => {
"item": "Ethernet1/49"
}
ok: [localhost] => (item=Ethernet1/50) => {
"item": "Ethernet1/50"
…
You can use the site http://jmespath.org/ to tests filters