I have a ansible play that tries to split a string in the format of domain\user into its parts:
This is the task
tasks:
- name: do something which requires domain and user
win_shell: echo "{{ lookup('aws_ssm', 'service_user-account-2921', decrypt=True, region='eu-central-1' )}}.split('\\')[0] }}"
This results in:
ERROR! failed at splitting arguments, either an unbalanced jinja2 block or quotes: {{'DOMAIN\USER'.split('\')[0]}}
if I change the task to remove the lookup it still fails as long as I use \ as delimiter
#win_shell: echo "{{ 'test,strings'.split(',')[0] }}" #WORKS
win_shell: echo "{{ 'DOMAIN\\USER'.split('\\')[0]}}" #FAILS
how to split on a backslash in ansible / jinja?
Q: "How to split on a backslash in ansible/jinja?"
A: Put the separator into a variable. For example
vars:
separator: '\'
text: 'domain\user'
tasks:
- debug:
msg: "{{ text.split(separator) }}"
gives
"msg": [
"domain",
"user"
]
Related
I have an ansible task where I make use of shell module to run a custom bash script. This bash script expects an optional parameter which could be a multiline string. Example
~/myScript.sh -e "Each \nword \nin \nnew \nline"
This works perfectly fine when I run it in the bash shell. However, I have tried several ways but haven't made it run via the ansible task. The value being a variable itself. The task below -
- name: Sample Task
shell:
cmd: "{{ home_dir }}/myScript.sh -e '{{ string_with_escaped_newlines }}'"
Here, the value of variable string_with_escaped_newlines is set to Each \nword \nin \nnew \nline programmatically by other tasks. The output of the task confirms this.
I had put an echo in my shell script to debug & I observe that the echo would print 2 different sequences in the 2 cases(running directly via shell & running via ansible).
Debug output via shell-
Each
word
in
new
line
Debug output via ansible -
Each word in new line
Notice that there is an extra space introduced in the value in place on \n. I do not understand why this happens and how do I stop this. I have checked/tried shell as well as command modules of ansible.
cmd: "{{ home_dir }}/myScript.sh -e '{{ string_with_escaped_newlines }}'"
is first converted (by Jinja2) to
cmd: "<THE HOME>/myScript.sh -e 'Each \nword \nin \nnew \nline'"
Here \n is within double quotes and in YAML double quoted \ns are NEWLINEs (think "\n" in C or JSON).
For your case you can just write:
cmd: {{ home_dir }}/myScript.sh -e '{{ string_with_escaped_newlines }}'
Or to be more safe:
cmd: |
{{ home_dir }}/myScript.sh -e '{{ string_with_escaped_newlines }}'
YAML syntax is quite "confusing". You can take a look at Ansible's own YAML intro.
Given the script
shell> cat ~/myScript.sh
#!/usr/bin/sh
echo ${1}
shell> ~/myScript.sh "Each \nword \nin \nnew \nline"
Each
word
in
new
line
Q: "Newline '\n' is replaced by space ' ' in Ansible shell module."
A: It depends on how you quote and escape the string. The simplest option is single-quoted style because only ' needs to be escaped
string_with_escaped_newlines1: 'Each \nword \nin \nnew \nline'
You have to escape \n in double-quoted style
string_with_escaped_newlines2: "Each \\nword \\nin \\nnew \\nline"
Both variables expand to the same string
- debug:
var: string_with_escaped_newlines1
- debug:
var: string_with_escaped_newlines2
gives
string_with_escaped_newlines1: Each \nword \nin \nnew \nline
string_with_escaped_newlines2: Each \nword \nin \nnew \nline
Then, the quotation in the command is not significant. All options below give the same result
- command: ~/myScript.sh "{{ string_with_escaped_newlines1 }}"
- command: ~/myScript.sh '{{ string_with_escaped_newlines1 }}'
- command: ~/myScript.sh "{{ string_with_escaped_newlines2 }}"
- command: ~/myScript.sh '{{ string_with_escaped_newlines2 }}'
Example of a complete playbook for testing
- hosts: localhost
vars:
string_with_escaped_newlines1: 'Each \nword \nin \nnew \nline'
string_with_escaped_newlines2: "Each \\nword \\nin \\nnew \\nline"
tasks:
- command: ~/myScript.sh "{{ string_with_escaped_newlines1 }}"
register: out
- debug:
var: out.stdout_lines
- command: ~/myScript.sh '{{ string_with_escaped_newlines1 }}'
register: out
- debug:
var: out.stdout_lines
- command: ~/myScript.sh "{{ string_with_escaped_newlines2 }}"
register: out
- debug:
var: out.stdout_lines
- command: ~/myScript.sh '{{ string_with_escaped_newlines2 }}'
register: out
- debug:
var: out.stdout_lines
gives (abridged) four times the same result
TASK [debug] *********************************************************
ok: [localhost] =>
out.stdout_lines:
- 'Each '
- 'word '
- 'in '
- 'new '
- line
Notes:
Using shell instead of command gives the same results.
The best practice is using the module command unless the ansible.builtin.shell module is explicitly required.
Have you tried \r\n instead of \n
It helped me elsewhere in ansible
I'm trying write role to install MySql 8, and get problem with this:
- name: Extract root password from logs into {{ mysql_root_old_password }} variable
ansible.builtin.slurp:
src: "{{ mysql_logfile_path }}"
register: mysql_root_old_password
#when: "'mysql' in ansible_facts.packages"
- name: Extract root password from logs into {{ mysql_root_old_password }} variable
set_fact:
mysql_root_old_password: "{{ mysql_root_old_password.content | b64decode | regex_findall('generated for root#localhost: (.*)$', 'multiline=true') }}"
#when: "'mysqld' in ansible_facts.packages"
- name: Get Server template
ansible.builtin.template:
src: "{{ item.name }}.j2"
dest: "{{ item.path }}"
loop:
- { name: "my.cnf", path: "/root/.my.cnf" }
notify:
- Restart mysqld
on the .my.cnf I get password with quotes and brackets:
[client]
user=root
password=['th6k(gZeJSt4']
How to trim that?
What I try:
- name: trim password
set_fact:
mysql_root_old_password2: "{{ mysql_root_old_password | regex_findall('[a-zA-Z0-9,()!##$%^&*]{12}')}}"
Thanks.
The result of regex_findall is a list because there might be more matches. Take the last item
- set_fact:
mysql_root_old_password: "{{ mysql_root_old_password.content|
b64decode|
regex_findall('generated for root#localhost: (.*)$', 'multiline=true')|
last }}"
From your description
on the .my.cnf I get password with quotes and brackets ... How to trim that
I understand that you like to read a INI file like my.cnf.ini
[client]
user=root
password=['A1234567890B']
where the value of the key password looks like a list with one element in YAML and the structure doesn't change, but you are interested in the value without leading and trailing square brackets and single quotes only.
To do so there are several possibilities.
Via Ansible Lookup plugins
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- name: Extract root password from INI file
debug:
msg: "{{ lookup('ini', 'password section=client file=my.cnf.ini') }}"
register: result
- name: Show result with type
debug:
msg:
- "{{ result.msg }}"
- "result.msg is of type {{ result.msg | type_debug }}"
- "Show password only {{ result.msg[0] }}" # the first list element
Such approach will work on the Control Node.
Like all templating, lookups execute and are evaluated on the Ansible control machine.
Further Q&A
How to read a line from a file into an Ansible variable
What is the difference between .ini and .conf?
Further Documentation
ini lookup – read data from an INI file
Via Ansible shell module, sed and cut.
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- name: Extract root password from INI file
shell:
cmd: echo $(sed -n 's/^password=//p' my.cnf.ini | cut -d "'" -f 2)
register: result
- name: Show result
debug:
msg: "{{ result.stdout }}"
Please take note regarding "I get password with quotes and brackets ... ['<pass>'] ... How to trim that?" that from perspective of the SQL service, .cnf file, [' and '] are actually part of the password!
- name: Server template
template:
src: "my.cnf.ini"
dest: "/root/.my.cnf"
If that is correct they will be necessary for proper function of the service.
Is it possible to include a JSON query in the actual task? All of the examples show using an additional var for the query.
Taking the example from Ansible Filters
- name: "Display all ports from cluster1"
debug:
var: item
loop: "{{ domain_definition | json_query(server_name_cluster1_query) }}"
vars:
server_name_cluster1_query: "domain.server[?cluster=='cluster1'].port"
Converted to:
- name: "Display all ports from cluster1"
debug:
var: item
loop: "{{ domain_definition | json_query(domain.server[?cluster=='cluster1'].port) }}"
as is it returns:
FAILED! => {"reason": "Syntax Error while loading YAML.\n found unknown escape character '?'
I have tried to add an escape backslash before the question mark, but it still fails with:
"template error while templating string: unexpected char '?'
It's possible to use back-ticks ` . For example
- name: "Display all ports from cluster1"
debug:
var: item
loop: "{{ domain_definition | json_query('domain.server[?cluster==`cluster1`].port') }}"
(not tested)
Ok it appears you can escape double quote the query and it will work!
- name: "Display all ports from cluster1"
debug:
var: item
loop: "{{ domain_definition | json_query(\"domain.server[?cluster=='cluster1'].port\") }}"
To step a little bit aside from escape hell, I like to take advantage of yaml scalar blocks
- name: "Display all ports from cluster1"
vars:
my_query: >-
domain.server[?cluster=='cluster1'].port
debug:
var: item
loop: "{{ domain_definition | json_query(my_query) }}"
You don't have to escape anything this way (works for quotes and backslash as well, nice for regexps)
I have a set of jinja2 actions within curly braces separated by pipes. Within that set of actions I need to add a variable, but I keep getting syntax errors.
debug:
msg: "{{ item.path | basename | regex_replace('{{ variable }}', '') }}"
with_items: "{{ content.files }}"
Note that, the variable will contain some regex string for example...
The problem ansible has with this, is that it contains a double quote inside a double quote. I tried escaping, inverting double quotes to single quotes...nothing worked.
When I run the above as is, it considers the variable as a literal value.
You don't need curly braces to denote variables inside of curly braces. Here's a simple playbook to demonstrate:
---
- name: test
hosts: localhost
gather_facts: false
vars:
content:
files:
- path: /path1/itemXXX.jpg
- path: /path2/itXem.pdf
regex_pattern: '[X]+' # Match one or more X's
tasks:
- debug:
msg: "{{ item.path | basename | regex_replace(regex_pattern, '') }}"
with_items: "{{ content.files }}"
Results are:
TASK [debug] ***********************************************************************************************************************************************************************
ok: [localhost] => (item={'path': '/path1/itemXXX.jpg'}) => {
"msg": "item.jpg"
}
ok: [localhost] => (item={'path': '/path2/itXem.pdf'}) => {
"msg": "item.pdf"
}
I have playbook with windows path name in the extra arguments. first argument not escaping the drive letter and slash.
ansible-playbook d.yaml --extra-vars "ainstalldir=c:\\test stagedir=D:\packages outdir=d:\output\log"
TASK [print inpurt arguments] ********************************************************************************************************
ok: [127.0.0.1] => {
"msg": "installdir=c:\test, stragedir=D:\\packages, outdir=d:\\output\\log"
}
installdir prints as c:\test, I expect it should print as c:\\test
Here is my playbook.
---
- name: test command line arguments
connection: local
hosts: 127.0.0.1
gather_facts: false
vars:
installdir: "{{ ainstalldir }}"
stagedir: "{{ stagedir }}"
outdir: "{{ outdir }}"
tasks:
- name: print inpurt arguments
debug:
msg="installdir={{ installdir }}, stragedir={{ stagedir }}, outdir={{ outdir }}"
Any idea how to resolve this issue?
installdir prints as c:\test, I expect it should print as c:\\test
installdir contains: c : tab e s t.
tab is replaced with \t in the debug module output and in effect you see c:\test on the screen.
Other characters starting with backslash in your example (\p, \o, \l) do not have special meaning, so they are treated as two character strings; but you'd observe the same phenomenon with \n (and other escape sequences).
Don't use debug module to debug things concerned with data, it processes strings to make them printable.
Instead, use copy with content parameter and check the output in a file:
- copy:
content: |-
installdir={{ installdir }}
stragedir={{ stagedir }}
outdir={{ outdir }}
dest: ./result.txt
(remember you could/should use hexdump to verify what's really inside).
Use:
ansible-playbook d.yaml --extra-vars "ainstalldir=c:\\\test stagedir=D:\\\packages outdir=d:\\\output\\\log"
or
ansible-playbook d.yaml --extra-vars 'ainstalldir=c:\\test stagedir=D:\\packages outdir=d:\\output\\log'
Backslashes in double- and single quotes are interpreted differently by shell (see for example this question).