Ansible how to extract parts of a string? - ansible

I am working on a project where I have to perform multiple queries to the system and the steps that follow it depend on the results of the query (there are 2 to 3 different ways each query can branch out).
The results from the queries are stored in a string variable using the register module. For instance, for one of the queries I have to check if there are multiple standby servers configured and the expected output can look like this:
database role = PRIMARY
host name = random_name
service name = service1
target list = server1:10201|server4:40704|server8:52125
timeout value = 120
Where the only needed part is the target list and I need to extract each server (they are separated by the "|" and I wont know how many servers will be on the target list). So essentially I need a way to scan the string until it finds the word "target list" and then extract whatever comes after the "=" and before the new-line, each one of those values could be stored in an array I guess.
I don't even know where to start, anyone know any modules or how I can go about extracting the needed parts of the strings?

Here is the sample playbook, only the last line with regex_search is relevant here. Rest is for illustration purpose. regex_search can be used for extracting the desired part from the text:
regex_search('target list\\s+=\\s+(.*)','\\1')
above regex would capture everything after = on the line containing target list and back-referenced using \1.
- name: Sample playbook
connection: local
# gather_facts: false
hosts: localhost
vars:
data: "{{ lookup('env', 'x') }}"
tasks:
- debug:
msg: "{{ data }}"
- debug:
msg: "{{ data |regex_search('target list\\s+=\\s+(.*)','\\1')}}"
Output of the above playbook:
PLAY [Sample playbook] **********************************************************************************************************************************************
TASK [Gathering Facts] **********************************************************************************************************************************************
ok: [localhost]
TASK [debug] ********************************************************************************************************************************************************
ok: [localhost] => {}
MSG:
database role = PRIMARY
host name = random_name
service name = service1
target list = server1:10201|server4:40704|server8:52125
timeout value = 120
TASK [debug] ********************************************************************************************************************************************************
ok: [localhost] => {}
MSG:
['server1:10201|server4:40704|server8:52125']
PLAY RECAP **********************************************************************************************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0

Given the data registered in the variable results
result:
stdout_lines:
- database role = PRIMARY
- host name = random_name
- service name = service1
- target list = server1:10201|server4:40704|server8:52125
- timeout value = 120
use the attribute stdout_lines, e.g.
- set_fact:
servers: "{{ _val.split('|') }}"
vars:
_line: "{{ result.stdout_lines|select( 'match', '^target list.*$') }}"
_arr: "{{ _line.0.split('=')|map('trim') }}"
_val: "{{ _arr.1 }}"
gives
servers:
- server1:10201
- server4:40704
- server8:52125
Use cli_parse if you have to repeatedly parse the same structures.

Related

How to print a list of items in a multiline string output in ansible

I have a list in ansible and want to print the contents in a block, if I do loop like below:
- test_list
- one
- two
- three
- debug:
msg: "{{ item }}"
loop: "{{ test_list }}"
it will produce output something like:
{ msg: "one" }
{ msg: "two" }
{ msg: "three" }
here I have multiple msg values, I want output like:
msg:"one
two
three"
where list items are broken won into multiple lines. Can anyone direct me to the correct path or provide a hint.
Any help would be appreciated.
You can achieve the desired result by the following steps :
Set callback plugin to debug using the following command:
export ANSIBLE_STDOUT_CALLBACK=debug
Replace loop with for loop on jinja2:
Example:
The desired output could be obtained using the following playbook:
---
- name: Sample playbook
connection: local
gather_facts: no
hosts: localhost
vars:
test_list:
- one
- two
- three
tasks:
- debug:
msg: "{% for item in test_list %}{{ item + '\n'}}{% endfor %}"
The above playbook would result in:
PLAY [Sample playbook] ***************************************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************
ok: [localhost]
TASK [debug] *************************************************************************************************************************
ok: [localhost] => {}
MSG:
one
two
three
PLAY RECAP ***************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0
if you need starting/ending double quotes:
msg: "\"{% for item in test_list %}{{ item + '\n'}}{% endfor %}\""

Use variable for default value if imported role failed

I have an ansible role that reads data from a DB. That data might not exist in which case it fails. I cannot change this role. Currently I'm using this role like:
- name: Import role and read values
import_role:
name: shared_role
register: value
This works well when the data in the DB exists and the role doesn't fail. However when that data is missing it's causing more problems. So in case of an error I want to ignore that error and use a default value.
- import_role:
name: shared_role
register: value
ignore_errors: true
- set_fact:
value: "{{ value | default({{ default_var }}) }}"
where default_var is defined in group_vars. Now this doesn't work obviously and I'm kind of stuck. How could I use a variable as a default value foor another variable registered in a role that might have failed... if that makes sense.
default will by default (sorry for this redundancy but I don't know how to write it better) replace values for undefined variables only. A variable defined as None is defined.
Fortunately, there is a second parameter that can be used to replace also "empty" vars (i.e. None, or empty string). See Documentation for this
Moreover, you cannot use jinja2 expansion while inside a jinja2 expansion already. Check the jinja2 documentation to learn more.
So you should achieve your requirement with the following expression (if all you described about your data is correct):
- set_fact:
value: "{{ value | default(default_var, true) }}"
You should not have problems with variable precedence here: set_fact and register are on the same level so the latest assignment should win. But for clarity and security, I would rename that var so you are sure you never get into trouble:
- import_role:
name: shared_role
register: role_value
ignore_errors: true
- set_fact:
value: "{{ role_value | default(default_var, true) }}"
Here is my group_vars/all.yml file. I am using food that is defined here to set in my play. you can see the value 6 in output.
logs: /var/log/messages
foo: 6
and here is my play-book
---
- name: assign a variable value in default state
hosts: localhost
tasks:
- name: set fact and then print
set_fact:
new_var: "{{ doodle | default( foo ) }}"
- name: debug
debug: msg="{{ new_var }}"
and here is the output
PLAY [assign a variable value in default state] ********************************
TASK [Gathering Facts] *********************************************************
ok: [localhost]
TASK [set fact and then print] *************************************************
ok: [localhost]
TASK [debug] *******************************************************************
ok: [localhost] => {
> "msg": "6"
}
PLAY RECAP *********************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
This is a simple syntax error. You can not use the {{}} in a filter.
The correct syntax is
- set_fact:
value: "{{ value | default(default_var) }}"
EDIT:
If your value is "None" and you want to replace that, you can use something like this:
- set_fact:
value: "{%- if not value is defined or value == 'None' -%}{{ default_var }}{%- else -%}{{ value }}{%- endif -%}"
Take note of the use of {{}} here.

Avoid unused and undefined variable in playbook

I have following data in a variable file
data: [
{
service: "ServiceName",
var2: "file/path/value",
var3: "{{ check_var }}",
}, {
service: "ServiceName",
var2: "file/path/value",
var3: "{{ check_var }}",
}
]
I have two playbooks which require the same data. However one playbook does not require var3.
- debug: msg="{{ item['service'] }} uses - {{ item['var2'] }}"
with_items: "{{ data }}"
This gives error - "'check_var' is undefined".
TRIED:
I don't want to populate the playbook with bad standards and use
when: check_var is undefined
Or use false dummy data in playbook's vars atrribute. Is there any way around this while maintaining standards. Also the actual data is quite huge so I don't want to repeat it twice for each playbook.
In Ansible data has to be assigned to hosts not to playbooks.
You have to create two host groups. Those hosts, who needs just two variables go into the first group. And those hosts which need 3 variables go into both groups. You can include the hosts of the first group in the second group.
Then you create two group var files. In the first you put 2 variables and in the second the 3rd variable.
By this each host gets the right amount of information. Playbook 1 uses 3 variables and playbook 2 uses just 2 variables.
Update: Minimal example
$ diff -N -r none .
diff -N -r none/check_var.yaml ./check_var.yaml
0a1,4
> ---
> - hosts: localhost
> tasks:
> - debug: var=check_var
diff -N -r none/group_vars/myhosts.yaml ./group_vars/myhosts.yaml
0a1
> check_var: "Hello World!"
diff -N -r none/inventory ./inventory
0a1,2
> [myhosts]
> localhost
$ ansible-playbook -i inventory check_var.yaml
PLAY [localhost] ***************************************************************************
TASK [Gathering Facts] *********************************************************************
ok: [localhost]
TASK [debug] *******************************************************************************
ok: [localhost] => {
"check_var": "Hello World!"
}
PLAY RECAP *********************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0

How can I build a string from a value and a list of values?

[First time questioner. I think that I've targeted this as an Ansible question. If not, gentle redirection is welcome!]
Given:
gid: 80
ports: [80, 443]
where the number of ports may vary from 0 to many
I'd like to produce a string like this:
"gid:80:tcp:80,gid:80:tcp:443"
(which happens to be a FreeBSD mac_portacl rule string)
The furthest I've gotten is:
portacl_rules: "{{ ports | zip_longest([], fillvalue='80') | list }}"
Which gives me somethign like this:
"msg": [
[
80,
"80"
],
[
443,
"80"
]
]
but:
the gid is hardcoded, I can't figure out how to interpolate the variable value; and
I can't translate the list of into the final string.
I can create the gid string, gid:80 by defining a temporary variable:
gid: 80
_tmp_gid: "gid:{{ gid }}"
but since I can't interpolate a string into the fillvalue, I'm stuck.
I monkeyed around the format filter, but it appears to take the output string as its input and the values as its arguments, which is the inverse of my situation.
Any suggestions?
If you don't mind pair of set_fact tasks, you can do it like this:
- set_fact:
rules_list: "{{ rules_list|default([]) + ['gid:{}:tcp:{}'.format(gid, item)] }}"
loop: "{{ ports }}"
- set_fact:
rules_str_1: "{{ ','.join(rules_list) }}"
- debug:
var: rules_str_1
The first task creates a list of the form:
[
"gid:80:tcp:80",
"gid:80:tcp:443"
]
The second task joins those items using ,.
You can complete that in a single operation using a slightly hairier expression involving the regex_replace filter:
- set_fact:
rules_str_2: '{{ ",".join(ports|map("regex_replace", "^(.*)$", "gid:{}:tcp:\1".format(gid))) }}'
- debug:
var: rules_str_2
For that set_fact task to work as written, you must use single quotes on the outside (this inhibits the use of \ as an escape character). You could swap the quotes, but then you would need to write \\ instead of \. Recall that (...) in the match expression creates a capture group, and \1 in the replacement string expands to the value of the first capture group.
Putting it all together in a playbook:
---
- hosts: localhost
gather_facts: false
vars:
gid: 80
ports: [80, 443]
tasks:
- set_fact:
rules_list: "{{ rules_list|default([]) + ['gid:{}:tcp:{}'.format(gid, item)] }}"
loop: "{{ ports }}"
- set_fact:
rules_str_1: "{{ ','.join(rules_list) }}"
- debug:
var: rules_str_1
- set_fact:
rules_str_2: '{{ ",".join(ports|map("regex_replace", "(.*)", "gid:{}:tcp:\1".format(gid))) }}'
- debug:
var: rules_str_2
Which will yield the following output:
PLAY [localhost] ******************************************************************************************************************************************************************************
TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost] => (item=80)
ok: [localhost] => (item=443)
TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"rules_str_1": "gid:80:tcp:80,gid:80:tcp:443"
}
TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"rules_str_2": "gid:80:tcp:80,gid:80:tcp:443"
}
PLAY RECAP ************************************************************************************************************************************************************************************
localhost : ok=5 changed=0 unreachable=0 failed=0

Create VLANs only if they don't exist on a Nexus switch

I'm trying to create an Ansible playbook which should create VLANs defined in file vlans.dat on a Cisco Nexus switch only when they don't exist on device.
File vlans.dat contains:
---
vlans:
- { vlan_id: 2, name: TEST }
And Ansible file:
---
- name: Verify and create VLANs
hosts: switches_group
gather_facts: no
vars_files:
- vlans.dat
tasks:
- name: Get Nexus facts
nxos_facts:
register: data
- name: Create new VLANs only
nxos_vlan:
vlan_id: "{{ item.vlan_id }}"
name: "{{ item.name }}"
state: "{{item.state | default('present') }}"
with_items: "{{ vlans }}"
when: item.vlan_id not in data.ansible_facts.vlan_list
In the when statement I'm trying to limit execution only to the case when vlan_id (defined in the file) doesn't exist in the vlan_list gathered by nxos_facts module. Unfortunately it gets executed even when the vlan_id already exists in the vlan_list and I don't know why?
PLAY [Verify and create VLANs]
TASK [Get Nexus facts]
ok: [10.1.1.1]
TASK [Create new VLANs only]
ok: [10.1.1.1] => (item={u'name': u'TEST', u'vlan_id': 2})
TASK [debug]
skipping: [10.1.1.1]
PLAY RECAP
10.1.1.1 : ok=2 changed=0 unreachable=0 failed=0
Can you help me with that or provide some solution what I'm doing wrong here?
It appears you have stumbled upon a side-effect of YAML having actual types. Because in {vlan_id: 2} the 2 is an int but the list is strings. As you might imagine {{ 1 in ["1"] }} is False.
There are two ways out of that situation: make the vlan_id a string via - { vlan_id: "2" } or coerce the vlan_id to a string just for testing the list membership:
when: (item.vlan_id|string) not in data.ansible_facts.vlan_list

Resources