mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-04 19:17:20 +09:00
test/ci-e2e-flake-hunt
4083
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
660b79401f |
test(e2e): self-heal Cilium orphaned-endpoint leak during install
A churn-heavy platform install intermittently wedges a pod in
ContainerCreating because the cilium-agent rejects its sandbox with
'[PUT /endpoint/{id}][400] putEndpointIdInvalid "IP ipv4:<X> is already
in use"', repeated until the agent restarts or the install times out.
The disputed IP has no live owner: IPAM and the ipcache released it, but
the agent's in-memory endpointManager still holds a stale Endpoint from a
previously-deleted pod whose CNI DEL was skipped or raced (so
removeReferencesLocked never ran). Neither the operator's CiliumEndpoint
GC nor the agent's veth-based endpoint GC can reap it. This surfaced as
~6 of the post-#2558 e2e failures, each on a different victim pod
(velero node-agent, fluent-bit, fdb-operator, cdi-operator, capi
bootstrap), failing PRs unrelated to the change under test. Tracked
upstream at cilium/cilium#38313 (closed stale); no released or
pre-release Cilium fixes it and no flag disables it.
hack/e2e-cilium-endpoint-leak-healer.sh runs as a file-scoped background
watchdog (setup_file/teardown_file). It detects a wedged pod, confirms
the IP is a true orphan -- an endpoint in the owning node's registry
holds it, that endpoint backs no live Running pod, and a different pod is
wedged requesting the same IP -- then evicts only that endpoint via
'cilium-dbg endpoint disconnect ipv4:<X>' (DELETE /endpoint/{id} ->
unexpose -> removeReferencesLocked). Blast radius is one dead endpoint,
not an agent restart. It is read-only until an orphan is positively
confirmed and refuses (loudly) to touch an endpoint backing a live pod,
so a genuine duplicate-IP bug stays visible rather than masked.
This is an interim CI mitigation, not a product fix; remove the script
and the bats hooks once a fixed Cilium ships.
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Signed-off-by: Ivan Okhotnikov <ivan.okhotnikov@aenix.io>
|
||
|
|
178dbdba9a |
fix(seaweedfs): drop volumeSizeLimitMB so multiple collections fit
Cozystack overrides the upstream seaweedfs volumeSizeLimitMB from 1000 to 30000 (30 GB), but the e2e sandbox provisions only a 10 GiB PVC per volume server. With maxVolumes=0 (auto), seaweedfs computes maxVolumes as int(10240 / 30000) → 0, then rounds up to 1, so each volume server can host exactly 1 logical volume. With defaultReplication=001 (one replica on a different node within the same rack) two volume servers share that single volume, giving the cluster a hard limit of one writable collection at a time. Since #2716 added a default bucket-cozy-backups Bucket to every tenant-root, that bucket now grabs the only slot on install. Any second collection — the bucket E2E (bucket-test in tenant-test), a tenant app's own bucket, etc. — sits in 'assign volume: No writable volumes' forever, and mc cp hangs until the test times out. Master logs reflect this clearly: failed to find writable volumes for collection:bucket-... replication:001 ttl: error: No writable volumes and no free volumes left topo failed to pick 1 from 0 node candidates volume_growth.go:135 create 6 volume, created 0: Not enough data nodes found! Lower volumeSizeLimitMB back to the upstream chart default of 1000, which makes a 10 GiB PVC fit ~10 logical volumes per server and unblocks all per-collection bucket workloads on small sandboxes. Production clusters still get plenty of headroom — volume size is a soft cap, not a preallocation. Signed-off-by: Ivan Okhotnikov <ivan.okhotnikov@aenix.io> |
||
|
|
f74c44b063 |
test(e2e): drop bash-only RETURN trap from bucket.bats port-forward
The previous attempt added a bash `trap '...' RETURN` to clean up kubectl port-forward when the test function returned. cozytest.sh sources its converted bats test bodies into the runner shell, which on the GitHub-hosted Linux runner is /bin/sh -> dash. dash does not implement the bash-specific RETURN pseudo-signal, so the trap command exits non-zero, the surrounding `set -eu` aborts the test body immediately, and the captured output stops two lines after `name=test` with no other diagnostic — the test never reaches the mc S3 operations at all. Drop the trap and rely on cozytest.sh's existing process-group cleanup: when the test function returns, the leftover port-forward job in the same group is terminated. This restores POSIX shell compatibility while keeping the lifecycle fix from the previous commit (no `bash -c '... &'` wrapper, port-forward stays alive through the mc operations because it is a child of the calling shell, not of a transient subshell that exits immediately). Signed-off-by: Ivan Okhotnikov <ivan.okhotnikov@aenix.io> |
||
|
|
07cca8b6b3 |
test(e2e): keep bucket.bats port-forward alive across mc operations
The test used to wrap kubectl port-forward in a transient subshell: bash -c 'timeout 100s kubectl port-forward ... > /dev/null 2>&1 &' The wrapper subshell exits immediately after backgrounding the kubectl process, the kubectl child is reparented to PID 1, and on the GitHub-hosted runner it gets reaped between the `nc -z` readiness probe and the first `mc cp` call. The captured failure looks like + bash -c timeout 100s kubectl port-forward ... + timeout 30 sh -ec until nc -z localhost 8333; do sleep 1; done + mc alias set rw-user https://127.0.0.1:8333 ... --insecure Added `rw-user` successfully. + mc cp --insecure /tmp/rw-test.txt rw-user/$BUCKET/rw-test.txt mc: <ERROR> Put "https://127.0.0.1:8333/$BUCKET/...": dial tcp 127.0.0.1:8333: connect: connection refused even though every S3 resource (Bucket, BucketClaim, both BucketAccess) is Ready/Granted=true at that point and the seaweedfs-s3 Service is healthy in tenant-root. Run kubectl port-forward as a job of the calling shell, capture its PID, and tear it down via a bats RETURN trap. The trap is enough on its own: the test no longer needs the outer `timeout 100s` belt because the port-forward will be killed when the test function returns, even on failure. Also switch the readiness probe to 127.0.0.1 to match the mc alias and avoid any IPv6 resolution path on hosts where kubectl binds IPv4 only. Signed-off-by: Ivan Okhotnikov <ivan.okhotnikov@aenix.io> |
||
|
|
9bd0254b72 |
test(e2e): bind bucket.bats S3 client to 127.0.0.1 instead of localhost
`kubectl port-forward service/seaweedfs-s3 -n tenant-root 8333:8333` binds the listener to 127.0.0.1 only — it has no IPv6 listener. The runner's getaddrinfo resolves `localhost` to `::1` first, and `mc` keeps a single connection target across the whole alias, so the initial `mc alias set` (which only sets credentials, no network I/O) silently succeeds while the subsequent `mc cp` upload tries `https://[::1]:8333` and gets immediate `connection refused`. The test then exits with the misleading bucket-test failure even though the S3 backend, credentials, and bucket all reconciled correctly. Use a literal IPv4 address (`127.0.0.1`) for the mc aliases and the `nc -z` readiness probe so the test no longer depends on the runner's IPv4/IPv6 resolution policy. Signed-off-by: Ivan Okhotnikov <ivan.okhotnikov@aenix.io> |
||
|
|
634a5c519b |
fix(seaweedfs): restore s3 Service name to chart-prefix form
The seaweedfs 4.31 upstream bump (#2834) switched the s3 Service name from "$chartname-s3" (built with the seaweedfs.name helper, evaluates to "seaweedfs-s3") to the seaweedfs.componentName helper, which is release-name based and now evaluates to "$release-s3" — "seaweedfs- system-s3" for the tenant-root deployment. The bats E2E asset hack/e2e-apps/bucket.bats hard-codes "service/seaweedfs-s3" for the port-forward that sets up its S3 client; backup-controller and other in-cluster consumers also referenced the legacy name. After the bump, the legacy Service is gone and the port-forward immediately exits, the test then times out on the 30-second nc poll for localhost:8333. Restore the legacy Service name. Internal references inside the chart (s3-deployment -filer=, etc) keep using componentName for the filer endpoint, so this change only re-exposes the public-facing name without touching the internal topology. Signed-off-by: Ivan Okhotnikov <ivan.okhotnikov@aenix.io> |
||
|
|
700149eb61 |
fix(seaweedfs): restore -lock BucketClass and -readonly BucketAccessClass
The seaweedfs 4.31 upstream bump (#2834) rewrote packages/system/seaweedfs/charts/seaweedfs/templates/cosi/cosi-bucket-class.yaml from scratch and silently dropped the supplementary COSI resources that cozystack ships on top of the upstream chart: - BucketClass {name}-lock (object lock, COMPLIANCE mode, 365 days retention) — used by apps that store immutable backups - BucketAccessClass {name} parameters.accessPolicy: readwrite — the explicit grant for the default access class - BucketAccessClass {name}-readonly (accessPolicy: readonly) — used by every Bucket app that declares readonly users apps/bucket still renders BucketAccess resources that reference {name}-readonly, but the class no longer exists, so the COSI controller never picks the BucketAccess up: no finalizer is added, status.accessGranted stays empty, and the bucket E2E test waits 300s and times out on the viewer access. The admin (readwrite) access class still works because it falls back to the default BAC. Restore the three resources so the system chart again matches the shape that packages/extra/seaweedfs/templates/client/cosi-bucket-class.yaml already ships for the client topology (where this never regressed). Signed-off-by: Ivan Okhotnikov <ivan.okhotnikov@aenix.io> |
||
|
|
c402f953af |
fix(seaweedfs): restore S3 TLS and COSI provisioner SA after 4.31 bump
The 4.31 chart bump (#2834) broke E2E for every PR: tenant-root seaweedfs-system never becomes ready. 1. S3 served plaintext. Until 4.05 the -cert.file/-key.file args were rendered whenever enableSecurity was set, so weed s3 served TLS on the main port (8333); 4.31 nested them under 'if s3.httpsPort' and we ship httpsPort=0, so s3 spoke HTTP while the HTTPS probes, the ingress backend-protocol and the COSI sidecar endpoint all expected HTTPS ('server gave HTTP response to HTTPS client'). Restore the 4.05 behaviour via patch. 2. COSI provisioner ServiceAccount mismatch. cosi-service-account.yaml and the ClusterRoleBinding name the SA <serviceAccountName>-objectstorage-provisioner, but the Deployment referenced componentName (<release>-objectstorage-provisioner). When serviceAccountName != release name (tenant-root-seaweedfs vs seaweedfs-system) the Deployment could not create pods ('serviceaccount not found'). Align the Deployment with the SA + binding. Both fixes are vendored as patches re-applied by 'make update', and covered by helm-unittest. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io> Signed-off-by: Ivan Okhotnikov <ivan.okhotnikov@aenix.io> |
||
|
|
93e5db82f5 |
refactor(api): initialize schema extensions with spec.Extensions{}
Address review feedback from gemini-code-assist on pkg/cmd/server/openapi.go:
markOpenObject and sanitizeForV2 initialized s.Extensions with the generic
map[string]any{}; use the named spec.Extensions{} type instead, matching the
kube-openapi types and the generated definitions in zz_generated.openapi.go.
No behavioral change — spec.Extensions is defined as map[string]interface{}.
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Signed-off-by: Ivan Okhotnikov <ivan.okhotnikov@aenix.io>
|
||
|
|
9dbee5f20f |
test(api): cover nested-in-additionalProperties and anyOf/oneOf/not in sanitizer regression
Address review feedback from lexfrei and coderabbitai on pkg/cmd/server/openapi_test.go: TestPatchSpecSanitizesUserSuppliedBooleanAdditionalProperties only exercised the top-level, properties, items, and allOf positions, leaving the one remaining type-checker-reachable sanitizer branch untested: a boolean additionalProperties nested inside an object-form additionalProperties. Deleting that recursion line kept the whole suite green while the input panicked SchemaDeclType again (#2863). Add the nested-under-additionalproperties case (the reachable blocker, verified red->green by mutation) plus anyOf/oneOf/not cases (defense-in-depth: those branches are recursed by the sanitizer but never traversed by SchemaDeclType, so the hasBooleanAdditionalProperties assertion guards them). Rewrite the test docstring to distinguish the type-checker-reachable cases from the defense-in-depth ones, replacing the inaccurate "each case panics SchemaDeclType" note. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com> Signed-off-by: Ivan Okhotnikov <ivan.okhotnikov@aenix.io> |
||
|
|
65850e1424 |
fix(api): recursively sanitize boolean additionalProperties in published spec
Addresses review feedback on #2863. The previous fix only converted the open spec wrapper cozystack-api injects, so a user-supplied ApplicationDefinition openAPISchema carrying a boolean additionalProperties — declared explicitly at the top level, or nested under properties/items/allOf/anyOf/oneOf — was republished verbatim and re-triggered the kube-controller-manager VAP type-checker crash. openAPISchema is untrusted input (external-apps and operator-authored definitions flow through unvalidated), so close the whole class rather than only the schemas cozystack ships. Recursively rewrite every boolean-form additionalProperties (nil inner schema): additionalProperties:true becomes x-kubernetes-preserve-unknown-fields:true, additionalProperties:false is dropped. Both JSON forms carry a nil inner schema and crash the type-checker identically; object-form maps are recursed into and left intact. Also publish the schemaless spec from a fresh schema so no inherited $ref or properties leak into the open object. Regression tests cover top-level true/false and nested (properties, items, allOf) boolean forms, asserting the published schema carries no boolean additionalProperties node and does not panic SchemaDeclType. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com> Signed-off-by: Ivan Okhotnikov <ivan.okhotnikov@aenix.io> |
||
|
|
ebde1c9155 |
fix(api): publish open spec as x-kubernetes-preserve-unknown-fields, not additionalProperties:true
cozystack-api published the free-form ".spec" of aggregated apps.cozystack.io
resources (Tenant, Application, ...) as the boolean `additionalProperties: true`.
The Kubernetes ValidatingAdmissionPolicy status type-checker run by
kube-controller-manager nil-dereferences when it recurses into that node, so as
soon as a VAP matches one of these resources (cozystack-tenant-host-policy on
tenants) KCM CrashLoopBackOffs cluster-wide and stalls reconciliation, failing
cold installs once the 3x install retry was removed.
Emit `x-kubernetes-preserve-unknown-fields: true` instead. It is semantically
equivalent ("arbitrary fields allowed") and the type-checker handles it safely.
All app custom schemas already use the safe object-form additionalProperties,
so patchSpec was the only source of the boolean form.
Adds a regression test that reproduces the exact crash through
k8s.io/apiserver/pkg/cel/openapi.SchemaDeclType (the entry point KCM uses): it
panics on the pre-fix code and passes after the fix.
Fixes #2863
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Signed-off-by: Ivan Okhotnikov <ivan.okhotnikov@aenix.io>
|
||
|
|
0794c85a57 |
test(ci): collect source-watcher diagnostics in cozyreport
Last run exposed 7 PackageSources stuck with Ready=Unknown reason=Progressing for 30 minutes (openbao-application, qdrant-application, vpn-application, vpa, linstor, virtualprivatecloud-application, capi-provider-bootstrap-kubeadm). PackageSourceReconciler copies its Ready condition straight from the owned ArtifactGenerator, so the real culprit lives in source-watcher — but the current cozyreport bundle does not include any source-watcher artifacts, so we cannot tell whether the OCIRepository fetch is the bottleneck, the ArtifactGenerator queue is starved, or something else entirely. Add a dedicated source-watcher section to hack/cozyreport.sh that captures: - all ArtifactGenerators (list + full YAML) - all OCIRepositories (list + full YAML) - source-watcher pod listing and full logs (current and previous) - events in cozy-fluxcd namespace Signed-off-by: Ivan Okhotnikov <ivan.okhotnikov@aenix.io> |
||
|
|
d1ee1540eb |
test(ci): seed branch for E2E flake hunt
No-op trailing newline in hack/cozytest.sh to trigger the full Pull Request workflow on a clean main, so subsequent commits on this branch can iterate on real E2E flakes. Signed-off-by: Ivan Okhotnikov <ivan.okhotnikov@aenix.io> |
||
|
|
42ca3e6bd3 | test(api): add read-path authorization tests for TenantNamespace registry (#2864) | ||
|
|
25a6b56fab |
docs(release): add changelog for v1.4.3 (#2841)
This PR adds the changelog for release `v1.4.3`.
✅ Changelog has been automatically generated in
`docs/changelogs/v1.4.3.md`.
|
||
|
|
1b7e74fb87 |
docs: add changelog for v1.4.3
Signed-off-by: cozystack-ci[bot] <274107086+cozystack-ci[bot]@users.noreply.github.com> |
||
|
|
8348f96b14 |
chore(seaweedfs): bump SeaweedFS to 4.31 (#2834)
## What this PR does Re-vendors the SeaweedFS Helm chart from 4.05 (chart 4.0.405) to 4.31 (chart 4.31.0) via `make update`, and adapts the package layer to the breaking changes that accumulated across the gap. What lands with the bump: the PostgreSQL filer store (the backend used here) moves to pgx v5 with PgBouncer support and an `ON CONFLICT` upsert default; HTTPS certificates hot-reload without a pod restart on cert-manager rotation; more S3 and volume Prometheus metrics are exported, and the S3 Service now exposes a metrics port by default. Package-layer adaptations: - **Dropped `s3-traffic-distribution.patch`.** Native `s3.trafficDistribution` support landed upstream in chart 4.12. The native field is gated to Kubernetes >= 1.31 and auto-converts `PreferClose` to `PreferSameZone` on >= 1.35, which the hardcoded patch did not do. The patch is replaced with `s3.trafficDistribution: PreferClose` in the package values, plus helm-unittest coverage pinning all three version cases. - **Migrated `global.*` to `global.seaweedfs.*`.** Chart 4.18 namespaced the app-specific globals to avoid polluting the shared global namespace when the chart is used as a subchart. The package values move `enableSecurity`, `serviceAccountName`, `extraEnvironmentVars` and `monitoring` to the new path instead of relying on the chart's backward-compat shim. Verified with `helm template` and `helm unittest`: the cluster env vars reach the pods, the gRPC mTLS `security.toml` renders intact (including the new admin/worker sections from the 4.24/4.26 security tightening), and both retained patches still apply. ### Screenshots N/A — no UI changes. ### Release note ```release-note chore(seaweedfs): bump SeaweedFS to 4.31 ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * OpenShift support and dedicated values for secure deployments * S3 Iceberg gateway with optional ingress and service settings * Volume UI ingress support and RocksDB variant guidance * Grafana: new “Upload Errors” panel * **Bug Fixes** * Improved S3 traffic distribution handling across Kubernetes versions * **Documentation** * Expanded README: DB init, admin secret options, worker job types, OpenShift notes * **Chores** * Helm chart bumped to 4.31.0; values restructured for SeaweedFS and COSI bucket class simplified <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
0c1b438ef3 |
fix(api): close TenantNamespace watch result channel on termination
The Watch goroutine never closed the events channel it owns, and ProxyWatcher.Stop does not close the channel it proxies. When the backing watcher ended, consumers ranging over ResultChan blocked until their own timeout instead of seeing end-of-stream. Closing the channel on goroutine exit matches the watch.Interface contract and the pattern already used by the tenantmodule and application registries. Goroutine-termination tests now assert the result channel closes. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> |
||
|
|
f546696dfb |
test(api): extract fake-watcher stop polling into a helper
Four goroutine-termination tests duplicated the same IsStopped polling loop; waitForFakeWatcherStop keeps the deadline logic in one place. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> |
||
|
|
9c1c23bdaf |
test(api): share one scheme helper across TenantNamespace registry tests
Manual runtime.NewScheme setups in the List/Get error-path tests silently discarded AddToScheme errors; the shared helper fails the test instead. Renamed from newWatchTestScheme since it now serves both test files. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> |
||
|
|
13e310fe71 | Merge branch 'main' into chore/seaweedfs-4.31 | ||
|
|
6fc2606111 |
test(api): add read-path authorization tests for TenantNamespace registry
Cover the per-event filtering paths of REST.Watch directly instead of only transitively through the bookmark test: authorization filtering via RoleBindings, privileged groups (system:masters, cozystack-cluster-admin), non-tenant namespace and foreign object drops, backing bookmark forwarding, field and label selector filtering, missing user rejection, access-check error resilience, ADDED deduplication by resourceVersion, terminating bookmark flush on backing watcher close, and goroutine shutdown on Stop and context cancellation. Also close the remaining gaps on the sibling read paths sharing the same authorization logic: List/filterAccessible (per-user filtering, privileged groups, missing user, RoleBindings outside the tenant set, backing list errors) and the Get error branches. Branches the controller-runtime fake client cannot drive (backing bookmarks, non-Namespace objects, chosen resourceVersions, backing watch errors) are exercised through a stub client.WithWatch backed by a test-owned FakeWatcher. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> |
||
|
|
c6f6fee617 |
docs(gpu-operator): document passthrough variant host-driver prerequisite (#2767)
## What this PR does Documents an undocumented prerequisite of the `cozystack.gpu-operator` `default` (passthrough) variant: it assumes the GPU is not already claimed by a host-installed NVIDIA driver. The chart's `vfio-manager` DaemonSet runs the upstream NVIDIA `k8s-driver-manager` init container, which is hard-coded to exit non-zero and label the node `nvidia.com/gpu.deploy.driver=pre-installed` the moment `chroot /host nvidia-smi` returns a driver version (a `PATH` lookup, not a fixed file path). There is no env-var override in `k8s-driver-manager` v0.10.0, so today the only working path is a clean host. The new "Passthrough variant: host preparation" section in `docs/gpu-vgpu.md` covers: - The exact symptom (init log + sandbox-validator crashloop + `lspci` evidence) so operators can recognise it without spelunking. - A clean-host workaround: a build-from-installed purge list (`dpkg-query`, so a zero-match pattern can't silently abort the whole `apt` transaction the way a hardcoded `'cuda-*'` pattern does on a host without the CUDA repo), `/etc/modprobe.d/blacklist-nvidia.conf` (incl. `nvidia_peermem`), `update-initramfs -k all`, reboot. Warns that `autoremove` is dangerous on converged GPU + NVIDIA DOCA / Mellanox hosts because of the shared `nvidia-` prefix. - Post-reboot verification (no `nvidia` modules loaded, no `nvidia-smi` on `PATH`, no leftover NVIDIA DKMS module) and a label-cleanup step — the DaemonSet pods crash-loop and retry on their own, but the operator's success path never resets the `pre-installed` label, so it must be cleared manually. - A "Known limitation" subsection that names the upstream constraint and the planned mitigation path (upstream env-var override + a future `container` variant of the package). Linked to #2763. ### Release note ```release-note docs(gpu-operator): document that the passthrough (`default`) variant requires a clean host and explain how to purge a pre-installed NVIDIA host driver before enabling the variant. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Renamed GPU guide to cover passthrough and vGPU scenarios; updated verification/last-verified info. * Added passthrough host-preparation and troubleshooting, including a clean-host workaround with driver removal, kernel-module blacklist, initramfs update, reboot, and steps to force a fresh device bind. * Clarified FORCE_REINSTALL does not bypass early host-driver detection and noted pending mitigation paths. * Updated OS support table: passthrough requires clean-host on Ubuntu 20.04/22.04/24.04; Ubuntu 26.04 includes usr-merge notes and vGPU marked as caution (⚠️). <!-- end of auto-generated comment: release notes by coderabbit.ai -->v1.5.0-rc.2 api/apps/v1alpha1/v1.5.0-rc.2 |
||
|
|
cd36b0d3e3 |
docs(gpu-operator): document passthrough variant host-driver prerequisite
The vfio-manager DaemonSet's k8s-driver-manager init container exits non-zero and labels the node pre-installed whenever it finds a host NVIDIA driver, leaving vfio-pci unbound and the sandbox validator in CrashLoopBackOff. Upstream offers no override knob, so the only working path today is a clean host. Document the symptom (including the Init:CrashLoopBackOff pod state), a build-from-installed purge plus module-blacklist workaround (with the autoremove-vs-DOCA warning and the apt zero-match-abort caveat), the post-reboot verification, and clearing the stale pre-installed label that the operator's success path never resets. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> |
||
|
|
1d747b3d8d |
feat(platform): auto-wire KubeVirt permittedHostDevices and HostDevices feature gate when gpu-operator is enabled (#2768)
Closes #2765. ## What this PR does Makes the passthrough and vGPU variants of `cozystack.gpu-operator` produce a KubeVirt CR that is immediately usable, without an out-of-band `kubectl patch`. Today the gpu-operator stack reports green, `nvidia.com/<model>` shows up under node `.status.allocatable`, but every `VirtualMachine` referencing the resource via `domain.devices.hostDevices` is rejected by the admission webhook because `HostDevices` is not in `developerConfiguration.featureGates` and the resource is not listed under `permittedHostDevices`. That manual reconciliation is now driven by the platform. The change has three parts. `packages/system/kubevirt` now parameterizes the KubeVirt CR template. `developerConfiguration.featureGates` keeps its existing static defaults and appends `.Values.extraFeatureGates`; `spec.configuration.permittedHostDevices` renders verbatim from `.Values.permittedHostDevices` when set; `spec.configuration.mediatedDevicesConfiguration` does the same. Defaults stay empty so the rendered CR is byte-identical to the previous template when no overrides are supplied. The chart's `make update` target re-applies the four sed transforms after fetching the upstream CR, so refreshing the KubeVirt release version stays a single command. `packages/core/platform` adds two defaults tables under `files/`. `gpu-passthrough-defaults.yaml` covers Hopper (H100/H200), Ada Lovelace (L4/L40/L40S), Ampere (A100 PCIe/SXM, A40, A30, A10), Turing (T4) and Volta (V100/V100S); PCI vendor:device pairs are stable, `resourceName` slugs follow the `<arch>_<model>_<form>_<mem>` convention `nvidia-sandbox-device-plugin` generates as of v25.x, and `externalResourceProvider: true` is set everywhere because those resources are advertised by the sandbox plugin rather than KubeVirt's in-tree device plugin. `gpu-vgpu-defaults.yaml` covers the Pascal–Ampere mdev profile range (A100-40C/80C, A40-24Q/48Q, A30-24C, A10-24Q, V100D-32C, T4-16Q) — the same family span the upstream `vgpu-device-manager` walks `/sys/class/mdev_bus/` for. Ada Lovelace and Blackwell SR-IOV vGPU is intentionally out of scope for the default table and is expected to be advertised via the user-override hook described below. `templates/bundles/iaas.yaml` now selects between the two tables via a new `bundles.iaas.gpuOperatorVariant` knob (`default` for passthrough, `vgpu` for mediated; default is passthrough). The same variant flows through to the gpu-operator Package CR, replacing the previous always-default call. When the package is enabled, the platform injects `HostDevices` into `extraFeatureGates` plus the chosen defaults into `permittedHostDevices` for the kubevirt component's values. Operators extend or replace via top-level platform values: ```yaml gpu: replaceDefaults: false # append (default) vs. wipe-and-replace permittedHostDevices: pciHostDevices: [] # cluster-specific PCI passthrough entries mediatedDevices: [] # cluster-specific vGPU entries mediatedDevicesConfiguration: {} # extra mediated-device types / per-node mappings ``` `docs/gpu-vgpu.md` is updated to drop the manual `kubectl patch` step and describe the auto-wiring contract, the per-variant defaults, the escape hatches, and the sandbox-device-plugin `resourceName` slug convention so an operator whose node publishes a different name knows where to override. ## Test coverage `helm-unittest` covers eight branches in `packages/core/platform/tests/bundles_gpu_kubevirt_wiring_test.yaml`: gpu-operator disabled emits no extras; gpu-operator enabled at default variant emits the HostDevices gate plus `pciHostDevices`; vgpu variant switches to `mediatedDevices` plus `mediatedDevicesConfiguration`; user overrides append by default; `replaceDefaults: true` wipes the platform table; an unknown variant fails fast with a descriptive error. `packages/system/kubevirt/tests/kubevirt_cr_test.yaml` pins the empty-values render and the three populated shapes at the CR template level. `make helm-unit-tests` passes locally on the full repo. ### Release note ```release-note feat(platform): the KubeVirt CR is now auto-configured for GPU host-device passthrough (HostDevices feature gate plus permittedHostDevices populated from NVIDIA defaults) whenever cozystack.gpu-operator is in bundles.enabledPackages. Operators no longer need a manual kubectl patch to make VMs schedule against a GPU. The variant is selected via bundles.iaas.gpuOperatorVariant (default: passthrough; vgpu: mediated). Extend or replace the NVIDIA defaults via .gpu.permittedHostDevices and .gpu.replaceDefaults. **Upgrade note:** the bundle now OWNS spec.configuration.permittedHostDevices on the KubeVirt CR. If you previously hand-edited it (kubectl edit kubevirt), the first reconcile after upgrade overwrites your manual entries with the rendered NVIDIA default table. Before upgrading, move any custom entries into .gpu.permittedHostDevices (set .gpu.replaceDefaults: true to keep only your list) and verify each resourceName against what your nodes advertise (kubectl describe node <node> | grep nvidia.com/) — the default table now carries the exact slug the sandbox device plugin generates, which upper-cases the card's PCI-IDs name and keeps the Tesla/GL tokens (e.g. a Tesla T4 is nvidia.com/TU104GL_TESLA_T4), but a different plugin build can publish a different string; a mismatch makes the admission webhook reject GPU VMs on their next restart or migration. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Bundle-managed GPU Operator with selectable passthrough vs vGPU variants; platform now injects GPU settings into KubeVirt and forwards vGPU manager overrides when enabled. * Default platform mappings for PCI passthrough and vGPU mediated-device resources added. * **Documentation** * Expanded GPU/vGPU deployment guide with bundle-managed vs manual guidance, wiring/precedence rules, resource-name conventions, and verification steps. * **Tests** * New and expanded tests for bundle→KubeVirt wiring, variant behaviors, default/override merging, and idempotent template parameterization. * **Chores** * Added Helm test target and hardened KubeVirt template update/idempotency checks. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
b6108039b7 |
feat(gpu-operator): add container variant for preinstalled host driver (#2766)
## What this PR does Add a third architectural variant to `cozystack.gpu-operator` for hosts where the NVIDIA driver and `nvidia-container-toolkit` are already installed by the distro package manager (the typical Ubuntu/Debian GPU node shape: apt-installed driver + apt-installed CT). The existing variants do not produce a working setup in that shape. `default` (passthrough) runs `vfioManager`, which unbinds the host driver to bind `vfio-pci` — with the host driver active, `k8s-driver-manager` early-exits and the bind never happens, so the GPU never reaches a pod. `vgpu` requires the proprietary NVIDIA vGPU host driver and a license server; the user-installed open or proprietary driver does not satisfy it. The new `container` variant maps onto upstream gpu-operator's third mode (`sandboxWorkloads.enabled=false` + `devicePlugin.enabled=true`) with every component that would conflict with the host install switched off: - `driver.enabled=false` — use the host driver. - `toolkit.enabled=false` — host already has `nvidia-container-toolkit`. Enabling the operator's toolkit DaemonSet would overwrite the `/etc/containerd/config.toml` the host package configured (via `nvidia-ctk runtime configure`), breaking the host runtime wiring. Matches NVIDIA's documented guidance and the existing `examples/values-native-talos.yaml` reference, which pins the same flag for the same reason. - `devicePlugin.enabled=true` — publish `nvidia.com/gpu` to the kubelet. - `vfioManager.enabled=false` — do not unbind the host driver. Upstream renders this `true` unconditionally; it only stays at zero pods because no node carries a `vm-passthrough` sandbox assignment when `sandboxWorkloads` is off — pinned off so the rendered policy matches intent. - `cdi.enabled=false` — upstream defaults this `true`, but CDI spec generation is serviced by the operator-managed toolkit DaemonSet this variant disables. An apt host doesn't auto-generate CDI specs (`nvidia-ctk cdi generate` is a manual step) and uses the classic `nvidia-container-runtime` hook, so leaving CDI on would point the device plugin at CDI injection with no specs on the host and silently break allocation. - `sandboxWorkloads.enabled=false` — regular pod workloads, not VMs. With this off, upstream `defaultWorkload` is already `container`, so no override is needed. Host prerequisite: because the operator's toolkit component is disabled, the node's containerd must already have the `nvidia` runtime registered (`nvidia-ctk runtime configure --runtime=containerd`); `apt install nvidia-container-toolkit` installs binaries only and does not wire containerd. With `driver.enabled=false` the operator uses the pre-installed host driver at its standard location, so a stock apt install needs no `hostPaths.driverInstallDir` override (Talos lands the driver under a non-standard prefix — see `examples/values-native-talos.yaml`). The bundle wiring (`packages/core/platform/templates/bundles/iaas.yaml`) already enables `cozystack.gpu-operator` as an optional package — no bundle changes required. Users opt into this variant by setting `variant: container` in their `Package` CR. Verified locally with `helm template` against `values.yaml` + `values-container.yaml`: ClusterPolicy renders with `driver.enabled=false`, `toolkit.enabled=false`, `devicePlugin.enabled=true`, `vfioManager.enabled=false`, `cdi.enabled=false`, `sandboxWorkloads.enabled=false`. Platform `helm unittest` and the `gpu-operator` variant bats suite are green. Not yet smoke-tested on a real Ubuntu GPU node — the config matches the documented apt-host wiring (legacy `nvidia-container-runtime` hook, CDI off), but an end-to-end CUDA-pod run on hardware is still owed. Closes #2764 ### Release note ```release-note feat(gpu-operator): add `container` variant for hosts with a pre-installed NVIDIA driver and nvidia-container-toolkit, exposing containerized GPU workloads via the standard NVIDIA device plugin without VFIO passthrough or vGPU. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a "container" GPU operator variant to support containerized GPU workloads on hosts with the NVIDIA driver and nvidia-container-toolkit. * **Documentation** * Updated variants documentation and OS support table to include the container variant and per-host requirements (including Ubuntu and Talos notes). * Clarified Talos native pod example wording. * **Tests** * Added regression tests to verify container-variant configuration and component enablement/disablement. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
fb070ffa21 |
feat(hack): rewrite check-readiness as a Go command with test suite (#2755)
## What this PR does Replaces the `hack/check-readiness.sh` diagnostic with a Go command at `cmd/check-readiness`, integrated into the build the same way as `cozypkg`: - `make check-readiness` builds `_out/bin/check-readiness`, with `Version` injected via `-ldflags`. - The tool shells out to `kubectl` and reports only **non-ready** cozystack / Flux / Kubernetes resources, parsed from status conditions (not column heuristics). Flags: `--wait`/`--timeout`, `--watch`, `--core`, `--parallel`, `--verbose`, `-n`/`-l` scoping, `--no-color`, `--version`. Adds a black-box golden test suite under `test/check-readiness/`: - A mock `kubectl` serves canned fixtures; the suite diffs stdout/stderr/exit against golden files across 30 scenarios (arg parsing, condition/phase/suspend handling, CRD-not-installed, kubectl arg propagation, wait/timeout, edge cases) plus a PTY test for color output. - `make test-check-readiness` runs it; wired into `make unit-tests`. Supersedes #2740, which added the same feature set to the bash script. ### Release note ```release-note feat(hack): replace check-readiness.sh with a `check-readiness` Go command (build with `make check-readiness`); reports non-ready cozystack/Flux/Kubernetes resources with --wait/--watch/--core/--parallel/--verbose and namespace/label scoping. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Added `check-readiness` command for verifying Kubernetes resource readiness status across namespaces * Supports multiple execution modes: one-shot, wait with timeout, and watch with configurable intervals * Includes namespace filtering, label selectors, parallel resource fetching, and color output control * **Tests** * Added comprehensive test suite validating command behavior across various scenarios <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/cozystack/cozystack/pull/2755?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
8b4d31866e |
ci: add Go-scoped Renovate configuration (#2850)
## What this PR does Raises the OpenSSF Scorecard **Dependency-Update-Tool** check (was **0/10**) by adding a Renovate configuration at `.github/renovate.json`. Scorecard scores this check on the presence of a recognized config file, so the check turns green as soon as this merges. ### Why Renovate (not Dependabot) Renovate is already the cozystack org standard — it actively raises and merges PRs on `cozystack/ansible-cozystack`, and `cozystack/talm` + `cozystack/boot-to-talos` already ship `renovate.json`. The Renovate GitHub App is authorized on the org, so this config is picked up **without creating a new credential**. There is no Dependabot usage anywhere in the org. > If the app installation is scoped to "selected repositories", `cozystack/cozystack` needs to be toggled into the existing installation by an org admin — a one-click change, not a new auth artifact. The Scorecard check does not depend on that toggle. ### Scoped to Go dependencies only (per the 2026-05-27 decision) - **`enabledManagers: ["gomod"]`** — Renovate only ever touches Go modules. It will **not** open PRs for Dockerfiles, Helm charts, shipped component/operator versions, or GitHub Actions. Components and images stay hand-managed. - **`gomodTidy` + `gomodUpdateImportPaths`** run after each bump. The `gomod` manager auto-discovers both the root module and the `api/apps/v1alpha1` submodule. - **Kubernetes libraries grouped** (`sigs.k8s.io/controller-runtime` + `k8s.io/**`) so `client-go`/`apimachinery` never bump out of lockstep with controller-runtime (a split bump fails to compile). - **Go toolchain directive pinned** (`matchDepTypes: ["golang"]` disabled) — `go.mod` is generated and a toolchain bump needs a matching `setup-go` + codegen run, out of scope for an automated PR. ### "do not merge" on every bot PR (requirement) - `automerge` and `platformAutomerge` are **off**. - Every Renovate PR is labeled **`do-not-merge/hold`** + **`automated`** on creation. Both labels already exist in `.github/labels.yml`; `stale.yaml` already exempts `do-not-merge/hold` PRs from auto-close, so held bumps won't rot. - Bumps therefore stay parked until tests pass and a maintainer removes the label after a hands-on check. > Note: `do-not-merge/hold` is a **signal/marker** (cozystack uses native GitHub, not Prow Tide). Combined with automerge being off, nothing can auto-merge a held PR. Hard merge-blocking would need a status check tied to the label, which would require branch-protection changes that were explicitly excluded. ### Verification - `renovate-config-validator` (Renovate v42): **Config validated successfully.** - Valid JSON. ### Release note ```release-note ci: add a Go-scoped Renovate configuration (gomod only, no automerge, bot PRs held with do-not-merge/hold) to automate Go dependency updates ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Configured automated dependency management for Go modules, including synchronized updates for Kubernetes-related dependencies, rate limiting on concurrent updates, and manual approval requirements to ensure controlled rollouts. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
28dfb40579 |
fix(dashboard): grant tenant dashboard read on cozy-public PVCs (#2843)
## What this PR does The vm-disk **source image** dropdown in the console lists `PersistentVolumeClaims` in `cozy-public` (the `vm-default-images-*` golden images) to offer them when creating a VM disk. That list is issued by the browser as the **tenant identity** (e.g. `system:serviceaccount:tenant-root:tenant-root`), but the `cozy:tenant:dashboard` Role in `cozy-public` only granted read on Flux `helmrepositories`/`helmcharts`. The list returned `403 Forbidden` and the dropdown stayed empty even when images had been enabled (e.g. via `bundles.enabledPackages`). This is identity-dependent, which is why it can look intermittent: a cluster-admin login bypasses tenant RBAC and sees the images, while any real tenant — including `tenant-root-super-admin` — hits the 403. This PR adds `get`/`list`/`watch` on `persistentvolumeclaims` to the Role. `cozy-public` is a shared public namespace, so read on its PVCs is safe to expose to every tenant dashboard subject (the Role is already bound to the tenant groups and the tenant ServiceAccount). A helm-unittest regression pins the grant. Verified with `kubectl auth can-i list pvc -n cozy-public`: - `--as=system:serviceaccount:tenant-root:tenant-root` → no (before this PR) - `--as-group=tenant-root-super-admin` → no (before this PR) - cluster-admin → yes (why it "worked" for admins) No console code changed — this is an RBAC-only fix. ### Release note ```release-note fix(dashboard): allow tenant dashboards to list VM images in cozy-public, fixing the empty source-image dropdown when creating a vm-disk ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Dashboard access permissions have been expanded to include read-level access to persistent volume claims, enabling users to manage and select images through the dashboard more effectively. * **Tests** * Added test suite to verify dashboard role permissions are correctly configured and functional. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
d037ff3c71 |
fix(platform): order cozystack-basics after the APIs its admission policies type-check (#2842)
## What this PR does `cozystack-basics` ships cluster-wide ValidatingAdmissionPolicies — the gateway / route / tenant host policies — that match `tenants.apps.cozystack.io` (served by `cozystack-api`) and the Gateway API types (`gateways`, `httproutes`, `tlsroutes`). kube-controller-manager's ValidatingAdmissionPolicy *status* type-checker panics with a nil-pointer dereference in `k8s.io/apiserver/pkg/cel/openapi` when it type-checks a policy against a matched type whose schema it cannot resolve yet. During a cold install this is a deadlock. `cozystack-basics` can install before `cozystack-api` is Ready; the tenant-host policy is created; KCM panics type-checking it against the not-yet-served `tenants` schema and CrashLoops; that stalls cert/token issuance, so `cozystack-api` never starts and the schema never resolves — KCM keeps crashing and every HelmRelease times out on `DependencyNotReady`. This adds `dependsOn` edges so `cozystack-basics` installs only after `cozystack-engine` (which serves `tenants.apps.cozystack.io` via `cozystack-api`) and `gateway-api-crds` are Ready. The policies are then never created before the APIs they type-check are served. A template capability gate (`.Capabilities.APIVersions.Has`) was considered and rejected: operator-generated HelmReleases run without drift detection, so helm-controller is checksum-driven — it would render the policy out at first install and never re-add it once the API appears, silently dropping the protection. Ordering via `dependsOn` gates the first install on Ready, which is reliable. No cycle risk: nothing `dependsOn` `cozystack-basics`, and no `cozystack-engine` component consumes a `cozystack-basics` output, so engine becomes Ready independently. Includes a helm-unittest regression test asserting the `dependsOn` edges stay. The underlying nil-pointer panic is an upstream Kubernetes bug (the best-effort status type-checker should never crash kube-controller-manager); it is unguarded as of the current release and will be reported separately. This change removes the trigger on the cozystack side. ### Release note ```release-note fix(platform): order cozystack-basics after the APIs its admission policies type-check, fixing a cold-install deadlock where kube-controller-manager could crash type-checking policies against APIs (tenants, Gateway API) not yet served ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **Bug Fixes** * Improved cluster stability by implementing proper dependency ordering for validation policies during initialization, preventing resource configuration conflicts. * **Tests** * Added integration tests to validate cluster-wide policy dependencies and ordering, ensuring continuous system stability verification. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
5982018629 |
feat(monitoring): add tenant overview dashboard (#2809)
## What Adds a cross-tenant **Tenant Overview** dashboard to the root/infra Grafana. ## Why Platform admins currently have no single view of which tenants consume what. This shows resource utilization, app counts, and health per tenant at a glance. ## Panels - **Fleet summary** — tenants, apps, running pods, CPU/memory/storage totals - **Tenant leaderboard** — per-tenant table: CPU & memory (usage vs requests), storage, pods, PVCs, app count, not-ready apps - **Top consumers** — Top-N bar gauges by CPU, memory, storage, app count - **Trends** — per-tenant CPU & memory usage over time - **Health** — pod restart rate and not-ready HelmReleases per tenant ## Details - Registered in `dashboards-infra.list` only, so it deploys to the root/`cozy-monitoring` Grafana and never exposes cross-tenant data on per-tenant instances (same pattern as `gpu/gpu-tenants`). - Apps are counted from `gotk_resource_info` HelmReleases, excluding the paired `*-system`/`cozy-*` charts. - Uses only standard cadvisor / kube-state-metrics / Flux metrics. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## New Features * Added a Tenant Overview dashboard for comprehensive tenant infrastructure monitoring, including: * Fleet summary with tenant counts, app deployments, and aggregate resource usage * Tenant performance leaderboard with CPU, memory, and storage metrics * Top consumers visualization by resource type and app count * Historical usage trends and pod health metrics <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
c74964bf25 |
docs(release): expand release.md into release-engineer playbook (#2732)
## What this PR does Expands `docs/release.md` from a ~165-line process-design doc into a ~573-line single source covering both the process design (kept verbatim — mermaid diagrams and the RC/regular/patch numbered walkthroughs) and the operational playbook for cutting a release. New sections added under the existing diagrams: - **What CI does on a tag push** — phase-by-phase walk through `tags.yaml` (`prepare-release`, `generate-changelog`, `update-website-docs`), `pull-requests-release.yaml` (finalize), and `update-releasenotes.yaml` (sync release body). - **Automated patch tag** — `auto-release.yaml`'s nightly behavior. - **Backports** — bot mechanism, recursive-backport regression history (fix #2584), manual fallback, cherry-pick triage include/skip rule, monotonic-counter trap for migration backports. - **Changelogs** — pointer to `agents/changelog.md` plus the recurring failure modes that have shipped in real changelogs, with a verification snippet (chart-bump coverage, range-vs-changelog PR sweep, URL liveness, website PR-author check). - **Release-time fires** — three customer-impacting regressions (v1.3.0 cert-manager ingressClassName, v1.4.0 kubevirt-instancetypes upgrade, v1.4.0 Flux 2.7 → 2.8 readiness deadlock) with "what would have caught this" notes. - **Pre-release verification checklist**, **CI failures release engineers commonly hit**, **Force-retagging** (with open RFC #2677 context), **Splitting a release-blocking bundle PR**, **Cleanup after release**. Example version numbers across the walkthrough switched from the upstream `v0.42.x` legacy placeholders to simple semver-style (`v1.2.0`, `v1.2.1`, `release-1.2`) consistent with semver.org examples and applicable at any point in v1.x. ### Release note ```release-note NONE ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Enhanced release process documentation with clarified CI workflows, comprehensive verification checklists, expanded troubleshooting guidance, and case studies to ensure more reliable releases. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/cozystack/cozystack/pull/2732?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
c961ffe286 |
feat(ci): test-impact analysis E2E (default-on) + release E2E workflow (#2559)
## What this PR does
Adds **test-impact analysis (TIA)** to the PR E2E workflow so that only
bats files affected by the diff are exercised, and a **release-time E2E
workflow** that runs the full suite on every release tag to keep
coverage on the shipped code.
### 1. Test-impact analysis selector — `hack/select-e2e.sh`
Reads `packages/core/platform/sources/*.yaml` (the PackageSource
dependency graph) and emits the bats files affected by a PR diff. The
walk is the same one `cozypkg dot` renders.
| Input | Output |
|---|---|
| `packages/apps/postgres/values.yaml` | `postgres` |
| `packages/system/postgres-operator/...` | `postgres harbor`
(transitive) |
| `packages/system/cilium/...` | full suite (cilium has many transitive
`*-application` dependents) |
| `packages/library/cozy-lib/...` | full suite (library affects
everything) |
| `hack/e2e-install-cozystack.bats` | full suite (shared install affects
all apps) |
| `hack/e2e-apps/redis.bats` | `redis` (per-app edit, never escalates) |
| `docs/agents/contributing.md` | (empty — no E2E impact) |
| `dashboards/gpu/gpu-fleet.json` | (empty) |
Conservative fallbacks:
- An unrecognised `packages/*` path or a system source with no
`*-application` descendants escalates to the full suite, so a path
inside the graph is never silently dropped.
- Per-app bats edits are matched **before** the full-suite trigger, so
editing one bats file selects only that app rather than escalating.
### 2. Default-on TIA + `full-e2e` opt-out — `pull-requests.yaml`
- The `Select E2E tests` step runs by default and produces `apps` +
`skip` outputs.
- `Run E2E tests` runs unless `skip=true` (docs / dashboards / `*.md`
only).
- Adding the `full-e2e` label skips the selector step (output empty) and
falls through to the unchanged full-bats-list path — the safety hatch
when reviewers want the full suite on a PR.
### 3. `release-e2e.yaml` — full E2E on every release tag
Triggered on `v*.*.*`, `v*.*.*-rc.*`, `v*.*.*-beta.*`, `v*.*.*-alpha.*`.
Builds images + Talos image, then runs the full suite. Independent of
`tags.yaml`'s release publish flow — failure here is an alarm, not a
release blocker.
This closes the coverage gap left by making PR runs default to TIA: the
full suite is now exercised at tag-cut time against the shipped code.
## Tests
`hack/select-e2e_test.bats` covers 11 cases — single app, transitive
operator, networking/library full-suite, docs/dashboards skip,
`kubernetes-application` two-bats mapping, shared E2E helper, install
bats, per-app bats no-escalation, release-e2e workflow change.
YAML parse verified for both new workflow files.
## Workflow injection safety
All `${{ }}` contexts that could carry untrusted data
(`github.base_ref`, label arrays) flow through env vars, never directly
into shell text. `github.workspace` is also indirected through
`WORKSPACE` env in the release workflow.
### Release note
```
NONE
```
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* CI now runs a Test Impact Analysis to pick only affected apps,
reducing E2E runtime.
* Added a release-level E2E workflow that runs full acceptance tests for
version tags with deterministic sandboxing, artifact capture,
conditional retries, teardown, and report upload.
* E2E jobs accept selected-apps metadata and support conditional
full-suite overrides.
* **Tests**
* New test suite validates selection logic, operator expansion,
full-suite escalation, and docs-only/no-op behaviors.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
||
|
|
931f286b9f |
fix(ingress,platform): deliver publishing.proxyProtocol to host ingress-nginx (#2799)
## What this PR does `publishing.proxyProtocol: true` is documented to enable PROXY-protocol on the host ingress-nginx, but the three nginx config keys (`use-proxy-protocol`, `real-ip-header`, `enable-real-ip`) never reached the controller. They were written into the `ingress-system` component values of the `cozystack.ingress-application` Package, but that component has no `install:` block, so the package controller never builds a HelmRelease for it and the values are dropped. External traffic behind a PROXY-protocol L4 load balancer kept breaking. This routes the switch through `_cluster.proxy-protocol` in the `cozystack-values` Secret — the same path `expose-external-ips` already travels to reach the host ingress release — and the `extra/ingress` chart applies it to the external-facing ingress (the namespace named by `expose-ingress`). The dead component-values injection is removed. The `extra/ingress` chart also gains a first-class `proxyProtocol` value, so PROXY-protocol can be enabled per Ingress application (a per-app boolean knob like `cloudflareProxy`). Two `fail` guards keep the new knobs safe: the per-app `proxyProtocol` value is rejected on the external-facing (host) ingress — that path must go through `publishing.proxyProtocol`, which also installs ouroboros (hairpin handling) and arms the disable-hazard guard — and `cloudflareProxy` + `proxyProtocol` together are refused, since Cloudflare's HTTP proxy does not speak PROXY-protocol to origins and its `real_ip_header` server-snippet would override `real-ip-header: proxy_protocol`. `use-forwarded-headers` / `compute-full-forwarded-for` are deliberately not set: without a paired `proxy-real-ip-cidr` they would let clients spoof `X-Forwarded-For`, and the PROXY-protocol frame is already the source of truth for the client address. helm-unittest coverage pins runtime delivery on the host path, the per-app path, and that non-host tenants stay untouched; the previous test that only checked template emission into the (non-consumed) Package component is replaced. Affected versions: v1.4.0–v1.4.2 and current main (introduced in #2561). **Upgrade note (action required).** Because the flag was a silent no-op before this fix, a v1.4.x cluster that set `publishing.proxyProtocol: true` aspirationally — without its L4 load balancer actually injecting PROXY frames — kept working by accident. After this fix the flag really enables `use-proxy-protocol`, which makes ingress-nginx require a PROXY frame on every connection; if the LB does not send one, all external ingress breaks. Before upgrading, confirm the upstream LB injects PROXY-protocol v1 frames, or unset `publishing.proxyProtocol`. Closes #2796 ### Release note ```release-note fix(ingress): publishing.proxyProtocol now enables PROXY-protocol on the host ingress-nginx (the config was silently dropped before reaching the controller); the Ingress application also gains a proxyProtocol value to enable PROXY-protocol per-app. ACTION REQUIRED on upgrade: the flag was previously a no-op, so a cluster that set it without the upstream LB injecting PROXY-protocol v1 frames will lose all external ingress once it takes effect — verify the LB, or unset publishing.proxyProtocol before upgrading. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Tenant ingress support for PROXY-protocol via a new proxyProtocol option (default: false). * Platform-level publishing.proxyProtocol now controls external/host ingress behavior. * **Bug Fixes / Validation** * Rejects enabling per-app proxyProtocol on external-facing host ingresses. * Rejects using proxyProtocol together with cloudflareProxy to prevent conflicting real‑IP handling. * **Documentation** * Updated docs describing proxyProtocol usage, defaults, and safety requirements. * **Tests** * Added tests covering proxyProtocol wiring, config emission, and failure cases. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
479929ebfb |
ci(e2e): drop 3x retry on Run E2E + Install Cozystack, wait out gateway.bats tenant teardown (#2558)
## What this PR does
Drops the 3× retry loop on `Run E2E tests` and `Install Cozystack into
sandbox`. `Prepare environment` keeps its 3× retry — that step is pure
infrastructure (Talos image download, sandbox VM boot, network) where
transient runner hiccups warrant a retry.
On failure, the test step now captures `kubectl get hr -A -o wide` and
`kubectl get events -A` under a collapsible group so triage starts with
the actual broken-state snapshot.
> [!NOTE]
> An earlier revision of this PR also doubled every bats timeout. That
commit was dropped in a rebase and is intentionally **not restored**:
the timeout class that actually matters (per-app HR-Ready waits) has
since been standardized at 5m on `main` (
|
||
|
|
ed367f30dd |
test(packages/system/seaweedfs): cover s3.trafficDistribution version gating
Pin the native s3.trafficDistribution behaviour that replaced the dropped s3-traffic-distribution.patch: PreferClose is kept on Kubernetes 1.33, auto-converts to PreferSameZone on 1.35, and the field is omitted below the 1.31 gate. Adds the helm-unittest harness (test target + suite) the package previously lacked. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> |
||
|
|
068af203ac |
chore(packages/system/seaweedfs): bump seaweedfs to 4.31
Re-vendor the SeaweedFS Helm chart from 4.05 to 4.31. Improvements that land with the binary: the PostgreSQL filer store moves to pgx v5 with PgBouncer support and an ON CONFLICT upsert default, HTTPS certificates hot-reload without a pod restart on cert-manager rotation, and more S3/volume Prometheus metrics are exported. The S3 service now exposes a metrics port by default. Adaptations: - Drop s3-traffic-distribution.patch: native s3.trafficDistribution support landed upstream in chart 4.12. The native field is gated to Kubernetes >=1.31 and auto-converts to PreferSameZone on >=1.35, which the hardcoded patch did not do. Set s3.trafficDistribution: PreferClose in values instead. - Migrate global.* to global.seaweedfs.* in values: chart 4.18 namespaced the app-specific globals to avoid polluting the shared global namespace when the chart is used as a subchart. Covers enableSecurity, serviceAccountName, extraEnvironmentVars and monitoring. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> |
||
|
|
c32812ccf9 |
fix(platform): deliver publishing.proxyProtocol to host ingress-nginx
The host PROXY-protocol switch wrote the nginx config keys into the ingress-system component values of the cozystack.ingress-application Package. That component has no install block, so the package controller never built a HelmRelease for it and the values were dropped — the host ingress-nginx never received use-proxy-protocol and external traffic behind a PROXY-protocol L4 load balancer kept breaking. Route the switch through _cluster.proxy-protocol in the cozystack-values Secret instead, the same path expose-external-ips travels to reach the host ingress release, and drop the dead component-values injection. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> |
||
|
|
8df4b0b0f1 |
feat(ingress): add proxyProtocol knob to ingress-nginx
Expose a proxyProtocol value on the Ingress application that turns on ingress-nginx use-proxy-protocol with real-ip-header=proxy_protocol and enable-real-ip. The host control plane also enables it through the _cluster.proxy-protocol switch, applied only to the external-facing ingress (the namespace named by expose-ingress). use-forwarded-headers / compute-full-forwarded-for are deliberately not set: without a paired proxy-real-ip-cidr they let clients spoof X-Forwarded-For, and the PROXY-protocol frame is already the source of truth for the client address. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> |
||
|
|
5d72a32e11 |
fix(build): read --cache-from from ghcr :latest so PR builds warm-start (#2855)
## What this PR does [#2711](https://github.com/cozystack/cozystack/pull/2711) moved PR builds to unique `pr-<N>-<sha>` tags (`PUBLISH_FLOATING=0`) to end concurrent-push 409 conflicts, but left `--cache-from` pointing at `$(REGISTRY)/<img>:latest`. PR builds set `REGISTRY` to the per-CI registry, which never publishes `:latest`, so every PR build's registry cache lookup `404`s and rebuilds cold — slowing builds and, on a loaded runner, pushing them into the 30-minute job timeout. This adds a `CACHE_REGISTRY` knob (default `ghcr.io/cozystack/cozystack`, where release builds *do* publish `:latest`) and points every `--cache-from` at it. Push targets (`$(REGISTRY)`) and the unique per-PR tags are unchanged, so #2711's anti-conflict behaviour is preserved while PR builds regain a warm cache from the last release. - `hack/common-envs.mk`: new `CACHE_REGISTRY ?= ghcr.io/cozystack/cozystack` with rationale. - 31 package Makefiles: 37 `--cache-from type=registry,ref=$(REGISTRY)/…` → `$(CACHE_REGISTRY)/…`. Verified with `make -n image REGISTRY=iad.ocir.io/…`: push tag stays on OCIR (`…/cozystack-controller:pr-<N>-<sha>`), cache-from now reads `ghcr.io/cozystack/cozystack/cozystack-controller:latest`. Makefile-only change — no generated artifacts affected. > Note: cache freshness is bounded by the last release's `:latest`. PR-to-PR freshness (a `main`-built `:buildcache` tag) is a possible follow-up, intentionally out of scope here. ### Release note ```release-note fix(build): PR CI image builds now read layer cache from ghcr.io `:latest` (published by releases) instead of the per-CI registry where `:latest` was never pushed after #2711, restoring warm-start caching for PR builds. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Updated Docker build configuration across multiple components to use a dedicated cache registry for improved build layer reuse. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
9868c43e53 |
fix(build): read --cache-from from ghcr :latest so PR builds warm-start
PR #2711 switched PR builds to push only :pr-<N>-<sha> tags (PUBLISH_FLOATING=0) to stop concurrent-push 409 conflicts, but left --cache-from pointing at $(REGISTRY)/<img>:latest. PR builds set REGISTRY to the per-CI registry (iad.ocir.io) and never publish :latest there, so every PR build's registry cache lookup 404s and rebuilds cold. Add a CACHE_REGISTRY knob (default ghcr.io/cozystack/cozystack, where release builds do publish :latest) and point every --cache-from at it. Push targets ($(REGISTRY)) and the unique per-PR tags are unchanged, so the #2711 anti-conflict behaviour is preserved while PR builds regain a warm cache from the last release. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com> |
||
|
|
ac56d4cea1 |
ci: add Go-scoped Renovate configuration
Raise the OpenSSF Scorecard Dependency-Update-Tool check (was 0/10) by adding a Renovate config. Renovate is already the cozystack org standard (active on ansible-cozystack, talm, boot-to-talos), so the app is authorized org-wide and this file is picked up without a new credential. Scoped deliberately so automated bumps only ever touch Go dependencies: - enabledManagers: [gomod] — Renovate ignores Dockerfiles, Helm charts, shipped component/operator versions, and GitHub Actions. Components and images stay hand-managed, per the 2026-05-27 decision. - gomodTidy + gomodUpdateImportPaths run after each bump; the gomod manager auto-discovers both the root module and the api/apps/v1alpha1 submodule. - controller-runtime and k8s.io/** are grouped so client-go and controller-runtime never bump out of lockstep. - The Go toolchain directive is pinned (matchDepTypes golang disabled): go.mod is generated and a toolchain bump needs a matching setup-go and a codegen run, out of scope for an automated PR. - automerge/platformAutomerge are off and every Renovate PR is labeled do-not-merge/hold + automated, so bumps are held until tests pass and a maintainer reviews them by hand. Validated with renovate-config-validator (config validated successfully). Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com> Assisted-By: Claude <noreply@anthropic.com> |
||
|
|
117d317fd8 |
fix(platform): drop fragile numeric vGPU mediatedDeviceTypes defaults
The vgpu defaults shipped a mediatedDevicesConfiguration block whose mediatedDeviceTypes were hardcoded nvidia-NNN ids. Those ids are not stable identifiers — they are the per-(SKU, driver) indices under which a host enumerates mdev types in /sys/.../mdev_supported_types/, so the same profile gets a different number on a different card or driver branch. Auto-injecting them made KubeVirt try to create mdev types that usually do not exist on the operator's nodes, and duplicated work the GPU Operator's vGPU Device Manager already does. Delegate mdev creation to the vGPU Device Manager, which is name-based and stable, and keep only the name-based permittedHostDevices.mediatedDevices exposure list (mdevNameSelector) in the defaults. KubeVirt-driven creation stays available through the .gpu.mediatedDevicesConfiguration override for operators who supply host-verified type ids; document the lookup recipe. Update the bundle wiring, the gpu/vgpu values comments, the vGPU guide and the tests accordingly (the default vgpu path now emits no mediatedDevicesConfiguration; the override path is covered explicitly). Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> |
||
|
|
919073601d |
fix(platform): match NVIDIA passthrough defaults to the resourceName the plugin advertises
The auto-injected permittedHostDevices only works if each resourceName equals what nvidia-sandbox-device-plugin advertises on the node; otherwise the admission webhook rejects the VM — the very manual step this bundle removes. Several defaults did not match the slug the plugin (nvcr.io/nvidia/kubevirt-gpu-device-plugin) derives from the PCI-IDs database name, which uppercases the name and keeps the die suffix, the Tesla brand and the form-factor/memory tokens: 10DE:1EB8 TU104GL_T4 -> TU104GL_TESLA_T4 10DE:20B7 GA100_A30 -> GA100GL_A30_PCIE 10DE:2331 GH100_H100_PCIE_80GB-> GH100_H100_PCIE 10DE:2321 GH100_H100_NVL_94GB -> GH100_H100L_94GB 10DE:1DB1/1DB4/1DF6 + new 1DB5 -> GV100GL_TESLA_V100_* family Device 10DE:1DB1 was also mislabelled: it is the SXM2 16GB part, not the 32GB one — the 32GB SXM2 V100 is a distinct device (10DE:1DB5) that was missing entirely. Relabel 1DB1 as 16GB and add 1DB5. Restate the header comment as the actual generation rule and pin the corrected slugs (T4, A30, both V100 SXM2 parts) with a regression test that also asserts the tidy/short forms never reappear. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> |
||
|
|
285f42aaeb |
fix(kubevirt): reinsert each GPU CR patch independently in make update
The update target gated the insertion of both the permittedHostDevices and mediatedDevicesConfiguration directives behind a single `grep permittedHostDevices` guard. A hand-merge from a newer upstream KubeVirt CR that dropped only mediatedDevicesConfiguration left permittedHostDevices in place, so the guard skipped the combined sed and mediatedDevicesConfiguration was never reinserted — then the sanity-check tail aborted the target with a non-zero exit instead of self-healing. Split the two directives into independent guarded inserts so each self-heals on its own. Reword the sanity-check failures to name the directive that failed to land and the anchor its insert keys off, instead of implying the anchor itself is missing. Add a shell regression test that strips one directive at a time and asserts `make update` restores it without duplicating the other; the update target gains a CR_FILE override so the test can drive it against a throwaway copy. Wired into the package test target and guarded to skip on BSD sed. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la> |
||
|
|
da0dcf9edf |
test(platform): pin nil-safe GPU bundle wiring against null gpu values
The .Values.gpu accesses in the iaas bundle are fully parenthesized
((( .Values.gpu ).x).y) so the field walk stays nil-safe when an operator
nulls the whole gpu block or leaves a sub-key blank (an empty YAML key
parses to null, which Helm then drops from .Values). A bare dotted walk
would panic with "nil pointer evaluating interface {}.<field>" on the
first nil hop.
Add regression cases covering gpu: null in both passthrough and vgpu
variants plus blank sub-keys, and a comment warning against flattening
the parenthesized form. A mutation check confirms the cases fail with the
nil-pointer panic the moment the parentheses are removed.
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
|
||
|
|
cb3c84e24d |
docs(gpu-vgpu): drop the manual KubeVirt patch step
The platform now wires permittedHostDevices and the HostDevices feature gate into the KubeVirt CR automatically whenever cozystack.gpu-operator is in bundles.enabledPackages. The KubeVirt configuration section now describes the auto-wiring contract, the per-variant defaults, the .gpu.permittedHostDevices / .gpu.replaceDefaults escape hatches, and calls out the sandbox-device-plugin resourceName slug convention so operators know where to look when a node's allocatable name does not match the platform default. Signed-off-by: Aleksei Sviridkin <f@lex.la> |
||
|
|
91efa1f4eb |
feat(platform): auto-wire KubeVirt host-device support when gpu-operator is enabled
When cozystack.gpu-operator is in bundles.enabledPackages, the platform
now mirrors the chosen GPU variant into the KubeVirt CR — the HostDevices
feature gate is appended to developerConfiguration.featureGates, and a
starter permittedHostDevices block is filled from a per-variant defaults
table. Without this, a passthrough-variant GPU shows up under
.status.allocatable on the node but the KubeVirt admission webhook
rejects every VM that references it as a hostDevices.deviceName.
Two defaults tables ship under packages/core/platform/files/:
- gpu-passthrough-defaults.yaml covers Hopper (H100/H200), Ada
Lovelace (L4/L40/L40S), Ampere (A100 PCIe/SXM, A40, A30, A10),
Turing (T4), Volta (V100/V100S). PCI vendor:device pairs are
stable; resourceName slugs mirror nvidia-sandbox-device-plugin's
<arch>_<model>_<form>_<mem> convention. externalResourceProvider
is set on every entry because the resources are advertised by
the sandbox-device-plugin DaemonSet, not by KubeVirt itself.
- gpu-vgpu-defaults.yaml covers the Pascal-Ampere mdev profile
range the upstream vgpu-device-manager walks /sys/class/mdev_bus/
for. Ada Lovelace and Blackwell SR-IOV vGPU is out of scope for
the chart's default list (NVIDIA driver path differs) and is
expected to be advertised via the user-override hook below.
bundles.iaas.gpuOperatorVariant picks which table applies (default =
passthrough, vgpu = mediated). The same variant flows through to the
gpu-operator Package CR, replacing the previous always-default call.
Operators extend or replace the defaults via .gpu in platform values:
gpu:
replaceDefaults: false
permittedHostDevices:
pciHostDevices: [...]
mediatedDevices: [...]
mediatedDevicesConfiguration: {...}
replaceDefaults: false (the default) appends the user list to the
NVIDIA table; true drops the table entirely for clusters with only
non-NVIDIA accelerators or a strict allowlist requirement.
Companion helm-unittest suite covers the eight branches: no gpu
package emits no extras; gpu-operator at default variant emits the
HostDevices gate plus pciHostDevices; vgpu variant emits the
HostDevices gate plus mediatedDevices and mediatedDevicesConfiguration;
user overrides append by default; replaceDefaults wipes; unknown
variant fails fast.
Signed-off-by: Aleksei Sviridkin <f@lex.la>
|
||
|
|
8c507fe4c5 |
feat(kubevirt): parameterize CR for host-device GPU configuration
The KubeVirt CR's developerConfiguration.featureGates list, the
permittedHostDevices block, and mediatedDevicesConfiguration are now
driven by chart values:
- extraFeatureGates is appended after the static default set so
HostDevices (or any other future gate) can be enabled without
forking the template.
- permittedHostDevices is rendered verbatim when set, allowing the
pciHostDevices (passthrough) and mediatedDevices (vGPU) shapes the
KubeVirt admission webhook requires for VMs that reference a GPU
by resourceName.
- mediatedDevicesConfiguration is rendered verbatim when set,
carrying mediatedDeviceTypes / nodeMediatedDeviceTypes for the
vGPU profile auto-creation loop.
Defaults stay empty so the rendered CR is byte-identical to the
previous template when no overrides are supplied.
The Makefile's update target re-applies the four sed transforms
in-place on templates/kubevirt-cr.yaml — it does not download anything
from upstream KubeVirt because the published CR is a near-empty stub
and would clobber the hand-curated cozystack defaults. To refresh from
a newer upstream release the operator hand-merges the new fields into
the in-tree template, then runs `make update` to re-assert the four
cozystack parameterization patches; grep sanity-checks at the end of
the target fail loudly if any anchor (developerConfiguration,
- VMExport, evictionStrategy) gets dropped during the hand-merge.
Companion helm-unittest coverage pins the default render and the
three populated shapes.
Signed-off-by: Aleksei Sviridkin <f@lex.la>
|