Commit Graph
210 Commits
Author SHA1 Message Date
Myasnikov Daniil 76ad921fdd ci: harden workflow token permissions and pin actions by SHA
Raise the OpenSSF Scorecard Token-Permissions and Pinned-Dependencies
checks (both currently 0/10) ahead of CNCF Incubation due diligence:

- Declare a top-level read-only default (permissions: contents: read) in
  every workflow, plus an explicit per-job permissions: block that grants
  only the writes that job actually needs. Workflows that previously held
  write scopes at the workflow level (backport, pr-labeler, pr-size,
  stale) now hold them at job level instead.
- Pin every GitHub-owned and third-party action to a full commit SHA with
  a version comment. scorecard.yml was already pinned in #2721.

No triggers, job graph, or step logic change; only token scopes and
action refs. Self-hosted release jobs keep the exact write scopes they
already declared.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Assisted-By: Claude <noreply@anthropic.com>
2026-06-23 11:32:16 +05:00
Myasnikov DaniilandClaude Opus 4.8 1a6dd6feef ci(tests): fix OCIR registry login in Release E2E workflow
The Release E2E build job referenced secrets OCI_REGISTRY_USERNAME /
OCI_REGISTRY_PASSWORD, which do not exist in the repo, so
docker/login-action received empty credentials and failed every run
with "Username and password required". The workflow has never built
since it was added (2026-05-02): v1.5.0-rc.2, v1.5.0-rc.3 and v1.5.0
all died at this step before any image was built or test run.

Use the configured OCIR_USER / OCIR_TOKEN secrets, matching
build-main.yaml and pull-requests.yaml, and isolate the Docker config
per job (Set up Docker config step + DOCKER_CONFIG on the login and
build steps) so login does not clobber the shared ~/.docker/config.json
on the self-hosted runner.

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-22 16:32:43 +05:00
myasnikovdaniil 6329ffecfc fix(ci): create buildx builder in the build step's DOCKER_CONFIG (#2962)
## What this PR does

The `Build cache (main)` workflow (`build-main.yaml`, added in #2948)
failed on
its first run:
https://github.com/cozystack/cozystack/actions/runs/27929277423

The `Set up Buildx` step ran without `DOCKER_CONFIG`, so
`docker/setup-buildx-action` created the docker-container builder under
the
runner's default `~/.docker`. The build step runs with
`DOCKER_CONFIG=$RUNNER_TEMP/.docker` (seeded earlier, holding the OCIR
auth), so
buildx couldn't find the builder and failed immediately:

```
ERROR: no builder "builder-6fb46242-…" found
```

The post-job `docker buildx rm` still succeeded — because the action's
cleanup
runs under the default config where the builder actually lived — which
confirms
the builder existed but in the wrong config store.

This sets `DOCKER_CONFIG` on the `Set up Buildx` step so the builder
instance is
created in the same config the build step reads.

Impact: only the main cache-warmer was broken. PR builds `--cache-from`
this
ref, so a missing cache is a cold build, not a failure — but the
warm-cache
speedup from #2948 wasn't being delivered until this fix.

### Release note

```release-note
NONE
```


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

## Summary by CodeRabbit

* **Chores**
* Improved build pipeline stability by ensuring consistent Docker
configuration across workflow steps. This enhances the reliability of
the container build and deployment process.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-22 14:34:30 +05:00
myasnikovdaniil 1564081f76 ci(release): auto-patch-release only the 2 newest release lines (#2856)
## What this PR does

The **Auto Patch Release** cron (`.github/workflows/auto-release.yaml`)
walked
*every* `release-X.Y` branch — 21 of them, back to `release-0.9` — and
cut a
`vX.Y.(Z+1)` tag whenever a branch had commits after its latest
published
release. This fires patch releases on long-EOL lines (e.g. `v1.3.6`),
whose
release PRs then sit blocked on unrelated CI failures and accumulate
stray
draft releases, tags and changelog PRs.

This restricts the cron to the **2 newest minor lines**
(`SUPPORTED_LINE_COUNT`).
The window is derived from the live branch list, so it slides
automatically as
new `release-X.Y` branches are cut — once `release-1.5` exists the
window
becomes `{1.5, 1.4}` and the trailing line retires with no further
edits.

Currently active: `release-1.4`, `release-1.3`. Skipped: 19 EOL lines.

### Release note

```release-note
ci(release): the automated patch-release job now only cuts releases for the two newest release branches; older EOL lines no longer receive automatic patch tags.
```


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

* **Chores**
* Updated the automated release workflow to focus patch automation on
only the two newest minor `release-X.Y` maintenance lines.
* Enhanced workflow logging to report the selected branches and any
older/EOL branches skipped.
* **Documentation**
* Refined release documentation to explain the sliding-window selection
for automated patch tagging, including how next patch versions are
determined and when tags are created.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-22 13:52:01 +05:00
myasnikovdaniil f9185e3f4b ci: add CodeQL workflow for Go (SAST) (#2851)
## What this PR does

Raises the OpenSSF Scorecard **SAST** check (was **0/10**) by adding
GitHub's recommended CodeQL static analysis for Go. CodeQL is free for
public repositories and is the SAST tool Scorecard recognizes directly.

- Triggers on push to `main`, PRs to `main`, and a weekly schedule (Mon
03:27 UTC, offset from the other Monday cron jobs).
- Results are uploaded to the code-scanning dashboard via
`github/codeql-action/analyze`.

### Why `autobuild`

The Go analysis uses **`build-mode: autobuild`**. Go's CodeQL extractor
only supports `autobuild` or `manual` (not `none`), so autobuild is the
low-friction choice. It discovers every `go.mod` in the tree and
extracts each module separately — the root module, `api/apps/v1alpha1`,
and the three nested image modules a root `go build ./...` never reaches
— giving broader coverage than a single root build. Before falling back
to the Go build it first attempts `make` from the repo root, which runs
the default Makefile goal (including a network `git fetch upstream
--tags`). `setup-go` pins the toolchain to the `go.mod` version so the
extractor uses the project's Go rather than whatever the runner ships.

### Security posture

- Top-level token is **read-only**; the `analyze` job opts into
`security-events: write` (plus `contents: read`, `actions: read`) only.
- `actions/checkout` is pinned to **v6.0.2** (matching `scorecard.yml`)
with `persist-credentials: false`.
- `codeql-action/init` and `/analyze` are pinned to the **v4.36.2 SHA**
— the same commit as `scorecard.yml`'s `upload-sarif`, keeping every
codeql-action ref in the repo on one commit and clear of the v3
deprecation warning.

### Verification

- `actionlint`: **exit 0**.
- Valid YAML.
- No `run:` steps → no untrusted-input injection surface.

> SAST is a ramping check: Scorecard credits the CodeQL workflow's
presence immediately and the score climbs toward full as subsequent
merged PRs get analyzed.

### Release note

```release-note
ci: add CodeQL static analysis (SAST) for Go, running on push/PR to main and weekly
```


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

* **Chores**
* Enabled automated security scanning via CodeQL on pushes, pull
requests targeting the main branch, and on a weekly schedule.
* Added workflow controls for concurrency and longer execution time,
with support for Go-based analysis using an autobuild setup.
* Updated the SARIF upload step to use the latest CodeQL action version
to improve reliability while maintaining secure, minimal permissions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-22 13:51:53 +05:00
Myasnikov DaniilandClaude Opus 4.8 e7abea9576 fix(ci): create buildx builder in the build step's DOCKER_CONFIG
The "Set up Buildx" step in build-main.yaml ran without DOCKER_CONFIG, so
docker/setup-buildx-action created the docker-container builder under the
default ~/.docker. The build step runs with DOCKER_CONFIG=$RUNNER_TEMP/.docker
(seeded before the builder existed and holding the OCIR auth), so it could not
find the builder and failed immediately with `no builder "builder-<uuid>"
found`. The post-job `docker buildx rm` still succeeded because the action's
cleanup runs under the default config where the builder actually lived.

Set DOCKER_CONFIG on the Set up Buildx step so the builder instance is created
in the same config the build step reads.

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-22 11:39:36 +05:00
Myasnikov Daniil 3bcab58453 ci(release): order skipped EOL branches numerically in the log
Address review feedback from lexfrei on .github/workflows/auto-release.yaml:82: the skipped-branches log used a lexicographic sort (release-0.9 after release-0.41). Reuse the existing lineNum helper so the log lists EOL branches in numeric order.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-21 14:26:15 +03:00
Myasnikov Daniil 94582fa07c ci(release): auto-patch-release only the 2 newest release lines
The Auto Patch Release cron walked every release-X.Y branch (21 of them,
back to release-0.9) and cut a vX.Y.(Z+1) tag whenever a branch had
commits after its latest published release. That fired patch releases on
long-EOL lines (e.g. v1.3.6), whose release PRs then sit blocked on
unrelated CI failures and accumulate stray draft releases, tags and
changelog PRs.

Restrict the cron to the SUPPORTED_LINE_COUNT (=2) newest minor lines.
The window is derived from the branch list, so it slides automatically as
new release-X.Y branches are cut — once release-1.5 exists the window
becomes {1.5, 1.4} and the trailing line retires with no further edits.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-21 14:25:43 +03:00
Myasnikov DaniilandClaude Opus 4.8 f3914d2053 ci(build): disable credential persistence in build-cache checkout
The build-main cache warmer is a privileged workflow (packages:write + an OCIR
push token) and never pushes git, so set persist-credentials: false to keep the
checkout's GITHUB_TOKEN out of .git/config where a later build step could read
it. (SHA-pinning the actions is handled repo-wide in #2849.)

Addresses non-blocking follow-up 7 on #2948.

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-20 16:35:50 +05:00
Myasnikov Daniil 89b6c401a6 ci(codeql): align checkout with scorecard.yml and drop redundant concurrency prefix
Address non-blocking review feedback from lexfrei: bump actions/checkout to the v6.0.2 SHA already used by scorecard.yml so the security workflows track one version, and remove the hardcoded 'codeql-' prefix from the concurrency group (github.workflow already expands to 'CodeQL').

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-19 14:08:00 +05:00
Myasnikov Daniil 6b1212313a ci(codeql): fix the cron comment that mislabeled stale as a Monday job
Address review feedback from lexfrei on .github/workflows/codeql.yml:10: stale.yaml runs '37 4 * * *' (daily), but the comment listed it among the other Monday cron jobs. Note scorecard and labels as the Monday jobs and stale as daily.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-19 14:07:43 +05:00
Myasnikov Daniil 445a2fb1dc ci(codeql): correct the autobuild comment to match observed behavior
Address review feedback from lexfrei on .github/workflows/codeql.yml:43: the previous comment claimed autobuild 'runs the Go build the same way go build ./... does'. The run log shows autobuild discovers all five go.mod files and extracts each module separately (including the three nested image modules a root build never reaches), and first attempts make from the repo root, which triggers a network git fetch upstream --tags. Rewrite the comment to document this accurately.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-19 14:07:28 +05:00
Myasnikov Daniil e40db8cd43 ci(codeql): pin codeql-action to v4.36.2 to clear the v3 deprecation warning
Address review feedback from lexfrei on .github/workflows/codeql.yml:60: v3 is on a deprecation path (the workflow's own run already warns about it) and v4 is GA. Bump init, analyze, and scorecard.yml's upload-sarif to the same v4.36.2 commit SHA.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-19 14:07:07 +05:00
Myasnikov DaniilandClaude Opus 4.8 eba31194d7 ci(build): isolate each PR build on its own ephemeral runner VM (#2939)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-18 10:40:05 +05:00
Myasnikov DaniilandClaude Opus 4.8 d3958c5bcb refactor(build): mode=max registry cache with a main-only warmer (#2938)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-18 10:40:05 +05:00
Aleksei Sviridkin 8b4d31866e ci: add Go-scoped Renovate configuration (#2850)
## What this PR does

Raises the OpenSSF Scorecard **Dependency-Update-Tool** check (was
**0/10**) by adding a Renovate configuration at `.github/renovate.json`.
Scorecard scores this check on the presence of a recognized config file,
so the check turns green as soon as this merges.

### Why Renovate (not Dependabot)

Renovate is already the cozystack org standard — it actively raises and
merges PRs on `cozystack/ansible-cozystack`, and `cozystack/talm` +
`cozystack/boot-to-talos` already ship `renovate.json`. The Renovate
GitHub App is authorized on the org, so this config is picked up
**without creating a new credential**. There is no Dependabot usage
anywhere in the org.

> If the app installation is scoped to "selected repositories",
`cozystack/cozystack` needs to be toggled into the existing installation
by an org admin — a one-click change, not a new auth artifact. The
Scorecard check does not depend on that toggle.

### Scoped to Go dependencies only (per the 2026-05-27 decision)

- **`enabledManagers: ["gomod"]`** — Renovate only ever touches Go
modules. It will **not** open PRs for Dockerfiles, Helm charts, shipped
component/operator versions, or GitHub Actions. Components and images
stay hand-managed.
- **`gomodTidy` + `gomodUpdateImportPaths`** run after each bump. The
`gomod` manager auto-discovers both the root module and the
`api/apps/v1alpha1` submodule.
- **Kubernetes libraries grouped** (`sigs.k8s.io/controller-runtime` +
`k8s.io/**`) so `client-go`/`apimachinery` never bump out of lockstep
with controller-runtime (a split bump fails to compile).
- **Go toolchain directive pinned** (`matchDepTypes: ["golang"]`
disabled) — `go.mod` is generated and a toolchain bump needs a matching
`setup-go` + codegen run, out of scope for an automated PR.

### "do not merge" on every bot PR (requirement)

- `automerge` and `platformAutomerge` are **off**.
- Every Renovate PR is labeled **`do-not-merge/hold`** + **`automated`**
on creation. Both labels already exist in `.github/labels.yml`;
`stale.yaml` already exempts `do-not-merge/hold` PRs from auto-close, so
held bumps won't rot.
- Bumps therefore stay parked until tests pass and a maintainer removes
the label after a hands-on check.

> Note: `do-not-merge/hold` is a **signal/marker** (cozystack uses
native GitHub, not Prow Tide). Combined with automerge being off,
nothing can auto-merge a held PR. Hard merge-blocking would need a
status check tied to the label, which would require branch-protection
changes that were explicitly excluded.

### Verification

- `renovate-config-validator` (Renovate v42): **Config validated
successfully.**
- Valid JSON.

### Release note

```release-note
ci: add a Go-scoped Renovate configuration (gomod only, no automerge, bot PRs held with do-not-merge/hold) to automate Go dependency updates
```


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

## Summary by CodeRabbit

* **Chores**
* Configured automated dependency management for Go modules, including
synchronized updates for Kubernetes-related dependencies, rate limiting
on concurrent updates, and manual approval requirements to ensure
controlled rollouts.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-09 23:51:51 +03:00
Aleksei Sviridkin c961ffe286 feat(ci): test-impact analysis E2E (default-on) + release E2E workflow (#2559)
## What this PR does

Adds **test-impact analysis (TIA)** to the PR E2E workflow so that only
bats files affected by the diff are exercised, and a **release-time E2E
workflow** that runs the full suite on every release tag to keep
coverage on the shipped code.

### 1. Test-impact analysis selector — `hack/select-e2e.sh`

Reads `packages/core/platform/sources/*.yaml` (the PackageSource
dependency graph) and emits the bats files affected by a PR diff. The
walk is the same one `cozypkg dot` renders.

| Input | Output |
|---|---|
| `packages/apps/postgres/values.yaml` | `postgres` |
| `packages/system/postgres-operator/...` | `postgres harbor`
(transitive) |
| `packages/system/cilium/...` | full suite (cilium has many transitive
`*-application` dependents) |
| `packages/library/cozy-lib/...` | full suite (library affects
everything) |
| `hack/e2e-install-cozystack.bats` | full suite (shared install affects
all apps) |
| `hack/e2e-apps/redis.bats` | `redis` (per-app edit, never escalates) |
| `docs/agents/contributing.md` | (empty — no E2E impact) |
| `dashboards/gpu/gpu-fleet.json` | (empty) |

Conservative fallbacks:
- An unrecognised `packages/*` path or a system source with no
`*-application` descendants escalates to the full suite, so a path
inside the graph is never silently dropped.
- Per-app bats edits are matched **before** the full-suite trigger, so
editing one bats file selects only that app rather than escalating.

### 2. Default-on TIA + `full-e2e` opt-out — `pull-requests.yaml`

- The `Select E2E tests` step runs by default and produces `apps` +
`skip` outputs.
- `Run E2E tests` runs unless `skip=true` (docs / dashboards / `*.md`
only).
- Adding the `full-e2e` label skips the selector step (output empty) and
falls through to the unchanged full-bats-list path — the safety hatch
when reviewers want the full suite on a PR.

### 3. `release-e2e.yaml` — full E2E on every release tag

Triggered on `v*.*.*`, `v*.*.*-rc.*`, `v*.*.*-beta.*`, `v*.*.*-alpha.*`.
Builds images + Talos image, then runs the full suite. Independent of
`tags.yaml`'s release publish flow — failure here is an alarm, not a
release blocker.

This closes the coverage gap left by making PR runs default to TIA: the
full suite is now exercised at tag-cut time against the shipped code.

## Tests

`hack/select-e2e_test.bats` covers 11 cases — single app, transitive
operator, networking/library full-suite, docs/dashboards skip,
`kubernetes-application` two-bats mapping, shared E2E helper, install
bats, per-app bats no-escalation, release-e2e workflow change.

YAML parse verified for both new workflow files.

## Workflow injection safety

All `${{ }}` contexts that could carry untrusted data
(`github.base_ref`, label arrays) flow through env vars, never directly
into shell text. `github.workspace` is also indirected through
`WORKSPACE` env in the release workflow.

### Release note

```
NONE
```


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

* **Chores**
* CI now runs a Test Impact Analysis to pick only affected apps,
reducing E2E runtime.
* Added a release-level E2E workflow that runs full acceptance tests for
version tags with deterministic sandboxing, artifact capture,
conditional retries, teardown, and report upload.
* E2E jobs accept selected-apps metadata and support conditional
full-suite overrides.

* **Tests**
* New test suite validates selection logic, operator expansion,
full-suite escalation, and docs-only/no-op behaviors.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-09 23:51:24 +03:00
Myasnikov Daniil 0d6aaf93ac ci: use autobuild build-mode for CodeQL Go
CodeQL 2.25.x rejects build-mode 'none' for Go ('Go does not support the
none build mode; use autobuild or manual'), which failed the initial run.
Switch to autobuild and add a SHA-pinned setup-go (go-version-file: go.mod)
so the autobuilder uses the project's Go toolchain rather than whatever the
runner ships. go build ./... already compiles cleanly, so autobuild
succeeds.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Assisted-By: Claude <noreply@anthropic.com>
2026-06-09 14:44:08 +05:00
Myasnikov Daniil 540c4a1315 ci: add CodeQL workflow for Go (SAST)
Raise the OpenSSF Scorecard SAST check (was 0/10) by adding GitHub's
recommended CodeQL analysis for Go. Runs on push and PR to main plus a
weekly schedule, and uploads results to the code-scanning dashboard.

- build-mode: none extracts Go source without compiling, so the job
  cannot fail on the monorepo's generated files, build tags, or the
  api/apps/v1alpha1 submodule. It still produces real alerts and is
  credited by Scorecard. Can be switched to autobuild later for deeper
  build-aware analysis.
- Top-level token is read-only; the analyze job opts into
  security-events: write only.
- codeql-action pinned to the same v3 SHA already used by scorecard.yml;
  checkout pinned by SHA.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Assisted-By: Claude <noreply@anthropic.com>
2026-06-09 14:44:08 +05:00
Myasnikov Daniil ac56d4cea1 ci: add Go-scoped Renovate configuration
Raise the OpenSSF Scorecard Dependency-Update-Tool check (was 0/10) by
adding a Renovate config. Renovate is already the cozystack org standard
(active on ansible-cozystack, talm, boot-to-talos), so the app is
authorized org-wide and this file is picked up without a new credential.

Scoped deliberately so automated bumps only ever touch Go dependencies:

- enabledManagers: [gomod] — Renovate ignores Dockerfiles, Helm charts,
  shipped component/operator versions, and GitHub Actions. Components and
  images stay hand-managed, per the 2026-05-27 decision.
- gomodTidy + gomodUpdateImportPaths run after each bump; the gomod manager
  auto-discovers both the root module and the api/apps/v1alpha1 submodule.
- controller-runtime and k8s.io/** are grouped so client-go and
  controller-runtime never bump out of lockstep.
- The Go toolchain directive is pinned (matchDepTypes golang disabled):
  go.mod is generated and a toolchain bump needs a matching setup-go and a
  codegen run, out of scope for an automated PR.
- automerge/platformAutomerge are off and every Renovate PR is labeled
  do-not-merge/hold + automated, so bumps are held until tests pass and a
  maintainer reviews them by hand.

Validated with renovate-config-validator (config validated successfully).

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Assisted-By: Claude <noreply@anthropic.com>
2026-06-08 20:53:51 +05:00
Myasnikov Daniil 53edfc1f50 test(e2e): capture COSI bucket state in failure diagnostics and cozyreport
Run 27057500380 failed on harbor's BucketClaim never reaching
bucketReady=true, and neither artifact could explain why: the per-app
diagnostics dump has no bucket objects, and cozyreport collects
neither COSI resources nor objectstorage-controller / seaweedfs
provisioner logs — by the time the report is taken, the bucket state
has moved on.

Add a COSI module to cozyreport.sh (bucketclaims/bucketaccesses/
buckets/classes as custom-column tables plus full YAML, controller and
per-namespace provisioner logs) and a bucket-readiness snapshot to the
per-app failure dump in the PR workflow. The COSI CRDs ship no printer
columns, so plain kubectl get would show only NAME/AGE — readiness
fields are pulled explicitly.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-07 18:10:42 +05:00
Myasnikov Daniil d8d4c71e0f ci(pull-requests): dump cluster state on Install Cozystack failure
Mirror the diagnostics group already on `Run E2E tests` to the
`Install Cozystack into sandbox` step. Without this, install-step
failures (e.g. an HR not reaching Ready inside the bats `kubectl wait`
window) surface only as "error: timed out waiting for the condition
on helmreleases/<name>" with no cluster snapshot — re-running is the
only way to triage, which is the exact pattern the parent commit
removed.

On failure, dump three groups:

- `kubectl get hr -A -o wide` (last 100 lines): which HRs exist and
  their Ready/Status columns.
- `kubectl describe hr` for each not-Ready HR (tail -50): the
  Conditions block surfaces helm install/upgrade errors, missing
  CRDs, and chart-render failures — the actual cause.
- `kubectl get events -A --sort-by=.lastTimestamp` (last 50): cluster
  events that often pinpoint the failing pod/PVC/job.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-05 19:12:56 +05:00
Myasnikov Daniil 199781a7a5 ci(pull-requests): report total attempt count in prepare-env retry log
Address review feedback from coderabbitai on
.github/workflows/pull-requests.yaml:235: $attempt is incremented only on
failure, so first-try success printed "completed after 0 attempts". Use
$((attempt + 1)) so the message reflects the total number of runs.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-06-05 19:12:56 +05:00
Myasnikov Daniil e3b54cb201 ci(pull-requests): drop 3x retry on Run E2E + Install Cozystack
Audit of 30 successful PR runs found that across 5 sampled failure
attempts, **25/25 retries** on `Run E2E tests` failed — the retry
loop never recovered a flake, only stretched deterministic failures
and tripled diagnostic wall-time. Same data shape on `Install
Cozystack`. Drop both.

`Prepare environment` keeps its 3x retry — that step is pure
infrastructure (Talos image download, sandbox VM boot, network)
where transient runner hiccups warrant a retry.

On failure, the test step now captures `kubectl get hr -A -o wide`
and `kubectl get events -A` under a collapsible group so triage
starts with the actual broken-state snapshot.

Depends on:
- #2508 — installer namespace bootstrap (Helm namespace-ownership conflict)
- #2509 — operator HelmRelease config knobs (`seaweedfs-system` 2-min wait race)
- #2528 — harbor bucket-secret + BucketInfo gating (harbor ValuesError)
- #2529 — objectstorage-controller BucketAccess conflict retry
- the daniil/split-vminstance PR (vminstance disk race + VM IP/ready timeouts)
- the daniil/split-event-driven PR (existence backstops surfacing real errors)

Until those land, dropping the retry will fail CI for unrelated PRs
that hit the seaweedfs / harbor / installer / vminstance races.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Assisted-By: Claude <noreply@anthropic.com>
2026-06-05 19:12:56 +05:00
myasnikovdaniil 26574f08f3 ci(release): repair orphaned draft tag_name on retag (#2761)
## What this PR does

When a release-cut PR is merged after the underlying git tag was deleted
and re-created (e.g. the release branch was force-updated between the
initial tag push and the merge), GitHub orphans the corresponding draft
release — its `tag_name` becomes `untagged-<hash>` while `name` is
preserved.

The "Publish draft release" step in `pull-requests-release.yaml` looks
up the draft strictly by `tag_name === tag` and throws `Draft release
for <tag> not found`, blocking the release. Observed on run
[#26558920814](https://github.com/cozystack/cozystack/actions/runs/26558920814/job/78236959976)
cutting v1.4.2, and the same error fired on the earlier [v1.4.1 finalize
run](https://github.com/cozystack/cozystack/actions/runs/26271301257).

This change adds a fallback: when the primary lookup misses, search for
a draft where `name === tag` and `tag_name` matches `/^untagged-/`. If
found, repair the draft's `tag_name` to the real tag via `updateRelease`
before publishing, so the released artifact correctly points at the git
tag created earlier in the same job. The strict-fail path is preserved
when neither lookup matches.

The fix is forward-only and does **not** auto-recover the currently
stuck v1.4.2 draft — that needs a one-time manual `tag_name` patch + job
re-run, or publishing the draft via the GitHub UI.

### Release note

```release-note
ci(release): the release-finalize workflow now repairs an orphaned draft release (tag_name == `untagged-<hash>` after a tag delete + re-create) by re-linking it to the actual tag before publishing.
```

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

## Summary by CodeRabbit

* **Chores**
* Improved the robustness of the release publication process to handle
edge cases when locating draft releases, ensuring more reliable and
consistent release deployments.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/cozystack/cozystack/pull/2761?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-05 13:54:00 +05:00
Aleksei Sviridkin bac670d07b ci: bump cozyvalues-gen to v1.6.0 in pre-commit workflow
v1.6.0 adds the date-time string-format alias and the @example
annotation. Both are additive, opt-in directives: charts that do not
use them regenerate byte-identical schema, README and Go type output,
so the pre-commit check stays green without touching any committed
artifact.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-06-02 15:20:43 +03:00
Myasnikov Daniil 22187e069d ci(release): repair orphaned draft tag_name on retag
When the git tag was deleted and re-created between draft creation
and merge, GitHub orphaned the draft release: tag_name became
"untagged-<hash>" while `name` was preserved. The finalize step
looked up the draft by tag_name and threw `Draft release for <tag>
not found`, blocking the release.

Fall back to matching by `name` only when the candidate is the
GitHub-orphaned form (tag_name matches /^untagged-/), repair the
draft's tag_name via updateRelease, then publish. Hard-fail is
preserved when neither lookup matches.

Same failure recurred on the v1.4.1 finalize run; this commit
prevents recurrence but does not auto-recover the stuck v1.4.2
draft.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-05-28 12:00:54 +05:00
Aleksei Sviridkin 3756f5c459 feat(networking): Gateway API support via Cilium (supersedes #2213) (#2470)
## What this PR does

Replaces 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`. Supersedes
#2213 / #2208. Stacked on top of #2468.

## Architecture

The chart renders one `gateway.cozystack.io/v1alpha1 TenantGateway` CR
per tenant that owns its own Gateway. The cozystack-controller
reconciles the actual `Gateway`, per-tenant `Issuer`, `Certificate`
(wildcard or per-listener), and HTTP→HTTPS redirect `HTTPRoute` from
there. Helm does not render `Gateway` or `Certificate` directly — that
prevents the Helm-vs-controller race on `Gateway.spec.listeners` that
route-driven listener additions would otherwise cause.

```
┌── TenantGateway CR (rendered by extra/gateway chart for owning tenants)
│
└──▶ cozystack-controller reconciles:
        ├─ Gateway (with dynamic listeners, label-based allowedRoutes)
        ├─ Issuer (per-tenant ACME account)
        ├─ Certificate(s) — wildcard + per-child SANs in DNS-01, per-listener in HTTP-01
        ├─ HTTPRoute (controller-owned http→https redirect)
        ├─ Namespace label patching (cozy-* system namespaces from spec.AttachedNamespaces)
        └─ Watches HTTPRoute / TLSRoute attachments to materialise listeners
```

## Inheritance: tenants attach by default, opt into their own Gateway

A tenant gets a dedicated Gateway (own LB Service, own LB IP, own
Certificate) only when it explicitly asks via
`tenant.spec.gateway=true`. Every other tenant in the tree publishes
through the Gateway of the nearest ancestor that owns one — same shape
as the existing `_namespace.ingress` inheritance.

The `apps/tenant` chart writes a `namespace.cozystack.io/gateway` label
on each tenant namespace, carrying either the tenant's own name (when
owning a Gateway) or the inherited ancestor name (when inheriting). The
Gateway's listener `allowedRoutes.namespaces.selector` is keyed on that
label, so the same selector admits routes from every namespace pointing
at the owner — descendants and cozy-* system namespaces alike.
cozystack-controller patches the same label onto every namespace in
`tgw.Spec.AttachedNamespaces` and garbage-collects labels it wrote when
an entry is removed from the attach list (annotation-gated GC so labels
written by the apps/tenant chart are never stripped).

In DNS-01 mode, the controller extends the parent's wildcard Certificate
with `<child-apex>` + `*.<child-apex>` SANs per inheriting child, and
adds a `*.<child-apex>` HTTPS listener per child apex referencing the
same cert. Without this expansion the parent's single-level wildcard
cannot match a child route's hostname (`harbor.alice.example.org` is two
labels past the parent's `*.example.org`).

Opt-out into own Gateway is the right choice when: a tenant needs its
own LB IP (DNS pinned, firewall rule), the apex is not derived from the
parent (custom `host` value the ancestor's cert can't cover), or the
tenant wants its own ACME account / cert authority. Otherwise leave
`gateway` unset and inherit.

## End-to-end flow

1. Platform: `gateway.enabled=true` toggles cert-manager solver,
switches per-system-app templates from Ingress to HTTPRoute / TLSRoute.
2. Per tenant: explicit `tenant.spec.gateway=true` renders a
`TenantGateway` CR and triggers the controller to materialise the owner
Gateway. Tenants with the field unset inherit through
`_namespace.gateway` propagation in the tenant chart.
3. Adding a published app under any tenant (owner or inheriting) is just
deploying its HTTPRoute. The controller picks up the hostname, adds a
per-listener Certificate (HTTP-01) or extends the wildcard cert's SANs
(DNS-01), and updates the owner Gateway's listeners.

Defaults stay legacy: `gateway.enabled=false`,
`tenant.spec.gateway=false`. Existing clusters upgrade unchanged.

## External IP allocation — mechanism-agnostic

The per-tenant Gateway's auto-created `LoadBalancer` Service draws its
IP from whatever LB allocator the cluster admin has configured at the
platform layer — same shape as ingress-nginx today. The tenant API stays
mechanism-agnostic — no `gatewayIP` field, no allocator-specific
manifest in the tenant chart. Cozystack itself ships MetalLB installed
but does not render any `IPAddressPool` / `L2Advertisement` /
`BGPAdvertisement` from this chart; admins set up the allocator that
suits their environment (MetalLB pool with L2 / BGP, Cilium LB-IPAM with
announcer, robotlb against a cloud provider, or
`Service.spec.externalIPs` pinning).

If a tenant needs a specific address (DNS already pinned, firewall rule,
etc.), the operator pre-allocates it on the admin side: either
pre-create the Service with `loadBalancerIP` set, or hand the tenant a
reference to a named admin-managed pool. Per-Service IP uniqueness is
the allocator's responsibility — same as for any other LoadBalancer
Service.

## Cert mode: HTTP-01 (default) vs DNS-01 (opt-in)

`publishing.certificates.solver` selects how the controller sources TLS
certs.

**HTTP-01 (default)** — per-app HTTPS listener + per-app Certificate.
New apps require zero platform-side config: deploy the HTTPRoute,
controller does the rest. Inheriting children's hostnames pick up
per-listener certs the same way the owner's own apps do.

**DNS-01 (opt-in)** — single wildcard Certificate (`<apex>` +
`*.<apex>`) covering every published app under the apex, plus per-child
SANs (`<child-apex>` + `*.<child-apex>`) for every inheriting tenant.
Pick this for clusters where many apps share the apex and Let's Encrypt
rate limits matter. The DNS provider account must be able to write TXT
records under every apex level the parent serves — for deeply-nested
inherited children that requires zone delegation or a wide-scope
provider credential.

| `publishing.certificates.dns01.provider` | Required
`publishing.certificates.dns01.<provider>` keys |
| --- | --- |
| `cloudflare` (default) | `cloudflare.secretName`,
`cloudflare.secretKey` |
| `route53` | `route53.region`, `route53.secretName` (and `accessKeyID`
if not on IRSA) |
| `digitalocean` | `digitalocean.secretName` |
| `rfc2136` | `rfc2136.nameserver`, `rfc2136.tsigKeyName`,
`rfc2136.secretName` |

The platform chart wires every provider's keys into `_cluster.dns01-*`;
the per-tenant gateway chart and the cluster-wide `cluster-issuers.yaml`
both read them.

## Strengthens admission coverage on apps.cozystack.io/*

The `cozystack-tenant-host-policy` probe in `gateway.bats` initially
looked like a flake — the apply that was supposed to be VAP-rejected
just succeeded. Investigation traced it to the custom REST handler at
`pkg/registry/apps/application/rest.go`. genericapiserver hands every
storage method a callback into the validating admission chain:
`createValidation` for Create, `updateValidation` for Update,
`deleteValidation` for Delete. `Update` was correctly invoking its
callback since 23e399bd, but `Create` and `Delete` accepted the
parameter and never called it — admission was silently bypassed for both
verbs on every `apps.cozystack.io` resource (Tenant, MariaDB, Postgres,
Kubernetes, …).

This PR wires the missing `createValidation` and `deleteValidation`
calls into Create and Delete. Every ValidatingAdmissionPolicy and
ValidatingWebhook against `apps.cozystack.io/*` now fires on all three
verbs as the API contract requires. Per KEP-3488 aggregated APIServers
are responsible for enforcing admission on their own kinds;
cozystack-api is configured to do so via
`genericoptions.NewRecommendedOptions`, the only thing missing was the
explicit `*Validation` callbacks in the custom REST.

## Security — five guards, grouped by what they defend

Tenants in Cozystack interact with the platform exclusively through
`apps.cozystack.io/*` resources (Tenant, Bucket, Kubernetes, …) served
by `cozystack-api`. Tenant RBAC (`cozy:tenant:*` aggregated to a
RoleBinding in the tenant's own namespace) does not grant write access
to `gateway.networking.k8s.io/*`, core `Namespaces`, or
`cozystack.io/Package`. The protections below split into three groups by
who they defend against:

**Tenant-user-input gates.** Layer 4 (`cozystack-tenant-host-policy`).
`Tenant.spec.host` is the user-supplied field that surfaces as a
security boundary at the hostname layer; gated on every Create / Update
via `cozystack-api`'s admission chain.

**Defense-in-depth.** Layers 1, 2, 5, 7. These do not protect against
tenant-user input (tenants don't hold the relevant RBAC). They guard
against bugs in cozystack-controller / Flux, supply-chain compromise of
an app chart that emits Gateway API resources, and confused-deputy
mistakes by a cluster admin. Fail-closed via `failurePolicy: Fail` +
`validationActions: [Deny]`.

The five layers themselves:

1. **Listener `allowedRoutes` label selector** —
`namespace.cozystack.io/gateway = <owner-tenant-name>`
(controller-written for cozy-* via `AttachedNamespaces`, Helm-written
for tenant namespaces via the tenant chart, both feeding into the same
Gateway selector).
2. **`cozystack-gateway-hostname-policy`** — VAP on `Gateway`
CREATE/UPDATE. Restricts listener hostnames to the namespace's
`namespace.cozystack.io/host` apex.
4. **`cozystack-tenant-host-policy`** — VAP on `Tenant` CREATE/UPDATE.
5. **`cozystack-namespace-host-label-policy`** — VAP on core `Namespace`
CREATE/UPDATE. Immutability gate on the host label.
7. **`cozystack-route-hostname-policy`** — VAP on `HTTPRoute` and
`TLSRoute` CREATE/UPDATE. Closes the hostname-hijack vector across
namespaces under inheritance — a child cannot claim a hostname outside
its own apex.

The previous Layer 3 (`cozystack-gateway-attached-namespaces-policy`)
and Layer 6 (render-time `fail` in cozystack-basics) banned `tenant-*`
entries in `publishing.gateway.attachedNamespaces` on the assumption
that the attach list was the hijack vector. Under inheritance the attach
surface is the label selector, not the static list; the hijack vector is
closed by Layers 4/5/7 independently. Both Layer 3 and Layer 6 are
dropped — kept in numbering above for continuity with the earlier review
threads.

## Foreign-takeover guards

Five reconcile paths refuse to silently rewrite a pre-existing object
that shares the controller-derived name but carries no `OwnerReference`
back to the TenantGateway: `Gateway`, redirect `HTTPRoute`, per-tenant
`Issuer`, wildcard `Certificate`, per-listener `Certificate`. An
operator who hand-pinned a Certificate or Issuer at the controller's
derived name (private CA, manual cert pinning) gets an explicit
`Ready=False/ReconcileError` condition instead of having their config
silently destroyed and the resource re-issued from a different ACME
account.

The namespace-label patching path applies the same discipline:
cozystack-controller only writes / strips
`namespace.cozystack.io/gateway` on namespaces it annotates with
`cozystack.io/gateway-attached-by`. Labels written by the apps/tenant
chart (no annotation) are never touched, so inheritance for tenant
namespaces survives every reconcile.

## Tests

- **Go controller tests** — 50+ cases in
`internal/controller/tenantgateway/`: idempotency, OwnerReference
cascade, multi-parentRef status, HTTP-01↔DNS-01 mode-transition cert
lifecycle, foreign-takeover refusals (Gateway / HTTPRoute / Issuer /
Certificate / per-listener Certificate), DNS-01 provider matrix
(cloudflare / route53 / digitalocean / rfc2136), HTTPS Kinds=[HTTPRoute]
restriction, fail-closed CEL on missing host label, status condition
wiring. Plus, for inheritance: label-based allowedRoutes selector shape,
namespace label patch + garbage-collect via owner annotation, Helm-owned
label safety, DNS-01 wildcard SAN expansion per inheriting child,
per-child wildcard listener materialisation.
- **Admission-chain tests** —
`pkg/registry/apps/application/rest_admission_test.go` pins
`createValidation`, `updateValidation`, `deleteValidation` paths via
sentinel callbacks.
- **helm-unittest** across `extra/gateway` (TenantGateway CR rendering,
both modes), `core/platform` (DNS-01 wiring), `apps/tenant`
(gateway-effective decision matrix including inheritance),
`system/cozystack-basics` (three VAPs + Layer 7 VAP + tenant-root
namespace label including the conditional
`namespace.cozystack.io/gateway: tenant-root` + cozystack-values-secret
`_namespace.gateway` propagation), `system/cert-manager-issuers` (full
DNS-01 provider matrix), `system/cozystack-api` and `system/dashboard`
(Ingress↔TLSRoute/HTTPRoute toggle), `system/monitoring` (Grafana /
Alerta Ingress↔HTTPRoute toggle).
- **e2e bats** — `hack/e2e-apps/gateway.bats` (17 cases) covering
GatewayClass Accepted, Gateway Programmed + LB Service, HTTPRoute
Accepted with status, VAP rejects cross-tenant hostname hijack, VAP
rejects `tenant.spec.host` writes from non-trusted SA
(regression-detector for the admission-chain fix), VAP rejects
`namespace.cozystack.io/host` label changes from non-trusted SA,
route-hostname VAP rejects HTTPRoute hostnames outside the apex, and
three new cases for inheritance — Package admission accepts `tenant-*`
in `attachedNamespaces` (regression guard for the dropped ban), child
tenant without explicit gateway inherits `_namespace.gateway` from a
Gateway-owning parent in lockstep across the namespace label and the
cozystack-values Secret, and the cross-namespace attach end-to-end where
a child tenant's HTTPRoute reaches Accepted on the parent's Gateway via
the inheritance label.
- **CI wiring** — `make test-controllers` target invoked from
`pull-requests.yaml` runs `go test ./internal/...`.

## Backward compatibility

The only always-on change is the flip of `cilium.envoy.enabled` +
`cilium.gatewayAPI.enabled` to `true` (extra `cilium-envoy` DaemonSet,
~100 MB RAM/node idle). Everything else is gated behind
`gateway.enabled=false` / `tenant.spec.gateway=false`. Existing clusters
see no behavioural change until an operator explicitly opts in.

The cozystack-api admission-chain fix is unconditional. Existing
operators who do not define any VAP / admission webhook on
`apps.cozystack.io` resources see no observable change. Operators who DO
have their own webhooks/VAPs against these kinds will start seeing them
fire on Create and Delete (as intended) — review your policies before
merging if you maintain custom admission for `apps.cozystack.io/*`.

Operators who set `publishing.gateway.attachedNamespaces` to include
`tenant-*` entries used to hit a render-time fail or an admission
rejection — now the apply succeeds and the listed tenant namespaces
simply pick up the gateway-attach label alongside the cozy-* system
namespaces. This matches the inheritance model and was kvaps' explicit
ask in the May 25 review.

## Release note

```release-note
- Adds Gateway API support via Cilium, materialised per-tenant via the new `gateway.cozystack.io/v1alpha1 TenantGateway` CRD reconciled by `cozystack-controller`. Opt-in via `publishing.gateway.enabled=true` at the platform level; per-tenant opt-in via `tenant.spec.gateway=true` for dedicated Gateway / LB IP, otherwise the tenant inherits the nearest ancestor's Gateway via a label-based `allowedRoutes` selector (same shape as the existing `_namespace.ingress` inheritance). Includes HTTP-01 (default) and DNS-01 (opt-in, with cloudflare / route53 / digitalocean / rfc2136 providers) cert solvers; DNS-01 extends the parent's wildcard Certificate with per-child apex SANs and adds a `*.<child-apex>` listener per inheriting tenant. Tenant API is mechanism-agnostic for IP allocation — the LoadBalancer Service draws its IP from the cluster's admin-configured allocator (MetalLB / Cilium LB-IPAM / robotlb / externalIPs).
- Strengthens admission coverage on `apps.cozystack.io/*` resources: `cozystack-api` now invokes `createValidation` and `deleteValidation` callbacks on Create and Delete (Update was already wired). Operators with custom ValidatingAdmissionPolicies or webhooks targeting `apps.cozystack.io/*` will see them fire on all three verbs as the API contract requires; review your policies before merging if you maintain such admission.
```
2026-05-26 10:29:30 +03:00
myasnikovdaniil 2cb16d5ae6 refactor(build): standardize image tagging to fix concurrent PR push conflicts (#2711)
## What this PR does

Concurrent PR builds were racing in the OCIR registry. Every package
Makefile tagged its images via `settag`, which on any non-release commit
collapsed to `:latest` — so every image across every PR was pushed to
the same tag, and parallel builds got 409s on the manifest PUT. Two
outliers (`metallb`, `redis-operator`) used hardcoded version tags and
collided across PRs unconditionally.

This PR replaces `settag` and the `TAG=latest` fallback in
`hack/common-envs.mk` with a single `image-tags` macro driven by three
env vars:

| Env | Meaning |
|---|---|
| `IMAGE_TAG` | Build-unique tag, always pushed |
| `PUBLISH_VERSIONED` | Also push `:<component-version>` (when provided)
|
| `PUBLISH_FLOATING` | Also push `:latest` |

Wired into the two workflows:

- **`pull-requests.yaml`** — `IMAGE_TAG=pr-<N>-<sha>`, both `PUBLISH_*`
off. Every PR build pushes a unique tag set; no collisions, no `:latest`
move.
- **`tags.yaml`** — `IMAGE_TAG=<ref_name>`, both `PUBLISH_*` on. Release
output preserved (component versions + `:latest`) plus a uniform
`:<git-tag>` handle on every image for traceability.
- **Local** — defaults to `IMAGE_TAG=dev`, no flags, `PUSH=0`.

All 30 package Makefiles converted to `$(call
image-tags,<repo>,<versioned-tag>)`. Image consumers reference digests
pinned into `values.yaml` by each Makefile, so the tag is decorative for
cluster-side pulls — the build-unique tag is sufficient for E2E.

Also drops the redundant `:<component-version>-<git-tag>` form (cilium,
linstor, matchbox, ubuntu-container-disk, kubevirt-*,
cluster-autoscaler). Nothing in the repo or downstream values.yaml
consumed it; the same information is preserved via the new `:<git-tag>`
plus the existing `:<component-version>` tags on release.

`ubuntu-container-disk` builds a separate image per Kubernetes version
in a loop, so its primary tag became `:<k8s-ver>-<IMAGE_TAG>` to prevent
loop iterations within a single build from pushing to the same tag.

Verified by dry-running `make -n image` across representative packages
(simple, dual-tag, skopeo, flux-push, loop) under all four contexts (PR
/ release / main / dev) — tag sets are unique and behave as expected.

### Release note

```release-note
refactor(build): PR CI now pushes images under a unique `pr-<N>-<sha>` tag per build instead of `:latest`. Concurrent PR builds no longer race in the registry. Release builds keep the existing `:<component-version>` and `:latest` tags and additionally publish a uniform `:<git-tag>` handle across every image. The redundant `:<component-version>-<git-tag>` tag (cilium, linstor, matchbox, ubuntu-container-disk, kubevirt-*, cluster-autoscaler) is no longer published — image references in values.yaml are digest-pinned and were the only legitimate consumers.
```


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

* **Chores**
* Centralized and simplified container image tagging across build
workflows.
* PR builds now use unique image tags derived from PR metadata for
better isolation.
* Added configurable flags to control publishing of versioned and
floating tags, making release tagging behavior more predictable.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/cozystack/cozystack/pull/2711?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-25 22:16:57 +05:00
Aleksei Sviridkin 943441b0c7 ci: bump cozyvalues-gen pin to v1.5.0
v1.5.0 adds the @immutable annotation support
(cozystack/cozyvalues-gen#24). Without the bump, pre-commit re-runs
make generate with the v1.4.0 binary, silently strips any
x-kubernetes-validations entries that downstream PRs add to
values.schema.json / embedded openAPISchema, and reports drift against
the committed artefacts.

This is a no-op for the current main: no chart currently uses
@immutable, so regeneration with v1.5.0 produces identical output to
v1.4.0. Unblocks cozystack/cozystack#2639 (apply @immutable to
storageClass across stateful apps).

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-05-25 17:45:27 +03: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
Timur Tukaev 688f73f8f0 ci: pin Scorecard workflow actions by SHA
The initial Scorecard workflow used floating major-version tags
(@v2, @v4, @v3). ossf/scorecard-action does not publish a floating
v2 tag — only specific patch releases — so the first scheduled run
failed with `Unable to resolve action ossf/scorecard-action@v2`.

Pin every action by its full commit SHA with a trailing comment
showing the human-readable version. This also satisfies the
`Pinned-Dependencies` Scorecard check itself.

- actions/checkout@de0fac2 (v6.0.2)
- ossf/scorecard-action@4eaacf0 (v2.4.3)
- actions/upload-artifact@043fb46 (v7.0.1)
- github/codeql-action/upload-sarif@03e4368 (v3)

Signed-off-by: Timur Tukaev <timur.tukaev@aenix.io>
2026-05-23 09:25:31 +05:00
Timur Tukaev 2396d08ee5 ci: add OpenSSF Scorecard workflow
Runs the OpenSSF Scorecard action on:
- weekly schedule (Mon 06:00 UTC)
- branch protection rule changes
- pushes to main
- manual dispatch

Publishes results to:
- the public OpenSSF Scorecard API (scorecard.dev) for transparency
- the GitHub code-scanning dashboard (Security tab) for triage

No README badge is added yet — the badge will be introduced in a
follow-up PR once the first scans have run and any quick-win
improvements (token permissions, dependency pinning) have landed.

Aligns with the project's CNCF Incubation readiness work and the
security self-assessment in progress.

Signed-off-by: Timur Tukaev <timur.tukaev@aenix.io>
2026-05-23 09:12:06 +05:00
Myasnikov Daniil 7cc0194f20 ci(pre-commit): bump cozyvalues-gen to v1.4.0
v1.4.0 adds the `intOrString` type alias used by the postgres package's
`postgresql.parameters` schema in this PR. CI must install v1.4.0 so the
"Verify generated code is up to date" check regenerates the same schema
shape (anyOf + x-kubernetes-int-or-string: true) rather than reverting
to {type: string}.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-05-21 16:04:08 +05:00
Myasnikov Daniil ed0297eb87 refactor(build): standardize image tagging to fix concurrent PR push conflicts
Every package Makefile tagged images via `settag` which, on a non-release
commit, collapsed to `:latest`. Concurrent PR builds therefore raced on every
image's `:latest` manifest in the OCIR registry. Two outliers (metallb,
redis-operator) used hardcoded version tags and collided across PRs even when
`settag` would not have.

Replace `settag` and the `TAG=latest` fallback with an `image-tags` macro
driven by three env vars:
  - IMAGE_TAG          — build-unique tag, always pushed
  - PUBLISH_VERSIONED  — also push :<component-version>
  - PUBLISH_FLOATING   — also push :latest

PR CI sets IMAGE_TAG=pr-<N>-<sha> with both PUBLISH_* off, so every push is
unique across runs. Release CI sets IMAGE_TAG=<git-tag> with both PUBLISH_*
on, preserving the previous release output and adding a uniform :<git-tag>
tag across every image. Local builds default to IMAGE_TAG=dev with PUSH=0
already off.

Image consumers reference digests pinned into values.yaml by each Makefile,
so the tag is decorative for cluster-side pulls; the build-unique tag is
sufficient for E2E.

Drops the redundant `:<component-version>-<git-tag>` form (cilium, linstor,
matchbox, ubuntu-container-disk, kubevirt-*, cluster-autoscaler) — nothing
in the repo or downstream values.yaml consumed it. The same information is
preserved via the new uniform `:<git-tag>` plus the existing
`:<component-version>` on release.

ubuntu-container-disk builds a separate image per Kubernetes version in a
loop. Switched its primary tag to `:<k8s-ver>-<IMAGE_TAG>` so loop
iterations within a single build do not push to the same tag.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-05-21 15:23:18 +05:00
Myasnikov Daniil 00d47ee684 ci(tags): include data/versions in website docs commit
`release_next.sh` and `init_version.sh` (via `update-all → init-version`)
create `data/versions/<DOC_VERSION>.yaml` for new versions, but the
`update-website-docs` job only staged `content` and `hugo.yaml`. The new
file was silently dropped from the website PR, breaking
`{{< version-pin >}}` lookups for the released minor.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-05-21 12:16:10 +05:00
Andrei KvapilandClaude 71c713f002 ci(workflows): rebuild breakpoint authorized-users from active contributors
Replace MAINTAINERS.md snapshot with a list derived from actual recent
activity on main (commits, merged PRs and reviews over the last three
months). MAINTAINERS.md had drifted in both directions — listing people
no longer active on the project while missing several current
maintainers — so copying it verbatim was both handing SSH access to
inactive accounts and locking out people who would actually use this
debug feature.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2026-05-20 16:17:08 +02:00
Andrei KvapilandClaude 6c8e111ab6 ci(workflows): add SSH breakpoint on E2E failure for debug/release PRs
When the e2e job fails on a PR carrying the `debug` or `release` label
(release PRs are bot-created, so the label implies maintainer authorship),
hold the runner and open an SSH breakpoint via cozystack/breakpoint-action
(a fork of namespacelabs/breakpoint-action pinned by SHA) so a maintainer
can attach, inspect Talos/Cozystack state, and resume.

The fork adds three things on top of upstream:

* `mode: pause-idle` — waits 20m for the first SSH connection, then keeps
  the workflow paused while sessions are active, exiting 10m after the
  last session disconnects. Saves runner-minutes vs a fixed timeout.

* `endpoint` step output + `::notice::` annotation — surfaces the SSH
  endpoint while the step is still paused (step logs are buffered until
  step completion, so the endpoint would otherwise be invisible).

* A dedicated Check Run "Breakpoint Open" with conclusion=failure
  carrying the SSH endpoint and sandbox-entry hint in output.summary
  (Markdown, rendered in the UI). Surfaces as a standard ✗ in
  `gh pr checks` so any CI watcher sees the open breakpoint without
  bespoke marker parsing. Updated to conclusion=success when the
  breakpoint exits.

The step is configured via the `BREAKPOINT_ENDPOINT` repo variable. In
fork PRs, repo variables are not exposed, so the step is naturally
skipped and forks cannot reach the self-hosted rendezvous server. The
`authorized-users` list is kept as a second defense layer at the
breakpoint level.

Permissions on the e2e job: `checks: write` (for Check Run create+update).

Upstream PR: namespacelabs/breakpoint-action#16

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2026-05-20 16:17:07 +02:00
Myasnikov Daniil b7fd200e4b ci(tags): make generate-changelog non-blocking
Mark the generate-changelog job with continue-on-error and switch the
update-website-docs gate to always() so a failed or skipped changelog
generation no longer blocks the website docs update. prepare-release
remains the hard gate.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-05-18 19:23:14 +05:00
Aleksei Sviridkin 315802d8fc ci(pull-requests): raise E2E job timeout from 120m to 180m
The 2-hour ceiling on the E2E job was clipping otherwise-working
runs on the oracle-vm-24cpu-96gb-x86-64 runners: image pulls,
multi-stage Talos bootstrap, full helm-release fan-out plus the
post-install bats matrix routinely consume 100-180 minutes of
wall-clock, and runs that would have eventually gone green were
getting cancelled by GitHub at the timeout.

Recent successful E2E runs on the same runner pool sit between
60 and 184 minutes; lift the cap to 180m to cover the long tail
without inviting truly-stuck jobs to linger forever.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-05-10 16:09:56 +03:00
Andrey Kolkov 8da1e7a40a feat(platform): implement backup strategy for postgres (#2552)
<!-- Thank you for making a contribution! Here are some tips for you:
- Use Conventional Commits for the PR title: `type(scope): description`
- Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore
- Scopes are not an exhaustive list — pick the most specific scope for
the change and extend the list when a genuinely new area appears.
Examples:
- System components: dashboard, platform, operator, cilium, kube-ovn,
linstor, fluxcd, cluster-api
- Managed apps: postgres, mariadb, redis, kafka, clickhouse,
virtual-machine, kubernetes
- Development and maintenance: api, hack, tests, ci, docs, maintenance
- Breaking changes: append `!` after type/scope (`feat(api)!: ...`) or
add a `BREAKING CHANGE:` footer
- If it's a work in progress, consider creating this PR as a draft.
- Don't hesistate to ask for opinion and review in the community chats,
even if it's still a draft.
- Add the label `backport` if it's a bugfix that needs to be backported
to a previous version.
-->

## What this PR does


### Screenshots

<!-- REQUIRED for UI changes: attach screenshots or screen recordings
demonstrating
the visual impact of your changes. PRs with UI changes without
screenshots will not be merged. -->

### Release note

<!--  Write a release note:
- Explain what has changed internally and for users.
- Start with the same `type(scope):` prefix as in the PR title
- Follow the guidelines at
https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md.
-->

```release-note
Implemented CNPG-strategy controller for postgres backups with supporting in-place and to-copy restores.
```

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

* **New Features**
* CNPG-based backup strategy for PostgreSQL with S3-backed object store
and controller support
* Option to reference existing Kubernetes Secret for S3 credentials
(configurable key names)
* Controller supports scheduled/ad-hoc backups and both in-place and
to-copy restores; safer credential handling (no inline keys)

* **Examples**
* Complete manifests: bucket, credentials, CNPG strategy, BackupClass,
Plan, BackupJob, RestoreJob, Postgres source/target

* **Security/RBAC**
* Controller RBAC expanded for CNPG resources; tests enforce no
cluster-wide Secret read

* **Documentation**
* README, chart values/schema and UI schema updated to document
secret-reference workflow and restore guidance

* **Tests**
* New end-to-end and unit tests covering CNPG flows, restore semantics,
cleanup behavior and RBAC constraints
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-07 18:32:07 +04:00
Andrey Kolkov ed5ae16e36 feat(platform): implement backup strategy for postgres application
Signed-off-by: Andrey Kolkov <androndo@gmail.com>
2026-05-07 13:12:54 +04:00
Myasnikov Daniil af317f16a4 fix(ci): stop backport workflow from cascading on its own PRs
Two compounding issues caused merging a backport PR to spawn another
backport (e.g. #2581 [Backport release-1.2] [Backport release-1.3] ...
from #2579):

1. pr-labeler auto-added the `backport` label to any PR whose title
   started with `[Backport release-X.Y]` (regression introduced in
   c8ed1c65 when pr-labeler.yaml was first added). korthout/backport-
   action puts that prefix on every backport PR it creates, so the
   label was being re-applied to every backport, satisfying the
   workflow's gate when the backport itself was merged.

2. Even with that fixed, an external labeling bot (dosubot) still
   re-applies `backport-previous` to backport PRs based on similarity
   to the parent. That bot is configured at the org level and cannot
   be controlled from this repo.

Drop the `toAdd.add('backport')` line from pr-labeler so the label
isn't propagated by us, and gate both backport jobs on
`pull_request.base.ref == 'main'` so backport PRs (which target
release-X.Y) cannot trigger the workflow regardless of what labels
end up on them.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-05-06 18:09:09 +05:00
Myasnikov Daniil 4672d88f72 fix(ci): fetch full history in E2E checkout for TIA selector
The "Select E2E tests" step runs `git diff --name-only
origin/${BASE_REF}...HEAD` inside the sandbox copy of the workspace.
Without fetch-depth: 0 the e2e job's checkout only resolves a single
commit, so origin/main is missing and the diff aborts with
"ambiguous argument 'origin/main...HEAD': unknown revision".

Mirror the Build job's setup and request full history.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-05-03 20:47:18 +05:00
Myasnikov Daniil 0788e94a7c feat(ci): test-impact analysis E2E (default-on) + release E2E workflow
Three changes that compose into a single E2E policy:

1. **Test-impact analysis selector** (`hack/select-e2e.sh`).
   Reads `packages/core/platform/sources/*.yaml` (the PackageSource
   dependency graph also rendered by `cozypkg dot`) and emits the bats
   files affected by a PR diff. Library/core/networking/CI changes,
   shared `hack/*.sh` helpers, the install bats, and the
   `pull-requests` / `release-e2e` workflows trigger the full suite.
   Doc/dashboard/`*.md` diffs select nothing. Per-app bats edits
   select only that app. Anything inside `packages/` with no graph
   entry, or a system source whose graph has no `*-application`
   descendants, falls back to the full suite (conservative).

2. **Default-on TIA in `pull-requests.yaml`** with `full-e2e`
   opt-out. Drops the previous `e2e-tia` opt-in shape — the
   selector's behaviour has been validated, so making it the default
   gives PR runtime savings out of the box. PRs that need the full
   suite (e.g. cross-cutting refactors that the selector can't
   reason about) carry the `full-e2e` label and get the unchanged
   pre-TIA flow. Docs-only PRs (selector emits nothing) skip the
   `Run E2E tests` step entirely.

3. **`release-e2e.yaml`** runs the full suite on every tag push
   (`v*`). Independent of `tags.yaml`'s release publish flow — a
   failure here is an alarm, not a release blocker. Closes the
   coverage gap left by making PR runs use TIA: the full suite is
   now exercised against shipped code at tag-cut time.

Bug fixes vs. the original `feat/select-e2e-script` draft:
- `dashboard/` -> `dashboards/` (path is plural in this repo).
- `hack/e2e-apps/<name>.sh` and `hack/<name>.bats` were silently
  ignored; they now trigger the full suite (test infrastructure
  affecting all apps).
- A path matching the graph but yielding zero `*-application`
  descendants now falls back to the full suite instead of silently
  emitting nothing (system-only changes are no longer silently
  skipped).
- Per-app bats edits are matched before the full-suite pattern, so
  editing one bats file no longer escalates to the full suite.
- Removed the dead `skip_pattern` variable (PCRE syntax that
  `grep -qE` doesn't support; it was never matched).

Tests: `hack/select-e2e_test.bats` covers all 11 cases (the original
6 plus 5 for the bug fixes above).

Workflow inputs are passed via env vars rather than direct `${{ }}`
expansion into shell commands, per workflow injection guidance.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Assisted-By: Claude <noreply@anthropic.com>
2026-05-02 19:09:39 +05:00
Andrei Kvapil 9432cdfac8 chore(ci): replace Dosu stale and size labeling with native workflows (#2504)
## What this PR does

Replace Dosu's stale-management and PR-size labeling with native GitHub
Actions ahead of disabling the Dosu app on this repo, and migrate the
six `size:*` labels to the canonical `size/*` namespace while we're
rewriting the sizing path.

**Background**: Dosu (the SaaS) currently writes `lifecycle/stale`
lifecycle and `size:*` labels via its bot. Audit of `.github/workflows/`
(full grep) confirmed zero hard dependencies on Dosu's outputs —
`backport*` is human-applied, `release` is auto-applied by `tags.yaml`
itself, and nothing else consumes Dosu-applied labels in any workflow.

### `.github/workflows/stale.yaml` (new)

Daily cron at 04:37 UTC plus manual dispatch. Uses `actions/stale@v10`:

- Stale after 60 days idle: applies `lifecycle/stale`.
- Closes 14 days later, applying `lifecycle/rotten`.
- `remove-stale-when-updated: true` — activity un-stales automatically.
- 100 ops/run.

Issue and PR exempt lists differ on purpose: an accepted issue
(`triage/accepted`) is a known long-tail task, not stale; a held PR
(`do-not-merge/hold`) is paused intentionally.
Priority/security/regression labels protect issues someone explicitly
decided to keep visible.

### `.github/workflows/pr-size.yaml` (new)

Triggers on `opened`, `synchronize`, `reopened` (`pull_request_target`).
Inline `actions/github-script@v7` — no third-party labeler dependency.
Diff-line count with thresholds matching the descriptions in
`.github/labels.yml`:

| label | lines |
|---|---|
| `size/XS` | 0–9 |
| `size/S` | 10–29 |
| `size/M` | 30–99 |
| `size/L` | 100–499 |
| `size/XL` | 500–999 |
| `size/XXL` | 1000+ |

Inline ignore patterns skip:

- vendored Go deps (`vendor/**`)
- kubebuilder/openapi inline generated files
(`zz_generated.{deepcopy,conversion,defaults,openapi}.go`)
- whole code-generated trees: any path under `generated/`, plus
`*.pb.go`
- vendored Helm charts (`packages/system/*/charts/**`)
- lockfiles (`go.sum`, `*.lock`, `*.lockb`)

Concurrency group keyed on PR number with `cancel-in-progress: true`
(latest push wins). `removeLabel` is wrapped in `try/catch` tolerating
404 for races between event payload and execution.

### `size:` → `size/` namespace migration

The legacy `size:X` (colon) form was an artefact of the previous sizing
bot; with that bot being replaced and every other label namespace in the
repo using slashes (`kind/`, `area/`, `priority/`, `triage/`,
`lifecycle/`, `do-not-merge/`, `security/`), `size/` is the consistent
form. Migration via aliases in `labels.yml` — IDs preserved, every
existing labelled PR follows the rename automatically.

The new `pr-size.yaml` emits `size/*` from day 1 and accepts both
prefixes when looking up the previous size label, to handle the brief
window between merge and the next labels-sync run.

### Out of scope (intentional)

- **`lgtm` replacement** — deferred. 33/33 of `lgtm` events in the last
100 PRs come from Dosu; humans never apply it manually. Whether
reviewers actually rely on it is unclear — observe for 7 days
post-Dosu-disable before adding a `pull_request_review`-on-approve
workflow.
- **LLM-based issue auto-classification** (`kind/bug`/`kind/feature` on
inbound issues) — accepted loss; manual triage replaces.
- **Cleanup of `wontfix`/`invalid`** in `labels.yml` — separate cleanup
PR.

### Release note

```release-note
NONE
```


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

* **New Features**
- Pull requests now automatically receive size labels (XS, S, M, L, XL,
XXL) based on lines changed.
- Issues and pull requests are automatically marked as stale after 60
days of inactivity and closed 14 days later if still inactive.

* **Chores**
- PR size label namespace updated to a slash-based format (size/...)
while preserving the old names as aliases for backward compatibility.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-01 13:15:59 +02:00
myasnikovdaniil 30729d7f00 fix(ci): generate changelog from release tag, not from main (#2530)
## What this PR does

The `generate-changelog` job in `.github/workflows/tags.yaml` previously
checked out `main` and ran the AI agent there. The agent followed
`docs/agents/changelog.md`, which instructed it to compute the release
range with `git log <previous_version>..HEAD`. That works for minor
releases (cut from `main`), but it breaks for any patch release cut from
a `release-X.Y` branch — `HEAD-on-main` is a strict superset of the tag
and contains commits that were merged to `main` both before and after
the tag.

The v1.3.1 changelog generated by this workflow (#2480) demonstrated the
failure mode: 8 PRs that were merged to `main` but never shipped in
v1.3.1, 6 backport PRs that landed on `release-1.3` *after* v1.3.1 was
tagged, both originals and their backports as separate entries, a
hallucinated 2024 PR (#435), and the `cozystack-ci` bot in the
contributors list. The corrected v1.3.1 changelog is in #2480.

This PR fixes the root cause and tightens the agent guardrails:

* **`tags.yaml`** — check out the release tag commit (`ref: ${{
steps.tag.outputs.tag }}`) instead of `main`, so `HEAD == release
commit` and `git log v<prev>..HEAD` corresponds to what the tag actually
contains. The "Create changelog branch" step still creates the PR branch
from `origin/main`, so PRs continue to merge cleanly.
* **`tags.yaml`** — the AI prompt now states explicitly that `HEAD` is
the release commit and that the upper bound of the range is the new tag,
never `main`.
* **`docs/agents/changelog.md`** — every example replaces `..HEAD` with
`..v<new_version>`, so the instruction is unambiguous regardless of
which branch is checked out.
* **`docs/agents/changelog.md`** — hard rule: backport PRs MUST be
combined with the original into a single entry (`#1606, backport
#1609`), never listed as a second entry. Documented edge case: if the
original isn't in the range, drop the entry entirely (it shipped in a
previous release).
* **`docs/agents/changelog.md`** — forbid using the brief description
verbatim as the detailed description (the failure was `* **fix(foo):
X**: fix(foo): X (...)`), and forbid inventing entries for PRs outside
the release range.
* **`docs/agents/changelog.md`** — filter bot/CI accounts (`app/*`,
`*[bot]`, `cozystack-ci`, `github-actions`, `dependabot`, `renovate`)
out of the human Contributors list. Bot attribution on individual
entries is still valid.

The v1.3.1 changelog correction itself lives in #2480 (separate scope).

### Release note

```release-note
fix(ci): tags.yaml now generates the release changelog from the tag commit instead of `main`, fixing patch-release changelogs (e.g. v1.3.1) that previously included unrelated commits from `main`. The AI prompt and `docs/agents/changelog.md` were tightened to forbid duplicate backport entries, title-as-description, fabricated PRs, and bot accounts in the Contributors list.
```

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

* **Chores**
* Changelog generation now checks out the release tag commit and
verifies whether the release changelog already exists on main before
generating.
* Updated prompts and tooling to treat the release tag as the upper
bound and avoid using HEAD when computing diffs.
* Enforced combining original and backport PRs into single changelog
entries with explicit “backport #…”.

* **Documentation**
* Strengthened authoring rules (distinct brief/detail, forbid invented
entries, PRs must fall within tag-to-tag ranges).
* Updated examples and guidance to use tag-to-tag comparisons and
improved contributor extraction to use bold @user attribution while
excluding automation accounts and the current release file.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-30 17:20:58 +05:00
Myasnikov Daniil 39605f213f docs(agents,ci): finish "tag-not-HEAD" sweep and fix changelog-exists gate
- tags.yaml: the check_changelog step now looks at origin/main rather than
  the working tree. After Round 2 we check out the release tag, which never
  has the changelog file (the PR opened by this job merges to main after
  the tag is cut), so the previous `[ -f ]` test always returned false and
  the gate was effectively dead on reruns.
- changelog.md: Step 6's `RELEASE_END=$(git log -1 --format=%ai HEAD)`
  (lines 179, 222) was missed in the previous "never HEAD" sweep — now
  uses v<new_version>, matching Step 5.
- changelog.md: Step 2 ("on main branch") was incompatible with the CI flow
  which now runs detached at the release tag. Relaxed it to list both
  configurations (interactive on main; CI detached at v<version>), while
  the "Scope and boundaries" section continues to forbid the agent from
  switching branches.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-30 15:01:48 +05:00
Myasnikov Daniil ce63bfcdb0 docs(agents): fix new-contributor detection and tags.yaml/doc drift
- tags.yaml: drop "(or HEAD)" from the changelog prompt; the doc says
  "never HEAD" and the workflow now matches.
- docs/agents/changelog.md: exclude the current release file from the
  "previous contributors" set used for first-time-contributor detection.
  The previous globbing on `docs/changelogs/v*.md` swept up the file
  being generated, so `comm -23` would never report any newcomers.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-30 13:37:24 +05:00
Myasnikov Daniil 7d2fe2f7d1 fix(ci): pass app token to Publish draft release step
The Publish draft release step used the workflow's default GITHUB_TOKEN,
which does not return draft releases from listReleases — so the script
failed with "Draft release for vX.Y.Z not found" and every release tag
had to be undrafted manually.

This same bug was fixed once before in 4db55ac5eb ([ci] Add Github
token to fetch draft releases) by adding github-token: GH_PAT to the
listing step. It was lost during the refactor in 66a756b6 that combined
the separate listing/flags/publish steps into a single Publish step,
and not restored when f96b2da1 migrated remaining github-token usages
from GH_PAT to the cozystack-ci App token.

Add github-token: app-token.outputs.token to the merged step so it
shares the same auth as the rest of the workflow.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-04-29 15:47:41 +05:00