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, '') }} "
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 ...
I want to apply an ansible filter to each element in a list, then create a string from it. So lets say I have a list
my_files:
- /etc/passwd
- /etc/group
And then in an ansible task I want to create a string like "passwd,group". Without the filtering, this can be done with the jinja syntax
{{ ",".join(my_files) }}
which would create a string "/etc/passwd,/etc/group". But now I want to apply the ansible basename filter to each element. For a scalar this works like
{{ /etc/passwd | basename }}
which would generate the string "passwd". So the question is, how to combine these two operations to generate the string "passwd,group" from the list my_files?
You can use a map reduce pattern to achieve that. You would first map each entry using a basename filter and then join the resulting list.
The following playbook demonstrates how this can be done:
- hosts: localhost
gather_facts: false
vars:
my_files:
- /etc/passwd
- /etc/group
tasks:
- name: MapReduce
debug:
msg: "{{ my_files | map('basename') | list | join(',')}}" # results in passwd,group
I have 3 variables named IPOctet, ServerIPRange and epcrange.
If I perform the following operation in my terminal, it works perfectly
IPOctet=$(echo "$ServerIPRange/$epcrange+$IPOctet" | bc)
How to I do something similar in a ansible inside a task, for e.g
---
- hosts: localhost
gather_facts: False
vars_prompt:
- name: epcrange
prompt: Enter the number of EPCs that you want to configure
private: False
default: "1"
- name: serverrange
prompt: Enter the number of Clients that you want to configure
private: False
default: "1"
- name: ServerIPRange
prompt: Enter the ServerIP range
private: False
default: '128'
- name: LastIPOctet
prompt: Enter The last Octet of the IP you just entered
private: False
default: '10'
pre_tasks:
- name: Set some facts
set_fact:
ServerIP1: "{{ServerIP}}"
ServerIPRange1: "{{ServerIPRange}}"
IPOctet: "{{LastIPOctet}}"
- name: local action math
local_action: shell {{IPOctet}}=$(echo "${{ServerIPRange}}/${{epcrange}}+${{IPOctet}}" | bc) # Proper Syntax?
with_sequence: start=1 end=4
register: result
ignore_errors: yes
What is the proper syntax for this command? Maybe using shell echo "......." . I just need to save the contents of this command into the IPOctet variable and IPOctet will change with each loop iteration and the results should be stored in my result register
P.S: how can I access the individual items in the array separately?
Edit: Is anything like this possible, currently it just does the calculation once and stores it 4 times in the register...
- name: bashless math
set_fact:
IPOctet: "{{ (ServerIPRange|int/epcrange|int)+IPOctet|int }}"
register: IPOctet
with_sequence: "start=1 end={{stop}} "
register: my_ip_octet
Your terminal expression reassigns the IPOctet shell variable, so it gives a different result each time it is executed. This is fine, but difficult to reproduce in Ansible:
$ IPOctet=10 ServerIPRange=128 epcrange=1
$ IPOctet=$(echo "$ServerIPRange/$epcrange+$IPOctet" | bc); echo $IPOctet
138
$ IPOctet=$(echo "$ServerIPRange/$epcrange+$IPOctet" | bc); echo $IPOctet
266
The syntax: "shell {{IPOctet}}=$(echo ..." does NOT assign to the Ansible variable.
The shell attempts to execute a command like "10=138", which is not found.
When register is used within a loop, the target variable is not set until the loop completes - so your expression always sees the original value for {{IPOctet}}.
A solution is to run the whole loop as a single shell command:
- name: local action math2
local_action: shell IPOctet={{IPOctet}}; for i in 1 2 3 4; do IPOctet=$(expr {{ServerIPRange}} / {{epcrange}} + $IPOctet); echo $IPOctet; done
register: result
NOTE: I've used the expr command rather than bc, but the results are the same.
You can iterate over these results using result.stdout_lines:
- name: iterate results
local_action: debug msg={{item}}
with_items: result.stdout_lines
Firstly your Jinja template is incorrect, every single variable needs to be surrounded with a pair of brackets. You can not use multiple variables within single pair of brackets. For example,
{{ ServerIPRange }}
Secondly, set_fact is used only to set a fact value. You can not run shell commands using set_fact. You should use shell module instead.
- name: local action math
local_action: shell {{ IPOctet }}=$(echo {{ ServerIPRange|int }}/{{ epcrange|int }}+{{ IPOctet|int }}" | bc)
with_sequence: start=1 end=4
register: result
ignore_errors: yes
Ansible will do the calculation 4 times and store it in a list as 4 different elements. You can check what all is stored inside this list and can even access it by looping over it.
- debug: msg={{ result }}
Hope this helps :)
I am writing a playbook in which I need to execute mysql query and pass
output in json format. playbook code working fine just I want facing error in string concatenate part. If I am passing sample json string it is working fine.
- name: Execute php script with json parameter
command: php -f /path/to/php/test.php "{'colors':{'red','blue','yellow'}}"
register: php_output
output.stdout_lines is variable already set in my playbook which contains output in {'red','blue','yellow'} format.
- name: Execute php script with json parameter
command: php -f /path/to/php/test.php '{"stdout_lines": {{output.stdout_lines}} }'
register: php_output
So how can I concatenate output.stdout_lines variable in '{"stdout_lines": {{output.stdout_lines}} }' ? any suggestions
stdout_lines was created for convenience. Back in the day we only had stdout. Which is what you want, I think:
command: php -f /path/to/php/test.php '{"stdout_lines": {{output.stdout}} }'
and if you want to really concat yourself, say because you have your own list of strings then you can use the Jinja2 built-in filter join:
- hosts: localhost
gather_facts: False
tags: so9
vars:
- str_list: ['Hello', 'world', '!', ]
tasks:
- debug: msg="orig list={{ str_list }}"
- debug: msg="concated = {{ str_list | join(' ') }}"
- set_fact: concated_str="{{ str_list | join(' ') }}"
- debug: msg="assigned to a variable concated_str = {{ concated_str }}"
Try like this.
vars:
- img_path: "/var/lib/libvirt/images/{{ instance_name }}-disk2.img"
Where instance name is a another variable
This will do
- name: Generate JSON output based on template
template: src=colors.json.j2 dest=/tmp/colors.json
with_items: colors
It will generate a file like
{'colors':
{
'blue',
'red',
'green',
}
}
The to_json filter is what you want.
Ansible doesn't store variables as JSON strings, it stores them as Python objects. When you're debugging e.g. with -vvv, Ansible displays these Python objects as JSON strings, but that's for your convenience.
To produce the desired command:
- name: Execute php script with json parameter
vars:
# define my desired object
my_object:
colors: "{{ output.stdout_lines }}"
# my_object contains a dict with a single key
# "colors" who's value is the list contained
# in output.stdoutlines
command: "php -f /path/to/php/test.php {{ my_object | to_json | quote }}"
register: php_output
to_json takes the object stored in my_object and outputs a JSON string.
quote automatically handles any shell quoting that might be required for the JSON string. e.g. If my JSON string contains double-quotes, quote will wrap the whole JSON string in single-quotes.