Get the newest dictionary from a list in ansible - ansible

I have a list of dictionaries and I want to get the latest one.
reviewing jinja2 docs it seems I should be able to do this:
- set_fact:
t:
- a: 1
b: 2
- a: 3
b: 1
- debug:
msg: "{{ t | max(attribute='a') }}"
But that fails with
fatal: [localhost]: FAILED! => {
"msg": "Unexpected templating type error occurred on ({{ t | max(attribute='a') }}): max() > got an unexpected keyword argument 'attribute'"
}
what is the best whay to do this? Of course my use case is harder than that small demo.
My think looks something like this:
check_mode: no
set_fact:
tgs_info: "{{ tgs_info | default({}) | combine({ item: all_tg_info | to_json | from_json | json_query(query) | max(attribute='target_group_name') }) }}"
vars:
query: "target_groups[?contains(target_group_name, `{{ product }}`) == `true`] | [?ends_with(target_group_name, `{{ tg_suffix }}{{ item }}`) == `true`]"
loop: "{{ projects | selectattr('protocol', 'match', '^HTTP$') | map(attribute='port') | list }}"
The idea is that all_tg_info contains all the autoscaling of my aws account. I filter them and I want to get the latest one based on the name or any other parameter.
I'm kind of stuk here.

Update: As reported in #Hellseher comment below, from ansible 2.11 release, max|min(attribute='someattr') will be available. The answer below will therefore become obsolete for this version and onward. See the corresponding pull request.
reviewing jinja2 docs it seems I should be able to do this...
Even though the jinja2 builtin filters documentation mentions possible params for the max filter, there is an open bug report on ansible side to support those.
In this case you can easily acheive your requirement with the json_query filter. Here is a demo playbook with your simplistic data (as I don't have your more elaborate one...). You can probably adapt this to your actual json_query.
---
- hosts: localhost
gather_facts: false
vars:
t:
- a: 1
b: 2
- a: 3
b: 1
tasks:
- debug:
msg: "{{ t | json_query('max_by(#, &a)') }}"

Related

Filter list in Ansible for files ending with j2

I am trying to filter a list but have only limited success.
The list is hardcoded (all_files) and filtering starting with anchor '^' works just fine.
But what I really need is identify all *.j2 files
My Ansible version is 2.9.25
- name: Execute any command on localhost
hosts: localhost
gather_facts: false
tasks:
- set_fact:
all_files: [
"ansible_vars/dev-deploy-vars.yml",
"Precompiled/Application/config.js.j2",
"Precompiled/Application/web.config.j2"
]
- set_fact:
files_starting_with_pattern: "{{ j2_files | select('match', '^Precompiled') | list }}"
files_ending_with_pattern: "{{ j2_files | select('match', 'j2$') | list }}"
Any idea? All I need is a list of jinja2 files (which can be empty)
Thanks!
Your problem is that you're using match instead of search to look for a pattern at the end of the string. From the documentation:
match succeeds if it finds the pattern at the beginning of the string, while search succeeds if it finds the pattern anywhere within string. By default, regex works like search, but regex can be configured to perform other tests as well, by passing the match_type keyword argument. In particular, match_type determines the re method that gets used to perform the search. The full list can be found in the relevant Python documentation here.
So you want:
- name: Execute any command on localhost
hosts: localhost
gather_facts: false
vars:
all_files:
- ansible_vars/dev-deploy-vars.yml
- Precompiled/Application/config.js.j2
- Precompiled/Application/web.config.j2
tasks:
- set_fact:
files_starting_with_pattern: >-
{{ all_files | select("match", "^Precompiled") | list }}
files_ending_with_pattern: >-
{{ all_files | select("search", "j2$") | list }}
- debug:
var: files_starting_with_pattern
- debug:
var: files_ending_with_pattern
You could alternately use match if you modify your search pattern:
- set_fact:
files_starting_with_pattern: >-
{{ all_files | select("match", "^Precompiled") | list }}
files_ending_with_pattern: >-
{{ all_files | select("match", ".*j2$") | list }}

Ansible - Find host matching several variables or group memberships

I am attempting to create a playbook that will provision and setup a server and its standby server. The inventory is coming from Red Hat Satellite 6.x/Foreman and the playbook is being kicked off as a call back to Ansible AWX/Tower.
AWX calls the playbook with --limit to the specific server that made the callback. But I am able to access the whole inventory using inventory_hostnames.
The items that will be common between the primary and standby servers, that need to be matched are, foreman.location_id or foreman.location_name, foreman.hostgroup_id or foreman.hostgroup_title, foreman.environment_id or foreman.environment_name. These are also exposed as group memberships to foreman_location_[locationName], foreman_hostgroup_[hostgroupTitle], foreman_environment_[environmentName]. If all of these variables or groups are matched, only the two servers will be returned.
I am able to find servers in each of these groups with something like:
- debug:
msg: "{{ item }}"
loop: "{{ query('inventory_hostnames', 'foreman_location_datacenter,&foreman_environment_production') }}"
But I have not been able to figure out how to specify the groups dynamically based on the groups of the current host.
- debug:
msg: "{{ item }}"
loop: "{{ query('inventory_hostnames', 'foreman_location_{{ foreman.location_name | map('trim') }}') }}"
I get
FAILED! => {"msg": "template error while templating string: expected token ',', got 'trim'. String: {{ query('inventory_hostnames', 'foreman_location_{{ foreman.location_name | map('trim') }}') }}"}
And if I remove the 's from around trim I get nothing back.
When I try
- debug:
msg: "foreman_location_{{ foreman.location_name | map('trim') | list | join }}"
I get the correct string back.
EDIT:
I believe I have found a workable solution:
- name: Set Facts
set_fact:
jer_location: "foreman_location_{{ foreman.location_name | map('trim') | list | join | lower }}"
jer_hostgroup: "foreman_hostgroup_{{ foreman.hostgroup_title | map('trim') | list | join | lower | replace('/', '_') }}"
jer_environment: "foreman_environment_{{ foreman.environment_name | map('trim') | list | join | lower }}"
- name: List hosts from variable groups
debug:
msg: "{{ item }}"
loop: "{{ query('inventory_hostnames', '{{ jer_location }},&{{ jer_hostgroup }},&{{ jer_environment }}') }}"
If there is a better one, I would like to hear it.
Thank you in advance,
Jeremy

In Ansible, how to filter a dictionary based on regex pattern on the keys?

I need to extract a subset of a dictionary based on a pattern on the key names.
For example, in the v below I need to extract the key->values section1*.
The code below assigns the list of values, but I still haven't found a way to preserve the key->map setting.
- set_fact:
v:
section1_1: true
section1_2: false
section2_1: true
section2_2: false
section3: true
- set_fact:
v2: "{{ v | select('match','^section1_.*') | map('extract', v) | list }}"
- debug:
var: v2
Any help, please?
Thanks.
Combine dict2items and items2dict filters:
- debug:
msg: "{{ v | dict2items | selectattr('key', 'match', '^section1') | list | items2dict }}"
fatal: [localhost]: FAILED! => {"msg": "template error while templating string: no filter named 'items2dict'. String: {{ v | dict2items | selectattr('key', 'match', '^section1_') | list | items2dict }}"}
If I look at the sources in /usr/lib/python2.7/dist-packages I see that there are references to it, but not real function definitions
ansible/plugins/filter/core.py: raise AnsibleFilterError("items2dict requires a list, got %s instead." % type(mylist))
ansible/plugins/filter/core.py: 'items2dict': list_of_dict_key_value_elements_to_dict,
I'm runing 2.5.1. Do I need a later version?

Ansible / Jinja2 get item filer

Assume I have a list of:
vpcs:
- name: myvpc1
description: bla
zone: Enterprise
cidr: "10.5.0.0/16"
- name: vpc2
description: bla
zone: Private
cidr: "10.6.0.0/16"
I would like to select the zone of vpc1. So the filter below should return
Enterprise
My actual filter
{{ vpcs | selectattr('name', 'match', 'myvpc1') | first | attr('zone') }}
Does not work due to:
Get an attribute of an object. foo|attr("bar") works like foo.bar just
that always an attribute is returned and items are not looked up.
So it says key not found
How would I describe this filter
You should change the filter to the below, so it grabs the value of zone.
tasks:
- set_fact:
myvar: "{{ (vpcs | selectattr('name', 'match', 'myvpc1') | first)['zone'] }}"
- debug: var=myvar
The reason for this can be found in this Ansible's github issue, answered by jctanner (Member of Ansible)

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