Specifying multiple default groups as hosts in an Ansible playbook - ansible

I'm looking for a way to specify multiple default groups as hosts in an Ansible playbook. I've been using this method:
- name: Do things on hosts
hosts: "{{ specific_hosts | default('development') }}"
tasks: # do things on hosts
However I'm trying to avoid specifying hosts manually (it is error-prone), and the default hosts are inadequate if I want to run the same tasks against multiple groups of servers (for instance, development and QA).
I don't know if this is possible in a playbook:
- name: Do things on hosts
hosts: "{{ specific_hosts | default('development') && default('qa') }}"
I don't know if this is possible in an inventory:
[development]
1.2.3.4
[qa]
2.3.4.5
[dev_plus_qa]
development
qa
Creating multiple redundant tasks is undesirable as well - I would like to avoid forcing users to specify specific_qa_hosts (for instance) and I would like to avoid code repitition:
- name: Do things on DEV
hosts: "{{ specific_hosts | default('development') }}"
- name: Do things on QA
hosts: "{{ specific_hosts | default('qa') }}"
Is there any elegant way of accomplishing this?

This is indeed possible and is described in the common patterns targeting hosts and groups documentation of Ansible.
So targeting two groups is as simple as hosts: group1:group2, and, so, your playbook becomes:
- name: Do things on hosts
hosts: "{{ specific_hosts | default('development:qa') }}"
And if you rather want to achieve this via the inventory, this is also possible, as described in the documentation examples:
So in your case, it would be:
[development]
1.2.3.4
[qa]
2.3.4.5
[dev_plus_qa:children]
development
qa
And then, as a playbook:
- name: Do things on hosts
hosts: "{{ specific_hosts | default('dev_plus_qa') }}"

Related

Issue passing variable to Ansible playbook for hosts: with double quotes

I'm trying to write a playbook that kicks off role playbooks and pass a list of hosts to it. The "master" playbook has some load balancing logic in it that I don't want to repeat in every role playbook and can't put into site.yml.
inventory.yml
[webservers]
Web1
Web2
Web3
Web4
master.yml
---
- name: Split Inventory into Odd/Even
hosts: all
gather_facts: false
tasks:
- name: Set Group Odd
set_fact:
group_type: "odd"
when: (inventory_hostname.split(".")[0])[-1] | int is odd
- name: Set Group Even
set_fact:
group_type: "even"
when: (inventory_hostname.split(".")[0])[-1] | int is even
- name: Make new groups "odd" or "even"
group_by:
key: "{{ group_type }}"
- name: Perform Roles on Odd
include: webservers.yml hosts={{ groups['odd'] | join(' ')}}
- name: Perform Roles on Even
include: webservers.yml hosts={{ groups['even'] | join(' ')}}
webservers.yml
- name: Perform Tasks on Webservers
hosts: webservers:&"{{ hosts | replace('\"','')}}"
roles:
- pause
The join(' ') flattens the list of hosts into a string with a space separating each one. When I run the playbook it passes the list of hosts to webservers.yml, however it adds double quotes to the beginning and end, causing webservers.yml to do nothing since no hosts match. I would assume the replace('\"','') would remove the quotes around the string but doesn't seem to be the case. Here's an example output from webservers.yml:
[WARNING]: Could not match supplied host pattern, ignoring: Web4"
[WARNING]: Could not match supplied host pattern, ignoring: "Web2
Any ideas? Does hosts: handle filtering differently?
I feel that you use a role and a play in a wrong way. When you do tasks you should not change list of hosts this task or role is been executed upon. Basically, only play (a thing with 'hosts: ..., tasks: ..., roles: ...') can control where to run.
There are few exceptions, f.e. you can play with delegation and so on. But for your case any attempt to use tasks or roles to control list of the host will only bring misery and hate (toward yourself, toward ansible, etc).
To do it right, just add yet another play in to your playbook (playbook is a list of plays).
Here is your code, slightly modified.
---
- name: Split Inventory into Odd/Even
hosts: all
gather_facts: false
tasks:
- name: Set Group Odd
set_fact:
group_type: "odd"
when: (inventory_hostname.split(".")[0])[-1] | int is odd
- name: Set Group Even
set_fact:
group_type: "even"
when: (inventory_hostname.split(".")[0])[-1] | int is even
- name: Make new groups "odd" or "even"
group_by:
key: "{{ group_type }}"
- name: Doing odd things
hosts: odd
gather_facts: false
tasks:
- name: Perform Roles
include: webservers.yml
- name: Doing even things
hosts: even
gather_facts: false
tasks:
- name: Perform Roles
include: webservers.yml
You can see, I've just assigned a playbook to two groups ('odd' and 'even'). Dynamic groups are preserved between plays in a playbook, and they are no different from any other group in this matter.
P.S. Do not use 'include', use 'import_tasks' (includes are dangerous in newer versions of ansible, avoid them if you could.).

Filter hosts using a variable from with_items in ansible

I have the following set up for Ansible, and I would like to parameterize a filter that will loop, and filter out specific hosts.
- name: run on hosts
hosts: "{{ item }}"
roles:
- directory/role-name
with_items:
- us-east-1a
- us-east-1b
- us-east-1c
The result would be that the role called role-name would be first run on us-east-1a hosts, then us-east-1b... etc.
The above simple errors out with
ERROR! 'with_items' is not a valid attribute for a Play
Is there a way to accomplish what I am trying to do, which is chunking my host list into groups, and running the same role against them, one at a time?
The following achieves the result I am looking for, but is clunky, and not dynamic in length.
- name: run on us-east-1a
hosts: "us-east-1a"
roles:
- my-role
- name: run on us-east-1b
hosts: "us-east-1b"
roles:
- my-role
- name: run on us-east-1c
hosts: "us-east-1c"
roles:
- my-role
I think the only way to (1) have a common code and (2) serialise play execution per group of hosts (with targets inside a group running in parallel) would be to split your playbook into two:
playbook-main.yml
---
- import_playbook: playbook-sub.yml
vars:
host_group_to_run: us-east-1a
- import_playbook: playbook-sub.yml
vars:
host_group_to_run: us-east-1b
- import_playbook: playbook-sub.yml
vars:
host_group_to_run: us-east-1c
playbook-sub.yml
- hosts: "{{ host_group_to_run }}"
roles:
- my-role
# other common code
If you wanted to serialise per host, then there is a serial declaration that might be used in conjunction with this suggestion, but despite your comments and edit, it's unclear because once you refer to us-east-1a as a "host" in singular form, other times as a "group of hosts" or an "availability zone".
Will host patterns do the job?:
- name: run on us-east-1a
hosts: us-east-1a,us-east-1b,us-east-1c
roles:
- my-role
Update: #techraf has opened my eyes with his comment – host pattern alone will not do the job.
It will just concatenate all hosts from all groups.
But in a predictable way, which in some cases can be used to iterate hosts in every group separately.
See this answer for details.

Ansible first hostname of groups

I am not sure on how to find the first ansible hostname from group_names. Could you kindly advise me on how to do it?
hosts
[webservers]
server1
server2
server3
[webserver-el7]
server4
server5
server6
And i have 2 different playbook for each host groups
playbook1.yml
- name: deploy app
hosts: webservers
serial: 8
roles:
- roles1
playbook2.yml
- name: deploy app
hosts: webservers-el7
serial: 8
roles:
- roles1
the problem is that i have delegate task to first host of each group. previously i only used webservers group, so it was much easier by using the task below
- name: syncing web files to {{ version_dir }}
synchronize:
src: "{{ build_dir }}"
dest: "{{ version_dir }}"
rsync_timeout: 60
delegate_to: "{{ groups.webservers | first }}"
If i have 2 different group_names, how can i select the first one of each group? so it can be more dynamic
If you want the first host of current play to be a kind of master host to sync from, I'd recommend another approach: use one of play_hosts or ansible_play_hosts (depending on your Ansible version) variables. See magic variables.
Like delegate_to: "{{ play_hosts | first }}".
The thing is when you say hosts: webservers-el7 to Ansible webservers-el7 is a pattern here. Ansible search for hosts to match this pattern and feed them into Play. You may have written webservers-el* as well. So inside Play you don't have any variable that will tell you "I'm running this Play on hosts from group webserver-el7...". You may only make some guess work analyzing group_names and groups magic variables. But this become clumsy when you have one host in several groups.
For hosts in single group only, you may try: groups[group_names | first] | first
To get any element from group, use group[group_name][0...n].
This will get the first element from the group.
- debug: msg="{{ groups['group_name'][0] }}"

Ansible run the same role multiple times with different vars file included

I have a role which I would like to run multiple times with different vars files, I am currently doing the following:
- hosts: localhost
pre_tasks:
include_vars: "vars/vars1.yml"
roles:
- my_role
- hosts: localhost
pre_tasks:
include_vars: "vars/vars2.yml"
roles:
- my_role
Is there a less boilerplate way to do this? I know it is possible to parameterise roles but I can't find anything in the ansible documentation regarding running a role multiple times and calling a different include_vars each time.
i wanted to do something similar a while back and i ended up having two groups in my inventory
[group1]
localhost1
[group2]
localhost2
and then in group_vars i had different values. In your case that would be
# file: group_vars/group1/main.yml
include_file: vars/vars1.yml
and
# file: group_vars/group2/main.yml
include_file: vars/vars2.yml
Then, you can modify your playbook to something like this
- hosts: all
pre_tasks:
include_vars: "{{ include_file }}"
roles:
- my_role
and finally, execute your playbook for both groups
ansible-playbook pb.yml -l group1,group2
and it should take care of both instalations

can ansible run multiple playbooks same time?

- name: "API"
hosts: api
vars:
platform: "{{ application.api }}"
vars_files:
- vars/application-vars.yml
tasks:
- include: tasks/application-install.yml
- name: "JOBS"
hosts: jobs 
vars:
platform: "{{ application.jobs }}"
vars_files:
 - vars/application-vars.yml
tasks:
  - include: tasks/application-install.yml
playbook like before described, can I execute this difference tasks on difference hosts in the same time as parallel way?
No sure what do you actually want, but I'd combine it into single play:
- hosts: api:jobs
tasks:
- include: tasks/application-install.yml
And add group vars to inventory:
[api:vars]
platform="{{ application.api }}"
[jobs:vars]
platform="{{ application.jobs }}"
This way you can run your playbook on all hosts at once, and also you can use --limit option to choose api or jobs group only.
you can run more playbooks using "ansible-playbook [OPTIONS] *.yml" command. This will execute all the playbooks NOT IN PARALLEL WAY, but in serial way, so first one playbook and after the execution, another playbook. This command can be helpful if you have many playbooks.
I don't know a way to execute more playbooks in parallel.

Resources