Skip to content

No shared interface across statefulset.Mutator / deployment.Mutator blocks workload-kind-agnostic mutations #142

@sourcehawk

Description

@sourcehawk

Context

pkg/primitives/statefulset/mutator.go and pkg/primitives/deployment/mutator.go
expose an essentially identical editing surface for env/container/podspec/metadata
work: EditContainers, EditInitContainers, EditPodSpec,
EditPodTemplateMetadata, EditObjectMetadata, EnsureContainerEnvVar,
EnsureContainerArg, EnsureReplicas, RemoveContainer*, etc. They diverge only
in the spec editor (EditStatefulSetSpec vs EditDeploymentSpec) and the
StatefulSet-only VolumeClaimTemplate methods.

However *statefulset.Mutator and *deployment.Mutator are unrelated concrete
types, and the only framework interface spanning them, generic.FeatureMutator,
covers just Apply() and NextFeature() — not the editing methods.

The problem

A consumer that wants to write a workload-kind-agnostic mutation — e.g. a
shared "emit these auth/license/storage env vars on the app container" helper
that some components render as a StatefulSet (zeebe) and others as a Deployment
(gateway, operate, tasklist, identity, connectors) — cannot express it against a
framework type. There is no shared static type carrying EditContainers /
EnsureContainerEnvVar, so the helper either has to be duplicated per workload
kind or routed through a consumer-defined structural interface.

This surfaced building the Camunda orchestration cluster in camunda-operator:
five components share the same env-emission concerns but render as two different
workload kinds, and we wanted one set of emitters returning one mutation type.

Workarounds today (all unsatisfying)

  1. Define a local structural interface (WorkloadMutator) listing the shared
    methods, plus per-kind adapter funcs (AsSTS, AsDeploy) that wrap a
    Mutation[WorkloadMutator] into a statefulset.Mutation / deployment.Mutation.
    Works (Go structural typing means both concrete mutators satisfy it), but the
    interface is a hand-maintained mirror of the framework's method set: a
    framework bump that renames/adds a method silently drifts until a compile-time
    var _ WorkloadMutator = (*statefulset.Mutator)(nil) assertion catches it.
  2. Duplicate every shared emitter per workload kind. Defeats the point of shared
    emitters and doubles the per-mutation test surface.

Proposed solution shape

Export a framework interface (say primitives.WorkloadMutator or
generic.PodWorkloadMutator) that both *statefulset.Mutator and
*deployment.Mutator declare they implement, covering the shared
container/podspec/metadata/env editing methods. Optionally a small generic
adapter so a feature.Mutation[WorkloadMutator] can be lifted into a
statefulset.Mutation / deployment.Mutation without per-consumer boilerplate.

Open question

Should the shared interface be the full intersection of the two method sets, or a
deliberately minimal "env + container + metadata" subset (the spec-editor and
VolumeClaimTemplate methods stay kind-specific anyway)? A minimal subset keeps the
contract stable across primitives that may later host more workload kinds (Job,
DaemonSet), at the cost of consumers reaching for the concrete type for less-common
edits.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions