ADR-0034: ArtifactDeployment and Flux resource naming
- Status
- accepted
- Accepted
- 2026-07-30T00:00:00.000Z
- Author
- Category
- Architecture Pattern
- Impact
- Medium
- Related
- ADR-0015ADR-0027
Context
Konfidence deploys an artifact by creating an ArtifactDeployment (in konfidence) that the fluxdeployer (in kubernetes-landscape-orchestrator) turns into Flux resources on the target cluster. The name of that ArtifactDeployment propagates into every Flux resource and, ultimately, into the workloads applied to the cluster.
The problem: two deployments must never share a name that would make Flux overwrite live resources in place. The same artifact can legitimately exist more than once on one landscape — a different version, or a separate instance per VectorDeployment when allowReuse: false. Each needs its own resource identity. The previous naming was neither guaranteed unique nor stable.
Decision
How the name is built
konfidence constructs the name via ConstructArtifactDeploymentName:
<component-basename>-<version>-<hash>- Hash —
pkg/hash.Fnv(content, 10): FNV-1a rendered as 10 base36 characters ([0-9a-z]), giving ~5.4 trillion values. - Hash input —
component + versionwhen the artifact is reusable across vectors;component + version + vectorDeploymentUIDwhenallowReuse: false, so eachVectorDeploymentgets a distinct instance. - Length — the whole name is sanitised to a valid DNS-1123 label (
pkg/sanitize.DNSLabelName) and kept ≤63 chars.
The name is deterministic: the same inputs always produce the same name. That is what makes reconciliation idempotent and lets a reusable artifact be safely shared across vectors.
The construction context is recorded on the ArtifactDeployment as konfidence.cloud/* annotations (artifact-component, artifact-version, artifact-hash, allow-reuse, and vector-deployment-uid when reuse is disabled). Downstream resources read these annotations rather than recomputing anything.
How collisions are handled
When the computed name is already taken by an existing ArtifactDeployment, the controller decides whether that is intended reuse or a genuine clash by comparing the existing object's konfidence.cloud/artifact-component and konfidence.cloud/artifact-version annotations against what it expects:
- Annotations match → it is the same artifact. Sharing one
ArtifactDeploymentis exactly what reuse is meant to do — the controller simply adopts and updates it. This is designed behaviour, not a fault. - Annotations differ → two different artifacts hashed to the same short name. That is a real collision.
On a real collision Konfidence detects and recovers, mirroring how Kubernetes handles Deployment → ReplicaSet hash clashes:
- It emits an
ArtifactDeploymentHashCollisionwarning event, records acollisionCountfor that component on theVectorDeploymentstatus, increments it, and folds the count into the hash input — producing a fresh, still-deterministic name. - It requeues and retries.
collisionCount == 0reproduces the original algorithm exactly, so existing names are never disturbed by this mechanism. - After 5 consecutive collisions the controller fails loudly rather than looping forever — that many re-salts still clashing is a bug, not bad luck.
A real collision is therefore a self-healing operational event, not a fatal one, and the common case (creation, or intended reuse) is byte-identical to plain hashing.
How Flux resources are named
The fluxdeployer names every Flux resource exactly ArtifactDeployment.Name — konfidence already produced a valid DNS-1123 label, so no re-sanitisation happens:
HelmRepository,HelmRelease(andHelmRelease.spec.releaseName) →deployment.NameOCIRepository,Kustomization(and itssourceRef.Name) →deployment.Name- All Flux resources carry the label
konfidence.cloud/artifact-deployment: <deployment.Name>.
How per-instance uniqueness reaches the workloads
The ArtifactDeployment name distinguishes deployments; the following makes the rendered workloads unique:
- Kustomize — the deployer sets Flux
Kustomization.spec.nameSuffixto-<version>-<hash>(falling back to-<hash>when length-constrained). This is a native Flux field applied at render time and is owned by the deployer: anynameSuffixthe author sets in their ownkustomization.yamlis overwritten and discarded (see implications). - Helm — the deployer sets
HelmRelease.spec.releaseNameto the deployment name. Charts that derive names from.Release.Nameinherit uniqueness automatically.
Implications for users
Authors of artifacts consumed by the Kubernetes Deployer must follow a few rules (see the Kubernetes Deployer reference docs):
- Kustomize bundles must not set
nameSuffixornamespacein theirkustomization.yaml— the deployer overwrites both, and any author-set value is discarded. - Helm charts should derive every
metadata.namefrom{{ .Release.Name }}. Hard-coded resource names collide when the same chart is deployed at two versions, or by twoVectorDeployments withallowReuse: false. - Anything in the bundle/chart is duplicated per instance. Resources for which duplicate application is meaningless (e.g.
CustomResourceDefinition) must be delivered through a separate path. - Deploying the same component at a new version, or under a new vector with reuse disabled, always yields a distinct name and a distinct set of cluster resources — the old deployment is never silently overwritten.
Alternatives considered
- Random suffix — guarantees uniqueness but breaks determinism, and with it idempotent reconciliation and cross-vector reuse. Rejected in favour of a deterministic hash plus collision recovery.
- Rely on the author's own Kustomization
nameSuffixfor uniqueness — rejected: it is optional, not guaranteed unique per instance, and would leave uniqueness in the author's hands. Instead the deployer ownsnameSuffixoutright; the author must not set it. - Rename the
HelmReleaseto force uniqueness — insufficient on its own; only works for charts honouring.Release.Name. - Loud-fail on collision without recovery — simpler, but leaves a deployment permanently stuck. Rejected in favour of the
collisionCountrecovery above.
Consequences
- Deterministic & idempotent — same inputs → same name → diff-free reconciliation across both repos, and safe reuse of one deployment across vectors.
- Debuggable — names carry the component and version; the trailing hash matches the
artifact-hashannotation and is grep-friendly. - Clean repo split (ADR-0027) —
konfidenceowns the naming algorithm and sanitisation;kubernetes-landscape-orchestratortrusts the name verbatim and reads context from annotations, owning no hash logic. - Known limitation (deferred) — a rare interleaving of artifact reuse and deletion of a colliding "squatter" can still produce a duplicate; probability is effectively nil and a fix is not warranted yet.
Sequence
sequenceDiagram
autonumber
participant KF as VectorDeployment ctrl<br/>(konfidence)
participant AD as ArtifactDeployment
participant KLO as fluxdeployer<br/>(kubernetes-landscape-orchestrator)
participant Flux as Flux CRs to workloads
KF->>KF: name = basename-version-hash(component+version[+uid][+collisionCount])
alt name taken by a different artifact
KF->>KF: bump collisionCount, recompute, requeue
end
KF->>AD: create/update Name plus konfidence.cloud annotations
AD-->>KLO: reconcile
Note over KLO: read annotations
KLO->>Flux: Name equals deployment.Name plus Kustomization nameSuffix or HelmRelease releaseName
Flux->>Flux: render workloads with unique namesImplementation references
konfidence:pkg/hash/fnv.go—Fnv(content, 10)pkg/controller/const.go— thekonfidence.cloud/*annotation keysinternal/vectordeployment/internal/controller/util.go—ConstructArtifactDeploymentNameinternal/vectordeployment/internal/controller/vectordeployment_controller.go—collisionCountrecovery, annotation writes
kubernetes-landscape-orchestrator:internal/fluxdeployer/internal/fluxcd/reconciler/utils.go—buildKustomizationNameSuffixinternal/fluxdeployer/internal/controller/{helm,kustomize}_artifactdeployment_controller.go— reject>1OCM resource of a type (MultipleHelmChartResources/MultipleKustomizeResources)