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
Related
I have a simple playbook referencing an inventory hosts.yml file. I want to be able to query a group, and retrieve the IP address for hosts in that group.
Simple hosts.yml
---
all:
hosts:
w2k16-std:
ansible_host: 10.1.1.12
in_play_ip: 10.200.2.12
w2k12-x64:
ansible_host: 10.1.1.13
in_play_ip: 10.200.2.13
lin-test:
ansible_host: 10.1.1.20
in_play_ip: 10.200.2.20
children:
windows:
vars:
ansible_connection: winrm
ansible_winrm_transport: certificate
ansible_winrm_cert_pem: /home/user/.ssh/windows.pem
ansible_winrm_cert_key_pem: /home/user/.ssh/windows_key.pem
ansible_port: 5986
ansible_winrm_scheme: https
ansible_winrm_server_cert_validation: ignore
hosts:
w2k12-x64:
w2k16-std:
linux:
hosts:
lin-test:
targets:
hosts:
w2k12-x64:
w2k16-std:
Here's the simple playbook:
---
- hosts: kali
vars:
tasks:
- name: Create comma delimited list of target names from targets group.
set_fact:
targets: "{{ groups.targets | list | join(',') }}"
- name: List targets from Inventory
debug:
var: targets
Produces the following output:
TASK [List targets from Inventory] ***************************************
ok: [lin-test] => {
"targets": "w2k12-x64,w2k16-std"
I want be able do the following:
Retrieve the members of the targets group (w2k12-x64 & w2k16-std)
Get the IP address listed in ansible_host values.
Create a comma delimited list of the target's IPs (10.1.1.12,10.1.1.13).
So far I can:
Retrieve the members of the targets group (w2k12-x64 & w2k16-std)
Create a comma delimited list of the target names.
I'm not sure if my hosts file is correctly laid out, or if I just need help with the query?
Desired output:
Produce the following output:
TASK [List target IPs from Inventory] ***************************************
ok: [lin-test] => {
"targets": "10.1.1.12,10.1.1.13"
You can extract the value you are looking for from the hostvars special variable.
You can also filter the items of the dictionary hostvars contained in the group targets thanks to the filters dict2items, which will create a key/value list from the dictionary, and selectattr to get only the items of the created list having a key matching in the targets group.
Then, you just need to reduce the list of dictionaries to a simple list via map, because you are only interested in the ansible_host attribute, and join the whole thing together.
So, this end up with this single task:
- debug:
msg: >-
{{
hostvars
| dict2items
| selectattr('key', 'in', groups.targets)
| map(attribute="value.ansible_host")
| join(',')
}}
Which yields your expected:
TASK [debug] **************************************************************
ok: [localhost] =>
msg: 10.1.1.12,10.1.1.13
I need some help here in determining the best way to do this and how to setup my playbook to appropriately pull variables for each node.
Lets say I have 3 hosts
/etc/ansible/hosts:
host1
host2
host3
I have a variable file with multiple entries in it
vars/IPs.yaml:
---
IP: ['192.168.77.35', '192.167.77.36', '192.168.77.37']
I am running this playbook:
network_change.yaml:
---
- hosts: all
vars_files:
- vars/IPs.yaml
tasks:
- name: Check 10G Interface
stat:
path: /etc/sysconfig/network-scripts/ifcfg-card-10Gb-1
register: teng
- name: Change 10G Interface Settings
lineinfile:
path: /etc/sysconfig/network-scripts/ifcfg-card-10Gb-1
regexp: '{{item.From}}'
line: '{{item.To}}'
when: teng.stat.exists
with_items:
- { From: 'IPADDR=', To: 'IPADDR={{IP}}'}
I have this working for a single host just fine...but when I have multiple hosts I'm not sure how to loop through the IPs.yaml variables and pull the next value the next time the loop runs. Is there also a way for me to not use a dictionary .yaml variable, can I just use a raw text file that has the IPs on newlines?
Essentially I want to loop through and have the hosts show this in each of the respective hosts /etc/sysconfig/network-scripts/ifcfg-card-10Gb-1.
host1 = 'IPADDR=192.168.77.35'
host2 = 'IPADDR=192.168.77.36'
host3 = 'IPADDR=192.168.77.37'
The end game is to be able to do this over 100+ hosts with simple text files, rather than dictionary yaml files and will be including multiple variables, etc. The systems will all be able to be hit via dhcp/hostnames/IP.
Create a dictionary with the data, e.g.
- set_fact:
_dict: '{{ dict(ansible_play_hosts|zip(IP)) }}'
run_once: true
should give
_dict:
host1: 192.168.77.35
host2: 192.167.77.36
host3: 192.168.77.37
Then use this dictionary and select the IP, e.g.
- name: Change 10G Interface Settings
lineinfile:
path: /etc/sysconfig/network-scripts/ifcfg-card-10Gb-1
regexp: 'IPADDR='
line: 'IPADDR={{ _dict[inventory_hostname] }}'
when: teng.stat.exists
You might want to test it first, e.g.
- name: Change 10G Interface Settings
debug:
msg: 'IPADDR={{ _dict[inventory_hostname] }}'
should give
ok: [host2] =>
msg: IPADDR=192.167.77.36
ok: [host1] =>
msg: IPADDR=192.168.77.35
ok: [host3] =>
msg: IPADDR=192.168.77.37
The next option, instead of creating the dictionary, is to calculate the index in the list, e.g. should give the same result
- name: Change 10G Interface Settings
debug:
msg: 'IPADDR={{ IP[_index|int] }}'
vars:
_index: '{{ ansible_play_hosts.index(inventory_hostname) }}'
Q: "Use a raw text file that has the IPs on newlines"
A: Create the file, e.g.
shell> cat IP.txt
192.168.77.35
192.167.77.36
192.168.77.37
Create the list on the fly, e.g.
- set_fact:
IP: "{{ lookup('file', 'IP.txt').split('\n') }}"
run_once: true
should give
IP:
- 192.168.77.35
- 192.167.77.36
- 192.168.77.37
The more robust solution would be to put the hashes into the file, e.g.
shell> cat IP.txt
host1: 192.168.77.35
host2: 192.167.77.36
host3: 192.168.77.37
Then the task
- include_vars:
file: IP.txt
name: _dict
should create the dictionary
_dict:
host1: 192.168.77.35
host2: 192.167.77.36
host3: 192.168.77.37
But, the most simple solution would be to store this dictionary in IP.yml and put it into the directory group_vars/all.
I have an inventory like:
all:
children:
server_group1:
hosts:
host1:
server_group2:
children:
app1:
hosts:
host2:
host3:
app2:
hosts:
host4:
host5:
server_group3:
...
I have organized my server variables like so:
> cat group_vars/server_group2/app1
app1:
name1: value1
name2: value2
> cat group_vars/server_group2/app2
app2:
name1: value11
name2: value21
I am trying to name my dict after the group (thus making them unique) and access it in my playbook:
hosts: server_group2
tasks:
- name: check file
local_action: stat path=path/to/test/{{hostvars[0].name1}}
register: payld_txt
- name: conditional transfer
copy:
src: path/to/test/{{hostvars[0].name1}}
dest: /svr/path/{{hostvars[0].name2}}
when: payld_txt.stat.exists
I end up with this error:
The task includes an option with an undefined variable. The error was: 'name1' is undefined
Where am I going wrong?
Before you go any further, you need to fix your inventory which does not respect ansible's structure for yaml sources. A simple command as the following can give you some hints:
$ ansible -i inventories/test.yml all --list-hosts
[WARNING]: Skipping unexpected key (server_group1) in group (all), only "vars", "children" and "hosts" are valid
[WARNING]: Skipping unexpected key (server_group2) in group (all), only "vars", "children" and "hosts" are valid
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
hosts (0):
The correct syntax is:
---
all:
children:
server_group1:
hosts:
host1:
server_group2:
children:
app1:
hosts:
host2:
host3:
app2:
hosts:
host4:
host5:
Which now gives:
$ ansible -i inventories/test.yml all --list-hosts
hosts (5):
host1
host2
host3
host4
host5
``hostvars[0].name1`` The error was: 'name1' is undefined
Q: "Where am I going wrong?"
A: The variable name1 is item of the dictionary app1 or app2. It must be referenced app1.name1 or app2.name1. In addition to this,
hostvars is a dictionary not an array. hostvars[0] does not exist. An item of a dictionary must be referenced by a key. For example the play below
- hosts: server_group2
tasks:
- set_fact:
my_keys: "{{ hostvars.keys()|list }}"
run_once: true
- debug:
var: my_keys
run_once: true
- debug:
msg: "{{ hostvars[item].app1.name1 }}"
loop: "{{ my_keys }}"
when: "item in groups['app1']"
run_once: true
- debug:
msg: "{{ hostvars[item].app2.name1 }}"
loop: "{{ my_keys }}"
when: "item in groups['app2']"
run_once: true
gives
ok: [host5] =>
my_keys:
- host5
- host4
- host3
- host2
- host1
ok: [host5] => (item=host3) =>
msg: value1
ok: [host5] => (item=host2) =>
msg: value1
ok: [host5] => (item=host5) =>
msg: value11
ok: [host5] => (item=host4) =>
msg: value11
Optionally use json_query to create the list of the keys
- set_fact:
my_keys: "{{ hostvars|dict2items|json_query('[].key') }}"
run_once: true
The simplified version of the playbook
- hosts: server_group2
tasks:
- debug:
msg: "{{ hostvars[inventory_hostname].app1.name1 }}"
when: "inventory_hostname in groups['app1']"
- debug:
msg: "{{ hostvars[inventory_hostname].app2.name1 }}"
when: "inventory_hostname in groups['app2']"
gives
skipping: [host5]
skipping: [host4]
ok: [host3] =>
msg: value1
ok: [host2] =>
msg: value1
ok: [host5] =>
msg: value11
ok: [host4] =>
msg: value11
skipping: [host3]
skipping: [host2]
In fact, addressing hostvars[inventory_hostname] is not necessary. The simplified tasks below give the same output.
- debug:
msg: "{{ app1.name1 }}"
when: "inventory_hostname in groups['app1']"
- debug:
msg: "{{ app2.name1 }}"
when: "inventory_hostname in groups['app2']"
Groups configured in Ansible inventory:
Group_A has 30 servers
Group_B has 40 Servers
Group_C has 15 Servers
I want to take 10 servers from each group and make a new group without editing the inventory manually.
These 10 servers is a variable that can change dynamically. If that works I got another question what if the inventory itself is dynamic
[Group_C]
server-1
server-2
server-3
...
server-10
''' New group created From 3 grouped servers now will be used in a playbook '''
(ansible 2.8.3)
If the inventory is dynamic we don't know the hosts. Let's assume we could choose any of them. Let's create the list of the selected hosts first and then loop add_hosts. With the inventory
[Group_A]
A-0
A-1
..
A-29
[Group_B]
B-0
B-1
..
B-39
[Group_C]
C-0
C-1
..
C-14
the plays below
- name: Create Group_X
hosts: localhost
vars:
no_of_servers: 2
my_groups:
- Group_A
- Group_B
- Group_C
tasks:
- set_fact:
my_list: "{{ my_list|default([]) +
groups[item][0:no_of_servers] }}"
loop: "{{ my_groups }}"
- add_host:
name: "{{ item }}"
groups: Group_X
loop: "{{ my_list }}"
- debug:
msg: "{{ groups['Group_X'] }}"
- name: Use Group_X
hosts: Group_X
gather_facts: false
tasks:
- debug:
msg: "{{ inventory_hostname }} is member of {{ group_names }}"
run_once: true
give
ok: [localhost] => {
"msg": [
"A-0",
"A-1",
"B-0",
"B-1",
"C-0",
"C-1"
]
}
ok: [A-0] => {
"msg": "A-0 is member of [u'Group_A', u'Group_X']"
}
Random choice.
It is possible to make the selection of the hosts random with the simple plugin below
$ cat filter_plugins/list_methods.py
import random
def list_sample(l,n):
return random.sample(l,n)
class FilterModule(object):
def filters(self):
return {
'list_sample' : list_sample
}
With the modification below
- set_fact:
my_list: '{{ my_list|default([]) +
groups[item]|list_sample(no_of_servers) }}'
the plays give for example
ok: [localhost] => {
"msg": [
"A-8",
"A-9",
"B-8",
"B-2",
"C-2",
"C-5"
]
}
ok: [A-8] => {
"msg": "A-8 is member of [u'Group_A', u'Group_X']"
}
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.