1/*
2Copyright 2015 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package validation
18
19import (
20	"fmt"
21	"regexp"
22	"unicode"
23
24	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25	"k8s.io/apimachinery/pkg/types"
26	"k8s.io/apimachinery/pkg/util/sets"
27	"k8s.io/apimachinery/pkg/util/validation"
28	"k8s.io/apimachinery/pkg/util/validation/field"
29)
30
31func ValidateLabelSelector(ps *metav1.LabelSelector, fldPath *field.Path) field.ErrorList {
32	allErrs := field.ErrorList{}
33	if ps == nil {
34		return allErrs
35	}
36	allErrs = append(allErrs, ValidateLabels(ps.MatchLabels, fldPath.Child("matchLabels"))...)
37	for i, expr := range ps.MatchExpressions {
38		allErrs = append(allErrs, ValidateLabelSelectorRequirement(expr, fldPath.Child("matchExpressions").Index(i))...)
39	}
40	return allErrs
41}
42
43func ValidateLabelSelectorRequirement(sr metav1.LabelSelectorRequirement, fldPath *field.Path) field.ErrorList {
44	allErrs := field.ErrorList{}
45	switch sr.Operator {
46	case metav1.LabelSelectorOpIn, metav1.LabelSelectorOpNotIn:
47		if len(sr.Values) == 0 {
48			allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified when `operator` is 'In' or 'NotIn'"))
49		}
50	case metav1.LabelSelectorOpExists, metav1.LabelSelectorOpDoesNotExist:
51		if len(sr.Values) > 0 {
52			allErrs = append(allErrs, field.Forbidden(fldPath.Child("values"), "may not be specified when `operator` is 'Exists' or 'DoesNotExist'"))
53		}
54	default:
55		allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), sr.Operator, "not a valid selector operator"))
56	}
57	allErrs = append(allErrs, ValidateLabelName(sr.Key, fldPath.Child("key"))...)
58	return allErrs
59}
60
61// ValidateLabelName validates that the label name is correctly defined.
62func ValidateLabelName(labelName string, fldPath *field.Path) field.ErrorList {
63	allErrs := field.ErrorList{}
64	for _, msg := range validation.IsQualifiedName(labelName) {
65		allErrs = append(allErrs, field.Invalid(fldPath, labelName, msg))
66	}
67	return allErrs
68}
69
70// ValidateLabels validates that a set of labels are correctly defined.
71func ValidateLabels(labels map[string]string, fldPath *field.Path) field.ErrorList {
72	allErrs := field.ErrorList{}
73	for k, v := range labels {
74		allErrs = append(allErrs, ValidateLabelName(k, fldPath)...)
75		for _, msg := range validation.IsValidLabelValue(v) {
76			allErrs = append(allErrs, field.Invalid(fldPath, v, msg))
77		}
78	}
79	return allErrs
80}
81
82func ValidateDeleteOptions(options *metav1.DeleteOptions) field.ErrorList {
83	allErrs := field.ErrorList{}
84	if options.OrphanDependents != nil && options.PropagationPolicy != nil {
85		allErrs = append(allErrs, field.Invalid(field.NewPath("propagationPolicy"), options.PropagationPolicy, "orphanDependents and deletionPropagation cannot be both set"))
86	}
87	if options.PropagationPolicy != nil &&
88		*options.PropagationPolicy != metav1.DeletePropagationForeground &&
89		*options.PropagationPolicy != metav1.DeletePropagationBackground &&
90		*options.PropagationPolicy != metav1.DeletePropagationOrphan {
91		allErrs = append(allErrs, field.NotSupported(field.NewPath("propagationPolicy"), options.PropagationPolicy, []string{string(metav1.DeletePropagationForeground), string(metav1.DeletePropagationBackground), string(metav1.DeletePropagationOrphan), "nil"}))
92	}
93	allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...)
94	return allErrs
95}
96
97func ValidateCreateOptions(options *metav1.CreateOptions) field.ErrorList {
98	return append(
99		ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager")),
100		ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...,
101	)
102}
103
104func ValidateUpdateOptions(options *metav1.UpdateOptions) field.ErrorList {
105	return append(
106		ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager")),
107		ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...,
108	)
109}
110
111func ValidatePatchOptions(options *metav1.PatchOptions, patchType types.PatchType) field.ErrorList {
112	allErrs := field.ErrorList{}
113	if patchType != types.ApplyPatchType {
114		if options.Force != nil {
115			allErrs = append(allErrs, field.Forbidden(field.NewPath("force"), "may not be specified for non-apply patch"))
116		}
117	} else {
118		if options.FieldManager == "" {
119			// This field is defaulted to "kubectl" by kubectl, but HAS TO be explicitly set by controllers.
120			allErrs = append(allErrs, field.Required(field.NewPath("fieldManager"), "is required for apply patch"))
121		}
122	}
123	allErrs = append(allErrs, ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager"))...)
124	allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...)
125	return allErrs
126}
127
128var FieldManagerMaxLength = 128
129
130// ValidateFieldManager valides that the fieldManager is the proper length and
131// only has printable characters.
132func ValidateFieldManager(fieldManager string, fldPath *field.Path) field.ErrorList {
133	allErrs := field.ErrorList{}
134	// the field can not be set as a `*string`, so a empty string ("") is
135	// considered as not set and is defaulted by the rest of the process
136	// (unless apply is used, in which case it is required).
137	if len(fieldManager) > FieldManagerMaxLength {
138		allErrs = append(allErrs, field.TooLong(fldPath, fieldManager, FieldManagerMaxLength))
139	}
140	// Verify that all characters are printable.
141	for i, r := range fieldManager {
142		if !unicode.IsPrint(r) {
143			allErrs = append(allErrs, field.Invalid(fldPath, fieldManager, fmt.Sprintf("invalid character %#U (at position %d)", r, i)))
144		}
145	}
146
147	return allErrs
148}
149
150var allowedDryRunValues = sets.NewString(metav1.DryRunAll)
151
152// ValidateDryRun validates that a dryRun query param only contains allowed values.
153func ValidateDryRun(fldPath *field.Path, dryRun []string) field.ErrorList {
154	allErrs := field.ErrorList{}
155	if !allowedDryRunValues.HasAll(dryRun...) {
156		allErrs = append(allErrs, field.NotSupported(fldPath, dryRun, allowedDryRunValues.List()))
157	}
158	return allErrs
159}
160
161const UninitializedStatusUpdateErrorMsg string = `must not update status when the object is uninitialized`
162
163// ValidateTableOptions returns any invalid flags on TableOptions.
164func ValidateTableOptions(opts *metav1.TableOptions) field.ErrorList {
165	var allErrs field.ErrorList
166	switch opts.IncludeObject {
167	case metav1.IncludeMetadata, metav1.IncludeNone, metav1.IncludeObject, "":
168	default:
169		allErrs = append(allErrs, field.Invalid(field.NewPath("includeObject"), opts.IncludeObject, "must be 'Metadata', 'Object', 'None', or empty"))
170	}
171	return allErrs
172}
173
174func ValidateManagedFields(fieldsList []metav1.ManagedFieldsEntry, fldPath *field.Path) field.ErrorList {
175	var allErrs field.ErrorList
176	for i, fields := range fieldsList {
177		fldPath := fldPath.Index(i)
178		switch fields.Operation {
179		case metav1.ManagedFieldsOperationApply, metav1.ManagedFieldsOperationUpdate:
180		default:
181			allErrs = append(allErrs, field.Invalid(fldPath.Child("operation"), fields.Operation, "must be `Apply` or `Update`"))
182		}
183		if len(fields.FieldsType) > 0 && fields.FieldsType != "FieldsV1" {
184			allErrs = append(allErrs, field.Invalid(fldPath.Child("fieldsType"), fields.FieldsType, "must be `FieldsV1`"))
185		}
186		allErrs = append(allErrs, ValidateFieldManager(fields.Manager, fldPath.Child("manager"))...)
187	}
188	return allErrs
189}
190
191func ValidateConditions(conditions []metav1.Condition, fldPath *field.Path) field.ErrorList {
192	var allErrs field.ErrorList
193
194	conditionTypeToFirstIndex := map[string]int{}
195	for i, condition := range conditions {
196		if _, ok := conditionTypeToFirstIndex[condition.Type]; ok {
197			allErrs = append(allErrs, field.Duplicate(fldPath.Index(i).Child("type"), condition.Type))
198		} else {
199			conditionTypeToFirstIndex[condition.Type] = i
200		}
201
202		allErrs = append(allErrs, ValidateCondition(condition, fldPath.Index(i))...)
203	}
204
205	return allErrs
206}
207
208// validConditionStatuses is used internally to check validity and provide a good message
209var validConditionStatuses = sets.NewString(string(metav1.ConditionTrue), string(metav1.ConditionFalse), string(metav1.ConditionUnknown))
210
211const (
212	maxReasonLen  = 1 * 1024
213	maxMessageLen = 32 * 1024
214)
215
216func ValidateCondition(condition metav1.Condition, fldPath *field.Path) field.ErrorList {
217	var allErrs field.ErrorList
218
219	// type is set and is a valid format
220	allErrs = append(allErrs, ValidateLabelName(condition.Type, fldPath.Child("type"))...)
221
222	// status is set and is an accepted value
223	if !validConditionStatuses.Has(string(condition.Status)) {
224		allErrs = append(allErrs, field.NotSupported(fldPath.Child("status"), condition.Status, validConditionStatuses.List()))
225	}
226
227	if condition.ObservedGeneration < 0 {
228		allErrs = append(allErrs, field.Invalid(fldPath.Child("observedGeneration"), condition.ObservedGeneration, "must be greater than or equal to zero"))
229	}
230
231	if condition.LastTransitionTime.IsZero() {
232		allErrs = append(allErrs, field.Required(fldPath.Child("lastTransitionTime"), "must be set"))
233	}
234
235	if len(condition.Reason) == 0 {
236		allErrs = append(allErrs, field.Required(fldPath.Child("reason"), "must be set"))
237	} else {
238		for _, currErr := range isValidConditionReason(condition.Reason) {
239			allErrs = append(allErrs, field.Invalid(fldPath.Child("reason"), condition.Reason, currErr))
240		}
241		if len(condition.Reason) > maxReasonLen {
242			allErrs = append(allErrs, field.TooLong(fldPath.Child("reason"), condition.Reason, maxReasonLen))
243		}
244	}
245
246	if len(condition.Message) > maxMessageLen {
247		allErrs = append(allErrs, field.TooLong(fldPath.Child("message"), condition.Message, maxMessageLen))
248	}
249
250	return allErrs
251}
252
253const conditionReasonFmt string = "[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?"
254const conditionReasonErrMsg string = "a condition reason must start with alphabetic character, optionally followed by a string of alphanumeric characters or '_,:', and must end with an alphanumeric character or '_'"
255
256var conditionReasonRegexp = regexp.MustCompile("^" + conditionReasonFmt + "$")
257
258// isValidConditionReason tests for a string that conforms to rules for condition reasons. This checks the format, but not the length.
259func isValidConditionReason(value string) []string {
260	if !conditionReasonRegexp.MatchString(value) {
261		return []string{validation.RegexError(conditionReasonErrMsg, conditionReasonFmt, "my_name", "MY_NAME", "MyName", "ReasonA,ReasonB", "ReasonA:ReasonB")}
262	}
263	return nil
264}
265