Blog | WeScale

Comment mettre à jour ses dépendances applicatives avec Renovate

Rédigé par Antoine Boursin | 29/11/2023

Présentation de Renovate

Renovate est un outil permettant de mettre à jour des versions de dépendances. Il est disponible en open-source sur GitHub et est géré par la société mend.io.

Renovate est compatible avec la majorité des plateformes de versioning de code : GitHub, GitLab, Bitbucket, Azure DevOps, AWS CodeCommit, Gitea et Forgejo.

Sur GitHub (.com et Enterprise Server), Renovate est disponible sous la forme d’une application partagée que l’on peut intégrer à son dépôt. Sinon, pour toutes les plateformes, Renovate peut être utilisé en auto-hébergement.

Son utilisation peut être découpée en trois grandes parties :

  1. Un dépôt Git qui comporte des fichiers avec des versions de dépendances. De nombreux langages sont nativement compatibles avec Renovate, via des managers créés par la communauté. On peut notamment citer Ansible, GitLab CI/GitHub Actions, Dart, Docker, .NET, Elixir, Go, Helm, Java, JavaScript, Perl, PHP, Python, Ruby, Rust, Swift ou encore Terraform. Il est également possible de définir ses propres managers pour répondre à des besoins particuliers.
  2. Un fichier de configuration de Renovate pour définir de quelle façon on veut mettre à jour nos dépendances. De nombreuses options de configuration sont disponibles pour personnaliser l’outil au maximum. Leur prise en main peut néanmoins être fastidieuse au premier abord, on peut donc utiliser un jeu de règles par défaut fourni par Renovate. Par exemple, le preset config:recommended fournit une bonne base pour débuter avec des valeurs par défaut adaptées à la plupart des projets. Ensuite, il est possible de personnaliser ce jeu de règles avec différentes options ; c’est d’ailleurs ce que nous verrons dans la suite de cet article !
  3. Un processus périodique pour exécuter Renovate en lui-même. Le principe de base est de le lancer régulièrement pour qu’il propose les modifications de versions des dépendances via des Pull Requests. Plusieurs options d’installation sont disponibles : via un package NPM qu’on pourra ensuite lancer périodiquement via un cron, ou bien une image Docker que l’on peut faire tourner avec un orchestrateur. Cette image Docker peut également être utilisée dans un pipeline de CI. C’est d’ailleurs ce que nous allons voir dans la prochaine section grâce à un exemple avec GitLab.

Tutoriel sur Renovate avec GitLab

Nous allons utiliser une application d’exemple avec un frontend en React et un backend en Node.js, et une base de données MongoDB.

La première étape est de copier ces fichiers dans un projet GitLab. Dans cette application, on retrouve 3 types de dépendances : des packages NPM, des fichiers Dockerfile et un fichier Docker-Compose.

Clone de l’application de démonstration

On peut alors activer Renovate sur ce projet. Pour cela, sur GitLab, l’outil met à disposition un pipeline Renovate Runner en libre accès, qui se base sur les pipeline schedules. Il s’agit simplement d’une façon de lancer de manière régulière un pipeline sur GitLab. Il fonctionne par l’inclusion d’un pipeline via la directive include. Il suffit donc de créer un fichier .gitlab-ci.yml qui contient le code suivant (en remplaçant la version par la dernière disponible) : 

# .gitlab-ci.yml
include:
  - remote: https://gitlab.com/renovate-bot/renovate-runner/-/raw/v16.46.2/templates/renovate.gitlab-ci.yml

Inclusion de pipeline GitLab

Pour configurer ce pipeline, il faut définir 2 variables de CI/CD au niveau du projet GitLab : 

  • RENOVATE_TOKEN qui contient un Personal Access Token généré au niveau de notre projet GitLab ;
  • RENOVATE_EXTRA_FLAGS qui contient des arguments supplémentaires pour la CLI Renovate, dans notre cas --autodiscover=true . Il indique à Renovate de travailler sur tous les projets auxquels il a accès, dans notre cas uniquement le projet d’exemple, car le token est restreint à ce scope.

Ensuite, il faut créer un pipeline schedule (Build > Pipeline Schedules). Pour l'exemple, nous allons l’exécuter tous les jours à midi.

Pipeline Schedule dans GitLab

Il est alors possible de forcer l’exécution du pipeline schedule depuis la page dédiée de GitLab, et on peut retrouver le résultat de l’exécution dans les logs : 

 INFO: Autodiscovered repositories
       "repositories": ["antoine.boursin/wescale-demo-renovate"]
 INFO: Repository started (repository=antoine.boursin/wescale-demo-renovate)
       "renovateVersion": "37.57.2"
 INFO: Branch created (repository=antoine.boursin/wescale-demo-renovate, branch=renovate/configure)
       "commit": "5fe8ef91d09da95b0d08eaa423ce08ce67827f69",
       "onboarding": true
 INFO: Dependency extraction complete (repository=antoine.boursin/wescale-demo-renovate, baseBranch=main)
       "stats": {
         "managers": {
           "docker-compose": {"fileCount": 1, "depCount": 1},
           "dockerfile": {"fileCount": 2, "depCount": 6},
           "npm": {"fileCount": 2, "depCount": 22}
         },
         "total": {"fileCount": 5, "depCount": 29}
       }
 INFO: Onboarding PR created (repository=antoine.boursin/wescale-demo-renovate)
       "pr": "Pull Request #1"
 INFO: Repository finished (repository=antoine.boursin/wescale-demo-renovate)
       "cloned": true,
       "durationMs": 16352

Logs de l’exécution de Renovate

On y constate que Renovate a détecté notre projet, les dépendances qu’il contient et qu’il a créé une Merge Request de OnBoarding pour nous aider à le configurer sur ce projet. Si on ne souhaite finalement pas activer Renovate, on peut simplement fermer cette merge request et il ne prendra plus en compte ce projet.

Merge Request de OnBoarding

Dedans, il y a un fichier de configuration basique de Renovate, qui peut être utilisé en l’état même si des améliorations sont possibles (ce que nous ferons dans la seconde moitié de l’article !).

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:recommended"
  ]
}

Configuration minimale proposée par Renovate

On y retrouve également la liste des dépendances détectées et les mises à jour disponibles. Après relecture, on peut merger cette Merge Request et réexécuter le pipeline schedule de Renovate. Cette fois-ci, il y a beaucoup plus d’actions dans les logs : Renovate est allé vérifier les différentes versions de nos dépendances et a créé des Merge Requests pour celles qui n’étaient pas à jour.

(nombreuses) Merge Requests ouvertes par Renovate

Renovate a également ouvert une Issue nommée Dependency Dashboard, pour nous permettre de suivre les mises à jour de nos dépendances. En cas de comportement non attendu, on peut s’y référer pour trouver une explication. C’est par exemple le cas si des Merge Requests ont été bloquées à cause d’un rate-limiting. On peut alors forcer leur exécution en cochant les cases présentes sur l’issue.

Dependency Dashboard de Renovate

On peut alors passer à une configuration plus fine de Renovate, c’est ce que nous allons voir avec les meilleures options de Renovate dans la partie suivante.

Nos options recommandées pour Renovate

Les options présentées ci-dessous peuvent être ajoutées dans le fichier renovate.json pour améliorer son efficacité et l’adapter à nos besoins.

Regrouper les mises à jour

La première chose que l’on constate est que Renovate peut ouvrir un grand nombre de Merge Requests. Il est possible de grouper les mises à jour d’un même type dans une seule Merge Request. Pour cela, il faut utiliser le paramètre packageRules. Il permet d’appliquer des règles uniquement à certaines dépendances en fonction de leur nom ou bien du fichier dans lequel elles se trouvent. Dans l’exemple ci-dessous, on regroupe toutes les dépendances Node.js dans une seule Merge Request.

{
    "packageRules": [
     {
       "matchManagers": ["npm"],
       "groupName": "nodejs dependencies"
     }
   ]
}

packageRules pour grouper les dépendances Node.js

Si on relance Renovate, on constate que la plupart des Merge Requests ont été fermées, et les mises à jour regroupées dans deux Merge Requests : une pour les mises à jour majeures et une pour les autres, mineures ou patch.

Il ne reste que deux Merge Requests pour les dépendances Node.js

Bloquer certaines mises à jour majeures ou mineures

Dans certains cas, on veut ne mettre à jour que la version de patch d’une dépendance. C’est notamment le cas si on travaille avec des versions de Kubernetes (v1.Y.Z) sans mettre à jour la version mineure (le Y). Par exemple, si on utilise la version 1.25.2, on souhaite une mise à jour vers la version 1.25.3 mais pas vers la version 1.26.1.

Pour cela, on peut de nouveau utiliser le paramètre packageRules pour modifier les actions sur les versions de Kubernetes. Dans l’exemple ci-dessous, la première règle permet de séparer les Merge Requests majeures, mineures et de patch (car par défaut les mineures et les patch sont regroupées). La seconde désactive les mises à jour majeures et mineures pour Kubernetes, pour ne garder que les patch.

{
    "packageRules": [
      {
        "matchPackageNames": ["kubernetes/kubernetes"],
        "separateMajorMinor": true,
        "separateMinorPatch": true
      },
      {
        "matchPackageNames": ["kubernetes/kubernetes"],
        "matchUpdateTypes": ["major", "minor"],
        "enabled": false
      }
    ]
}

packageRules pour mettre à jour uniquement les patch

Se conformer à Conventional Commits

L’initiative Conventional Commits vise à encadrer les messages de commit que l’on peut pousser sur les projets Git. Cela peut donner des commits comme deps(renovate): update package to vX.Y.Z. Pour obtenir ce résultat, trois paramètres sont à activer pour configurer l’utilisation de Conventional Commits, et personnaliser le type et le scope. Leur format est un peu différent des autres car il s’agit de presets, des morceaux de configuration fournis par Renovate (comme config:recommended que nous avons utilisé précédemment).

{
 "extends": [
   ":semanticCommits",
   ":semanticCommitTypeAll(deps)",
   ":semanticCommitScope(renovate)"
 ]
}

Activation des Conventional Commits avec Renovate

Il est à noter que la valeur par défaut pour le paramètre semanticCommits est auto, qui va analyser les commits précédents dans le projet et détecter s’ils utilisent les Conventional Commits ou non. Les nouveaux commits de Renovate seront adaptés en fonction de cette détection.

Accéder à des registries privées

Parfois, les dépendances que notre application utilise sont disponibles uniquement après authentification. Il est donc nécessaire pour Renovate d’avoir des identifiants qui lui permettent d’aller vérifier les versions qui existent. Le paramètre à utiliser est hostRules, qui fonctionne de manière semblable à packageRules mais qui configure les paramètres liés à des hôtes distants.

{
  "detectHostRulesFromEnv": true,
  "hostRules": [
    {
      "hostType": "docker",
      "matchHost": "registry.company.com",
      "username": "serviceaccount"
    }
  ]
}

hostRules pour accéder à une registry privée

Dans l’exemple ci-dessus, nous n’avons bien évidemment pas le mot de passe écrit en clair dans le fichier de configuration, il est donc nécessaire de le passer en tant que variable d’environnement avec un format spécifique. Pour cela, il faut activer le paramètre detectHostRulesFromEnv et créer une variable d’environnement secrète DOCKER_REGISTRY_COMPANY_COM_PASSWORD=P4S$W0RD pour le définir.

Pour les autres types de gestionnaires de package, le format de la variable d’environnement est assez proche, par exemple pour Artifactory on pourra utiliser une règle "hostType": "maven" et une variable MAVEN_ARTIFACTORY_COMPANY_COM_PASSWORD.

Détecter des formats de dépendances spécifiques

Parfois, certaines dépendances ne peuvent pas être détectées automatiquement, et on peut alors utiliser des expressions régulières pour aller les chercher dans les différents fichiers.

Dans l’exemple suivant, on va trouver dans le fichier Dockerfile une ligne qui commence par ENV YARN_VERSION, pour récupérer la version actuelle et proposer des mises à jour.

{
  "customManagers": [
    {
      "customType": "regex",
      "fileMatch": ["^Dockerfile$"],
      "matchStrings": ["ENV YARN_VERSION=(?<currentValue>.*?)\\n"],
      "depNameTemplate": "yarn",
      "datasourceTemplate": "npm"
    }
  ]
}

customManager regex

Plus d’informations sont disponibles dans la documentation officielle de Renovate : https://docs.renovatebot.com/modules/manager/regex/ 

Mettre à jour Renovate lui-même

En plus des dépendances applicatives, Renovate peut se mettre à jour lui-même ! Dans notre cas, on utilise un pipeline GitLab avec une version fixée qui peut être détectée avec le manager regex de la section précédente. Ensuite, Renovate ouvrira une Merge Request lorsque des nouvelles versions seront disponibles.

{
  "customManagers": [
    {
      "customType": "regex",
      "fileMatch": ["^\\.gitlab-ci\\.yml$"],
      "matchStrings": ["https://gitlab\\.com/renovate-bot/renovate-runner/-/raw/(?<currentValue>.*?)/templates/renovate\\.gitlab-ci\\.yml"],
      "depNameTemplate": "renovate-bot/renovate-runner",
      "datasourceTemplate": "gitlab-tags"
    }
  ]
}

Configuration Renovate pour se mettre à jour lui-même sur GitLab

Si on active ce paramètre, il est aussi intéressant d’activer la migration automatique de configuration pour que Renovate mette à jour lui-même sa configuration lorsque des options sont renommées ou modifiées. Pour cela, il faut utiliser le paramètre "configMigration": true. Lorsqu’un paramètre sera mis à jour, Renovate ouvrira une Merge Request pour proposer le changement.

Accélérer l’exécution de Renovate

Certaines dépendances (comme Renovate lui-même) sortent des nouvelles versions de manière régulière. Dans ces cas-là, l’exécution de Renovate peut être ralentie le temps que toutes les notes de versions soient lues. Pour éviter cela, le paramètre fetchChangeLogs permet de ne pas les récupérer. En le combinant avec le paramètre packageRules que nous avons vu précédemment, il est possible d’ignorer la récupération de ces notes de version uniquement pour certaines dépendances qui comportent beaucoup de releases et de le garder pour toutes les autres qui sont plus légères.

{
    "packageRules": [
      {
        "matchDepNames": ["renovate-bot/renovate-runner"],
        "fetchChangeLogs": "off"
      }
    ]
}

Ignorer la récupération des notes de version pour Renovate

Adapter la fréquence de mise à jour

Pour finir, il est nécessaire de bien réfléchir à quelle fréquence Renovate doit s’exécuter. Il est par exemple inutile de le lancer toutes les heures si on n’applique les mises à jour qu’une fois par semaine. De plus, cela peut lancer un pipeline de build lors de la mise à jour d’une Merge Request, et donc consommer des ressources inutilement.

Avec le preset recommandé par défaut, Renovate ne va ouvrir que 2 Merge Requests maximum par heure, il peut donc être intéressant de mettre le paramètre prHourlyLimit à 0 pour lever cette limite.

On peut ne pas le lancer le weekend ou la nuit avec le paramètre schedule qui prend en compte une syntaxe cron ou des mots clés. Par exemple, after 9am and before 6pm every weekday autorisera la création de Merge Request par Renovate de 9h à 18h en semaine. Cette restriction peut également se faire au niveau de la planification de l’exécution de Renovate (directement dans le cron ou le pipeline schedule par exemple).

Conclusion

Renovate est un outil puissant pour mettre à jour les dépendances de versions, qu’elles soient applicatives, d’infrastructure ou même dans des cas plus génériques. Cela permet d’éviter de potentielles failles de sécurité apportées par des bibliothèques externes dont dépendent nos codes.

L’outil est également très flexible et peut s’adapter à tous les projets sur lesquels on veut l’utiliser. On peut d’ailleurs le faire fonctionner avec la plupart des plateformes d’hébergement de code, là où certains de ses concurrents comme Dependabot ne fonctionnent que sur une plateforme donnée (GitHub en l’occurrence). La configuration par défaut fonctionne bien pour des cas simples, et si on veut faire des choses plus poussées, il suffit de consulter la documentation (bien fournie) de Renovate : https://docs.renovatebot.com.

Dans cet article, nous vous avons proposé quelques-unes de ces options que nous utilisons nous-même et qui permettent d’améliorer l’efficacité de Renovate. Même si elles ne couvrent bien évidemment pas tous les cas d’utilisation, elles permettent néanmoins d’améliorer la configuration par défaut, bien qu’elle soit déjà très efficace.