Repository navigation
Description
Describe the bug
The cozystack aggregated apiserver (cozystack-api) ignores UpdateOptions on the update path. As a result, kubectl patch/apply --dry-run=server against any apps.cozystack.io resource (Postgres, MariaDB, Kafka, Kubernetes, VMDisk, …) is silently treated as a real write and persists the change. --dry-run=server is supposed to validate and admit a request without persisting it.
Environment
- Cozystack version: v1.5.0 (code path also present on
main) - Component:
cozystack-apiaggregated apiserver —pkg/registry/apps/application/rest.go
To Reproduce
- Create any
apps.cozystack.ioresource in a tenant namespace:kubectl apply -f - <<'EOF' apiVersion: apps.cozystack.io/v1alpha1 kind: Postgres metadata: name: dryrun-demo namespace: tenant-example spec: size: 1Gi storageClass: local replicas: 1 EOF
- Issue a server-side dry-run patch that changes a field:
kubectl patch postgres.apps.cozystack.io dryrun-demo -n tenant-example \ --type merge -p '{"spec":{"storageClass":"replicated"}}' --dry-run=server - Re-read the object:
kubectl get postgres.apps.cozystack.io dryrun-demo -n tenant-example \ -o jsonpath='{.spec.storageClass}{"\n"}'
Expected behaviour
Dry-run does not persist. Step 3 prints local. (Standard Kubernetes --dry-run=server contract: run admission/validation, return the would-be result, write nothing.)
Actual behaviour
Step 2 reports postgres.apps.cozystack.io/dryrun-demo patched and step 3 prints replicated — the dry-run request mutated the live resource and its backing HelmRelease.
Root cause
REST.Update() (pkg/registry/apps/application/rest.go, func at ~L481) receives options *metav1.UpdateOptions but discards it. At ~L592:
updateErr := r.c.Update(ctx, helmRelease, &client.UpdateOptions{Raw: &metav1.UpdateOptions{}})A fresh empty &metav1.UpdateOptions{} is constructed, dropping DryRun (and also FieldManager / FieldValidation). The sibling methods forward the incoming options correctly:
Create(~L238):r.c.Create(ctx, helmRelease, &client.CreateOptions{Raw: options})Delete(~L674):r.c.Delete(ctx, helmRelease, &client.DeleteOptions{Raw: options})
Additionally, the create-on-update (upsert) path at ~L495 calls r.Create(ctx, obj, createValidation, &metav1.CreateOptions{}), which likewise drops dry-run for the create case.
Impact
- Server-side dry-run is ignored for every
apps.cozystack.iokind on update/patch/apply — a correctness/safety bug for anyone using--dry-run=serverto preview changes (GitOps diff tooling, CI policy checks, manual pre-flight review). The preview silently applies. FieldManager/FieldValidationare also dropped, which can affect server-side-apply field ownership and request validation on updates.- Not specific to v1.5.0 (the code path predates it), so not a release blocker — but it is latent in v1.5.0.
Suggested fix
Forward the incoming options, mirroring Create/Delete:
updateErr := r.c.Update(ctx, helmRelease, &client.UpdateOptions{Raw: options})and carry options.DryRun into the CreateOptions for the upsert path at ~L495. Add a regression test asserting that a --dry-run=server update leaves the backing HelmRelease unchanged.
Activity
lexfrei commentedon Sep 23, 2026
Fixed by #3937, which makes the aggregated API honour server-side dry-run. Same bug as #3982.