Ir al contenido

Documentación

Flujos y automatización

Un flujo es un dibujo que se ejecuta. Se colocan nodos en un lienzo, se trazan hilos entre ellos y la plataforma recorre ese dibujo cada vez que llega el momento elegido. Esta página explica qué puede hacer un flujo, qué significa cada nodo y dónde están exactamente sus límites.

Qué es un flujo

Un flujo es un grafo dirigido: nodos con hilos entre ellos, recorridos desde un único punto de partida. No es, deliberadamente, un guion con un bucle. Cuatro reglas fijan la forma, y el editor se niega a publicar un dibujo que rompa alguna de ellas, señalando el nodo que falla.

  • Exactamente un disparador. El nodo disparador es la única entrada, y decide qué variables existen en el resto del dibujo.
  • Sin ciclos. Un hilo que vuelve a un nodo del que salió se rechaza, porque un flujo no sabe esperar: un bucle sencillamente no terminaría nunca.
  • Todo nodo tiene que ser alcanzable desde el disparador. Un nodo suelto es un error que no se ve en una captura de pantalla, así que es un error al publicar.
  • Las ramas se recorren una tras otra, nunca a la vez. Dos ramas simultáneas escribirían en state al mismo tiempo, y el resultado dependería del azar del reloj.
Un dibujo admite como mucho 80 nodos y 160 hilos, de los cuales como mucho 25 hacen algo de verdad. Ese último número es el mismo que tenían las antiguas listas de pasos; dibujar en lugar de enumerar no lo sube.

El lienzo

El editor es un lienzo y no hay un segundo. Un flujo nuevo se abre directamente en él, y un flujo que era una lista recta de pasos se abre como una cadena recta de nodos.

  • La paleta contiene los doce tipos de nodo. Basta con pulsar uno para ponerlo en el lienzo, o arrastrarlo al sitio deseado.
  • Un nodo se mueve arrastrándolo por su cabecera. Todo lo demás del nodo es un control, así que no inicia un arrastre.
  • Un hilo va de un punto de salida a un punto de entrada. Se puede arrastrar de uno a otro, o pulsar primero la salida y después la entrada. Ambas cosas hacen lo mismo, y la segunda es el camino por teclado.
  • Soltar un hilo en el vacío no hace nada. Para quitar uno se selecciona y se pulsa Suprimir, o se usa la cruz que lleva encima.
  • El zoom va del 50 al 200 por ciento, Ajustar muestra el dibujo entero y arrastrando el fondo se desplaza la vista. No hay minimapa.
La vista de lista es el mismo grafo en texto, con el mismo panel de propiedades al lado. Es una alternativa completa y no un resumen: cada nodo, cada hilo y cada ajuste se alcanzan allí sin ratón.

Los nodos

Doce tipos, y la lista es corta a propósito. Cada tipo es un panel, nueve traducciones, una rama en el ejecutor y una fila en la tabla de verdad, así que un tipo que se gana su sitio vale más que uno que casi lo hace.

Tipos de nodo, sus salidas y sus límites
NodoSalidasQué hace y dónde se detiene
triggeroutLa única entrada. Su evento decide qué variables existen. Un disparador manual lleva además el JSON que se inyecta: como mucho 2048 caracteres, 40 claves y 512 caracteres por valor.
scriptok · errorRenderiza una plantilla de script contra un destino. Sale por ok, o por error cuando el renderizado falla. Marcando continuar tras un error, un fallo sigue adelante igualmente.
switchrule-0 … · otherwiseMira un valor y toma la primera regla que encaja; todo lo demás sale por otherwise. Como mucho doce reglas, y un valor comparado de como mucho 512 caracteres.
iftrue · falseUna condición, dos salidas. Los mismos operadores y la misma evaluación que una regla de switch, dibujados como el sí o no que realmente son.
setoutEscribe en state, de donde un nodo posterior o un guion vuelve a leerlo. Como mucho doce asignaciones por nodo y 4096 caracteres por valor. Los nombres los elige quien dibuja; http está reservado.
httpok · errorLlama a una URL y deja la respuesta en state.http. GET o POST, como mucho ocho cabeceras, un tiempo límite de entre 0,5 y 10 segundos, y un tope de respuesta de entre 256 bytes y 256 KiB, 64 KiB por omisión. Como mucho tres redirecciones, cada una filtrada de nuevo.
notifyoutEscribe un aviso en el panel de esta cuenta, con nivel información o advertencia. Título de como mucho 120 caracteres y texto de como mucho 1000. Marcando Enviar también por correo, sale además por correo hacia todas las personas con acceso a Scripts que no hayan desactivado Avisos de flujos en Notificaciones por correo. Una ejecución de prueba nunca envía correo.
actionok · errorArranca, para, apaga, suspende o reanuda el servidor de esta ejecución, y espera hasta 300 segundos a que el comando termine. Cero significa no esperar: aceptado no es lo mismo que en marcha.
delayoutPausa la ejecución. Hasta 30 segundos dentro de la petición que la inició; más tiempo y la ejecución queda aparcada y se retoma después, hasta un máximo de 30 días.
approvalapproved · rejectedDetiene la ejecución hasta que una persona apruebe o rechace, con un plazo y un resultado elegido si nadie responde.
foreachloop · doneRecorre la salida del bucle una vez por elemento de una lista, leída como JSON, uno por línea o separados por comas.
repeatloop · doneRecorre la salida del bucle mientras se cumpla una condición, y vuelve a leerla antes de cada vuelta.
break—Sale del bucle en el que está; la ejecución continúa en la salida Listo de ese bucle.
subflowok · errorEjecuta otro flujo de esta cuenta y espera su resultado. El hijo recibe su entrada como input.*.
logoutEscribe una línea en la ejecución, igual que el nodo de depuración en Node-RED. El campo admite 1000 caracteres; en la ejecución quedan los primeros 200.
note—Texto sobre el lienzo. No tiene puertos y nunca se recorre, así que tampoco aparece jamás en una ejecución.
end—Termina esta rama y dice cómo acabó la ejecución: con éxito, con fallo, u omitida para un servidor al que este flujo no iba dirigido. Le acompaña un mensaje de como mucho 280 caracteres.
Un nodo action y un nodo http tienen salida de error por la misma razón: tratan con otra parte, un agente o el servidor de un tercero, y solo quien dibujó el flujo sabe si un fallo ahí es grave.

Disparadores

Cinco momentos, y el elegido no es solo cuándo se ejecuta el flujo. Es también qué variables existen, cuáles ofrece el selector y cuáles se niega a guardar la plataforma. Una ruta que no puede existir en ese momento es un error al guardar y no una cadena vacía a mitad de una ejecución.

Los cinco disparadores y lo que tiene cada uno
DisparadorCuándo se ejecutaQué hay y qué no
server.provisionedUna vez, justo después de que un pedido haya producido la máquina.Todo. Es el único momento en el que existe cada grupo.
server.reinstalledDespués de que la reinstalación de una máquina existente haya salido bien.La misma máquina, otra imagen. La dirección no cambia y la fecha de creación sigue siendo la original. Hoy solo se dispara para contenedores; la reinstalación de una máquina virtual la remata una pieza que no sabe renderizar.
server.deletedEn cuanto se acepta una eliminación, mientras la fila todavía se puede leer.Todo menos la imagen. Ya no queda nada de donde leer una imagen, y la plataforma no se la inventa, así que os sencillamente falta.
manualNunca por su cuenta. Se lanza desde el editor con Inject.Una máquina existente que se elige, más el JSON propio como input. Aquí tampoco hay imagen: lo que hay instalado en una máquina no es algo que la plataforma pueda consultar después.
webhookEn cuanto un tercero envía una petición a una URL propia.Ninguna máquina en absoluto, así que ni plan, ni dirección, ni conexión, ni agente. Quedan la cuenta, el flujo, el cuerpo enviado como input y la propia petición como webhook.
Un plan cuelga como mucho un flujo de cada uno de los tres momentos de servidor, así que provisioned, reinstalled y deleted llevan uno cada uno y nunca una lista. Un flujo manual o de webhook no se puede colgar de un plan: a ninguno de los dos lo arranca un pedido, y para eso están precisamente esos dos.

Variables

Todo valor que un flujo puede leer procede de uno de estos grupos. El selector del editor muestra los grupos que existen en el disparador elegido y nada más. Leer una ruta desconocida es un error y nunca un valor vacío: una suposición equivocada tiene que sonar fuerte.

Los grupos de variables, cuándo existen y qué contienen
GrupoExiste conQué contiene
org.*Cualquier disparadorLa cuenta: id, nombre y nombre corto.
user.*Los cuatro de servidorLa persona por la que actúa la ejecución: id, nombre y dirección de correo.
flow.*Cualquier disparadorEste flujo y esta ejecución: nombre, id de ejecución, número y total de pasos, disparador y hora de inicio.
server.*Los cuatro de servidorLa máquina: nombre, nombre de host, tipo, estado, id externo, nodo, núcleos, memoria, disco y fecha de creación.
server.compute.*Los cuatro de servidorQué clase de huésped es y qué puede hacer de verdad esa clase: clase, plantilla, unprivileged, almacenamiento, puntos de montaje, capabilities y unsupported. Los siete son opcionales, y que falte uno significa sin determinar, no que sea que no.
plan.*Los cuatro de servidorEl plan que hay detrás del pedido: nombre, núcleos, memoria, disco y puente.
os.*Creado y reinstaladoLa imagen: nombre, nombre corto y categoría. No hay familia ni versión, porque el catálogo no tiene esas columnas y adivinarlas elige el gestor de paquetes equivocado.
ip.*Los cuatro de servidorLa dirección entregada y si es de versión 4 o 6.
network.*Los cuatro de servidorTodo lo que rodea a esa dirección: cidr, pasarela, máscara, red, difusión y los resolutores.
prefix.*Los cuatro de servidorEl conjunto de direcciones del que salió, con su rango y su padre.
integration.*Los cuatro de servidorLa conexión que sostiene la máquina: nombre, tipo, nodo, almacenamiento, puente y el rango de vmid.
agent.*Los cuatro de servidorEl agente que hizo el trabajo: nombre, estado, versión, nombre de host, sistema operativo, núcleo y núcleos de proceso.
input.*Manual y webhookLo inyectado, o lo que envió el webhook. Las claves son propias, y el selector las sigue.
webhook.*Solo webhookLa petición en sí: qué extremo, cuándo llegó y desde qué dirección.
state.*Tras un nodo setLo que haya escrito un nodo set de este flujo. Los nombres son propios, y solo existen por debajo del nodo que los escribe.
state.http.<node>.*Tras un nodo httpPor cada nodo http: status, ok, body, json, error y url. Los seis existen siempre, en el camino bueno y en el malo.
device.*Todavía en ningunoDiseñado para equipos de red. No hay ningún proveedor que pueda rellenarlo, así que hoy ningún flujo puede usarlo.
Un valor que falta y un valor vacío no son lo mismo. Los grupos que no existen en el disparador elegido se eliminan en lugar de vaciarse, y un campo opcional pide una comprobación alrededor antes de leerlo.

Expresiones

Un nodo set escribe en state. Su valor es texto literal, salvo que se marque la casilla de expresión; entonces se lee como una plantilla, con las variables de esta ejecución ya puestas.

  • Esa casilla es una decisión deliberada. Un valor que por casualidad lleva dos llaves, un trozo de Ansible o un fragmento de Helm, sigue significando lo que decía. Nada cambia de sentido por un cambio que nadie pidió.
  • El lenguaje es solo salida. Dos llaves y una ruta, y nada más: sin assign, sin capture, sin bucle, sin include. Lo que se ve en el lienzo es lo que sale, con los valores puestos.
  • Hay 46 filtros: texto, números, fechas, los pocos filtros de lista que producen texto, y los propios quote, ini e ipoctet. El selector del editor los enumera. Faltan dos familias a propósito, las que echan a suertes y las que devuelven un objeto donde se quería una palabra.
  • Los secretos no están disponibles aquí, ni tampoco los fragmentos reutilizables. Lo que escribe un nodo set acaba en el registro de la ejecución, y un secreto no pinta nada ahí.
El título y el texto de un nodo notify se leen siempre como plantilla, sin casilla. Son prosa, y poner un nombre dentro es justamente todo su sentido.

Aprobaciones y bucles

Un flujo puede detenerse y esperar a una persona, y puede recorrer los mismos nodos más de una vez. Ninguna de las dos cosas era posible mientras una ejecución no se pudiera aparcar y despertar después; esa cola ya existe.

  • Un nodo de aprobación detiene la ejecución, escribe una solicitud y avisa a la cuenta. Responderla requiere un permiso aparte, de modo que quien escribió el flujo no es automáticamente quien lo firma.
  • Cada solicitud lleva un plazo y qué ocurre sin respuesta: continuar como aprobado, continuar como rechazado o terminar la ejecución como no aplicable. No hay una cuarta opción, porque una ejecución que espera para siempre es una de la que nadie vuelve a saber.
  • Para cada recorre su salida de bucle una vez por elemento de una lista. Dentro del bucle, state.loop contiene el elemento actual, su posición y el total.
  • Nada de fuera de un bucle puede conectarse a los nodos que hay dentro. Esos nodos se ejecutan de nuevo en cada vuelta, y un aviso conectado a ambos escribiría un mensaje por vuelta.
  • Un paso de aprobación también puede enviar correo. Ese correo llega solo a los miembros que pueden decidir sobre aprobaciones, no a todos los que ven flujos, y la notificación del panel se escribe igualmente.
Cada bucle lleva un límite duro de vueltas, y una ejecución uno de pasos. Por encima del límite la ejecución se detiene con un error en vez de hacer parte del trabajo en silencio: un flujo que recorrió cien de mil elementos y no dijo nada es peor que uno que falló.

Esperar, y flujos que llaman a flujos

Una ejecución detenida no es una ejecución fallida. Tres cosas pueden aparcarla, y las tres usan la misma cola: una aprobación, una pausa larga y un subflujo cuyo hijo también espera.

  • Una pausa de medio minuto o menos espera dentro de la petición que la inició, que es lo que se quiere al dar un momento a un agente. Todo lo más largo aparca la ejecución, de modo que el pedido que la inició termina enseguida y el resto del flujo llega después.
  • Un subflujo ejecuta otro flujo de esta cuenta como un solo paso. Su resultado decide la salida: un hijo correcto, o que no encontró nada que hacer, sigue por OK; uno fallido, por Error. El estado, la ejecución y el nombre del flujo están en state.subflow.
  • Un flujo nunca puede alcanzarse a sí mismo, ni directamente ni a través de otro flujo. Se comprueba al desplegar, y el mensaje nombra la cadena encontrada.
  • Los flujos pueden llamarse como máximo tres niveles de profundidad. Como cada flujo se edita por separado, ese límite se comprueba dos veces: al desplegar y mientras la ejecución avanza de verdad.
Un hijo que se detiene en una aprobación detiene también a su padre, y la página de cada ejecución enlaza con la otra. Nada se pierde mientras una ejecución espera: el recorrido a medias queda guardado y continúa exactamente con el grafo con el que empezó.

Webhooks

Un extremo de webhook da a un sistema externo una URL propia. Ese sistema envía JSON, se ejecuta el flujo que hay detrás de la URL, y la respuesta es un código de estado desnudo. Los extremos viven bajo Scripts, en la pestaña Webhooks, y son objetos propios: el mismo extremo puede apuntar a otro flujo sin que el remitente note nada.

  1. Crear el extremo y darle un nombre reconocible más adelante. La URL se genera sola y no se puede elegir: 32 caracteres al azar, o sea 160 bits, con lo cual adivinarla no es una estrategia.
  2. Elegir cómo se acredita el remitente y copiar el secreto. Se muestra una sola vez y nunca más; si se pierde, se regenera, y entonces también cambia la URL.
  3. Apuntar el extremo a un flujo cuyo disparador sea un webhook. El extremo puede existir antes que el flujo, y hasta entonces una petición se acepta y se responde con un 202 que no arranca nada. Ese es el orden en que la gente construye esto de verdad, así que no se trata como un error.
Las tres maneras de acreditar a un remitente
EsquemaQué hace
noneNinguna prueba: quien conozca la URL puede lanzar el flujo. Defendible con una lista de direcciones al lado, y difícil de defender sin ella.
hmac_sha256El remitente firma el cuerpo en bruto junto con una marca de tiempo. Esta es la opción a elegir: ata la petición a su propio cuerpo, de modo que una petición interceptada no se puede editar y reenviar.
bearerEl remitente pone el token compartido en la cabecera Authorization. Sencillo, y no dice nada del cuerpo que venía con él.

La firma es la palabra sha256, un signo igual y el HMAC-SHA256 sobre exactamente los bytes de la marca de tiempo, un punto y el cuerpo, en hexadecimal minúsculo. Va en X-Provibr-Signature, y la marca de tiempo en sí va en X-Provibr-Timestamp, en segundos enteros desde la época.

Una petición cuya marca de tiempo tenga más de 300 segundos, o esté en el futuro, se rechaza con un 401. Esa ventana es una regla real y no un adorno, porque la marca de tiempo está dentro del material firmado: una petición interceptada no se puede reenviar mañana pegándole encima una marca nueva.

Firmar una petición es una línea de openssl. Este ejemplo envía un objeto pequeño; el texto de consola se queda en inglés a propósito, para que coincida con lo que muestra la terminal.

shell
# 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"
Qué acepta un extremo
LímiteDetalles
body64 KiB por omisión, ajustable entre 1 KiB y 256 KiB. Una petición mayor se corta mientras todavía está llegando, no se lee entera para luego rechazarla.
rate60 peticiones por minuto por omisión, ajustable entre 1 y 600. Se cuenta por extremo y no por remitente, porque una fuente real tiene una dirección y un atacante tiene mil.
payloadUn objeto JSON de un solo nivel. Cada clave pasa a ser input seguido de esa clave; un objeto anidado se rechaza en lugar de aplanarse.
allowlistComo mucho 20 direcciones o rangos, en v4 o v6. Vacío significa ninguna restricción.
endpointsComo mucho 50 extremos por cuenta.
Qué significa cada respuesta
CódigoSignificado
202Aceptada. O bien ha arrancado una ejecución, o bien no había nada que arrancar: aún no hay flujo enlazado, el flujo está apagado, o la propia ejecución se vino abajo. En los cuatro casos el remitente no hizo nada mal, y un recibo indica cuál de los cuatro fue.
401Falta la firma o el token, o no cuadran, o la firma cuadra y su marca de tiempo queda fuera de la ventana.
403El extremo está apagado, o la dirección de origen no está en la lista.
404No hay ningún extremo con ese identificador.
410El extremo se ha revocado. El identificador queda ocupado para siempre, y esta respuesta le dice a una fuente honesta que necesita una URL nueva, cosa que un 404 no haría.
413El cuerpo es mayor de lo que acepta este extremo.
422El cuerpo no es un objeto JSON de un solo nivel.
429Por encima del límite de caudal de este extremo.

Cada petición deja un recibo, aceptada o no: el desenlace, el estado devuelto, la dirección de origen completa y cuántos bytes se leyeron realmente. El cuerpo no se guarda nunca. Los recibos desaparecen a los 90 días, antes que las propias ejecuciones, porque una dirección de origen es un dato personal de alguien que no es cliente nuestro.

Apagar un extremo no es lo mismo que revocarlo. Apagado responde 403 y se puede volver a encender; revocado responde 410 para siempre, y la URL no se vuelve a entregar.

Revisiones y ejecuciones

Dos preguntas que se parecen tienen aquí dos respuestas distintas. Quién cambió qué y cuándo es una revisión. Qué recorrió realmente esta ejecución concreta es una copia guardada en la propia ejecución. Una instantánea no puede responder a la primera pregunta y una revisión no puede responder a la segunda, así que existen las dos.

  • Cada publicación escribe una revisión, incluso una publicación que no cambió nada. El número sube por flujo y no se reutiliza jamás.
  • La diferencia entre dos revisiones es una lista legible y no JSON en bruto: un nodo añadido, quitado o cambiado con los campos nombrados, un hilo añadido o quitado. Dónde está un nodo en el lienzo no es un cambio.
  • Restaurar no rebobina. Escribe el dibujo antiguo como una revisión nueva encima de la historia, con una nota de dónde venía.
  • Cada ejecución conserva el dibujo que recorrió. Editar un flujo después nunca reescribe lo que hizo una ejecución más antigua.
  • Una ejecución tiene una fila por nodo visitado, con el desenlace y la salida por la que se fue, y una fila por nodo no visitado con el motivo. No visitado es un hecho que merece anotarse, no una ausencia.
  • La lista de ejecuciones filtra por disparador, estado y revisión, y se exporta a CSV. La exportación se detiene en 5000 filas y lo dice dentro del propio fichero en lugar de acabarse en silencio.

Las plantillas de script llevan al lado su propia historia: cada versión guardada del contenido, y una diferencia línea a línea entre dos cualesquiera de ellas.

Una ejecución que llega a una aprobación se aparca: el recorrido se guarda, la ejecución pasa a En espera y un proceso la continúa en cuanto alguien decide o vence el plazo. El resto se sigue recorriendo dentro de la petición que la inició, y por eso una pausa llega hasta cinco minutos.