ansible JEMSPATH errors while parasing ansible facts - ansible

i am trying to filter all the strings which contains "RegButton-" from the below ansible facts and use the output as list of items in the next play.
trying to use json_query filter but it is failing with below error
ansible fact
{
"ansible_facts": {
"srcgrpname": [
"RegButton-48773",
"test_vio",
"RegButton-23395",
"RegButton-520859",
"RegButton-743141",
"RegButton-297578",
"RegButton-186156"
]
},
"changed": false
}
playbook entry
- name: "Filter Regbutton policy Names"
set_fact:
srcgrpname2: "{{ resultid1 | json_query(query) }}"
vars:
query: "ansible_facts.srcgrpname[?contains(#, 'RegButton-') == `true`]"
Error that i am receiving.
{
"msg": "JMESPathError in json_query filter plugin:\nIn function contains(), invalid type for value: RegButton-48773, expected one of: ['array', 'string'], received: \"unknown\"",
"_ansible_no_log": false
}

It's possible to use select and regex. For example the tasks below
- set_fact:
srcgrpname2: "{{ ansible_facts.srcgrpname|
select('regex', '^RegButton-(.*)$')|
list }}"
- debug:
var: srcgrpname2
give
"srcgrpname2": [
"RegButton-48773",
"RegButton-23395",
"RegButton-520859",
"RegButton-743141",
"RegButton-297578",
"RegButton-186156"
]
Notes
It's an open issue with contains.
json_query filter fails when using the functions "contains", "starts_with", others #27299
Allow for subclassed types #158
It's reproducible "RegButton-48773, expected one of: ['array', 'string'], received: unknown"
See json format query with contains

Related

Select item from simple list with jmespath query

I have a simple list with strings. I want to select the item in the list based on the presence of a predefined string 'fixedaddress'. I created a query and I expect the query to return the first item/string from the example input list, but it is retruning the following error:
fatal: [localhost]: FAILED! => {"msg": "JMESPathError in json_query filter plugin:\nIn function contains(), invalid type for value: fixedaddress/ZG5zLmZpeGVkX2FkZHJlc3MkMTAuMjM5LjEyLja, expected one of: ['array', 'string'], received: \"unknown\""}
Input (strings are shortened because of sensitive info, rest of the string contains another : and /, not sure if that is related to the error):
{
[
"fixedaddress/ZG5zLmZpeGVkX2FkZHJlc3MkMTAuMjM5LjEyLja",
"record:a/ZG5zLmJpbmRfYSQuMjMubmwubW9kLG1hcmMwMDAxLW1"
]
}
Ansible code:
- name: "Delete IP Record: Task 2.2a: Filter Results."
vars:
jmesquery: "[?contains(#,`fixedaddress`)]"
set_fact:
ip_record_ref: "{{ ip_record_refs | json_query(jmesquery) }}"
when: ip_record_refs | length > 1
I found the solution. Apparently the Ansible version we are using has a bug. See also:
https://github.com/ansible/ansible/issues/27299
So workaround was to use the following as JMESpath query:
ip_record_ref: "{{ ip_record_refs | to_json | from_json | json_query(jmesquery) }}"

Collecting variables from multiple hosts in a playbook

I have a playbook that collects a list of information from multiple hosts, and I want to collect those lists together into a single variable.
So for example, my output might look like this:
ok: [ALBSWA052] => {
"myvar": [
"ALBSWA052 exists",
"ALBSWA052 is secondary"
]
}
ok: [ALBSWA051] => {
"myvar": [
"ALBSWA051 exists",
"ALBSWA051 is primary"
]
}
and I'd like to combine them into a list like
"myvar": [
"ALBSWA052 exists",
"ALBSWA052 is secondary"
"ALBSWA051 exists",
"ALBSWA051 is primary"
]
Is there an elegant way to do this? The only way I can think of is to write it into a file and read it back in.
For example
- set_fact:
myvar: "{{ ansible_play_hosts|map('extract', hostvars, 'myvar')|flatten }}"
run_once: true
gives the expected result
myvar:
- ALBSWA051 exists
- ALBSWA051 is secondary
- ALBSWA052 exists
- ALBSWA052 is secondary

Difficulty Parsing Ansible Output

[ansible 2.9.6, Ubuntu 20.04]
This seems pretty straightforward, but I keep getting an error message saying:
fatal: [192.168.254.100]: FAILED! => {"msg": "template error while templating string: expected name or number. String: {{ipv4addrs.'host[0]'}}"}
Here's my ansible playbook:
---
- hosts: nios
connection: local
vars:
nios_provider:
host: 192.x.x.x
username: xxx
password: xxx
wapi_version: "2.11.2"
tasks:
- name: Find client app server records
set_fact:
recs: "{{ lookup('nios', 'record:host', filter={'name~':'sdk' }, provider=nios_provider) }}"
- name: check return
debug:
msg: "{{ recs }}"
- name: get host name
debug:
var: ipv4addrs.'host[0]'
And here's the output:
TASK [check return] **************************************************************************************************************************************************************
ok: [192.x.x.x] => {
"msg": [
{
"_ref": "record:host/ZG5zLmhvc3QkLl9kZWZhdWx0LmNvbS5lYWdsZWFjY2Vzcy5zZGstZDAwMQ:sdk-d001.somename.com/default",
"ipv4addrs": [
{
"_ref": "record:host_ipv4addr/ZG5zLmhvc3RfYWRkcmVzcyQuX2RlZmF1bHQuY29tLmVhZ2xlYWNjZXNzLnNkay1kMDAxLjEwLjcwLjAuMS4:10.70.0.1/sdk-d001.somename.com/default",
"configure_for_dhcp": false,
"host": "sdk-d001.somename.com",
"ipv4addr": "10.70.0.1"
}
],
"name": "sdk-d001.somename.com",
"view": "default"
},
{
"_ref": "record:host/ZG5zLmhvc3QkLl9kZWZhdWx0LmNvbS5lYWdsZWFjY2Vzcy5zZGstZDAwMg:sdk-d002.somename.com/default",
"ipv4addrs": [
{
"_ref": "record:host_ipv4addr/ZG5zLmhvc3RfYWRkcmVzcyQuX2RlZmF1bHQuY29tLmVhZ2xlYWNjZXNzLnNkay1kMDAyLjEwLjcwLjAuMi4:10.70.0.2/sdk-d002.somename.com/default",
"configure_for_dhcp": false,
"host": "sdk-d002.somename.com",
"ipv4addr": "10.70.0.2"
}
],
"name": "sdk-d002.somename.com",
"view": "default"
}
]
}
TASK [get host name] ***************************************************************************************************************************************************************
fatal: [192.168.254.100]: FAILED! => {"msg": "template error while templating string: expected name or number. String: {{ipv4addrs.'host[0]'}}"}
My objective is to find all of the host names beginning with (for example) "sdk". There may be 1 or several. And then I want to get the full name and ip address captured as variables. I have tried lots of different options: rec.ipv4addrs[0].host, ipv4addrs[0].host, rec.ipv4addrs[0].'host', rec.ipv4addrs[0].['host'] . . . but I cannot find the proper syntax.
This appears to be an array of dict blocks with an ipv4addrs array within it. So the first thing I tried was "ipv4addrs[0]['host']" with no joy.
Thanks in advance for the help.
You're trying to make use of a variable called ipv4addrs, but you
haven't defined any variables with that name. From the previous
debug task you apparently have a variable named recs, which is a
list of dictionaries each of which contains an ipv4addrs attribute.
If you wanted to look up the host attribute for each record in
recs, you could write something like this:
- name: get host name
debug:
var: item.ipv4addrs[0].host
loop: "{{ recs }}"
If you wanted the ipv4addrs.host value from the first record, you
could write:
- name: get host name
debug:
var: recs[0].ipv4addrs[0].host
The error you're seeing stems from the fact that the expression
ipv4addrs.'host[0]' doesn't make any sense; that's not a syntax that
Ansible supports. You can't use a string with dot-notation like that.

json_query filter list of dicts which has value in a list

I have a list of dict mydicts=[{name: foo, data: 1}, {name: foo1, data: 3}, {name: bar, data: 2}] and a list of names names=[foo, foo1, mars, jonh]
I want to create the list of dict only contains names in the list. I know if I want to select single dict I can do jq="[?(name=='foo')]" then mydicts | json_query(jq). However I cannot make the contains version work so far. I need something like [?(contains(names, name))]. Can any one show me an example about how to do this?
It seems json_query correctly get the value name with contains in jq2, but it think it is a variable instead of string. I just found out there is a bug in ansible we need this | to_json | from_json to handle it.
In jq3, it seems it never get the value names I guess I need to wrap both mydicts and names into a dict and pass to jsn_query
- hosts: localhost
gather_facts: False
vars:
mydicts:
- name: foo
data: 1
- name: bar
data: 2
names:
- foo
- foo1
- mars
- jonh
jq1: "[?(name == 'foo')]"
jq2: "[?(contains(name, 'f'))]"
jq3: "[?(contains(names, name))]"
tasks:
- debug:
var: json
- name: JMEPath test equal
debug:
msg: "{{mydicts | json_query(jq1)}}"
- name: JMEPath test str contain str does not work
debug:
msg: "{{mydicts | json_query(jq2)}}"
- name: JMEPath test another list contain str
debug:
msg: "{{mydicts | json_query(jq3)}}"
ignore_errors: yes
TASK [JMEPath test equal] ************************************************************************************************************************************************
ok: [localhost] => {
"msg": [
{
"data": 1,
"name": "foo"
}
]
}
TASK [JMEPath test str contain str] **************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "JMESPathError in json_query filter plugin:\nIn function contains(), invalid type for value: foo, expected one of: ['array', 'string'], received: \"unknown\""}
...ignoring
TASK [JMEPath test another list contain str] *****************************************************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "JMESPathError in json_query filter plugin:\nIn function contains(), invalid type for value: None, expected one of: ['array', 'string'], received: \"null\""}
...ignoring
For this simple example, there is a work around: using jinja filter "{{mydicts | selectattr('name', 'in', names) | list}}". But I still need the json_query functionality for deep nested keys.
JMESPath does not have visibility into the jinja2 variables in the same way that the jinja2 filters do, which is why the reference to names doesn't do what you are expecting: JMESPath thinks that is a field on the current-node named names, rather than a reference to that jinja2 variable
AFAIK you can either construct an artificial input structure just to make both of those pieces of data available to JMESPath (akin to {"names": [...], "mydicts": ...} or you can inline the names list into the JMESPath query:
- debug:
msg: '{{ mydicts | json_query(jq) }}'
vars:
jq: '[? contains(`{{ names | to_json }}`, name) ]'
Q: "[?(contains(names, name))]. Can anyone show me an example of how to do this?"
A: IMHO. It's not possible. The function contains takes the item from the input and searches if the item contains the parameter.
boolean contains(array|string $subject, any $search)
You need a function with the same functionality but switched parameters. Hypothetically,
boolean in(any $search, array|string $subject)
which could be translated to a (hypothetical) query
jq: "[?(in(name, names))]"
There is no such function.
Notes:
selectattr can handle nested attributes

Ansible and JMESPath, escape forward slash in a json_query

There is a simple JSON file, sample.json with the following content:
{
"test": {
"domain": [
{
"name": "cluster1"
}
]
}
}
With Ansible, I want to query over the test key, which works with the following Ansible playbook.
---
- hosts: localhost
vars:
tmpdata: "{{ lookup('file','sample.json') | from_json }}"
- debug:
msg: "{{ tmpdata | json_query('test') }}"
The play
ok: [localhost] => {
"msg": {
"domain": [
{
"name": "cluster1"
}
]
}
}
However, when they key in the JSON file is changed, from test to test/something, and the ansible json_query from test to test/something as well, Ansible/JMESPath produces an error.
fatal: [localhost]: FAILED! => {"msg": "JMESPathError in json_query filter plugin:\nBad jmespath expression: Unknown token /:\ntest/something\n ^"}
I've looked into the JMESpath documentation, but it does not make sense to me.
How can I ensure JMESpath works with forward slashes in the Ansible query.
JMESPath defines identifier as unquoted-string / quoted-string.
unquoted-string is A-Za-z_. Anything else should be quoted.
In your case:
- debug:
msg: "{{ tmpdata | json_query('\"test/something\"') }}"
Here we escape \" because we are inside YAML double quotes msg: "...".

Resources