1
16
17 package customresource
18
19 import (
20 "context"
21 "fmt"
22 "math"
23 "strings"
24
25 corev1 "k8s.io/api/core/v1"
26 "k8s.io/apimachinery/pkg/api/meta"
27 "k8s.io/apimachinery/pkg/api/validation"
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30 "k8s.io/apimachinery/pkg/runtime/schema"
31 "k8s.io/apimachinery/pkg/util/sets"
32 apimachineryvalidation "k8s.io/apimachinery/pkg/util/validation"
33 "k8s.io/apimachinery/pkg/util/validation/field"
34
35 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
36 apiextensionsvalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
37 )
38
39 type customResourceValidator struct {
40 namespaceScoped bool
41 kind schema.GroupVersionKind
42 schemaValidator apiextensionsvalidation.SchemaValidator
43 statusSchemaValidator apiextensionsvalidation.SchemaValidator
44 }
45
46 func (a customResourceValidator) Validate(ctx context.Context, obj *unstructured.Unstructured, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
47 if errs := a.ValidateTypeMeta(ctx, obj); len(errs) > 0 {
48 return errs
49 }
50
51 var allErrs field.ErrorList
52
53 allErrs = append(allErrs, validation.ValidateObjectMetaAccessor(obj, a.namespaceScoped, validation.NameIsDNSSubdomain, field.NewPath("metadata"))...)
54 allErrs = append(allErrs, apiextensionsvalidation.ValidateCustomResource(nil, obj.UnstructuredContent(), a.schemaValidator)...)
55 allErrs = append(allErrs, a.ValidateScaleSpec(ctx, obj, scale)...)
56 allErrs = append(allErrs, a.ValidateScaleStatus(ctx, obj, scale)...)
57
58 return allErrs
59 }
60
61 func (a customResourceValidator) ValidateUpdate(ctx context.Context, obj, old *unstructured.Unstructured, scale *apiextensions.CustomResourceSubresourceScale, options ...apiextensionsvalidation.ValidationOption) field.ErrorList {
62 if errs := a.ValidateTypeMeta(ctx, obj); len(errs) > 0 {
63 return errs
64 }
65
66 var allErrs field.ErrorList
67
68 allErrs = append(allErrs, validation.ValidateObjectMetaAccessorUpdate(obj, old, field.NewPath("metadata"))...)
69 allErrs = append(allErrs, apiextensionsvalidation.ValidateCustomResourceUpdate(nil, obj.UnstructuredContent(), old.UnstructuredContent(), a.schemaValidator, options...)...)
70 allErrs = append(allErrs, a.ValidateScaleSpec(ctx, obj, scale)...)
71 allErrs = append(allErrs, a.ValidateScaleStatus(ctx, obj, scale)...)
72
73 return allErrs
74 }
75
76 var standardFinalizers = sets.NewString(
77 metav1.FinalizerOrphanDependents,
78 metav1.FinalizerDeleteDependents,
79 string(corev1.FinalizerKubernetes),
80 )
81
82 func validateKubeFinalizerName(stringValue string, fldPath *field.Path) []string {
83 var allWarnings []string
84 for _, msg := range apimachineryvalidation.IsQualifiedName(stringValue) {
85 allWarnings = append(allWarnings, fmt.Sprintf("%s: %q: %s", fldPath.String(), stringValue, msg))
86 }
87 if len(strings.Split(stringValue, "/")) == 1 {
88 if !standardFinalizers.Has(stringValue) {
89 allWarnings = append(allWarnings, fmt.Sprintf("%s: %q: prefer a domain-qualified finalizer name to avoid accidental conflicts with other finalizer writers", fldPath.String(), stringValue))
90 }
91 }
92 return allWarnings
93 }
94
95 func (a customResourceValidator) ValidateStatusUpdate(ctx context.Context, obj, old *unstructured.Unstructured, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
96 if errs := a.ValidateTypeMeta(ctx, obj); len(errs) > 0 {
97 return errs
98 }
99
100 var allErrs field.ErrorList
101
102 allErrs = append(allErrs, validation.ValidateObjectMetaAccessorUpdate(obj, old, field.NewPath("metadata"))...)
103 if status, hasStatus := obj.UnstructuredContent()["status"]; hasStatus {
104 allErrs = append(allErrs, apiextensionsvalidation.ValidateCustomResourceUpdate(nil, status, old.UnstructuredContent()["status"], a.statusSchemaValidator)...)
105 }
106 allErrs = append(allErrs, a.ValidateScaleStatus(ctx, obj, scale)...)
107
108 return allErrs
109 }
110
111 func (a customResourceValidator) ValidateTypeMeta(ctx context.Context, obj *unstructured.Unstructured) field.ErrorList {
112 typeAccessor, err := meta.TypeAccessor(obj)
113 if err != nil {
114 return field.ErrorList{field.Invalid(field.NewPath("kind"), nil, err.Error())}
115 }
116
117 var allErrs field.ErrorList
118 if typeAccessor.GetKind() != a.kind.Kind {
119 allErrs = append(allErrs, field.Invalid(field.NewPath("kind"), typeAccessor.GetKind(), fmt.Sprintf("must be %v", a.kind.Kind)))
120 }
121 if typeAccessor.GetAPIVersion() != a.kind.Group+"/"+a.kind.Version {
122 allErrs = append(allErrs, field.Invalid(field.NewPath("apiVersion"), typeAccessor.GetAPIVersion(), fmt.Sprintf("must be %v", a.kind.Group+"/"+a.kind.Version)))
123 }
124 return allErrs
125 }
126
127 func (a customResourceValidator) ValidateScaleSpec(ctx context.Context, obj *unstructured.Unstructured, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
128 if scale == nil {
129 return nil
130 }
131
132 var allErrs field.ErrorList
133
134
135 specReplicasPath := strings.TrimPrefix(scale.SpecReplicasPath, ".")
136 specReplicas, _, err := unstructured.NestedInt64(obj.UnstructuredContent(), strings.Split(specReplicasPath, ".")...)
137 if err != nil {
138 allErrs = append(allErrs, field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, err.Error()))
139 } else if specReplicas < 0 {
140 allErrs = append(allErrs, field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, "should be a non-negative integer"))
141 } else if specReplicas > math.MaxInt32 {
142 allErrs = append(allErrs, field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, fmt.Sprintf("should be less than or equal to %v", math.MaxInt32)))
143 }
144
145 return allErrs
146 }
147
148 func (a customResourceValidator) ValidateScaleStatus(ctx context.Context, obj *unstructured.Unstructured, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
149 if scale == nil {
150 return nil
151 }
152
153 var allErrs field.ErrorList
154
155
156 statusReplicasPath := strings.TrimPrefix(scale.StatusReplicasPath, ".")
157 statusReplicas, _, err := unstructured.NestedInt64(obj.UnstructuredContent(), strings.Split(statusReplicasPath, ".")...)
158 if err != nil {
159 allErrs = append(allErrs, field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, err.Error()))
160 } else if statusReplicas < 0 {
161 allErrs = append(allErrs, field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, "should be a non-negative integer"))
162 } else if statusReplicas > math.MaxInt32 {
163 allErrs = append(allErrs, field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, fmt.Sprintf("should be less than or equal to %v", math.MaxInt32)))
164 }
165
166
167 if scale.LabelSelectorPath != nil {
168 labelSelectorPath := strings.TrimPrefix(*scale.LabelSelectorPath, ".")
169 labelSelector, _, err := unstructured.NestedString(obj.UnstructuredContent(), strings.Split(labelSelectorPath, ".")...)
170 if err != nil {
171 allErrs = append(allErrs, field.Invalid(field.NewPath(*scale.LabelSelectorPath), labelSelector, err.Error()))
172 }
173 }
174
175 return allErrs
176 }
177
View as plain text