package validation
import (
"fmt"
"net"
"net/url"
"os"
"strings"
"github.com/spf13/pflag"
kvalidation "k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/util/fielderrors"
"k8s.io/kubernetes/pkg/util/sets"
"github.com/openshift/origin/pkg/cmd/server/api"
cmdflags "github.com/openshift/origin/pkg/cmd/util/flags"
)
func ValidateHostPort(value string, field string) fielderrors.ValidationErrorList {
allErrs := fielderrors.ValidationErrorList{}
if len(value) == 0 {
allErrs = append(allErrs, fielderrors.NewFieldRequired(field))
} else if _, _, err := net.SplitHostPort(value); err != nil {
allErrs = append(allErrs, fielderrors.NewFieldInvalid(field, value, "must be a host:port"))
}
return allErrs
}
func ValidateCertInfo(certInfo api.CertInfo, required bool) fielderrors.ValidationErrorList {
allErrs := fielderrors.ValidationErrorList{}
if required || len(certInfo.CertFile) > 0 || len(certInfo.KeyFile) > 0 {
if len(certInfo.CertFile) == 0 {
allErrs = append(allErrs, fielderrors.NewFieldRequired("certFile"))
}
if len(certInfo.KeyFile) == 0 {
allErrs = append(allErrs, fielderrors.NewFieldRequired("keyFile"))
}
}
if len(certInfo.CertFile) > 0 {
allErrs = append(allErrs, ValidateFile(certInfo.CertFile, "certFile")...)
}
if len(certInfo.KeyFile) > 0 {
allErrs = append(allErrs, ValidateFile(certInfo.KeyFile, "keyFile")...)
}
return allErrs
}
func ValidateServingInfo(info api.ServingInfo) fielderrors.ValidationErrorList {
allErrs := fielderrors.ValidationErrorList{}
allErrs = append(allErrs, ValidateHostPort(info.BindAddress, "bindAddress")...)
allErrs = append(allErrs, ValidateCertInfo(info.ServerCert, false)...)
switch info.BindNetwork {
case "tcp", "tcp4", "tcp6":
default:
allErrs = append(allErrs, fielderrors.NewFieldInvalid("bindNetwork", info.BindNetwork, "must be 'tcp', 'tcp4', or 'tcp6'"))
}
if len(info.ServerCert.CertFile) > 0 {
if len(info.ClientCA) > 0 {
allErrs = append(allErrs, ValidateFile(info.ClientCA, "clientCA")...)
}
} else {
if len(info.ClientCA) > 0 {
allErrs = append(allErrs, fielderrors.NewFieldInvalid("clientCA", info.ClientCA, "cannot specify a clientCA without a certFile"))
}
}
return allErrs
}
func ValidateHTTPServingInfo(info api.HTTPServingInfo) fielderrors.ValidationErrorList {
allErrs := ValidateServingInfo(info.ServingInfo)
if info.MaxRequestsInFlight < 0 {
allErrs = append(allErrs, fielderrors.NewFieldInvalid("maxRequestsInFlight", info.MaxRequestsInFlight, "must be zero (no limit) or greater"))
}
if info.RequestTimeoutSeconds < -1 {
allErrs = append(allErrs, fielderrors.NewFieldInvalid("requestTimeoutSeconds", info.RequestTimeoutSeconds, "must be -1 (no timeout), 0 (default timeout), or greater"))
}
return allErrs
}
func ValidateDisabledFeatures(disabledFeatures []string, field string) fielderrors.ValidationErrorList {
allErrs := fielderrors.ValidationErrorList{}
known := sets.NewString()
for _, feature := range api.KnownOpenShiftFeatures {
known.Insert(strings.ToLower(feature))
}
for i, feature := range disabledFeatures {
if !known.Has(strings.ToLower(feature)) {
allErrs = append(allErrs, fielderrors.NewFieldInvalid(fmt.Sprintf("%s[%d]", field, i), disabledFeatures[i], fmt.Sprintf("not one of valid features: %s", strings.Join(api.KnownOpenShiftFeatures, ", "))))
}
}
return allErrs
}
func ValidateKubeConfig(path string, field string) fielderrors.ValidationErrorList {
allErrs := fielderrors.ValidationErrorList{}
allErrs = append(allErrs, ValidateFile(path, field)...)
// TODO: load and parse
return allErrs
}
func ValidateRemoteConnectionInfo(remoteConnectionInfo api.RemoteConnectionInfo) fielderrors.ValidationErrorList {
allErrs := fielderrors.ValidationErrorList{}
if len(remoteConnectionInfo.URL) == 0 {
allErrs = append(allErrs, fielderrors.NewFieldRequired("url"))
} else {
_, urlErrs := ValidateURL(remoteConnectionInfo.URL, "url")
allErrs = append(allErrs, urlErrs...)
}
if len(remoteConnectionInfo.CA) > 0 {
allErrs = append(allErrs, ValidateFile(remoteConnectionInfo.CA, "ca")...)
}
allErrs = append(allErrs, ValidateCertInfo(remoteConnectionInfo.ClientCert, false)...)
return allErrs
}
func ValidatePodManifestConfig(podManifestConfig *api.PodManifestConfig) fielderrors.ValidationErrorList {
allErrs := fielderrors.ValidationErrorList{}
// the Path can be a file or a directory
allErrs = append(allErrs, ValidateFile(podManifestConfig.Path, "path")...)
if podManifestConfig.FileCheckIntervalSeconds < 1 {
allErrs = append(allErrs, fielderrors.NewFieldInvalid("fileCheckIntervalSeconds", podManifestConfig.FileCheckIntervalSeconds, "interval has to be positive"))
}
return allErrs
}
func ValidateSpecifiedIP(ipString string, field string) fielderrors.ValidationErrorList {
allErrs := fielderrors.ValidationErrorList{}
ip := net.ParseIP(ipString)
if ip == nil {
allErrs = append(allErrs, fielderrors.NewFieldInvalid(field, ipString, "must be a valid IP"))
} else if ip.IsUnspecified() {
allErrs = append(allErrs, fielderrors.NewFieldInvalid(field, ipString, "cannot be an unspecified IP"))
}
return allErrs
}
func ValidateSecureURL(urlString string, field string) (*url.URL, fielderrors.ValidationErrorList) {
url, urlErrs := ValidateURL(urlString, field)
if len(urlErrs) == 0 && url.Scheme != "https" {
urlErrs = append(urlErrs, fielderrors.NewFieldInvalid(field, urlString, "must use https scheme"))
}
return url, urlErrs
}
func ValidateURL(urlString string, field string) (*url.URL, fielderrors.ValidationErrorList) {
allErrs := fielderrors.ValidationErrorList{}
urlObj, err := url.Parse(urlString)
if err != nil {
allErrs = append(allErrs, fielderrors.NewFieldInvalid(field, urlString, "must be a valid URL"))
return nil, allErrs
}
if len(urlObj.Scheme) == 0 {
allErrs = append(allErrs, fielderrors.NewFieldInvalid(field, urlString, "must contain a scheme (e.g. https://)"))
}
if len(urlObj.Host) == 0 {
allErrs = append(allErrs, fielderrors.NewFieldInvalid(field, urlString, "must contain a host"))
}
return urlObj, allErrs
}
func ValidateNamespace(namespace, field string) fielderrors.ValidationErrorList {
allErrs := fielderrors.ValidationErrorList{}
if len(namespace) == 0 {
allErrs = append(allErrs, fielderrors.NewFieldRequired(field))
} else if ok, _ := kvalidation.ValidateNamespaceName(namespace, false); !ok {
allErrs = append(allErrs, fielderrors.NewFieldInvalid(field, namespace, "must be a valid namespace"))
}
return allErrs
}
func ValidateFile(path string, field string) fielderrors.ValidationErrorList {
allErrs := fielderrors.ValidationErrorList{}
if len(path) == 0 {
allErrs = append(allErrs, fielderrors.NewFieldRequired(field))
} else if _, err := os.Stat(path); err != nil {
allErrs = append(allErrs, fielderrors.NewFieldInvalid(field, path, "could not read file"))
}
return allErrs
}
func ValidateDir(path string, field string) fielderrors.ValidationErrorList {
allErrs := fielderrors.ValidationErrorList{}
if len(path) == 0 {
allErrs = append(allErrs, fielderrors.NewFieldRequired(field))
} else {
fileInfo, err := os.Stat(path)
if err != nil {
allErrs = append(allErrs, fielderrors.NewFieldInvalid(field, path, "could not read info"))
} else if !fileInfo.IsDir() {
allErrs = append(allErrs, fielderrors.NewFieldInvalid(field, path, "not a directory"))
}
}
return allErrs
}
func ValidateExtendedArguments(config api.ExtendedArguments, flagFunc func(*pflag.FlagSet)) fielderrors.ValidationErrorList {
allErrs := fielderrors.ValidationErrorList{}
// check extended arguments for errors
for _, err := range cmdflags.Resolve(config, flagFunc) {
switch t := err.(type) {
case *fielderrors.ValidationError:
allErrs = append(allErrs, t)
default:
allErrs = append(allErrs, fielderrors.NewFieldInvalid("????", config, err.Error()))
}
}
return allErrs
}