Commit Graph
5128 Commits
Author SHA1 Message Date
Myasnikov DaniilandClaude Opus 4.6 e6ba9817be [platform] Make vm-default-images opt-in, not default in iaas bundle
The package imports ~320Gi of golden-image PVCs (ubuntu-noble, fedora,
debian, centos, etc.) as soon as it's installed. On small test and dev
clusters that's enough to consume the entire replicated storage pool,
after which no tenant PVCs — including the ones E2E itself provisions —
can be bound. It's also unreasonable to force that cost on every iaas
user: many deployments don't need prebuilt images at all, and the ones
that do often want to curate their own subset.

Switch the bundle entry from 'package.default' to 'package.optional.default',
matching the treatment already applied to gpu-operator directly below it.
Users who want the golden images can opt in via:

    bundles:
      enabledPackages:
        - cozystack.vm-default-images

The package, its Source, and migration 38 all stay in place — nothing
else changes for users who explicitly enable it. Users who previously
relied on the bundle auto-installing it will need to add the package
to enabledPackages on upgrade; this is called out in the release note.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-15 19:47:25 +05:00
Aleksei Sviridkin d369b64d71 fix(application): address review feedback for WorkloadMonitor integration
Tenant applications live in a child namespace computed from the Tenant
name, not in the HelmRelease's namespace. Scope the WorkloadMonitor
watch cluster-wide when kindName is Tenant and reverse-map WM events
back to the owning HelmRelease via computeTenantNamespace. The
condition-enrichment path now looks up monitors in the computed child
namespace too, so WorkloadsReady is populated for Tenants.

Buffer WorkloadMonitor events that arrive before the initial-events-end
bookmark and replay them once the bookmark is emitted, to preserve the
watch-list contract while not dropping workload-state transitions that
happen during the snapshot window.

Pass the fresh WorkloadMonitor object from the watch event into the
conversion path so WorkloadsReady reflects the state that triggered
the event, even when the cache client is lagging behind the watch
client.

Derive WorkloadsReady.LastTransitionTime from a stable source
(max of HelmRelease creation and condition timestamps, plus monitor
timestamps) instead of metav1.Now(), so repeated conversions of the
same underlying state produce identical timestamps.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-04-15 17:22:15 +03:00
Aleksei Sviridkin dc3387c635 fix(workload-monitor): use fresh spec for MinReplicas check on retry
Inside the RetryOnConflict block, derive the operational status from
fresh.Spec.MinReplicas instead of the stale monitor.Spec.MinReplicas
so that concurrent spec updates observed by the retry are respected.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-04-15 17:20:45 +03:00
Aleksei Sviridkin 1bae0775be feat(application): add WorkloadsReady condition and Events tab
Add two features to improve application observability in the dashboard:

1. WorkloadsReady condition on Application status
   - Query WorkloadMonitor resources to determine if all pods are running
   - Expose WorkloadsReady as a separate condition alongside Ready
   - Ready continues to reflect HelmRelease state only (no override) to
     preserve backward compatibility with tooling and avoid false-negatives
     during normal startup windows when pods are still coming up
   - Handle three states: operational, not operational, unknown (pending)
   - Fail-open on WorkloadMonitor query errors
   - Integrate with Application Watch to emit MODIFIED events on
     WorkloadMonitor changes (with initial-events-end safety)
   - Register cozystack.io/v1alpha1 types in API server scheme

2. Events tab in dashboard
   - Show Kubernetes Events scoped to application namespace
   - Use status.namespace for Tenant applications
   - Include both lastTimestamp and eventTime columns for K8s compat

3. Bug fix: WorkloadMonitor Operational status persistence
   - Operational was written to stale 'monitor' instead of 'fresh'
     inside RetryOnConflict, so it was never persisted to the cluster

Closes #2359
Closes #2360

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-04-15 17:20:45 +03:00
Aleksei Sviridkin c87cccae99 docs: adopt Conventional Commits for commit and PR titles (#2395)
## What this PR does

Two independent cleanups in agent-facing documentation:

1. **Refocus `docs/agents/contributing.md` on project-side
conventions.** The file previously mixed project rules (commit format,
PR structure) with personal workflow preferences (e.g. "do not commit
automatically, show the diff first", subjective guidance on how to
evaluate AI-bot review comments). Personal preferences belong in
individual contributors' own agent config (`~/CLAUDE.md` or equivalent),
not in a shared project doc that every agent reads. This PR keeps only
the project-side artifacts: what a commit/PR must look like when it
reaches the repository.

2. **Adopt Conventional Commits.** Recent commit history mixes
`[component]` prefix and `type(scope):` styles. This PR picks
Conventional Commits and aligns all agent-facing docs and the PR
template on it, so contributors and bots get one consistent answer.

Also documents the `Assisted-By:` trailer convention for AI-authored
commits.

Changes:
- `.github/PULL_REQUEST_TEMPLATE.md`: update guidance and release-note
example
- `docs/agents/contributing.md`: drop personal workflow guidance, link
to the PR template instead of duplicating it, switch examples to
Conventional Commits, document `Assisted-By:` trailer
- `docs/agents/overview.md`, `AGENTS.md`, `.gemini/styleguide.md`:
update commit format references

### Release note

```release-note
docs: adopt Conventional Commits (`type(scope): description`) for commit and PR titles
```


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

* **Documentation**
* Switched contribution & PR guidance to Conventional Commits
(`type(scope): description`) and added explicit allowed types, scope
examples, and breaking-change notation (`!` or `BREAKING CHANGE:`).
* Updated PR template, release-note expectations, validation messaging,
and contributor checklist to match the new convention.
* Added AI Agent Attribution trailers for assisted commits and
streamlined branch/PR workflow guidance (rebase/cleanup and PR creation
examples).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-15 17:19:36 +03:00
myasnikovdaniil cbc79600dd [linstor-gui] Add Keycloak-protected Ingress for the UI (#2390)
## What this PR does

Stacked on top of #2382. Publishes the linstor-gui UI on
`https://linstor-gui.<root-host>` behind the cluster Keycloak realm
using the same oauth2-proxy gatekeeper pattern that the dashboard uses,
so the LINSTOR REST API is never exposed unauthenticated through
Ingress.

The Ingress is rendered only when **both** gates are satisfied:
- `_cluster.oidc-enabled == "true"`
- `linstor-gui` is listed in `_cluster.expose-services` (i.e. in
`publishing.exposedServices` in the core cozystack values)

If OIDC is disabled cluster-wide we intentionally do **not** ship an
unauthenticated ``token-proxy`` fallback — unlike dashboard, there is no
reason to front a raw storage-management API with k8s bearer tokens.
Operators can still reach the UI via \`kubectl port-forward\` to the
ClusterIP service.

## What's new

- \`templates/gatekeeper.yaml\` — oauth2-proxy Deployment (OIDC mode),
upstream \`linstor-gui.<ns>.svc:80\`
- \`templates/gatekeeper-svc.yaml\` — ClusterIP :8000 in front of it
- \`templates/gatekeeper-sa.yaml\` — dedicated SA, no auto-token
- \`templates/keycloakclient.yaml\` — persistent \`linstor-gui-client\`
+ \`linstor-gui-auth-config\` Secrets, \`KeycloakClient\` CRD that
auto-provisions the OIDC client with \`redirectUris:
[/oauth2/callback/*]\`
- \`templates/ingress.yaml\` — Ingress to the gatekeeper Service with
cert-manager ClusterIssuer, gated on expose-services + oidc-enabled
- \`tests/ingress_auth_test.yaml\` — unit tests for each conditional
branch, KeycloakClient rendering, oauth2-proxy args

README updated with Option 1 (Keycloak-protected Ingress) and Option 2
(port-forward).

### Release note

\`\`\`release-note
Added an opt-in Keycloak-protected Ingress for the linstor-gui package.
Add \`linstor-gui\` to \`publishing.exposedServices\` to publish
\`https://linstor-gui.<root-host>\` behind the cluster OIDC realm.
\`\`\`

## Test plan

- [ ] Deploy to dev10 with \`linstor-gui\` added to
\`publishing.exposedServices\`
- [ ] Verify cert-manager issues the TLS certificate
- [ ] Open \`https://linstor-gui.<root-host>\` in a browser, confirm
Keycloak login challenge
- [ ] After login, confirm LINSTOR node list loads via the nginx → mTLS
proxy
- [ ] Verify helm-unittest cases pass in CI
- [ ] Sanity-check that when \`oidc-enabled=false\` or the service is
not in \`expose-services\`, the Ingress + gatekeeper resources are
skipped
2026-04-15 17:52:29 +05:00
myasnikovdaniil c6739cf95a [linstor-gui] Add package for LINBIT linstor-gui web UI (#2382)
## What this PR does

Adds a new Cozystack system package `linstor-gui` that ships [LINBIT's
LINSTOR web UI](https://github.com/LINBIT/linstor-gui) (GPL-3.0) so
operators can manage nodes, resources, volumes and snapshots from a
browser instead of the `linstor` CLI.

Changes:
- `packages/system/linstor-gui/` — umbrella chart with:
- `images/linstor-gui/Dockerfile` builds the image from the upstream
`pkg.linbit.com` tarball (v2.3.0) on top of
`nginxinc/nginx-unprivileged:1.29-alpine`, mirroring the build pattern
already used for `piraeus-server` and `linstor-csi`.
- `templates/configmap-nginx.yaml` — chart-supplied `nginx.conf` that
proxies `/v1` and `/metrics` to
`linstor-controller.cozy-linstor.svc:3371` over **mTLS**, using the
existing `linstor-client-tls` secret created by the `linstor` package.
- `templates/deployment.yaml` — read-only rootfs, non-root (UID 101),
`runAsNonRoot`, `RuntimeDefault` seccomp, no SA token mounted,
`reloader.stakater.com/auto` on the Deployment.
- `templates/service.yaml` — **ClusterIP only**. No Ingress is shipped
because the LINSTOR controller API is a privileged cluster-wide storage
surface and auth depends on the deployment's OIDC setup; operators wire
up ingress + auth explicitly.
- `tests/deployment_test.yaml` — `helm-unittest` covering Service shape,
TLS secret mount, securityContext, and nginx proxy+mTLS config.
- `packages/core/platform/sources/linstor-gui.yaml` — new
`PackageSource` depending on `cozystack.linstor`.
- `packages/core/platform/templates/bundles/system.yaml` — registers
`linstor-gui` as an **optional** system package (controlled via
`bundles.enabledPackages`), alongside `velero`, `telepresence`, etc.

### Why standalone (not a sidecar in the controller pod)

LINBIT's upstream Dockerfile is designed for this standalone
reverse-proxy topology, and keeping the UI as its own Deployment means
it scales, upgrades, and restarts independently of `linstor-controller`.
The price is the small amount of mTLS config in `nginx.conf`, all of
which lives in the chart's ConfigMap.

### Release note

```release-note
[linstor-gui] Add an opt-in system package that deploys LINBIT's linstor-gui web UI alongside the LINSTOR controller. Enable via `bundles.enabledPackages: [cozystack.linstor-gui]`, then `kubectl -n cozy-linstor port-forward svc/linstor-gui 3373:80` to access.
```

## Test plan

- [ ] `make unit-tests` (requires `helm-unittest` plugin) passes on the
new `tests/deployment_test.yaml`
- [ ] `make image` in `packages/system/linstor-gui/` builds and pushes
the image
- [ ] Install the package on a dev cluster with LINSTOR already
deployed; confirm the pod comes up
- [ ] `kubectl -n cozy-linstor port-forward svc/linstor-gui 3373:80` →
browser loads the UI at `localhost:3373`
- [ ] UI lists LINSTOR nodes, storage pools and resources correctly
(proves mTLS proxy to `/v1` works)
- [ ] Disable via `bundles.enabledPackages` removal; verify the
`Package` resource is cleaned up


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

* **New Features**
* LINSTOR GUI added as an optional system package deployable to the
cozy-linstor namespace.
* Web UI serves on port 3373, proxies LINSTOR controller over mTLS, and
exposes a /healthz endpoint.
* Image build and release targets added to produce multi-arch container
images.

* **Documentation**
* New README with deployment guidance, connection examples, and
configurable options (endpoint, client secret, image, replicas).

* **Tests**
* Helm chart tests validating service, deployment, config and mTLS
behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-15 17:32:50 +05:00
Andrei Kvapil 9328f0b7af [docs] Add changelogs for v1.2.2 and v1.1.6 (#2398)
## Summary

* Add changelog for v1.2.2 (`docs/changelogs/v1.2.2.md`)
* Add changelog for v1.1.6 (`docs/changelogs/v1.1.6.md`)

Once merged, the `update-releasenotes.yaml` workflow will sync these to
the corresponding GitHub releases.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

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

* **Bug Fixes**
* Fixed Docker image tagging during builds; pinned system PostgreSQL
images.
* Corrected Cilium AppArmor handling and BPF load‑balancing exclusions
for external VM LoadBalancer services.
* Restored monitoring dashboard rendering for default platform variant.

* **New Features**
* Upgraded LINSTOR (piraeus-server) to v1.33.2 with backported
reliability patches.

* **Documentation**
* Expanded website docs: controller naming, Talos/version pairing,
troubleshooting, bundle naming, new --take-ownership flag, networking.*
fields, OpenAPI refresh, badges, and release notes.

* **Chores**
* Switched CI/CD from long‑lived PATs to short‑lived GitHub App tokens
and updated bot commit identity.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-15 10:02:57 +02:00
Myasnikov DaniilandClaude Opus 4.6 cc3af6c0db [docs] Address review comments on v1.2.2 changelog
- Fix LINSTOR verification version: v1.33.1 → v1.33.2 (upstream base)
- Move postgres image fix entry from Features to Fixes, add missing #2364 ref
- Fix ApplicationDefinition docs description to match actual website#478 scope

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-14 23:10:10 +05:00
Myasnikov DaniilandClaude Opus 4.6 0b5a1df3c2 [platform] Bump migration targetVersion to 39 for migration 38
Address review feedback from lexfrei on migrations/38:2:
Migration 38 requires targetVersion 39 to execute, since run-migrations.sh
iterates seq $CURRENT_VERSION $((TARGET_VERSION - 1)).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-14 20:00:25 +05:00
Myasnikov DaniilandClaude Opus 4.6 2def6cda67 [vm-default-images] Default to replicated storageClass and document storage requirements
The package is wired into the iaas bundle where LINSTOR replicated storage is
available. Set storageClass to "replicated" by default and add a note that the
full image set needs ~320Gi (16 images × 20Gi).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-14 19:59:20 +05:00
Myasnikov DaniilandClaude Opus 4.6 eeda31eb95 [api] Regenerate deepcopy and CRD manifests via make generate
Running make generate at repo root regenerates the zz_generated.deepcopy.go
files.  Key fix: Source.DeepCopyInto in the vmdisk package now handles
the new Disk *SourceDisk pointer field, which was previously missing and
would cause pointer aliasing on deep-copy.

Other generated changes are incidental updates to vminstance (Networks
field added, Subnet renamed to Network) and backups CRD manifests picked
up by controller-gen from the current HEAD.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-14 19:59:19 +05:00
Myasnikov DaniilandClaude Opus 4.6 776b4f2c8d [dashboard] Guard nested type assertions in VMDisk customformsoverride
Accessing imgName["properties"].(map[string]any) and
diskName["properties"].(map[string]any) without an ok-check would panic
if the schema does not contain a "properties" key.  Wrap both assertions
in ok-guarded form consistent with the outer defensive style.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-14 19:59:19 +05:00
Myasnikov DaniilandClaude Opus 4.6 551b4e65ac [vm-default-images] Fix CentOS Stream image URLs
The x86_64 architecture token was placed inside the filename segment
(CentOS-Stream-GenericCloud-x86_64-N-latest.x86_64.qcow2) which does
not match the actual CentOS mirror layout.  Correct both CentOS Stream 9
and 10 URLs to the canonical form without the extra architecture infix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-14 19:59:19 +05:00
Myasnikov DaniilandClaude Opus 4.6 15dfc2521e [vm-disk] Remove unused DataVolume lookup in dv.yaml
The \$dv variable assigned by the lookup call on the image PVC was never
read.  Remove the dead line to keep the template tidy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-14 19:59:19 +05:00
Myasnikov DaniilandClaude Opus 4.6 b9a17295cc [platform] Wire vm-default-images into iaas bundle
The PackageSource for vm-default-images existed but was never included
in any bundle, so it would never be installed.  Add it to iaas.yaml
immediately after kubevirt-cdi (which it depends on) so it is deployed
together with the rest of the KubeVirt IaaS stack.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-14 19:59:19 +05:00
Myasnikov DaniilandClaude Opus 4.6 6b6118ce56 [platform] Add migration 38 to rename vm-image-* DataVolumes to vm-default-images-*
The vm-disk package previously referenced golden-image DataVolumes with
the prefix "vm-image-<name>" in the cozy-public namespace.  The new
vm-default-images package creates them as "vm-default-images-<name>",
so any existing vm-disk that sources an image by name would fail to
find its PVC after upgrade.

Migration 38 renames every DataVolume in cozy-public whose name starts
with "vm-image-" to "vm-default-images-" (dropping the old object and
re-creating with the new name) so live vm-disk resources keep resolving
their source PVC transparently.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-14 19:59:19 +05:00
Myasnikov Daniil 939a7d8eb3 [vm-disk] Fix vm-disk-rd
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-14 19:59:19 +05:00
Myasnikov Daniil bec35e3aad [vm-default-images] Added new optional package
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-14 19:59:19 +05:00
Myasnikov DaniilandClaude Opus 4.6 25f6ae2f29 docs: add changelogs for v1.2.2 and v1.1.6
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-14 14:38:50 +05:00
Myasnikov DaniilandClaude Opus 4.6 1509f5a620 [linstor-gui] Lock the right endpoints to disable in-app auth setup
The previous commit blocked /v1/security/* on the LINSTOR controller,
but that turned out to be wishful thinking: that path doesn't exist in
the LINSTOR REST API at all (the controller responds 404 for everything
under /v1/security), and the linstor-gui SPA never calls it.

The actual storage for the GUI's authentication panel is two LINSTOR
KeyValueStore instances managed by the SPA itself:

  - __gui__settings  — holds `authenticationEnabled` (the on/off toggle)
  - __gui__users     — holds the encrypted admin credential

The SPA reads them on every page load via
`GET /v1/key-value-store/__gui__settings`, so we cannot blanket-block
the path or the UI fails to render. Instead, restrict to read-only:
allow GET/HEAD (so the SPA can confirm auth=off and skip its login
screen), reject every mutating method (PUT/POST/PATCH/DELETE) with the
same explanatory 403 JSON. Other GUI key-value entries (e.g.
__gui__mode) are unaffected.

The dev10 cluster had this footgun tripped already
(authenticationEnabled=true, an admin entry in __gui__users); cleared
manually with `linstor key-value-store modify __gui__settings
authenticationEnabled false` so the existing release stops gating the
UI behind a login the user no longer has the password for.

Verified end-to-end on dev10 after upgrade:
  GET    /v1/key-value-store/__gui__settings -> 200
  PUT    /v1/key-value-store/__gui__settings -> 403 (with JSON body)
  POST   /v1/key-value-store/__gui__users    -> 403
  PUT    /v1/key-value-store/__gui__mode     -> 200 (other KV writes ok)
  GET    /v1/nodes                           -> 200

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-14 12:34:15 +05:00
Myasnikov DaniilandClaude Opus 4.6 7bf1622172 [linstor-gui] Block in-app LINSTOR auth setup at the nginx proxy
The upstream linstor-gui SPA exposes a "Users" / sign-in panel that
POSTs to /v1/security/* on the LINSTOR controller. If a cozystack user
turns on HTTP auth via that panel, every subsequent REST call —
including from this very GUI, which talks to the controller via mTLS,
not bearer tokens — starts returning 401, locking the user out of
LINSTOR with no in-product recovery path.

Now that authentication for the cozystack-shipped GUI is enforced one
layer up at the Ingress (oauth2-proxy + Keycloak), the in-app auth is
both redundant and a footgun. Short-circuit /v1/security/* in the
gateway nginx with a 403 + explanatory JSON body so the setting cannot
be enabled regardless of what the SPA renders. Other LINSTOR REST paths
(/v1/nodes, /v1/resource-definitions, /metrics, …) continue to proxy
through unchanged.

Verified on dev10:
  POST /v1/security/sign-in  -> 403 (with explanation body)
  GET  /v1/security/users    -> 403
  GET  /v1/nodes             -> 200

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-14 12:34:15 +05:00
Myasnikov DaniilandClaude Opus 4.6 574941123b [linstor-gui] Bump nginx proxy buffer for oauth2-proxy session cookies
Keycloak access+refresh+id tokens push the oauth2-proxy session cookie
to ~8-10 KB (split across multiple Set-Cookie headers), which overflows
ingress-nginx's default proxy_buffer_size and produces "upstream sent too
big header" -> HTTP 502 on /oauth2/callback right after a successful
Keycloak login.

Mirror the dashboard ingress: proxy-buffer-size: 100m and
proxy-buffers-number: "4". Verified on dev10 — unauthenticated requests
now return 302 to Keycloak instead of 502.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-14 12:34:15 +05:00
Myasnikov DaniilandClaude Opus 4.6 80968410b0 [tenant] Widen linstor-gui egress to whole cozy-linstor namespace
Narrowing to (instance=linstor-gui, name=gatekeeper) also blocked the
transient cm-acme-http-solver pods that cert-manager spins up in the
same namespace during http-01 challenges — the solver pods carry
different labels, so the Certificate never became Ready and TLS
handshakes failed.

Broaden the rule to the whole cozy-linstor namespace to mirror
allow-to-dashboard / allow-to-keycloak. The ingress controller only
initiates upstream HTTP calls driven by Ingress resources, so allowing
namespace-wide egress is not a practical escalation.

Verified on dev10 after patching the live CNP: Certificate
linstor-gui-ingress-tls becomes Ready (Let's Encrypt prod), and the
Ingress returns HTTP 302 to the Keycloak authorize endpoint with the
correct client_id and redirect_uri.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-14 12:34:15 +05:00
Myasnikov DaniilandClaude Opus 4.6 e2b6c2c3dd [tenant] Allow ingress to linstor-gui gatekeeper
The tenant chart declares per-namespace egress CNPs so the tenant-root
ingress-nginx can reach auth-fronted UIs in the system namespaces
(dashboard, keycloak, cdi-upload-proxy). linstor-gui's gatekeeper in
cozy-linstor needs the same allowance — without it, requests to
https://linstor-gui.<root-host> time out at the Ingress with 504
because tenant-root → cozy-linstor egress is denied by default-deny.

Scoped to the gatekeeper pods (instance=linstor-gui, name=gatekeeper)
rather than the whole cozy-linstor namespace — the ingress controller
does not need to reach the LINSTOR controller, satellites, or CSI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-14 12:34:15 +05:00
Myasnikov DaniilandClaude Opus 4.6 dc4513a8af [linstor-gui] Fix gatekeeper label conflicts and drop realm-capped session attrs
Two issues surfaced while deploying to dev10:

1. The shared linstor-gui.labels / linstor-gui.selectorLabels helpers
   set app.kubernetes.io/name=linstor-gui, which overrode the
   app.kubernetes.io/name=gatekeeper that the gatekeeper templates set
   first. The resulting Deployment had selector.matchLabels=gatekeeper
   but template.metadata.labels=linstor-gui, failing admission with
   "selector does not match template labels". Drop the helper include
   from the three gatekeeper templates and inline the remaining common
   labels (managed-by, part-of) so the gatekeeper keeps its own
   name label.

2. Copying client.session.idle.timeout=86400 and
   client.session.max.lifespan=604800 from the dashboard KeycloakClient
   caused the keycloak-operator to reject the client ("Client session
   idle timeout cannot exceed realm SSO session idle timeout"). Our
   dev10 realm has smaller defaults, and dashboard's own client is
   also stuck in this failed state. Drop the overrides and let the
   realm defaults apply — the UI has no reason to demand longer
   sessions than the realm is willing to grant.

Verified on dev10:
  * gatekeeper Deployment rolls out cleanly
  * KeycloakClient reconciles to status=OK
  * https://linstor-gui.<root-host>/ returns HTTP 302 to
    keycloak.<root-host>/realms/cozy/protocol/openid-connect/auth
    with client_id=linstor-gui and the correct callback URI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-14 12:34:15 +05:00
Myasnikov DaniilandClaude Opus 4.6 bdb0e3a72c [linstor-gui] Add Keycloak-protected Ingress for the UI
Follow-up to the initial linstor-gui package (#2382). Publishes the UI on
https://linstor-gui.<root-host> behind the cluster Keycloak realm using
the same oauth2-proxy gatekeeper pattern that dashboard uses, so the
LINSTOR REST API is never exposed unauthenticated through Ingress.

Rendered only when both gates are satisfied:
  * _cluster.oidc-enabled == "true"
  * "linstor-gui" is listed in _cluster.expose-services
    (i.e. in publishing.exposedServices in the core cozystack values).

If OIDC is disabled cluster-wide we intentionally do not ship an
unauthenticated token-proxy fallback — unlike dashboard, there is no
reason to front a raw storage-management API with k8s bearer tokens.
Operators can still reach the UI via `kubectl port-forward` to the
ClusterIP service.

New resources:
  * templates/gatekeeper.yaml       — oauth2-proxy Deployment (OIDC mode)
  * templates/gatekeeper-svc.yaml   — ClusterIP :8000 in front of it
  * templates/gatekeeper-sa.yaml    — dedicated SA, no autotoken
  * templates/keycloakclient.yaml   — persistent client + cookie
                                      Secrets, KeycloakClient CRD with
                                      redirectUris for /oauth2/callback
  * templates/ingress.yaml          — Ingress to the gatekeeper Service,
                                      cert-manager ClusterIssuer, gated
                                      on expose-services + oidc-enabled
  * tests/ingress_auth_test.yaml    — unit tests for each conditional
                                      branch and the client CRD

README.md updated with both Option 1 (Keycloak-protected Ingress) and
the existing Option 2 (port-forward).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-14 12:34:15 +05:00
Myasnikov DaniilandClaude Opus 4.6 43222e8be0 [linstor-gui] Fix test regex for proxy_ssl_certificate spacing
The matchRegex assertion used 5 literal spaces but the template
renders 9 (aligned with proxy_ssl_trusted_certificate). Use \s+
so the test is resilient to alignment changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-14 12:32:41 +05:00
Aleksei Sviridkin 90a9d6e905 docs(contributing): sync scope lists and fix lint nits
Address review feedback:
- Sync scope lists between PR template and contributing guide
- Add 'maintenance' to Other scopes in contributing guide
- Add scope to the docs example for format consistency
- Simplify rebase push example to drop unnecessary refspec mapping
- Add 'text' language tag to trailer code fence (MD040)

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-04-13 23:09:27 +03:00
Aleksei Sviridkin 525cc9eab2 docs(agents): document Assisted-By trailer for AI-authored commits
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-04-13 22:50:50 +03:00
Aleksei Sviridkin 6bf3c86aad docs: adopt Conventional Commits across contributing docs
Refocus docs/agents/contributing.md on project-side conventions only
and drop personal agent-behavior guidance.

Switch commit format from [component] prefix to Conventional Commits
(type(scope): description) in contributing.md, overview.md, AGENTS.md,
.gemini/styleguide.md and the PR template.

Reference .github/PULL_REQUEST_TEMPLATE.md instead of duplicating the
PR body template in contributing.md.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-04-13 22:50:08 +03:00
Myasnikov DaniilandClaude Opus 4.6 727401c2c6 [linstor-gui] Address coderabbit feedback on /healthz and reload annotation
- Fix /healthz Content-Type header: nginx silently drops `add_header`
  after `return` because the response is already finalized. Switch to
  `default_type text/plain;` placed BEFORE the `return 200` so the
  probe response is actually labeled as text/plain.
- Drop the redundant `checksum/nginx-config` pod-template annotation:
  the deployment already carries `reloader.stakater.com/auto: "true"`,
  so Stakater Reloader handles ConfigMap rollouts. Matches the sister
  `linstor` package convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-13 20:42:14 +05:00
Aleksei Sviridkin ad52992301 [ci] Add Gemini Code Assist and CodeRabbit configuration (#2385)
## What this PR does

Adds configuration for the two AI reviewers active on this repository —
Gemini Code Assist and CodeRabbit.

Previously neither had a repo-level config, so both ran with defaults:
Gemini had no knowledge of the project's vendoring model and would flag
issues in vendored upstream charts and generated code; CodeRabbit only
ran on initial PR open, with no review on subsequent pushes.

Changes:

- `.gemini/config.yaml` — ignore patterns for vendored charts,
`vendor/`, generated code, build artifacts, dashboard JSONs, and
upstream patch files; severity threshold set to `LOW` for strict review
of hand-written code; comment cap to prevent floods on chart-update PRs.
- `.gemini/styleguide.md` — natural-language guide covering project
architecture, vendoring rules (where to send bug fixes instead of
editing `charts/`), commit and PR format requirements, sensitive
components, Go code standards, and explicit anti-patterns the reviewer
should not flag.
- `.coderabbit.yaml` — enables incremental reviews so CodeRabbit
re-reviews each push, and skips drafts.

### Release note

```release-note
[ci] Add Gemini Code Assist and CodeRabbit configuration
```


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

## Summary by CodeRabbit

* **Chores**
  * Added code review automation configuration settings
* Added repository style guide and development conventions documentation

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-13 18:26:19 +03:00
Timofei Larkin a8559e1a9b [scheduler] Make cozystack-scheduler storage-aware via LINSTOR extender (#2330)
## What this PR does

Makes `cozystack-scheduler` LINSTOR storage-aware by configuring it to
call the existing `linstor-scheduler-extender` as a scheduler extender.
This fixes #2328 (phase 1): pods with both a `SchedulingClass` and
LINSTOR PVCs now get storage-locality-aware placement instead of
bypassing LINSTOR's filter/prioritize logic.

Changes:
- **linstor-scheduler package**: expose the extender sidecar (port 8099)
via a new ClusterIP Service; patch the vendored deployment to add a
`app.kubernetes.io/component: scheduler` label for clean Service
targeting
- **cozystack-scheduler package**: bump to v0.3.0 which adds
configurable `extenders` support; set the LINSTOR extender URL in
wrapper values
- **tests**: helm-unittest for extender rendering in the ConfigMap

### Release note

```release-note
[scheduler] cozystack-scheduler now calls the LINSTOR scheduler extender for storage-aware pod placement. Pods assigned to a SchedulingClass that also use LINSTOR-backed PVCs will prefer nodes with local volume replicas.
```

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

* **New Features**
* Added scheduler extender support: exposes an extender Service and adds
pod metadata to enable extender routing.

* **Chores**
  * Bumped scheduler chart and image versions to 0.3.0.
* Added default extender configuration and a sample remote extender
entry.
* Updated build scripts to apply an additional patch and added a test
target.

* **Tests**
* Added Helm tests validating rendered ConfigMap with and without
extender configuration.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-13 19:19:10 +04:00
Andrei Kvapil 93d0c5a77a ci: use cozystack org noreply email for bot commits (#2392)
## Summary
- Replace `3297617+cozystack-ci[bot]@users.noreply.github.com` with
`cozystack@users.noreply.github.com` across all CI workflows
- DCO probot rejects the `[bot]` brackets in email as invalid, causing
release PRs to fail DCO checks

## Test plan
- [ ] Verify DCO passes on release PRs after backport to release
branches
2026-04-13 16:31:01 +02:00
Andrei KvapilandClaude ce0e709be8 ci: use cozystack org noreply email for bot commits
The cozystack-ci[bot] noreply email with brackets is rejected by DCO
probot as an invalid email address, causing release PRs to fail DCO
checks.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2026-04-13 16:29:07 +02:00
Aleksei Sviridkin c77d0b86fc [api] Reject tenant names with dashes at Create time (#2380)
## What this PR does

The aggregated API previously accepted tenant names containing hyphens
(e.g. `foo-bar`) because `ValidateApplicationName` delegated entirely to
`IsDNS1035Label`, which permits them. The tenant Helm chart's
`tenant.name` helper then rejected the release at template time — so
users saw a successful `kubectl apply` followed by a confusing
Flux/HelmRelease reconciliation error.

This PR extends `ValidateApplicationName` with a `kindName` parameter
and enforces an alphanumeric-only rule (`^[a-z][a-z0-9]*$`) for the
Tenant kind. A `TenantKind` constant centralizes the kind string, and
`REST.validateNameFormat` wraps the check symmetrically with the
existing `validateNameLength`. The tenant chart README is updated to
match the already-correct website documentation.

Test coverage includes:
- Unit tests for the validation package (format + error message
contract)
- REST wrapper and Update→Create fall-through path tests with a fake
client
- E2E BATS regression test with explicit exit-code checks and cleanup

Fixes #2375

### Release note

```release-note
[api] Tenant names containing dashes are now rejected at API level during creation, with a specific error message, instead of being silently accepted and failing later during Helm reconciliation.
```

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

* **New Features**
* Tenant names now require lowercase letters and digits only, must start
with a letter; dashes are rejected with a tenant-specific error.

* **Documentation**
* Tenant naming guidelines updated to reflect the stricter
alphanumeric-only requirement.

* **Tests**
* New unit and end-to-end tests added to verify tenant naming
enforcement and tenant-specific error messaging.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-13 17:25:30 +03:00
Aleksei Sviridkin 81a0d523d6 [ci] Fix factual errors in Gemini style guide
Three corrections from PR review:

- Package group list was incomplete. Added library/ (reusable helper
  charts) and tests/ (tests for library charts, which are not directly
  testable otherwise).
- CRD generation was described incorrectly. Static CRDs are generated
  by hack/update-codegen.sh from types under api/v1alpha1/, api/backups/,
  and api/dashboard/. Types under api/apps/v1alpha1/ are not static
  CRDs — they are registered at runtime from ApplicationDefinition.
- Removed the tgz anti-pattern. No *.tgz files exist outside _out/,
  which is gitignored. The guidance was fabricated.

Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-04-13 17:18:34 +03:00
Timofei LarkinandClaude Opus 4.6 37229aa0ac feat(scheduler): storage-aware scheduling
Expose the existing linstor-scheduler-extender sidecar as a ClusterIP
Service and configure cozystack-scheduler to call it during the
scheduling cycle. Pods with both a SchedulingClass and LINSTOR PVCs
now get storage-locality-aware placement.

- Add extender Service (port 8099) for linstor-scheduler
- Patch vendored deployment to label pods for Service selector
- Bump cozystack-scheduler to v0.3.0 (configurable extenders)
- Add "linstor" PackageSource variant with extender values
- Default variant ships without extender for non-LINSTOR clusters
- Select linstor variant in system bundle
- Add helm-unittest tests for both packages

Ref: #2328

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2026-04-13 17:15:09 +03:00
Andrei Kvapil 632414daf1 fix(build): filter git describe to match only v* tags (#2386)
## Summary
- Add `--match 'v*'` to all `git describe` calls in
`hack/common-envs.mk`
- The `api/apps/v1alpha1/*` subtags share the same commit as release
tags, causing `git describe --exact-match` to pick
`api/apps/v1alpha1/vX.Y.Z` instead of `vX.Y.Z`, producing invalid Docker
image tags like `ghcr.io/.../image:v1.35-api/apps/v1alpha1/v1.1.6`

## Test plan
- [ ] Rerun Versioned Tag pipelines after merge and backport to release
branches

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

## Summary by CodeRabbit

* **Chores**
* Refined version and tag derivation in the build system to selectively
recognize Git tags, improving consistency in how release versions are
identified.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-13 15:26:26 +02:00
Andrei KvapilandClaude fbbccdbb7b fix(build): filter git describe to match only v* tags
The api/apps/v1alpha1/* subtags share the same commit as the release
tags. git describe --exact-match picks the first match alphabetically,
returning api/apps/v1alpha1/vX.Y.Z instead of vX.Y.Z, which produces
invalid Docker image tags.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2026-04-13 14:25:25 +02:00
Aleksei Sviridkin c8488815e0 [ci] Enable CodeRabbit incremental reviews
Add a minimal .coderabbit.yaml that ensures CodeRabbit re-reviews each
push to a PR (incremental review on new commits) and skips drafts.
Without this file, the org-level configuration left incremental reviews
disabled, so only the initial PR open triggered a review.

Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-04-13 13:32:15 +03:00
Aleksei Sviridkin b74a9610d4 [ci] Address CodeRabbit feedback on Gemini style guide
Fix two issues caught in PR review:

- Use quadruple-backtick fence around the release-note example so the
  inner triple-backtick block renders correctly and satisfies markdownlint.
- Correct the platform migration path. The previous text pointed at
  scripts/migrations/, which does not exist. The actual migration flow
  lives in packages/core/platform/templates/migration-hook.yaml and
  packages/core/platform/images/migrations/.

Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-04-13 13:27:35 +03:00
Andrei Kvapil 655eb39ac2 ci(pull-requests): replace GH_PAT with cozystack-ci GitHub App token (#2383)
## Summary
- Replace `GH_PAT` (cozystack-bot PAT) with `cozystack-ci` GitHub App
token in `pull-requests.yaml`
- This was missed in #2351 and broke the `resolve_assets` / `e2e` jobs
for release PRs (draft releases not visible with invalid token)

## Test plan
- [ ] Re-run the release PR pipeline for `release-1.1.6` and verify
`resolve_assets` job passes

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

## Summary by CodeRabbit

* **Chores**
* Updated continuous integration authentication mechanism to use GitHub
App tokens instead of personal access tokens.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-13 10:01:50 +02:00
Myasnikov DaniilandClaude Opus 4.6 bbeaaf3dab [linstor-gui] Update test to match 0444 secret mount mode
Follow-up to 4b76a93d: the assertion still expected defaultMode 0400
and would fail in CI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-13 12:49:48 +05:00
Myasnikov DaniilandClaude Opus 4.6 4b76a93dc1 [linstor-gui] Fix secret mount mode so nginx can read mTLS client certs
The linstor-client-tls secret was mounted with defaultMode 0400 and
owned by root, so the nginx process (UID 101) got EACCES on tls.crt
and crash-looped with:

  [emerg] cannot load certificate "/etc/linstor/client/tls.crt":
  BIO_new_file() failed ... Permission denied

Set defaultMode to 0444. The secret volume is pod-local and
readOnlyRootFilesystem is on, so making it world-readable inside the
pod is not a broader disclosure.

Verified on dev10: pod Ready, /healthz 200, /v1/nodes proxied through
mTLS to linstor-controller:3371 returns real node data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-13 12:41:58 +05:00
Andrei KvapilandClaude 1144211a85 ci(pull-requests): replace GH_PAT with cozystack-ci GitHub App token
The GH_PAT secret tied to cozystack-bot is no longer valid after
migrating release workflows to the cozystack-ci GitHub App in #2351.
This broke the resolve_assets and e2e jobs for release PRs.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2026-04-13 09:28:52 +02:00
Myasnikov DaniilandClaude Opus 4.6 ba9f9e9f2c [linstor-gui] Address review comments on #2382
- Dockerfile: chown html root to 101:101 (match runAsGroup, not 101:0)
- Makefile: declare .PHONY targets (image, image-linstor-gui, test)
- deployment: drop priorityClassName system-cluster-critical (GUI is
  not control-plane critical; chart does not need to reserve the slot)
- configmap-nginx:
  - fail the render if linstor.endpoint is not https:// so a misconfig
    cannot silently downgrade the mTLS-protected controller path
  - hoist proxy_set_header and proxy_ssl_* directives to the server
    block instead of duplicating across /v1 and /metrics locations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-13 11:59:46 +05:00
Myasnikov DaniilandClaude Opus 4.6 ae88fe3779 [linstor-gui] Add package for LINBIT linstor-gui web UI
Ships LINBIT's LINSTOR web UI (GPL-3.0) as an opt-in system package under
packages/system/linstor-gui so operators can manage LINSTOR nodes, resources,
and volumes from a browser instead of the linstor CLI.

The UI is built from the upstream pkg.linbit.com tarball onto an
nginx-unprivileged base image. A chart-supplied nginx.conf proxies /v1 and
/metrics to the LINSTOR controller REST API over mTLS using the existing
linstor-client-tls secret created by the linstor package.

Only a ClusterIP Service is created; no Ingress is shipped, because LINSTOR's
controller API is a privileged cluster-wide storage surface and auth depends
on the deployment's OIDC setup. Operators opt in via bundles.enabledPackages
and wire up ingress + auth themselves (port-forward works out of the box).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-13 11:48:35 +05:00
Aleksei Sviridkin 677e186286 [ci] Add Gemini Code Assist configuration
Configure the Gemini Code Assist GitHub reviewer with project-specific
ignore patterns and a natural-language style guide.

The config silences noise from vendored upstream Helm charts, generated
Go code, build artifacts, and Grafana dashboard JSONs, while keeping the
severity threshold at LOW for strict review of hand-written code.

The style guide teaches the reviewer about the vendoring model, commit
format requirements, sensitive components like packages/core/platform/,
and lists anti-patterns not to flag (patch files, go.sum, upstream forks).

Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-04-13 02:03:03 +03:00