Un pas de scurtător de URL în GitHub Actions înseamnă câteva linii de shell: citești o cheie API dintr-un secret criptat, verifici dacă slugul există deja, apoi fie îi actualizezi destinația, fie îl creezi. Rulează-l la fiecare push și același link scurt arată mereu spre cel mai nou preview, build de documentație sau artefact. Nu ai nevoie de nicio acțiune din marketplace. curl și jq vin pe fiecare runner Ubuntu găzduit de GitHub.
Acesta este răspunsul întreg, iar restul articolului este partea care îl face să reziste la câteva sute de rulări de workflow. Cine caută cum creezi un link scurt în GitHub Actions ajunge de obicei până la o singură cerere POST, și merge până la al doilea push în același pull request, când apelul de creare returnează un conflict, iar jobul devine roșu. Remediul este să tratezi pasul ca pe un upsert, nu ca pe o creare. Celălalt lucru care merge prost este cheia însăși, care tinde să fie tokenul personal al cuiva, cu mult mai multă acoperire decât are nevoie un job CI.
Dacă îți gestionezi deja linkurile ca cod, linkurile scurte ca Terraform este versiunea declarativă a aceleiași idei și se potrivește mai bine linkurilor care se schimbă într-un ritm uman. Un pas de workflow câștigă când destinația există abia după ce un build s-a terminat.
Cum funcționează un pas de scurtător de URL în GitHub Actions
Fiecare rulare face aceleași trei lucruri față de API-ul REST de la https://api.elido.app/v1. Listează linkurile workspace-ului filtrate după slug. Trimite un PATCH către linkul găsit sau un POST dacă nu a găsit nimic. Scrie URL-ul scurt în $GITHUB_OUTPUT, ca pasul următor să îl poată folosi.
De ce să nu lași scurtătorul să genereze un slug aleatoriu? Pentru că atunci nu mai poți regăsi linkul. Slugul trebuie să vină din ceva ce workflowul știe deja la fiecare rulare: numărul pull requestului, numele branch-ului, un cuvânt fix precum latest. Un slug stabil înseamnă un URL scurt stabil, iar asta este toată valoarea pentru recenzenții care îl pun la favorite sau pentru managerii de produs care îl lipesc într-un tichet.
Stocarea cheii API ca secret criptat
Creează cheia în dashboard și copiază-o imediat, pentru că este afișată exact o singură dată (începe cu elido_), apoi salvează-o la Settings, apoi Secrets and variables, apoi Actions, ca ELIDO_API_KEY. Ghidul GitHub despre utilizarea secretelor în GitHub Actions acoperă nivelurile de repository și de mediu, plus secretele la nivel de organizație. Pentru orice se implementează, eu l-aș pune pe un mediu cu recenzenți obligatorii. Așa un branch rătăcit nu îl poate folosi.
Trei valori nu sunt secrete: ELIDO_WORKSPACE_ID, ELIDO_DOMAIN_ID și ELIDO_HOST. Ele aparțin variabilelor de configurare, unde le poți citi înapoi mai târziu, iar dintre cele trei, ID-ul domeniului este cel de reglat primul, pentru că apelul de creare îl cere. Îl poți căuta o singură dată cu GET /v1/workspaces/{workspace_id}/domains, care returnează id și hostname pentru fiecare domeniu.
Mapează secretul în singurul pas care apelează API-ul, nu în tot jobul. Un env la nivel de pas îl ține departe de orice alt proces pornit de job, inclusiv de acțiunile terțe pe care nu le-ai scris tu. Restrânge-l cât poți.
Un workflow funcțional care scurtează un URL la fiecare pull request
Acesta este fișierul complet pentru cel mai frecvent motiv de a scurta un URL într-un workflow GitHub, un link de preview per pull request. Pune-l în .github/workflows/preview-link.yml și schimbă linia DEST cu locul unde ajung implementările tale de 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 }}"
Câteva linii merită un comentariu. --fail-with-body transformă un 4xx sau 5xx într-un pas eșuat, afișând totuși corpul erorii, ceea ce un simplu curl -s nu face; el iese cu 0 la un 401, iar jobul merge mai departe liniștit. Corpurile cererilor sunt construite cu jq -n, nu prin interpolare de șiruri, așa că o destinație cu ghilimele sau cu un ampersand nu poate sparge JSON-ul. Iar pasul de comentariu rulează doar la opened. Fiindcă URL-ul scurt nu se schimbă niciodată, un singur comentariu rămâne corect cât trăiește PR-ul, și nimeni nu e notificat la fiecare push.
Blocul concurrency nu e decor. Altfel, două push-uri rapide ar porni două rulări care văd amândouă „încă niciun link" și încearcă amândouă să creeze unul. Documentația GitHub despre controlul concurenței workflowurilor explică gruparea; aici, rularea mai veche este anulată, iar condiția de cursă nu apare niciodată.
Idempotență: actualizează linkul scurt, nu îl duplica
Două eșecuri diferite se ascund sub cuvântul idempotent, iar workflowul le tratează separat. Primul este rerularea: un al doilea push, un „Re-run jobs" manual, un PR redeschis. Pentru asta există ramura căutare-apoi-PATCH. Al doilea este cererea reîncercată, unde curl trimite POST-ul, rețeaua cade înainte să sosească răspunsul, iar curl îl trimite din nou. Headerul Idempotency-Key o acoperă pe aceasta. Elido stochează primul răspuns reușit pe cheie timp de 24 de ore și îl redă pentru o reîncercare identică, deci crearea rulează o singură dată; mecanica completă este în limite de rată, reîncercări și idempotență.
Coliziunile de slug sunt partea pe care o ratează oamenii. Slugurile sunt unice per domeniu de redirecționare, nu per workspace. Pe un domeniu partajat, fiecare alt client Elido este în același spațiu de nume, iar un slug la fel de simplu ca pr-12 a fost probabil revendicat de cineva. Căutarea nu va vedea linkul lui (listează doar workspace-ul tău), așa că POST-ul pleacă și revine cu 409 slug already exists for this domain. Două remedii: adaugă un cuvânt de proiect la slug sau pune linkurile CI pe propriul domeniu personalizat, unde spațiul de nume este doar al tău. Eu le-aș face pe amândouă.
Există și un al doilea motiv, mai perfid, pentru care numele repository-ului stă la sfârșitul slugului, nu la început. Parametrul q este o potrivire de subșir în slug, destinație și titlu. Cu myapp-pr-1, căutarea returnează și myapp-pr-10 până la myapp-pr-199, ceea ce depășește cele 100 de rezultate pe care le întoarce o singură pagină, iar singurul link pe care îl voiai, fiind cel mai vechi, cade de la capăt. pr-1-myapp nu potrivește nimic în afară de el însuși. Un detaliu mic, iar mie mi-a luat o după-amiază jenant de lungă de „de ce primește PR-ul #1 mereu un 409" ca să îl observ.
Trei joburi de linkuri scurte în CI care merită automatizate
Workflowul de preview este un singur tipar. Schimbă triggerul, slugul și destinația, iar același pas acoperă cea mai mare parte din ce automatizează echipele de fapt. (Notele de lansare sunt un subiect separat, tratat în scurtarea linkurilor din notele de lansare.)
| Caz de utilizare | Trigger | Slug | Ce face pasul |
|---|---|---|---|
| Preview deploy per PR | pull_request | pr-42-myapp | Upsert la fiecare push, ștergere la închidere |
| Deploy de documentație | push pe main | docs-myapp | PATCH către URL-ul documentației proaspăt implementate |
| Ultimul build | push pe main sau un tag | latest-myapp | PATCH după un ID de link salvat, fără căutare |
| Artefact nocturn | schedule | nightly-myapp | PATCH către URL-ul celui mai nou artefact |
Cazul ultimului build este cel mai simplu dintre toate. Creează linkul o singură dată de mână, salvează-i ID-ul numeric ca variabilă, iar jobul se reduce la un singur apel:
- 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}')"
Ține aceste linkuri mobile pe un 302, care este valoarea implicită când nu setezi redirect_status. Un 301 le spune browserelor că pot reține răspunsul în cache, iar cei care au dat clic ieri vor continua să ajungă pe build-ul de ieri; articolul nostru despre redirecționările 301 versus 302 are versiunea lungă.
Pentru linkurile de preview, curăță după ce PR-ul se închide. Adaugă closed la tipurile de trigger, refolosește căutarea și trimite DELETE /v1/workspaces/{workspace_id}/links/{link_id}. Un slug șters este liber pentru reutilizare. Dacă preferi să păstrezi istoricul clicurilor, folosește în schimb PATCH cu {"status": "disabled"}; upsertul de mai sus setează status: "active" la fiecare rulare, deci un PR redeschis își recapătă linkul.
Vrei să încerci pe un singur repository? Pornește un workspace gratuit, creează o cheie, iar workflowul de mai sus rulează exact cum e scris, după ce setezi cele trei variabile.
Chei API cu privilegii minime pentru CI
Cheia dintr-un secret CI ar trebui să poată face exact ce face workflowul, nimic în plus. E mai greu decât pare, din cauza felului în care funcționează cheile personale.
O cheie API personală se autentifică drept persoana care a creat-o. Orice poate face acea persoană, poate face și cheia, iar când omul pleacă din companie, cheia pleacă odată cu contul lui. Pentru CI aș folosi în schimb un utilizator-mașină: un cont de serviciu care aparține unui singur workspace, are propriul rol și tokenuri pe care doar un administrator uman autentificat le poate emite sau revoca. Creează-l la Machine users în dashboard, cu rolul de editor, care este cel mai mic rol încorporat ce poate crea, edita și șterge linkuri, apoi emite pentru el un token cu dată de expirare. Dezactivarea utilizatorului-mașină anulează dintr-o dată toate tokenurile pe care le deține, ceea ce este exact butonul de care ai nevoie în ziua în care un secret se scurge.
Alte patru obiceiuri nu costă nimic:
- Un token per repository, numit după el, ca urma de audit să spună ce repository a creat ce link.
- Secrete de mediu cu recenzenți obligatorii pentru orice workflow care schimbă un link pe care se bazează oamenii.
permissions:setat explicit la începutul workflowului, ca în exemplu, caGITHUB_TOKENsă primească doar ce are nevoie jobul.- Niciodată
pull_request_targetca să ajungi la secret din PR-uri de fork. Articolul GitHub Security Lab despre prevenirea cererilor pwn arată de ce rularea de cod necontrolat lângă un token cu drept de scriere se termină prost.
Workspaces pot restricționa accesul la API și printr-o listă de IP-uri permise. E un control puternic pentru runnerele self-hosted cu ieșire fixă și aproape inutil pentru runnerele găzduite de GitHub, ale căror adrese vin dintr-un grup foarte mare și schimbător. Referința GitHub pentru utilizare sigură merită o oră dacă workflowurile tale ating producția.
Ce se strică în practică
Cele mai multe eșecuri vin din patru locuri, iar fiecare apare ca o eroare lizibilă dacă --fail-with-body este activat. Un 404 la fiecare apel înseamnă de obicei că variabila cu ID-ul workspace-ului e greșită sau că cheia aparține altui workspace. Un 400 care spune domain_id is required înseamnă că variabila este goală, de obicei pentru că a fost setată pe alt mediu decât cel folosit de job. Un 409 este coliziunea de spațiu de nume partajat din secțiunea despre idempotență. Iar un 429 înseamnă că ai depășit limita de rată pe cheie, pe care un singur upsert per rulare nu o atinge, dar o matrice de cincizeci de joburi o poate atinge.
Un lucru nu este deloc o eroare. După un PATCH, un vizitator poate ajunge încă un timp la vechea destinație, pentru că redirecționările sunt reținute în cache aproape de vizitator, ca să rămână rapide. Un smoke test care verifică noua destinație imediat după actualizare va fi instabil. Interoghează cu un backoff scurt sau verifică în schimb răspunsul API.
Dacă vrei ca pasul să raporteze spre exterior, asociază-l cu webhookuri pentru evenimente de link, care se declanșează când un link se schimbă, sau cu tiparele curl și jq din ghidul CLI pentru teste locale înainte să faci commit workflowului. Referința API și SDK listează fiecare câmp pe care îl acceptă endpointurile de linkuri.
Citește articolul-pilon → Gestionează-ți linkurile scurte ca Terraform
Pe același subiect pe blog
- API de scurtător de URL: limite de rată, reîncercări și idempotență - regulile de siguranță la reîncercare pe care se bazează acest workflow.
- CLI pentru scurtător de URL - aceleași apeluri curl și jq, din terminalul tău.
- API gratuit de scurtător de URL - apelul de creare în curl, JavaScript, Python și Go.
- Scurtătoare de URL pentru dezvoltatori - linkuri scurte în prezentări, README-uri și proiecte open-source.
- Webhookuri pentru evenimente de link - reacționează când un job CI schimbă un link.
- Linkuri scurte în GitLab CI - același tipar upsert, ca job .gitlab-ci.yml.
Întrebări frecvente
Pot crea linkuri scurte din GitHub Actions?
Da. Un pas de workflow poate apela API-ul REST al oricărui scurtător cu curl, preinstalat pe runnerele găzduite de GitHub, împreună cu jq. Pasul citește cheia API dintr-un secret criptat, trimite URL-ul de destinație și scrie URL-ul scurt rezultat într-un output de pas, ca pașii următori să îl poată posta într-un comentariu de pull request sau într-un rezumat de job.
Cum stochez o cheie API de scurtător de URL în GitHub Actions?
Salvează-o ca secret criptat de repository sau de mediu, apoi mapează-o în singurul pas care o folosește, cu o intrare env precum ELIDO_API_KEY: secrets.ELIDO_API_KEY în sintaxa de expresii. GitHub maschează valoarea în loguri. Păstrează valorile care nu sunt secrete, precum ID-ul workspace-ului și ID-ul domeniului, în variabile de configurare, ca să rămână lizibile.
Cum evit crearea de linkuri scurte duplicate la fiecare rulare a workflow-ului?
Fă din pas un upsert. Derivă slugul din ceva stabil, de exemplu numărul pull requestului, caută-l întâi și trimite un PATCH ca să schimbi destinația când există deja. Creează doar când căutarea revine goală. Un header Idempotency-Key pe apelul de creare acoperă cazul separat al unei cereri reîncercate după un timeout de rețea.
De ce primește workflow-ul meu un 409 la crearea unui link scurt?
Slugul este deja ocupat pe acel domeniu. În Elido, slugurile sunt unice per domeniu de redirecționare, iar un domeniu partajat este împărțit cu toate celelalte workspaces, deci un slug generic precum pr-12 există probabil deja. Adaugă un prefix sau un sufix de proiect la slug sau folosește propriul domeniu personalizat, unde tot spațiul de nume îți aparține.
Funcționează pașii cu linkuri scurte pe pull requesturi din fork-uri?
Nu cu triggerul simplu pull_request, pentru că GitHub nu transmite secretele repository-ului către workflowurile pornite dintr-un fork. Sari peste job pentru fork-uri cu o condiție if pe repository-ul head. Trecerea la pull_request_target ca să obții secretul este riscantă, pentru că rulează cu acces de scriere lângă cod pe care nu l-ai revizuit.
Ar trebui un link care indică ultimul build să folosească o redirecționare 301 sau 302?
Folosește 302 sau 307. Browserele au voie să rețină un 301 în cache pe termen nelimitat, deci vizitatorii care revin ar ateriza mereu pe un build vechi după ce workflowul tău a mutat linkul. Linkurile Elido folosesc implicit 302 când nu setezi redirect_status, ceea ce este alegerea corectă pentru orice link a cărui destinație o schimbă un pipeline.
Încearcă Elido
Lipește un URL, obții un link scurt funcțional
Fără înregistrare. Linkul este activ timp de 30 de zile. Înregistrează-te ca să-l păstrezi pentru totdeauna.
Fără înregistrare. Creează un cont gratuit pentru 50 linkuri pe lună.