Files
jakman85andClaude Opus 4.8 d24a443c7d chore(flux): migrate image-automation API v1beta2 -> v1
Migrate image.toolkit.fluxcd.io/v1beta2 -> /v1 for ImageRepository,
ImagePolicy, and ImageUpdateAutomation ahead of the Flux v2.7+ upgrade
(cluster-upgrade-plan §3.2). Flux v2.4 (current) already serves /v1, so
migrating the manifests now is a safe no-op at runtime.

- catalog/render.py: r_image_automation emits v1 for ImageRepository/ImagePolicy
- platform/image-automation/{image-update-automation,per-service/pihole.example}
- conformance L3 hands-off-update chainsaw applies v1 CRs
- conformance L1 goldens re-rendered to match (apiVersion-only diff)

No spec-field changes required: all spec fields used (image, interval,
imageRepositoryRef, filterTags.pattern/extract, policy.semver.range,
sourceRef, git.*, update.strategy/path) are unchanged in v1; the v1
messageTemplate already uses .Changed (not the removed .Updated).
apps/ golden unaffected (canonical render uses --no-image-automation).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-29 23:19:20 -05:00
..

Per-service image-automation — the render contract (update block → Flux CRs)

Owner: WS-UPDATE (this contract) + WS-GITOPS (emits these next to each app's Deployment/HelmRelease under apps/). Serves: R-UPD-01 (stable-only), R-UPD-02 (no human/AI authoring), R-UPD-03 (pin / auto-rollback). Source of truth: renderer-contract.md §update, ADR-0009, docs/11.

These files are reference examples, deliberately not wired into any kustomization.yaml, so they never deploy. WS-GITOPS renders the equivalent per real container service into apps/<svc>/. The cluster-wide write-back engine that consumes the setter markers is ../image-update-automation.yaml.

What a container kind ServiceDefinition update block renders

For every kind: container service whose update.pin is unset, render THREE things:

update field Renders to Notes
(implicit — source.image) ImageRepository scans the registry repo on interval.
channel: stable + range ImagePolicy .spec.policy.semver.range stable-only: the range plus a filter that excludes pre-release tags (-alpha/-rc/…).
cadence ImageRepository.spec.interval (scan schedule) the "on a schedule" of R-UPD-02. Cron cadence → nearest Flux duration (Flux scans on a duration, not cron).
(image ref in the Deployment) a setter marker comment on the image: field # {"$imagepolicy": "<ns>:<name>"} — where image-automation writes the advanced tag.
autoRollbackOnFailedHealth: true delivery-side health gate + rollback see below — HelmRelease remediation, or Deployment progressDeadline + revert.
pin: <version> freeze render the image image: pinned to <version> with NO setter marker, and suspend (or omit) the ImagePolicy. Automation cannot advance a pinned service (R-UPD-03).

kind: vm services do not render these — they update in-guest (dnf-automatic/unattended-upgrades on cadence, cloud-init), per docs/11 §3. See catalog/vm-demo.yaml.

Stable-only (R-UPD-01) — the two guardrails

  1. semver range (.spec.policy.semver.range) — only versions matching the operator's range.
  2. pre-release exclusion — either a filterTags pattern that only admits X.Y.Z (no -suffix), or rely on semver's rule that pre-releases (1.2.3-rc1) sort below their release and are only selected if the range explicitly asks for them. We do BOTH (belt-and-braces) so an upstream latest or -rc tag can never be picked.

Auto-rollback (R-UPD-03) — where the health gate lives

The setter write only changes a tag in git; the rollback is enforced at delivery:

  • HelmRelease-delivered services (preferred): Flux HelmRelease.spec.upgrade.remediation.retries
    • .spec.rollback — a failed, health-gated upgrade auto-rolls-back to the last-good Helm revision. The image tag lives in the HelmRelease values with the setter marker, so a bad advance is caught and reverted by Flux with no human step. This is the mechanism the conformance test exercises.
  • Plain-Deployment services: the Deployment progressDeadlineSeconds + readiness probe make a bad image a stalled (never-Available) rollout; the old ReplicaSet keeps serving, and the UpdateHealthGateFailed alert fires (see platform/monitoring/alerts.yaml). Recovery is the next good advance or the operator pin escape hatch.

Either way a failed/rolled-back update alerts by email (R-UPD-02 + R-OBS) via the automated-updates PrometheusRule group.