diff --git a/pkg/apis/discovery/v1/zz_generated.validations.go b/pkg/apis/discovery/v1/zz_generated.validations.go index 6a7d813549c..2766f592446 100644 --- a/pkg/apis/discovery/v1/zz_generated.validations.go +++ b/pkg/apis/discovery/v1/zz_generated.validations.go @@ -91,7 +91,25 @@ func Validate_Endpoint(ctx context.Context, op operation.Operation, fldPath *fie func Validate_EndpointSlice(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *discoveryv1.EndpointSlice) (errs field.ErrorList) { // field discoveryv1.EndpointSlice.TypeMeta has no validation // field discoveryv1.EndpointSlice.ObjectMeta has no validation - // field discoveryv1.EndpointSlice.AddressType has no validation + + // field discoveryv1.EndpointSlice.AddressType + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *discoveryv1.AddressType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("addressType"), &obj.AddressType, safe.Field(oldObj, func(oldObj *discoveryv1.EndpointSlice) *discoveryv1.AddressType { return &oldObj.AddressType }), oldObj != nil)...) // field discoveryv1.EndpointSlice.Endpoints errs = append(errs, diff --git a/pkg/apis/discovery/v1beta1/zz_generated.validations.go b/pkg/apis/discovery/v1beta1/zz_generated.validations.go index ef44355086f..ba337446aa7 100644 --- a/pkg/apis/discovery/v1beta1/zz_generated.validations.go +++ b/pkg/apis/discovery/v1beta1/zz_generated.validations.go @@ -90,7 +90,25 @@ func Validate_Endpoint(ctx context.Context, op operation.Operation, fldPath *fie func Validate_EndpointSlice(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *discoveryv1beta1.EndpointSlice) (errs field.ErrorList) { // field discoveryv1beta1.EndpointSlice.TypeMeta has no validation // field discoveryv1beta1.EndpointSlice.ObjectMeta has no validation - // field discoveryv1beta1.EndpointSlice.AddressType has no validation + + // field discoveryv1beta1.EndpointSlice.AddressType + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *discoveryv1beta1.AddressType, oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("addressType"), &obj.AddressType, safe.Field(oldObj, func(oldObj *discoveryv1beta1.EndpointSlice) *discoveryv1beta1.AddressType { return &oldObj.AddressType }), oldObj != nil)...) // field discoveryv1beta1.EndpointSlice.Endpoints errs = append(errs, diff --git a/pkg/apis/discovery/validation/validation.go b/pkg/apis/discovery/validation/validation.go index 9c9e441a583..be93d297206 100644 --- a/pkg/apis/discovery/validation/validation.go +++ b/pkg/apis/discovery/validation/validation.go @@ -207,7 +207,7 @@ func validateAddressType(addressType discovery.AddressType) field.ErrorList { allErrs := field.ErrorList{} if addressType == "" { - allErrs = append(allErrs, field.Required(field.NewPath("addressType"), "")) + allErrs = append(allErrs, field.Required(field.NewPath("addressType"), "").MarkCoveredByDeclarative()) } else if !supportedAddressTypes.Has(addressType) { allErrs = append(allErrs, field.NotSupported(field.NewPath("addressType"), addressType, sets.List(supportedAddressTypes))) } diff --git a/pkg/registry/discovery/endpointslice/declarative_validation_test.go b/pkg/registry/discovery/endpointslice/declarative_validation_test.go index cf4bb110d4e..9c78e9caa06 100644 --- a/pkg/registry/discovery/endpointslice/declarative_validation_test.go +++ b/pkg/registry/discovery/endpointslice/declarative_validation_test.go @@ -70,6 +70,12 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) { field.TooMany(field.NewPath("endpoints").Index(0).Child("addresses"), 101, 100).WithOrigin("maxItems"), }, }, + "invalid missing addressType": { + input: mkValidEndpointSlice(tweakAddressType("")), + expectedErrs: field.ErrorList{ + field.Required(field.NewPath("addressType"), ""), + }, + }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { @@ -164,3 +170,9 @@ func tweakAddresses(count int) func(*discovery.EndpointSlice) { obj.Endpoints[0].Addresses = addrs } } + +func tweakAddressType(addrType discovery.AddressType) func(*discovery.EndpointSlice) { + return func(obj *discovery.EndpointSlice) { + obj.AddressType = addrType + } +} diff --git a/staging/src/k8s.io/api/discovery/v1/generated.proto b/staging/src/k8s.io/api/discovery/v1/generated.proto index 7c987d8162f..146865d8fc0 100644 --- a/staging/src/k8s.io/api/discovery/v1/generated.proto +++ b/staging/src/k8s.io/api/discovery/v1/generated.proto @@ -184,6 +184,8 @@ message EndpointSlice { // The EndpointSlice controller only generates, and kube-proxy only processes, // slices of addressType "IPv4" and "IPv6". No semantics are defined for // the "FQDN" type. + // +required + // +k8s:required optional string addressType = 4; // endpoints is a list of unique endpoints in this slice. Each slice may diff --git a/staging/src/k8s.io/api/discovery/v1/types.go b/staging/src/k8s.io/api/discovery/v1/types.go index 20b4c43f044..a682a4596df 100644 --- a/staging/src/k8s.io/api/discovery/v1/types.go +++ b/staging/src/k8s.io/api/discovery/v1/types.go @@ -48,6 +48,8 @@ type EndpointSlice struct { // The EndpointSlice controller only generates, and kube-proxy only processes, // slices of addressType "IPv4" and "IPv6". No semantics are defined for // the "FQDN" type. + // +required + // +k8s:required AddressType AddressType `json:"addressType" protobuf:"bytes,4,rep,name=addressType"` // endpoints is a list of unique endpoints in this slice. Each slice may diff --git a/staging/src/k8s.io/api/discovery/v1beta1/generated.proto b/staging/src/k8s.io/api/discovery/v1beta1/generated.proto index 1de1d5e3c2a..2be880149e1 100644 --- a/staging/src/k8s.io/api/discovery/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/discovery/v1beta1/generated.proto @@ -170,6 +170,8 @@ message EndpointSlice { // * IPv4: Represents an IPv4 Address. // * IPv6: Represents an IPv6 Address. // * FQDN: Represents a Fully Qualified Domain Name. + // +required + // +k8s:required optional string addressType = 4; // endpoints is a list of unique endpoints in this slice. Each slice may diff --git a/staging/src/k8s.io/api/discovery/v1beta1/types.go b/staging/src/k8s.io/api/discovery/v1beta1/types.go index 0f3b403d5ae..fbc65ab5fdf 100644 --- a/staging/src/k8s.io/api/discovery/v1beta1/types.go +++ b/staging/src/k8s.io/api/discovery/v1beta1/types.go @@ -45,6 +45,8 @@ type EndpointSlice struct { // * IPv4: Represents an IPv4 Address. // * IPv6: Represents an IPv6 Address. // * FQDN: Represents a Fully Qualified Domain Name. + // +required + // +k8s:required AddressType AddressType `json:"addressType" protobuf:"bytes,4,rep,name=addressType"` // endpoints is a list of unique endpoints in this slice. Each slice may