Using Host Group as Variable in Ansible Task - ansible

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

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 alias in separate file

I'm trying to separate my vars from the playbook file.
My vars use an alias to make things easier.
Example of my playbook.yaml:
---
- hosts: localhost
name: playbook name
vars:
login: &login
username: "{{ login.user }}"
password: "{{ login.pass }}"
vars_files:
- creds.yaml
tasks:
- name: using creds
some_module:
some_command: true
<<: *login
How can I move the "vars" section to a file outside the playbook and keep the same template inside "tasks"?
Your way to achieve that is to create a proper inventory and use the inventory variables. ansible inventory docs
Create this schema tree:
hosts (directory)
group_vars (directory)
host_vars (directory)
hosts-groups (directory)
hosts-all (file)
Then either you choose to define your hosts individually in the hosts-all
localhost ansible_ssh_host=x.x.x.x {{ potentially any other needed argument/variable }}
Or You group hosts in groups and define new group in hosts/hosts-groups: Create a file with the desired group name, ex: hosts/hosts-groups/local and inside it define your host-group
[local]
localhost
And then, either in the hosts_var or group_vars (depending on your first step), you create a file either with host name or group name, and inside it, you write your variables. Ex (by group): Create hosts/group_vars/local and inside it write your variables
login: user1
pass: pass1
And then automatically, your play will search the right vars for each hosts according to your inventory structure.

How to combine multiple vars files per host?

I have a playbook running against multiple servers. All servers require a sudo password to be specified, which is specific to each user running the playbook. When running the playbook, I can't use --ask-become-pass, because the sudo passwords on the servers differ. This is the same situation as in another question about multiple sudo passwords.
A working solution is to specify ansible_become_pass in host_vars:
# host_vars/prod01.yml
ansible_become_pass: secret_prod01_password
domain: prod01.example.com
# host_vars/prod02.yml
ansible_become_pass: secret_prod02_password
domain: prod02.example.com
Besides ansible_become_pass, there are other variables defined per host. These variables should be committed to the git repository. However, as ansible_become_pass is specific to each user running the playbook, I'd like to have a separate file (ideally, vaulted) which specifies the password per host.
I imagine the following:
# host_vars/prod01.yml: shared in git
domain: prod01.example.com
# host_vars/prod01_secret.yml: in .gitignore
ansible_become_pass: secret_prod01_password
I imagine both files to be combined by Ansible when running the playbook. Is this possible in Ansible? If so, how?
You should be able to use the include_vars task with the inventory_hostname or ansible_hostname variable. For example:
- name: Include host specific variables
include_vars: "{{ ansible_hostname }}.yml"
- name: Include host specific secret variables
include_vars: "{{ ansible_hostname }}_secret.yml"
An even better solution would be to address the problem of users having unique passwords on different hosts.
You could create a new group in the inventory file, maybe sudo-hosts. Put all your sudo host in this group. Then create a file under the directory group_vars with the name of this goup. In this file put the secret yaml-structured text.
sudo_hosts:
host1:
password: xyz
othersecret_stuff: abc
host2:
...
then use ansbile-vault to encrypt this file with ONE password. Call the playbook with option --ask-vault-pass
and you can use your secrets with
"{{ sudo_host['ansible_host'].password }}"

How do you get group name of the executing role in ansible [duplicate]

This question already has answers here:
How to list groups that host is member of?
(3 answers)
Closed 4 years ago.
I have a use case where I need to create a directory in all hosts in a group whose name will be the name of the group.
Eg :
I create a dynamic inventory file with output of the form :
{
"db": ["host1", "host2", "host3"],
"logs": ["host2"],
"misc": ["host4"]
}
I need to create a directory by the name db in every host of the group db and directory named log in every host of the group log.
My playbook so far looks like :
- hosts: db
tasks:
- name: create db directory
file: path=db state=directory
- hosts: logs
tasks:
- name: create logs directory
file: path=logs state=directory
The number of groups is at least 10+. This may grow in the future.
I want to write something that will be easily extendable and manageable for the future groups.
I read that ansible doesn't provide a default fact or variable for group name for the task. I see that this can be best done if I provide a variable like groupname in host.
Is there a better solution that could use looping instead of repeating the same task?
You can use the magic variable group_names. The variable is a list (array) of all the groups the current host is in. More information is available per the documentation
- hosts: all
tasks:
- name: create db directory
file:
path: "{{ item }}"
state: directory
with_items: "{{ group_names }}"
Disclaimer: Did not test this code.

Can I update the hosts inventory and use new hosts in same playbook?

I'm adding few hosts in the hosts inventory file through playbook. Now I'm using those newly added hosts in the same playbook. But those newly added hosts are not readble by the same playbook in the same run it seems, because I get -
skipping: no hosts matched
When I run it separately, i.e. I update hosts file through one playbook and use the updated hosts in it through another playbook, it works fine.
I wanted to do something like this recently, using ansible 1.8.4. I found that add_host needs to use a group name, or the play will be skipped with "no hosts matched". At the same time I wanted play #2 to use facts discovered in play #1. Variables and facts normally remain scoped to each host, so this requires using the magic variables hostvars and groups.
Here's what I came up with. It works, but it's a bit ugly. I'd love to see a cleaner alternative.
# test.yml
#
# The name of the active CFN stack is provided on the command line,
# or is set in the environment variable AWS_STACK_NAME.
# Any host in the active CFN stack can tell us what we need to know.
# In real life the selection is random.
# For a simpler demo, just use the first one.
- hosts:
tag_aws_cloudformation_stack-name_{{ stack
|default(lookup('env','AWS_STACK_NAME')) }}[0]
gather_facts: no
tasks:
# Get some facts about the instance.
- action: ec2_facts
# In real life we might have more facts from various sources.
- set_fact: fubar='baz'
# This could be any hostname.
- set_fact: hostname_next='localhost'
# It's too late for variables set in this play to affect host matching
# in the next play, but we can add a new host to temporary inventory.
# Use a well-known group name, so we can set the hosts for the next play.
# It shouldn't matter if another playbook uses the same name,
# because this entry is exclusive to the running playbook.
- name: add new hostname to temporary inventory
connection: local
add_host: group=temp_inventory name='{{ hostname_next }}'
# Now proceed with the real work on the designated host.
- hosts: temp_inventory
gather_facts: no
tasks:
# The host has changed, so the facts from play #1 are out of scope.
# We can still get to them through hostvars, but it isn't easy.
# In real life we don't know which host ran play #1,
# so we have to check all of them.
- set_fact:
stack='{{ stack|default(lookup("env","AWS_STACK_NAME")) }}'
- set_fact:
group_name='{{ "tag_aws_cloudformation_stack-name_" + stack }}'
- set_fact:
fubar='{% for h in groups[group_name] %} {{
hostvars[h]["fubar"]|default("") }} {% endfor %}'
- set_fact:
instance_id='{% for h in groups[group_name] %} {{
hostvars[h]["ansible_ec2_instance_id"]|default("") }} {% endfor %}'
# Trim extra leading and trailing whitespace.
- set_fact: fubar='{{ fubar|replace(" ", "") }}'
- set_fact: instance_id='{{ instance_id|replace(" ", "") }}'
# Now we can use the variables instance_id and fubar.
- debug: var='{{ fubar }}'
- debug: var='{{ instance_id }}'
# end
It's not entirely clear what you're doing - but from what I gather, you're using the add_host module in a play.
It seems logical that you cannot limit that same play to those hosts, because they don't exist yet... so this can never work:
- name: Play - add a host
hosts: new_host
tasks:
- name: add new host
add_host: name=new_host
But you're free to add multiple plays to a single plabook file (which you also seem to have figured out):
- name: Play 1 - add a host
hosts: a_single_host
tasks:
- name: add new host
add_host: name=new_host
- name: Play 2 - do stuff
hosts: new_host
tasks:
- name: do stuff
It sounds like you are modifying the Ansible inventory file with your playbook, and then wanting to use the new contents of the file. Just modifying the contents of the file on disk, however, won't cause the inventory that Ansible is working with to be updated. The way Ansible works is that it reads that file (and any other inventory source you have) when it first begins and puts the host names it finds into memory. From then on it works only with the inventory that it has stored in memory, the stuff that existed when it first started running. It has no knowledge of any subsequent changes to the file.
But there are ways to do what you want! One option you could use is to add the new host into the inventory file, and also load it into memory using the add_host module. That's two separate steps: 1) add the new host to the file's inventory, and then 2) add the same new host to in-memory inventory using the add_host module:
- name: add a host to in-memory inventory
add_host:
name: "{{ new_host_name }}"
groups: "{{ group_name }}"
A second option is to tell Ansible to refresh the in-memory inventory from the file. But you have to explicitly tell it to do that. Using this option, you have two related steps: 1) add the new host to the file's inventory, like you already did, and then 2) use the meta module:
- name: Refresh inventory to ensure new instances exist in inventory
meta: refresh_inventory

Resources