Usar Jinja na StackSpot
Nesta seção, você encontra: Informações sobre o uso da linguagem Jinja na StackSpot.
Jinja
A engine da StackSpot faz o uso da linguagem Jinja para permitir que os Plugins façam a geração de arquivos baseados 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 permitindo uma grande flexibilidade na definição de 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. A depender do seu uso, algumas expressões podem não se comportar da maneira esperada, para mais informações sobre restrições consulte a documentação oficial do Jinja.
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 de passos ocorre:
- O arquivo de configuração (
plugin.yaml
) é carregado; - São executados os Hooks com trigger
before-input
; - Os inputs definidos na configuração são perguntados para o usuário;
- São calculados os
computed-inputs
; - São executados os Hooks com trigger
before-render
; - São renderizados os templates Jinja contidos na pasta
templates
do Plugin; - São executados os Hooks com trigger
after-render
. - 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 alguns pontos da configuração dos Plugins.
Conceitos básicos do Jinja
A linguagem Jinja assim como a maioria das linguagens de templates, permite através de marcações específicas em um texto, a definição de expressões que são interpoladas pela engine para renderizar o resultado final.
As marcações Jinja em um texto podem ser:
{{ ... }}
, para expressões que serão renderizadas;{% ... %}
, para estruturas de controle;{# ... #}
, para comentários.
Confira um exemplo simples de um template Jinja:
<!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>
Quando a variável name
receber os valores ['João', 'Maria']
, a renderização do template deve retornar o seguinte resultado:
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Webpage</title>
</head>
<body>
<h1>Hello João!</h1>
<h1>Hello Maria!</h1>
</body>
Em resumo, ao serem avaliadas pela engine de templates da StackSpot, as expressões e estruturas de controle do Jinja são avaliadas para produzir 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 algo que deve ser renderizado pelo Jinja. As expressões podem conter valores literais, variáveis, cálculos e expressões complexas.
Para saber mais sobre todas as expressões Jinja, consulte a documentação do Jinja.
Confira a seguir 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 é o de 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 perguntados para o usuário quando eles são aplicados 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 em questão, no projeto aplicado, 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 renderizados no lugar da expressão.
Se você criar uma variável Jinja com um hífen, ex my-var, que pode ser uma variável de input, deve ser chamada como {{inputs["my-var"]}}. Caso contrário, a chamada é feita normalmente, como: {{myvar}} ou {{inputs.myvar}}.
Filtros
As variáveis podem ser transformadas através 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 ( |
), conforme o exemplo:
{{ variavel | filtro1 | filtro2 }}
No exemplo acima o valor da variável é modificado pelo filtro1
e depois modificado pelo filtro2
.
Confira o exemplo do filtro padrão capitalize
:
Esse filtro converte o nome informado para o formato onde, a primeira letra é maiúscula e as outras minúsculas.
{{ nome | capitalize }}
Para mais informações sobre os filtros padrões do Jinja, consulte a documentação do Jinja.
Além dos filtros padrão do Jinja, a StackSpot disponibiliza alguns filtros úteis para os 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
: Convete strings trocando "." por "/". Esse filtro é útil para montar nomes de pastas a partir de nomes e pacotes Java.regex_replace
: Retorna uma cópia do valor com todas as ocorrências de uma substring (um padrão) substituídas por uma nova. O primeiro argumento é a substring que deve ser substituída, o segundo é a string de substituição.
Você pode fornecer um terceiro argumento opcional "contador" (count
), mas apenas as primeiras ocorrências de contagem 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"|replace("a", "two", 2) }}
-> two, two, athree
Você pode usar variáveis que contenham o valor para ser substituído:
Considere que a variável hello_word
possui o valor "dogs, cats, birds"
;
# {{ variable|regelex_replace('substring pattern', 'replacement string')}}
{{ hello_world|regex_replace('birds', 'humans') }}
-> docs, cats, humans
A variável hello_word
passa a ter o valor "dogs, cats, humans"
.
É possível usar no campo de substituição os grupos da expressão regular na string substituição:
Considere que a variável hello_word
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_word
passa a ter o valor 'cheeseburger'
.
from_json
: Converte uma string no formato JSON em um dict python.
Considere a string JSON:
{
"input1": "value1"
}
Para usar o dict do Python no JINJA, você deve acessá-lo da seguinte forma:
{% 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. 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 as if/elif/else/endif
e for/else/endfor
.
Usar a estrutura if/elif/else/endif
Os comandos da estrutura if/elif/else/endif
permitem a definição de blocos que serão renderizados de acordo com uma condição. O exemplo ilustra a utilização de um bloco if
para controlar a impressão de parte de um template:
{% if idade True %}
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 que será impressa de acordo com o valor da variável idade
.
Os operadores de comparação disponíveis no Jinja são:
==
: Compara os dois operandos retornandotrue
se forem iguais.!=
: Compara os dois operandos retornandotrue
se forem diferentes.>
:true
se o operando à esquerda for maior que o operando à direita.>=
:true
se o operando à esquerda for maior ou igual ao operando à direita.<
:true
se o operando à esquerda for menor que o operando à direita.<=
:true
se o operando à esquerda for menor ou igual ao operando à direita.
Os operadores lógicos podem ser usados nas expressões:
-
and
: Retornatrue
se as expressões à esquerda e à direta forem verdadeiras. -
or
: Retornatrue
se uma das expressões for verdadeira. -
not
: Nega o resultado de uma expressão. -
(expr)
: Agrupa expressões lógicas.
Os blocos
elif
eelse
são opcionais e pode-se usar mais de um blocoelif
, caso necessário.
Usar a estrutura for/else/endfor
Os comandos da estrutura for/else/endfor
permitem a definição de um bloco de repetição para cada elemento de uma lista conforme o exemplo:
{% for nome in nomes %}
Olá {{ nome }}!
{% else %}
Sem nomes informados!
{% endfor %}
No exemplo acima, para cada nome na lista de nomes nomes
será 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 caso não seja necessário.
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: v3
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
que são strings.
Observe na estrutura do Plugin que há uma pasta chamada {{folder_name}}
e um arquivo {{file_name}}.txt
que exemplifica como usar expressões Jinja para definir nomes de pastas e arquivos dinamicamente, baseando-se 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 só 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 ser aplicado o Plugin em uma pasta vazia app
, o STK CLI irá perguntar 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.
E 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 que foi 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: v3
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 apresentamos os atributos que aceitam expressões Jinja para cada tipo de Hook.
- Hook Declarativo do tipo
run
: Expressões Jinja podem ser usadas tanto nocommand
quanto noworkdir
conforme o exemplo:
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 usavalue
e nonot-exists
dentro de umwhen
, conforme o exemplo:
- 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 nopath
e em qualquervalue
, conforme o exemplo:
- 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 nopath
e em qualquervalue
, conforme o exemplo:
- 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 nopath
e em qualquervalue
, conforme o exemplo:
- 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
V.1
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 ele seja utilizado na v2.
V2:
Agora é possível acessar os atributos das Connection Interfaces usando um connection
. 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. Confira o exemplo:
{{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 um Camel Case. Confira o exemplo:
{{connections["connectionInterfaceOne"].engine}}
Próximo passo
Confira a seção sobre o uso do Metadatae conheça onde e como utilizar metadados na criação de seus Plugins.