Replace and eval in Jinja - jinja2

I have a string like this as Jinja variable:
foo-VERSION-bar
I want to replace VERSION with {{ grains.lsb_distrib_release }} and I want this to get evaluated.
if grains.lsb_distrib_release contains 123 I want the result to be foo-123-bar.
How to replace and eval in jinja?

Set value of your grain to a variable:
{% set version = salt['grains.get']('lsb_distrib_release', {}) %}
Use Jinja replace function:
{{ "foo-VERSION-bar"|replace("VERSION", version) }}

Without using replace Jinja filter, you can use its concatenation possibilities
{{ 'foo-' ~ salt['grains.get']('lsb_distrib_release') ~ '-bar' }}

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') %}

Concatenating a variable in Jinja with a single quote

I'm having trouble trying to concatenate variables (one with a single quote) in Jinja. My code looks like this:
{%- set my_quote = "'" -%}
{%- set invocation = invocation_id -%}
And the output I'm attempting is this:
{{ invocation ~ my_quote }}
The output from this is:
f21f9039-44e5-452f-8d7a-ee64245ada23'
Ok great! Now when I attempt to add the single quote to the beginning as well:
{{ my_quote ~ invocation ~ my_quote }}
The output is the invocation variable value without any single quotes:
f21f9039-44e5-452f-8d7a-ee64245ada23
How can I get this to output both single quotes properly?
Try the following:
"'{{ invocation_id }}'"
oh I love this question and the great reproducible example you provided. First thought, what happens if you escape the ' like this?
{%- set my_quote = "\'" -%}
Is there a reason it has to be part of the variable?
Could you do:
'{{ invocation }}'

jinja to json in Salt

I have a jinja template in salt that every time apply.state is ran, salt thinks it has changed when it hasn't.
The issue is that a reconfigure command executes when the file "changes", and this reconfigure would change the file from a single lined JSON to a lined JSON. For example:
salt creates the file like:
{ "key1": { "sub-key1": "sub-value1", "sub-key2": "sub-value2"}, "key2": "value2" }
but when the reconfigure command executes, it changes the file to:
{
"key1": {
"sub-key1": "sub-value1",
"sub-key2": "sub-value2"
},
"key2": "value2"
}
Is there a way to have salt create the file as a formatted JSON to begin with?
This is what I have:
{{ gb_server.secrets_file }}:
file.managed:
- source: {{ gb_server.secrets_tmpl }}
- user: {{ gb_server.secure_user }}
- group: {{ gb_server.secure_user }}
- mode: 600
- template: jinja
the template is as follow:
{%- import_yaml 'gb-server/defaults.yaml' as defaultmap -%}
{%- set secrets = gb_server_config['secrets'] -%}
{{ secrets|tojson|replace('\\\\n','\\n') }}
The values are in pillar in yaml format.
Ideally, the resulted JSON would have its key sorted alphabetically, that way the content of the resulted file wouldn't "change" and the reconfigure command wouldn't run every single time.
Is there a way to make this happen with salt?
Thank you.
It looks like file.serialize will do what you want here
{{ gb_server.secrets_file }}:
file.serialize:
- user: {{ gb_server.secure_user }}
- group: {{ gb_server.secure_user }}
- mode: 600
- data: {{ gb_server.secrets }}
- serializer: json
- serializer_opts:
- indent: 2
- sort_keys: true

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 %}

Ansible & Jinja2: combine a dictionary to each element of a list

I have a variable that is an array [{'foo':1},{'bar':2}].
I want to combine it with the following hash: {'baz':3} using a set fact (?) such as my output registered variable is:
[{'foo':1, 'baz':3},{'bar':2, 'baz':3}]
I've looked into the combine filter, but it only works when I already have an hash to work with. In my case I have an array.
Is there a way to achieve that using ansible?
Actually, I have found a way. map can be used with any filters, and arguments have to be passed after a comma
- name: test
set_fact:
_test: "{{ [{'foo':1}, {'bar':2}] | map('combine', {'baz':3}) | list }}"
produces:
ok: [localhost] => {
"_test": [
{
"baz": 3,
"foo": 1
},
{
"bar": 2,
"baz": 3
}
]
}
Jinja2 doesn't have list comprehension, but I think you can use set and for loop to achieve it:
{% set outputarray = [] -%}
{% for d in inputarray -%}
{% set r = d|combine({'baz': 3}) -%}
{{ ouputarray.append(r) and '' }}
{%- endfor %}