I have what I'm hoping is a simple question to answer, we have multiple groups of servers that patch monthly with Ansible Tower. The morning prior to a group being patched I have Ansible send out an email to the respective groups that would work on those servers so they are aware prior to the patches.
Currently, I have the playbooks written manually where I have typed out the name of every server in each group, but as groups grow and change, this becomes a pain as I'm having to edit these playbooks each time.
How can I have Ansible populate the names of servers in a group without me having to type them out? Here's my example playbook:
- name: Alert for pending updates
hosts: localhost
tasks:
- name: Send email to groups
community.general.mail:
to:
- 'email address 1'
- 'email address 2'
- 'email address 3'
cc:
- 'email address 1'
- 'email address 2'
subject: Scheduled Update Alert
body: "Monthly OS patches will begin tonight at 9pm CST on the following systems: Server 1, Server 2, Server 3, Server 4, Server 5, and Server 6"
sender: "ansible.updates#domain.com"
headers: 'Reply-To=email#domain.com'
delegate_to: localhost
I'm not sure if this is relevant, but we do not use static inventory files, our inventories are synced with vCenter and the groups are created using tags in vCenter.
Thanks to anyone who takes time to help!
Edit
Using #U880D playbook:
---
- name: Test output of hosts in a group
hosts: localhost
gather_facts: false
tasks:
- name: Show Facts
debug:
msg: "{{ groups }}"
...
I'm using Ansible Tower, so the inventory in my original question is dynamically created from VMware and I use tags on the VMs in vCenter to create groups in my Ansible inventory, so rather than using the dynamically created inventory and having a massive output of groups and servers, I used a static "test" inventory with one server in it. I created a few different test groups and left some of them empty and populated others with the test server.
Here's the output of that:
ok: [localhost] => {
"msg": {
"Test_Group_1": [
"test server 1"
],
"Test_Group_2": [],
"Test_Group_3": [
"test server 1"
],
"all": [
"test server 1"
],
"ungrouped": []
}
}
How do you now filter this output down to a single group? Currently using this method it just prints all groups and their members. I want to be able to tell Ansible to select, for example Test_Group_3 and list it's members in my email body.
Ansible provides Specical Variables like groups_names and groups
A dictionary/map with all the groups in inventory and each group has the list of hosts that belong to it
An inventory like
[test]
test.example.com
ansible-inventory --graph
#all:
|--#test:
| |--test.example.com
|--#ungrouped:
ansible-inventory --list
{
"_meta": {
"hostvars": {}
},
"all": {
"children": [
"test",
"ungrouped"
]
},
"test": {
"hosts": [
"test.example.com"
]
}
}
has Default groups and a sample playbook like
---
- hosts: test
become: false
gather_facts: false
tasks:
- name: Show Facts
debug:
msg: "{{ groups }}"
will result into an output of
TASK [Show Facts] *******
ok: [test.example.com] =>
msg:
all:
- test.example.com
test:
- test.example.com
ungrouped: []
How can I have Ansible populate the names of servers in a group without me having to type them out?
The above mentioned approach could be the first step to get a list of hostnames for an email body.
How do you now filter this output down to a single group? Currently using this method it just prints all groups and their members. I want to be able to tell Ansible to select, for example test group and list it's members in my email body.
For this have a look into the following approach
---
- hosts: localhost
become: false
gather_facts: false
vars:
PATCHGROUP: 'test'
tasks:
- name: Show Facts
debug:
msg: "{{ groups[PATCHGROUP] | join(', ') }}"
which will result into an output of
TASK [Show Facts] **********************************************************************************************************************************
ok: [localhost] =>
msg: test.example.com, added.example.com
In other words, one need just to define the group dynamically and make a string out of the list by simply joining the elements and in the case shown probably end up with something like
body: "Monthly OS patches will begin tonight at 9pm CST on the following systems: {{ groups[PATCHGROUP] | join(', ') }}"
Here it was used Referencing nested variables from Dictionary variables and Manipulating strings.
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 have the following groups defined in my inventory:
[webservers]
[server_storage]
[server_ws1]
[webservers:children]
server_storage
server_ws1
During my play, I'm in need of getting the names of the children groups of group['webservers'] ONLY (I think of theese as 'subgroups')
So, let's say I would need to set_fact a variable that contains in this case, the list of strings:
- server_storage
- server_ws1
This would have to be dynamic, so if I add group 'server_ws2' to group['webservers'], this should return
- server_storage
- server_ws1
- server_ws2
I've been playing with the use of group_names, group['webservers'] (which doesn't return subgroups, but hostnames)
Basically, I need a simple way of getting a the list of subgroups of a specific group. Is this possible without the use of black magic?
UPDATE: The idea, is that the hosts could belong to more groups, but I need only the subgroups or children of webservers group. It's constant, no matter the host, the output should allways be the same.
By the way, this one didn't work How can I get a list of child groups in Ansible?, because it retuns all groups for the current host, I need only subgroups of the specified group.
Q: "Get the names of the children groups of group['webservers']"
A: For example, the inventory
shell> cat hosts
[webservers]
[server_storage]
host_01
[server_ws1]
host_02
[webservers:children]
server_storage
server_ws1
and the playbook
shell> cat playbook.yml
- hosts: webservers
tasks:
- set_fact:
subgroups: "{{ subgroups|default([]) + hostvars[item].group_names }}"
loop: "{{ groups.webservers }}"
run_once: true
- debug:
msg: "{{ subgroups|unique|difference(['webservers']) }}"
run_once: true
give (abridged)
shell> ansible-playbook -i hosts playbook.yml
msg:
- server_storage
- server_ws1
As a workaround, it's possible to use ansible-inventory to list the structure of the inventory, store the output in a file, and use this file as needed, e.g. include_vars in a playbook. See the example below
shell> cat hosts
host_01
host_02
[webservers]
[server_storage]
[server_ws1]
[webservers:children]
server_storage
server_ws1
shell> ansible-inventory -i hosts --list --output hosts.json
shell> cat hosts.json
{
"_meta": {
"hostvars": {}
},
"all": {
"children": [
"ungrouped",
"webservers"
]
},
"ungrouped": {
"hosts": [
"host_01",
"host_02"
]
},
"webservers": {
"children": [
"server_storage",
"server_ws1"
]
}
I cannot seem to get an Ansible debug statement in a loop to display the individual item values when running the debug statement in a role. For comparison, given this playbook named ./test.yaml:
- hosts: localhost
tasks:
- name: test
debug:
var: item
loop:
- 1
- 2
This command:
ansible-playbook test.yaml
Produces this result:
PLAY [localhost] *****...
TASK [test] ****...
ok: [localhost] => (item=1) => {
"item": 1
}
ok: [localhost] => (item=2) => {
"item": 2
}
But given this file: ./roles/TestRole/tasks/main.yaml:
- name: test
debug:
var: item
loop:
- 1
- 2
This command:
ansible localhost -m include_role -a name=TestRole
Produces this result:
localhost | SUCCESS => {
"changed": false,
"include_variables": {
"name": "FooRole"
}
}
localhost | SUCCESS => {
"msg" "All items completed"
}
So - rather than displaying the item values, the debug statement in the role just says "All items completed". It looks like looped debug statements in roles behave differently than looped debug statements in playbooks. Am I doing something wrong? Running Ansible 2.7.9 on python 2.7.5.
This is effectively what you get from the adhoc command (and I have absolutely no clue why). Meanwhile this is a rather edge case of using it. You would rather include a role in a playbook. Both playbook examples below will give you the result you are expecting:
Classic role execution
---
- name: test1 for role
hosts: localhost
gather_facts: false
roles:
- role: TestRole
Include role
---
- name: test2 for roles
hosts: localhost
gather_facts: false
tasks:
- name: include role
include_role:
name: TestRole
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.
I am using ansible to create a env var list of hosts and ports to be used for a database connection string.
The application requires two vars: a host list and a port list and uses that to create a connection string.
hosts: 127.0.0.1,127.0.0.1,127.0.0.1
ports: 123,123,123
host and port are matched based on index postion.
I am able to get hosts and join them as required. What I am unable to do is dynamically create a ports string based on the number of hosts. We are currently using the same port so it should be easier.
What I would like to do is create ports:123,123,123 where the number of times 123 is repeated is equal to the number of hosts.
Looked at this link for gettingnumber of hsots: Ansible: Get number of hosts in group
How i just need to print 123 that numebr of times and assign it to ports.
You could use sequence plugin
vars:
hosts: ['127.0.0.1','127.0.0.1','127.0.0.1']
- name: ports fact with size of hosts
set_fact:
ports: "{{ ports | default([]) + [123]}}"
with_sequence: count="{{ hosts | length }}"
- debug: msg="{{ ports }}"
TASK [debug] ********************* ok: [localhost] => {
"ports": [
123,
123,
123
] }
Source: https://docs.ansible.com/ansible/2.5/plugins/lookup/sequence.html