20

I have a macro that is used to build a local repository using debmirror.

Here's the snippet of code:

{%- set gnupghome = kwargs.pop('gnupghome', '/root/.gnupg') %}
{%- set env = { 'GNUPGHOME': gnupghome } %}
keyring_import:
  cmd:
    - run
{%- if 'keyid' in kwargs and 'keyserver' in kwargs %}
    {%- set keyid = kwargs.pop('keyid') %}
    {%- set keyserver = kwargs.pop('keyserver') %}
    - name: 'gpg --no-default-keyring --keyring {{ gnupghome }}/trustedkeys.gpg --keyserver {{ keyserver }} --recv-keys {{ keyid }}'
{%- elif 'key_url' in kwargs %}
    {%- set key_url = kwargs.pop('key_url') %}
    - name: 'wget -q -O- "{{ key_url }}" | gpg --no-default-keyring --keyring {{ gnupghome }}/trustedkeys.gpg --import'
{%- endif %}
    - require:
      - pkg: wget
      - pkg: gnupg

At the endif keyword, I would like to use else to raise an exception, for e.g:

Either key_url or both keyserver and keyid required.

Is it possible?

quanta
  • 3,960
  • 4
  • 40
  • 75
  • 5
    No, Jinja2 doesn't support raising exceptions. You can use python functions or filters, which can raise exceptions, but not in pure Jinja. – Martijn Pieters Feb 14 '14 at 11:34

7 Answers7

22

Dean Serenevy's answer is elegant. Here is a shorter solution, which adds a global to jinja's environment.

def raise_helper(msg):
    raise Exception(msg)

env = jinja2.Environment(...
env.globals['raise'] = raise_helper

Then in your template:

{{ raise("uh oh...") }}
Lee
  • 2,610
  • 5
  • 29
  • 36
18

Super quick workaround, as long as you don't mind raising a ZeroDivisionError:

Insert an {{ 0/0 }} wherever you want to raise an exception.

hyperknot
  • 13,454
  • 24
  • 98
  • 153
14

This can be handled in an extension. From https://github.com/duelafn/python-jinja2-apci

# FROM: https://github.com/duelafn/python-jinja2-apci/blob/master/jinja2_apci/error.py
from jinja2 import nodes
from jinja2.ext import Extension
from jinja2.exceptions import TemplateRuntimeError

class RaiseExtension(Extension):
    # This is our keyword(s):
    tags = set(['raise'])

    # See also: jinja2.parser.parse_include()
    def parse(self, parser):
        # the first token is the token that started the tag. In our case we
        # only listen to "raise" so this will be a name token with
        # "raise" as value. We get the line number so that we can give
        # that line number to the nodes we insert.
        lineno = next(parser.stream).lineno

        # Extract the message from the template
        message_node = parser.parse_expression()

        return nodes.CallBlock(
            self.call_method('_raise', [message_node], lineno=lineno),
            [], [], [], lineno=lineno
        )

    def _raise(self, msg, caller):
        raise TemplateRuntimeError(msg)

Pass the extension to your Environment: jinja2.Environment(... extensions=[RaiseExtension]) then use it in your template:

{%- if 'keyid' in kwargs and 'keyserver' in kwargs %}
    ...
{%- else %}
    {% raise "Either key_url or both keyserver and keyid required." %}
{% endif %}
Dean Serenevy
  • 1,284
  • 12
  • 13
10

Insert a {{ "My error explained here"/0 }} expression. e.g.

{% if not required_parameter %}
{{ "required_parameter must be defined."/0 }}
{% endif %}

(builds on zsero's answer of 0/0)

Vincent Scheib
  • 17,142
  • 9
  • 61
  • 77
  • 5
    this doesn't really provide a useful error -- for me it generates the error text: """ unsupported operand type(s) for /: 'str' and 'int' """ – Marc Tamsky Oct 08 '18 at 00:02
6

If this is being done from Ansible, Ansible adds the mandatory filter to Jinja, which can be used to do this:

{{ ('OK text' if condition_ok) | mandatory('Text of error message') }}

gives the failure:

fatal: [hostname]: FAILED! => {"msg": "Text of error message"}

(Replace condition_ok with the check that you need to make; 'OK text' can be just ''.)

Jiří Baum
  • 6,697
  • 2
  • 17
  • 17
4

Another quick hack that prints a given value:

{% include 'error: ' ~ value_to_print %}

Result:

jinja2.exceptions.TemplateNotFound: error: abc
Kirill Bulygin
  • 3,658
  • 1
  • 17
  • 23
3

I also wanted to throw an exception with an error message from within a jinja2 template, but without any external dependencies (globals / extensions). I ended up with this macro:

{% macro abort(error) %}
    {{ None['[ERROR] ' ~ error][0] }}
{% endmacro %}

To use:

{{ abort("Either key_url or both keyserver and keyid required.") }}  

Which gives a stack trace with this exception:

jinja2.exceptions.UndefinedError: 'None' has no attribute '[ERROR] Either key_url or both keyserver and keyid required.'
Charlie
  • 31
  • 2