Files
cozystack/packages/system
Aleksei Sviridkin a5cabca73e fix(ouroboros): render controller rollout strategy in every mode (#3323)
## What this PR does

Bumps the ouroboros chart and controller image to 0.8.1, which fixes a
latent upgrade break in the controller Deployment's rollout strategy,
and pins the fixed contract from the cozystack side.

Up to chart 0.8.0 the controller Deployment rendered a rollout strategy
**only** in `external-dns` mode, as `type: Recreate`, and rendered no
strategy block at all in the other modes. The apiserver defaults a
`rollingUpdate` block onto any Deployment created without one, so
switching a live release into `external-dns` laid `Recreate` on top of
that defaulted block, and the apiserver rejected the object:

```
spec.strategy.rollingUpdate: Forbidden: may not be specified when strategy `type` is 'Recreate'
```

0.8.1 renders the same `RollingUpdate` block, with no surge, in every
mode. The strategy no longer changes when `controller.mode` does, so the
rejected combination cannot arise on any transition. Rendering
`Recreate` unconditionally would not have worked: existing
`coredns`-mode releases already carry the defaulted `rollingUpdate`
block and would hit the identical rejection on their first upgrade.
Always-`RollingUpdate` is upgrade-safe from both existing states, and
rollback works for the same reason.

**Reachability, stated plainly:** this is a latent break, not an active
one. The host pins `coredns` and tenants pin `coredns-import`, neither
of which rendered a strategy in 0.8.0, and no shipped configuration
selects `external-dns`. But `controller.mode` is a documented,
user-settable value that the tenant wrapper exposes through
`valuesOverride`, so the transition is reachable by configuration rather
than only in theory.

**Why it went unnoticed:** the break only surfaces under server-side
apply, which helm-controller uses by default. `DeploymentSpec.Strategy`
carries `patchStrategy:"retainKeys"`, so a client-side strategic merge
clears `rollingUpdate` on the way through and a plain `helm upgrade`
never reproduces it. Only the upgrade breaks, never the install, so
nothing catches it at render time either.

**Why no surge, in every mode:** the controller has no leader-election
in any mode and no serving path — no ports, no probes — so surging a
second pod buys no availability anywhere. In `external-dns` mode it also
removes a real hazard: that reconciler `Update`s DNSEndpoints with no
conflict retry, so two overlapping reconcilers race on Get→Update. The
`coredns` paths wrap Get→Update in `RetryOnConflict` and converge
instead, which is why only `external-dns` rejects `replicaCount > 1`;
that asymmetry is deliberate and is now called out in the template so it
does not get tidied away later. The upstream comment is explicit that
`maxSurge: 0` is *not* Recreate-equivalent exclusivity — the surge
budget counts desired replicas, not live pods — and rests the argument
on what is actually guaranteed: the old pod's SIGTERM precedes the new
pod's creation, and SIGTERM cancels the reconciler context. The proxy
Deployment is untouched and keeps its default rolling surge, as the
data-path component.

The fix was made upstream in `lexfrei/ouroboros` and released as v0.8.1
rather than patched into the vendored tree here, since `make update`
regenerates `charts/` wholesale. The vendored chart in this PR is a
verbatim `make update` pull of the published 0.8.1 artifact,
byte-identical to the registry.

Chart manifest digest and controller image digest are bumped in lockstep
with the version, as the package Makefile requires. 0.8.1 also carries
an upstream Renovate bump of the cleanup-hook kubectl image to 1.36.1
(verified to resolve, multi-arch).

The `update` target additionally drops the empty digest-named directory
that `helm pull` leaves beside the untarred chart when pulling by
digest. Git ignores empty directories, which is exactly why it lingered
unnoticed.

### Testing

`make -C packages/system/ouroboros test` → 17/17 pass. The three new
cases pin the rendered strategy on `coredns`, `coredns-import` and
`external-dns`. `external-dns` is included deliberately: it is the only
mode that ever rendered `Recreate`, so a future bump that re-gated the
block would slip past a coredns-only suite. Verified by re-gating the
bug surgically against the new suite — it fails on exactly that case,
and passes without it. Upstream additionally tests this contract across
every mode in its own suite.

### Screenshots

Not applicable — no UI change.

### Downstream repositories

Walked the trigger map in `docs/agents/contributing.md` against the diff
file by file. The diff touches only `packages/system/ouroboros/`: the
package Makefile's version and digest pins, the vendored chart
(regenerated), the image pin in `values.yaml`, and the package's own
tests. It adds, renames and removes no package under `packages/apps/` or
`packages/extra/`; touches no `packages/core/platform/values.yaml` key,
no platform variant, bundle or component; changes no
`ApplicationDefinition`, no `values.schema.json`, no version enum, no
`release.prefix`; touches nothing under `hack/`, no namespace, and no
node prerequisite. No trigger matches.

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:

### Release note

```release-note
fix(ouroboros): render the controller rollout strategy in every controller.mode. Up to chart 0.8.0 the strategy was rendered only in external-dns mode, as type: Recreate, so switching a live release into that mode applied Recreate on top of the rollingUpdate block the apiserver defaults onto a Deployment created without one, and the upgrade was rejected with "spec.strategy.rollingUpdate: Forbidden: may not be specified when strategy `type` is 'Recreate'". The rejection only occurs under server-side apply, which helm-controller uses by default. The strategy is now identical across all modes, so a mode switch changes no strategy field.
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
  * Updated Ouroboros to version 0.8.1 with refreshed, pinned images.
* Improved controller rollouts using a consistent rolling-update
strategy across supported modes.
  * Updated cleanup hook tooling to version 1.36.1.

* **Bug Fixes**
* Prevented leftover chart directories from accumulating during chart
updates.
* Improved deployment behavior by limiting rollout disruption while
maintaining controller availability.

* **Tests**
* Added coverage validating rollout behavior across all supported
controller modes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-16 20:36:46 +03:00
..
2026-03-25 15:57:25 +05:00
2026-02-10 20:19:03 +01:00