Saltstack - Unable to print output in Jinja templating - jinja2

I am trying to reuse the output of a command in Saltstack, but when I try to print the output using "cmd.run", it is failing with the below error. Not sure in which format the data is getting returned from "cmd.run".
{% set output = salt['cmd.shell']('ifconfig') %}
display:
cmd.run:
- name: echo '{{ output }}'
Error:
Data failed to compile:
----------
Rendering SLS 'base:patching.install_patches' failed: mapping values are not allowed in this context

The issue seems to be due to the placement of quotes. The single quotes are required around the entire command to run.
Below should work:
{% set output = salt['cmd.shell']('ifconfig') %}
display:
cmd.run:
- name: 'echo "{{ output }}"'

Related

Using Saltstack cmd.script with args to Insert Jinja Variable

I am running a powershell script to obtain a particular user's credentials, and then use those credentials in a salt state. This works fine when the username is hardcoded directly in the powershell script. But, I am unable to pass the username as an argument. Here is my salt state:
{% set creds = salt['cmd.powershell']('C:\test2.ps1' 'username') %}
test_output:
cmd.run:
- name: echo {{ creds }}
I have also tried this too...but it doesn't work.
{% set creds = salt['cmd.script'](shell='powershell' source='C:\test2.ps1' args='username')
%}
How do i correctly pass an argument to my powershell script to set my variable?
The rules for strings and function calls are the same in Jinja as for Python. Either " or ' are the string delimiters, and \ is the escape character. Function arguments are separated by commas.
{% set creds = salt['cmd.powershell']('C:\\test2.ps1 username') %}
For cmd.script, the source should be a remote URL, not a local file.
{% set creds = salt['cmd.script'](shell='powershell', source='salt://path/test2.ps1', args='username') %}

ansible passing JSON as a string without quoting it

I was running into this particularly painful Ansible task of:
Reading JSON from a file.
Passing the JSON as a string to helm, BUT not quoting it.
- name: deploy release
community.kubernetes.helm:
name: my_release
chart_ref: ./charts/my_chart
release_namespace: "{{namespace}}"
state: "{{state}}"
release_values:
x: "{{ lookup('file', './stuff.json') }}"
What I want the helm values file to look like is:
x: |
{ "hello": "world" }
The issue I ran into with the following lookup {{ lookup('file', './stuff.json') }} is that ansible will interpret it as a dict and pass the dict to helm. This does not work as I need a string. Here's what the output in the helm values file looks like:
x:
hello: world
Then I tried {{ lookup('file', './stuff.json') | quote}}. Ansible passes a string to helm, but that string has a quote around it. When I try to read the JSON in my deployment, I get a parse error. Here's what the output would look like:
x: '{ "hello": "world" }'
I even tried {{ lookup('file', './stuff.json') | to_json }}, as recommended here, but that failed as well.
Using {{ lookup('file', './stuff.json') | string }} will force Ansible to evaluate it as a string without adding quotes.
There are several examples in Using filters to manipulate data that use this filter.
Documentation for the filter can be found in the Jinja2 documentation. The documentation states that the filter will:
Make a string unicode if it isn’t already. That way a markup string is not converted back to unicode.
I'm not particularly sure why this corrects the issue, but it did.

Jinja2 if/else on user defined variable

Attempting to make a decision in a template based on the last character of a variable (third level domain hostname) , but the epiphany alludes me. Make a config stanza if value else, do the other.
I set a fact in play:
- name: Set third level domain name to a variable
set_fact:
my_3rd_levelname: "{{ ansible_nodename.split('.')[0] }}"
- name: Ascertain if which server we're on
set_fact:
my_one_or_two: "{{ my_3rd_levelname[-1]|int }}"
...which appears to echo out with debug, save the casting as an int...see below.
TASK [role-test : Echo out my_one_or_two] *******************************************************************************************************************
ok: [w.x.y.42] => {
"my_one_or_two": "2"
}
Then in the template.j2...
{# If my_one_or_two is even list server1 first. If not, second. #}
{% if lookup('vars,',my_one_or_two) + my_one_or_two|int is 1 %}
[some config file stanza here]
{% else %}
[some other config file stanza instead]
I've poked and hoped until I can stand it no longer and am reaching out. I've tried just using the raw variable, e.g., {% if my_one_or_two|int == 1 %} along with many other attempts, but I'm stuck. I can't seem to overcome this error:
AnsibleError: template error while templating string: expected token 'name', got 'integer'. String: [the contents of my template]
Any input would be greatly appreciated at this juncture.
Thanks
Okay...leaving this here in case someone else doesn't realize you can use any Python method that the object supports. Here's what I did. Remember the server names end in 1 or 2 and its a String.
Created a varible in /roles/[rolename]/vars...
my_simple_hostname: "{{ ansible_nodename.split('.')[0] }}"
Then used the 'endswith' method to evaluate it....
% if my_simple_hostname.endswith('1') == true %}
[content if true]
{% else %}
[content when false]
{% endif %}

How to prevent Jinja2 substitution in Ansible playbook?

In my playbook, a JSON file is included using the include_vars module. The content of the JSON file is as given below:
{
"Component1": {
"parameter1" : "value1",
"parameter2" : "value2"
},
"Component2": {
"parameter1" : "{{ NET_SEG_VLAN }}",
"parameter2": "value2"
}
}
After the JSON file is included in the playbook, I am using uri module to sent an http request as given below:
- name: Configure Component2 variables using REST API
uri:
url: "http://0.0.0.0:5000/vse/api/v1.0/config/working/Component2/configvars/"
method: POST
return_content: yes
HEADER_x-auth-token: "{{ login_resp.json.token }}"
HEADER_Content-Type: "application/json"
body: "{{ Component2 }}"
body_format: json
As it can be seen, the body of the http request is send with the JSON data Component2. However, Jinja2 tries to substitute the {{ NET_SEG_VLAN }} in the JSON file and throws and undefined error. The intention is not to substitute anything inside the JSON file using Jinja2 and send the body as it is in http request.
How to prevent the Jinja2 substitution for the variables included from the JSON file?
You should able to escape the variable even with {{'{{NET_SEG_VLAN}}'}} to tell jinja not to template anything inside that block.
You should be able to escape the variable with {% raw %} and {% endraw %} to tell Jinja not to template anything inside that block.
!unsafe
From documentation at https://docs.ansible.com/ansible/2.10/user_guide/playbooks_advanced_syntax.html#unsafe-or-raw-strings:
When handling values returned by lookup plugins, Ansible uses a data type called unsafe to block templating. Marking data as unsafe prevents malicious users from abusing Jinja2 templates to execute arbitrary code on target machines. The Ansible implementation ensures that unsafe values are never templated. It is more comprehensive than escaping Jinja2 with {% raw %} ... {% endraw %} tags.
You can use the same unsafe data type in variables you define, to prevent templating errors and information disclosure. You can mark values supplied by vars_prompts as unsafe. You can also use unsafe in playbooks. The most common use cases include passwords that allow special characters like { or %, and JSON arguments that look like templates but should not be templated.
I am using it all the time, like this:
# Load JSON content, as a raw string with !unsafe
- tags: ["always"]
set_fact:
dashboard_content: !unsafe "{{ lookup('file', './dash.json') | to_json }}"
# Build dictionnary via template
- tags: ["always"]
set_fact:
cc: "{{ lookup('template', './templates/cm_dashboard.yaml.j2') | from_yaml }}"
## cm_dashboard.yaml.j2 content:
hello: {{ cc_dashboard_content }}
# Now, "cc" is a dict variable, with "hello" field protected!

SaltStack cmd.run with curl and json body

I have a trivial task that I am stuck to write salt state for. I need to call REST endpoint using curl with json body. This is
curl localhost/endpoint -d '{"some" : "data"}'
My idea was to simply take this and put it into salt state by using cmd.run. Does not work. So far I have this:
{%- set data = {'some': 'data'} %}
Use echo instead of curl:
cmd.run:
- name: echo '{{ data|json }}'
And this gives me
failed: Unknown yaml render error; line 5
Use echo instead of curl:
cmd.run:
- name: echo '{"some": "data"}' <======================
I have Salt version 2014.7.1
For me the problem was the ":" within the curl command that was interpreted as YAML (see: How to escape indicator characters (i.e. : or - ) in YAML)
I ended up using the multi-line approach. That allows me to write the command with no escaping while variables (e.g pillar data) are still interpreted correctly.
E.g.
Salt state description:
cmd.run:
- name: >-
curl -X GET "https://api.example.com/client/{{ pillar['client_id'] }}" -H "X-Auth-Email: name#example.co.za" -H "X-Auth-Key: {{ pillar['api_key'] }}" -H "Content-Type: application/json" --data '{"some_json":true}'
When working with json it is sometimes easier to avoid the jinja renderer altogether. The following example uses the pybojects renderer (which is nice for a lot of other reasons too).
echo.sls:
#!pyobjects
import json
data = {'some': 'data'}
def dump(d):
return "'" + json.dumps(d).replace("'", "'\\''") + "'"
Cmd.run("echo {}".format(dump(data)))
Note that the custom dump function definition and usage is added for the sake of completeness.