SaltStack check if grain exists in Jinja file - jinja2

I'm using SaltStack to manage my infra. Machines are hosted in different DCs, so they also have slightly different network setup.
Currently, I'm running into the following issue:
Comment: Unable to manage file: Jinja variable 'dict object' has no attribute 'macaddress'; line 9
---
[...]
ethernets:
{{ grains['interface_context'] }}:
dhcp4: {{ grains['dhcp4'] }}
dhcp6: {{ grains['dhcp6'] }}
addresses: [{{ grains['ipv4'] }}, "{{ grains['ipv6'] }}"]
{% if grains['macaddress'] %} <======================
match:
macaddress: {{ grains['macaddress'] }}
{% endif %}
routes:
- to: default
[...]
---
As the message indicates, the grain "macaddress" is missing, which I can confirm, it's not set for this minion. But What I do not understand is how I can simply check if this variable/grain exists at all within a jinja template?
I wouldn't expect this error to come up, as I actually wanted to catch it with the if statement.
Can somebody help?

Use get to return None instead of raising:
{% if grains.get('macaddress') is not none %}
Or if you want to treat "empty" values the same:
{% if not grains.get('macaddress') %}

Related

how can i use jinja template and pillar in saltstack states

there is my sls file:
{% set java_script_path = salt['pillar.get']('script_path', default='/opt/java-app') %}
{% if salt['pillar.get']('script_path') %}
{% set file = {{ java_script_path }}/startup.sh %} ## seem this line have Jinja syntax error
{% if salt['file.file_exists']('{{ file }}') %}
cmd.run:
- name: mv {{ java_script_path }}/startup.sh {{ java_script_path }}/startup.sh.backup-$(date +"%Y-%m-%d-%H-%M-%S")
{% endif %}
{% endif %}
is using salt['pillar.get']('script_path') can not split other string?
example: name: {{ salt['pillar.get']('script_path') }}/startup.sh will raise error like: failed: Jinja syntax error: expected token ':', got '}' how can i fix ?
can you help me to fix my sls file to work?
{% already starts a Jinja context. You do not need to try to start another one with {{.
{% set file = java_script_path ~ "/startup.sh" %}
It was expecting a : because { starts a dict literal.
In simple use case such as shown in your question, you might not even need to set another variable at all. We could directly use {{ java_script_path }}/startup.sh in the if condition. Like below:
{% if salt['file.file_exists']("{{ java_script_path }}/startup.sh") %}
cmd.run:
- name: mv {{ java_script_path }}/startup.sh {{ java_script_path }}/startup.sh.backup-$(date +"%Y-%m-%d-%H-%M-%S")
{% endif %}
You could also reconsider using command to backup the file, and use an appropriate Saltstack module instead.

Passing list of Relation object to dbt_utils.union_relation macro fails

Related to dbt and jinja2
I am using union_relations from dbt_utils package (0.5.0).
I created my macro which takes list of fully qualified name (like database.schema.identifier) splits it and uses api.Relations.create (link) to create a relation and append each relation to a list.
{{ list_of_relation }} is given to dbt_utils.union_relations(as relations=my_macro([list of fully qualified names])), it's giving me an _is_relation error, I did use log to debug and see if it actually creates a relation and it does. What could be wrong?
It sounds like you have a macro written something like this:
{% macro my_macro(names) %}
{% set list_of_relations = [] %}
{% for name in names %}
{% set relation = something(name) %}
{% do list_of_relations.append(relation) %}
{% endfor %}
{{ list_of_relations }}
{% endmacro %}
Instead of using {{ list_of_relation }}, you’ll want {{ return(list_of_relation) }} or {% do return(list_of_relation) %}. The problem is that {{ ... }} turns things into strings in jinja macros, and macros by default return strings.
The documentation on return is here.

Why does this jekyll template not work?

Short Version:
Why does the following code not produce an output when navbox.next_article is the string '2018-01-05-man-command'?!
{% capture np %} {{ site.posts | where:"post","navbox.next_article contains post.title" }} {% endcapture %}
The next post is {{ np.title }}
Details
My post 2018-01-05-man-command.md has a YAML front matter:
---
layout : post
title : 'Man Command'
tags : [RHCSA, RHCSA_mod, Using Essential Tools, Man Command]
categories: [RHCSA]
navbox:
# prev_article:
next_article: 2018-01-05-understanding-globbing-and-wildcards
---
This is accessed by the _includes/post.html file through:
{% unless include.excerpt %}
{{ post.content }}
{% include navbox.html navbox=page.navbox %}
{% endunless %}
This is used by the _layout/post.html which sets the layout for the post:
{% include post.html post=page link_title=false %}
My navbox.html contains:
{% assign navbox = include.navbox %}
{% capture np %} {{ site.posts | where:"post","navbox.next_article contains post.title" }} {% endcapture %}
The next post is {{ np.title }}
However, all I get when I run bundle exec jekyll serve is:
The next post is
Why does that line not work? I'm new to jekyll so it's possible I've made a blunder somewhere that's intuitive to most. Please tell me what I can fix.
I believe that the capture tag only captures strings, not posts. See here for more info.
I'm not convinced that a where filter supports the contains syntax you're using. See here for more info.
On top of that, where returns an array. You have to get the first item from that array.
You need to fix these issues. Use an assign instead of a capture to store a post. And change your where filter to not use the contains syntax, which isn't valid. (Unless it's been added since the issue I just linked.)
Here is how I've done it:
{% assign post = site.posts | where:"url", targetUrl | first %}

including grain data when querying pillar in saltstack managed file

I have a state using file.managed, which generates a config file via a jinja for loop from a key in pillar.
My pillar looks like this:
configuration:
server01:
key1: value1
key2: value2
server02:
key03: value03
key04: value04
and the managed file:
{% set kv = pillar['configuration']['server01'] %}
{% for key, value in kv.iteritems() %}
{{ key }}:{ value }};
{% endfor %}
The way I differentiate between different servers right now in my state file is
config:
file.managed:
- name: /etc/config.conf
- source: salt://files/{{ grains['id'] }}.conf.jinja
- template: jinja
but this is less than ideal, since I have to create an almost identical file for every server.
Is there a way to dynamically replace server01 with the ID of the actual server, something like
{% set kv = pillar['configuration']['{{ grains[id''] }}'] %}
The goal is to generally limit the necessary changes only to the corresponding pillar file, when adding a new server, so other suggestions are also welcome too.
i think you should use pillar info in your state file.
your state file like bellow :
{% if grains['id'] in pillar['configuration'] %}
{% set nodeinfo = pillar['configuration'][grains['id']] %}
config:
file.managed:
- name: /etc/config.conf
- source: salt://conf.jinja
- template: jinja
- defaults :
nodeinfo: {{nodeinfo}}
{% endif %}
then, conf.jinja:
{% for key, value in nodeinfo.iteritems() -%}
{{ key }}:{{ value }};
{% endfor -%}
i hope that will solve your problem, thanks.

Google Deployment manager: MANIFEST_EXPANSION_USER_ERROR multiline variables

Trying to use Google Deployment Manager with YAML and Jinja with a multi-line variables, such as:
startup_script_passed_as_variable: |
line 1
line 2
line 3
And later:
{% if 'startup_script_passed_as_variable' in properties %}
- key: startup-script
value: {{properties['startup_script_passed_as_variable'] }}
{% endif %}
Gives MANIFEST_EXPANSION_USER_ERROR:
ERROR: (gcloud.deployment-manager.deployments.create) Error in
Operation operation-1432566282260-52e8eed22aa20-e6892512-baf7134:
MANIFEST_EXPANSION_USER_ERROR
Manifest expansion encountered the following errors: while scanning a simple key in "" could not found expected ':' in ""
Tried (and failed):
{% if 'startup_script' in properties %}
- key: startup-script
value: {{ startup_script_passed_as_variable }}
{% endif %}
also
{% if 'startup_script' in properties %}
- key: startup-script
value: |
{{ startup_script_passed_as_variable }}
{% endif %}
and
{% if 'startup_script' in properties %}
- key: startup-script
value: |
{{ startup_script_passed_as_variable|indent(12) }}
{% endif %}
The problem is the combination of YAML and Jinja. Jinja escapes the variable but fails to indent it as YAML would require when being passed as a variable.
Related: https://github.com/saltstack/salt/issues/5480
Solution: Pass the multi-line variable as an array
startup_script_passed_as_variable:
- "line 1"
- "line 2"
- "line 3"
The quoting is important if your value starts with # (which startup script on GCE does, ie #!/bin/bash) since it will be treated as a comment otherwise.
{% if 'startup_script' in properties %}
- key: startup-script
value:
{% for line in properties['startup_script'] %}
{{line}}
{% endfor %}
{% endif %}
Putting it here since there aren't much Q&A material for Google Deployment manager.
There is no clean way to do this in Jinja. As you yourself have pointed out, because YAML is a whitespace-sensitive language, it is difficult to effectively template around.
One possible hack is to split the string property into a list and then iterate over the list.
For example, providing the property:
startup-script: |
#!/bin/bash
python -m SimpleHTTPServer 8080
you can use it in your Jinja template:
{% if 'startup_script' in properties %}
- key: startup-script
value: |
{% for line in properties['startup-script'].split('\n') %}
{{ line }}
{% endfor %}
Here is also a full working example of this.
This method will work, but generally cases like this are when people start considering using a python template. Because you are working with an object model in python, you do not have to deal with indentation problems. For example:
'metadata': {
'items': [{
'key': 'startup-script',
'value': context.properties['startup_script']
}]
}
An example of the python template can be found in the Metadata From File example.