feat(validation-gen): Add declarative validation support for ClusterRole(v1,v1alpha1,v1beta1)

This commit is contained in:
pranshul gupta 2025-09-26 10:45:39 +05:30
parent 3f8a5219e2
commit 4e660cc05b
9 changed files with 640 additions and 3 deletions

View file

@ -22,7 +22,16 @@ limitations under the License.
package v1
import (
context "context"
fmt "fmt"
rbacv1 "k8s.io/api/rbac/v1"
equality "k8s.io/apimachinery/pkg/api/equality"
operation "k8s.io/apimachinery/pkg/api/operation"
safe "k8s.io/apimachinery/pkg/api/safe"
validate "k8s.io/apimachinery/pkg/api/validate"
runtime "k8s.io/apimachinery/pkg/runtime"
field "k8s.io/apimachinery/pkg/util/validation/field"
)
func init() { localSchemeBuilder.Register(RegisterValidations) }
@ -30,5 +39,151 @@ func init() { localSchemeBuilder.Register(RegisterValidations) }
// RegisterValidations adds validation functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterValidations(scheme *runtime.Scheme) error {
// type ClusterRole
scheme.AddValidationFunc((*rbacv1.ClusterRole)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_ClusterRole(ctx, op, nil /* fldPath */, obj.(*rbacv1.ClusterRole), safe.Cast[*rbacv1.ClusterRole](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type ClusterRoleList
scheme.AddValidationFunc((*rbacv1.ClusterRoleList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_ClusterRoleList(ctx, op, nil /* fldPath */, obj.(*rbacv1.ClusterRoleList), safe.Cast[*rbacv1.ClusterRoleList](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type Role
scheme.AddValidationFunc((*rbacv1.Role)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_Role(ctx, op, nil /* fldPath */, obj.(*rbacv1.Role), safe.Cast[*rbacv1.Role](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type RoleList
scheme.AddValidationFunc((*rbacv1.RoleList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_RoleList(ctx, op, nil /* fldPath */, obj.(*rbacv1.RoleList), safe.Cast[*rbacv1.RoleList](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
return nil
}
// Validate_ClusterRole validates an instance of ClusterRole according
// to declarative validation rules in the API schema.
func Validate_ClusterRole(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1.ClusterRole) (errs field.ErrorList) {
// field rbacv1.ClusterRole.TypeMeta has no validation
// field rbacv1.ClusterRole.ObjectMeta has no validation
// field rbacv1.ClusterRole.Rules
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []rbacv1.PolicyRule) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_PolicyRule)...)
return
}(fldPath.Child("rules"), obj.Rules, safe.Field(oldObj, func(oldObj *rbacv1.ClusterRole) []rbacv1.PolicyRule { return oldObj.Rules }))...)
// field rbacv1.ClusterRole.AggregationRule has no validation
return errs
}
// Validate_ClusterRoleList validates an instance of ClusterRoleList according
// to declarative validation rules in the API schema.
func Validate_ClusterRoleList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1.ClusterRoleList) (errs field.ErrorList) {
// field rbacv1.ClusterRoleList.TypeMeta has no validation
// field rbacv1.ClusterRoleList.ListMeta has no validation
// field rbacv1.ClusterRoleList.Items
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []rbacv1.ClusterRole) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_ClusterRole)...)
return
}(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *rbacv1.ClusterRoleList) []rbacv1.ClusterRole { return oldObj.Items }))...)
return errs
}
// Validate_PolicyRule validates an instance of PolicyRule according
// to declarative validation rules in the API schema.
func Validate_PolicyRule(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1.PolicyRule) (errs field.ErrorList) {
// field rbacv1.PolicyRule.Verbs
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []string) (errs field.ErrorList) {
// don't revalidate unchanged data
if 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("verbs"), obj.Verbs, safe.Field(oldObj, func(oldObj *rbacv1.PolicyRule) []string { return oldObj.Verbs }))...)
// field rbacv1.PolicyRule.APIGroups has no validation
// field rbacv1.PolicyRule.Resources has no validation
// field rbacv1.PolicyRule.ResourceNames has no validation
// field rbacv1.PolicyRule.NonResourceURLs has no validation
return errs
}
// Validate_Role validates an instance of Role according
// to declarative validation rules in the API schema.
func Validate_Role(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1.Role) (errs field.ErrorList) {
// field rbacv1.Role.TypeMeta has no validation
// field rbacv1.Role.ObjectMeta has no validation
// field rbacv1.Role.Rules
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []rbacv1.PolicyRule) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_PolicyRule)...)
return
}(fldPath.Child("rules"), obj.Rules, safe.Field(oldObj, func(oldObj *rbacv1.Role) []rbacv1.PolicyRule { return oldObj.Rules }))...)
return errs
}
// Validate_RoleList validates an instance of RoleList according
// to declarative validation rules in the API schema.
func Validate_RoleList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1.RoleList) (errs field.ErrorList) {
// field rbacv1.RoleList.TypeMeta has no validation
// field rbacv1.RoleList.ListMeta has no validation
// field rbacv1.RoleList.Items
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []rbacv1.Role) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_Role)...)
return
}(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *rbacv1.RoleList) []rbacv1.Role { return oldObj.Items }))...)
return errs
}

View file

@ -22,7 +22,16 @@ limitations under the License.
package v1alpha1
import (
context "context"
fmt "fmt"
rbacv1alpha1 "k8s.io/api/rbac/v1alpha1"
equality "k8s.io/apimachinery/pkg/api/equality"
operation "k8s.io/apimachinery/pkg/api/operation"
safe "k8s.io/apimachinery/pkg/api/safe"
validate "k8s.io/apimachinery/pkg/api/validate"
runtime "k8s.io/apimachinery/pkg/runtime"
field "k8s.io/apimachinery/pkg/util/validation/field"
)
func init() { localSchemeBuilder.Register(RegisterValidations) }
@ -30,5 +39,151 @@ func init() { localSchemeBuilder.Register(RegisterValidations) }
// RegisterValidations adds validation functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterValidations(scheme *runtime.Scheme) error {
// type ClusterRole
scheme.AddValidationFunc((*rbacv1alpha1.ClusterRole)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_ClusterRole(ctx, op, nil /* fldPath */, obj.(*rbacv1alpha1.ClusterRole), safe.Cast[*rbacv1alpha1.ClusterRole](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type ClusterRoleList
scheme.AddValidationFunc((*rbacv1alpha1.ClusterRoleList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_ClusterRoleList(ctx, op, nil /* fldPath */, obj.(*rbacv1alpha1.ClusterRoleList), safe.Cast[*rbacv1alpha1.ClusterRoleList](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type Role
scheme.AddValidationFunc((*rbacv1alpha1.Role)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_Role(ctx, op, nil /* fldPath */, obj.(*rbacv1alpha1.Role), safe.Cast[*rbacv1alpha1.Role](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type RoleList
scheme.AddValidationFunc((*rbacv1alpha1.RoleList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_RoleList(ctx, op, nil /* fldPath */, obj.(*rbacv1alpha1.RoleList), safe.Cast[*rbacv1alpha1.RoleList](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
return nil
}
// Validate_ClusterRole validates an instance of ClusterRole according
// to declarative validation rules in the API schema.
func Validate_ClusterRole(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1alpha1.ClusterRole) (errs field.ErrorList) {
// field rbacv1alpha1.ClusterRole.TypeMeta has no validation
// field rbacv1alpha1.ClusterRole.ObjectMeta has no validation
// field rbacv1alpha1.ClusterRole.Rules
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []rbacv1alpha1.PolicyRule) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_PolicyRule)...)
return
}(fldPath.Child("rules"), obj.Rules, safe.Field(oldObj, func(oldObj *rbacv1alpha1.ClusterRole) []rbacv1alpha1.PolicyRule { return oldObj.Rules }))...)
// field rbacv1alpha1.ClusterRole.AggregationRule has no validation
return errs
}
// Validate_ClusterRoleList validates an instance of ClusterRoleList according
// to declarative validation rules in the API schema.
func Validate_ClusterRoleList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1alpha1.ClusterRoleList) (errs field.ErrorList) {
// field rbacv1alpha1.ClusterRoleList.TypeMeta has no validation
// field rbacv1alpha1.ClusterRoleList.ListMeta has no validation
// field rbacv1alpha1.ClusterRoleList.Items
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []rbacv1alpha1.ClusterRole) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_ClusterRole)...)
return
}(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *rbacv1alpha1.ClusterRoleList) []rbacv1alpha1.ClusterRole { return oldObj.Items }))...)
return errs
}
// Validate_PolicyRule validates an instance of PolicyRule according
// to declarative validation rules in the API schema.
func Validate_PolicyRule(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1alpha1.PolicyRule) (errs field.ErrorList) {
// field rbacv1alpha1.PolicyRule.Verbs
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []string) (errs field.ErrorList) {
// don't revalidate unchanged data
if 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("verbs"), obj.Verbs, safe.Field(oldObj, func(oldObj *rbacv1alpha1.PolicyRule) []string { return oldObj.Verbs }))...)
// field rbacv1alpha1.PolicyRule.APIGroups has no validation
// field rbacv1alpha1.PolicyRule.Resources has no validation
// field rbacv1alpha1.PolicyRule.ResourceNames has no validation
// field rbacv1alpha1.PolicyRule.NonResourceURLs has no validation
return errs
}
// Validate_Role validates an instance of Role according
// to declarative validation rules in the API schema.
func Validate_Role(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1alpha1.Role) (errs field.ErrorList) {
// field rbacv1alpha1.Role.TypeMeta has no validation
// field rbacv1alpha1.Role.ObjectMeta has no validation
// field rbacv1alpha1.Role.Rules
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []rbacv1alpha1.PolicyRule) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_PolicyRule)...)
return
}(fldPath.Child("rules"), obj.Rules, safe.Field(oldObj, func(oldObj *rbacv1alpha1.Role) []rbacv1alpha1.PolicyRule { return oldObj.Rules }))...)
return errs
}
// Validate_RoleList validates an instance of RoleList according
// to declarative validation rules in the API schema.
func Validate_RoleList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1alpha1.RoleList) (errs field.ErrorList) {
// field rbacv1alpha1.RoleList.TypeMeta has no validation
// field rbacv1alpha1.RoleList.ListMeta has no validation
// field rbacv1alpha1.RoleList.Items
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []rbacv1alpha1.Role) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_Role)...)
return
}(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *rbacv1alpha1.RoleList) []rbacv1alpha1.Role { return oldObj.Items }))...)
return errs
}

View file

@ -22,7 +22,16 @@ limitations under the License.
package v1beta1
import (
context "context"
fmt "fmt"
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
equality "k8s.io/apimachinery/pkg/api/equality"
operation "k8s.io/apimachinery/pkg/api/operation"
safe "k8s.io/apimachinery/pkg/api/safe"
validate "k8s.io/apimachinery/pkg/api/validate"
runtime "k8s.io/apimachinery/pkg/runtime"
field "k8s.io/apimachinery/pkg/util/validation/field"
)
func init() { localSchemeBuilder.Register(RegisterValidations) }
@ -30,5 +39,151 @@ func init() { localSchemeBuilder.Register(RegisterValidations) }
// RegisterValidations adds validation functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterValidations(scheme *runtime.Scheme) error {
// type ClusterRole
scheme.AddValidationFunc((*rbacv1beta1.ClusterRole)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_ClusterRole(ctx, op, nil /* fldPath */, obj.(*rbacv1beta1.ClusterRole), safe.Cast[*rbacv1beta1.ClusterRole](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type ClusterRoleList
scheme.AddValidationFunc((*rbacv1beta1.ClusterRoleList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_ClusterRoleList(ctx, op, nil /* fldPath */, obj.(*rbacv1beta1.ClusterRoleList), safe.Cast[*rbacv1beta1.ClusterRoleList](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type Role
scheme.AddValidationFunc((*rbacv1beta1.Role)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_Role(ctx, op, nil /* fldPath */, obj.(*rbacv1beta1.Role), safe.Cast[*rbacv1beta1.Role](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type RoleList
scheme.AddValidationFunc((*rbacv1beta1.RoleList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_RoleList(ctx, op, nil /* fldPath */, obj.(*rbacv1beta1.RoleList), safe.Cast[*rbacv1beta1.RoleList](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
return nil
}
// Validate_ClusterRole validates an instance of ClusterRole according
// to declarative validation rules in the API schema.
func Validate_ClusterRole(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1beta1.ClusterRole) (errs field.ErrorList) {
// field rbacv1beta1.ClusterRole.TypeMeta has no validation
// field rbacv1beta1.ClusterRole.ObjectMeta has no validation
// field rbacv1beta1.ClusterRole.Rules
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []rbacv1beta1.PolicyRule) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_PolicyRule)...)
return
}(fldPath.Child("rules"), obj.Rules, safe.Field(oldObj, func(oldObj *rbacv1beta1.ClusterRole) []rbacv1beta1.PolicyRule { return oldObj.Rules }))...)
// field rbacv1beta1.ClusterRole.AggregationRule has no validation
return errs
}
// Validate_ClusterRoleList validates an instance of ClusterRoleList according
// to declarative validation rules in the API schema.
func Validate_ClusterRoleList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1beta1.ClusterRoleList) (errs field.ErrorList) {
// field rbacv1beta1.ClusterRoleList.TypeMeta has no validation
// field rbacv1beta1.ClusterRoleList.ListMeta has no validation
// field rbacv1beta1.ClusterRoleList.Items
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []rbacv1beta1.ClusterRole) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_ClusterRole)...)
return
}(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *rbacv1beta1.ClusterRoleList) []rbacv1beta1.ClusterRole { return oldObj.Items }))...)
return errs
}
// Validate_PolicyRule validates an instance of PolicyRule according
// to declarative validation rules in the API schema.
func Validate_PolicyRule(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1beta1.PolicyRule) (errs field.ErrorList) {
// field rbacv1beta1.PolicyRule.Verbs
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []string) (errs field.ErrorList) {
// don't revalidate unchanged data
if 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("verbs"), obj.Verbs, safe.Field(oldObj, func(oldObj *rbacv1beta1.PolicyRule) []string { return oldObj.Verbs }))...)
// field rbacv1beta1.PolicyRule.APIGroups has no validation
// field rbacv1beta1.PolicyRule.Resources has no validation
// field rbacv1beta1.PolicyRule.ResourceNames has no validation
// field rbacv1beta1.PolicyRule.NonResourceURLs has no validation
return errs
}
// Validate_Role validates an instance of Role according
// to declarative validation rules in the API schema.
func Validate_Role(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1beta1.Role) (errs field.ErrorList) {
// field rbacv1beta1.Role.TypeMeta has no validation
// field rbacv1beta1.Role.ObjectMeta has no validation
// field rbacv1beta1.Role.Rules
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []rbacv1beta1.PolicyRule) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_PolicyRule)...)
return
}(fldPath.Child("rules"), obj.Rules, safe.Field(oldObj, func(oldObj *rbacv1beta1.Role) []rbacv1beta1.PolicyRule { return oldObj.Rules }))...)
return errs
}
// Validate_RoleList validates an instance of RoleList according
// to declarative validation rules in the API schema.
func Validate_RoleList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *rbacv1beta1.RoleList) (errs field.ErrorList) {
// field rbacv1beta1.RoleList.TypeMeta has no validation
// field rbacv1beta1.RoleList.ListMeta has no validation
// field rbacv1beta1.RoleList.Items
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []rbacv1beta1.Role) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_Role)...)
return
}(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *rbacv1beta1.RoleList) []rbacv1beta1.Role { return oldObj.Items }))...)
return errs
}

View file

@ -103,7 +103,7 @@ func ValidateClusterRoleUpdate(role *rbac.ClusterRole, oldRole *rbac.ClusterRole
func ValidatePolicyRule(rule rbac.PolicyRule, isNamespaced bool, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(rule.Verbs) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("verbs"), "verbs must contain at least one value"))
allErrs = append(allErrs, field.Required(fldPath.Child("verbs"), "verbs must contain at least one value").MarkCoveredByDeclarative())
}
if len(rule.NonResourceURLs) > 0 {

View file

@ -0,0 +1,150 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package clusterrole
import (
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
apitesting "k8s.io/kubernetes/pkg/api/testing"
rbac "k8s.io/kubernetes/pkg/apis/rbac"
)
var apiVersions = []string{"v1", "v1alpha1", "v1beta1"}
func TestDeclarativeValidateForDeclarative(t *testing.T) {
for _, apiVersion := range apiVersions {
testDeclarativeValidateForDeclarative(t, apiVersion)
}
}
func testDeclarativeValidateForDeclarative(t *testing.T, apiVersion string) {
ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{
APIGroup: "rbac.authorization.k8s.io",
APIVersion: apiVersion,
})
testCases := map[string]struct {
input rbac.ClusterRole
expectedErrs field.ErrorList
}{
"missing verbs": {
input: rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: "test-role"},
Rules: []rbac.PolicyRule{
{
APIGroups: []string{""},
Resources: []string{"pods"},
},
},
},
expectedErrs: field.ErrorList{
field.Required(field.NewPath("rules").Index(0).Child("verbs"), "must have at least one verb"),
},
},
"valid rule": {
input: rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: "valid-role"},
Rules: []rbac.PolicyRule{
{
APIGroups: []string{""},
Resources: []string{"pods"},
Verbs: []string{"get", "list"},
},
},
},
expectedErrs: field.ErrorList{},
},
}
for k, tc := range testCases {
t.Run(k, func(t *testing.T) {
apitesting.VerifyValidationEquivalence(t, ctx, &tc.input, Strategy.Validate, tc.expectedErrs)
})
}
}
func TestValidateUpdateForDeclarative(t *testing.T) {
for _, apiVersion := range apiVersions {
testValidateUpdateForDeclarative(t, apiVersion)
}
}
func testValidateUpdateForDeclarative(t *testing.T, apiVersion string) {
ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{
APIGroup: "rbac.authorization.k8s.io",
APIVersion: apiVersion,
})
testCases := map[string]struct {
old rbac.ClusterRole
update rbac.ClusterRole
expectedErrs field.ErrorList
}{
"update missing verbs": {
old: rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: "role"},
Rules: []rbac.PolicyRule{
{
APIGroups: []string{""},
Resources: []string{"pods"},
Verbs: []string{"get"},
},
},
},
update: rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: "role", ResourceVersion: "1"},
Rules: []rbac.PolicyRule{
{
APIGroups: []string{""},
Resources: []string{"pods"},
},
},
},
expectedErrs: field.ErrorList{
field.Required(field.NewPath("rules").Index(0).Child("verbs"), "must have at least one verb"),
},
},
"valid update": {
old: rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: "role"},
Rules: []rbac.PolicyRule{
{
APIGroups: []string{""},
Resources: []string{"pods"},
Verbs: []string{"get"},
},
},
},
update: rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: "role", ResourceVersion: "1"},
Rules: []rbac.PolicyRule{
{
APIGroups: []string{""},
Resources: []string{"pods"},
Verbs: []string{"get", "list"},
},
},
},
expectedErrs: field.ErrorList{},
},
}
for k, tc := range testCases {
t.Run(k, func(t *testing.T) {
apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.update, &tc.old, Strategy.ValidateUpdate, tc.expectedErrs)
})
}
}

View file

@ -19,6 +19,7 @@ package clusterrole
import (
"context"
"k8s.io/apimachinery/pkg/api/operation"
metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
@ -75,7 +76,16 @@ func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorLis
opts := validation.ClusterRoleValidationOptions{
AllowInvalidLabelValueInSelector: false,
}
return validation.ValidateClusterRole(clusterRole, opts)
allErrs := validation.ValidateClusterRole(clusterRole, opts)
return rest.ValidateDeclarativelyWithMigrationChecks(
ctx,
legacyscheme.Scheme,
clusterRole,
nil,
allErrs,
operation.Create,
)
}
// WarningsOnCreate returns warnings for the creation of the given object.
@ -93,7 +103,16 @@ func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) fie
opts := validation.ClusterRoleValidationOptions{
AllowInvalidLabelValueInSelector: hasInvalidLabelValueInLabelSelector(oldObj),
}
return validation.ValidateClusterRoleUpdate(newObj, old.(*rbac.ClusterRole), opts)
errs := validation.ValidateClusterRoleUpdate(newObj, oldObj, opts)
return rest.ValidateDeclarativelyWithMigrationChecks(
ctx,
legacyscheme.Scheme,
newObj,
oldObj,
errs,
operation.Update,
)
}
// WarningsOnUpdate returns warnings for the given update.

View file

@ -49,6 +49,7 @@ const (
type PolicyRule struct {
// Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs.
// +listType=atomic
// +k8s:required
Verbs []string `json:"verbs" protobuf:"bytes,1,rep,name=verbs"`
// APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of

View file

@ -49,6 +49,7 @@ const (
type PolicyRule struct {
// Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs.
// +listType=atomic
// +k8s:required
Verbs []string `json:"verbs" protobuf:"bytes,1,rep,name=verbs"`
// APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of

View file

@ -49,6 +49,7 @@ const (
type PolicyRule struct {
// Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs.
// +listType=atomic
// +k8s:required
Verbs []string `json:"verbs" protobuf:"bytes,1,rep,name=verbs"`
// APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of