Renovating GitLab Repos


Over the past week, I've been working on getting my various dependencies up to date in my GitLab instance repositories. The tool I'm using is Mend Renovate, an open-source solution by the folks at Mend (formerly WhiteSource).

Let me state up front that I don't love the license here, it's AGPL (formerly MIT for versions prior to version 12.0.0), but for my purposes, it's OK since I'm not planning on modifying (other that potentially to submit bug fixes or improvements) and I'm not providing the application as a service to others (which is the key additional restriction).

Generally speaking, you're going to want a container environment for running Renovate. Although you can run it using NPM, you're going to need an environment in which the code and dependencies are available for inspection, so you need all of the dependency-retrieving code available in your environment. For this, and for isolation, I'm using containers.

Documentation for self-hosting Renovate in general is pretty extensive and is available for Self-hosted Mend Renovate online. Fair warning: there are a lot of knobs on this software.

Bootstrapping renovate

My first experiments involved using the well-maintained GitLab Runner, that is freely available and itself is updated by mend to the latest docker. The docs for the running in this configuration are straightforward and provide a sufficient understanding of how to install in order to get your first results quickly.

The recommendation is to use a dedicated private project to host the runner, and I concur with that. I have a dedicated group for experiments like this and it fit well in that location.

You'll need to define a number of CI/CD variables in order for it to work, but that's straightforward and well documented.

Initially, I used the RENOVATE_EXTRA_FLAGS to specify individual projects instead of using automated onboarding. As a rule, I found the explicit support to work well, and wildcards were OK, but regex was very finicky, especially when using negatives via the prefix ! .

Make sure you put in a GitHub token as well as a GitLab token, since you will want to have authenticated requests to GitHub in order to avoid the rate limits.

My final .gitlab-ci.yml used the full image in order to be able to handle a broader array of dependencies and allowed for manual as well as scheduled operation:

include:
    - remote: https://gitlab.com/renovate-bot/renovate-runner/-/raw/v12.13.0/templates/renovate.gitlab-ci.yml

image: ${CI_RENOVATE_IMAGE_FULL}

renovate:
    rules:
      - if: $CI_PIPELINE_SOURCE == "schedule"
      - if: $CI_PIPELINE_SOURCE == "web"
      - if: $CI_PIPELINE_SOURCE == "push"

Note: you may want to renovate to keep the script version up-to-date.

Once you've got this running, individual repositories carry their configurations in renovate.json files (which may be stored in a variety of locations, I generally put mine in .gitlab/renovate.json). When not present, the repository is ignored.

Permissions and users

While it is possible to run Renovate as yourself, especially when you're only running it on your repositories, there is less confusion if you have a separate bot user dedicated to providing these updates.

By using a separate user, you can fully scope access, which is especially important if you have admin access to your repositories or have access to a wider array of repos than you want to give Renovate access to.

Further, if you decide to go with Mend Renovate On-Premises, you'll find that the webhooks are basically ineffective if it can't distinguish between your actions and its own by the user making the change.

In the end, I thought it was definitely worth it, because I was also able to enable autodiscovery since each repo (or group) was intentionally onboarded to Renovate.

Mend Renovate On-Premises

There's also a version of Renovate that's designed to run with its own scheduler and responds to webhooks from GitLab. This version of Mend Renovate On-Premises is what I've moved to over the weekend. This one definitely wants a stable container environment to run in, and one in which you'll need to be able to communicate between your source control system (in my case GitLab) and the renovate server (otherwise, you won't get the benefits of the webhooks, and might as well stick with the easier-to-manage cron-only version above).

This is licensed software from Mend, and requires a key in order to run. The keys are available for Free once you request them. It took a little time, and you should expect to be prospected for sales, but that's certainly fair.

Once the key is received, you'll need to prepare to configure your environment. In my case, this is a small kubernetes environment. There's a Helm chart that is mostly well documented in the aforementioned repository, that goes through the basics, installation with Helm and configuration for both GitHub and GitLab—in both cases either self-hosted or SaaS.

Setting up the environment via Helm was straightforward and I created an application in my gitlab-managed environment, using .gotmpl in order to fill in my secret information using the GitLab CI/CD variables and runtime information.

My helmfile.yaml looks like this:

repositories:
- name: renovate-on-prem
  url: https://mend.github.io/renovate-on-prem

releases:
- name: renovate
  namespace: YOUR-NAMESPACE-HERE
  chart: renovate-on-prem/whitesource-renovate
  version: 3.1.3
  installed: true
  values:
    - values.yaml.gotmpl

which basically adds the chart repo and then runs a specific version based on the chart available and using the included values file.

My values.yaml.gotmpl is a bit more complex:

credentials:
  gitlab_access_token: {{ requiredEnv "RENOVATE_GITLAB_TOKEN" }}
  github_access_token: {{ requiredEnv "RENOVATE_GITHUB_TOKEN" }}

renovate:
  acceptWhiteSourceTos: 'y'
  licenseKey: {{ requiredEnv "RENOVATE_LICENSE_KEY" }}
  renovatePlatform: gitlab
  renovateEndpoint: {{ requiredEnv "GITLAB_API_URL" }}
  renovateToken: {{ requiredEnv "RENOVATE_GITLAB_TOKEN" }}
  githubComToken: {{ requiredEnv "RENOVATE_GITHUB_TOKEN" }}
  webhookSecret: {{ requiredEnv "RENOVATE_WEBHOOK_TOKEN" }}

  config: |
    module.exports =  {
      "autodiscoverFilter": ["!/{data,experiment,imported}/.*/"],
      "packageRules": [
        {
          "matchUpdateTypes": ["major", "minor", "patch", "digest", "bump"],
          "addLabels": ["dependencies"]
        },
        {"matchLanguages": ["ruby"], "addLabels": ["ruby"]},
        {"matchLanguages": ["java"], "addLabels": ["java"]},
        {"matchLanguages": ["python"], "addLabels": ["python"]},
        {"matchLanguages": ["php"], "addLabels": ["php"]},
        {"matchLanguages": ["js"], "addLabels": ["js"]},
        {"matchLanguages": ["docker"], "addLabels": ["docker"]},
        {"matchLanguages": ["git-submodules"], "addLabels": ["submodule"]}
      ],
      "git-submodules": {"enabled": true},
      "hostRules": [
        { "matchHost": "{{ requiredEnv "CI_SERVER_HOST" }}", "token":"{{ requiredEnv "RENOVATE_GITLAB_TOKEN" }}" },
        { "matchHost": "{{ requiredEnv "CI_SERVER_HOST" }}", "hostType": "docker", "username": "token", "password":"{{ requiredEnv, "RENOVATE_GITLAB_TOKEN" }}" },
        { "matchHost": "{{ requiredEnv "CI_SERVER_HOST" }}", "hostType": "helm", "username": "token", "password":"{{ requiredEnv "RENOVATE_GITLAB_TOKEN" }}" }
      ],
      "dependencyDashboard":"true",
      "dependencyDashboardLabels": ["dashboard"]
    }

podSecurityContext:
  fsGroup: 1000

cachePersistence:
  enabled: true
  storageClass: longhorn

ingress:
  enabled: true
  ingressClassName: nginx
  annotations:
    cert-manager.io/cluster-issuer: "YOUR_CERT_ISSUER"
    nginx.ingress.kubernetes.io/whitelist-source-range: "YOUR_CI_SERVER"

  hosts:
    - YOUR_RENOVATE_HOSTNAME
  tls:
    - secretName: renovate-fe-cert
      hosts:
        - YOUR_RENOVATE_HOSTNAME

Before using this, take a good look at it. There are some common items here, and some lessons learned.

  • If you don't set the fsGroup and you use the cache, you may find that the cache is not writable. The application runs as UID/GID 1000, so this setting just makes sure the application can write to it.
  • I'm using Longhorn for my local storage, so storageClass is set to that. If you're using something different, take appropriate changes
  • For getting the webhooks, I am using an nginx ingress controller, metallb, and cert-manager with support for ACME. I constrained the access to the ingress server using the nginx.ingress.kubernetes.io/whitelist-source-range annotation. TLS is configured and my gitlab server is checking for appropriate TLS certificates.
  • The weird hostRules lines are because I need to authenticate back to the gitlab server for a couple of repository types. I've pulled this discussion into Renvating GitLab Registries after finding some further nuance.
  • The documentation on Renovate has a lot of settings. There are multiple ways to add default changes. In this case, I chose to force settings by applying them globally. You can also do these locally using renovate.json files (which will be sought out as the indicator that you want Renovate to run in those repos), and by specifying global defaults. Read the documentation and look for best practices.
  • The defaults mostly worked, but I like to have my PRs labeled, so I added the various packageRules in order to set labels.
  • I find the Dependency Dashboard to be useful, so I configured it (and also set a label for it, so I could do a an easy global search for a sort-of dashboard of dashboards).

Although the helm chart seems up-to-date for the most part, I did eventually start playing with the image tags in order to get a more up-to-date version of the underlying image. Do this at your own peril. There's nothing theoretically wrong with it, as they are released at least in some fashion, but you may want to stick with the standards.

If you do decide to go with the modified image:

image:
  repository: whitesource/renovate
  tag: VERSION
  pullPolicy: IfNotPresent

is what I added to retrieve the latest renovate image, and you'll need to visit Docker Hub in order to get the latest tag.

Results

All told, I'm happy with the implementation. It took a little while to bootstrap, but it is doing a good job at finding my updates and providing a pretty uncluttered and automated mechanism for keeping up to date.