Commit Graph
119 Commits
Author SHA1 Message Date
Aleksei Sviridkin 595e9f5b77 test(tests): run the unit and controller lanes with no cluster to reach
A unit test that misses a stub, or whose stub an exec chain steps past,
reaches the cluster of whatever kubeconfig the caller has, locally and
on a CI runner alike. Point KUBECONFIG at /dev/null for unit-tests,
each of its prerequisites and test-controllers, and empty
KUBERNETES_SERVICE_HOST: with an empty kubeconfig client-go falls back
to in-cluster credentials when that variable and a service-account
token are present.

hack/unit-lane-no-cluster.bats runs every prerequisite of unit-tests as
its own goal under a hostile environment and fails if one of them gets
the caller's kubeconfig or service host.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-10-07 15:41:06 +03:00
Myasnikov Daniil cc942fed68 test(tests): run unit suites with Bats and retain POSIX coverage
The shell translator rewrites test bodies and loses Bats reporting.
The unit lane needs complete TAP results and JUnit reports without
changing how live-cluster suites run.

Sourced production helpers still need a dash pass, and unit test bodies
need nounset without exporting shell options into subprocesses.

Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-10-07 11:04:00 +03:00
Myasnikov Daniil a0b1dfb45b feat(release): add per-commit backport audit
Backport labels record requests, and a merged conflict draft may still
lack commits. Release cuts need a check of what reached the branch.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Assisted-by: LLM
2026-10-07 05:19:15 +03:00
Alexey Artamonov 474377e8ca build(monitoring): build the traces collector with a rate limiter
A shared-central traces store needs each tenant held to a write rate,
or one tenant can fill the disk every tenant shares. No upstream
OpenTelemetry distribution or processor limits throughput. Elastic's
EDOT Collector registers Elastic's ratelimitprocessor among its default
components, but it ships under the Elastic License 2.0 and carries far
more components than the chart uses.

The collector is therefore built here with the OpenTelemetry Collector
Builder from the upstream components the chart already uses, at the
same v0.136.0, plus that ratelimitprocessor (Apache-2.0). Its v0.8.2
is the release built against v0.135.0, the last before v0.137.0. The
image is versioned by the collector release with a cozystack respin
counter, runs as the upstream image's user, and is built with the
other package images.

The builder stage is alpine, which has no git, so a proxy error during
its go install would have had neither the direct fallback nor a retry;
the stage installs git. The module-fetch contract saw only go build, so
it now checks go install too, and takes only git as cover for a
versioned go install, which resolves outside the stage's module.

Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
2026-10-06 09:39:04 +03:00
Aleksei Sviridkin 71a943277f build(make): keep running bats unit files after one fails
Each bats file is its own make target, so a plain `make bats-unit-tests`
stopped scheduling files at the first red one and never ran the rest,
while the output looked like a finished suite. CI passes -k and was not
affected; a local run was. The target now runs the files in a
--keep-going sub-make, which still exits non-zero when any file fails
and shares the caller's job slots and output sync.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-10-01 02:09:49 +03:00
Aleksei Sviridkin 5a619c5fdc Merge remote-tracking branch 'origin/feat/velero-kubevirt-plugin-multiarch' into feat/arm64-image-builds
Signed-off-by: Aleksei Sviridkin <f@lex.la>

# Conflicts:
#	Makefile
2026-09-28 19:57:27 +03:00
Aleksei Sviridkin 8c1266649d Merge remote-tracking branch 'origin/feat/harbor-multiarch' into feat/arm64-image-builds
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-09-28 19:49:55 +03:00
Aleksei Sviridkin 79854b16fb Merge remote-tracking branch 'origin/fix/build-list-orphan-images' into feat/arm64-image-builds
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-09-28 19:49:54 +03:00
Aleksei Sviridkin 11991b9d05 feat(harbor): build the Harbor images for amd64 and arm64
Upstream publishes the Harbor v2.15 component images for amd64 only,
so every Harbor pod crash-loops with "exec format error" on an arm64
node. The multi-arch build upstream exists on main but is in no
release yet, so the package now rebuilds the pinned v2.15.1 release
itself: the Go components cross-compile on the build platform, the
registry and the Trivy adapter build from the tags upstream's Makefile
pins, the Trivy binary is fetched per architecture against its release
checksum, and the runtime images keep upstream's photon base, users
and entrypoints. The build fails when those registry and Trivy pins,
the swagger generator or the golang and node build images no longer
match upstream's Makefile at the pinned release, so a version bump
cannot pair a new core with an old registry, scanner or toolchain.

The portal compiles to static JS, so it lives in its own Dockerfile
listed as architecture-neutral in the cross-compile check, and takes
the release pin from the main one so both files build the same
commit.

Because values.yaml now pins the images, `make update` pins the chart
release that ships the same Harbor version and fails when its
appVersion differs, instead of pulling the newest chart onto old
images. Renovate stops proposing tag bumps for the rebuild's build
bases, which follow upstream's toolchain for that release.

The committed refs stay on the upstream digests until CI stamps the
rebuilt ones.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-09-28 02:11:46 +03:00
Aleksei Sviridkin 7753394bfe build: rebuild every package that has an image target
flux-plunger, keycloak-operator, kilo and migration-controller have
image targets but were missing from the root build list, which the
build matrix, the main build and release preparation all iterate. No
CI path rebuilt them, so their pins stayed on images pushed by hand
while Dockerfile, patch and base-image changes never shipped, and the
multi-arch pre-release build would find single-arch pins it cannot
replace. migration-controller shipped a bare :latest with no digest.

Add the four packages, and a test that derives the set from the
package Makefiles so a new image target cannot be left out again.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-09-28 00:57:24 +03:00
Aleksei Sviridkin 05fb7385d3 feat(vpn): build shadowbox for amd64 and arm64
Upstream publishes quay.io/outline/shadowbox for amd64 only, so the
VPN app crash-loops with an exec format error on arm64 nodes. The
upstream build already targets arm64 (node base, outline-ss-server,
prometheus), it is just never published for it.

Rebuild the pinned release (v1.12.3) as a multi-arch image following
upstream's own Taskfile layout: the webpack bundle is built once on the
build platform, the Go server and prometheus per target. The runtime
base is the node:18.18.0-alpine3.18 index whose per-arch manifests are
exactly the ones upstream pins, so amd64 keeps the same base layers.

The chart reads the image from images/shadowbox.tag like the other
first-party images, pinned by digest instead of the floating :stable
tag. Until the first build stamps it, the file holds upstream v1.12.3
by digest, which is what :stable resolves to today.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-09-27 19:13:42 +03:00
Aleksei Sviridkin 72ec1189fd feat(velero): build the KubeVirt plugin image for amd64 and arm64
Upstream publishes quay.io/kubevirt/kubevirt-velero-plugin for amd64
only, so on an arm64 cluster the velero server never starts: its
plugin init container fails with "exec format error". Rebuild the
plugin in-tree from the v0.9.0 release tag, cross-compiled on the
build platform, into an image that behaves like upstream's: it copies
the plugin binary into the plugins volume at /target.

The image target stamps the built reference by digest into
values.yaml, and the package joins the root build list so every build
path refreshes that pin. Until the first stamp is committed the
upstream reference stays in place, which the chart test accepts
alongside the digest-pinned in-tree one.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-09-26 20:30:40 +03:00
Aleksei Sviridkin 2c2c0301f7 chore(deps): roll up pending Renovate updates and move to Go 1.27.1 (#4447)
## What this PR does

This replaces the open Renovate PRs with one rollup. Go modules move to
their newest releases that keep the Kubernetes libraries and
controller-runtime where they are, the toolchain and builder images move
to Go 1.27.1, and the first-party code gets the Go 1.27 modernizers.
Tests for the code the bumps touch come first, in their own commits, and
pass on both the old and the new dependencies.

Before the bumps:

- The e2e sandbox image builds again: dl.min.io answers 410 for mc since
MinIO archived it, so mc now comes from its last GitHub release with a
pinned checksum.
- Renovate stops offering tags of the apimachinery fork. They mirror
upstream, so #2999 is a downgrade that drops our patch.
- `make unit-tests` now runs the tests of the nested Go modules
(kubevirt-csi-driver, token-proxy, kubeovn-webhook, api/apps/v1alpha1).
They never ran in CI before.
- New tests: JWT verification in the dashboard token proxy, the kube-ovn
plunger metrics, cert-manager object idempotency in the tenant gateway,
values-file layering in the operator, and the kubevirt CSI driver
wrapper.
- One real fix. The lineage webhook decoded admitted objects with
encoding/json, so an int64 above 2^53 came back rounded. jsonpatch v2.5
compares numbers exactly and would emit a replace that rewrites such a
field in the pod. The webhook now decodes with apimachinery's json.

Held back on purpose, with the reasons in the commit messages:

- Kubernetes libraries stay on v0.35. #3001 needs the apimachinery fork
rebased onto release-1.37 first.
- cel-go v0.29.2 and etcd v3.6.15, the lines k8s.io/apiserver v0.35 is
built and tested with.
- grpc v1.83.2. v1.84.0 is affected by GO-2026-6443 and no v1.84 patch
release carries the fix yet.
- kubevirt.io/api and the CDI API stay on what upstream csi-driver
builds against. From CDI v1.60 it no longer compiles.
- api/apps/v1alpha1 keeps go 1.26.4, so consumers of the published types
are not forced onto Go 1.27.
- go fix runs without embedlit (Go 1.27-only syntax, breaks backports to
release branches on Go 1.26) and omitzero (would make metadata, spec and
status required in the published OpenAPI schemas), and one errorsastype
rewrite that did not compile is left out.

govulncheck reports nothing reachable in any of the five modules,
standard library included.

Supersedes #4311, #4310, #4309, #3452, #3993, #3927, #3902, #3805,
#3451, #3415, #3414, #3413, #3302, #4050 and #3363. #3001 stays open.

Not checked locally: the images that build upstream projects (kube-ovn,
kamaji, cilium, linstor-csi and others) with the Go 1.27 builder. The
image build jobs of this PR cover them.

Closes #4487.

### Screenshots

No UI changes.

### Downstream repositories

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

### Release note

```release-note
chore(deps): move the toolchain and builder images to Go 1.27.1 and raise Go dependencies to their latest compatible releases, closing the published advisories; the lineage webhook no longer rewrites int64 fields above 2^53 in admitted objects.
```


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

* **New Features**
  * Added a restore resource policy option for Velero strategies.
  * Added a test target to run Go tests across tracked modules.
* **Bug Fixes**
* Updated the end-to-end test environment to download and verify the
MinIO client from its current release location.
* Added coverage for token validation, webhook patching, controller
behavior, and reconciliation.
* **Maintenance**
* Updated the Go toolchain, dependencies, and container image versions.
  * Refreshed pinned workflow actions and base image digests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-26 10:17:29 +04:00
Myasnikov Daniil 9faa18a239 test(hack): detect BATS suites without a runner
Nested monitoring suites remained unexecuted after their runner was
removed. Audit both runner inventories so missing invocations and stale
suite paths fail in the unit lane. Make-variable arguments must be
reported even when their values hide the file suffix.

Assisted-By: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-09-24 20:00:27 +05:00
Aleksei Sviridkin dbf7d60164 test(hack): run the nested Go modules' tests in unit-tests
The image sources (kubevirt-csi-driver, token-proxy, kubeovn-webhook)
and the published api/apps/v1alpha1 types are separate Go modules,
outside ./... of the root module, so their tests never ran in CI and
a dependency bump in them was covered by nothing but a build.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-09-24 16:55:02 +03:00
Aleksei Sviridkin 08bbed3fb5 feat(talos-log-collector): add talos-log-collector package (#3260)
## What this PR does

Adds `talos-log-collector`, an optional system package: a node-local
Vector
DaemonSet that receives Talos machine and kernel logs and forwards them
to the
tenant VictoriaLogs (vlinsert), alongside the container/audit/event logs
already collected by monitoring-agents.

Talos keeps system and kernel (kmsg) logs inside the OS and does not
write them
to a host file the Fluent Bit `tail` input could read, so these logs are
currently never collected. Vector receives them over the node loopback
(Talos pushes to 127.0.0.1:5170). Talos exposes the two streams through
independent config paths, and both must point at the loopback socket:
`machine.logging.destinations` forwards the JSON-lines service logs, and
a
`KmsgLogConfig` document forwards the kernel (kmsg) stream (including
DRBD
kmsg). The node-side setup for both is in the README.

Design notes:
- The pod stays on the pod network so cluster DNS resolves vlinsert with
no
  extra config. Talos pushes to 127.0.0.1:5170; a `hostPort` bound to
`hostIP: 127.0.0.1` publishes that loopback socket into the pod via
portmap,
  keeping the receiver off the host network (no node-wide pod exposure).
- The receiver binds inside the pod netns, so a NetworkPolicy is
load-bearing:
a CiliumNetworkPolicy restricts ingress to the host entity when Cilium
is
present, otherwise a default-deny-ingress `networking.k8s.io/v1`
fallback
  keeps the socket protected on non-Cilium variants.
- Runs non-root, read-only root filesystem, all capabilities dropped.
- Optional package (opt-in via `bundles.enabledPackages`): inert until
each
Talos node is configured to push logs. Node-side setup is in the README.

Verified end-to-end on a Talos v1.12 dev cluster: service and kernel
logs
(including DRBD kmsg) reach VictoriaLogs; pods run non-root with 0
restarts.

### Release note

```release-note
feat(talos-log-collector): add talos-log-collector package to collect Talos machine and kernel logs into VictoriaLogs
```


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

* **New Features**
* Added an optional node-local Talos log collector (via system bundle
packages) that forwards Talos service and kernel logs over loopback to
the platform log ingestion endpoint.
* Introduced the collector Helm chart (Vector-based) with configurable
log level, loopback TCP receive port, digest-pinned image, default
resources, and routing target.
* Added conditional Cilium network policy support with a safe
deny-by-default fallback.
* **Security**
* Hardened the collector to run non-root with reduced privileges,
read-only filesystem, and dropped Linux capabilities (and disabled
service account token automount).
* **Documentation**
* Documented configuration requirements (including kernel log
enablement), operational behavior, and teardown steps.
* **Build & Release**
  * Extended the build to include building the collector’s image.
* **Tests**
* Added Helm rendering/contract tests covering daemonset, configmap,
service account, and network policy.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-22 23:49:33 +04:00
Yan Bondarenko c8f4906e93 fix(cluster-api): backport live migration before host eviction
Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com>
2026-09-18 17:30:13 +04:00
Aleksei Sviridkin 88a7ea0e3c fix(kubeovn): retry VM migration setup after scheduling (#4253)
## What this PR does

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

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

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

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

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

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

### Screenshots

Not applicable; no UI change.

### Downstream repositories

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

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

### Release note

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


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

## Summary by CodeRabbit

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

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

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-18 17:29:45 +04:00
Timofei Larkin c398e0e116 Merge branch 'main' into chore/drop-http-cache
main took migration 57 and moved migrations.targetVersion to 58 while this
branch was open, so that line is the one conflict: 57 at the merge base, 58
on main, 59 here. Resolved as 59, which is what this branch's migration 58
requires, since run-migrations.sh loops up to targetVersion - 1.

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

Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2026-09-18 13:20:03 +03:00
Yan Bondarenko 55a2918fcb fix(kubeovn): retry VM migration setup after scheduling
Signed-off-by: Yan Bondarenko <202671653+yankawai@users.noreply.github.com>
2026-09-18 13:24:49 +04:00
IvanHunters b222cb6580 fix(opensearch-operator): keep a single-replica cluster electable after bootstrap
The operator deletes the bootstrap pod as soon as initialization completes and
never looks at the voting configuration first. With one cluster_manager replica
there are exactly two eligible nodes, OpenSearch keeps the configuration at an
odd size of one, and the voter it keeps is the bootstrap node that formed the
cluster. Deleting that pod takes away the only vote there was, and the survivor
can never hold an election again, because changing the configuration itself
needs a quorum. The cluster is unusable about a minute after it installs, and a
node with no cluster-manager still answers the readiness probe, so nothing says
so until something restarts the pod.

Build the operator in-tree, the way kamaji and redis-operator already are, and
carry a patch that excludes the bootstrap node from the voting configuration and
waits for that to commit before deleting the pod. Reaching the cluster is a
precondition rather than a nicety: every failure keeps the pod and requeues,
because a bootstrap pod that outlives its purpose costs a pod, while a cluster
that cannot elect does not recover without a human.

Deleting a pod is a request rather than a wait, so clearing the exclusion
afterwards can find the node still in the cluster and fail. That path requeues
and is retried once the pod is really gone, which keeps a stale entry out of the
capped exclusion list; clearing stays at the API default wait_for_removal=true
so it cannot re-admit a node some other operation is still draining.

Upstream carries the same defect, tracked as
opensearch-project/opensearch-k8s-operator#1448.

Assisted-by: LLM
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2026-09-14 18:45:41 +03:00
Andrei Kvapil b24856561c chore(http-cache): remove the application and its ResourceDefinition
HTTPCache is the least-installed application in the catalog and the only
one whose dependency-refresh path does not work: every sed in its update
target rewrites images/nginx/Dockerfile while the image lives at
images/nginx-cache/Dockerfile, so the target fails on its first line. The
pins show the result - nginx sits at 1.25.3 against 1.31.5 upstream and
ngx_cache_purge a major version behind. Maintaining a bespoke nginx build
with third-party C modules is not something we are prepared to carry for
this level of use.

Assisted-by: LLM
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2026-09-14 13:10:48 +02:00
Myasnikov Daniil 186547d835 test(e2e): run the merge-gating lanes on Talos containers
The srv1-srv3 nodes of both lanes that gate a merge -- the same-repo one
in pull-requests.yaml and the fork one in e2e-fork.yaml -- run as Talos
containers instead of QEMU guests, so a tenant worker sits at L2 rather
than L3. Measured in CI on commits where both substrates ran the same
tree: whole-job wall clock down 24-49 minutes, essentially all of it in
Chainsaw, while the two tenant-Kubernetes suites go from 1143-2040s to
466-809s and stop depending on the runner's vgif flag.

The node-join soft-red gate goes with the substrate, which is what it
was for. It existed because nested virtualisation on the shared runners
degraded far enough that a worker registering in minutes elsewhere could
miss any deadline this test could afford; #3513 established that the
discriminator was the host's kvm_amd vgif flag, and removing a nesting
level removes what it acted on. So hack/e2e-node-join-soft-red.sh, the
SOFT-RED-node-join.txt marker and the soft_red job outputs are gone from
all four workflows, and a missed deadline is an ordinary red again. The
::warning annotation survives, because the reason a run went red should
be legible without opening the log, and so does the 124-versus-everything
-else test, because "the workers were slow" and "the wait never ran"
send a reader to different places.

Done by swapping the substrate inside the existing e2e jobs rather than
by promoting a separate container job. Those jobs carry more than a
substrate -- the GitHub App token, the report-overrun guard, the images
list, the SSH breakpoint -- and promoting a job that never had them
would have dropped four features without saying so.

Three properties of container mode drive the design, and each fails
quietly rather than loudly. machine.kernel.modules is a silent no-op --
kernel_module_spec.go returns early on ModeContainer with no error and
no event -- so the host loads openvswitch and zfs before the nodes start
and the workflow step asserts both rather than trusting them; a missing
module surfaces an hour later looking like a CNI or a storage
regression. Docker gives a privileged container its own tmpfs /dev
seeded once at creation, so /dev/kvm works but ZFS zvols created later
never appear and linstor-csi fails ControllerPublishVolume; the compose
file binds the host devtmpfs, which is also why talosctl cluster create
docker can never carry LINSTOR. And there is no ARP VIP, so
192.168.123.11 stands in for the QEMU lane's .10.

The QEMU machinery stays in the tree and keeps running: nightly.yaml and
e2e-tag.yaml still call `make prepare-env`, so DRBD, the replicated
StorageClass, its Immediate binding mode, the tenant StorageClass
propagation that only applies to remotely-accessible classes, and the
Cozystack Talos node image with its extensions all keep nightly and
release-candidate coverage. None of them is exercised per pull request
any more. That is the trade.

Nothing on the PR path builds or downloads a nocloud disk now, so
build-talos drops the talos-nocloud step and the talos-image artifact.
build-talos itself stays: finalize consumes its digest fragment.

One property this does not fix, recorded beside the runner class because
it will be read off a red run eventually: a container node reports the
HOST's CPU and memory as capacity, and hack/e2e-container-up.sh counters
that with kubelet systemReserved. That corrects the scheduler and not
what a pod reads from /proc, so a workload sizing itself from
/proc/cpuinfo or /proc/meminfo is configured for the whole runner.

Also carried here because the same files carry them: the ghcr.io
pull-through mirror removal (see #4007 -- whichever lands first makes the
other a no-op), the authoritative HelmRelease readiness gate, the
backup-credential preflight, the prepull guard against an image-less
container fragment, and folding the two standalone OIDC suites into the
latest-version tenant cluster.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-09-05 13:19:17 +05:00
Myasnikov Daniil e49633208c ci(tests): parallelize unit test targets
The BATS half of `make unit-tests` ran as one shell loop over ~60 files,
which is a nine-minute serial tail no amount of runner CPU could touch.
Each file becomes its own Make prerequisite instead, so `-j4` schedules
them, and the workflow asks for `make unit-tests test-controllers -j4
--output-sync=target` in a single step: one make process owns all four
runner CPUs, the Helm and Go targets share the same bounded pool rather
than waiting for each other, and output stays grouped by target instead
of four logs interleaving.

The existence check moves into its own prerequisite rather than
disappearing. It guards against the wildcard matching nothing, which
would otherwise make the whole target succeed by having no work.

COZYTEST_TRACE rides per target. It arrived with the serial loop it now
replaces, and a target that dropped it would go back to streaming full
xtrace for every suite -- so the parallelism guard requires the prefix
rather than tolerating it.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-09-05 13:19:17 +05:00
Andrey Kolkov 86fe8e5a7e feat(valkey): add managed Valkey service (#3380)
## What this PR does

Adds a **managed Valkey** service to the catalog, alongside managed
Redis.

> **Stacked on #3406** (redis-operator migration to freshworks-oss).
This PR targets that branch and depends on it; review/merge #3406 first.

### Why

Redis relicensed at 8.x to a source-available family (AGPLv3 / SSPL /
RSALv2), and the managed Redis app currently ships `v8: 8.4.0` with no
BSD-licensed option ≤ 7.2. That is a licensing risk for CNCF Incubation.
[Valkey](https://valkey.io) is the BSD-3-Clause fork of Redis 7.2.4,
governed by the Linux Foundation, and a drop-in replacement for Redis.
This is the same move Argo CD made —
[`cncf/foundation#750`](https://github.com/cncf/foundation/issues/750)
was closed with "no need for exception" after they migrated to Valkey.

### How it works

Valkey runs on the same operator as managed Redis. #3406 migrates that
operator from the archived `spotahome/redis-operator` to the
actively-maintained **freshworks-oss/redis-operator** fork, which adds a
native `spec.engine: Valkey` switch. With it, the operator runs
`valkey-server` / `valkey-cli` natively (in the launch command and in
its liveness / readiness / shutdown probes), so the chart uses the
official `valkey/valkey` image directly — **no Redis-compatibility shim
image**.

The app mirrors managed Redis: same shape/values (`replicas`,
`resources`/`resourcesPreset`, `size`, `storageClass`, `external`,
`version`, `authEnabled`), Sentinel-based HA, metrics exporter,
`WorkloadMonitor`s, dashboard resource map, and `ApplicationDefinition`
(`kind: Valkey`).

Versions offered: `v8 → 8.1.8` (default) and `v7 → 7.2.13` (the
BSD-3-Clause Redis 7.2 line, most conservative drop-in).

### How it was verified

- `helm template` renders cleanly for `v8`, `v7`, and `external: true`;
the `RedisFailover` carries `spec.engine: Valkey` and `image:
valkey/valkey:<version>`. `helm lint` shows only the pre-existing
icon-URL / `cozy-lib` warnings that managed Redis emits too.
- `helm template` of `valkey-rd` renders a valid `ApplicationDefinition`
(`kind: Valkey`, `plural: valkeys`, `category: PaaS`).
- Generated artifacts (`values.schema.json`, `README.md`, `types.go`,
`zz_generated.deepcopy.go`, the ApplicationDefinition `openAPISchema`)
reproduce with the pinned `cozyvalues-gen` v1.6.0 and `controller-gen`
v0.16.4 — root `make generate` and per-app generate both produce no
drift; `go build ./valkey/...` passes.
- A live-cluster e2e was not run from this environment; the Chainsaw
suite `hack/e2e-chainsaw/valkey/` (mirroring the redis suite) is
included and `hack/select-e2e.sh` maps the `valkey-application` source
to it, so it runs in CI once the freshworks operator image is built.

### Notes

- The Valkey logo (`packages/apps/valkey/logos/valkey.svg`) is a
placeholder in Valkey's brand colors, pending the official brand asset.
- A Grafana `db/valkey` dashboard was intentionally left out of this
initial PoC; it can follow.

### Screenshots

N/A — no UI changes.

### Downstream repositories

This adds a new managed-app kind, which likely warrants follow-ups in
`cozystack/website` (user-facing docs for the new service) and possibly
`cozystack/terraform-provider-cozystack`. No follow-up PRs are opened
yet — leaving these for a maintainer to decide, per the template
guidance.

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

### Release note

```release-note
feat(valkey): add managed Valkey service — a BSD-3-Clause, Linux Foundation-governed alternative to managed Redis, running on the freshworks-oss redis-operator via native spec.engine: Valkey (versions 8.1.8 and 7.2.13)
```


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

* **New Features**
* Added managed Valkey service support with high availability and
configurable replicas.
* Supports Valkey versions 7 and 8, persistent storage, resource
presets, authentication, and optional external access.
  * Added monitoring metrics and dashboard integration.
  * Added platform package integration and tenant administrator access.
* **Documentation**
* Added Valkey configuration guidance and storage-class immutability
documentation.
* **Tests**
* Added end-to-end coverage for deployment readiness, storage, and
replicas.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-02 18:18:42 +04:00
Andrey KolkovandClaude Opus 4.8 99abee5da3 build(redis-operator): build the operator image in CI
The valkey readiness fix is a source patch on the operator image
(patches/valkey-cli-auth.diff), but nothing rebuilds that image: the
root build target has no redis-operator entry, and hack/build-matrix.sh
derives the CI build set from those entries, so e2e runs the hand-pinned
pre-patch digest and the patch ships zero bits. Register the image build
so CI rebuilds the operator from source and the patch reaches the tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Andrey Kolkov <androndo@gmail.com>
2026-09-02 14:18:53 +04:00
Andrei Kvapil 785b0feb68 feat(valkey): use native engine:Valkey instead of a redis-compat image
With the operator on freshworks-oss/redis-operator, RedisFailover supports
spec.engine: Valkey — the operator runs valkey-server/valkey-cli natively. Drop
the thin Redis-compat wrapper image entirely and point the chart at the official
valkey/valkey image directly:

- RedisFailover: set spec.engine: Valkey, image valkey/valkey:<version>
- remove images/ (Dockerfile + per-version tags), the image build target, the
  root build wiring and the images helmignore entry

Depends on the freshworks-oss operator migration.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
2026-09-01 11:44:29 +04:00
Andrei Kvapil 440c3b58f3 test(valkey): add e2e test and wire build, labeler and docs
Add the Valkey e2e bats test (mirrors redis), build the compat image in the
top-level build target, map the valkey scope to area/database in the PR
labeler, and list valkey among charts with an immutable storageClass.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
2026-09-01 11:44:07 +04:00
Myasnikov Daniil c88da18139 test(tests): keep trace switch suite ASCII-only
Assisted-By: GPT-5 <noreply@openai.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-08-28 15:31:22 +05:00
Myasnikov Daniil a5c90d984e fix(tests): guard the finishedAt leaf and make three guards actually bite
Findings from two independent adversarial reviews of the preceding two
commits. Each was reproduced locally before being fixed.

The ordering inverted itself on a shape I had not probed. `finishedAt`
is a metav1.Time behind `json:",omitempty"`, which cannot omit a
struct, and its zero value marshals to `null`; the kubelet does
synthesize a terminated state with no time (ContainerStatusUnknown on a
pod deleted mid-run, which also bumps restartCount and so clears the
restart filter). Two guards deep left the leaf exposed, and kubectl
renders it as the literal `<no value>` -- which sorts ABOVE every digit,
so that row took the front of the queue from the most recent real
restart, and the header printed `previous instance ended <no value>`, a
claim about a time nothing observed. Verified with a text/template probe
over seven lastState shapes: `null` and a missing key both produced it.

Fixed at both layers, because they fail independently. The template now
guards the leaf, so the string cannot be produced. And the sort
normalises any field-6 value not shaped like a timestamp to empty, so
the ordering stays correct and the header stays quiet even if some other
producer emits one. Earlier verification against a live cluster showed
no `<no value>` and I read that as the guard being right; it only meant
no container on that cluster was in that state.

Three guards did not bite:

  - both stability fixtures were pre-sorted ascending, which is exactly
    what sort's last-resort whole-line compare reproduces with no -s at
    all, so the tests written for -s passed without it. Reversed; they
    now fail when -s is dropped.
  - no test fed a six-field row through the whole script, so the
    timestamp reached the dump header uncovered -- deleting the read
    variable left the suite green at 39/39. One integration test now
    asserts the header names the time.
  - the e2e guard matched any mention of COZYTEST_TRACE, so documenting
    the design over there would have turned it red. It now matches an
    assignment, and tolerating a comment is verified too.

Also: a failed sort no longer discards a whole namespace group. The
caller compares row counts to report the cap, so losing a group silently
became "0 of N captured, N more not captured" and sent the reader after
a cap that never fired. It falls back to discovery order and says so.
LC_ALL=C on the sort, since the key is fixed-format ASCII.

And the claim itself was too strong. "Nothing diagnostic is lost" holds
only for a test that RETURNS: the fail handler runs after the pipeline,
and there is no INT/TERM trap, so a test killed rather than failed -- a
hang reaped by the job timeout -- never dumps its log, and under quiet
mode its trace is gone rather than already printed. The comments in
hack/cozytest.sh and the Makefile now say so and name the escape hatch.
A trap that flushed the tail of the log would close it; left out
deliberately rather than bundled in here.

Every fix is mutation-proven: reverting the leaf guard, removing the
key normalisation, dropping the read variable, dropping -s, and
silencing an e2e recipe are each caught. Unit lane 1513 passed, 1 failed
(hack/ghcr-mirror_test.bats, which reads a live cluster and fails the
same way under main's runner).

Assisted-By: Claude <noreply@anthropic.com>
Assisted-By: GPT-5 <noreply@openai.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-08-27 22:06:53 +05:00
Myasnikov Daniil fdb2e89fe7 test(unit): make the bats trace stream opt-in, quiet on the unit lane
hack/cozytest.sh runs every @test body under `set -x` and streams the
trace live. Across the ~40 hack/*.bats unit suites that is 233,444 of
238,794 lines - 98% of a 31 MB job log - and the single failing
assertion sits at line 238,573 of it. The web log viewer is unusable at
that size, and anyone (or anything) reading the job to find out what
broke pays for the whole file first.

Nothing in that stream is load-bearing on the unit lane. run_one
already tees the raw output to $log and the fail handler dumps all of
it for whichever test fails, so suppressing the live stream drops the
output of PASSING tests only.

Add COZYTEST_TRACE, default 1 so the e2e call sites in
packages/core/testing/Makefile keep the live stream - there it is the
only progress signal, and the only record at all when a step timeout or
a dead runner kills the job before the fail handler can run. The
bats-unit-tests target sets 0; running the runner by hand stays
verbose, which is the case where a human is actually watching.

Measured on 25 suites locally, quiet vs verbose: 29,528 -> 951 lines,
3.9 MB -> 94 KB, and 71s -> 48s wall, the last because the per-line
formatter forks date twice for every line it prints. Quiet output is
byte-for-byte the non-trace subset of verbose, and a hanging test still
shows as a ╭ with no matching ╰.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-08-27 12:19:17 +05:00
Aleksei Sviridkin 5d436fad43 fix(build): declare the root test target phony
The root Makefile declares nine of its targets phony and `test` is not
among them, while a `test/` directory sits beside it. Make therefore
takes that directory for the target's product: `make test` prints
"'test' is up to date", exits 0, and the recipe -- which installs the
platform and runs the e2e suite -- never executes.

docs/agents/overview.md advertises the command as the way to run the
full suite, so someone following it by hand gets a green exit and no
tests. CI is unaffected: the workflow calls test-controllers, and the
e2e path is driven out of packages/core/testing directly.

Every other undeclared target in this file is unshadowed today, so this
is the only one that misbehaves, and one name closes it.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-08-12 18:42:59 +03:00
Andrei Kvapil 9c63df7545 build(monitoring): stop building and publishing the Grafana rebuild
Remove the custom Grafana image build now that the deployment runs on the
upstream grafana/grafana image with catalog plugins. Drops the Dockerfile,
the pinned image tag, the image targets in both monitoring Makefiles, and
the build step from the root Makefile, so ghcr.io/cozystack/cozystack/grafana
is no longer produced or published.

Assisted-By: Claude
Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
2026-07-24 16:01:46 +02:00
IvanHunters c3d3e2596e feat(ci): guard migrations.targetVersion against silent skip
Adds hack/check-migrations-target.sh and wires it into unit-tests via the
migrations-target-check Makefile target. The check fails when the highest
numbered file under packages/core/platform/images/migrations/migrations/
is not matched by a bumped migrations.targetVersion in
packages/core/platform/values.yaml.

run-migrations.sh:34 loops `seq $CURRENT_VERSION $((TARGET_VERSION - 1))`,
so a migration whose number equals targetVersion never runs and the
intended state change is silently skipped on cluster upgrade.

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2026-07-14 13:08:54 +03:00
IvanHunters 1fb4bcf703 feat(system): add talos-log-collector package
Add a node-local Vector DaemonSet that receives Talos machine and kernel
logs (pushed via machine.logging over the node loopback) and forwards them
to the tenant VictoriaLogs. Talos keeps its system and kernel logs inside
the OS and does not expose them as host files, so the tail-based Fluent Bit
in monitoring-agents never collects them.

The receiver stays on the pod network so cluster DNS resolves the vlinsert
service, and accepts Talos logs via a hostPort bound to hostIP 127.0.0.1,
avoiding the host-network DNS breakage a hostNetwork receiver would hit.
Shipped as an optional system package.

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2026-07-10 15:34:08 +03:00
Myasnikov DaniilandClaude Fable 5 9297e51170 Merge origin/main into feat/e2e-chainsaw-example
Reconcile the Chainsaw e2e migration with ~500 commits of main drift:
- keep main's run-kubernetes.sh helpers (kubectl_wait_retry, tenant
  drain, LINSTOR pool-free wait, SC-fallback probe) under the branch's
  _lib/ rename with the Chainsaw finally/snapshot deltas re-applied
- resolve modify/delete on 10 drifted hack/e2e-apps/*.bats as deleted;
  their post-branch assertion drift is re-ported into the Chainsaw
  suites in follow-up commits
- drop the 4 new main-side bats (kubernetes-oidc-*, securitygroup,
  serviceexposure) from _lib/ relocation; ported as Chainsaw suites in
  follow-up commits
- docs/agents/e2e-testing.md: merge main's plan-job TIA wording with
  the Chainsaw suite phrasing

Assisted-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-07-08 18:04:08 +05:00
Myasnikov Daniil 27add81772 fix(release): stamp openapi.json with COZYSTACK_VERSION on promotion
The openapi-json target used `git describe`, so at promote time (checked out on
the rc staging branch, before the stable tag exists) the stable release's
openapi.json reported the rc version. Honor COZYSTACK_VERSION when set, mirroring
the v-prefixed convention the cozypkg/check-readiness targets already use, and
fall back to git describe only when it is unset.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-07-06 10:03:48 +05:00
Myasnikov Daniil c3fb1e21d8 fix(build): guard empty COZYSTACK_VERSION in operator render
Address review feedback from gemini-code-assist on Makefile:56,64,73: when COZYSTACK_VERSION is empty, v$(COZYSTACK_VERSION) expanded to a bare 'v', which is truthy and made the installer template render COZYSTACK_VERSION="v", overriding the operator's baked-in version. Guard each --set with $(if ...) so an empty version passes an empty value and the operator falls back to its build-time version.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-07-06 10:03:47 +05:00
Myasnikov DaniilandClaude Opus 4.8 fd7fcbda4e refactor(operator): read platform version from env (decouple from build)
The operator's platform version was baked into the image at build time
(`-ldflags -X pkg/version.Version` from `git describe`), so a
`vX.Y.Z-rc.N` image literally carried the rc string. That blocks promoting
a release-candidate to stable by retag: the bits would still self-report
the rc version.

Resolve the version from the COZYSTACK_VERSION env var at runtime, falling
back to the build-time value when unset (local builds still work). The
installer chart threads `cozystackOperator.platformVersion` into the
operator Deployment as that env var, and `make manifests` stamps it into
the kubectl-apply release artifacts. The same image bits can now report any
release name, so promotion needs no rebuild.

The only behavioral reader is the telemetry metric
`cozy_cluster_info{cozystack_version=...}`; cozypkg/check-readiness keep
their own build-time Version and are unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-07-06 10:03:46 +05:00
Aleksei Sviridkin bf14a73b39 feat(api): add SecurityGroup network policy resource (sdn.cozystack.io) (#2922)
## What this PR does

Introduces a tenant-facing `SecurityGroup` resource in a new
`sdn.cozystack.io/v1alpha1` API group, served by the Cozystack
aggregated API server as a 1:1 projection of a `CiliumNetworkPolicy`. A
SecurityGroup attaches to one of the tenant's managed applications by
reference (`spec.targetRef: {apiGroup, kind, name}`) and declares
allow-list ingress/egress for that application. Tenants manage network
policy for their own apps entirely through
`securitygroups.sdn.cozystack.io`, without being granted any access to
the `cilium.io` API group.

The translation is synchronous and stateless — the API server converts
each SecurityGroup into a CiliumNetworkPolicy in the same namespace on
write and back on read; there is no controller. The backing policy's
`endpointSelector` is **derived** from `targetRef` (matched on the
application's lineage labels
`apps.cozystack.io/application.{group,kind,name}`), never copied from
tenant input, so the selector is machine-generated and can only ever
match the referenced application's own pods in the tenant's namespace.
Policies owned by this API carry an `sdn.cozystack.io/securitygroup`
marker label that the storage always re-asserts and hides from the view,
so it only ever surfaces or mutates its own policies and never touches
platform-managed network policies.

The value is self-service network policy on a Cozystack-native,
RBAC-scoped surface, without exposing Cilium's version-coupled CRDs to
tenants. Because the selector is derived from an authorized application
reference rather than free-form tenant input, the RBAC boundary is
structural, not selector-validated: a tenant cannot express network
policy against arbitrary or platform-owned pods. The full design —
selector derivation from lineage labels, the structural boundary, the
additive-policy reality (a SecurityGroup widens a managed application's
allowed traffic but cannot sever its platform-managed management plane,
and equally cannot tighten below the platform baseline), the in-tree
CiliumNetworkPolicy mirror that avoids a Cilium Go-module dependency,
RBAC, and deferred extensions (raw-pod targeting, cluster scope,
reusable groups) — is in `pkg/apis/sdn/DESIGN.md`.

Implements the attachment model from #1614.

### Release note

```release-note
feat(api): add the SecurityGroup resource (sdn.cozystack.io/v1alpha1) — a tenant-facing, namespace-scoped firewall that attaches to a managed application by reference and projects 1:1 onto a CiliumNetworkPolicy with a machine-derived endpoint selector, letting tenants manage their application's network policy without cilium.io access.
```

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

* **New Features**
* Added the `SecurityGroup` API resource (`sdn.cozystack.io` v1alpha1)
with a namespaced projection backed by Cilium network policies.
* Enabled the SDN `APIService` and wired SDN routing, scheme, and REST
support.
* Expanded RBAC to include SecurityGroup access and required Cilium
policy permissions.
* **Documentation**
* Added a design document describing projection behavior and rule
semantics.
* **Bug Fixes**
  * Improved API rule validation coverage for missing list-type fields.
* **Tests**
* Added SDN round-trip, fuzzing, RBAC, and comprehensive REST
create/get/list/update/delete/watch/resource-version tests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-30 14:30:23 +03:00
Aleksei Sviridkin 09f4d8bb82 build: build the securitygroup-controller image in the top-level build target
The securitygroup-controller package has a working `make image` target, but it
was never wired into the top-level Makefile `build:` list, so CI never builds or
pushes the controller image. The shipped values.yaml therefore keeps its
`securitygroup-controller:v0.0.0` placeholder and the controller Deployment
lands in ImagePullBackOff, since that image tag does not exist in the registry.
Add the missing build line next to the other first-party controllers so CI
builds, pushes, and digest-pins the image like every other controller.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-06-29 15:04:46 +03:00
Myasnikov DaniilandClaude Opus 4.8 e22d84f2c8 test(e2e): migrate app suite from BATS to Chainsaw and wire into CI
Replaces the per-app BATS E2E suite (hack/e2e-apps/*.bats) with declarative
Kyverno Chainsaw tests under hack/e2e-chainsaw/, extending this branch's pilot
(postgres + bucket) to all 18 app suites.

- Declarative ports assert on status/conditions and concrete fields; the
  repeated "timeout ... until kubectl get" + "kubectl wait" boilerplate
  collapses into a single polling assert with structured failure diffs and
  automatic events/describe/podLogs capture on failure.
- Inherently imperative suites keep their logic in script steps: openbao
  init/unseal, kuberture split-horizon probes, vminstance, the gateway
  admission/impersonation cases, and kubernetes-latest/previous, which wrap
  the relocated hack/e2e-chainsaw/_lib/run-kubernetes.sh verbatim.
- Gateway tests derive the tenant apex from the namespace host label at
  runtime, so they are host-independent.

CI: the e2e job now runs "chainsaw test hack/e2e-chainsaw/" via a new
test-chainsaw target and uploads the JUnit chainsaw-report.xml; the chainsaw
binary is added to the e2e-sandbox image. install-cozystack and test-openapi
stay BATS. The per-app 3-retry loop is dropped, since assertion polling
replaces the fixed-timeout flakiness it papered over.

Verified against a dev cluster (DB/app, storage and VM suites pass). Service
port assertions use the ports[*].port projection form because Chainsaw
v0.2.15 mis-evaluates JMESPath number-literal port filters.

Assisted-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-25 13:42:34 +05:00
Andrei KvapilandMyasnikov Daniil 0d8514c934 feat(capi-providers-cpprovider): rebuild Kamaji control-plane provider (v0.19.0 + additionalServicePorts backport)
Rebuild the Kamaji control-plane provider from upstream v0.19.0 with a backport of additionalServicePorts (upstream PR #347), exposing KamajiControlPlane.spec.network.additionalServicePorts so trustd (50001/TCP) can be published on the apiserver Service. Image is built from source and pinned by digest; the 0.17-0.19 release series is registered in metadata.

Co-authored-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-23 14:30:34 +05:00
Myasnikov DaniilandClaude dc9427285d Merge branch 'main' into feat/flux-shard-operator-impl
Resolve go.mod conflict: keep main's go 1.26.4 and golang.org/x/* bumps, and keep gomodules.xyz/jsonpatch/v2 v2.4.0 as a direct dependency (imported by the flux-shard-operator admission webhook) instead of main's indirect entry. go mod tidy leaves go.sum consistent.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-22 10:19:07 +05:00
Andrei KvapilandClaude d75fac0096 feat(flux-shard-operator): add system package
New cozy-fluxcd system package (Deployment with 2 replicas, webhook
cert via cert-manager, CREATE-only MutatingWebhookConfiguration with
failurePolicy Ignore scoped to HelmReleases born on the legacy tenants
bucket), registered in the platform system bundle and the root image
build.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
2026-06-03 19:47:19 +02:00
Myasnikov Daniil f85ec76e19 feat(hack): rewrite check-readiness as a Go command with test suite
Replace hack/check-readiness.sh with a Go command at cmd/check-readiness,
wired into the build like cozypkg (`make check-readiness`, Version via
-ldflags). The tool shells out to kubectl and reports only non-ready
cozystack / Flux / Kubernetes resources parsed from status conditions,
supporting --wait/--timeout, --watch, --core, --parallel, --verbose,
-n/-l scoping, --no-color and --version.

Add a black-box golden test suite under test/check-readiness driven by a
mock kubectl over fixtures; `make test-check-readiness` runs it and it is
wired into `make unit-tests`.

Supersedes #2740, which added the same feature set to the bash script.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-05-27 01:49:49 +05:00
Aleksei Sviridkin d2e851f63b feat: Gateway API support via Cilium
Replace ingress-nginx as the default ingress layer for cozystack-native
services with Cilium Gateway API, materialised per-tenant via a new
TenantGateway CRD reconciled by cozystack-controller.

The extra/gateway chart renders one gateway.cozystack.io/v1alpha1
TenantGateway per tenant; the controller owns the resulting Gateway,
per-tenant Issuer, Certificate(s), and the http->https redirect
HTTPRoute. This avoids the helm-vs-controller race on
Gateway.spec.listeners when route-driven listeners are added.

publishing.certificates.solver selects HTTP-01 (default; per-app HTTPS
listener + per-app Certificate) or DNS-01 (opt-in; single wildcard
Certificate per apex; providers: cloudflare, route53, digitalocean,
rfc2136). HTTP-01 keeps zero-config per-app onboarding; DNS-01 saves
Let's Encrypt budget on apex-shared clusters.

Security model -- seven independent layers, all admission-time gates
fail-closed:

  1. Listener allowedRoutes namespace whitelist via the
     kube-apiserver-written kubernetes.io/metadata.name label;
     plain-HTTP listener is strictly narrower than HTTPS, and HTTPS /
     passthrough listeners restrict allowedRoutes.kinds to
     HTTPRoute / TLSRoute respectively.
  2. cozystack-gateway-hostname-policy VAP on Gateway.
  3. cozystack-gateway-attached-namespaces-policy VAP on Package --
     blocks tenant-* in gateway.attachedNamespaces.
  4. cozystack-tenant-host-policy VAP on Tenant -- blocks spec.host
     writes from non-trusted callers.
  5. cozystack-namespace-host-label-policy VAP on Namespace -- blocks
     writes to namespace.cozystack.io/host from non-trusted callers.
  6. Render-time fail in cozystack-basics for tenant-* in
     gateway.attachedNamespaces.
  7. cozystack-route-hostname-policy VAP on HTTPRoute / TLSRoute,
     scoped to tenant-* namespaces.

Within-apex cross-namespace hostname conflict is resolved at reconcile
time by a HostnameConflict condition (cozy-* namespace wins).

Five reconcile paths refuse to rewrite pre-existing objects with the
controller-derived name but no OwnerReference back to the
TenantGateway: Gateway, redirect HTTPRoute, per-tenant Issuer, wildcard
Certificate, per-listener Certificate.

Derived-apex tenants (tenants whose host is a subdomain of a parent
tenant's) auto-enable Gateway when the platform-level gateway.enabled
is on.

Bonus fix: pkg/registry/apps/application/rest.go was ignoring the
createValidation and deleteValidation callbacks that genericapiserver
hands to every storage method, silently bypassing admission for Create
and Delete on every apps.cozystack.io resource (Tenant, MariaDB,
Postgres, Kubernetes, ...). Aligned with the existing updateValidation
pattern.

Always-on changes: cilium.envoy.enabled + cilium.gatewayAPI.enabled
flip to true (extra cilium-envoy DaemonSet, ~100 MB RAM/node idle),
and the cozystack-api admission-chain fix. Everything else is gated
behind gateway.enabled=false and tenant.spec.gateway=false; existing
clusters see no behavioural change until an operator opts in.

Tests: 60+ Go cases in internal/controller/tenantgateway/, admission-
chain regression tests in pkg/registry/apps/application/, helm-unittest
across 7 charts (extra/gateway, core/platform, apps/tenant,
system/cozystack-basics, system/cert-manager-issuers,
system/cozystack-api, system/dashboard), and 14 e2e bats scenarios in
hack/e2e-apps/gateway.bats. New make test-controllers target wired
into pull-requests.yaml.

Deps: sigs.k8s.io/gateway-api v1.4.1, cert-manager v1.17.4.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-05-25 17:07:02 +03:00
Aleksei SviridkinandMatthieu ROBIN 55850aa3d8 test(apps): backward-compat helm-unittest and CI invariants
Pin the 1:1 equivalence between legacy flat preset names and their
instance-type counterparts in helm-unittest for three charts that
exercise different preset shapes:

  - postgres   — top-level only
  - clickhouse — top + nested clickhouseKeeper preset
  - opensearch — top + nested dashboards preset

Each case asserts that nano <-> t1.nano, small <-> t1.small,
medium <-> c1.small, large <-> c1.medium, and micro <-> t1.micro
render the same CPU and memory.

packages/apps/{clickhouse,opensearch}/Makefile gain a `test:` target
that calls helm unittest so hack/helm-unit-tests.sh actually runs the
new suites in CI; without it the suites would land as dead weight.

hack/check-rd-presets.sh enforces two invariants in `make unit-tests`:

  - every packages/system/*-rd/cozyrds/*.yaml openAPISchema carries
    the canonical 47-preset enum (40 instance-type + 7 legacy);
  - no chart template carries a hardcoded legacy preset literal in
    a defaultingSanitize (list "<legacy>" ...) form.

Both turn an accidental drop of a per-chart Makefile step or an
accidental legacy hardcode into a CI failure rather than silent
production-time validation rejection.

Co-authored-by: Matthieu ROBIN <matthieu@hidora.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-05-11 21:09:50 +03:00
Myasnikov Daniil 730037c85d fix(installer): address review feedback on cozy-system bootstrap
- Bump alpine/k8s to 1.33.4 (was 1.32.0, EOL); matches in-tree pin used
  by cozystack-api, seaweedfs, vm-disk, and vm-instance hooks.
- Skip hostNetwork=true and not-ready/CNI tolerations on the hosted
  variant: managed clusters provide CNI before install, and kube-system
  may carry strict PSA there that would reject hostNetwork pods.
- Add packages/core/installer/templates/cozy-system-namespace.yaml gated
  on .Values.bareNamespace (default false). The top-level Makefile's
  manifests target now passes --set bareNamespace=true and renders
  Namespace + operator into _out/assets/cozystack-operator-*.yaml so
  the kubectl-apply install path stays self-contained. Helm
  install/upgrade callers keep the default and bootstrap via
  --create-namespace + the pre-install labeler hook.
- Add a bats assertion in hack/e2e-install-cozystack.bats checking
  cozy-system carries pod-security.kubernetes.io/enforce=privileged and
  cozystack.io/system=true after helm install.
- Inline comment near hostNetwork=true documenting why dnsPolicy left
  at Default (cluster DNS not up at install time;
  ClusterFirstWithHostNet would not help).

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-05-04 22:13:39 +05:00
Aleksei Sviridkin 036eb98554 build(linstor): include linstor-gui in root image build target (#2498)
## What this PR does

The `linstor-gui` package (added in #2382) was never wired into the root
`Makefile`'s `build:` target, so the image is not built or published by
CI. `ghcr.io/cozystack/cozystack/linstor-gui` returns `NAME_UNKNOWN`
from the GHCR API, and `values.yaml` still pins `tag: 2.3.0` without a
digest because the per-package Makefile that rewrites the tag after a
successful push has never run in CI.

This PR adds the missing line so the next build publishes the image and
digest-pins `values.yaml` automatically.

### Release note

```release-note
build(linstor): include linstor-gui in root image build target so the image is built and published by CI (the chart previously referenced an image that did not exist in the registry)
```

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

* **Chores**
* Integrated the linstor-gui container image build into the main image
build workflow so the GUI image is produced as part of standard builds.
* Streamlined the GUI image build configuration to use consolidated
build arguments, improving consistency and maintainability of automated
image builds and metadata updates.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-28 13:55:25 +03:00