diff --git a/pkg/apis/resource/v1/zz_generated.validations.go b/pkg/apis/resource/v1/zz_generated.validations.go index 49f4f141468..6f2134bb96f 100644 --- a/pkg/apis/resource/v1/zz_generated.validations.go +++ b/pkg/apis/resource/v1/zz_generated.validations.go @@ -196,7 +196,21 @@ func Validate_AllocationResult(ctx context.Context, op operation.Operation, fldP // to declarative validation rules in the API schema. func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.Device) (errs field.ErrorList) { // field resourcev1.Device.Name has no validation - // field resourcev1.Device.Attributes has no validation + + // field resourcev1.Device.Attributes + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[resourcev1.QualifiedName]resourcev1.DeviceAttribute) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // iterate the map and call the value type's validation function + errs = append(errs, validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_DeviceAttribute)...) + return + }(fldPath.Child("attributes"), obj.Attributes, safe.Field(oldObj, func(oldObj *resourcev1.Device) map[resourcev1.QualifiedName]resourcev1.DeviceAttribute { + return oldObj.Attributes + }))...) + // field resourcev1.Device.Capacity has no validation // field resourcev1.Device.ConsumesCounters has no validation // field resourcev1.Device.NodeName has no validation @@ -372,6 +386,108 @@ func Validate_DeviceAllocationResult(ctx context.Context, op operation.Operation return errs } +var unionMembershipFor_k8s_io_api_resource_v1_DeviceAttribute_ = validate.NewUnionMembership(validate.NewUnionMember("int"), validate.NewUnionMember("bool"), validate.NewUnionMember("string"), validate.NewUnionMember("version")) + +// Validate_DeviceAttribute validates an instance of DeviceAttribute according +// to declarative validation rules in the API schema. +func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.DeviceAttribute) (errs field.ErrorList) { + errs = append(errs, validate.Union(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_api_resource_v1_DeviceAttribute_, func(obj *resourcev1.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.IntValue != nil + }, func(obj *resourcev1.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.BoolValue != nil + }, func(obj *resourcev1.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.StringValue != nil + }, func(obj *resourcev1.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.VersionValue != nil + })...) + + // field resourcev1.DeviceAttribute.IntValue + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int64) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("int"), obj.IntValue, safe.Field(oldObj, func(oldObj *resourcev1.DeviceAttribute) *int64 { return oldObj.IntValue }))...) + + // field resourcev1.DeviceAttribute.BoolValue + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("bool"), obj.BoolValue, safe.Field(oldObj, func(oldObj *resourcev1.DeviceAttribute) *bool { return oldObj.BoolValue }))...) + + // field resourcev1.DeviceAttribute.StringValue + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("string"), obj.StringValue, safe.Field(oldObj, func(oldObj *resourcev1.DeviceAttribute) *string { return oldObj.StringValue }))...) + + // field resourcev1.DeviceAttribute.VersionValue + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("version"), obj.VersionValue, safe.Field(oldObj, func(oldObj *resourcev1.DeviceAttribute) *string { return oldObj.VersionValue }))...) + + return errs +} + // Validate_DeviceClaim validates an instance of DeviceClaim according // to declarative validation rules in the API schema. func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.DeviceClaim) (errs field.ErrorList) { diff --git a/pkg/apis/resource/v1beta1/zz_generated.validations.go b/pkg/apis/resource/v1beta1/zz_generated.validations.go index 83525532c61..56fa1e98cbd 100644 --- a/pkg/apis/resource/v1beta1/zz_generated.validations.go +++ b/pkg/apis/resource/v1beta1/zz_generated.validations.go @@ -197,7 +197,20 @@ func Validate_AllocationResult(ctx context.Context, op operation.Operation, fldP // Validate_BasicDevice validates an instance of BasicDevice according // to declarative validation rules in the API schema. func Validate_BasicDevice(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.BasicDevice) (errs field.ErrorList) { - // field resourcev1beta1.BasicDevice.Attributes has no validation + // field resourcev1beta1.BasicDevice.Attributes + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[resourcev1beta1.QualifiedName]resourcev1beta1.DeviceAttribute) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // iterate the map and call the value type's validation function + errs = append(errs, validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_DeviceAttribute)...) + return + }(fldPath.Child("attributes"), obj.Attributes, safe.Field(oldObj, func(oldObj *resourcev1beta1.BasicDevice) map[resourcev1beta1.QualifiedName]resourcev1beta1.DeviceAttribute { + return oldObj.Attributes + }))...) + // field resourcev1beta1.BasicDevice.Capacity has no validation // field resourcev1beta1.BasicDevice.ConsumesCounters has no validation // field resourcev1beta1.BasicDevice.NodeName has no validation @@ -393,6 +406,108 @@ func Validate_DeviceAllocationResult(ctx context.Context, op operation.Operation return errs } +var unionMembershipFor_k8s_io_api_resource_v1beta1_DeviceAttribute_ = validate.NewUnionMembership(validate.NewUnionMember("int"), validate.NewUnionMember("bool"), validate.NewUnionMember("string"), validate.NewUnionMember("version")) + +// Validate_DeviceAttribute validates an instance of DeviceAttribute according +// to declarative validation rules in the API schema. +func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceAttribute) (errs field.ErrorList) { + errs = append(errs, validate.Union(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_api_resource_v1beta1_DeviceAttribute_, func(obj *resourcev1beta1.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.IntValue != nil + }, func(obj *resourcev1beta1.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.BoolValue != nil + }, func(obj *resourcev1beta1.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.StringValue != nil + }, func(obj *resourcev1beta1.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.VersionValue != nil + })...) + + // field resourcev1beta1.DeviceAttribute.IntValue + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int64) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("int"), obj.IntValue, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceAttribute) *int64 { return oldObj.IntValue }))...) + + // field resourcev1beta1.DeviceAttribute.BoolValue + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("bool"), obj.BoolValue, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceAttribute) *bool { return oldObj.BoolValue }))...) + + // field resourcev1beta1.DeviceAttribute.StringValue + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("string"), obj.StringValue, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceAttribute) *string { return oldObj.StringValue }))...) + + // field resourcev1beta1.DeviceAttribute.VersionValue + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("version"), obj.VersionValue, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceAttribute) *string { return oldObj.VersionValue }))...) + + return errs +} + // Validate_DeviceClaim validates an instance of DeviceClaim according // to declarative validation rules in the API schema. func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceClaim) (errs field.ErrorList) { diff --git a/pkg/apis/resource/v1beta2/zz_generated.validations.go b/pkg/apis/resource/v1beta2/zz_generated.validations.go index fccb171ab34..377bc7a8999 100644 --- a/pkg/apis/resource/v1beta2/zz_generated.validations.go +++ b/pkg/apis/resource/v1beta2/zz_generated.validations.go @@ -198,7 +198,21 @@ func Validate_AllocationResult(ctx context.Context, op operation.Operation, fldP // to declarative validation rules in the API schema. func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.Device) (errs field.ErrorList) { // field resourcev1beta2.Device.Name has no validation - // field resourcev1beta2.Device.Attributes has no validation + + // field resourcev1beta2.Device.Attributes + errs = append(errs, + func(fldPath *field.Path, obj, oldObj map[resourcev1beta2.QualifiedName]resourcev1beta2.DeviceAttribute) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // iterate the map and call the value type's validation function + errs = append(errs, validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, Validate_DeviceAttribute)...) + return + }(fldPath.Child("attributes"), obj.Attributes, safe.Field(oldObj, func(oldObj *resourcev1beta2.Device) map[resourcev1beta2.QualifiedName]resourcev1beta2.DeviceAttribute { + return oldObj.Attributes + }))...) + // field resourcev1beta2.Device.Capacity has no validation // field resourcev1beta2.Device.ConsumesCounters has no validation // field resourcev1beta2.Device.NodeName has no validation @@ -374,6 +388,108 @@ func Validate_DeviceAllocationResult(ctx context.Context, op operation.Operation return errs } +var unionMembershipFor_k8s_io_api_resource_v1beta2_DeviceAttribute_ = validate.NewUnionMembership(validate.NewUnionMember("int"), validate.NewUnionMember("bool"), validate.NewUnionMember("string"), validate.NewUnionMember("version")) + +// Validate_DeviceAttribute validates an instance of DeviceAttribute according +// to declarative validation rules in the API schema. +func Validate_DeviceAttribute(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceAttribute) (errs field.ErrorList) { + errs = append(errs, validate.Union(ctx, op, fldPath, obj, oldObj, unionMembershipFor_k8s_io_api_resource_v1beta2_DeviceAttribute_, func(obj *resourcev1beta2.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.IntValue != nil + }, func(obj *resourcev1beta2.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.BoolValue != nil + }, func(obj *resourcev1beta2.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.StringValue != nil + }, func(obj *resourcev1beta2.DeviceAttribute) bool { + if obj == nil { + return false + } + return obj.VersionValue != nil + })...) + + // field resourcev1beta2.DeviceAttribute.IntValue + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *int64) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("int"), obj.IntValue, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceAttribute) *int64 { return oldObj.IntValue }))...) + + // field resourcev1beta2.DeviceAttribute.BoolValue + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("bool"), obj.BoolValue, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceAttribute) *bool { return oldObj.BoolValue }))...) + + // field resourcev1beta2.DeviceAttribute.StringValue + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("string"), obj.StringValue, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceAttribute) *string { return oldObj.StringValue }))...) + + // field resourcev1beta2.DeviceAttribute.VersionValue + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + }(fldPath.Child("version"), obj.VersionValue, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceAttribute) *string { return oldObj.VersionValue }))...) + + return errs +} + // Validate_DeviceClaim validates an instance of DeviceClaim according // to declarative validation rules in the API schema. func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceClaim) (errs field.ErrorList) { diff --git a/pkg/apis/resource/validation/validation.go b/pkg/apis/resource/validation/validation.go index 06d2cf2aa33..1f49f30e419 100644 --- a/pkg/apis/resource/validation/validation.go +++ b/pkg/apis/resource/validation/validation.go @@ -910,11 +910,11 @@ func validateDeviceAttribute(attribute resource.DeviceAttribute, fldPath *field. switch numFields { case 0: - allErrs = append(allErrs, field.Required(fldPath, "exactly one value must be specified")) + allErrs = append(allErrs, field.Invalid(fldPath, "", "exactly one value must be specified").MarkCoveredByDeclarative()) case 1: // Okay. default: - allErrs = append(allErrs, field.Invalid(fldPath, attribute, "exactly one value must be specified")) + allErrs = append(allErrs, field.Invalid(fldPath, attribute, "exactly one value must be specified").MarkCoveredByDeclarative()) } return allErrs } diff --git a/pkg/apis/resource/validation/validation_resourceslice_test.go b/pkg/apis/resource/validation/validation_resourceslice_test.go index 7dfe927e8d7..d113d68aad4 100644 --- a/pkg/apis/resource/validation/validation_resourceslice_test.go +++ b/pkg/apis/resource/validation/validation_resourceslice_test.go @@ -336,8 +336,8 @@ func TestValidateResourceSlice(t *testing.T) { "bad-attribute": { wantFailures: field.ErrorList{ field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(badName), badName, "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', regex used for validation is '[A-Za-z_][A-Za-z0-9_]*')"), - field.Required(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(badName), "exactly one value must be specified"), - field.Invalid(field.NewPath("spec", "devices").Index(2).Child("attributes").Key(goodName), resourceapi.DeviceAttribute{StringValue: ptr.To("x"), VersionValue: ptr.To("1.2.3")}, "exactly one value must be specified"), + field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(badName), "", "exactly one value must be specified").MarkCoveredByDeclarative(), + field.Invalid(field.NewPath("spec", "devices").Index(2).Child("attributes").Key(goodName), resourceapi.DeviceAttribute{StringValue: ptr.To("x"), VersionValue: ptr.To("1.2.3")}, "exactly one value must be specified").MarkCoveredByDeclarative(), field.Invalid(field.NewPath("spec", "devices").Index(3).Child("attributes").Key(goodName).Child("version"), strings.Repeat("x", resourceapi.DeviceAttributeMaxValueLength+1), "must be a string compatible with semver.org spec 2.0.0"), field.TooLongMaxLength(field.NewPath("spec", "devices").Index(3).Child("attributes").Key(goodName).Child("version"), strings.Repeat("x", resourceapi.DeviceAttributeMaxValueLength+1), resourceapi.DeviceAttributeMaxValueLength), field.TooLongMaxLength(field.NewPath("spec", "devices").Index(4).Child("attributes").Key(goodName).Child("string"), strings.Repeat("x", resourceapi.DeviceAttributeMaxValueLength+1), resourceapi.DeviceAttributeMaxValueLength), diff --git a/pkg/registry/resource/resourceslice/declarative_validation_test.go b/pkg/registry/resource/resourceslice/declarative_validation_test.go index 84ab9e78fac..44aeba1803f 100644 --- a/pkg/registry/resource/resourceslice/declarative_validation_test.go +++ b/pkg/registry/resource/resourceslice/declarative_validation_test.go @@ -97,6 +97,31 @@ func TestDeclarativeValidate(t *testing.T) { field.Required(field.NewPath("spec", "devices").Index(0).Child("taints").Index(0).Child("effect"), ""), }, }, + // spec.Devices[%].attribute + "valid: device attribute int": { + input: mkResourceSlice(tweakDeviceAttribute("test.io/int", resource.DeviceAttribute{IntValue: ptr.To[int64](123)})), + }, + "valid: device attribute bool": { + input: mkResourceSlice(tweakDeviceAttribute("test.io/bool", resource.DeviceAttribute{BoolValue: ptr.To(true)})), + }, + "valid: device attribute string": { + input: mkResourceSlice(tweakDeviceAttribute("test.io/string", resource.DeviceAttribute{StringValue: ptr.To("value")})), + }, + "valid: device attribute version": { + input: mkResourceSlice(tweakDeviceAttribute("test.io/version", resource.DeviceAttribute{VersionValue: ptr.To("1.2.3")})), + }, + "invalid: device attribute with multiple values": { + input: mkResourceSlice(tweakDeviceAttribute("test.io/multiple", resource.DeviceAttribute{IntValue: ptr.To[int64](123), BoolValue: ptr.To(true)})), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "devices").Index(0).Child("attributes").Key("test.io/multiple"), "", ""), + }, + }, + "invalid: device attribute no value": { + input: mkResourceSlice(tweakDeviceAttribute("test.io/multiple", resource.DeviceAttribute{})), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "devices").Index(0).Child("attributes").Key("test.io/multiple"), "", ""), + }, + }, // TODO: Add more test cases } @@ -176,6 +201,17 @@ func TestDeclarativeValidateUpdate(t *testing.T) { field.Required(field.NewPath("spec", "devices").Index(0).Child("taints").Index(0).Child("effect"), ""), }, }, + "valid update: device attribute": { + old: mkResourceSlice(tweakDeviceAttribute("test.io/int", resource.DeviceAttribute{IntValue: ptr.To[int64](123)})), + update: mkResourceSlice(tweakDeviceAttribute("test.io/int", resource.DeviceAttribute{BoolValue: ptr.To(true)})), + }, + "invalid update: device attribute with multiple values": { + old: mkResourceSlice(), + update: mkResourceSlice(tweakDeviceAttribute("test.io/multiple", resource.DeviceAttribute{IntValue: ptr.To[int64](123), BoolValue: ptr.To(true)})), + expectedErrs: field.ErrorList{ + field.Invalid(field.NewPath("spec", "devices").Index(0).Child("attributes").Key("test.io/multiple"), "", "may have only one of the following fields set: bool, int, string, version"), + }, + }, } for k, tc := range testCases { t.Run(k, func(t *testing.T) { @@ -249,3 +285,12 @@ func tweakDeviceTaintEffect(effect string) func(*resource.ResourceSlice) { rs.Spec.Devices[0].Taints[0].Effect = resource.DeviceTaintEffect(effect) } } + +func tweakDeviceAttribute(name resource.QualifiedName, value resource.DeviceAttribute) func(*resource.ResourceSlice) { + return func(rs *resource.ResourceSlice) { + if rs.Spec.Devices[0].Attributes == nil { + rs.Spec.Devices[0].Attributes = make(map[resource.QualifiedName]resource.DeviceAttribute) + } + rs.Spec.Devices[0].Attributes[name] = value + } +} diff --git a/staging/src/k8s.io/api/resource/v1/generated.proto b/staging/src/k8s.io/api/resource/v1/generated.proto index ad14e8b416a..9b3c48be57e 100644 --- a/staging/src/k8s.io/api/resource/v1/generated.proto +++ b/staging/src/k8s.io/api/resource/v1/generated.proto @@ -514,26 +514,30 @@ message DeviceAttribute { // IntValue is a number. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional int64 int = 2; // BoolValue is a true/false value. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional bool bool = 3; // StringValue is a string. Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional string string = 4; // VersionValue is a semantic version according to semver.org spec 2.0.0. // Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional string version = 5; } diff --git a/staging/src/k8s.io/api/resource/v1/types.go b/staging/src/k8s.io/api/resource/v1/types.go index c992b728730..771ceafd614 100644 --- a/staging/src/k8s.io/api/resource/v1/types.go +++ b/staging/src/k8s.io/api/resource/v1/types.go @@ -581,26 +581,30 @@ type DeviceAttribute struct { // IntValue is a number. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember IntValue *int64 `json:"int,omitempty" protobuf:"varint,2,opt,name=int"` // BoolValue is a true/false value. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember BoolValue *bool `json:"bool,omitempty" protobuf:"varint,3,opt,name=bool"` // StringValue is a string. Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember StringValue *string `json:"string,omitempty" protobuf:"bytes,4,opt,name=string"` // VersionValue is a semantic version according to semver.org spec 2.0.0. // Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember VersionValue *string `json:"version,omitempty" protobuf:"bytes,5,opt,name=version"` } diff --git a/staging/src/k8s.io/api/resource/v1beta1/generated.proto b/staging/src/k8s.io/api/resource/v1beta1/generated.proto index 6acd34d0bbf..464867b5e04 100644 --- a/staging/src/k8s.io/api/resource/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/resource/v1beta1/generated.proto @@ -522,26 +522,30 @@ message DeviceAttribute { // IntValue is a number. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional int64 int = 2; // BoolValue is a true/false value. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional bool bool = 3; // StringValue is a string. Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional string string = 4; // VersionValue is a semantic version according to semver.org spec 2.0.0. // Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional string version = 5; } diff --git a/staging/src/k8s.io/api/resource/v1beta1/types.go b/staging/src/k8s.io/api/resource/v1beta1/types.go index 727d9e81085..1f4b844a31c 100644 --- a/staging/src/k8s.io/api/resource/v1beta1/types.go +++ b/staging/src/k8s.io/api/resource/v1beta1/types.go @@ -585,26 +585,30 @@ type DeviceAttribute struct { // IntValue is a number. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember IntValue *int64 `json:"int,omitempty" protobuf:"varint,2,opt,name=int"` // BoolValue is a true/false value. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember BoolValue *bool `json:"bool,omitempty" protobuf:"varint,3,opt,name=bool"` // StringValue is a string. Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember StringValue *string `json:"string,omitempty" protobuf:"bytes,4,opt,name=string"` // VersionValue is a semantic version according to semver.org spec 2.0.0. // Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember VersionValue *string `json:"version,omitempty" protobuf:"bytes,5,opt,name=version"` } diff --git a/staging/src/k8s.io/api/resource/v1beta2/generated.proto b/staging/src/k8s.io/api/resource/v1beta2/generated.proto index 713cf9c5399..f80b9e09526 100644 --- a/staging/src/k8s.io/api/resource/v1beta2/generated.proto +++ b/staging/src/k8s.io/api/resource/v1beta2/generated.proto @@ -514,26 +514,30 @@ message DeviceAttribute { // IntValue is a number. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional int64 int = 2; // BoolValue is a true/false value. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional bool bool = 3; // StringValue is a string. Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional string string = 4; // VersionValue is a semantic version according to semver.org spec 2.0.0. // Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember optional string version = 5; } diff --git a/staging/src/k8s.io/api/resource/v1beta2/types.go b/staging/src/k8s.io/api/resource/v1beta2/types.go index 2319979c00c..f23745e81ca 100644 --- a/staging/src/k8s.io/api/resource/v1beta2/types.go +++ b/staging/src/k8s.io/api/resource/v1beta2/types.go @@ -581,26 +581,30 @@ type DeviceAttribute struct { // IntValue is a number. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember IntValue *int64 `json:"int,omitempty" protobuf:"varint,2,opt,name=int"` // BoolValue is a true/false value. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember BoolValue *bool `json:"bool,omitempty" protobuf:"varint,3,opt,name=bool"` // StringValue is a string. Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember StringValue *string `json:"string,omitempty" protobuf:"bytes,4,opt,name=string"` // VersionValue is a semantic version according to semver.org spec 2.0.0. // Must not be longer than 64 characters. // // +optional - // +oneOf=ValueType + // +k8s:optional + // +k8s:unionMember VersionValue *string `json:"version,omitempty" protobuf:"bytes,5,opt,name=version"` }