Ansible: Appending to list with variable files - ansible

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.

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

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 Setup Module to search and find an IP address

My hosts have 3 network IP addresses and one of them is needed later in my playbook.
In my playbook I have ran the following setup module:
- name: Gather Networks Facts into Variable
setup:
register: setup
- name: Debug Set Facts
debug:
var: setup.ansible_facts.ansible_ip_addresses
The provides the following output:
{
"setup.ansible_facts.ansible_ip_addresses": [
"10.0.2.15",
"fe80::85ae:2178:df12:8da0",
"192.168.99.63",
"fe80::3871:2201:c0ab:6e39",
"192.168.0.63",
"fe80::79c5:aa03:47ff:bf65",
"fd89:8d5f:2227:0:79c5:aa03:47ff:bf65",
"2a02:c7f:9420:7100:79c5:aa03:47ff:bf65"
]
}
I am trying to find a way to find the 192.168.0.63 by searching using the first three octets or 192.168.0. I also then want to get that value into a fact so I can use this later in my playbook.
What would be the best way to search and find that value with Ansible or Jinja2?
Will this do?
- set_fact:
my_fact: "{{ (setup.ansible_facts.ansible_ip_addresses | select('match','192.168.0.') | list)[0] }}"
If there are multiple values matching the pattern, it will get the first one in order.

How to list groups that host is member of?

I have a very complex Ansible setup with thousands of servers and hundreds of groups various servers are member of (dynamic inventory file).
Is there any way to easily display all groups that a specific host is member of?
I know how to list all groups and their members:
ansible localhost -m debug -a 'var=groups'
But I want to do this not for ALL hosts, but only for a single one.
Create a playbook called 'showgroups' (executable file) containing:
#!/usr/bin/env ansible-playbook
- hosts: all
gather_facts: no
tasks:
- name: show the groups the host(s) are in
debug:
msg: "{{group_names}}"
You can run it like this to show the groups of one particular host (-l) in your inventory (-i):
./showgroups -i develop -l jessie.fritz.box
There is group_names magic variable:
Additionally, group_names is a list (array) of all the groups the current host is in. This can be used in templates using Jinja2 syntax to make template source files that vary based on the group membership (or role) of the host
cat /etc/ansible/hosts | grep -e [[] && ansible all --list-hosts

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