jekyll/liquid: given key access value from hash in template - jekyll

given the following code in a jekyll template
{% assign resource = site.data.resources | where: "id", page.resource %}
that derives the following hash:
{
"id"=>"resource-1234",
"title"=>"testing",
"description"=>"Quis autem vel eum iure reprehenderit qui"
}
How do I user liquid to output the value of the title key? I have tried the following:
{{ resource }} # outputs the hash
{{ resource.title }} # nil
{{ resource["title"] }} # nil

In fact, the where filter returns an array.
When you print this array with {{ resource }}, it simply output all items one after an other. Here it print your hash, And this makes you think that resource is a hash.
For debugging you can use {{ resource | inspect }} that will return :
[{"id"=>"resource-1234", "title"=>"testing", "description"=>"Quis ..."}]
And here you see the brackets, and you know that resource is an array.
In your case, you know that only one (or zero) resource is linked to your page. In order to get only the first resource hash, you can do :
{% assign resource = site.data.resources | where: "id", page.resource | first %}
Now {{ resource.title }} returns testing.
Cool isn't it ?

Related

Passing column divided by value as parameter in macro dbt jinja

I'd like to pass a column divided by a value as a parameter in a jinja macro.
I'm using the macro in a dbt model like this {{ pmt('rate'/1200, 'nper', 'pv', 'fv') }}
However, this gives the error message
"Encountered an error: unsupported operand type(s) for /: 'str' and 'int'"
Most likely you have to treat the whole argument as a string literal (quote the whole thing):
{{ pmt('rate/1200', 'nper', 'pv', 'fv') }}
The reason this works is because it is likely that the macro templates this string into SQL code, e.g.,
{% macro pmt(arg1, arg2, arg3, arg4) %}
...
select {{ arg1 }}
...
{% endmacro %}
In this toy example, {{ arg1 }} will take on the value {{ 'rate/1200' }}, which enters the template (unquoted) as
...
select rate/1200
...
which is valid sql (if you have a field called rate).
It's possible this won't work with all macros, though! In dbt, since the macros are typically templating SQL code, you usually want to pass in arguments that contain field or table references as string literals. However, the argument to the macro could stay inside the jinja context, in which case, you'll need to keep the argument unquoted, or modify a variable before it's passed into the jinja macro. As another toy example:
{% macro print_value(val) %}
{{ log(val, info=True) }}
{% endmacro %}
-- if val is a string literal:
{{ print_value('rate/1200') }}
-- $ rate/1200
-- if val is unquoted:
{% set rate = 2400 %}
{{ print_value(rate/1200) }}
-- $ 2

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

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

Check if variable type is hash or array in liquid

I am attempting to write a somewhat generic layout that can take as a parameter either an array of strings or a hash of options, so you can either do:
option:
- "<li><b>One:</b> This is</li>"
- "<li><b>Two:</b> Raw HTML</li>"
Or you can do:
option:
One: This is
Two: a mapping
The reason I want to support both of these is that this is a public layout and the first option is already supported, but I would prefer to use the second option, so I want something of a deprecation period where both versions are supported.
I saw in check if a variable is type of string or array in liquid that there's a way to determine if something is an array or a string, but both arrays and hashes have a first attribute! A practical way to re-use this function might be to check if the first element of the variable also has a first attribute, like so:
{% if site.option.first %}
{% if site.option.first.first %}
hash
{% else %}
array
{% endif %}
{% else %}
Something else!
{% endif %}
But this seems a bit unwieldy and a bit of a hack - plus it will give the wrong answer if passed an array of arrays (even though "array of arrays" is not considered a valid input in this case). Is there a better way to do this?
For arrays you know will not include numbers, you can use the following:
---
arr:
- ""
- "2"
- three
- null
hash:
foo: bar
baz: null
"0": 1
string: "a string"
---
nil: {{ page.nil_prop | map: "" | join: "," | size }} # 0
str: {{ page.string | map: "" | join: "," | size }} # 0
hash: {{ page.hash | map: "" | join: "," | size }} # 0
arr: {{ page.arr | map: "" | join: "," | size }} # 3
However, if a number sneaks into your array, you get Liquid Exception: no implicit conversion of String into Integer.
This is on Jekyll 3.8.