Running a playbook on multiple host groups one at a time - ansible

I want to run a playbook containing some roles on multiple host groups I create dynamically with the group_by module.
I'm able to do it like the example below (ping replacing my actual role).
I was wondering if there is a way to run each group separately in a loop instead of listing all instance ids. I don't want to create a duplicate line with every instance id.
The purpose here is to deploy to one instance in every data center at a time instead of running all with a low serial which takes a long time.
There might be a different way of doing it, I don't want to create static groups in the inventory for each instance_id as well.
---
- hosts: tag_type_edgenode
tasks:
- group_by: key=instance_id_{{instance_id}}
register: dyn_groups
- hosts: instance_id_1
tasks:
- ping:
- hosts: instance_id_2
tasks:
- ping:
- hosts: instance_id_3
tasks:
- ping:
- hosts: instance_id_4
tasks:
- ping:

If you have equal count of hosts in each group, you can use pattern + serial.
Ansible forms host list by pattern moving through groups sequentially. So if you have equal count of hosts, then batches formed by serial will be equal to groups.
In your example, if you have exactly 3 hosts in each group, you can use:
- hosts: instance_id_*
serial: 3
tasks:
- ping:
If you don't mind a bit of Ansible patching, you can modify _get_serialized_batches method.
Add this code just before while len(all_hosts) > 0::
if 'serialize_by_var' in play.get_vars():
param = play.get_vars()['serialize_by_var']
sb = []
def by_param(x):
vrs = x.get_vars()
if param in vrs:
return vrs[param]
else:
return None
s_hosts = sorted(all_hosts,key=by_param)
for k, g in itertools.groupby(s_hosts, by_param):
sb.append(list(g))
display.vv('Serializing by host var "{}": {}'.format(param,sb))
return sb
And you can serialize hosts by any variable like this:
- hosts: tag_type_edgenode
vars:
serialize_by_var: instance_id
tasks:
- ping

There is a simpler method using the size (length) of each group. However, this runs on all members of a group, flowing through the groups sequentially. I think OP was asking for how to act on the first member of each group, which I am also working on figuring out.
- name: "Restore selected devices in model_dc"
hosts: group_1, group_2, group_3, group_4, group_5, group_6, group_7, !excluded
any_errors_fatal: true # Stop entire play if one host fails
gather_facts: no
order: inventory
serial:
- "{{ groups['group_1'] | length }}" # The number of hosts for first batch
- "{{ groups['group_2'] | length }}" # The number of hosts for second batch
- "{{ groups['group_3'] | length }}"
- "{{ groups['group_4'] | length }}"
- "{{ groups['group_5'] | length }}"
- "{{ groups['group_6'] | length }}"
- "{{ groups['group_7'] | length }}" # The number of hosts for every batch until the end.

Building off of Konstantin's idea, you can do something like this using aliases and a list of patterns:
---
- hosts: "*-server-batch-1,*-servers-batch-2,*-server-batch-3"
serial: 3
...
...
[london]
london-server-batch-1 ansible_host=server1.london.com
london-server-batch-2 ansible_host=server2.london.com
london-server-batch-3 ansible_host=server3.london.com
[tokyo]
tokyo-server-batch-1 ansible_host=server1.tokyo.com
tokyo-server-batch-2 ansible_host=server2.tokyo.com
tokyo-server-batch-3 ansible_host=server3.tokyo.com

Related

Remove empty element from list in ansible

input:
- tests_to_run
tasks:
modify_user_input:
< Remove the empty elements from tests_to_run>
Have been searching for hours. Does anyone know a way to filter the input, so I can manipulate the tests_to_run list, so it does not have any empty elements.
Sample input and output
Sample tests_to_run = ['test_a','','test_b','test_c','yrls']
Expected variable to be used in later tasks test_to_run_clean = ['test_a','test_b','test_c','yrls']
To remove the empty element from the list:
---
- name: Sample playbook
connection: local
gather_facts: false
hosts: localhost
vars:
mylist:
- abc
- def
-
- jkl
tasks:
- set_fact:
mylist: "{{ mylist|select()|list }}"

How can I resolve an inventory member who has a certain attribute?

I need to pass a specific host from my inventory as a parameter into a role. The host is part of a group but is demarcated by a variable that none of the other hosts have.
snippet of: hosts.yml
dbservers:
hosts:
pg01:
ansible_host: pg01.domain.com
master_slave: master
pg02:
ansible_host: pg02.domain.com
master_slave: slave
I want to be able to resolve pg01 based on the fact that the variable master_slave is set to 'master' such that I can call into a role like this:
- name: Do something
include_role:
name: a.database.role.to.run.on.master
vars:
master_database_host: {{ something that resolves to pg01 }}
How can I resolve the appropriate host from inventory?
You can use a mix of filters to extract the host you need:
tasks:
- debug:
msg: '{{groups["group_name"] | map("extract", hostvars) | selectattr("master_slave", "equalto", "master") | map(attribute="inventory_hostname") | list}}'
Step by step:
groups["group_name"] is a list of all the hosts in the group group_name.
map("extract", hostvars) takes hostvars, a dictionary mapping the host to their variables, and extracts the hosts that are in group_name (i.e. groups["group_name"]). This results in a list containing the hosts in group_name mapped to their variables.
selectattr("master_slave", "equalto", "master") selects all hosts who have an attribute master_slave that equals to master. This result in a list with all the hosts that are masters mapped to their variables.
map(attribute="inventory_hostname") takes a list as input and returns the inventory_hostname attribute of every item. This result in a list with all the hosts that are masters.
The play below (with json_query)
- hosts: dbservers
tasks:
- set_fact:
master_database_host: "{{ groups['dbservers']|
map('extract',hostvars)|
list|
json_query('[?master_slave==`master`].inventory_hostname')|
first }}"
- debug:
var: master_database_host
gives
ok: [pg02] => {
"master_database_host": "pg01"
}
ok: [pg01] => {
"master_database_host": "pg01"
}
You can use if else condition in vars to assign the values.
So your play should be Something like this.
- name: Do something
include_role:
name: a.database.role.to.run.on.master
vars:
master_database_host: "{{ hostvars['pg01']['ansible_host'] if \"{{ hostvars['pg01']['master_slave'] }}\" == 'master' else 'default value goes here'}}"
Make sure to use proper escaping to make the conditional statements work.
The reason this works is Since ansible internally uses python to do stuff it is a way to use ternary operator in python.
You could also generate dynamic groups based on the master/slave status:
---
- name: Play to create dynamic groups
hosts: dbservers
gather_facts: false
tasks:
- name: Create groups based on variable master_slave
group_by:
key: "database-{{ hostvars[inventory_hostname]['master_slave'] }}"
- name: Play to use the dynamic group database-master
hosts: database-master
gather_facts: false
tasks:
- name: Show hosts in group
debug:
msg: "This is {{ inventory_hostname }} from the dynamic database-master group."
The first play uses all dbservers and creates the dynamic groups based on the master_slave variable.
The dynamic groups are:
database-master containing pg01
database-slave containing pg02
The second play uses one of the dynamic created groups.
To use group_by the used variable has to exist for all hosts used.
This concept works best on automatic variable gathered by ansibles setup e.g. ansible_distribution to create dynamic groups based on the Distribution (Debian, Redhat, Ubuntu ...) or distribution versions using ansible_distribution_version.

Ansible: restrict list to unique elements

I'm writing a playbook to manage users on our servers defined in users.yml:
---
users:
- login: ab
full_login: abcdef
name: Aaaa Bbbb,,,
admin_on: server1, server2
regular_on: server3
active: yes
I would like to include some protection from a situation when there will be two different users with the same login defined. The playbook looks like this:
---
- name: Provision users on servers
hosts: all
remote_user: morty
become: yes
vars_files:
- users.yml
tasks:
- name: Create users
user:
name: "{{ item.login }}"
comment: "{{ item.name }}"
update_password: on_create
with_items:
- "{{ users }}"
when: ???
What is the recommended course of action? Should I create another list that will keep track of already processed logins or is there a better way?
Use assertion task to make preflight checks at the very beginning of your playbook:
- name: Safety check
assert:
that: >
users | map(attribute='login') | list | count
==
users | map(attribute='login') | list | unique | count
In this case we check that the length of original list of logins is the same as of list with unique logins.

How can I pass a parameter to a role in Ansible from inventory file?

I have a set of roles that are all unique and use a common role that is pulled in via a dependency to perform a bunch of the same actions that all these other roles need to have done. I need to be able to pass into a specific role a parameter to say in this case pull a docker image from a registry, in that other case save the image out and do some other things. Variables cause issues as they are explicit for a host and I might have several roles per host. How can I structure my ansible playbook to do this?
Example:
I have a common role that is pulled into other roles as a dependency:
---
- name: Included Playbook - What vars do I see
debug:
msg: "Name Pull: {{ imagename }}"
- name: Local Running
debug:
msg: "Local option selected"
when: localimage == "true"
- name: Not local
debug:
msg: "Not Local remote"
when: localimage == "false"
Then the primary role tasks\main.yml
---
- name: Included Playbook - What vars do I see from Primary
vars:
myname: "{{ imagemainname }}"
debug:
msg: "Name-primary: {{ myname }}"
and its meta\main.yml
---
dependencies:
- { role: image-pull, imagename: "{{ imagemainname }}" }
This is the same for a second role
---
- name: Included Playbook - What vars do I see from Second
vars:
myname: "{{ secondname }}"
debug:
msg: "Name-second: {{ myname }}"
and its meta\main.yml
---
dependencies:
- { role: image-pull, imagename: "{{ secondname }}" }
My main playbook calls both primary and second roles and the role specific vars works fine.
---
- name: Master
hosts: imagemaster
remote_user: root
vars:
imagemainname: "Top Dog"
roles:
- image-master
- name: Second
hosts: second
remote_user: root
vars:
imagemainname: "Second Dog"
roles:
- second
What doesn't work is when I want to state the do option a or b in the "pulled" role.
If my inventory file looks like this:
[imagemaster]
127.0.0.1
[imagemaster:vars]
localimage=false
[second]
127.0.0.1
[second:vars]
localimage=true
It doesn't work as whatever is the last entry for localimage is what all roles will use.
What can I do to pass in something from the inventory/host_vars/etc that means my playbook doesn't change for every iteration in this setup?
If you plan to apply primary and secondary role to the same host (as in your example (127.0.0.1), then you have no options:
Within any section, redefining a var will overwrite the previous instance. If multiple groups have the same variable, the last one loaded wins. If you define a variable twice in a play’s vars: section, the 2nd one wins.
(from the docs)
If you plan to apply them to different hosts, then test is properly, e.g.:
[imagemaster]
127.0.0.1
[imagemaster:vars]
localimage=false
[second]
127.0.1.1
[second:vars]
localimage=true
In this scenario, when role (primary/secondary) is applied to imagemaster group, then localimage=false; when applied to second group – localimage=true.

Ansible. How to choose N hosts from group

Assume that a group has 10 hosts.
How to run playbook on N hosts from 10. N is arbitrary value from 1 to 10.
Example:
- hosts: groups['group_name'][1:3] it works.
But it doesn't work if instead of 3 I use variable, like this
- hosts: groups['group_name'][1:N]
It could be random N, first N, last N what ever.
Thank you.
This work absolutely fine in ansible 2.1:
---
- hosts: all
gather_facts: no
tasks:
- group_by: key=limited_selection
when: play_hosts.index(inventory_hostname) < max_index | int
- hosts: limited_selection
gather_facts: no
tasks:
- debug: msg="I'm in the limited selection group!"
Exec like this: ansible-playbook -e max_index=3 playbook.yml or define max_index somewhere else.

Resources