Skip to main content

Using Jinja on StackSpot

In this section, you will find information about using the Jinja language in StackSpot.

Jinja

The StackSpot engine uses the Jinja language to allow Plugins to generate files based on user input parameters when creating an app or applying a Plugin.

In addition to file generation, Jinja expressions can be used in Plugin configuration files, allowing great flexibility in defining settings based on user input.

tip

For security reasons, Jinja runs in Sandbox mode, separated from your system. Depending on how you use expressions, some of them may not behave as expected.
For more information about restrictions, see the official Jinja documentation about Sandbox.

Plugins rendering cycle

Before using Jinja, it is essential to understand how the Plugin rendering cycle works in StackSpot. Whenever a Plugin is applied to a project, either by the stk create app or stk apply plugin commands, the following steps occur:

  1. The configuration file (plugin.yaml) is loaded.
  2. Hooks with trigger before-input are executed.
  3. The inputs defined in the configuration are requested from the user.
  4. The computed-inputs (computed values) are calculated.
  5. Hooks with trigger before-render are executed.
  6. Jinja templates contained in the Plugin's templates folder are rendered.
  7. Hooks with trigger after-render are executed.
  8. Files are merged, if they exist in the target.

The input parameters entered by the user in step 3 are used as variables. They can be used in Jinja expressions both in the templates used for file generation and at specific points in the Plugin configuration.

Jinja basic concepts

The Jinja language, like most template languages, allows you to define, through specific tags in a text, expressions that are interpolated by the engine to render the final result.

Jinja markup in text can be:

  • {{ ... }}: for expressions that will be rendered.
  • {% ... %}: for control structures (conditionals, loops).
  • {# ... #}: for comments (not rendered in the final output).

Check out a simple example of a Jinja template:

<!DOCTYPE html>
<html lang="en">
<head>
<title>My Webpage</title>
</head>
<body>
{# print a Hello message for each name in names list #}
{% for name in names %}
<h1>Hello {{ name }}!</h1>
{% endfor %}
</body>
</html>

When the variable names has the value ['John', 'Mary'], the template rendering returns:

<h1>Hello John!</h1>
<h1>Hello Mary!</h1>

In short, when evaluated by StackSpot's template engine, Jinja expressions and control structures are evaluated to produce the final result, either when generating files or when configuring commands and Hooks in the Plugin configuration files.

Jinja expressions

Expressions must be enclosed within {{ ... }}. This indicates something that should be rendered by Jinja. Expressions can contain literal values, variables, calculations and complex expressions.

To learn more about all the Jinja expressions, see the official Jinja documentation about expressions.

Below is a summary of Jinja's most relevant points for a content creator at StackSpot.

Variables

The most common use of Jinja expressions in StackSpot is to use the value of a variable in the rendered files. The variables present in the Jinja context come from the inputs in the Plugin configuration files and are requested from the user when the Plugin is applied to a project.

For example, imagine that a Plugin has the following input defined in its YAML file:

inputs:
- label: Enter your name
type: text
name: name

The expression to render the name entered by the user in the input will be:

{{ name }}

When applying the Plugin, in the applied project, the input asks the user for their name. Generated files that contain the expression {{ name }} will have the name typed by the user rendered instead of the expression.

Warning

If the Jinja variable has a hyphen, for example my-var, it can be an input variable and must be accessed as {{ inputs["my-var"] }}.
If it does not have a hyphen, you can use {{ myvar }} or {{ inputs.myvar }}.

Filters

Variables can be transformed using filters. The syntax for applying a filter to a variable is:

{{ variable | filter }}

It is possible to apply more than one filter by chaining several pipes (|), as in the example below:

{{ variable | filter1 | filter2 }}

In the above example, the variable value is modified by filter1 and then modified by filter2.

Example of the default capitalize filter:

{{ name | capitalize }}

This filter converts the entered name so that the first letter is uppercase and the others are lowercase.

For more information on Jinja's default filters, see the official Jinja documentation about filters.

In addition to Jinja's standard filters, StackSpot provides some useful filters for Stack creators:

  • pascalcase: converts strings to PascalCase format.
  • camelcase: converts strings to camelCase format.
  • kebabcase: converts strings to kebab-case format.
  • cobolcase: converts strings to COBOL-CASE format.
  • snakecase: converts strings to snake_case format.
  • macrocase: converts strings to MACRO_CASE format.
  • group_id_folder: converts strings by replacing . with /. This filter is useful for building folder names from Java package names.
  • regex_replace: returns a copy of the value with all occurrences of a substring replaced with a new one.
  • from_json: converts a JSON string into a Python dictionary.

You can provide an optional third argument count to regex_replace. Only the first count occurrences will be replaced:

# {{ "string value" | regex_replace("substring pattern", "replacement string") }}
{{ "Hello World" | regex_replace("Hello", "Goodbye") }}
-> Goodbye World

# {{ "string value" | regex_replace("substring pattern", "replacement string", count) }}
{{ "aaathree" | regex_replace("a", "two", 2) }}
-> two, two, athree

You can use variables that contain the value to be replaced.

Consider that the variable hello_world has the value "dogs, cats, birds":

# {{ variable | regex_replace('substring pattern', 'replacement string') }}
{{ hello_world | regex_replace('birds', 'humans') }}
-> dogs, cats, humans

The variable hello_world now has the value "dogs, cats, humans".

It is possible to use regular expression groups in the replacement string:

Consider that the variable hello_world has the value "burger":

# {{ string value or variable | regex_replace(define group pattern, replacement string group) }}
{{ hello_world | regex_replace('((\w+\s*)+)', 'cheese\1y') }}
-> cheeseburger

The variable hello_world now has the value "cheeseburger".

Example of the from_json filter:

{
"input1": "value1"
}
{% set my_dict = json_input | from_json %}
{{ my_dict.input1 }}

Control structures

warning

The StackSpot portal does not support using Jinja control structures in the web interface. Therefore, it is not guaranteed that all Jinja templates will be successfully executed in the browser.

The control structures present in Jinja are if/elif/else/endif and for/else/endfor.

Use the if/elif/else/endif structure

Commands of the if/elif/else/endif structure allow you to define blocks that will be rendered according to a condition. The example below illustrates the use of an if block to control the printing of part of a template:

{% if age >= 18 %}
Would you like a drink?
{% elif age >= 5 %}
Would you like a soft drink?
{% else %}
Would you like a juice?
{% endif %}

In the above example, logic is implemented to determine the message that will be printed according to the value of the age variable.

The comparison operators available in Jinja are:

  • ==: compares the two operands, returning true if they are equal.
  • !=: compares the two operands, returning true if they are different.
  • >: true if the operand on the left is larger than the operand on the right.
  • >=: true if the operand on the left is greater than or equal to the operand on the right.
  • <: true if the operand on the left is smaller than the operand on the right.
  • <=: true if the operand on the left is less than or equal to the operand on the right.

The following logical operators can be used in expressions:

  • and: returns true if both left and right expressions are true.
  • or: returns true if one of the expressions is true.
  • not: negates the result of an expression.
  • (expr): groups logical expressions.

The elif and else blocks are optional, and you can use more than one elif block if needed.

Use the for/else/endfor structure

The for/else/endfor commands allow you to define a repeating block for each element in a list, as in the example below:

{% for name in names %}
Hello {{ name }}!
{% else %}
No names given!
{% endfor %}

In the example above, for each name in the names list, a line with a greeting for the entered name will be rendered.

If the list of names is empty, the content of the block after the else will be rendered.

The else block is optional and can be omitted if not needed.

Using Jinja on StackSpot

Generation of files in Plugins

Jinja templates are text files that can contain expressions and variables interpolated by the engine to generate the final result. When creating a Plugin, a templates folder is created, which contains the Jinja templates used to generate files when the Plugin is used.

When a user applies a Plugin or creates a new app, the Plugin rendering cycle is executed. The Jinja templates present in each of the applied Plugins are interpolated using the inputs entered by the user as variables that can be used in Jinja expressions in those files.

Jinja expressions can also be used to define dynamic file and folder names based on user inputs.

To illustrate the use of Jinja in file generation, consider a Plugin with the following structure:

example-plugin/
├── plugin.yaml
└── templates
├── file.txt
├── example_folder
│ └── nested_file.txt
└── {{folder_name}}
└── {{file_name}}.txt

The Plugin configuration in the plugin.yaml file contains the definition of the inputs that will be requested from the user:

schema-version: v4
kind: plugin
metadata:
name: example-plugin
display-name: example-plugin
description: Example plugin
version: 0.0.1
spec:
type: app
compatibility:
- python
docs:
pt-br: docs/pt-br/docs.md
en-us: docs/en-us/docs.md
technologies:
- Api
inputs:
- label: Folder name
type: text
name: folder_name
- label: File name
type: text
name: file_name
- label: Content
type: text
name: content

In the example above, three inputs are requested: folder_name, file_name and content, all strings.

Notice that there is a folder called {{folder_name}} and a file called {{file_name}}.txt, which show how to use Jinja expressions to define folder and file names dynamically based on user inputs.

The contents of the {{file_name}}.txt file will be:

Content: {{content}}

The file.txt and nested_file.txt files are empty and only illustrate that the entire file structure contained in the templates folder is generated after the Plugin is applied and can have as many files and folders as required.

When applying the Plugin to an empty app folder, the STK CLI will ask for inputs as in the example below:

$ cd app
$ stk apply plugin -p ../example-plugin
? The current folder doesn't seem to be the part of a StackSpot project. Do you still want to proceed applying the plugin? Yes
? Folder name my-folder
? File name my-file
? Content my-content
- Plugin example-plugin applied.

And the file structure generated will be:

app
├── example_folder
│ └── nested_file.txt
├── example.txt
├── file.txt
├── my-folder
│ └── my-file.txt
└── stk.yaml

Note that the folder name and files containing Jinja expressions were interpolated with the values entered by the user in the inputs.

The contents of the my-file.txt file after rendering will be:

Content: my-content

Note that the contents of the file have been interpolated by the Jinja engine by replacing the expression {{content}} with the contents typed in by the user, my-content.

Jinja expressions in configuration files

In addition to file generation, Jinja expressions can be used in the configuration YAML of Plugins.

Jinja expressions on computed inputs and global computed inputs

Jinja expressions can be used to calculate the computed-inputs and global-computed-inputs of Plugins, as in the example below:

schema-version: v4
kind: plugin
metadata:
name: example-plugin
display-name: example-plugin
description: Example plugin
version: 0.0.1
spec:
type: app
compatibility:
- python
docs:
pt-br: docs/pt-br/docs.md
en-us: docs/en-us/docs.md
technologies:
- Api
inputs:
- label: Name
type: text
name: name

computed-inputs:
name_uppercase: "{{ name | upper }}"

global-computed-inputs:
name_lowercase: "{{ name | lower }}"

In the example above, the variables name_uppercase and name_lowercase are created using Jinja expressions that take the input name and apply filters to convert the typed name to uppercase and lowercase.

Jinja Expressions in Declarative Hooks

Jinja expressions can be used in some Declarative Hooks configurations and in the file snippets used by Hooks. The following are the attributes that accept Jinja expressions for each Hook type.

  • Declarative Hook of type run: Jinja expressions can be used in both command and workdir:
hooks:
- type: run
trigger: before-render
workdir: {{ folder_name }}/{{ nested_folder_name }}
command: echo "Hello {{ name }}!"
  • Declarative Hook of type edit: Jinja expressions can be used on the path, changes.search.string, changes.search.pattern attributes, at all points where value is used, and on the not-exists within a when:
- type: edit
trigger: after-render
path: src/{{ some_input }}.txt
changes:
- insert:
line: 0
value: "{{ another_input }}"
when:
not-exists: "{{ another_input }}"
- search:
string: "{{ example }}"
insert-before:
value: "using {{ value }}"
- search:
pattern: {{ pattern_to_search }}
replace-by:
value: bla
  • Declarative hook of type edit-xml: Jinja expressions can be used in the path and any value:
- type: edit-xml
trigger: after-render
path: {{ some_variable }}.xml
changes:
- xpath: .//dependency
append:
value: |
<a>{{ some_variable }}</a>
  • Declarative Hook of type edit-json: Jinja expressions can be used on path and any value:
- type: edit-json
trigger: after-render
path: using-template.json
encoding: ascii
changes:
- jsonpath: "$.scripts"
update:
value: |
{
"{{ json_tag }}": "{{ json_value }}"
}
  • Declarative Hook of type edit-yaml: Jinja expressions can be used on path and any value:
- type: edit-yaml
trigger: after-render
path: using-template.yaml
encoding: ascii
changes:
- yamlpath: "$.scripts"
update:
value: |
{{ json_tag }}: {{ json_body }}

Referencing Connection Interface in Terraform

V1
requires:
connection-interface:
- connection-interface-one

In Terraform, attributes are referenced with the Connection Interface, according to the model:

{{ connection_interface_one_CONNECTOR }}
{{ connection_interface_one_CONNECTOR_engine }}
{{ connection_interface_one_CONNECTOR_is_stackspot_fun }}

In V1, this model continues to work, but it is not recommended to use it in V2.

V2

Now it is possible to access the attributes of the Connection Interfaces using the connections object. You can also include an alias in the syntax. There are two ways to access the attributes:

  1. Kebab case: access using [...] and a string, similar to a dictionary:

    {{ connections["connection-interface-one"].engine }}
  2. Snake case: access the attributes by converting the Kebab to snake_case:

    {{ connections.connection_interface_one.is_stackspot_fun }}

An extra option is to access by camelCase, if the alias uses this convention:

{{ connections["connectionInterfaceOne"].engine }}

Next step

Check the section about Metadata usage in Plugins and learn where and how to use metadata when creating your Plugins.