Customize dynamic inventory - ansible

Groups configured in Ansible inventory:
Group_A has 30 servers
Group_B has 40 Servers
Group_C has 15 Servers
I want to take 10 servers from each group and make a new group without editing the inventory manually.
These 10 servers is a variable that can change dynamically. If that works I got another question what if the inventory itself is dynamic
[Group_C]
server-1
server-2
server-3
...
server-10
''' New group created From 3 grouped servers now will be used in a playbook '''

(ansible 2.8.3)
If the inventory is dynamic we don't know the hosts. Let's assume we could choose any of them. Let's create the list of the selected hosts first and then loop add_hosts. With the inventory
[Group_A]
A-0
A-1
..
A-29
[Group_B]
B-0
B-1
..
B-39
[Group_C]
C-0
C-1
..
C-14
the plays below
- name: Create Group_X
hosts: localhost
vars:
no_of_servers: 2
my_groups:
- Group_A
- Group_B
- Group_C
tasks:
- set_fact:
my_list: "{{ my_list|default([]) +
groups[item][0:no_of_servers] }}"
loop: "{{ my_groups }}"
- add_host:
name: "{{ item }}"
groups: Group_X
loop: "{{ my_list }}"
- debug:
msg: "{{ groups['Group_X'] }}"
- name: Use Group_X
hosts: Group_X
gather_facts: false
tasks:
- debug:
msg: "{{ inventory_hostname }} is member of {{ group_names }}"
run_once: true
give
ok: [localhost] => {
"msg": [
"A-0",
"A-1",
"B-0",
"B-1",
"C-0",
"C-1"
]
}
ok: [A-0] => {
"msg": "A-0 is member of [u'Group_A', u'Group_X']"
}
Random choice.
It is possible to make the selection of the hosts random with the simple plugin below
$ cat filter_plugins/list_methods.py
import random
def list_sample(l,n):
return random.sample(l,n)
class FilterModule(object):
def filters(self):
return {
'list_sample' : list_sample
}
With the modification below
- set_fact:
my_list: '{{ my_list|default([]) +
groups[item]|list_sample(no_of_servers) }}'
the plays give for example
ok: [localhost] => {
"msg": [
"A-8",
"A-9",
"B-8",
"B-2",
"C-2",
"C-5"
]
}
ok: [A-8] => {
"msg": "A-8 is member of [u'Group_A', u'Group_X']"
}

Related

Unique values from ansible output dict's

I have some servers with a lot of wordpress instances, who I ask them what versions they have.
- name: CONTADOR WP VERSIONES
shell: mycommand
register: wp_versions
- debug: msg: "{{ wp_versions.stdout_lines }}"
For example:
TASK [debug] *********************************************************************
ok: [server1] => {
"msg": [
"5.1.13"
]
}
ok: [server2] => {
"msg": [
"5.1.12",
"5.1.13"
]
}
ok: [server3] => {
"msg": [
"5.1.10",
"5.1.13",
]
}
I need to list a unique values like this:
"msg": [
"5.1.10",
"5.1.12",
"5.1.13",
]
I have tried all that i found but nothing works as I want.
Thanks
Use special variable ansible_play_hosts and extract the variables from the hostvars
- set_fact:
all_vers: "{{ ansible_play_hosts|
map('extract', hostvars, ['wp_versions', 'stdout_lines'])|
flatten|unique }}"
run_once: true
gives
all_vers:
- 5.1.13
- 5.1.12
- 5.1.10
You could do something like this:
- hosts: all
gather_facts: false
tasks:
- name: CONTADOR WP VERSIONES
shell: mycommand
register: wp_versions
- hosts: localhost
gather_facts: false
tasks:
# This tasks builds a flattened list of all the
# wp_versions.stdout_lines values collected from your hosts.
- name: Collect wp_versions information
set_fact:
all_wp_versions_pre: "{{ all_wp_versions_pre + hostvars[item].wp_versions.stdout_lines }}"
loop: "{{ groups.all }}"
vars:
all_wp_versions_pre: []
# Here we use the `unique` filter to produce a list of
# unique versions.
- name: Set all_wp_versions fact
set_fact:
all_wp_versions: "{{ all_wp_versions_pre|unique }}"
- debug:
var: all_wp_versions
Given you examples, this would produce the following output:
TASK [debug] ********************************************************************************************
ok: [localhost] => {
"all_wp_versions": [
"5.1.13",
"5.1.12",
"5.1.10"
]
}

How do i count task success/failure in ansible?

I am using ansible to set up a distributed application. i'm installing nodes, and then creating virtual interfaces, and cannot have more virtual interfaces than nodes. therefore, if i install on X nodes, and Y nodes fail, I need to check there are no more that (X-Y) virtual interfaces.
Is there a way to get, for a specific task, a numerical value of how many nodes succeeded/failed, so i can later use it to check the number of virtual interfaces?
Use ansible-runner. See Runner Artifact Job Events and "stats" in particular. For example ansible-runner and the playbook
shell> cat private3/project/test.yml
- hosts: test_01:test_02
gather_facts: false
tasks:
- debug:
var: inventory_hostname
- fail:
msg: Fail test_02
when: inventory_hostname == 'test_02'
shell> ansible-runner -p test.yml -i ID01 run private3
...
ASK [fail] ********************************************************************
skipping: [test_01]
fatal: [test_02]: FAILED! => {"changed": false, "msg": "Fail test_02"}
...
created records in the directory private3/artifacts/ID01/job_events/. I'm not aware of any publicly available tool to analyze the events. I've created a playbook that displays failed tasks
shell> cat pb.yml
- hosts: localhost
gather_facts: false
vars:
events_dir: private3/artifacts/ID01/job_events
tasks:
- find:
paths: "{{ events_dir }}"
register: result
- include_vars:
file: "{{ item }}"
name: "{{ 'my_var_' ~ my_idx }}"
loop: "{{ result.files|json_query('[].path') }}"
loop_control:
index_var: my_idx
label: "{{ my_idx }}"
- set_fact:
my_events: "{{ my_events|default({})|
combine({my_key: lookup('vars', my_key)}) }}"
loop: "{{ range(0, result.matched)|list }}"
loop_control:
index_var: my_idx
vars:
my_key: "{{ 'my_var_' ~ my_idx }}"
- set_fact:
my_list: "{{ my_events|json_query('*.{counter: counter,
event: event,
task: event_data.task_action,
host: event_data.host}') }}"
- debug:
var: item
loop: "{{ my_list|sort(attribute='counter') }}"
loop_control:
label: "{{ item.counter }}"
when: item.event == 'runner_on_failed'
gives
shell> ansible-playbook pb.yml
...
skipping: [localhost] => (item=11)
ok: [localhost] => (item=12) => {
"ansible_loop_var": "item",
"item": {
"counter": 12,
"event": "runner_on_failed",
"host": "test_02",
"task": "fail"
}
}
skipping: [localhost] => (item=13)
...
Feel free to fit the playbook to your needs.

Get facts when var defined in hostvars

ansible 2.9.1
I have inventory:
[group1]
server1 master=yes
server2 master=no
server3 master=no
How get ansible_fqdn with master=yes from server2, server3?
Example:
server2 or server3 facts:
...
master_server: server1
...
I think so, but it did not work:
- name: set fact
set_fact:
master_server: {{ ansible_fqdn }}
when: master == 'yes'
delegate_to: "{{ item }}"
loop: "{{ ansible_play_hosts }}"
UPDATE. RESOLVE
add:
run_once: True
Q: "How to get ansible_fqdn with master=yes from server2, server3?"
A: There are more options. For example, either use selectattr
- hosts: group1
tasks:
- set_fact:
master_server: "{{ (groups.group1|
map('extract', hostvars)|
selectattr('master', 'eq', True)|
list|
first).ansible_fqdn }}"
run_once: true
- debug:
var: master_server
, or use json_query
- hosts: group1
tasks:
- set_fact:
master_server: "{{ groups.group1|
map('extract', hostvars)|
list|
json_query('[?master].ansible_fqdn')|
first }}"
run_once: true
- debug:
var: master_server
Both options give
ok: [test_02] => {
"master_server": "test_01.example.org"
}
ok: [test_01] => {
"master_server": "test_01.example.org"
}
ok: [test_03] => {
"master_server": "test_01.example.org"
}
Inventory used in the examples
$ cat hosts
group1:
hosts:
test_01:
master: yes
ansible_fqdn: test_01.example.org
test_02:
master: no
ansible_fqdn: test_02.example.org
test_03:
master: no
ansible_fqdn: test_03.example.org

Variable value for another variable ansible

Sorry if there are many posts about variables inside variable my use case is different.
Trying to access an element from a variable list "efs_list" based on the index-number of the current host. There are three hosts in the inventory
vars:
efs_list:
- efs1
- efs2
- efs3
sdb_index: "{{ groups['all'].index(inventory_hostname) }}"
The values should be as follows
host1- efs1
host2- efs2
host3- efs3
Tried accessing it through efs_list.{{ sdb_index }}
for - debug: var=efs_list.{{ sdb_index }} the output is as intended
ok: [10.251.0.174] => {
"efs_list.0": "efs1"
}
ok: [10.251.0.207] => {
"efs_list.1": "efs2"
}
ok: [10.251.0.151] => {
"efs_list.2": "efs3"
}
But for
- debug:
msg: "{{ efs_list.{{ sdb_index }} }}"
fatal: [10.251.0.174]: FAILED! => {"msg": "template error while templating string: expected name or number. String: {{ efs_list.{{ sdb_index }} }}"}
---
- name: SDB Snapshots Creation
hosts: all
remote_user: "centos"
become: yes
vars:
efs_list:
- efs1
- efs2
- efs3
sdb_index: "{{ groups['all'].index(inventory_hostname) }}"
tasks:
- debug: var=efs_list.{{ sdb_index }}
- debug:
msg: "{{ efs_list.{{ sdb_index }} }}"
- name: Get Filesystem ID
become: false
local_action: command aws efs describe-file-systems --creation-token "{{ efs_list.{{ sdb_index }} }}"
--region us-east-1 --query FileSystems[*].FileSystemId --output text
register: fs_id
It should attribute the element of list to current indexenter code here
extract filter will do the job. The input of the filter must be a list of indices and a container (array in this case). The tasks below
- set_fact:
sdb_index: "{{ [] + [ groups['all'].index(inventory_hostname) ] }}"
- debug:
msg: "{{ sdb_index|map('extract', efs_list)|list }}"
give
ok: [host1] => {
"msg": [
"efs1"
]
}
ok: [host2] => {
"msg": [
"efs2"
]
}
ok: [host3] => {
"msg": [
"efs3"
]
}
If the hosts are not sorted in the inventory it's necessary to sort them in the play
- set_fact:
my_hosts: "{{ groups['all']|sort }}"
- set_fact:
sdb_index: "{{ [] + [ my_hosts.index(inventory_hostname) ] }}"
- debug:
msg: "{{ sdb_index|map('extract', efs_list)|list }}"

Run task only if host does not belong to a mare than one group

Run task only if host does not belong more than one group:
eg:
[web_1]
[one_web_2]
[server_3]
I whant to mach on first two groups:
"'web' not in group_names" ->> first and second
?
There is the Special Variable group_names
group_names List of groups the current host is part of
The play below shows how many from the selected groups (my_groups) the host is a member of.
- hosts: all
gather_facts: no
vars:
my_groups: [ 'web_1', 'web_2', 'web_3' ]
tasks:
- debug:
msg: "{{ inventory_hostname }} is member of
{{ my_groups|intersect(group_names)|length }} group(s)."
Let's have the inventory below
[test]
test_01
test_02
test_03
[web_1]
test_01
test_02
[web_2]
test_01
test_02
[web_3]
test_03
The play gives
ok: [test_01] => {
"msg": "test_01 is member of 2 group(s)."
}
ok: [test_02] => {
"msg": "test_02 is member of 2 group(s)."
}
ok: [test_03] => {
"msg": "test_03 is member of 1 group(s)."
}
Use the play below to
Run task only if host does not belong more than one group
- hosts: all
gather_facts: no
vars:
my_groups: [ 'web_1', 'web_2', 'web_3' ]
tasks:
- fail:
msg: "{{ inventory_hostname }} is member of
{{ my_groups|intersect(group_names)|length }} groups.
Play failed."
when: my_groups|intersect(group_names)|length != 1
- debug:
msg: "{{ inventory_hostname }} is member of
{{ my_groups|intersect(group_names)|length }} group.
Play continues."

Resources