Aller au contenu

Documentation

Flux et automatisation

Un flux est un dessin qui s’exécute. Vous posez des nœuds sur un plan de travail, vous tirez des fils entre eux, et la plateforme parcourt ce dessin chaque fois que le moment choisi se présente. Cette page dit ce qu’un flux sait faire, ce que signifie chaque nœud et où se trouvent exactement ses limites.

Ce qu’est un flux

Un flux est un graphe orienté : des nœuds reliés par des fils, parcourus depuis un seul point de départ. Ce n’est délibérément pas un script avec une boucle. Quatre règles en fixent la forme, et l’éditeur refuse de publier un dessin qui en casse une, en nommant le nœud fautif.

  • Exactement un déclencheur. Le nœud déclencheur est la seule entrée, et c’est lui qui décide des variables qui existent dans le reste du dessin.
  • Aucun cycle. Un fil qui ramène vers un nœud d’où il venait est refusé, car un flux ne sait pas attendre : une boucle ne se terminerait tout simplement jamais.
  • Chaque nœud doit être atteignable depuis le déclencheur. Un nœud resté en l’air est une erreur qu’une capture d’écran ne montre pas, donc c’est une erreur au moment de publier.
  • Les branches sont parcourues l’une après l’autre, jamais côte à côte. Deux branches simultanées écriraient dans state en même temps, et le résultat dépendrait du hasard de l’horloge.
Un dessin accepte au plus 80 nœuds et 160 fils, dont au plus 25 qui font réellement quelque chose. Ce dernier nombre est celui qu’avaient déjà les anciennes listes d’étapes ; dessiner au lieu d’énumérer ne le relève pas.

Le plan de travail

L’éditeur est un plan de travail, et il n’y en a pas de second. Un flux neuf s’y ouvre directement, et un flux qui était une liste droite d’étapes s’ouvre comme une chaîne droite de nœuds.

  • La palette contient les douze types de nœud. Cliquez sur l’un d’eux pour le poser sur le plan, ou faites-le glisser à l’endroit voulu.
  • Déplacez un nœud en le saisissant par son en-tête. Tout le reste du nœud est une commande et ne démarre donc pas de glissement.
  • Un fil va d’un point de sortie à un point d’entrée. Faites glisser de l’un vers l’autre, ou cliquez d’abord la sortie puis l’entrée. Les deux font la même chose, et la seconde est le chemin au clavier.
  • Relâcher un fil dans le vide ne fait rien. Pour en supprimer un, sélectionnez-le et appuyez sur Suppr, ou utilisez la croix posée dessus.
  • Le zoom va de 50 à 200 %, Ajuster montre le dessin entier, et faire glisser le fond déplace la vue. Il n’y a pas de minicarte.
La vue en liste est le même graphe en texte, avec le même panneau de propriétés à côté. C’est une solution de rechange complète et non un résumé : chaque nœud, chaque fil et chaque réglage y sont atteignables sans souris.

Les nœuds

Douze types, et cette liste est courte à dessein. Chaque type est un panneau, neuf traductions, une branche dans l’exécuteur et une ligne dans la table de vérité ; un type qui porte son poids vaut donc mieux qu’un type qui le porte presque.

Types de nœud, leurs sorties et leurs limites
NœudSortiesCe qu’il fait et où il s’arrête
triggeroutL’unique entrée. Son événement décide des variables qui existent. Un déclencheur manuel porte en plus le JSON que vous injectez : au plus 2048 caractères, 40 clés et 512 caractères par valeur.
scriptok · errorRend un modèle de script vers une cible. Il sort par ok, ou par error lorsque le rendu échoue. Cochez la poursuite après erreur et un échec continue malgré tout.
switchrule-0 … · otherwiseRegarde une valeur et prend la première règle qui convient ; tout le reste sort par otherwise. Au plus douze règles, et une valeur comparée de 512 caractères au maximum.
iftrue · falseUne condition, deux sorties. Les mêmes opérateurs et la même évaluation qu’une règle de switch, dessinés comme le oui ou non que c’est réellement.
setoutÉcrit dans state, d’où un nœud ultérieur ou un script le relit. Au plus douze affectations par nœud et 4096 caractères par valeur. Les noms vous appartiennent ; http est réservé.
httpok · errorAppelle une URL et place la réponse dans state.http. GET ou POST, au plus huit en-têtes, un délai entre 0,5 et 10 secondes, et un plafond de réponse entre 256 octets et 256 Kio, 64 Kio par défaut. Au plus trois redirections, chacune filtrée à nouveau.
notifyoutÉcrit une notification dans le panneau de ce compte, au niveau information ou avertissement. Titre de 120 caractères au maximum, texte de 1000 au maximum. Cochez Envoyer aussi par e-mail et elle part en plus par courriel, vers toutes les personnes ayant accès aux scripts qui n’ont pas désactivé Notifications de flux dans Notifications par e-mail. Un test n’envoie jamais de courriel.
actionok · errorDémarre, arrête, éteint, suspend ou reprend le serveur de cette exécution, et attend jusqu’à 300 secondes la fin de la commande. Zéro signifie ne pas attendre : acceptée n’est pas la même chose qu’en marche.
delayoutMet l’exécution en pause. Jusqu’à 30 secondes dans la requête qui l’a lancée ; au-delà, l’exécution est mise de côté et reprise plus tard, pendant 30 jours au maximum.
approvalapproved · rejectedArrête l'exécution jusqu'à ce qu'une personne approuve ou refuse, avec un délai et une issue choisie si personne ne répond.
foreachloop · doneParcourt la sortie de boucle une fois par élément d'une liste, lue en JSON, un par ligne ou séparés par des virgules.
repeatloop · doneParcourt la sortie de boucle tant qu'une condition est vraie, et relit cette condition avant chaque tour.
break—Quitte la boucle où il se trouve ; l'exécution continue à la sortie Terminé de cette boucle.
subflowok · errorExécute un autre flux de ce compte et attend son résultat. L’enfant reçoit son entrée sous la forme input.*.
logoutÉcrit une ligne dans l’exécution, comme le nœud de débogage le fait dans Node-RED. Le champ accepte 1000 caractères ; ce qui arrive dans l’exécution, ce sont les 200 premiers.
note—Du texte sur le plan de travail. Il n’a pas de ports et n’est jamais parcouru, il n’apparaît donc jamais non plus dans une exécution.
end—Termine cette branche et dit comment l’exécution s’est terminée : réussie, échouée, ou ignorée pour un serveur auquel ce flux ne s’adressait pas. Un message de 280 caractères au maximum l’accompagne.
Un nœud action et un nœud http ont une sortie error pour la même raison : ils concernent quelqu’un d’autre, un agent ou le serveur d’un tiers, et seul celui qui a dessiné le flux sait si un échec là-bas est grave.

Déclencheurs

Cinq moments, et celui que vous choisissez ne dit pas seulement quand le flux s’exécute. Il dit aussi quelles variables existent, lesquelles le sélecteur vous propose et lesquelles la plateforme refuse d’enregistrer. Un chemin qui ne peut pas exister à ce moment-là est une erreur à l’enregistrement, et non une chaîne vide au milieu d’une exécution.

Les cinq déclencheurs et ce dont chacun dispose
DéclencheurQuand il s’exécuteCe qui est là et ce qui ne l’est pas
server.provisionedUne fois, juste après qu’une commande a produit la machine.Tout. C’est le seul moment où chaque groupe existe.
server.reinstalledAprès la réussite de la réinstallation d’une machine existante.La même machine, une autre image. L’adresse ne change pas et la date de création reste l’originale. Aujourd’hui il ne se déclenche que pour les conteneurs ; la réinstallation d’une machine virtuelle est achevée par un composant qui ne sait pas rendre.
server.deletedDès qu’une suppression est acceptée, tant que la ligne est encore lisible.Tout sauf l’image. Il ne reste rien d’où lire une image, et la plateforme n’en invente pas : os est donc simplement absent.
manualJamais de lui-même. Vous le lancez depuis l’éditeur avec Inject.Une machine existante que vous désignez, plus votre propre JSON comme input. Ici non plus il n’y a pas d’image : ce qui est installé sur une machine, la plateforme ne peut pas le retrouver après coup.
webhookDès qu’un tiers publie vers une URL qui lui est propre.Aucune machine du tout, donc ni forfait, ni adresse, ni connexion, ni agent. Restent le compte, le flux, le corps envoyé comme input et la requête elle-même comme webhook.
Un forfait attache au plus un flux à chacun des trois moments de serveur : provisioned, reinstalled et deleted en portent donc un chacun, jamais une liste. Un flux manuel ou un flux webhook ne peut pas du tout être attaché à un forfait : aucun des deux n’est déclenché par une commande, et c’est précisément à cela que servent ces deux-là.

Variables

Chaque valeur qu’un flux peut lire provient de l’un de ces groupes. Le sélecteur de l’éditeur montre les groupes qui existent pour votre déclencheur, et rien d’autre. Lire un chemin inconnu est une erreur et jamais une valeur vide : une supposition fausse doit faire du bruit.

Les groupes de variables, quand ils existent et ce qu’ils contiennent
GroupeExiste avecCe qu’il contient
org.*Tout déclencheurLe compte : id, nom et nom court.
user.*Les quatre de serveurLa personne pour qui l’exécution agit : id, nom et adresse électronique.
flow.*Tout déclencheurCe flux et cette exécution : nom, id d’exécution, numéro et nombre d’étapes, déclencheur et heure de début.
server.*Les quatre de serveurLa machine : nom, nom d’hôte, type, état, id externe, nœud, cœurs, mémoire, disque et date de création.
server.compute.*Les quatre de serveurQuel genre d’invité c’est et ce que ce genre sait vraiment faire : genre, modèle, unprivileged, stockage, points de montage, capabilities et unsupported. Les sept sont facultatifs, et une absence signifie non établi et non pas non.
plan.*Les quatre de serveurLe forfait derrière la commande : nom, cœurs, mémoire, disque et pont.
os.*Créée et réinstalléeL’image : nom, nom court et catégorie. Il n’y a ni famille ni version, car le catalogue n’a pas ces colonnes et les deviner choisit le mauvais gestionnaire de paquets.
ip.*Les quatre de serveurL’adresse attribuée, et si elle est de version 4 ou 6.
network.*Les quatre de serveurTout ce qui entoure cette adresse : cidr, passerelle, masque, réseau, diffusion et les résolveurs.
prefix.*Les quatre de serveurLe lot d’adresses d’où elle vient, avec sa plage et son parent.
integration.*Les quatre de serveurLa connexion qui porte la machine : nom, type, nœud, stockage, pont et la plage de vmid.
agent.*Les quatre de serveurL’agent qui a fait le travail : nom, état, version, nom d’hôte, système d’exploitation, noyau et cœurs.
input.*Manuel et webhookCe que vous avez injecté, ou ce que le webhook a envoyé. Les clés vous appartiennent, et le sélecteur les suit.
webhook.*Webhook seulementLa requête elle-même : quel point d’entrée, quand elle est arrivée et depuis quelle adresse.
state.*Après un nœud setCe qu’un nœud set de ce flux a écrit. Les noms vous appartiennent, et ils n’existent qu’en dessous du nœud qui les écrit.
state.http.<node>.*Après un nœud httpPar nœud http : status, ok, body, json, error et url. Les six existent toujours, sur le bon chemin comme sur le mauvais.
device.*Nulle part encoreConçu pour du matériel réseau. Aucun fournisseur ne peut le remplir, donc aucun flux ne peut s’en servir aujourd’hui.
Une valeur absente et une valeur vide ne sont pas la même chose. Les groupes qui n’existent pas pour votre déclencheur sont retirés plutôt que vidés, et un champ facultatif demande une vérification autour de lui avant d’être lu.

Expressions

Un nœud set écrit dans state. Sa valeur est du texte littéral, sauf si vous cochez la case expression ; elle est alors lue comme un modèle, les variables de cette exécution déjà remplies.

  • Cette case est un choix délibéré. Une valeur qui contient par hasard deux accolades, un morceau d’Ansible ou un fragment de Helm, continue de signifier ce qu’elle disait. Rien ne change de sens à cause d’un changement que personne n’a demandé.
  • Le langage n’est que de la sortie. Deux accolades et un chemin, et rien d’autre : pas d’assign, pas de capture, pas de boucle, pas d’include. Ce qui est écrit sur le plan est ce qui en sort, valeurs remplies.
  • Il y a 46 filtres : texte, nombres, dates, les quelques filtres de liste qui produisent du texte, et les propres quote, ini et ipoctet. Le sélecteur de l’éditeur les énumère. Deux familles manquent à dessein, celles qui tirent au sort et celles qui rendent un objet là où vous vouliez un mot.
  • Les secrets ne sont pas disponibles ici, les fragments réutilisables non plus. Ce qu’écrit un nœud set finit dans le compte rendu de l’exécution, et un secret n’a rien à y faire.
Le titre et le texte d’un nœud notify sont toujours lus comme un modèle, sans case à cocher. C’est de la prose, et y insérer un nom en est tout l’intérêt.

Approbations et boucles

Un flux peut s'arrêter et attendre une personne, et il peut parcourir les mêmes nœuds plusieurs fois. Ni l'un ni l'autre n'était possible tant qu'une exécution ne pouvait pas être mise en attente puis réveillée ; cette file existe désormais.

  • Un nœud d'approbation arrête l'exécution, crée une demande et prévient le compte. Y répondre demande un droit distinct, afin que celui qui a écrit le flux ne soit pas automatiquement celui qui le valide.
  • Chaque demande porte un délai et ce qui se passe sans réponse : continuer comme approuvé, continuer comme refusé, ou terminer l'exécution comme sans objet. Il n'y a pas de quatrième choix, car une exécution qui attend indéfiniment est une exécution dont personne n'entend plus parler.
  • Pour chaque parcourt sa sortie de boucle une fois par élément d'une liste. Dans la boucle, state.loop contient l'élément courant, sa position et le total.
  • Rien d'extérieur à une boucle ne peut être relié aux nœuds qu'elle contient. Ces nœuds s'exécutent à chaque tour, et une notification reliée aux deux écrirait un message par tour.
  • Une étape d’approbation peut aussi envoyer un e-mail. Celui-ci ne part qu’aux membres autorisés à décider des approbations, et non à toutes les personnes qui voient les flux ; la notification du panneau est écrite dans tous les cas.
Chaque boucle porte une limite stricte du nombre de tours, et une exécution une limite du nombre d'étapes. Au-delà, l'exécution s'arrête sur une erreur au lieu de faire discrètement une partie du travail : un flux qui a traité cent éléments sur mille sans rien dire est pire qu'un flux en échec.

Attendre, et les flux qui appellent des flux

Une exécution à l’arrêt n’est pas une exécution en échec. Trois choses peuvent la mettre de côté, et toutes trois passent par la même file : une approbation, une longue pause et un sous-flux dont l’enfant attend lui-même.

  • Une pause d’une demi-minute ou moins attend simplement dans la requête qui l’a lancée, ce qui est exactement le comportement voulu lorsque vous laissez un instant à un agent. Au-delà, l’exécution est mise de côté : la commande qui l’a déclenchée se termine immédiatement et la suite du flux vient plus tard.
  • Un sous-flux exécute un autre flux de ce compte comme une seule étape. Son résultat détermine la sortie : un enfant réussi, ou qui n’a rien trouvé à faire, poursuit par OK ; un enfant en échec par Erreur. L’état, l’exécution et le nom du flux se trouvent dans state.subflow.
  • Un flux ne doit jamais s’atteindre lui-même, ni directement ni via un autre flux. C’est vérifié au déploiement, et le message nomme la chaîne trouvée.
  • Les flux peuvent s’appeler sur trois niveaux au maximum. Comme chaque flux se modifie séparément, cette limite est vérifiée deux fois : au déploiement, puis pendant que l’exécution avance réellement.
Un enfant arrêté sur une approbation arrête aussi son parent, et la page d’exécution de chacun renvoie à l’autre. Rien ne se perd pendant l’attente : le parcours à moitié fait est conservé, et il reprend avec exactement le graphe du départ.

Webhooks

Un point d’entrée webhook donne à un système extérieur une URL bien à lui. Ce système envoie du JSON, le flux placé derrière l’URL s’exécute, et la réponse est un code d’état nu. Les points d’entrée vivent sous Scripts, dans l’onglet Webhooks, et sont des objets à part : le même point d’entrée peut viser un autre flux sans que l’expéditeur s’en aperçoive.

  1. Créez le point d’entrée et donnez-lui un nom que vous reconnaîtrez plus tard. L’URL est engendrée pour vous et ne se choisit pas : 32 caractères aléatoires, soit 160 bits, ce qui fait que deviner n’est pas une stratégie.
  2. Choisissez comment l’expéditeur prouve son identité, et copiez le secret. Il n’est montré qu’une seule fois ; s’il est perdu, vous le régénérez, et vous obtenez alors aussi une nouvelle URL.
  3. Faites pointer le point d’entrée vers un flux dont le déclencheur est un webhook. Le point d’entrée peut exister avant le flux, et d’ici là une requête est acceptée et reçoit un 202 qui ne démarre rien. C’est l’ordre dans lequel les gens construisent réellement cela, ce n’est donc pas traité comme une faute.
Les trois manières pour un expéditeur de prouver son identité
SchémaCe qu’il fait
noneAucune preuve : quiconque connaît l’URL peut lancer le flux. Défendable avec une liste d’adresses à côté, difficile à défendre sans.
hmac_sha256L’expéditeur signe le corps brut avec un horodatage. C’est le choix à faire : il lie la requête à son propre corps, si bien qu’une requête interceptée ne peut pas être modifiée puis renvoyée.
bearerL’expéditeur place le jeton partagé dans l’en-tête Authorization. Simple, et cela ne dit rien du corps qui l’accompagnait.

La signature est le mot sha256, un signe égal, puis le HMAC-SHA256 sur exactement les octets de l’horodatage, d’un point et du corps, en hexadécimal minuscule. Elle va dans X-Provibr-Signature, et l’horodatage lui-même va dans X-Provibr-Timestamp, en secondes entières depuis l’époque.

Une requête dont l’horodatage a plus de 300 secondes, ou se situe dans le futur, est refusée par un 401. Cette fenêtre est une vraie règle et non un ornement, car l’horodatage se trouve dans la matière signée : une requête interceptée ne peut pas être renvoyée demain en collant par-dessus un horodatage frais.

Signer une requête tient en une ligne d’openssl. Cet exemple envoie un petit objet ; le texte destiné au terminal reste en anglais à dessein, pour qu’il corresponde à ce que votre terminal affiche.

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"
Ce qu’accepte un point d’entrée
LimiteDétails
body64 Kio par défaut, réglable entre 1 Kio et 256 Kio. Une requête plus grande est coupée pendant qu’elle arrive, et non lue en entier puis refusée.
rate60 requêtes par minute par défaut, réglable entre 1 et 600. Le compte est par point d’entrée et non par expéditeur, car une vraie source a une adresse et un attaquant en a mille.
payloadUn objet JSON d’un seul niveau. Chaque clé devient input suivi de cette clé ; un objet imbriqué est refusé plutôt qu’aplati.
allowlistAu plus 20 adresses ou plages, en v4 ou v6. Vide signifie aucune restriction du tout.
endpointsAu plus 50 points d’entrée par compte.
Ce que signifie chaque réponse
CodeSignification
202Acceptée. Soit une exécution a démarré, soit il n’y avait rien à démarrer : aucun flux encore relié, le flux est éteint, ou l’exécution elle-même est tombée. Dans les quatre cas l’expéditeur n’a rien fait de mal, et un reçu dit lequel des quatre c’était.
401La signature ou le jeton manque ou ne correspond pas, ou bien la signature correspond et son horodatage tombe hors de la fenêtre.
403Le point d’entrée est éteint, ou l’adresse source n’est pas dans la liste.
404Il n’existe aucun point d’entrée avec cet identifiant.
410Le point d’entrée a été révoqué. L’identifiant reste occupé pour toujours, et cette réponse dit à une source honnête qu’il lui faut une nouvelle URL, ce qu’un 404 ne ferait pas.
413Le corps est plus grand que ce que ce point d’entrée accepte.
422Le corps n’est pas un objet JSON d’un seul niveau.
429Au-dessus du débit autorisé pour ce point d’entrée.

Chaque requête laisse un reçu, acceptée ou non : l’issue, l’état renvoyé, l’adresse source en entier et le nombre d’octets réellement lus. Le corps n’est jamais conservé. Les reçus disparaissent au bout de 90 jours, plus tôt que les exécutions elles-mêmes, car une adresse source est une donnée personnelle appartenant à quelqu’un qui n’est pas notre client.

Éteindre un point d’entrée n’est pas la même chose que le révoquer. Éteint, il répond 403 et peut être rallumé ; révoqué, il répond 410 pour toujours, et l’URL n’est plus jamais attribuée.

Révisions et exécutions

Deux questions qui se ressemblent ont ici deux réponses différentes. Qui a changé quoi, et quand, c’est une révision. Ce que cette exécution précise a réellement parcouru, c’est une copie conservée sur l’exécution elle-même. Un instantané ne peut pas répondre à la première question et une révision ne peut pas répondre à la seconde, les deux existent donc.

  • Chaque publication écrit une révision, y compris une publication qui n’a rien changé. Le numéro monte par flux et n’est jamais réutilisé.
  • L’écart entre deux révisions est une liste lisible et non du JSON brut : un nœud ajouté, retiré ou modifié avec les champs nommés, un fil ajouté ou retiré. L’endroit où se trouve un nœud sur le plan n’est pas une modification.
  • Restaurer ne rembobine pas. Cela réécrit l’ancien dessin comme une nouvelle révision par-dessus l’historique, avec une note disant d’où il venait.
  • Chaque exécution conserve le dessin qu’elle a parcouru. Modifier un flux ensuite ne réécrit jamais ce qu’une exécution plus ancienne a fait.
  • Une exécution comporte une ligne par nœud visité, avec l’issue et la sortie empruntée, et une ligne par nœud non visité avec le motif. Non visité est un fait qui mérite d’être noté, pas une absence.
  • La liste des exécutions filtre par déclencheur, état et révision, et s’exporte en CSV. L’export s’arrête à 5000 lignes et le dit dans le fichier plutôt que de cesser en silence.

Les modèles de script tiennent à côté leur propre historique : chaque version enregistrée du contenu, et un écart ligne à ligne entre deux d’entre elles.

Une exécution qui atteint une approbation est mise en attente : le parcours est enregistré, l'exécution passe en attente, et un worker la reprend dès que quelqu'un décide ou que le délai expire. Le reste est toujours parcouru dans la requête qui l'a lancée, et c'est pourquoi une pause va jusqu'à cinq minutes.