Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KEP-4008: CRDValidationRatcheting: Add support for optional oldSelf #121034

Conversation

alexzielenski
Copy link
Contributor

@alexzielenski alexzielenski commented Oct 6, 2023

What type of PR is this?

/kind feature

What this PR does / why we need it:

Which issue(s) this PR fixes:

NONE

Special notes for your reviewer:

Does this PR introduce a user-facing change?

Adds `optionalOldSelf` to `x-kubernetes-validations` to support ratcheting CRD schema constraints

Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.:

- [KEP]: https://kep.k8s.io/4008

/cc @cici37

@k8s-ci-robot
Copy link
Contributor

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@k8s-ci-robot k8s-ci-robot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/S Denotes a PR that changes 10-29 lines, ignoring generated files. kind/feature Categorizes issue or PR as related to a new feature. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. do-not-merge/needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. needs-priority Indicates a PR lacks a `priority/foo` label and requires one. kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. size/M Denotes a PR that changes 30-99 lines, ignoring generated files. area/code-generation and removed do-not-merge/needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. size/S Denotes a PR that changes 10-29 lines, ignoring generated files. labels Oct 6, 2023
Copy link
Member

@apelisse apelisse left a comment

Choose a reason for hiding this comment

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

I'd love to see a test.

@alexzielenski alexzielenski force-pushed the apiserver/apiextensions/ratcheting-oldself-create branch from a866a71 to bddcf76 Compare October 10, 2023 20:55
@k8s-ci-robot k8s-ci-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Oct 10, 2023
@alexzielenski alexzielenski marked this pull request as ready for review October 10, 2023 21:28
@k8s-ci-robot k8s-ci-robot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Oct 10, 2023
@alexzielenski
Copy link
Contributor Author

tests added

@jiahuif
Copy link
Member

jiahuif commented Oct 12, 2023

/assign
/assign @cici37
/triage accepted

@k8s-ci-robot k8s-ci-robot added the triage/accepted Indicates an issue or PR is ready to be actively worked on. label Oct 12, 2023
@k8s-ci-robot k8s-ci-robot removed the needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. label Oct 12, 2023
@alexzielenski alexzielenski force-pushed the apiserver/apiextensions/ratcheting-oldself-create branch from bddcf76 to 65d0f25 Compare October 18, 2023 16:39
@alexzielenski alexzielenski force-pushed the apiserver/apiextensions/ratcheting-oldself-create branch from 53e8924 to 26facf7 Compare November 3, 2023 18:52
@alexzielenski
Copy link
Contributor Author

/retest

@alexzielenski alexzielenski force-pushed the apiserver/apiextensions/ratcheting-oldself-create branch from 26facf7 to 0ac3857 Compare November 3, 2023 19:14
Copy link
Contributor

@jpbetz jpbetz left a comment

Choose a reason for hiding this comment

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

Two minor comments, then the implementation LGTM. I glanced at the API changes as well and don't have any concerns, but will defer to API review for that.

Comment on lines 195 to 197
DeclTypes: []*apiservercel.DeclType{
scopedType,
},
Copy link
Contributor

Choose a reason for hiding this comment

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

Curious if this was strictly needed since the decl is already added to the environment in the prior version options? If we don't need to declare it again maybe omit it? (if we do need to declare it again, I'll want to dig in to figure out why..)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Was able to remove it. It needed to be added in an earlier implementation when I had called extend multiple times like the following pseudocode:

selfEnvSet := baseEnv.Extend({
     // self definition
    DeclTypes: [scopedType]
})

oldSelfEnvSet := selfEnvSet.Extend({
  // oldself definition
})


optionalOldSelfEnvSet := selfEnvSet.Extend({
    // optionalOldSelf definition
})

This caused field resolution errors that weren't resolved until we either added the DeclType to each call to Extend, or made it the form it is currently.

@jpbetz is it expected that the definition wouldn't be available in successive calls to Extend?

Comment on lines 71 to 72
// (e.g. self and oldSelf) to CEL values. One activation must be produced
// for
Copy link
Contributor

Choose a reason for hiding this comment

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

Last sentence is incomplete?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

whoops. added the rest:


	// celActivationFactory produces a Activations, which resolve identifiers
	// (e.g. self and oldSelf) to CEL values. One activation must be produced
	// for each of the cases when oldSelf is optional and non-optional.

Comment on lines 291 to 292
// - During a create, `oldSelf` evaluates to `null`
// - During an update, `oldSelf` evaluates to `null` when the old object is
Copy link
Contributor

Choose a reason for hiding this comment

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

Just caught up on the latest code. The use of CEL Optionals here is really nice.

// `transition rule`.
//
// Transition rules by default are applied only on UPDATE requests and are
// skipped if and old value could not be found. You can opt a transition
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// skipped if and old value could not be found. You can opt a transition
// skipped if an old value could not be found. You can opt a transition

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added

Comment on lines 262 to 263
// When enabled, if the old object is missing the value or is being
// created for the first time, then `oldSelf` will be a CEL optional.
Copy link
Member

Choose a reason for hiding this comment

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

when enabled, isn't oldSelf always a CEL optional?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Rephrased:

	// When enabled `oldSelf` will be a CEL optional whose value will be
	// `None` if there is no old value, or when the object is initially created.
	//
	// You may check for presence of oldSelf using `oldSelf.hasValue()` and
	// unwrap it after checking using `oldSelf.value()`. Check the CEL
	// documentation for Optional types for more information:
	// https://pkg.go.dev/github.com/google/cel-go/cel#OptionalTypes
	//

@@ -210,6 +210,14 @@ type ValidationRule struct {
// - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values
// are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with
// non-intersecting keys are appended, retaining their partial order.
//
// If `rule` makes use of the `oldSelf` variable it is implicitly a
// `transition rule`.
Copy link
Member

Choose a reason for hiding this comment

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

By default, the `oldSelf` variable is the same type as `self`.
When `optionalOldSelf` is true, the `oldSelf` variable is a CEL
optional variable whose value() is the same type as `self`.
See the documentation for the `optionalOldSelf` field for details.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added

va := &validationActivation{
self: UnstructuredToVal(obj, sts),
}
optionalVA := &validationActivation{
self: va.self,
hasOldSelf: true,
Copy link
Member

Choose a reason for hiding this comment

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

this is worth a comment

Suggested change
hasOldSelf: true,
hasOldSelf: true, // this means the oldSelf variable is defined for CEL to reference, not that it has a value

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added

@@ -622,21 +632,44 @@ type validationActivation struct {
hasOldSelf bool
}

func validationActivationWithOldSelf(sts *schema.Structural, obj, oldObj interface{}) interpreter.Activation {
func validationActivationWithOldSelf(sts *schema.Structural, obj, oldObj interface{}) (interpreter.Activation, interpreter.Activation) {
Copy link
Member

Choose a reason for hiding this comment

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

godoc or name the return values

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added names to the return values

return va, optionalVA
}

func validationActivationWithOptionalOldSelf(sts *schema.Structural, obj, oldObj interface{}) interpreter.Activation {
Copy link
Member

Choose a reason for hiding this comment

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

this looks unused, drop it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed

// transition rules are evaluated only if there is a comparable existing value
continue
} else if compiled.UsesOldSelf && allowsNilOldSelf {
Copy link
Member

Choose a reason for hiding this comment

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

it's confusing to make this an else block and make it rely on the feature gate... a rule that sets optionalOldSelf: true should always use optionalOldSelfActivation

I'd suggest separating the calculation for which activation to use (based solely on the rule field) and whether to evaluate, based on both the feature gate and the rule:

optionalOldSelfRule := pointer.BoolDeref(rule.OptionalOldSelf, false)

if compiled.UsesOldSelf && oldObj == nil {
  if !utilfeature.DefaultFeatureGate.Enabled(features.CRDValidationRatcheting) {
    continue
  }
  if !optionalOldSelfRule {
    continue
  }
}

ruleActivation := activation
if optionalOldSelfRule {
  ruleActivation = optionalOldSelfActivation
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

restructured as suggested

Copy link
Member

Choose a reason for hiding this comment

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

copy doc updates to v1 as well

Copy link
Contributor Author

Choose a reason for hiding this comment

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

should be there now

Copy link
Member

Choose a reason for hiding this comment

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

there should be type checking unit tests on the optional oldSelf to make sure we catch type stuff:

diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/compilation_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/compilation_test.go
index 18df5a42475..a774e818b9d 100644
--- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/compilation_test.go
+++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/compilation_test.go
@@ -26,13 +26,18 @@ import (
 	"github.com/google/cel-go/common/types"
 	"github.com/google/cel-go/common/types/ref"
 
+	"k8s.io/utils/pointer"
+
 	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
 	"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model"
+	apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
 	"k8s.io/apimachinery/pkg/util/version"
 	celconfig "k8s.io/apiserver/pkg/apis/cel"
 	"k8s.io/apiserver/pkg/cel"
 	"k8s.io/apiserver/pkg/cel/environment"
+	utilfeature "k8s.io/apiserver/pkg/util/feature"
+	featuregatetesting "k8s.io/component-base/featuregate/testing"
 )
 
 const (
@@ -151,6 +156,8 @@ func (v transitionRuleMatcher) String() string {
 }
 
 func TestCelCompilation(t *testing.T) {
+	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CRDValidationRatcheting, true)()
+
 	cases := []struct {
 		name            string
 		input           schema.Structural
@@ -625,6 +632,92 @@ func TestCelCompilation(t *testing.T) {
 				matchesAll(noError(), transitionRule(true)),
 			},
 		},
+		{
+			name: "optional primitive transition rule type checking",
+			input: schema.Structural{
+				Generic: schema.Generic{
+					Type: "integer",
+				},
+				Extensions: schema.Extensions{
+					XValidations: apiextensions.ValidationRules{
+						{Rule: "self >= oldSelf.value()", OptionalOldSelf: pointer.Bool(true)},
+						{Rule: "self >= oldSelf.orValue(1)", OptionalOldSelf: pointer.Bool(true)},
+						{Rule: "oldSelf.hasValue() ? self >= oldSelf.value() : true", OptionalOldSelf: pointer.Bool(true)},
+						{Rule: "self >= oldSelf", OptionalOldSelf: pointer.Bool(true)},
+						{Rule: "self >= oldSelf.orValue('')", OptionalOldSelf: pointer.Bool(true)},
+					},
+				},
+			},
+			expectedResults: []validationMatcher{
+				matchesAll(noError(), transitionRule(true)),
+				matchesAll(noError(), transitionRule(true)),
+				matchesAll(noError(), transitionRule(true)),
+				matchesAll(invalidError("optional")),
+				matchesAll(invalidError("orValue")),
+			},
+		},
+		{
+			name: "optional complex transition rule type checking",
+			input: schema.Structural{
+				Generic: schema.Generic{
+					Type: "object",
+				},
+				Properties: map[string]schema.Structural{
+					"i": schema.Structural{Generic: schema.Generic{Type: "integer"}},
+					"b": schema.Structural{Generic: schema.Generic{Type: "boolean"}},
+					"s": schema.Structural{Generic: schema.Generic{Type: "string"}},
+					"a": schema.Structural{
+						Generic: schema.Generic{Type: "array"},
+						Items:   &schema.Structural{Generic: schema.Generic{Type: "integer"}},
+					},
+					"o": schema.Structural{
+						Generic: schema.Generic{Type: "object"},
+						Properties: map[string]schema.Structural{
+							"i": schema.Structural{Generic: schema.Generic{Type: "integer"}},
+							"b": schema.Structural{Generic: schema.Generic{Type: "boolean"}},
+							"s": schema.Structural{Generic: schema.Generic{Type: "string"}},
+							"a": schema.Structural{
+								Generic: schema.Generic{Type: "array"},
+								Items:   &schema.Structural{Generic: schema.Generic{Type: "integer"}},
+							},
+							"o": schema.Structural{
+								Generic: schema.Generic{Type: "object"},
+							},
+						},
+					},
+				},
+				Extensions: schema.Extensions{
+					XValidations: apiextensions.ValidationRules{
+						{Rule: "self.i >= oldSelf.i.value()", OptionalOldSelf: pointer.Bool(true)},
+						{Rule: "self.s == oldSelf.s.value()", OptionalOldSelf: pointer.Bool(true)},
+						{Rule: "self.b == oldSelf.b.value()", OptionalOldSelf: pointer.Bool(true)},
+						{Rule: "self.o == oldSelf.o.value()", OptionalOldSelf: pointer.Bool(true)},
+						{Rule: "self.o.i >= oldSelf.o.i.value()", OptionalOldSelf: pointer.Bool(true)},
+						{Rule: "self.o.s == oldSelf.o.s.value()", OptionalOldSelf: pointer.Bool(true)},
+						{Rule: "self.o.b == oldSelf.o.b.value()", OptionalOldSelf: pointer.Bool(true)},
+						{Rule: "self.o.o == oldSelf.o.o.value()", OptionalOldSelf: pointer.Bool(true)},
+						{Rule: "self.o.i >= oldSelf.o.i.orValue(1)", OptionalOldSelf: pointer.Bool(true)},
+						{Rule: "oldSelf.hasValue() ? self.o.i >= oldSelf.o.i.value() : true", OptionalOldSelf: pointer.Bool(true)},
+						{Rule: "self.o.i >= oldSelf.o.i", OptionalOldSelf: pointer.Bool(true)},
+						{Rule: "self.o.i >= oldSelf.o.s.orValue(0)", OptionalOldSelf: pointer.Bool(true)},
+					},
+				},
+			},
+			expectedResults: []validationMatcher{
+				matchesAll(noError(), transitionRule(true)),
+				matchesAll(noError(), transitionRule(true)),
+				matchesAll(noError(), transitionRule(true)),
+				matchesAll(noError(), transitionRule(true)),
+				matchesAll(noError(), transitionRule(true)),
+				matchesAll(noError(), transitionRule(true)),
+				matchesAll(noError(), transitionRule(true)),
+				matchesAll(noError(), transitionRule(true)),
+				matchesAll(noError(), transitionRule(true)),
+				matchesAll(noError(), transitionRule(true)),
+				matchesAll(invalidError("optional")),
+				matchesAll(invalidError("orValue")),
+			},
+		},
 		{
 			name: "whitespace-only rule",
 			input: schema.Structural{
diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/validation_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/validation_test.go
index 236564f7d36..63119ebaa22 100644
--- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/validation_test.go
+++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/validation_test.go
@@ -3667,6 +3667,18 @@ func TestOptionalOldSelf(t *testing.T) {
 				"foo": stringType,
 			}), "self.foo == 'valid' || (oldSelf.hasValue() && oldSelf.value().foo != 'valid')"),
 		},
+		{
+			name: "allow invalid new value if old value is also invalid with chained optionals",
+			obj: map[string]interface{}{
+				"foo": "invalid again",
+			},
+			oldObj: map[string]interface{}{
+				"foo": "invalid",
+			},
+			schema: withRulePtr(objectTypePtr(map[string]schema.Structural{
+				"foo": stringType,
+			}), "self.foo == 'valid' || oldSelf.foo.orValue('') != 'valid'"),
+		},
 		{
 			name: "block invalid new value if old value is valid",
 			obj: map[string]interface{}{

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added

@liggitt
Copy link
Member

liggitt commented Nov 3, 2023

looks like some TestValidateCustomResourceDefinitionValidation unit test failures as well

@liggitt
Copy link
Member

liggitt commented Nov 3, 2023

I know we validate a default specified in openapi against any CEL rules provided:

if insideMeta {
obj, _, err := f(runtime.DeepCopyJSONValue(s.Default.Object))
if err != nil {
// this should never happen. f(s.Default.Object) only gives an error if f is the
// root object func, but the default value is not a map. But then we wouldn't be
// in this case.
return nil, fmt.Errorf("failed to validate default value inside metadata: %v", err), remainingCost
}
// check ObjectMeta/TypeMeta and everything else
if err := schemaobjectmeta.Coerce(nil, obj, rootSchema, true, false); err != nil {
allErrs = append(allErrs, field.Invalid(pth.Child("default"), s.Default.Object, fmt.Sprintf("must result in valid metadata: %v", err)))
} else if errs := schemaobjectmeta.Validate(nil, obj, rootSchema, true); len(errs) > 0 {
allErrs = append(allErrs, field.Invalid(pth.Child("default"), s.Default.Object, fmt.Sprintf("must result in valid metadata: %v", errs.ToAggregate())))
} else if errs := apiservervalidation.ValidateCustomResource(pth.Child("default"), s.Default.Object, validator); len(errs) > 0 {
allErrs = append(allErrs, errs...)
} else if celValidator := cel.NewValidator(s, isResourceRoot, celconfig.PerCallLimit); celValidator != nil {
celErrs, rmCost := celValidator.Validate(ctx, pth.Child("default"), s, s.Default.Object, s.Default.Object, remainingCost)
remainingCost = rmCost
allErrs = append(allErrs, celErrs...)
if remainingCost < 0 {
return allErrs, nil, remainingCost
}
}
} else {
// check whether default is pruned
if requirePrunedDefaults {
pruned := runtime.DeepCopyJSONValue(s.Default.Object)
pruning.Prune(pruned, s, s.XEmbeddedResource)
if !reflect.DeepEqual(pruned, s.Default.Object) {
allErrs = append(allErrs, field.Invalid(pth.Child("default"), s.Default.Object, "must not have unknown fields"))
}
}
// check ObjectMeta/TypeMeta and everything else
if err := schemaobjectmeta.Coerce(pth.Child("default"), s.Default.Object, s, s.XEmbeddedResource, false); err != nil {
allErrs = append(allErrs, err)
} else if errs := schemaobjectmeta.Validate(pth.Child("default"), s.Default.Object, s, s.XEmbeddedResource); len(errs) > 0 {
allErrs = append(allErrs, errs...)
} else if errs := apiservervalidation.ValidateCustomResource(pth.Child("default"), s.Default.Object, validator); len(errs) > 0 {
allErrs = append(allErrs, errs...)
} else if celValidator := cel.NewValidator(s, isResourceRoot, celconfig.PerCallLimit); celValidator != nil {
celErrs, rmCost := celValidator.Validate(ctx, pth.Child("default"), s, s.Default.Object, s.Default.Object, remainingCost)
remainingCost = rmCost
allErrs = append(allErrs, celErrs...)
if remainingCost < 0 {
return allErrs, nil, remainingCost
}
}
}
}

It looks like we're currently passing s.Default.Object as both the old and new object, to run as many validation rules as possible.

If the ratcheting validation feature is enabled, and it passed that first validation check, should we also validate with s.Default.Object as the new object and nil as the old object, to make sure defaults pass any ratcheting rules that behave differently on create?

@alexzielenski
Copy link
Contributor Author

If the ratcheting validation feature is enabled, and it passed that first validation check, should we also validate with s.Default.Object as the new object and nil as the old object, to make sure defaults pass any ratcheting rules that behave differently on create?

I think that'd be prudent. Ive added it and a test for it

}

return compResults, nil
}

func prepareEnvSet(baseEnvSet *environment.EnvSet, declType *apiservercel.DeclType) (*environment.EnvSet, error) {
func prepareEnvSet(baseEnvSet *environment.EnvSet, declType *apiservercel.DeclType) (*environment.EnvSet, *environment.EnvSet, error) {
Copy link
Member

Choose a reason for hiding this comment

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

doc or name the return vars

Copy link
Contributor Author

Choose a reason for hiding this comment

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

named return vars

@@ -96,6 +98,11 @@ func validate(ctx context.Context, pth *field.Path, s *structuralschema.Structur
if remainingCost < 0 {
return allErrs, nil, remainingCost
}

if len(celErrs) == 0 && utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CRDValidationRatcheting) {
Copy link
Member

Choose a reason for hiding this comment

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

needs a comment explaining why we're calling this again

also, I think we need to capture the cost of both types of runs and take whichever leaves less remaining cost

celErrs, rmCost := celValidator.Validate(ctx, pth.Child("default"), s, s.Default.Object, s.Default.Object, remainingCost)
allErrs = append(allErrs, celErrs...)

if len(celErrs) == 0 && utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CRDValidationRatcheting) {
  celErrs, rmCostWithoutOldObject := celValidator.Validate(ctx, pth.Child("default"), s, s.Default.Object, nil, remainingCost)
  allErrs = append(allErrs, celErrs...)
  if rmCostWithoutOldObject < rmCost {
    rmCost = rmCostWithoutOldObject
  }
}

remainingCost = rmCost
if remainingCost < 0 {
  return allErrs, nil, remainingCost
}

Copy link
Member

Choose a reason for hiding this comment

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

same comment applies below

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Applied feedback as suggested and added comments

@alexzielenski alexzielenski force-pushed the apiserver/apiextensions/ratcheting-oldself-create branch from 0101780 to 77942a4 Compare November 3, 2023 21:46
@liggitt
Copy link
Member

liggitt commented Nov 3, 2023

/lgtm
/approve

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

LGTM label has been added.

Git tree hash: 12b79c4256148f1e4495ff3df690d37574308a91

@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: alexzielenski, jpbetz, liggitt

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

The pull request process is described here

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 the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Nov 3, 2023
@alexzielenski alexzielenski force-pushed the apiserver/apiextensions/ratcheting-oldself-create branch from 77942a4 to 42a70cd Compare November 3, 2023 22:45
@k8s-ci-robot k8s-ci-robot removed the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Nov 3, 2023
@apelisse
Copy link
Member

apelisse commented Nov 3, 2023 via email

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

LGTM label has been added.

Git tree hash: 5ea11e89f552ad0224f32363d568455aa988e3c5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-review Categorizes an issue or PR as actively needing an API review. approved Indicates a PR has been approved by an approver from all required OWNERS files. area/code-generation cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API kind/feature Categorizes issue or PR as related to a new feature. lgtm "Looks good to me", indicates that a PR is ready to be merged. needs-priority Indicates a PR lacks a `priority/foo` label and requires one. release-note Denotes a PR that will be considered when it comes time to generate release notes. sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. triage/accepted Indicates an issue or PR is ready to be actively worked on.
Projects
Status: API review completed, 1.29
Archived in project
Development

Successfully merging this pull request may close these issues.

None yet

8 participants