Ansible create var from hosts - ansible

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

Related

Ansible: Listing inventory group members

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.

Get IP address from hosts in a Group

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

How to get the value(ip address from interface) from the output and store it to variable

I am writing a playbook task to get the IP address from the output and store the value and use it for other tasks.
Ansible playbook
name: Extract the secondary router ipsec tunnel address
hosts: secondary
gather_facts: false
connection: local
tags:
"sec_tunnel_ip"
tasks:
name: Extract Tunnel1 ipsec interface address
ios_command:
commands: "sh ip int br | sec Tunnel1"
register: save_tunn_out
debug:
msg: "{{save_tunn_out.stdout}}"
I am getting the output like below:
ok: [172.16.12.1] => {
"msg": [
"Tunnel1 172.16.121.54 YES manual up up \nTunnel100 10.0.0.101 YES manual up up"
]
}
But I want to extract the first ip interface output(for tunnel1) like below, and store it in a variable.
172.16.121.54
I am not sure how to get it without regex and store it on the variable.
Please help!
If you don't want to use regex, which would be your best bet, you have to rely on splitting the string.
Assuming it will always be after Tunnel1 and followed by a space, you can do it like this.
- name: Extract Tunnel1 ipsec interface address
set_fact:
save_tunn_out: "Tunnel1 172.16.121.54 YES manual up up \nTunnel100 10.0.0.101 YES manual up up"
- name: Extract IP address
debug:
var: save_tunn_out.split("Tunnel1 ")[1].split(" ")[0]
Which gives you the desired output
ok: [localhost] => {
"save_tunn_out.split(\"Tunnel1 \")[1].split(\" \")[0]": "172.16.121.54"
}
To store in a variable afterward, you can use set_fact like this
- name: Store in a variable
set_fact:
ip_address: "{{save_tunn_out.split('Tunnel1 ')[1].split(' ')[0]}}"
- name: Debug variable
debug:
msg: "Ip address is : {{ip_address}}"
Output :
ok: [localhost] => {
"msg": "Ip address is : 172.16.121.54"
}

How can I resolve an inventory member who has a certain attribute?

I need to pass a specific host from my inventory as a parameter into a role. The host is part of a group but is demarcated by a variable that none of the other hosts have.
snippet of: hosts.yml
dbservers:
hosts:
pg01:
ansible_host: pg01.domain.com
master_slave: master
pg02:
ansible_host: pg02.domain.com
master_slave: slave
I want to be able to resolve pg01 based on the fact that the variable master_slave is set to 'master' such that I can call into a role like this:
- name: Do something
include_role:
name: a.database.role.to.run.on.master
vars:
master_database_host: {{ something that resolves to pg01 }}
How can I resolve the appropriate host from inventory?
You can use a mix of filters to extract the host you need:
tasks:
- debug:
msg: '{{groups["group_name"] | map("extract", hostvars) | selectattr("master_slave", "equalto", "master") | map(attribute="inventory_hostname") | list}}'
Step by step:
groups["group_name"] is a list of all the hosts in the group group_name.
map("extract", hostvars) takes hostvars, a dictionary mapping the host to their variables, and extracts the hosts that are in group_name (i.e. groups["group_name"]). This results in a list containing the hosts in group_name mapped to their variables.
selectattr("master_slave", "equalto", "master") selects all hosts who have an attribute master_slave that equals to master. This result in a list with all the hosts that are masters mapped to their variables.
map(attribute="inventory_hostname") takes a list as input and returns the inventory_hostname attribute of every item. This result in a list with all the hosts that are masters.
The play below (with json_query)
- hosts: dbservers
tasks:
- set_fact:
master_database_host: "{{ groups['dbservers']|
map('extract',hostvars)|
list|
json_query('[?master_slave==`master`].inventory_hostname')|
first }}"
- debug:
var: master_database_host
gives
ok: [pg02] => {
"master_database_host": "pg01"
}
ok: [pg01] => {
"master_database_host": "pg01"
}
You can use if else condition in vars to assign the values.
So your play should be Something like this.
- name: Do something
include_role:
name: a.database.role.to.run.on.master
vars:
master_database_host: "{{ hostvars['pg01']['ansible_host'] if \"{{ hostvars['pg01']['master_slave'] }}\" == 'master' else 'default value goes here'}}"
Make sure to use proper escaping to make the conditional statements work.
The reason this works is Since ansible internally uses python to do stuff it is a way to use ternary operator in python.
You could also generate dynamic groups based on the master/slave status:
---
- name: Play to create dynamic groups
hosts: dbservers
gather_facts: false
tasks:
- name: Create groups based on variable master_slave
group_by:
key: "database-{{ hostvars[inventory_hostname]['master_slave'] }}"
- name: Play to use the dynamic group database-master
hosts: database-master
gather_facts: false
tasks:
- name: Show hosts in group
debug:
msg: "This is {{ inventory_hostname }} from the dynamic database-master group."
The first play uses all dbservers and creates the dynamic groups based on the master_slave variable.
The dynamic groups are:
database-master containing pg01
database-slave containing pg02
The second play uses one of the dynamic created groups.
To use group_by the used variable has to exist for all hosts used.
This concept works best on automatic variable gathered by ansibles setup e.g. ansible_distribution to create dynamic groups based on the Distribution (Debian, Redhat, Ubuntu ...) or distribution versions using ansible_distribution_version.

How to loop through inventory and assign value in 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.

Resources