How to get minion id from jinja script inside a salt state - python

I need to apply a if-else logic based on a static minion id inside a state file. The target glob qualifies a whole bunch of servers, but I need to run a small piece of logic on a single server and run a bunch of common things on all of them. How can I do this?
When I put this in a Jinja file, its errors:
{% import salt.config %}
{% minion_opts = salt.config.minion_config('/etc/salt/minion') %}
{% print(minion_opts['id']) %}
{% if minion_opts['id'] == 'xyz.server': %}
-- run the logic here
..
..
Error:
- Rendering SLS 'base:bin.test' failed: Jinja syntax error: expected token 'as', got 'end of statement block'; line 1
-
- ---
- {% import salt.config %} <======================
- {% minion_opts = salt.config.minion_config('/etc/salt/minion') %}
- {% print(minion_opts['id']) %}
It probably goes without saying I am not a Saltstack expert by any means.

So first off, I realize this is an old question, but it still shows up at the top of the my search results for both DDG and Google for a somewhat related problem and the current answers don't really deliver on the ask, so here's an answer based off the latest SaltStack version (3002):
In order to get control flow working for Jinja based off minion id:
{% if salt['grains.get']('id') == 'minion_a' %}
some text to render for minion_a
{% else %}
some text to render otherwise
{% endif %}
I realize there were other issues in how the question was phrased and for the sample code provided, but my code snippet answers the actual question of:
I need to apply a if-else logic based on a static minion id inside a state file.
How can I do this?

The error occurs because you're using Jinja's import in an unexpected way. If this is a Flask app, you should use something like this in your views.py route to provide variables to the template:
render_template('my_template.html', salt=salt)
The corrected code should look something like this:
{% set minion_opts = salt.config.minion_config('/etc/salt/minion') %}
{{ minion_opts['id'] }}
{% if minion_opts['id'] == 'xyz.server' %}
{{ 'logic goes here' }}
{% endif %}
See the Assignments docs on how to assign values to variables.
No colon is necessary at the end of the if statement, and remember to use {% endif %} after you're done with your conditional statements.

Related

Do statement not working in jinja

I'm altering an existing web interface to view ROBOT doc libraries, which uses a mixture of jinja (Python inside HTML) and HTML. I have never worked with jinja or HTML before and am having issues getting even a simple test case to work. When the browser loads the docs, I want our project's directory structure for the docs to be preserved to make finding things easier, and so I want to use jinja to create the dir structure. Here is a snippet of the code I'm working with:
{% extends "base.html" %}
{% block body %}
<div class="well" id="left">
<ul class="list-group list-unstyled">
{% set collection_list = [] %}
{% for collection in data.hierarchy %}
{% if collection.collection_id|string == data.collection_id|string %}
{% do collection_list.append(collection.path) %}
{% else %}
{% for link in collection.path_chain %}
<li>
<label class="tree-toggler nav-header"
title="file path: {{collection.path}}">{{link}}</label>
<ul class="list-group tree collapse"
id={{link}}>
</ul>
{% endfor %}
</li>
{% endif %}
...there's more after that, but this is where I hit the error. It sets the collection_list var fine, and the if statements work, but when it goes to execute the 'do' statement it fails with:
TemplateSyntaxError: Encountered unknown tag 'do'. Jinja was looking for the following tags: 'elif' or 'else' or 'endif'. The innermost block that needs to be closed is 'if'.
I don't believe this is an unclosed loop or something because if I replace the do statement with a simple test print statement, it works. Does anyone know what I'm doing wrong?
From the template documentation:
Expression Statement
If the expression-statement extension is loaded, a tag called do is available that works exactly like the regular variable expression ({{ ... }}); except it doesn’t print anything. This can be used to modify lists:
{% do navigation.append('a string') %}
You need to enable the Expression statement extension for this to work.
You didn't show how you load the Jinja2 environment, but loading extensions takes place via the extensions argument to the Environment() class:
jinja_env = Environment(extensions=['jinja2.ext.do'])

Catching TemplateDoesNotExist in Django

I am trying to use the templatetag described in SO answer: https://stackoverflow.com/a/6217194/493211 in a project using Django 1.4.3 (with Python 2.7.2).
I adapted it like this:
from django import template
register = template.Library()
#register.filter
def template_exists(template_name):
try:
template.loader.get_template(template_name)
return True
except template.TemplateDoesNotExist:
return False
So that I could use it like this in another template:
{% if 'profile/header.html'|template_exists %}
{% include 'profile/header.html' %}
{% else %}
{% include 'common/header.html' %}
{% endif %}
This way, I could have avoided using solutions such as changing the order of my apps in INSTALLED_APPS.
However, it does not work. If the template does not exist, then the exception is raised within the stack/console but it is not propagated up to get_template(..) (from inside this statement), and thus not to my foolish API. Hence, this blows up in my face during the rendering. I uploaded the stacktrace to pastebin
Is this a wanted behavior from Django?
I ended up stop doing foolish things as is. But my question would remain.
What about a custom tag? This doesn't provide the full functionality of include but seems to meet the needs in the question.:
#register.simple_tag(takes_context=True)
def include_fallback(context, *template_choices):
t = django.template.loader.select_template(template_choices)
return t.render(context)
Then in your template:
{% include_fallback "profile/header.html" "common/header.html" %}
I found some kind of an answer to my question so I am posting it here for future refence.
If I use my template_exists filter like this
{% if 'profile/header.html'|template_exists %}
{% include 'profile/header.html' %}
{% else %}
{% include 'common/header.html' %}
{% endif %}
and if profile/header.html does not exist, then the TemplateDoesNotExist gets strangely propagated at page load and I get a server error. However, if instead, I use this in my template:
{% with 'profile/header.html' as var_templ %}
{% if var_templ|template_exists %}
{% include var_templ %}
{% else %}
{% include 'common/header.html' %}
{% endif %}
{% endwith %}
Then, it works like a charm!
Obviously, I could have used
django.template.loader.select_template(['profile/header.html','common/header.html'])
in the view (from this SO answer). But I am using a CBV which I wanted to keep rather generic and this was called from the main template. And also I thought it would be nice to have my site working if this apps goes down for whatever reason. If this seems silly to you, please leave a comment (or yet a better answer).

django philosophy: when to include templates and when to have code generate html?

When using Django templates, should I have some templates that act like "subroutines", so to speak, or should I generate HTML from within my code in these cases?
For example, I have a template with several lists of names, each of which I want to turn into a select. Should I have a template that renders the name_list variable into a select, and do something like this:
#in the view:
return {'name_list_1': name_list_1,
'name_list_2': name_list_2,
'name_list_3': name_list_3}
#in the template:
{% with name_list_1 as name_list %}
{% include "sub_name_list_select.html" %}
{% endwith %}
{% with name_list_2 as name_list %}
{% include "sub_name_list_select.html" %}
{% endwith %}
{% with name_list_3 as name_list %}
{% include "sub_name_list_select.html" %}
{% endwith %}
Or should I have a function in my code, name_list_to_select_html, which does the same job, and do this:
return {'name_list_1_html': name_list_to_select_html(name_list_1),
'name_list_2_html': name_list_to_select_html(name_list_2),
'name_list_3_html': name_list_to_select_html(name_list_3)}
#in the template:
{{ name_list_1_html|safe }}
{{ name_list_2_html|safe }}
{{ name_list_3_html|safe }}
Or are both of these wrong and I am getting the philosophy totally wrong?
Additional question: in terms of speed, is it slow to constantly include templates? Is that a bonus point for the in-code html generation?
Generally, HTML should only be generated in the templating system or directly related code. That keeps the view of the data completely separate from the business and functional logic. I feel that's a proper separation of concerns. Go with your first solution.
As for performance, Django should probably take around the same amount of time running either code. But it has built-in view and template fragment caching if you know those segments of code don't need to be regenerated on every request.

Could not parse the remainder in template

This is my DjangoTemplates/(mysite)/index.html. I'm trying to do some math in my index.html. I want to display the sum of the entire Stakes.amount_won for each user_name in the for loop. I think I have the syntax correct for the math, but it does not seem to work in the template.
The for loop should go through each user_name, and display their user_name as a link with the Amount Won: (total of amount_won for that user_name) below.
from django.db.models import Sum
<h1> Players </h1>
{% if latest_player_list %}
<ul>
{% for player in latest_player_list %}
<li>{{ player.user_name }} <br>Total Won: {{Stakes.objects.filter(player__user_name).aggregate(Sum('amount_won'))}}
</li>
{% endfor %}
</ul>
<br>
{% else %}
<p>No players are available.</p>
{% endif %}
<h3>New Player</h3>
Sorry if this is noobish. Thank you in advance for the help!
Although the import statement is not part of the template language, this isn't actually your problem - it will just show as text. The actual problem is your function call: Django doesn't allow calls with parentheses in templates. Only functions with no parameters are allowed. You need to do the lookup in the view and pass it in the context.
Of course, this is all clearly explained in the documentation.
The quick answer here is that you're trying to do too much in the template - for example, you can't use a Python import statement in a template at all, so I imagine that's just rendering as text. What you'd generally want here is to do the query logic in your view class, then pass the data to the template for rendering.

Jinja's loop variable is not available in include-d templates

I have code similar to the following in one of my jinja template
{% for post in posts %}
{% include ["posts/" + post.type + ".html", "posts/default.html"] %}
{% endfor %}
which is supposed to render each post inside the posts collection, depending on the .type of the post. I have a different template setup for each post.type. And for those I don't have a template, it reverts to the default post template.
Now, I want the index of the post being displayed from bottom, inside the post templates, which is provided by loop.revindex. But for some reason, if I use loop.revindex inside the post template, I get a error saying UndefinedError: 'loop' is undefined.
So, is loop not available in the included templates? Is this by design? Am I doing something wrong with how I organised my templates for this to be not available?
Edit Okay, I came up with a workaround, in the for loop, before I include my template, I do
{% set post_index = loop.revindex %}
and use post_index inside the post template. Not ideal, but seems like the only way. I still want to know your solutions though.
Edit 2 One other thing, I am able to access the post variable inside the included template, but not the loop variable.
If might be possible with the {% with %} statement.
Try this:
{% with %}
{% set loop_revindex = loop.revindex %}
{% include ... %}
{% endwith %}
Instead of using loop.revindex in the included template, use loop_revindex.
Another option is to pass the entire loop variable into the included template by setting a local variable to loop
{% for post in posts %}
{% set post_loop = loop %}
{% include ["posts/" + post.type + ".html", "posts/default.html"] %}
{% endfor %}
This gives you access to all of the loops properties, and, to me, makes it more clear in the included template what the variable is.

Categories

Resources