package validation import ( "fmt" "net/url" "path" "path/filepath" "strings" "github.com/golang/glog" kapi "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util/strategicpatch" kvalidation "k8s.io/kubernetes/pkg/util/validation" "k8s.io/kubernetes/pkg/util/validation/field" oapi "github.com/openshift/origin/pkg/api" buildapi "github.com/openshift/origin/pkg/build/api" "github.com/openshift/origin/pkg/build/api/v1" buildutil "github.com/openshift/origin/pkg/build/util" imageapi "github.com/openshift/origin/pkg/image/api" ) // ValidateBuild tests required fields for a Build. func ValidateBuild(build *buildapi.Build) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, validation.ValidateObjectMeta(&build.ObjectMeta, true, validation.NameIsDNSSubdomain, field.NewPath("metadata"))...) allErrs = append(allErrs, validateCommonSpec(&build.Spec.CommonSpec, field.NewPath("spec"))...) return allErrs } func ValidateBuildUpdate(build *buildapi.Build, older *buildapi.Build) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&build.ObjectMeta, &older.ObjectMeta, field.NewPath("metadata"))...) allErrs = append(allErrs, ValidateBuild(build)...) if buildutil.IsBuildComplete(older) && older.Status.Phase != build.Status.Phase { allErrs = append(allErrs, field.Invalid(field.NewPath("status", "phase"), build.Status.Phase, "phase cannot be updated from a terminal state")) } if !kapi.Semantic.DeepEqual(build.Spec, older.Spec) { diff, err := diffBuildSpec(build.Spec, older.Spec) if err != nil { glog.V(2).Infof("Error calculating build spec patch: %v", err) diff = "[unknown]" } detail := fmt.Sprintf("spec is immutable, diff: %s", diff) allErrs = append(allErrs, field.Invalid(field.NewPath("spec"), "content of spec is not printed out, please refer to the details", detail)) } return allErrs } func diffBuildSpec(newer buildapi.BuildSpec, older buildapi.BuildSpec) (string, error) { codec := kapi.Codecs.LegacyCodec(v1.SchemeGroupVersion) newerObj := &buildapi.Build{Spec: newer} olderObj := &buildapi.Build{Spec: older} newerJSON, err := runtime.Encode(codec, newerObj) if err != nil { return "", fmt.Errorf("error encoding newer: %v", err) } olderJSON, err := runtime.Encode(codec, olderObj) if err != nil { return "", fmt.Errorf("error encoding older: %v", err) } patch, err := strategicpatch.CreateTwoWayMergePatch(olderJSON, newerJSON, &v1.Build{}) if err != nil { return "", fmt.Errorf("error creating a strategic patch: %v", err) } return string(patch), nil } // refKey returns a key for the given ObjectReference. If the ObjectReference // doesn't include a namespace, the passed in namespace is used for the reference func refKey(namespace string, ref *kapi.ObjectReference) string { if ref == nil || ref.Kind != "ImageStreamTag" { return "nil" } ns := ref.Namespace if ns == "" { ns = namespace } return fmt.Sprintf("%s/%s", ns, ref.Name) } // ValidateBuildConfig tests required fields for a Build. func ValidateBuildConfig(config *buildapi.BuildConfig) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, validation.ValidateObjectMeta(&config.ObjectMeta, true, validation.NameIsDNSSubdomain, field.NewPath("metadata"))...) // image change triggers that refer fromRefs := map[string]struct{}{} specPath := field.NewPath("spec") triggersPath := specPath.Child("triggers") buildFrom := buildutil.GetInputReference(config.Spec.Strategy) for i, trg := range config.Spec.Triggers { allErrs = append(allErrs, validateTrigger(&trg, buildFrom, triggersPath.Index(i))...) if trg.Type != buildapi.ImageChangeBuildTriggerType || trg.ImageChange == nil { continue } from := trg.ImageChange.From if from == nil { from = buildFrom } fromKey := refKey(config.Namespace, from) _, exists := fromRefs[fromKey] if exists { allErrs = append(allErrs, field.Invalid(triggersPath, config.Spec.Triggers, "multiple ImageChange triggers refer to the same image stream tag")) } fromRefs[fromKey] = struct{}{} } switch config.Spec.RunPolicy { case buildapi.BuildRunPolicyParallel, buildapi.BuildRunPolicySerial, buildapi.BuildRunPolicySerialLatestOnly: default: allErrs = append(allErrs, field.Invalid(specPath.Child("runPolicy"), config.Spec.RunPolicy, "run policy must Parallel, Serial, or SerialLatestOnly")) } allErrs = append(allErrs, validateCommonSpec(&config.Spec.CommonSpec, specPath)...) return allErrs } func ValidateBuildConfigUpdate(config *buildapi.BuildConfig, older *buildapi.BuildConfig) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&config.ObjectMeta, &older.ObjectMeta, field.NewPath("metadata"))...) allErrs = append(allErrs, ValidateBuildConfig(config)...) return allErrs } // ValidateBuildRequest validates a BuildRequest object func ValidateBuildRequest(request *buildapi.BuildRequest) field.ErrorList { return validation.ValidateObjectMeta(&request.ObjectMeta, true, oapi.MinimalNameRequirements, field.NewPath("metadata")) } func validateCommonSpec(spec *buildapi.CommonSpec, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} s := spec.Strategy if s.DockerStrategy != nil && s.JenkinsPipelineStrategy == nil && spec.Source.Git == nil && spec.Source.Binary == nil && spec.Source.Dockerfile == nil && spec.Source.Images == nil { allErrs = append(allErrs, field.Invalid(fldPath.Child("source"), spec.Source, "must provide a value for at least one of source, binary, images, or dockerfile")) } allErrs = append(allErrs, validateSource( &spec.Source, s.CustomStrategy != nil, s.DockerStrategy != nil, s.JenkinsPipelineStrategy != nil && len(s.JenkinsPipelineStrategy.Jenkinsfile) == 0, fldPath.Child("source"))..., ) if spec.CompletionDeadlineSeconds != nil { if *spec.CompletionDeadlineSeconds <= 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("completionDeadlineSeconds"), spec.CompletionDeadlineSeconds, "completionDeadlineSeconds must be a positive integer greater than 0")) } } allErrs = append(allErrs, validateOutput(&spec.Output, fldPath.Child("output"))...) allErrs = append(allErrs, validateStrategy(&spec.Strategy, fldPath.Child("strategy"))...) allErrs = append(allErrs, validatePostCommit(spec.PostCommit, fldPath.Child("postCommit"))...) // TODO: validate resource requirements (prereq: https://github.com/kubernetes/kubernetes/pull/7059) return allErrs } const ( maxDockerfileLengthBytes = 60 * 1000 maxJenkinsfileLengthBytes = 100 * 1000 ) func hasProxy(source *buildapi.GitBuildSource) bool { return (source.HTTPProxy != nil && len(*source.HTTPProxy) > 0) || (source.HTTPSProxy != nil && len(*source.HTTPSProxy) > 0) } func validateSource(input *buildapi.BuildSource, isCustomStrategy, isDockerStrategy, isJenkinsPipelineStrategyFromRepo bool, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} // Ensure that Git and Binary source types are mutually exclusive. if input.Git != nil && input.Binary != nil && !isCustomStrategy { allErrs = append(allErrs, field.Invalid(fldPath.Child("git"), "", "may not be set when binary is also set")) allErrs = append(allErrs, field.Invalid(fldPath.Child("binary"), "", "may not be set when git is also set")) return allErrs } // Validate individual source type details if input.Git != nil { allErrs = append(allErrs, validateGitSource(input.Git, fldPath.Child("git"))...) } if input.Binary != nil { allErrs = append(allErrs, validateBinarySource(input.Binary, fldPath.Child("binary"))...) } if input.Dockerfile != nil { allErrs = append(allErrs, validateDockerfile(*input.Dockerfile, fldPath.Child("dockerfile"))...) } if input.Images != nil { for i, image := range input.Images { allErrs = append(allErrs, validateImageSource(image, fldPath.Child("images").Index(i))...) } } if isJenkinsPipelineStrategyFromRepo && input.Git == nil { allErrs = append(allErrs, field.Invalid(fldPath.Child("git"), "", "must be set when using Jenkins Pipeline strategy with Jenkinsfile from a git repo")) } allErrs = append(allErrs, validateSecrets(input.Secrets, isDockerStrategy, fldPath.Child("secrets"))...) allErrs = append(allErrs, validateSecretRef(input.SourceSecret, fldPath.Child("sourceSecret"))...) if len(input.ContextDir) != 0 { cleaned := path.Clean(input.ContextDir) if strings.HasPrefix(cleaned, "..") { allErrs = append(allErrs, field.Invalid(fldPath.Child("contextDir"), input.ContextDir, "context dir must not be a relative path")) } else { if cleaned == "." { cleaned = "" } input.ContextDir = cleaned } } return allErrs } func validateDockerfile(dockerfile string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if len(dockerfile) > maxDockerfileLengthBytes { allErrs = append(allErrs, field.Invalid(fldPath, "", fmt.Sprintf("must be smaller than %d bytes", maxDockerfileLengthBytes))) } return allErrs } func validateSecretRef(ref *kapi.LocalObjectReference, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if ref == nil { return allErrs } if len(ref.Name) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) } return allErrs } func isHTTPScheme(in string) bool { u, err := url.Parse(in) if err != nil { return false } return u.Scheme == "http" || u.Scheme == "https" } func validateGitSource(git *buildapi.GitBuildSource, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if len(git.URI) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("uri"), "")) } else if !IsValidURL(git.URI) { allErrs = append(allErrs, field.Invalid(fldPath.Child("uri"), git.URI, "uri is not a valid url")) } if git.HTTPProxy != nil && len(*git.HTTPProxy) != 0 && !IsValidURL(*git.HTTPProxy) { allErrs = append(allErrs, field.Invalid(fldPath.Child("httpproxy"), *git.HTTPProxy, "proxy is not a valid url")) } if git.HTTPSProxy != nil && len(*git.HTTPSProxy) != 0 && !IsValidURL(*git.HTTPSProxy) { allErrs = append(allErrs, field.Invalid(fldPath.Child("httpsproxy"), *git.HTTPSProxy, "proxy is not a valid url")) } if hasProxy(git) && !isHTTPScheme(git.URI) { allErrs = append(allErrs, field.Invalid(fldPath.Child("uri"), git.URI, "only http:// and https:// GIT protocols are allowed with HTTP or HTTPS proxy set")) } return allErrs } func validateSecrets(secrets []buildapi.SecretBuildSource, isDockerStrategy bool, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} for i, s := range secrets { if len(s.Secret.Name) == 0 { allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("secret"), "")) } if reasons := validation.ValidateSecretName(s.Secret.Name, false); len(reasons) != 0 { allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("secret"), s, "must be valid secret name")) } if strings.HasPrefix(path.Clean(s.DestinationDir), "..") { allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("destinationDir"), s.DestinationDir, "destination dir cannot start with '..'")) } if isDockerStrategy && filepath.IsAbs(s.DestinationDir) { allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("destinationDir"), s.DestinationDir, "for the docker strategy the destinationDir has to be relative path")) } } return allErrs } func validateImageSource(imageSource buildapi.ImageSource, fldPath *field.Path) field.ErrorList { allErrs := validateFromImageReference(&imageSource.From, fldPath.Child("from")) if imageSource.PullSecret != nil { allErrs = append(allErrs, validateSecretRef(imageSource.PullSecret, fldPath.Child("pullSecret"))...) } if len(imageSource.Paths) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("paths"), "")) } for i, path := range imageSource.Paths { allErrs = append(allErrs, validateImageSourcePath(path, fldPath.Child("paths").Index(i))...) } return allErrs } func validateImageSourcePath(imagePath buildapi.ImageSourcePath, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if len(imagePath.SourcePath) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("sourcePath"), "")) } if len(imagePath.DestinationDir) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("destinationDir"), "")) } if !filepath.IsAbs(imagePath.SourcePath) { allErrs = append(allErrs, field.Invalid(fldPath.Child("sourcePath"), imagePath.SourcePath, "must be an absolute path")) } if filepath.IsAbs(imagePath.DestinationDir) { allErrs = append(allErrs, field.Invalid(fldPath.Child("destinationDir"), imagePath.DestinationDir, "must be a relative path")) } if strings.HasPrefix(path.Clean(imagePath.DestinationDir), "..") { allErrs = append(allErrs, field.Invalid(fldPath.Child("destinationDir"), imagePath.DestinationDir, "destination dir cannot start with '..'")) } return allErrs } func validateBinarySource(source *buildapi.BinaryBuildSource, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if len(source.AsFile) != 0 { cleaned := strings.TrimPrefix(path.Clean(source.AsFile), "/") if len(cleaned) == 0 || cleaned == "." || strings.HasPrefix(cleaned, "..") || strings.Contains(cleaned, "/") || strings.Contains(cleaned, "\\") { allErrs = append(allErrs, field.Invalid(fldPath.Child("asFile"), source.AsFile, "file name may not contain slashes or relative path segments and must be a valid POSIX filename")) } else { source.AsFile = cleaned } } return allErrs } func validateToImageReference(reference *kapi.ObjectReference, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} kind, name, namespace := reference.Kind, reference.Name, reference.Namespace switch kind { case "ImageStreamTag": if len(name) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) } else if _, _, ok := imageapi.SplitImageStreamTag(name); !ok { allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), name, "ImageStreamTag object references must be in the form <name>:<tag>")) } if len(namespace) != 0 && len(kvalidation.IsDNS1123Subdomain(namespace)) != 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), namespace, "namespace must be a valid subdomain")) } case "DockerImage": if len(namespace) != 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), namespace, "namespace is not valid when used with a 'DockerImage'")) } if _, err := imageapi.ParseDockerImageReference(name); err != nil { allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), name, fmt.Sprintf("name is not a valid Docker pull specification: %v", err))) } case "": allErrs = append(allErrs, field.Required(fldPath.Child("kind"), "")) default: allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), kind, "the target of build output must be an 'ImageStreamTag' or 'DockerImage'")) } return allErrs } func validateFromImageReference(reference *kapi.ObjectReference, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} kind, name, namespace := reference.Kind, reference.Name, reference.Namespace switch kind { case "ImageStreamTag": if len(name) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) } else if _, _, ok := imageapi.SplitImageStreamTag(name); !ok { allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), name, "ImageStreamTag object references must be in the form <name>:<tag>")) } if len(namespace) != 0 && len(kvalidation.IsDNS1123Subdomain(namespace)) != 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), namespace, "namespace must be a valid subdomain")) } case "DockerImage": if len(namespace) != 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), namespace, "namespace is not valid when used with a 'DockerImage'")) } if len(name) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) } else if _, err := imageapi.ParseDockerImageReference(name); err != nil { allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), name, fmt.Sprintf("name is not a valid Docker pull specification: %v", err))) } case "ImageStreamImage": if len(name) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) } if len(namespace) != 0 && len(kvalidation.IsDNS1123Subdomain(namespace)) != 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), namespace, "namespace must be a valid subdomain")) } case "": allErrs = append(allErrs, field.Required(fldPath.Child("kind"), "")) default: allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), kind, "the source of a builder image must be an 'ImageStreamTag', 'ImageStreamImage', or 'DockerImage'")) } return allErrs } func validateOutput(output *buildapi.BuildOutput, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} // TODO: make part of a generic ValidateObjectReference method upstream. if output.To != nil { allErrs = append(allErrs, validateToImageReference(output.To, fldPath.Child("to"))...) } allErrs = append(allErrs, validateSecretRef(output.PushSecret, fldPath.Child("pushSecret"))...) return allErrs } func validateStrategy(strategy *buildapi.BuildStrategy, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} strategyCount := 0 if strategy.SourceStrategy != nil { strategyCount++ } if strategy.DockerStrategy != nil { strategyCount++ } if strategy.CustomStrategy != nil { strategyCount++ } if strategy.JenkinsPipelineStrategy != nil { strategyCount++ } if strategyCount != 1 { return append(allErrs, field.Invalid(fldPath, strategy, "must provide a value for exactly one of sourceStrategy, customStrategy, dockerStrategy, or jenkinsPipelineStrategy")) } if strategy.SourceStrategy != nil { allErrs = append(allErrs, validateSourceStrategy(strategy.SourceStrategy, fldPath.Child("sourceStrategy"))...) } if strategy.DockerStrategy != nil { allErrs = append(allErrs, validateDockerStrategy(strategy.DockerStrategy, fldPath.Child("dockerStrategy"))...) } if strategy.CustomStrategy != nil { allErrs = append(allErrs, validateCustomStrategy(strategy.CustomStrategy, fldPath.Child("customStrategy"))...) } if strategy.JenkinsPipelineStrategy != nil { allErrs = append(allErrs, validateJenkinsPipelineStrategy(strategy.JenkinsPipelineStrategy, fldPath.Child("jenkinsPipelineStrategy"))...) } return allErrs } func validateDockerStrategy(strategy *buildapi.DockerBuildStrategy, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if strategy.From != nil { allErrs = append(allErrs, validateFromImageReference(strategy.From, fldPath.Child("from"))...) } allErrs = append(allErrs, validateSecretRef(strategy.PullSecret, fldPath.Child("pullSecret"))...) if len(strategy.DockerfilePath) != 0 { cleaned, errs := validateRelativePath(strategy.DockerfilePath, "dockerfilePath", fldPath.Child("dockerfilePath")) allErrs = append(allErrs, errs...) if len(errs) == 0 { strategy.DockerfilePath = cleaned } } allErrs = append(allErrs, ValidateStrategyEnv(strategy.Env, fldPath.Child("env"))...) return allErrs } func validateRelativePath(filePath, fieldName string, fldPath *field.Path) (string, field.ErrorList) { allErrs := field.ErrorList{} cleaned := path.Clean(filePath) switch { case filepath.IsAbs(cleaned), cleaned == "..", strings.HasPrefix(cleaned, "../"): allErrs = append(allErrs, field.Invalid(fldPath, filePath, fieldName+" must be a relative path within your source location")) default: if cleaned == "." { cleaned = "" } } return cleaned, allErrs } func validateSourceStrategy(strategy *buildapi.SourceBuildStrategy, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, validateFromImageReference(&strategy.From, fldPath.Child("from"))...) allErrs = append(allErrs, validateSecretRef(strategy.PullSecret, fldPath.Child("pullSecret"))...) allErrs = append(allErrs, ValidateStrategyEnv(strategy.Env, fldPath.Child("env"))...) return allErrs } func validateCustomStrategy(strategy *buildapi.CustomBuildStrategy, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, validateFromImageReference(&strategy.From, fldPath.Child("from"))...) allErrs = append(allErrs, validateSecretRef(strategy.PullSecret, fldPath.Child("pullSecret"))...) allErrs = append(allErrs, ValidateStrategyEnv(strategy.Env, fldPath.Child("env"))...) return allErrs } func validateJenkinsPipelineStrategy(strategy *buildapi.JenkinsPipelineBuildStrategy, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if len(strategy.JenkinsfilePath) != 0 && len(strategy.Jenkinsfile) != 0 { return append(allErrs, field.Invalid(fldPath, strategy, "must provide a value for at most one of jenkinsfilePath, or jenkinsfile")) } if len(strategy.Jenkinsfile) > maxJenkinsfileLengthBytes { allErrs = append(allErrs, field.Invalid(fldPath.Child("jenkinsfile"), "", fmt.Sprintf("must be smaller than %d bytes", maxJenkinsfileLengthBytes))) } if len(strategy.JenkinsfilePath) != 0 { cleaned, errs := validateRelativePath(strategy.JenkinsfilePath, "jenkinsfilePath", fldPath.Child("jenkinsfilePath")) allErrs = append(allErrs, errs...) if len(errs) == 0 { strategy.JenkinsfilePath = cleaned } } return allErrs } func validateTrigger(trigger *buildapi.BuildTriggerPolicy, buildFrom *kapi.ObjectReference, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if len(trigger.Type) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("type"), "")) return allErrs } // Validate each trigger type switch trigger.Type { case buildapi.GitHubWebHookBuildTriggerType: if trigger.GitHubWebHook == nil { allErrs = append(allErrs, field.Required(fldPath.Child("github"), "")) } else { allErrs = append(allErrs, validateWebHook(trigger.GitHubWebHook, fldPath.Child("github"), false)...) } case buildapi.GenericWebHookBuildTriggerType: if trigger.GenericWebHook == nil { allErrs = append(allErrs, field.Required(fldPath.Child("generic"), "")) } else { allErrs = append(allErrs, validateWebHook(trigger.GenericWebHook, fldPath.Child("generic"), true)...) } case buildapi.ImageChangeBuildTriggerType: if trigger.ImageChange == nil { allErrs = append(allErrs, field.Required(fldPath.Child("imageChange"), "")) break } if trigger.ImageChange.From == nil { if buildFrom == nil || buildFrom.Kind != "ImageStreamTag" { invalidKindErr := field.Invalid( fldPath.Child("imageChange"), fmt.Sprintf("build from: %v", buildFrom), "a default ImageChange trigger can only be used when the build strategy includes an ImageStreamTag reference.") allErrs = append(allErrs, invalidKindErr) break } break } if kind := trigger.ImageChange.From.Kind; kind != "ImageStreamTag" { invalidKindErr := field.Invalid( fldPath.Child("imageChange").Child("from").Child("kind"), kind, "only an ImageStreamTag type of reference is allowed in an ImageChange trigger.") allErrs = append(allErrs, invalidKindErr) break } allErrs = append(allErrs, validateFromImageReference(trigger.ImageChange.From, fldPath.Child("from"))...) case buildapi.ConfigChangeBuildTriggerType: // doesn't require additional validation default: allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), trigger.Type, "invalid trigger type")) } return allErrs } func validateWebHook(webHook *buildapi.WebHookTrigger, fldPath *field.Path, isGeneric bool) field.ErrorList { allErrs := field.ErrorList{} if len(webHook.Secret) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("secret"), "")) } if !isGeneric && webHook.AllowEnv { allErrs = append(allErrs, field.Invalid(fldPath.Child("allowEnv"), webHook, "git webhooks cannot allow env vars")) } return allErrs } func IsValidURL(uri string) bool { _, err := url.Parse(uri) return err == nil } func ValidateBuildLogOptions(opts *buildapi.BuildLogOptions) field.ErrorList { allErrs := field.ErrorList{} // TODO: Replace by validating PodLogOptions via BuildLogOptions once it's bundled in popts := buildapi.BuildToPodLogOptions(opts) if errs := validation.ValidatePodLogOptions(popts); len(errs) > 0 { allErrs = append(allErrs, errs...) } if opts.Version != nil && *opts.Version <= 0 { allErrs = append(allErrs, field.Invalid(field.NewPath("version"), *opts.Version, "build version must be greater than 0")) } if opts.Version != nil && opts.Previous { allErrs = append(allErrs, field.Invalid(field.NewPath("previous"), opts.Previous, "cannot use previous when a version is specified")) } return allErrs } const cIdentifierErrorMsg string = `must be a C identifier (matching regex ` + kvalidation.CIdentifierFmt + `): e.g. "my_name" or "MyName"` func ValidateStrategyEnv(vars []kapi.EnvVar, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} for i, ev := range vars { idxPath := fldPath.Index(i) if len(ev.Name) == 0 { allErrs = append(allErrs, field.Required(idxPath.Child("name"), "")) } else if !kvalidation.IsCIdentifier(ev.Name) { allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), ev.Name, cIdentifierErrorMsg)) } if ev.ValueFrom != nil { allErrs = append(allErrs, field.Invalid(idxPath.Child("valueFrom"), ev.ValueFrom, "valueFrom is not supported in build strategy environment variables")) } } return allErrs } func validatePostCommit(spec buildapi.BuildPostCommitSpec, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if spec.Script != "" && len(spec.Command) > 0 { allErrs = append(allErrs, field.Invalid(fldPath, spec, "cannot use command and script together")) } return allErrs }