Krok skracacza URL w GitHub Actions to kilka linii powłoki: odczytaj klucz API z zaszyfrowanego sekretu, sprawdź, czy slug już istnieje, a następnie zaktualizuj jego cel albo go utwórz. Uruchamiaj ten krok przy każdym pushu, a ten sam krótki link będzie zawsze prowadzić do najnowszego preview, buildu dokumentacji albo artefaktu. Nie potrzebujesz akcji z marketplace. curl i jq są dostępne na każdym runnerze Ubuntu hostowanym przez GitHub.
To cała odpowiedź, a reszta tego wpisu pokazuje, jak sprawić, żeby rozwiązanie wytrzymało kilkaset uruchomień workflow. Osoby szukające sposobu na utworzenie krótkiego linku w GitHub Actions zwykle docierają do pojedynczego żądania POST, które działa aż do drugiego pushu do tego samego pull requesta. Wtedy żądanie tworzące zwraca konflikt, a zadanie kończy się na czerwono. Rozwiązaniem jest potraktowanie tego kroku jako operacji upsert, a nie tworzenia. Drugi problem dotyczy samego klucza: często jest to osobisty token o znacznie szerszym zakresie dostępu, niż potrzebuje zadanie CI.
Jeśli już zarządzasz linkami jako kodem, krótkie linki jako Terraform są deklaratywną wersją tego samego pomysłu i lepiej pasują do linków zmienianych zgodnie z harmonogramem człowieka. Krok workflow wygrywa, gdy cel pojawia się dopiero po zakończeniu buildu.
Jak działa krok skracacza URL w GitHub Actions
Każde uruchomienie wykonuje wobec REST API pod adresem https://api.elido.app/v1 te same trzy czynności. Listuje linki workspace'u, filtrując je po slugu. Wysyła PATCH do znalezionego linku albo POST, jeśli niczego nie znaleziono. Zapisuje krótki URL w $GITHUB_OUTPUT, aby następny krok mógł go użyć.
Dlaczego nie pozwolić skracaczowi wygenerować losowego sluga? Bo później nie znajdziesz ponownie tego linku. Slug musi pochodzić z czegoś, co workflow zna przy każdym uruchomieniu: numeru pull requesta, nazwy gałęzi, stałego słowa takiego jak latest. Stabilny slug oznacza stabilny krótki URL, a to właśnie jest najważniejsze dla recenzentów, którzy dodają go do zakładek, oraz menedżerów produktu, którzy wklejają go do zadania.
Przechowywanie klucza API jako zaszyfrowanego sekretu
Utwórz klucz w dashboardzie, skopiuj go raz (jest wyświetlany dokładnie jeden raz i zaczyna się od elido_), a następnie zapisz go w sekcjach Settings, potem Secrets and variables, a następnie Actions, jako ELIDO_API_KEY. Przewodnik GitHub dotyczący używania sekretów w GitHub Actions obejmuje poziom repozytorium, środowiska i organizacji. W przypadku wszystkiego, co wykonuje wdrożenie, umieściłbym go w środowisku z wymaganymi recenzentami, aby przypadkowa gałąź nie mogła go użyć.
Trzy wartości nie są sekretami i powinny trafić do zmiennych konfiguracyjnych, gdzie możesz je później odczytać: ELIDO_WORKSPACE_ID, ELIDO_DOMAIN_ID i ELIDO_HOST. Identyfikator domeny ma znaczenie, ponieważ żądanie tworzące go wymaga. Możesz odszukać go raz za pomocą GET /v1/workspaces/{workspace_id}/domains, które zwraca id i hostname każdej domeny.
Przekaż sekret do jedynego kroku wywołującego API, a nie do całego zadania. env na poziomie kroku sprawia, że sekret nie trafia do żadnego innego procesu uruchamianego przez zadanie, w tym do zewnętrznych akcji, których nie napisano w Twoim zespole.
Działający workflow do skracania URL dla każdego pull requesta
To kompletny plik dla najczęstszego powodu skracania URL w workflow GitHub: osobny link preview dla każdego pull requesta. Włóż go do .github/workflows/preview-link.yml i zmień linię DEST, wskazując miejsce, do którego trafiają wdrożenia preview.
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 }}"
Kilka linii zasługuje na komentarz. --fail-with-body zamienia błąd 4xx lub 5xx w nieudany krok, a jednocześnie wypisuje treść błędu. Zwykłe curl -s tego nie robi: przy 401 kończy się kodem 0, a zadanie zadowolone działa dalej. Treści żądań są budowane przez jq -n, a nie interpolację tekstu, więc cel zawierający cudzysłowy albo znak ampersand nie uszkodzi JSON-a. Krok z komentarzem uruchamia się tylko przy opened. Ponieważ krótki URL nigdy się nie zmienia, jeden komentarz pozostaje prawidłowy przez cały czas istnienia PR-a i nikt nie dostaje powiadomienia przy każdym pushu.
Blok concurrency nie jest ozdobnikiem. Bez niego dwa szybkie pushe uruchomiłyby dwa przebiegi, które oba zobaczyłyby, że "nie ma jeszcze linku", i oba spróbowałyby go utworzyć. Dokumentacja GitHub dotycząca sterowania współbieżnością workflow wyjaśnia grupowanie; tutaj starszy przebieg zostaje anulowany i wyścig nigdy nie zachodzi.
Idempotencja: aktualizuj krótki link, nie duplikuj go
Pod słowem idempotent kryją się dwa różne problemy, a workflow obsługuje je osobno. Pierwszy to ponowne uruchomienie: drugi push, ręczne "Re-run jobs" albo ponownie otwarty PR. Do tego służy gałąź wyszukująca, a następnie wykonująca PATCH. Drugi to ponowione żądanie: curl wysyła POST, sieć zrywa połączenie przed nadejściem odpowiedzi, więc curl wysyła je ponownie. Ten przypadek obsługuje nagłówek Idempotency-Key. Elido przechowuje pierwszą pomyślną odpowiedź powiązaną z kluczem przez 24 godziny i odtwarza ją przy pasującym ponowieniu, więc tworzenie odbywa się raz; pełny mechanizm opisano w artykule limity szybkości, ponowienia i idempotencja.
Kolizje slugów to element, który często umyka. Slug jest unikatowy w obrębie domeny przekierowań, a nie workspace. We współdzielonej domenie każdy inny klient Elido korzysta z tej samej przestrzeni nazw, a tak prosty slug jak pr-12 prawdopodobnie ktoś już zajął. Wyszukiwanie nie zobaczy jego linku (listuje tylko Twój workspace), więc POST zostanie wysłany i wróci z komunikatem 409 slug already exists for this domain. Są dwa rozwiązania: dodaj do sluga nazwę projektu albo umieść linki CI we własnej domenie niestandardowej, gdzie przestrzeń nazw należy wyłącznie do Ciebie. Zrobiłbym jedno i drugie.
Istnieje jeszcze jeden, bardziej podstępny powód, dla którego nazwa repozytorium znajduje się na końcu sluga, a nie na początku. Parametr q dopasowuje fragment tekstu w slugu, celu i tytule. Przy myapp-pr-1 wyszukiwanie zwróci także myapp-pr-10 do myapp-pr-199, czyli więcej niż 100 wyników zwracanych na jednej stronie, a potrzebny link, jako najstarszy, wypadnie poza koniec. pr-1-myapp pasuje tylko do siebie. To drobny szczegół, ale znalezienie go zajęło mi żenująco długie popołudnie pełne pytania "dlaczego PR #1 ciągle dostaje 409".
Trzy zadania CI z krótkimi linkami, które warto zautomatyzować
Workflow preview to jeden ze schematów. Zmień wyzwalacz, slug i cel, a ten sam krok obsłuży większość rzeczy, które zespoły faktycznie automatyzują. (Informacje o wydaniu to osobny temat, omówiony w artykule skracanie linków w informacjach o wydaniu.)
| Zastosowanie | Wyzwalacz | Slug | Działanie kroku |
|---|---|---|---|
| Preview dla każdego PR | pull_request | pr-42-myapp | Upsert przy każdym pushu, usunięcie przy zamknięciu |
| Wdrożenie dokumentacji | push do main | docs-myapp | PATCH do świeżo wdrożonego URL dokumentacji |
| Najnowszy build | push do main lub taga | latest-myapp | PATCH po zapisanym ID linku, bez wyszukiwania |
| Artefakt nocny | schedule | nightly-myapp | PATCH do URL najnowszego artefaktu |
Przypadek najnowszego buildu jest najprostszy z całej czwórki. Utwórz link ręcznie raz, zapisz jego numeryczny identyfikator jako zmienną, a zadanie skurczy się do pojedynczego wywołania:
- 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}')"
Dla tych zmieniających się linków używaj 302, które jest wartością domyślną, jeśli nie ustawisz redirect_status. Kod 301 mówi przeglądarkom, że mogą buforować odpowiedź, więc osoby, które kliknęły wczoraj, nadal będą trafiać do wczorajszego buildu; dłuższą wersję znajdziesz w naszym artykule o przekierowaniach 301 a 302.
W przypadku linków preview sprzątaj po zamknięciu PR-a. Dodaj closed do typów wyzwalacza, ponownie użyj wyszukiwania i wyślij DELETE /v1/workspaces/{workspace_id}/links/{link_id}. Usunięty slug można ponownie wykorzystać. Jeśli wolisz zachować historię kliknięć, użyj zamiast tego PATCH z {"status": "disabled"}; opisany wyżej upsert ustawia status: "active" przy każdym uruchomieniu, więc ponownie otwarty PR przywróci jego link.
Chcesz wypróbować to w jednym repozytorium? Załóż bezpłatny workspace, utwórz klucz, a powyższy workflow zadziała bez zmian po ustawieniu trzech zmiennych.
Klucze API CI z najmniejszymi uprawnieniami
Klucz w sekrecie CI powinien móc robić dokładnie to, co workflow, i nic więcej. To trudniejsze, niż brzmi, ze względu na sposób działania kluczy osobistych.
Osobisty klucz API uwierzytelnia jako osoba, która go utworzyła. Wszystko, co może ta osoba, może też klucz, a gdy odejdzie z firmy, klucz odejdzie razem z jej kontem. W CI użyłbym zamiast tego użytkownika maszynowego: konta usługi należącego do jednego workspace, z własną rolą i tokenami, które może utworzyć albo unieważnić tylko zalogowany administrator będący człowiekiem. Utwórz go w dashboardzie w sekcji Machine users, nadaj mu rolę edytora, czyli najniższą wbudowaną rolę pozwalającą tworzyć, edytować i usuwać linki, a następnie wygeneruj dla niego token z datą wygaśnięcia. Wyłączenie użytkownika maszynowego natychmiast unieważnia wszystkie posiadane przez niego tokeny, czyli dokładnie tego przycisku, którego chcesz użyć w dniu wycieku sekretu.
Cztery kolejne nawyki nic nie kosztują:
- Jeden token na repozytorium, nazwany jego nazwą, aby ślad audytowy pokazywał, które repozytorium utworzyło który link.
- Sekrety środowiska z wymaganymi recenzentami dla każdego workflow zmieniającego link, na którym polegają inni.
permissions:ustawione jawnie na początku workflow, jak w przykładzie, abyGITHUB_TOKENotrzymał tylko to, czego potrzebuje zadanie.- Nigdy nie używaj
pull_request_targetdo uzyskania sekretu z PR-ów pochodzących z forków. Artykuł GitHub Security Lab o zapobieganiu żądaniom pwn pokazuje, dlaczego uruchamianie niezaufanego kodu obok tokena z prawem zapisu źle się kończy.
Workspace'y mogą również ograniczać dostęp do API za pomocą listy dozwolonych adresów IP. To silna kontrola dla self-hosted runnerów ze stałym wyjściem do sieci i niemal bezużyteczna dla runnerów hostowanych przez GitHub, których adresy pochodzą z bardzo dużej, zmiennej puli. Własny przewodnik GitHub dotyczący bezpiecznego użycia warto przeczytać przez godzinę, jeśli Twoje workflow dotykają produkcji.
Co psuje się w praktyce
Większość awarii ma jedno z czterech źródeł, a każda z nich pojawia się jako czytelny błąd, jeśli włączone jest --fail-with-body. Kod 404 przy każdym wywołaniu zwykle oznacza nieprawidłową zmienną z identyfikatorem workspace albo klucz należący do innego workspace. Kod 400 z komunikatem domain_id is required oznacza pustą zmienną, zazwyczaj dlatego, że ustawiono ją w innym środowisku niż to używane przez zadanie. Kod 409 to kolizja współdzielonej przestrzeni nazw opisana w sekcji o idempotencji. Kod 429 oznacza przekroczenie limitu szybkości dla klucza, którego nie osiągnie pojedynczy upsert na uruchomienie, ale może go osiągnąć macierz pięćdziesięciu zadań.
Jedna rzecz wcale nie jest błędem. Po PATCH-u odwiedzający może jeszcze przez chwilę trafić do starego celu, ponieważ przekierowania są buforowane blisko odwiedzającego, aby działały szybko. Test dymny sprawdzający nowy cel natychmiast po aktualizacji będzie niestabilny. Odpytywanie wykonuj z krótkim narastającym opóźnieniem albo sprawdzaj odpowiedź API.
Jeśli chcesz, aby krok raportował na zewnątrz, połącz go z webhookami dla zdarzeń linków, które uruchamiają się po zmianie linku, albo ze schematami curl i jq z przewodnika CLI do lokalnych testów przed zatwierdzeniem workflow. Dokumentacja API i SDK zawiera listę wszystkich pól akceptowanych przez endpointy linków.
Przeczytaj cornerstone → Zarządzaj krótkimi linkami jako Terraform
Powiązane artykuły na blogu
- API skracacza URL: limity szybkości, ponowienia i idempotencja - zasady bezpiecznego ponawiania, na których opiera się ten workflow.
- CLI skracacza URL - te same wywołania curl i jq z terminala.
- Bezpłatne API skracacza URL - wywołanie tworzące w curl, JavaScript, Pythonie i Go.
- Skracacze URL dla deweloperów - krótkie linki w prezentacjach, README i projektach open source.
- Webhooki dla zdarzeń linków - reaguj, gdy zadanie CI zmieni link.
- Krótkie linki GitLab CI - ten sam schemat upsert jako zadanie .gitlab-ci.yml.
Najczęściej zadawane pytania
Czy GitHub Actions może tworzyć krótkie linki?
Tak. Krok workflow może wywołać REST API dowolnego skracacza za pomocą curl, który wraz z jq jest preinstalowany na runnerach hostowanych przez GitHub. Krok odczytuje klucz API z zaszyfrowanego sekretu, wysyła docelowy adres URL i zapisuje wynikowy krótki URL w wyjściu kroku, aby późniejsze kroki mogły opublikować go w komentarzu do pull requesta albo w podsumowaniu zadania.
Jak przechowywać klucz API skracacza URL w GitHub Actions?
Zapisz go jako zaszyfrowany sekret repozytorium albo środowiska, a następnie przekaż go do jedynego kroku, który go potrzebuje, za pomocą wpisu env, takiego jak ELIDO_API_KEY: secrets.ELIDO_API_KEY wewnątrz składni wyrażeń. GitHub maskuje tę wartość w logach. Wartości, które nie są sekretami, takie jak identyfikator workspace i identyfikator domeny, trzymaj zamiast tego w zmiennych konfiguracyjnych, aby pozostały czytelne.
Jak uniknąć tworzenia duplikatów krótkich linków przy każdym uruchomieniu workflow?
Zamień ten krok w operację upsert. Wyprowadź slug z czegoś stabilnego, na przykład numeru pull requesta, najpierw go wyszukaj, a gdy już istnieje, wyślij PATCH zmieniający cel. Twórz link tylko wtedy, gdy wyszukiwanie nic nie zwróci. Nagłówek Idempotency-Key przy żądaniu tworzącym obsługuje osobny przypadek ponowionego żądania po przekroczeniu limitu czasu sieci.
Dlaczego mój workflow otrzymuje 409 podczas tworzenia krótkiego linku?
Ten slug jest już zajęty w tej domenie. W Elido slugi są unikatowe w obrębie domeny przekierowań, a domena współdzielona jest wspólna dla wszystkich pozostałych workspace'ów, więc ogólny slug, taki jak pr-12, prawdopodobnie już istnieje. Dodaj do sluga prefiks albo przyrostek projektu lub użyj własnej domeny niestandardowej, gdzie cała przestrzeń nazw należy do Ciebie.
Czy kroki tworzące krótkie linki działają dla pull requestów z forków?
Nie przy zwykłym wyzwalaczu pull_request, ponieważ GitHub nie przekazuje sekretów repozytorium do workflow uruchomionych przez fork. Pomijaj zadanie dla forków za pomocą warunku if sprawdzającego repozytorium źródłowe. Przełączenie na pull_request_target w celu uzyskania sekretu jest ryzykowne, ponieważ workflow uruchamia się z prawem zapisu obok kodu, którego nie sprawdzono.
Czy link prowadzący do najnowszego buildu powinien używać przekierowania 301 czy 302?
Użyj 302 albo 307. Przeglądarki mogą bezterminowo buforować 301, więc powracający użytkownicy nadal trafialiby na stary build po przeniesieniu linku przez workflow. Linki Elido domyślnie używają 302, gdy nie ustawisz redirect_status, co jest właściwym wyborem dla każdego linku, którego cel zmienia pipeline.
Wypróbuj Elido
Wklej URL, otrzymaj krótki link
Bez rejestracji. Link działa 30 dni. Zarejestruj się, aby zachować go na zawsze.
Za darmo, bez rejestracji · 2 dziennie