I have a version variable, which I need to split and insert a . in between
I tried this
ansible localhost -e version=300 -m debug -a "msg={{ version | regex_replace('\d{1,2}', '.\g<0>' ) }}"
But the o/p is
TASK [debug] ********************************************************************************************************************************************************************************************************************************
ok: [localhost] =>
msg: .30.0
There is a . getting .30.0 added in the first place. I can use regex_repace to remove the first. after that.
But is there any other better way? Why is pattern putting the decimal point in the first place?
Q: "Why is pattern putting the decimal point in the first place?"
A: Regex \d{1,2} matches one or two digits. Given the string 300 this regex matches the first two digits30. It will be replaced with .\g<0> which gives
.30
Next match is 0 because only one digit has left. The replacement gives
.0
Put together the result is
.30.0
Q: "Is there any way I can directly insert "." (dot) after the second place? ie 30.0?"
A: For example the play
- hosts: localhost
vars:
my_string: '300'
tasks:
- debug:
msg: "{{ my_string[0:2] ~ '.' ~ my_string[2:] }}"
- debug:
msg: "{{ my_string[:-1] ~ '.' ~ my_string[-1] }}"
gives
"msg": "30.0"
"msg": "30.0"
Related
I have a list like the following
fruit: [ 'apple','orange2','grape3apple1']
I want an output like the following and want this to be stored in another var for further use
fruit_out: [ 'apple','orange','grape3apple' ]
If the last character is a number, I want the number stripped, if not, I need the string returned as is. I have looked around a bit without much luck getting any usable solution.
Reiterating my comment for context:
Given a string variable somevariable, somevariable[-1] is the last character, and somevariable[:-1] is the string not including the last character. –
I am looking for a validation to check if the last character is indeed a number. If there is a number at in the middle it should not count for the check
Ansible uses Python, so you can use any Python string method in your Ansible playbooks. For example, to have a task execute when a value ends with a number, you could write:
- debug:
msg: Your variable ends with a number!
when: somevariable[-1].isdigit()
You could perform the same check using a regular expression filter:
- debug:
msg: Your variable ends with a number!
when: somevariable is search('[0-9]$')
One possibility would be to use a regex and the filter regex_search.
So, given the task:
- debug:
msg: >-
{{
fruits
| map('regex_search', '^(.*?)[0-9]?$', '\1')
| map('first')
}}
vars:
fruits:
- apple
- orange2
- grape3apple1
This yields the expected:
ok: [localhost] =>
msg:
- apple
- orange
- grape3apple
The only tricky part in that regex is that you have to make your capturing group lazy, with the help of the lazy quantifier *?, otherwise your matched group will spawn over the unwanted number.
Use the filter regex_replace. For example, put the declarations below as appropriate
fruit: [apple, orange2, grape3apple1]
fruit_regex: '^(.+?)\d*$'
fruit_replace: '\1'
fruit_out: "{{ fruit|map('regex_replace', fruit_regex, fruit_replace)|list }}"
gives
fruit_out:
- apple
- orange
- grape3apple
Explanation of the regex
^ ....... matches the beginning of a string
(.+?) ... creates a capture group
. ... matches any character
+? ... 1 or more (append ? for non-greedy)
\d* ... digit, 0 or more
$ ....... matches the end of a string
Example of a complete playbook
- hosts: localhost
vars:
fruit: [apple, orange2, grape3apple1]
fruit_regex: '^(.+?)\d*$'
fruit_replace: '\1'
fruit_out: "{{ fruit|map('regex_replace', fruit_regex, fruit_replace)|list }}"
tasks:
- debug:
var: fruit_out
Pulling my hair out. I know I've done this eons ago with much older versions of ansible. I haven't touched ansible much so don't recall how I did this. This seems so ridiculously easy at first glance so obviously something stupid i'm overlooking.
Given sample inventory, nothing fancy:
Note: removing FQDN's for brevity.
Note: this automation is working with a large number of hosts and many many more variables than I'm representing here. This is a majorly dumbed down example.
[nodes]
foo1
foo2
foo3
[all:vars]
minio_proto=http
disks_per_node=64
Ultimately I need to build a string that wraps text around the hostnames. This is for minio if you are familiar with it.
You start a server by speficying all the nodes and the disk paths:
minio server http://foo1/data/disk{0...79} http://foo2/data/disk{0...63} http://foo3/data/disk{0...63}
So inside my play I need to construct this string that eventually gets passed to a shell command to start the server on each node.
So I have a playbook (eventually this will be a role) that will be run once, not for every host, just to construct this server parameter.
I've tried so many permutations of stuff I can't possibly list them all but here's just one of my latest stabs:
---
- hosts: all
gather_facts: false
become: true
ignore_errors: false
vars:
minio_startup1: []
minio_startup2: []
tasks:
- name: Verifying hosts are up and we can become root
become: true
ping:
# THIS WORKS FINE, APPENDING A SIMPLE VARIABLE WORKS
- name: Building minio startup parameter
set_fact:
minio_startup1: "{{ minio_startup1 + [item] }}"
with_items:
- "{{ ansible_play_hosts }}"
- debug: var=minio_startup1
# DOING STUFF LIKE THIS DOES NOT WORK
- name: Building minio startup parameter
set_fact:
minio_startup2: "{{ minio_startup2 }} + [{{ minio_proto }}//{{ item }}/data{0...{{ disks_per_node|int - 1 }}}] }}"
with_items:
- "{{ ansible_play_hosts }}"
- debug: var=minio_startup2
Basically at this point in the playbook I want a list that looks like this:
[ 'http://foo1/data{0...63}', 'http://foo2/data{0...63}', 'http://foo2/data{0...63}']
Then later in the playbook, I can concatenate this into a single string I can feed my minio container thru a shell command:
{{ minio_startup|join(' ') }}
I know I did this years ago but after 3 hrs of hair pulling it eludes me.
* UPDATE *
Well I figured out a way, not sure if this is the best. If interested, this is what I did. It looks like you can use '~' to concatenate items inside the []'s. I still had to do the math of determinig the ending disk index number in a separate variable. (disk_ending_index)
minio_startup2: "{{ minio_startup2 + [ minio_proto ~ '//' ~ item ~ '/data{0...' ~ disk_ending_index ~ '}' ] }}"
Use Single-Quoted Style. The playbook below
- hosts: foo1:foo2:foo3
gather_facts: false
vars:
minio_proto: http
disks_per_node: 64
minio_startup: []
tasks:
- set_fact:
minio_startup: '{{ minio_startup +
[minio_proto ~ "://" ~ item ~
"/data{0.." ~ disks_per_node ~ "}"] }}'
loop: "{{ ansible_play_hosts }}"
run_once: true
- debug:
var: minio_startup
run_once: true
- debug:
msg: "{{ minio_startup|join(' ') }}"
run_once: true
gives (abridged)
ok: [foo1] => {
"minio_startup": [
"http://foo1/data{0..63}",
"http://foo2/data{0..63}",
"http://foo3/data{0..63}"
]
}
ok: [foo1] => {
"msg": "http://foo1/data{0..63} http://foo2/data{0..63} http://foo3/data{0..63}"
}
Quoting from Single-Quoted Style:
... the “\” and “"” characters may be freely used. This restricts single-quoted scalars to printable characters ...
The special variable ansible_role_names contains a list with roles names in my play. For example
"ansible_role_names": [
"geerlingguy.nfs",
"ANXS.openssh",
"robertdebock.bootstrap",
"robertdebock.squid"
]
However what I want to access is this list with everything before the dot removed.
"roles": [
"nfs",
"openssh",
"bootstrap",
"squid" ]
Q: "How to manipulate each item in an array when using Ansible?"
A: It's possible to map the regex_replace filter. For example the play
- set_fact:
my_list: "{{ ansible_role_names|
map('regex_replace', regex, replace)|
list }}"
vars:
regex: '^(.*)\.(.*)$'
replace: '\2'
- debug:
var: my_list
gives
"my_list": [
"nfs",
"openssh",
"bootstrap",
"squid"
]
regex: '^(.*)\.(.*)$'
^ matches the beginning of the string
(.*) matches any character in capture group No.1
\. matches .
(.*) matches any character in capture group No.2
$ matches the end of the string
replace: '\2'
\2 matches previously defined capture group No.2
Building-up on #Arbab Nazar answer: splitting the strings on dots and using index 1 on the resulting list might break on some occasions.
If your role name does not contain a dot (pretty common for local roles), you will get an error with an undefined variable => list object has no element 1
In the (unlikely...) case your role has several dots in its name, you will always get the second element in the string, not the last
Using -1 as the index (e.g. first element starting from the end) will fix both of those potential problems like in the following example:
- debug:
msg: "{{ item.split('.')[-1] }}"
loop:
- simplerole
- classic.galaxyrole
- non.standard.customrole
Which gives the following result:
TASK [debug] ************************************************************************************************************************************************************************************************************************************************************
ok: [localhost] => (item=simplerole) => {
"msg": "simplerole"
}
ok: [localhost] => (item=classic.galaxyrole) => {
"msg": "galaxyrole"
}
ok: [localhost] => (item=non.standard.customrole) => {
"msg": "customrole"
}
You can get it by using the split filter:
- debug:
msg: "{{ item.split('.')[-1] }}"
loop: "{{ ansible_role_names }}"
I need to replace all the / by \ in a string stored in a variable.
I'm just trying to do it a simple as possible to test it with a debug, but no matter how I try it I dont get the expected result of just replacing character to character. I think it's probably just a single/double quote problem or maybe the \ needs to be escaped in a certain way I don't know.
vars:
- SecGroup: '/stuff/foo/thing'
tasks:
- name: Display modified var
debug:
msg: "{{ SecGroup | replace('/','\') }}"
Expected output : \stuff\foo\thing
Output with differents tries :
- name: Display modified var
debug:
msg: "{{ SecGroup | replace('/','\') }}"
TASK [Display modified var]
ok: [localhost] => {
"msg": "stufffoothing"
}
- name: Display modified var
debug:
msg: "{{ SecGroup | replace('/','\\') }}"
TASK [Display modified var]
fatal: [localhost]: FAILED! => {"msg": "Unexpected failure during module execution."}
- name: Display modified var
debug:
msg: "{{ SecGroup | replace('/','\\\') }}"
TASK [Display modified var]
fatal: [localhost]: FAILED! => {"msg": "Unexpected failure during module execution."}
- name: Display modified var
debug:
msg: "{{ SecGroup | replace('/','\\\\') }}"
TASK [Display modified var]
ok: [localhost] => {
"msg": "\\\\stuff\\\\foo\\\\thing"
}
I also tried to revert the quotes :
- name: Display modified var
debug:
msg: '{{ SecGroup | replace("/","\") }}'
TASK [Display modified var]
fatal: [localhost]: FAILED! => {"msg": "Unexpected failure during module execution."}
I can't explain the output of this one
- name: Display modified var
debug:
msg: '{{ SecGroup | replace("/","\\") }}'
TASK [Display modified var]
ok: [localhost] => {
"msg": "\\\\stuff\\\\foo\\\\thing"
}
I think you've stumbled upon an edge case that involves the interaction between YAML escaping and Python escaping. The only way I was able to get it to work was introducing a guard character -- something to ensure that the \ isn't the last character in the expression, which we then remove with a subsequent replace() filter. Here I'm using a semicolon (;), but you could use anything that you're certain won't be in your SecGroup string. Note that your choice of quotes is significant; quoting the entire string with single quotes inhibits YAML escaping:
- name: With guard character
debug:
msg: '{{ SecGroup | replace("/","\;") | replace(";", "") }}'
Outputs:
TASK [With guard character] *******************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "\\stuff\\foo\\thing"
}
Which is exactly what you want (remembering that a single \ is encoded as \\ in JSON output).
Regarding this:
- name: Display modified var
debug:
msg: '{{ SecGroup | replace("/","\\") }}'
TASK [Display modified var]
ok: [localhost] => {
"msg": "\\\\stuff\\\\foo\\\\thing"
}
You are successfully replacing / with two backslashes, \\. Since a backslash must be encoded as \\ in JSON output, a double backslash will end up represented as \\\\, so this:
"msg": "\\\\stuff\\\\foo\\\\thing"
Means you actually have the string:
\\stuff\\foo\\thing
I wanted to add an alternative solution:
If you're familiar with Python, you can just write a custom filter module and avoid multiple layers of escaping. E.g., if you were to create filter_plugins/reslash.py with the following content:
#!/usr/bin/python
def filter_reslash(val):
return val.replace('/', '\\')
class FilterModule(object):
filter_map = {
'reslash': filter_reslash
}
def filters(self):
return self.filter_map
You could then write your playbook like this:
---
- hosts: localhost
gather_facts: false
vars:
- SecGroup: '/stuff/foo/thing'
tasks:
- debug:
msg: "{{ SecGroup | reslash }}"
That's arguably a cleaner solution.
The solution by #larsks didn't entirely work for me as described. I needed to escape the backslash with double slashes \ plus the guard character in order for it to work in the Ansible Playbook.
This works: replace('/','\\;') | replace(';', '')
Another easy solution is to leave escaping backslash to ansible itself. This is how i would have done.
- set_fact:
replacer: '\'
- name: With guard character
debug:
msg: '{{ SecGroup | replace("/",replacer)}}'
Same workaround if you want replace 1 backslash with double backslash on a windows path.
- hosts: localhost
gather_facts: False
vars:
- iis_manager_logdir: 'C:\inetpub\logs\manager-logs'
tasks:
- set_fact:
iis_mng_logs: "{{ iis_manager_logdir | regex_replace('\\\\', '\\\\;') | regex_replace(';', '\\\\') }}"
- name: Original path
debug:
msg: "{{ iis_manager_logdir }}"
- name: New path
debug:
msg: "{{ iis_mng_logs }}"
Thanks to the #larsks's answer i've managed to replace backslashes in ansible string variable value without intermediate replace. It's possible by supplying into regex_replace expression a regex quantifier {1} between last backslash and closing quote.
For example, expression like {{ install_path | regex_replace('\\\\{1}', '/') }} replaces all occurences of backslash \ to forward slash /. It was used to replace Windows path delimiters with Unix-like ones:
- name: install libs
win_shell: "pip install --no-index --find-links \"file://{{ install_path | regex_replace('\\\\{1}', '/') }}/libs\" attrs requests"
become: true
For what its worth, after countless struggles, this is what has worked for me without any workarounds:
Forward to Back Slash
ForwardtoBackSlash: "{{ 'c:/test' | regex_replace('\\\/', '\\\\') }}"
output:
c:\test
Single Slash to Double Slash
SingleSlashtoDoble: "{{ 'C:\test\logs\logfile.txt'| regex_replace('\\\\', '\\\\\\\\') }}"
Output:
C:\\test\\logs\\logfile.txt
I hope it helps someone.
I am generating an yaml template in Ansible and I am trying to truncate two concatenated strings:
Here the following code doesn't work because of the concatenation does not pipe into the regex_replace correctly.
I am only wanting the first n characters (first 10 characters in this example)
Normally I could just combine these two into one variable and then do
{{variabel [:10] }}
But I am no able to do that in this case because the file I am working in is getting combined with variables and then saved as a yaml file...
Basically I want to truncate the string without first combining or creating a new variable.
- hosts: localhost
gather_facts: False
vars:
foo: "somelongstring"
tasks:
- name: Display debug output
debug:
msg: "{{ foo + '-moretext' | regex_replace('^.{0,10}', '\\1') }} "
To apply a filter or an operator on a complex expression (other than a sequence of filters), you have to surround it with parenthesis.
So to truncate the result of a concatenation in 1 action:
msg: "{{ (foo + '-moretext')[:10] }} "
BTW, there is also the truncate filter:
msg: "{{ (foo + '-moretext') | truncate(10, True, '') }} "