Get Number from hostname in Ansible - ansible

In Puppet I can extract the number of the Hostname with this example:
$host_number = regsubst($hostname, '^\w+(\d\d)', '\1')
Is there something similar in Ansible?
e.g.:
fqdn: test01.whatever
hostname: test01
output -> newvariable: 01
I want to extract only the number out of the Hostname, so I can use it in my Playbook as a variable.

This will fetch the inventory_hostname and replace any character text with nothing, leaving the numeric text. Obviously, you can use your imagination to apply whatever regex needed.
Ansible playbook:
vars:
host_names:
- {{ inventory_hostname | regex_replace('[^0-9]','') }}
Jinja2 Template:
{% for host in groups['some_group_in_inventory'] %}
Some description of the host where I needed the Number {{ host | regex_replace('[^0-9]','') }}
{% endfor %}

Theres multiple ways of doing the same thing...
You can use ansible_facts, ansible_hostname, shell commands, etc...
---
- hosts: localhost
gather_facts: yes
tasks:
- debug: var=ansible_facts.hostname
- debug: var=ansible_hostname
As raVan96 said, you need to explain better what you need and what you expect...

If you need to extract only "test01" from hostname, then you can use ansible build in filter "regex_replace". Here is my example:
{{ ansible_hostname | regex_replace('^([a-zA-Z_]+)(\d+)$','\\2') | int }}
Then you get: 1
If you need "01", then delete part to pass to integer - "int"

Related

Jinja2 & Ansible - Loop over lines in several text files

I'm using Ansible to retrieve a list of website we host over 50+ servers. I'm using a SQL query to store in a text file named as hostname_websites.txt using Ansible.
The next step is to build a prometheus (blackbox exporter) configuration file using that data.
Each line of each text file represent an URL. For the sake of representation I'll declare that the URL is $URL and the hostname is $HOSTNAME.
I need some jinja2 magic to generate a single yaml file as :
{% for site in *** each text file *** %}
- targets: ['{{ $URL }}']
labels: {'node': '{{ $HOSTNAME }}'}
{% endfor %}
Given that in the end I need to have a config line containing each time the URL which is a line in text file, and the hostname as a label which is in the txt filename.
Every hostname is in a group called production in Ansible, so I tried looping over that group using jinja2 as follow:
{% for host in group['production'] %}
{{ lookup('file', '{{ host }}_websites.txt).splitlines()' }}
{% endfor %}
That gave me some jinja2 parsing error, as if it wasn't rendering the for loop at all.
I stopped here, in the spirit that I would build by configuration skel around this statement.
There are two problems with your template that jump out immediately:
There is no variable group in Ansible (unless you've created one explicitly); you probably want groups.
You never nest Jinja {{...}} markers. If you want to generate a string containing a variable value, you can use string concatenation:
lookup('file', host ~ '_websites.txt')
Or string formatting:
lookup('file', '%s_websites.txt' % host)
Assuming that our inventory has a host node0 in the group production and that there exists a file node0_websites.txt with the following content:
http://stackoverflow.com/
http://ansible.com/
http://stackexchange.com/
Then running this playbook:
- hosts: localhost
gather_facts: false
tasks:
- copy:
dest: output.txt
content: |
{% for host in groups['production'] %}
# Host: {{ host }}
{% for url in lookup('file', host ~ '_websites.txt').splitlines() %}
- {{ url }}
{% endfor %}
{% endfor %}
Generates the following content in output.txt:
# Host: node0
- http://stackoverflow.com/
- http://ansible.com/
- http://stackexchange.com/

can I run a shell command in jinja2 template in ansible

This is a playbook that connects with all the servers in my inventory file, and makes a note of the server ip and mount point information of hosts where mount point usage exceeds 80% and it writes to a text file on the localhost (ansible-controller).
- hosts: all
tasks:
- shell:
cmd: df -h | sed 's/%//g' | awk '$5 > 80 {if (NR > 1) print $5"%",$6}'
register: disk_stat
- debug:
var: disk_stat
- file:
path: /home/app/space_report_{{ td }}.txt
state: touch
run_once: true
delegate_to: localhost
- shell: echo -e "{{ ansible_host }} '\n' {{ disk_stat.stdout_lines| to_nice_yaml }}" >> /home/thor/space_report_{{ td }}.txt
args:
executable: /bin/bash
delegate_to: localhost
I was wondering if I could create a jinja2 template and bring the playbook down to one task. I am stuck at integrating a shell command inside the jinja2 template and I am not sure if it is possible. Please advise.
- hosts: all
tasks:
- template:
src: monitor.txt.j2
dest: /home/app/playbooks/monitor.txt
delegate_to: localhost
monitor.txt.j2
{% for host in groups['all'] %}
{{ hostvars[host].ansible_host }}
--shell command--
{% endfor %}
As I say in my comment under your question, while it is possible to use shell or command modules, Ansible is a configuration / automation tool, so it's better to forget your shell coding / logic to use native ansible functionalities, that'll ease the tasks / playbook writing.
For instance, it's no needed to do a df because ansible when connecting will gather facts about the target, including devices and their capacity and current usage so you can use that directly.
For the jinja question, you can use the module copy and pass directly jinja code in the option content on this module:
- name: Trigger a tasks on hosts to gather facts
hosts: all
tasks:
- copy:
dest: /home/app/playbooks/monitor.txt
content: |
{% for host in groups['all'] %}
{% for dev in hostvars[host].ansible_mounts %}
{% if (dev.block_used / dev.block_total * 100 ) > 80 %} {{ dev.block_used / dev.block_total * 100 }} {{ dev.mount }} {% endif %}
{% endfor %}
{% endfor %}
run_once: true
delegate_to: localhost

Ansible Jinja Template config and stdout comparison

My pseudocode:
1. Get the ntp server config from "sh run"
2. Store that to a list
3. Jinja template generates the required config. I am passing the ntp_server IPs via -e (extra variables).
4. Add the config from 3, compare 3 and 4 and remove the rest.
I am struggling on step 4 [comparison part]. How do I compare the current config with the config generated from the jinja template? I am using roles.
Please advise.
# Jinja Template
{% for ntp_srv in ntp_servers %}
ntp server {{ ntp_srv }}
{% endfor %}
# tasks file for ansible-ios-ntp
---
- name: Current Edge servers before
ios_command:
commands:
- sh run | include ntp server
register: runconfser
- debug:
var: runconfser
# NTP SECTION - START
- name: Set NTP servers
ios_config:
src: ntprequired.j2
notify: Save Config
- name: Remove the rest NTP Servers
with_items: "{{ runconfser.stdout_lines[0] }}"
when: (item not in {src: 'ntprequired.j2'} and (item!=""))
ios_config:
lines:
- "no {{ item }}"
If I understand your question correctly, I believe you'd want to extract the current IPs from the registered output, then capture the ones which are not in the ntp_servers list:
- set_fact:
need_ips: |
{{ ntp_servers | difference(stdout_lines | join(" ") | regex_findall('[0-9.]+')) }}
Or you can obtain the "extra" ones by inverting the order of the difference:
- set_fact:
extra_ips: |
{{ stdout_lines | join(" ") | regex_findall('[0-9.]+') | difference(ntp_servers) }}
I cheated by just searching for [0-9.]+ but you can, of course, make that expression less tolerant by being more specific (aka [1-9](.[0-9.]){3})

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"

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

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') }}

Resources