How to assign an array to a variable in an Ansible-Playbook - ansible

In a playbook I got the following code:
---
- hosts: db
vars:
postgresql_ext_install_contrib: yes
postgresql_pg_hba_passwd_hosts: ['10.129.181.241/32']
...
I would like to replace the value of postgresql_pg_hba_passwd_hosts with all of my webservers private ips. I understand I can get the values like this in a template:
{% for host in groups['web'] %}
{{ hostvars[host]['ansible_eth1']['ipv4']['address'] }}
{% endfor %}
What is the simplest/easiest way to assign the result of this loop to a variable in a playbook? Or is there a better way to collect this information in the first place? Should I put this loop in a template?
Additional challenge: I'd have to add /32 to every entry.

You can assign a list to variable by set_fact and ansible filter plugin.
Put custom filter plugin to filter_plugins directory like this:
(ansible top directory)
site.yml
hosts
filter_plugins/
to_group_vars.py
to_group_vars.py convert hostvars into list that selected by group.
from ansible import errors, runner
import json
def to_group_vars(host_vars, groups, target = 'all'):
if type(host_vars) != runner.HostVars:
raise errors.AnsibleFilterError("|failed expects a HostVars")
if type(groups) != dict:
raise errors.AnsibleFilterError("|failed expects a Dictionary")
data = []
for host in groups[target]:
data.append(host_vars[host])
return data
class FilterModule (object):
def filters(self):
return {"to_group_vars": to_group_vars}
Use like this:
---
- hosts: all
tasks:
- set_fact:
web_ips: "{{hostvars|to_group_vars(groups, 'web')|map(attribute='ansible_eth0.ipv4.address')|list }}"
- debug:
msg: "web ip is {{item}}/32"
with_items: web_ips

in playbook:
vars:
- arrayname:
- name: itemname
value1: itemvalue1
value2: itemvalue2
- name: otheritem
value1: itemvalue3
value2: itemvalue4
in template: (example is of type ini file, with sections, keys and values):
{% for item in arrayname %}
[{{ item.name }}]
key1 = {{ item.value1 }}
key2 = {{ item.value2 }}
{% endfor %}
This should render the template as:
[itemname]
key1 = itemvalue1
key2 = itemvalue2
[otheritem]
key1 = itemvalue3
key2 = itemvalue4

Variables can be represented as standard YAML structures so you can assign a list value to a key like this:
---
- hosts: db
vars:
postgresql_ext_install_contrib: yes
postgresql_pg_hba_passwd_hosts:
- '10.129.181.241/32'
- '1.2.3.0/8'

You can use jinja2 filters:
{{ groups['nodes']|map('extract', hostvars, ['ansible_eth1','ipv4', 'address']) |list }}
will return a list of ip addresses. i.e.
---
- hosts: db
vars:
postgresql_ext_install_contrib: yes
postgresql_pg_hba_passwd_hosts: {{ groups['nodes']|map('extract', hostvars, ['ansible_eth1','ipv4', 'address']) |list }}
...
Does not include the challange (appending /32). But it should also be possible somehow with jinja2 filters.
Reqiures ansible version >= 2.1

To add '/32' to the address, you can use the Ansible ipaddr filter (converting to CIDR notation).
{{ ip_addresses|ipaddr('host') }}

Related

Ansible - how to use if condition to set variable in jinja based on inventory_hostname

I have an ansible jinja which is making use of variable {{ mydata }} the value of this variable differs for each host available in the inventory. I am trying to use if condition in my vars to set it up but it's throwing below error. Can someone guide me?
Jinja :
appkey = {{ mydata }}
vars/vars.yml
{% if inventory_hostname == "my-host-name" %}
mydata: myvalue
{% endif %}
Expecting value: line 2 column 1 (char 1)\n\nSyntax Error while loading YAML.\n found character that cannot start any token\n\nThe error appears to be in
Put the data into a dictionary
mydata_dict:
my_host_name: myvalue
default: default value
mydata: "{{ mydata_dict[inventory_hostname]|default(mydata_dict.default) }}"
For example the playbook
- hosts: my_host_name,my_host_name2
vars:
mydata_dict:
my_host_name: myvalue
default: default value
mydata: "{{ mydata_dict[inventory_hostname]|default(mydata_dict.default) }}"
tasks:
- debug:
var: mydata
gives
ok: [my_host_name] =>
mydata: myvalue
ok: [my_host_name2] =>
mydata: default value

get list of addresses (ansible_host) from a group of servers

I have an ansible inventory with this format:
[servers:children]
s1
s2
s3
[s1]
10.10.0.80 hostname=s1.example.com
[s2]
10.10.1.80 hostname=s2.example.com
[s3]
jail_s3 ansible_host=10.10.2.80 ansible_port=2222 hostname=s3.example.com
I would like to get a comma-separated of all the ansible hosts belonging to the group servers, for this, I have been using:
ips = "{{ query('inventory_hostnames', 'sites') | map('ipaddr') | join(',') }}"
But the problem I have now with this inventory is that query('inventory_hostnames') don't return the IP for s3 instead returns a string jail_3 the output of the list is like:
'10.10.0.80'
'10.10.1.80'
'jail_s3'
I found that I could use a loop and filter the output something like this:
debug:
msg: "{{ item if (item| ipaddr) else hostvars[item].ansible_host }}"
loop: "{{ query('inventory_hostnames','servers') }}"
Or in a template within a loop:
{% for host in query('inventory_hostnames', 'servers') %}
{{ hostvars[host].ansible_host|default(hostvars[host].inventory_hostname) }}
{% endfor %}
In this case, the filter ipaddr returns false for jail_s3 and then I fall back to hostvars[item].ansible_host.
This works but I would like to know if it could be done in "one-liner" probably by using a map, or something that could take the list of query('inventory_hostnames','servers') | ???
Any ideas?

ansible - get host by variable

I would like to filter hosts by a variable set on them. For example:
I have a group of hosts, one is master, the rest are slaves. On the master the variable replica_type: master is set. I would now like to get the master server dynamically.
My working aproach is:
- set_fact:
master_server_string: >-
{% for server in groups.my_servers %}
{% if hostvars[server]['replica_type']=='master' -%}
{{ server }}
{% endif %}
{% endfor %}
- set_fact:
master_server: "{{ master_server_string|trim }}"
Is there a way to perform the same with a filter (or at least in a single task)?
e.g. something like
"{{ server for server in groups.my_servers if hostvars[server]['replica_type'] == 'master' }}"
I believe you are looking for the group_by module.
https://docs.ansible.com/ansible/latest/modules/group_by_module.html
- group_by:
key: {{ replica_type }}
You can pull a value out of a group of hosts by a property by looping the group and checking for a matching value like this too:
- set_fact:
master_server: "{{ item }}"
with_items: "{{ groups.my_servers }}"
when: hostvars[item].replica_type == 'master'

How to dynamically create an ansible list out of hostvars?

I have a few variables defined for every host. Like...
hosts:
- hostA:
vars:
self_ip: "192.168.1.10"
self_port: "8001"
- hostB:
vars:
self_ip: "192.168.1.11"
self_port: "8002"
Inside one of the roles, I want to define a variable, which is a combination of few host variables. For example...
all_endpoints: 192.168.1.10:8001,192.168.1.11:8002
How can I do this?
I tried using Jinja2 for loops like below:
rs_members:
"{% for host in groups['all_hosts'] %}
- {{hostvars[host]['self_ip']}}:{{hostvars[host]['self_port']}}
{% endfor %}"
This seems to be creating a string. Not a list.
Can someone tell me what is wrong? And is there a way to use ansible filters to achieve this?
- set_fact:
all_endpoints: "{{ hosts|json_query('[].vars.[self_ip, self_port]') }}"
- set_fact:
touples: "{{ touples|default([]) + [ item.0 + ':' + item.1 ] }}"
loop: "{{ all_endpoints }}"
- debug:
var: touples
gives
"touples": [
"192.168.1.10:8001",
"192.168.1.11:8002"
]

ansible how to use a variable in a for loop

I have an ansible task like this:
- name: coreos network configuration
{% for interface in argument['interfaces'] %}
{% if argument[interface]['role'] == 'ingest' %}
script: netconfiginput.sh -i {{interface}} #incorrect, how to get the value of the interface variable of the for loop?
{% endif %}
{% endfor %}
While running this ansible task, I pass a JSON string argument:
ansible-playbook --extra-vars 'argument={"interfaces":["eno1","eno2","ep8s0"],"eno2":{"role":"ingest"}}' network-config.yml
What I want to do is, loop through the JSON array called interfaces, which are a list of network interfaces, when the role of the interface is called ingest, I run a script and pass the network interface as an argument to the script, my implementation is incorrect, how can I do this?
You need to use with_items and replace variable name with item.
A rough example:
name: task name
script: netconfiginput.sh -i {{ item }}
with_items: interfaces_array
when: some_role == 'ingest'
To understand what kind of data you're sending, use the following:
name: debugging
debug:
var: argument
That should show you, amongst other things, whether or not Ansible is considering parts of your variable's structure valid arrays or not.
Jinja2 can be used in ansible templates, not in playbooks.
Ansible supports looping over hashes. You can try this:
---
- hosts: <test_servers> # replace with your hosts
vars:
interfaces:
eno1:
role: null
eno2:
role: ingest
ep8s0:
role: null
tasks:
- name: coreos network configuration
script: netconfiginput.sh -i {{ item.key }}
with_dict: "{{interfaces}}"
when: item.value.role == "ingest"

Resources