↓Skip to main content

Self-destructing hotfixes with updatecli

·1331 words·7 mins

podman 5.8.7 broke every uses: step on our Forgejo runners (podman#29805). The hotfix moved the runners to Docker. We also added two updatecli policies: one to propose reverting the hotfix, and one to remove both policies afterwards.

We already use updatecli to keep dependencies up to date. Its sources, conditions, targets and PR actions work for this too: read the installed version, check upstream, then propose a git revert.

When to revert #

The policies run daily:

  1. Read podman’s version from the nixpkgs locked in our nix repo.
  2. If it is newer than 5.8.7 and the upstream issue is closed, open a PR reverting the Docker workaround.
  3. When the cleanup policy detects that the workaround is gone, open a PR deleting both policies and their workflow entries.

The version comes from our pinned nixpkgs, because an upstream release alone would not change what our runners use:

command: nix eval --raw --impure --expr '(builtins.getFlake (toString ./.)).inputs.nixpkgs.legacyPackages.x86_64-linux.podman.version'

These are the revert conditions, with the repository wiring omitted:

upstream-closed:
  kind: json
  disablesourceinput: true
  spec:
    file: https://api.github.com/repos/podman-container-tools/podman/issues/29805
    key: state
    value: closed
fixed:
  kind: shell
  sourceid: podman
  spec:
    command: sh -c '[ "$(printf "%s\n" 5.8.7 "$1" | sort -V | tail -n1)" != 5.8.7 ]' --
workaround-present:
  kind: file
  disablesourceinput: true
  spec:
    file: modules/forgejo-runner.nix
    matchpattern: 'virtualisation\.docker'

A newer version and a closed issue are enough to ask for a review, but they do not prove that our package contains the fix. The PR body asks the reviewer to check the release notes for the buildah copier change before merging, then re-run an ubuntu-latest job using actions/checkout after deployment.

Reverting and cleaning up #

The revert target runs this shell command in the nix repo:

sh -c '
  set -- $(git log -1 -F --grep="(#255)" --format="%H %P")
  [ $# -gt 0 ] || { echo "o27/nix#255 is not on the branch" >&2; exit 2; }
  commit=$1; mainline=""
  [ $# -gt 2 ] && mainline="-m 1"
  [ "$DRY_RUN" = true ] && { echo "would revert $commit"; exit 0; }
  git revert --no-commit $mainline "$commit" || { git revert --abort; exit 2; }
  git diff --cached --quiet && exit 1
  exit 0
'

It finds the hotfix by the (#255) marker in its commit message. Reverting that commit also removes the comments explaining the workaround. Both shell targets check DRY_RUN, so PR checks report the proposed changes without applying them.

The cleanup policy checks the nix repo for two things: the hotfix’s commit message is in the history, and modules/forgejo-runner.nix no longer contains virtualisation.docker. Checking only for Docker’s absence would also pass before the hotfix merged.

That is our proxy for a merged revert; it does not inspect the revert PR’s status. The cleanup target then opens a PR in the gitops repo, deleting both policy files and removing their entries from the updatecli workflow.

Full updatecli policies

These use our existing nix_ci values and credentials from the environment.

forgejo-runner-podman.yaml

# Temporary: o27/nix#255 moved the Forgejo runners to Docker because podman 5.8.7
# cannot copy actions into /var/run/act (podman#29805). This proposes reverting
# that PR once the pinned nixpkgs carries a newer podman;
# forgejo-runner-podman-cleanup.yaml then deletes both policies.
name: Revert the Forgejo runner Docker workaround
{{ if .nix_ci.enabled }}
scms:
  nix:
    kind: gitea
    spec:
      url: '{{ .nix_ci.url }}'
      owner: '{{ .nix_ci.owner }}'
      repository: '{{ .nix_ci.repository }}'
      branch: '{{ .nix_ci.branch }}'
      token: '{{ requiredEnv "GITEA_TOKEN" }}'
      user: '{{ requiredEnv "UPDATECLI_GIT_USER" }}'
      email: '{{ requiredEnv "UPDATECLI_GIT_EMAIL" }}'
      gpg:
        signingkey: {{ requiredEnv "UPDATECLI_GPG_PRIVATE_KEY" | quote }}
        passphrase: {{ env "UPDATECLI_GPG_PASSPHRASE" | quote }}
      commitmessage:
        type: revert
        scope: forgejo-runner
        hidecredit: true
        footers: 'Signed-off-by: {{ requiredEnv "UPDATECLI_GIT_USER" }} <{{ requiredEnv "UPDATECLI_GIT_EMAIL" }}>'
{{ end }}

sources:
  podman:
    name: podman version in the nixpkgs pinned by o27/nix
    kind: shell
{{ if .nix_ci.enabled }}
    scmid: nix
{{ end }}
    spec:
      command: nix eval --raw --impure --expr '(builtins.getFlake (toString ./.)).inputs.nixpkgs.legacyPackages.x86_64-linux.podman.version'
      environments:
        - name: PATH
        - name: HOME

conditions:
  # A newer podman alone does not prove the buildah copier change was relaxed.
  upstream-closed:
    name: podman#29805 is closed
    kind: json
    disablesourceinput: true
    spec:
      file: https://api.github.com/repos/podman-container-tools/podman/issues/29805
      key: state
      value: closed
  fixed:
    name: the pinned podman is newer than 5.8.7
    kind: shell
    sourceid: podman
    spec:
      command: sh -c '[ "$(printf "%s\n" 5.8.7 "$1" | sort -V | tail -n1)" != 5.8.7 ]' --
      environments:
        - name: PATH
  # Without it the revert would be proposed again, and fail, once it has merged.
  workaround-present:
    name: o27/nix still runs the runners on Docker
    kind: file
    disablesourceinput: true
{{ if .nix_ci.enabled }}
    scmid: nix
{{ end }}
    spec:
      file: modules/forgejo-runner.nix
      matchpattern: 'virtualisation\.docker'

targets:
  revert:
    name: revert o27/nix#255
    kind: shell
    disablesourceinput: true
{{ if .nix_ci.enabled }}
    scmid: nix
{{ end }}
    spec:
      # Reverting the merge rather than editing the module also drops the comments that justified the workaround.
      # updatecli runs this under DRY_RUN=true in diff mode, so it only reports there.
      command: |
        sh -c '
          set -- $(git log -1 -F --grep="(#255)" --format="%H %P")
          [ $# -gt 0 ] || { echo "o27/nix#255 is not on the branch" >&2; exit 2; }
          commit=$1; mainline=""
          [ $# -gt 2 ] && mainline="-m 1"
          [ "$DRY_RUN" = true ] && { echo "would revert $commit"; exit 0; }
          git revert --no-commit $mainline "$commit" || { git revert --abort; exit 2; }
          git diff --cached --quiet && exit 1
          exit 0
        '        
      environments:
        - name: PATH
        - name: HOME
      changedif:
        kind: exitcode
        spec:
          warning: 0
          success: 1
          failure: 2
{{ if .nix_ci.enabled }}
actions:
  nix:
    kind: gitea/pullrequest
    scmid: nix
    title: 'revert(forgejo-runner): go back to podman {{ source "podman" }}'
    spec:
      url: '{{ .nix_ci.url }}'
      owner: '{{ .nix_ci.owner }}'
      repository: '{{ .nix_ci.repository }}'
      body: |
        Reverts o27/nix#255. The pinned nixpkgs now carries podman {{ source "podman" }}, newer than the 5.8.7 that broke action copies into /var/run/act (podman#29805).

        A newer version is not proof of the fix: check the podman release notes for the buildah copier change before merging. After the deploy, re-run an ubuntu-latest job that uses actions/checkout.

        Once this merges, forgejo-runner-podman-cleanup.yaml in o27/gitops opens the PR that deletes both policies.        
{{ end }}

forgejo-runner-podman-cleanup.yaml

# Temporary: deletes itself and forgejo-runner-podman.yaml once o27/nix has
# reverted the #255 Docker workaround.
name: Remove the Forgejo runner podman policies
{{ if .nix_ci.enabled }}
scms:
  nix:
    kind: gitea
    spec:
      url: '{{ .nix_ci.url }}'
      owner: '{{ .nix_ci.owner }}'
      repository: '{{ .nix_ci.repository }}'
      branch: '{{ .nix_ci.branch }}'
      token: '{{ requiredEnv "GITEA_TOKEN" }}'
  gitops:
    kind: gitea
    spec:
      url: '{{ .nix_ci.url }}'
      owner: '{{ .nix_ci.owner }}'
      repository: gitops
      branch: main
      token: '{{ requiredEnv "GITEA_TOKEN" }}'
      user: '{{ requiredEnv "UPDATECLI_GIT_USER" }}'
      email: '{{ requiredEnv "UPDATECLI_GIT_EMAIL" }}'
      gpg:
        signingkey: {{ requiredEnv "UPDATECLI_GPG_PRIVATE_KEY" | quote }}
        passphrase: {{ env "UPDATECLI_GPG_PASSPHRASE" | quote }}
      commitmessage:
        type: chore
        scope: updatecli
        hidecredit: true
        footers: 'Signed-off-by: {{ requiredEnv "UPDATECLI_GIT_USER" }} <{{ requiredEnv "UPDATECLI_GIT_EMAIL" }}>'
{{ end }}

conditions:
  # "Workaround gone" alone is also true before #255 merges, which would delete the policies before they ran.
  reverted:
    name: o27/nix merged PR 255 and no longer runs the runners on Docker
    kind: shell
    disablesourceinput: true
{{ if .nix_ci.enabled }}
    scmid: nix
{{ end }}
    spec:
      command: sh -c 'git log -1 -F --grep="(#255)" --format=%H | grep -q . && ! grep -q "virtualisation\.docker" modules/forgejo-runner.nix'
      environments:
        - name: PATH

targets:
  remove:
    name: delete both policies and their workflow entries
    kind: shell
    disablesourceinput: true
{{ if .nix_ci.enabled }}
    scmid: gitops
{{ end }}
    spec:
      command: |
        sh -c '
          [ -e updatecli/nix-ci.d/forgejo-runner-podman.yaml ] || exit 1
          [ "$DRY_RUN" = true ] && { echo "would delete the forgejo-runner-podman policies"; exit 0; }
          git rm -q updatecli/nix-ci.d/forgejo-runner-podman.yaml updatecli/nix-ci.d/forgejo-runner-podman-cleanup.yaml || exit 2
          sed -i "/nix-ci\.d\/forgejo-runner-podman/d" .forgejo/workflows/updatecli.yaml || exit 2
          exit 0
        '        
      environments:
        - name: PATH
      changedif:
        kind: exitcode
        spec:
          warning: 0
          success: 1
          failure: 2
{{ if .nix_ci.enabled }}
actions:
  gitops:
    kind: gitea/pullrequest
    scmid: gitops
    title: 'chore(updatecli): remove the Forgejo runner podman policies'
    spec:
      url: '{{ .nix_ci.url }}'
      owner: '{{ .nix_ci.owner }}'
      repository: gitops
      body: |
        o27/nix has merged the revert of #255, so the runners are back on podman and these two policies have nothing left to do.        
{{ end }}