Ansible - Avoid duplicates between group and host vars - ansible

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.

Related

Extracting group_name From hosts

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']

Give Input from Text file to Ansible Playbook

In my ansible playbook I want to get the input from text file and perform some set of operations.
I have 10 user names in the text file, then the play has to pick first name from the text file and do few tasks. Once done for the first user and the play has to pick it for second user and so on.
I wrote play for a single user. Kindly help me or give some sample play for this kind of scenario.
You should be using roles and manage everything from inventory.
But however, simplest way; Create a file, and store your user names in a variable that way
users:
- user1
- user2
- user3
- user4
- user5
At the beginning of your playbook, include that file
- hosts: whatever
become: yes
vars_file:
- <<path_to_your_var_file>>
Then in the task you can use that users variable you included from the variable file
tasks:
- name: create 10 users
user:
name: "{{ item }}"
state: present
with_items:
- "{{ users }}"
Ansible will import the users variable from your var file, and loop n times with number of users you have.

How to iterate over dictionary subitems with multiple different items in Ansible

Thanks for all the help,
I was interested in finding out how to set up a vars file with the following structure and how to iterate multiple deeply nested items in a dictionary. The end goal is to have a task performed on hosts that match a multiple or single roles or are used by a particular team, or teams.
1) Is this structure correct? I have seen so many different examples, I'm not sure anymore...
2) Is it possible in ansible to perhaps perform a task on multiple hosts by matching a single role but matching multiple teams?
3) How can I iterate through the dictionary and get an individual item from within a subelement that has multiple items, like "teams: sales" or "teams: dev" ? For example, I'd like to perform the task on host1 and matching operation and dev and sales teams files I have available for them. For example, copy a file to a team directory on that one host. Host2 has different teams and therefore the same task could be used but deliver the file to only those team directories on that single host.
4) The same desire would apply to the role of each host... being able to have multiple roles for a single host but matching on the role value (either both roles or a single role).
Anyhow, I've looked at a lot of things and I haven't quite found something that fits this but I feel it is possible -- sorta??
### hostmap.yml
---
host_team_map:
host_list:
host1:
comment: "host description"
application_role: [ 'role1', 'role2' ] <----------- multiple subitems
teams: [ 'operations', 'dev', 'sales' ] <----------- multiple subitems
host2:
comment: "host description"
application_role: [ 'role3' ]
teams: [ 'operations', 'dev' ]
host3:
comment: "host description"
application_role: [ 'role3', 'role4' ]
teams: [ 'operations' ]
Thanks again for any help!!
So, if I understand well, you want to apply the list of roles for an host, and from this role, only a part of tasks based on the teams elements.
If it is the case, then you may have, for each role, a task file with the name of the teams and use the include_role module: https://docs.ansible.com/ansible/latest/modules/include_role_module.html
Here is an example of what you may do:
1) As #larsks suggest, you should put necessary vars in the inventory (thus, having application_role an teams specific for "current" host)
For example, in host_vars:
### host1.yml
---
application_role:
- role1
- role2
- ...
teams:
- dev
- operation
- ...
2) Role directory structure example:
roles/
role1/
tasks/
operation.yml
dev.yml
...
role2/
tasks/
dev.yml
...
3) And the task in the playbook(s):
- name: Apply every role, but only the teams part
include_role:
name: "{{ item.0 }}"
tasks_from: "{{ item.1 }}"
loop: "{{ application_role|product(teams)|list }}"

Ansible: Appending to list with variable files

Is there a way to have multiple variable files append to an array?
The end goal is to have each file append an AWS security group for the launch configuration without the need to copy those groups into each file
ex:
group_vars/all.yml
group_vars/php.yml
group_vars/web.yml
all.yml:
aws_security_groups:
- sg-ssh
php.yml
aws_security_groups:
- sg-mysql
web.yml
aws_security_groups:
- sg-http
Debugging aws_security_groups produces:
TASK [Debugging] ***************************************************************
ok: [XXX.XXX.XXX.XXX] => {
"aws_security_groups": [
"sg-mysql"
]
}
We have API servers and front-end servers which have different scaling polices. I would like the web servers to have the groups: sg-web and sg-ssh with the api servers to have: sg-web, sg-ssh and sg-mysql
AFAIK there is no way to merge list variables during definition time in Ansible.
As a workaround, you can make a dict in your all.yml:
aws_security_groups:
all:
- sg-ssh
- sg-all
php:
- sg-mysql
web:
- sg-http
Or define each subkey (all, php, web, etc.) in corresponding group vars file and use merge hash_behavior (but changing hash_behavior is not generally recommended).
And then use this magic to get a list of security groups:
- debug: msg="{{ (group_names + ['all']) | intersect(aws_security_groups.keys()) | map('extract', aws_security_groups) | sum(start=[]) }}"
This will make plain list of all security groups depending on groups for current host.

A special variable for ansible roles installed?

Is there a variable or a method allowing one to list all the roles applied to a group of ansible hosts?
For example:
- hosts: webservers
gather_facts: true
roles:
- nginx
- php-fpm
tasks:
- debug:
msg: {{ item }} installed
with_items: ansible_roles
or perhaps another way to achieve this?
It depends on what applied means in your question.
The variable role_names holds all roles of the current play, so it would be an array: [nginx, php-fpm].
- debug:
msg: {{ item }} installed
with_items: role_names
But these roles not necessarily have been applied to the hosts, if you mean by that they have been processed. There is no such thing that will update once a role has been run on a host.
If that is what you're looking for you could implement it yourself with a callback plugin. AFAIK there is no callback for starting/completing a role itself. But since the names of the roles are present in the task names you could simply use the playbook_on_task_start, extract the role name from the task name and store it in some way. I have not yet looked into callback plugins in Ansible 2 where the API changed, but I expect you have access to the global variable list and can manipulate it.

Resources