How to loop through inventory and assign value in Ansible - ansible

I have a task in my Ansible playbook that I'm wanting iterate over each host in the group that I have and for each host I would like to assign a name from the hostname list that I've created in the vars folder.
I'm familiar with looping through inventory already by writing loop: "{{ groups['mygroup'] }}" and I have a list of hostnames I would like to assign each IP in 'mygroup' within the host file.
# In tasks file - roles/company/tasks/main.yml
- name: change hostname
win_hostname:
name: "{{ item }}"
loop: "{{ hostname }}"
register: res
# In the Inventory file
[company]
10.0.10.128
10.0.10.166
10.0.10.200
# In vars - roles/company/vars/main.yml
hostname:
- GL-WKS-18
- GL-WKS-19
- GL-WKS-20
# site.yml file located under /etc/ansible
- hosts: company
roles:
- common
- company #This is where the loop exists mentioned above.
# Command to run playbook
ansible-playbook -i hosts company.yml
I seem to have the individual pieces down or know about it, but how can I combine iterating over hosts from an inventory group and assign names that I have in an already created list (in roles vars folder) already?
UPDATE
the task mentioned above has been updated to reflect changes mentioned in answer:
- name: change hostname
win_hostname:
name: "{{ item.1 }}"
loop: {{ groups.company|zip(hostname)|list }}"
register: res
However the output I'm getting is incorrect, this should not run 9 times rather only three times, once per IP in the [company] group in the inventory. Also there are only three hostnames in a list that need to be assigned to each of the hosts in the inventory sheet.
changed: [10.0.10.128] => (item=[u'10.0.10.128', u'GL-WKS-18'])
changed: [10.0.10.166] => (item=[u'10.0.10.128', u'GL-WKS-18'])
changed: [10.0.10.200] => (item=[u'10.0.10.128', u'GL-WKS-18'])
changed: [10.0.10.128] => (item=[u'10.0.10.166', u'GL-WKS-19'])
changed: [10.0.10.166] => (item=[u'10.0.10.166', u'GL-WKS-19'])
changed: [10.0.10.200] => (item=[u'10.0.10.166', u'GL-WKS-19'])
ok: [10.0.10.128] => (item=[u'10.0.10.200', u'GL-WKS-20'])
ok: [10.0.10.166] => (item=[u'10.0.10.200', u'GL-WKS-20'])
ok: [10.0.10.200] => (item=[u'10.0.10.200', u'GL-WKS-20'])

Whenever I have a question about looping in Ansible I also go visit the Loops documentation. It sounds like you want to iterate over two lists in parallel, pairing an item from the list of hosts in your inventory with an item from the list of hostnames. In previous versions of Ansible that would suggest using the with_together loop, while with more recent versions of Ansible that suggests the zip filter (there's an example in the docs here).
To demonstrate this for your use case, I started with an inventory file that has three hosts:
[mygroup]
hostA ansible_host=localhost
hostB ansible_host=localhost
hostC ansible_host=localhost
And the following playbook:
---
- hosts: all
- hosts: localhost
gather_facts: false
vars:
hostnames:
- hostname01
- hostname02
- hostname03
tasks:
- name: change hostname
debug:
msg:
win_hostname:
name: "{{ item }}"
loop: "{{ groups.mygroup|zip(hostnames)|list }}"
Here I'm using a debug task instead of actually running the win_hostname task. The output of running:
ansible-playbook -i hosts playbook.yml
Looks like:
TASK [change hostname] ********************************************************************************************************************************
ok: [localhost] => (item=[u'hostA', u'hostname01']) => {
"msg": {
"win_hostname": {
"name": [
"hostA",
"hostname01"
]
}
}
}
ok: [localhost] => (item=[u'hostB', u'hostname02']) => {
"msg": {
"win_hostname": {
"name": [
"hostB",
"hostname02"
]
}
}
}
ok: [localhost] => (item=[u'hostC', u'hostname03']) => {
"msg": {
"win_hostname": {
"name": [
"hostC",
"hostname03"
]
}
}
}
As you can see, it's paired each host from the inventory with a hostname from the hostnames list.
Update
Based on the additional information you've provided, I think what you
actually want is this:
- name: change hostname
win_hostname:
name: "{{ hostnames[group.company.index(inventory_hostname) }}"
This will assign one value from hostname to each host in your
inventory. We're looking up the position of the current
inventory_hostname in your group, and then using that to index into
the hostnames list.

Related

Generate dictionary by template in inventory

At the moment I have the following inventory (simplified):
children:
child-1111:
param: some-data-1111
child-1112:
param: some-data-1112
child-1113:
param: some-data-1113
Of course, it's not good when you need a thousand hosts instead of just three and many parameters with substitutions. I would like to write something like this (which doesn't work):
children:
"child-{{item}}":
param: "some-data-{{item}}"
loop:
- 1111
- 1112
- 1113
All the examples I see always produce lists, not dictionaries.
Also, loop seems to work only for tasks, not inventories.
How do I achieve this?
Those kind of requirements tend to be managed with the add_host module.
Given the playbook:
- hosts: localhost
gather_facts: no
tasks:
- add_host:
name: "child-{{ item.name }}"
param: "some-data-{{ item.param }}"
group: dynamic_hosts
loop: "{{ _hosts }}"
vars:
_hosts:
- name: 1111
param: 1111-bis
- name: 1112
param: 1112-bis
- name: 1113
param: 1113-bis
- hosts: dynamic_hosts
gather_facts: no
tasks:
- debug:
msg: "On host `{{ item }}` the value of `param` is `{{ hostvars[item].param }}`"
loop: "{{ ansible_play_hosts }}"
run_once: true
We are constructing a dynamic inventory group named dynamic_hosts, which we can then use in the same playbook.
This would actually yields:
PLAY [localhost] ****************************************************************
TASK [add_host] *****************************************************************
ok: [localhost] => (item={'name': 1111, 'param': '1111-bis'})
ok: [localhost] => (item={'name': 1112, 'param': '1112-bis'})
ok: [localhost] => (item={'name': 1113, 'param': '1113-bis'})
PLAY [dynamic_hosts] ************************************************************
TASK [debug] ********************************************************************
ok: [child-1111] => (item=child-1111) =>
msg: On host `child-1111` the value of `param` is `some-data-1111-bis`
ok: [child-1111] => (item=child-1112) =>
msg: On host `child-1112` the value of `param` is `some-data-1112-bis`
ok: [child-1111] => (item=child-1113) =>
msg: On host `child-1113` the value of `param` is `some-data-1113-bis`
Some forenotes:
Your question is not very precise. My example will only give a general approach which is a bit more scalable than yours but will hit limits very soon.
Your example is not a valid yaml inventory. Moreover it is not clear if you use the name children to declare children groups for the default all or if you want to use this as a group name (which I strongly suggest you don't do).
Having to declare host specific variables depending on the host name inside a static inventory for loads of hosts is generally a sign of either a bad design or that you should switch to a fully dynamic inventory or by managing the hosts in your playbook dynamically through add_host (see #β.εηοιτ.βε's answer)
My example below declares a range of hosts in a group named my_children and takes advantage of the host naming convention to capture their number and reuse it in a variable declared for the group
inv.yml
---
my_children:
vars:
param: "some-data-{{ inventory_hostname.split('-')[1] }}"
hosts:
child-[1111:1113]:
You can see the hosts are parsed correctly:
$ ansible-inventory -i inv.yml --list
{
"_meta": {
"hostvars": {
"child-1111": {
"param": "some-data-{{ inventory_hostname.split('-')[1] }}"
},
"child-1112": {
"param": "some-data-{{ inventory_hostname.split('-')[1] }}"
},
"child-1113": {
"param": "some-data-{{ inventory_hostname.split('-')[1] }}"
}
}
},
"all": {
"children": [
"my_children",
"ungrouped"
]
},
"my_children": {
"hosts": [
"child-1111",
"child-1112",
"child-1113"
]
}
}
And the vars are interpreted correctly:
$ ansible my_children -i inv.yml -m debug -a "msg={{ param }}"
child-1111 | SUCCESS => {
"msg": "some-data-1111"
}
child-1112 | SUCCESS => {
"msg": "some-data-1112"
}
child-1113 | SUCCESS => {
"msg": "some-data-1113"
}
Q: "Need a thousand hosts (.e.g. 'child-1111' param: 'some-data-1111')"
A: Use ranges and constructed plugin. For example
shell> cat inventory/01-hosts
child-[1111:1113]
shell> cat inventory/02-constructed.yml
plugin: constructed
strict: True
groups:
group_children: inventory_hostname.startswith('child-')
compose:
param: "'some-data-' ~ inventory_hostname|split('-')|last"
Test it
shell> ansible-inventory -i inventory --list --yaml
all:
children:
group_children:
hosts:
child-1111:
param: some-data-1111
child-1112:
param: some-data-1112
child-1113:
param: some-data-1113
ungrouped: {}
and use it in a playbook
- hosts: all
tasks:
- debug:
var: param
gives
ok: [child-1111] =>
param: some-data-1111
ok: [child-1112] =>
param: some-data-1112
ok: [child-1113] =>
param: some-data-1113

ansible groups.variable in a tasks

I am trying to use a variable I have defined in a group_vars/file.yml and use it in a taks of my role like groups.my_variable (my_variable is the name of the group define in my inventory)
Here is my role tasks:
---
- name: "Create strusted storage pool"
gluster_peer:
state: "{{ CURRENT_NODES.state }}"
nodes: "{{groups.group_name}}"
tags: STORAGE_POOL
my inventory
[gluster]
glusterfs01
glusterfs02
ly-lab-elk
lpic
when I use it like this it works:
---
- name: "Create strusted storage pool"
gluster_peer:
state: "{{ CURRENT_NODES.state }}"
nodes: "{{ groups.gluster }}"
tags: STORAGE_POOL
When I use groups.gluster directly in my tasks/pool_storage.yml as above I have expected result
TASK [debug] ******************************************************************************************************************************************************************************************************
ok: [glusterfs01] => {
"groups.gluster": [
"glusterfs01",
"glusterfs02",
"ly-lab-elk",
"lpic"
]
}
ok: [glusterfs02] => {
"groups.gluster": [
"glusterfs01",
"glusterfs02",
"ly-lab-elk",
"lpic"
]
}
ok: [ly-lab-elk] => {
"groups.gluster": [
"glusterfs01",
"glusterfs02",
"ly-lab-elk",
"lpic"
]
}
ok: [lpic] => {
"groups.gluster": [
"glusterfs01",
"glusterfs02",
"ly-lab-elk",
"lpic"
]
}
But when I use the variable set in group_vars/gluster.yml
group_names: gluster
in my tasks tasks/pool_storage.yml
nodes: "{{groups.group_name}}"
I have this result which is not good
TASK [debug] ******************************************************************************************************************************************************************************************************
ok: [glusterfs01] => {
"group_names": [
"gluster"
]
}
ok: [glusterfs02] => {
"group_names": [
"gluster"
]
}
ok: [ly-lab-elk] => {
"group_names": [
"gluster"
]
}
ok: [lpic] => {
"group_names": [
"gluster"
]
}
As I have different groups in my inventory file, I want to use a variable in which I will define my group name and then append it to groups.group_name or other way but to have the expected result.
Can someone help ?
Thank you in advance.
So, to reference a group_name variable in groups you can try using the groups[group_name] syntax.
An example inventory:
[servers]
host-101.xxx.com
host-202.xxx.com
[group1]
g1-server1
g1-server2
A group_vars/servers.yaml:
group_name: servers
A playbook:
- hosts: servers
connection: local
tasks:
- debug:
var: groups[group_name]
Gives the servers under group1 defined as group_name:
ok: [host-101.xxx.com] => {
"groups[group_name]": [
"host-101.xxx.com",
"host-202.xxx.com"
]
}
ok: [host-202.xxx.com] => {
"groups[group_name]": [
"host-101.xxx.com",
"host-202.xxx.com"
]
}
One more thing to note is that you don't need to establish the peers on each host of GlusterFS cluster. So, either:
target any 1 host of that group
or
add run_once: true to the task
Example:
- name: "Create strusted storage pool"
gluster_peer:
state: "{{ CURRENT_NODES.state }}"
nodes: "{{ groups[group_name] }}"
tags: STORAGE_POOL
run_once: true
Put the variable into the file group_vars/gluster.yml if you want the hosts of the group gluster to use it. The name of the file must be identical to the name of the group. This way Ansible will recognize to which groups the files in the directory group_vars belong.
If you can't change the name of group_vars/file.yml link it to group_vars/gluster.yml.
For example, given the simplified inventory for testing
shell> cat hosts
[gluster]
glusterfs01
glusterfs02
The group_vars
shell> cat group_vars/gluster.yml
my_variable: foo
and the playbook
shell> cat playbook.yml
- hosts: gluster
gather_facts: false
tasks:
- debug:
var: my_variable
give (abridged)
shell> ansible-playbook -i hosts playbook.yml
TASK [debug] *******************************************************
ok: [glusterfs01] =>
my_variable: foo
ok: [glusterfs02] =>
my_variable: foo

Ansible 2.8: Access a list object element

In Ansible 2.8, I need to deploy and config Bind 9 DNS on Ubuntu Server VMs. I have a:
DNS Ansible role to do the installation and config,
a list of variables per domain zone (as DNS record type, domain name, dns entry,...). Until here, it works, the issues appear when I try to make it accept the next requirement:
potentially, several domain zones to configure in the same call, threfore, I send to it a list with groups of variables (mentioned in 2).
For now, in the shell, I call it with 1 element list, using:
--extra-vars "{"dns_entry_conf":
[domain=example.gal ip=192.168.167.166
nameserver1=example.gal nameserver1_ip=192.168.167.164
dns_record1_type=A ...]}"
Inside the role, the roles/dns/tasks/configure.yml file receives the right value, but the file that follows, doesn't: it says "list object has no attribute", and I started debugging in the configure.yml file, but I am not sure how to access the list object item:
---
- debug:
msg: "{{dns_entry_conf}}"
- debug:
msg: "{{dns_entry_conf | json_query(\"domain\") }}"
The first line prints what it should, but the 2nd does not... How I can access the value
ASK [dns : debug] **********************************************************************************
task path: /etc/ansible/roles/dns/tasks/configure.yml:2
ok: [ubuntuServer16_test] => {
"msg": [
"domain=example.gal ip=192.168.167.166 nameserver1=example.gal nameserver1_ip=192.168.167.164
dns_record1_type=A ...
]
}
TASK [dns : debug] **********************************************************************************
task path: /etc/ansible/roles/dns/tasks/configure.yml:4
ok: [ubuntuServer16_test] => {
"msg": ""
}
In debug, tried with the msg's: "{{ dns_entry_conf.domain }}", "{{ dns_entry_conf.0 }}", "{{dns_entry_conf | json_query(\"domain\") }}", "{{ dns_entry_conf.list | json_query('[*].domain') }}", and others that were sintactically wrong, but it never outputs what I want.
Probably there are more wrong things (I am no Ansible expert), but, for now, just trying to debug and fix one by one, so, I just want to know how I can access the "dns_entry_conf.domain" item, please... some idea?
Option1:
with extra-vars as below:
--extra-vars '{"dns_entry_conf":{"domain":example,"ip":1.2.3.4}}'
Playbook:
- debug:
msg: "{{dns_entry_conf.domain}}"
Output:
ok: [localhost] => {
"msg": "example"
}
Option2:
with extra vars as below:
--extra-vars '{"dns_entry_conf":["domain":example,"ip":1.2.3.4]}'
In Playbook tey as below:
- debug:
msg: "{{dns_entry_conf[0].domain}}"
Output:
ok: [localhost] => {
"msg": "example"
}
Option 3:
Pass the variables in the playbook.
vars:
dns_entry_conf:
domain: example
ip: 1.2.34.4
tasks:
- debug:
msg: "{{dns_entry_conf.domain}}"
Output:
ok: [localhost] => {
"msg": "example"
}

Get a list of inventory hostnames

host1: abc
host2: xyz
host1 and host2 are listed under test-hosts
[test-hosts]
abc
xyz
When i debug for inventory_hostnames, i see them like below
> TASK [debug inventory_hostname]
> *************************************************************************************************************************************************************************
ok: [abc] => {
> "inventory_hostname": "abc" }
ok: [xyz] => {
> "inventory_hostname": "xyz" }
Is there any way we can gather inventory_hostname's like a list by assigning it to a variable.
Expected result:
exp_result: [abc, xyz]
You can use groups['test-hosts'] to get those hosts as a list.
For example:
---
- hosts: all
gather_facts: false
tasks:
- set_fact:
host_list: "{{ groups['test-hosts'] }}"
- debug:
msg: "{{host_list}}"
You can also use the variable inventory_hostnames if you want to select multiple groups or exclude groups with patterns.
In these examples, we're selecting all hosts except ones in the www group.
Saving the list of hosts as a variable for a task:
- debug:
var: hostnames
vars:
hostnames: "{{ query('inventory_hostnames', 'all:!www') }}"
You can also use the lookup plugin to loop over the hosts that match the pattern. From the docs:
- name: show all the hosts matching the pattern, i.e. all but the group www
debug:
msg: "{{ item }}"
with_inventory_hostnames:
- all:!www

Ansible - read inventory hosts and variables to group_vars/all file

I have a dummy doubt that keeps me stuck for a long time. I have a very banal inventory file with hosts and variables:
[lb]
10.112.84.122
[tomcat]
10.112.84.124
[jboss5]
10.112.84.122
...
[tests:children]
lb
tomcat
jboss5
[default:children]
tests
[tests:vars]
data_base_user=NETWIN-4.3
data_base_password=NETWIN
data_base_encrypted_password=
data_base_host=10.112.69.48
data_base_port=1521
data_base_service=ssdenwdb
data_base_url=jdbc:oracle:thin:#10.112.69.48:1521/ssdenwdb
The problem is that I need to access all these hosts and variables, in the inventory file, from the group_vars/all file.
I've tried the following manners to access the host IP:
{{ lb }}
"{{ hostvars[lb] }}"
"{{ hostvars['lb'] }}"
{{ hostvars[lb] }}
To access a host variable I tried:
"{{ hostvars[tests].['data_base_host'] }}"
All of them are wrong!!! Can anyone help me find out the best practice to access hosts and variables, not from a playbook but from a variables file?
EDIT:
Ok. Let's clarify.
Problem: Use a host declared in the inventory file in a variable file, let's say: group_vars/all.
Example: I have a DB host with IP:10.112.83.37.
Inventory file:
[db]
10.112.83.37
In the group:vars/all file I want to use that IP to build a variable.
group_vars/all file:
data_base_url=jdbc:oracle:thin:#{{ db }}:1521/ssdenwdb
In a template I use the variable built in the group_vars/all file.
Template file:
oracle_url = {{ data_base_url }}
The problem is that the {{ db }} variable in the group_vars/all file is not replaced by the DB host IP. The user can only edit the inventory file.
- name: host
debug: msg="{{ item }}"
with_items:
- "{{ groups['tests'] }}"
This piece of code will give the message:
'10.112.84.122'
'10.112.84.124'
as groups['tests'] basically return a list of unique ip addresses ['10.112.84.122','10.112.84.124'] whereas groups['tomcat'][0] returns 10.112.84.124.
If you want to programmatically access the inventory entries to include them in a task for example. You can refer to it like this:
{{ hostvars.tomcat }}
This returns you a structure with all variables related with that host. If you want just an IP address (or hostname), you can refer to it like this:
{{ hostvars.jboss5.ansible_ssh_host }}
Here is a list of variables which you can refer to: click. Moreover, you can declare a variable and set it with for example result of some step in a playbook.
- name: Change owner and group of some file
file: path=/tmp/my-file owner=new-owner group=new-group
register: chown_result
Then if you play this step on tomcat, you can access it from jboss5 like this:
- name: Print out the result of chown
debug: msg="{{ hostvars.tomcat.chown_result }}"
Just in case if the problem is still there,
You can refer to ansible inventory through ‘hostvars’, ‘group_names’, and ‘groups’ ansible variables.
Example:
To be able to get ip addresses of all servers within group "mygroup", use the below construction:
- debug: msg="{{ hostvars[item]['ansible_eth0']['ipv4']['address'] }}"
with_items:
- "{{ groups['mygroup'] }}"
Considering your previous example:
inventory file:
[db]
10.112.83.37
group_vars/all
data_base_url=jdbc:oracle:thin:#{{ db }}:1521/ssdenwdb
template file:
oracle_url = {{ data_base_url }}
You might want to replace your group_vars/all with
data_base_url="jdbc:oracle:thin:#{{ groups['db'][0] }}:1521/ssdenwdb"
Yes the example by nixlike works very well.
Inventory:
[docker-host]
myhost1 user=barbara
myhost2 user=heather
playbook:
---
- hosts: localhost
connection: local
tasks:
- name: loop debug inventory hostnames
debug:
msg: "the docker host is {{ item }}"
with_inventory_hostnames: docker-host
- name: loop debug items
debug:
msg: "the docker host is {{ hostvars[item]['user'] }}"
with_items: "{{ groups['docker-host'] }}"
output:
ansible-playbook ansible/tests/vars-test-local.yml
PLAY [localhost]
TASK [setup]
******************************************************************* ok: [localhost]
TASK [loop debug inventory hostnames]
****************************************** ok: [localhost] => (item=myhost2) => {
"item": "myhost2",
"msg": "the docker host is myhost2" } ok: [localhost] => (item=myhost1) => {
"item": "myhost1",
"msg": "the docker host is myhost1" }
TASK [loop debug items]
******************************************************** ok: [localhost] => (item=myhost1) => {
"item": "myhost1",
"msg": "the docker host is barbara" } ok: [localhost] => (item=myhost2) => {
"item": "myhost2",
"msg": "the docker host is heather" }
PLAY RECAP
********************************************************************* localhost : ok=3 changed=0 unreachable=0
failed=0
thanks!
If you want to refer one host define under /etc/ansible/host in a task or role, the bellow link might help:
https://www.middlewareinventory.com/blog/ansible-get-ip-address/
If you want to have your vars in files under group_vars, just move them here. Vars can be in the inventory ([group:vars] section) but also (and foremost) in files under group_vars or hosts_vars.
For instance, with your example above, you can move your vars for group tests in the file group_vars/tests :
Inventory file :
[lb]
10.112.84.122
[tomcat]
10.112.84.124
[jboss5]
10.112.84.122
...
[tests:children]
lb
tomcat
jboss5
[default:children]
tests
group_vars/tests file :
data_base_user=NETWIN-4.3
data_base_password=NETWIN
data_base_encrypted_password=
data_base_host=10.112.69.48
data_base_port=1521
data_base_service=ssdenwdb
data_base_url=jdbc:oracle:thin:#10.112.69.48:1521/ssdenwdb

Resources