func ValidateAPIService(apiServer *discoveryapi.APIService) field.ErrorList { requiredName := apiServer.Spec.Version + "." + apiServer.Spec.Group allErrs := validation.ValidateObjectMeta(&apiServer.ObjectMeta, false, func(name string, prefix bool) []string { if minimalFailures := path.IsValidPathSegmentName(name); len(minimalFailures) > 0 { return minimalFailures } // the name *must* be version.group if name != requiredName { return []string{fmt.Sprintf("must be `spec.version+\".\"+spec.group`: %q", requiredName)} } return []string{} }, field.NewPath("metadata")) // in this case we allow empty group if len(apiServer.Spec.Group) == 0 && apiServer.Spec.Version != "v1" { allErrs = append(allErrs, field.Required(field.NewPath("spec", "group"), "only v1 may have an empty group and it better be legacy kube")) } if len(apiServer.Spec.Group) > 0 { for _, errString := range utilvalidation.IsDNS1123Subdomain(apiServer.Spec.Group) { allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "group"), apiServer.Spec.Group, errString)) } } for _, errString := range utilvalidation.IsDNS1035Label(apiServer.Spec.Version) { allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "version"), apiServer.Spec.Version, errString)) } if apiServer.Spec.Priority <= 0 || apiServer.Spec.Priority > 1000 { allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "priority"), apiServer.Spec.Priority, "priority must be positive and less than 1000")) } if len(apiServer.Spec.Service.Namespace) == 0 { allErrs = append(allErrs, field.Required(field.NewPath("spec", "service", "namespace"), "")) } if len(apiServer.Spec.Service.Name) == 0 { allErrs = append(allErrs, field.Required(field.NewPath("spec", "service", "name"), "")) } if apiServer.Spec.InsecureSkipTLSVerify && len(apiServer.Spec.CABundle) > 0 { allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "insecureSkipTLSVerify"), apiServer.Spec.InsecureSkipTLSVerify, "may not be true if caBundle is present")) } return allErrs }
func ValidateClusterRoleBinding(roleBinding *rbac.ClusterRoleBinding) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, validation.ValidateObjectMeta(&roleBinding.ObjectMeta, false, minimalNameRequirements, field.NewPath("metadata"))...) // TODO allow multiple API groups. For now, restrict to one, but I can envision other experimental roles in other groups taking // advantage of the binding infrastructure if roleBinding.RoleRef.APIGroup != rbac.GroupName { allErrs = append(allErrs, field.NotSupported(field.NewPath("roleRef", "apiGroup"), roleBinding.RoleRef.APIGroup, []string{rbac.GroupName})) } switch roleBinding.RoleRef.Kind { case "ClusterRole": default: allErrs = append(allErrs, field.NotSupported(field.NewPath("roleRef", "kind"), roleBinding.RoleRef.Kind, []string{"ClusterRole"})) } if len(roleBinding.RoleRef.Name) == 0 { allErrs = append(allErrs, field.Required(field.NewPath("roleRef", "name"), "")) } else { for _, msg := range minimalNameRequirements(roleBinding.RoleRef.Name, false) { allErrs = append(allErrs, field.Invalid(field.NewPath("roleRef", "name"), roleBinding.RoleRef.Name, msg)) } } subjectsPath := field.NewPath("subjects") for i, subject := range roleBinding.Subjects { allErrs = append(allErrs, validateRoleBindingSubject(subject, false, subjectsPath.Index(i))...) } return allErrs }
func validateIngressRules(ingressRules []extensions.IngressRule, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if len(ingressRules) == 0 { return append(allErrs, field.Required(fldPath, "")) } for i, ih := range ingressRules { if len(ih.Host) > 0 { if isIP := (net.ParseIP(ih.Host) != nil); isIP { allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, "must be a DNS name, not an IP address")) } // TODO: Ports and ips are allowed in the host part of a url // according to RFC 3986, consider allowing them. if strings.Contains(ih.Host, "*") { for _, msg := range validation.IsWildcardDNS1123Subdomain(ih.Host) { allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, msg)) } continue } for _, msg := range validation.IsDNS1123Subdomain(ih.Host) { allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, msg)) } } allErrs = append(allErrs, validateIngressRuleValue(&ih.IngressRuleValue, fldPath.Index(0))...) } return allErrs }
func validateHTTPIngressRuleValue(httpIngressRuleValue *extensions.HTTPIngressRuleValue, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if len(httpIngressRuleValue.Paths) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("paths"), "")) } for i, rule := range httpIngressRuleValue.Paths { if len(rule.Path) > 0 { if !strings.HasPrefix(rule.Path, "/") { allErrs = append(allErrs, field.Invalid(fldPath.Child("paths").Index(i).Child("path"), rule.Path, "must be an absolute path")) } // TODO: More draconian path regex validation. // Path must be a valid regex. This is the basic requirement. // In addition to this any characters not allowed in a path per // RFC 3986 section-3.3 cannot appear as a literal in the regex. // Consider the example: http://host/valid?#bar, everything after // the last '/' is a valid regex that matches valid#bar, which // isn't a valid path, because the path terminates at the first ? // or #. A more sophisticated form of validation would detect that // the user is confusing url regexes with path regexes. _, err := regexp.CompilePOSIX(rule.Path) if err != nil { allErrs = append(allErrs, field.Invalid(fldPath.Child("paths").Index(i).Child("path"), rule.Path, "must be a valid regex")) } } allErrs = append(allErrs, validateIngressBackend(&rule.Backend, fldPath.Child("backend"))...) } return allErrs }
// ValidateEvent makes sure that the event makes sense. func ValidateEvent(event *api.Event) field.ErrorList { allErrs := field.ErrorList{} // Make sure event.Namespace and the involvedObject.Namespace agree if len(event.InvolvedObject.Namespace) == 0 { // event.Namespace must also be empty (or "default", for compatibility with old clients) if event.Namespace != api.NamespaceNone && event.Namespace != api.NamespaceDefault { allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace")) } } else { // event namespace must match if event.Namespace != event.InvolvedObject.Namespace { allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match event.namespace")) } } // For kinds we recognize, make sure involvedObject.Namespace is set for namespaced kinds if namespaced, err := isNamespacedKind(event.InvolvedObject.Kind, event.InvolvedObject.APIVersion); err == nil { if namespaced && len(event.InvolvedObject.Namespace) == 0 { allErrs = append(allErrs, field.Required(field.NewPath("involvedObject", "namespace"), fmt.Sprintf("required for kind %s", event.InvolvedObject.Kind))) } if !namespaced && len(event.InvolvedObject.Namespace) > 0 { allErrs = append(allErrs, field.Invalid(field.NewPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, fmt.Sprintf("not allowed for kind %s", event.InvolvedObject.Kind))) } } for _, msg := range validation.IsDNS1123Subdomain(event.Namespace) { allErrs = append(allErrs, field.Invalid(field.NewPath("namespace"), event.Namespace, msg)) } return allErrs }
// ValidateNetworkPolicySpec tests if required fields in the networkpolicy spec are set. func ValidateNetworkPolicySpec(spec *extensions.NetworkPolicySpec, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(&spec.PodSelector, fldPath.Child("podSelector"))...) // Validate ingress rules. for _, i := range spec.Ingress { // TODO: Update From to be a pointer to slice as soon as auto-generation supports it. for _, f := range i.From { numFroms := 0 if f.PodSelector != nil { numFroms++ allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(f.PodSelector, fldPath.Child("podSelector"))...) } if f.NamespaceSelector != nil { if numFroms > 0 { allErrs = append(allErrs, field.Forbidden(fldPath, "may not specify more than 1 from type")) } else { numFroms++ allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(f.NamespaceSelector, fldPath.Child("namespaces"))...) } } if numFroms == 0 { // At least one of PodSelector and NamespaceSelector must be defined. allErrs = append(allErrs, field.Required(fldPath, "must specify a from type")) } } } return allErrs }
// ValidateStatefulSetSpec tests if required fields in the StatefulSet spec are set. func ValidateStatefulSetSpec(spec *apps.StatefulSetSpec, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...) if spec.Selector == nil { allErrs = append(allErrs, field.Required(fldPath.Child("selector"), "")) } else { allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...) if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is not valid for statefulset.")) } } selector, err := metav1.LabelSelectorAsSelector(spec.Selector) if err != nil { allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "")) } else { allErrs = append(allErrs, ValidatePodTemplateSpecForStatefulSet(&spec.Template, selector, fldPath.Child("template"))...) } if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways { allErrs = append(allErrs, field.NotSupported(fldPath.Child("template", "spec", "restartPolicy"), spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)})) } return allErrs }
func ValidateDeploymentRollback(obj *extensions.DeploymentRollback) field.ErrorList { allErrs := apivalidation.ValidateAnnotations(obj.UpdatedAnnotations, field.NewPath("updatedAnnotations")) if len(obj.Name) == 0 { allErrs = append(allErrs, field.Required(field.NewPath("name"), "name is required")) } allErrs = append(allErrs, ValidateRollback(&obj.RollbackTo, field.NewPath("rollback"))...) return allErrs }
func ValidateClusterSpec(spec *federation.ClusterSpec, fieldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} // address is required. if len(spec.ServerAddressByClientCIDRs) == 0 { allErrs = append(allErrs, field.Required(fieldPath.Child("serverAddressByClientCIDRs"), "")) } return allErrs }
// ValidateObjectMeta validates an object's metadata on creation. It expects that name generation has already // been performed. // It doesn't return an error for rootscoped resources with namespace, because namespace should already be cleared before. func ValidateObjectMeta(meta *metav1.ObjectMeta, requiresNamespace bool, nameFn apimachineyvalidation.ValidateNameFunc, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if len(meta.GenerateName) != 0 { for _, msg := range nameFn(meta.GenerateName, true) { allErrs = append(allErrs, field.Invalid(fldPath.Child("generateName"), meta.GenerateName, msg)) } } // If the generated name validates, but the calculated value does not, it's a problem with generation, and we // report it here. This may confuse users, but indicates a programming bug and still must be validated. // If there are multiple fields out of which one is required then add an or as a separator if len(meta.Name) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("name"), "name or generateName is required")) } else { for _, msg := range nameFn(meta.Name, false) { allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), meta.Name, msg)) } } if requiresNamespace { if len(meta.Namespace) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), "")) } else { for _, msg := range apimachineyvalidation.ValidateNamespaceName(meta.Namespace, false) { allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), meta.Namespace, msg)) } } } else { if len(meta.Namespace) != 0 { allErrs = append(allErrs, field.Forbidden(fldPath.Child("namespace"), "not allowed on this type")) } } if len(meta.ClusterName) != 0 { for _, msg := range ValidateClusterName(meta.ClusterName, false) { allErrs = append(allErrs, field.Invalid(fldPath.Child("clusterName"), meta.ClusterName, msg)) } } allErrs = append(allErrs, apimachineyvalidation.ValidateNonnegativeField(meta.Generation, fldPath.Child("generation"))...) allErrs = append(allErrs, v1validation.ValidateLabels(meta.Labels, fldPath.Child("labels"))...) allErrs = append(allErrs, ValidateAnnotations(meta.Annotations, fldPath.Child("annotations"))...) allErrs = append(allErrs, ValidateOwnerReferences(meta.OwnerReferences, fldPath.Child("ownerReferences"))...) for _, finalizer := range meta.Finalizers { allErrs = append(allErrs, ValidateFinalizerName(finalizer, fldPath.Child("finalizers"))...) } return allErrs }
func ValidateCrossVersionObjectReference(ref autoscaling.CrossVersionObjectReference, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if len(ref.Kind) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("kind"), "")) } else { for _, msg := range pathvalidation.IsValidPathSegmentName(ref.Kind) { allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), ref.Kind, msg)) } } if len(ref.Name) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) } else { for _, msg := range pathvalidation.IsValidPathSegmentName(ref.Name) { allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), ref.Name, msg)) } } return allErrs }
// validateProvisioner tests if provisioner is a valid qualified name. func validateProvisioner(provisioner string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if len(provisioner) == 0 { allErrs = append(allErrs, field.Required(fldPath, provisioner)) } if len(provisioner) > 0 { for _, msg := range validation.IsQualifiedName(strings.ToLower(provisioner)) { allErrs = append(allErrs, field.Invalid(fldPath, provisioner, msg)) } } return allErrs }
func validateHorizontalPodAutoscalerAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if annotationValue, found := annotations[podautoscaler.HpaCustomMetricsTargetAnnotationName]; found { // Try to parse the annotation var targetList v1beta1.CustomMetricTargetList if err := json.Unmarshal([]byte(annotationValue), &targetList); err != nil { allErrs = append(allErrs, field.Invalid(fldPath.Child("annotations"), annotations, "failed to parse custom metrics target annotation")) } else { if len(targetList.Items) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("annotations", "items"), "custom metrics target must not be empty")) } for _, target := range targetList.Items { if target.Name == "" { allErrs = append(allErrs, field.Required(fldPath.Child("annotations", "items", "name"), "missing custom metric target name")) } if target.TargetValue.MilliValue() <= 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("annotations", "items", "value"), target.TargetValue, "custom metric target value must be greater than 0")) } } } } return allErrs }
// validateIngressBackend tests if a given backend is valid. func validateIngressBackend(backend *extensions.IngressBackend, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} // All backends must reference a single local service by name, and a single service port by name or number. if len(backend.ServiceName) == 0 { return append(allErrs, field.Required(fldPath.Child("serviceName"), "")) } else { for _, msg := range apivalidation.ValidateServiceName(backend.ServiceName, false) { allErrs = append(allErrs, field.Invalid(fldPath.Child("serviceName"), backend.ServiceName, msg)) } } allErrs = append(allErrs, apivalidation.ValidatePortNumOrName(backend.ServicePort, fldPath.Child("servicePort"))...) return allErrs }
func validateConcurrencyPolicy(concurrencyPolicy *batch.ConcurrencyPolicy, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} switch *concurrencyPolicy { case batch.AllowConcurrent, batch.ForbidConcurrent, batch.ReplaceConcurrent: break case "": allErrs = append(allErrs, field.Required(fldPath, "")) default: validValues := []string{string(batch.AllowConcurrent), string(batch.ForbidConcurrent), string(batch.ReplaceConcurrent)} allErrs = append(allErrs, field.NotSupported(fldPath, *concurrencyPolicy, validValues)) } return allErrs }
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")) } if len(rule.NonResourceURLs) > 0 { if isNamespaced { allErrs = append(allErrs, field.Invalid(fldPath.Child("nonResourceURLs"), rule.NonResourceURLs, "namespaced rules cannot apply to non-resource URLs")) } if len(rule.APIGroups) > 0 || len(rule.Resources) > 0 || len(rule.ResourceNames) > 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("nonResourceURLs"), rule.NonResourceURLs, "rules cannot apply to both regular resources and non-resource URLs")) } return allErrs } if len(rule.APIGroups) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("apiGroups"), "resource rules must supply at least one api group")) } if len(rule.Resources) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("resources"), "resource rules must supply at least one resource")) } return allErrs }
func ValidateCertificateSigningRequest(csr *certificates.CertificateSigningRequest) field.ErrorList { isNamespaced := false allErrs := apivalidation.ValidateObjectMeta(&csr.ObjectMeta, isNamespaced, ValidateCertificateRequestName, field.NewPath("metadata")) err := validateCSR(csr) specPath := field.NewPath("spec") if err != nil { allErrs = append(allErrs, field.Invalid(specPath.Child("request"), csr.Spec.Request, fmt.Sprintf("%v", err))) } if len(csr.Spec.Usages) == 0 { allErrs = append(allErrs, field.Required(specPath.Child("usages"), "usages must be provided")) } return allErrs }
func validateRoleBindingSubject(subject rbac.Subject, isNamespaced bool, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if len(subject.Name) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) } switch subject.Kind { case rbac.ServiceAccountKind: if len(subject.Name) > 0 { for _, msg := range validation.ValidateServiceAccountName(subject.Name, false) { allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), subject.Name, msg)) } } if !isNamespaced && len(subject.Namespace) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), "")) } case rbac.UserKind: // TODO(ericchiang): What other restrictions on user name are there? if len(subject.Name) == 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), subject.Name, "user name cannot be empty")) } case rbac.GroupKind: // TODO(ericchiang): What other restrictions on group name are there? if len(subject.Name) == 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), subject.Name, "group name cannot be empty")) } default: allErrs = append(allErrs, field.NotSupported(fldPath.Child("kind"), subject.Kind, []string{rbac.ServiceAccountKind, rbac.UserKind, rbac.GroupKind})) } return allErrs }
func ValidateCronJobSpec(spec *batch.CronJobSpec, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if len(spec.Schedule) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("schedule"), "")) } else { allErrs = append(allErrs, validateScheduleFormat(spec.Schedule, fldPath.Child("schedule"))...) } if spec.StartingDeadlineSeconds != nil { allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.StartingDeadlineSeconds), fldPath.Child("startingDeadlineSeconds"))...) } allErrs = append(allErrs, validateConcurrencyPolicy(&spec.ConcurrencyPolicy, fldPath.Child("concurrencyPolicy"))...) allErrs = append(allErrs, ValidateJobTemplateSpec(&spec.JobTemplate, fldPath.Child("jobTemplate"))...) return allErrs }
func ValidateLabelSelectorRequirement(sr metav1.LabelSelectorRequirement, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} switch sr.Operator { case metav1.LabelSelectorOpIn, metav1.LabelSelectorOpNotIn: if len(sr.Values) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified when `operator` is 'In' or 'NotIn'")) } case metav1.LabelSelectorOpExists, metav1.LabelSelectorOpDoesNotExist: if len(sr.Values) > 0 { allErrs = append(allErrs, field.Forbidden(fldPath.Child("values"), "may not be specified when `operator` is 'Exists' or 'DoesNotExist'")) } default: allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), sr.Operator, "not a valid selector operator")) } allErrs = append(allErrs, ValidateLabelName(sr.Key, fldPath.Child("key"))...) return allErrs }
func ValidateJobSpec(spec *batch.JobSpec, fldPath *field.Path) field.ErrorList { allErrs := validateJobSpec(spec, fldPath) if spec.Selector == nil { allErrs = append(allErrs, field.Required(fldPath.Child("selector"), "")) } else { allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...) } // Whether manually or automatically generated, the selector of the job must match the pods it will produce. if selector, err := metav1.LabelSelectorAsSelector(spec.Selector); err == nil { labels := labels.Set(spec.Template.Labels) if !selector.Matches(labels) { allErrs = append(allErrs, field.Invalid(fldPath.Child("template", "metadata", "labels"), spec.Template.Labels, "`selector` does not match template `labels`")) } } return allErrs }
func ValidateDeploymentStrategy(strategy *extensions.DeploymentStrategy, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} switch strategy.Type { case extensions.RecreateDeploymentStrategyType: if strategy.RollingUpdate != nil { allErrs = append(allErrs, field.Forbidden(fldPath.Child("rollingUpdate"), "may not be specified when strategy `type` is '"+string(extensions.RecreateDeploymentStrategyType+"'"))) } case extensions.RollingUpdateDeploymentStrategyType: // This should never happen since it's set and checked in defaults.go if strategy.RollingUpdate == nil { allErrs = append(allErrs, field.Required(fldPath.Child("rollingUpdate"), "this should be defaulted and never be nil")) } else { allErrs = append(allErrs, ValidateRollingUpdateDeployment(strategy.RollingUpdate, fldPath.Child("rollingUpdate"))...) } default: validValues := []string{string(extensions.RecreateDeploymentStrategyType), string(extensions.RollingUpdateDeploymentStrategyType)} allErrs = append(allErrs, field.NotSupported(fldPath, strategy, validValues)) } return allErrs }
// TODO: generalize for other controller objects that will follow the same pattern, such as ReplicaSet and DaemonSet, and // move to new location. Replace batch.Job with an interface. // // ValidateGeneratedSelector validates that the generated selector on a controller object match the controller object // metadata, and the labels on the pod template are as generated. func ValidateGeneratedSelector(obj *batch.Job) field.ErrorList { allErrs := field.ErrorList{} if obj.Spec.ManualSelector != nil && *obj.Spec.ManualSelector { return allErrs } if obj.Spec.Selector == nil { return allErrs // This case should already have been checked in caller. No need for more errors. } // If somehow uid was unset then we would get "controller-uid=" as the selector // which is bad. if obj.ObjectMeta.UID == "" { allErrs = append(allErrs, field.Required(field.NewPath("metadata").Child("uid"), "")) } // If selector generation was requested, then expected labels must be // present on pod template, and must match job's uid and name. The // generated (not-manual) selectors/labels ensure no overlap with other // controllers. The manual mode allows orphaning, adoption, // backward-compatibility, and experimentation with new // labeling/selection schemes. Automatic selector generation should // have placed certain labels on the pod, but this could have failed if // the user added coflicting labels. Validate that the expected // generated ones are there. allErrs = append(allErrs, apivalidation.ValidateHasLabel(obj.Spec.Template.ObjectMeta, field.NewPath("spec").Child("template").Child("metadata"), "controller-uid", string(obj.UID))...) allErrs = append(allErrs, apivalidation.ValidateHasLabel(obj.Spec.Template.ObjectMeta, field.NewPath("spec").Child("template").Child("metadata"), "job-name", string(obj.Name))...) expectedLabels := make(map[string]string) expectedLabels["controller-uid"] = string(obj.UID) expectedLabels["job-name"] = string(obj.Name) // Whether manually or automatically generated, the selector of the job must match the pods it will produce. if selector, err := metav1.LabelSelectorAsSelector(obj.Spec.Selector); err == nil { if !selector.Matches(labels.Set(expectedLabels)) { allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("selector"), obj.Spec.Selector, "`selector` not auto-generated")) } } return allErrs }
// Validates the given template and ensures that it is in accordance with the desired selector. func ValidatePodTemplateSpecForStatefulSet(template *api.PodTemplateSpec, selector labels.Selector, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if template == nil { allErrs = append(allErrs, field.Required(fldPath, "")) } else { if !selector.Empty() { // Verify that the StatefulSet selector matches the labels in template. labels := labels.Set(template.Labels) if !selector.Matches(labels) { allErrs = append(allErrs, field.Invalid(fldPath.Child("metadata", "labels"), template.Labels, "`selector` does not match template `labels`")) } } // TODO: Add validation for PodSpec, currently this will check volumes, which we know will // fail. We should really check that the union of the given volumes and volumeClaims match // volume mounts in the containers. // allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(template, fldPath)...) allErrs = append(allErrs, unversionedvalidation.ValidateLabels(template.Labels, fldPath.Child("labels"))...) allErrs = append(allErrs, apivalidation.ValidateAnnotations(template.Annotations, fldPath.Child("annotations"))...) allErrs = append(allErrs, apivalidation.ValidatePodSpecificAnnotations(template.Annotations, &template.Spec, fldPath.Child("annotations"))...) } return allErrs }
// ValidateReplicaSetSpec tests if required fields in the ReplicaSet spec are set. func ValidateReplicaSetSpec(spec *extensions.ReplicaSetSpec, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...) allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...) if spec.Selector == nil { allErrs = append(allErrs, field.Required(fldPath.Child("selector"), "")) } else { allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...) if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is not valid for deployment.")) } } selector, err := metav1.LabelSelectorAsSelector(spec.Selector) if err != nil { allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "invalid label selector.")) } else { allErrs = append(allErrs, ValidatePodTemplateSpecForReplicaSet(&spec.Template, selector, spec.Replicas, fldPath.Child("template"))...) } return allErrs }
// Validates the given template and ensures that it is in accordance with the desired selector and replicas. func ValidatePodTemplateSpecForReplicaSet(template *api.PodTemplateSpec, selector labels.Selector, replicas int32, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if template == nil { allErrs = append(allErrs, field.Required(fldPath, "")) } else { if !selector.Empty() { // Verify that the ReplicaSet selector matches the labels in template. labels := labels.Set(template.Labels) if !selector.Matches(labels) { allErrs = append(allErrs, field.Invalid(fldPath.Child("metadata", "labels"), template.Labels, "`selector` does not match template `labels`")) } } allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(template, fldPath)...) if replicas > 1 { allErrs = append(allErrs, apivalidation.ValidateReadOnlyPersistentDisks(template.Spec.Volumes, fldPath.Child("spec", "volumes"))...) } // RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec(). if template.Spec.RestartPolicy != api.RestartPolicyAlways { allErrs = append(allErrs, field.NotSupported(fldPath.Child("spec", "restartPolicy"), template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)})) } } return allErrs }
// Validates given deployment spec. func ValidateDeploymentSpec(spec *extensions.DeploymentSpec, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...) if spec.Selector == nil { allErrs = append(allErrs, field.Required(fldPath.Child("selector"), "")) } else { allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...) if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is not valid for deployment.")) } } selector, err := metav1.LabelSelectorAsSelector(spec.Selector) if err != nil { allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "invalid label selector.")) } else { allErrs = append(allErrs, ValidatePodTemplateSpecForReplicaSet(&spec.Template, selector, spec.Replicas, fldPath.Child("template"))...) } allErrs = append(allErrs, ValidateDeploymentStrategy(&spec.Strategy, fldPath.Child("strategy"))...) allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...) if spec.RevisionHistoryLimit != nil { // zero is a valid RevisionHistoryLimit allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.RevisionHistoryLimit), fldPath.Child("revisionHistoryLimit"))...) } if spec.RollbackTo != nil { allErrs = append(allErrs, ValidateRollback(spec.RollbackTo, fldPath.Child("rollback"))...) } if spec.ProgressDeadlineSeconds != nil { allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.ProgressDeadlineSeconds), fldPath.Child("progressDeadlineSeconds"))...) if *spec.ProgressDeadlineSeconds <= spec.MinReadySeconds { allErrs = append(allErrs, field.Invalid(fldPath.Child("progressDeadlineSeconds"), spec.ProgressDeadlineSeconds, "must be greater than minReadySeconds.")) } } return allErrs }
func ValidateThirdPartyResource(obj *extensions.ThirdPartyResource) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&obj.ObjectMeta, false, ValidateThirdPartyResourceName, field.NewPath("metadata"))...) versions := sets.String{} if len(obj.Versions) == 0 { allErrs = append(allErrs, field.Required(field.NewPath("versions"), "must specify at least one version")) } for ix := range obj.Versions { version := &obj.Versions[ix] if len(version.Name) == 0 { allErrs = append(allErrs, field.Invalid(field.NewPath("versions").Index(ix).Child("name"), version, "must not be empty")) } else { for _, msg := range validation.IsDNS1123Label(version.Name) { allErrs = append(allErrs, field.Invalid(field.NewPath("versions").Index(ix).Child("name"), version, msg)) } } if versions.Has(version.Name) { allErrs = append(allErrs, field.Duplicate(field.NewPath("versions").Index(ix).Child("name"), version)) } versions.Insert(version.Name) } return allErrs }
func TestNewInvalid(t *testing.T) { testCases := []struct { Err *field.Error Details *metav1.StatusDetails }{ { field.Duplicate(field.NewPath("field[0].name"), "bar"), &metav1.StatusDetails{ Kind: "Kind", Name: "name", Causes: []metav1.StatusCause{{ Type: metav1.CauseTypeFieldValueDuplicate, Field: "field[0].name", }}, }, }, { field.Invalid(field.NewPath("field[0].name"), "bar", "detail"), &metav1.StatusDetails{ Kind: "Kind", Name: "name", Causes: []metav1.StatusCause{{ Type: metav1.CauseTypeFieldValueInvalid, Field: "field[0].name", }}, }, }, { field.NotFound(field.NewPath("field[0].name"), "bar"), &metav1.StatusDetails{ Kind: "Kind", Name: "name", Causes: []metav1.StatusCause{{ Type: metav1.CauseTypeFieldValueNotFound, Field: "field[0].name", }}, }, }, { field.NotSupported(field.NewPath("field[0].name"), "bar", nil), &metav1.StatusDetails{ Kind: "Kind", Name: "name", Causes: []metav1.StatusCause{{ Type: metav1.CauseTypeFieldValueNotSupported, Field: "field[0].name", }}, }, }, { field.Required(field.NewPath("field[0].name"), ""), &metav1.StatusDetails{ Kind: "Kind", Name: "name", Causes: []metav1.StatusCause{{ Type: metav1.CauseTypeFieldValueRequired, Field: "field[0].name", }}, }, }, } for i, testCase := range testCases { vErr, expected := testCase.Err, testCase.Details expected.Causes[0].Message = vErr.ErrorBody() err := NewInvalid(kind("Kind"), "name", field.ErrorList{vErr}) status := err.ErrStatus if status.Code != 422 || status.Reason != metav1.StatusReasonInvalid { t.Errorf("%d: unexpected status: %#v", i, status) } if !reflect.DeepEqual(expected, status.Details) { t.Errorf("%d: expected %#v, got %#v", i, expected, status.Details) } } }