feat: add enum validation for AllocationConfigSource

This commit introduces enum-based validation for the `AllocationConfigSource` field.

The following changes are included:
- The `AllocationConfigSource` type is now marked with `+k8s:enum`.
- The `Source` field in `DeviceAllocationConfiguration` is marked as required.
- Generated validation files are updated to enforce the enum constraint.
- A declarative validation test is added for the `AllocationConfigSource` field.
This commit is contained in:
Lalit Chauhan 2025-10-15 20:56:39 +00:00
parent 6298c4e6ab
commit 9020a17731
14 changed files with 179 additions and 18 deletions

View file

@ -1602,8 +1602,8 @@ type AllocationConfigSource string
// Valid [DeviceAllocationConfiguration.Source] values.
const (
AllocationConfigSourceClass = "FromClass"
AllocationConfigSourceClaim = "FromClaim"
AllocationConfigSourceClass AllocationConfigSource = "FromClass"
AllocationConfigSourceClaim AllocationConfigSource = "FromClaim"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View file

@ -32,6 +32,7 @@ import (
validate "k8s.io/apimachinery/pkg/api/validate"
runtime "k8s.io/apimachinery/pkg/runtime"
types "k8s.io/apimachinery/pkg/types"
sets "k8s.io/apimachinery/pkg/util/sets"
field "k8s.io/apimachinery/pkg/util/validation/field"
)
@ -123,6 +124,16 @@ func Validate_AllocatedDeviceStatus(ctx context.Context, op operation.Operation,
return errs
}
var symbolsForAllocationConfigSource = sets.New(resourcev1.AllocationConfigSourceClaim, resourcev1.AllocationConfigSourceClass)
// Validate_AllocationConfigSource validates an instance of AllocationConfigSource according
// to declarative validation rules in the API schema.
func Validate_AllocationConfigSource(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.AllocationConfigSource) (errs field.ErrorList) {
errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForAllocationConfigSource, nil)...)
return errs
}
// Validate_AllocationResult validates an instance of AllocationResult according
// to declarative validation rules in the API schema.
func Validate_AllocationResult(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.AllocationResult) (errs field.ErrorList) {
@ -146,7 +157,29 @@ func Validate_AllocationResult(ctx context.Context, op operation.Operation, fldP
// Validate_DeviceAllocationConfiguration validates an instance of DeviceAllocationConfiguration according
// to declarative validation rules in the API schema.
func Validate_DeviceAllocationConfiguration(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.DeviceAllocationConfiguration) (errs field.ErrorList) {
// field resourcev1.DeviceAllocationConfiguration.Source has no validation
// field resourcev1.DeviceAllocationConfiguration.Source
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *resourcev1.AllocationConfigSource) (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.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
// call the type's validation function
errs = append(errs, Validate_AllocationConfigSource(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("source"), &obj.Source, safe.Field(oldObj, func(oldObj *resourcev1.DeviceAllocationConfiguration) *resourcev1.AllocationConfigSource {
return &oldObj.Source
}))...)
// field resourcev1.DeviceAllocationConfiguration.Requests has no validation
// field resourcev1.DeviceAllocationConfiguration.DeviceConfiguration

View file

@ -32,6 +32,7 @@ import (
validate "k8s.io/apimachinery/pkg/api/validate"
runtime "k8s.io/apimachinery/pkg/runtime"
types "k8s.io/apimachinery/pkg/types"
sets "k8s.io/apimachinery/pkg/util/sets"
field "k8s.io/apimachinery/pkg/util/validation/field"
)
@ -123,6 +124,16 @@ func Validate_AllocatedDeviceStatus(ctx context.Context, op operation.Operation,
return errs
}
var symbolsForAllocationConfigSource = sets.New(resourcev1beta1.AllocationConfigSourceClaim, resourcev1beta1.AllocationConfigSourceClass)
// Validate_AllocationConfigSource validates an instance of AllocationConfigSource according
// to declarative validation rules in the API schema.
func Validate_AllocationConfigSource(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.AllocationConfigSource) (errs field.ErrorList) {
errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForAllocationConfigSource, nil)...)
return errs
}
// Validate_AllocationResult validates an instance of AllocationResult according
// to declarative validation rules in the API schema.
func Validate_AllocationResult(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.AllocationResult) (errs field.ErrorList) {
@ -148,7 +159,29 @@ func Validate_AllocationResult(ctx context.Context, op operation.Operation, fldP
// Validate_DeviceAllocationConfiguration validates an instance of DeviceAllocationConfiguration according
// to declarative validation rules in the API schema.
func Validate_DeviceAllocationConfiguration(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceAllocationConfiguration) (errs field.ErrorList) {
// field resourcev1beta1.DeviceAllocationConfiguration.Source has no validation
// field resourcev1beta1.DeviceAllocationConfiguration.Source
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *resourcev1beta1.AllocationConfigSource) (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.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
// call the type's validation function
errs = append(errs, Validate_AllocationConfigSource(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("source"), &obj.Source, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceAllocationConfiguration) *resourcev1beta1.AllocationConfigSource {
return &oldObj.Source
}))...)
// field resourcev1beta1.DeviceAllocationConfiguration.Requests has no validation
// field resourcev1beta1.DeviceAllocationConfiguration.DeviceConfiguration

View file

@ -32,6 +32,7 @@ import (
validate "k8s.io/apimachinery/pkg/api/validate"
runtime "k8s.io/apimachinery/pkg/runtime"
types "k8s.io/apimachinery/pkg/types"
sets "k8s.io/apimachinery/pkg/util/sets"
field "k8s.io/apimachinery/pkg/util/validation/field"
)
@ -123,6 +124,16 @@ func Validate_AllocatedDeviceStatus(ctx context.Context, op operation.Operation,
return errs
}
var symbolsForAllocationConfigSource = sets.New(resourcev1beta2.AllocationConfigSourceClaim, resourcev1beta2.AllocationConfigSourceClass)
// Validate_AllocationConfigSource validates an instance of AllocationConfigSource according
// to declarative validation rules in the API schema.
func Validate_AllocationConfigSource(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.AllocationConfigSource) (errs field.ErrorList) {
errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForAllocationConfigSource, nil)...)
return errs
}
// Validate_AllocationResult validates an instance of AllocationResult according
// to declarative validation rules in the API schema.
func Validate_AllocationResult(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.AllocationResult) (errs field.ErrorList) {
@ -148,7 +159,29 @@ func Validate_AllocationResult(ctx context.Context, op operation.Operation, fldP
// Validate_DeviceAllocationConfiguration validates an instance of DeviceAllocationConfiguration according
// to declarative validation rules in the API schema.
func Validate_DeviceAllocationConfiguration(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceAllocationConfiguration) (errs field.ErrorList) {
// field resourcev1beta2.DeviceAllocationConfiguration.Source has no validation
// field resourcev1beta2.DeviceAllocationConfiguration.Source
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *resourcev1beta2.AllocationConfigSource) (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.RequiredValue(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
// call the type's validation function
errs = append(errs, Validate_AllocationConfigSource(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("source"), &obj.Source, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceAllocationConfiguration) *resourcev1beta2.AllocationConfigSource {
return &oldObj.Source
}))...)
// field resourcev1beta2.DeviceAllocationConfiguration.Requests has no validation
// field resourcev1beta2.DeviceAllocationConfiguration.DeviceConfiguration

View file

@ -513,10 +513,10 @@ func validateAllocationConfigSource(source resource.AllocationConfigSource, fldP
var allErrs field.ErrorList
switch source {
case "":
allErrs = append(allErrs, field.Required(fldPath, ""))
allErrs = append(allErrs, field.Required(fldPath, "").MarkCoveredByDeclarative())
case resource.AllocationConfigSourceClaim, resource.AllocationConfigSourceClass:
default:
allErrs = append(allErrs, field.NotSupported(fldPath, source, []resource.AllocationConfigSource{resource.AllocationConfigSourceClaim, resource.AllocationConfigSourceClass}))
allErrs = append(allErrs, field.NotSupported(fldPath, source, []resource.AllocationConfigSource{resource.AllocationConfigSourceClaim, resource.AllocationConfigSourceClass}).MarkCoveredByDeclarative())
}
return allErrs
}

View file

@ -1198,8 +1198,8 @@ func TestValidateClaimStatusUpdate(t *testing.T) {
},
"configuration": {
wantFailures: field.ErrorList{
field.Required(field.NewPath("status", "allocation", "devices", "config").Index(1).Child("source"), ""),
field.NotSupported(field.NewPath("status", "allocation", "devices", "config").Index(2).Child("source"), resource.AllocationConfigSource("no-such-source"), []resource.AllocationConfigSource{resource.AllocationConfigSourceClaim, resource.AllocationConfigSourceClass}),
field.Required(field.NewPath("status", "allocation", "devices", "config").Index(1).Child("source"), "").MarkCoveredByDeclarative(),
field.NotSupported(field.NewPath("status", "allocation", "devices", "config").Index(2).Child("source"), resource.AllocationConfigSource("no-such-source"), []resource.AllocationConfigSource{resource.AllocationConfigSourceClaim, resource.AllocationConfigSourceClass}).MarkCoveredByDeclarative(),
field.Required(field.NewPath("status", "allocation", "devices", "config").Index(3).Child("opaque"), ""),
field.Required(field.NewPath("status", "allocation", "devices", "config").Index(4).Child("opaque", "driver"), "").MarkCoveredByDeclarative(),
field.Required(field.NewPath("status", "allocation", "devices", "config").Index(4).Child("opaque", "parameters"), ""),

View file

@ -48461,10 +48461,11 @@ func schema_k8sio_api_resource_v1_DeviceAllocationConfiguration(ref common.Refer
Properties: map[string]spec.Schema{
"source": {
SchemaProps: spec.SchemaProps{
Description: "Source records whether the configuration comes from a class and thus is not something that a normal user would have been able to set or from a claim.",
Description: "Source records whether the configuration comes from a class and thus is not something that a normal user would have been able to set or from a claim.\n\n\nPossible enum values:\n - `\"FromClaim\"`\n - `\"FromClass\"`",
Default: "",
Type: []string{"string"},
Format: "",
Enum: []interface{}{"FromClaim", "FromClass"},
},
},
"requests": {
@ -50950,10 +50951,11 @@ func schema_k8sio_api_resource_v1beta1_DeviceAllocationConfiguration(ref common.
Properties: map[string]spec.Schema{
"source": {
SchemaProps: spec.SchemaProps{
Description: "Source records whether the configuration comes from a class and thus is not something that a normal user would have been able to set or from a claim.",
Description: "Source records whether the configuration comes from a class and thus is not something that a normal user would have been able to set or from a claim.\n\n\nPossible enum values:\n - `\"FromClaim\"`\n - `\"FromClass\"`",
Default: "",
Type: []string{"string"},
Format: "",
Enum: []interface{}{"FromClaim", "FromClass"},
},
},
"requests": {
@ -53121,10 +53123,11 @@ func schema_k8sio_api_resource_v1beta2_DeviceAllocationConfiguration(ref common.
Properties: map[string]spec.Schema{
"source": {
SchemaProps: spec.SchemaProps{
Description: "Source records whether the configuration comes from a class and thus is not something that a normal user would have been able to set or from a claim.",
Description: "Source records whether the configuration comes from a class and thus is not something that a normal user would have been able to set or from a claim.\n\n\nPossible enum values:\n - `\"FromClaim\"`\n - `\"FromClass\"`",
Default: "",
Type: []string{"string"},
Format: "",
Enum: []interface{}{"FromClaim", "FromClass"},
},
},
"requests": {

View file

@ -568,6 +568,7 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) {
Subresource: "status",
})
poolPath := field.NewPath("status", "allocation", "devices", "results").Index(0).Child("pool")
configSourcePath := field.NewPath("status", "allocation", "devices", "config").Index(0).Child("source")
testCases := map[string]struct {
old resource.ResourceClaim
update resource.ResourceClaim
@ -796,6 +797,29 @@ func testValidateStatusUpdateForDeclarative(t *testing.T, apiVersion string) {
field.TooMany(field.NewPath("status", "allocation", "devices", "config"), 33, 32).WithOrigin("maxItems"),
},
},
// .Status.Allocation.Devices.Config[%d].Source
"valid status.allocation.devices.config source FromClass": {
old: mkValidResourceClaim(),
update: mkResourceClaimWithStatus(tweakStatusAllocationConfigSource(resource.AllocationConfigSourceClass)),
},
"valid status.allocation.devices.config source FromClaim": {
old: mkValidResourceClaim(),
update: mkResourceClaimWithStatus(tweakStatusAllocationConfigSource(resource.AllocationConfigSourceClaim)),
},
"invalid status.allocation.devices.config source empty": {
old: mkValidResourceClaim(),
update: mkResourceClaimWithStatus(tweakStatusAllocationConfigSource("")),
expectedErrs: field.ErrorList{
field.Required(configSourcePath, "").MarkCoveredByDeclarative(),
},
},
"invalid status.allocation.devices.config source invalid": {
old: mkValidResourceClaim(),
update: mkResourceClaimWithStatus(tweakStatusAllocationConfigSource("invalid")),
expectedErrs: field.ErrorList{
field.NotSupported(configSourcePath, resource.AllocationConfigSource("invalid"), []string{string(resource.AllocationConfigSourceClaim), string(resource.AllocationConfigSourceClass)}).MarkCoveredByDeclarative(),
},
},
}
for k, tc := range testCases {
t.Run(k, func(t *testing.T) {
@ -1069,3 +1093,26 @@ func addStatusAllocationResult(obj resource.ResourceClaim) resource.ResourceClai
}
return obj
}
func tweakStatusAllocationConfigSource(source resource.AllocationConfigSource) func(rc *resource.ResourceClaim) {
return func(rc *resource.ResourceClaim) {
if rc.Status.Allocation == nil {
rc.Status.Allocation = &resource.AllocationResult{}
}
if len(rc.Status.Allocation.Devices.Config) == 0 {
rc.Status.Allocation.Devices.Config = append(rc.Status.Allocation.Devices.Config, resource.DeviceAllocationConfiguration{
Source: resource.AllocationConfigSourceClaim,
Requests: []string{"req-0"},
DeviceConfiguration: resource.DeviceConfiguration{
Opaque: &resource.OpaqueDeviceConfiguration{
Driver: "dra.example.com",
Parameters: runtime.RawExtension{
Raw: []byte(`{"kind": "foo", "apiVersion": "dra.example.com/v1"}`),
},
},
},
})
}
rc.Status.Allocation.Devices.Config[0].Source = source
}
}

View file

@ -464,6 +464,7 @@ message DeviceAllocationConfiguration {
// or from a claim.
//
// +required
// +k8s:required
optional string source = 1;
// Requests lists the names of requests where the configuration applies.

View file

@ -1628,6 +1628,7 @@ type DeviceAllocationConfiguration struct {
// or from a claim.
//
// +required
// +k8s:required
Source AllocationConfigSource `json:"source" protobuf:"bytes,1,name=source"`
// Requests lists the names of requests where the configuration applies.
@ -1644,12 +1645,14 @@ type DeviceAllocationConfiguration struct {
DeviceConfiguration `json:",inline" protobuf:"bytes,3,name=deviceConfiguration"`
}
// +enum
// +k8s:enum
type AllocationConfigSource string
// Valid [DeviceAllocationConfiguration.Source] values.
const (
AllocationConfigSourceClass = "FromClass"
AllocationConfigSourceClaim = "FromClaim"
AllocationConfigSourceClass AllocationConfigSource = "FromClass"
AllocationConfigSourceClaim AllocationConfigSource = "FromClaim"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View file

@ -472,6 +472,7 @@ message DeviceAllocationConfiguration {
// or from a claim.
//
// +required
// +k8s:required
optional string source = 1;
// Requests lists the names of requests where the configuration applies.

View file

@ -1636,6 +1636,7 @@ type DeviceAllocationConfiguration struct {
// or from a claim.
//
// +required
// +k8s:required
Source AllocationConfigSource `json:"source" protobuf:"bytes,1,name=source"`
// Requests lists the names of requests where the configuration applies.
@ -1652,12 +1653,14 @@ type DeviceAllocationConfiguration struct {
DeviceConfiguration `json:",inline" protobuf:"bytes,3,name=deviceConfiguration"`
}
// +enum
// +k8s:enum
type AllocationConfigSource string
// Valid [DeviceAllocationConfiguration.Source] values.
const (
AllocationConfigSourceClass = "FromClass"
AllocationConfigSourceClaim = "FromClaim"
AllocationConfigSourceClass AllocationConfigSource = "FromClass"
AllocationConfigSourceClaim AllocationConfigSource = "FromClaim"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View file

@ -464,6 +464,7 @@ message DeviceAllocationConfiguration {
// or from a claim.
//
// +required
// +k8s:required
optional string source = 1;
// Requests lists the names of requests where the configuration applies.

View file

@ -1628,6 +1628,7 @@ type DeviceAllocationConfiguration struct {
// or from a claim.
//
// +required
// +k8s:required
Source AllocationConfigSource `json:"source" protobuf:"bytes,1,name=source"`
// Requests lists the names of requests where the configuration applies.
@ -1644,12 +1645,14 @@ type DeviceAllocationConfiguration struct {
DeviceConfiguration `json:",inline" protobuf:"bytes,3,name=deviceConfiguration"`
}
// +enum
// +k8s:enum
type AllocationConfigSource string
// Valid [DeviceAllocationConfiguration.Source] values.
const (
AllocationConfigSourceClass = "FromClass"
AllocationConfigSourceClaim = "FromClaim"
AllocationConfigSourceClass AllocationConfigSource = "FromClass"
AllocationConfigSourceClaim AllocationConfigSource = "FromClaim"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object