Ein GitHub-Actions-Schritt zum Kürzen von URLs besteht aus wenigen Shell-Zeilen: einen API-Schlüssel aus einem verschlüsselten Secret lesen, prüfen, ob der Slug bereits existiert, und dann entweder sein Ziel aktualisieren oder ihn erstellen. Führen Sie ihn bei jedem Push aus, und derselbe Kurzlink verweist immer auf die neueste Vorschau, den neuesten Dokumentations-Build oder das neueste Artefakt. Eine Aktion aus dem Marketplace ist nicht nötig. curl und jq sind auf jedem von GitHub gehosteten Ubuntu-Runner vorhanden.
Das ist die ganze Antwort. Der Rest dieses Beitrags sorgt dafür, dass sie auch über einige hundert Workflow-Läufe hinweg Bestand hat. Wer danach sucht, wie man in GitHub Actions einen Kurzlink erstellt, kommt meist bis zu einem einzelnen POST-Request, und der funktioniert genau bis zum zweiten Push auf denselben Pull Request: Dann liefert der Erstellungsaufruf einen Konflikt zurück und der Job schlägt fehl. Die Lösung besteht darin, den Schritt als Upsert und nicht als Erstellung zu behandeln. Das andere Problem ist der Schlüssel selbst, der häufig ein persönliches Token mit weit mehr Reichweite ist, als ein CI-Job benötigt.
Wenn Sie Links bereits als Code verwalten, ist Kurzlinks als Terraform die deklarative Variante derselben Idee und besser für Links geeignet, die sich nach einem menschlichen Zeitplan ändern. Ein Workflow-Schritt ist die bessere Wahl, wenn das Ziel erst nach Abschluss eines Builds existiert.
So funktioniert ein GitHub-Actions-Schritt zum Kürzen von URLs
Jeder Lauf führt über die REST-API unter https://api.elido.app/v1 dieselben drei Aktionen aus. Er listet die Links des Workspace, gefiltert nach dem Slug. Er sendet ein PATCH an den gefundenen Link oder ein POST, wenn er nichts gefunden hat. Er schreibt die Kurz-URL nach $GITHUB_OUTPUT, damit der nächste Schritt sie verwenden kann.
Warum soll der URL-Shortener keinen zufälligen Slug erzeugen? Weil Sie den Link dann nicht wiederfinden. Der Slug muss aus etwas stammen, das der Workflow bei jedem Lauf bereits kennt: der Pull-Request-Nummer, dem Branch-Namen oder einem festen Wort wie latest. Ein stabiler Slug bedeutet eine stabile Kurz-URL, und genau darin liegt der gesamte Wert für Reviewer, die sie als Lesezeichen speichern, oder Produktmanager, die sie in ein Ticket einfügen.
Den API-Schlüssel als verschlüsseltes Secret speichern
Erstellen Sie den Schlüssel im Dashboard, kopieren Sie ihn einmal (er wird genau einmal angezeigt und beginnt mit elido_) und speichern Sie ihn unter Settings, dann Secrets and variables, dann Actions, als ELIDO_API_KEY. Der GitHub-Leitfaden zur Verwendung von Secrets in GitHub Actions behandelt die Ebenen Repository, Umgebung und Organisation. Für alles, was etwas bereitstellt, würde ich ihn in einer Umgebung mit erforderlichen Reviewern hinterlegen, damit ein versehentlicher Branch ihn nicht verwenden kann.
Drei Werte sind nicht geheim und gehören in Konfigurationsvariablen, wo Sie sie wieder auslesen können: ELIDO_WORKSPACE_ID, ELIDO_DOMAIN_ID und ELIDO_HOST. Die Domain-ID ist wichtig, weil der Erstellungsaufruf sie verlangt. Sie können sie einmal mit GET /v1/workspaces/{workspace_id}/domains abfragen; dieser Aufruf liefert die id und den hostname jeder Domain zurück.
Ordnen Sie das Secret nur dem einzelnen Schritt zu, der die API aufruft, nicht dem gesamten Job. Ein schrittbezogenes env hält es aus allen anderen Prozessen heraus, die der Job startet, einschließlich der Drittanbieter-Actions, die Sie nicht selbst geschrieben haben.
Ein funktionierender Workflow zum Kürzen einer URL bei jedem Pull Request
Dies ist die vollständige Datei für den häufigsten Grund, eine URL in einem GitHub-Workflow zu kürzen: ein Vorschau-Link pro Pull Request. Legen Sie sie unter .github/workflows/preview-link.yml ab und ändern Sie die Zeile DEST so, dass sie auf den Ort zeigt, an dem Ihre Vorschau-Bereitstellungen landen.
name: Preview short link
on:
pull_request:
types: [opened, reopened, synchronize]
permissions:
contents: read
pull-requests: write
concurrency:
group: preview-link-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
short-link:
# Forks get no secrets; skip them instead of failing.
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
env:
API: https://api.elido.app/v1/workspaces/${{ vars.ELIDO_WORKSPACE_ID }}
DOMAIN_ID: ${{ vars.ELIDO_DOMAIN_ID }}
HOST: ${{ vars.ELIDO_HOST }}
SLUG: pr-${{ github.event.pull_request.number }}-myapp
DEST: https://pr-${{ github.event.pull_request.number }}.preview.example.com
steps:
- name: Create or update the short link
id: link
env:
ELIDO_API_KEY: ${{ secrets.ELIDO_API_KEY }}
run: |
set -euo pipefail
auth=(-H "Authorization: Bearer $ELIDO_API_KEY" -H "Content-Type: application/json")
# 1. Find an existing link with exactly this slug on this domain.
link_id=$(curl -sS --fail-with-body "${auth[@]}" "$API/links?q=$SLUG&limit=100" \
| jq -r --arg s "$SLUG" --argjson d "$DOMAIN_ID" \
'.items[] | select(.slug == $s and .domain_id == $d) | .id' | head -n1)
if [ -n "$link_id" ]; then
# 2a. Found: point it at the new destination.
curl -sS --fail-with-body -X PATCH "${auth[@]}" "$API/links/$link_id" \
-d "$(jq -n --arg u "$DEST" '{destination_url: $u, status: "active"}')" > /dev/null
else
# 2b. Not found: create it. The key makes curl's retries safe.
curl -sS --fail-with-body --retry 3 -X POST "${auth[@]}" "$API/links" \
-H "Idempotency-Key: $GITHUB_REPOSITORY-$SLUG-$GITHUB_RUN_ID" \
-d "$(jq -n --arg u "$DEST" --arg s "$SLUG" --argjson d "$DOMAIN_ID" \
'{domain_id: $d, slug: $s, destination_url: $u, tags: ["ci", "preview"]}')" > /dev/null
fi
echo "url=https://$HOST/$SLUG" >> "$GITHUB_OUTPUT"
- name: Comment once, when the pull request opens
if: github.event.action == 'opened'
env:
GH_TOKEN: ${{ github.token }}
run: |
gh pr comment "${{ github.event.pull_request.number }}" \
--repo "${{ github.repository }}" \
--body "Preview: ${{ steps.link.outputs.url }}"
Einige Zeilen verdienen eine Erläuterung. --fail-with-body macht einen 4xx- oder 5xx-Fehler zu einem fehlgeschlagenen Schritt und gibt trotzdem den Fehlertext aus, was einfaches curl -s nicht tut; bei einem 401 beendet es sich mit 0 und der Job läuft zufrieden weiter. Die Request-Bodies werden mit jq -n statt durch String-Interpolation erstellt, sodass ein Ziel mit Anführungszeichen oder einem kaufmännischen Und das JSON nicht beschädigen kann. Und der Kommentarschritt läuft nur bei opened. Da sich die Kurz-URL nie ändert, bleibt ein Kommentar während der gesamten Lebensdauer des PRs korrekt, und niemand wird bei jedem Push benachrichtigt.
Der concurrency-Block ist keine Dekoration. Zwei schnelle Pushes würden sonst zwei Läufe starten, die beide "noch kein Link" sehen und beide versuchen, einen zu erstellen. Die GitHub-Dokumentation zur Steuerung der Workflow-Konkurrenz erklärt die Gruppierung. Hier wird der ältere Lauf abgebrochen, sodass der Wettlauf gar nicht entsteht.
Idempotenz: Den Kurzlink aktualisieren, nicht duplizieren
Unter dem Wort idempotent verbergen sich zwei verschiedene Fehler, und der Workflow behandelt sie getrennt. Der erste ist der erneute Lauf: ein zweiter Push, ein manuelles "Re-run jobs" oder ein wiedereröffneter PR. Dafür ist der Zweig mit Suche und anschließendem PATCH zuständig. Der zweite ist der wiederholte Request: curl sendet den POST, das Netzwerk bricht ab, bevor die Antwort eintrifft, und curl sendet ihn erneut. Der Idempotency-Key-Header deckt diesen Fall ab. Elido speichert die erste erfolgreiche Antwort 24 Stunden lang unter dem Schlüssel und gibt sie bei einem passenden Wiederholungsversuch erneut aus, sodass die Erstellung einmal erfolgt. Die vollständige Funktionsweise beschreibt Ratenlimits, Wiederholungen und Idempotenz.
Slug-Kollisionen sind der Teil, den viele übersehen. Slugs sind pro Redirect-Domain eindeutig, nicht pro Workspace. Auf einer gemeinsam genutzten Domain befindet sich jeder andere Elido-Kunde im selben Namensraum, und ein schlichter Slug wie pr-12 wurde wahrscheinlich bereits vergeben. Die Suche sieht dessen Link nicht (sie listet nur Ihren Workspace), daher wird der POST gesendet und kommt mit 409 slug already exists for this domain zurück. Es gibt zwei Lösungen: Fügen Sie ein Projektwort zum Slug hinzu oder legen Sie CI-Links auf Ihrer eigenen Custom Domain ab, deren Namensraum ausschließlich Ihnen gehört. Ich würde beides tun.
Es gibt noch einen zweiten, weniger offensichtlichen Grund, warum der Repository-Name am Ende des Slugs statt am Anfang steht. Der Parameter q sucht als Teilstring in Slug, Ziel und Titel. Bei myapp-pr-1 liefert die Suche auch myapp-pr-10 bis myapp-pr-199, also mehr als die 100 Ergebnisse einer einzelnen Seite. Der gesuchte Link, als ältester, fällt dann am Ende heraus. pr-1-myapp findet nichts außer sich selbst. Ein kleines Detail, dessen Entdeckung mich einen beschämend langen Nachmittag lang mit der Frage "Warum erhält PR #1 immer einen 409?" beschäftigt hat.
Drei CI-Kurzlink-Jobs, deren Automatisierung sich lohnt
Der Vorschau-Workflow ist ein Muster. Ändern Sie Trigger, Slug und Ziel, und derselbe Schritt deckt das meiste ab, was Teams tatsächlich automatisieren. (Release-Notes sind ein eigenes Thema, das in Links in Release-Notes kürzen behandelt wird.)
| Anwendungsfall | Trigger | Slug | Aufgabe des Schritts |
|---|---|---|---|
| Vorschau-Bereitstellung pro PR | pull_request | pr-42-myapp | Upsert bei jedem Push, beim Schließen löschen |
| Dokumentations-Bereitstellung | push auf main | docs-myapp | PATCH auf die frisch bereitgestellte Dokumentations-URL |
| Neuester Build | push auf main oder einen Tag | latest-myapp | PATCH über eine gespeicherte Link-ID, keine Suche |
| Nächtliches Artefakt | schedule | nightly-myapp | PATCH auf die neueste Artefakt-URL |
Der Fall mit dem neuesten Build ist der einfachste. Erstellen Sie den Link einmal von Hand, speichern Sie seine numerische ID als Variable, und der Job schrumpft auf einen einzigen Aufruf:
- name: Point the latest link at this build
env:
ELIDO_API_KEY: ${{ secrets.ELIDO_API_KEY }}
API: https://api.elido.app/v1/workspaces/${{ vars.ELIDO_WORKSPACE_ID }}
DEST: https://builds.example.com/${{ github.sha }}/
run: |
curl -sS --fail-with-body -X PATCH \
-H "Authorization: Bearer $ELIDO_API_KEY" -H "Content-Type: application/json" \
"$API/links/${{ vars.ELIDO_LATEST_LINK_ID }}" \
-d "$(jq -n --arg u "$DEST" '{destination_url: $u}')"
Belassen Sie diese wechselnden Links bei einem 302, dem Standardwert, wenn Sie redirect_status nicht setzen. Ein 301 teilt Browsern mit, dass sie die Antwort zwischenspeichern dürfen, und wer gestern geklickt hat, landet weiterhin beim Build von gestern. Unsere ausführliche Erklärung zu 301- und 302-Redirects behandelt die Details.
Räumen Sie Vorschau-Links auf, wenn der PR geschlossen wird. Fügen Sie closed zu den Trigger-Typen hinzu, verwenden Sie die Suche erneut und senden Sie DELETE /v1/workspaces/{workspace_id}/links/{link_id}. Ein gelöschter Slug kann wiederverwendet werden. Wenn Sie die Klickhistorie lieber behalten möchten, senden Sie stattdessen ein PATCH mit {"status": "disabled"}. Das obige Upsert setzt bei jedem Lauf status: "active", sodass ein wiedereröffneter PR seinen Link zurückbekommt.
Bereit, es in einem Repository auszuprobieren? Starten Sie einen kostenlosen Workspace, erstellen Sie einen Schlüssel, und der obige Workflow läuft unverändert, sobald die drei Variablen gesetzt sind.
API-Schlüssel mit geringsten Berechtigungen für CI
Der Schlüssel in einem CI-Secret sollte genau das können, was der Workflow tut, und nicht mehr. Das ist schwieriger, als es klingt, weil persönliche Schlüssel auf eine bestimmte Weise funktionieren.
Ein persönlicher API-Schlüssel authentifiziert als die Person, die ihn erstellt hat. Alles, was diese Person tun kann, kann auch der Schlüssel tun, und wenn sie das Unternehmen verlässt, bleibt der Schlüssel mit ihrem Konto verbunden. Für CI würde ich stattdessen einen Maschinenbenutzer verwenden: ein Dienstkonto, das zu einem Workspace gehört, seine eigene Rolle besitzt und Tokens hat, die nur ein angemeldeter menschlicher Administrator ausstellen oder widerrufen kann. Erstellen Sie ihn im Dashboard unter Machine users mit der Editor-Rolle, der niedrigsten integrierten Rolle, die Links erstellen, bearbeiten und löschen kann, und stellen Sie anschließend ein Token mit einem Ablaufdatum dafür aus. Wenn Sie den Maschinenbenutzer deaktivieren, werden alle seine Tokens auf einmal ungültig. Genau diese Schaltfläche möchten Sie an dem Tag haben, an dem ein Secret durchsickert.
Vier weitere Gewohnheiten kosten nichts:
- Ein Token pro Repository, nach dem Repository benannt, damit der Audit-Trail zeigt, welches Repository welchen Link erstellt hat.
- Umgebungs-Secrets mit erforderlichen Reviewern für jeden Workflow, der einen Link ändert, auf den sich andere verlassen.
permissions:wie im Beispiel am Anfang des Workflows ausdrücklich setzen, damit derGITHUB_TOKENnur erhält, was der Job benötigt.- Niemals
pull_request_targetverwenden, um bei Fork-PRs an das Secret zu gelangen. Der Beitrag des GitHub Security Lab zur Verhinderung von Pwn Requests zeigt, warum das Ausführen nicht vertrauenswürdigen Codes neben einem Token mit Schreibzugriff schlecht endet.
Workspaces können den API-Zugriff auch durch eine IP-Allowlist einschränken. Das ist eine starke Kontrolle für selbst gehostete Runner mit festem ausgehendem Datenverkehr und nahezu nutzlos für von GitHub gehostete Runner, deren Adressen aus einem sehr großen, wechselnden Pool stammen. Die Referenz zur sicheren Verwendung von GitHub ist eine Stunde wert, wenn Ihre Workflows die Produktion berühren.
Was in der Praxis schiefgeht
Die meisten Fehler haben vier Ursachen, und jeder davon erscheint als lesbarer Fehler, wenn --fail-with-body aktiviert ist. Ein 404 bei jedem Aufruf bedeutet meist, dass die Workspace-ID-Variable falsch ist oder der Schlüssel zu einem anderen Workspace gehört. Ein 400 mit domain_id is required bedeutet, dass die Variable leer ist, typischerweise weil sie in einer anderen Umgebung als der des Jobs gesetzt wurde. Ein 409 ist die Kollision im gemeinsam genutzten Namensraum aus dem Abschnitt über Idempotenz. Und ein 429 bedeutet, dass Sie das Ratenlimit pro Schlüssel überschreiten, was ein einzelnes Upsert pro Lauf nicht schafft, eine Matrix mit fünfzig Jobs aber schon.
Eine Sache ist überhaupt kein Fehler. Nach einem PATCH kann ein Besucher noch kurze Zeit das alte Ziel erreichen, weil Redirects in der Nähe des Besuchers zwischengespeichert werden, damit sie schnell bleiben. Ein Smoke-Test, der direkt nach der Aktualisierung das neue Ziel erwartet, wird sporadisch fehlschlagen. Fragen Sie mit einem kurzen Backoff wiederholt ab oder prüfen Sie stattdessen die API-Antwort.
Wenn der Schritt nach außen berichten soll, kombinieren Sie ihn mit Webhooks für Link-Ereignisse, die bei einer Linkänderung ausgelöst werden, oder mit den curl- und jq-Mustern aus dem CLI-Leitfaden, um lokal zu testen, bevor Sie den Workflow committen. Die API- und SDK-Referenz listet jedes Feld auf, das die Link-Endpunkte akzeptieren.
Lesen Sie den Cornerstone-Beitrag → Ihre Kurzlinks als Terraform verwalten
Weitere Beiträge im Blog
- URL-Shortener-API: Ratenlimits, Wiederholungen und Idempotenz - die Regeln für sichere Wiederholungen, auf die sich dieser Workflow stützt.
- URL-Shortener-CLI - dieselben curl- und jq-Aufrufe aus Ihrem Terminal.
- Kostenlose URL-Shortener-API - der Erstellungsaufruf mit curl, JavaScript, Python und Go.
- URL-Shortener für Entwickler - Kurzlinks in Vorträgen, READMEs und Open-Source-Projekten.
- Webhooks für Link-Ereignisse - reagieren, wenn ein CI-Job einen Link ändert.
- GitLab-CI-Kurzlinks - dasselbe Upsert-Muster als
.gitlab-ci.yml-Job.
Häufig gestellte Fragen
Kann GitHub Actions Kurzlinks erstellen?
Ja. Ein Workflow-Schritt kann mit curl die REST-API jedes URL-Shorteners aufrufen, denn curl ist zusammen mit jq auf von GitHub gehosteten Runnern vorinstalliert. Der Schritt liest den API-Schlüssel aus einem verschlüsselten Secret, sendet die Ziel-URL und schreibt die resultierende Kurz-URL in eine Schrittausgabe, damit spätere Schritte sie in einem Pull-Request-Kommentar oder einer Job-Zusammenfassung veröffentlichen können.
Wie speichere ich einen API-Schlüssel für einen URL-Shortener in GitHub Actions?
Speichern Sie ihn als verschlüsseltes Repository- oder Umgebungs-Secret und ordnen Sie ihn dann mit einem env-Eintrag wie ELIDO_API_KEY: secrets.ELIDO_API_KEY innerhalb der Ausdruckssyntax genau dem einen Schritt zu, der ihn benötigt. GitHub maskiert den Wert in den Logs. Hinterlegen Sie nicht geheime Werte wie Workspace-ID und Domain-ID stattdessen in Konfigurationsvariablen, damit sie lesbar bleiben.
Wie verhindere ich, dass bei jedem Workflow-Lauf doppelte Kurzlinks erstellt werden?
Machen Sie den Schritt zu einem Upsert. Leiten Sie den Slug aus etwas Stabilem ab, etwa der Pull-Request-Nummer, suchen Sie ihn zuerst nach und senden Sie ein PATCH, um das Ziel zu ändern, wenn er bereits existiert. Erstellen Sie ihn nur, wenn die Suche leer zurückkommt. Ein Idempotency-Key-Header beim Erstellungsaufruf deckt den separaten Fall eines wiederholten Requests nach einem Netzwerk-Timeout ab.
Warum erhält mein Workflow beim Erstellen eines Kurzlinks einen 409-Fehler?
Der Slug ist auf dieser Domain bereits vergeben. Bei Elido sind Slugs pro Redirect-Domain eindeutig, und eine gemeinsam genutzte Domain wird mit jedem anderen Workspace geteilt, daher existiert ein allgemeiner Slug wie pr-12 wahrscheinlich bereits. Ergänzen Sie den Slug um ein Projektpräfix oder -suffix oder verwenden Sie Ihre eigene Custom Domain, deren gesamter Namensraum Ihnen gehört.
Funktionieren Kurzlink-Schritte bei Pull Requests aus Forks?
Nicht mit dem einfachen pull_request-Trigger, weil GitHub Repository-Secrets nicht an Workflows weitergibt, die von einem Fork gestartet wurden. Überspringen Sie den Job für Forks mit einer if-Bedingung für das Head-Repository. Der Wechsel zu pull_request_target, um an das Secret zu gelangen, ist riskant, weil der Workflow mit Schreibzugriff neben Code läuft, den Sie nicht geprüft haben.
Soll ein Link auf den neuesten Build mit einem 301- oder einem 302-Redirect arbeiten?
Verwenden Sie einen 302 oder 307. Browser dürfen einen 301 unbegrenzt zwischenspeichern, sodass wiederkehrende Besucher nach dem Verschieben des Links durch Ihren Workflow weiter bei einem alten Build landen würden. Elido-Links verwenden standardmäßig 302, wenn Sie redirect_status nicht setzen. Das ist die richtige Wahl für jeden Link, dessen Ziel eine Pipeline ändert.
Elido testen
URL einfügen, kurzer Link in Sekunden
Kein Konto nötig. Link bleibt 30 Tage aktiv. Konto erstellen, um ihn dauerhaft zu behalten.
Kostenlos, keine Anmeldung erforderlich · 2 pro Tag