Wiki anlegen: Langform zu den Bausteinen und das Gemessene
Vier Seiten, bewusst nicht mehr. Die Aufteilung ist dieselbe wie in jlab-cluster/helm-charts: Das README bleibt die Kurzreferenz fuer Aufrufer — was ein Baustein tut, die Eingabe-Tabelle, ein Aufrufbeispiel, die Voraussetzungen — und wird dafuer nicht gekuerzt. Das Wiki ist die Langform: warum die Dinge so sind und was dafuer gemessen wurde. Home nennt das Repository, verweist auf die Bausteine und fasst die zwei Hausregeln in je einem Satz: an einen Tag binden, ein Eigentuemer-Segment nie raten. docker-build-push und publish-helm-chart tragen die Begruendungen. Beim Publish-Baustein vor allem die Eigenschaften des Upload-Schritts: warum --fail-with-body noetig ist, weil curl einen 401 sonst mit Code 0 verlaesst, und warum der Dateiname gebaut und nicht geglobt wird, weil --upload-file genau eine Datei nimmt. Im README waeren diese Saetze zu lang; ohne sie entfernt sie irgendwann jemand als Umstand. Gemessenes-Verhalten sammelt die Eigenheiten dieser Forge, die hier teuer waren: boolesche workflow_call-Eingaben kommen nicht an, wandernde Action-Verweise, und ein fehlendes Secret laesst die Job-Erzeugung scheitern statt den Job, sodass der Lauf ohne Schritte dasteht. Letzteres ist in jlab-cluster/helm-charts gemessen; die Quelle steht auf der Seite. Keine Tag-Nummern und keine Fassungen als Momentaufnahme: Welcher Tag gilt, steht im README und in der Workflow-Datei. Was hier steht, sind Begruendungen und Messungen.
4 files changed
+444
No files matched your search
@@ -0,0 +1,95 @@
|
|||||||
|
# Gemessenes Verhalten dieser Forge
|
||||||
|
|
||||||
|
Eigenheiten der Gitea-Instanz, an der diese Bausteine hängen. Alles hier ist
|
||||||
|
**gemessen**, nicht aus einer Dokumentation übernommen, und jedes Stück hat
|
||||||
|
einmal Zeit gekostet. Die Seite steht quer zu den Bausteinen, weil keine dieser
|
||||||
|
Eigenheiten nur einen davon betrifft.
|
||||||
|
|
||||||
|
Gemeinsames Muster, das sie teuer macht: Keiner dieser Fehler ist rot. Der Lauf
|
||||||
|
bleibt grün oder sieht harmlos aus, und erst viel später sucht jemand das
|
||||||
|
Ergebnis dort, wo es hätte landen sollen.
|
||||||
|
|
||||||
|
## Boolesche `workflow_call`-Eingaben kommen nicht an
|
||||||
|
|
||||||
|
Gemessen an **Gitea 1.27.0**,
|
||||||
|
[Issue #4](https://git.jk-dev.de/workflow-util/pipelines/issues/4).
|
||||||
|
|
||||||
|
Eine Eingabe vom Typ `boolean` erreicht den aufgerufenen Workflow nicht. Weder
|
||||||
|
ihre Vorgabe noch ein ausdrücklich übergebener Wert: `inputs.push` blieb leer.
|
||||||
|
Der Baustein baute dadurch immer im Prüfmodus — **bei grünem Lauf**, mit einem
|
||||||
|
`--push`, das in der Kommandozeile einfach fehlte, und einer Warnung im
|
||||||
|
Protokoll, die niemand liest, solange nichts rot ist.
|
||||||
|
|
||||||
|
Zwei Läufe des ersten Abnehmers:
|
||||||
|
|
||||||
|
| Lauf | Aufruf | Ergebnis |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 272 | `push` weggelassen, Vorgabe gesetzt | kein `--push`, nichts veröffentlicht, Lauf grün |
|
||||||
|
| 276 | `push: true` ausdrücklich übergeben | kein `--push`, nichts veröffentlicht, Lauf grün |
|
||||||
|
|
||||||
|
Die Eingrenzung ist das Belastbare daran: Im **selben** Lauf kamen alle Eingaben
|
||||||
|
vom Typ `string` korrekt an — Plattformen, Registry, Eigentümer, Container-Name
|
||||||
|
wurden alle richtig aufgelöst. `push` war die einzige boolesche Eingabe und die
|
||||||
|
einzige, die versagte.
|
||||||
|
|
||||||
|
**Konsequenz für alle Bausteine hier: Jede Eingabe ist `type: string`, auch
|
||||||
|
eine, die inhaltlich ein Schalter ist.** Werte dann als `'true'` und `'false'`,
|
||||||
|
und im Aufruf in Anführungszeichen — ein unquotiertes `push: false` ist im YAML
|
||||||
|
ein Boolean und wird verschluckt.
|
||||||
|
|
||||||
|
Der Fehler war so langlebig, weil nirgends sichtbar war, was überhaupt ankommt.
|
||||||
|
Dagegen hilft nur eines, und `docker-build-push` hat es seitdem: ein
|
||||||
|
Diagnoseschritt, der den rohen Wert der Eingabe und den daraus abgeleiteten
|
||||||
|
Modus ausgibt. Rein informativ und so abgesichert, dass er den Job unter keinen
|
||||||
|
Umständen scheitern lässt.
|
||||||
|
|
||||||
|
## Ein fehlendes Secret lässt die Job-Erzeugung scheitern, nicht den Job
|
||||||
|
|
||||||
|
Gemessen in `jlab-cluster/helm-charts`; dort festgehalten im README-Abschnitt
|
||||||
|
„Where this repo lives, and what a Gitea runner does differently" und als
|
||||||
|
Kommentar an der Secret-Deklaration von dessen `install-test.yml`.
|
||||||
|
|
||||||
|
Wiederverwendbare Workflows mit `secrets: inherit` funktionieren auf dieser
|
||||||
|
Instanz. Die Eigenheit: Fehlt ein Secret, das der aufgerufene Workflow als
|
||||||
|
`required: true` deklariert, **scheitert nicht der Job, sondern seine
|
||||||
|
Erzeugung** — der Lauf steht dann ohne Schritte da. Nichts in ihm benennt die
|
||||||
|
Ursache, und man debuggt mit Vorliebe das Falsche: das Workflow-YAML, den
|
||||||
|
Runner, die Action.
|
||||||
|
|
||||||
|
Daraus folgt, dass `required` an einem Secret eine echte Entscheidung ist und
|
||||||
|
keine Formalie:
|
||||||
|
|
||||||
|
- Braucht **jeder** Aufrufer das Secret, ist `required: true` richtig — so hält
|
||||||
|
es [publish-helm-chart](publish-helm-chart), denn ohne Connect-Token kann
|
||||||
|
niemand veröffentlichen. Dass der Lauf ohne Schritte dasteht, ist dann der
|
||||||
|
verständliche Fall: Es fehlt genau diese Voraussetzung.
|
||||||
|
- Braucht nur **ein Teil** des Bausteins es, muss es `required: false` sein. Im
|
||||||
|
Herkunfts-Repository hängt das Token an genau einem Zweig einer Matrix; mit
|
||||||
|
`required: true` verlöre ein Pull Request aus einem Fork — der keine Secrets
|
||||||
|
trägt — die gesamte Prüfung, jedes Chart auf einmal, ohne dass etwas die
|
||||||
|
Ursache nennt. Optional deklariert scheitert nur der eine Zweig, und zwar laut
|
||||||
|
an einem Wächter, der das fehlende Tresor-Feld benennt.
|
||||||
|
|
||||||
|
Dieselbe Linie wie an jeder anderen Stelle dieses Repositories: Wenn etwas
|
||||||
|
fehlen kann, soll es laut und benannt fehlen, nicht still.
|
||||||
|
|
||||||
|
## Wandernde Action-Verweise
|
||||||
|
|
||||||
|
[Issue #5](https://git.jk-dev.de/workflow-util/pipelines/issues/5), Punkt 1.
|
||||||
|
|
||||||
|
Ein Verweis auf einen Zweig statt auf eine Hauptversion holt bei jedem Lauf den
|
||||||
|
jeweils neuesten Stand. Eine Änderung dort ändert das Verhalten **aller**
|
||||||
|
Projekte, ohne dass hier etwas angefasst wurde — und der Fehler zeigt sich bei
|
||||||
|
einem Aufrufer, der nichts geändert hat.
|
||||||
|
|
||||||
|
Das ist dieselbe Mechanik, wegen der das README von Aufrufern verlangt, auf
|
||||||
|
einen Tag statt auf den Hauptzweig zu zeigen: Ein beweglicher Verweis macht aus
|
||||||
|
dem Übernehmen einer neuen Fassung eine unbeobachtete Nebenwirkung statt einer
|
||||||
|
Entscheidung. Nach innen gilt die Regel genauso wie nach außen, sonst ist die
|
||||||
|
Sorgfalt an der Aufrufgrenze wertlos.
|
||||||
|
|
||||||
|
In `docker-build-push` steht noch ein solcher Verweis — die Buildx-Action zeigt
|
||||||
|
auf einen Zweig, während die übrigen Actions auf Hauptversionen gepinnt sind.
|
||||||
|
Festgehalten als bekannte Schwachstelle unter
|
||||||
|
[docker-build-push](docker-build-push), damit sie nicht erneut entdeckt werden
|
||||||
|
muss.
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
# pipelines
|
||||||
|
|
||||||
|
Geteilte CI/CD-Bausteine für die Gitea-Instanz `git.jk-dev.de`. Die Workflows in
|
||||||
|
diesem Repository lösen sich nicht selbst aus; sie werden von anderen
|
||||||
|
Repositories über `uses:` aufgerufen (`on: workflow_call`).
|
||||||
|
|
||||||
|
## Die Bausteine
|
||||||
|
|
||||||
|
| Baustein | Was er tut |
|
||||||
|
| --- | --- |
|
||||||
|
| [docker-build-push](docker-build-push) | Baut ein Docker-Image und schiebt es als Multi-Arch-Manifest in eine Registry. Mit abgeschaltetem Push dient derselbe Baustein als Bau-Prüfung. |
|
||||||
|
| [publish-helm-chart](publish-helm-chart) | Packt ein Helm-Chart und lädt es in eine klassische, indexbasierte Helm-Registry. |
|
||||||
|
|
||||||
|
Dazu eine Seite quer zu beiden: [Gemessenes-Verhalten](Gemessenes-Verhalten) —
|
||||||
|
die Eigenheiten dieser Forge, die hier Geld gekostet haben, mit dem Lauf oder
|
||||||
|
dem Issue, an dem sie gemessen wurden.
|
||||||
|
|
||||||
|
## Die zwei Hausregeln
|
||||||
|
|
||||||
|
**An einen Tag binden, nie an `@main`.** Mit `@main` ändert jede Anpassung hier
|
||||||
|
unangekündigt das Verhalten sämtlicher Projekte, und der Fehler zeigt sich dann
|
||||||
|
dort, wo niemand etwas geändert hat.
|
||||||
|
|
||||||
|
**Ein Eigentümer-Segment wird nie geraten.** Jeder Baustein, der einen
|
||||||
|
Eigentümer in eine Adresse einsetzt — `<registry>/<owner>/<image>` ebenso wie
|
||||||
|
`<basis>/<owner>/helm/api/charts` —, nimmt ihn als Pflichteingabe ohne Vorgabe.
|
||||||
|
Genau das war der Fehler des früher fest verdrahteten Eigentümers, unter dem die
|
||||||
|
Images aller Projekte in einem fremden Konto landeten.
|
||||||
|
|
||||||
|
## Was hier steht und was im README
|
||||||
|
|
||||||
|
Das `README.md` im Repository ist die **Kurzreferenz für Aufrufer**: was ein
|
||||||
|
Baustein tut, seine Eingaben als Tabelle, ein Aufrufbeispiel, die
|
||||||
|
Voraussetzungen im aufrufenden Repository. Wer einen Aufruf schreibt, braucht
|
||||||
|
nur das.
|
||||||
|
|
||||||
|
Dieses Wiki ist die **Langform**: warum die Dinge so sind, wie sie sind, und was
|
||||||
|
dafür gemessen wurde. Es wiederholt das README nicht, es begründet es.
|
||||||
|
|
||||||
|
Eine Grenze, die beide Seiten betrifft: **Tag-Nummern und Fassungen stehen nicht
|
||||||
|
hier.** Welcher Tag aktuell zu verwenden ist, steht im README und in der
|
||||||
|
jeweiligen Workflow-Datei; eine Nummer in diesem Wiki wäre eine Momentaufnahme,
|
||||||
|
die beim nächsten Tag still falsch wird. Was hier steht, sind Begründungen und
|
||||||
|
Messungen — die veralten nicht.
|
||||||
+133
@@ -0,0 +1,133 @@
|
|||||||
|
# docker-build-push
|
||||||
|
|
||||||
|
`.gitea/workflows/docker-build-push.yml` — baut ein Docker-Image und schiebt es
|
||||||
|
als Multi-Arch-Manifest in eine Registry. Die Eingabe-Tabelle, das
|
||||||
|
Aufrufbeispiel und die Voraussetzungen stehen im README; diese Seite begründet,
|
||||||
|
warum der Baustein so aussieht.
|
||||||
|
|
||||||
|
## Warum `image-owner` Pflicht ist und `registry` nicht
|
||||||
|
|
||||||
|
Der Eigentümer ist je Projekt verschieden, die Registry praktisch immer
|
||||||
|
dieselbe. Deshalb hat `registry` eine Vorgabe und `image-owner` keine.
|
||||||
|
|
||||||
|
Das ist keine Geschmacksfrage: Der Eigentümer war früher fest verdrahtet, und
|
||||||
|
die Folge war, dass die Images **aller** aufrufenden Projekte unter demselben
|
||||||
|
fremden Konto landeten. Ein geratener Eigentümer scheitert nicht, er schiebt das
|
||||||
|
Image woandershin — und das fällt erst auf, wenn jemand es dort sucht, wo es
|
||||||
|
hingehört. Die Regel steht daher allgemein auf [Home](Home): Ein
|
||||||
|
Eigentümer-Segment wird nie geraten.
|
||||||
|
|
||||||
|
## Warum `push` eine Zeichenkette ist und kein Boolean
|
||||||
|
|
||||||
|
Weil ein boolescher `workflow_call`-Eingang auf dieser Forge-Fassung nicht
|
||||||
|
ankommt — nicht einmal seine Vorgabe. Der Baustein baute dadurch immer im
|
||||||
|
Prüfmodus und meldete dabei Erfolg. Die Messung, die Läufe und die Eingrenzung
|
||||||
|
stehen auf [Gemessenes-Verhalten](Gemessenes-Verhalten).
|
||||||
|
|
||||||
|
Zwei Dinge, die man dazu wissen muss:
|
||||||
|
|
||||||
|
**Die Anführungszeichen sind nicht kosmetisch.** `push: false` ohne sie ist im
|
||||||
|
YAML ein Boolean und kommt nicht an; es griffe dann die Vorgabe, und der Lauf
|
||||||
|
veröffentlichte trotz `push: false`. Richtig ist `push: 'false'`.
|
||||||
|
|
||||||
|
**Die Bedingung musste bei der Umstellung nicht angefasst werden.** Sie lautet
|
||||||
|
`inputs.push && inputs.push != 'false'` und war von Anfang an für beide
|
||||||
|
Schreibweisen gebaut: `inputs.push` allein wäre bei der Zeichenkette `'false'`
|
||||||
|
wahr, weil nichtleere Zeichenketten truthy sind; `inputs.push == 'true'` wäre
|
||||||
|
beim echten Boolean `true` falsch, weil ein Vergleich über Typgrenzen über
|
||||||
|
Zahlen läuft und `'true'` dabei zu `NaN` wird. Die Kombination deckt alle vier
|
||||||
|
Fälle ab.
|
||||||
|
|
||||||
|
Wer das beim nächsten Aufräumen „korrekt" auf `boolean` zurückstellt, holt den
|
||||||
|
Fehler still zurück: Es wird nichts veröffentlicht, und der Lauf bleibt grün.
|
||||||
|
Darum steht die Begründung auch als Kommentar an der Eingabe selbst.
|
||||||
|
|
||||||
|
## Der Prüfmodus und sein Preis
|
||||||
|
|
||||||
|
Mit abgeschaltetem Push baut der Baustein nur. Der Zweck ist, dass die CI eines
|
||||||
|
Projekts dieselbe Bau-Logik benutzt wie das Deployment, statt sie zu
|
||||||
|
duplizieren.
|
||||||
|
|
||||||
|
Der Preis hängt an einer Kopplung, die nicht offensichtlich ist: Ohne Push wird
|
||||||
|
der 1Password-Schritt übersprungen, und aus **demselben** Schritt stammt der
|
||||||
|
Checkout-Token. Der Checkout fällt dann auf den Standard-Token des Jobs zurück.
|
||||||
|
Der genügt für das ausführende Repository, nicht aber für Submodule in eigenen
|
||||||
|
privaten Repositories — deren Klon scheitert mit `Repository not found`.
|
||||||
|
|
||||||
|
Daraus folgt: Projekte **mit** privaten Submodulen können den Prüfmodus nicht
|
||||||
|
verwenden. Für Projekte ohne Submodule ist er unproblematisch.
|
||||||
|
|
||||||
|
## Warum zwei verschiedene Zugänge
|
||||||
|
|
||||||
|
Der Token, den die Forge jedem Job stellt, taugt für zwei Dinge nicht: Er gilt
|
||||||
|
nur für das ausführende Repository, während die Submodule in eigenen privaten
|
||||||
|
Repositories liegen, und die Container-Registry lehnt ihn ab
|
||||||
|
(`container.Authenticate` antwortet mit 401). Beides deckt je ein eigener Zugang
|
||||||
|
ab, gepflegt in 1Password und zur Laufzeit über den Connect-Server im Cluster
|
||||||
|
geholt. In der Forge liegt nur dessen Zugang.
|
||||||
|
|
||||||
|
Benutzername und Token für die Registry kommen aus **einem** Eintrag, damit sie
|
||||||
|
zwangsläufig zusammenpassen — die Registry prüft den Token gegen genau das
|
||||||
|
Konto, das im Feld `username` steht.
|
||||||
|
|
||||||
|
## Warum es keinen `env`-Block auf Workflow-Ebene gibt
|
||||||
|
|
||||||
|
`env` auf Workflow-Ebene wird ausgewertet, **bevor** es einen `inputs`-Kontext
|
||||||
|
gibt. Registry und Eigentümer standen dort früher; als sie Eingaben wurden,
|
||||||
|
konnten sie nicht bleiben. Sie werden deshalb an den beiden Stellen gelesen, an
|
||||||
|
denen der Kontext sicher vorhanden ist: im Login-Schritt und in der Tag-Liste.
|
||||||
|
|
||||||
|
Dieselbe Falle gilt für jeden künftigen Baustein hier.
|
||||||
|
|
||||||
|
## Die Vorgaben der Versionsangaben sind absichtlich unecht
|
||||||
|
|
||||||
|
Die vier Versionsangaben dienen ausschließlich der Tag-Liste. Ihre Vorgabe ist
|
||||||
|
ein sichtbar unechter Wert und nicht leer, weil die Tag-Liste auch ohne Push
|
||||||
|
gebildet wird: Die Bau-Action gibt sie als `--tag` an den Build weiter, und ein
|
||||||
|
leerer Wert ergäbe `<registry>/<owner>/<name>:` — der Build scheiterte an
|
||||||
|
`invalid reference format`.
|
||||||
|
|
||||||
|
## Der Runner braucht ein Docker-Socket, und das Label entscheidet darüber
|
||||||
|
|
||||||
|
Der Baustein läuft auf einem Runner mit einem eigenen Label, nicht auf dem
|
||||||
|
Standard-Label. Gemessen in `jlab-cluster/helm-charts`: Auf dieser Instanz hat
|
||||||
|
der Standard-Runner **kein** Docker-Socket, der mit dem Docker-Label hat eines.
|
||||||
|
Dasselbe Label bedeutet auf den beiden Forges, auf denen dieses Setup lebt,
|
||||||
|
verschiedene Maschinen.
|
||||||
|
|
||||||
|
Läuft der Daemon des Runners rootless, liegt sein Socket nicht unter
|
||||||
|
`/var/run/docker.sock`, sondern unter `/run/user/<uid>/docker.sock`. Der Pfad
|
||||||
|
kommt deshalb aus einer Variablen des aufrufenden Repositories; ist sie nicht
|
||||||
|
gesetzt, bleibt sie leer und Docker nutzt seinen Standard. Ein eigener,
|
||||||
|
rein informativer Schritt gibt aus, welches Socket im Job-Container ankommt —
|
||||||
|
weil das beim Scheitern des Buildx-Starts immer die erste Frage ist.
|
||||||
|
|
||||||
|
## Bekannte Schwachstellen
|
||||||
|
|
||||||
|
Festgehalten, damit sie nicht erneut entdeckt werden müssen; alle drei in
|
||||||
|
[Issue #5](https://git.jk-dev.de/workflow-util/pipelines/issues/5).
|
||||||
|
|
||||||
|
**Der Buildx-Action-Verweis wandert.** Er zeigt auf einen Zweig statt auf eine
|
||||||
|
Hauptversion. Das widerspricht genau der Regel, die das README von Aufrufern
|
||||||
|
verlangt — und hat dieselbe Folge: Eine Änderung dort ändert das Verhalten aller
|
||||||
|
Projekte, ohne dass hier etwas angefasst wurde. Siehe
|
||||||
|
[Gemessenes-Verhalten](Gemessenes-Verhalten).
|
||||||
|
|
||||||
|
**Die Beschreibung von `platforms` behauptet etwas Falsches.** Sie sagt, die
|
||||||
|
Bau-Stufen aller Dockerfiles seien auf die Bau-Plattform gepinnt, eine
|
||||||
|
zusätzliche Plattform fügte also nur eine Laufzeitstufe hinzu. Für das
|
||||||
|
Dockerfile des ersten Abnehmers stimmt das nicht, und zwar bewusst nicht:
|
||||||
|
Tailwind, lightningcss und der Next-Compiler bringen plattformgebundene
|
||||||
|
Binärmodule mit, ein Cross-Build baute die falsche Architektur ein. Eine
|
||||||
|
zusätzliche Plattform bedeutet dort einen **vollständigen** zweiten
|
||||||
|
Kompilierlauf unter Emulation. Gemessen: 72 Sekunden für eine Plattform gegen
|
||||||
|
417 Sekunden für zwei. Als allgemeine Aussage in einem geteilten Baustein führt
|
||||||
|
der Satz in die Irre; er hat bei einem Abnehmer zu einer Fehleinschätzung
|
||||||
|
beigetragen.
|
||||||
|
|
||||||
|
**Die Tag-Liste erzeugt einen Tag doppelt.** Bei einem dreiteiligen
|
||||||
|
Versionsschema ergeben die vollständige Version und die aus Major, Minor und
|
||||||
|
Patch zusammengesetzte denselben Wert. Harmlos — Docker pusht denselben Tag
|
||||||
|
zweimal —, aber beim Nachzählen verwirrend. Nebenbei: Bei einem Kalenderschema
|
||||||
|
sind die mitwandernden Tags „neuestes Image aus diesem Jahr" beziehungsweise
|
||||||
|
„aus diesem Monat" von zweifelhaftem Nutzen; bei SemVer ergeben sie Sinn.
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
# publish-helm-chart
|
||||||
|
|
||||||
|
`.gitea/workflows/publish-helm-chart.yml` — packt ein Helm-Chart und lädt es in
|
||||||
|
eine Helm-Registry. Die Eingabe-Tabelle, das Aufrufbeispiel und die
|
||||||
|
Voraussetzungen stehen im README; diese Seite begründet den Baustein, und vor
|
||||||
|
allem den Upload-Schritt. Jede seiner Eigenschaften ist einmal bezahlt worden.
|
||||||
|
|
||||||
|
Der Baustein kommt aus `jlab-cluster/helm-charts`, wo er die dünnen
|
||||||
|
`publish-<chart>.yml`-Hüllen bedient, und ist mit dem Umzug hierher an zwei
|
||||||
|
Stellen angepasst worden — siehe „Der Eigentümer ist eine Eingabe" und „Der
|
||||||
|
Connect-Host ist eine Variable des Aufrufers" weiter unten.
|
||||||
|
|
||||||
|
## Die Registry ist klassisch, nicht OCI
|
||||||
|
|
||||||
|
Das Ziel ist eine **indexbasierte** Helm-Registry. Es gibt dort keine
|
||||||
|
Registry-Sitzung, also kein `helm registry login` und kein `helm push`: Der
|
||||||
|
Upload ist ein einzelnes HTTP-`POST` des gepackten `.tgz`, je Anfrage
|
||||||
|
authentifiziert. Das ist der Grund, warum der Schritt `curl` benutzt und nicht
|
||||||
|
`helm` — und damit der Grund für fast alles, was danach kommt. `helm push`
|
||||||
|
würde einen Fehler von selbst zum Fehler machen; `curl` tut das nicht.
|
||||||
|
|
||||||
|
## Die Ziel-URL wird zusammengesetzt, nicht gespeichert
|
||||||
|
|
||||||
|
```
|
||||||
|
<charts-url>/<registry-owner>/helm/api/charts
|
||||||
|
```
|
||||||
|
|
||||||
|
`<charts-url>` liegt im Tresor und enthält **nur** die Basis bis
|
||||||
|
`/api/packages`. Der Eigentümer kommt als Eingabe, Pakettyp und Endpunkt setzt
|
||||||
|
der Workflow selbst an.
|
||||||
|
|
||||||
|
Der Gewinn: Im aufrufenden Repository steht kein Wert, der auf die Registry
|
||||||
|
zeigt — nicht einmal ein Pfadsegment. Und ein zweiter Eigentümer auf derselben
|
||||||
|
Instanz braucht keine zweite Einstellung.
|
||||||
|
|
||||||
|
Der Preis: Eine falsche Form des Tresor-Felds ergibt eine falsche URL, und die
|
||||||
|
URL ist im Protokoll maskiert. Ein 404 dagegen wäre nicht zu deuten. Deshalb
|
||||||
|
weisen drei Wächter eine andere Form **namentlich** zurück und sagen, was im
|
||||||
|
Tresor-Eintrag zu ändern ist:
|
||||||
|
|
||||||
|
- der Wert endet auf `/api/charts` — das ist die alte, vollständige Form;
|
||||||
|
- der Wert enthält das Segment `/helm` — ebenfalls die alte Form;
|
||||||
|
- der Wert endet nicht auf `/api/packages` — irgendetwas anderes.
|
||||||
|
|
||||||
|
## Der Eigentümer ist eine Eingabe
|
||||||
|
|
||||||
|
`registry-owner` ist Pflicht und hat keine Vorgabe. Das folgt der Hausregel von
|
||||||
|
[Home](Home) — ein Eigentümer-Segment wird nie geraten — und ersetzt die frühere
|
||||||
|
Ableitung aus dem Eigentümer des laufenden Repositories.
|
||||||
|
|
||||||
|
Die Ableitung war im Herkunfts-Repository richtig, dort ist der Wert gemessen.
|
||||||
|
Dass der Kontext auch in einem **von fremd** aufgerufenen Workflow der des
|
||||||
|
Aufrufers bleibt, ist dokumentiert, auf dieser Forge-Fassung aber nicht
|
||||||
|
gemessen. Als Eingabe wird der Wert dort ausgewertet, wo er bekannt ist, und der
|
||||||
|
Baustein hängt an keiner Kontext-Semantik — auch nicht an einer zugesicherten.
|
||||||
|
Ein Chart, das unter dem falschen Eigentümer veröffentlicht wird, ist nichts,
|
||||||
|
wofür man sich darauf verlassen müsste.
|
||||||
|
|
||||||
|
Ein zweiter Wächter prüft den Wert auf URL-taugliche Zeichen. Ein leerer
|
||||||
|
Eigentümer ergäbe `<basis>//helm/api/charts`, ein `/` oder `?` darin etwas
|
||||||
|
völlig anderes — beides sind nackte 404 gegen eine maskierte Adresse.
|
||||||
|
|
||||||
|
Alle Eingaben des Bausteins sind `type: string`, auch wenn künftig ein Schalter
|
||||||
|
hinzukommt. Warum: [Gemessenes-Verhalten](Gemessenes-Verhalten).
|
||||||
|
|
||||||
|
## Der Connect-Host ist eine Variable des Aufrufers
|
||||||
|
|
||||||
|
Im Herkunfts-Repository war der 1Password-Connect-Host fest verdrahtet. Hier ist
|
||||||
|
er eine Variable des aufrufenden Repositories — ein geteilter Baustein trägt
|
||||||
|
nicht die Infrastruktur-Adresse eines einzelnen Mandanten.
|
||||||
|
|
||||||
|
Der Preis ist eine Voraussetzung, die man vergessen kann, und eine nicht
|
||||||
|
gesetzte Variable kommt als **leere Zeichenkette** an. Ein leerer Host scheitert
|
||||||
|
nicht dort, wo er gesetzt wird, sondern zwei Schritte später in der Lade-Action,
|
||||||
|
gegen eine Adresse, die niemand sieht. Deshalb prüft der erste Schritt des Jobs
|
||||||
|
die Variable und benennt beim Abbruch die Einstellung, den Ort und die erwartete
|
||||||
|
Form.
|
||||||
|
|
||||||
|
## Die Abhängigkeiten gehören dem Aufrufer
|
||||||
|
|
||||||
|
`ci/helm-dependencies.sh` ist ein Skript des **aufrufenden** Repositories, und
|
||||||
|
das bleibt nach dem Umzug so: Der Checkout holt den Baum des Repositories, zu
|
||||||
|
dem der Lauf gehört — den des Aufrufers —, also ist der Arbeitsbereich dessen
|
||||||
|
Baum und das Skript dessen Kopie. Es ist damit eine Voraussetzung jedes
|
||||||
|
Aufrufers, genauso wie der Connect-Host, und ein Wächter sagt das beim Fehlen,
|
||||||
|
statt die Shell eine fehlende Datei melden zu lassen.
|
||||||
|
|
||||||
|
Der Aufruf steht **vor** `helm package`, weil `helm package` mit
|
||||||
|
`found in Chart.yaml, but missing in charts/ directory` abbricht, solange eine
|
||||||
|
deklarierte Abhängigkeit nicht geholt ist. Für ein Chart ohne `dependencies:`
|
||||||
|
tut das Skript nichts.
|
||||||
|
|
||||||
|
Eine Folge für das veröffentlichte Artefakt, die man kennen muss: Das `.tgz` des
|
||||||
|
Subcharts wird **in** unseres gepackt. Die in `Chart.yaml` gepinnte Version ist
|
||||||
|
damit die Version, die Betreiber bekommen, und nichts stromabwärts sieht die
|
||||||
|
Abhängigkeit jemals wieder — ein Sicherheitsupdate des Subcharts erreicht einen
|
||||||
|
Cluster erst, wenn das äußere Chart neu veröffentlicht wird.
|
||||||
|
|
||||||
|
## Der Upload-Schritt, Eigenschaft für Eigenschaft
|
||||||
|
|
||||||
|
Dies ist der Kern der Seite. Jeder Punkt ist einmal teuer gewesen; wer einen
|
||||||
|
davon als Umstand entfernt, holt den jeweiligen Fehler zurück.
|
||||||
|
|
||||||
|
### `--fail-with-body`, sonst ist ein 401 ein Erfolg
|
||||||
|
|
||||||
|
`curl` verlässt einen HTTP-Fehler mit Code **0**, solange man nichts anderes
|
||||||
|
verlangt. Ohne dieses Flag wäre die Veröffentlichung also grün, während nichts
|
||||||
|
hochgeladen wurde. Gemessen gegen Gitea 1.24.7: Ein 401 ohne das Flag ist
|
||||||
|
Exit 0, mit dem Flag Exit 22 — und der Antwortkörper wird weiterhin ausgegeben,
|
||||||
|
sodass das Protokoll die Ursache nennt.
|
||||||
|
|
||||||
|
Das ist der wichtigste Unterschied zu `helm push`, das von selbst scheitert.
|
||||||
|
|
||||||
|
### `curl --config -` statt `--user`
|
||||||
|
|
||||||
|
`curl --user $U:$P` schreibt die Zugangsdaten in `argv`, wo sie in der
|
||||||
|
Prozessliste stehen. `curl --config -` liest seine Optionen stattdessen von der
|
||||||
|
Standardeingabe; nichts Sensibles erscheint in irgendeinem Argument.
|
||||||
|
|
||||||
|
Der Preis dieser Konfigurationsdatei ist ihre Wertsyntax: Ein **unquotierter
|
||||||
|
Wert endet am ersten Leerzeichen**. Gemessen an curl 8.5.0 — aus `has space`
|
||||||
|
kam `has` an. Ein Leerzeichen im Passwort käme deshalb als rätselhafter 401
|
||||||
|
zurück. Der Schritt weist Leerzeichen in den Zugangsdaten vorher ab und sagt
|
||||||
|
warum. Ein Zugriffstoken dieser Forge ist 40 Hexzeichen lang, der Wächter
|
||||||
|
greift also nur bei einem falsch eingetragenen Tresor-Eintrag.
|
||||||
|
|
||||||
|
### Der Dateiname wird gebaut, nicht geglobt
|
||||||
|
|
||||||
|
`--upload-file` nimmt **genau eine** Datei. Ein Glob, der zwei trifft, macht die
|
||||||
|
zweite zum URL-Argument von `curl`; ein Glob, der keine trifft, wird wörtlich
|
||||||
|
durchgereicht. Beides scheitert unverständlich.
|
||||||
|
|
||||||
|
Der Name wird deshalb aus Chart-Name und gelesener Version zusammengesetzt, und
|
||||||
|
ein `test -f` prüft ihn vorher — beim Fehlschlag listet die Meldung auf, was
|
||||||
|
`helm package` tatsächlich erzeugt hat. Dass das zusammenpasst, ist gemessen:
|
||||||
|
`helm package` behält das `+` einer Version der Form `1.4.0+up1.9.2` wörtlich im
|
||||||
|
Dateinamen, `<name>-<version>.tgz` ist also eine exakte Entsprechung.
|
||||||
|
|
||||||
|
### Die Werte stehen im `env` des Schritts, nicht in der Kommandozeile
|
||||||
|
|
||||||
|
Sie werden als quotierte Shell-Variablen gelesen und nie in die Kommandozeile
|
||||||
|
interpoliert. Jeder Wert hat einen `:?`-Wächter, der beim Fehlen **das fehlende
|
||||||
|
`op://`-Feld benennt**.
|
||||||
|
|
||||||
|
Warum das nötig wurde: Mit dem Sprung auf die neue Hauptfassung der
|
||||||
|
1Password-Lade-Action exportiert sie die geladenen Werte **nicht mehr** als
|
||||||
|
Umgebungsvariablen für Folgeschritte, wie die Vorfassung es tat; nur noch die
|
||||||
|
Step-Outputs tragen sie. Jeder Verweis über die Umgebung löste von diesem Sprung
|
||||||
|
an zu einer leeren Zeichenkette auf — und **nichts meldete einen Fehler**: Der
|
||||||
|
Ladeschritt protokolliert „Populating variable" und bleibt so oder so grün. Die
|
||||||
|
Wächter scheitern laut, und das Quoten verhindert, dass ein leerer Wert
|
||||||
|
stillschweigend die Argumente verschiebt.
|
||||||
|
|
||||||
|
### Der Host wird zusätzlich maskiert
|
||||||
|
|
||||||
|
1Password maskiert die vollständige URL im Protokoll. Ein Verbindungsfehler von
|
||||||
|
`curl` gibt aber nur `<host>:<port>` aus, und das deckt diese Maske nicht. Der
|
||||||
|
Schritt leitet den Host aus der rohen URL ab und maskiert ihn selbst, bevor
|
||||||
|
sonst etwas läuft.
|
||||||
|
|
||||||
|
## Was der Baustein nicht tut
|
||||||
|
|
||||||
|
Er löst sich nicht selbst aus und hat keinen `workflow_dispatch`. Ein Dispatch
|
||||||
|
hier liefe gegen dieses Repository, in dem kein Chart liegt. Wann veröffentlicht
|
||||||
|
wird, entscheidet die Hülle im aufrufenden Repository über ihr eigenes `on:` —
|
||||||
|
üblicherweise ein Push auf den Hauptzweig, eingeschränkt auf das
|
||||||
|
Chart-Verzeichnis, dazu ein `workflow_dispatch` für den Fall, dass von Hand
|
||||||
|
nachgeschoben werden muss.
|
||||||
|
|
||||||
|
Er prüft das Chart auch nicht. Lint, Render und Install-Test sind Sache des
|
||||||
|
aufrufenden Repositories, weil sie an dessen Fixtures hängen. Ein Baustein, den
|
||||||
|
nur ein Repository benutzen kann, wäre nicht geteilt, sondern verlagert.
|
||||||
Reference in new issue
Block a user