jinja2 get list of attributes - jinja2

I'm using salt + jinja2 to template a context.xml file. I have the following pillar:
context:
db_server: some_server
resources:
some_customer:
name: some_name
user: some_user
passwd: some_passwd
this_customer:
name: this_name
user: this_user
passwd: this_passwd
I need to create a string with a list of the names for each customer. Right now I have this:
{%- set nameList = pillar['context']['resources']|list()|join(', ') %}
This gives me this list: 'some_customer, this_customer'. I'd like this list: 'some_name, this_name'.
How can I do this?

The following one-liner worked for me:
{%- set nameList = pillar['context']['resources'].values()
|map(attribute='name')|join(', ') %}

Related

Jinja2 using numeric expressions in Template Expressions

I need to do something apparently very simple:
typedef enum {
{% for e in mylist %}
{{ e }} = 0x{{ '%04X' % (1 << loop.index0) }},
{%- endfor %}
ALL = 0x0FFF
} Sound_Region_t;
but this bombs with "jinja2.exceptions.TemplateSyntaxError: unexpected '<'"
Intention is to get something like:
typedef enum {
foo = 0x0001,
bar = 0x0002,
fie = 0x0004,
fom = 0x0008,
...,
ALL = 0x0FFF
} Sound_Region_t;
I.e.: value is a "walking bit" so I can "bitwise or" together them.
Same behavior if I try to use other variations including "{% with bit = 1 << loop.index %}" or similar.
What am I missing?
Jinja2 does not allow bitwise operators inside the templates, so we need to create a small global function that executes such operators and returns a number:
def leftshift(amount):
return 1 << amount
# Get or create a Jinja2 environment
from jinja2 import Environment
env = Environment(...)
# Add leftshift function to the global context
env.globals['leftshift'] = leftshift
And in the templates now we can call leftshift function with the loop index:
typedef enum {
{% for e in mylist %}
{{ e }} = 0x{{ '{:04X}'.format(leftshift(loop.index0))}},
{%- endfor %}
ALL = 0x0FFF
} Sound_Region_t;

Unable to read Pillar having array with dictionary structure using Jinja

I am trying to read an array of dictionary present in my salt pillar file inside my state sls file, but unable to read.
root#xxxxxxx:/srv/salt# salt-master --version
salt-master 3003.3
I am having pillar file as below:
Pillar File:
common:
sysctl.net.core.netdev_max_backlog: 16384
sysctl.net.core.optmem_max: 25165824
sysctl.net.core.rmem_max: 33554432
sysctl.net.core.somaxconn: 16384
deploy2:
packages:
- repo_name: xxxxxxxx
tag: xxxxxx
path: /tmp/sp_repo_1
- repo_name: xxxxxx
tag: sxxxxxx
path: /tmp/sp_repo_2
My state file as below:
{% for package in salt.pillar.get('deploy2:packages') %}
print my data:
cmd.run:
- name: "echo {{ package.repo_name }}"
{% endfor %}
Error:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:
Data failed to compile:
----------
Rendering SLS 'base:test' failed: while constructing a mapping
in "<unicode string>", line 3, column 1
found conflicting ID 'print my data'
in "<unicode string>", line 9, column 1
The ID of the states being run in context of an execution should be unique.
As you are iterating over an array of dictionaries having two elements, it creates two state IDs as print my data, which is not allowed. To avoid this, we need to use some element of the dict in the ID itself so that it is unique.
Example:
{% for package in salt.pillar.get('deploy2:packages') %}
print-my-{{ package.repo_name }}:
cmd.run:
- name: "echo {{ package.repo_name }}"
{% endfor %}
This will create 2 unique IDs - print-my-xxxxxxxx and print-my-xxxxxx. You could use package.tag or any such key which has unique values.

Get value using mine.get from a ordered dictionary in state.sls

I have a mine defined in pillar to get the ip of a salt minion. When I use the mine in a salt state I get a dictionary. Is there a way to filter out and get the first item from it.
Here is my mine which I have defined in a pillar:
ip_add:
- mine_function: grains.get
- ipv4
Here is my state file:
{% set id = 'aws-vm1' %}
{% set addrs = salt['mine.get'](id, 'ip_add') %}
{% for key in salt['mine.get'](id, 'ip_add') %}
Test:
file.append:
- name: C:\test
- text: {{addrs}}
The output i get is :
OrderedDict([('aws-vm1', ['10.93.143.235', '127.0.0.1'])])
I want to get the first ip so that I can share it between minions
I was able to find a work around. I get the list of IPs from the mine
aws-vm1:
----------
aws-vm1:
- 10.93.143.235
- 127.0.0.1
and grab the first value by {{ip[0]}} which is the private IP I want.
{% set id = 'aws-vm1' %}
# Returns a dict OrderedDict
{% set ip_list = salt['mine.get'](id, 'ip_add') %}
{% set ip= ip_list.values() | first %}
Test:
file.append:
- name: C:\test
- text: {{ip[0]}}

Can you use two where conditions in an assign Liquid tag?

Can you use two where conditions in an assign Liquid tag?
I'm getting this build error when trying to bundle exec jekyll serve --trace:
Liquid Exception: Liquid error (line 7): wrong number of arguments (given 4, expected 3) in party/democratic/index.md
Error: Liquid error (line 7): wrong number of arguments (given 4, expected 3)
This is the Liquid tag in question:
{% assign people = site.data.people | where: election_2020.office, 'U.S. President' and election_2020.party, 'Democratic' | sort: 'last_names' %}
I can't find documentation one way or the other whether you can have two where filters in an assign tag.
PS: here's a sample of the people.yml file:
-
id: 'julian-castro'
first_names: 'Julián'
last_names: 'Castro'
full_name: 'Julián Castro'
image: '/images/people/julian-castro-wikipedia.jpg'
gender: 'male'
image: '/images/people/julian-castro-wikipedia.jpg'
election_2020:
office: 'U.S. President'
address: 'P.O. Box 501'
latitude: '29.430018'
longitude: '-98.4987548'
city: 'San Antonio'
state: 'TX'
zip: '78292'
donate: 'https://secure.actblue.com/donate/julianforthefuture'
facebook: 'https://www.facebook.com/julianforthefuture/'
instagram: 'https://www.instagram.com/juliancastrotx/?hl=en'
twitter: 'https://twitter.com/JulianCastro'
website: 'https://www.julianforthefuture.com/'
party: 'Democratic'
election_type: 'primary'
source: 'https://voteinfo.utah.gov/2020-presidential-candidates/'
candidate_status: 'withdrew'
last_updated: '2020-02-20'
Where filter allows only to compare a key value with another value. If you want to use complex filtering you can use where_exp filter.
{% assign people = site.data.people | where_exp: "someone", "someone.election_2020.office == 'U.S. President' and someone.election_2020.party == 'Democratic'" | sort: "last_names" %}

Insert Environment Variable using Jinja in SaltStack

I am trying to read a JSON file inside a folder. using import_json.
Here is my code
{% set instance_id = grains['INSTANCE_ID'] %}
INSTANCE_ID Env Var:
environ.setenv:
- name: INSTANCE_ID
- value: {{ grains['INSTANCE_ID'] }}
- update_minion: True
{% import_json "/tmp/$INSTANCE_ID/conf.json" as config_properties %}
But I am getting this error
Data failed to compile:
Rendering SLS 'base:cloud.steps.conf' failed: Jinja error: /tmp/$INSTANCE_ID/conf.json.
Although when I insert the INSTANCE_ID manually it works as expected.
What I want is to be able to insert either $INSTANCE_ID or directly the grain value {{ grains['INSTANCE_ID'] }}
Can someone please help me with this?
Thanks.
{% import_json "/tmp/$INSTANCE_ID/conf.json" as config_properties %}
I imagine you are trying to evaluate the variable $INSTANCE_ID in the above statement. Jinja template evaluates the variables in expression statements.
In this case, the variable is set in the first line, using set
{% set instance_id = grains['INSTANCE_ID'] %}
So, you can use it in expression along with string appends, like
{% import_json "/tmp/" ~ instance_id ~ "/conf.json" as config_properties %}
The above statement should resolve your error.
Also, I would suggest using a variable to evaluate the value of the string expression above, like
{% set conf_json_path = "/tmp/" ~ instance_id ~ "/conf.json" %}
and use it like this
{% import_json conf_json_path as config_properties %}
Hope this help!
In case, you wish to use grains dictionary directly, you can use the value like so
{% set conf_json_path = "/tmp/" ~ grains['INSTANCE_ID'] ~ "/conf.json" %}