Skip to content

Sync toolchain, finalizers and chart with auth0-operator - #14

Merged
wasabii merged 6 commits into
mainfrom
claude/auth-zero-seek-operator-sync-6y67xf
Sep 6, 2026
Merged

wasabii merged 6 commits into
mainfrom
claude/auth-zero-seek-operator-sync-6y67xf

Conversation

@wasabii

@wasabii wasabii commented Sep 6, 2026 •

Copy link
Copy Markdown
Contributor

Brings this repository in line with alethic/auth0-operator, which moved to .NET 10 and KubeOps 13 and reworked its finalizers, chart and build while this repository stayed on .NET 9 / KubeOps 9.11.8.

Still deliberately not included, from the same review: the .slnx migration, the CI test job, compare-before-write (NeedsUpdate), owner references to the parent Instance, spec.policy / spec.init, deprecated-field events, and the whole Auth0 rate-limiting stack (token bucket, adaptive pacer, circuit breaker), which has no counterpart in Seq's API.

Toolchain

  • global.json to SDK 10.0.100, MSTest.Sdk 4.3.3, NoTargets 3.7.134; both projects to net10.0
  • KubeOps.Operator/.Generator and kubeops.cli to 13.1.0, with KubeOpsCli pointed at the tool manifest
  • Drops the Microsoft.Extensions.Caching.Memory and System.Text.Json references NU1510 flags as redundant on net10.0
  • Container env uses ASPNETCORE_HTTP_PORTS instead of the deprecated ASPNETCORE_URLS. Both are inert while the operator runs on the generic host; this is the form to carry forward if it ever serves HTTP.

KubeOps 9 → 13 API

IEntityController and IEntityFinalizer moved under KubeOps.Abstractions.Reconciliation and now return ReconciliationResult<TEntity>. The EntityRequeue<TEntity> delegate is gone — the next pass is scheduled from the returned result, so every Requeue call becomes a Success/Failure carrying an interval and controllers no longer take a requeue parameter. A failed delete returns Failure so the finalizer stays attached and the entity is retried, matching the previous behaviour.

Program.cs binds the operator options before building the operator so it can set MaxParallelReconciliations (new, default 4) and disable AutoAttachFinalizers. It stays on the generic host: the web host auth0-operator uses is there to serve conversion webhooks, which this operator has none of.

Version-independent finalizers

KubeOps derives the identifier in metadata.finalizers from the finalizer's class name. Naming finalizers after the API version mints a new identifier on every version bump and strands the old one on live entities — and the reconciler inspects Finalizers()[0] only and returns without removing it when nothing is registered under that identifier, wedging the entity in Terminating forever. This is the failure mode that bit auth0-operator.

  • Finalizers move to Finalizers/ and lose their version prefix (AlertFinalizer, not V1alpha1AlertFinalizer). These names must not change again.
  • Finalizers/Legacy/ holds a shim per retired identifier so entities carrying seq.k8s.datalust.co/v1alpha1*finalizer still drain. Never delete one.
  • EntityFinalizers is the identifier table, deriving each string from the finalizer type so a class rename cannot silently desync it.
  • AutoAttachFinalizers is off operator-wide; V1alpha1Controller.ReconcileFinalizers adds the current identifier and strips retired ones on every live reconciliation.

InstanceFinalizer now delegates to the controller like every other kind, where the old one was a bare no-op, so Instance deletion emits that controller's "unsupported operation" warning.

KubernetesClient 17 → 19 (transitive)

KubernetesClient 19 removes every generated model constructor, leaving property initializers only, so all 28 model construction sites are rewritten. Two related traps:

  • IKubernetesClient.ListAsync gained a fieldSelector parameter before cancellationToken. Both existing calls passed it positionally, so the token would have bound to the wrong parameter. Both now name the argument.
  • IntstrIntOrString was renamed IntOrString. The type is never named here and the implicit string/int conversions survive.

The property sets of all 35 k8s model types this repository references were diffed between 17.0.14 and 19.0.2: nothing was removed, so the constructor removal was the whole model-level break.

Chart

CRDs now ship as templates rather than from the chart's crds/ directory, via a YamlDotNet rewrite task that injects the chart labels and annotations and wraps each document in a crds.enabled guard. Helm installs crds/ exactly once and never updates it, so schema changes previously had to be applied by hand. New values crds.enabled and crds.keep.

This requires a one-time migration on existing releases — Helm refuses to adopt a CRD whose live metadata does not name the release, and that check reads the cluster object, not the rendered manifest, so the chart cannot annotate its way out of it. The commands are documented in the README's new Upgrading section. Fresh installs need nothing.

The webhook caBundle rewriting auth0-operator does is deliberately omitted: there is no webhook, Service or TLS secret here to point at, and it has to be added alongside those on the day a second API version lands.

Generated CRDs and RBAC are no longer committed

src/Alethic.Seq.Operator/config/ is gitignored, matching auth0-operator. Two things were needed to make that safe:

  • CI had no dotnet tool restore, so the KubeOps CLI was absent and generation had been failing silently all along (the MSBuild target uses IgnoreExitCode/ContinueOnError).
  • The chart project takes a ProjectReference on the operator, and the dist project gains auth0-operator's PrePrepare/Prepare two-pass, so the operator is compiled and then rebuilt in Release with generation on before anything is published. The build fails with a clear message if no CRDs were produced.

The chart's committed templates/clusterrole.yaml is now the sole RBAC source of truth and must be synced by hand from the generated operator-role.yaml when [EntityRbac] changes.

CI and versioning

Actions move to current majors (ubuntu-24.04, checkout v7, setup-dotnet v6, upload-artifact v7, download-artifact v8, podman-login v2, GitVersion 6 via gittools v4), clearing the Node 20 deprecation warnings. helm template now gates the build, so a chart that does not render fails CI instead of shipping.

GitVersion 6 required two config changes in the same commit:

  • mode: ContinuousDelivery per branch. v6's ContinuousDeployment strips the prerelease label, so main would have built clean release versions; tag becomes label; increment is pinned on feature/pull-request/hotfix/unknown, which otherwise inherit develop's Minor.
  • next-version: 0.1.0. This repository has no version tags, so every version derives from the configured base, and v6 changed that default from 0.1.0 to 0.0.1 — the first run under v6 produced 0.0.1-PullRequest14.138, which would have walked the published image and chart versions backwards. Pinned to preserve the existing base.

Image and chart push paths stay under github.repository rather than moving to repository_owner, and the release job keeps its explicit permissions: block, so published locations do not move.

Behavioural notes

  • KubeOps 13 introduces ReconcileStrategy, defaulting to ByGeneration; 9.11.8 reconciled on every watch event. Metadata-only and status writes no longer retrigger reconciliation, which breaks the status-write feedback loop; periodic requeue still detects drift.
  • MaxParallelReconciliations bounds the burst of Seq API calls on startup.

Testing

Not compiled locally. No .NET SDK was available and egress policy blocked every .NET SDK download host, so CI is the compiler for this change. In place of a build, each API was verified against source: buehler/dotnet-operator-sdk diffed at v13.1.0 against v9.11.8 (including confirming FinalizerName is byte-identical across the two, so existing finalizer identifiers still resolve), and kubernetes-client/csharp plus both KubernetesClient packages diffed at 17.0.14 against 19.0.2. Project and workflow files are parsed locally with real XML and YAML parsers before each push.

Unrelated pre-existing issue noticed and left alone: src/dist-tests/dist-tests.msbuildproj still lists IKVM.ByteCode.Tests targets from the template it was copied from, so the tests artifact CI uploads is empty — confirmed by this PR's runs, where it is 247 bytes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01M3mJmvzVTi5AtFhQComUcs

wasabii and others added 6 commits September 6, 2026 18:26
Brings the toolchain in line with alethic/auth0-operator, which moved to
.NET 10 and KubeOps 13 while this repository stayed on .NET 9 / KubeOps
9.11.8.

Toolchain:
- global.json to SDK 10.0.100, MSTest.Sdk 4.3.3, NoTargets 3.7.134
- operator and test projects to net10.0
- KubeOps.Operator/.Generator and kubeops.cli to 13.1.0, with
  KubeOpsCli pointed at the tool manifest
- CI installs .NET 10 (the .NET 8 step stays because the GitVersion 5.x
  tool runs on it) and restores dotnet tools

KubeOps 9 -> 13 API:
- IEntityController and IEntityFinalizer moved under
  KubeOps.Abstractions.Reconciliation and now return
  ReconciliationResult<TEntity>. The EntityRequeue<TEntity> delegate is
  gone; the next pass is scheduled from the returned result instead, so
  every Requeue call becomes a Success/Failure with a requeue interval
  and controllers no longer take a requeue parameter.
- A failed delete returns Failure so the finalizer stays attached and
  the entity is retried, matching the previous requeue behaviour.

The finalizer identifiers written into metadata.finalizers are unchanged:
KubeOps derives them from the finalizer class name and the algorithm is
byte-identical between 9.11.8 and 13.1.0, so entities already carrying
seq.k8s.datalust.co/v1alpha1*finalizer still resolve and can drain.

KubernetesClient 17 -> 19 (pulled in by KubeOps 13) removes every
generated model constructor, leaving property initializers only. All
model construction is rewritten accordingly, and the two ListAsync calls
now name the cancellationToken argument because v13 inserted a
fieldSelector parameter ahead of it.

Generated CRDs and RBAC are no longer committed, matching auth0-operator:
config/ is gitignored and the chart project takes a ProjectReference on
the operator so generation runs before the chart is packaged, failing the
build if no CRDs were produced.

Note that KubeOps 13 defaults ReconcileStrategy to ByGeneration, so
metadata-only and status writes no longer retrigger reconciliation;
periodic requeue still detects drift.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M3mJmvzVTi5AtFhQComUcs
Fixes the two compile errors from the previous CI run: a multi-line
V1Secret/V1ObjectMeta construction in the ApiKey controller that the
earlier sweep missed, now a property initializer like the rest.

Version-independent finalizers:
- Finalizer classes move to Finalizers/ and lose their version prefix
  (AlertFinalizer, not V1alpha1AlertFinalizer). KubeOps derives the
  identifier in metadata.finalizers from the class name, so naming them
  after the API version mints a new identifier on every version bump and
  strands the old one on live entities -- and the reconciler returns
  without removing a finalizer it cannot resolve, wedging the entity in
  Terminating forever.
- Finalizers/Legacy holds a shim per retired identifier so entities
  carrying seq.k8s.datalust.co/v1alpha1*finalizer still drain. These are
  never attached and must never be deleted.
- EntityFinalizers is the identifier table, deriving each string from the
  finalizer type so a class rename cannot silently desync it.
- AutoAttachFinalizers is disabled operator-wide; V1alpha1Controller owns
  attachment, adding the current identifier and stripping retired ones on
  every live reconciliation.

Program.cs binds the operator options before building the operator so it
can set MaxParallelReconciliations (new, default 4, bounding the burst of
Seq API calls on startup) and AutoAttachFinalizers. It stays on the
generic host: the web host auth0-operator uses is there to serve
conversion webhooks, which this operator has none of.

The chart now ships CRDs as templates rather than from crds/, via a
YamlDotNet rewrite task that injects the chart labels and annotations and
wraps each document in a crds.enabled guard. Helm installs crds/ once and
never updates it, so schema changes previously required applying the CRDs
by hand. New values crds.enabled and crds.keep control installation and
retention. The webhook caBundle rewriting auth0-operator does is
deliberately omitted; there is no webhook, Service or TLS secret here to
point at, and it has to be added alongside those.

The dist project gains the PrePrepare/Prepare two-pass so the operator is
compiled and then rebuilt in Release with resource generation on before
anything is published; the KubeOps generator otherwise skips an
incremental build and leaves config/ stale, which now means absent.

CI actions move to current majors: ubuntu-24.04, checkout v7,
setup-dotnet v6, upload-artifact v7, download-artifact v8, podman-login
v2 and GitVersion 6 via gittools v4, clearing the Node 20 deprecation
warnings. GitVersion 6 requires the config fix in the same commit: mode
must be ContinuousDelivery per branch (6's ContinuousDeployment strips
the prerelease label, so main would build clean release versions), tag
becomes label, and increment is pinned on feature, pull-request, hotfix
and unknown, which otherwise inherit develop's Minor. The image and chart
push paths stay under the repository, not the owner, so published
locations do not move.

Container env uses ASPNETCORE_HTTP_PORTS instead of the deprecated
ASPNETCORE_URLS. Both are inert while the operator runs on the generic
host; this is the form to carry forward if it ever serves HTTP.

Also drops the Microsoft.Extensions.Caching.Memory and System.Text.Json
package references, which NU1510 flags as redundant on net10.0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M3mJmvzVTi5AtFhQComUcs
The dist project failed to load with MSB4025: an XML comment cannot
contain '--', and the two-pass comment used it as an em dash.

GitVersion 6 also changed the default base version from 0.1.0 to 0.0.1,
and this repository has no version tags, so every version derives from
that default. The build reported 0.0.1-PullRequest14.138 where GitVersion
5 reported 0.1.0-PullRequest0014.136, which would walk the published
image and chart versions backwards on main. Pin next-version to 0.1.0 to
keep the version base where it was.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M3mJmvzVTi5AtFhQComUcs
TestHelm ran under ContinueOnError, so 'helm template' could report a
broken chart without failing CI. That was tolerable while the CRDs were
static files copied into crds/, but they are now rewritten into templates,
and 'helm package' only tars templates without rendering them, so an
unrenderable CRD template would ship and surface only at install time.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M3mJmvzVTi5AtFhQComUcs
Gating on 'helm template' broke the build with MSB3077: helm exited 0 and
the chart rendered correctly, but Exec reports any stderr output as an
error, and --debug writes its progress there.

Drop --debug and set IgnoreStandardErrorWarningFormat, which stops Exec
inferring failure from output text while still failing the task on a
non-zero exit. The render is now genuinely gated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M3mJmvzVTi5AtFhQComUcs
The CRDs now ship as chart templates so that helm upgrade applies schema
changes, but a release installed before that change owns its CRDs outside
Helm, and Helm refuses to adopt a resource whose live metadata does not
name the release. That check reads the object in the cluster, not the
rendered manifest, so the chart cannot annotate its way out of it.

Document the label and annotation to apply once before upgrading, and the
crds.enabled and crds.keep values.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M3mJmvzVTi5AtFhQComUcs
@wasabii wasabii changed the title Upgrade to .NET 10 and KubeOps 13, matching auth0-operator Sync toolchain, finalizers and chart with auth0-operator Sep 6, 2026
@wasabii
wasabii merged commit 0fd3883 into main Sep 6, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant