Json_query filter in Ansible - ansible

I cannot manage to make json_query work, even with the simplest examples.
I have a fact old_new_nodes like:
ok: [localhost] => {
"msg": [
{
"id_new": 2430,
"id_old": 2263
},
{
"id_new": 2431,
"id_old": 2283
}
]
}
I want to save the id_new parameter whenever the id_old is equal to a certain value.
I do:
- name: Get id of the new node
set_fact:
new_node_id: "{{ (old_new_nodes | first) |json_query('[?id_old==original_node.id].id_new') }}"
I have checked that original_node.id is 2263 so the expected result is 2430.
Moreover, I have tried without the first and I still get [] as a result

A literal in JMESPath expression must be quoted. See Grammar
list-filter-expr = expression comparator expression
comparator = "<" / "<=" / "==" / ">=" / ">" / "!="
expression =/ "*" / multi-select-list / multi-select-hash / literal
literal = "`" json-value "`"
Unquoted literal causes the error bellow
- debug:
msg: "{{ old_new_nodes|json_query('[?id_old == 2263].id_new') }}"
msg: |-
JMESPathError in json_query filter plugin:
invalid token: Parse error at column 12, token "2263" (NUMBER), for expression:
"[?id_old == 2263].id_new"
^
The query works as expected if you quote the literal
- debug:
msg: "{{ old_new_nodes|json_query('[?id_old == `2263`].id_new') }}"
gives
msg:
- 2430
If you want to put the searched value into a variable simplify the quotation of the query and put it into a variable too. The task below gives the same result
- debug:
msg: "{{ old_new_nodes|json_query(query) }}"
vars:
id: 2263
query: '[?id_old == `{{ id }}`].id_new'
It's possible to iterate the list, e.g.
- debug:
msg: "{{ old_new_nodes|json_query(query) }}"
loop: "{{ old_new_nodes|map(attribute='id_old')|list }}"
vars:
query: '[?id_old == `{{ item }}`].id_new'
gives
msg:
- 2430
msg:
- 2431
It'd be better to convert the list to a dictionary if you have to search it repeatedly, e.g.
- set_fact:
old_new: "{{ old_new_nodes|
items2dict(key_name='id_old', value_name='id_new') }}"
gives
old_new:
2263: 2430
2283: 2431
This would make the searching both trivial and fast.

Related

Remove last character from variable

I am trying to remove the last character of the below output but I am having trouble doing it:
- debug:
msg: "{{ ansible_mounts|json_query('[?mount == `/`].device') }}"
register: rootpart
The below works with simple text:
- debug:
msg: "{{ '/dev/sda4'[:-1] }}"
But not with a variable:
- debug:
msg: "{{ rootpart[:-1] }}"
Error:
msg: Unexpected templating type error occurred on ({{ rootpart[:-1] }}): unhashable type: 'slice'
There is a big "don't" in your current attempt: you should not register on a debug task.
If you want to create a new variable, then use the set_fact module.
This is not only a better way to do it, it is also saving you from having a dictionary with the keys changed, failed and msg to dive into in order to get the variable you were expecting out of the msg property.
Then, your json_query is going to return you a list of devices, no matter if there is only one match thanks to your filter. So, you also need to get the first element of this list.
So, with all this, here are your two tasks:
- set_fact:
root_part: "{{ ansible_mounts | json_query(_query) }}"
vars:
_query: "[?mount == `/`].device | [0]"
- debug:
var: root_part[:-1]

jinja2 turning lists into strings even when from_json is being used

I am trying to run a nested for loop in order to retrieve a nested value.
I would like to retrieve some_value_4 when some_value_3 matches a criteria that's predefined.
{
"some_dict": {
"some_key_0": "value_0",
"some_key_1": "value_1"
},
"testval_dict": {
"test_key_0": "some_value_0",
"test_key_1": "some_value_1",
"test_key_2": "some_value_2",
"test_key_3": "some_value_3",
"test_key_4": "some_value_4"
}
}
The playbook:
- hosts: localhost
tasks:
- set_fact:
mydict: "{{ lookup('file', '/tmp/file.json' ) | from_json }}"
- debug:
msg: |
"{% for item in mydict %}
{{ item }}
{% endfor %}"
when run, it alreay looks like dict names are treated as string and nothing more:
ansible-playbook /tmp/test_playbook.yml -c local -i ', localhost'
TASK [debug] ******************************************************************
ok: [localhost] => {}
MSG:
" somee_dict
testval_dict
"
Then when I add an itme.key to the debug task, the playbook fails:
MSG:
The task includes an option with an undefined variable. The error was: 'str object' has no attribute 'value'
Thank you.
edit for clarification
In the real example, I will not know the names of the dicts, so I cannot use some_dict or testval_dict, that is why I'm trying to go over this data source in an item.key or item.value form.
Q: "{% for item in mydict %} ... dict names are treated as string and nothing more."
A: This is correct. A dictionary, when evaluated as a list, returns the list of its keys. See some examples below
- debug:
msg: "{{ mydict.keys()|list }}"
- debug:
msg: "{{ mydict[item] }}"
loop: "{{ mydict.keys()|list }}"
- debug:
msg: "{{ mydict|difference(['testval_dict']) }}"
give
msg:
- some_dict
- testval_dict
msg:
some_key_0: value_0
some_key_1: value_1
msg:
test_key_0: some_value_0
test_key_1: some_value_1
test_key_2: some_value_2
test_key_3: some_value_3
test_key_4: some_value_4
msg:
- some_dict
See How to iterate through a list of dictionaries in Jinja template?
If you need to loop over the dictionary, you can use with_dict loop functionality. This way, if you loop over mydict and get item.key you will get somee_dict and testval_dict.
tasks:
- set_fact:
mydict: "{{ lookup('file', '/tmp/file.json')|from_json }}"
# This will get the top level dictionaries somee_dict and testval_dict
- debug:
var: item.key
with_dict: "{{ mydict }}"
And if you get item.value of mydict you will get the sub-dictionary:
- debug:
var: item.value
with_dict: "{{ mydict }}"
Will produce (showing output only for testval_dict):
"item.value": {
"test_key_0": "some_value_0",
"test_key_1": "some_value_1",
"test_key_2": "some_value_2",
"test_key_3": "some_value_3",
"test_key_4": "some_value_4"
}

Checking the first digit in a number in ansible playbook

I have an ansible playbook that reads in a vars_file containing usernames and uids
users:
- name: josh
uid: 1201
- name: peter
uid: 1202
- name: paul
uid: 2101
- name: ryan
uid: 2102
I have two host groups in my inventory file, db and web. I want users to be created in db if their uid starts with a 1, and web if it starts with 2.
My playbook so far looks like this
---
- name: users playbook
hosts: all
become: yes
vars_files:
- vars/user_list.yml
tasks:
- name: test debug
debug:
msg: "{{ item.username }}, {{ item.uid }}"
loop: "{{ users }}"
when: '{{ item.uid[0] }} == 1'
But my when conditional throws the error
The error was: error while evaluating conditional ({{ item.uid[0] }} == 1)
Is there a better way of doing this for both conditionals?
Several problems.
First, you are not comparing anything. In the expression '{{ item.uid[0] }} == 1' the last part (i.e. == 1) will be literally treated as a string and written as output. If used in a full jinja2 expression, the comparison must be inside the markers: {{ item.uid[0] == 1 }}
Second, when clauses should not contain any jinja2 markers to expand variables. This is also the case for failed_when and changed_when. See the conditionals doc
Lastly, getting the character with an index will only work if the input is a string and not an int. So you first need to make sure or that by casting it correctly with the string filter. The char you will then get will be itself a string. Comparing it to an integer will always return false. So you either have to write the comparison value as a string (i.e. '1') or cast the extracted car to an integer with the int filter.
This is how I would fix your task:
- name: test debug
debug:
msg: "{{ item.username }}, {{ item.uid }}"
loop: "{{ users }}"
when: (item.uid | string)[0] | int == 1

Output hosts from ansible inventory group to a template [duplicate]

In Ansible, I have a list of strings that I want to join with newline characters to create a string, that when written to a file, becomes a series of lines. However, when I use the join() filter, it works on the inner list, the characters in the strings, and not on the outer list, the strings themselves. Here's my sample code:
# Usage: ansible-playbook tst3.yaml --limit <GRP>
---
- hosts: all
remote_user: root
tasks:
- name: Create the list
set_fact:
my_item: "{{ item }}"
with_items:
- "One fish"
- "Two fish"
- "Red fish"
- "Blue fish"
register: my_item_result
- name: Extract items and turn into a list
set_fact:
my_list: "{{ my_item_result.results | map(attribute='ansible_facts.my_item') | list }}"
- name: Examine the list
debug:
msg: "{{ my_list }}"
- name: Concatenate the public keys
set_fact:
my_joined_list: "{{ item | join('\n') }}"
with_items:
- "{{ my_list }}"
- name: Examine the joined string
debug:
msg: "{{ my_joined_list }}"
I want to get output that looks like:
One fish
Two fish
Red fish
Blue Fish
What I get instead is:
TASK: [Examine the joined string] *********************************************
ok: [hana-np-11.cisco.com] => {
"msg": "B\nl\nu\ne\n \nf\ni\ns\nh"
}
ok: [hana-np-12.cisco.com] => {
"msg": "B\nl\nu\ne\n \nf\ni\ns\nh"
}
ok: [hana-np-13.cisco.com] => {
"msg": "B\nl\nu\ne\n \nf\ni\ns\nh"
}
ok: [hana-np-14.cisco.com] => {
"msg": "B\nl\nu\ne\n \nf\ni\ns\nh"
}
ok: [hana-np-15.cisco.com] => {
"msg": "B\nl\nu\ne\n \nf\ni\ns\nh"
}
How do I properly concatenate a list of strings with the newline character?
Solution
join filter works on lists, so apply it to your list:
- name: Concatenate the public keys
set_fact:
my_joined_list: "{{ my_list | join('\n') }}"
Explanation
While my_list in your example is a list, when you use with_items, in each iterationitem is a string. Strings are treated as lists of characters, thus join splits them.
It’s like in any language: when you have a loop for i in (one, two, three) and refer to i inside the loop, you get only one value for each iteration, not the whole set.
Remarks
Don’t use debug module, but copy with content to have\n rendered as newline.
The way you create a list is pretty cumbersome. All you need is (quotation marks are also not necessary):
- name: Create the list
set_fact:
my_list:
- "One fish"
- "Two fish"
- "Red fish"
- "Blue fish"

When to use from_json filter in Ansible?

When should I use the from_json filter in Ansible?
I found out that using it sometimes has and sometimes have no effect.
Please consider the following example which illustrates the inconsistency I am getting.
Included in reverse order are: the questions - expected result - actual result - the playbook - the data. The data is taken from this question and the playbook is based on this answer.
The question(s):
Why storing the left part (before json_query) of the following expression in a variable and then using json_query on the variable causes the expression to be evaluated differently?
"{{ lookup('file','test.json') | json_query(query) }}"
Why does adding from_json filter alter the results (but does not if processing a variable):
"{{ lookup('file','test.json') | from_json | json_query(query) }}"
Expected result:
Last four tasks should give the same result. Alternatively, last two tasks should give the same result as previous two tasks.
Actual result (last four tasks only):
One task result differs.
TASK [This query is run against lookup value with from_json stored in a variable] ***
ok: [localhost] => {
"msg": [
678
]
}
TASK [This query is run against lookup value without from_json stored in a variable] ***
ok: [localhost] => {
"msg": [
678
]
}
TASK [This query is run directly against lookup value with from_json] **********
ok: [localhost] => {
"msg": [
678
]
}
TASK [This query is run directly against lookup value without from_json - the result is empty - why?] ***
ok: [localhost] => {
"msg": ""
}
The playbook:
---
- hosts: localhost
gather_facts: no
connection: local
tasks:
- set_fact:
from_lookup_with_from_json: "{{ lookup('file','test.json') | from_json }}"
- set_fact:
from_lookup_without_from_json: "{{ lookup('file','test.json') }}"
- name: Save the lookup value stored in a variable in a file for comparison
copy: content="{{ from_lookup_with_from_json }}" dest=./from_lookup_with_from_json.txt
- name: Save the lookup value stored in a variable in a file for comparison (they are the same)
copy: content="{{ from_lookup_without_from_json }}" dest=./from_lookup_without_from_json.txt
- name: This query is run against lookup value with from_json stored in a variable
debug: msg="{{ from_lookup_with_from_json | json_query(query) }}"
vars:
query: "Foods[].{id: Id, for: (Tags[?Key=='For'].Value)[0]} | [?for=='Tigger'].id"
- name: This query is run against lookup value without from_json stored in a variable
debug: msg="{{ from_lookup_without_from_json | json_query(query) }}"
vars:
query: "Foods[].{id: Id, for: (Tags[?Key=='For'].Value)[0]} | [?for=='Tigger'].id"
- name: This query is run directly against lookup value with from_json
debug: msg="{{ lookup('file','test.json') | from_json | json_query(query) }}"
vars:
query: "Foods[].{id: Id, for: (Tags[?Key=='For'].Value)[0]} | [?for=='Tigger'].id"
- name: This query is run directly against lookup value without from_json - the result is empty - why?
debug: msg="{{ lookup('file','test.json') | json_query(query) }}"
vars:
query: "Foods[].{id: Id, for: (Tags[?Key=='For'].Value)[0]} | [?for=='Tigger'].id"
The data (test.json):
{ "Foods" :
[ { "Id": 456
, "Tags":
[ {"Key":"For", "Value":"Heffalump"}
, {"Key":"Purpose", "Value":"Food"}
]
}
, { "Id": 678
, "Tags":
[ {"Key":"For", "Value":"Tigger"}
, {"Key":"Purpose", "Value":"Food"}
]
}
, { "Id": 911
, "Tags":
[ {"Key":"For", "Value":"Roo"}
, {"Key":"Purpose", "Value":"Food"}
]
}
]
}
json_query requires Python object (dict) as input, if you feed it with string, it gives empty string as result.
You get different result because of Ansible templating engine tricky work.
I should definitely write a post about it on my site...
After evaluating jijna2 expression Ansible try to cast complex types to Python objects (like dict or list). See my other answer.
In your case:
1.
- set_fact:
from_lookup_with_from_json: "{{ lookup('file','test.json') | from_json }}"
from_lookup_with_from_json is a dict, because you manually convert JSON-string from file to dict with from_json filter.
2.
- set_fact:
from_lookup_without_from_json: "{{ lookup('file','test.json') }}"
from_lookup_with_from_json becomes dict, because Ansible converts it when jinja2 expression ends with }}. So from_json is actually unnecessary as the last filter in chain.
3.
debug: msg="{{ lookup('file','test.json') | from_json | json_query(query) }}"
Again, you manually convert JSON-string here. So json_query get dict as input.
4.
debug: msg="{{ lookup('file','test.json') | json_query(query) }}"
In this case you feed JSON-string (not dict) as input to json_query filter. As everything happens inside one jinja2 expression, Ansible doesn't attempt to convert anything in between.
You can also get empty string result with a variable this way:
- set_fact:
from_lookup_force_string: "{{ lookup('file','test.json') | string }}"
In this case from_lookup_force_string will not be converted by Ansible tempating engine, and json_query will give you empty response on it.

Resources