Ansible - Find host matching several variables or group memberships - ansible

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

Related

how to set default value if key is not available in dict in ansible play

I am setting fact as dict using host names like below,
set_fact:
processed_hosts: "{{ processed_hosts | default([]) + [dict(host_name=item, result=hostvars[item]['_OS_upgraded'])] }}"
with_items:
- "{{ groups['reachable_a_hosts'] }}"
Its working good when all the host has "_OS_upgraded".
But when task failed in any of the host then "_OS_upgraded" is not set, on that scenario i am getting error when i call this hostvars[item]['_OS_upgraded']
so want to set it false by default if that item is not in hostvars,
Can some one let me know how to do that?
It worked after adding | default(False)
set_fact:
processed_hosts: "{{ processed_hosts | default([]) + [dict(host_name=item, result=hostvars[item]['_OS_upgraded']|default(False))] }}"
with_items:
- "{{ groups['reachable_a_hosts'] }}"

Get the newest dictionary from a list in 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)') }}"

Ansible: how to filter using varible

i have ansible-playbook that gives list of lines in debug output.
I am able to filter debug OUTPUT using a string (exp: CUST) but I am struggling to filter the list using a variable.
- debug:
msg: "{{ List.msg | select('match', '^(CUST)[0-9]+') | list }}"
List msg output:
CUST1
CUST2
NEW1
NEW2
from the above debug command, i get CUST1, CUST2 in filtered output.
- set_fact:
filter: "{{ fileout.results[0].content }}"
above filter generates "CUST" and i want to use this filter variable in above debug command.
using below syntax i get nothing, may be ansible is NOT taking it as appropriate variable.
- debug:
msg: "{{ List.msg | select('match', '^("{{ filter }}")[0-9]+') | list }}"
Please help.
thanks in advance.
It's possible to isolate the declaration of the regex filter and simplify the quotation. For example
vars:
List:
msg: ['CUST1','CUST2','NEW1','NEW2']
Patterns: ['CUST','NEW']
tasks:
- debug:
msg: "{{ List.msg | select('match', filter) | list }}"
vars:
filter: '^{{ item }}[0-9]+'
loop: "{{ Patterns }}"
gives
"msg": [
"CUST1",
"CUST2"
]
"msg": [
"NEW1",
"NEW2"
]

Ansible returns only one item from a dictionary

I'm trying to get a list of IPs from an specific Service, and ansible returns only one item from the loop.
I have tried many things and is always the same result.
Need help.
- name: "Amazon IPs"
include_vars:
file: /home/user1/ansible/AWS/ip-ranges.json
name: amazon
# - set_fact:
# # # test: "{{ (variable.stdout | from_json).prefixes | map(attribute='ip_prefix') | list }}"
# amazonipv4: "{{ item }}"
# # amazonipv6: "{{ amazon.ipv6_prefixes | map(attribute='ipv6_prefix') | list }}"
# loop: "{{amazon.prefixes | map(attribute='ip_prefix') | list }}"
# # when: '"AMAZON" in item.service'
- set_fact:
test3: "{{item.ip_prefix}}"
loop: "{{amazon.prefixes | list }}"
when: '"AMAZON" in item.service'
- debug:
var: test3
I expect to get a list based on the service, but I only get one item.
example:
TASK [debug] ***********************************************
ok: [localhost] => {
"test3": "54.190.198.32/28"
in each iteration in the set_fact loop, you are setting the value, not pushing to a list. you need to change your syntax to:
- set_fact:
test3: "{{ test3 | default([]) + [item.ip_prefix] }}"
hope it helps.

Ansible get first element from list

Suppose I have the following vars_file:
mappings:
- primary: 1.1.1.1
secondary: 2.2.2.2
- primary: 12.12.12.12
secondary: 11.11.11.11
and hosts file
1.1.1.1
12.12.12.12
5.5.5.5
and the following playbook task
- name: Extract secondary from list
debug:
msg: "{{ (mappings | selectattr('primary', 'search', inventory_hostname) | list | first | default({'secondary':None})).secondary }}"
The current task works and will give empty string when no match are found, but I would like to know if there is a better way/cleaner way of doing it without passing a dictionary to the default constructor.
An option would be to use json_query
- debug:
msg: "{{ mappings | json_query(\"[?primary=='\" + inventory_hostname + \"'].secondary\") }}"
, but selectattr works too
- debug:
msg: "{{ mappings | selectattr('primary', 'equalto', inventory_hostname) | map(attribute='secondary') | list }}"

Resources