Commit Graph
3971 Commits
Author SHA1 Message Date
Andrey Kolkov cc51e040ae fix(mongodb): keep the cleanup escape hatch off legacy operator CRs, and surface a stuck finalizer
The skip-artifact-cleanup annotation reaches releaseMongoDBCleanup before the
ownership check, and the operator-CR delete sat outside the finalizer gate, so
a legacy backup (no delete-backup finalizer) lost its PerconaServerMongoDBBackup
record when a tenant set the annotation to release a stuck Backup or applied it
across a namespace during teardown. That record is what a psmdb restore-by-name
resolves against, and both the RBAC comment and the docs promise it is never
touched. Gate every write in the release on the finalizer being present: an
unowned (legacy) CR or one that cannot be read at all is left intact, and the
Cozystack Backup still releases.

Surface a failed finalizer strip. The release must proceed even against an
unreachable CR, but the swallowed patch error left the operator CR holding
percona.com/delete-backup — pinning its namespace in Terminating, the outcome
the branch exists to prevent — while the Event reported the success case. Emit
a FinalizerNotStripped Warning naming the object so an operator can find it.

Mark the sibling backup fields (schedule, retentionPolicy, destinationPath,
endpointURL, insecureSkipTLSVerify, s3AccessKey, s3SecretKey) as ignored under
useSystemBucket, so a tenant editing one instead of the flag itself is not
misled by a description that describes only the legacy flow; regenerate the
schema, README and mongodb-rd. Pair the insecureSkipTLSVerify rendering
assertion with a case that flips the platform knob on, so the two together
discriminate the rendered value from a hardcoded default.

Assisted-By: LLM
Signed-off-by: Andrey Kolkov <androndo@gmail.com>
2026-09-22 12:03:58 +04:00
Andrey Kolkov 542f42ef67 fix(mongodb): make the system-bucket cleanup teardown-safe and single-source its ownership
Four issues in the system-bucket cleanup path.

Teardown wedge. The cleanup requeued forever waiting for the operator to
prune, and the escape-hatch annotation released the Cozystack Backup without
touching the delete-backup finalizer the driver stamps on the operator CR.
So a namespace teardown, where the operator's delete-backup step cannot run
(it execs against a pbm agent that is going away), left the operator CR — and
the namespace — stuck in Terminating with no way out but hand-patching
finalizers. Cleanup now detects a terminating namespace (as Redis/Rabbitmq
do) and, on that or the annotation, strips its own finalizer and releases,
leaving the object for a bucket lifecycle policy. Needs patch on
perconaservermongodbbackups for the finalizer strip.

Ownership was read from two places written at different times: the finalizer
(stamped when the operator CR is created) and a driverMetadata flag (written
when the artifact is created, a later reconcile). If the app's useSystemBucket
flag flipped between them they disagreed. Read ownership from the finalizer on
the one CR named in driverMetadata (the one the driver minted): resolving it by
name, never by scan, keeps the operator's own cron-* task-minted backups (which
do carry percona.com/delete-backup at v1.22.0) out of scope, so the finalizer on
that named CR is an unambiguous single marker. Drops the driverMetadata flag.

Injection error was terminal. A transient server-side apply — an apiserver
hiccup, or a conflict while the operator writes the same cluster — failed the
BackupJob outright. Requeue with backoff instead, bounded by the existing
deadline, matching the cluster/app read errors alongside it.

Injection ran before the enabled check, mutating a cluster that can never
back up. Gate it on backup.enabled so a disabled cluster is left untouched.

Also: reframe the restore S3 backfill as the defensive guard it is (the
pinned operator always echoes status.s3, so the live path normally carries
coordinates), and correct the psmdbtypes doc that still claimed the driver
never patches the operator CRs.

Assisted-By: LLM
Signed-off-by: Andrey Kolkov <androndo@gmail.com>
2026-09-22 12:03:58 +04:00
Andrey Kolkov f27a02f1b6 docs(mongodb): correct the inlined-storage story and surface the TLS knob
The strategy godoc still said the storage config is NOT inlined and the
strategy only names the storage — which controller-gen copies verbatim into
the CRD, so kubectl explain served the new s3 field and its own denial from
one object. Rewrite it (and the StorageName field doc) to describe both
flows, and regenerate the CRD.

Render insecureSkipTLSVerify on the default MongoDB strategy: the field is
plumbed end to end into the driver-injected storage entry and backed by a
platform knob, but the template set only forcePathStyle, pinning it to
false so a self-signed external S3 that Redis backups honour would fail
MongoDB x509 verification. Pin it in the rendering test alongside the other
s3 coordinates.

Fix the useSystemBucket field description: angle brackets rendered as empty
HTML tags in the README table and the dashboard schema, so wrap the path in
backticks (as the postgres field already does) and note that schedule,
retentionPolicy and PITR are ignored on this flow. Regenerate the chart
schema, README and mongodb-rd.

Assisted-By: LLM
Signed-off-by: Andrey Kolkov <androndo@gmail.com>
2026-09-22 12:03:58 +04:00
Andrey Kolkov 1d05dafb2e fix(mongodb): own and prune the system-bucket archive, and heal the restore read path
Two correctness gaps on the useSystemBucket flow.

Retention. Gating spec.backup.tasks out of the chart also dropped the keep
that psmdb task retention used, and an on-demand PerconaServerMongoDBBackup
is not covered by task retention, so nothing pruned the shared cozy-backups
bucket — a retention-swept Plan grew it unboundedly for every tenant at
once. Own the archive the way the Redis/Rabbitmq drivers own theirs: stamp
the operator's percona.com/delete-backup finalizer onto the backups minted
on this flow, and on Cozystack Backup deletion delete that operator CR and
wait for the finalizer to free the object before releasing. A legacy backup
(tenant's own bucket) keeps the pre-existing hands-off no-op. Adds the
delete verb on perconaservermongodbbackups and a skip-artifact-cleanup
escape hatch for a wedged operator, matching the sibling drivers. Also drops
the unused update verb from the psmdb cluster RBAC (server-side apply is a
patch, not an update).

Restore. resolveMongoDBBackupSource returned early off the live operator
Backup CR whenever it reported a destination, copying status.s3 as-is. On
this flow the operator can echo a destination with status.s3 empty (the
storage was injected, not declared), and the target has no declared storage
to resolve from either, so the restore stranded with no coordinates.
Backfill a missing live S3 block from the snapshot the BackupJob recorded.

Also correct the precondition godoc: the pbm agents start on
spec.backup.enabled alone; the declared storage is required by the backup
CR's resolution, not by the agents.

Assisted-By: LLM
Signed-off-by: Andrey Kolkov <androndo@gmail.com>
2026-09-22 12:03:58 +04:00
Andrey Kolkov 9545bee233 fix(mongodb): correct system-bucket prose, pin strategy rendering, make restore credential swap flow-aware
Several comments and the useSystemBucket field description had drifted from
what the code does.

The strategy header still said the $bucketName gate was "not because it
needs the value" and that the sibling strategies needed the bucket name
while this one only asked for ordering, but the strategy now renders
bucket: {{ $bucketName }} for the injected coordinates, so it needs the
value; the precondition comment claimed the s3-storage entry exists on
backup.enabled=true, which is only the legacy flow. The rendering-test
comment carried the same "needs no bucket coordinates" claim. The
useSystemBucket field description said injection happens "at first BackupJob
time" (it now runs on every BackupJob) and that backups scope to
<namespace>/<release> (the prefix templates the application name, not the
release), and it ran ~700 chars of implementation detail into the CRD that
backup-classes.md already carries; trimmed and corrected, then regenerated
schema/README/types/cozyrds. Same <release> slip fixed in the driver
comment and the docs.

Nothing asserted the new strategy rendering. Added the MongoDB row to the
endpoint-form suite (s3.endpointUrl, scheme preserved both ways) and field
assertions to the rendering suite — bucket, endpointUrl, region,
credentialsSecret, forcePathStyle, and prefix, which must reach the CR as a
literal go-template string so the driver can template it per-application;
a stray brace would empty it with every doc-count test still green.

The restore credential swap broke across flows. It replaced the source
credentialsSecret with whatever the target declared, comparing no buckets,
so a system-bucket backup restored into a legacy target kept the platform
bucket against the tenant's own -s3-creds, and a legacy backup restored
into a system-bucket target got cozy-backups-creds against the tenant
bucket - both AccessDenied. Skip the swap when the source already names the
platform-projected cozy-backups-creds (it outlives the source app, nothing
to repair), and only adopt a target credential whose storage is on the
same bucket as the archive. psmdbStorageS3 now reads .s3.bucket alongside
.s3.credentialsSecret; TestPsmdbTargetCredentialsSecret covers the match,
the cross-bucket refusal, and the empty-bucket no-op.

Also drop the post-apply re-Get: the typed SSA Patch decodes the server's
merged object back into the passed struct, so the precondition reads the
injected storage from it instead of a follow-up Get that hit the informer
cache and briefly reported Ready=False on the happy path.

Signed-off-by: Andrey Kolkov <androndo@gmail.com>
Assisted-By: LLM
2026-09-22 12:03:58 +04:00
Andrey Kolkov 4ea4888685 fix(mongodb): key system-bucket injection off the app flag, not live storage
The MongoDB backup driver decided what to inject and what to record from
the live cluster's storage set rather than from the app's opt-in flag. That
had three consequences.

The snapshot fallback fired for legacy apps too. Its gate was
`snap.S3 == nil && rendered.S3 != nil`, but the coordinates moved onto the
shared cozy-default strategy, so rendered.S3 is now non-nil for every
MongoDB app. A legacy backup written to the tenant's own bucket got its
snapshot rewritten to the platform bucket, sending restore to the wrong
place. Gate the fallback on the app's backup.useSystemBucket flag instead,
threaded through createMongoDBBackupArtifact, and pin the legacy case.

The injected storage never caught up with the strategy. Injection was
gated on the named storage being absent, so it ran once per cluster and
froze the coordinates at the first backup; a later endpoint/region change
or bucket re-provision was a silent no-op, and Helm could not repair it
because the useSystemBucket chart renders no storages. Key injection off
the app flag and re-apply on every BackupJob (the prefix is deterministic,
so this never splits the archive the way CNPG's serverName would). Add
UseSystemBucket to mongodbapp.MongoDBSpec so a legacy app is protected by
the flag, not by whatever happens to be on the cluster.

The docs said the opposite of what the driver does. backup-classes.md
claimed "the MongoDB driver cannot inject its S3 target per-backup";
correct it and add an opt-in-to-the-system-bucket section that names which
values stop being read and that scheduled backups + PITR must move to a
Plan. Fix the endpoint table row, the tuning reference, the RBAC comment
that said the driver never patches, the strategy header/inline comments,
and the example comment. Also assert notExists on spec.backup.pitr in the
helm test, and lift the duplicated cozy-backups-creds default into a const.

Signed-off-by: Andrey Kolkov <androndo@gmail.com>
Assisted-By: LLM
2026-09-22 12:03:58 +04:00
Andrey Kolkov c036efaa5a test(mongodb): cover system-bucket storage injection gate and builder
Extract the two untested contracts from applyMongoDBSystemStorage into
pure functions and unit-test both, mirroring the CNPG driver's
buildBarmanObjectStore split:

- shouldInjectMongoDBSystemStorage decides whether the driver injects the
  system-bucket storage; the tests pin both branches, including the one
  that leaves a legacy app's manual bucket untouched.
- buildMongoDBSystemStorageEntry builds the {type:s3,s3:{...}} entry,
  defaulting credentialsSecret and omitting forcePathStyle when nil.

Also add a helm-unittest for the restore.yaml guard rejecting
bootstrap.enabled together with backup.useSystemBucket.

Signed-off-by: Andrey Kolkov <androndo@gmail.com>
Assisted-By: LLM
2026-09-22 12:03:58 +04:00
Andrey Kolkov 730592f323 feat(mongodb): support the system-bucket backup flow (useSystemBucket)
MongoDB was the only cozy-default app with no useSystemBucket flow: its
backup storage was always static on the app CR (bucket/endpoint/creds
from backup.* values + a chart-emitted <release>-s3-creds Secret), so a
tenant could not back up to the platform system bucket out of the box the
way postgres/clickhouse/etc. do.

PBM takes bucket/endpointUrl/region/prefix as static CR fields (not
secret refs) and the platform bucket name/endpoint are only known at
BackupJob time, so - unlike ClickHouse's runtime env secretKeyRef - the
chart cannot render them. The fix mirrors the CNPG driver: the chart adds
a useSystemBucket flag that keeps spec.backup.enabled (so the pbm agents
run) but omits the static storages/tasks/pitr and the creds Secret; the
cozy-default MongoDB strategy carries the coordinates (endpointUrl already
scheme'd by the endpoint helper, credentialsSecret cozy-backups-creds);
and the psmdb driver SSA-injects spec.backup.storages[s3-storage] onto the
live PerconaServerMongoDB at BackupJob time under its own field manager
with ForceOwnership, so a Flux re-render never reverts it. Injection is
gated on the storage being absent, so a legacy app that ships its own
static storage is left untouched. The backup snapshot falls back to the
strategy coordinates when the operator status echo is empty, so the
restore path rebuilds backupSource (credentialsSecret cozy-backups-creds
is projected into the restore namespace before dispatch).

RBAC grows patch/update on perconaservermongodbs for the injection. A
bootstrap.enabled + useSystemBucket render guard mirrors postgres.

Signed-off-by: Andrey Kolkov <androndo@gmail.com>
Assisted-By: LLM
2026-09-22 12:03:58 +04:00
IvanHunters aa20495df3 feat(marketplace): register a tapped repository's apps on connect
Connecting a marketplace repository materialized its PackageSource but created no Package, so its ApplicationDefinitions never rendered and its apps never appeared in the dashboard catalog until a manual `cozypkg add` the dashboard cannot drive (the package list is computed from the ApplicationDefinitions, which do not exist yet).

The tap materializer now creates a tap-managed registration Package for each materialized PackageSource, the way the platform ships a Package per built-in app, so the ApplicationDefinition registrations deploy on connect and the apps become browsable; the app components carry no install block and are still instantiated per user resource.

Privileged install components of an unconfirmed tap auto-registration (a Package that still carries the marketplace-tap label) are refused on several best-effort layers: the Package reconciler skips their HelmRelease and prunes a lingering one at the install site, and the PackageSource reconciler withholds their ExternalArtifact so the helm-controller has no privileged content to deploy, re-running on confirmation via a Package watch. `cozypkg add --allow-privileged` sheds the label as an explicit ownership handover, which is the confirmation; a platform Package never carries the label. A benign component flipped to privileged in a new revision can still be materialized by source-watcher before these layers observe the flip; closing that race fully requires decoupling the catalog registration from install and is tracked in issue #4359.

`cozypkg untap` and the dashboard disconnect remove the registration Package (de-registering the apps) under a UID + ResourceVersion precondition, so a concurrent `cozypkg add` handover is never clobbered; running instances are left but unmanaged. `cozypkg add` re-opens a tap auto-registration for variant selection and hands it over. The cozystack-api ClusterRole gains delete on packages for the disconnect path.

Assisted-by: LLM
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2026-09-21 21:57:04 +03:00
Alexey Artamonov 142a14cb69 fix(platform): reject an empty realm name instead of coercing it to cozy
realmName resolved through `| default "cozy"`, which fires on both null
and "". Unset (null) legitimately means the default, but an explicit ""
is a meaningless value and coercing it hid the mistake. Split the two:
null falls back to cozy, an empty string reaches the charset guard and is
rejected — the same null-vs-"" distinction keycloakInternalUrl already
draws a few lines below.

Assisted-by: LLM
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
2026-09-21 12:15:10 +03:00
Alexey Artamonov ff637ed9a2 fix(platform): validate the realm-name charset and regenerate kubernetes artifacts
The realm-name guard checked only the type and echoed the parsed value
back, so its hint told an operator who wrote `on` to set realmName:
"true" — the token YAML had already discarded. Drop the echo and add a
slug-charset check: the name is spliced verbatim into URL path segments
and a shell token, so a "/" or a space must be rejected at the source
rather than split a URL or the hook script downstream.

The description edit in the previous commit was hand-applied to the
generated kubernetes README and never reached the Go types, leaving
pre-commit red; regenerate both with cozyvalues-gen. Finish the
oidc-tenant System-mode prose that still read cozy as a fixed name.

Assisted-by: LLM
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
2026-09-21 12:15:10 +03:00
Alexey Artamonov 949096694b fix(platform): reject non-string realm names and shell-quote the hook realm
A realmName that YAML resolves to an int or a bool (a bare 123, or a
reserved word like on/off) was coerced with toString, silently renaming
the realm to something the operator never typed (on -> "true"). Fail the
render with a quoting hint at the source instead of surfacing it later as
a broken issuer URL.

The update-email hook Job interpolated the realm into its shell script
unquoted, so a name carrying a space or a shell metacharacter could break
or inject into the script; single-quote it. The Grafana and managed-cluster
issuer helpers build the URL with printf %s straight off the channel value,
so guard them with toString for the same non-string reason.

Name the configured realm in the tenant kube-apiserver oidc.mode
description and the Grafana identity-mode comment, which still read cozy as
if it were fixed.

Assisted-by: LLM
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
2026-09-21 12:15:10 +03:00
Alexey Artamonov a3df02df23 fix(kubernetes): name the configured realm in the OIDC denial message
The claimValidationRules message in the System-mode AuthenticationConfiguration
hardcoded "in the cozy realm", so on a renamed realm it told a rejected user to
look in a realm that no longer exists. Build it from the same realm the issuer
uses (toString-guarded, as the derived issuer URL already is).

Assisted-by: LLM
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
2026-09-21 12:15:10 +03:00
Alexey Artamonov 80d32b3ab7 fix(keycloak): complete realm-name coverage and guard non-string names
The enable-update-email hook Job in keycloak-configure still pinned
REALM=cozy, so on a custom realmName it waited on a realm the chart never
provisions and the cold install timed out — the one seventh consumer left
behind, and the only value nobody could set without breaking the install.

A bare-integer or YAML-reserved realm name reached the printf %s that
builds the derived keycloakInternalUrl as a Go int, rendering a
"%!s(...)" format error into the URL; coerce the name to a string once
at the source.

Document that renaming the realm on a running cluster provisions a fresh
empty realm and orphans the accounts in the old one, and note in the OIDC
tenant and grafana guides that the realm name is now the configurable
default. info/Makefile carried NAME=etcd, so cozyhr drove the wrong
release from that package; correct it while touching the file.

Assisted-by: LLM
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
2026-09-21 12:15:10 +03:00
Alexey Artamonov 44a9a8e735 feat(platform): make the Keycloak realm name configurable
The realm name "cozy" was hardcoded in keycloak-configure (which owns
the realm, its clients and the cozystack-cluster-admin roles), in the
dashboard and linstor-gui gatekeepers, in the info kubeconfig, and in the
Grafana and managed-Kubernetes apiserver OIDC issuers. Operators could
not rename the realm without patching vendored templates, which
`make update` reverts. The apiserver OIDC issuer is already configurable
via talm oidcIssuerUrl, so pointing it at a custom realm left every SSO
component on "cozy" and broke login with an issuer mismatch.

authentication.oidc.realmName (default "cozy") now rides the _cluster
channel so every consumer resolves the same realm, and the default
keycloakInternalUrl is derived from it so the realm is named in one place.

Assisted-by: LLM
Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
2026-09-21 12:15:10 +03:00
Aleksei Sviridkin 8799a4e4aa fix(cluster-api): backport live migration before host eviction (#4291)
## What this PR does

CAPK v0.1.10 handles host eviction by draining and deleting the guest
VMI. It also looks up the physical host name in the guest cluster, and a
recreated guest can remain cordoned. Backport CAPK #374, #389 and #392
onto the current provider API and runtime, with separate bounded
submission, active migration and fallback windows, and ordinary VMIM
objects without a priority, a field the pinned `kubevirt.io/api` v1.5.0
does not carry. The submitted VMIM deadline survives reconciliation;
expired, invalid or legacy marker values enter the existing fallback
path.

Add the manager build to the package pipeline, read its digest pin
through the provider imageUrl override, and ship VMIM create permission
in the compressed provider manifest. Source tests and four packaging
checks pass, including failed-build pin preservation and the rendered
ConfigMap payload. The regression tests include unprocessed submissions,
legacy and invalid deadline values, repeated reconciles, and a fresh
active migration window after submission expiry. The updated manager
also cross-compiles for linux/amd64 with Go1.26.4. The previous OCI
build predates the submission-deadline correction and does not validate
an image containing this change. The updated source still needs the
maintainer-owned image build described below.

The checked-in pin still identifies the original upstream image, so
installing this source chart directly does not enable the backport. The
regular main and release builds now include this package, stamp the
built image digest, and package that exact artifact; the release
workflow also commits the generated pin. Pre-merge E2E validation needs
a maintainer-owned build because fork publication only allows image
repositories already present in the base tree.

### Downstream repositories

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

### Release note

```release-note
fix(cluster-api): attempt live migration before drain/delete on External eviction, use the guest node name for fallback drain, and uncordon recreated guests.
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added a Cluster API Provider KubeVirt image pinned to version 0.1.10.
- Added migration-first workload eviction with fallback draining and
node recovery.
  - Added permission to create virtual machine migration resources.
  - Included the provider image in builds and deployment templates.

- **Bug Fixes**
- Preserved migration deadlines across retries and improved recovery
after interrupted or completed migrations.
  - Added a bounded wait for migrations before falling back to draining.

- **Documentation**
- Documented the provider image version, patches, build process, and
release considerations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-18 22:23:10 +04:00
Aleksei Sviridkin 3219f7ee9a fix(backups): give the barman-cloud sidecar its own resources (#4220)
## What this PR does

The barman-cloud plugin injects its sidecar with no resource requests or
limits. What that means depends on the namespace. A tenant with
`resourceQuotas` set ships a `LimitRange` that defaults containers to
128Mi (`packages/apps/tenant/templates/quota.yaml`), so there the
sidecar inherits that default and is OOMKilled mid-backup. A namespace
without a `LimitRange`, which is `cozy-keycloak` and any tenant that
leaves `resourceQuotas` empty, gave the sidecar no requests and no limit
at all, so nothing reserved memory for it and nothing bounded it. The
failure on the tenant path is quiet from the control plane: the
`ObjectStore` stays healthy, the `Cluster` stays `Ready`, and only the
backup fails.

Observed on a 1.6 cluster while backing up a 38 MB database:
`lastState.terminated.reason: OOMKilled`, `exit 137`, four restarts on
the sidecar, and a cgroup high-water mark of 254 MiB.
`container_memory_working_set_bytes` peaked at only 64 MiB over the same
window — it is sampled, and it misses the spike that actually triggers
the kill, which is why the working-set number does not explain the
failure on its own.

Both paths that build an ObjectStore now carry resources:

- `cozy-lib.barman.sidecarConfiguration`, used by
`packages/apps/postgres` for the backup and recovery ObjectStores and by
`packages/system/keycloak`;
- `barmanSidecarConfiguration()` in
`internal/backupcontroller/cnpgstrategy_controller.go`, which builds the
platform's own ObjectStore on the `useSystemBucket=true` path.

Requests are 100m/256Mi with a 1Gi memory limit on every caller: 256Mi
holds the measured working set and 1Gi is four times the measurement,
which is also the ceiling a caller without a LimitRange gets where it
had none. No CPU limit is set, following the `entityOperator` precedent
in `packages/apps/kafka/templates/kafka.yaml`: a throttled sidecar
stalls WAL archiving instead of failing it, which is harder to notice
than an outright failure. Measured CPU peak was 50 mCPU. Inside a tenant
the limit is charged against the `ResourceQuota` `limits.memory` budget,
1Gi per instance pod; a 4Gi tenant spends a quarter of it per Postgres
instance.

The helper is renamed from `checksumSidecarConfiguration` to
`sidecarConfiguration`, because it no longer carries only the checksum
pin and its name and doc comment would otherwise be wrong.

Tests cover both paths and both render sites: a new helm suite in
`packages/apps/postgres`, extra assertions in the existing keycloak
suite, an assertion in the controller test, and a deepcopy test for the
new field. Each of them fails against the unfixed sources.

One thing this PR deliberately does not do. This is the fifth time the
128Mi tenant default has been worked around per component — kafka and
zookeeper presets (#2537), the kafka entity-operator (#2934), the
cert-manager cainjector (#3199), and the note carried in the
etcd-operator values. Whether the default itself should move looks like
your call rather than something to fold into a fix for one sidecar, so
it is left alone here.

### Screenshots

Not a UI change.

### Downstream repositories

Walked the trigger map in `docs/agents/contributing.md` against the
diff, entry by entry. The change touches one cozy-lib helper, the two
chart templates that include it, one Go function, and tests. It adds,
renames or removes no package; changes no `values.yaml`,
`values.schema.json`, `Chart.yaml` or `README.md`, so no version enum,
default or generated artifact moves; changes no `ApplicationDefinition`
semantics, no `release.prefix`, no output Secret or Service name;
touches no CRD, no namespace, no `hack/` file, no telemetry metric or
label, no `cozy-proxy` annotation, no node or network requirement, and
no commit or PR convention. No entry matches.

- [x] No downstream repository is affected by this change

### Release note

```release-note
fix(backups): give the barman-cloud sidecar its own resources
The barman-cloud sidecar was injected without resource requests or limits, so in tenant namespaces it inherited the 128Mi `LimitRange` default and was OOMKilled during backups while the `ObjectStore` and the `Cluster` both stayed healthy. It now requests 100m/256Mi and caps memory at 1Gi on the chart path and on the platform's Go path alike; in namespaces without a LimitRange the sidecar had no limit before and gets the same 1Gi ceiling.
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added explicit CPU and memory resource settings for barman-cloud
backup and recovery sidecars.
- Backup sidecars request 100m CPU and 256Mi memory, with a 1Gi memory
limit.
  - Recovery sidecars receive a 1Gi memory limit.
  - Preserved the S3 checksum configuration.

- **Bug Fixes**
- Prevented sidecars from inheriting unsuitable tenant resource limits.
- Ensured resource settings are preserved when backup configuration is
copied.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-18 22:15:36 +04:00
Aleksei Sviridkin d71e78a74a fix(linstor): wait for temporary probe devices (#4100)
## What this PR does

Backports
[LINBIT/linstor-server#528](https://github.com/LINBIT/linstor-server/pull/528)
to the LINSTOR 1.33.3 build used by piraeus-server. A ZFS probe volume
can be created before udev publishes its device path, leaving the pool
without I/O properties and blocking replica placement on 4K pools.

Wait for the probe device before reading its properties, retry up to
three times and report failures. Skip null paths in the existing volume
list and fall back to a probe when ZFS has no usable path. Serialize
probe creation and cleanup, stop after cleanup failures and preserve
interrupts. Inactive LVM volumes retain the existing no-probe behavior.

Validation: all six patches apply together to v1.33.3. All 1026 tests
and `assemble` pass on Linux amd64 with JDK 11, including 17 regression
tests; 14 of those fail without this patch. `checkstyleMain` reports the
same existing return-inside-for error at `DrbdAdm.java:970` from
`retry-secondary-after-mkfs.diff`, reproduced without this patch; no new
findings.

### Downstream repositories

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

### Release note

```release-note
fix(linstor): wait for ZFS probe devices before reading pool I/O properties to avoid incorrect block sizes and failed replica placement
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- Improved storage device probing by waiting for temporary devices to
become available and retrying when necessary.
- Improved compatibility with ZFS volumes when standard device paths are
unavailable.
  - Prevented unnecessary probing of inactive shared LVM volumes.
  - Improved handling of interrupted probes and cleanup failures.

- **Documentation**
- Added documentation describing storage probing behavior and
compatibility updates.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-18 21:50:25 +04:00
Aleksei Sviridkin ee09e73343 fix(kafka): make topics[].config optional (#3935)
## What this PR does

Makes `topics[].config` optional across the Kafka typed API, generated
schema, documentation, and chart contract. The KafkaTopic template
already omits an empty config, and Strimzi accepts the field as
optional; the Cozystack schema was the layer incorrectly rejecting such
topics.

The Helm test renders a topic without config and asserts `spec.config`
is absent. The Chainsaw fixture also omits config and waits for the
resulting KafkaTopic to reach Ready.

Go API compatibility: `Topic.Config` changes from `runtime.RawExtension`
to `*runtime.RawExtension`, so Go callers constructing that field must
pass a pointer. The Terraform provider intentionally does not model
Kafka topic tuning (`config`), so this does not require a provider
follow-up.

Validation:

- `helm unittest packages/apps/kafka`: 36/36 passed.
- `go vet ./kafka` in `api/apps/v1alpha1`: clean; the package has no
test files, so this shows it compiles.
- root `make generate`, which owns `zz_generated.deepcopy.go`, and `make
-C packages/apps/kafka generate`: no diff.

### Screenshots

Not a UI change.

### Downstream repositories

Walked the trigger map against all nine changed files. The provider's
Kafka model deliberately excludes `topics[].config`; no downstream
repository mirrors this nested field or its requiredness.

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:

### Release note

```release-note
fix(kafka): allow topics to omit the optional Strimzi config map
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Kafka topic configuration is now optional. Topics can be created
without explicitly providing configuration settings.
- Topic configuration is omitted from serialized resources when not
specified.

- **Bug Fixes**
- Improved handling of topic resources without configuration, including
correct readiness validation and rendering.

- **Documentation**
- Updated Kafka configuration documentation and schemas to reflect that
topic configuration is optional.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-18 21:29:50 +04:00
Yan Bondarenko 9fac0add84 docs(mongodb): describe the upgrade as a carry-over, not a rotation
The upgrade section still promised a password rotation and a regenerated
credentials Secret. With the operator-copy fallback the chart reads the
current passwords from internal-<release>-users and writes the same values,
so nothing rotates and no session breaks.

Assisted-by: LLM
Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com>
2026-09-18 17:30:26 +04:00
Yan Bondarenko 0e17c6f65e test(mongodb): pin the reuse, fallback and ownership branches with lookup fixtures
Every test rendered with an empty lookup, so the branch that keeps a live
password could be deleted with the suite green. Three fixtures through
kubernetesProvider cover what a fresh render never reaches: a helm-owned
users Secret is reused, a Secret the release does not own stays out of the
manifest while its password still reaches the credentials, and the operator
copy rebuilds a missing users Secret. Values with URI-reserved characters
cover the userinfo encoding, and both Secrets are asserted Opaque again.

Assisted-by: LLM
Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com>
2026-09-18 17:30:26 +04:00
Yan Bondarenko f0da352b30 fix(mongodb): refuse to invent a password for a users Secret the chart does not own
On the not-owned path a missing password key produced a generated password
that reached <release>-credentials but never the users Secret the operator
reads, so the dashboard advertised a password MongoDB never had. The render
now fails and names the Secret and the key. The operator fills every key
itself, so only a hand-made partial Secret reaches this branch.

Assisted-by: LLM
Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com>
2026-09-18 17:30:26 +04:00
Yan Bondarenko 348a65d18b docs(mongodb): keep the credentials paragraphs on one line
Two paragraphs under Credentials were hard-wrapped at 80 columns, the one
place in this README that was; the rest follows the one-line-per-paragraph
rule the repository asks for in markdown.

Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com>
Assisted-by: LLM
2026-09-18 17:30:26 +04:00
Yan Bondarenko 66a00f552e fix(mongodb): url-encode the username in the connection uri
Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com>
2026-09-18 17:30:26 +04:00
Yan Bondarenko b4be956d7e fix(mongodb): fill the dashboard credentials on a first install
Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com>
2026-09-18 17:30:26 +04:00
Yan Bondarenko 4dc6065ec9 fix(backups): give the sidecar sizing its reason per caller
The Keycloak comment and the helper doc claimed the tenant LimitRange as the
reason for the resources, but cozy-keycloak has no LimitRange and neither
does a tenant that leaves resourceQuotas empty: there the sidecar had no
requests and no limit at all, and the change gives it a 1Gi ceiling where
there was none. The comments and both doc blocks now say what applies to
each caller. No rendered object changes.

Assisted-by: LLM
Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com>
2026-09-18 17:30:20 +04:00
Yan Bondarenko 9214430bd8 fix(backups): give the barman-cloud sidecar its own resources
The barman-cloud plugin injects its sidecar without resource requests or
limits. Tenant namespaces ship a LimitRange that defaults containers to
128Mi, so the sidecar inherits that default and is OOMKilled mid-backup:
the ObjectStore stays healthy, the Cluster stays Ready, and only the
backup fails. A cgroup high-water mark of 254 MiB on a 38 MB database
shows the default cannot hold it.

The shared cozy-lib helper and the Go path that builds the platform's own
ObjectStore now request 100m/256Mi and cap memory at 1Gi. No CPU limit is
set, matching the entityOperator precedent in the kafka chart: a
throttled sidecar stalls WAL archiving instead of failing it.

The helper is renamed from checksumSidecarConfiguration to
sidecarConfiguration, since it no longer carries only the checksum pin.

Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com>
2026-09-18 17:30:20 +04:00
Yan Bondarenko bc2ba106e1 docs(cluster-api): explain the omitted migration priority by the pinned API
CAPK v0.1.10 pins kubevirt.io/api v1.5.0, whose
VirtualMachineInstanceMigrationSpec carries only vmiName; upstream #392
sets priority against v1.9.0. The typed migration does not compile against
the pinned API, which is why the patch builds it unstructured.

Assisted-by: LLM
Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com>
2026-09-18 17:30:13 +04:00
Yan Bondarenko c85f35070c fix(cluster-api): bound pending migration submission
A migration that KubeVirt never processes must not block host eviction indefinitely. Keep its submission deadline separate from active migration and fallback drain windows.

Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com>
2026-09-18 17:30:13 +04:00
Yan Bondarenko bb3a2fa220 docs(cluster-api): clarify trusted backport image delivery
Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com>
2026-09-18 17:30:13 +04:00
Yan Bondarenko c8f4906e93 fix(cluster-api): backport live migration before host eviction
Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com>
2026-09-18 17:30:13 +04:00
Yan Bondarenko 8ec76a3614 fix(kafka): make topics[].config optional
Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com>
2026-09-18 17:30:06 +04:00
Yan Bondarenko cee9065589 fix(linstor): wait for temporary probe devices
Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com>
2026-09-18 17:30:00 +04:00
Aleksei Sviridkin 88a7ea0e3c fix(kubeovn): retry VM migration setup after scheduling (#4253)
## What this PR does

Kube-OVN can drop VM migration setup when a migration enters
`Scheduling` before the target Pod is visible or the source or target
node is known. The phase may remain unchanged afterward, leaving the
target Pod waiting for network setup.

Backport the final fix from kubeovn/kube-ovn#6834 to the pinned v1.15.10
image: return an error for the transient missing-node case so the
existing rate-limited queue retries it. Keep the same-node behavior
unchanged. That backport can be removed when updating to v1.15.14 or
later.

Add a separate retry guard for the target-Pod creation race. It returns
an error when the informer cache has no target Pod yet, instead of
forgetting the migration event. This guard is not part of #6834 and must
remain until an equivalent fix is verified in the selected upstream
version.

Also include the existing kubeovn image target in the root build list.
Without it, a kubeovn patch produces an empty PR build matrix and the
image is omitted from the regular build and release paths.

Validation:
- All six regression cases pass on Linux/amd64 using the real controller
workqueue. The new missing-target-Pod case fails without its guard and
passes after the Pod appears in the informer cache, without changing the
VMIM phase. The existing deferred-node, already-scheduled, and same-node
cases continue to pass.
- All 19 build-matrix tests pass; the full matrix includes kubeovn
exactly once.
- Both lint steps, pinned golangci-lint v2.10.1 and upstream `modernize
-test -skipgenerated ./...`, pass for the Linux/amd64 target using Go
1.26.4. On macOS, modernize is built for the host and run with the Linux
target environment, since `go tool` otherwise tries to execute a
cross-compiled Linux binary.
- All three packaged patches apply cleanly to pinned v1.15.10 with `git
apply --check --whitespace=error`; the resulting source matches the
tested tree. Formatting and source/build whitespace checks pass.

The regression test covers controller queue behavior; the standard
container E2E environment does not cover DRBD-backed live migration. The
regular image build applies the backport and stamps the resulting digest
before packaging.

### Screenshots

Not applicable; no UI change.

### Downstream repositories

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

The change adds an existing image target to the build list; package
layout, make target interfaces, chart values, APIs, and published
image-reference formats stay the same. CCP discovers package image
targets dynamically and does not carry the root build list. The website
has no documented workaround for kubeovn/kube-ovn#6823 or #6834 to
remove.

### Release note

```release-note
fix(kubeovn): retry VM migration network setup when the target Pod or source/target scheduling information is not yet available, and include the Kube-OVN image in the regular build matrix.
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- Improved VM migration handling when source or destination node
scheduling information is temporarily unavailable.
- Migration events are now retried after scheduling completes instead of
being dropped.
- Preserved existing behavior when migration source and destination
nodes are identical.

- **Tests**
- Added coverage for migration scheduling and retry scenarios, including
partially assigned and same-node cases.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-18 17:29:45 +04:00
Aleksei Sviridkin 08a2e76707 fix(opensearch): remove the data PVCs when the application is deleted (#4136)
## What this PR does

Fixes #4086.

The OpenSearch operator provisions its data volumes through a
StatefulSet, so the PVCs carry no ownerReference, and the `opensearch`
app chart has no cleanup hook. Removing the application therefore leaves
every data volume `Bound`, counting against the tenant's storage quota
with nothing left in the namespace that could reclaim it. `clickhouse`,
`kafka` and `harbor` remove their operator-owned PVCs in a post-delete
hook.

This adds a post-delete Job to `opensearch` that requests the deletion
of the claims labelled `opster.io/opensearch-cluster=<release>` with
`--ignore-not-found --wait=false` and exits non-zero when the request
fails, with its own ServiceAccount, a Role limited to
`persistentvolumeclaims` (`get`, `list`, `delete`), the
`clastix/kubectl` image pinned by digest through `cozy-lib.image`, a
non-root restricted securityContext, `activeDeadlineSeconds: 120` so a
kubectl that never returns cannot hold the uninstall, and
`before-hook-creation` alone on the Job so a failed run and its log
survive; the ServiceAccount, Role and RoleBinding keep `hook-succeeded`.

The delete does not wait for the claims to disappear, on purpose:
OpenSearch data pods stop slowly, and a claim they still hold would keep
a waiting hook running into helm-controller's 300s uninstall budget. The
claims go once their pods have stopped, which is what the log says. This
is the shape the monitoring, etcd and seaweedfs hooks use. Retention is
not offered as a knob, consistent with the other charts; an opt-out can
be added if the maintainers prefer one.

`helm unittest`: 64/64 in the chart, including
`tests/cleanup_pvc_test.yaml` (hook annotations and deadline, selector
and exit path, digest pin and registry prefix, securityContext, Role
rules, RoleBinding). `helm template` renders the Job, ServiceAccount,
Role and RoleBinding next to the existing objects.

### Downstream repositories

A new hook template and its tests only: no values, schema, README or
defaults change.

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

### Release note

```release-note
fix(opensearch): reclaim the data PVCs of a removed application with a post-delete hook
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Automatically cleans up orphaned OpenSearch persistent volume claims
after a release is deleted.
* Cleanup waits for associated pods to stop and uses restricted
permissions and hardened security settings.
  * Supports optional container registries with pinned cleanup images.

* **Tests**
* Added coverage for cleanup behavior, hook configuration, security
settings, registry overrides, and required access permissions.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-18 17:29:38 +04:00
Timofei Larkin 8f19910988 chore(platform)!: remove the HTTPCache application (#4200)
## What this PR does

Removes the HTTPCache application.

Telemetry says it is the least installed app we ship: 3 instances in
June, July and August 2026, 4 so far in September, out of 1162-1418
clusters reporting per month. That is not zero, so I am not going to
claim nobody uses it. The maintenance side is what does not hold up.

`make update` in this package is broken. All eight sed lines in the
`update` target patch `images/nginx/Dockerfile`, but the image lives in
`images/nginx-cache/Dockerfile`, so the target fails on the first line.
It is the only package under `packages/apps/` pointing at a Dockerfile
that does not exist. The pins show what that cost us:

```
nginx              1.25.3   -> 1.31.5
ngx_cache_purge    2.5.3    -> 3.0.2
nginx-module-vts   0.2.2    -> 0.2.7
ip2proxy-c         4.1.2    -> 4.3.0
IP2Location-C      8.6.1    -> 8.7.0
ip2location-nginx  8.6.0    -> 8.7.0
ip2proxy-nginx     8.1.1    -> 8.2.0
51Degrees          3.2.21.1 -> 3.2.21.1 (frozen upstream)
```

So we ship an old nginx with third party C modules compiled into it, and
nothing tests it: no chainsaw suite, no `helm unittest` target. I don't
think we should keep carrying that for 4 instances.

Removed: the chart in `packages/apps/http-cache`, its ResourceDefinition
in `packages/system/http-cache-rd`, the values type in
`api/apps/v1alpha1/httpcache`, the `cozystack.http-cache-application`
PackageSource, its line in the `naas` bundle, the `httpcaches` entry in
`cozy:tenant:admin:base`, the dashboard icon, and the entry in the root
`Makefile` build list.

`cozy:tenant:admin:base` enumerates the `apps.cozystack.io` resources a
tenant owner may write, so `httpcaches` comes out of it with the rest of
the application. A cluster that freezes gets the grant back from the
migration instead, which keeps the chart's list tracking the catalog and
leaves no entry behind for a later sweep to read as dead config.

### Existing installations

A cluster that still runs an HTTPCache keeps it, served from a frozen
source. A cluster that never created one keeps nothing.

Without a migration an upgrade would break both.
`templates/sources.yaml` is a bare glob with no keep annotation, so Helm
prunes the http-cache PackageSource; the ArtifactGenerator is owned by
it and goes too, and both ExternalArtifacts go with the generator. Those
are the `chartRef` targets of `hr/http-cache-rd` and of every tenant
`http-cache-<name>` release, so all of them would sit `Ready=False` for
good and `HelmReleaseNotReady` would fire on every cluster with the naas
bundle.

Migration 58 runs as a pre-upgrade hook, before that prune, and branches
on whether any instance exists. Its header comment carries the mechanism
in full; this is the summary.

If one does, it copies the packages `OCIRepository` and pins the copy to
the manifest digest that object resolves at hook time, which is the
release the instances are rendering from right then. `spec.ref.digest`
is used when the installer pinned one; a tag or semver ref goes through
`status.artifact.revision`, whose digest half is the manifest digest.
`status.artifact.digest` is the checksum of the downloaded tarball and
cannot stand in, and a source with neither aborts the migration rather
than publishing a source that points nowhere. Reading the digest live
rather than baking one into the script makes the freeze a no-op by
construction and asks no question about which release was the last to
ship the chart.

The copy takes the url, the pull secret, the interval and any
verification block from the live object, so an air-gapped or mirrored
registry keeps serving exactly what it already served. It is written
with no owner references and no Helm metadata, and with the
`platform.cozystack.io/no-delete` label the platform chart puts on the
original, because it becomes the only chart source those instances have.
The PackageSource is then repointed at the copy, annotated
`helm.sh/resource-policy: keep` and stripped of its release metadata, so
neither Helm nor a later release adopts or prunes it. Helm re-reads that
annotation off the live object when it prunes, which is what makes
disowning from inside a pre-upgrade hook work.

Names stay as they are, deliberately. The ApplicationDefinition inside
that artifact hardcodes its `chartRef`, so a renamed PackageSource would
leave every release pointing at an artifact nobody produces. Because the
names hold, `hr/http-cache-rd` and every tenant release keep the
`chartRef` they already have and nothing goes not ready. What changes is
where the chart comes from: a pinned digest that no later release moves.

The migration also applies `cozy:tenant:admin:http-cache-frozen`, a
ClusterRole labelled `rbac.cozystack.io/aggregate-to-tenant-admin`,
granting the verbs `cozy:tenant:admin:base` used to carry on
`httpcaches`. The hook applies it before the upgrade drops the rule from
the chart, so an owner's access never lapses and nothing changes for
them. Without it a frozen instance would be visible and undeletable by
the person who owns it: the wildcard over `apps.cozystack.io` lives in
`cozy:tenant:base`, which aggregates into `cozy:tenant` and not into
`cozy:tenant:admin`, `cozy:tenant:view:base` is read only, and no role
in the tenant chain carries a verb on `helm.toolkit.fluxcd.io`.

If no instance exists, the migration removes the platform side instead:
the Package first and waited on, because the operator reconciles off an
informer cache and re-creates the release it owns while that Package is
still there, then the release, its ApplicationDefinition and its Helm
storage. Nothing is pinned or granted on a cluster that never used the
application.

Every path is fail-closed. A failed fleet scan, a failed read of the
source OCIRepository, a source with no digest to freeze on, a failed
patch or a failed delete stops the migration before it stamps the
version.

The migration is numbered 58, because main already carries a 57 from the
SeaweedFS volume-size-limit change and its `targetVersion` is already
58. #4287 wants 58 as well, so whichever of the two lands second
renumbers and bumps `targetVersion` with it. main is merged in rather
than rebased, because the branch carries commits that are not mine to
rewrite; the one conflict was `migrations.targetVersion`, resolved as
59, one past this branch's migration number.

### The procedure, written down

`docs/deprecating-applications.md` is new, and is the point of doing
this one carefully. It covers how to choose between migrating instances
onto a successor and freezing them, the chain that makes a bare chart
deletion strand every surviving instance, how the freeze reads its
digest off the cluster's own source, what the migration owes a cluster
on each branch, and where the tenant RBAC grant goes. `AGENTS.md` points
at it so the next retirement starts there rather than rediscovering it.

### If you run HTTPCache

Nothing changes on upgrade. Your instance keeps running and stays
manageable through the Cozystack API and the dashboard, and `kubectl get
httpcaches` keeps working.

What changes is that it is frozen. The chart now comes from the packages
artifact your cluster was already running, pinned by digest, and gets no
further updates from us, including security updates to the nginx build.
There is no replacement in the catalog. Delete the HTTPCache object when
you no longer need it, the way you always did; the migration keeps the
tenant permission that lets you.

### Verification

`helm template` on `packages/core/platform` before and after, for
`isp-full`, `isp-full-generic` and `isp-hosted`. In all three the
differences are the `http-cache` Package and PackageSource going away
and `migrations.targetVersion` moving from 57 to 58, which on a cluster
below 58 also renders the migration hook Job and its RBAC.

The migration suite `hack/migration-58-http-cache-freeze.bats` covers
both branches, the order of every step and each fail-closed path.
Freezing on the tarball checksum instead of the manifest digest,
dropping the `no-delete` label, dropping the `managed-by` null, dropping
the tenant grant, returning from the Package delete without waiting,
freezing unconditionally and granting on a cluster with no instance each
turn the matching test red.
`packages/system/cozystack-basics/tests/clusterroles-tenant-admin-aggregation_test.yaml`
pins the aggregation label the migration's ClusterRole reaches tenant
admins through, so renaming it fails in the chart rather than silently
on the clusters that need the grant.

Green: `helm unittest` for `packages/core/platform` and
`packages/system/cozystack-basics`, `make migrations-target-check`,
`hack/cozystack-version-stamp.bats`, `go test
./internal/controller/cacert/...`, `go build ./...` in the
`api/apps/v1alpha1` module, the console `ServicesTab` and
`sidebar-icons` suites with `tsc --noEmit`, `make rd-presets-check`, and
the bats suites that enumerate packages and image refs.

### Screenshots

The catalog entry and its nginx sidebar icon are gone. Nothing new to
show.

### Downstream repositories

Two repos are affected. No follow-up is open yet, so no box is ticked.

`cozystack/website` carries `content/en/docs/*/networking/http-cache.md`
in eight doc versions and lists `http-cache` in the hardcoded
`NETWORKING` list in its Makefile. The `next` page and the Makefile
entry are what need to go, released versions stay as history.

`cozystack/terraform-provider-cozystack` has a `cozystack_httpcache`
resource and data source. The resource keeps working after this: it is
hand written against its own terraform-plugin-framework model and
imports nothing from here. What breaks, once that repo bumps its
`api/apps/v1alpha1` pin past this release, is
`internal/provider/httpcache_model_test.go`, the per kind schema guard,
which is the only file over there importing this module. The resource
and the guard should go together.

The other repos have no reference to the package.

- [ ] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

### Release note

```release-note
chore(http-cache)!: remove the HTTPCache application from the catalog. New instances can no longer be created from the catalog and the `HTTPCache` values type is gone from the `api/apps/v1alpha1` Go module. A platform migration keeps existing instances working: on a cluster that runs one, the application is frozen on the packages artifact that cluster was already rendering from, pinned by digest, and stays manageable through the Cozystack API and the dashboard, but receives no further updates, including security updates to the nginx build. On a cluster with no instance the application is removed outright. There is no replacement in the catalog; delete the HTTPCache object when you no longer need it.
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Removed**
* Removed the HTTP Cache application and its deployment, configuration,
dashboard metadata, and packaging from the platform.
  * The HTTP Cache application is no longer included in the NaaS bundle.
* HTTP Cache resources and related API definitions are no longer
available.

* **Migration**
* Existing HTTP Cache installations are frozen during migration;
environments without instances are cleaned up.
  * Platform migration target advanced to version 58.

* **Documentation**
* Updated storage immutability documentation to reflect Valkey instead
of HTTP Cache.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-18 17:03:21 +04:00
IvanHunters 4844e1d99c fix(opensearch-operator): stop excluding a node whose drain cannot finish (#4323)
## What this PR does

The previous patch let a permanently yellow cluster past the restart
health gate. With three or more nodes `packages/apps/opensearch` renders
`drainDataNodes: true`, and the restart then reaches
`PreparePodForDelete`, which excludes the candidate node from allocation
before it can know whether the drain will finish.

On the cluster that made the gate wait it never finishes: a shard on
that node has nowhere else to go, which is the same condition holding
the cluster yellow. The exclusion is only withdrawn after a pod is
really deleted (`rollingRestart.go`, `upgrade.go`), so a data node stays
out of allocation for as long as the restart is stuck, shedding its
shards onto the rest of the cluster and buying nothing. Before the
yellow gate this path was unreachable with draining on, so nothing was
written at all.

`patches/skip-unusable-drain-exclusion.diff` asks the allocator before
excluding anything, and only while nothing is moving, so a drain making
progress costs nothing extra. A started shard answering
`can_move_to_other_node: no` means the drain cannot finish: no exclusion
is written, and one left by an earlier pass is withdrawn. A drain that
can still progress behaves exactly as before, and the two-data-node path
is untouched because it never waits for a full drain.

Checking after the exclusion instead would flap: the next reconcile ten
seconds later would apply it again.

The v2.8.0 base has no allocation-explain call of its own, so the patch
adds the client method and a response type carrying only the field the
decision reads.

The second commit makes the shipped tests real. The image build ran `go
build` alone, so a test file that stopped compiling, or stopped
asserting, still produced an image. `go test` over the two package trees
the patches touch now runs in the builder stage; both run without a
cluster.

Upstream as opensearch-project/opensearch-k8s-operator#1585, fixed there
by opensearch-project/opensearch-k8s-operator#1586. Both patches go away
with the first operator release carrying that change.

### Screenshots

Not a UI change.

### Downstream repositories

Walked the trigger map against the diff: two files under
`packages/system/opensearch-operator/images/opensearch-operator/`. No
package README or values schema, no API or CRD, no node prerequisites in
`hack/`, no vendored chart, no make target.

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

### Release note

```release-note
fix(opensearch-operator): a rolling restart or upgrade that cannot drain its next node no longer leaves that node excluded from shard allocation for as long as the restart is stuck
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
  * Improved node draining during OpenSearch maintenance and restarts.
* The operator now checks whether shards can move before excluding a
node from allocation.
* If a shard cannot relocate, the existing exclusion is withdrawn and
draining pauses, helping prevent nodes from remaining unnecessarily
unavailable.
* Existing behavior is preserved for two-node clusters and while shard
relocation is already active.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-18 15:17:18 +03:00
Yan Bondarenko 54ac8ed135 fix(opensearch): keep the PVC cleanup hook from holding the uninstall
kubectl delete waits for the claims to be gone unless told otherwise,
and OpenSearch data pods stop slowly, so the post-delete hook could
outlive helm-controller's 300s uninstall budget on a claim that would
not release and leave the HelmRelease stuck uninstalling.

The delete now runs with --wait=false, the shape the monitoring, etcd
and seaweedfs hooks use: the deletion is requested and the claims go
once their pods have stopped, which the log now says instead of
"complete". The Job carries activeDeadlineSeconds 120 as the backstop
for a kubectl that never returns and keeps only before-hook-creation,
so a failed run and its log survive. The Role drops watch, which
nothing needs once the wait is gone.

Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com>
Assisted-by: LLM
2026-09-18 16:09:19 +04:00
Yan Bondarenko 13be303fbb fix(opensearch): remove the data PVCs when the application is deleted
The OpenSearch operator creates its data volumes through a StatefulSet,
so the PVCs carry no ownerReference, and the chart had no cleanup hook.
Removing the application left every data volume Bound and counting
against the tenant quota, unlike clickhouse, kafka and harbor, whose
post-delete hooks reclaim the operator-owned storage.

A post-delete hook in the same shape as the clickhouse one now deletes
the PVCs labelled opster.io/opensearch-cluster=<release>, with a Role
limited to persistentvolumeclaims and the kubectl image pinned by digest.

Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com>
2026-09-18 16:09:19 +04:00
Aleksei Sviridkin bf26ce9744 fix(apps): let the cleanup hooks watch the objects they delete (#4135)
## What this PR does

Fixes #4085.

`kubectl delete` waits for the object to disappear unless `--wait=false`
is given, and that wait is a watch on the object. The post-delete hook
Roles of `harbor`, `clickhouse`, `mariadb` and `qdrant` grant `get`,
`list` and `delete` only, so every uninstall logs

```
Failed to watch *unstructured.Unstructured: persistentvolumeclaims "..." is forbidden: User "system:serviceaccount:<ns>:<release>-cleanup" cannot watch resource "persistentvolumeclaims"
```

the wait is skipped, and the hook prints `PVC cleanup complete` while
the claims are still terminating.

Every collection-scoped rule behind a delete in these four hooks now
also carries `watch`: `persistentvolumeclaims` in all four;
`certificates`, `challenges` and the solver `ingresses` in harbor. The
four named Secrets the mariadb hook removes keep `get` and `delete`
only: they carry no finalizer, so the delete never reaches the watch,
and a `resourceNames` rule does not authorize a collection watch anyway.

A real wait needs a bound. kubectl's default is a week, and a claim held
by a pod that will not stop, or a stuck detach, would hold the hook
until helm-controller's 300s uninstall budget ran out and leave the
HelmRelease stuck uninstalling. The PVC deletes now run with
`--wait=true --timeout=120s`, so the expiry is reported by the script
with the claims still terminating named, and each Job carries
`activeDeadlineSeconds: 270` with `terminationGracePeriodSeconds: 10`,
the numbers `packages/apps/tenant/templates/cleanup-job.yaml` documents
against that budget, as the backstop for a kubectl that never returns.
`--wait=false` was not an option: it is exactly the
report-complete-while-terminating behaviour this PR removes.

The chart unit tests are updated and pass: harbor 21/21, clickhouse
24/24, mariadb 111/111, qdrant 104/104. The behavioural suite
`hack/app-cleanup-hooks_test.bats` passes 11/11 against the changed
scripts.

The second observation in #4085, the harbor hook pod occasionally
starting after its ServiceAccount is gone, is a hook-weight question and
is not touched here.

### Downstream repositories

Hook templates and their tests only: no values, schema, README or
defaults change.

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

### Release note

```release-note
fix(apps): let the harbor, clickhouse, mariadb and qdrant cleanup hooks wait for the deleted objects, with the wait bounded so a stuck claim cannot hang the uninstall
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Enhancements**
- Cleanup operations for ClickHouse, Harbor, MariaDB, and Qdrant now
wait for persistent volume claim deletion to complete, with a 120-second
timeout.
- Cleanup jobs are limited to 270 seconds and allow 10 seconds for
graceful termination.
- Cleanup permissions now support monitoring relevant resources,
including persistent volume claims and, where applicable, certificates,
challenges, and ingresses.
  - Named Secret access is limited to retrieval and deletion.

- **Tests**
- Updated chart validation to verify cleanup timing, deletion behavior,
permissions, and restrictions on unrelated resources.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-18 16:00:45 +04:00
IvanHunters 000e2e3121 build(opensearch-operator): run the patched tree's tests in the image build
The patches under images/opensearch-operator/patches carry their own tests and
nothing runs them. The build compiles main.go, and go-unit-tests in the root
Makefile covers this repository's own Go tree, so a test file that had stopped
compiling still produced an image:

    printf 'func Broken() { this is not go }' >> restart_gate_test.go
    go build -a -o manager main.go   # exit 0

Run go test over the two package trees the patches touch before building the
binary. Both run without a cluster, and the stage takes about eight minutes on
an arm64 builder, most of it the module download the build needs anyway.

Assisted-by: LLM
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2026-09-18 14:21:14 +03:00
IvanHunters 28195d3ef8 fix(opensearch-operator): stop excluding a node whose drain cannot finish
The yellow restart gate carried in the previous patch lets a permanently
yellow cluster through, and with three or more nodes packages/apps/opensearch
turns drainDataNodes on. The restart then reaches PreparePodForDelete, which
excludes the candidate from allocation before it can know whether the drain
will finish. On the cluster that made the gate wait it never finishes: a shard
on that node has nowhere else to go, which is the same condition holding the
cluster yellow. The exclusion is withdrawn only after a pod is really deleted,
so a data node stays out of allocation for as long as the restart is stuck,
shedding its shards onto the rest of the cluster and buying nothing.

Ask the allocator before excluding anything, and only while nothing is moving,
so a drain that is making progress costs nothing. A started shard answering
can_move_to_other_node: no means the drain cannot finish, and then no exclusion
is written and one left by an earlier pass is withdrawn. The two-data-node path
keeps its behaviour, because it never waits for a full drain.

Checking after the fact instead would flap: the next reconcile, ten seconds
later, would exclude the node again.

Upstream as opensearch-project/opensearch-k8s-operator#1585, fixed there by
#1586; the patch goes away with the first release carrying it.

Assisted-by: LLM
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2026-09-18 14:04:59 +03:00
Timofei Larkin c398e0e116 Merge branch 'main' into chore/drop-http-cache
main took migration 57 and moved migrations.targetVersion to 58 while this
branch was open, so that line is the one conflict: 57 at the merge base, 58
on main, 59 here. Resolved as 59, which is what this branch's migration 58
requires, since run-migrations.sh loops up to targetVersion - 1.

Merged rather than rebased because the branch carries commits that are not
mine to rewrite.

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2026-09-18 13:20:03 +03:00
Timofei Larkin 2cbc02bd10 fix(platform): freeze HTTPCache on the artifact the cluster runs
Pinning the frozen source to a digest written into the migration picks one
release for every cluster, and v1.6.0 is not even the last one that ships
http-cache: v1.6.1 through v1.6.3 and v1.7.0-alpha.1 all do, and 1.6 patch
releases may keep doing so. A cluster on any of those would be rolled back
to an older chart by the upgrade that was meant to leave its instances
alone, and an air-gapped mirror resolves the constant only if someone
mirrored that exact manifest.

The hook runs before the upgrade re-points cozystack-packages, so the live
object still names the release the instances render from, in whatever
registry the operator serves it from and with whatever pull secret and
verification they configured. Reading the digest off it makes the freeze a
no-op by construction and needs no knowledge of which tag was last.
spec.ref.digest is used when the installer pinned one; a tag or semver ref
goes through status.artifact.revision, whose digest half is the manifest
digest. status.artifact.digest is the checksum of the downloaded tarball
and cannot stand in. A source with neither has nothing to freeze on and the
migration stops there. The copy also carries the no-delete label the
platform chart puts on the original, because it is the only chart source
those instances have left.

The tenant grant travels with the freeze as well. cozy:tenant:admin:base
enumerates the apps.cozystack.io resources a tenant owner may write, and an
application that has left the catalog has no business in that list: the
next sweep over it takes the entry as dead config, and the failure is
silent and remote, a Forbidden for the one person who still needs to remove
what they have. The chart drops httpcaches, and a cluster that freezes gets
a ClusterRole under the tenant admin aggregation label instead, applied
before the upgrade drops the rule so the access never lapses and carrying
the verb set the chart had so nothing changes for the owner. A cluster with
no instance is granted nothing.

The Package delete on the no-instance path has to block. The operator
reconciles off an informer cache, so a reconcile in flight against the
pre-delete entry re-creates the release it owns, and with it the
ApplicationDefinition and the Helm storage the following lines remove.
Returning immediately made the ordering a property of the command log
rather than of the cluster.

The preset check's SIGPIPE note named http-cache-rd as its example of a
large enum, and that chart goes away here; kubernetes-rd carries the same
enums.

Numbered 58 rather than 57: main already carries a migration 57, the
SeaweedFS volume-size-limit one, and its targetVersion is already 58.

The whole shape of this retirement was worked out here rather than
anywhere reusable, so docs/deprecating-applications.md writes it down as a
procedure: how to pick between migrating onto a successor and freezing,
what the prune takes with it, and what the migration owes a cluster on
each branch. The next application to go should not have to rediscover it.

Assisted-by: LLM
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2026-09-18 13:12:56 +03:00
Aleksei Sviridkin 301a7fed27 fix(nats): merge config.merge.accounts with the generated accounts map (#4134)
## What this PR does

Fixes #4084.

With `users` set, `templates/nats.yaml` rendered
`config.merge.accounts.A.users` from the generated passwords and then
appended `.Values.config.merge` verbatim. A tenant `config.merge` that
also carried `accounts`, which is the only place to set
`accounts.A.jetstream` for the generated account, produced the key twice
in the HelmRelease values, and the install failed at post-render with
`mapping key "accounts" already defined`.

The template now builds a single `merge` map: a deep copy of
`.Values.config.merge` with the generated users merged in under
`accounts.A.users` via `mergeOverwrite`. The generated user list wins
for account `A`; other keys under `accounts.A`, other accounts and
top-level keys such as `max_payload` are kept; a `config.merge` without
`users` passes through unchanged; and no `merge` block is rendered when
neither is set. The values contract does not change.

`helm unittest`: 67/67 in the chart. The new
`tests/config_merge_test.yaml` covers the six shapes above; against the
previous template the same suite fails on the three cases that combine
`users` with `config.merge.accounts`.

### Downstream repositories

Template and tests only: no values, schema, README or defaults change.

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

### Release note

```release-note
fix(nats): merge config.merge.accounts with the generated accounts instead of rendering the key twice
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- Improved NATS configuration merging so generated user credentials and
custom account settings are combined consistently.
- Preserved tenant-listed users, additional accounts, and top-level
configuration during merges.
- Prevented empty merge sections from being rendered when no users or
custom merge settings are configured.

- **Tests**
- Added coverage for user generation, account merging, configuration
preservation, and empty-configuration scenarios.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-18 13:44:15 +04:00
Aleksei Sviridkin a04887db3f fix(linstor): add opt-in graceful satellite shutdown on Talos (#4292)
## What this PR does

Cozystack's Talos configuration removes the systemd DRBD shutdown guard
without a replacement. Add `talos.gracefulShutdown.enabled`, disabled by
default, to release unused Secondary DRBD resources before the satellite
stops so their backing devices, including ZFS zvols, can be released
during normal shutdown. The hook skips Primary resources and open
devices, rechecks each candidate, and aborts further actions on
ambiguous status, command failure or timeout. It uses no force,
demotion, Kubernetes API calls or additional permissions.

The hook is available only when both Talos and DRBD are enabled. Keep
the value in
`spec.components.linstor.values.talos.gracefulShutdown.enabled` on the
`cozystack.linstor` package. The chart embeds the script in its existing
`cozystack-talos` satellite configuration, which Piraeus merges into the
generated DaemonSets. No custom satellite image is required. Default
renders remain unchanged for Talos, generic Linux and DRBD-less Talos.

This is opt-in because preStop also runs on ordinary satellite restarts.
Unused Secondary replicas disconnect until their replacement satellite
reattaches them, and Piraeus has one DaemonSet per node without a
cluster-wide rollout barrier. Simultaneous restarts can therefore pause
I/O. Enable this only with sequential satellite/node maintenance and
replication checks between nodes. The README covers the 60-second hook
budget, pod versus node shutdown grace periods, rollback behavior and
the unsupported forced-reboot/broken-kubelet paths. Related upstream
discussion:
https://github.com/piraeusdatastore/piraeus-operator/issues/860. The
reported outage from unconditional forced demotion in
https://github.com/piraeusdatastore/piraeus-operator/issues/860#issuecomment-5635881307
is why this hook never demotes a Primary, even when its devices appear
closed.

Validation: 22 Helm tests and 20 behavioral Python tests pass. The same
20 tests pass inside the existing Cozystack satellite image. Two of them
pin the rendered command form: `python3 -c <script> --execute` reaches
`main()` with `["--execute"]` because CPython sets `sys.argv[0]` to
`-c`, and a script-name placeholder before `--execute` is rejected.
Failure-path tests cover nonzero status/down exits, bounded stderr,
partial timeout diagnostics, and stopping before the next resource. Full
default renders match the base commit for all three supported substrate
variants. The rendered script survives strategic merging against Piraeus
2.10.2 and 2.11.0 satellite manifests; the base chart fails the new
feature's regression tests.

Runtime validation on Talos 1.13.6 used the script from commit
`b8c86f2e70d029166939ff29eab8a45ac191e34b` through per-node satellite
configurations: a normal node reboot with drain completed without force
or an external reset, and the node returned with its ZFS pool online and
DRBD replicas UpToDate. That version is installed on all three nodes of
the tested cluster after sequential satellite rollouts and replication
checks. The later diagnostic-only update in
`527570d8079cba7c91b3c345050cab1b73a290a8` is covered by the local and
exact-image tests above but has not been rolled out to that cluster.
This does not validate forced reboot, a failed kubelet, or simultaneous
satellite restarts. GitHub Actions for this fork require maintainer
approval; local checks do not replace that CI.

### Screenshots

Not applicable.

### Downstream repositories

The diff adds an opt-in system-package value and its
implementation/tests. It does not change app schemas, platform variants,
node prerequisites, shared tooling contracts or an existing documented
workaround.

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

### Release note

```release-note
fix(linstor): add an opt-in Talos satellite preStop hook that releases unused Secondary DRBD resources without force. Enable it with talos.gracefulShutdown.enabled for sequential node maintenance; existing installations keep their current behavior by default.
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added optional graceful shutdown support for LINSTOR satellites
running on Talos.
* When enabled, unused Secondary DRBD resources are released before
termination, with safeguards for active resources and bounded shutdown
timing.
* Added a 90-second termination grace period and configuration
validation for the new setting.
* Added documentation covering configuration, operational requirements,
and shutdown behavior.

* **Tests**
* Added Helm and Python coverage for shutdown behavior, validation,
timeouts, and failure scenarios.
  * The test target now runs both Helm and Python unit tests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-18 13:43:57 +04:00
Aleksei Sviridkin a4bdddf030 feat(kubevirt): expose migration configuration through platform values (#4254)
## What this PR does

Cluster-wide migration settings could only be applied by patching the
KubeVirt CR: the kubevirt chart did not render `migrations`, and the
platform bundle did not forward it. Add the optional
`kubevirt.migrations` platform value and pass it through the generated
Package to `spec.configuration.migrations`.

For example, set the following under `spec.components.platform.values`
on the `cozystack.cozystack-platform` Package:

```yaml
kubevirt:
  migrations:
    bandwidthPerMigration: 625M
    parallelMigrationsPerCluster: 2
    parallelOutboundMigrationsPerNode: 1
```

`bandwidthPerMigration` is a quantity in bytes per second. Empty or null
settings leave the field unset; existing KubeVirt defaults are
unchanged. The map is forwarded intact, including explicit `false` and
`0` values. `make update` restores the new parameterization
independently after a hand-merge.

Validation:

- All 207 Helm tests in `packages/system/kubevirt` and
`packages/core/platform` passed (42 suites).
- Before the template changes, the new limit and false/zero cases failed
at both forwarding stages (four expected regression failures).
- The GNU sed / make update-idempotency suite passed, including
restoration of a missing migrations block, repeated runs, and rejection
of a missing insertion anchor.
- Standalone KubeVirt rendering and `helm lint` passed. Neither modified
chart defines a `make generate` target. Full platform rendering requires
its live source repository lookup; the platform template behavior was
checked through its existing Helm unit-test fixtures.

This is separate from #3989, which exposes `vmStateStorageClass` in the
same chart and bundle files.


### Screenshots

Not applicable; no UI changes.

### Downstream repositories

- [ ] No downstream repository is affected by this change
- [x] [cozystack/website](https://github.com/cozystack/website) -
follow-up: [#697](https://github.com/cozystack/website/pull/697)
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

The website follow-up
[#697](https://github.com/cozystack/website/pull/697) documents this new
platform value in the unreleased `next` reference and should be merged
after this implementation. The merged website PR
[#678](https://github.com/cozystack/website/pull/678) documents
`kubevirt.disabledFeatureGates` only. The other listed repositories do
not need a coordinated change: this adds an optional platform value
without changing app schemas, required installer values, CRDs, package
layout, source kinds, shared tooling contracts, or node prerequisites.

### Release note

```release-note
feat(kubevirt): Configure cluster-wide KubeVirt migration settings through the platform Package value kubevirt.migrations. Empty settings preserve existing defaults.
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added optional KubeVirt migration configuration.
* Supports migration bandwidth, concurrency limits, auto-convergence,
and completion timeout settings.
  * Migration bandwidth can be specified in bytes per second.
  * Empty or unset migration settings preserve KubeVirt defaults.
* **Bug Fixes**
* Migration values now preserve valid `false` and `0` settings when
provided.
* **Tests**
* Added coverage for default, empty, null, configured, and
repeated-update scenarios.
* Added validation to prevent incomplete or duplicated migration
configuration.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-18 13:43:52 +04:00
Aleksei Sviridkin 325a4e3cee fix(opensearch-operator): let a permanently yellow cluster restart (#4317)
## What this PR does

A single-replica OpenSearch release stops rolling once any index on it
asks for a replica. The copy has nowhere to go on one data node, so the
cluster stays yellow for good. The operator's restart gate at v2.8.0
opens on green, or on yellow only when `.opensearch-observability` is
the yellow index, and at OpenSearch 2.11.1 that index is
`auto_expand_replicas: 0-2`, green on one node. So the gate never opens.

Every release gets such an index by itself. The operator sets
`plugins.security.audit.type: internal_opensearch`, and the first
audited event creates a daily `security-auditlog-*` index with one
replica. After that nothing that needs a restart is rolled out, a
version upgrade or the move of the HTTP listener onto the cert-manager
chain included.

This is what timed out the `opensearch` e2e suite on some runs. Both
failed runs I read show the same thing in the OpenSearch log: the
bootstrap node leaves, `Connection reset` on the transport layer, and
`[security-auditlog-<date>] creating index ... shards [1]/[1]` within 40
ms. The security plugin audits that exception
(`SecuritySSLNettyTransport` hands it to `AuditLogSslExceptionHandler`),
so the reset is what creates the index. Then the operator logs `waiting
for health to be green` every ten seconds until the assert gives up. Not
sure why the green runs had no audit index before the roll, a green run
keeps no snapshot.

The fix carries opensearch-project/opensearch-k8s-operator#1569 back to
v2.8.0 as a second patch on the in-tree operator image, with the
upstream tests. The new gate lets a yellow cluster through once
allocation is enabled, every data node has joined and no shard is
moving, and `PreparePodForDelete` refuses to restart a node that holds
the only active copy of a shard meant to have others. A lone data node
is exempt. I left out the third commit of that PR (tiered data roles in
`HasDataRole`), the chart renders only the `data` role.

A bump would not help: the only operator build after v2.8.0 is
`3.0.0-alpha` from January, and its gate holds this case the same way.
The patch goes away on the first release that includes #1569.

The suite now creates an index with an unassignable replica before it
moves the listener, so the roll always runs against a yellow cluster.
The catch block also reads cluster health and the shard table from the
node, because the operator logs that health held a restart back and
never what the health was.

The image digest in `values.yaml` stays as it is.

Closes #4259

### Screenshots

Not a UI change.

### Downstream repositories

Walked the trigger map against the diff: an e2e suite, the operator
image's Dockerfile header and patch directory, one comment in the
operator package's `values.yaml`. No values key, schema, make target or
documented workaround changes.

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

### Release note

```release-note
fix(opensearch-operator): a single-replica OpenSearch release no longer gets stuck ahead of every rolling restart once an index with a replica, such as the security audit log or any index created with default settings, leaves the cluster permanently yellow
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- Improved rolling restart handling for clusters that remain yellow,
allowing restarts to proceed safely once shard activity has settled.
- Prevented restart operations from becoming blocked by temporary
shard-allocation settings.
- Added safeguards to ensure data is not disrupted while replicas are
relocating or initializing.

- **Diagnostics**
- Added automatic cluster health and shard diagnostics when
restart-related test operations time out or encounter issues.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-18 13:43:47 +04:00
Aleksei Sviridkin 74c87247ed fix(linstor): reconnect only the stalled peer in the satellite plunger (#4184)
## What

`drbd_stall_act` in
`packages/system/linstor/hack/plunger/plunger-satellite.sh` disconnects
a single peer with `drbdadm disconnect res:peer` and then calls `drbdadm
connect res`, which walks every connection of the resource. Each peer
that is still connected makes `drbdsetup connect` fail with `Device has
a net-config (use disconnect first)` (exit code 10), the helper only
logs `WARN: action failed for res:peer`, and the peer that was just
disconnected is never reconnected.

## Observed

cozystack 1.4.6 sandbox, LINSTOR 1.33.2 / DRBD 9.3.2 on Talos 1.13.6,
resync of a 20 GiB volume while the Primary keeps writing:

```
STALL detected: res=pvc-9d99d1b9-1efe-4340-bbb3-13421c6bc7f2 sync_peer=sb-cs0203 percent_in_sync=90.4 -> disconnect/connect
pvc-9d99d1b9-1efe-4340-bbb3-13421c6bc7f2: Failure: (125) Device has a net-config (use disconnect first)
Command 'drbdsetup connect pvc-9d99d1b9-1efe-4340-bbb3-13421c6bc7f2 5' terminated with exit code 10
WARN: action failed for pvc-9d99d1b9-1efe-4340-bbb3-13421c6bc7f2:sb-cs0203
```

## Fix

Reconnect the same `res:peer` pair that was disconnected. Verified on
the sandbox by patching the live `linstor-plunger` ConfigMap: the peer
reconnects and the resync completes.

## Note

Not part of this change: the stall detector compares `percent-in-sync`,
which `drbdsetup status --json` reports with one decimal, so a slow
resync (for example one throttled down to `c-min-rate` by application
I/O) can look stalled for `STALL_ITERS` iterations and trigger a
reconnect every couple of minutes. Happy to follow up on that separately
if you think it is worth it.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- Improved reconnection behavior by targeting the explicitly selected
resource-peer endpoint instead of reconnecting only at the resource
level.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-18 13:38:19 +04:00
Yan Bondarenko 6e90447091 fix(kubeovn): retry migration setup until target pod exists
Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com>
2026-09-18 13:24:49 +04:00