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