Skip to content

Support external system state as a component resource category #208

Description

@sourcehawk

Problem

component.Resource is bound to client.Object (Mutate(client.Object) / Object() (client.Object, error) / Identity() string), so everything ocf offers (converge status, per-concern conditions, grace periods, prerequisites, apply events, metrics) applies only to state the API server holds. generic.IntegrationBuilder does not change this: it is for Services, Ingresses and Gateways, not for external systems.

Operators spend much of their code reconciling state the API server does not hold. konsole-is/camunda-operator has more than ten such reconcilers: Elasticsearch snapshot repositories (internal/controller/elasticsearchcluster/snapshotrepository.go), restore repositories (internal/controller/logicalrestoreelasticsearch/secondary.go), Keycloak callbacks and roles (internal/controller/camundamanagementcluster/optimize.go, finalizer.go, plus the admin role on PR konsole-is/camunda-operator#317), Web Modeler users (webmodeleruser.go), Console ping settings (ping.go), Postgres roles and databases (internal/controller/database/controller.go, pkg/pgbootstrap), admin password rotation (internal/controller/camundacluster/rotation.go), management API backups (internal/controller/logicalbackupelasticsearch/statemachine.go), and object store archives (pkg/objectstore).

Every one re-implements the same six-beat loop by hand: build the client from a secret reference and map absence to a pre-check failure rather than an error; read current external state; diff against desired and return early when equal; write; map the write failure to a reason and stage a per-concern condition; emit an event on actual change. Withdrawal is a second fully hand-written path with no shared shape. The copies differ only in idempotence cost (one keeps a fingerprint cache), whether drift is a merge or a replace, and per-target authentication. The condition, event and status plumbing is identical and is exactly what ocf already owns for client.Object.

Approach

An ExternalResource interface as a sibling category to Resource: Identity() string, Read(ctx) (current any, err error), Diff(current) (changed bool, plan any, err error), Apply(ctx, plan) error, and an optional Withdraw(ctx) error for teardown. Registered through the component builder like any resource, so it inherits converge status, conditions, grace periods, prerequisites, RecordApplyOperationEvent, and the apply metrics.

Design questions to settle up front, which is why this is the riskiest of the audit's candidates: there is no server-side apply and no resourceVersion for external state, so drift semantics and conflict behavior must be defined by the interface contract; a failed Read must be distinguishable from "absent"; and the framework must not cache external state itself (the ES repository case shows consumers sometimes need a fingerprint cache, which should stay consumer-side).

Verification

  • One of the smaller call sites above (Console ping settings or the ES snapshot repository) can be ported and loses its hand-written condition, event and early-return plumbing.
  • A ported resource reports the same converge operations, conditions and events as an equivalent Kubernetes-backed resource.
  • Withdrawal runs through the same registered resource in the finalizer path instead of a parallel hand-written function.
  • A Read failure and an absent external object produce distinguishable outcomes in the component status.

Verdict from the audit

Highest repetition count of every pattern found, and the clearest case of machinery ocf already has being unusable only because of the client.Object bound. Filed as a design discussion first: the interface commits the framework to drift semantics that deserve deliberate choice.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

featureUser-observable capability or surface

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions