I would like to be able to reference the direct parent of an ansible host as a variable.
Take the following example inventory:
[resourcegroup1]
host1
host2
[resourcegroup2]
host3
host4
[application:children]
[resourcegroup1]
[database:children]
[resourcegroup2]
[environoments:children]
[application]
[database]
[enivronoments]
dev
staging
prod
I would like to run a looping task in a play which can reference a hosts parent. Example:
tasks:
- name: Start Datanbase Servers
with_items: "{{ groups['database'] }}"
azure_rm_virtualmachine:
name: "{{ item }}"
resource_group: "{{ item.parent }}"
started: yes
allocated: yes
{{ item }} would iterate through the values of "host3" and "host4" while I am looking for what I could put in place of {{ item.parent }} that would be the hosts direct parent, in this case: "resourcegroup2".
Is there any way to reference the hierarchy of an inventory?
After doing some quick research, it appears Ansible doesn't have that sort of functionality builtin.
According to the tickets opened in Ansible Github repo, it sounds like people were interested in this feature, but was never developed. Probably because of the battle between usefulness & complexity.
Ansible Tix
Ansible Tix
Here are some snippets from the above links:
It would be nice to have a way of recreating inventory group hierarchy inside i.e. Jinja2 template.
#kinvaris We have dynamic inventories for this. You could just write a small dynamic inventory script, and have it translate. I don't see why this is a necessary addition, it only complicates the tools even more.
If you just want a list of groups associated with a host, but don't care about hierarchical structure:
'{{hostvars["host1"]}}'
You'll get an list like this:
"groups": {
"all": [
"host"
],
"child": [
"host1"
],
"parent": [
"host1"
],
"ungrouped": []
},
Related
I am running ansible role to get package version on 50 servers. As example, I am looking to get output in form below
{
server1: "1.0",
server2: "1.0",
.
.
.
servert50: "1.1"
}
I have the logic to get the version but can't get a clear way to collect one fact from each run after role execution is finished.
The following didn't produce what I am looking for since it runs after each execution NOT once at the end. Also I need to have facts in list of dictionaries.
post_tasks:
- name: Print installed versions
run_once: true
copy:
content: "{{ hostvars | to_nice_json }}"
dest: /tmp/hostvar.json
delegate_to: localhost
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 }}"
I'm completely new to Ansible so I'm still struggling with its way of working... I have an inventory file with several hosts sorted by environment and function:
[PRO-OSB]
host-1
host-2
[PRO-WL]
host-3
host-4
[PRO:children]
PRO-OSB
PRO-WL
But I think sometimes I might need to run playbooks specifying even more, i.e attending to the environment, its function, cluster of hosts and application running on the host. So in resume every host must have 4 "categories": environment, function, cluster and app.
How could I achieve this without having to constantly repeat entries??
How could I achieve this without having to constantly repeat entries?
You can't. You have to declare in each needed groups the machines that belong to it. So if a machine belongs to 4 distinct groups (not taking into account parent groups), you'll have to declare that host in the 4 relevant groups.
Ansible could have chosen the other way around (i.e. list for each hosts the groups it belongs to) but this is not the retained solution and it would be as verbose.
To make things easier and IMO a bit more secure, you can split your inventory in several environment inventories (prod, dev....) so that you can remove one level of complexity inside each inventory. The downside is that you cannot target all your envs at once with such a setup.
If your inventory is big and targeting some sort of cluster/cloud environment (vsphere, aws...), dynamic inventories can help.
Q: "Every host must have 4 "categories": environment, function, cluster and app. How could I achieve this without having to constantly repeat entries?"
A: It's possible to declare default options in a [*:vars] section and override it with the host's specific options. See How variables are merged. For example the inventory
$ cat hosts
[PRO_OSB]
test_01 my_cluster='cluster_A'
test_02
[PRO_WL]
test_03 my_cluster='cluster_A'
test_04
[PRO:children]
PRO_OSB
PRO_WL
[PRO:vars]
my_environment='default_env'
my_function='default_fnc
my_cluster='default_cluster'
my_app='default_app'
with the playbook
- hosts: PRO
gather_facts: false
tasks:
- debug:
msg: "{{ inventory_hostname }}
{{ my_environment }}
{{ my_function }}
{{ my_cluster }}
{{ my_app }}"
gives (my_cluster variable of test_01 and test_03 was overridden by hosts' value)
"msg": "test_01 default_env 'default_fnc cluster_A default_app"
"msg": "test_02 default_env 'default_fnc default_cluster default_app"
"msg": "test_04 default_env 'default_fnc default_cluster default_app"
"msg": "test_03 default_env 'default_fnc cluster_A default_app"
Q: "Run playbooks specifying even more, i.e attending to the environment, its function, cluster of hosts and application."
It's possible to create dynamic groups with the module add_host and select the hosts as needed. For example create new group cluster_A in the first play and use it in the next one
- hosts: all
tasks:
- add_host:
name: "{{ item }}"
group: cluster_A
loop: "{{ hostvars|
dict2items|
json_query('[?value.my_cluster == `cluster_A`].key') }}"
delegate_to: localhost
run_once: true
- hosts: cluster_A
tasks:
- debug:
var: inventory_hostname
gives
ok: [test_01] => {
"inventory_hostname": "test_01"
}
ok: [test_03] => {
"inventory_hostname": "test_03"
}
for folks who might need something like this --
I ended up using this format for my inventory file when I was trying to establish multiple variables for each host for delivering files based on role or team.
inventory/support/hosts
support:
hosts:
qahost:
host_role:
waiter
dishwasher
host_teams:
ops
sales
awshost:
host_role:
waiter
host_teams:
dev
testhost1:
host_role:
dishwasher
host_teams:
ops
dev
testhost2:
host_role:
dishwasher
boss
host_teams:
ops
dev
sales
and referenced them in this play:
- name: parse inventory file host variables
debug:
msg: |
- "role attribute of host: {{ item }} is {{ hostvars[item]['host_role'] }}"
- "team attribute of host: {{ item }} is {{ hostvars[item]['host_teams'] }}"
with_items: "{{ inventory_hostname }}"
when: hostvars[item]['host_teams'] is contains (team_name)
ansible command with custom extra_var that is passed to the playbook and matched in the inventory with the when conditional:
ansible-playbook -i inventory/support/hosts -e 'team_name="sales"' playbook.yml
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
I am trying to get a list of the IP's of all the hosts in my inventory file from a playbook that only runs in a single group.
Assume the following inventory file:
[dbservers]
db1.example.com
db2.example.com
[webservers]
www1.example.com
www2.example.com
And the playbook:
---
- hosts: dbservers
roles:
- dosomething
And the dosomething role:
- name: print all host ips
template: src=hosts.j2 dest=/tmp/hosts.txt
And the hosts.j2 template:
{% for host in hostvars %}
{{ hostvars[host].ansible_eth0.ipv4.address }}
{% endfor %}
Problem:
When running this, I only ever get the dbserver ip's listed, not all ip's
Question:
How can I gain access to the entire inventory from within this playbook? Changing the hosts to all in the playbook works, but then the dosomething playbook also runs on all hosts, which is not what I want. I only want the list on the dbservers.
In order to gain access to all the hosts in hostvars, you first have to create a task for all hosts. I created a simple role that would simple echo something on all hosts. This role then forces gather facts on all hosts and add each to the hostvars group.
Please note that if you then run the playbook with a tag limit, the hostvars group is again effected.
I got the tip here: https://groups.google.com/forum/#!msg/Ansible-project/f90Y4T4SJfQ/L1YomumcPEQJ
The special variable groups is probably what you want to do this, in your case the value of groups will be:
"groups" : {
"dbservers": [
"db1.example.com",
"db2.example.com"
],
"webservers": [
"www1.example.com",
"www2.example.com"
]
}
which you can loop over in your jinja template.