Commit Graph
5128 Commits
Author SHA1 Message Date
Aleksei Sviridkin 4f8c200c5b test(securitygroup): cover egress toApp/toSG peer projection round-trip
The ingress fromApp/fromSG projection is exercised end to end, but the
symmetric egress toApp/toSG path — toApp to a lineage-label
endpointSelector and toSG to the referenced group's membership-label
endpointSelector — had no test. A wrong label key there would silently
point an egress allow at the wrong endpoints. Pin the projection and the
reverse reconstruction with a Create+Get round-trip, mirroring the
existing ingress coverage.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-06-30 01:20:25 +03:00
Aleksei Sviridkin a86817915a fix(securitygroup): report async deletion for finalizer-guarded policies
The backing CiliumNetworkPolicy carries the membership finalizer, so
deleting a SecurityGroup is virtually always asynchronous: the policy
gets a deletionTimestamp and survives until the securitygroup-controller
strips the membership labels off member pods and removes the finalizer.
REST.Delete nonetheless returned (nil, true), reporting the object as
instantly deleted, so the endpoint emitted a Success status claiming
completion instead of surfacing the terminating object — a
rest.GracefulDeleter contract violation for any client that distinguishes
the two to detect pending teardown.

After the delete, read the object's state back through the direct
(uncached) client and report deleted=true only when it is actually gone;
while a finalizer holds it, return deleted=false with the terminating
object. Using the direct client avoids a stale cache-backed read
misreporting an async delete as instant, and a dry-run (which leaves the
object in place) is resolved from the object's authoritative finalizers.
Implement rest.MayReturnFullObjectDeleter so the DELETE endpoint is
advertised and serialized as returning the SecurityGroup object, matching
the generic registry.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-06-30 01:20:14 +03:00
Aleksei Sviridkin cc4fb40da7 fix(securitygroup): project rules-less SecurityGroup to a valid policy
A SecurityGroup with attachments but no ingress or egress rules exists
only to stamp its membership label on the attached applications' pods, so
other SecurityGroups can reference it as a peer. Its backing
CiliumNetworkPolicy carried only an endpointSelector, which the
cilium.io/v2 CRD rejects synchronously: the spec anyOf requires at least
one of ingress/ingressDeny/egress/egressDeny, so a selector-only policy
fails with "spec.ingress: Required value". A membership-only group
could therefore never be created.

Always serialize the backing policy's ingress section, emitting an empty
list when the group has no ingress rules. An empty list is the CRD's
documented no-op ("if omitted or empty, this rule does not apply at
ingress") and enableDefaultDeny defaults to false for a direction with
no rules, so the policy is schema-valid yet datapath-inert: it neither
allows nor denies, matching the additive, non-isolating semantics of an
empty rule list. The projection guarantees the slice is non-nil so it
never marshals to null, which the CRD (no nullable fields) would also
reject. The empty section collapses back to nil on read, so a
membership-only group round-trips with no ingress or egress.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-06-30 00:36:09 +03:00
Алексей Артамонов 2587458bd4 fix(qdrant)!: reclaim data PVC by name prefix, not volumeClaimTemplate label
The previous approach routed a release-unique label through
qdrant.persistence.additionalLabels, which the upstream chart renders into
the StatefulSet's volumeClaimTemplates[].metadata.labels. That field is
immutable on an existing StatefulSet, so for any already-deployed Qdrant the
API server would reject the upgrade and the <release>-system HelmRelease
(retries: -1) would retry-fail forever — permanently wedging reconciliation.
Label changes on a volumeClaimTemplate also never apply to PVCs that already
exist, so the cleanup hook could not reclaim those instances anyway.

Switch the post-delete hook to select PVCs by the deterministic StatefulSet
naming prefix (qdrant-storage-<release>-) instead. PVC names derive purely
from the release name, so this needs no StatefulSet change (upgrade-safe) and
also reclaims PVCs from instances created before the hook existed.

- qdrant.yaml: drop the additionalLabels passthrough entirely.
- hooks/cleanup-pvc.yaml: delete by name prefix via kubectl get -o name | grep.
- tests: assert the name-prefix command instead of the label passthrough.
- e2e: assert qdrant-storage-<name>-0 is gone after delete (async-waited).

BREAKING CHANGE: deleting a Qdrant app now permanently removes its data PVC
and the vector data it holds.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Алексей Артамонов <alexeyartamonov1987@gmail.com>
2026-06-29 23:10:57 +03:00
Алексей Артамонов 6a43adbd09 fix(bucket): drop no-op label-based order/challenge delete
Per maintainer review: the label selector
cert-manager.io/certificate-name=<release>-ui-tls matches nothing —
Challenge objects carry no labels, and on Orders certificate-name is an
annotation (not a label). Orders are already reaped by the Certificate
owner cascade and Challenges by the host-scoped .spec.dnsName sweep, so
the line deleted nothing and its comment misstated cert-manager labeling.

Remove the dead delete, replace the inaccurate comment, and drop the
test assertion that pinned it; add negative assertions so a label-based
order/challenge delete cannot be reintroduced.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Алексей Артамонов <alexeyartamonov1987@gmail.com>
2026-06-29 23:10:24 +03:00
Aleksei Sviridkin fdbb9cc55a test(e2e): make etcd.bats teardown run and settle under cozytest
The e2e runner (hack/cozytest.sh) is not real bats: it never invokes
setup()/teardown() and never opens fd 3. So etcd.bats's setup() never ran
and dump_diagnostics wrote to an unopened fd 3, silently aborting on
failure — and etcd leaked into the shared tenant-test namespace.

Move teardown into etcd_drain() and run it inline at the end of the last
scenario (under set -e, so a teardown that does not settle fails the
suite) and as cozy_cleanup (the best-effort hook cozytest runs at suite
exit and on the first failing test). The first scenario also drains up
front so a dirty namespace cannot taint it.

etcd_drain reclaims what a plain CR/HR delete leaves behind:
- the StatefulSet's retained data-etcd-* PVCs (not owned by the Etcd CR);
  a later install otherwise reuses them, boots the old member identity,
  never forms a fresh quorum, and the chart's helm --wait fails; and
- the cluster-scoped Kamaji DataStore, whose leaked finalizer wedges the
  HR uninstall — cleared each pass until the Etcd CR, HR, DataStore, and
  PVCs are all gone, bounded by a deadline that fails the drain on
  timeout. Remnants are probed by name so a transient API error is never
  mistaken for a successful delete.

Drop --ignore-not-found from the teardown waits (not a valid wait flag);
the drain polls for existence instead. Diagnostics now go to stdout,
which cozytest captures and prints on failure.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-06-29 16:01:48 +03:00
Aleksei Sviridkin 4404c5c5eb fix(etcd): pin tenant etcd to v3.5.31 to restore consistent watches (#3122)
## What this PR does

Tenant Kubernetes control planes get their etcd from the `extra/etcd`
chart, which creates an `EtcdCluster` (`etcd.aenix.io/v1alpha1`)
reconciled by etcd-operator v0.4.5. That operator hardcodes the etcd
image to `quay.io/coreos/etcd:v3.5.12`, and the v1alpha1 CRD exposes no
version field, so every tenant etcd runs 3.5.12.

kube-apiserver only enables the `RequestWatchProgress` storage feature
on etcd >= 3.5.13 (or >= 3.4.31). Kubernetes >= 1.31 enables
`ConsistentListFromCache` — locked-to-true in 1.35 — which depends on
it. On etcd 3.5.12 every consistent watch therefore fails inside the
stream with HTTP 500 (`the required storage feature RequestWatchProgress
is disabled`), which breaks watch-based clients of aggregated APIs in
modern (k8s 1.35) tenants.

This pins the etcd container image to a >= 3.5.13 patch release
(v3.5.31) by setting `image` on the `etcd` container in the
EtcdCluster's `spec.podTemplate.spec`. etcd-operator v0.4.5
strategically merges `podTemplate.spec` over its generated pod spec
(matched by container name), so the override takes effect with no
operator change — the same mechanism the chart already uses for the
metrics port, probes, and resources. It restores consistent watches
without waiting on the etcd-operator v1alpha2 migration (#2859), which
is the longer-term fix.

A helm-unittest suite asserts the etcd container carries the pinned
image and is no longer left on the operator default v3.5.12.

Addresses #3080.

### Upgrade impact

On merge, etcd-operator reconciles every existing tenant `EtcdCluster`
and rolls its StatefulSet from 3.5.12 to 3.5.31. This is a safe in-minor
rolling patch upgrade (no etcd storage-format change, quorum preserved
by the operator's rolling strategy), so expect a brief, sequential etcd
pod restart per tenant and no data migration.

### Release note

```release-note
fix(etcd): pin tenant etcd to v3.5.31 so kube-apiserver can enable RequestWatchProgress (consistent watches) on Kubernetes 1.35 tenants
```


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

* **Bug Fixes**
* Pinned the bundled etcd image to `quay.io/coreos/etcd:v3.5.31` to
improve compatibility with Kubernetes watch-related storage behavior.
* Prevented deployments from reverting to the older default etcd image.
* **Tests**
* Added a new test suite to verify the template uses the expected pinned
etcd image and blocks regressions.
* **Chores**
* Updated dependency automation to recognize and manage the pinned etcd
image version.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-29 15:46:12 +03:00
Aleksei Sviridkin f03d2a510f docs(securitygroup): correct empty-list semantics — an empty list does not deny
The Ingress/Egress field docs claimed "an empty list denies all ingress/egress",
which is a false security guarantee. A SecurityGroup projects onto a
CiliumNetworkPolicy that only ADDS allow rules, and Cilium policies are additive
over the tenant's blanket-allow baseline, so an empty list does not isolate a
pod — effective connectivity stays open. Deny / default-deny enforcement depends
on the default-deny baseline, which is separate future work.

Rewrite the API field docs to the accurate semantics, regenerate the OpenAPI so
the served schema description matches, and fix the one DESIGN.md sentence that
still asserted "denies all traffic" (it already contradicted its own safety
section, which correctly states the API cannot deny anything today).

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-06-29 15:40:23 +03:00
Aleksei Sviridkin 881591d34d fix(securitygroup): log swallowed errors in the membership reconciler
The reconciler intentionally treats a malformed attachments annotation and a
failed pod-mapping List as empty / no-op to stay resilient, but it swallowed
both errors silently. That hides real defects: a bad annotation writer, or a
transient API error that drops a pod update until the next periodic resync with
no trace. Log both while preserving the resilient return behavior.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-06-29 15:40:23 +03:00
Aleksei Sviridkin 11ed2a960f fix(securitygroup): forward watch errors and field-filter deletions in the API
Two watch-proxy defects in the SecurityGroup REST storage:

- watch.Error events from the backing CiliumNetworkPolicy watch (e.g. a 410 Gone
  for an expired resourceVersion) carry a *metav1.Status, not a
  CiliumNetworkPolicy. The CNP type assertion dropped them, so the client saw a
  cleanly closed stream and never performed the required relist. Forward error
  events verbatim before the type assertion.

- The DELETED-event bypass the label selector needs (a policy whose labels mutate
  out of the selector is deleted carrying non-matching labels, so dropping it
  would strand cached clients) was wrongly applied to the field selector too.
  Name and namespace are immutable, so a field-selected watch (metadata.name=sg-a)
  received other objects' deletions and could corrupt filtered client caches.
  Apply the field filter to every event type.

Both paths are covered by new tests driving a fake backing watch.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-06-29 15:24:24 +03:00
Aleksei Sviridkin 09f4d8bb82 build: build the securitygroup-controller image in the top-level build target
The securitygroup-controller package has a working `make image` target, but it
was never wired into the top-level Makefile `build:` list, so CI never builds or
pushes the controller image. The shipped values.yaml therefore keeps its
`securitygroup-controller:v0.0.0` placeholder and the controller Deployment
lands in ImagePullBackOff, since that image tag does not exist in the registry.
Add the missing build line next to the other first-party controllers so CI
builds, pushes, and digest-pins the image like every other controller.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-06-29 15:04:46 +03:00
scooby87 4db565282b fix(postgres): garbage-collect init-job after completion (#3055) (#3069)
## Problem

After deleting a `Postgres` app CR, the chart-managed init Job
`<release>-init-job` and its completed Pod remain in the tenant
namespace indefinitely (reported in #3055).

The init Job is a Helm hook (`post-install,post-upgrade`) whose only
delete policy is `before-hook-creation`. That policy deletes the
previous hook only right before a new one is created (next upgrade) — it
never fires on `helm uninstall`. With no `hook-succeeded` and no TTL,
the completed Job and Pod leak.

## Root cause

`packages/apps/postgres/templates/init-job.yaml` — the Job has no
garbage-collection mechanism for its completed state.

## Fix

Add `ttlSecondsAfterFinished: 120` to the Job spec. The native
Kubernetes TTL controller then GCs the Job and its Pod ~2 minutes after
completion, regardless of the Helm hook lifecycle.

The init Job is a one-shot, idempotent reconciler of
users/databases/roles (`init-script.yaml`) — once it has run it is not
needed until the next upgrade (when Helm recreates it), so GC-ing the
finished Job is safe.

This approach deliberately **does not** touch the hook-delete-policy /
hook-timeout semantics, which the `bootstrap.recovery` path depends on
(see the header comment in `init-job.yaml`). Under `bootstrap.enabled`
the Job is skipped entirely, so the change is inert there.

## Tests

Added a `helm-unittest` suite
`packages/apps/postgres/tests/init_job_cleanup_test.yaml`:
- asserts the init Job renders `spec.ttlSecondsAfterFinished: 120`
- asserts the init Job is skipped under `bootstrap.enabled: true`

Verified locally with `helm template` (TTL present on default render;
Job absent under bootstrap recovery).

## Test plan
- [x] `helm template` renders `ttlSecondsAfterFinished: 120` on the init
Job
- [ ] `helm unittest packages/apps/postgres` green in CI
- [ ] e2e `test-apps-postgres` green

Fixes #3055

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

* **New Features**
* Postgres bootstrap init Jobs now automatically clean up after
completion using a post-finish TTL.

* **Tests**
* Added a new integration test suite to verify the init Job renders with
the expected cleanup TTL, and that the init Job is skipped when
bootstrap is enabled.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-29 14:34:51 +03:00
scooby87 49555e562b fix(openbao): garbage-collect the data PVC on delete (#3058) (#3073)
## What this PR does

After deleting an `OpenBAO` app CR, the StatefulSet data PVC
(`data-<release>-0`)
was left orphaned in the tenant namespace, consuming storage
permanently. The
OpenBAO app renders a Flux `HelmRelease` over the vendored upstream
OpenBAO chart,
and StatefulSet-templated PVCs are never garbage-collected on
StatefulSet deletion
by default — the chart set no `persistentVolumeClaimRetentionPolicy`.

This passes a retention policy through the HelmRelease values. The
upstream chart
renders it onto the StatefulSet from
`server.persistentVolumeClaimRetentionPolicy`
(verified in the vendored `server-statefulset.yaml`) — **not** from
`server.dataStorage.*`:

```yaml
server:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Delete
    whenScaled: Retain
```

A `test` target is added to the package Makefile so the new
helm-unittest suite
(asserting the passthrough) actually runs in CI —
`hack/helm-unit-tests.sh` only
runs packages that define a `test` target.

> [!WARNING]
> **Deleting an OpenBAO app now permanently removes its data PVC and the
data it holds.**
> This is intentional and fixes the storage leak. `whenScaled: Retain`
is kept on
> purpose so scaling an HA/raft cluster down does **not** destroy a
member's data —
> only full deletion of the release reclaims storage. Back up before
deleting.

Fixes #3058.

### Release note

```release-note
fix(openbao): set persistentVolumeClaimRetentionPolicy so the data PVC is removed when an OpenBAO app is deleted, instead of being orphaned. WARNING: deleting an OpenBAO app now permanently removes its data PVC and the data it holds (scale-down still retains data).
```


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

* **New Features**
* Added configurable PVC retention behavior for OpenBao: storage is
deleted when the release is removed and retained when scaling down.
* **Tests**
  * Added a Helm unit testing workflow to run chart/unit tests.
* Introduced assertions to confirm PVC retention settings are correctly
passed through and rendered as expected.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-29 14:32:30 +03:00
scooby87 959a9b1fdb fix(kubernetes): clear kamaji datastore-secret finalizer to unblock namespace deletion (#3062) (#3078)
## What this PR does

After deleting a `Kubernetes` app CR / tenant, the secret
`<release>-datastore-config` kept the finalizer
`finalizer.kamaji.clastix.io/datastore-secret`, which was never removed
—
**blocking deletion of the entire namespace** (observed stuck in
`Terminating`
for 115 minutes). Kamaji adds this finalizer to the datastore connection
Secret
and normally strips it as part of TenantControlPlane (TCP) deletion; if
the TCP
is force-deleted or Kamaji does not reconcile in time, the finalizer
hangs and
the Secret can never be garbage-collected, wedging the namespace.

This extends the existing `pre-delete` cleanup hook
(`packages/apps/kubernetes/templates/delete.yaml`):

- **Step 4** now deletes the TenantControlPlane with `--wait=true
--timeout=120s`
so Kamaji's own finalizer-cleanup chain has a chance to run before we
fall
  through.
- **Step 4b (backstop)** patches `metadata.finalizers: null` on
`<release>-datastore-config` only if it still exists. By this point the
TCP is
gone, the Secret is owner-ref'd to the deleted TCP and only awaiting GC,
so
clearing the finalizer merely lets GC reap it — it cannot strip a
finalizer
while a live TCP still needs the datastore. The Secret holds only
connection
config, not tenant data (which lives in the external etcd datastore), so
no
  data is lost.
- **RBAC** adds a `secrets` rule scoped via `resourceNames` to exactly
  `<release>-datastore-config`, verbs `get`/`patch` only.

The Job is hardened: non-root (uid 65534), read-only root fs, all caps
dropped,
`seccompProfile: RuntimeDefault`, writable `/tmp` for kubectl cache.
Failures at
both steps are logged (the Step 4b warning explicitly mentions the
namespace may
stay Terminating) but the hook still completes so it cannot itself block
teardown.

This is the most impactful of the create→delete leak sweep findings:
without it,
tenant teardown hangs and the create→delete cycle requires manual
intervention.

Fixes #3062.

### Release note

```release-note
fix(kubernetes): clear the leftover Kamaji `finalizer.kamaji.clastix.io/datastore-secret` finalizer on the datastore-config Secret during tenant teardown, so deleting a Kubernetes app / tenant no longer leaves the namespace stuck in Terminating.
```


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

* **New Features**
* Extended the pre-delete cleanup workflow to more reliably remove
`TenantControlPlane` and handle slow deletions with a bounded wait.
* **Security**
* Hardened the cleanup job by running as non-root with restricted
security settings, dropped capabilities, and a safe writable temp
location.
* **Bug Fixes**
* Added a conditional backstop to remove leftover `finalizers` from the
relevant datastore Secret so namespace termination isn’t blocked.
* **Tests**
* Added a YAML test suite to validate the wait-and-patch behavior and
ensure the patch runs only after the `TenantControlPlane` is gone.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-29 14:25:49 +03:00
scooby87 080ceac9c0 fix(gateway): clean up orphaned ACME account secret on tenant module delete (#3089) (#3093)
## What this PR does

When the `gateway` tenant module is disabled (its HelmRelease
uninstalled), cert-manager's ACME account private-key secret
`cozystack-acme-account` was left orphaned in the tenant namespace
(label `app.kubernetes.io/managed-by: cert-manager`, no
`ownerReferences`).

This adds a `post-delete` cleanup hook
(`packages/extra/gateway/templates/hooks/cleanup-acme.yaml`) that
deletes the secret by name. Verified on a live cluster that
`cozystack-acme-account` is referenced by exactly one Issuer — the
gateway module's own `cozystack-gateway` Issuer — so it is
gateway-specific and safe to remove on gateway teardown (it is not
shared with other modules, which use the cluster-wide `letsencrypt-prod`
issuer).

The cleanup Job is hardened: non-root (uid 65534), read-only root fs,
all capabilities dropped, `seccompProfile: RuntimeDefault`, resource
requests/limits; RBAC scoped via `resourceNames` to exactly
`cozystack-acme-account` (get/delete). A failure is logged but never
blocks teardown.

No user data is affected — only cert-manager's ACME account key
(re-registered on next enable).

Fixes #3089.

### Release note

```release-note
fix(gateway): clean up the orphaned cert-manager ACME account secret (cozystack-acme-account) left behind when a tenant's gateway module is disabled.
```


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

## Summary by CodeRabbit

* **New Features**
* Added automatic cleanup after gateway removal to delete the stored
ACME account secret in the release namespace.
* Included a dedicated, restricted cleanup job with the required
permissions to run safely during uninstall.

* **Bug Fixes**
  * Made the gateway test target always runnable.
* Cleanup now ignores missing secrets and warns on failure instead of
stopping uninstall flows.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-29 14:21:42 +03:00
Aleksei Sviridkin 511f2a3c1c test(e2e): smoke-test the ServiceExposure controller
Add a deterministic e2e for the externalIPs backend: it creates an
ExposureClass, a Service with pinned externalIPs, and a ServiceExposure,
then asserts the controller resolves the class, renders no MetalLB/Cilium
pool, reports the assigned IPs, and cleans up via its finalizer on delete.

The externalIPs path is chosen so the test creates no backend pool — it
cannot collide with the cluster's existing LoadBalancer address pools or
depend on real L2 reachability. The metallb and cilium rendering paths are
covered by the Go unit tests.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-06-29 13:41:25 +03:00
Myasnikov DaniilandClaude Opus 4.8 9f1819cdde ci(renovate): force chore(deps) on direct Go modules via user packageRule
The top-level semanticCommitType=chore did not actually take effect for
direct dependencies. config:recommended extends
:semanticPrefixFixDepsChoreOthers, whose packageRule re-sets
semanticCommitType=fix for depTypes [dependencies, require]. Renovate
packageRules override top-level config, and the gomod manager assigns
depType "require" to direct deps — so direct-dep PRs (the dominant type)
still resolved to fix(deps) and were auto-labeled kind/bug, the exact
pollution this PR set out to remove.

Add a user packageRule (evaluated last, so it wins over the preset rule)
that forces semanticCommitType=chore / scope=deps for the gomod manager.
The top-level keys are now redundant but kept for readability.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-29 15:18:26 +05:00
Myasnikov DaniilandClaude Opus 4.8 d098423f8a ci(renovate): force chore(deps) commits and add area/dependencies label
Renovate's :semanticCommits preset picks fix(deps) for stable (>=1.0)
production modules and chore(deps) for 0.x modules, so routine bumps
landed inconsistently — fix(deps) bumps were auto-labeled kind/bug,
polluting the bug list. Pin semanticCommitType to chore (and the scope
to deps) so every dependency PR is chore(deps): … and maps to
kind/cleanup.

The PR auto-labeler had no deps -> area/* mapping, so every Renovate PR
fell through to area/uncategorized. Add an area/dependencies label and
map the deps scope to it.

Also disable Renovate for kubevirt.io/csi-driver: it is pinned to an
untagged pseudo-version with no release tags, producing a permanent
no-result lookup failure on the Dependency Dashboard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-29 15:18:26 +05:00
myasnikovdaniil 90aa850c95 ci(labeler): map cozystack-ui and console scopes to area/dashboard (#3117)
## What this PR does

The cozystack-ui SPA was vendored into the monorepo in #2963 and now
lives under `packages/system/dashboard`. PRs touching the UI are likely
to use `cozystack-ui` or `console` as their Conventional Commits scope,
neither of which the PR auto-labeler recognizes today — so they fall
through to `area/uncategorized`.

This maps both scopes to the existing `area/dashboard` (already
described as "dashboard / UI"). No new label is introduced;
`.github/labels.yml` is unchanged.

### Release note

```release-note
NONE
```


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

* **Chores**
* Expanded automatic PR labeling to add the `area/dashboard` label when
a PR title scope is `cozystack-ui` or `console`.
* Existing label rules and parsing behavior remain unchanged for all
other scopes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-29 15:16:38 +05:00
Aleksei Sviridkin 6896a2a4db feat(kubernetes): expose tenant apiserver authentication in app values (#3123)
## What this PR does

The managed `Kubernetes` app (Kamaji-backed tenant control planes)
exposed no way to configure the tenant `kube-apiserver` authentication
through the application values — there was no knob for OIDC, the
structured `AuthenticationConfiguration`, extra apiserver flags, or
extra volumes. The only workaround was patching the underlying
`KamajiControlPlane` out-of-band, which is invisible to GitOps and lost
when the cluster is recreated from values.

This adds three opt-in fields under `controlPlane.apiServer`, all empty
by default so existing clusters render unchanged:

- `extraArgs` → `KamajiControlPlane.spec.apiServer.extraArgs`
- `extraVolumeMounts` → `spec.apiServer.extraVolumeMounts`
- `extraVolumes` → appended to `spec.deployment.extraVolumes`

This unblocks OIDC SSO via the `--oidc-*` flags and, more importantly,
structured authentication: `--authentication-config` mounted from a
ConfigMap/Secret plus `--requestheader-uid-headers`, which is the only
way to map a user UID for aggregated/extension apiservers.

Because the control-plane Deployment runs on the management cluster, the
passthrough is validated to keep tenant and management isolated:
`extraVolumes` accept only `configMap`/`secret` sources (host-reaching
sources such as `hostPath`/`csi` and token projection via `projected`
are rejected); each volume needs a unique, non-empty name and exactly
one source; the reserved `talos-ca`/`talos-tls-cert` names are rejected;
and each `extraVolumeMount` must reference a user-declared
`extraVolume`, so the chart-managed talos secret volumes cannot be
mounted. Secret/ConfigMap references resolve in the tenant's own
management-side namespace and are intentionally allowed — this stays
within the tenant's existing security domain.

Generated artifacts (`values.schema.json`, README, Go types + deepcopy,
ApplicationDefinition) are regenerated from the values annotations. Helm
unit tests cover the default no-op render, each field's passthrough, and
every rejection path.

Addresses #3074.

### Screenshots

N/A — no UI changes.

### Release note

```release-note
feat(kubernetes): expose tenant apiserver authentication via controlPlane.apiServer.extraArgs / extraVolumes / extraVolumeMounts, enabling OIDC and structured AuthenticationConfiguration on tenant clusters
```


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

## Summary by CodeRabbit

* **New Features**
* Added support for customizing the Kubernetes API server with extra
command-line arguments, additional volume mounts, and extra volumes.
* Updated chart values and schema so these options can be configured
directly.

* **Bug Fixes**
* Added validation to prevent invalid or conflicting volume names and
disallowed volume sources.
* Ensured mounts only reference declared extra volumes and reserved
control-plane names can’t be overridden.

* **Tests**
* Added coverage for default behavior, valid customization paths, and
invalid configuration scenarios.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-29 13:13:54 +03:00
myasnikovdaniil 80e3fa9cbe fix(dashboard): render error and unknown-type states instead of an infinite spinner (#3098)
## What this PR does

Two console pages ordered their loading guard ahead of the terminal
state, making the latter unreachable. Both are pre-existing upstream
behaviors carried in by the cozystack-ui vendoring (#2963) and flagged
as non-blocking findings in that PR's review.

- **`ApplicationDetailPage`** checked `isLoading || !instance || !ad`
before `if (error)`. On a failed GET (403/404/5xx) `isLoading` is false
and `instance` stays undefined, so the page rendered the loading spinner
forever and never reached the "not found" branch. The error branch now
runs first.
- **`ApplicationListPage`**'s `defsLoading || (!ad && plural)` guard
stayed true for any unrecognized `plural` once definitions had loaded,
so the "Unknown application type." branch was unreachable. The guard now
only spins while definitions are loading.

## Verification

- Added co-located regression tests for the failed-GET (detail) and
unknown-plural (list) paths.
- `pnpm typecheck` clean; full console suite green (`pnpm test`: 317
passed, +4 new).

```release-note
fix(dashboard): the console now shows a not-found message on a failed application GET and an unknown-type message for an unrecognized resource, instead of spinning forever
```


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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved loading and error handling on application pages so users see
the correct message at the right time.
* “Unknown application type” now appears only after application
definitions finish loading, instead of being masked by a loading state.
* Application detail pages now show a “not found” message when the
requested app can’t be loaded, rather than continuing to display a
spinner.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-29 14:02:49 +05:00
Myasnikov DaniilandClaude Opus 4.8 df221f98d4 ci(labeler): map cozystack-ui and console scopes to area/dashboard
The cozystack-ui SPA was vendored into the monorepo (#2963) and now lives under packages/system/dashboard. Map its likely PR title scopes to the existing area/dashboard so UI PRs no longer fall through to area/uncategorized.

Assisted-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-29 14:02:29 +05:00
myasnikovdaniil 61a574393a build(dashboard): pin console image builder to pnpm 11.1.2 and repoint vendor doc link (#3099)
## What this PR does

Two post-vendor cleanups from the #2963 review:

- **Pin the console image builder to pnpm 11.1.2.** The `Containerfile`
ran `corepack prepare pnpm@latest`, so the container build could drift
to a newer pnpm major than the `pnpm@11.1.2` pinned in `package.json`
and used by CI. `--frozen-lockfile` guards the lockfile, but pinning the
builder makes the image build reproducible and consistent with CI.
- **Repoint a stale doc link.** `docs/storage-immutability.md`
referenced the now-vendored `cozystack/cozystack-ui#6`; it now points at
the in-tree console path.

## Verification

- No source change; `pnpm typecheck` + `pnpm test` unaffected.
- Builder pnpm version now matches `package.json`'s `packageManager`
field.

```release-note
NONE
```


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

## Summary by CodeRabbit

* **Documentation**
* Clarified the storage immutability documentation by updating the UI
reference to an in-repo path.

* **Chores**
* Standardized the build environment to use a specific pnpm version,
helping keep installs and builds consistent.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-29 14:02:28 +05:00
myasnikovdaniil aec16212ee refactor(build): parallel matrix image builds on ephemeral runners (#2983)
## What this PR does

Completes #2937. Replaces the single serial `Build` job — one ephemeral
VM running `make build` (~26 images one after another) that wedged on
the shared embedded buildkit under concurrent multi-PR load — with a
parallel, diff-scoped matrix.

**New job graph (regular PR path):**

```
plan         changed files -> hack/build-matrix.sh -> scoped matrix + docs gate
checks       make unit-tests + test-controllers  (gates finalize -> e2e)
build        one package per job, own docker-container buildx,
             uploads a per-package digest patch fragment   [parallel]
build-talos  talos image + nocloud disk (its own leg; e2e always needs the disk)
finalize     download+apply all fragments, then build the installer LAST
             (its flux-push bundles the whole digest-patched tree),
             emit the unified pr.patch
e2e          consumes talos-image + pr.patch unchanged
```

**Why finalize is special:** `packages/core/installer`'s `image` target
runs `flux push artifact --path=packages`, bundling the *entire*
packages tree into the OCI artifact the operator pulls — so it must run
after every other package's digest edit is applied, never as a peer
matrix job. `talos` is likewise pulled out of the matrix (its image and
nocloud disk share heavy `_out/assets` and build once).

**Gating:** `finalize` (and therefore `e2e`, which needs it) depends on
`checks`, so a red unit/controller test skips the expensive install+e2e
path instead of running it for ~1h — the gating the old in-build-job
tests gave, without re-coupling the cheap parallel builds.

**Change-scoping:** `hack/build-matrix.sh` parses the root Makefile
`build:` target (single source of truth) and emits only the packages a
PR touches — most PRs build ~0–1 images — escalating to the full set on
a shared-dependency change (`cozy-lib`, the build macros, `go.mod`, the
build workflows). It fans `seaweedfs` out to `objectstorage-controller`
(cross-package sidecar-tag dependency). 16 bats cases cover the
selector.

**SBOM:** `SBOM=1` wires buildx `--sbom` through `common-envs.mk` (off
by default). It stays off in the matrix until an OCIR
referrers/attestation probe confirms the registry accepts attestations.

**Decommission:** the cache warmer (`build-main.yaml`) moves to the
ephemeral pool, so no build path depends on the dedicated self-hosted
runner. The runner remains referenced only by the e2e `debug` breakpoint
path; fully de-registering it is a separate infra step.

### Validation

- Local: `hack/build-matrix_test.bats` 16/16; `actionlint` clean (custom
runner labels registered in `.github/actionlint.yaml`); `make -n`
confirms `--sbom` is off by default and on with `SBOM=1`.
- CI: this PR edits `pull-requests.yaml`, so `build-matrix.sh` forces a
full matrix — the PR exercises the new pipeline on itself. First run was
green through `plan → 25 parallel builds → build-talos → finalize →
install → 21/22 app tests`; the only red was the pre-existing
`bucket`/BucketAccess flake (install and the other 21 suites passed on
the matrix-built tree), which validates the decomposition end-to-end.

### Follow-ups (tracked on #2937)

- Probe OCIR referrers/attestation support; enable `SBOM=1` in the
matrix if supported.
- Infra: de-register the self-hosted build runner (the e2e `debug`
breakpoint path is its only remaining user).

### Release note

```release-note
NONE
```


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

* **Chores**
* Refined CI to a staged, artifact-based pipeline with gated checks,
per-package parallel builds, and centralized patch assembly before
building final images
* Improved ephemeral runner performance and reliability (runner
selection, longer timeout, build toolchain setup)
* Added build-matrix auto-selection with targeted fan-out rules and
“full rebuild” triggers
  * Updated CI lint configuration to recognize ephemeral runner labels
  * Introduced an optional SBOM toggle for build artifacts
* **Documentation**
* Updated e2e testing docs to better explain Test-Impact Analysis scope
and gating semantics
* **Tests**
  * Added Bats tests validating build-matrix selection and edge cases
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-29 13:44:40 +05:00
Aleksei Sviridkin 7a8d1267f2 fix(seaweedfs): point S3 consumers at the reachable S3 service (#3119)
## What this PR does

The S3 Service is named with the release-independent `seaweedfs.name`
helper (always `seaweedfs-s3`), but several in-chart consumers still
resolved it through `componentName` (`<fullname>-s3`). Since the shipped
release is `seaweedfs-system`, those consumers pointed at
`seaweedfs-system-s3` — a Service that does not exist — so ingress-nginx
returns 503 on the external S3 endpoint and the COSI provisioner cannot
reach S3 in-cluster.

This aligns every in-chart S3 consumer with the actual Service:

- S3 ingress backend and iceberg ingress backend → `seaweedfs-s3` (fixes
the 503 on the default `s3.ingress.enabled: true` path).
- COSI provisioner in-cluster `ENDPOINT` (both the s3-gateway and
filer-S3 branches) → `seaweedfs-s3` on the configured S3 port. These
were also portless (defaulting to 443 against an S3 that listens on
8333), and the filer branch additionally targeted the filer Service,
which serves no S3.

The all-in-one ingress branch is intentionally left on `componentName
"all-in-one"` (a different Service). Changes are captured as a
downstream patch (`s3-service-name-consumers.patch`) wired into `make
update`, and pinned by helm-unittest rendered under the production
release name so the mismatch cannot recur.

### Release note

```release-note
fix(seaweedfs): point the S3 ingress, iceberg ingress, and COSI provisioner endpoint at the actual seaweedfs-s3 service, fixing 503 on the external S3 endpoint
```


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

## Summary by CodeRabbit

* **Bug Fixes**
* Updated S3-related routing and in-cluster endpoints to use the correct
service name and explicit port values.
* Improved ingress behavior for newer Kubernetes versions while keeping
compatibility with older versions.
  * Preserved the existing all-in-one routing path for S3 deployments.

* **Tests**
* Added coverage for S3 service naming, ingress backend routing, and
COSI endpoint resolution to prevent regressions.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-29 11:24:49 +03:00
Myasnikov Daniil bdf520bf74 fix(harbor): preserve jobservice and trivy storageClass on upgrade
The cozy-harbor wrapper never passed storageClass to the jobservice
jobLog PVC, so it was created without a storageClassName and the
default-StorageClass admission stamped the cluster default. Because PVC
storageClassName is immutable, this caused rendered-vs-live drift on
upgrade. Preserve an existing PVC's class via lookup and fall back to the
configured storageClass for fresh installs.

The trivy StatefulSet's volumeClaimTemplate had the same latent drift --
.Values.storageClass was forwarded blindly into a VCT that cannot be
patched at all -- so it gets the same lookup-preserve treatment, keyed
off data-<release>-trivy-0.

A live object pinned to an explicit "" (no dynamic provisioning) now
round-trips through upstream's "-" sentinel instead of collapsing to an
omitted storageClassName, and the spec lookup is guarded with
`| default dict` so a PVC carrying no storageClassName key stays omitted.

Adds a helm-unittest regression suite and wires a test target so
make unit-tests stops skipping packages/apps/harbor.

Fixes #2368

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-29 13:16:34 +05:00
Aleksei Sviridkin 60c9b39578 docs(platform): document branding keys including login theme
The branding map shipped as an undocumented empty map. Describe the keys it
actually supports (brandName, brandHtmlName) and the new login-theme keys
(loginTheme, loginThemeImage) consumed by the Keycloak charts, with an example.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-06-28 05:08:47 +03:00
Aleksei Sviridkin e78a5842f5 feat(keycloak): ship a login theme image via branding
Build the StatefulSet theme list from .Values.themes plus, when
branding.loginTheme and branding.loginThemeImage are both set, an auto-derived
entry. This lets an operator deliver a Keycloak login theme through platform
branding values alone (the init container mounts the theme image) without
patching the StatefulSet or wiring per-package values. branding arrives via
_cluster.branding, the same path that already feeds the realm displayName.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-06-28 05:08:38 +03:00
Aleksei Sviridkin fd9100c632 feat(keycloak-configure): select realm login theme from branding
Render ClusterKeycloakRealm.spec.themes.loginTheme from
_cluster.branding.loginTheme, alongside the existing brandName -> displayName
path. Without it a realm keeps the stock Keycloak login theme and there is no
declarative way to switch it through platform values.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-06-28 05:08:27 +03:00
Aleksei Sviridkin 243ce65071 feat(kubernetes): expose tenant apiserver authentication in app values
Tenant Kubernetes clusters had no way to configure the kube-apiserver
authentication through the app values: no knob for OIDC, the structured
AuthenticationConfiguration, extra apiserver flags, or extra volumes.
The only workaround was patching the underlying KamajiControlPlane
out-of-band, which is invisible to GitOps and lost on recreation.

Add three opt-in fields under controlPlane.apiServer, all empty by
default so existing clusters render unchanged:

- extraArgs        -> KamajiControlPlane spec.apiServer.extraArgs
- extraVolumeMounts -> spec.apiServer.extraVolumeMounts
- extraVolumes     -> appended to spec.deployment.extraVolumes

This unblocks OIDC SSO via the --oidc-* flags and, more importantly,
structured authentication: --authentication-config mounted from a
ConfigMap/Secret plus --requestheader-uid-headers, which is the only
way to map a user UID for aggregated/extension apiservers.

Because the control-plane Deployment runs on the management cluster,
the passthrough is validated to preserve tenant<->management isolation:

- extraVolumes allow only configMap and secret sources; host-reaching
  sources (hostPath, csi, nfs, persistentVolumeClaim) and token
  projection (projected) are rejected, so a tenant with self-service
  access to the Kubernetes CR cannot mount management-node paths or
  project arbitrary tokens into the apiserver pod.
- each extraVolume must have a unique, non-empty name and exactly one
  source; the reserved names talos-ca and talos-tls-cert are rejected
  to avoid a duplicate volume name in the rendered podspec.
- each extraVolumeMount must reference a user-declared extraVolume; the
  chart-managed talos secret volumes cannot be mounted.

Regenerate values.schema.json, README, the Go types and deepcopy, and
the ApplicationDefinition. Add helm unittest coverage for the default
(no-op) render, each field's passthrough, the name/source guards, and
the mount-reference guard (valid and rejected inputs).

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-06-28 04:01:39 +03:00
Aleksei Sviridkin d1a2c1bc25 ci(renovate): track the pinned etcd image in extra/etcd
The etcd image in the extra/etcd EtcdCluster podTemplate is a hardcoded
string that overrides etcd-operator v0.4.5's baked-in default. None of
the enabled managers (gomod, dockerfile, github-actions) can see a
literal image string in a Helm template, so the pin would silently age
- and etcd 3.5.x releases are overwhelmingly security fixes.

Add a custom.regex manager that matches the image line and resolves
updates from the docker datasource, so future etcd patch releases
surface as Renovate PRs like any other tracked dependency.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-06-28 03:55:36 +03:00
Aleksei Sviridkin 26b3110c59 fix(etcd): pin etcd image to v3.5.31 for consistent watches
etcd-operator v0.4.5 hardcodes quay.io/coreos/etcd:v3.5.12 and the
v1alpha1 EtcdCluster CRD exposes no version field, so the tenant
control-plane etcd runs 3.5.12. kube-apiserver only enables the
RequestWatchProgress storage feature on etcd >= 3.5.13 / >= 3.4.31, and
Kubernetes >= 1.31 enables ConsistentListFromCache (locked-to-true in
1.35), which depends on it. On etcd 3.5.12 every consistent watch fails
inside the stream with HTTP 500 ("the required storage feature
RequestWatchProgress is disabled"), breaking watch-based clients of
aggregated APIs in modern tenants.

Override the etcd container image through spec.podTemplate.spec, which
the operator strategically merges over its generated pod spec (matched
by container name), pinning a >= 3.5.13 patch release. This restores
consistent watches without waiting on the etcd-operator v1alpha2
migration.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-06-28 03:55:36 +03:00
Aleksei Sviridkin 8a14de9abf fix(seaweedfs): point S3 consumers at the reachable S3 service
The s3-service-name.patch renamed the S3 Service to <name>-s3
(seaweedfs-s3), but several consumers still resolved it through
componentName -> <fullname>-s3. name and fullname only coincide when the
release name equals the chart name; the shipped release is
seaweedfs-system, so they pointed at seaweedfs-system-s3 while the real
service stayed seaweedfs-s3:

- the S3 ingress backend and the iceberg ingress backend, which left
  ingress-nginx returning 503 on the external S3 endpoint.

The COSI provisioner in-cluster ENDPOINT was wrong on the same axis and
also on the port. Both the s3-gateway branch and the filer-S3 branch
rendered a portless URL that defaults to 443, while the s3 service only
listens on the S3 port (8333); the filer branch additionally targeted
the filer service, which serves no S3. Both in-cluster branches now
resolve seaweedfs-s3 on the configured S3 port.

The default path (s3.ingress.enabled=true) is repaired by the ingress
fix; the COSI in-cluster paths are latent until an operator disables the
ingress. Resolve every consumer through the same name helper the Service
uses, and pin the contract with unittests rendered under the production
release name so the mismatch cannot recur.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-06-27 22:26:40 +03:00
myasnikovdaniil f5c408d2fc ci(renovate): bump indirect Go deps and enable OSV vuln alerts (#3026)
## What

Makes Renovate open the dependency PRs that close the OpenSSF Scorecard
`Vulnerabilities` advisories, instead of doing them by hand (as in
#3019).

- Enable **indirect** gomod updates (grouped into one rolling "indirect
go modules" PR). Renovate skips indirect Go deps by default, which is
why `golang.org/x/crypto`, `x/net`, and `x/sys` — the transitive deps
carrying the 20 OSV advisories — never got a PR.
- Enable **`osvVulnerabilityAlerts`** so vulnerable deps get dedicated,
mergeable security PRs (labeled `security`, not the global
`do-not-merge/hold`).

## Why #3019 wasn't enough

#3019 bumped `x/crypto` in the **root** module only. The repo has 5
`go.mod` modules; the same advisories live in `token-proxy` (x/crypto
v0.50.0), `kubevirt-csi-driver`, and `kubeovn-webhook` (x/net / x/sys).
The Scorecard `Vulnerabilities` check scans repo-wide and is
all-or-nothing, so the count never dropped (still 0/10, 21 advisories).

## Known gap

This does **not** fix `GO-2025-3512` (`kubevirt.io/csi-driver`): no
fixed version exists upstream and Renovate can't even resolve the module
(`no-result` in the Dependency Dashboard #2996). Until that one advisory
is suppressed via `.osv-scanner.toml` or patched upstream, the
Vulnerabilities check stays at 0/10. Tracked separately as a follow-up.

## Validation

- `renovate-config-validator`: **Config validated successfully**.

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

* **New Features**
  * Added support for automated handling of vulnerability alerts.
* Enabled updates for indirect Go module dependencies and grouped them
together for easier review.
* **Chores**
* Updated automation settings to label security-related update pull
requests appropriately.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-27 15:18:51 +05:00
Aleksei Sviridkin e295f90c78 feat(kubernetes): propagate remote-accessible LINSTOR StorageClasses to tenant clusters (#2872)
## What this PR does

Tenant Kubernetes clusters previously exposed a single hardcoded
`kubevirt` StorageClass mapped to one infrastructure class
(`.Values.storageClass`). Users running multiple storage tiers on the
management cluster could not reach them from inside tenant clusters
(#2094, #1424).

This change auto-discovers the management cluster's **remote-accessible
LINSTOR** StorageClasses (`provisioner: linstor.csi.linbit.com` with
`allowRemoteVolumeAccess != "false"`) via Helm `lookup` and propagates
each to tenant clusters under the same name. No Go changes — the
kubevirt-csi-driver already supports multiple infra StorageClasses
natively.

**Behavior**

- `packages/apps/kubernetes` `csi.yaml` builds a `storageClasses` map
filtered to remote-accessible LINSTOR classes and passes it to the
`kubevirt-csi-node` child chart.
- `kubevirt-csi-node` renders one tenant StorageClass per entry
(`provisioner: csi.kubevirt.io`, `infraStorageClassName: <name>`, `bus:
scsi`).
- The class named by `.Values.storageClass` (default `replicated`) is
marked the tenant default; exactly one default is guaranteed.
- The legacy `kubevirt` StorageClass is retained as a non-default alias,
so **existing tenant PVCs keep binding with no migration**.
- If the `lookup` returns empty (e.g. dry-run), a non-destructive
fallback preserves the legacy `replicated` + `kubevirt` mapping — tenant
StorageClasses are never wiped on a transient reconcile.

**Scope decisions**

- **LINSTOR-only**: only StorageClasses cozystack ships and controls are
auto-propagated. Node-local classes (e.g. `local`,
`allowRemoteVolumeAccess: false`) are excluded because the backing
volume cannot follow a live-migrated worker-node VM. Other provisioners
(NFS, etc.) are out of scope.
- **No enforcement flip**: `infraStorageClassEnforcement` is
intentionally left at the current `AllowAll` default. Enabling the
allowList would break tenants that have already self-served custom
StorageClasses against the management cluster — exactly the users this
feature serves. Tightening tenant isolation is left to a separate,
discovery-based change.

This is a focused, additive alternative to #2095 (kept open), scoped per
the LINSTOR-only / no-breakage decisions above.

**Upgrade note**

If you previously created a tenant StorageClass **named `replicated`**
by hand, delete it before upgrading — otherwise the child Helm release
hits an ownership conflict adopting the now-managed `replicated` class.

### Tests

- Helm unit tests for the fallback map (`kubernetes`) and per-entry
rendering + single-default invariant + absence of an enforcement block
(`kubevirt-csi-node`; new `Makefile` `test` target so the package is no
longer skipped by the runner).
- e2e (`run-kubernetes.sh`): asserts `replicated` propagates as the
default kubevirt-CSI class, the `kubevirt` alias is retained, and
node-local `local` is filtered out.

### Release note

```release-note
feat(kubernetes): auto-propagate remote-accessible LINSTOR StorageClasses from the management cluster to tenant Kubernetes clusters under the same name. The class set by `storageClass` (default `replicated`) becomes the tenant default; the legacy `kubevirt` StorageClass is kept as an alias so existing PVCs are unaffected. Node-local classes (e.g. `local`) are not propagated. If you manually created a tenant StorageClass named `replicated`, delete it before upgrading to avoid a Helm ownership conflict.
```


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

* **New Features**
* Remote-accessible LINSTOR `StorageClass` entries now auto-propagate
into tenant clusters using the same name.
* Tenant-side CSI/KubeVirt `StorageClass` rendering now supports
multiple configurable classes with deterministic default selection.
* **Improvements**
* Enhanced e2e checks to verify `StorageClass` provisioner, parameters,
default annotation, and legacy `kubevirt` alias compatibility.
* **Tests**
* Added Helm unittest suites covering fallback and explicit
`storageClass` cases.
  * Added kubevirt CSI node rendering tests and a `test` target.
* **Documentation**
* Clarified `storageClass` descriptions and upgrade caveats across
charts and schemas.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-26 22:19:17 +03:00
myasnikovdaniil ee54a6eb32 ci: prune stale and redundant workflows (#2982)
## What this PR does

Cleans up four GitHub Actions workflows that are redundant, broken, or
noisier than they need to be. Each change is its own commit so they can
be reviewed (or reverted) independently.

- **`retest.yaml` — removed.** The `/retest` command re-ran a job named
`Prepare environment`, but the TIA / job-split refactor of
`pull-requests.yaml` collapsed that into a *step* inside the `E2E Tests`
job, so the job lookup can never match. Every real `/retest` invocation
since fails with `Could not find "Prepare environment" job` — **10
failed runs spanning 2026-01-08 → 2026-06-24, none that ever completed a
retest**. (The 41 runs GitHub records as `success` are `issue_comment`
events where the job-level `if`-guard was false, so no steps ran.) `gh
run rerun --failed` covers the need.

- **`codeql.yml` — switch Go to manual build; keep the per-PR run.** The
Go `autobuild` ran `make`, whose included `hack/common-envs.mk` does a
`$(shell git fetch upstream --tags)` at parse time — a network call to a
non-origin remote that has nothing to do with the analysis and is a
flake vector. Replace it with `build-mode: manual` that compiles each
first-party module explicitly (the root module, `api/apps/v1alpha1`, and
the three nested image modules; the gitignored `_repos/*` upstream
clones drop out for free on a clean checkout). Same coverage, no
upstream fetch. The `pull_request` trigger is **kept on purpose**:
per-PR analysis is the only mode that diffs *new* alerts against the
base branch and annotates them on the changed lines — the basis for
promoting CodeQL to a required pre-merge check (follow-up: add `CodeQL /
Analyze (go)` to branch protection and set the code-scanning failure
threshold to errors-only). No `paths` filter — a required check that is
path-skipped never reports and would block PRs that touch no Go.

- **`scorecard.yml` — badge-only; stop polluting code-scanning.**
Scorecard uploads one code-scanning alert per check, which floods
Security → Code scanning and buries CodeQL's first-party findings. Drop
the `upload-sarif` step and the now-unneeded `security-events: write`;
keep `publish_results` (the public scorecard.dev badge is the canonical
view). Also drop the redundant `push:[main]` trigger — the weekly cron +
`branch_protection_rule` already cover it.

- **`update-releasenotes.yaml` — path-gate to changelog changes + manual
hatch.** The release body is synced from `docs/changelogs/v*.md`, which
only lands on main when the changelog PR merges or a maintainer edits an
existing changelog. Gate the `push` trigger to `docs/changelogs/v*.md`
(the `v*` glob skips `template.md` / `patch-template.md`) so the
self-hosted job stops firing on every unrelated main push, and add
`workflow_dispatch` as a manual re-sync hatch.

- **`SECURITY.md` — document the security scanners.** Add an "Automated
security analysis" subsection describing the CodeQL (SAST) and OpenSSF
Scorecard (supply-chain) setup, including the manual Go build, the
new-alert-on-diff behaviour, the intent to make CodeQL a required
pre-merge check, and how findings are dismissed — so the posture is
documented, not just encoded in YAML.

Not touched: `release-e2e.yaml` — already removed by #2978.

### Release note

```release-note
NONE
```


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

## Summary by CodeRabbit

* **Chores**
* Updated automation workflows for security checks and release-note
generation.
  * Removed an unused retry workflow and tightened when some checks run.

* **Documentation**
* Expanded security guidance to better explain automated analysis, alert
handling, and reporting.
  * Clarified when release notes updates are generated.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-26 23:06:34 +05:00
Myasnikov DaniilandClaude Opus 4.8 da084ef51c test(kubernetes): cover fallback-default StorageClass selection (#2872 B1)
Pin the PR #2872 B1 fix: when the management cluster exposes only remote
LINSTOR StorageClasses whose names sort after "kubevirt" and none is named
the configured storageClass (default "replicated"), the tenant default must
be chosen among the propagated classes and never the legacy "kubevirt" alias.

helm-unittest cannot reach this branch (no live cluster -> lookup returns
empty -> the storageClasses map collapses to the "replicated" fallback), so
it is exercised in e2e via a single server-side dry-run render of the
kubernetes chart against the live management cluster: two remote LINSTOR
classes are added and "replicated" is removed for the one render, then the
rendered -csi HelmRelease storageClasses map is asserted to carry exactly one
default among the propagated classes, never the "kubevirt" alias.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-26 21:29:04 +05:00
Myasnikov Daniil 6423cb53e0 fix(kubernetes): never auto-select the kubevirt alias as tenant default SC
The legacy "kubevirt" StorageClass alias was inserted into the candidate
map before the default was chosen, so the sortAlpha fallback could pick it
when propagated class names sort after "kubevirt" (e.g. nvme, ssd) and no
configured/replicated class is present. That made the tenant default point
at an infraStorageClass that may not exist on the infra cluster, leaving
default PVCs Pending. Choose the default from the propagated set before
adding the alias.

Also quote the templated StorageClass name and infraStorageClassName in
kubevirt-csi-node, and document the propagation upgrade caveat, render-time
evaluation limitation, and allowRemoteVolumeAccess assumption in the
kubernetes README.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-26 21:20:30 +05:00
Myasnikov DaniilandClaude Opus 4.8 6a1b56f2b5 test(kubernetes): cover LINSTOR StorageClass propagation
- csi_test.yaml: assert the fallback storageClasses map yields a default
  "replicated" plus the non-default "kubevirt" alias, and that the
  alias tracks the storageClass selector.
- kubevirt-csi-node: add a Makefile test target (otherwise the package
  is silently skipped by the unit-test runner) and storageclasses_test
  covering per-entry rendering, the single-default invariant, and the
  absence of any enforcement allowList in driver-config.
- run-kubernetes.sh: verify in e2e that "replicated" propagates as the
  default kubevirt-CSI class, the "kubevirt" alias is retained, and the
  node-local "local" infra class is filtered out.

Refs: #2094

Assisted-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-26 21:20:30 +05:00
Myasnikov DaniilandClaude Opus 4.8 021f6141ca feat(kubernetes): propagate remote-accessible LINSTOR StorageClasses to tenant clusters
Tenant Kubernetes clusters previously exposed a single hardcoded
"kubevirt" StorageClass mapped to one infra class. Discover all
remote-accessible LINSTOR infra StorageClasses (provisioner
linstor.csi.linbit.com, allowRemoteVolumeAccess != false) via Helm
lookup and propagate each to the tenant under the same name.

- csi.yaml builds a storageClasses map filtered to remote LINSTOR
  classes; node-local classes (e.g. "local") are excluded because the
  backing volume cannot follow a live-migrated worker VM.
- The class named by .Values.storageClass (default "replicated") is
  marked the tenant default; the legacy "kubevirt" StorageClass is kept
  as a non-default alias so existing tenant PVCs keep binding.
- A non-destructive fallback preserves the legacy mapping when the
  lookup returns empty (dry-run), so tenant StorageClasses are never
  wiped on a transient reconcile.
- infraStorageClassEnforcement is intentionally left disabled (AllowAll)
  to avoid breaking tenants that already self-served custom
  StorageClasses under the current default.

Refs: #2094, #1424

Assisted-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-26 21:20:29 +05:00
Myasnikov Daniil 61f638ea79 Merge remote-tracking branch 'origin/main' into refactor/build-matrix-2937 2026-06-26 20:59:29 +05:00
Aleksei Sviridkin 4752161dc1 fix(platform): stamp migrations 45/46 via the cozystack-version helper (#3118)
## What this PR does

Migrations 45 and 46 (added by #2931) stamp the `cozystack-version`
ConfigMap
with an inline `kubectl apply` heredoc instead of sourcing
`lib/cozystack-version.sh` and calling `stamp_cozystack_version`, the
way
migrations 42/43/44 do. The `cozystack-version-stamp.bats` guard forbids
any
migration ≥42 from bypassing the helper, so `make bats-unit-tests` fails
on
migration 45.

This only surfaces in PR CI (`make unit-tests`) — `main`-push CI does
not run
the bats suite — so #2931 landed red on this test and now blocks every
PR that
merges current `main`.

This routes both stamps through the helper. The helper renders a
byte-identical labeled manifest (golden-pinned in the bats suite), so
the
change is behaviour-preserving: it just restores the no-label-drift
guarantee
the heredoc form could silently lose.

Verified: `make bats-unit-tests` passes (was failing on migration 45),
bash
syntax-check clean on both scripts.

```release-note
NONE
```


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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved platform version stamping to keep the version record
correctly labeled and consistent during upgrades.
* Updated tenant Kubernetes resources to move from v1.30 to v1.31 as
part of the migration flow.

* **Chores**
* Standardized migration behavior by using a shared version-stamping
helper for more reliable updates.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-26 18:56:08 +03:00
Myasnikov Daniil 70320e0abf fix(platform): stamp migrations 45/46 via the cozystack-version helper
Migrations 45 and 46 (added by #2931) stamp the cozystack-version ConfigMap
with an inline `kubectl apply` heredoc instead of sourcing
lib/cozystack-version.sh and calling stamp_cozystack_version, the way 42/43/44
do. The cozystack-version-stamp.bats guard forbids any migration >= 42 from
bypassing the helper, so `make bats-unit-tests` fails on migration 45.

This only surfaces in PR CI (make unit-tests); main-push CI does not run the
bats suite, so #2931 landed red on this test and now blocks every PR that
merges current main.

Route both stamps through the helper. The helper renders a byte-identical
labeled manifest (golden-pinned in the bats suite), so this is behaviour-
preserving — it just restores the no-label-drift guarantee the heredoc form
could silently lose.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-26 20:40:02 +05:00
Aleksei Sviridkin f4a000ef11 fix(gateway): pin identical allowedRoutes.kinds on port 443 listeners (cilium#45559) (#3084)
## What this PR does

Cilium collapses all listeners that share a port when their
`allowedRoutes.kinds` sets differ
([cilium#45559](https://github.com/cilium/cilium/issues/45559)). In the
default `TLSPassthroughServices` configuration both HTTPS-terminate
listeners (kinds: `[HTTPRoute]`) and TLS-passthrough listeners (kinds:
`[TLSRoute]`) land on port 443. The divergent kinds trigger the
collapse, which then drops the HTTPRoutes that were accepted by the
HTTPS listener.

**Fix (Option B — security intact):** give every port-443 listener the
same kinds set `[HTTPRoute, TLSRoute]`.

- Introduces a shared `port443Kinds` slice in `renderGateway` and
assigns it to both `httpsAllowedRoutes.Kinds` and
`passthroughAllowed.Kinds`.
- GRPCRoute / TCPRoute / UDPRoute are still excluded, so the Layer 7 VAP
(`cozystack-route-hostname-policy`) continues to gate exactly the two
expected route types. Gateway API itself rejects any HTTPRoute that
references a Passthrough `sectionName` at attach time, so the expanded
kinds on passthrough listeners add no actual attach surface.
- Adds regression test `TestReconcile_Port443ListenersShareKinds` that
reconciles a TenantGateway with both an attached HTTPRoute and a
TLS-passthrough service, then asserts every port-443 listener carries a
non-empty, identical `{HTTPRoute, TLSRoute}` kinds set.
- Updates two pre-existing tests
(`TestReconcile_TLSPassthroughListenersRendered`,
`TestReconcile_HTTPSListenersRestrictRouteKindsToHTTPRoute`) that pinned
the old single-kind behavior.

### Release note

```release-note
fix(gateway): all port-443 listeners now carry identical allowedRoutes.kinds=[HTTPRoute,TLSRoute] to prevent Cilium (cilium#45559) from collapsing HTTPS-terminate and TLS-passthrough listeners and silently dropping HTTPRoutes
```

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

* **Bug Fixes**
* Updated port 443 gateway listeners to use a consistent set of allowed
route types.
* HTTPS (TLS-terminate) and TLS-passthrough listeners now both allow
**HTTPRoute** and **TLSRoute**, avoiding configuration discrepancies.
* **Tests**
* Added regression coverage to verify all port 443 listeners share the
same allowed route-kind set and exclude unsupported route types.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Closes #3070
2026-06-26 18:28:37 +03:00
Myasnikov Daniil 0b9f5a1005 Merge remote-tracking branch 'origin/main' into refactor/build-matrix-2937
Resolve the .github/workflows/pull-requests.yaml conflict: keep this branch's
per-package build matrix and carry main's `git diff --binary` patch fix
(#e566992) onto every fragment and pr.patch capture, so provider bundles that
package image targets regenerate (components*.gz) survive fragment reassembly
in finalize and reach the patch e2e applies.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-26 20:15:52 +05:00
shreyaabaranwal 6acf34aef2 test(gateway): pin full port-443 kinds contract across all cert modes (cilium#45559)
Signed-off-by: shreyaabaranwal <shreyabaranwal229@gmail.com>
2026-06-26 20:38:14 +05:30
myasnikovdaniil 1df8c7c557 feat(kubernetes): bootstrap tenant workers with Talos via CABPT (#2931)
## What this PR does

> **Supersedes #2610.** Same work, rebased cleanly onto current `main`
so it now sits on top of #2867 (the `kube-controller-manager` VAP
type-checker fix). That fix is what was killing the base install in
#2610's E2E *before* the `kubernetes` app test ever ran, leaving the
Talos worker-bootstrap rewrite verified only by code reading. #2610's
67-commit branch is squashed here into 8 logical commits with original
authorship preserved (@kvaps and @IvanHunters as authors); the tree is
**byte-identical** to #2610's head — no code changes, just the rebase
onto `main` (to pick up #2867) plus history cleanup. The detailed review
history lives on #2610.

Phase 1 of the Kubernetes-app split design (see cozystack/community#8):
replace the Ubuntu+kubeadm worker bootstrap path with Talos, driven by
`cluster-api-bootstrap-provider-talos` (CABPT) and a
`clastix/talos-csr-signer` sidecar embedded in the Kamaji control-plane
pod. Existing tenants keep working — old machines roll out and get
replaced by Talos workers without manual intervention.

Highlights:

- Add CABPT (v0.6.12) as a second `BootstrapProvider` alongside the
existing kubeadm one in `capi-providers-bootstrap`.
- Bump the Kamaji control-plane provider with an upstream-bound patch
that exposes `KamajiControlPlane.spec.network.additionalServicePorts`,
used to publish trustd (50001/TCP) on the apiserver service.
- Generate Talos PKI (Ed25519 CA + trustd TLS) via cert-manager and
stable random Talos secrets (`token`, `clusterId`, `clusterSecret`,
`bootstrapToken`) using the helm lookup-and-reuse pattern.
- Materialise a kubeadm-format `bootstrap-token-<id>` Secret inside the
tenant `kube-system` via a Helm post-install/upgrade Job.
- Render a `TalosConfigTemplate` (worker machineconfig, `generateType:
none`) and switch the `MachineDeployment.bootstrap` reference from
`KubeadmConfigTemplate` to `TalosConfigTemplate`, gated on Talos secrets
and the Kamaji apiserver service being ready.
- Boot workers from the Talos openstack image via a CDI `DataVolume`
(`source.http.url` → `factory.talos.dev`), expose the system disk as
virtio-blk with `blockSize.custom: logical=512, physical=4096` so
4Ki-native block storage backends (LINSTOR/DRBD) play nicely with QEMU's
O_DIRECT writes and SeaBIOS still boots, drop the separate kubelet disk
(Talos lays out EPHEMERAL itself).
- Pin pod/service CIDRs in the worker machineconfig to the tenant ranges
(`10.243.0.0/16` / `10.95.0.0/16`) to avoid Talos's address-overlap
diagnostic against the host pod CIDR.
- Set `cluster.controlPlane.endpoint` and `cilium.k8sServiceHost` to the
Kamaji apiserver Service ClusterIP (looked up at render time), so
bootstrap survives the chicken-and-egg moment where tenant DNS does not
exist yet and the host CoreDNS does not serve the tenant zone.

End-to-end on a dev cluster: a fresh tenant brings up a Talos worker,
kubelet CSRs get approved (apiserver-client by Kamaji, kubelet-serving
by the talos-csr-signer sidecar), Cilium initialises with hostNetwork
against the apiserver ClusterIP, CoreDNS comes up, and the node
transitions to `Ready`.

### Release note

```release-note
feat(kubernetes): bootstrap tenant workers with Talos Linux instead of Ubuntu+kubeadm. Existing clusters roll over to Talos workers automatically; new tenants come up on Talos from the start. Powered by cluster-api-bootstrap-provider-talos and the talos-csr-signer sidecar in the Kamaji control-plane pod. Note: the tenant `kubernetes` HelmRelease now reports Ready as soon as Helm completes (Install/Upgrade DisableWait) and no longer blocks on worker/addon readiness — worker-rollout health is tracked by WorkloadMonitor, not the HelmRelease.
```


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

## Release Notes

**New Features**
* Added Talos worker configuration for `talos.version` and
`talos.schematicID`
* Introduced `nodeHealthCheck` (`maxUnhealthy`, `nodeStartupTimeout`)
for worker remediation tuning
* Added image overrides for `kubectl` and `talosCsrSigner`
* Exposed additional tenant control-plane Service ports support

**Breaking Changes**
* Removed Kubernetes v1.30; supported versions start at v1.31
* Renamed `nodeGroups.ephemeralStorage` → `nodeGroups.diskSize` with
consolidated Talos system-disk semantics

**Removals**
* Removed Ubuntu container-disk images and related containerd patch
mechanisms
* Air-gapped tenant workers temporarily unsupported during the Talos
transition

**Documentation**
* Updated chart docs, parameters, and GitOps upgrade guidance for
version bumps
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-26 20:06:49 +05:00
Myasnikov Daniil 133303ec12 build(dashboard): pin console image builder to pnpm 11.1.2 and repoint vendor doc link
The console Containerfile ran `corepack prepare pnpm@latest`, so the image build could drift to a newer pnpm major than the `pnpm@11.1.2` pinned in package.json and used by CI. `--frozen-lockfile` guards the lockfile, but pinning the builder makes the image build reproducible and consistent with CI.

Also repoints docs/storage-immutability.md off the now-vendored cozystack-ui#6 link to the in-tree console path (follow-up from #2963).

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-26 20:00:12 +05:00
Myasnikov Daniil 4838bafbc1 fix(dashboard): render error and unknown-type states instead of an infinite spinner
Two console pages ordered their loading guard ahead of the terminal state, making the latter unreachable:

- ApplicationDetailPage checked `isLoading || !instance || !ad` before `if (error)`. On a failed GET, isLoading is false and instance stays undefined, so the page spun forever and never showed the not-found message. The error branch now runs first.

- ApplicationListPage's `defsLoading || (!ad && plural)` guard stayed true for any unrecognized plural once definitions had loaded, so 'Unknown application type.' was unreachable. The guard now only spins while definitions are loading.

Both are pre-existing upstream behaviors carried in by the cozystack-ui vendoring (#2963). Adds co-located regression tests for the failed-GET and unknown-plural paths.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-26 19:59:04 +05:00
Алексей Артамонов 6e093f11bc fix(gateway): clean up orphaned ACME account secret on tenant module delete (#3089)
When the gateway tenant module is disabled, cert-manager's ACME account
private-key secret cozystack-acme-account was left orphaned (no ownerRef).
Add a post-delete cleanup hook that deletes it by name. Verified the secret
is referenced only by the gateway module's own cozystack-gateway Issuer, so
removing it on gateway teardown is safe. The cleanup Job runs non-root with a
read-only root fs and dropped capabilities; RBAC is scoped via resourceNames.

Signed-off-by: Алексей Артамонов <aleksei.artamonov@aenix.io>
2026-06-26 17:29:44 +03:00