package validation
import (
"fmt"
"net"
"net/url"
"reflect"
"regexp"
"strings"
"time"
apiserveroptions "k8s.io/kubernetes/cmd/kube-apiserver/app/options"
controlleroptions "k8s.io/kubernetes/cmd/kube-controller-manager/app/options"
kvalidation "k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/serviceaccount"
knet "k8s.io/kubernetes/pkg/util/net"
"k8s.io/kubernetes/pkg/util/sets"
kuval "k8s.io/kubernetes/pkg/util/validation"
"k8s.io/kubernetes/pkg/util/validation/field"
"github.com/openshift/origin/pkg/cmd/server/api"
"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy"
"github.com/openshift/origin/pkg/security/mcs"
"github.com/openshift/origin/pkg/security/uid"
"github.com/openshift/origin/pkg/util/labelselector"
)
// TODO: this should just be two return arrays, no need to be clever
type ValidationResults struct {
Warnings field.ErrorList
Errors field.ErrorList
}
func (r *ValidationResults) Append(additionalResults ValidationResults) {
r.AddErrors(additionalResults.Errors...)
r.AddWarnings(additionalResults.Warnings...)
}
func (r *ValidationResults) AddErrors(errors ...*field.Error) {
if len(errors) == 0 {
return
}
r.Errors = append(r.Errors, errors...)
}
func (r *ValidationResults) AddWarnings(warnings ...*field.Error) {
if len(warnings) == 0 {
return
}
r.Warnings = append(r.Warnings, warnings...)
}
func ValidateMasterConfig(config *api.MasterConfig, fldPath *field.Path) ValidationResults {
validationResults := ValidationResults{}
if _, urlErrs := ValidateURL(config.MasterPublicURL, fldPath.Child("masterPublicURL")); len(urlErrs) > 0 {
validationResults.AddErrors(urlErrs...)
}
switch {
case config.ControllerLeaseTTL > 300,
config.ControllerLeaseTTL < -1,
config.ControllerLeaseTTL > 0 && config.ControllerLeaseTTL < 10:
validationResults.AddErrors(field.Invalid(fldPath.Child("controllerLeaseTTL"), config.ControllerLeaseTTL, "TTL must be -1 (disabled), 0 (default), or between 10 and 300 seconds"))
}
validationResults.AddErrors(ValidateDisabledFeatures(config.DisabledFeatures, fldPath.Child("disabledFeatures"))...)
if config.AssetConfig != nil {
assetConfigPath := fldPath.Child("assetConfig")
validationResults.Append(ValidateAssetConfig(config.AssetConfig, assetConfigPath))
colocated := config.AssetConfig.ServingInfo.BindAddress == config.ServingInfo.BindAddress
if colocated {
publicURL, _ := url.Parse(config.AssetConfig.PublicURL)
if publicURL.Path == "/" {
validationResults.AddErrors(field.Invalid(assetConfigPath.Child("publicURL"), config.AssetConfig.PublicURL, "path can not be / when colocated with master API"))
}
// Warn if they have customized the asset certificates in ways that will be ignored
if !reflect.DeepEqual(config.AssetConfig.ServingInfo.ServerCert, config.ServingInfo.ServerCert) ||
!reflect.DeepEqual(config.AssetConfig.ServingInfo.NamedCertificates, config.ServingInfo.NamedCertificates) {
validationResults.AddWarnings(field.Invalid(assetConfigPath.Child("servingInfo"), "<not displayed>", "changes to assetConfig certificate configuration are not used when colocated with master API"))
}
}
if config.OAuthConfig != nil {
if config.OAuthConfig.AssetPublicURL != config.AssetConfig.PublicURL {
validationResults.AddErrors(
field.Invalid(assetConfigPath.Child("publicURL"), config.AssetConfig.PublicURL, "must match oauthConfig.assetPublicURL"),
field.Invalid(fldPath.Child("oauthConfig", "assetPublicURL"), config.OAuthConfig.AssetPublicURL, "must match assetConfig.publicURL"),
)
}
}
// TODO warn when the CORS list does not include the assetConfig.publicURL host:port
// only warn cause they could handle CORS headers themselves in a proxy
}
if config.DNSConfig != nil {
dnsConfigPath := fldPath.Child("dnsConfig")
validationResults.AddErrors(ValidateHostPort(config.DNSConfig.BindAddress, dnsConfigPath.Child("bindAddress"))...)
switch config.DNSConfig.BindNetwork {
case "tcp", "tcp4", "tcp6":
default:
validationResults.AddErrors(field.Invalid(dnsConfigPath.Child("bindNetwork"), config.DNSConfig.BindNetwork, "must be 'tcp', 'tcp4', or 'tcp6'"))
}
}
if config.EtcdConfig != nil {
etcdConfigErrs := ValidateEtcdConfig(config.EtcdConfig, fldPath.Child("etcdConfig"))
validationResults.Append(etcdConfigErrs)
if len(etcdConfigErrs.Errors) == 0 {
// Validate the etcdClientInfo with the internal etcdConfig
validationResults.AddErrors(ValidateEtcdConnectionInfo(config.EtcdClientInfo, config.EtcdConfig, fldPath.Child("etcdClientInfo"))...)
} else {
// Validate the etcdClientInfo by itself
validationResults.AddErrors(ValidateEtcdConnectionInfo(config.EtcdClientInfo, nil, fldPath.Child("etcdClientInfo"))...)
}
} else {
// Validate the etcdClientInfo by itself
validationResults.AddErrors(ValidateEtcdConnectionInfo(config.EtcdClientInfo, nil, fldPath.Child("etcdClientInfo"))...)
}
validationResults.AddErrors(ValidateEtcdStorageConfig(config.EtcdStorageConfig, fldPath.Child("etcdStorageConfig"))...)
validationResults.AddErrors(ValidateImageConfig(config.ImageConfig, fldPath.Child("imageConfig"))...)
validationResults.AddErrors(ValidateImagePolicyConfig(config.ImagePolicyConfig, fldPath.Child("imagePolicyConfig"))...)
validationResults.AddErrors(ValidateKubeletConnectionInfo(config.KubeletClientInfo, fldPath.Child("kubeletClientInfo"))...)
builtInKubernetes := config.KubernetesMasterConfig != nil
if config.KubernetesMasterConfig != nil {
validationResults.Append(ValidateKubernetesMasterConfig(config.KubernetesMasterConfig, fldPath.Child("kubernetesMasterConfig")))
}
if (config.KubernetesMasterConfig == nil) && (len(config.MasterClients.ExternalKubernetesKubeConfig) == 0) {
validationResults.AddErrors(field.Invalid(fldPath.Child("kubernetesMasterConfig"), config.KubernetesMasterConfig, "either kubernetesMasterConfig or masterClients.externalKubernetesKubeConfig must have a value"))
}
if (config.KubernetesMasterConfig != nil) && (len(config.MasterClients.ExternalKubernetesKubeConfig) != 0) {
validationResults.AddErrors(field.Invalid(fldPath.Child("kubernetesMasterConfig"), config.KubernetesMasterConfig, "kubernetesMasterConfig and masterClients.externalKubernetesKubeConfig are mutually exclusive"))
}
if len(config.NetworkConfig.ServiceNetworkCIDR) > 0 {
if _, _, err := net.ParseCIDR(strings.TrimSpace(config.NetworkConfig.ServiceNetworkCIDR)); err != nil {
validationResults.AddErrors(field.Invalid(fldPath.Child("networkConfig", "serviceNetworkCIDR"), config.NetworkConfig.ServiceNetworkCIDR, "must be a valid CIDR notation IP range (e.g. 172.30.0.0/16)"))
} else if config.KubernetesMasterConfig != nil && len(config.KubernetesMasterConfig.ServicesSubnet) > 0 && config.KubernetesMasterConfig.ServicesSubnet != config.NetworkConfig.ServiceNetworkCIDR {
validationResults.AddErrors(field.Invalid(fldPath.Child("networkConfig", "serviceNetworkCIDR"), config.NetworkConfig.ServiceNetworkCIDR, fmt.Sprintf("must match kubernetesMasterConfig.servicesSubnet value of %q", config.KubernetesMasterConfig.ServicesSubnet)))
}
}
validationResults.AddErrors(ValidateKubeConfig(config.MasterClients.OpenShiftLoopbackKubeConfig, fldPath.Child("masterClients", "openShiftLoopbackKubeConfig"))...)
if len(config.MasterClients.ExternalKubernetesKubeConfig) > 0 {
validationResults.AddErrors(ValidateKubeConfig(config.MasterClients.ExternalKubernetesKubeConfig, fldPath.Child("masterClients", "externalKubernetesKubeConfig"))...)
}
validationResults.AddErrors(ValidatePolicyConfig(config.PolicyConfig, fldPath.Child("policyConfig"))...)
if config.OAuthConfig != nil {
validationResults.Append(ValidateOAuthConfig(config.OAuthConfig, fldPath.Child("oauthConfig")))
}
validationResults.Append(ValidateServiceAccountConfig(config.ServiceAccountConfig, builtInKubernetes, fldPath.Child("serviceAccountConfig")))
validationResults.Append(ValidateHTTPServingInfo(config.ServingInfo, fldPath.Child("servingInfo")))
validationResults.Append(ValidateProjectConfig(config.ProjectConfig, fldPath.Child("projectConfig")))
validationResults.AddErrors(ValidateRoutingConfig(config.RoutingConfig, fldPath.Child("routingConfig"))...)
validationResults.Append(ValidateAPILevels(config.APILevels, api.KnownOpenShiftAPILevels, api.DeadOpenShiftAPILevels, fldPath.Child("apiLevels")))
if config.AdmissionConfig.PluginConfig != nil {
validationResults.AddErrors(ValidateAdmissionPluginConfig(config.AdmissionConfig.PluginConfig, fldPath.Child("admissionConfig", "pluginConfig"))...)
}
return validationResults
}
func ValidateAPILevels(apiLevels []string, knownAPILevels, deadAPILevels []string, fldPath *field.Path) ValidationResults {
validationResults := ValidationResults{}
if len(apiLevels) == 0 {
validationResults.AddErrors(field.Required(fldPath, ""))
}
deadLevels := sets.NewString(deadAPILevels...)
knownLevels := sets.NewString(knownAPILevels...)
for i, apiLevel := range apiLevels {
idxPath := fldPath.Index(i)
if deadLevels.Has(apiLevel) {
validationResults.AddWarnings(field.Invalid(idxPath, apiLevel, "unsupported level"))
}
if !knownLevels.Has(apiLevel) {
validationResults.AddWarnings(field.Invalid(idxPath, apiLevel, "unknown level"))
}
}
return validationResults
}
func ValidateEtcdStorageConfig(config api.EtcdStorageConfig, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, ValidateStorageVersionLevel(
config.KubernetesStorageVersion,
api.KnownKubernetesStorageVersionLevels,
api.DeadKubernetesStorageVersionLevels,
fldPath.Child("kubernetesStorageVersion"))...)
allErrs = append(allErrs, ValidateStorageVersionLevel(
config.OpenShiftStorageVersion,
api.KnownOpenShiftStorageVersionLevels,
api.DeadOpenShiftStorageVersionLevels,
fldPath.Child("openShiftStorageVersion"))...)
if strings.ContainsRune(config.KubernetesStoragePrefix, '%') {
allErrs = append(allErrs, field.Invalid(fldPath.Child("kubernetesStoragePrefix"), config.KubernetesStoragePrefix, "the '%' character may not be used in etcd path prefixes"))
}
if strings.ContainsRune(config.OpenShiftStoragePrefix, '%') {
allErrs = append(allErrs, field.Invalid(fldPath.Child("openShiftStoragePrefix"), config.OpenShiftStoragePrefix, "the '%' character may not be used in etcd path prefixes"))
}
return allErrs
}
func ValidateStorageVersionLevel(level string, knownAPILevels, deadAPILevels []string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(level) == 0 {
allErrs = append(allErrs, field.Required(fldPath, ""))
return allErrs
}
supportedLevels := sets.NewString(knownAPILevels...)
supportedLevels.Delete(deadAPILevels...)
if !supportedLevels.Has(level) {
allErrs = append(allErrs, field.NotSupported(fldPath, level, supportedLevels.List()))
}
return allErrs
}
func ValidateServiceAccountConfig(config api.ServiceAccountConfig, builtInKubernetes bool, fldPath *field.Path) ValidationResults {
validationResults := ValidationResults{}
managedNames := sets.NewString(config.ManagedNames...)
managedNamesPath := fldPath.Child("managedNames")
if !managedNames.Has(bootstrappolicy.BuilderServiceAccountName) {
validationResults.AddWarnings(field.Invalid(managedNamesPath, "", fmt.Sprintf("missing %q, which will require manual creation in each namespace before builds can run", bootstrappolicy.BuilderServiceAccountName)))
}
if !managedNames.Has(bootstrappolicy.DeployerServiceAccountName) {
validationResults.AddWarnings(field.Invalid(managedNamesPath, "", fmt.Sprintf("missing %q, which will require manual creation in each namespace before deployments can run", bootstrappolicy.DeployerServiceAccountName)))
}
if builtInKubernetes && !managedNames.Has(bootstrappolicy.DefaultServiceAccountName) {
validationResults.AddWarnings(field.Invalid(managedNamesPath, "", fmt.Sprintf("missing %q, which will prevent creation of pods that do not specify a valid service account", bootstrappolicy.DefaultServiceAccountName)))
}
for i, name := range config.ManagedNames {
if ok, msg := kvalidation.ValidateServiceAccountName(name, false); !ok {
validationResults.AddErrors(field.Invalid(managedNamesPath.Index(i), name, msg))
}
}
if len(config.PrivateKeyFile) > 0 {
privateKeyFilePath := fldPath.Child("privateKeyFile")
if fileErrs := ValidateFile(config.PrivateKeyFile, privateKeyFilePath); len(fileErrs) > 0 {
validationResults.AddErrors(fileErrs...)
} else if privateKey, err := serviceaccount.ReadPrivateKey(config.PrivateKeyFile); err != nil {
validationResults.AddErrors(field.Invalid(privateKeyFilePath, config.PrivateKeyFile, err.Error()))
} else if err := privateKey.Validate(); err != nil {
validationResults.AddErrors(field.Invalid(privateKeyFilePath, config.PrivateKeyFile, err.Error()))
}
} else if builtInKubernetes {
validationResults.AddWarnings(field.Invalid(fldPath.Child("privateKeyFile"), "", "no service account tokens will be generated, which could prevent builds and deployments from working"))
}
if len(config.PublicKeyFiles) == 0 {
validationResults.AddWarnings(field.Invalid(fldPath.Child("publicKeyFiles"), "", "no service account tokens will be accepted by the API, which will prevent builds and deployments from working"))
}
for i, publicKeyFile := range config.PublicKeyFiles {
idxPath := fldPath.Child("publicKeyFiles").Index(i)
if fileErrs := ValidateFile(publicKeyFile, idxPath); len(fileErrs) > 0 {
validationResults.AddErrors(fileErrs...)
} else if _, err := serviceaccount.ReadPublicKey(publicKeyFile); err != nil {
validationResults.AddErrors(field.Invalid(idxPath, publicKeyFile, err.Error()))
}
}
if len(config.MasterCA) > 0 {
validationResults.AddErrors(ValidateFile(config.MasterCA, fldPath.Child("masterCA"))...)
} else if builtInKubernetes {
validationResults.AddWarnings(field.Invalid(fldPath.Child("masterCA"), "", "master CA information will not be automatically injected into pods, which will prevent verification of the API server from inside a pod"))
}
return validationResults
}
func ValidateAssetConfig(config *api.AssetConfig, fldPath *field.Path) ValidationResults {
validationResults := ValidationResults{}
validationResults.Append(ValidateHTTPServingInfo(config.ServingInfo, fldPath.Child("servingInfo")))
if len(config.LogoutURL) > 0 {
_, urlErrs := ValidateURL(config.LogoutURL, fldPath.Child("logoutURL"))
if len(urlErrs) > 0 {
validationResults.AddErrors(urlErrs...)
}
}
urlObj, urlErrs := ValidateURL(config.PublicURL, fldPath.Child("publicURL"))
if len(urlErrs) > 0 {
validationResults.AddErrors(urlErrs...)
}
if urlObj != nil {
if !strings.HasSuffix(urlObj.Path, "/") {
validationResults.AddErrors(field.Invalid(fldPath.Child("publicURL"), config.PublicURL, "must have a trailing slash in path"))
}
}
if _, urlErrs := ValidateURL(config.MasterPublicURL, fldPath.Child("masterPublicURL")); len(urlErrs) > 0 {
validationResults.AddErrors(urlErrs...)
}
if len(config.LoggingPublicURL) > 0 {
if _, loggingURLErrs := ValidateSecureURL(config.LoggingPublicURL, fldPath.Child("loggingPublicURL")); len(loggingURLErrs) > 0 {
validationResults.AddErrors(loggingURLErrs...)
}
} else {
validationResults.AddWarnings(field.Invalid(fldPath.Child("loggingPublicURL"), "", "required to view aggregated container logs in the console"))
}
if len(config.MetricsPublicURL) > 0 {
if _, metricsURLErrs := ValidateSecureURL(config.MetricsPublicURL, fldPath.Child("metricsPublicURL")); len(metricsURLErrs) > 0 {
validationResults.AddErrors(metricsURLErrs...)
}
} else {
validationResults.AddWarnings(field.Invalid(fldPath.Child("metricsPublicURL"), "", "required to view cluster metrics in the console"))
}
for i, scriptFile := range config.ExtensionScripts {
validationResults.AddErrors(ValidateFile(scriptFile, fldPath.Child("extensionScripts").Index(i))...)
}
for i, stylesheetFile := range config.ExtensionStylesheets {
validationResults.AddErrors(ValidateFile(stylesheetFile, fldPath.Child("extensionStylesheets").Index(i))...)
}
nameTaken := map[string]bool{}
for i, extConfig := range config.Extensions {
idxPath := fldPath.Child("extensions").Index(i)
extConfigErrors := ValidateAssetExtensionsConfig(extConfig, idxPath)
validationResults.AddErrors(extConfigErrors...)
if nameTaken[extConfig.Name] {
dupError := field.Invalid(idxPath.Child("name"), extConfig.Name, "duplicate extension name")
validationResults.AddErrors(dupError)
} else {
nameTaken[extConfig.Name] = true
}
}
return validationResults
}
var extNameExp = regexp.MustCompile(`^[A-Za-z0-9_]+$`)
func ValidateAssetExtensionsConfig(extConfig api.AssetExtensionsConfig, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, ValidateDir(extConfig.SourceDirectory, fldPath.Child("sourceDirectory"))...)
if len(extConfig.Name) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
} else if !extNameExp.MatchString(extConfig.Name) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), extConfig.Name, fmt.Sprintf("does not match %v", extNameExp)))
}
return allErrs
}
func ValidateImageConfig(config api.ImageConfig, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(config.Format) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("format"), ""))
}
return allErrs
}
func ValidateImagePolicyConfig(config api.ImagePolicyConfig, fldPath *field.Path) field.ErrorList {
errs := field.ErrorList{}
if config.MaxImagesBulkImportedPerRepository == 0 || config.MaxImagesBulkImportedPerRepository < -1 {
errs = append(errs, field.Invalid(fldPath.Child("maxImagesBulkImportedPerRepository"), config.MaxImagesBulkImportedPerRepository, "must be a positive integer or -1"))
}
if config.ScheduledImageImportMinimumIntervalSeconds <= 0 {
errs = append(errs, field.Invalid(fldPath.Child("scheduledImageImportMinimumIntervalSeconds"), config.ScheduledImageImportMinimumIntervalSeconds, "must be a positive integer"))
}
if config.MaxScheduledImageImportsPerMinute == 0 || config.MaxScheduledImageImportsPerMinute < -1 {
errs = append(errs, field.Invalid(fldPath.Child("maxScheduledImageImportsPerMinute"), config.MaxScheduledImageImportsPerMinute, "must be a positive integer or -1"))
}
return errs
}
func ValidateKubeletConnectionInfo(config api.KubeletConnectionInfo, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if config.Port == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("port"), ""))
}
if len(config.CA) > 0 {
allErrs = append(allErrs, ValidateFile(config.CA, fldPath.Child("ca"))...)
}
allErrs = append(allErrs, ValidateCertInfo(config.ClientCert, false, fldPath)...)
return allErrs
}
func ValidateKubernetesMasterConfig(config *api.KubernetesMasterConfig, fldPath *field.Path) ValidationResults {
validationResults := ValidationResults{}
if len(config.MasterIP) > 0 {
validationResults.AddErrors(ValidateSpecifiedIP(config.MasterIP, fldPath.Child("masterIP"))...)
}
if config.MasterCount == 0 || config.MasterCount < -1 {
validationResults.AddErrors(field.Invalid(fldPath.Child("masterCount"), config.MasterCount, "must be a positive integer or -1"))
}
validationResults.AddErrors(ValidateCertInfo(config.ProxyClientInfo, false, fldPath.Child("proxyClientInfo"))...)
if len(config.ProxyClientInfo.CertFile) == 0 && len(config.ProxyClientInfo.KeyFile) == 0 {
validationResults.AddWarnings(field.Invalid(fldPath.Child("proxyClientInfo"), "", "if no client certificate is specified, TLS pods and services cannot validate requests came from the proxy"))
}
if len(config.ServicesSubnet) > 0 {
if _, _, err := net.ParseCIDR(strings.TrimSpace(config.ServicesSubnet)); err != nil {
validationResults.AddErrors(field.Invalid(fldPath.Child("servicesSubnet"), config.ServicesSubnet, "must be a valid CIDR notation IP range (e.g. 172.30.0.0/16)"))
}
}
if len(config.ServicesNodePortRange) > 0 {
if _, err := knet.ParsePortRange(strings.TrimSpace(config.ServicesNodePortRange)); err != nil {
validationResults.AddErrors(field.Invalid(fldPath.Child("servicesNodePortRange"), config.ServicesNodePortRange, "must be a valid port range (e.g. 30000-32000)"))
}
}
if len(config.SchedulerConfigFile) > 0 {
validationResults.AddErrors(ValidateFile(config.SchedulerConfigFile, fldPath.Child("schedulerConfigFile"))...)
}
for i, nodeName := range config.StaticNodeNames {
if len(nodeName) == 0 {
validationResults.AddErrors(field.Invalid(fldPath.Child("staticNodeName").Index(i), nodeName, "may not be empty"))
} else {
validationResults.AddWarnings(field.Invalid(fldPath.Child("staticNodeName").Index(i), nodeName, "static nodes are not supported"))
}
}
if len(config.PodEvictionTimeout) > 0 {
if _, err := time.ParseDuration(config.PodEvictionTimeout); err != nil {
validationResults.AddErrors(field.Invalid(fldPath.Child("podEvictionTimeout"), config.PodEvictionTimeout, "must be a valid time duration string (e.g. '300ms' or '2m30s'). Valid time units are 'ns', 'us', 'ms', 's', 'm', 'h'"))
}
}
for group, versions := range config.DisabledAPIGroupVersions {
keyPath := fldPath.Child("disabledAPIGroupVersions").Key(group)
if !api.KnownKubeAPIGroups.Has(group) {
validationResults.AddWarnings(field.NotSupported(keyPath, group, api.KnownKubeAPIGroups.List()))
continue
}
allowedVersions := sets.NewString(api.KubeAPIGroupsToAllowedVersions[group]...)
for i, version := range versions {
if version == "*" {
continue
}
if !allowedVersions.Has(version) {
validationResults.AddWarnings(field.NotSupported(keyPath.Index(i), version, allowedVersions.List()))
}
}
}
if config.AdmissionConfig.PluginConfig != nil {
validationResults.AddErrors(ValidateAdmissionPluginConfig(config.AdmissionConfig.PluginConfig, fldPath.Child("admissionConfig", "pluginConfig"))...)
}
validationResults.AddErrors(ValidateAPIServerExtendedArguments(config.APIServerArguments, fldPath.Child("apiServerArguments"))...)
validationResults.AddErrors(ValidateControllerExtendedArguments(config.ControllerArguments, fldPath.Child("controllerArguments"))...)
return validationResults
}
func ValidatePolicyConfig(config api.PolicyConfig, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, ValidateFile(config.BootstrapPolicyFile, fldPath.Child("bootstrapPolicyFile"))...)
allErrs = append(allErrs, ValidateNamespace(config.OpenShiftSharedResourcesNamespace, fldPath.Child("openShiftSharedResourcesNamespace"))...)
allErrs = append(allErrs, ValidateNamespace(config.OpenShiftInfrastructureNamespace, fldPath.Child("openShiftInfrastructureNamespace"))...)
for i, matchingRule := range config.UserAgentMatchingConfig.DeniedClients {
_, err := regexp.Compile(matchingRule.Regex)
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("userAgentMatchingConfig", "deniedClients").Index(i), matchingRule.Regex, err.Error()))
}
}
for i, matchingRule := range config.UserAgentMatchingConfig.RequiredClients {
_, err := regexp.Compile(matchingRule.Regex)
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("userAgentMatchingConfig", "requiredClients").Index(i), matchingRule.Regex, err.Error()))
}
}
return allErrs
}
func ValidateProjectConfig(config api.ProjectConfig, fldPath *field.Path) ValidationResults {
validationResults := ValidationResults{}
if _, _, err := api.ParseNamespaceAndName(config.ProjectRequestTemplate); err != nil {
validationResults.AddErrors(field.Invalid(fldPath.Child("projectRequestTemplate"), config.ProjectRequestTemplate, "must be in the form: namespace/templateName"))
}
if len(config.DefaultNodeSelector) > 0 {
_, err := labelselector.Parse(config.DefaultNodeSelector)
if err != nil {
validationResults.AddErrors(field.Invalid(fldPath.Child("defaultNodeSelector"), config.DefaultNodeSelector, "must be a valid label selector"))
}
}
if alloc := config.SecurityAllocator; alloc != nil {
securityAllocatorPath := fldPath.Child("securityAllocator")
if _, err := uid.ParseRange(alloc.UIDAllocatorRange); err != nil {
validationResults.AddErrors(field.Invalid(securityAllocatorPath.Child("uidAllocatorRange"), alloc.UIDAllocatorRange, err.Error()))
}
if _, err := mcs.ParseRange(alloc.MCSAllocatorRange); err != nil {
validationResults.AddErrors(field.Invalid(securityAllocatorPath.Child("mcsAllocatorRange"), alloc.MCSAllocatorRange, err.Error()))
}
if alloc.MCSLabelsPerProject <= 0 {
validationResults.AddErrors(field.Invalid(securityAllocatorPath.Child("mcsLabelsPerProject"), alloc.MCSLabelsPerProject, "must be a positive integer"))
}
} else {
validationResults.AddWarnings(field.Invalid(fldPath.Child("securityAllocator"), "null", "allocation of UIDs and MCS labels to a project must be done manually"))
}
return validationResults
}
func ValidateRoutingConfig(config api.RoutingConfig, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(config.Subdomain) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("subdomain"), ""))
} else if !kuval.IsDNS1123Subdomain(config.Subdomain) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("subdomain"), config.Subdomain, "must be a valid subdomain"))
}
return allErrs
}
func ValidateAPIServerExtendedArguments(config api.ExtendedArguments, fldPath *field.Path) field.ErrorList {
return ValidateExtendedArguments(config, apiserveroptions.NewAPIServer().AddFlags, fldPath)
}
func ValidateControllerExtendedArguments(config api.ExtendedArguments, fldPath *field.Path) field.ErrorList {
return ValidateExtendedArguments(config, controlleroptions.NewCMServer().AddFlags, fldPath)
}
func ValidateAdmissionPluginConfig(pluginConfig map[string]api.AdmissionPluginConfig, fieldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
for name, config := range pluginConfig {
if len(config.Location) > 0 && config.Configuration != nil {
allErrs = append(allErrs, field.Invalid(fieldPath.Key(name), "", "cannot specify both location and embedded config"))
}
if len(config.Location) == 0 && config.Configuration == nil {
allErrs = append(allErrs, field.Invalid(fieldPath.Key(name), "", "must specify either a location or an embedded config"))
}
}
return allErrs
}