Filter data using JSON query in Ansible to extract data from an ansible_fact - linux

I have created this playbook to extract all mount points starting with any element in the variable whitelist matching the type= ext2, ext3, ext4.
The problem is that I can get all mount_points but I am not able to filter the result with the variables.
- hosts: all
gather_facts: True
become: True
vars:
whitelist:
- /boot
- /home
- /opt
- /var
- /bin
- /usr
tasks:
- name: extract mount_points
set_fact:
mount_point: "{{ansible_facts.mounts | selectattr('fstype', 'in', ['ext2', 'ext3', 'ext4']) | map(attribute='mount') | list }}"
- debug:
var: mount_point
vars:
query: "[?starts_with(mount, whitelist)].mount"
When I execute the playbook I get this
ok: [ansible#controller] => {
"mount_point": [
"/",
"/boot",
"/tmp",
"/home",
"/var",
"/var/opt",
"/var/tmp",
"/var/log",
"/var/log/audit",
"/opt",
]
}
/tmp is included which means that query: "[?starts_with(mount, whitelist)].mount" was skipped and I don't know how to achieve the playbook goal.

You don't really need a json query here IMO. An easy way is to filter the list with match and construct a regex containing all possible prefixes:
- name: show my filtered mountpoints:
vars:
start_regex: "{{ whitelist | map('regex_escape') | join('|') }}"
degug:
msg: "{{ {{ansible_facts.mounts | selectattr('fstype', 'in', ['ext2', 'ext3', 'ext4'])
| map(attribute='mount') | select('match', start_regex) | list }}"

While testing i used a filter expression within the json_query string which achieved the same goal for me Query String with Dynamic Variable
.
In this example the filter expression is used with a built-in function, ?starts_with. This function provides a boolean result, based on a match with a search string, on any element within the fstype and mount_point using a dynamic variable {{whitelist}}.
- name: find mount_points
debug:
msg: "{{ ansible_mounts | to_json | from_json | json_query(mount_point) }}"
vars:
mount_point: " #[?starts_with(fstype, 'ext')] | #[?starts_with(mount, '{{item}}')].mount"
loop: "{{whitelist}}"
this is the output
ok: [ansible#ansible1] => (item=/var) => {
"msg": [
"/var",
"/var/opt",
"/var/tmp",
"/var/log",
"/var/log/audit"
}
ok: [ansible#ansible1] => (item=/usr) => {
"msg": []
}
ok: [ansible#ansible1] => (item=/home) => {
"msg": [
"/home"
>>>

Related

Ansible loop over a list to define a dynamic dictionary

I am trying to define a dictionary FACTS_VAR in which the key must contain the word SUB. Argument_value and the value is true but when I loop over the variable Argument
hosts: all
gather_facts: true
vars:
Argument:
- value1
- value2
tasks:
- name: DEFINING A variable
set_fact:
FACTS_VAR: {'SUB..item': true}
loop: "{{Argument}}"
- debug:
var: FACTS_VAR
I got this result so I don't know what is missing there. I am expecting to get a dictionary like this
FACTS_ENTRY:
SUB.value1: true
SUB.value2: true
TASK [debug] *****************************************************************************************************************
ok: [control] => {
"FACTS_ENTRY": {
"SUB..item": true
}
}
ok: [ansible4] => {
"FACTS_ENTRY": {
"SUB..item": true
}
Create the keys
facts_entry_keys: "{{ ['sub']|product(argument)|map('join','.')|list }}"
gives
facts_entry_keys:
- sub.value1
- sub.value2
Create the dictionary
facts_entry: "{{ dict(facts_entry_keys|product([true])) }}"
gives
facts_entry:
sub.value1: true
sub.value2: true
Example of a complete playbook
- hosts: localhost
vars:
argument:
- value1
- value2
facts_entry_keys: "{{ ['sub']|product(argument)|map('join','.')|list }}"
facts_entry: "{{ dict(facts_entry_keys|product([true])) }}"
tasks:
- debug:
var: facts_entry
From your current description I understand only that you probably like to define variables somehow dynamically.
The following example could give some guidance.
---
- hosts: test
become: false
gather_facts: false
vars:
ARG:
- val1
- val2
tasks:
- name: Dynamically define variable
set_fact:
FACTS_VAR: "{ 'SUB_{{ item }}': true }"
loop: "{{ ARG }}"
- debug:
var: FACTS_VAR
resulting into an output of
TASK [Dynamically define variable] **
ok: [test.example.com] => (item=val1)
ok: [test.example.com] => (item=val2)
TASK [debug] ************************
ok: [test.example.com] =>
FACTS_VAR:
SUB_val2: true
Please take note that according your current description there can be only one value as result.
Further Q&A
Build variable name at play level

Creating a dictionary of Hostnames to IPs with Ansible

Been hitting my head against this problem for three or four days. I'm trying to write an Ansible playbook that creates a Docker Swarm cluster comprised of my four Docker hosts. As part of this, I'm gathering the Ansible hostnames and specific ethernet device IPv4 addresses from each node via Ansible facts and creating a dictionary:
- name: "Clear swarm_ips dictionary"
set_fact:
swarm_ips: "{{ swarm_ips | default([]) }}"
- name: "Create dictionary with enp42s0 IP and hostname of manager"
set_fact:
swarm_ips: "{{ swarm_ips | combine ({item.key : item.value}) }}"
with_items:
- { 'key': '{{ ansible_facts.hostname | string }}.ip' , 'value': '{{ ansible_facts.enp42s0.ipv4.address | string }}' }
when: (ansible_facts.hostname == "manager")
- name: "Add eth0 IP and hostname of worker[1,2] to swarm_ips"
set_fact:
swarm_ips: "{{ swarm_ips | combine ({item.key : item.value}) }}"
with_items:
- { 'key': '{{ ansible_facts.hostname | string }}.ip' , 'value': '{{ ansible_facts.eth0.ipv4.address | string }}' }
when: (ansible_facts.hostname == "worker1") or
(ansible_facts.hostname == "worker2")
- name: "Add eth0 IP and hostname of worker0 to swarm_ips dictionary"
set_fact:
swarm_ips: "{{ swarm_ips | combine ({item.key : item.value}) }}"
with_items:
- { 'key': '{{ ansible_facts.hostname | string }}.ip' , 'value': '{{ ansible_facts.br0.ipv4.address | string }}' }
when: (ansible_facts.hostname == "worker0")
- name: "Echo eth0 IP for all nodes"
debug:
var: swarm_ips
I expect the output of the last step in the play to be something like what follows:
ok: {
"swarm_ips": {
"manager.ip": "10.0.1.203"
"worker0.ip": "10.0.1.42"
"worker1.ip": "10.0.1.201"
"worker2.ip": "10.0.1.252"
}
}
Instead I get
ok: [manager.local] => {
"swarm_ips": {
"manager.ip": "10.0.1.203"
}
}
ok: [worker0.local] => {
"swarm_ips": {
"worker0.ip": "10.0.1.42"
}
}
ok: [worker1.local] => {
"swarm_ips": {
"worker1.ip": "10.0.1.201"
}
}
ok: [worker2.local] => {
"swarm_ips": {
"worker2.ip": "10.0.1.252"
}
}
There's likely a way I could run some or all of this on localhost and use hostvars to get information about the cluster hosts, but I think this should also work as I expect. Any idea what am I missing, either specifically or conceptually?

Skip current host and print the rest in a config file ansible

Folks, I'm trying to achieve the below while running my task to edit an XML
Requirement:
Skip current host in which config file is created and print the rest of host from the inventory group to the config file
<ManagerNode ip = "**node IP**" port = "**node port**"/> <haNodes>IP2:port,IP3:port</haNodes> <!-- Comma Seperated Node IPs with port, ,Except the Same Node -->
Can anyone help with achieving this goal?
Q: "Skip current host ... and print the rest of hosts from the inventory group."
A: Create a list of all IPs and use filter difference to remove the current IP. For example the inventory
shell> cat hosts
[ha]
test_01 IP=10.1.0.11
test_02 IP=10.1.0.12
test_03 IP=10.1.0.13
[ha:vars]
port=4567
and the playbook
shell> cat playbook.yml
- hosts: ha
tasks:
- set_fact:
all_IP: "{{ groups.ha|map('extract', hostvars, 'IP')|list }}"
run_once: true
- debug:
msg: "{{ all_IP|difference([IP])|
product([port])|
map('join', ':')|
list }}"
give (abridged)
shell> ansible-playbook -i hosts playbook.yml
ok: [test_01] =>
msg:
- 10.1.0.12:4567
- 10.1.0.13:4567
ok: [test_02] =>
msg:
- 10.1.0.11:4567
- 10.1.0.13:4567
ok: [test_03] =>
msg:
- 10.1.0.11:4567
- 10.1.0.12:4567
Limit the play to test_01 gives abridged
shell> ansible-playbook -i hosts -l test_01 playbook.yml
ok: [test_01] =>
msg:
- 10.1.0.12:4567
- 10.1.0.13:4567
defined a variable
tg_hosts: : "{{ groups['tgzone']|map('extract',hostvars,'ansible_host')|list }}"
used template as:
{{ tg_hosts | difference([ansible_host])| list | join(':port,') + ':port' }}

Print json output in Ansible and store in a list?

Ansible playbook:
---
- task:-
- code goes here
- name: Trying to get instance Private IP from ASG
ec2_instance_facts:
instance_ids:
- "{{ item }}"
with_items: "{{ INSTANCE_IDS_FROM_ASG }}"
register: instance_ids_result
- set_fact:
msg: "{{ instance_ids_result | json_query('results[*].instances[*].network_interfaces[*].private_ip_address') }} "
- debug: var=msg
I have the output as follows:
ok: [localhost] => {
"msg": [
[
"172.31.144.74"
],
[
"172.31.147.69"
]
]
}
But, I would need the output in a list as ["172.31.144.74", "172.31.147.69"] or "172.31.147.69" "172.31.147.69".
What is the best way to print it that way?
You could flatten your list using the filter
- set_fact:
msg: "{{ instance_ids_result | json_query('results[*].instances[*].network_interfaces[*].private_ip_address') | flatten }} "

Ansible changes variable values when set

I have a series of variables set. Call them Hosts and Inthosts. Each has an appropriate value set, as seen in the debug output. When I try to assign the value of inthosts to hosts, it does not actually make them the same, it bolloxes it up changing the double quotes to single quotes and putting a "u" in front of each "item". It there a way to force Ansible to actually do a literal equals in this case without parsing the text? The text should just be treated as a string. In this case the "modified" value is being output to a file, and the change breaks things.
The plan was to use the default hosts, and override it with inthosts if the server in question should be using a different set of servers.
Default Variables Set
filebeat_kafka_hosts: '["x.compute-1.amazonaws.com:9093", "y.compute-1.amazonaws.com:9093"]'
filebeat_kafka_inthosts: '["x.compute-1.amazonaws.com:9093", "y.compute-1.amazonaws.com:9093", "z.compute-1.amazonaws.com:9093"]'
Ansible Code
- debug:
msg: "Hosts {{ filebeat_kafka_hosts }} "
- debug:
msg: "IntHosts {{ filebeat_kafka_inthosts }} "
- set_fact:
filebeat_kafka_hosts="{{ filebeat_kafka_inthosts }}"
- debug:
msg: "Inthosts -> hosts {{ filebeat_kafka_hosts }} "
Output (edited)
"msg": "Hosts [\"x.compute-1.amazonaws.com:9093\", \"y.compute-1.amazonaws.com:9093\"] " |
"msg": "IntHosts [\"x.compute-1.amazonaws.com:9093\", \"y.compute-1.amazonaws.com:9093\", \"z.compute-1.amazonaws.com:9093\"] "
set {"ansible_facts": {"filebeat_kafka_hosts": ["x.compute-1.amazonaws.com:9093", "y.compute-1.amazonaws.com:9093", "z.compute-1.amazonaws.com:9093"]}, "changed": false}
"msg": "Inthosts -> hosts [u'x.compute-1.amazonaws.com:9093', u'y.compute-1.amazonaws.com:9093', u'z.compute-1.amazonaws.com:9093'] "
Ansible is interpreting filebeat_kafka_inthosts and filebeat_kafka_hosts as lists. That gives you the 'u' characters before each item in your debug. The tasks below
- debug:
msg: "{{ item }}"
with_items: "{{ filebeat_kafka_hosts }}"
- debug:
msg: "{{ item }}"
with_items: "{{ filebeat_kafka_inthosts }}"
Would give you
TASK [debug] *******************************************************************
ok: [127.0.0.1] => (item=y.compute-1.amazonaws.com:9093) => {
"item": "y.compute-1.amazonaws.com:9093",
"msg": "y.compute-1.amazonaws.com:9093"
}
ok: [127.0.0.1] => (item=x.compute-1.amazonaws.com:9093) => {
"item": "x.compute-1.amazonaws.com:9093",
"msg": "x.compute-1.amazonaws.com:9093"
}
TASK [debug] *******************************************************************
ok: [127.0.0.1] => (item=x.compute-1.amazonaws.com:9093) => {
"item": "x.compute-1.amazonaws.com:9093",
"msg": "x.compute-1.amazonaws.com:9093"
}
ok: [127.0.0.1] => (item=y.compute-1.amazonaws.com:9093) => {
"item": "y.compute-1.amazonaws.com:9093",
"msg": "y.compute-1.amazonaws.com:9093"
}
ok: [127.0.0.1] => (item=z.compute-1.amazonaws.com:9093) => {
"item": "z.compute-1.amazonaws.com:9093",
"msg": "z.compute-1.amazonaws.com:9093"
}
Since your writing this line to a file, you shouldn't have to worry about it too much. The 'u' character is a side effect of the debug module. Writing the variable to a file would give the same result (although with single quotes instead of double).
- lineinfile:
path: some_file
line: "{{ filebeat_kafka_hosts }}"
some_file
['x.compute-1.amazonaws.com:9093', 'y.compute-1.amazonaws.com:9093', 'z.compute-1.amazonaws.com:9093']
If you really need double quotes, you can use the to_json filter
- lineinfile:
path: some_file
line: "{{ filebeat_kafka_hosts | to_json }}"
some_file
["x.compute-1.amazonaws.com:9093", "y.compute-1.amazonaws.com:9093", "z.compute-1.amazonaws.com:9093"]
Seems that casting the variable as a string also works. Thanks for the advice!

Resources