February 13, 2024 by olblak7 minutes
Time flies, and It’s time for another successful experiment to go out.
Shareable Update Policies
A recurring problem over time has been, how to reuse the same Updatecli manifests for different projects.
From the beginning of the Updatecli project, it was possible to parameterize Updatecli manifests using values or secrets files, but still, we had to copy the same Updatecli manifest over and over. This led to duplication, maintenance burden, and ultimately, It didn’t scale well.
So what if we could:
Well, it turned out to be amazing
To test the various commands mentioned in this blog post, you’ll need:
export UPDATECLI_GITHUB_TOKEN=<INSERT YOUR PAT>The first step is to write a policy. To make this step a bit easier, we added a new sub-command :
updatecli manifest init my_policyThis command scaffolds a new Updatecli policy in the directory my_policy that includes the following files “CHANGELOG.md”, “Policy.yaml”, “README.md”, “updatecli.d”, and “values.yaml”
where:
We can add as many files as we need in the directory updatecli.d. All of them define different update pipelines specific to this policy.
This post is not about how to write Updatecli manifests, instead feel free to look, first, into:
To simplify this section, We can update the file values.yaml and updatecli.d/default.yaml with the following contents:
---
# Values.yaml contains settings used from Updatecli manifest.
scm:
default:
user: updatecli-bot
email: updatecli-bot@updatecli.io
owner: updatecli-test
repository: fleet-lab
username: "updatecli-bot"
branch: main---
name: Update Rancher Fleet projects dependencies
## scms defines the source control management system to interact with.
## Default values are coming from the file values.yaml
scms:
default:
kind: github
spec:
owner: '{{ .scm.default.owner }}'
repository: '{{ .scm.default.repository }}'
branch: '{{ .scm.default.branch }}'
user: '{{ .scm.default.user }}'
email: '{{ .scm.default.email }}'
username: '{{ .scm.default.username }}'
token: '{{ requiredEnv "UPDATECLI_GITHUB_TOKEN" }}'
## actions defines what to do when a target with the same scmid is modified.
actions:
default:
kind: "github/pullrequest"
scmid: "default"
spec:
automerge: false
labels:
- "dependencies"
## autodiscovery specifies that we want to update whatever we find
## in the context of helm or rancher/fleet dependencies.
autodiscovery:
groupby: all
scmid: default
actionid: default
crawlers:
helm:
enabled: true
rancher/fleet:
enabled: trueWe can now test our policy running:
updatecli diff --config updatecli.d/default.yaml --values values.yamlThis works, but remember, we want to publish this policy to a registry so that we can reuse it over and over without having to duplicate the same files.
Updatecli policies can be published to any OCI-compliant registries like Dockerhub, ghcr.io, etc. Updatecli relies on the same technology used to distribute Docker images, Helm charts, Kubewarden policies, etc.
As of today, we need to authenticate with a registry such as ghcr.io by running:
docker login ghcr.ioThen, we can publish our new policy to ghcr.io by running:
updatecli manifest push \
--values values.yaml \
--config updatecli.d \
--policy Policy.yaml \
--tag ghcr.io/updatecli-test/policies/autodiscovery/fleet \
.A few important pieces of information to consider
Policy.yaml and must be semantic versioning compliant.latest has a specific meaning and represents the latest version from a semantic versioning point of view.0.3.0@sha256:eff30ebc0dc129ef3b82cfbca1e5c3d9ea1014f360f4c51b04ea4b0f951bae91Voila, it’s as simple as that. Now we can start using our new policy.
Using Updatecli policies can be done through two approaches. One policy at a time or composing them using the file updatecli-compose.yaml.
To manipulate a single policy, we can reuse familiar commands like:
updatecli diff ghcr.io/updatecli/policies/updatecli/autodiscovery:0.2.0updatecli manifest show ghcr.io/updatecli/policies/updatecli/autodiscoveryupdatecli apply ghcr.io/updatecli/policies/updatecli/autodiscovery:latestQuickly, we end up with different policies. This is why we introduced a new file named updatecli-compose.yaml with its syntax that allows us to compose our policies.
policies:
- name: Local Updatecli Website Policies
config:
- updatecli/updatecli.d/
- name: Handle Nodejs version in githubaction
policy: ghcr.io/updatecli/policies/nodejs/githubaction:0.3.0
values:
- updatecli/values.d/scm.yaml
- updatecli/values.d/nodejs.yaml
- name: Handle Nodejs version in Netlify
policy: ghcr.io/updatecli/policies/nodejs/netlify:0.3.0@sha256:41c2af6a10da1f4b4b91717ebaa4659332dd3d7107919c494c71f1f618aeaad8
values:
- updatecli/values.d/scm.yaml
- updatecli/values.d/nodejs.yamlNow we can use the following commands to achieve the same results as the standalone approach
updatecli compose diffupdatecli compose showupdatecli compose applyAdditional values can be specified at command execution to override those defined by the policy. It doesn’t use deep merge copy.
Similarly to Updatecli manifests, any IDE supporting Jsonschema store like VScode, IntelliJ, etc., automatically validate and provide auto-completion for files named updatecli-compose.yaml

The benefits of having versioned Update policies, means we can update the various projects at different speed. We managed the policy in one location and then use a policy like ghcr.io/updatecli/policies/updatecli/autodiscovery:0.2.0 to automate policy update on all updatecli-compose.yaml file.
While we’ve been using this feature for a few months now on various projects, shareable policies are still in experimental mode.
We created the label
updatecli-policies to track related issues, but so far nothing major appear.
Our goal is to move that feature out of experimental as soon as we are confident enough that this feature is stable.
We are still collecting feedback, so feel free to provide yours on:
On the projects that we maintain, we shifted from maintaining manifest(s) per project/repository to one central repository with all Updatecli policies defined there.
Updatecli policies used on the Updatecli project are now defined on
github.com/updatecli/policies.
Adding new policies is as simple as adding a new policy to the directory updatecli/policies
Then we have a GitHub workflow that publishes new policies automatically to ghcr.io/updatecli/policies/
All those policies are available on ghcr.io/updatecli/policies
Another great approach, suggested by Mathew Warman, is to use the following GitHub Action workflow, which offer a similar result:
name: Policy Release
on:
push:
branches:
- main
paths:
- "updatecli/policies/**"
defaults:
run:
shell: bash
permissions: {}
jobs:
changed-policies:
name: Get changed policies
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v41
with:
json: true
quotepath: false
files: "updatecli/policies/**/Policy.yaml"
dir_names: true
- name: Set changed files as output
id: set-matrix
run: |
matrix="{\"directory\":${{ steps.changed-files.outputs.all_changed_files }}}"
echo "$matrix" | jq .
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
release:
name: Release
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
needs:
- changed-policies
strategy:
matrix: ${{ fromJSON(needs.changed-policies.outputs.matrix) }}
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup updatecli
uses: updatecli/updatecli-action@v2
- name: Log in to the container registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set policy details
id: policy
env:
POLICY_DIR: ${{ matrix.directory }}
run: |
name=$(realpath --relative-to=./updatecli/policies "${POLICY_DIR}")
version=$(yq .version "${POLICY_DIR}/Policy.yaml")
{
printf "name=$name\n"
printf "version=$version\n"
printf "release=$name-$version\n"
} >>"${GITHUB_OUTPUT}"
- name: Create github release
id: github-release
env:
GH_TOKEN: ${{ github.token }}
RELEASE: ${{ steps.policy.outputs.release }}
run: |
git tag "${RELEASE}"
git push origin "${RELEASE}"
gh release create "${RELEASE}" --verify-tag --latest
- name: Push updatecli manifest
working-directory: ${{ matrix.directory }}
env:
GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
GITHUB_REPOSITORY_NAME: ${{ github.event.repository.name }}
POLICY_NAME: ${{ steps.policy.outputs.name }}
run: |
updatecli manifest push \
--config updatecli.d \
--policy Policy.yaml \
--values values.yaml \
--tag "ghcr.io/${GITHUB_REPOSITORY_OWNER}/${GITHUB_REPOSITORY_NAME}/${POLICY_NAME}" \
.We hope you are as excited as we are about this new feature and that it will simplify the maintenance of your project through time, as it does for us.