Zum Inhalt springen

Blog ·

Ein Draht zum Ziehen, und genau ein Auslöser

Einen Draht zu ziehen ist einfach. Schwer war, was ein Testknopf berühren darf, wer handelt, wenn niemand zusieht, und wie lange eine Pause dauert.

Von Provibr

Unsere Automatisierungsabläufe begannen als Liste von Schritten, wurden ein Graph, dann eine Zeichenfläche, und in dieser Runde bekamen sie endlich die Geste, die sich jeder unter einem „Ablauf-Editor“ vorstellt: Sie fassen den Punkt rechts an einem Knoten an und ziehen einen Draht zum Punkt links an einem anderen.

Diese Geste besteht aus einer Handvoll Zeilen Code und einem langen Kommentar darüber, warum diese Zeilen dort stehen, wo sie stehen. Der Rest der Runde bestand aus Fragen, die mit dem Ziehen nichts zu tun haben. Wie viele Stellen dürfen einen Ablauf starten. Was darf ein Testknopf berühren. Wer handelt, wenn niemand zusieht. Und wie lange darf ein Schritt pausieren.

Die Erfassung, die den Klick auffrisst

Die Zeichenfläche hat eine einzige Transformationsebene. Die Knoten stehen absolut darin und die Drähte liegen als SVG in genau derselben Ebene, es gibt also kein zweites Koordinatensystem, das gegenüber dem ersten verrutschen könnte. Dieser Teil ist absichtlich langweilig.

Interessant ist die Zeigererfassung. Sobald Sie auf einen Ausgangspunkt drücken, muss etwas dem Zeiger folgen, bis Sie loslassen, auch außerhalb des Fensters. Die naheliegende Umsetzung erfasst auf dem Punkt, den Sie gedrückt haben, und sie ist auf eine Weise falsch, die keinen einzigen Fehler erzeugt.

Die Erfassung leitet jedes folgende Zeigerereignis auf das erfassende Element um, pointerup eingeschlossen, und der Browser feuert click danach auf dem gemeinsamen Vorfahren von Nieder und Auf. Erfasst der Punkt selbst, dann kommt eine Ziehbewegung, die auf einem anderen Punkt endet, dort nie an; sie landet wieder dort, wo sie begann. Also erfasst die Fläche, und wo der Finger tatsächlich losgelassen hat, ist eine eigene Frage, beantwortet von document.elementFromPoint().

Diese Antwort hat drei Stufen. Ein Eingangspunkt unter dem Zeiger. Andernfalls eine Knotenfläche unter dem Zeiger, die zu ihrem ersten Eingang führt. Andernfalls ein Magnet: der nächstgelegene Eingang innerhalb von sechzehn Bildschirmpixeln, und genau das macht die Geste nachsichtig, ohne dass jemand ein zehn Pixel breites Ziel treffen muss.

Zwei kleinere Funde, die erst beim Bauen auftauchen. React hängt wheel passiv an die Wurzel, das Unterdrücken der Standardaktion im Handler bewirkt also nichts und die Seite scrollt, während Sie zoomen wollen; dieser Zuhörer muss ausdrücklich nicht passiv am Element selbst hängen. Und die Mitte einer Bezierkurve ist nicht die Mitte ihres umschließenden Rechtecks, einen Draht zu treffen heißt also, den Pfad nach einem Punkt bei einer bestimmten Länge zu fragen und ihn durch die aktuelle Transformationsmatrix zu schicken.

Klick, dann Klick, funktioniert weiterhin. Das ist nicht der schlechtere Weg: es ist der Weg, der mit einer Tastatur funktioniert, und deshalb sind die Punkte echte Schaltflächen geblieben.

Ein Auslöser, und vier Dinge, die daran hängen

Ein Graph hat genau einen Auslöserknoten. Null ist ein Fehler beim Speichern und zwei ist ein Fehler beim Speichern. Das klingt nach Ordnungsliebe. Es ist tragend, und vier Funktionen ruhen darauf.

Was es liefertWarum ein Auslöser nötig ist
ErreichbarkeitJeder ausführbare Knoten muss von einem einzigen bekannten Startpunkt aus erreichbar sein
ZykluserkennungEine farbmarkierende Tiefensuche braucht eine Wurzel; zwei Wurzeln sind zwei Suchen und zwei zusammenzuführende Antworten
Die VariablenauswahlWelche Variablen existieren, hängt davon ab, welches Ereignis ausgelöst hat, und ein zweiter Auslöser bedeutete zwei Antworten in einem Editor
KontextbeschnittEine Variablengruppe, die dieses Ereignis nicht liefern kann, wird entfernt, das Lesen ist damit ein Fehler und keine leere Zeichenkette

Die letzte Zeile zahlt sich selbst zurück. Es gibt vier Auslöserereignisse: ein Server wurde bereitgestellt, neu installiert, gelöscht oder von Hand gestartet. Ein gelöschter Server hat keinen Betriebssystem-Slug, deshalb verschwindet die ganze Betriebssystemgruppe aus dem Kontext, statt auf ein leeres Objekt gesetzt zu werden. Mit strikten Variablen erzeugt das Lesen dann einen lesbaren Fehler „unbekannte Variable“ in dem Moment, in dem Sie speichern. Leeren Sie sie stattdessen, wird die Bedingung, die sie liest, unwahr, still, für immer, auf dem Zweig, auf den es ankam.

Deshalb gibt es auch keinen eigenen Inject-Knoten. Das Starten von Hand ist ein Auslöserereignis und kein zweiter Eingang; einen hinzuzufügen bräche die Invariante, auf der alle vier Funktionen ruhen, im Tausch gegen einen Knoten, der etwas hübscher zeichnet.

Ein Testlauf berührt nichts und läuft trotzdem

Der Editor hat eine Schaltfläche Test run, und ein Graph kann jetzt einen Knoten enthalten, der den Server dieses Laufs startet, stoppt, herunterfährt, pausiert oder fortsetzt. Diese beiden Tatsachen auf einem Bildschirm sind eine Falle: jemand drückt auf Testen, um zu sehen, welchen Zweig ein Graph nimmt, und schaltet eine Kundenmaschine aus.

Ein Testlauf sendet deshalb nie ein Kommando. Der Aktionsknoten kehrt sofort zurück, als übersprungen markiert, mit einem Grundcode in der Sprache des Lesers. Entscheidend ist: er nimmt den Zweig ok. Ein Test, der in den Fehlerzweig fiele, behauptete, der Graph tue etwas, das er nicht tut, und das ist schlimmer, als nichts zu sagen. Wirkliches Handeln geht über Inject, das ist ein echter Lauf, und der verlangt eine Bestätigung.

Wer handelt, wenn niemand zusieht

Ein Ablauf hat keinen Zuschauer. Bei einer Bestellung ist die Person, die auf Bestellen geklickt hat, bestenfalls zufällig diejenige, der die Handlung zuzurechnen wäre. Bei einer Löschung ist sie vielleicht längst weg. Aber einen Server zu finden verlangt einen Handelnden mit einem Rechteprofil, und dieses Profil muss irgendwoher kommen.

Die Entscheidung lautet: ein Lauf handelt als Eigentümer des Kontos, aus drei Gründen in der Reihenfolge ihres Gewichts. Der Autorisierungsmoment ist das Ausrollen und nicht der Lauf: einen Ablauf zu speichern verlangt das Modul Skripte, ihn an einen Tarif zu hängen verlangt Tarifrechte, und das ist dieselbe Form wie ein Cron oder der Überwachungsarbeiter. Als der Klickende zu handeln machte den Graphen unreproduzierbar: derselbe Ablauf startete bei dem einen Besteller eine Maschine und beim anderen nicht, wegen eines Rechtehäkchens, das mit dem Ablauf nichts zu tun hat. Und die wirkliche Grenze bleibt ohnehin hart: die Suche bekommt die Organisations-ID des Laufs mit und weist jede Zeile außerhalb ab, und dieser Knoten kann konstruktionsbedingt nur den Server dieses Laufs ansprechen.

Bevor ein Kommando entsteht, müssen zwei Siebe beide ja sagen: kann dieser Anbieter dieses Kommando, und kann diese Art von Ressource es. Auf Proxmox weist heute keine der fünf Aktionen einen Container ab, und genau deshalb verlangt die Ende-zu-Ende-Prüfung auch eine Festplattenvergrößerung und fordert, dass diese abgewiesen wird. Ein Sieb, das nie nein sagt, ist von einem Sieb, das nicht angeschlossen ist, nicht zu unterscheiden.

Fünf Minuten, weil die Pause in einer Anfrage steckt

Der Verzögerungsknoten erlaubt fünf bis dreihundert Sekunden. Der Auftrag verlangte sechshundert, und sechshundert ist aus einem messbaren Grund falsch: ein von einer Bestellung ausgelöster Ablauf wird innerhalb der Bestellanfrage abgewartet. Zehn Minuten Pause sind zehn Minuten, in denen ein zahlender Kunde auf einen Ladekreis schaut.

Es gibt keinen dauerhaften Wartezustand, und solange es keinen gibt, kann eine Pause eine Anfrage nicht überdauern. Diese Nahtstelle trägt einen Namen im Code, damit „warum kann mein Ablauf nicht eine Stunde warten“ eine Frage mit einer auffindbaren Antwort ist. Sie verlangt eine Warteschlange, die einen halb beendeten Lauf aufbewahren und später fortsetzen kann, und erst wenn die existiert, kommen Schleifen überhaupt in Betracht.

Der HTTP-Knoten, und warum er kein fetch ist

Ein HTTP-Knoten in einem Automatisierungswerkzeug ist eine Maschine für serverseitige Anfragefälschung, wenn man nicht aufpasst, und Aufpassen schließt hier den Standardclient aus.

fetch kann das eine nicht, was diese Regel verlangt: sich mit einer Adresse verbinden, die wir gewählt haben, während der Hostname in Host und SNI stehen bleibt. Er löst selbst auf, und diese zweite Auflösung ist genau das Fenster, in dem DNS-Rebinding lebt. Sie sieben eine öffentliche Adresse, und eine halbe Sekunde später verbindet sich der Client mit dem Loopback, weil die Lebensdauer des Eintrags null war.

Der eigene HTTP-Client von Node nimmt eine eigene Auflösung entgegen, und unsere gibt genau eine Adresse zurück: die, die das Sieb freigegeben hat. Host und SNI werden weiterhin aus dem Hostnamen abgeleitet, TLS wird also gegen den Namen geprüft. Die Zertifikatsprüfung ist ohne Ausschalter aktiv, und Proxy-Umgebungsvariablen bewirken nichts, weil dieser Client sie nicht liest.

Umleitungen werden von Hand verfolgt, höchstens drei, und jeder Sprung wird von vorn gesiebt: Schema, Benutzerinformation, Port, DNS, jede Adresse. Eine offene Umleitung auf einen Metadatenendpunkt einer Cloud wird beim zweiten Sprung abgewiesen, auch wenn der erste ein anständiger öffentlicher Host war. Sieben Kopfzeilennamen sind erlaubt, und zwei davon dürfen nur einen Verweis auf ein gespeichertes Geheimnis tragen, das für die Anfrage entschlüsselt, aus allem Gespeicherten geschwärzt und ausschließlich dem Namen nach festgehalten wird.

Was wir festhalten würden

  1. Erfassen Sie auf der Hülle, nicht auf dem, was Sie angefasst haben. Die Zeigererfassung leitet alles Folgende um, und das Symptom ist eine Geste, die still dort endet, wo sie begann.
  2. Eine nachsichtige Geste braucht eine Kette von Rückfällen, kein größeres Ziel. Element unter dem Zeiger, dann dessen Elternteil, dann eine Abstandsgrenze.
  3. Ein Eingang kauft vier Funktionen. Erreichbarkeit, Zykluserkennung, eine Variablenauswahl und der Kontextbeschnitt brauchen alle vier eine bekannte Wurzel. Zählen Sie, was Sie verlieren, bevor Sie einen zweiten hinzufügen.
  4. Entfernen Sie, was nicht existieren kann; leeren Sie es nicht. Eine fehlende Variablengruppe scheitert laut beim Speichern; eine leere scheitert still zur Laufzeit.
  5. Ein Testknopf, der die Produktion berühren kann, ist kein Testknopf. Überspringen und trotzdem den Erfolgszweig nehmen ist die ehrliche Kombination.
  6. Eine Pause wird von dem begrenzt, der auf sie wartet. Ist der Aufrufer eine HTTP-Anfrage, gehört die Pause in Sekunden.

Zur Klarheit darüber, was das ist: Abläufe sind über den Skriptbildschirm erreichbar und die Zeichenfläche ist jetzt der einzige Editor. Drei Dinge sind unbewiesen. Das Warten auf ein wirklich abgeschlossenes Kommando wurde nicht an einem echten Hypervisor gemessen, die Ziehbewegung wurde mit nachgebildeten Browserprotokollereignissen statt mit einer Hand angetrieben, und nichts davon wurde an einem Produktionsbuild gemessen.

Aus der Plattformarbeit von Provibr. Warum der Graph überhaupt endlich sein muss, steht in ein Flow ist ein Graph, und er muss endlich sein, und wie ein Übersichtsbildschirm fremde Tabellen liest, ohne je in sie zu schreiben, steht in projizieren, niemals ersetzen.

Fangen Sie mit einem Host und einem Agenten an

Konto erstellen, den Agenten mit einem einzigen Befehl installieren und Ihren ersten Proxmox-Host verbinden. Die Testphase läuft 14 Tage und fragt nicht nach Zahlungsdaten.