devantler-tech / devantler-tech/ksail
SetupInstance replaces the whole FluxInstance spec, dropping fields KSail does not model
- Dominant language
- Go
- Stars
- 165
- Forks
- 12
- Avg merge
- 5h 41m
- Merged PRs (30d)
- 337
Description
> 🤖 Generated by the Agentic Engineer
## Evidence
`instanceManager.tryUpsert` fetches the existing FluxInstance into KSail's **typed** `FluxInstance`, assigns the whole spec, and issues a full `Update`:
```go
existing := &FluxInstance{}
err := fluxClient.Get(ctx, key, existing)
...
existing.Spec = desired.Spec
err = fluxClient.Update(ctx, existing)
```
KSail's `InstanceSpec` models **two** fields:
```go
type InstanceSpec struct {
Distribution Distribution `json:"distribution"`
Sync *Sync `json:"sync,omitempty"`
}
```
The flux-operator `FluxInstance` v1 CRD carries eight: `cluster`, `commonMetadata`, `components`, `distribution`, `kustomize`, `migrateResources`, `sharding`, `storage`, `sync`. A typed decode drops the six KSail does not model, and a full `Update` writes the object back without them.
## Why it matters
`spec.kustomize.patches` is the supported way to customise a Flux installation, and a consumer's patches there can be load-bearing. On the platform's production cluster the live FluxInstance carries six — controller replica counts, topology spread, and DNS `ndots` — one of which is annotated in-repo as having previously wedged reconciliation when resolved wrongly. `spec.components` and `spec.cluster` are likewise set there and unmodelled here.
`SetupInstance` is reached from `cluster update` through the component handlers (`reconcileWorkloadTag`, `reconcileFluxVersion`), so this is on the routine deploy path, not only bootstrap.
## What is verified vs inferred
**Verified by reading the source** at `26efa6b5`: the type has two fields; the update path assigns `existing.Spec = desired.Spec` and calls `Update`.
**Not yet observed live.** The production FluxInstance currently *has* all of those fields (generation 23, six patches present), so if the strip does occur it is being repaired — most plausibly by the Flux Kustomization that declares the FluxInstance in-repo, reconciling at a 1m interval. That would make the effect a transient window rather than permanent loss, which is still a window in which the controllers run without the patches.
**The first step is therefore measurement, not a fix**: confirm whether an `Update` from this path actually removes the unmodelled fields, and if so how long the gap lasts before GitOps restores it.
## Acceptance criteria
- [ ] A test demonstrates whether a FluxInstance carrying `spec.kustomize.patches`, `spec.components` and `spec.cluster` retains them across `SetupInstance`.
- [ ] If they are dropped, the upsert preserves unmodelled fields — e.g. a merge patch, or an unstructured read-modify-write — instead of a typed full replace.
- [ ] A consumer that declares a FluxInstance in Git is not fought by KSail on every update.
## Size
Small-to-medium. The measurement is small; the fix is a change of update strategy on one call path.
Found while fixing devantler-tech/platform#2922, whose fix adds one more (one-shot, convergent) trigger for this same path — see devantler-tech/ksail#6454.
Contributor guide
Research direction
Start by tracing SetupInstance through instanceManager.tryUpsert and the component handlers reconcileWorkloadTag and reconcileFluxVersion at source revision 26efa6b5. Add a test for a FluxInstance containing spec.kustomize.patches, spec.components, and spec.cluster, then verify whether SetupInstance retains them. Done means unmodelled fields survive updates and a Git-declared FluxInstance is not repeatedly overwritten.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, kubernetes
- Domain
- devops, infrastructure
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100