The key to a sustainable codebase: continuous maintenance
As a Software Engineer, you often have to reconcile two conflicting objectives: maintaining your existing applications available and secured, while delivering new and exciting features to the world. Since you have limited time on your hands, keeping your old projects up to date with the latest software version is treated as a tedious task, which is postponed until an EOL of a major framework is coming.
It often seems counter-intuitive: why change something that already works? That’s why management is often not keen on investing time (and therefore money) into maintaining frozen projects.
They would pay just to keep the same value or even lose a bit since, every time you touch old software, there is indeed a risk of breaking the existing setup. Yep, even with the 80% test coverage, you touched on an inconspicuous part of the code which was holding everything together.
But this is a short-term view, in fact, you invest a bit of time to prevent big incidents (or a complete software rewrite) months or years later. For a more concrete example, let’s assume that you manage a few Java applications, and then the Log4J vulnerability is revealed to the world. Suddenly, you have to drop all your current work to update a bunch of applications that you may have never touched.
Their maintainer has obviously left the company, but you just need to update a dependency file, commit to the main
branch, and CI/CD will take care of the rest, right? That is often the moment that you discover that the app needs some manual steps for proper development, that a token is expired, and that the app only works because it had not been restarted for 6 months.
Now that you understand the struggle for one exceptional event, the hard truth is that it happens quite often (EOL of a framework is also a good example), and for your teams, it is nerve-breaking to always struggle with big and dangerous updates.
That’s why continuous dependency management is the key to sustainable project management. It promotes a healthy work schedule for your teams (no urgency, just daily or weekly updating work) and forces them to invest in proper continuous delivery automation to reduce the toil. You don’t leave any old project behind, and there is no uncharted part of your codebase.
Renovate: a great tool to automate dependency updates
While there are other tools (listed below), I recommend Renovate for both its simplicity and heavy customization.
On GitHub, you simply need to add the Renovate App to some or all the repos of your organization. It is a managed app and runs regularly on repositories, or when a change occurs, such as a commit on the main
branch.
When Renovate runs on a repository that doesn’t contain a renovate.json
file, it creates a welcome Pull Request, which lists all the detected dependencies of your project.
It adds the following renovate.json
at the root of the repository.
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
]
}
You can merge it right away to go with the default configuration (which is quite fine), or modify the PR to configure Renovate to your needs (see below for recommended configurations).
How it works
Renovate runs regularly on your repos, detecting dependencies such as NPM packages, Docker tags, Terraform versions, etc… It then fetches the latest versions from the upstream registries. If a new version is available, it will then open a PR with the proposed changes. Renovate will group changes if possible: for example, it will update the python version on all Dockerfile with the same PR).
Renovate can also automatically merge PRs if they successfully pass tests. For patch or minor versions, it is a sane idea, as long as you have automated tests… If you modify the main
branch, it will automatically rebase the PR to keep up with the latest trunk versions.
50 ways to configure Renovate
Renovate can be extensively configured with its renovate.json
file.
The easiest way is to extend an existing configuration, which packages one or several options, in a preset. By default, it extends the config:base
preset.
When you need to modify a parameter, have a look at the existing presets. If you find one that suits you, add it to the extends
list. For instance, if you want to deactivate Major version updates, use the:disableMajorUpdates
preset.
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",
":disableMajorUpdates"
]
}
For example, the following basic presets are interesting:
:label(renovate)
: use the renovate label on all PRs opened by Renovate. It helps organize PRs.:separateMultipleMajorReleases
: open one PR per Major update. It allows to migrate one major at a time, which is recommended to handle breaking changes or a bug.
Lastly, if you need finer control over this tool, the packageRules block is another powerful element of Renovate: by matching package names, update types, and other criteria, you can configure specific actions on some packages only.
For example, a useful packageRules is to regroup all Patches together, to reduce the number of PR.
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",
":disableMajorUpdates"
]
}
All these options will contribute to creating quite a lot of pull requests, that you can track with a handy Dependency Dashboard, opened as an issue in your repository.
Now your maintenance work is automatically delivered to your backlog!
Going on with automation
However, you now have a bunch of small updates to handle, which you often merge anyway since it’s a minor and unimpactful update. It seems that you have added toil to your daily work. As an SRE, a tedious and repetitive task is an opportunity for automation!
That’s why one of the more powerful features of Renovate is the automated merging, or automerging, of dependencies updates if tests are OK. The official Renovate documentation recommends that “you enable automerge for any type of dependency update where you would just click Merge anyway.”
:automergePatch
preset for patch versions (v1.0.x):automergeMinor
preset for minor versions (v1.x.0)
If you have tests, and all your tests are successful, Renovate will merge the PR. That is now a good excuse to invest in a proper CI/CD toolchain, and some code coverage.
Renovate also correctly handles semantic versioning with conventional commits to integrate tightly into your CI/CD workflow, which is mandatory if you use release automation tools such as Release Please or Semantic Release. By default, Renovate will analyze your repo commits, and determine if you use semantic commits for its PR, with the following convention:
- defaults to the
chore
prefix:chore(deps): update...
- uses the
fix
prefix for npm production dependencies:fix(deps): update...
- uses the
chore
prefix for npm development dependencies (devDependencies
)
For a lot of use cases, such as Terraform modules, it means it will use chore
by default, which is not always the desired behaviour. Indeed, if you update a dependency in your module, it will impact downstream, you want to inform your users of a new release. Therefore you can force the use of the fix
prefix with the following preset :semanticPrefixFix
Finally, if you have strong test and deployment automation with tools such as ArgoCD Image Updater, you will have a self-maintained repository, for which you will only handle major updates.
Self-hosting and alternatives
If you are not on GitHub, or simply value your privacy, you can easily self-host Renovate. It is a simple CLI, which only needs a Git Provider token and some configuration. For example, it can run in a GitLab CI schedule, or as a Kubernetes cronjob.
There are other tools in the same field that I find less complete or easy to use:
- Github Dependabot is the strongest opponent of Renovate, GitHub’s integrated supply chain security bot. It bases its security alerts on the GitHub Advisory Database, which contains advisories reviewed by GitHub, as well as many, many more unreviewed advisories. It will then open PRs with suggested updates. It supports fewer languages that Renovate and does not have the same automation options. However, if you only want to be warned of security updates, it is a fine tool.
- Snyk is a security chain SaaS. It is similar to SonarQube as it has a separate interface to monitor security, According to the Snyk documentation, “Snyk currently supports the Automatic dependency upgrade pull requests feature for npm, Yarn, and Maven-Central repositories”. I haven’t had an opportunity to test it, but it seems less powerful than the other tools.
- ArgoCD Image Updater can be seen as a dependency automation tool since it commits new versions to a Git Repository. If you use ArgoCD, it is a great tool that I recommend! It is complementary to Renovate, for the final container images that you deploy.
Conclusion
I hope that I convinced you to try to Renovate to automate the maintenance of your repositories. As the mass of software keeps growing in your projects, investing in automation is the key to sustainable software companies. Renovate can be one of the many tools in your CI/CD toolchain!