Skip to content

✨ Add support for k8s:immutable#1354

Open
alvaroaleman wants to merge 1 commit intokubernetes-sigs:mainfrom
alvaroaleman:immutable
Open

✨ Add support for k8s:immutable#1354
alvaroaleman wants to merge 1 commit intokubernetes-sigs:mainfrom
alvaroaleman:immutable

Conversation

@alvaroaleman
Copy link
Member

This change adds support for k8s:immutable as described in the KEP.

It replaces #1216 and follows @jpbetz suggestion for the CEL expression. It also adds integration tests.

Closes #1216

@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: alvaroaleman

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added approved Indicates a PR has been approved by an approver from all required OWNERS files. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. labels Feb 22, 2026
@alvaroaleman
Copy link
Member Author

/assign @JoelSpeed @lalitc375

@k8s-ci-robot
Copy link
Contributor

@alvaroaleman: GitHub didn't allow me to assign the following users: lalitc375.

Note that only kubernetes-sigs members with read permissions, repo collaborators and people who have commented on this issue/PR can be assigned. Additionally, issues/PRs can only have 10 assignees at the same time.
For more information please see the contributor guide

Details

In response to this:

/assign @JoelSpeed @lalitc375

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

func (m Immutable) ApplyToSchema(schema *apiextensionsv1.JSONSchemaProps) error {
optionalOldSelf := true
schema.XValidations = append(schema.XValidations, apiextensionsv1.ValidationRule{
Rule: "!oldSelf.hasValue() || self == oldSelf.value()",
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this achieve the same thing if we use self == oldSelf and drop the OptionalOldSelf?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, good catch, updated it to do that instead

Comment on lines +577 to +578
// Note that immutable fields that are nested below optional fields can still be
// updated by unsetting the optional parent field and re-setting it again.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there anything we can do to handle the "no clear" element of the KEP?

The usual pattern is to find the nearest required pattern and add a validation to check that if the oldSelf has the immutable field set, that it cannot be unset

Copy link
Member Author

Choose a reason for hiding this comment

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

This is handled by the code in schema.go that adds a CEL rule to disallow unsetting once set


// +k8s:immutable
// +optional
OptionalString *string `json:"optionalString,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to test a version of this where string isn't a pointer?

Copy link
Member Author

Choose a reason for hiding this comment

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

Added


// +k8s:immutable
// +optional
OptionalStruct *NestedStruct `json:"optionalStruct,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

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

A version without a pointer, but with omitzero to test that combination?

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure but at that point we are mostly testing the go serialization?

Copy link
Contributor

Choose a reason for hiding this comment

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

Are we actually using these structs in the tests? If not don't worry

I was just thinking we could example all of the combinations CRD authors might use

Copy link
Member Author

Choose a reason for hiding this comment

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

Added

Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(got), got)).To(Succeed())

mutate(got)
Expect(k8sClient.Update(ctx, got)).NotTo(Succeed())
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we check the error contains the immutable substring to make sure this is doing what we think it is?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

Comment on lines +138 to +139
clear(got)
Expect(k8sClient.Update(ctx, got)).NotTo(Succeed())
Copy link
Contributor

Choose a reason for hiding this comment

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

I thought the doc said we weren't expecting clear to be rejected?

Copy link
Member Author

Choose a reason for hiding this comment

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

Comment on lines +549 to +551
// For optional immutable fields, add a parent-level validation rule to prevent
// clearing the field once set. The field-level rule prevents value changes, but
// when an optional field is removed, the field-level rule doesn't execute.
Copy link
Contributor

Choose a reason for hiding this comment

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

If this is an optional parent, nothing stops them from removing the optional parent, you need to find the nearest required parent and add it there

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Fair, I had missed that part of the EP

Copy link
Member Author

Choose a reason for hiding this comment

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

This surprised me initially as well which is why I left the comment on the marker. For the purpose of implementing the markers, I would just go by the assumption that the relevant discussions if that does or doesn't make sense were already had when the KEP was written/approved

@JoelSpeed
Copy link
Contributor

/hold

Since this has approve, lets avoid an accidental merge with a fly by lgtm

@k8s-ci-robot k8s-ci-robot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Feb 26, 2026
@k8s-ci-robot k8s-ci-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Feb 27, 2026
@JoelSpeed
Copy link
Contributor

/lgtm

@alvaroaleman having re-read the EP, I think this is good to go

Would be good if @lalitc375 could ack the behaviour as well, fields are immutable, and cannot be unset unless their parent is cleared

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Mar 2, 2026
@k8s-ci-robot
Copy link
Contributor

LGTM label has been added.

DetailsGit tree hash: 201dc2db4af33e575a36a9b13bbcaf83716bb63a

@lalitc375
Copy link
Contributor

Would be good if @lalitc375 could ack the behaviour as well, fields are immutable, and cannot be unset unless their parent is cleared

I am out of office right now. @aaron-prindle Can you please take a look?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. lgtm "Looks good to me", indicates that a PR is ready to be merged. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants