Extracting group_name From hosts - ansible

When I run my Ansible playbook, I define my hosts which looks to a group in my inventory.
$ ansible-playbook -i inv/hosts conf.yml
conf.yml:
- name: Configure QA Nodes
hosts: conf_qa
inv/hosts:
[conf_qa]
confqa1
# confqa2
[conf_prod]
prod1
# prod2
prod3
Is there a way in my Roles (or other elements of the Playbook) where I can back out which group_name (or equivalent) is being used?
I know I could set a variable in group_vars/conf_qa.yml such as qa: true and then reference it later in my Roles
roles/init/tasks/main.yml:
- name: Do this when dealing with the conf_qa group
when: qa == true
But using group_vars/conf_qa.yml seems like an extra intermediary step when I was hoping to reference the host groups more directly. Is there a better way?

You can add the following condition, this is from a playbook I created and I can confirm that it works.
It only runs the task in the servers that belong to that group, the rest of them will appear as "skipped"
- name: create api folder
file:
path: /var/log/api
state: directory
when: inventory_hostname in groups['switch']

Related

Ansible - Avoid duplicates between group and host vars

I'm new at using ansible for server management and I need some help managing users and group's membership definition according to host and hosts-group, with a minimum of duplication and a maximum of scalability.
(25 users/20 groups over 50 hosts, with different "sudo" and "groups membership" at the end).
The idea is to have:
"groups_vars" files defining the users (list or hash) to create on each host of the host group.
"host_vars" files defining users for a specific host. (At the end, I will need nested groups, more than specific host_vars files)
I need these \*_vars files contents to be merged and not to be replaced (I understand how "vars precedence" work) because I want to avoid user declaration duplication.
To achieve this, I used hash syntax in \*_vars files and set hash_behaviour=merge in /etc/ansible/ansible.cfg.
Here are my files:
My inventory:
all:
children:
type_a:
hosts:
vm1:
vm2:
My debugging playbook :
- hosts: type_a
tasks:
- name: Debugging
debug:
msg: "{{ users }}"
group_vars/type_a.yaml :
users:
user1:
name: user1
user2:
name: user2
host_vars/vm1.yaml
users:
user3_vm1_specific:
name: user3_vm1_specific
At the end, I need the 3 users on the "vm1" and only "user1" and "user2" on "vm2" and then I will use the vars for the user creation.
Using the merge option (that will be deprecated in newer version of ansible) is working, but doesn't seem to be a best practice.
I searched here on StackOverflow and on other web sites, and most of the time the solutions are:
to duplicate the user definition
(more than 8 properties for each user and too many hostsgroup: unacceptable.)
to use an other name for the second user list, then to assemble both using {{ user_list1 + user_list2 }}.
Not very scalable if we want to add many nested groups. You will need to add custom named list each time. It also, makes duplicates if "host_vars" and "group_vars" have the same user defined: it does not merge the content, but declares it twice with a different content each time.
My first solution is working, but using a near-deprecated option.
So what are the best practices in managing vars in this kind of situation ? (already have read the ansible documentation about vars but it didn't really helped me).
Also, maybe ansible tower or foreman could solve this problem?
A very simple, easily maintainable, and flexible solution is to put users into one dictionary. For example
shell> cat group_vars/all/users.yml
users:
groups:
type_a:
- user1
- user2
type_b:
- user3
- user4
hosts:
vm1:
- user3_vm1_specific
vm2:
- user4_vm2_specific
Then the task below extracts all group's lists, add the host's list, makes the items unique, and sort it
shell> cat playbook.yml
- hosts: type_a
tasks:
- set_fact:
my_users: "{{ (group_names|
map('extract', users.groups)|list +
users.hosts[inventory_hostname]|default([]))|
flatten|unique|sort }}"
- debug:
var: my_users
gives
shell> ansible-playbook playbook.yml
PLAY [type_a] ****
TASK [debug] ****
ok: [vm1] =>
my_users:
- user1
- user2
- user3_vm1_specific
ok: [vm2] =>
my_users:
- user1
- user2
- user4_vm2_specific
Notes:
Lists are simpler in this use-case. Dictionaries are also possible, but the code would be more complex.
A host can be a member of more groups.
Single dictionary is easily maintainable, potentially created from an external DB (e.g. Forman Manage Users).
The task selecting the list is simple and under the admin's control.

Ansible playbook-wide settings and tasks

I have a playbook with this structure:
---
- hosts: foo-servers
roles:
- foo_setup
become: yes
tags: tweaks
- hosts: bar-servers
roles:
- bar_setup
become: yes
tags: tweaks
[a few more server groups with a similar pattern]
I have a somewhat similar feature to deploy in all servers but each server group has it's own small differences, so I need to keep separate roles for each group.
I also want to run just a select group of tasks from each role, the ones tagged 'tweaks', in all hosts.
And all hosts should be run with raised privileges, but that is not true for all playbooks, so I want that setting to apply just to this playbook (no global vars).
I would like to move all the repeated parameters - become: yes and tags: tweaks outside of host: plays where they will be indicated to apply to all roles bellow. Something to the effect of
--
- all_hosts_this_playbook:
become: yes
tags: tweaks
- hosts: foo-servers
roles:
- foo_setup
- hosts: bar-servers
roles:
- bar_setup
I suppose this is possible in the command line. Like ansible-playbook setup_tweaks.yml --tags "tweak" --become? But is there a playbook equivalent? I'd rather have these in the file than in the command line, where I often forget to add stuff.
And looping doesn't work...
ERROR! 'loop' is not a valid attribute for a Play
- name: Make tweaks in many servers
become: yes
tags: tweaks
hosts: "{{ item.host }}"
roles:
- "{{ item.role }}"
loop:
- { host: 'foo-servers', role: 'foo_setup' }
- { host: 'bar-servers', role: 'bar_setup' }
I also want to add post_tasks: to be run in all servers (maybe they also need to be tagged?):
post_tasks_all_hosts:
- name: Upgrade system
apt:
autoremove: yes
autoclean: yes
update_cache: yes
upgrade: dist
tags: tweaks
- name: Reboot
shell: sleep 2 && reboot
async: 3
poll: 0
tags: tweaks
Is it possible to define playbook-wide pre_tasks or post_tasks?
Here Ansible: How to declare global variable within playbook? it is indicated that one 'cannot define a variable accessible on a playbook level', but in my case it's not variables - it's task parameters and post_tasks:.
Maybe the parameters and the 'pre/post tasks' are different problems with different solutions, but I decided to ask in the same place because they both fall on the same category of parameters that I'd like to set for the whole playbook, outside of host: plays.
Q: "I suppose this is possible in the command line. Like ansible-playbook setup_tweaks.yml --tags "tweak" --become? But is there a playbook equivalent?"
A: No. There is no such playbook equivalent.
Isn't this is a misunderstanding of the command-line option --tags
only run plays and tasks tagged with these values
versus
tag inheritance ?
Adding tags: to a play, or to statically imported tasks and roles, adds those tags to all of the contained tasks...When you apply tags: attributes to structures other than tasks, Ansible processes the tag attribute to apply ONLY to the tasks they contain. Applying tags anywhere other than tasks is just a convenience so you don’t have to tag tasks individually.
Details
In the play below tag "tweaks" is added to all of the contained tasks
- hosts: foo-servers
roles:
- foo_setup
tags: tweaks
The command below selects only tasks tagged "tweaks"
ansible-playbook setup_tweaks.yml --tags "tweak"
Q: "Is it possible to define playbook-wide pre_tasks or post_tasks?"
A: No. It's not possible. The scope of pre/post_tasks is the play.

Using Host Group as Variable in Ansible Task

I'm working on putting together a playbook that will deploy local facts scripts to various groups in my Ansible inventory, and I would to be able to utilize the group name being worked on as a variable in the tasks themselves. Assume for this example that I have the traditional Ansible roles directory structure on my Ansible machine, and I have subdirectories under the "files" directory called "apache", "web", and "db". I'll now illustrate by example, ...
---
- hosts: apache:web:db
tasks:
- name: Set facts for facts directories
set_fact:
facts_dir_local: "files/{{ group_name }}"
facts_dir_remote: "/etc/ansible/facts.d"
- name: Deploy local facts
copy:
src: "{{ item }}"
dest: "{{ facts_dir_remote }}"
owner: ansible
group: ansible
mode: 0750
with_fileglob:
- "{{ facts_dir_local }}/*.fact"
The goal is to have {{ group_name }} above take on the value of "apache" for the hosts in the apache group, "web" for the hosts in the web group, and "db" for the hosts in the db group. This way I don't have to copy and paste this task and assign custom variables for each group. Any suggestions for how to accomplish this would be greatly appreciated.
While there is no group_name variable in ansible, there is a group_names (plural). That is because any host may be part of one or more groups.
It is described in the official documentation as
group_names
List of groups the current host is part of
In the most common case each host would only be part of one group and so you could simply say group_names[0] to get what you want.
TLDR;
Use group_names[0].
You can use group variables to achieve this, either specified in the inventory file or in separate group_vars/<group> files - see https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html
Note though that these group variables are squashed into host variables when you run the playbook. Hosts that are listed in multiple groups will end up with variables based on a precedence order

Ansible: Include playbook according to inventory variable

I am trying to set up Ansible to be able to run a playbook according to what inventory group the host is in. For example, in the inventory, we have:
[group1]
host1.sub.domain.tld ansible_host=10.0.0.2
...
[group1:vars]
whatsmyplaybook=build-server.yml
Then we want to make a simple playbook that will more or less redirect to the playbook that is in the inventory:
---
- name: Load Playbook from inventory
include: "{{hostvars[server].whatsmyplaybook}}"
Where the "server" variable would be the host's FQDN, passed in from the command line:
ansible-playbook whatsmyplaybook.yml -e "server=host1.sub.domain.tld"
Our reasoning for this would be to have a server bootstrap itself from a fresh installation (PXE boot), where it will only really know its FQDN, then have a firstboot script SSH to our Ansible host and kick off the above command. However, when we do this, we get the below error:
ERROR! 'hostvars' is undefined
This suggests that the inventory is not parsed until a host list is provided, which sucks a lot. Is there another way to accomplish this?
A bit strange workflow, honestly.
Your setup doesn't work, because most of variables are not defined during playbook parse time.
You may be more lucky with defining single playbook with different plays for different groups (no need to set group var, just use correct host pattern (group name in my example)) and execute it limiting to specific host:
site.yml:
---
- hosts: group1
tasks:
- include: build-web-server-tasks.yml
- hosts: group2
tasks:
- include: build-db-server-tasks2.yml
command to provision specific server:
ansible-playbook -l host1.sub.domain.tld site.yml
You can develop your own dynamic inventory file so that all machines which needs to be bootstrapped will automatically added into your inventory and group respectively with out an manual entry in to the inventory file.
For developing dynamic inventory you can follow the below link:
http://docs.ansible.com/ansible/latest/dev_guide/developing_inventory.html
You can include multiple playbooks targeted to different groups as follows.
---
- hosts: all
tasks:
- include: build-web-server-tasks.yml
where: inventory_hostname in groups['group1']
- include: build-db-server-tasks2.yml
where: inventory_hostname in groups['group2']
inventory_hostname is the name of the hostname as configured in Ansible’s inventory host file. This can be useful for when you don’t want to rely on the discovered hostname ansible_hostname or for other mysterious reasons. If you have a long FQDN, inventory_hostname_short also contains the part up to the first period, without the rest of the domain.

How to install multiple instances of a service on a host with Ansible?

I have a host on which I want to install the same service multiple times, but with different paths, service names, etc. (stuff that can be configured via variables).
I usually don't use the same host for this, but this is a special use-case scenario and I can't change the architecture.
What is the optimal way of doing this using Ansible (I am already using 2.0)?
Given you have a role to install your application, you could use roll parameters to configure all the moving pieces.
- role: cool-app
location: /some/path/A
config:
some: stuff
- role: cool-app
location: /some/path/B
config:
some: other stuff
Then inside your role you could directly access {{ location }} and {{ config.some }} etc.
A bit more dynamic but also more complex to create - especially if you already have this working role and now need to change it - is to loop all tasks over a set of instances.
You could again pass this as role parameters:
- role: cool-app
instances:
- location: /some/path/A
config:
some: stuff
- location: /some/path/B
config:
some: other stuff
Or better define it in your host- or group-vars.
Then every task which is unique to an instance would need to loop over the instances variable. So for example unzipping:
- unarchive:
src: cool-app.tgz
dest: "{{ item.location }}"
with_items: instances
In addition to udondan's response there
is a third solution. Let's consider following directory structure:
host_vars/myapp01.yml
host_vars/myapp02.yml
roles/touch/tasks/main.yml
inventory.yml
play.yml
and following file contents:
# host_vars/myapp01.yml
myvar: myval01
# host_vars/myapp02.yml
myvar: myval02
# roles/touch/tasks/main.yml
- name: touch
command: touch {{ myvar }}
# inventory.yml
myapp01 ansible_host=192.168.0.1
myapp02 ansible_host=192.168.0.1
# play.yml
- hosts: all
roles:
- touch
Idea
The idea is to alias host with app instance names (one alias per instance of
application). In the example two aliases (myapp01 and myapp02) target the same
host: 192.168.0.1. Now this two instances of application are treated by
ansible exactly as two separate hosts and:
ansible-playbook play.yml -i inventory.ini
will install two instances of application (touch files myval01 and myval02)
on host 192.168.0.1.
Advantages
This solution allows, for example, to execute play only on one instance of
application:
ansible-playbook play.yml -i inventory.ini --limit myapp01
Note
Two DNS or IP addresses also can target the same machine.

Resources