Jinja2 expression for split , replace and join - ansible

In Ansible playbooks, We have the variable 'dns_name: xyz.abc.pqr.*.com' where as we have one template file called es_config.yml there the value of cname should be (cname: .abc.pqr..com)
How can we write jinja2 expression for this ?
dns_name: xyz.abc.com (Or) xyz.abc.pqr.***.com
cname: *.abc.com (Or) .abc.pqr.**.com (We have to use variable of dns_name)
Playbook
- hosts: elastic-search-servers
gather_facts: true
vars:
es_admin_hostname: test.develop123.com
tasks:
- name: split string
set_fact:
cname: "{{ es_admin_hostname.split('.') }} | first | replace('*')"
- name: debug
debug:
msg: "{{ cname[1:] }} is dns name"
Required Output
*.develop123.com

just change your split by regex_replaces:
- name: split string
set_fact:
cname: "{{ es_admin_hostname| regex_replace('^[^\\.]+', '*') }}"
- name: debug
debug:
msg: "{{ cname }} is dns name"
result:
ok: [localhost] => {
"msg": "*.develop123.com is dns name"
}
'^[^\\.]+' means trap all chars from beginning of string until i meet a dot and replace them by * (the \\ is needed because . is special char)

Related

Ansible: 'regex_replace' removing every character starting with "#"

I have following registered variable with stdout and I want to remove every character starting with "#".
ok: [localhost] => {
"msg": [
"wazuh#4.3.10-4311"
]
}
Example: wazuh#4.3.10-4311 should become wazuh.
remove every character after "#"
---
- hosts: localhost
gather_facts: false
vars:
my_string: wazuh#4.3.10-4311
tasks:
- debug:
msg: "{{ my_string | regex_replace('#.*', '') }}"
Provides:
wazuh
One could also look at the use case as just interested in the left part of the #
delimitered string.
---
- hosts: localhost
gather_facts: false
vars:
my_string: wazuh#4.3.10-4311
tasks:
# For Ansible v2.9 and later
- name: Use of Python string method
debug:
msg: "{{ my_string.split('#') | first }}"
So not removing the part behind but only deliver the part before.
# For Ansible v2.10 and later
- name: Show left part with filter
debug:
msg: "{{ my_string | split('#') | first }}"
Also this solution it's working:
regex_replace('#.*$','')

List name server from resolv.conf with hostname in one line per host

I need to get the DNS server(s) from my network, I tried using:
- hosts: localhost
gather_facts: no
tasks:
- name: check resolv.conf exists
stat:
path: /etc/resolv.conf
register: resolv_conf
- name: check nameservers list in resolv.conf
debug:
msg: "{{ contents }}"
vars:
contents: "{{ lookup('file', '/etc/resolv.conf') | regex_findall('\\s*nameserver\\s*(.*)') }}"
when: resolv_conf.stat.exists == True
But this does not quite gives the result I need.
Will it be possible to write a playbook in such a way that the result looks like the below?
hostname;dns1;dns2;dnsN
The declaration below gives the list of nameservers
nameservers: "{{ lookup('file', '/etc/resolv.conf').splitlines()|
select('match', '^nameserver.*$')|
map('split', ' ')|
map('last')|list }}"
You can join the hostname and the items on the list
msg: "{{ inventory_hostname }};{{ nameservers|join(';') }}"
Notes
Example of a complete playbook for testing
- hosts: localhost
vars:
nameservers: "{{ lookup('file', '/etc/resolv.conf').splitlines()|
select('match', '^nameserver.*$')|
map('split', ' ')|
map('last')|list }}"
tasks:
- debug:
var: nameservers
- debug:
msg: |
{{ inventory_hostname }};{{ nameservers|join(';') }}
The simplified declaration below works fine if there is no nameserver.* in the comments
nameservers: "{{ lookup('file', '/etc/resolv.conf')|
regex_findall('\\s*nameserver\\s*(.*)') }}"
Unfortunately, the Linux default file /etc/resolv.conf contains the comment:
| # run "systemd-resolve --status" to see details about the actual nameservers.
This regex will match nameservers.
nameservers:
- s.
You can solve this problem by putting at least one space behind the keyword nameserver.
regex_findall('\\s*nameserver\\s+(.*)') }}"
However, this won't help if there is the keyword nameserver in the comment.
Q: "No filter named 'split'"
A: There is no filter split in Ansible less than 2.11. Use regex_replace instead
nameservers: "{{ lookup('file', '/etc/resolv.conf').splitlines()|
select('match', '^nameserver.*$')|
map('regex_replace', '^(.*) (.*)$', '\\2')|list }}"
Since your regex_findall already creates you a list with all DNS servers, you just need to add the hostname to that list and join the whole list with a semicolon.
- name: check nameservers list in resolv.conf
debug:
msg: >-
{{
(
[ ansible_hostname ] +
lookup('file', '/etc/resolv.conf', errors='ignore')
| regex_findall('\s*nameserver\s*(.*)')
) | join(';')
}}
Which will result in something like (b176263884e6 being the actual hostname of a container):
TASK [check nameservers list in resolv.conf] *****************************
ok: [localhost] =>
msg: b176263884e6;1.1.1.1;4.4.4.4;8.8.8.8
Note that you don't even need the stat task, as you can ignore errors of the lookup with errors='ignore'.
This will, then, give you only the hostname, along with a warning:
TASK [check nameservers list in resolv.conf] *****************************
[WARNING]: Unable to find '/etc/resolv.conf' in expected paths
(use -vvvvv to see paths)
ok: [localhost] =>
msg: b176263884e6

JMESPath filtering on response

I try to filter to get lldp-remote-system-name when lldp-remote-system-name contains slc1.
But I get the error:
Error in jmespath.search in json_query filter plugin:\n'in ' requires string as left operand, not NoneType
Tasks:
- name: get system information
juniper_junos_rpc:
rpc: get-lldp-neighbors-information
register: response
- name: Get remote system name
set_fact:
lldp_interface: "{{ response.parsed_output | to_json | from_json | json_query(interface) }}"
vars:
interface: '"lldp-neighbors-information"."lldp-neighbor-information"[?contains("lldp-remote-system-name","slc1")]."lldp-remote-system-name"'
- name: Print response
debug:
msg:
- "{{ lldp_interface }}"
Response
{
"lldp-neighbors-information": {
"lldp-neighbor-information": [
{
"lldp-local-parent-interface-name": "ae1",
"lldp-local-port-id": "et-0/0/50",
"lldp-remote-chassis-id": "22:22:22:22:22:22",
"lldp-remote-chassis-id-subtype": "Mac address",
"lldp-remote-port-description": "las1-router-1:et-0/0/50",
"lldp-remote-system-name": "las1-router-1"
},
{
"lldp-local-parent-interface-name": "ae0",
"lldp-local-port-id": "xe-0/0/1",
"lldp-remote-chassis-id": "11:11:11:11:11:11",
"lldp-remote-chassis-id-subtype": "Mac address",
"lldp-remote-port-description": "slc1-router-1-xe-0/0/1",
"lldp-remote-system-name": "slc1-router-1"
}
]
}
}
In JMESPath, double quotes are not string delimiters, they serve a specific purpose: they delimit an identifier that have special characters:
An identifier can also be quoted. This is necessary when an identifier has characters not specified in the unquoted-string grammar rule. In this situation, an identifier is specified with a double quote, followed by any number of unescaped-char or escaped-char characters, followed by a double quote.
Source: https://jmespath.org/specification.html#identifiers
If you want to have a raw string littoral, use back ticks instead: `.
So, your JMESPath query should be — split on multiple lines to make it more readable:
interface: >-
"lldp-neighbors-information"
."lldp-neighbor-information"[?
contains("lldp-remote-system-name",`slc1`)
]
."lldp-remote-system-name"
Given the task:
- debug:
msg: "{{ json | json_query(interface) }}"
vars:
interface: >-
"lldp-neighbors-information"
."lldp-neighbor-information"[?
contains("lldp-remote-system-name",`slc1`)
]
."lldp-remote-system-name"
json:
lldp-neighbors-information:
lldp-neighbor-information:
- lldp-local-parent-interface-name: ae1
lldp-local-port-id: et-0/0/50
lldp-remote-chassis-id: 22:22:22:22:22:22
lldp-remote-chassis-id-subtype: Mac address
lldp-remote-port-description: las1-router-1:et-0/0/50
lldp-remote-system-name: las1-router-1
- lldp-local-parent-interface-name: ae0
lldp-local-port-id: xe-0/0/1
lldp-remote-chassis-id: 11:11:11:11:11:11
lldp-remote-chassis-id-subtype: Mac address
lldp-remote-port-description: slc1-router-1-xe-0/0/1
lldp-remote-system-name: slc1-router-1
This yields:
ok: [localhost] =>
msg:
- slc1-router-1
no need to use jmepath:
- name: testplaybook jinja2
hosts: localhost
gather_facts: no
vars:
response:
lldp-neighbors-information:
lldp-neighbor-information:
- lldp-local-parent-interface-name: ae1
lldp-local-port-id: et-0/0/50
lldp-remote-chassis-id: 22:22:22:22:22:22
lldp-remote-chassis-id-subtype: Mac address
lldp-remote-port-description: las1-router-1:et-0/0/50
lldp-remote-system-name: las1-router-1
- lldp-local-parent-interface-name: ae0
lldp-local-port-id: xe-0/0/1
lldp-remote-chassis-id: 11:11:11:11:11:11
lldp-remote-chassis-id-subtype: Mac address
lldp-remote-port-description: slc1-router-1-xe-0/0/1
lldp-remote-system-name: slc1-router-1
tasks:
- name: Disp
debug:
msg: "{{ item['lldp-remote-system-name'] }}"
loop: "{{ response['lldp-neighbors-information']['lldp-neighbor-information'] }}"
loop_control:
label: "interface name: {{ item['lldp-local-parent-interface-name'] }}"
when: "'slc1' in item['lldp-remote-system-name']"
result:
skipping: [localhost] => (item=interface name: ae1)
ok: [localhost] => (item=interface name: ae0) => {
"msg": "slc1-router-1"
}

Ansible regex replace in variable to cisco interface names

I'm currently working with a script to create interface descriptions based on CDP neighbor info, but it's placing the full names e.g., GigabitEthernet1/1/1, HundredGigabitEthernet1/1/1.
My regex is weak, but I would like to do a regex replace to keep only the first 3 chars of the interface name.
I think a pattern like (dredGigatbitEthernet|abitEthernet|ntyGigabitEthernet|etc) should work, but not sure how to put that into the playbook line below to modify the port value
nxos_config:
lines:
- description {{ item.value[0].port }} ON {{ item.value[0].host }}
E.g, I am looking for GigabitEthernet1/1/1 to end up as Gig1/1/1
Here is an example of input data:
{
"FastEthernet1/1/1": [{
"host": "hostname",
"port": "Port 1"
}]
}
Final play to make it correct using ansible net neighbors as the source
Thank you - I updated my play, adjusted for ansible net neighbors
- name: Set Interface description based on CDP/LLDP discovery
hosts: all
gather_facts: yes
connection: network_cli
tasks:
- debug:
msg: "{{ ansible_facts.net_neighbors }}"
- debug:
msg: >-
description
{{
item[0].port
| regex_search('(.{3}).*([0-9]+\/[0-9]+\/[0-9]+)', '\1', '\2')
| join
}}
ON {{ item.value[0].host }}"
loop: "{{ ansible_facts.net_neighbors | dict2items }}"
loop_control:
label: "{{ item.key }}"
Thanks for the input!
Given that you want the three first characters along with the last 3 digits separated by a slash, then the regex (.{3}).*([0-9]+\/[0-9]+\/[0-9]+) should give you two capturing groups containing those two requirements.
In Ansible, you can use regex_search to extract those groups, then join them back, with the help on the join Jinja filter.
Given the playbook:
- hosts: localhost
gather_facts: no
tasks:
- debug:
msg: >-
description
{{
item.key
| regex_search('(.{3}).*([0-9]+\/[0-9]+\/[0-9]+)', '\1', '\2')
| join
}}
ON {{ item.value[0].host }}"
loop: "{{ interfaces | dict2items }}"
loop_control:
label: "{{ item.key }}"
vars:
interfaces:
GigabitEthernet1/1/1:
- port: Port 1
host: example.org
HundredGigabitEthernet1/1/1:
- port: Port 2
host: example.com
This yields:
TASK [debug] ***************************************************************
ok: [localhost] => (item=eth0) =>
msg: description Gig1/1/1 ON example.org"
ok: [localhost] => (item=eth1) =>
msg: description Hun1/1/1 ON example.com"

Ansible set_fact array and populate it from in loop

I want to create an array and insert value from the the array IP_TO_DNS to reversed IP address.
The idea is to restructure the IP address given in the argument to be matchable later in my code.
Code
- name: create array reversed
set_fact: reversed_ip=[]
- name: set convert ips from cli to matchable reversed ip
set_fact: reversed_ip='{{ item | regex_replace('^(?P<first_range>\d{1,3})\.(?P<second_range>\d{1,3})\.(?P<third_range>\d{1,3})\.', 'named.\\g<third_range>.\\g<second_range>.\\g<first_range>')}}'
with_items: '{{IP_TO_DNS}}'
- name: Match first block of results in path name
debug: var=item
with_items: '{{reversed_ip}}'
Output
TASK [dns : set convert ips from cli to matchable reversed ip] *****************
ok: [10.1.10.5] => (item=10.1.10.1)
ok: [10.1.10.5] => (item=10.1.10.2)
ok: [10.1.10.5] => (item=10.1.10.3)
TASK [dns : Match first block of results in path name] *************************
ok: [10.1.10.5] => (item=named.10.1.103) => {
"item": "named.10.1.103"
}
It look like my variable is not set as an array and only the first value is populate.
Any ideas ?
This is the one of the ways which I used
vars:
my_new_list: []
tasks:
- name: Get list of elements from list_vars
set_fact:
my_new_list: "{{ my_new_list + [item] }}"
with_items: "{{ list_vars }}"
You are setting the same fact three times and it gets overwritten.
You should register the output:
- name: set convert ips from cli to matchable reversed ip
set_fact: reversed_ip='{{ item | regex_replace('^(?P<first_range>\d{1,3})\.(?P<second_range>\d{1,3})\.(?P<third_range>\d{1,3})\.', 'named.\\g<third_range>.\\g<second_range>.\\g<first_range>')}}'
with_items: '{{IP_TO_DNS}}'
register: reversed_ip_results_list
- name: Match first block of results in path name
debug: var=item.ansible_facts.reversed_ip
with_items: '{{reversed_ip_results_list.results}}'
or if you want a list:
- debug: msg="{{ reversed_ip_results_list.results | map(attribute='ansible_facts.reversed_ip') | list }}"
You can assign the default of reversed_ip as a list and append the item to the list.
- name: set convert ips from cli to matchable reversed ip
set_fact: reversed_ip='{{ reversed_ip |default([]) + [item | regex_replace('^(?P<first_range>\d{1,3})\.(?P<second_range>\d{1,3})\.(?P<third_range>\d{1,3})\.', 'named.\\g<third_range>.\\g<second_range>.\\g<first_range>')] }}'
with_items: "{{ IP_TO_DNS }}"
- name: Match first block of results in path name
debug: var=item
with_items: '{{reversed_ip}}'

Resources