mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-09 14:02:56 +09:00
8f4c19daaeb00a2aeccf761cd96879867a4d86ce
5128
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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 --> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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 --> |
||
|
|
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 -->
|
||
|
|
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 --> |
||
|
|
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 --> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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 --> |
||
|
|
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 --> |
||
|
|
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 --> |
||
|
|
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> |
||
|
|
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 --> |
||
|
|
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 --> |
||
|
|
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 --> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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 --> |
||
|
|
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 --> |
||
|
|
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 --> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
61f638ea79 | Merge remote-tracking branch 'origin/main' into refactor/build-matrix-2937 | ||
|
|
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 --> |
||
|
|
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> |
||
|
|
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 |
||
|
|
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> |
||
|
|
6acf34aef2 |
test(gateway): pin full port-443 kinds contract across all cert modes (cilium#45559)
Signed-off-by: shreyaabaranwal <shreyabaranwal229@gmail.com> |
||
|
|
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 --> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |