I have the following json data to work with:
"result": {
"json": {
"licences": {
"4216": {
"license": "4512-5421-5134-7413"
}
}
}
}
I first tried to get my expected value with the following debug task:
- name: Display Licence ID
ansible.builtin.debug:
var: result.json.licences[0].license
But it returns :
"VARIABLE IS NOT DEFINED!"
As a work-around I'm currently using :
- name: Display Licence ID
ansible.builtin.debug:
var: result.json.licences[result.json.licences|first].license
This works but isn't there a better way than repeating the whole variable name?
Your input data is effectively a dict but you are trying to use it as a list. There is no element named 0 (i.e. having a key which value is 0) in your dict called licences (and since it is not a list, there is no first element at index 0 either).
What I understand from your question is that your licences dict will always contain a single key and that you want to get the licence number inside that key whatever the key name is. Please edit your question to be more specific if my understanding is wrong.
One way to do this is:
- name: Display licence ID
debug:
msg: "{{ result.json.licences | dict2items | map(attribute='value.license') | first }}"
Explanation:
the dict2items filter transforms your result.json.licenses dict
{
"4216": {
"license": "4512-5421-5134-7413"
}
}
into a list of key/value dicts
[
{
"key": "4216",
"value": {
"license": "4512-5421-5134-7413"
}
}
]
the map(attribute='value.license)` filter extracts the nested attribute from each element into a list
[
"4512-5421-5134-7413"
]
the first filter keeps only the first element from that list:
"4512-5421-5134-7413"
If you ever have several results and want to get a list of license number, just remove the first filter at the end. You can also build up on this example to get the key for the license if you need it one day.
Create the complete path. Attribute '4216' is a string. It must be quoted. Put it into the brackets
- debug:
var: result.json.licences['4216'].license
vars:
result:
json:
licences:
'4216':
license: 4512-5421-5134-7413
gives
result.json.licences['4216'].license: 4512-5421-5134-7413
If you don't know the number, or, in general, the name of the attribute, use json_query. For example,
- debug:
var: result.json.licences|json_query('*.license')|first
As a side note: If the attribute were a number 4216 you could have used it without quotes and brackets. For example,
- debug:
var: result.json.licences.4216.license
vars:
result:
json:
licences:
4216:
license: 4512-5421-5134-7413
gives
result.json.licences.4216.license: 4512-5421-5134-7413
For details see Referencing key:value dictionary variables.
Related
I get the below lists from HOSTS:
"HOSTNAME": [
"H1",
"H2",
"H3"
]
"SW_VERSION": [
"7.2.2",
"5.2.2",
"6.2.2"
]
"OSPF_NEIGHBOR": [
"10.1.1.1",
"10.1.1.2",
"10.1.1.3"
]
And I am converting them to List of Dict Objects as below: (objective is to create a report from the data that we get)
- set_fact:
host_data: "{{ h_data|default([]) + [ { 'HOSTNAME': item.0, 'SW_VERSION': item.1, 'OSPF_NEIGHBOR': item.2} ] }}"
with_together:
- "{{ HOSTNAME }}"
- "{{ SW_VERSION }}"
- "{{ OSPF_NEIGHBOR }}"
I get the output as below:
"host_data": [
{
"HOSTNAME": "H1",
"OSPF_NEIGHBOR": "10.1.1.1",
"SW_VERSION": "7.2.2"
},
{
"HOSTNAME": "H2",
"OSPF_NEIGHBOR": "10.1.1.2",
"SW_VERSION": "6.2.2"
},
{
"HOSTNAME": "H3",
"OSPF_NEIGHBOR": "10.1.1.3",
"SW_VERSION": "5.2.2"
}
]
I gave HOSTNAME as item.0, SW_VERSION as item.1 and OSPF_NEIGHBOR as item.2, but in the output OSPF_NEIGHBOR comes second in the dict elements which affects the table format/column order.
How can we make sure the order is preserved?
Or Is there a way to re-arrange the dict elements order in the host_data list according to the required column order?
I think this deserves an answer because it is not as simple as what #Zeitounator originally commented:
Dictionaries don't have order. Said differently you cannot rely on any order the data is spitted out from a dictionary. Use the key name that correspond to the information you need.
Actually, this is true up until Python 3.6, where a change as been made and:
New dict implementation
The dict type now uses a “compact” representation based on a proposal by Raymond Hettinger which was first implemented by PyPy. The memory usage of the new dict() is between 20% and 25% smaller compared to Python 3.5.
The order-preserving aspect of this new implementation is considered an implementation detail and should not be relied upon (this may change in the future, but it is desired to have this new dict implementation in the language for a few releases before changing the language spec to mandate order-preserving semantics for all current and future Python implementations; this also helps preserve backwards-compatibility with older versions of the language where random iteration order is still in effect, e.g. Python 3.5).
Source: https://docs.python.org/3/whatsnew/3.6.html#new-dict-implementation
Based on this, a change, that was originally added in the version 2.10 of Ansible: Add support for OrderedDict has been undone as the requirement to have Python 3.8 for Ansible was added.
Based on all this, my guess is you are on an old version of Ansible (prior to 2.10), and your solution should just be upgrading your Ansible version, also because the 2.9 branch has been out of support since 31 December 2021.
I have a simple vlan config file which I like to have the keys to match with JunOS syntax (this way I can pass them as aggrogate if I need to), so I'm using the vlan-id key like this example variable file.
# vlans.yaml
vlans:
- name: general
description: "General"
vlan-id: 100
- name: hotline
description: "Accounting"
vlan-id: 110
but i can't access the vlan-id key because of the hyphen
- debug:
msg: "{{ item.vlan-id }}"
loop: "{{ vlans }}"
tags: debug
"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'vlan'
if I output the item directly I can see the key
- debug:
msg: "{{ item }}"
loop: "{{ vlans }}
ok: [SW02] => (item={'name': 'external', 'description': 'External', 'vlan-id': 209}) => {
"msg": {
"description": "External",
"name": "external",
"vlan-id": 209
}
}
ok: [SW01] => (item={'name': 'external', 'description': 'External', 'vlan-id': 209}) => {
"msg": {
"description": "External",
"name": "external",
"vlan-id": 209
}
}
Any ideas how to solve this?
Put the attributes into the brackets '[]' aka array notation. For example
msg: "{{ item['vlan-id'] }}"
Quoting from Ansible allows dot notation and array notation for variables. Which notation should I use?
If your variable contains dots (.), colons (:), or dashes (-), if a key begins and ends with two underscores, or if a key uses any of the known public attributes, it is safer to use the array notation.
Q: "This is not a variable, it's the key."
A: Right. The only restriction is the keys are unique. Quoting from YAML 1.2
Mapping. The content of a mapping node is an unordered set of key: value node pairs, with the restriction that each of the keys is unique. YAML places no further restrictions on the nodes. In particular, keys may be arbitrary nodes, the same node may be used as the value of several key: value pairs, and a mapping could even contain itself as a key or a value (directly or indirectly).
I am looking to retrieve the value of a key (which will have a different key name each time) from a third-party module output.
A simple replication of what I am trying to achieve is as follows:
I have a variable -
secure_name: "ALIAS_HTTPD_HOSTNAME1'
I then run the task:
- name: retrieve param name
shell:
cmd: "echo {{ secure_name }} | cut -'_' -f2-"
register secure_param_name
I have a third module which takes the above {{ secure_param_name.stdout|upper }} as a parameter to retrieve the name value pair from a third party software for holding secure key pairs.
The output of the third party module is stored in a register var called: secure_results
The output of the third party module call is:
{
"changed": false,
"_ansible_no_log: false,
"HTTPD_HOSTNAME1": "SERVERNAME1"
}
if i issue:
-debug: msg="{{ secure_results.HTTPD_HOSTNAME1 }}"
i get the required output SERVERNAME1
I don't however want to have to hardcode every param I wish to retrieve. I wish to be able to use the value of secure_param_name.stdout to make up the variable_name
I have tried:
-debug: msg="{{'secure_results.'+secure_param_name.stdout }}
but this only returns: secure_results.HTTPD_HOSTNAME1
How do I resolve the above dynamic variable name?
using 2 sets of {{ {{ }} }} doesn't work.
I have also tried:
- debug: msg="{{ vars['secure_results.' ~ secure_param_name.stdout] }}"
this errors with 'dict object' has no attibute u'secure_results.HTTPD_HOSTNAME1'
I am a little confused as to why its not finding the dictionary object 'secure_results.HTTPD_HOSTNAME1' when placing that same string in {{ }} retrieves the value as shown in the first debug above.
Any help much appreciated
I think I have this working now by using the following:
- debug: msg="{{ hostvars[inventory_hostname]['secure_results'][secure_param_name.stdout] }}"
would that be the best way to do this?
register: dnsfact
- debug: var=dnsfact.ansible_facts.azure_dnszones[0].name
If I debug like above, I am getting the below output:
ok: [openshift-infra01.example.net] => {
"dnsfact.ansible_facts.azure_dnszones[0].name": "226********"
debug: var=dnsfact.ansible_facts.azure_dnszones[1].name > this will give me two values.
debug: var=dnsfact.ansible_facts.azure_dnszones[2].name > this will give me three values.
If I want to print all the values, which value do I need to pass? I tried with dnszones[:] and dnszones[':']. But I am not able to fetch the values.
please find the actual output below.
"dnsfact.ansible_facts.azure_dnszones": [
{
"etag": "00000002-0000-0000-9ed1-be810a8bd401",
"id": "/subscription*****/dnszones/226.10.in-addr.arpa",
"location": "global",
"name": "226.10.in-addr.arpa",
From this output we are trying to filter the "name".
You'll want the map jinja2 filter:
- debug:
msg: "{{ dnsfact.ansible_facts.azure_dnszones | map(attribute="name") | list }}"
Its job is to do almost the same thing that python's map does, but more targeted toward attribute extraction than just arbitrary computation over a list. You'll (usually) need that | list at the end because map() is lazy and returns a generator, which needs to be evaluated if you just want to see the output
When map'ing a attribute in a list of variables, Ansible is adding a 'Undefined' to the beginning and end of the key.
The variables:
vault_config_listener_params:
- address: "0.0.0.0:8200"
- tls_cert_file: "/etc/ssl/certs/wildcard.crt"
- tls_key_file: "/etc/ssl/certs/wildcard.key"
The debug task:
- debug: var=vault_config_listener_params|map(attribute="tls_cert_file")|list
The output:
ok: [id70118] => {
"vault_config_listener_params|map(attribute=\"tls_cert_file\")|list":
"[Undefined, u'/etc/ssl/certs/wildcard.crt', Undefined]"
}
The maping seems to have worked, as the key path has been extracted. But where are the 'Undefined' coming from?
PS: The variables needs to be a list, as they are looped it in another place with jinja2.
First of all, don't use debug's var when printing arbitrary expressions, use msg instead.
As for your question, map is quite dumb and doesn't do what you don't ask it to, so you actually need to select items with specified attributes defined first, and then get its values:
- debug:
msg: "{{ vault_config_listener_params | selectattr('tls_cert_file','defined') | map(attribute='tls_cert_file') | list }}"