This article is also available in English

Es gibt einige Gründe, wieso wir unsere Abhängigkeiten auf dem aktuellen Stand halten wollen und sollten. Neben neuen Features, die neue Versionen oft mitbringen, und möglichen Verbesserungen der Performanz spielt hier vor allem Sicherheit eine große Rolle. Neue Versionen beheben gefundene, bekannte oder noch unbekannte Sicherheitslücken und machen somit unsere Anwendung robuster. Und auch langfristig hilft es uns, neue Versionen zeitnah zu integrieren, auch ohne dass diese akut Sicherheitsprobleme beheben. Nämlich in Vorbereitung für das nächste Sicherheitsupdate.

Gut zu sehen war dieses Problem bei Log4Shell. Für diese Lücke gab es zwar zeitnah Sicherheitsupdates, allerdings haben es manche Anwendungen bis heute nicht geschafft, auf diese zu aktualisieren. Vielfach liegt dies daran, dass diese Anwendungen einen sehr alten Stand einsetzen und das Update dementsprechend kompliziert ist und dadurch ein hohes Risiko für Fehler aufweist.

Damit wir selbst nicht in dieser Falle landen, sollten wir Updates unserer Abhängigkeiten zeitnah erledigen. Dabei gibt es vor allem drei Herausforderungen. Zuerst die hohe Anzahl von Abhängigkeiten. In der Regel haben wir, selbst in kleineren Anwendungen, schnell eine mittlere zweistellige Anzahl von Abhängigkeiten. Die Programmiersprache selbst, Frameworks und Bibliotheken, die wir einsetzen und Werkzeuge, die wir zum Bauen und Paketieren benutzen. Anschließend ist auch potenziell jedes Update selbst eine Herausforderung. Je nach Versionssprung und den enthaltenen Änderungen reicht dieses dabei von der Erhöhung einer Versionsnummer bis zu größeren Umbauten an unserem Code. Und zuletzt müssen wir überhaupt erst einmal mitbekommen, dass es ein Update gibt.

Um neue Updates mitzubekommen, habe ich mich bisher zumeist auf RSS-Feeds oder Mailinglisten verlassen. Zumindest für große Projekte funktioniert dies zuverlässig. Allerdings muss ich dann immer noch aktiv daran denken, das Update auch durchzuführen. Und bei einer hohen Anzahl von Abhängigkeiten muss ich auch eine große Menge von Quellen verfolgen.

Genau an dieser Stelle gibt es mit Renovate eine Bot­basierte Lösung für dieses Problem. Der Bot analysiert dafür unser Source-Code-Repository und extrahiert unsere Abhängigkeiten. Für diese wird anschließend geprüft, ob es neuere Versionen gibt. Sollte es neuere geben, erstellt der Bot einen neuen Branch, erhöht dort die Versionsnummer in einem Commit und erstellt einen Merge Request (MR), bei GitHub auch Pull Request genannt.

Um in diesem Artikel nicht, mehr oder weniger, die Dokumentation zu wiederholen, wollen wir uns im Folgenden die Verwendung, erweiterte Konfiguration und den Betrieb von Renovate an einem Beispiel anschauen.

Beispielprojekt

Da uns in diesem Artikel nur die Abhängigkeiten unseres Projekts interessieren, spielt die Fachlichkeit, ausnahmsweise, keine Rolle. Deswegen wird hier auch der eigentliche Code der Anwendung nicht zu sehen sein.

Es handelt sich bei unserem Projekt um eine Java-Anwendung, die mit Maven gebaut wird und als Framework auf Spring Boot setzt. Zur Verwaltung der Java- und Maven-Version wird SDKMAN! verwendet. Für Maven wird zusätzlich der Maven-Wrapper verwendet.

Zusätzlich benötigen wir Node.js, um CSS und JavaScript zu bundlen. Hierzu setzen wir zum einen den Node Version Manager (nvm) ein und zum anderen, innerhalb von Maven, das frontend-maven-plugin. Da die Anwendung als Container betrieben wird, gibt es zudem ein Dockerfile.

Als Source-Code-Repository wird ein selbst gehostetes GitLab eingesetzt. In diesem läuft auch eine Pipeline mit GitLab CI zum Bauen und Paketieren der Anwendung. Diese benötigt ein eigenes Container-Image, das über die Datei Dockerfile.release gebaut wird. Zu guter Letzt wird innerhalb der Pipeline auch noch jbang genutzt, um in Java geschriebene Skripte auszuführen.

Dieser Stack ist dabei nicht aus der Luft gegriffen, sondern entspricht dem wirklichen Stack einer von mir entwickelten und intern eingesetzten Anwendung. Es gibt mit Sicherheit einfachere, aber auch komplizierte Stacks.

Um eine Menge an Listings zu vermeiden, verzichte ich hier darauf, alle Konfigurationsdateien im Detail zu zeigen. Wenn der konkrete Inhalt jedoch relevant ist, oder für Renovate angepasst werden muss, werden diese natürlich gezeigt.

Onboarding und erste Konfiguration

Sobald der Bot unser Repository das erste Mal sieht, wird er einen MR, siehe Abbildung 1, für das Onboarding unseres Projekts erstellen. Die eigentliche Codeänderung besteht dabei aus dem Hinzufügen der Datei renovate.json mit dem Inhalt aus Listing 1. Die Beschreibung des MR gibt uns allerdings schon einen guten Überblick darüber, was Renovate erkannt hat und welche Updates sich daraus direkt ergeben werden.

Abb. 1: Onboarding Merge Request
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json"
}
Listing 1: Onboarding renovate.json

Nachdem dieser MR gemergt wurde, wollen wir den Bot weiter konfigurieren. Zum einen möchten wir, dass sämtliche neuen MRs für Updates mir als Bearbeitendem zugewiesen werden. Zum anderen hat uns der Onboarding MR erklärt, dass nur zwei MRs pro Stunde geöffnet werden. Dieses Limit möchten wir aufheben, um alle MRs so schnell wie möglich zu erhalten. Hierzu fügen wir drei Konfigurationswerte zur Datei renovate.json hinzu, die nun wie in Listing 2 aussieht.

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "assignees": [
    "michael"
  ],
  "prConcurrentLimit": 0,
  "prHourlyLimit": 0
}
Listing 2: renovate.json mit Konfigurationswerten

Nachdem der Bot nun ohne Limits für die Anzahl von MRs und mit der Anweisung, mir diese zuzuweisen, das nächste Mal läuft, ergeben sich eine Menge an neuen MRs, siehe Abbildung 2.

Abb. 2: Renovate Merge Requests

Um komplexere Konfigurationen wiederverwenden zu können, bietet uns Renovate, neben Konfigurationswerten, auch sogenannte Presets zur Verwendung an. Diese werden über den Konfigurationswert extends eingebunden. Renovate bringt hier von Haus aus schon eine große Anzahl mit. Darunter befindet sich auch das Preset config:recommended. Da dieses, wie der Name sagt, empfohlen wird, wollen wir es auch nutzen. Die Dokumentation dieses Presets zeigt uns, dass dieses wiederum aus einer Menge an anderen Presets besteht.

Jetzt, wo wir Presets kennen, stellen wir auch unsere vorherigen Konfigurationswerte auf diese um. Für alle drei gibt es jeweils ein spezialisiertes, welches etwas lesbarer ist und die Konfiguration verkürzt. Außerdem wollen wir noch, dass Renovate alle offenen MRs jeweils rebased, sobald es neue Commits auf dem Hauptbranch gibt. Hierzu fügen wir noch das Preset :rebaseStalePrs hinzu. Unsere Renovate­Konfiguration sieht nun wie in Listing 3 aus.

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:recommended",
    ":assignee(michael)",
    ":prConcurrentLimitNone",
    ":prHourlyLimitNone",
    ":rebaseStalePrs"
  ]
}
Listing 3: renovate.json mit Presets

Durch diese neue Konfiguration ergeben sich beim nächsten Lauf von Renovate drei Änderungen. Zuerst werden alle schon offenen MRs gerebased, weil wir durch die Konfigurationsänderungen neue Commits auf unserem Hauptbranch durchgeführt haben. Zweitens ergibt sich ein neuer MR für unsere Abhängigkeiten zum JDK, da Renovate dieses durch die empfohlene Konfiguration nun kennt.

Zuletzt hat Renovate auch noch ein Dependency Dashboard (s. Abb. 3) in Form eines Issues angelegt. Dieses listet dabei primär alle aktuell offenen MRs auf. Es enthält jedoch auch eine Liste aller erkannten Abhängigkeiten und wird auch genutzt, um gefundene Probleme zu kommunizieren. Kann Renovate beispielsweise gewisse Abhängigkeiten nicht auflösen oder wird eine Fehlkonfiguration erkannt, werden diese hier gemeldet. Außerdem kann das Dashboard auch für die Interaktion mit dem Bot genutzt werden, wie wir im nächsten Abschnitt sehen werden.

Abb. 3: Dependency Dashboard

Updates konfigurieren

Wie wir sehen können, gibt es schnell, vor allem in Bestandsprojekten, eine große Anzahl von Updates, die durchgeführt werden sollten. So schlägt uns der Bot beispielsweise ein Update von Spring Boot 3.0.10 auf 3.1.4 vor. Es gibt jedoch, mit 3.0.11, eine weitere Version von 3.0. Möchten wir auch für dieses Update einen MR erhalten, sollten wir den Konfigurationswert separateMinorPatch auf true setzen oder das Preset :separatePatchReleases hinzufügen. Anschließend werden wir einen zusätzlichen MR für das Update auf 3.0.11 erhalten. Möchten wir zusätzlich für die Major und Minor Updates von Spring Boot, da wir diese auf andere Wege mitbekommen, gar keine MRs mehr erhalten, können wir dies über eine spezifische Konfiguration für einzelne Abhängigkeiten, siehe Listing 4, erledigen.

{
  ...
  "packageRules": [
    {
      "matchDatasources": ["maven"],
      "matchPackageNames": [
        "org.springframework.boot:spring-boot-starter-parent",
        "org.springframework.boot:spring-boot-dependencies"
      ],
      "matchUpdateTypes": ["major", "minor"],
      "enabled": false
    }
  ]
}
Listing 4: Deaktivieren von Major und Minor Updates für Spring Boot

Der empfohlene Mittelweg für diesen Fall wäre es jedoch, wie oben erwähnt, das Dependency Dashboard für diese Updates zu verwenden. Hierzu ersetzen wir die Konfiguration von "enabled": false aus Listing 4 durch "dependencyDashboardApproval": true. Anschließend enthält das Dependency Dashboard Issue den in Abbildung 4 zu sehenden Teil und der MR für das Update auf Spring Boot 3.1.4 würde erst nach Aktivierung der Checkbox erstellt.

Abb. 4: Dashboard Approval für Minor Spring Boot Update

Unterstützung für jbang, SDKMAN! und das frontend-maven-plugin

Nicht immer werden alle Anwendungsfälle direkt von Renovate unterstützt. Wenn es jedoch darum geht, Abhängigkeiten in bisher nicht unterstützten Dateien zu erkennen, können wir selbst für eine Lösung sorgen.

Die Komponente, um Abhängigkeiten in Dateien zu erkennen, wird innerhalb von Renovate als Manager bezeichnet. Neben vielen spezialisierten gibt es auch einen konfigurierbaren, welcher die Extraktion über Reguläre Ausdrücke vornimmt. So ermöglicht es uns jbang über Kommentare am Anfang der Datei, siehe Listing 5, Abhängigkeiten zu spezifizieren, welche dann zur Ausführung des Skripts heruntergeladen werden. Diese werden leider standardmäßig nicht von Renovate erkannt. Allerdings lassen sie sich gut über einen regulären Ausdruck erkennen, sodass wir einen eigenen Manager, siehe Listing 6, konfigurieren können.

///usr/bin/env jbang "$0" "$@" ; exit $?
//JAVA 17+
//JAVA_OPTIONS -Dapple.awt.UIElement=true
//DEPS org.springframework.boot:spring-boot-dependencies:3.0.10@pom
//DEPS org.springframework.boot:spring-boot-starter-jdbc
//DEPS com.h2database:h2
//DEPS org.json:json:20230227
//DEPS commons-codec:commons-codec

class GenerateDatabase {
    public static void main(String[] args) {
        ...
    }
}
Listing 5: jbang-Skript
{
  ...
  "customManagers": [
    {
      "customType": "regex",
      "fileMatch": [
        "^bin/.*\\.java$"
      ],
      "matchStrings": [
        "//DEPS (?<depName>.*?:.*?):(?<currentValue>[^@\n]*)(@pom)?"
      ],
      "datasourceTemplate": "maven"
    }
  ]
}
Listing 6: Regex-Manager für jbang-Skripte

Dieser Manager guckt in allen Dateien, die auf .java enden und im Ordner bin liegen, ob die definierte Zeichenkette gefunden wird. Wird sie gefunden, werden über benannte Gruppen Informationen aus dieser extrahiert. Die Gruppe currentValue ist dabei Pflicht und muss die aktuell genutzte Version enthalten. Weiterhin müssen wir noch den Namen der Abhängigkeit und die Quelle, bei Renovate datasource genannt, erkennen. Dies können wir entweder über den regulären Ausdruck oder fixe Templates der Konfiguration erledigen. In diesem Fall extrahieren wir den Namen über die Gruppe depName und setzen die Quelle über das Template datasourceTemplate fix auf maven.

Beim nächsten Lauf wird Renovate nun zwei weitere Abhängigkeiten, nämlich org.springframework.boot:spring-boot-dependencies und org.json:json, erkennen. Da über unsere config:recommended auch das Preset group:recommended angezogen wird und dieses wiederum auf group:springBoot verweist, wird für neue Spring Boot-Versionen nur ein MR eröffnet, welcher gleichzeitig die Version in unserem jbang-Skript und in der POM aktualisiert.

Ein ähnliches Problem existiert für unsere Nutzung von SDKMAN!. Diese wird nicht automatisch unterstützt, lässt sich aber über weitere Regex-Manager, siehe Listing 7, lösen.

{
  ...
  "customManagers": [
    ...
    {
      "customType": "regex",
      "fileMatch": [
        "^\\.sdkmanrc$"
      ],
      "matchStrings": [
        "java=(?<currentValue>.*)\\n"
      ],
      "datasourceTemplate": "java-version",
      "depNameTemplate": "java"
    },
    {
      "customType": "regex",
      "fileMatch": [
        "^\\.sdkmanrc$"
      ],
      "matchStrings": [
        "maven=(?<currentValue>.*)\\n"
      ],
      "datasourceTemplate": "maven",
      "depNameTemplate": "maven",
      "packageNameTemplate": "org.apache.maven:apache-maven",
      "versioningTemplate": "maven"
    }
  ]
}
Listing 7: Regex-Manager für Java- und Maven-Versionen in SDKMAN!

In diesem Projekt gibt es noch eine weitere Stelle, die wir mit einem Regex-Manager lösen. So führt die Nutzung des frontend-maven-plugin dazu, dass wir dort die zu verwendende Version von Node.js angeben müssen. Auch diese Stelle wird nicht automatisch von Renovate erkannt. Listing 8 löst jedoch auch dieses Problem.

{
  ...
  "customManagers": [
    ...
    {
      "customType": "regex",
      "fileMatch": [
        "^pom.xml$"
      ],
      "matchStrings": [
        "<nodeVersion>v(?<currentValue>.*)</nodeVersion>"
      ],
      "datasourceTemplate": "node-version",
      "depNameTemplate": "node"
    }
  ]
}
Listing 8: Regex-Manager für Node.js-Version im frontend-maven-plugin

Wie wir in Abbildung 5 sehen können, werden nun über diese Zusatzkonfiguration weitere fünf Abhängigkeiten erkannt und wir haben weniger manuelle Arbeit zu erledigen.

Abb. 5: Vom Regex-Manager erkannte Abhängigkeiten

Container und Digest Pinning/Updates

Standardmäßig bringt Renovate bereits eine breite Unterstützung für Container mit. So wurden sowohl die Basis-Images in unseren beiden Dockerfiles als auch innerhalb der GitLab CI-Konfiguration erkannt (s. Abb. 6).

Abb. 6: Erkannte Container-Abhängigkeiten

Allerdings werden in Dockerfile.release, siehe Listing 9, zusätzliche Pakete über den Paketmanager von Alpine Linux installiert. Hierbei wird empfohlen, auch für diese Pakete eine fixe Version anzugeben.

FROM alpine:3.18.2
SHELL ["/bin/sh", "-euo", "pipefail", "-c"]

RUN apk --no-cache add --update \
      bash=5.2.15-r5 \
      docker-cli=23.0.6-r4 \
      httpie=3.2.1-r4
listing 9: Dockerfile.release

Natürlich möchten wir auch für diese von Renovate über Updates benachrichtigt werden. Hierzu existiert zum Glück bereits mit regexManagers:dockerfileVersions ein Preset, das wir nutzen können. Allerdings müssen wir hierzu auch unser Dockerfile.release, wie in Listing 10 zu sehen, anpassen. Hier nutzen wir eine Kombination aus Kommentaren und Variablen, um die Versionen zu spezifizieren, welche unter der Haube erneut durch einen Regex-Manager erkannt werden können.

FROM alpine:3.18.2
SHELL ["/bin/sh", "-euo", "pipefail", "-c"]

# renovate: datasource=repology depName=alpine_3_18/bash versioning=loose
ENV APK_BASH_VERSION="5.2.15-r5"

# renovate: datasource=repology depName=alpine_3_18/docker-cli versioning=loose
ENV APK_DOCKER_CLI_VERSION="23.0.6-r4"

# renovate: datasource=repology depName=alpine_3_18/httpie versioning=loose
ENV APK_HTTPIE_VERSION="3.2.1-r4"

RUN apk --no-cache add --update \
      bash="${APK_BASH_VERSION}" \
      docker-cli="${APK_DOCKER_CLI_VERSION}" \
      httpie="${APK_HTTPIE_VERSION}"
Listing 10: Dockerfile.release mit Renove-Unterstützung

Container haben jedoch noch eine zweite Besonderheit, nämlich Floating Tags. Kurz gesagt bedeutet dies, dass auch wenn ich einen spezifischen Tag von einem Container-Image wie eclipse-temurin:17.0.7_7-jdk-alpine angebe, ich nicht zwangsweise immer exakt dasselbe Image erhalte. Container Tags sind nämlich nicht unveränderlich und dementsprechend kann sich das Ziel, auf das diese zeigen, über die Zeit ändern. Deswegen ist es eine gute Idee, das exakt genutzte Image anzugeben. Hierzu ist die zusätzliche Angabe eines Digests erforderlich. Hierbei können wir uns wieder von Renovate unterstützen lassen. Hierzu können wir zuerst das Preset docker:pinDigests nutzen. Beim nächsten Lauf wird uns Renovate einen MR erstellen, siehe Abbildung 7, in dem alle erkannten Images mit einem Digest versehen werden.

Abb. 7: MR für Container Digest Pinning

Zwar können sich Tags aus beliebigen Gründen ändern, in der Praxis liegt dies jedoch meistens daran, dass innerhalb unseres Tags die Version einer Abhängigkeit, zum Beispiel des JDKs, fixiert wird, sich aber das zugrunde liegende Basis-Image aufgrund von Paketupdates verändert hat. Da wir diese Updates auch durchführen sollten, wird es nach dem Digest Pinning vermehrt MRs von Renvoate geben, bei denen nur das Digest aktualisiert wird. Um von diesen nicht überrannt zu werden, kann es, bei passender Testabdeckung und Sicherheitsempfinden, sinnvoll sein, diese von Renovate selbst automatisch mergen zu lassen, ohne selbst etwas tun zu müssen. Hierzu können wir das Preset :automergeDigest nutzen. Renovate wird nun, sobald die Pipeline für einen Digest Update MR erfolgreich gelaufen ist, diesen selbst mergen.

Eigene Presets

Durch all diese Konfigurationen ist unsere renovate.json nun bereits ein ganzes Stück gewachsen und hier und da auch unübersichtlich geworden. Möchten wir nun auch noch Teile dieser Konfiguration in anderen Projekten wiederverwenden, ist spätestens der Zeitpunkt gekommen, diese in eigene Presets auszulagern. Das Ergebnis für dieses Projekt ist in Listing 11 zu sehen.

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "local>example/renovate//presets/java",
    ":assignee(michael)",
    ":automergeDigest",
    ":separatePatchReleases"
  ],
  "packageRules": [
    {
      "matchDatasources": ["maven"],
      "matchPackageNames": [
        "org.springframework.boot:spring-boot-starter-parent",
        "org.springframework.boot:spring-boot-dependencies"
      ],
      "matchUpdateTypes": ["major", "minor"],
      "dependencyDashboardApproval": true
    }
  ],
  "customManagers": [
    {
      "customType": "regex",
      "fileMatch": [
        "^bin/.*\\.java$"
      ],
      "matchStrings": [
        "//DEPS (?<depName>.*?:.*?):(?<currentValue>[^@\n]*)(@pom)?"
      ],
      "datasourceTemplate": "maven"
    },
    {
      "customType": "regex",
      "fileMatch": [
        "^pom.xml$"
      ],
      "matchStrings": [
        "<nodeVersion>v(?<currentValue>.*)</nodeVersion>"
      ],
      "datasourceTemplate": "node-version",
      "depNameTemplate": "node"
    }
  ]
}
Listing 11: Renovate-Konfiguration mit eigenen Presets

Wir haben sowohl einige der vorher verwendeten Presets als auch die Regex-Manager für die SDKMAN!-Konfiguration entfernt und dafür das Preset local>example/renovate//presets/java hinzugefügt. Dieses Preset sagt, dass Renovate im gleichen GitLab (local) im Projekt example/renovate die Datei presets/java.json, siehe Listing 12, einbinden soll. In diesem Preset hat nun die SDKMAN!-Konfiguration einen globalen Platz gefunden. Außerdem wird ein Preset für die Unterstützung von Maven-Properties zur Konfiguration von Abhängigkeitsversionen, regexManagers:mavenPropertyVersions, aktiviert und ein weiteres eigenes Preset, siehe Listing 13, verwendet.

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "local>example/renovate//presets/default",
    "regexManagers:mavenPropertyVersions"
  ],
  "customManagers": [
    {
      "customType": "regex",
      "fileMatch": [
        "^\\.sdkmanrc$"
      ],
      "matchStrings": [
        "java=(?<currentValue>.*)\\n"
      ],
      "datasourceTemplate": "java-version",
      "depNameTemplate": "java"
    },
    {
      "customType": "regex",
      "fileMatch": [
        "^\\.sdkmanrc$"
      ],
      "matchStrings": [
        "maven=(?<currentValue>.*)\\n"
      ],
      "datasourceTemplate": "maven",
      "depNameTemplate": "maven",
      "packageNameTemplate": "org.apache.maven:apache-maven",
      "versioningTemplate": "maven"
    }
  ]
}
Listing 12: Eigenes Preset für Java-Projekte
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:best-practices",
    ":prConcurrentLimitNone",
    ":prHourlyLimitNone",
    ":rebaseStalePrs",
    "regexManagers:dockerfileVersions"
  ]
}
Listing 13: Eigenes Default Preset

Dieses Preset wiederum stellt die Basis auch für nicht Java-Projekte dar. Hierbei verlassen wir uns auf unzählige, sinnvolle Einstellungen, die über das eingebaute Preset config:best-practices eingestellt werden. Zusätzlich heben wir die Limits auf, aktivieren das automatische Rebase und die Unterstützung von Paketen in Dockerfiles.

Konfiguration und Setup des Bots

Jetzt haben wir viel über die Nutzung und Konfiguration von Renovate aus Sicht der Projekte gesehen. Jedoch stellt sich noch die Frage, wie wir diesen Bot nun betreiben können. In unserem aktuellen Setup nutzen wir die in GitLab selbst vorhandene Funktion von Scheduled Pipelines. Dabei wird periodisch die in Listing 14 zu sehende Pipeline durchlaufen.

stages:
  - renovate

renovate:
  stage: renovate
  image: renovate/renovate:37.11.1@sha256:64090c90f002fc7668921d119d245b85535ee4575330e03993acd7bad48570ab
  rules:
    - if: '$CI_PIPELINE_SOURCE == "schedule"'
  script:
    - renovate
  cache:
    key: ${CI_COMMIT_REF_SLUG}-renovate
    paths:
      - renovate/cache/renovate/repository/
Listing 14: Renovate GitLab CI-Konfiguration

Diese basiert auf dem aktuellen Renovate-Container-Image und startet den Bot. Dieser wird über die Datei config.js, siehe Listing 15, konfiguriert. Durch die Verwendung von autodiscover wird der Bot automatisch alle Projekte verwalten, die er finden kann. Dies ermöglicht es uns, dass Projekte, die Renovate benutzen möchten, nur den technischen GitLab-Nutzer des Bots als Mitglied ins Projekt einladen müssen. Durch unsere eigene onboardingConfig sorgen wir dafür, dass bereits beim Onboarding standardmäßig unser eigenes Default Preset konfiguriert wird.

module.exports = {
  platform: 'gitlab',
  endpoint: process.env.CI_API_V4_URL,
  autodiscover: true,
  dryRun: null,
  gitAuthor: 'Renovate Bot <[email protected]>',
  onboardingConfig: {
    '$schema': 'https://docs.renovatebot.com/renovate-schema.json',
    extends: [
      'local>example/renovate//presets/default',
    ],
  },
  baseDir: `${process.env.CI_PROJECT_DIR}/renovate`,
  optimizeForDisabled: true,
  repositoryCache: 'enabled',
  registryAliases: {
    '$CI_REGISTRY': process.env.CI_REGISTRY,
  },
  hostRules: [
    {
      hostType: 'docker',
      matchHost : 'https://index.docker.io',
      username : 'examplebot,
      password : process.env.DOCKERHUB_TOKEN,
    },
    {
      matchHost: `https://${process.env.CI_REGISTRY}`,
      username: 'renovate-bot',
      password: process.env.RENOVATE_TOKEN,
    },
    {
      hostType: 'maven',
      matchHost: process.env.CI_API_V4_URL,
      token: process.env.RENOVATE_TOKEN,
    },
  ],
}
Listing 15: Renovate-Konfiguration

Der Eintrag unter registryAliases ermöglicht es, in den projektspezifischen GitLab CI-Konfigurationen die Umgebungsvariable CI_REGISTRY zu verwenden. Ohne diesen Eintrag würde Renovate nicht erkennen, dass dieser Teil des Images ein Platzhalter ist.

Die hostRules dienen der Autorisierung bei diversen Abhängigkeitsquellen. So müssen wir uns bei der öffentlichen Docker Registry, dem Docker Hub, einloggen, um ein höheres Limit an API Requests zu haben und nicht blockiert zu werden. Die beiden anderen Einträge erlauben es, die GitLab eigene Container und Package Registry abzufragen. Hierbei ist jedoch zusätzlich zu beachten, dass der technische Nutzer des Bots Zugriff auf die passenden Projekte benötigt, da er diese ansonsten nicht sehen darf.

Da wir innerhalb desselben GitLab laufen, in dem wir auch die Projekte verwalten, können wir an vielen Stellen auf vorhandene Umgebungsvariablen zurückgreifen. Trotzdem müssen wir zusätzliche, siehe Abbildung 8, definieren. Neben dem Token für den Docker Hub, DOCKERHUB_TOKEN, benötigen wir einen Token für GitHub: GITHUB_COM_TOKEN. Von dort werden Release-Notes per API abgefragt und in die Beschreibung des MR eingefügt. Der RENOVATE_GIT_PRIVATE_KEY wird von Renovate genutzt, um die erstellten Commits in Git zu signieren. Der RENOVATE_TOKEN letztlich dient dem Zugriff auf GitLab selbst und wird deswegen auch zwingend benötigt.

Abb. 8: Zusätzliche Umgebungsvariablen für den Bot

Fazit

In diesem Artikel haben wir mit Renovate einen Bot kennengelernt, der uns dabei unterstützt, unsere Abhängigkeiten auf dem neusten Stand zu halten. Dies ist vor allem notwendig, um Sicherheitslücken schnell schließen zu können.

Neben der Basiskonfiguration haben wir uns auch diverse Möglichkeiten zur Anpassung angeschaut. Dabei haben wir gesehen, wie wir über Konfigurationswerte oder Presets die standardmäßigen Limits aufheben können, wie wir dafür sorgen, dass die MRs immer auf dem aktuellen Commit-Stand bleiben und auch einer Person zugewiesen werden. Anschließend haben wir gesehen, wie spezifische Updates weiter konfiguriert werden können und wie wir über eigene Regex-Manager weitere Abhängigkeiten erkennen können. Auch haben wir eine Lösung für Pakete innerhalb von Container-Images und die Nutzung von Floating Tags kennengelernt.

Zum Schluss haben wir auch noch gesehen, wie wir den Bot innerhalb von GitLab mithilfe von Scheduled Pipelines betreiben können.