Documentação
Fluxos e automação
Um fluxo é um desenho que corre. Colocam-se nós numa tela, traçam-se fios entre eles, e a plataforma percorre esse desenho sempre que chega o momento escolhido. Esta página diz o que um fluxo consegue fazer, o que significa cada nó e onde estão exatamente os seus limites.
O que é um fluxo
Um fluxo é um grafo orientado: nós ligados por fios, percorridos a partir de um único ponto de partida. Não é, de propósito, um guião com um ciclo. Quatro regras fixam a forma, e o editor recusa publicar um desenho que quebre alguma delas, apontando o nó errado.
- Exatamente um disparador. O nó disparador é a única entrada, e é ele que decide que variáveis existem no resto do desenho.
- Sem ciclos. Um fio que volta a um nó de onde saiu é recusado, porque um fluxo não sabe esperar: um ciclo simplesmente nunca terminaria.
- Todos os nós têm de ser alcançáveis a partir do disparador. Um nó solto é um erro que uma captura de ecrã não mostra, por isso é um erro no momento de publicar.
- Os ramos são percorridos um a seguir ao outro, nunca lado a lado. Dois ramos ao mesmo tempo escreveriam em state no mesmo instante, e o resultado dependeria do acaso do relógio.
A tela
O editor é uma tela, e não há uma segunda. Um fluxo novo abre logo nela, e um fluxo que era uma lista direita de passos abre como uma cadeia direita de nós.
- A paleta contém os doze tipos de nó. Basta clicar num deles para o pousar na tela, ou arrastá-lo para o sítio pretendido.
- Um nó move-se arrastando-o pelo seu cabeçalho. Tudo o resto no nó é um controlo e portanto não inicia um arrasto.
- Um fio vai de um ponto de saída para um ponto de entrada. Arrasta-se de um para o outro, ou clica-se primeiro na saída e depois na entrada. As duas coisas fazem o mesmo, e a segunda é o caminho pelo teclado.
- Largar um fio no vazio não faz nada. Para remover um, seleciona-se e carrega-se em Delete, ou usa-se a cruz que está em cima dele.
- O zoom vai de 50 a 200 por cento, Ajustar mostra o desenho inteiro, e arrastar o fundo desloca a vista. Não há minimapa.
Os nós
Doze tipos, e a lista é curta de propósito. Cada tipo é um painel, nove traduções, um ramo no executor e uma linha na tabela de verdade, por isso um tipo que ganha o seu lugar vale mais do que um que quase o ganha.
| Nó | Saídas | O que faz e onde para |
|---|---|---|
trigger | out | A única entrada. O seu evento decide que variáveis existem. Um disparador manual leva ainda o JSON que se injeta: no máximo 2048 caracteres, 40 chaves e 512 caracteres por valor. |
script | ok · error | Compõe um modelo de script contra um destino. Sai por ok, ou por error quando a composição falha. Marcando continuar após um erro, uma falha segue mesmo assim. |
switch | rule-0 … · otherwise | Olha para um valor e toma a primeira regra que serve; tudo o resto sai por otherwise. No máximo doze regras, e um valor comparado de no máximo 512 caracteres. |
if | true · false | Uma condição, duas saídas. Os mesmos operadores e a mesma avaliação de uma regra de switch, desenhados como o sim ou não que realmente são. |
set | out | Escreve em state, de onde um nó posterior ou um guião o volta a ler. No máximo doze atribuições por nó e 4096 caracteres por valor. Os nomes são escolhidos por quem desenha; http está reservado. |
http | ok · error | Chama um URL e põe a resposta em state.http. GET ou POST, no máximo oito cabeçalhos, um tempo limite entre 0,5 e 10 segundos, e um teto de resposta entre 256 bytes e 256 KiB, 64 KiB por omissão. No máximo três redirecionamentos, cada um filtrado de novo. |
notify | out | Escreve um aviso no painel desta conta, ao nível informação ou alerta. Título de no máximo 120 caracteres, texto de no máximo 1000. Marcando Enviar também por e-mail, sai também por correio para todas as pessoas com acesso aos scripts que não tenham desativado Notificações de fluxos em Notificações por e-mail. Uma execução de teste nunca envia correio. |
action | ok · error | Arranca, para, desliga, suspende ou retoma o servidor desta execução, e espera até 300 segundos pelo fim do comando. Zero significa não esperar: aceite não é o mesmo que a funcionar. |
delay | out | Coloca a execução em pausa. Até 30 segundos dentro do pedido que a iniciou; mais do que isso e a execução fica de lado e é retomada mais tarde, no máximo 30 dias. |
approval | approved · rejected | Para a execução até que uma pessoa aprove ou rejeite, com um prazo e um resultado escolhido se ninguém responder. |
foreach | loop · done | Percorre a saída do ciclo uma vez por elemento de uma lista, lida como JSON, um por linha ou separados por vírgulas. |
repeat | loop · done | Percorre a saída do ciclo enquanto uma condição for verdadeira e lê essa condição de novo antes de cada volta. |
break | — | Sai do ciclo em que está; a execução continua na saída Concluído desse ciclo. |
subflow | ok · error | Executa outro fluxo desta conta e aguarda o resultado. O filho recebe a sua entrada como input.*. |
log | out | Escreve uma linha na execução, tal como o nó de depuração faz no Node-RED. O campo aceita 1000 caracteres; na execução ficam os primeiros 200. |
note | — | Texto sobre a tela. Não tem portas e nunca é percorrido, por isso também nunca aparece numa execução. |
end | — | Termina este ramo e diz como acabou a execução: com êxito, com falha, ou ignorada para um servidor a que este fluxo não se dirigia. Acompanha-o uma mensagem de no máximo 280 caracteres. |
Disparadores
Cinco momentos, e o que se escolhe não é só quando o fluxo corre. É também que variáveis existem, quais o seletor oferece e quais a plataforma recusa guardar. Um caminho que não pode existir nesse momento é um erro ao guardar, e não um texto vazio a meio de uma execução.
| Disparador | Quando corre | O que há e o que não há |
|---|---|---|
server.provisioned | Uma vez, logo depois de uma encomenda ter produzido a máquina. | Tudo. É o único momento em que existe cada grupo. |
server.reinstalled | Depois de a reinstalação de uma máquina existente ter corrido bem. | A mesma máquina, outra imagem. O endereço não muda e a data de criação continua a ser a original. Hoje só dispara para contentores; a reinstalação de uma máquina virtual é rematada por uma peça que não sabe compor. |
server.deleted | Assim que uma eliminação é aceite, enquanto a linha ainda se consegue ler. | Tudo menos a imagem. Já não resta nada de onde ler uma imagem, e a plataforma não a inventa: os fica portanto simplesmente ausente. |
manual | Nunca por si próprio. Lança-se a partir do editor com Inject. | Uma máquina existente que se indica, mais o próprio JSON como input. Aqui também não há imagem: o que está instalado numa máquina não é algo que a plataforma consiga consultar depois. |
webhook | Assim que um terceiro envia um pedido para um URL só dele. | Máquina nenhuma, portanto nem plano, nem endereço, nem ligação, nem agente. Ficam a conta, o fluxo, o corpo enviado como input e o próprio pedido como webhook. |
Variáveis
Todos os valores que um fluxo pode ler vêm de um destes grupos. O seletor do editor mostra os grupos que existem para o disparador escolhido, e mais nada. Ler um caminho desconhecido é um erro e nunca um valor vazio: um pressuposto errado tem de fazer barulho.
| Grupo | Existe com | O que contém |
|---|---|---|
org.* | Qualquer disparador | A conta: id, nome e nome curto. |
user.* | Os quatro de servidor | A pessoa por quem a execução age: id, nome e endereço de correio. |
flow.* | Qualquer disparador | Este fluxo e esta execução: nome, id de execução, número e total de passos, disparador e hora de início. |
server.* | Os quatro de servidor | A máquina: nome, nome de anfitrião, tipo, estado, id externo, nó, núcleos, memória, disco e data de criação. |
server.compute.* | Os quatro de servidor | Que género de hóspede é e o que esse género consegue mesmo fazer: género, modelo, unprivileged, armazenamento, pontos de montagem, capabilities e unsupported. Os sete são facultativos, e faltar significa não apurado e não que seja não. |
plan.* | Os quatro de servidor | O plano por trás da encomenda: nome, núcleos, memória, disco e ponte. |
os.* | Criada e reinstalada | A imagem: nome, nome curto e categoria. Não há família nem versão, porque o catálogo não tem essas colunas e adivinhá-las escolhe o gestor de pacotes errado. |
ip.* | Os quatro de servidor | O endereço atribuído, e se é da versão 4 ou 6. |
network.* | Os quatro de servidor | Tudo o que rodeia esse endereço: cidr, gateway, máscara, rede, difusão e os resolvedores. |
prefix.* | Os quatro de servidor | O lote de endereços de onde veio, com o seu intervalo e o seu pai. |
integration.* | Os quatro de servidor | A ligação que sustenta a máquina: nome, tipo, nó, armazenamento, ponte e o intervalo de vmid. |
agent.* | Os quatro de servidor | O agente que fez o trabalho: nome, estado, versão, nome de anfitrião, sistema operativo, núcleo e núcleos de processamento. |
input.* | Manual e webhook | O que se injetou, ou o que o webhook enviou. As chaves são próprias, e o seletor segue-as. |
webhook.* | Só webhook | O próprio pedido: que ponto de entrada, quando chegou e de que endereço. |
state.* | Depois de um nó set | Aquilo que um nó set deste fluxo escreveu. Os nomes são próprios, e só existem abaixo do nó que os escreve. |
state.http.<node>.* | Depois de um nó http | Por cada nó http: status, ok, body, json, error e url. Os seis existem sempre, no caminho bom e no mau. |
device.* | Ainda em nenhum | Pensado para equipamento de rede. Não há nenhum fornecedor que o consiga preencher, por isso hoje nenhum fluxo o pode usar. |
Expressões
Um nó set escreve em state. O seu valor é texto literal, a não ser que se marque a caixa de expressão; aí é lido como um modelo, com as variáveis desta execução já preenchidas.
- Essa caixa é uma decisão deliberada. Um valor que por acaso tem duas chavetas, um pedaço de Ansible ou um fragmento de Helm, continua a significar o que dizia. Nada muda de sentido por causa de uma alteração que ninguém pediu.
- A linguagem é só saída. Duas chavetas e um caminho, e mais nada: sem assign, sem capture, sem ciclo, sem include. O que está na tela é o que sai, com os valores preenchidos.
- Há 46 filtros: texto, números, datas, os poucos filtros de lista que produzem texto, e os próprios quote, ini e ipoctet. O seletor do editor enumera-os. Faltam duas famílias de propósito, as que tiram à sorte e as que devolvem um objeto onde se queria uma palavra.
- Os segredos não estão disponíveis aqui, nem os fragmentos reutilizáveis. O que um nó set escreve acaba no relato da execução, e um segredo não tem nada que fazer ali.
Aprovações e ciclos
Um fluxo pode parar e esperar por uma pessoa, e pode percorrer os mesmos nós mais do que uma vez. Nenhuma das duas coisas era possível enquanto uma execução não pudesse ser estacionada e acordada depois; essa fila já existe.
- Um nó de aprovação para a execução, cria um pedido e avisa a conta. Responder exige um direito separado, para que quem escreveu o fluxo não seja automaticamente quem o assina.
- Cada pedido leva um prazo e o que acontece sem resposta: continuar como aprovado, continuar como rejeitado ou terminar a execução como não aplicável. Não há uma quarta opção, porque uma execução que espera para sempre é uma execução de que ninguém volta a saber.
- Para cada percorre a sua saída de ciclo uma vez por elemento de uma lista. Dentro do ciclo, state.loop contém o elemento atual, a sua posição e o total.
- Nada de fora de um ciclo pode ligar-se aos nós que estão dentro. Esses nós correm de novo em cada volta, e um aviso ligado a ambos escreveria uma mensagem por volta.
- Um passo de aprovação também pode enviar e-mail. Esse e-mail chega apenas aos membros que podem decidir sobre aprovações, não a todos os que veem fluxos, e a notificação do painel é escrita de qualquer forma.
Esperar, e fluxos que chamam fluxos
Uma execução parada não é uma execução falhada. Três coisas podem pô-la de lado, e as três usam a mesma fila: uma aprovação, uma pausa longa e um subfluxo cujo filho também espera.
- Uma pausa de meio minuto ou menos espera dentro do pedido que a iniciou, que é o que se pretende ao dar um momento a um agente. Tudo o que demore mais põe a execução de lado, para que a encomenda que a iniciou termine já e o resto do fluxo venha depois.
- Um subfluxo executa outro fluxo desta conta como um único passo. O resultado decide a saída: um filho bem-sucedido, ou que nada encontrou para fazer, segue por OK; um filho falhado segue por Erro. O estado, a execução e o nome do fluxo ficam em state.subflow.
- Um fluxo nunca pode alcançar-se a si próprio, nem diretamente nem através de outro fluxo. Isso é verificado ao publicar, e a mensagem nomeia a cadeia encontrada.
- Os fluxos podem chamar-se no máximo três níveis de profundidade. Como cada fluxo é editado por si, esse limite é verificado duas vezes: ao publicar e enquanto a execução avança de facto.
Webhooks
Um ponto de entrada de webhook dá a um sistema exterior um URL só dele. Esse sistema envia JSON, o fluxo por trás do URL corre, e a resposta é um código de estado nu. Os pontos de entrada vivem em Scripts, no separador Webhooks, e são objetos próprios: o mesmo ponto de entrada pode apontar para outro fluxo sem que o remetente dê por isso.
- Criar o ponto de entrada e dar-lhe um nome que se reconheça mais tarde. O URL é gerado e não se pode escolher: 32 caracteres aleatórios, ou seja 160 bits, com o que adivinhar não é uma estratégia.
- Escolher como o remetente se identifica, e copiar o segredo. É mostrado uma só vez e nunca mais; se se perder, regenera-se, e então também muda o URL.
- Apontar o ponto de entrada para um fluxo cujo disparador seja um webhook. O ponto de entrada pode existir antes do fluxo, e até lá um pedido é aceite e recebe um 202 que não arranca nada. É a ordem pela qual as pessoas constroem isto de verdade, por isso não é tratado como um erro.
| Esquema | O que faz |
|---|---|
none | Nenhuma prova: quem conhecer o URL pode lançar o fluxo. Defensável com uma lista de endereços ao lado, difícil de defender sem ela. |
hmac_sha256 | O remetente assina o corpo em bruto juntamente com uma marca temporal. É esta a escolha a fazer: prende o pedido ao seu próprio corpo, pelo que um pedido intercetado não se consegue editar e reenviar. |
bearer | O remetente põe a senha partilhada no cabeçalho Authorization. Simples, e não diz nada sobre o corpo que vinha com ela. |
A assinatura é a palavra sha256, um sinal de igual e o HMAC-SHA256 sobre exatamente os bytes da marca temporal, de um ponto e do corpo, em hexadecimal minúsculo. Vai em X-Provibr-Signature, e a própria marca temporal vai em X-Provibr-Timestamp, em segundos inteiros desde a época.
Um pedido cuja marca temporal tenha mais de 300 segundos, ou esteja no futuro, é recusado com um 401. Essa janela é uma regra a sério e não um adorno, porque a marca temporal está dentro do material assinado: um pedido intercetado não se consegue reenviar amanhã colando-lhe por cima uma marca nova.
Assinar um pedido é uma linha de openssl. Este exemplo envia um objeto pequeno; o texto para o terminal fica em inglês de propósito, para coincidir com aquilo que o terminal mostra.
# Sign the raw body together with the timestamp, then post it.
SECRET="$PROVIBR_WEBHOOK_SECRET"
BODY='{"reason":"maintenance","attempt":1}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1)
curl -sS -X POST 'https://app.provibr.com/api/hooks/flows/<your-slug>' \
-H 'Content-Type: application/json' \
-H "X-Provibr-Timestamp: $TS" \
-H "X-Provibr-Signature: sha256=$SIG" \
--data "$BODY"| Limite | Pormenores |
|---|---|
body | 64 KiB por omissão, ajustável entre 1 KiB e 256 KiB. Um pedido maior é cortado enquanto ainda está a chegar, e não lido por inteiro para depois ser recusado. |
rate | 60 pedidos por minuto por omissão, ajustável entre 1 e 600. A contagem é por ponto de entrada e não por remetente, porque uma fonte a sério tem um endereço e um atacante tem mil. |
payload | Um objeto JSON de um só nível. Cada chave passa a ser input seguido dessa chave; um objeto encaixado é recusado em vez de ser achatado. |
allowlist | No máximo 20 endereços ou intervalos, em v4 ou v6. Vazio significa nenhuma restrição. |
endpoints | No máximo 50 pontos de entrada por conta. |
| Código | Significado |
|---|---|
202 | Aceite. Ou arrancou uma execução, ou não havia nada para arrancar: ainda sem fluxo ligado, o fluxo está desligado, ou a própria execução caiu. Nos quatro casos o remetente não fez nada de errado, e um recibo diz qual dos quatro foi. |
401 | A assinatura ou a senha falta ou não bate certo, ou a assinatura bate certo e a sua marca temporal cai fora da janela. |
403 | O ponto de entrada está desligado, ou o endereço de origem não está na lista. |
404 | Não existe nenhum ponto de entrada com este identificador. |
410 | O ponto de entrada foi revogado. O identificador fica ocupado para sempre, e esta resposta diz a uma fonte honesta que precisa de um URL novo, coisa que um 404 não faria. |
413 | O corpo é maior do que este ponto de entrada aceita. |
422 | O corpo não é um objeto JSON de um só nível. |
429 | Acima do caudal permitido para este ponto de entrada. |
Cada pedido deixa um recibo, aceite ou não: o desfecho, o estado devolvido, o endereço de origem por inteiro e quantos bytes foram mesmo lidos. O corpo nunca é guardado. Os recibos desaparecem ao fim de 90 dias, mais cedo do que as próprias execuções, porque um endereço de origem é um dado pessoal de alguém que não é nosso cliente.
Revisões e execuções
Duas perguntas parecidas têm aqui duas respostas diferentes. Quem mudou o quê, e quando, é uma revisão. O que esta execução em concreto percorreu de facto é uma cópia guardada na própria execução. Uma fotografia não consegue responder à primeira pergunta e uma revisão não consegue responder à segunda, por isso existem ambas.
- Cada publicação escreve uma revisão, mesmo uma publicação que não mudou nada. O número sobe por fluxo e nunca é reutilizado.
- A diferença entre duas revisões é uma lista legível e não JSON em bruto: um nó acrescentado, removido ou alterado com os campos nomeados, um fio acrescentado ou removido. Onde está um nó na tela não é uma alteração.
- Repor não rebobina. Escreve o desenho antigo como uma revisão nova por cima da história, com uma nota a dizer de onde veio.
- Cada execução guarda o desenho que percorreu. Editar um fluxo depois nunca reescreve o que uma execução mais antiga fez.
- Uma execução tem uma linha por cada nó visitado, com o desfecho e a saída por onde passou, e uma linha por cada nó não visitado com o motivo. Não visitado é um facto que vale a pena anotar, não uma ausência.
- A lista de execuções filtra por disparador, estado e revisão, e exporta-se para CSV. A exportação para às 5000 linhas e di-lo dentro do próprio ficheiro em vez de acabar em silêncio.
Os modelos de script mantêm ao lado uma história própria: cada versão guardada do conteúdo, e uma diferença linha a linha entre duas quaisquer delas.