Avez-vous déjà rencontré des difficultés à installer un package R depuis ses sources hébergées sur un GitLab privé ?
Vous avez trop bataillé avec {remotes}, {gert}, {git2r} et obtenu des :
Error: Failed to install 'unknown package' from Git:
Error in 'git2r_remote_ls': unexpected http status code: 403
Que vous avez résolu (ou pas), sans trop comprendre comment vous avez fait ? (Et parfois en mettant en dur des tokens sur-puissants dans vos codes…)
Si oui, cet article devrait vous intéresser. Faisons le point sur les différentes options possibles.
Je ne vais me concentrer que sur les cas problématiques, c’est-à-dire ceux où votre package n’est pas ouvert en lecture au monde entier ,autrement dit, les cas qui nécessitent de batailler avec des tokens. Je pars du principe que votre GitLab privé est à jour (au moins en version 17.x).
Spoiler : il faudra bien distinguer le cas d’une installation en local sur votre ordinateur de celui d’une chaîne de CI. Et il faut avoir les idées claires sur les dépendances nécessaires à votre package, en particulier si ces dépendances sont elles aussi sur votre GitLab privé.
Sommaire
Token, kézako ?
Un token (ou jeton d’accès) est une chaîne de caractères qui remplace votre mot de passe pour l’authentification automatisée. Contrairement à votre mot de passe, un token peut :
- Avoir des droits limités (lecture seule, accès à certains projets uniquement…)
- Avoir une date d’expiration configurable
- Être révoqué individuellement sans changer votre mot de passe
Pour générer un token GitLab, rendez-vous sur https://votre.gitlab.fr/-/user_settings/personal_access_tokens`. Sélectionnez au minimum les scopesread_apietread_repository` pour pouvoir installer des packages.
⚠️ Règle d’or : n’utilisez jamais votre mot de passe en lieu et place d’un token ! Si votre token fuite (dans un script, un dépôt public, un log de CI…), vous pouvez le révoquer sans compromettre l’accès à votre compte. Avec un mot de passe, c’est une autre histoire… pensez a utiliser des token différents en fonction de votre usage.
En local : la base avec remotes::install_gitlab
Notre premier réflexe serait d’utiliser remotes::install_gitlab. Celui-ci utilise par défaut gitlab.com en tant que host. Il est possible de surcharger le paramètre host pour pointer vers votre GitLab privé comme ceci :
remotes::install_gitlab(
repo = "vincent/monpackage@main",
host = "https://gitlab.thinkr.fr",
auth_token = "glpat-XXX35"
)
Cette approche ne fonctionnera en l’état que si :
- Le token que vous utilisez a accès à l’API et accès en lecture au dépôt
- Votre package
monpackagene dépend pas lui-même d’un autre package privé de votre GitLab
Et oui, même si les droits associés au token permettraient de récupérer la dépendance, le auth_token n’est pas transmis aux « sous-processus » mis en œuvre par {remotes} pour installer les dépendances.
Donc ce n’est pas pleinement satisfaisant comme approche.
Gérer les dépendances privées : configurer les credentials
Pour pouvoir faire une installation propre avec des dépendances privées, il faut aussi définir votre token dans les options de {remotes} :
options(remotes.git_credentials = git2r::cred_user_pass("oauth2", "glpat-XXX35"))
Ou plus proprement, en passant par une variable d’environnement. Éditez votre .Renviron (avec usethis::edit_r_environ()), puis redémarrez R :
GITLAB_PAT=glpat-XXX35
Ensuite :
options(remotes.git_credentials = git2r::cred_user_pass("oauth2", Sys.getenv("GITLAB_PAT")))
À noter que install_gitlab() utilisera par défaut la variable d’environnement GITLAB_PAT si elle existe, ce qui permet de lancer directement :
remotes::install_gitlab(
repo = "vincent/monpackage@main",
host = "https://gitlab.thinkr.fr"
)
Pour des raisons de maintenance et d’explicitation, j’ai plutôt tendance à faire ceci, c’est plus clair :
remotes::install_gitlab(
repo = "vincent/monpackage@main",
host = "https://gitlab.thinkr.fr",
auth_token = Sys.getenv("GITLAB_PAT")
)
Ou à utiliser l’approche « généraliste » de {remotes} :
remotes::install_git(
url = "https://gitlab.thinkr.fr/vincent/monpackage.git",
credentials = git2r::cred_user_pass("oauth2", Sys.getenv("GITLAB_PAT"))
)
Déclarer une dépendance privée dans votre package
Maintenant, regardons comment indiquer dans le code source de votre package que celui-ci dépend d’un autre package de votre GitLab privé.
Ça se définit au niveau du fichier DESCRIPTION de votre package : il faut expliciter dans le champ Imports le nom du package en dépendance, puis dans le champ Remotes indiquer où se trouve ce package.
Package: monpackage
...
Imports:
unedependance
Remotes:
git::https://gitlab.thinkr.fr/thinkr/bakacode/unedependance.git@main
Vous pourrez aussi trouver des écritures de la forme :
[email protected]::thinkr/bakacode/unedependance.git@main ou
gitlab::thinkr/bakacode/[email protected] dans le champ Remotes.
C’est très spécifique à GitLab et impose l’usage d’un GITLAB_PAT. Ça gère mal les hôtes autres que gitlab.com et en pratique ça n’apporte aucun confort d’usage, donc restons sur des Remotes de la forme `git::https://gitlab.thinkr.fr` . (Mais si je suis passé à côté d’un avantage de cette syntaxe, je suis preneur de vos retours !)
Le cas particulier de la CI
Si vous êtes arrivé jusqu’ici, vous savez comment installer un package depuis un GitLab privé. La seule condition : disposer d’un token avec les droits suffisants sur le package à installer et ses dépendances.
Parlons maintenant du cas particulier de la CI.
L’approche naïve (qui fonctionne mais…)
Techniquement, il est possible d’appliquer exactement ce que vous avez compris de la partie précédente dans la chaîne de CI. Il « suffit » de définir puis utiliser un token suffisamment puissant que vous passerez en variable à votre chaîne de CI comme ceci:
building:
stage: analyse
script:
- R -e "options(remotes.git_credentials = git2r::cred_user_pass('oauth2', Sys.getenv('GITLAB_PAT')));devtools::install_git(url = 'https://gitlab.thinkr.fr/vincent/monpackage.git', credentials = git2r::cred_user_pass('oauth2', Sys.getenv('GITLAB_PAT')))"
Cela va fonctionner dans votre gitlab-ci.yml, puisque rien ne vous en empêche.
Mais d’expérience :
- La génération du token avec exactement les bons droits est complexe
- Le token expire au bout d’un moment, ca demande de la maintenance
- Et on finit par mettre le token d’un admin à l’échelle du groupe dans GitLab… ce qui n’est pas optimal niveau sécurité
La bonne approche : le CI_JOB_TOKEN
Quand un runner se lance pour exécuter une chaîne de CI, celui-ci embarque son propre token, accessible via la variable d’environnement CI_JOB_TOKEN. C’est ce token qui est utilisé pour le git clone qui récupère le code de votre dépôt.
Ce token est :
- Temporaire : il expire à la fin du job
- Limité : il ne dispose (normalement) des droits de lecture que sur le dépôt qui lance la CI
La question devient : comment s’assurer que le CI_JOB_TOKEN dispose bien des droits pour récupérer les packages en dépendance ?
Configurer les autorisations inter-projets
Cela se gère au niveau de GitLab dans Settings > CI/CD > Job token permissions des projets en dépendance.
Prenons un exemple concret : vous souhaitez installer le package A, qui dépend du package B, qui lui-même dépend du package C (A, B et C étant privés sur votre GitLab).
Vous devez :
- Dans les options du package C : autoriser les packages A et B dans la « CI/CD job token allowlist »
- Dans les options du package B : autoriser le package A
┌─────────┐ dépend de ┌─────────┐ dépend de ┌─────────┐
│ A │ ───────────────▶ │ B │ ───────────────▶ │ C │
└─────────┘ └─────────┘ └─────────┘
│ │
autorise A autorise A, B
Astuce : si vous avez pris l’habitude de ranger vos projets dans des groupes, vous pouvez par exemple autoriser tous les projets du groupe « production » à utiliser tous les projets du groupe « outils ». Ça simplifie grandement la maintenance !
Le gitlab-ci.yml fonctionnel
Dans ce contexte, on utilisera cette option :
options(remotes.git_credentials = git2r::cred_user_pass("gitlab-ci-token", Sys.getenv("CI_JOB_TOKEN")))
Voici un exemple de gitlab-ci.yml fonctionnel pour un check de package avec des dépendances internes :
image: rocker/geospatial:4.5.0
stages:
- build-and-check-package
build-and-check-package:
stage: build-and-check-package
script:
- Rscript -e 'install.packages("remotes")'
- Rscript -e 'install.packages("git2r")'
- Rscript -e 'options(remotes.git_credentials = git2r::cred_user_pass("gitlab-ci-token", Sys.getenv("CI_JOB_TOKEN")));remotes::install_deps(upgrade = "never")'
- R -e 'rcmdcheck::rcmdcheck(args = c("--no-manual"), error_on = "warning", check_dir = "check")'
Récapitulatif : GITLAB_PAT vs CI_JOB_TOKEN
Ne pas confondre ces deux tokens, ils n’ont pas la même portée ni le même usage :
GITLAB_PAT |
CI_JOB_TOKEN |
|
|---|---|---|
| Type | Jeton personnel | Jeton automatique |
| Lié à | Un utilisateur | Un job CI |
| Droits | Hérités du compte utilisateur | Limités via allowlist |
| Durée de vie | Longue (configurable) | Expire à la fin du job |
| Usage principal | Installation locale | CI/CD |
{remotes} peut utiliser l’un ou l’autre :
# Jeton CI
options(remotes.git_credentials = git2r::cred_user_pass("gitlab-ci-token", Sys.getenv("CI_JOB_TOKEN")))
# Jeton personnel
options(remotes.git_credentials = git2r::cred_user_pass("oauth2", Sys.getenv("GITLAB_PAT")))
Checklist de dépannage
Votre installation échoue avec une erreur 403 ? Voici les points à vérifier :
En local
- [ ] Votre
GITLAB_PATest-il défini dans.Renviron? - [ ] Le token a-t-il les scopes
read_apietread_repository? - [ ] Avez-vous redémarré R après avoir modifié
.Renviron? - [ ] L’option
remotes.git_credentialsest-elle bien définie avant l’appel àinstall_*? - [ ] Le token a-t-il accès à toutes les dépendances privées (pas seulement au package principal) ?
En CI
- [ ] Les projets en dépendance autorisent-ils votre projet dans leur « Job token allowlist » ?
- [ ] Utilisez-vous bien
"gitlab-ci-token"(et non"oauth2") avecCI_JOB_TOKEN? - [ ] Le champ
Remotesde votreDESCRIPTIONutilise-t-il la syntaxe `git::https://…` ?
Pour debugger
Vous pouvez tester l’accès à l’API GitLab directement :
# Tester votre token
httr2::request("https://gitlab.thinkr.fr/api/v4/projects") |>
httr2::req_headers("PRIVATE-TOKEN" = Sys.getenv("GITLAB_PAT")) |>
httr2::req_perform() |>
httr2::resp_status()
Un code 200, c’est bon. Un 401 ou 403, c’est que votre token n’a pas les bons droits.
En résumé
Installer un package R depuis un GitLab privé, ce n’est pas sorcier, mais ça demande de comprendre quelques subtilités :
- En local : utilisez
GITLAB_PATdans votre.Renvironet configurezremotes.git_credentials - Pour vos dépendances : déclarez-les proprement dans le champ
Remotesavec la syntaxe `git::https://…` - En CI : privilégiez le
CI_JOB_TOKENet configurez les autorisations inter-projets dans GitLab
Le plus important ? Ne jamais mettre de token en dur dans votre code. Votre futur vous (et vos collègues) vous remercieront.
Vosu êtes un humain et non une IA qui passe par là ? Vous avez des questions ou des retours d’expérience sur le sujet ? Les commentaires sont ouverts !


