Skip to content

feat: add @immutable annotation emitting CEL XValidation rule#24

Merged
Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
feat/immutable-marker
May 25, 2026
Merged

feat: add @immutable annotation emitting CEL XValidation rule#24
Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
feat/immutable-marker

Conversation

@lexfrei

Copy link
Copy Markdown
Contributor

Summary

A new ## @immutable flag annotation in values.yaml marks a field as immutable-after-creation.

## @param {string} storageClass - StorageClass used to store the data.
## @immutable
storageClass: ""

cozyvalues-gen now:

  • Parses the annotation alongside the existing @minimum/@maxLength/etc. constraint family.
  • Carries it on Raw and Node via a new Immutable bool field.
  • Emits a kubebuilder validation marker on the generated Go field:
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="storageClass is immutable after creation"
StorageClass string `json:"storageClass"`

controller-gen translates that marker into the standard x-kubernetes-validations entry on the CRD; by passthrough it lands in the chart's values.schema.json as well.

Why

I want the Cozystack UI to be able to mark immutable resource fields as read-only on edit forms. The decision was to use the schema as the source of truth — specifically the standard K8s x-kubernetes-validations CEL rule self == oldSelf — rather than maintaining a hand-rolled mapping on the UI side. cozyvalues-gen is the canonical pipeline that produces that schema, so the annotation belongs here.

Tests

Four new tests pin the behaviour:

  • TestParseImmutable / TestParseImmutable_AbsentByDefault for the parser path.
  • TestGenerateImmutableMarker / TestGenerateImmutableMarker_NotEmittedByDefault for the Go-code emitter.

Full suite is green:

ok  github.com/cozystack/cozyvalues-gen
ok  github.com/cozystack/cozyvalues-gen/internal/openapi
ok  github.com/cozystack/cozyvalues-gen/internal/readme

Downstream

Two follow-up PRs depend on this:

  • cozystack/cozystack#TBD — annotate MariaDB storageClass and regenerate.
  • cozystack/cozystack-ui#TBD — consume the marker in edit forms.

@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@lexfrei, we couldn't start this review because you've used your available PR reviews for now.

Your plan includes 1 review of capacity. Refill in 10 minutes and 46 seconds.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more review capacity refills, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than trial, open-source, and free plans. In all cases, review capacity refills continuously over time.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a4156952-ad8d-4091-ac80-e009c8e4adc9

📥 Commits

Reviewing files that changed from the base of the PR and between 9de5dfc and 4af7f4c.

📒 Files selected for processing (4)
  • README.md
  • internal/openapi/openapi.go
  • internal/openapi/openapi_test.go
  • internal/patterns/patterns.go
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/immutable-marker

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for an Immutable (@immutable) annotation in OpenAPI definitions. It updates the parsing logic to recognize the tag, propagates the immutable state through the internal representation, and generates a Kubernetes XValidation marker (self == oldSelf) to enforce immutability during updates. Unit tests were added to verify both the parsing and the marker generation. I have no feedback to provide.

Comment thread internal/openapi/openapi.go
A new `## @immutable` flag annotation in values.yaml marks a field
as immutable-after-creation. cozyvalues-gen now:

- parses the annotation alongside the existing @minimum/@maxLength/etc.
  constraint family;
- carries it on Raw and Node via the new Immutable bool;
- emits a kubebuilder validation marker
  `+kubebuilder:validation:XValidation:rule="self == oldSelf",
  message="<field> is immutable after creation"` on the generated
  Go field, which controller-gen translates into the standard
  x-kubernetes-validations entry on the CRD and, by passthrough,
  into the chart's values.schema.json.

Three new tests pin the behaviour:
- TestParseImmutable / TestParseImmutable_AbsentByDefault for the
  parser path;
- TestGenerateImmutableMarker / TestGenerateImmutableMarker_NotEmittedByDefault
  for the Go-code emitter.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
@lexfrei Aleksei Sviridkin (lexfrei) merged commit 56fefc1 into main May 25, 2026
2 checks passed
@lexfrei Aleksei Sviridkin (lexfrei) deleted the feat/immutable-marker branch May 25, 2026 14:31
Aleksei Sviridkin (lexfrei) added a commit to cozystack/cozystack that referenced this pull request May 25, 2026
v1.5.0 (cozystack/cozyvalues-gen#24) adds the @immutable annotation
support that the storageClass changes in this PR depend on. Without
the bump, pre-commit re-runs make generate with the v1.4.0 binary and
silently strips the x-kubernetes-validations entries from every
regenerated values.schema.json and embedded openAPISchema, producing
a drift against what's committed.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Aleksei Sviridkin (lexfrei) added a commit to cozystack/cozystack that referenced this pull request May 25, 2026
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 #2639 (apply @immutable to
storageClass across stateful apps).

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Aleksei Sviridkin (lexfrei) added a commit to cozystack/cozystack that referenced this pull request May 25, 2026
## Summary

Bump the `cozyvalues-gen` binary pinned in
`.github/workflows/pre-commit.yml` from `v1.4.0` to `v1.5.0`.

`v1.5.0` (cozystack/cozyvalues-gen#24) adds support for the `@immutable`
annotation. Without this bump, pre-commit re-runs `make generate` with
the old `v1.4.0` binary on every PR; the old binary silently ignores `##
@immutable` directives and regenerates `values.schema.json` / embedded
`openAPISchema` without the corresponding `x-kubernetes-validations`
entries — which then shows up as committed-vs-regenerated drift on any
PR that tries to use the new annotation.

## No-op for main today

No chart on main currently uses `@immutable` (zero hits for `##
@immutable` under `packages/apps/*/values.yaml`), so this bump
regenerates byte-identical output. The change is purely lifting the
floor for downstream PRs that will start using the annotation.

## Unblocks

- #2639 — apply `@immutable` to `storageClass` across
every stateful Cozystack app (currently fails pre-commit on the v1.4.0
pin).

## Release info

- v1.5.0 release notes:
https://github.com/cozystack/cozyvalues-gen/releases/tag/v1.5.0
- Tag signed by F20B7CCBF00A5CAE8F9F8B8C7988329FDF395282 (Aleksey
Sviridkin)
- Goreleaser uploaded `cozyvalues-gen-linux-amd64.tar.gz` (the artefact
this workflow downloads) along with the rest of the OS/arch matrix.

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

## Summary by CodeRabbit

* **Chores**
* Updated development tooling dependencies for build process
improvements.

<!-- 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/2730?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 -->
Aleksei Sviridkin (lexfrei) added a commit to cozystack/cozystack that referenced this pull request May 27, 2026
## Summary

Annotate `storageClass` as immutable across every stateful chart that
exposes one: clickhouse, foundationdb, harbor, http-cache, kafka (Kafka
+ ZooKeeper), kubernetes (top-level + nodeGroups[]), mariadb, mongodb,
nats, openbao, opensearch, postgres, qdrant, rabbitmq, redis, vm-disk.
The PVC-pinning argument is identical for every chart — Kubernetes fixes
`storageClassName` on each PVC at creation, so editing this field on an
existing resource never migrated data; the schema annotation makes that
contract explicit and lets schema-aware consumers refuse the edit
up-front.

Schema-aware consumers today: the Cozystack UI
(cozystack/cozystack-ui#6) reads the rule from the chart's openAPISchema
and renders the field read-only on edit forms.

This PR is **schema-side only**. The cozystack aggregated apiserver does
not yet evaluate CEL rules in `openAPISchema` (`MariaDB` / `Postgres` /
etc. are application-backed, not CRDs); tracking issue
#2657 covers wiring CEL into
`pkg/registry/apps/application/rest.go::Update`. Until that lands, an
apiserver-level `kubectl patch` that changes `storageClass` is still
accepted — but the underlying StatefulSet PVCs do not move, so there's
no data-corruption risk, only a confusing UX. The UI catches that case
today; the apiserver gate closes it permanently.

## What changes

For each app in the list above:

- `packages/apps/<app>/values.yaml` — gain a `## @immutable` directive
directly above (or below, for fields inside a `@typedef`) the
`storageClass` line.
- Regenerated via `make generate` in each package:
- `api/apps/v1alpha1/<app>/types.go` — kubebuilder `XValidation` marker
on the relevant field(s).
- `packages/apps/<app>/values.schema.json` — `x-kubernetes-validations:
[{rule: "self == oldSelf", message: …}]` on `storageClass`.
  - `packages/apps/<app>/README.md` — regenerated parameter table.
- `packages/system/<app>-rd/cozyrds/<app>.yaml` — the embedded
`openAPISchema` on the ApplicationDefinition reflects the change.

Refactor: `pkg/registry/apps/application/rest.go` factors the inline
schema-build path into a new `buildSpecSchema` helper so it can be
unit-tested without standing up a fake apiserver. Three regression tests
in `pkg/registry/apps/application/rest_spec_schema_test.go` pin the
contract that `x-kubernetes-validations` survives the
v1→internal→structural conversion (test loads the actual MariaDB
ApplicationDefinition fixture on disk, asserts the rule reaches the
structural schema). Empty-input and malformed-JSON paths are also
pinned.

No e2e admission test is added because the apiserver does not yet
enforce the rule — see #2657.

No changelog entry: schema-side annotation is invisible at the apiserver
layer today (UI-only consumer) and the release-cut PR will add the entry
when admission enforcement lands.

## Dependencies

Requires cozystack/cozyvalues-gen#24 to be merged and released, and the
`cozyvalues-gen` binary pin in `.github/workflows/pre-commit.yml` line
50 bumped to the release that includes that PR. Pre-commit CI will fail
until both happen — the stock `v1.3.0` binary silently ignores `##
@immutable` and regenerates artefacts without the rule, producing a
drift against what's committed.

Once that ordering is satisfied, rebase this branch and the pre-commit
job will be green.

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

* **New Features**
* storageClass is now treated as immutable after creation for many
stateful apps (enforced by chart/CRD schema validation).

* **Documentation**
* Added a storage immutability guide explaining the contract and
enforcement.
* Updated chart READMEs and values docs to mark storageClass as
immutable and clarified nodeGroup storage remains intentionally mutable.

* **Tests**
* Added tests validating schema parsing and preservation of immutability
rules.

<!-- 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/2639?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 -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants