The KEDA-missing fail-closed guard's comment still described the replaced
drop-instances design ("db.yaml drops spec.instances ... CNPG collapses to 1");
under the shipped seed design db.yaml seeds instances at max(replicas,
effectiveMin), so a skipped ScaledObject leaves a fixed-size cluster with no HPA
- autoscaling silently absent, not a collapse. Correct the rationale. Also note
on the cozyrds apply-strategy annotation that the client-side switch rolls out
per-app as each Application is next written through the aggregated API, not
fleet-wide at upgrade (the ApplicationDefinition reconciler syncs only
chartRef/valuesFrom/labels onto a live HelmRelease). Follow-up from #3954 (#4187).
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
A Postgres app with autoscaling.enabled but no resolvable metrics endpoint (a
monitoring: true tenant who did not point serverAddress at a vmselect carrying
kube_pod_labels) renders a ScaledObject that stays Ready=False reason=TriggerError
forever. helm-controller's default wait gates the whole HelmRelease on every
rendered resource's kstatus, so that one unready ScaledObject drives the release to
UpgradeFailed and it retries forever - wedging an otherwise-healthy database and
blocking every later chart change to it, while the tenant cannot even read the
ScaledObject to see why (observed on dev3).
Autoscaling health is a degraded-but-alive state surfaced by the DatabaseAutoscaler*
alerts, not a release-readiness gate. Add a spec.release.healthCheckExprs entry on
the postgres ApplicationDefinition that treats the ScaledObject as always-ready for
release readiness (current: "true"); the CNPG Cluster and every other resource keep
their built-in kstatus wait, so the database's own health still gates the release.
Setting any healthCheckExprs couples WaitStrategy to poller
(config.ResolveWaitStrategy); that the poller wait stays equivalent to the default
for the kstatus-checked Cluster is verified on cozystack-pr-test, not here
(render-blind). The cozyrd unittest pins the expr so a regeneration cannot drop it.
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
The release.cozystack.io/helm-server-side-apply annotation moves every Postgres
HelmRelease to client-side apply, including releases that never enable
autoscaling. IvanHunters flagged that this platform-wide side effect was
unexplained and that non-autoscaled releases silently lose SSA drift
re-assertion (client-side diffs previous-vs-new render only, never live state;
driftDetection is configured nowhere in the repo).
Record why the scope is deliberate rather than narrowing it. helm-controller
v1.5.0 resolves an unset Upgrade.ServerSideApply through "auto" from the last
release's apply method, so gating client-side on "autoscaling active" would
force an SSA->client-side ownership handoff on the live spec.instances the
moment a tenant enables autoscaling - the exact live-field handoff the seed
design was chosen to avoid. Keeping the apply method stable for the kind's whole
lifecycle keeps enable/dry-run/transition/disable clean two-way merges with no
apply-method transition. The drift-re-assertion cost on never-autoscaled
releases is called out as an accepted trade-off, with a per-instance apply
strategy as the follow-up if it is ever needed.
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
Closes IvanHunters' MINOR on scaledobject.yaml:97. The API-owner-gate marker was a Go
template comment, stripped at render, so the ownership question and the pin's residual
surface were recorded where nobody reads them again. State the constraint on the
serverAddress field doc (propagated into values.schema.json, README and the cozyrds
openAPISchema) including the accepted residual: tenant-root is always in the allow list
because the derived default points there, so a tenant can still aim the operator at a root
Service as a blind in-cluster probe (no response body reaches the tenant), which the egress
NetworkPolicy does not narrow. The API-owner sign-off is tracked on the PR, not in a
stripped comment.
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
Closes IvanHunters' MINOR on values.yaml:58. The replicas description (propagated into
values.schema.json, README and the cozyrds openAPISchema) said the count is "restored on
disable". Under client-side apply it is not: the two-way merge writes instances only when
the rendered value changes, so a cluster KEDA grew past replicas stays there after
autoscaling.enabled: false with no autoscaler left to reduce it — the sibling dryRun doc
already states the true rule, so the two contradicted each other. Reword both the @param
and the @typedef to say the live count is not auto-restored and that the operator lowers
replicas to reclaim the grown capacity; regenerated the derived artifacts.
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
Closes IvanHunters' MAJOR on cozyrds/postgres.yaml:12 (coverage half). Dropping the
release.cozystack.io/helm-server-side-apply annotation left the whole suite green: the
Go builder test pins the plumbing that consumes it but nothing pinned the annotation at
the point it is set, so the single load-bearing invariant of the feature — client-side
apply, without which server-side apply reverts KEDA's live /scale on every reconcile —
was unguarded. Add a helm-unittest that renders the ApplicationDefinition and asserts the
annotation, plus a test target so hack/helm-unit-tests.sh runs it; removing the annotation
now goes red.
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
The ApplicationDefinition annotation comment called client-side apply a
"three-way-merge", contradicting the corrected wording everywhere else in the
change (config.go, _autoscaling.tpl, db.yaml, values.yaml all say the client-side
patch diffs the previous vs new render and does not consult live state). For a
CRD, helm-controller v1.5.0 leaves threeWayMergeForUnstructured at its default,
so Helm takes the two-way jsonpatch.CreateMergePatch(original, modified) path.
This comment ships on the live ApplicationDefinition, so a maintainer reading it
in isolation would otherwise get the wrong mental model of the safety property.
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
Under active read-replica autoscaling the chart seeds spec.instances at a
constant max(replicas, effectiveMin) rather than omitting it (an omitted field
defaults to 1, which the CNPG quorum webhook rejects, and — under server-side
apply — the phase-2 omit prunes the field whenever KEDA has not yet written
/scale, collapsing an idle-at-floor cluster to 1 and shedding its replica PVCs).
Keeping the constant only works if Flux does not force-own and revert it. The
postgres ApplicationDefinition therefore carries
release.cozystack.io/helm-server-side-apply: "false", so the tenant HelmRelease
applies client-side: helm-controller patches the CNPG Cluster from the
previous-vs-new rendered manifest without consulting live state, the unchanged
constant yields no patch, and KEDA's higher live /scale count is preserved. The
constant also equals the disabled/dryRun/transition render (instances: replicas)
whenever replicas sits at/above the floor, so those flips are patch-free too.
Correct the merge-semantics comments across db.yaml, _autoscaling.tpl and
values.yaml to describe the two-way previous-vs-new patch precisely (not a
"three-way no-op"), and document the residual floor-change hazard: editing
replicas / minReplicas / quorum.maxSyncReplicas moves the seed, so that one
reconcile does patch spec.instances to the new seed. Add autoscaling.serverAddress
so a monitoring:true tenant can point the ScaledObject at a vmselect that carries
both the CNPG metrics and kube_pod_labels. Regenerated README/schema/types/cozyrds.
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
Address review findings on the KEDA read-replica autoscaler where the
implementation diverged from the merged design proposal.
Fail-safe read load. Gate the `or vector(0)` floor on the existence of ANY CNPG
instance pod (`count(kube_pod_labels{...instance_role=~".+"}) > 0`) — primary or
replica — so the query is empty ONLY when no CNPG pod labels exist at all (a
broken kube-state-metrics pipeline), not during initial provisioning when the
sole replica-side pod is CNPG's join Job (jobRole, not instanceRole). Also pin
`ignoreNullValues: "false"` on the trigger: KEDA's prometheus scaler defaults it
true, turning an EMPTY result into metric 0 (not an error) — the HPA would then
desire 0 and clamp to the floor, i.e. silently scale a broken-pipeline cluster
DOWN. With it false an empty result is a scaler error, so the HPA HOLDS and
DatabaseAutoscalerScalerErrors fires; gating on any instance role keeps a healthy
cluster (including a long basebackup) from tripping that alert.
Constant seed = max(replicas, effectiveMin), so enable/dryRun/transition flips
are a merge no-op with no dip and never undercut the quorum floor.
Fail closed when the quorum floor exceeds maxReplicas (was: effectiveMax silently
raised both bounds, unbounded for a large maxSyncReplicas).
Dry-run / transition keep the HPA (unidirectional pause pair, not the HPA-deleting
full pause) but both SelectPolicy=Disabled, so desired normalizes to current — the
recommendation is read from the read-load metric, not a diverging desired count.
Observability. DatabaseAutoscalerAtMax keys on desired >= maxReplicas, NOT the
ScalingLimited condition (which is also true at the floor, so it fired on every
idle cluster with wrong "raise maxReplicas" advice). Dashboard read-load panels
are per-cluster (label_cnpg_io_cluster) with a CPU panel for ReadCPUUtilization
and the exact clamp in descriptions; ScaleStuck's Pending intersection is scoped
to CNPG pods; alerts.yaml narrative comments moved to Go-template comments so they
do not ship into the PrometheusRule.
Guard the seeding invariant: TestBuildHelmReleaseSpecNoForceOrDriftDetection.
Regenerated README/schema/types.
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
Address the remaining IvanHunters review points on #3954:
- [MAJOR] Disabling autoscaling wedged the HelmRelease under synchronous quorum.
An autoscaled + maxSyncReplicas>=1 cluster runs with instances seeded from
effectiveMin while replicas stays at its default, so flipping autoscaling off
rendered instances: <replicas> next to the quorum and CNPG's webhook rejected
it ("maxSyncReplicas must be lower than the number of instances"). Add a
symmetric render-time guard on the static-count path (covers off/dryRun/
transition): fail with an actionable message when replicas <= maxSyncReplicas.
- [MINOR] Role-scope the freeze brake's currentInstances count
(label_cnpg_io_instance_role=~".+") so lingering CNPG join/bootstrap Job pods
(cnpg.io/cluster but jobRole, not instanceRole) don't inflate it and nudge
desired up while the brake is meant to hold it.
- [MINOR] Default maxReplicationLagSeconds to 0 (brake opt-in): only the
non-braking path is validated live, so ship the validated default and let the
freeze be enabled explicitly until its branch is exercised under load.
- [MINOR] Document the floor-change and non-quorum-disable dips (same three-way-
merge hazard) and the pin-replicas-first mitigation.
- [NIT] Coerce the cozy-lib helper's `paused` (eq "true") so a caller passing the
string "false" does not pause the ScaledObject.
- [NIT] Trim the keda.clusterDomain injection comment so it no longer ships ~8
verbatim lines into the cozystack-values Secret on every cluster.
Regenerate types/schema/README/postgres-rd. New tests cover the disable-wedge
guard, the role-scoped freeze count, and the string-"false" paused coercion.
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
Two review findings:
- The enablement note claimed the two-phase `transition` flow avoids the
count dip when enabling autoscaling on an existing cluster. It does not:
helm-controller upgrades with Helm's three-way merge (not SSA), which keys off
the old-vs-new manifest diff, not KEDA's live /scale value. At phase 2 the
chart stops rendering the staged static count, so the field is deleted (no
quorum -> CNPG defaults to 1, a brief collapse) or changed (quorum -> rebased
to effectiveMin), then the warm KEDA restores it. Rewrite the docs to state the
flow reduces (KEDA is pre-warmed, no cold-start) but does not eliminate the dip,
and spell out the suspend + scale-subresource pin that actually makes the
handoff dip-free. This is the SSA-vs-three-way-merge handoff the design proposal
left for the PoC to settle.
- Pin metricType: AverageValue on the Prometheus trigger in
cozy-lib.keda.scaledObject. The desired = ceil(query / threshold) identity
holds only for an AverageValue external metric; it was relying on KEDA's
default. Make it explicit and assert it so a re-vendor or default change cannot
silently break the arithmetic.
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
Under active autoscaling the chart previously omitted .spec.instances entirely,
letting the KEDA HPA own it via /scale. But CNPG defaults an absent
.spec.instances to 1, and its admission webhook rejects a Cluster where
maxSyncReplicas >= instances ("maxSyncReplicas must be lower than the number of
instances"). So a fresh database created with autoscaling.enabled: true AND
synchronous quorum (maxSyncReplicas >= 1) was rejected at admission before KEDA
could ever scale it to effectiveMin (= maxSyncReplicas+1) — the whole tenant
HelmRelease failed. The default maxSyncReplicas: 0 path was unaffected, which is
why it went unnoticed. Verified live: a rendered Cluster with maxSyncReplicas: 1
and an omitted instances is rejected by the dev-cluster webhook; with the seeded
floor it is admitted.
Render instances: effectiveMin under active autoscaling instead of omitting it,
so a fresh Cluster starts at a valid, quorum-satisfying count. Because the chart
emits the same constant floor on every reconcile, Helm's three-way merge is a
no-op on the field and never reverts KEDA's higher live value, so the HPA still
owns the runtime count. Update the enablement docs (a new DB can enable directly
even with synchronous replication; existing DBs above the floor rebase to it
unless the two-phase transition flow is used) and add render tests for the fresh
create + maxSyncReplicas>=1 case.
Addresses the fresh-create-with-quorum admission gap found in branch review.
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
autoscaling.enabled is a tenant-facing knob, but KEDA is an optional platform
package that is not installed by default. A tenant enabling autoscaling on a
cluster without KEDA would render a ScaledObject against an unknown kind and the
whole database HelmRelease would fail with an opaque `no matches for kind
"ScaledObject"`. Guard the ScaledObject template on
`.Capabilities.APIVersions.Has "keda.sh/v1alpha1"` and stop with an actionable
message instead. Skipping the render is deliberately NOT an option: db.yaml
already drops the static .spec.instances under active autoscaling, so a missing
ScaledObject would let CNPG collapse the cluster to 1 instance. Document the
KEDA prerequisite on the `enabled` field (README/schema).
Also reject a non-positive autoscaling.target at render time: it is a plain
integer now, but the desired-replica formula divides the read load by it, so 0
is undefined and negatives invert scaling. cozyvalues-gen has no minimum
directive, so the bound is a template guard.
Add unittests for both guards (a dedicated suite renders without the KEDA CRD so
the fail-closed path is exercised; the main suite now advertises keda.sh/v1alpha1
via capabilities).
Addresses IvanHunters (KEDA-absent) and CodeRabbit (non-positive target) review
feedback on #3954.
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
Two review findings on the generated API:
- autoscaling.target was a resource.Quantity, which accepts suffixed values
like `150m`. The chart splices target straight into the PromQL query and the
KEDA threshold, where `150m` means 150 minutes to PromQL and is not a usable
KEDA float threshold. Retype it as a dimensionless integer so the schema
rejects suffixed input - helm unittest's schema validation now enforces it,
and the existing autoscaling suite exercises the integer contract.
- ConfigSpec carried a root `metric` field (and spec.metric in the postgres-rd
schema) that nothing reads - the chart consumes only autoscaling.metric - so
setting it silently did nothing. Remove the dead root param, keeping the
ReadMetric enum that autoscaling.metric references.
Regenerate types.go, zz_generated.deepcopy.go, values.schema.json, README.md
and the postgres-rd schema; update the autoscaling tests to the integer target.
Addresses CodeRabbit review feedback on #3954.
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
Make enabling autoscaling on an existing database count-preserving, and
add a second read-load signal:
- autoscaling.transition (migration phase 1): keep .spec.instances
rendered as a floor (from replicas, which the operator stages to the
live count) AND pause the ScaledObject, so KEDA stands up without
contending with Flux for the field. Flip transition off (phase 2) to
drop the static field and hand the count to KEDA — suspend the
HelmRelease across that apply, since helm-controller uses Helm's
three-way merge (not SSA) and would otherwise race CNPG's default-to-1.
The procedure is documented on the values field.
- autoscaling.metric (ReadConnections | ReadCPUUtilization): the query
helper emits either the active-connections sum or the postgres-container
CPU sum, both + target.
active = enabled && !dryRun && !transition; the ScaledObject is paused
whenever dryRun or transition is set. Covered by helm unittest (phase-1
holds the staged count + pauses; CPU metric query) and regenerated
schema/README/Go types/postgres-rd.
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
Implement the KEDA-based read-replica autoscaler for postgres per the
merged design proposal (cozystack/community#44):
- cozy-lib.keda.scaledObject: engine-agnostic helper rendering a KEDA
ScaledObject (scale target, effective bounds, prometheus trigger,
scale-down behavior, optional pause), fail-closed on bad input.
- postgres: an `autoscaling` values block (enabled / minReplicas /
maxReplicas / target / maxReplicationLagSeconds / dryRun). Under active
autoscaling the chart drops the static .spec.instances so Flux and the
KEDA-managed HPA no longer contend, and renders a ScaledObject whose
minReplicaCount folds in the synchronous-quorum floor
(max(minReplicas, maxSyncReplicas+1, 2)); when the floor exceeds
maxReplicas, quorum wins. dry-run keeps the static count and pauses the
ScaledObject. The WorkloadMonitor tracks the quorum floor instead of a
stale static value.
Covered by helm unittest (cozy-lib helper: valid render, pause, three
fail-closed guards; postgres: instances dropped, quorum-floored bounds,
quorum-over-max, dry-run pause). Regenerated values.schema.json, README,
Go types and postgres-rd.
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
An in-place RestoreJob (targetApplicationRef omitted) re-bootstraps the
Postgres app under its own name. On stock v1.6.2 it never comes back up,
for two independent reasons in the same restore patch - both surface only
in place, because a to-copy restore targets a different app.
1. Archive-vs-recovery serverName collision. The driver set the restored
cluster's WAL-archive serverName equal to the recovery source
(bootstrap.serverName = bootstrap.oldName = sourceServerName) while the
chart pinned the archiver serverName to the release name. In place those
coincide, so the cluster archived onto the source's own WAL prefix and
barman-cloud-check-wal-archive refused to start ("Expected empty
archive"). Fixed by giving the restored cluster a fresh
bootstrap.newServerName, keyed by the RestoreJob UID (unique per
restore, stable across reconciles); the chart archives there while
externalClusters still recovers from bootstrap.serverName. This is what
the manual CNPG strategies hit (serverName defaults to the cluster
name); the cozy-default flow dodged it (serverName is <ns>-<app>).
2. useSystemBucket guard. The restore populates explicit S3 coordinates
(destinationPath / s3CredentialsSecret / endpointCA) but left
backup.useSystemBucket=true, so the chart's "bootstrap.enabled is
incompatible with useSystemBucket" guard failed the HelmRelease upgrade
and the cluster never re-rendered. This is what the cozy-default
(system-bucket) flow hits on an in-place restore. Fixed by clearing
useSystemBucket in the restore patch, since the app is now on the
explicit-creds path. The driver's app view (postgresapp.Backup) gains
the field, without omitempty so a false survives the merge patch.
Both are exercised only by an in-place restore; the postgres backup e2e
drove only to-copy, so neither surfaced in CI. A step 50 in-place restore
now guards reason 1 (manual-strategy flow); reason 2 is covered by a Go
unit assertion (a system-bucket in-place e2e needs the platform bucket in
CI and is left as a follow-up).
Signed-off-by: Andrey Kolkov <androndo@gmail.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CloudNativePG stores its self-signed CA in <release>-ca alongside the
private key and offers no way to label it, so a tenant cannot be handed
the trust anchor without also being handed the key. Render a
TenantProjection that lifts ca.crt alone into the key-free
<release>.tenant-ca Secret, and select that label from the postgres
tenant-resource definition so the projection is served through the
tenantsecrets API. Document the new retrieval path in the chart README.
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Two defects of the same class — the WAL-archive S3 prefix (serverName)
changing or defaulting differently between components:
Recovery default regression (review blocker): native CNPG defaulted an
absent externalClusters serverName to the entry name (= bootstrap.oldName),
but the barman-cloud plugin defaults it to the NEW cluster's name, so a
legacy-flow restore with bootstrap.serverName unset would read the wrong
S3 prefix (hard failure, or silently restoring another cluster's data).
The chart now always emits serverName, defaulting to bootstrap.oldName.
Live-cluster serverName flip (found by the gated postgres backup e2e on a
real cluster): the chart renders spec.plugins without serverName
(effective prefix = cluster name) while a strategy template may name a
different one; the driver's ForceOwnership patch then flipped the prefix
mid-stream, splitting the WAL archive across two prefixes — the WAL
holding the backup's start was lost between them and the restore failed
with "WAL not found". The driver now preserves the live Cluster's
effective serverName (explicit parameter, or the cluster name when the
plugin is attached without one) and only applies the strategy's value to
a Cluster with no plugin attached; both charts pin serverName explicitly,
and the example strategy no longer sets one.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Andrey Kolkov <androndo@gmail.com>
Review follow-up (cozystack#3300): the apps/postgres user-facing docs still
described the removed native spec.backup.barmanObjectStore / archive_command
model. Update the values.yaml @field/@typedef/@param annotations (useSystemBucket,
schedule, S3CredentialsSecret, EndpointCA, endpointCA, the @param backup note,
bootstrap.serverName) and the hand-written README Operations prose to the
barman-cloud plugin + ObjectStore model, then regenerate values.schema.json, the
README parameters table, the api types, and the postgres-rd ApplicationDefinition
schema (which feed the dashboard form help + cozyrd schema). Field names,
defaults, and the storageClass immutability marker are unchanged; only backup/
bootstrap description text changed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Andrey Kolkov <androndo@gmail.com>
Regenerate values.schema.json and embedded openAPISchema in the -rd
ApplicationDefinitions so the x-cozystack-options dynamic-source
directives declared in each app values.yaml are carried into the
generated artifacts. Brings the -rd schemas in sync with the
hand-edited values.schema.json files (CI runs make generate +
git diff --exit-code).
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
Resolve the conflict in the generated packages/system/postgres-rd/cozyrds/postgres.yaml by regenerating openAPISchema and keysOrder from the merged values. The CRD now carries both the chart's tls.enabled field and main's backup useSystemBucket / BackupClass-driven fields.
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
version is a general deployment parameter, not a TLS setting; relocate
its @param/@enum block back into Common parameters so the generated
parameter reference groups it correctly instead of under TLS.
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
The generated JSON schema types tls.enabled as a scalar boolean, which
rejects an explicit null value; describe the tri-state unset case as
"omitted" so the wording matches what the schema actually accepts.
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
Remove the jq post-processing patch that widened tls.enabled type to
["boolean","null"] in values.schema.json and the openAPISchema embedded
in the cozyrds CRD. Plain "type":"boolean" is emitted directly by
cozyvalues-gen and is the correct form.
The cozystack-api server unmarshals openAPISchema into
apiextv1.JSONSchemaProps whose Type field is a single string; an array
value silently zeros the field and disables server-side defaulting for
the entire app.
Tri-state semantics are preserved on the Helm template side via
kindIs "invalid": omitting tls.enabled triggers the external-based
fallback; null is no longer a valid input and is rejected by the schema.
Remove two helm-unittest cases that injected tls.enabled: null (now
intentionally rejected) and update the suite comment to match.
Signed-off-by: Arsolitt <arsolitt@gmail.com>
The dashboard RBAC intentionally does not grant access to the operator-managed
<release>-ca Secret (it contains ca.key, which would leak the private key).
Advertising it in cozyrds secrets.include created a UX gap: the dashboard
showed a Secret the tenant could never read.
CNPG bundles ca.crt in every user-credentials Secret it creates, so tenants
already have the trust anchor they need via the <release>-credentials Secret
which is covered by RBAC. Update README and the db.yaml comment to point there.
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Arsolitt <arsolitt@gmail.com>
The README still described the chart-side cert-manager chain that was
replaced when the chart switched to CNPG operator-managed TLS via
spec.certificates.serverAltDNSNames. Rewrite the section to explain the
actual cert ownership boundary (CNPG owns the chain, chart only injects
the external hostname), the trust anchor location (operator-managed
<release>-ca Secret, key ca.crt), and the chart-side semantics of the
tri-state tls.enabled flag. The values.yaml @field comment is updated
in the same direction, with regenerated schema/types.go/cozyrds.
Clarifies that tls.enabled=false only skips the chart-side SAN
injection - CNPG retains its built-in TLS on the wire regardless of
this flag. To force PostgreSQL TLS off entirely, the caller would set
postgresql.parameters.ssl="off" at the CNPG layer.
Signed-off-by: Arsolitt <arsolitt@gmail.com>
- Extend PostgresSpec with nullable Tls.Enabled (*bool) for tri-state control
- Regenerate deepcopy and update go.mod with k8s.io/utils as direct dep
- Add TLS fields to values.yaml, values.schema.json (with nullable support)
- Update README and Makefile; propagate TLS field to cozyrds postgres.yaml
Signed-off-by: Arsolitt <arsolitt@gmail.com>
Annotate storageClass as immutable across every stateful app whose
chart exposes one — clickhouse, foundationdb, harbor, http-cache,
kafka (Kafka + ZooKeeper), kubernetes (top-level only), mariadb,
mongodb, nats, openbao, opensearch, postgres, qdrant, rabbitmq,
redis, vm-disk. Without coverage across the catalogue the UI would
render storageClass read-only on some apps and editable on others —
a worse mental model than uniform behaviour.
The annotation lands at the schema layer (CEL XValidation rule
'self == oldSelf' on values.schema.json / types.go / embedded
openAPISchema in cozyrds/<app>.yaml). The cozystack aggregated
apiserver does not yet evaluate CEL on application-backed resources;
that is tracked at cozystack/cozystack#2657. The UI
(cozystack/cozystack-ui#6) consumes the marker today.
The PVC-pinning argument is identical for every chart: Kubernetes
fixes a PVC's storageClassName at creation time, so editing this
field on an existing resource never migrated data. The schema
annotation makes that contract explicit.
kubernetes.nodeGroups[name].storageClass is intentionally NOT
annotated: the field is optional and undefaulted, so a strict
'self == oldSelf' rule would block any future attempt to set it on
an existing node group. A richer ratcheting form is needed there —
see docs/storage-immutability.md.
A refactor in pkg/registry/apps/application/rest.go extracts the
inline schema-build path into buildSpecSchema so it can be
unit-tested. Three regression tests in rest_spec_schema_test.go pin
the contract that x-kubernetes-validations survives the
v1->internal->structural conversion (loaded from the actual MariaDB
ApplicationDefinition fixture on disk, not a hand-edited mock).
Each per-app README gains a one-line blockquote pointing to a new
docs/storage-immutability.md that explains the contract and which
consumers enforce it today.
Requires cozystack/cozyvalues-gen with @immutable support — bump the
pre-commit pin in .github/workflows/pre-commit.yml once the release
exists.
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Ivan's original schema edits were correct in intent but lived in the
generated artifacts (values.schema.json and the embedded openAPISchema
in postgres-rd/cozyrds/postgres.yaml). `make generate` reverts them
because values.yaml's `@field {map[string]string}` annotation is the
source of truth -- same regeneration trap that caught #2466 (issue #2597).
cozyvalues-gen v1.4.0 (cozystack/cozyvalues-gen#25, merged 2026-05-21)
adds an `intOrString` type alias for map values that emits
`additionalProperties.anyOf: [integer, string]` plus
`x-kubernetes-int-or-string: true`. Move the change to source by
flipping the annotation, then regenerate:
- packages/apps/postgres/values.yaml: `@field {map[string]intOrString}`
with updated description noting integer values are accepted.
- packages/apps/postgres/values.schema.json: regenerated -- now carries
anyOf + x-kubernetes-int-or-string: true.
- packages/system/postgres-rd/cozyrds/postgres.yaml: embedded
openAPISchema regenerated to match.
- packages/apps/postgres/README.md: regenerated type column.
- api/apps/v1alpha1/postgresql/types.go: Parameters is now
`map[string]intstr.IntOrString`.
The result survives future regenerations. The template's
`{{ \$value | toString | quote }}` already coerces both inputs to
quoted strings, verified end-to-end with helm template against both
`max_connections: 100` (int) and `max_connections: "100"` (string) --
identical render output. Dangerous-param block list still rejects
archive_cleanup_command and recovery_end_command additions from the
prior commit.
Co-Authored-By: IvanHunters <xorokhotnikov@gmail.com>
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Add archive_cleanup_command and recovery_end_command to the dangerous
parameters blocklist in db.yaml. Both are PostgreSQL shell-exec hooks
identical in threat model to archive_command/restore_command and must
not be settable by tenants.
Also update the BLOCKED list in values.schema.json, values.yaml, and
the embedded openAPISchema in cozyrds/postgres.yaml to reflect the
two new blocked parameters.
Additionally update the embedded openAPISchema in cozyrds/postgres.yaml
to allow integer values for postgresql.parameters (anyOf string|integer),
matching the fix already applied to values.schema.json.
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
Rewrite resourcesPreset values in every apps/* and extras/* chart from
the legacy flat names to the new instance-type form, using the 1:1
mapping:
nano -> t1.nano micro -> t1.micro
small -> t1.small medium -> c1.small
large -> c1.medium xlarge -> c1.large
2xlarge -> c1.xlarge
Each affected values.yaml carries the canonical 47-value enum (40
instance-type names plus 7 legacy aliases) so the schema accepts both
forms during the deprecation window. cozyvalues-gen regenerates the
matching api/apps/v1alpha1/*/types.go kubebuilder enum and defaults,
the chart values.schema.json, the README parameter table, and the
ApplicationDefinition openAPISchema in packages/system/*-rd/cozyrds.
Three hardcoded legacy literals in chart templates migrated:
- packages/apps/harbor/templates/harbor.yaml:121 (portal)
- packages/apps/opensearch/templates/opensearch.yaml:43 (dashboards)
- packages/system/keycloak/templates/sts.yaml:115 (heap fallback)
packages/apps/foundationdb/Makefile gains the missing hack/update-crd.sh
call. Without it, every regen left foundationdb-rd cozyrds stale and
new FoundationDB app CRs through cozystack-api were rejected at
validation for any non-legacy resourcesPreset value.
Every default-rendered chart produces identical CPU and memory to the
value applied before the rename, courtesy of the legacy alias table in
cozy-lib and the 1:1 default mapping.
Co-authored-by: Matthieu ROBIN <matthieu@hidora.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Allow users to configure any PostgreSQL server parameter via the
postgresql.parameters field. All parameter values must be provided
as strings (numeric values should be quoted).
Security measures:
- Block parameters that allow shell command execution (archive_command,
restore_command, ssl_passphrase_command, log_destination, event_source)
- Validation implemented in templates to prevent security vulnerabilities
Generated files updated:
- types.go: Add Parameters field with map[string]string type
- README.md: Add parameter documentation and warnings
- values.schema.json: Update schema for new field
- CRD: Update OpenAPI schema with new field and reordered properties
CloudNativePG manages certain parameters automatically (archive_mode,
primary_conninfo, wal_level). Users should not override these as it
may break backup/replication functionality.
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
Update oldName and serverName field descriptions based on code review
feedback to avoid confusion about their actual roles:
- oldName: Remove misleading "(matches serverName in backup.info)"
text. This field represents the Kubernetes cluster resource name,
not the Barman server name.
- serverName: Provide clearer explanation that it's the S3 path prefix
(barmanObjectStore.serverName) used by the original cluster, and should
only be set when it differs from the Kubernetes resource name.
Updated in:
- values.yaml (source of truth for field documentation)
- types.go (Go API type comments)
- values.schema.json (JSON schema for validation)
- postgres.yaml (CRD with embedded OpenAPI schema)
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
Add serverName field to bootstrap configuration to explicitly specify
Barman server name from backup.info. This fixes "no target backup found"
errors when server_name in backup.info differs from Kubernetes cluster name.
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
The apps.cozystack.io/application.kind, apps.cozystack.io/application.group,
and apps.cozystack.io/application.name labels already exist on all HelmReleases
and provide more specific identification. Remove the redundant cozystack.io/ui
label and update all selectors to use apps.cozystack.io/application.kind instead.
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Rename the CRD and all related types for better clarity:
- CozystackResourceDefinition -> ApplicationDefinition
- CozystackResourceDefinitionList -> ApplicationDefinitionList
- CozystackResourceDefinitionSpec -> ApplicationDefinitionSpec
- All related nested types updated accordingly
Updated components:
- API types and generated deepcopy code
- Controllers and reconcilers
- Dashboard, lineagecontrollerwebhook, crdmem packages
- CRD YAML definition and Helm chart
- All 25 cozyrds YAML manifests
- Migration scripts and documentation
Added migration 23 to remove old cozystack-resource-definition-crd HelmRelease.
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Move common-envs.mk and package.mk from scripts/ to hack/ directory.
Update all Makefile includes to use new paths. Remove unused
issue-flux-certificates.sh script.
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Update all CozystackResourceDefinition files to use chartRef
with ExternalArtifact instead of OCIRepository sourceRef.
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>