Oui, GitLab CI peut servir de raccourcisseur d'URL. Un job avec curl et une seule clé API peut créer un lien court à chaque merge request, le faire pointer vers la review app et déplacer un lien stable latest vers la nouvelle version lorsque vous poussez un tag. Cela représente environ quarante lignes de YAML. Et cela fonctionne dès aujourd'hui.
Ce que les gens font mal ne concerne pas l'appel HTTP. C'est la clé : où elle est stockée, quels pipelines peuvent la lire et l'ampleur des dégâts si un job de branche la divulgue. Ce guide consacre donc autant de place aux variables et à leur portée qu'au fichier .gitlab-ci.yml lui-même. Si vous préférez gérer des liens longue durée de manière déclarative, l'approche Terraform des liens courts conviendra mieux ; les pipelines sont adaptés aux liens qui naissent et disparaissent avec le code.
Commençons par une précision sur le statut. L'intégration GitLab native d'Elido arrive, mais elle n'est pas encore disponible, et rien de ce qui suit n'en dépend. Vous voulez la version gérée ? La page d'intégration GitLab propose la liste d'attente.
Ce que fait un job de raccourcisseur d'URL GitLab CI
Un raccourcisseur dans un pipeline fait trois choses, et seulement trois. Il crée un lien quand le slug n'existe pas, modifie la destination quand il existe et désactive le lien quand ce vers quoi il pointait disparaît. Les analyses et les QR codes restent du côté d'Elido.
La surface de l'API est réduite. Les liens se trouvent sous /v1/workspaces/{workspace_id}/links : POST en crée un et nécessite un domain_id ainsi qu'une destination_url, PATCH /links/{link_id} modifie les champs d'un lien existant et GET /links?q= recherche par slug, destination ou titre. L'authentification tient à un seul en-tête : Authorization: Bearer elido_.... La clé vient de la page des clés API du tableau de bord.
C'est tout le contrat. La présentation de l'API et des SDK répertorie les autres endpoints, mais un pipeline a rarement besoin de plus que ces trois-là.
Stocker la clé comme variable masquée et protégée
GitLab vous donne ici deux interrupteurs importants, qui ont des rôles différents. Le masquage cache une valeur dans les journaux de job. La protection contrôle quels pipelines reçoivent cette valeur.
Dans Settings, CI/CD, Variables, choisissez Masked and hidden lors de la création de la variable. Hidden (généralement disponible depuis GitLab 17.6) signifie que personne ne pourra ensuite révéler la valeur dans la page des paramètres, ce qui est souhaitable pour un identifiant. La documentation des variables CI/CD de GitLab énumère les exigences d'une valeur masquée : une seule ligne, sans espaces et d'au moins 8 caractères. Les clés Elido sont composées de elido_ suivi de base32, elles remplissent donc ces conditions.
La même page est très claire sur la limite : le masquage "is not a guaranteed way to prevent malicious users from accessing variable values." Un job qui encode la variable en base64 puis l'affiche passe directement au-delà du masque. Considérez le masquage comme une mesure d'hygiène des journaux, pas comme un contrôle d'accès.
La protection est le contrôle d'accès. Une variable protégée n'atteint que les pipelines des branches ou tags protégés, ce qui crée le problème que toutes les équipes rencontrent pendant leur première semaine : votre pipeline de merge request s'exécute sur une branche de fonctionnalité, la clé protégée arrive donc sous forme de chaîne vide et le job échoue avec une 401 qui ressemble à une faute de frappe.
Je réglerais cela avec deux clés plutôt qu'en affaiblissant la première. Voici la configuration que j'utiliserais :
| Variable | Visibilité | Protégée | Lue par |
|---|---|---|---|
ELIDO_PREVIEW_KEY | Masked and hidden | Non | Pipelines de merge request |
ELIDO_RELEASE_KEY | Masked and hidden | Oui | Pipelines de tags protégés |
ELIDO_PREVIEW_WS, ELIDO_RELEASE_WS | Visible | Non | Tout job (les ID ne sont pas secrets) |
ELIDO_DOMAIN_ID, SHORT_HOST | Visible | Non | Tout job |
La clé de preview appartient à un workspace distinct qui ne contient que des liens de review. Toute personne capable de pousser une branche peut, en principe, exfiltrer une variable non protégée ; assurez-vous donc que le pire résultat possible se limite à un tas de liens jetables mr-142, tandis que la clé de release vit dans votre workspace réel et ne s'exécute que sur les tags que vous avez protégés.
Attribuez aux deux clés le rôle Editor et une date d'expiration ; 90 jours conviennent à celle de preview. Editor est le préréglage le moins élevé capable d'écrire des liens, et il peut aussi les supprimer ; les clés API utilisent l'un des rôles prédéfinis, et j'aimerais disposer d'un préréglage permettant uniquement de créer et de modifier pour ce cas précis ; il n'existe pas encore. C'est la séparation des workspaces qui limite réellement le rayon d'action.
Un job .gitlab-ci.yml fonctionnel pour créer un lien court
Voici le morceau commun : une opération de création ou de mise à jour qui recherche le slug, crée le lien s'il manque et le modifie dans le cas contraire. Placez-le dans un job masqué et étendez-le.
.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
La recherche q est une correspondance partielle, donc le filtre jq la réduit au slug exact sur le domaine exact. Sans lui, une recherche de web-mr-14 renverrait volontiers web-mr-142. Obtenez une fois votre domain_id avec GET /v1/workspaces/{id}/domains et stockez-le comme variable ordinaire ; un hôte brandé configuré grâce aux domaines personnalisés est plus lisible dans une merge request qu'un hôte générique.
Des liens courts de review app pour chaque merge request
Les review apps sont le nom que GitLab donne à un environnement temporaire par branche ou merge request, et la documentation des review apps les construit avec des environnements dynamiques. Leurs URL sont généralement peu élégantes : un hash, un namespace, le nom d'hôte d'un fournisseur cloud. Un lien court comme go.example.com/web-mr-142 est quelque chose que vous pouvez prononcer à voix haute pendant un point d'équipe.
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"
L'astuce est le rapport dotenv. L'opération de création ou mise à jour écrit SHORT_URL dans link.env, GitLab le relit et environment:url devient le lien court ; le bouton View app de la merge request ouvre donc web-mr-142 au lieu du nom d'hôte brut. La documentation des environnements décrit ce modèle d'URL dynamique.
CI_MERGE_REQUEST_IID est unique par projet et ne change jamais pendant toute la durée de la merge request ; chaque push vers la même MR arrive donc sur le même slug et l'opération le modifie au lieu de créer des doublons. La référence des variables prédéfinies contient la liste complète si vous voulez utiliser une autre clé.
Le nettoyage est un job avec action: stop. Il doit partager les mêmes rules que le job de démarrage, sinon GitLab ne peut pas le déclencher automatiquement :
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"
Je désactive plutôt que de supprimer. Un lien désactivé conserve son historique de clics et, si quelqu'un rouvre la MR, le pipeline suivant le repasse à active via la même opération de création ou mise à jour. GIT_STRATEGY: none est présent parce que la branche peut avoir disparu entre-temps.
Si vos review apps sont dix fois plus nombreuses que vos releases, c'est là que les limites du plan commencent à se faire sentir. Vérifiez le quota de liens sur la page de tarification avant de brancher cela à un monorepo actif, et commencez avec un workspace gratuit pour les previews pendant vos essais.
Réorienter un lien latest stable dans les pipelines de tags
Le deuxième modèle s'exécute sur les tags et fait l'inverse du lien de review : un slug qui ne change jamais, dont la destination avance à chaque release. Votre README peut pointer indéfiniment vers 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+$/
Associez la règle à un modèle de tag protégé comme v* afin que seuls les mainteneurs puissent créer les tags qui le déclenchent ; sinon la clé protégée ne sera tout simplement pas disponible et le job échouera par défaut, ce qui est le comportement recherché. Si vous voulez également un lien permanent par version, exécutez une seconde fois le même job avec SLUG: "cli-${CI_COMMIT_REF_SLUG}", ce qui transforme v1.4.0 en cli-v1-4-0.
Ne définissez pas redirect_status sur 301 pour un lien latest. Une 301 est une promesse de permanence que les navigateurs sont autorisés à mettre en cache, et un lien latest rompt cette promesse à chaque release. Elido utilise 302 par défaut lorsque vous laissez le champ vide, et notre article sur les redirections 301 et 302 détaille les cas où ce choix a réellement des conséquences.
Une réserve honnête : une nouvelle destination peut mettre quelques minutes à atteindre chaque emplacement edge, si bien qu'un test rapide qui interroge le lien court dès la seconde suivante peut encore voir la version précédente. Vérifiez plutôt la réponse de l'API ou attendez avant de contrôler l'en-tête Location.
Idempotence, nouvelles tentatives et limites de débit
Les pipelines réessaient. Les runners s'arrêtent au milieu d'un job, quelqu'un clique sur Retry dans un job en échec, et deux pushes arrivent à trente secondes d'intervalle en se disputant la même ressource. L'opération de création ou mise à jour ci-dessus résiste aux trois situations, et il vaut la peine de comprendre pourquoi.
L'en-tête Idempotency-Key rend un POST relancé sûr : l'API met en cache une réponse réussie pendant 24 heures et la rejoue pour la même clé, si bien qu'une nouvelle tentative du même pipeline récupère le lien d'origine au lieu d'une erreur. Construire la clé à partir de CI_PIPELINE_ID et du slug signifie que les nouvelles tentatives au sein d'un pipeline sont rejouées, tandis qu'un nouveau pipeline obtient une nouvelle tentative. La branche 409 gère la course entre deux pipelines différents, et le chemin recherche puis modification rend une seconde exécution effectivement sans effet.
Les limites de débit s'appliquent par clé, en plus d'une limite par workspace, et les workspaces tout juste créés ont aussi un plafond quotidien plus bas de création de liens pendant qu'ils construisent leur réputation. Quelques merge requests ne le remarqueront pas. Un monorepo qui lance quarante review apps à la fois pourrait le remarquer ; traitez donc une 429 comme réessayable avec le mot-clé retry de GitLab et échouez explicitement sur une 402, qui signifie une limite de plan plutôt qu'une erreur transitoire. Notre article approfondi sur les limites de débit et l'idempotence des API de raccourcisseurs couvre le backoff plus en détail que nécessaire dans un job CI.
Ignorez le filtre jq de correspondance exacte et le pipeline de la MR 14 modifiera discrètement le lien de la MR 142. Le premier symptôme est généralement un designer perplexe. Gardez le filtre.
Si le shell dans YAML devient difficile à manier, les mêmes appels s'intègrent proprement dans un script que vous validez dans le dépôt, et le guide du CLI de raccourcisseur d'URL montre cette forme.
Consultez l'article pilier → Liens courts avec Terraform : gérer les liens comme du code
Articles associés du blog
- Démarrage rapide de l'API du raccourcisseur d'URL - la surface REST appelée par chacun des jobs ci-dessus.
- Limites de débit et idempotence des API de raccourcisseurs - pourquoi la logique de nouvelle tentative est conçue ainsi.
- Un raccourcisseur d'URL en ligne de commande - les mêmes appels sous forme de script réutilisable.
- Redirections 301 et 302 - pourquoi un lien mobile nécessite une redirection temporaire.
- Surveiller les redirections de liens avec Sentry et Datadog - détecter une destination défaillante une fois le pipeline terminé avec succès.
- Des liens courts depuis GitHub Actions - l'équivalent GitHub, avec les secrets et la concurrence.
Questions fréquentes
GitLab CI peut-il créer des liens courts ?
Oui. Tout job capable d'exécuter curl peut appeler l'API REST d'un raccourcisseur d'URL, donc un job GitLab CI peut créer un lien court, modifier sa destination ou le désactiver. La clé API est stockée dans une variable CI/CD masquée et le job l'envoie comme jeton Bearer. Aucune intégration GitLab native n'est nécessaire pour cela.
Comment stocker une clé API en toute sécurité dans GitLab CI ?
Ajoutez-la sous Settings, CI/CD, Variables avec la visibilité définie sur Masked and hidden, et cochez Protect variable si seuls les branches ou tags protégés doivent pouvoir la lire. Le masquage empêche la valeur d'apparaître dans les journaux de job, mais la documentation de GitLab précise que ce n'est pas une défense garantie ; limitez donc la clé elle-même au strict nécessaire.
Pourquoi ma variable protégée est-elle vide dans un pipeline de merge request ?
Les variables protégées ne sont transmises qu'aux pipelines exécutés sur des branches ou tags protégés. Par défaut, un pipeline de merge request issu d'une branche de fonctionnalité ne remplit pas cette condition, donc la variable arrive vide. Utilisez soit une clé distincte, non protégée et moins privilégiée pour les jobs de review, soit gardez la clé protégée uniquement pour les pipelines de tags.
Comment donner un lien court à chaque review app GitLab ?
Exécutez un job sur les pipelines de merge request qui crée ou met à jour un slug construit à partir du nom du projet et de CI_MERGE_REQUEST_IID, en le faisant pointer vers l'URL de la review app. Écrivez l'URL courte obtenue dans un rapport dotenv et utilisez-la comme environment:url, afin que le widget de la merge request y mène directement. Un job d'arrêt désactive le lien quand l'environnement s'arrête.
Un lien court vers la dernière version doit-il utiliser une redirection 301 ou 302 ?
Utilisez une 302. La destination d'un lien latest change à chaque version, tandis qu'une 301 indique aux navigateurs et aux caches que le déplacement est permanent ; certains clients continueront donc à envoyer les visiteurs vers l'ancienne version. Elido définit par défaut les nouveaux liens sur 302 quand vous ne renseignez pas redirect_status, ce qui convient ici.
Existe-t-il une intégration GitLab native pour Elido ?
Pas encore. Une intégration GitLab native est en préparation et vous pouvez vous inscrire sur la liste d'attente depuis la page d'intégration GitLab. Tout ce guide fonctionne dès aujourd'hui via l'API REST publique depuis un job de pipeline, sans rien installer côté GitLab au-delà d'une variable CI/CD.
Essayer Elido
Collez une URL, obtenez un lien court
Sans inscription. Lien actif 30 jours. Inscrivez-vous pour le garder pour toujours.
Gratuit, sans inscription · 2 par jour