Ansible Jinja Template config and stdout comparison - ansible

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

Related

Filter list in Ansible for files ending with j2

I am trying to filter a list but have only limited success.
The list is hardcoded (all_files) and filtering starting with anchor '^' works just fine.
But what I really need is identify all *.j2 files
My Ansible version is 2.9.25
- name: Execute any command on localhost
hosts: localhost
gather_facts: false
tasks:
- set_fact:
all_files: [
"ansible_vars/dev-deploy-vars.yml",
"Precompiled/Application/config.js.j2",
"Precompiled/Application/web.config.j2"
]
- set_fact:
files_starting_with_pattern: "{{ j2_files | select('match', '^Precompiled') | list }}"
files_ending_with_pattern: "{{ j2_files | select('match', 'j2$') | list }}"
Any idea? All I need is a list of jinja2 files (which can be empty)
Thanks!
Your problem is that you're using match instead of search to look for a pattern at the end of the string. From the documentation:
match succeeds if it finds the pattern at the beginning of the string, while search succeeds if it finds the pattern anywhere within string. By default, regex works like search, but regex can be configured to perform other tests as well, by passing the match_type keyword argument. In particular, match_type determines the re method that gets used to perform the search. The full list can be found in the relevant Python documentation here.
So you want:
- name: Execute any command on localhost
hosts: localhost
gather_facts: false
vars:
all_files:
- ansible_vars/dev-deploy-vars.yml
- Precompiled/Application/config.js.j2
- Precompiled/Application/web.config.j2
tasks:
- set_fact:
files_starting_with_pattern: >-
{{ all_files | select("match", "^Precompiled") | list }}
files_ending_with_pattern: >-
{{ all_files | select("search", "j2$") | list }}
- debug:
var: files_starting_with_pattern
- debug:
var: files_ending_with_pattern
You could alternately use match if you modify your search pattern:
- set_fact:
files_starting_with_pattern: >-
{{ all_files | select("match", "^Precompiled") | list }}
files_ending_with_pattern: >-
{{ all_files | select("match", ".*j2$") | list }}

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 2 template value should not changed

I have written jinja2 template in ansible. What i am trying to achieve is that if the service_name is not mentioned and if service_name already exists on the remote machine, ansible should not change the service_name with default name mentioned in the template. However, when the service_name is not defined, ansible replaces service name with "abc" on remote machine even service_name exists. Any help would be appreciated.
active={{ active_status}}
instrument={{ instrument_status }}
{% if service_name is defined %}
service_name={{ service_name }}
{% else %}
service_name=abc
{% endif %}
Thanks
Following my above comment, here is a possible example implementation to meet your requirements. test_template.j2 is the exact copy of your current template. You can pass the service name as an extra variable to test (-e service_name=my_service)
Basically, if service_name is not defined, we:
Check if the remote file already exists and slurp its content into a var
Look for the relevant line in the file. Note: the regex_replace('None', '') is here to make sure we get an empty string if previous search/matches did not return anything.
Set the service name only if something relevant was found in the prior tasks
Once this check/setting is done correctly, you simply have to copy your template, what ever the case is.
---
- name: Conditional writing of template
hosts: localhost
gather_facts: false
vars:
my_dest: /tmp/test_file.txt
active_status: some active value
instrument_status: some instrument value
tasks:
- name: Try to read service name from existing file when it is not defined
when: service_name is not defined
block:
- name: Check if file exists
stat:
path: "{{ my_dest }}"
register: my_file
- name: Try to read target file if exists
slurp:
src: "{{ my_dest }}"
when: my_file.stat.exists
register: my_file_slurp
- name: Look for service name if there
set_fact:
looked_service: >-
{{
my_file_slurp.content
| b64decode
| regex_search('^service_name=.*$', multiline=true)
| regex_replace('^service_name=(.*)$', '\1')
| regex_replace('None', '')
}}
when: my_file.stat.exists
- name: Update service name if found
set_fact:
service_name: "{{ looked_service }}"
when: looked_service | default('') | length > 0
- name: Copy template file to destination
template:
src: test_template.j2
dest: "{{ my_dest }}"

Get Number from hostname in 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"

Ansible conditional template variable substitution

I am trying to create configuration files from a template with include variables based on the fourth character of {{ ansible_hostname }}.
What works:
playbook:
---
- hosts: spock
roles:
- templaterole
role:
---
- name: testing autofs template on spock
template:
src=autofs
dest=/tmp/autofs
with_items:
- "{{ var_a }}"
when: ('{{ ansible_hostname }}' == "spock")
vars/main.yml:
var_a:
-
var_1: 'this is var_a1'
var_2: 'this is var_a2'
var_b:
-
var_1: 'this is var_b1'
var_2: 'this is var_b2'
template:
{{ item.var_1 }}
#
{{ item.var_2 }}
#
This works as expected and the output produces a /tmp/autofs file on the spock host that looks like:
this is var_a1
#
this is var_a2
#
Now, if I try to write the file based on trying to pull out the 4th character of the {{ ansible_hostname }}, the play does not get a match on the conditional and does not write the file. I'm trying this conditional in my role:
---
- name: testing autofs template on spock
template:
src=autofs
dest=/tmp/autofs
with_items:
- "{{ var_a }}"
when: ('{{ ansible_hostname }} | cut -c4' == "c") or
('{{ ansible_hostname }} | cut -c4' == "k")
the play skips this task due to not matching on the conditional. Ultimately i want to be able to pull any 4th character of our hostnames as this will always be predictable (can only be one of 4 known characters which defines my environment and lets me define the correct template variables based on these diff production environments.)
Can anyone help me to redefine my when statement such that i can do or conditionals and pull characters out of defined ansible variables like ansible_hostname?
Don't use curly brackets inside when statement, it's already a Jinja2 statement.
And in Jinja2 statements you use | to apply filter, but there is no cut filter available.
Your statement should be as simple as:
when: ansible_hostname[3] in 'ck'

Resources