Getting regex value from ansible stdout - regex

I want to extract an boolean value from ansible command stdout:
- name: Get Status
ansible.builtin.command:
cmd: "vault status"
register: vault_status
ignore_errors: true
- ansible.builtin.set_fact:
vault_sealed: "{{ (vault_status.stdout | regex_search(regexp,'\\1')) == 'true' }} "
vars:
regexp: "Sealed\\s+(.+)\\n"
It brings that output
TASK [Get Status] **************************************************************
task path: /ansible/tasks/vault_setup.yml:124
fatal: [vault_instance]: FAILED! => {"changed": true, "cmd": ["vault", "status"], "delta": "0:00:00.071161", "end": "2022-11-11 21:32:06.405020", "msg": "non-zero return code", "rc": 2, "start": "2022-11-11 21:32:06.333859", "stderr": "", "stderr_lines": [], "stdout": "Key Value\n--- -----\nSeal Type shamir\nInitialized true\nSealed true\nTotal Shares 6\nThreshold 3\nUnseal Progress 1/3\nUnseal Nonce 2e079ec7-772f-7811-801e-c098c68c0400\nVersion 1.12.1\nBuild Date 2022-10-27T12:32:05Z\nStorage Type file\nHA Enabled false", "stdout_lines": ["Key Value", "--- -----", "Seal Type shamir", "Initialized true", "Sealed true", "Total Shares 6", "Threshold 3", "Unseal Progress 1/3", "Unseal Nonce 2e079ec7-772f-7811-801e-c098c68c0400", "Version 1.12.1", "Build Date 2022-10-27T12:32:05Z", "Storage Type file", "HA Enabled false"]}
...ignoring
TASK [ansible.builtin.set_fact] ************************************************
task path: /ansible/tasks/vault_setup.yml:130
ok: [vault_instance] => {"ansible_facts": {"vault_sealed": "False "}, "changed": false}
Can anybody tell me, how to get the vault_sealed to the right value "true" ?
Thanks guys !

In case you want to check if there is a match for a string, with the regex_search filter, you don't need any parameter other than the regex.
To determine the end of line, rather use the option multiline=True and end your regex with a $, if you want to search from the beginning of the line, start with a ^.
A successful or unsuccessful matching can be determined with is not none or is none.
In your case, to determine the successful matching of the regex, you have to use is not none.
Your task should work like this:
- ansible.builtin.set_fact:
vault_sealed: "{{ vault_status.stdout | regex_search(regexp, multiline=True) is not none }}"
vars:
regexp: "^Sealed\\s+.+$"
If you just want to detect the beginning of the line with the word Sealed followed by a space, you can also write your regex like this: "^Sealed\\s"
If you want to determine whether the word true follows Sealed, then you can choose the regex as: "^Sealed\\s+true$"
Your task like this:
- ansible.builtin.set_fact:
vault_sealed: "{{ vault_status.stdout | regex_search(regexp, multiline=True) is not none }}"
vars:
regexp: "^Sealed\\s+true$"
Note: You have to make sure that you don't have a space after your jinja expression (}} "), so it should look like this: }}". Otherwise the result will be interpreted as a string instead of a truth value.
Example Task for string starting with Sealed :
- debug:
msg: "{{ vault_status.stdout | regex_search(regexp, multiline=True) is not none }}"
vars:
regexp: "^Sealed\\s+.+$"
vault_status:
stdout: "Sealed some thing."
Result:
TASK [debug] ***************************
ok: [localhost] => {
"msg": true
}
Example Tasks for string Sealed true:
- debug:
msg: "{{ vault_status.stdout | regex_search(regexp, multiline=True) is not none }}"
vars:
regexp: "^Sealed\\s+true$"
vault_status:
stdout: "{{ item }}"
with_items:
- "Sealed true"
- "Sealed true"
- "Sealed false"
- "Some error!"
Result:
TASK [debug] ***********************************************
ok: [localhost] => (item=Sealed true) => {
"msg": true
}
ok: [localhost] => (item=Sealed true) => {
"msg": true
}
ok: [localhost] => (item=Sealed false) => {
"msg": false
}
ok: [localhost] => (item=Some error!) => {
"msg": false
}

Related

Count number of occurrences of each unique element in a list in ansible

How can I count how many times is repeated each item in a list?
ok: [] => {
"list": [
"5.8.3",
"5.8.4",
"5.8.4",
"5.9.2",
"5.9.2",
"5.9.2"
]
}
I want to print something like this:
ok: [] => {
"list_counter": [
"5.8.3": 1
"5.8.4": 2
"5.9.2": 3
]
}
I tryed something like this, but no success
- set_fact:
list_counter: '{{ item : 1 + item.get(unique_int[item],0) }}'
loop: "{{ list }}"
Use community.general.counter if you can install latest collection Community.General
list_counter: "{{ list|community.general.counter }}"
gives the expected result
list_counter:
5.8.3: 1
5.8.4: 2
5.9.2: 3
If you're not able to install the collection take the code and create a custom filter plugin
shell> cat filter_plugins/my_counter.py
# -*- coding: utf-8 -*-
# Copyright (c) 2021, Remy Keil <remy.keil#gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.errors import AnsibleFilterError
from ansible.module_utils.common._collections_compat import Sequence
from collections import Counter
def my_counter(sequence):
''' Count elements in a sequence. Returns dict with count result. '''
if not isinstance(sequence, Sequence):
raise AnsibleFilterError('Argument for community.general.counter must be a sequence (string or list). %s is %s' %
(sequence, type(sequence)))
try:
result = dict(Counter(sequence))
except TypeError as e:
raise AnsibleFilterError(
"community.general.counter needs a sequence with hashable elements (int, float or str) - %s" % (e)
)
return result
class FilterModule(object):
''' Ansible counter jinja2 filters '''
def filters(self):
filters = {
'my_counter': my_counter,
}
return filters
Use it
list_counter: "{{ list|my_counter }}"
The declaration below gives the same result if you don't want to use collections or write a custom filter
list_counter: "{{ dict(data|
unique|
zip(data|
json_query('[].{key:#, value: `1`}')|
groupby('key')|
map(attribute=1)|
map('map', attribute='value')|
map('sum')|list)) }}"
If you don't want to install a collection or write a custom filter, you can do this using the combine filter:
- hosts: localhost
gather_facts: false
vars:
data: [
"5.8.3",
"5.8.4",
"5.8.4",
"5.9.2",
"5.9.2",
"5.9.2"
]
tasks:
- set_fact:
result_list: "{{ result_list | combine({item: result_list[item]|default(0) + 1}) }}"
loop: "{{ data }}"
vars:
result_list: {}
- debug:
msg: "{{ result_list }}"
The above playbook outputs:
PLAY [localhost] ***************************************************************
TASK [set_fact] ****************************************************************
ok: [localhost] => (item=5.8.3)
ok: [localhost] => (item=5.8.4)
ok: [localhost] => (item=5.8.4)
ok: [localhost] => (item=5.9.2)
ok: [localhost] => (item=5.9.2)
ok: [localhost] => (item=5.9.2)
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": {
"5.8.3": 1,
"5.8.4": 2,
"5.9.2": 3
}
}
PLAY RECAP *********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Create list with jijna2 regex_search - NoneType problem

I'm facing 2 problems:
Problem 1.
I'm trying to filter a list with jinja2 regex_search but I alse get None matches.
Problem 2.
Each elements of the new list seems to be list of one element (sigth!!!).
My code.
- name: Regex_Search Test
hosts: localhost
vars:
my_list:
- app-be-dev01-2
- app-be-dev02-2
- app-be-dev02-3
- app-be-dev03-2
- app-foo-2
- app-be-dev04-1
- app-be-dev04-2
tasks:
- name: Varsmng
set_fact:
customer_instances: >-
{% for instance in my_list -%} {{ customer_instances | default([]) + [ instance | string | regex_search('app-be-(.*)-([0-9]*)', '\1' ) ] }}
{%- endfor %}
- name: Debug
debug:
msg:
- "customer_instances: {{ customer_instances }}"
My output.
TASK [Varsmng] ****************************************************************************************************************************************
task path: /home/cin0633a/progetti/ansible/testenv/test.yml:19
ok: [localhost] => {
"ansible_facts": {
"customer_instances": "[[u'dev01']][[u'dev02']][[u'dev02']][[u'dev03']][None][[u'dev04']][[u'dev04']] "
},
"changed": false
As you can see, each element has a double square brackets. And can I avoid None values?
You get a list for each element because regex_search returns a list when you use the replace feature with capture groups in the expression.
$ ansible localhost -m debug -e toto=bla-bli-blo -a "msg={{ toto | regex_search('(bla).*') }}"
localhost | SUCCESS => {
"msg": "bla-bli-blo"
}
$ ansible localhost -m debug -e toto=bla-bli-blo -a "msg={{ toto | regex_search('(bla).*', '\\1') }}"
localhost | SUCCESS => {
"msg": [
"bla"
]
}
And you get None values because some items do not match your regex.
You can get your result with a better approach IMO using specific filters rather than a complex jinja2 template. The following playbook:
- name: Regex_Search Test
hosts: localhost
gather_facts: false
vars:
my_list:
- app-be-dev01-2
- app-be-dev02-2
- app-be-dev02-3
- app-be-dev03-2
- app-foo-2
- app-be-dev04-1
- app-be-dev04-2
searchreg: >-
app-be-(.*)-([0-9]*)
my_filtered_list: >-
{{
my_list |
select('regex', searchreg) |
map('regex_replace', searchreg, '\1')
}}
tasks:
- debug:
var: my_filtered_list
Gives:
PLAY [Regex_Search Test] *****************************************************************************
TASK [debug] *****************************************************************************
ok: [localhost] => {
"my_filtered_list": [
"dev01",
"dev02",
"dev02",
"dev03",
"dev04",
"dev04"
]
}
PLAY RECAP *****************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Note1: you can pipe the unique filter if you want unique environment results
Note2: if you are using ansible < 2.10, you will have to add the list filter at the end of all others to get the actual result.

Ansible: Unique Shortest Paths

Context
I have a list of files and directories to be deleted.
This is obtained from lines starting with the word "deleting" from an rsync stdout.
rsync stdout_lines: [
"building file list ... done",
"*deleting Lab02/Ex2/Doc1.txt",
"*deleting Lab02/Ex2/",
"*deleting Lab02/Ex1/Doc1.txt",
"*deleting Lab02/Ex1/",
"*deleting Lab02/",
".d..t...... ./",
"*deleting Lab01/Ex2/Doc1.txt",
"*deleting Lab01/Ex2/",
"*deleting Lab01/Ex1/Doc2.txt",
"*deleting Lab01/Ex1/Doc1.txt",
".d..t...... Lab01/",
".d..t...... Lab01/Ex1/",
"sent 350 bytes received 191 bytes 360.67 bytes/sec",
"total size is 614 speedup is 1.13 (DRY RUN)"
]
Formatted using:
'{{ sync_return.stdout_lines | select("regex", "^[*]deleting") | map("regex_replace", "^[*]deleting", "") | map("regex_replace", " ", "") | list }}'
A primitive example of the format of this list is as follows:
formatted list: [
"Lab02/Ex2/Doc1.txt",
"Lab02/Ex2/",
"Lab02/Ex1/Doc1.txt",
"Lab02/Ex1/",
"Lab02/",
"Lab01/Ex2/Doc1.txt",
"Lab01/Ex2/",
"Lab01/Ex1/Doc2.txt",
"Lab01/Ex1/Doc1.txt"
]
In an attempt to hasten the process of deleting (by reducing the number of elements to iterate over) - I separated the list into 2 sub-lists:
A list of directories. (elements of the main list that end in '/')
'{{ items_to_delete | select("regex", "/$") | list }}'
A list of file paths. (elements who's containing directory does not get deleted)
'{{ items_to_delete | reject("match", item) | list }}'
The sub-lists for the example above would be...
directories to delete: [
"Lab02/Ex2/",
"Lab02/Ex1/",
"Lab02/",
"Lab01/Ex2/"
]
files to delete: [
"Lab01/Ex1/Doc2.txt",
"Lab01/Ex1/Doc1.txt"
]
The Problem
Whilst the current solution works, it's not the best it could be. The dream is to have a solution where the "directories to delete" list only contains the highest level directories possible. i.e. Since we know the directory "Lab02/" is being deleted, "directories to delete" will NOT contain "Lab02/Ex2/" or "Lab02/Ex1/".
I believe my goal is somewhat similar to the os.path.commonprefix python function, however must be done for a variety of file paths within the list.
I'm relatively new to Ansible, so any guidance/help with this matter would be greatly appreciated.
I won't ask why you want to implement that, and I'll take it as an exercise.
Idea is, you can sort directories alphabetically, then while looping the paths, you strip any one that starts with the previous line.
You can write your filter like this (put in filter_plugins directory):
def common_paths(paths=[]):
sorted_paths = sorted(paths)
pfx = sorted_paths[0]
for path in sorted_paths[1:]:
if re.compile("^%s.*" % pfx).match(path):
sorted_paths.remove(path)
else:
pfx = path
return sorted_paths
class FilterModule(object):
def filters(self):
return { 'common_paths': common_paths }
then:
- name: Filter
set_fact:
bar: "{{ foo | common_paths }}"
Test locally with:
---
- hosts: localhost
tasks:
- name: Test data
set_fact:
foo:
- 'Lab01/'
- 'Lab01/Ex5/'
- 'Ex2/foo3/'
- 'Ex2/foo2/'
- 'Ex2/'
- 'Lab03/Ex5/e/'
- 'Lab02/y/z/Lab01/1/'
- 'Lab02/y/z/Lab01/3/'
- 'Lab01/Ex5/Lab02/'
- 'Lab03/Ex5/d/1'
- name: Filter
set_fact:
bar: "{{ foo | common_paths }}"
Output:
$ ansible-playbook common_paths.yml -vvv
ansible-playbook 2.10.4
PLAYBOOK: common_paths.yml *********************************************************************************
1 plays in common_paths.yml
PLAY [localhost] *********************************************************************************
TASK [Gathering Facts] *********************************************************************************
ok: [localhost]
TASK [Test data] ********************************************************************************* task path: /home/guido/Development/git/ansible-local/common_paths.yml:5
ok: [localhost] => {
"ansible_facts": {
"foo": [
"Lab01/",
"Lab01/Ex5/",
"Ex2/foo3/",
"Ex2/foo2/",
"Ex2/",
"Lab03/Ex5/e/",
"Lab02/y/z/Lab01/1/",
"Lab02/y/z/Lab01/3/",
"Lab01/Ex5/Lab02/",
"Lab03/Ex5/d/1/"
]
},
"changed": false
}
TASK [Filter] *********************************************************************************
task path: /home/guido/Development/git/ansible-local/common_paths.yml:19
ok: [localhost] => {
"ansible_facts": {
"bar": [
"Ex2/",
"Lab01/",
"Lab02/y/z/Lab01/1/",
"Lab02/y/z/Lab01/3/",
"Lab03/Ex5/d/1/",
"Lab03/Ex5/e/"
]
},
"changed": false
}
PLAY RECAP *********************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Combine two lists in Ansible when one list could be empty

actually I've a problem by compining two lists in Ansible to one new in case of one list could be empty or not.
EDIT:
One list is defined in defaults
standardvolumes: '/usr/share/zoneinfo:/usr/share/zoneinfo:ro'
and the other list could be defined in group_vars.yml for the hostgroup.
volumes:
- '/usr/data:/data'
- '/usr/data2:/data2'
- '/usr/data3:/data3'
At the task I comine the lists:
- name: Combine volume lists
set_fact:
volumestostart: '{{ standardvolumes|default([]) + volumes|default([]) }}'
In play I get this error:
TASK [do-docker-deployment : Combine volume lists] *****************************
fatal: [shs_de_postd_server1]: FAILED! => {"msg": "Unexpected templating type error occurred on ({{ standardvolumes|default([]) + volumes|default([]) }}): coercing to Unicode: need string or buffer, list found"}
So I've tried to redefine my defaults list:
standardvolumes:
- '/usr/share/zoneinfo:/usr/share/zoneinfo:ro'
But then I get a docker format error:
failed: [shs_de_postd_server1] (item={u'key': u'qit', u'value': {u'cgrouptype': u'blech', u'nexususer': u'cbs-qit-user', u'notstartable': u'no', u'nexuspassword': u'48vhw63u', u'nexusport': u'8191', u'nexuspath': u'ftg/postd-server', u'graylogip': u'tcp://10.20.30.40:12201'}}) => {"ansible_loop_var": "item", "changed": false, "item": {"key": "qit", "value": {"cgrouptype": "blech", "graylogip": "tcp://10.20.30.40:12201", "nexuspassword": "48vhw63u", "nexuspath": "ftg/postd-server", "nexusport": "8191", "nexususer": "cbs-qit-user", "notstartable": "no"}}, "msg": "Found invalid volumes mode: ro']"}
The lists are docker volumes for a container which I will start with my role.
Anybody good ideas?
Thanks and regards,
David
Try this
volumes: "{{ volumes|default([]) + standardvolumes|default([]) }}"
For example
- set_fact:
list_A: "{{ list_A|default([]) + list_B|default([]) }}"
- debug:
var: list_A
give
list_A: []

Ansible Regex Help needed: Unable to grab output and assign to a set_fact variable

I am trying to grab the "CRC Input Error" count and assign it to a variable "crc_count" however, the value of this variable is showing as an empty string instead ("crc_count": ""). What am I missing here?
Code:
- name: Checking CRC Errors on the Interfaces
nxos_command:
commands:
- show interface ethernet 1/1 counters detailed all
register: crc_output
- debug: var=crc_output.stdout_lines[0][50]
- set_fact: crc_count= "{{ crc_output.stdout_lines[0][50] | regex_search('(\d{0,20}$)') }}"
- debug:
msg: "{{ crc_count }}"
String output that I want to run regex on:
"crc_output.stdout_lines[0][50]": " 10. Input CRC Errors: = 4272"
Output I want to grab:
crc_count = "4272"
Playbook verbose output:
TASK [debug] **************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************
task path: /ansible/saurasar/nxos.crc.check.yml:19
ok: [ord12-pob-c1u1-dci-1.uspp1.oraclecloud.com] => {
"crc_output.stdout_lines[0][50]": " 10. Input CRC Errors: = 4272"
}
TASK [set_fact] ***********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************
task path: /ansible/xxxx/nxos.crc.check.yml:20
ok: [cisco.nexus.switch] => {"ansible_facts": {"_raw_params": "\"4272\"", "crc_count": ""}, "changed": false}
TASK [debug] **************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************
task path: /ansible/xxxxx/nxos.crc.check.yml:22
ok: [cisco.nexus.switch] => {
"msg": ""
}
META: ran handlers
META: ran handlers
PLAY RECAP ****************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************
<cisco.nexus.switch> : ok=5 changed=0 unreachable=0 failed=0
Try as below
- set_fact:
crc_count: "{{ result.stdout | regex_replace('^.*Input CRC Errors: = (.*)$', '\\1') }}"
I tried with your sample and got as below
ok: [localhost] => {
"ansible_facts": {
"crc_count": "4272"
},
"changed": false
}