Tak, GitLab CI może działać jako Twój skracacz URL. Zadanie z curl i jednym kluczem API może utworzyć krótki link dla każdego merge requestu, skierować go do aplikacji review i przesunąć stały link latest na nowe wydanie po wypchnięciu taga. To około czterdziestu linii YAML-a. Działa już dziś.
To, co ludzie zwykle robią źle, nie dotyczy wywołania HTTP. Chodzi o klucz: gdzie się znajduje, które pipeline'y mogą go odczytać i jak duże szkody może wyrządzić, jeśli zadanie brancha go wycieknie. Dlatego ten przewodnik poświęca zmiennym i zakresom uprawnień tyle samo miejsca co samemu .gitlab-ci.yml. Jeśli wolisz deklaratywnie zarządzać długowiecznymi linkami, lepiej pasuje podejście z Terraformem do krótkich linków; pipeline'y pasują do linków, które powstają i są wycofywane razem z kodem.
Na początek ważna informacja o statusie. Natywna integracja Elido z GitLabem jest w przygotowaniu, ale jeszcze nie działa, a nic poniżej od niej nie zależy. Chcesz wersję zarządzaną? Na stronie integracji z GitLabem znajdziesz listę oczekujących.
Co robi zadanie skracacza URL w GitLab CI
Skracacz działający w pipeline'ie robi dokładnie trzy rzeczy. Tworzy link, gdy slug nie istnieje, aktualizuje miejsce docelowe, gdy już istnieje, i wyłącza link, gdy znika zasób, na który wskazywał. Analityka i kody QR pozostają po stronie Elido.
Powierzchnia API jest niewielka. Linki znajdują się pod /v1/workspaces/{workspace_id}/links: POST tworzy link i wymaga domain_id oraz destination_url, PATCH /links/{link_id} zmienia pola istniejącego linku, a GET /links?q= wyszukuje po slugu, miejscu docelowym lub tytule. Uwierzytelnianie wymaga jednego nagłówka: Authorization: Bearer elido_.... Klucz pochodzi ze strony kluczy API w dashboardzie.
To cały kontrakt. Przegląd API i SDK zawiera listę pozostałych endpointów, ale pipeline rzadko potrzebuje więcej niż tych trzech.
Przechowywanie klucza jako zamaskowanej, chronionej zmiennej
GitLab daje Ci dwa istotne przełączniki, które działają różnie. Maskowanie ukrywa wartość w logach zadań. Ochrona kontroluje, które pipeline'y w ogóle otrzymują tę wartość.
W sekcji Settings, CI/CD, Variables podczas tworzenia zmiennej wybierz Masked and hidden. Hidden (ogólnie dostępne od GitLaba 17.6) oznacza, że nikt nie będzie mógł później ujawnić wartości na stronie ustawień, czego właśnie chcesz w przypadku poświadczenia. Dokumentacja zmiennych GitLab CI/CD wymienia wymagania dla zamaskowanej wartości: jeden wiersz, bez spacji, co najmniej 8 znaków. Klucze Elido składają się z elido_ i base32, więc spełniają te wymagania.
Ta sama strona jasno mówi o ograniczeniu: maskowanie "is not a guaranteed way to prevent malicious users from accessing variable values." Zadanie, które zakoduje zmienną w base64 i ją wypisze, bez problemu ominie maskowanie. Traktuj maskowanie jako higienę logów, a nie kontrolę dostępu.
Ochrona jest kontrolą dostępu. Chroniona zmienna dociera tylko do pipeline'ów na chronionych branchach lub chronionych tagach, co prowadzi do problemu, na który każdy zespół trafia w pierwszym tygodniu: pipeline merge requestu działa na branchu feature, więc chroniony klucz przychodzi jako pusty ciąg, a zadanie kończy się błędem 401 wyglądającym jak literówka.
Rozwiązałbym to dwoma kluczami zamiast osłabiania jednego. Oto konfiguracja, której sam bym użył:
| Zmienna | Widoczność | Chroniona | Odczytywana przez |
|---|---|---|---|
ELIDO_PREVIEW_KEY | Masked and hidden | Nie | Pipeline'y merge requestów |
ELIDO_RELEASE_KEY | Masked and hidden | Tak | Pipeline'y tagów na chronionych tagach |
ELIDO_PREVIEW_WS, ELIDO_RELEASE_WS | Visible | Nie | Dowolne zadanie (ID nie są sekretami) |
ELIDO_DOMAIN_ID, SHORT_HOST | Visible | Nie | Dowolne zadanie |
Klucz preview powinien należeć do osobnego workspace'u, w którym znajdują się wyłącznie linki review. Każdy, kto może wypchnąć branch, może w zasadzie wyeksfiltrować niechronioną zmienną, więc dopilnuj, aby najgorszym skutkiem był stos jednorazowych linków mr-142, podczas gdy klucz release znajduje się w prawdziwym workspace'ie i działa tylko na chronionych przez Ciebie tagach.
Nadaj obu kluczom rolę Editor i ustaw termin wygaśnięcia; 90 dni pasuje do klucza preview. Editor to najniższa gotowa rola, która może zapisywać linki, a przy tym może je także usuwać; klucze API korzystają z jednej z gotowych ról, a ja chętnie zobaczyłbym gotową rolę tylko do tworzenia i aktualizowania dokładnie dla tego przypadku, ale jeszcze jej nie ma. To rozdzielenie workspace'ów faktycznie ogranicza promień rażenia.
Działające zadanie .gitlab-ci.yml do tworzenia krótkiego linku
Oto wspólny fragment: upsert wyszukuje slug, tworzy link, jeśli go brakuje, a w przeciwnym razie wykonuje patch. Umieść go w ukrytym zadaniu i rozszerzaj je.
.elido_upsert:
image: alpine:3.20
before_script:
- apk add --no-cache curl jq
script:
- API="https://api.elido.app/v1/workspaces/${ELIDO_WS}"
- AUTH="Authorization: Bearer ${ELIDO_KEY}"
- |
find_id() {
curl -sS --fail-with-body -H "$AUTH" "$API/links?q=${SLUG}&limit=50" |
jq -r --arg s "$SLUG" --argjson d "$ELIDO_DOMAIN_ID" \
'.items[] | select(.slug == $s and .domain_id == $d) | .id' | head -n1
}
ID="$(find_id)"
if [ -z "$ID" ]; then
CODE=$(curl -sS -o resp.json -w '%{http_code}' -X POST "$API/links" \
-H "$AUTH" -H "Content-Type: application/json" \
-H "Idempotency-Key: ${CI_PIPELINE_ID}-${SLUG}" \
-d "$(jq -n --arg s "$SLUG" --arg u "$TARGET" --argjson d "$ELIDO_DOMAIN_ID" \
'{domain_id: $d, slug: $s, destination_url: $u, tags: ["ci"]}')")
case "$CODE" in
201) ;;
409) ID="$(find_id)" ;; # another pipeline created it first
*) cat resp.json; exit 1 ;;
esac
fi
if [ -n "$ID" ]; then
curl -sS --fail-with-body -X PATCH "$API/links/$ID" \
-H "$AUTH" -H "Content-Type: application/json" \
-d "$(jq -n --arg u "$TARGET" '{destination_url: $u, status: "active"}')"
fi
- echo "SHORT_URL=https://${SHORT_HOST}/${SLUG}" >> link.env
artifacts:
reports:
dotenv: link.env
Wyszukiwanie q dopasowuje fragment, więc filtr jq zawęża wynik do dokładnego sluga na dokładnej domenie. Bez tego wyszukiwanie web-mr-14 bez problemu zwróciłoby web-mr-142. Raz pobierz domain_id za pomocą GET /v1/workspaces/{id}/domains i zapisz go jako zwykłą zmienną; brandowany host skonfigurowany przez domeny niestandardowe lepiej wygląda w merge requeście niż generyczny.
Krótkie linki do aplikacji review dla każdego merge requestu
Aplikacje review to nazwa GitLaba na tymczasowe środowisko dla brancha lub merge requestu, a dokumentacja aplikacji review buduje je na dynamicznych środowiskach. Ich URL-e bywają brzydkie: hash, namespace, hostname dostawcy chmury. Krótki link taki jak go.example.com/web-mr-142 można wypowiedzieć na głos podczas daily.
review_link:
extends: .elido_upsert
stage: deploy
needs: [deploy_review]
variables:
ELIDO_KEY: $ELIDO_PREVIEW_KEY
ELIDO_WS: $ELIDO_PREVIEW_WS
SLUG: "${CI_PROJECT_NAME}-mr-${CI_MERGE_REQUEST_IID}"
TARGET: "https://${CI_ENVIRONMENT_SLUG}.review.example.com"
environment:
name: review/$CI_COMMIT_REF_SLUG
url: $SHORT_URL
on_stop: stop_review_link
auto_stop_in: 1 week
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
Sztuczka tkwi w raporcie dotenv. Upsert zapisuje SHORT_URL w link.env, GitLab odczytuje go ponownie, a environment:url staje się krótkim linkiem, więc przycisk View app w merge requeście otwiera web-mr-142 zamiast surowego hosta. Dokumentacja środowisk opisuje ten wzorzec dynamicznego URL-a.
CI_MERGE_REQUEST_IID jest unikalny w projekcie i nie zmienia się przez cały okres istnienia merge requestu, dlatego każde wypchnięcie zmian do tego samego MR trafia na ten sam slug, a upsert go aktualizuje zamiast duplikować. Dokumentacja predefiniowanych zmiennych zawiera pełną listę, jeśli chcesz użyć innego klucza.
Czyszczenie realizuje zadanie z action: stop. Musi ono współdzielić rules z zadaniem uruchamiającym, inaczej GitLab nie będzie mógł uruchomić go automatycznie:
stop_review_link:
image: alpine:3.20
stage: deploy
variables:
GIT_STRATEGY: none
SLUG: "${CI_PROJECT_NAME}-mr-${CI_MERGE_REQUEST_IID}"
script:
- apk add --no-cache curl jq
- API="https://api.elido.app/v1/workspaces/${ELIDO_PREVIEW_WS}"
- ID=$(curl -sS -H "Authorization:
Bearer ${ELIDO_PREVIEW_KEY}" "$API/links?q=${SLUG}" |
jq -r --arg s "$SLUG" --argjson d "$ELIDO_DOMAIN_ID" '.items[] | select(.slug == $s and .domain_id == $d) | .id' | head -n1)
- '[ -z "$ID" ] || curl -sS --fail-with-body -X PATCH "$API/links/$ID" -H "Authorization: Bearer ${ELIDO_PREVIEW_KEY}" -H "Content-Type: application/json" -d "{\"status\":\"disabled\"}"'
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
when: manual
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
Wyłączam link zamiast go usuwać. Wyłączony link zachowuje historię kliknięć, a jeśli ktoś ponownie otworzy MR, kolejny pipeline przełączy go z powrotem na active przez ten sam upsert. GIT_STRATEGY: none jest potrzebne, bo do tego czasu branch może już nie istnieć.
Jeśli aplikacji review masz dziesięć razy więcej niż wydań, właśnie wtedy zaczynają uwierać limity planu. Sprawdź limit linków na stronie cennika, zanim podepniesz to do zajętego monorepo, i podczas testów utwórz darmowy workspace dla podglądów.
Przekierowywanie stałego linku latest w pipeline'ach tagów
Drugi wzorzec działa na tagach i jest odwrotnością linku review: jeden niezmienny slug, którego miejsce docelowe przesuwa się naprzód przy każdym wydaniu. README może wiecznie wskazywać go.example.com/cli-latest.
latest_link:
extends: .elido_upsert
stage: release
variables:
ELIDO_KEY: $ELIDO_RELEASE_KEY
ELIDO_WS: $ELIDO_RELEASE_WS
SLUG: "cli-latest"
TARGET: "${CI_PROJECT_URL}/-/releases/${CI_COMMIT_TAG}"
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
Połącz regułę ze wzorcem chronionego taga, takim jak v*, aby tylko opiekunowie mogli tworzyć tagi uruchamiające zadanie; w przeciwnym razie chronionego klucza po prostu nie będzie, a zadanie zakończy się bezpieczną porażką, czyli dokładnie tak, jak chcesz. Jeśli potrzebujesz także stałego linku do każdej wersji, uruchom to samo zadanie drugi raz z SLUG: "cli-${CI_COMMIT_REF_SLUG}", co zamieni v1.4.0 na cli-v1-4-0.
Nie ustawiaj redirect_status na 301 dla linku latest. 301 to stała obietnica, którą przeglądarki mogą zachować w cache, a link latest łamie tę obietnicę przy każdym wydaniu. Elido domyślnie używa 302, gdy pominiesz to pole, a nasz tekst o przekierowaniach 301 i 302 omawia sytuacje, w których ten wybór naprawdę ma znaczenie.
Jedno uczciwe zastrzeżenie: zmiana miejsca docelowego może potrzebować kilku minut, aby dotrzeć do każdej lokalizacji brzegowej, więc test dymny wykonujący curl na krótkim linku sekundę później może nadal zobaczyć poprzednie wydanie. Sprawdzaj odpowiedź API albo odczekaj przed weryfikacją nagłówka Location.
Idempotencja, ponowienia i limity szybkości
Pipeline'y ponawiają zadania. Runner pada w połowie zadania, ktoś klika Retry przy czerwonym zadaniu, a dwa wypchnięcia zmian następują w odstępie trzydziestu sekund i ścigają się ze sobą. Powyższy upsert radzi sobie ze wszystkimi trzema sytuacjami i warto wiedzieć dlaczego.
Nagłówek Idempotency-Key sprawia, że ponowione POST jest bezpieczne: API przechowuje udaną odpowiedź przez 24 godziny i odtwarza ją dla tego samego klucza, więc ponowienie tego samego pipeline'u zwraca pierwotny link zamiast błędu. Zbudowanie klucza z CI_PIPELINE_ID i sluga oznacza, że ponowienia w obrębie pipeline'u są odtwarzane, a nowy pipeline dostaje świeżą próbę. Gałąź 409 obsługuje wyścig między dwoma różnymi pipeline'ami, a ścieżka wyszukaj, a następnie wykonaj patch sprawia, że drugie uruchomienie jest w praktyce operacją bez skutku.
Limity szybkości obowiązują na klucz, dodatkowo do limitu workspace'u, a zupełnie nowe workspace'y mają też niższy dzienny limit tworzenia linków, dopóki budują reputację. Kilka merge requestów tego nie zauważy. Monorepo uruchamiające naraz czterdzieści aplikacji review może już zauważyć, dlatego traktuj 429 jako możliwy do ponowienia za pomocą słowa kluczowego GitLaba retry, a przy 402 kończ wyraźnym błędem, bo oznacza on limit planu, a nie przejściowy problem. Nasz obszerniejszy tekst o limitach szybkości i idempotencji dla API skracaczy omawia wycofywanie w większym szczególe, niż potrzebuje tego zadanie CI.
Pomiń filtr jq sprawdzający dokładne dopasowanie, a pipeline MR 14 po cichu zaktualizuje link MR 142. Pierwszym objawem jest zwykle zdezorientowany projektant. Zostaw ten filtr.
Jeśli shell w YAML-u staje się nieporęczny, te same wywołania można wygodnie przenieść do skryptu commitowanego w repozytorium, a przewodnik po CLI skracacza URL pokazuje taki układ.
Przeczytaj artykuł filarowy → Krótkie linki jako Terraform: zarządzanie linkami jako kodem
Powiązane na blogu
- Szybki start z API skracacza URL - powierzchnia REST, z której korzysta każde opisane wyżej zadanie.
- Limity szybkości i idempotencja dla API skracaczy - dlaczego logika ponowień wygląda właśnie tak.
- Skracacz URL z wiersza poleceń - te same wywołania w postaci wielokrotnego użytku skryptu.
- Przekierowania 301 i 302 - dlaczego zmieniający się link potrzebuje tymczasowego przekierowania.
- Monitorowanie przekierowań linków za pomocą Sentry i Datadog - wykrywanie zepsutego miejsca docelowego po pomyślnym zakończeniu pipeline'u.
- Krótkie linki z GitHub Actions - odpowiednik dla GitHuba, z sekretami i współbieżnością.
Najczęściej zadawane pytania
Czy GitLab CI może tworzyć krótkie linki?
Tak. Każde zadanie, które może uruchomić curl, może wywołać REST API skracacza URL, więc zadanie GitLab CI może utworzyć krótki link, zmienić jego miejsce docelowe albo go wyłączyć. Klucz API znajduje się w zamaskowanej zmiennej CI/CD, a zadanie wysyła go jako token Bearer. Do tego nie jest potrzebna natywna integracja z GitLabem.
Jak bezpiecznie przechowywać klucz API w GitLab CI?
Dodaj go w sekcji Settings, CI/CD, Variables, ustaw widoczność na Masked and hidden i zaznacz Protect variable, jeśli tylko chronione branche lub tagi powinny go odczytywać. Maskowanie usuwa wartość z logów zadań, ale własna dokumentacja GitLaba mówi, że nie jest to gwarantowana ochrona, więc ogranicz sam klucz do minimalnego zakresu uprawnień.
Dlaczego moja chroniona zmienna jest pusta w pipeline'ie merge requestu?
Chronione zmienne są przekazywane tylko do pipeline'ów uruchamianych na chronionych branchach lub chronionych tagach. Pipeline merge requestu z brancha feature nie spełnia tego warunku domyślnie, więc zmienna dociera jako pusty ciąg, a zadanie kończy się błędem 401 wyglądającym jak literówka. Użyj osobnego, mniej uprzywilejowanego niechronionego klucza dla zadań review albo zostaw chroniony klucz wyłącznie dla pipeline'ów tagów.
Jak dać każdej aplikacji review GitLaba krótki link?
Uruchom zadanie w pipeline'ach merge requestów, które wykona upsert sluga zbudowanego z nazwy projektu i CI_MERGE_REQUEST_IID, wskazującego na URL aplikacji review. Zapisz wynikowy krótki URL w raporcie dotenv i użyj go jako environment:url, aby widget merge requestu prowadził bezpośrednio do niego. Zadanie stop wyłączy link, gdy środowisko zostanie zatrzymane.
Czy krótki link do najnowszego wydania powinien używać przekierowania 301 czy 302?
Użyj 302. Link latest zmienia miejsce docelowe przy każdym wydaniu, a 301 informuje przeglądarki i cache, że przeniesienie jest stałe, więc niektóre klienty będą nadal wysyłać użytkowników do starej wersji. Elido domyślnie ustawia 302 dla nowych linków, gdy nie podasz redirect_status, co jest tutaj właściwym wyborem.
Czy istnieje natywna integracja GitLaba z Elido?
Jeszcze nie. Natywna integracja z GitLabem jest w przygotowaniu i możesz dołączyć do listy oczekujących na stronie integracji z GitLabem. Wszystko w tym przewodniku działa już dziś przez publiczne REST API z zadania pipeline'u, bez instalowania czegokolwiek po stronie GitLaba poza zmienną CI/CD.
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