Usar Jinja na StackSpot
Nesta página, você encontra informações sobre o uso da linguagem Jinja na StackSpot.
Jinja
A engine da StackSpot usa a linguagem Jinja para permitir que os Plugins gerem arquivos com base nos parâmetros de entrada informados pelos usuários ao criar um app ou aplicar um Plugin.
Além da geração de arquivos, as expressões Jinja podem ser usadas nos arquivos de configuração dos Plugins. Isso oferece grande flexibilidade para definir configurações baseadas nas entradas do usuário.
Por questões de segurança, o Jinja é executado em modo Sandbox, ou seja, separado do seu sistema. Dependendo do uso que você fizer das expressões, alguns comandos podem não se comportar como esperado.
Para mais detalhes sobre restrições, consulte a documentação oficial do Jinja sobre Sandbox.
Ciclo de renderização de Plugins
Antes de utilizar o Jinja, é importante entender como funciona o ciclo de renderização dos Plugins na StackSpot. Sempre que um Plugin é aplicado em um projeto, seja pelos comandos stk create app ou stk apply plugin, a seguinte sequência ocorre:
- O arquivo de configuração (
plugin.yaml) é carregado. - São executados os Hooks com trigger
before-input. - Os inputs (entradas) definidos na configuração são perguntados ao usuário.
- São calculados os
computed-inputs(entradas calculadas). - São executados os Hooks com trigger
before-render. - São renderizados os templates Jinja contidos na pasta
templatesdo Plugin. - São executados os Hooks com trigger
after-render. - É feito o merge de arquivos, caso existam no destino.
Os parâmetros de entrada informados pelo usuário no passo 3 são usados como variáveis. Elas podem ser usadas em expressões Jinja tanto nos templates usados para geração de arquivos quanto em pontos específicos da configuração dos Plugins.
Conceitos básicos do Jinja
A linguagem Jinja, assim como a maioria das linguagens de template, permite definir, por meio de marcações específicas em um texto, expressões que são avaliadas pela engine para gerar o resultado final.
As marcações Jinja em um texto podem ser:
{{ ... }}: expressões que serão renderizadas.{% ... %}: estruturas de controle (condicionais, laços).{# ... #}: comentários (não aparecem no resultado final).
Confira um exemplo simples de um template Jinja:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Minha Página</title>
</head>
<body>
{# imprime uma mensagem de saudação para cada nome na lista nomes #}
{% for nome in nomes %}
<h1>Olá {{ nome }}!</h1>
{% endfor %}
</body>
</html>
Quando a variável nomes recebe o valor ['João', 'Maria'], a renderização do template retorna:
<h1>Olá João!</h1>
<h1>Olá Maria!</h1>
Em resumo, ao serem avaliadas pela engine de templates da StackSpot, as expressões e estruturas de controle do Jinja produzem o resultado final, seja na geração de arquivos ou na configuração de comandos e Hooks presentes nos arquivos de configuração dos Plugins.
Expressões Jinja
As expressões devem ser colocadas entre {{ ... }}. Isso indica que há um valor que deve ser renderizado pelo Jinja. As expressões podem conter valores literais, variáveis, cálculos e expressões mais complexas.
Para saber mais sobre todas as expressões Jinja, consulte a documentação oficial do Jinja sobre expressões.
A seguir, você encontra um resumo dos pontos mais relevantes do Jinja para um criador de conteúdo na StackSpot.
Variáveis
O uso mais comum de expressões Jinja na StackSpot é utilizar o valor de uma variável nos arquivos renderizados. As variáveis presentes no contexto do Jinja são provenientes dos inputs dos arquivos de configuração dos Plugins e são perguntadas ao usuário quando o Plugin é aplicado em um projeto.
Por exemplo, imagine que um Plugin tenha o seguinte input definido em seu arquivo YAML:
inputs:
- label: Digite seu nome
type: text
name: nome
A expressão para renderizar o nome digitado pelo usuário no input será:
{{ nome }}
Ao aplicar o Plugin, o input vai perguntar para o usuário o seu nome. Os arquivos gerados que contiverem a expressão {{ nome }} terão o nome digitado pelo usuário renderizado no lugar da expressão.
Se você criar uma variável de input com hífen, por exemplo my-var, ela deve ser acessada como {{ inputs["my-var"] }}.
Caso não haja hífen no nome, a chamada é feita normalmente, como {{ myvar }} ou {{ inputs.myvar }}.
Filtros
As variáveis podem ser transformadas por meio de filtros. A sintaxe para aplicar um filtro em uma variável é:
{{ variavel | filtro }}
É possível aplicar mais de um filtro encadeando vários pipes (|):
{{ variavel | filtro1 | filtro2 }}
No exemplo acima, o valor da variável é modificado pelo filtro1 e depois pelo filtro2.
Exemplo do filtro padrão capitalize:
Esse filtro converte o nome para o formato em que a primeira letra é maiúscula e as outras são minúsculas:
{{ nome | capitalize }}
Para mais informações sobre os filtros padrão do Jinja, consulte a documentação oficial do Jinja sobre filtros.
Além dos filtros padrão do Jinja, a StackSpot disponibiliza alguns filtros úteis para criadores de Stacks:
pascalcase: converte strings para o formato PascalCase.camelcase: converte strings para o formato camelCase.kebabcase: converte strings para o formato kebab-case.cobolcase: converte strings para o formato COBOL-CASE.snakecase: converte strings para o formato snake_case.macrocase: converte strings para o formato MACRO_CASE.group_id_folder: converte strings trocando.por/. Útil para montar nomes de pastas a partir de nomes de pacotes Java.regex_replace: retorna uma cópia do valor com todas as ocorrências de um padrão substituídas por uma nova string.from_json: converte uma string em formato JSON em um dicionário Python.
Você pode fornecer um terceiro argumento opcional count ao regex_replace; apenas as primeiras ocorrências indicadas serão substituídas:
# {{ "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
Você pode usar variáveis que contenham o valor a ser substituído:
Considere que a variável hello_world possui o valor "dogs, cats, birds":
# {{ variable | regex_replace('substring pattern', 'replacement string') }}
{{ hello_world | regex_replace('birds', 'humans') }}
-> dogs, cats, humans
A variável hello_world passa a ter o valor "dogs, cats, humans".
É possível usar, no campo de substituição, os grupos da expressão regular:
Considere que a variável hello_world possui o valor "burger":
# {{ string value or variable | regex_replace(define group pattern, replacement string group) }}
{{ hello_world | regex_replace('((\w+\s*)+)', 'cheese\1y') }}
-> cheeseburger
A variável hello_world passa a ter o valor "cheeseburger".
Exemplo do filtro from_json:
{
"input1": "value1"
}
{% set my_dict = json_input | from_json %}
{{ my_dict.input1 }}
Estruturas de controle
O portal da StackSpot não suporta o uso de estruturas de controle do Jinja na interface web. Por este motivo, não é possível garantir que todos os templates Jinja serão executados com sucesso no navegador.
As estruturas de controle presentes no Jinja são if/elif/else/endif e for/else/endfor.
Usar a estrutura if/elif/else/endif
Os comandos da estrutura if/elif/else/endif permitem definir blocos que serão renderizados de acordo com uma condição.
Exemplo:
{% if idade >= 18 %}
Aceita um drink?
{% elif idade >= 5 %}
Aceita um refrigerante?
{% else %}
Aceita um suco?
{% endif %}
No exemplo acima, é implementada uma lógica para determinar a mensagem exibida de acordo com o valor da variável idade.
Operadores de comparação disponíveis no Jinja:
==: compara os operandos e retornatruese forem iguais.!=: compara os operandos e retornatruese forem diferentes.>:truese o operando à esquerda for maior que o da direita.>=:truese o operando à esquerda for maior ou igual ao da direita.<:truese o operando à esquerda for menor que o da direita.<=:truese o operando à esquerda for menor ou igual ao da direita.
Operadores lógicos:
and: retornatruese as expressões à esquerda e à direita forem verdadeiras.or: retornatruese ao menos uma das expressões for verdadeira.not: nega o resultado de uma expressão.(expr): agrupa expressões lógicas.
Os blocos
elifeelsesão opcionais e é possível usar mais de um blocoelif.
Usar a estrutura for/else/endfor
Os comandos da estrutura for/else/endfor permitem definir um bloco de repetição para cada elemento de uma lista:
{% for nome in nomes %}
Olá {{ nome }}!
{% else %}
Sem nomes informados!
{% endfor %}
No exemplo acima, para cada nome na lista nomes é renderizada uma linha com uma saudação para o nome informado.
Caso a lista de nomes esteja vazia, será renderizado o conteúdo do bloco após o else.
O bloco
elseé opcional e pode ser omitido.
Utilizar Jinja na StackSpot
Geração de arquivos nos Plugins
Os templates Jinja são arquivos de texto que podem conter expressões e variáveis que são interpoladas pela engine para gerar o resultado final. Ao criar um Plugin, é criada uma pasta templates que contém os templates Jinja que serão usados para gerar os arquivos quando o Plugin for utilizado.
Quando um usuário aplica um Plugin ou cria um novo app, o ciclo de renderização do Plugin é executado. Os templates Jinja presentes em cada um dos Plugins sendo aplicados são interpolados usando os inputs informados pelo usuário como variáveis que podem ser usadas em expressões Jinja nesses arquivos.
As expressões Jinja também podem ser usadas para definir nomes dinâmicos de arquivos e pastas, baseados nos inputs do usuário.
Para ilustrar o uso de Jinja na geração de arquivos, considere um Plugin com a seguinte estrutura:
example-plugin/
├── plugin.yaml
└── templates
├── file.txt
├── example_folder
│ └── nested_file.txt
└── {{folder_name}}
└── {{file_name}}.txt
A configuração do Plugin no arquivo plugin.yaml contém a definição dos inputs que serão perguntados para o usuário:
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
No exemplo acima, são perguntados três inputs: folder_name, file_name e content, todos strings.
Observe que há uma pasta chamada {{folder_name}} e um arquivo {{file_name}}.txt, que exemplificam como usar expressões Jinja para definir nomes de pastas e arquivos dinamicamente, com base nos inputs dos usuários.
O conteúdo do arquivo {{file_name}}.txt será:
Content: {{content}}
Os arquivos file.txt e nested_file.txt estão vazios e apenas ilustram que toda a estrutura de arquivos contida na pasta templates é gerada após a aplicação do Plugin, podendo ter quantos arquivos e pastas forem necessários.
Ao aplicar o Plugin em uma pasta vazia app, o STK CLI pergunta os inputs conforme o exemplo:
$ 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.
A estrutura de arquivos gerada será:
app
├── example_folder
│ └── nested_file.txt
├── example.txt
├── file.txt
├── my-folder
│ └── my-file.txt
└── stk.yaml
Observe que o nome da pasta e dos arquivos que continham expressões Jinja foram interpolados com os valores digitados pelo usuário nos inputs.
O conteúdo do arquivo my-file.txt após a renderização será:
Content: my-content
Observe que o conteúdo do arquivo foi interpolado pela engine do Jinja, substituindo a expressão {{content}} pelo conteúdo digitado pelo usuário, my-content.
Expressões Jinja em arquivos de configuração
Além da geração de arquivos, as expressões Jinja podem ser usadas nos YAML de configuração de Plugins.
Expressões Jinja em computed-inputs e global-computed-inputs
As expressões Jinja podem ser usadas para calcular os computed-inputs e global-computed-inputs de Plugins, conforme o exemplo:
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: Nome
type: text
name: nome
computed-inputs:
nome_uppercase: "{{ nome | upper }}"
global-computed-inputs:
nome_lowercase: "{{ nome | lower }}"
No exemplo acima, as variáveis nome_uppercase e nome_lowercase são criadas usando expressões Jinja que pegam o input nome e aplicam filtros para converter o nome digitado em letras maiúsculas e minúsculas.
Expressões Jinja em Hooks Declarativos
As expressões Jinja podem ser usadas em algumas configurações de Hooks Declarativos e nos snippets de arquivos usados pelos Hooks. A seguir, são apresentados os atributos que aceitam expressões Jinja para cada tipo de Hook.
- Hook Declarativo do tipo
run: expressões Jinja podem ser usadas tanto emcommandquanto emworkdir:
hooks:
- type: run
trigger: before-render
workdir: {{ folder_name }}/{{ nested_folder_name }}
command: echo "Hello {{ name }}!"
- Hook Declarativo do tipo
edit: expressões Jinja podem ser usadas nos atributospath,changes.search.string,changes.search.pattern, em todos os pontos onde se usavaluee emnot-existsdentro dewhen:
- 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
- Hook Declarativo do tipo
edit-xml: expressões Jinja podem ser usadas empathe em qualquervalue:
- type: edit-xml
trigger: after-render
path: {{ some_variable }}.xml
changes:
- xpath: .//dependency
append:
value: |
<a>{{ some_variable }}</a>
- Hook Declarativo do tipo
edit-json: expressões Jinja podem ser usadas empathe em qualquervalue:
- type: edit-json
trigger: after-render
path: using-template.json
encoding: ascii
changes:
- jsonpath: "$.scripts"
update:
value: |
{
"{{ json_tag }}": "{{ json_value }}"
}
- Hook Declarativo do tipo
edit-yaml: expressões Jinja podem ser usadas empathe em qualquervalue:
- type: edit-yaml
trigger: after-render
path: using-template.yaml
encoding: ascii
changes:
- yamlpath: "$.scripts"
update:
value: |
{{ json_tag }}: {{ json_body }}
Referenciação de Connection Interface no Terraform
V1
requires:
connection-interface:
- connection-interface-one
No Terraform, os atributos são referenciados com a Connection Interface, de acordo com o modelo:
{{ connection_interface_one_CONNECTOR }}
{{ connection_interface_one_CONNECTOR_engine }}
{{ connection_interface_one_CONNECTOR_is_stackspot_fun }}
Na V1, este modelo continua funcionando, mas não é recomendado que seja utilizado na V2.
V2
Agora é possível acessar os atributos das Connection Interfaces usando o objeto connections. Você também pode incluir um alias na sintaxe. Há duas formas de acessar os atributos:
-
Kebab case: acessar usando
[...]e uma string, similar a um dicionário:{{ connections["connection-interface-one"].engine }} -
Snake case: acessar os atributos convertendo o Kebab em snake_case:
{{ connections.connection_interface_one.is_stackspot_fun }}
Uma opção extra é acessar por meio de uma forma camelCase, se o alias tiver esta convenção:
{{ connections["connectionInterfaceOne"].engine }}
Próximo passo
Confira a seção sobre o uso de Metadata em Plugins e conheça onde e como utilizar metadados na criação de seus Plugins.