Ansible - Print message - debug: msg="line1 \n {{ var2 }} \n line3 with var3 = {{ var3 }}" - action

In Ansible (1.9.4) or 2.0.0
I ran the following action:
- debug: msg="line1 \n {{ var2 }} \n line3 with var3 = {{ var3 }}"
$ cat roles/setup_jenkins_slave/tasks/main.yml
- debug: msg="Installing swarm slave = {{ slave_name }} at {{ slaves_dir }}/{{ slave_name }}"
tags:
- koba
- debug: msg="1 == Slave properties = fsroot[ {{ slave_fsroot }} ], master[ {{ slave_master }} ], connectingToMasterAs[ {{ slave_user }} ], description[ {{ slave_desc }} ], No.Of.Executors[ {{ slave_execs }} ], LABELs[ {{ slave_labels }} ], mode[ {{ slave_mode }} ]"
tags:
- koba
- debug: msg="print(2 == Slave properties = \n\nfsroot[ {{ slave_fsroot }} ],\n master[ {{ slave_master }} ],\n connectingToMasterAs[ {{ slave_user }} ],\n description[ {{ slave_desc }} ],\n No.Of.Executors[ {{ slave_execs }} ],\n LABELs[ {{ slave_labels }} ],\n mode[ {{ slave_mode }} ])"
tags:
- koba
But this is not printing the variable with new lines (for the 3rd debug action)?

debug module support array, so you can do like this:
debug:
msg:
- "First line"
- "Second line"
The output:
ok: [node1] => {
"msg": [
"First line",
"Second line"
]
}
Or you can use the method from this answer:
In YAML, how do I break a string over multiple lines?

The most convenient way I found to print multi-line text with debug is:
- name: Print several lines of text
vars:
msg: |
This is the first line.
This is the second line with a variable like {{ inventory_hostname }}.
And here could be more...
debug:
msg: "{{ msg.split('\n') }}"
It splits the message up into an array and debug prints each line as a string. The output is:
ok: [example.com] => {
"msg": [
"This is the first line.",
"This is the second line with a variable like example.com",
"And here could be more...",
""
]
}
Thanks to jhutar.

Pause module:
The most convenient and simple way I found to display a message with formatting (ex: new lines, tabs ...) is to use the pause module instead of debug module:
- pause:
seconds: 1
prompt: |
======================
line_1
line_2
======================
You can also include a variable that contains formatting (new lines, tabs...) inside the prompt and it will be displayed as expected:
- name: test
hosts: all
vars:
line3: "\n line_3"
tasks:
- pause:
seconds: 1
prompt: |
/////////////////
line_1
line_2 {{ line3 }}
/////////////////
Tip:
when you want to display an output from a command, and instead of running an extra task to run the command and register the output, you can directly use the pipe lookup inside the prompt and do the job in one shot:
- pause:
seconds: 1
prompt: |
=========================
line_1
{{ lookup('pipe', 'echo "line_2 with \t tab \n line_3 "') }}
line_4
=========================
Extra notes regarding the pause module:
If you have multiple hosts, note that the pause task will run
only once against the first host in the list of hosts.
This means that if the variable you want to display exists only in
part of the hosts and the first host does not contain that variable
then you will get an error.
To avoid such an issue, use {{ hostvars['my_host']['my_var'] }}
instead of {{ my_var }}
Combining pause with when conditional might skip the task! Why?
Because the task will only run once against the first host which
might not conform to the stated when conditions.
To avoid this, don't use conditions that constrain the number of
hosts! As you don't need it either, because you know that the task will
run only once anyway. Also use hostvars stated above to make sure
you get the needed variable whatever the picked up host is.
Example:
Incorrect:
- name: test
hosts: host1,host2
vars:
display_my_var: true
tasks:
- when: inventory_hostname == 'host2'
set_fact:
my_var: "hi there"
- when:
- display_my_var|bool
- inventory_hostname == 'host2'
pause:
seconds: 1
prompt: |
{{ my_var }}
This example will skip the pause task, because it will choose only the first host host1 and then starts to evaluate conditions, when it finds that host1 is not conforming to the second condition it will skip the task.
Correct:
- name: test
hosts: host1,host2
vars:
display_my_var: true
tasks:
- when: inventory_hostname == 'host2'
set_fact:
my_var: "hi there"
- when: display_my_var|bool
pause:
seconds: 1
prompt: |
{{ hostvars['host2']['my_var'] }}
Another example to display messages where the content depends on the host:
- set_fact:
my_var: "hi from {{ inventory_hostname }}"
- pause:
seconds: 1
prompt: |
{% for host in ansible_play_hosts %}
{{ hostvars[host]['my_var'] }}
{% endfor %}

You could use stdout_lines of register variable:
- name: Do something
shell: "ps aux"
register: result
- debug: var=result.stdout_lines

Suppressing the last empty string of apt with [:-1]
---
- name: 'apt: update & upgrade'
apt:
update_cache: yes
cache_valid_time: 3600
upgrade: safe
register: apt
- debug: msg={{ apt.stdout.split('\n')[:-1] }}
The above debug: line results in nice line breaks, due to .split('\n'), and a suppressed last empty string thanks to [:-1]; all of which is Python string manipulation, of course.
"msg": [
"Reading package lists...",
"Building dependency tree...",
"Reading state information...",
"Reading extended state information...",
"Initializing package states...",
"Building tag database...",
"No packages will be installed, upgraded, or removed.",
"0 packages upgraded, 0 newly installed, 0 to remove and 0 not upgraded.",
"Need to get 0 B of archives. After unpacking 0 B will be used.",
"Reading package lists...",
"Building dependency tree...",
"Reading state information...",
"Reading extended state information...",
"Initializing package states...",
"Building tag database..."
]

I dig a bit on #Bruce P answer about piping output through sed, and this is what I came up to :
ansible-playbook [blablabla] | sed 's/\\n/\n/g'
if anyone is interested.

This is discussed here. In short you either need to pipe your output through sed to convert the \n to an actual newline, or you need to write a callback plugin to do this for you.

As a workaround, I used with_items and it kind of worked for me.
- debug: msg="Installing swarm slave = {{ slave_name }} at {{ slaves_dir }}/{{ slave_name }}"
- debug: msg="Slave properties = {{ item.prop }} [ {{ item.value }} ]"
with_items:
- { prop: 'fsroot', value: "{{ slave_fsroot }}" }
- { prop: 'master', value: "{{ slave_master }}" }
- { prop: 'connectingToMasterAs', value: "{{ slave_user }}" }
- { prop: 'description', value: "{{ slave_desc }}" }
- { prop: 'No.Of.Executors', value: "{{ slave_execs }}" }
- { prop: 'LABELs', value: "{{ slave_labels }}" }
- { prop: 'mode', value: "{{ slave_mode }}" }
tags:
- koba

I had similar problem with log file which I wanted to print to console. split("\n") works fine but it adds visible \n to each line so I found nicer way
tasks:
- name: Read recent lines from logfile for service {{ appName }}
shell: tail -n 1000 {{ logFile }}
register: appNameLogFile
- debug:
msg: "This is a stdout lines"
with_items: "{{ appNameLogFile.stdout }}"
It iterates over each line from appNameLogFile and as the side effect prints this line into the console. You can update it to
msg: "This is a stdout lines: {{ item }}"
but in my case it was not needed

Related

In Ansible, how do I add a line without delete comment?

I want to add the lines that don't exist, without deleting the comments of the lines that already exist.
my /tmp/file
line1
line2 #comment2
line3 #comment3
my playbook
---
- hosts: all
vars:
varlist:
- line1
- line2
- line3
- line4
- line5
tasks:
- name: "Add line"
lineinfile:
dest: "/tmp/file"
regexp: "^{{ item }}.*"
line: "{{ item }}"
state: present
create: yes
with_items:
- "{{ varlist }}"
expectation of the result.
line1
line2 #comment2
line3 #comment3
line4
line5
real result.
line1
line2
line3
line4
line5
I tried it that way, but it didn't work either.
- name: "Add line"
lineinfile:
dest: "/tmp/file"
backrefs: yes
regexp: "^{{ item }}(.*)"
line: '{{ item }}\1'
state: present
create: yes
with_items:
- "{{ varlist }}"
I think you likely ran afoul of how lineinfile: thinks about the world, where your requirements fell into a "middle ground" that was triggered by the use of backrefs: true, which causes the behavior when regexp: failed to match (and its insertafter: friend) to be weird
I do appreciate this is a lot of jinja2, but it does do what you asked. If there are other more ansible-y ways, I'm sure others can chime in with their answers and you can pick the one you like the best
- name: read it in
slurp:
path: /tmp/file
register: the_file
- name: chop it up
set_fact:
# this creates a dict[str, str] consisting of any non-space terms
# mapped to their "tail" on the line; it also tosses out blank lines,
# if that matters in your case
file_parts: >-
{%- set results = {} -%}
{%- for line in the_file.content | b64decode | split(cr) -%}
{%- set ma = line | regex_findall("^([^ ]+)(.*)") -%}
{%- for it in ma if it|length > 0 -%}
{%- set _ = results.update({it[0]: it[1]}) -%}
{%- endfor -%}
{%- endfor -%}
{{ results }}
vars:
cr: "\n"
- name: write it back out
copy:
dest: /tmp/file
content: |
{% for v in varlist %}
{{ v ~ file_parts.get(v, "") }}
{% endfor %}

Unable to use regex or search on ansible task for a string search

Team,
I have this task below where I want to create a condition and based on that I want to run other tasks. so as first step, creating condition task itself is failing because am not able to figure out the syntax.. ran several iterations and also check online here https://docs.ansible.com/ansible/latest/user_guide/playbooks_tests.html
Any hint, am new.
- name: Identify device naming convention[nvme*] using REGEX
debug:
msg: "Found Block Device {{ item.0.device }}"
loop: "{{ local_volume_mount_disks|subelements('partitions') }}"
#when: "{{ item.0.device }}" is regex("nvme2\w+"). #<<<FAILED
when: "{{ item.0.device }}" is search("nvme")
register: nv_device_type
tags: tag_to_create_with_values, tag_to_delete
- name: Output device from REGEX results
debug:
var: nv_device_type
ignore_errors: no
output
ERROR! Syntax Error while loading YAML.
expected <block end>, but found '<scalar>'
The error appears to be in '/ansible-managed/jenkins-slave/slave0/workspace/nvdc/run_ansible_playbook#2/k8s/baremetal/roles/local_volume_mount/tasks/main.yml': line 45, column 33, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
#when: "{{ item.0.device }}" is regex("nvme2\w+")
when: "{{ item.0.device }}" is search("nvme")
^ here
We could be wrong, but this one looks like it might be an issue with
missing quotes. Always quote template expression brackets when they
start a value. For instance:
with_items:
- {{ foo }}
Should be written as:
with_items:
- "{{ foo }}"
my values are below
local_volume_mount_disks:
- device: /dev/nvme2n1
partitions:
- number: 1
Just figured out after several iterations..
- name: Identify device naming convention[nvme*] using REGEX
debug:
msg: "Found Block Device {{ item.0.device }}"
loop: "{{ local_volume_mount_disks|subelements('partitions') }}"
when: '"{{item.0.device }}" is search("nvme")'
register: nv_device_type
output
ok: [node1] => (item=[{'device': '/dev/nvmedn1', 'partitions': [{'number': 1, 'start': '0%', 'end': '100%'}]}, {'number': 1, 'start': '0%', 'end': '100%'}]) => {
"msg": "Found Block Device /dev/nvmedn1"
}

Replace last occurrence of .jar in ansible regex_replace

How can I replace only the last character of .jar. Below is the sample value for LIST_jar[0].nameOfJar and the sample output expected
my-firsrt.jar-1.jar >> my-firsrt.jar-1
my-firsrt-2.0.jar-1.jar >> my-firsrt-2.0.jar-1
my-firsrt-1.0-jar-1.jar >> my-firsrt-1.0-jar-1
my-firsrt-jar-1.0-jar-1.jar >> my-firsrt-jar-1.0-jar-1
my-firsrt-jar-1.0.jar.jar >> my-firsrt-jar-1.0.jar
my-firsrt-jar-1.0-jar.jar >> my-firsrt-jar-1.0.jar
This is my sample code but it is not working accordingly as it is replacing all the jar value.
- name: Replace string
copy:
content: "{ name: jack }"
dest: "{{ directory }}/JAR_LIST/{{ LIST_jar[0].nameOfJar | regex_replace('.jar') }}.log"
There are more options. The most simple is splitext. More versatile is split to manipulate multiple extensions. The filter regex_replace is more complex but universal.
splitext
Use splitext. For example
- debug:
msg: "dest: {{ (item|splitext).0 }}.log"
loop: "{{ list_jar }}"
gives (given the list of filenames is in the variable list_jar)
msg: 'dest: my-firsrt.jar-1.log'
msg: 'dest: my-firsrt-2.0.jar-1.log'
msg: 'dest: my-firsrt-1.0-jar-1.log'
msg: 'dest: my-firsrt-jar-1.0-jar-1.log'
msg: 'dest: my-firsrt-jar-1.0.jar.log'
msg: 'dest: my-firsrt-jar-1.0-jar.log'
split
The next option is split. The task below gives the same results
- debug:
msg: "dest: {{ item.split('.')[:-1]|join() }}.log"
loop: "{{ list_jar }}"
regex_replace
If you want to use regex_replace the tasks below give the same results. Either remove the extension and concatenate .log
- debug:
msg: "dest: {{ item|regex_replace('^(.*)\\.jar$', '\\1') }}.log"
loop: "{{ list_jar }}"
, or replace the extension in the filter
- debug:
msg: "dest: {{ item|regex_replace('^(.*)\\.jar$', '\\1.log') }}"
loop: "{{ list_jar }}"
To make the code more readable it's a good idea to put the regular expressions into the variables and use single-quoted style. For example
- debug:
msg: "dest: {{ item|regex_replace(my_regex, my_replace) }}"
loop: "{{ list_jar }}"
vars:
my_regex: '^(.*)\.jar$'
my_replace: '\1.log'
Below is my sample code but it is not working accordingly as it is replacing all the jar value.
Correct, because the . in regex means "any character." If you just want to replace the literal string .jar then don't use regex replace, just use replace() which will use the literal characters you provide to it.
Otherwise, if you wish to continue to use regex_replace, then quote that character and also specify that the pattern should only match the end of the string
- debug:
msg: "{{ nameOfJar | regex_replace('[.]jar$', '') }}.log"
vars:
nameOfJar: my-firsrt-jar-1.0-jar-1.jar
We're going to assume the last two of your samples were just copy paste in this question, and that you prefer the .log outside the mustaches instead of just including '.log' in the replacement position of regex_replace

The error was: error while evaluating conditional

I try to use insert a unique number to each server I have by using Ansible.
As below, I wish these is:
"1" in 192.168.60.6's myid.txt
"2" in 192.168.60.7's myid.txt
- hosts: all
gather_facts:True
vars:
servers:
host1:
ip: 192.168.60.6
num: 1
host2:
ip: 192.168.60.7
num: 2
host3:
ip: 192.168.60.8
num: 3
tasks:
-name: write a unique number in myid.txt
lineinfile:
dest: "/home/user/myid.txt"
line: "{{ item.value.num }}"
when: "{{ item.value.ip }} == {{ ansible_all_ipv4_addresses[1] }}"
`enter code here`with_dict: "{{ servers }}"
Unfortunately, I got this error:
TASK [write unique number in myid]
*********************************************
fatal: [192.168.60.6]: FAILED! => {"failed": true, "msg": "The conditional check '{{item.value.ip}} == {{ ansible_all_ipv4_addresses[1] }}' failed.
The error was: error while evaluating conditional ({{item.value.ip}} == {{ ansible_all_ipv4_addresses[1] }}): float object has no element 60
The error appears to have been in '/home/shihhao/Desktop/BlueTech/ansible/kafka_playbook.yml': line 101, column 7,
but may be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- name: write unique number in myid
^ here\n"}
It seems once I add this line I will get the error.
when: "{{item.value.ip}} == {{ ansible_all_ipv4_addresses[1] }}"
BTW: {{ ansible_all_ipv4_addresses[1] }} is 192.168.60.6
You should use bare variables in your when: statement:
when: item.value.ip == ansible_all_ipv4_addresses[1]
From the documentation:
This is easy to do in Ansible with the when clause, which contains a raw Jinja2 expression without double curly braces
Raw expressions are used in when: statement, with assert module and with debug: var=varname (but debug: msg="{{varname}}").

Saltstack load pillar in a for loop

I am developing a automatic proftd installation whit Salt, i wont to get the ftp users from a template but I cant get work the pillar, i initialized the pillar whit the users data and call it into a for loop, but you don't get the pillar user data in the loop.
When i make salt-call pillar.get ftpusers in the minion, the response is:
local:
This is my pillar ftpusers.sls:
ftp-server.ftpusers:
user:
- user: user
- passhash: j2k3hk134123l1234ljh!"ยท$ser
- uuid: 1001
- guid: 1001
- home: /srv/ftp/user
- shel: /bin/false
And this is the for loop:
{% for users in pillar.get('ftpusers', {}).items() %}
/srv/herma-ftp/.ftpusers:
file.managed:
- user: root
- group: root
- mode: 444
- contents:'{{ user }}:{{ args['passhash'] }}:{{args['uuid'] }}:{{ args['guid'] }}::{{ args['home'] }}:{{ args['shel'] }}'
- require:
- file: /srv/herma-ftp
/srv/herma-ftp/{{user}}:
file.directory:
- user: nobody
- group: nobody
- dir_mode: 775
- makedirs: True
- require:
- file: /srv/herma-ftp
- watch:
- file: /srv/herma-ftp
module.run:
- name: file.set_selinux_context
- path: {{ args['home']}}
- type: public_content_t
- unless:
- stat -c %C {{ args['home'] }} |grep -q public_content_t
{% endfor %}
When I make in the minion
salt-call -l debug state.sls herma-ftp-server saltenv=My-enviroment test=True
Don't expect this for because don't can get the pillar data.
Your loop should also look like:
{% for user, args in pillar.get('ftpusers', {}).items() %}
Also, contents argument for a file.managed doesn't support templating. What you need to do is move /srv/herma-ftp/.ftpusers state outside of the loop, and make the loop inside the file template. The final layout of your state should look like:
/srv/herma-ftp/.ftpusers
file.managed:
source: salt://ftpserver/dot.ftpusers
template: jinja
...
...
{% for user, args in pillar.get('ftpusers', {}).items() %}
/srv/herma-ftp/{{user}}:
file.managed:
...
{% endfor %}
And your ftpserver/dot.ftpusers would look like:
{% for user, args in pillar.get('ftpusers', {}).items() %}
{{ user }}:{{ args['passhash'] }}:{{args['uuid'] }}:{{ args['guid'] }}::{{ args['home'] }}:{{ args['shel'] }}
{% endfor %}