Filter hostvar by special propery - ansible

I have host.yml like this
---
all:
hosts:
server-a:
server_dc: "Hetzner"
ansible_host: 192.168.1.1
server-b:
server_dc: "OVH"
ansible_host: 192.168.1.2
And play book debug is:
- name: sample
debug:
var: hostvars
And all hostvars debug success.
How to get same hostvars variable but filtered. Any of that server_dc is equal OVH
I dont want to iterate for template, i just one new filtered variable that contain all other properies.
I need another variable that i debug see this output:
['server-b']

This I believe meets your requirement (removing 'no_log: true', will result in the complete dictionary being printed in your playbook output):
- set_fact:
filtered_hosts: "{{ filtered_hosts | default({}) | combine({item.key: item.value}) }}"
when: "item.value.server_dc == 'OVH'"
with_dict: "{{ hostvars }}"
no_log: true
- debug:
var: filtered_hosts

Related

Ansible: Output of 'loop' with 'register' module

I'm trying to get the output of my Ansible script using register module but the loop I'm using is probably causing some issue.
Whats a default way of using register module with loop?
Code:
---
- name:
hosts:
gather_facts:
tasks:
- name: Execute file
ansible.builtin.shell:
environment:
"setting environment"
register: output
loop:
"value"
- debug:
vars: output.std_lines
Whats a default way of using register module with loop?
It is just registering the result.
The only difference will be, that a single task will provide you just with an dictionary result (or output in your example) and registering in a loop will provide with a list result.results (or output.results in your example). To access the .stdout_lines you will need to loop over the result set .results too.
You may have a look into the following example playbook which will show some aspects of Registering variables, Loops, data structures, dicts and lists and type debugging.
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- name: Create STDOUT output (single)
command: 'echo "1"'
register: result
- name: Show full result (single)
debug:
var: result
- name: Show '.stdout' (single)
debug:
msg: "The result in '.stdout': {{ result.stdout }} is of type {{ result.stdout | type_debug }}"
- name: Create STDOUT output (loop)
command: 'echo "{{ item }}"'
register: result
loop: [1, 2, 3]
loop_control:
label: "{{ item }}"
- name: Show full result (loop)
debug:
var: result
- name: Show '.stdout' (loop)
debug:
msg: "The result in '.stdout': {{ item.stdout }} is of type {{ item.stdout | type_debug }}"
loop: "{{ result.results }}"
loop_control:
label: "{{ item.item }}"
By running it and going through the output you can get familiar with the differences in your tasks.
Further Q&A
Register Variables in Loop in an Ansible Playbook
Ansible: loop, register, and stdout
Register variables in loop in Ansible playbook
... and many more here on SO.

Ansible: Retrieve Data from inventory

I need to retrieve the IP of bob_server from the inventory file. I am not clear as to what combination to use in filter, lookup, and when? Depending on the inventory filebob_server and alice_server names can change, but app_type won't change. My playbook logic is obviously wrong, Can someone guide me the correct way to fetch IP address when app_type = bob
My current Inventory file:
---
all:
hosts:
children:
bob_server:
hosts: 10.192.2.6
vars:
app_type: bob
alice_server:
hosts: 10.192.2.53
vars:
app_type: alice
My Playbook
---
- hosts: localhost
name: Retrive data
tasks:
- name: Set Ambari IP
set_fact:
ambariIP: "{{ lookup('hosts', children) }}"
when: "hostvars[app_type] == 'bob'"
Given the inventory
shell> cat hosts-01
---
all:
hosts:
children:
bob_server:
hosts: 10.192.2.6
vars:
app_type: bob
alice_server:
hosts: 10.192.2.53
vars:
app_type: alice
The simple option is using ansible-inventory, e.g.
- hosts: localhost
tasks:
- command: ansible-inventory -i hosts-01 --list
register: result
- set_fact:
my_inventory: "{{ result.stdout|from_yaml }}"
- debug:
var: my_inventory.bob_server.hosts
gives
my_inventory.bob_server.hosts:
- 10.192.2.6
If you want to parse the file on your own read it into a dictionary and flatten the paths, e.g. (install ansible.utils ansible-galaxy collection install ansible.utils)
- include_vars:
file: hosts-01
name: my_hosts
- set_fact:
my_paths: "{{ lookup('ansible.utils.to_paths', my_hosts) }}"
- debug:
var: my_paths
gives
my_paths:
all.children.alice_server.hosts: 10.192.2.53
all.children.alice_server.vars.app_type: alice
all.children.bob_server.hosts: 10.192.2.6
all.children.bob_server.vars.app_type: bob
all.hosts: null
Now select the keys ending bob_server.hosts
- set_fact:
bob_server_hosts: "{{ my_paths|
dict2items|
selectattr('key', 'match', '^.*bob_server\\.hosts$')|
items2dict }}"
gives
bob_server_hosts:
all.children.bob_server.hosts: 10.192.2.6
and select the IPs
- set_fact:
bob_server_ips: "{{ bob_server_hosts.values()|list }}"
gives
bob_server_ips:
- 10.192.2.6
The inventory is missing the concept of groups. See Inventory basics: formats, hosts, and groups. Usually, the value of children is a group of hosts. In this inventory, the value of children is the single host. This is conceptually wrong but still valid, .e.g
- hosts: bob_server
gather_facts: false
tasks:
- debug:
var: inventory_hostname
gives
shell> ansible-playbook -i hosts-01 playbook.ym
...
TASK [debug] ****************************************************
ok: [10.192.2.6] =>
inventory_hostname: 10.192.2.6

Run an Ansible task to all hosts if at least one of the hosts has a variable

I want to run a task to all hosts if at least one of the hosts has a variable "new_node".
For example i have inventory
[all]
host1.example.net
host2.example.net
host3.example.net new_node=True
And if in one of the hosts has variable "new_node=True"
then run this task on all hosts
---
- hosts: all
tasks:
- name: Create file yep at all hosts
file:
path: /tmp/yep
state: file
What condition or filter should i apply? Any ideas. Thanks
extract the variables, select true items and evaluate the test is any, e.g.
- set_fact:
semaphore: "{{ ansible_play_hosts_all|
map('extract', hostvars, 'new_node')|
select is any }}"
run_once: true
gives true if any variable new_node is true and gives false otherwise
semaphore: true
Then use the variable semaphore in the conditions e.g.
when: semaphore|bool
Filter select explained
Quoting from select
"If no test is specified, each object will be evaluated as a boolean."
Given the inventory
shell> cat hosts
host1.example.net
host2.example.net new_node=False
host3.example.net new_node=True
The task extracts the variables
- debug:
msg: "{{ ansible_play_hosts_all|
map('extract', hostvars, 'new_node')|
list }}"
msg: '[Undefined, False, True]'
We don't have to care about Undefined because select() evaluates Undefined to False
- debug:
msg: "{{ ansible_play_hosts_all|
map('extract', hostvars, 'new_node')|
select|list }}"
msg:
- true
You can test the evaluation of Undefined to False separately, e.g.
- debug:
msg: "{{ ansible_play_hosts_all|
map('extract', hostvars, 'new_node')|
map('bool')|
list }}"
gives
msg:
- false
- false
- true
If no variable is defined
shell> cat hosts
host1.example.net
host2.example.net
host3.example.net
select returns an empty list
msg: '[Undefined, Undefined, Undefined]'
msg: []
You can create a variable, that will be set to true when one of the hosts in [all] group has the variable new_node defined. Then this variable can be used to conditionally run the task.
Example:
tasks:
- set_fact:
run_on_all: true
when: hostvars[item]['new_node']|default(false)
with_items: "{{ groups['all'] }}"
- file:
path: /tmp/yep
state: touch
when: run_on_all|default(false)
The first task, sets the variable run_on_all to true if any one of the hosts has the variable new_node=True. Then the next task will execute if the previous task set the run_on_all variable to true. I am using the default() filter to avoid the chances of the variables being "undefined".
You did not actually specify what should happen if this property was not present, so I will assume you would simply want to abort the play in that case.
Simplest solution that I could think of is to set the playbook to run against the group [all], and check if this property is present on any of the hosts. If it is not present on any hosts, you abort the play.
Example
---
- hosts: all
tasks:
- name: Check if the hostvar 'new_node' is present on any hosts
set_fact:
has_new_node: "{{ groups['all'] | map('extract', hostvars, 'new_node') | list | select('defined') | length | bool }}"
delegate_to: localhost
- name: Abort play if not 'has_new_node'
meta: end_play
when: not has_new_node
- debug:
msg: new_node found

access a variable in a variable

I am 90% sure this doesn't work because i'm doing it the wrong way, but i can't figure out what is the "right way",I hope you can get my point :
I am trying to access an ipv4 of a certain interface,I have in my hosts file interface_lan = enp4s0 because i need it in a role,so i thought i might just use it to have the IP address of that interface :
"{{hostvars[inventory_hostname]['ansible_{{interface_lan}']['ipv4.address']}}"
with that command,he is looking for "ansible_{{interface_lan}}" but i want him to look for
"ansible_"{{interface_lan}}"" and to consider "{{interface_lan}}" as a variable,not as a string.
I tried my best to explain,sorry if you did not understand you are free to enjoy the rest of your day without helping me, i have been ignoring this line for a few days now.
thank you !
Concatenate the name of the attribute an use it in the index. For example
- hosts: localhost
gather_facts: false
vars:
interface_lan: enp4s0
ansible_enp4s0:
ipv4:
address: 10.1.0.10
tasks:
- set_fact:
ansible_enp4s0: "{{ ansible_enp4s0 }}"
- debug:
msg: "{{ hostvars[inventory_hostname][my_ifc]['ipv4']['address'] }}"
vars:
my_ifc: "{{ 'ansible_' ~ interface_lan }}"
gives (abridged)
msg: 10.1.0.10
Note: set_fact is needed in the example to put the dictionary ansible_enp4s0 into the hostvars.
The indirect addressing of variables without hostvars is possible with the lookup plugin vars. For example
- hosts: localhost
vars:
test_eth0: 10.1.0.10
test_eth1: 10.1.0.11
tasks:
- debug:
msg: "{{ item }}: {{ lookup('vars', 'test_' ~ item ) }}"
loop:
- eth0
- eth1
gives (abridged)
msg: 'eth0: 10.1.0.10'
msg: 'eth1: 10.1.0.11'

set path when file exists in Ansible yml code

I'm trying to set a var only when a file exists, here is one of my attempts
---
- hosts: all
tasks:
- stat:
path: '{{ srch_path_new }}/bin/run'
register: result
- vars: srch_path="{{ srch_path_new }}"
when: result.stat.exists
This also didn't work
- vars: srch_path:"{{ srch_path_new }}"
The task you are looking for is called set_fact: and is the mechanism ansible uses to declare arbitrary "host variables", sometimes called "hostvars", or (also confusingly) "facts"
The syntax would be:
- set_fact:
srch_path: "{{ srch_path_new }}"
when: result.stat.exists
Also, while vars: is a legal keyword on a Task, its syntax is the same as set_fact: (or the vars: on the playbook): a yaml dictionary, not a key:value pair as you had. For example:
- debug:
msg: hello, {{ friend }}
vars:
friend: Jane Doe
and be aware that vars: on a task exist only for that task

Resources