diff --git a/pkg/api/testing/validation_test.go b/pkg/api/testing/validation_test.go index dfd0dfad863..df8df8c8a78 100644 --- a/pkg/api/testing/validation_test.go +++ b/pkg/api/testing/validation_test.go @@ -55,6 +55,9 @@ func TestVersionedValidationByFuzzing(t *testing.T) { {Group: "autoscaling", Version: "v1beta1"}, {Group: "autoscaling", Version: "v1beta2"}, {Group: "autoscaling", Version: "v2"}, + {Group: "admissionregistration.k8s.io", Version: "v1"}, + {Group: "admissionregistration.k8s.io", Version: "v1beta1"}, + {Group: "admissionregistration.k8s.io", Version: "v1alpha1"}, {Group: "discovery.k8s.io", Version: "v1"}, {Group: "discovery.k8s.io", Version: "v1beta1"}, {Group: "admissionregistration.k8s.io", Version: "v1"}, diff --git a/pkg/apis/admissionregistration/v1/zz_generated.validations.go b/pkg/apis/admissionregistration/v1/zz_generated.validations.go index fb3f6468821..0714ecc8d74 100644 --- a/pkg/apis/admissionregistration/v1/zz_generated.validations.go +++ b/pkg/apis/admissionregistration/v1/zz_generated.validations.go @@ -99,6 +99,27 @@ func Validate_ValidatingAdmissionPolicyBindingSpec(ctx context.Context, op opera // field admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec.ParamRef has no validation // field admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec.MatchResources has no validation - // field admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec.ValidationActions has no validation + + // field admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec.ValidationActions + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []admissionregistrationv1.ValidationAction, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("validationActions"), obj.ValidationActions, safe.Field(oldObj, func(oldObj *admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec) []admissionregistrationv1.ValidationAction { + return oldObj.ValidationActions + }), oldObj != nil)...) + return errs } diff --git a/pkg/apis/admissionregistration/v1alpha1/zz_generated.validations.go b/pkg/apis/admissionregistration/v1alpha1/zz_generated.validations.go index c96f9fc5326..a5b3b145835 100644 --- a/pkg/apis/admissionregistration/v1alpha1/zz_generated.validations.go +++ b/pkg/apis/admissionregistration/v1alpha1/zz_generated.validations.go @@ -99,6 +99,27 @@ func Validate_ValidatingAdmissionPolicyBindingSpec(ctx context.Context, op opera // field admissionregistrationv1alpha1.ValidatingAdmissionPolicyBindingSpec.ParamRef has no validation // field admissionregistrationv1alpha1.ValidatingAdmissionPolicyBindingSpec.MatchResources has no validation - // field admissionregistrationv1alpha1.ValidatingAdmissionPolicyBindingSpec.ValidationActions has no validation + + // field admissionregistrationv1alpha1.ValidatingAdmissionPolicyBindingSpec.ValidationActions + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []admissionregistrationv1alpha1.ValidationAction, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("validationActions"), obj.ValidationActions, safe.Field(oldObj, func(oldObj *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBindingSpec) []admissionregistrationv1alpha1.ValidationAction { + return oldObj.ValidationActions + }), oldObj != nil)...) + return errs } diff --git a/pkg/apis/admissionregistration/v1beta1/zz_generated.validations.go b/pkg/apis/admissionregistration/v1beta1/zz_generated.validations.go index c13fdb338ce..f553eb4b5a0 100644 --- a/pkg/apis/admissionregistration/v1beta1/zz_generated.validations.go +++ b/pkg/apis/admissionregistration/v1beta1/zz_generated.validations.go @@ -99,6 +99,27 @@ func Validate_ValidatingAdmissionPolicyBindingSpec(ctx context.Context, op opera // field admissionregistrationv1beta1.ValidatingAdmissionPolicyBindingSpec.ParamRef has no validation // field admissionregistrationv1beta1.ValidatingAdmissionPolicyBindingSpec.MatchResources has no validation - // field admissionregistrationv1beta1.ValidatingAdmissionPolicyBindingSpec.ValidationActions has no validation + + // field admissionregistrationv1beta1.ValidatingAdmissionPolicyBindingSpec.ValidationActions + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []admissionregistrationv1beta1.ValidationAction, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("validationActions"), obj.ValidationActions, safe.Field(oldObj, func(oldObj *admissionregistrationv1beta1.ValidatingAdmissionPolicyBindingSpec) []admissionregistrationv1beta1.ValidationAction { + return oldObj.ValidationActions + }), oldObj != nil)...) + return errs } diff --git a/pkg/apis/admissionregistration/validation/validation.go b/pkg/apis/admissionregistration/validation/validation.go index fcae248ca5f..80f71ed85cb 100644 --- a/pkg/apis/admissionregistration/validation/validation.go +++ b/pkg/apis/admissionregistration/validation/validation.go @@ -939,7 +939,7 @@ func validateValidationActions(va []admissionregistration.ValidationAction, fldP allErrors = append(allErrors, field.Invalid(fldPath, va, "must not contain both Deny and Warn (repeating the same validation failure information in the API response and headers serves no purpose)")) } if len(actions) == 0 { - allErrors = append(allErrors, field.Required(fldPath, "at least one validation action is required")) + allErrors = append(allErrors, field.Required(fldPath, "at least one validation action is required")).MarkCoveredByDeclarative() } return allErrors } diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index 995f0339399..8a800cb4440 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -2554,7 +2554,7 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingAdmissionPolicyBindingS }, }, }, - Required: []string{"policyName"}, + Required: []string{"policyName", "validationActions"}, }, }, Dependencies: []string{ @@ -4149,7 +4149,7 @@ func schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicyBi }, }, }, - Required: []string{"policyName"}, + Required: []string{"policyName", "validationActions"}, }, }, Dependencies: []string{ @@ -5751,7 +5751,7 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingAdmissionPolicyBin }, }, }, - Required: []string{"policyName"}, + Required: []string{"policyName", "validationActions"}, }, }, Dependencies: []string{ diff --git a/pkg/registry/admissionregistration/validatingadmissionpolicybinding/declarative_validation_test.go b/pkg/registry/admissionregistration/validatingadmissionpolicybinding/declarative_validation_test.go index 9bee254345d..4b4fc69eb9e 100644 --- a/pkg/registry/admissionregistration/validatingadmissionpolicybinding/declarative_validation_test.go +++ b/pkg/registry/admissionregistration/validatingadmissionpolicybinding/declarative_validation_test.go @@ -51,12 +51,18 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { "valid": { input: mkValidBinding(), }, - "invalid policyName": { + "spec.policyName is required": { input: mkValidBinding(tweakPolicyName("")), expectedErrs: field.ErrorList{ field.Required(field.NewPath("spec", "policyName"), ""), }, }, + "spec.validationActions is required": { + input: mkValidBinding(tweakValidateActions([]admissionregistration.ValidationAction{})), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "validationActions"), ""), + }, + }, // TODO: Add more test cases } for k, tc := range testCases { @@ -84,13 +90,20 @@ func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) { oldObj: mkValidBinding(), updateObj: mkValidBinding(), }, - "invalid update name": { + "update with empty spec.policyName": { oldObj: mkValidBinding(func(obj *admissionregistration.ValidatingAdmissionPolicyBinding) { obj.ResourceVersion = "1" }), updateObj: mkValidBinding(tweakPolicyName("")), expectedErrs: field.ErrorList{ field.Required(field.NewPath("spec", "policyName"), ""), }, }, + "update with empty spec.validationActions": { + oldObj: mkValidBinding(), + updateObj: mkValidBinding(tweakValidateActions([]admissionregistration.ValidationAction{})), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("spec", "validationActions"), ""), + }, + }, // TODO: Add more test cases } for k, tc := range testCases { @@ -131,3 +144,9 @@ func tweakPolicyName(policyName string) func(obj *admissionregistration.Validati obj.Spec.PolicyName = policyName } } + +func tweakValidateActions(actions []admissionregistration.ValidationAction) func(*admissionregistration.ValidatingAdmissionPolicyBinding) { + return func(obj *admissionregistration.ValidatingAdmissionPolicyBinding) { + obj.Spec.ValidationActions = actions + } +} diff --git a/staging/src/k8s.io/api/admissionregistration/v1/generated.proto b/staging/src/k8s.io/api/admissionregistration/v1/generated.proto index 83ab1cfddcc..13a045828ec 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1/generated.proto +++ b/staging/src/k8s.io/api/admissionregistration/v1/generated.proto @@ -682,6 +682,8 @@ message ValidatingAdmissionPolicyBindingSpec { // // Required. // +listType=set + // +required + // +k8s:required repeated string validationActions = 4; } diff --git a/staging/src/k8s.io/api/admissionregistration/v1/types.go b/staging/src/k8s.io/api/admissionregistration/v1/types.go index 2bc5ad50616..6f92b7c2095 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1/types.go +++ b/staging/src/k8s.io/api/admissionregistration/v1/types.go @@ -533,6 +533,8 @@ type ValidatingAdmissionPolicyBindingSpec struct { // // Required. // +listType=set + // +required + // +k8s:required ValidationActions []ValidationAction `json:"validationActions,omitempty" protobuf:"bytes,4,rep,name=validationActions"` } diff --git a/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.proto b/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.proto index 77b107d3f82..37c384ce0f0 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.proto +++ b/staging/src/k8s.io/api/admissionregistration/v1alpha1/generated.proto @@ -705,6 +705,8 @@ message ValidatingAdmissionPolicyBindingSpec { // // Required. // +listType=set + // +required + // +k8s:required repeated string validationActions = 4; } diff --git a/staging/src/k8s.io/api/admissionregistration/v1alpha1/types.go b/staging/src/k8s.io/api/admissionregistration/v1alpha1/types.go index fea592ea36e..5657ce2c1cc 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1alpha1/types.go +++ b/staging/src/k8s.io/api/admissionregistration/v1alpha1/types.go @@ -473,6 +473,8 @@ type ValidatingAdmissionPolicyBindingSpec struct { // // Required. // +listType=set + // +required + // +k8s:required ValidationActions []ValidationAction `json:"validationActions,omitempty" protobuf:"bytes,4,rep,name=validationActions"` } diff --git a/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.proto b/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.proto index fb7e24c6db0..e0be62dc45b 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.proto @@ -931,6 +931,8 @@ message ValidatingAdmissionPolicyBindingSpec { // // Required. // +listType=set + // +required + // +k8s:required repeated string validationActions = 4; } diff --git a/staging/src/k8s.io/api/admissionregistration/v1beta1/types.go b/staging/src/k8s.io/api/admissionregistration/v1beta1/types.go index 9bc7c447ef0..8bbbe3028fd 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1beta1/types.go +++ b/staging/src/k8s.io/api/admissionregistration/v1beta1/types.go @@ -486,6 +486,8 @@ type ValidatingAdmissionPolicyBindingSpec struct { // // Required. // +listType=set + // +required + // +k8s:required ValidationActions []ValidationAction `json:"validationActions,omitempty" protobuf:"bytes,4,rep,name=validationActions"` }