package cmd import ( "fmt" "io" "strconv" "strings" "github.com/fsouza/go-dockerclient" "github.com/golang/glog" kapi "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/types" "k8s.io/kubernetes/pkg/util" "k8s.io/kubernetes/pkg/util/errors" buildapi "github.com/openshift/origin/pkg/build/api" "github.com/openshift/origin/pkg/client" cmdutil "github.com/openshift/origin/pkg/cmd/util" "github.com/openshift/origin/pkg/dockerregistry" "github.com/openshift/origin/pkg/generate/app" "github.com/openshift/origin/pkg/generate/dockerfile" "github.com/openshift/origin/pkg/generate/source" imageapi "github.com/openshift/origin/pkg/image/api" "github.com/openshift/origin/pkg/template" "github.com/openshift/origin/pkg/util/namer" ) // AppConfig contains all the necessary configuration for an application type AppConfig struct { SourceRepositories util.StringList ContextDir string Components util.StringList ImageStreams util.StringList DockerImages util.StringList Templates util.StringList TemplateFiles util.StringList TemplateParameters util.StringList Groups util.StringList Environment util.StringList Labels map[string]string Name string Strategy string InsecureRegistry bool OutputDocker bool AsSearch bool AsList bool refBuilder *app.ReferenceBuilder dockerSearcher app.Searcher imageStreamSearcher app.Searcher imageStreamByAnnotationSearcher app.Searcher templateSearcher app.Searcher templateFileSearcher app.Searcher detector app.Detector typer runtime.ObjectTyper mapper meta.RESTMapper clientMapper resource.ClientMapper osclient client.Interface originNamespace string } // UsageError is an interface for printing usage errors type UsageError interface { UsageError(commandName string) string } // TODO: replace with upstream converting [1]error to error type errlist interface { Errors() []error } // NewAppConfig returns a new AppConfig func NewAppConfig(typer runtime.ObjectTyper, mapper meta.RESTMapper, clientMapper resource.ClientMapper) *AppConfig { dockerSearcher := app.DockerRegistrySearcher{ Client: dockerregistry.NewClient(), } return &AppConfig{ detector: app.SourceRepositoryEnumerator{ Detectors: source.DefaultDetectors, Tester: dockerfile.NewTester(), }, dockerSearcher: dockerSearcher, typer: typer, mapper: mapper, clientMapper: clientMapper, refBuilder: &app.ReferenceBuilder{}, } } func (c *AppConfig) dockerRegistrySearcher() app.Searcher { return app.DockerRegistrySearcher{ Client: dockerregistry.NewClient(), AllowInsecure: c.InsecureRegistry, } } func (c *AppConfig) ensureDockerSearcher() { if c.dockerSearcher == nil { c.dockerSearcher = c.dockerRegistrySearcher() } } // SetDockerClient sets the passed Docker client in the application configuration func (c *AppConfig) SetDockerClient(dockerclient *docker.Client) { c.dockerSearcher = app.DockerClientSearcher{ Client: dockerclient, RegistrySearcher: c.dockerRegistrySearcher(), Insecure: c.InsecureRegistry, } } // SetOpenShiftClient sets the passed OpenShift client in the application configuration func (c *AppConfig) SetOpenShiftClient(osclient client.Interface, originNamespace string) { c.osclient = osclient c.originNamespace = originNamespace namespaces := []string{originNamespace} if openshiftNamespace := "openshift"; originNamespace != openshiftNamespace { namespaces = append(namespaces, openshiftNamespace) } c.imageStreamSearcher = app.ImageStreamSearcher{ Client: osclient, ImageStreamImages: osclient, Namespaces: namespaces, } c.imageStreamByAnnotationSearcher = app.NewImageStreamByAnnotationSearcher(osclient, osclient, namespaces) c.templateSearcher = app.TemplateSearcher{ Client: osclient, TemplateConfigsNamespacer: osclient, Namespaces: namespaces, } c.templateFileSearcher = &app.TemplateFileSearcher{ Typer: c.typer, Mapper: c.mapper, ClientMapper: c.clientMapper, Namespace: originNamespace, } } // AddArguments converts command line arguments into the appropriate bucket based on what they look like func (c *AppConfig) AddArguments(args []string) []string { unknown := []string{} for _, s := range args { switch { case cmdutil.IsEnvironmentArgument(s): c.Environment = append(c.Environment, s) case app.IsPossibleSourceRepository(s): c.SourceRepositories = append(c.SourceRepositories, s) case app.IsComponentReference(s): c.Components = append(c.Components, s) case app.IsPossibleTemplateFile(s): c.Components = append(c.Components, s) default: if len(s) == 0 { break } unknown = append(unknown, s) } } return unknown } // individualSourceRepositories collects the list of SourceRepositories specified in the // command line that are not associated with a builder using a '~'. func (c *AppConfig) individualSourceRepositories() (app.SourceRepositories, error) { first := true for _, s := range c.SourceRepositories { if repo, ok := c.refBuilder.AddSourceRepository(s); ok && first { repo.SetContextDir(c.ContextDir) first = false } } _, repos, errs := c.refBuilder.Result() return repos, errors.NewAggregate(errs) } // set up the components to be used by the reference builder func (c *AppConfig) addReferenceBuilderComponents(b *app.ReferenceBuilder) { b.AddComponents(c.DockerImages, func(input *app.ComponentInput) app.ComponentReference { input.Argument = fmt.Sprintf("--docker-image=%q", input.From) input.Searcher = c.dockerSearcher if c.dockerSearcher != nil { input.Resolver = app.UniqueExactOrInexactMatchResolver{Searcher: c.dockerSearcher} } return input }) b.AddComponents(c.ImageStreams, func(input *app.ComponentInput) app.ComponentReference { input.Argument = fmt.Sprintf("--image-stream=%q", input.From) input.Searcher = c.imageStreamSearcher if c.imageStreamSearcher != nil { input.Resolver = app.FirstMatchResolver{Searcher: c.imageStreamSearcher} } return input }) b.AddComponents(c.Templates, func(input *app.ComponentInput) app.ComponentReference { input.Argument = fmt.Sprintf("--template=%q", input.From) input.Searcher = c.templateSearcher if c.templateSearcher != nil { input.Resolver = app.HighestScoreResolver{Searcher: c.templateSearcher} } return input }) b.AddComponents(c.TemplateFiles, func(input *app.ComponentInput) app.ComponentReference { input.Argument = fmt.Sprintf("--file=%q", input.From) input.Searcher = c.templateFileSearcher if c.templateFileSearcher != nil { input.Resolver = app.FirstMatchResolver{Searcher: c.templateFileSearcher} } return input }) b.AddComponents(c.Components, func(input *app.ComponentInput) app.ComponentReference { resolver := app.PerfectMatchWeightedResolver{} searcher := app.MultiWeightedSearcher{} if c.imageStreamSearcher != nil { resolver = append(resolver, app.WeightedResolver{Searcher: c.imageStreamSearcher, Weight: 0.0}) searcher = append(searcher, app.WeightedSearcher{Searcher: c.imageStreamSearcher, Weight: 0.0}) } if c.templateSearcher != nil { resolver = append(resolver, app.WeightedResolver{Searcher: c.templateSearcher, Weight: 0.0}) searcher = append(searcher, app.WeightedSearcher{Searcher: c.templateSearcher, Weight: 0.0}) } if c.templateFileSearcher != nil { resolver = append(resolver, app.WeightedResolver{Searcher: c.templateFileSearcher, Weight: 0.0}) } if c.dockerSearcher != nil { resolver = append(resolver, app.WeightedResolver{Searcher: c.dockerSearcher, Weight: 2.0}) searcher = append(searcher, app.WeightedSearcher{Searcher: c.dockerSearcher, Weight: 1.0}) } input.Resolver = resolver input.Searcher = searcher return input }) } // validate converts all of the arguments on the config into references to objects, or returns an error func (c *AppConfig) validate() (app.ComponentReferences, app.SourceRepositories, cmdutil.Environment, cmdutil.Environment, error) { b := c.refBuilder c.addReferenceBuilderComponents(b) b.AddGroups(c.Groups) refs, repos, errs := b.Result() if len(repos) > 0 { repos[0].SetContextDir(c.ContextDir) if len(repos) > 1 { glog.Warningf("You have specified more than one source repository and a context directory. "+ "The context directory will be applied to the first repository: %q", repos[0]) } } if len(c.Strategy) != 0 && len(repos) == 0 { errs = append(errs, fmt.Errorf("when --strategy is specified you must provide at least one source code location")) } env, duplicateEnv, envErrs := cmdutil.ParseEnvironmentArguments(c.Environment) for _, s := range duplicateEnv { glog.V(1).Infof("The environment variable %q was overwritten", s) } errs = append(errs, envErrs...) parms, duplicateParms, parmsErrs := cmdutil.ParseEnvironmentArguments(c.TemplateParameters) for _, s := range duplicateParms { glog.V(1).Infof("The template parameter %q was overwritten", s) } errs = append(errs, parmsErrs...) return refs, repos, env, parms, errors.NewAggregate(errs) } // componentsForRepos creates components for repositories that have not been previously associated by a builder // these components have already gone through source code detection and have a SourceRepositoryInfo attached to them func (c *AppConfig) componentsForRepos(repositories app.SourceRepositories) (app.ComponentReferences, error) { b := c.refBuilder errs := []error{} result := app.ComponentReferences{} for _, repo := range repositories { info := repo.Info() switch { case info == nil: errs = append(errs, fmt.Errorf("source not detected for repository %q", repo)) continue case info.Dockerfile != nil && (len(c.Strategy) == 0 || c.Strategy == "docker"): dockerFrom, ok := info.Dockerfile.GetDirective("FROM") if !ok || len(dockerFrom) > 1 { errs = append(errs, fmt.Errorf("invalid FROM directive in Dockerfile in repository %q", repo)) } refs := b.AddComponents(dockerFrom, func(input *app.ComponentInput) app.ComponentReference { resolver := app.PerfectMatchWeightedResolver{} if c.imageStreamSearcher != nil { resolver = append(resolver, app.WeightedResolver{Searcher: c.imageStreamSearcher, Weight: 0.0}) } if c.dockerSearcher != nil { resolver = append(resolver, app.WeightedResolver{Searcher: c.dockerSearcher, Weight: 1.0}) } resolver = append(resolver, app.WeightedResolver{Searcher: &app.PassThroughDockerSearcher{}, Weight: 2.0}) input.Resolver = resolver input.Use(repo) input.ExpectToBuild = true repo.UsedBy(input) repo.BuildWithDocker() return input }) result = append(result, refs...) default: // TODO: Add support for searching for more than one language if len(info.Types) > 1 if len(info.Types) == 0 { errs = append(errs, fmt.Errorf("no language was detected for repository at %q; please specify a builder image to use with your repository: [builder-image]~%s", repo, repo)) continue } refs := b.AddComponents([]string{info.Types[0].Term()}, func(input *app.ComponentInput) app.ComponentReference { resolver := app.PerfectMatchWeightedResolver{} if c.imageStreamByAnnotationSearcher != nil { resolver = append(resolver, app.WeightedResolver{Searcher: c.imageStreamByAnnotationSearcher, Weight: 0.0}) } if c.imageStreamSearcher != nil { resolver = append(resolver, app.WeightedResolver{Searcher: c.imageStreamSearcher, Weight: 1.0}) } if c.dockerSearcher != nil { resolver = append(resolver, app.WeightedResolver{Searcher: c.dockerSearcher, Weight: 2.0}) } input.Resolver = resolver input.ExpectToBuild = true input.Use(repo) repo.UsedBy(input) return input }) result = append(result, refs...) } } return result, errors.NewAggregate(errs) } // resolve the references to ensure they are all valid, and identify any images that don't match user input. func (c *AppConfig) resolve(components app.ComponentReferences) error { errs := []error{} for _, ref := range components { if err := ref.Resolve(); err != nil { errs = append(errs, err) continue } switch input := ref.Input(); { case !input.ExpectToBuild && input.ResolvedMatch.Builder: if c.Strategy != "docker" { glog.Infof("Image %q is a builder, so a repository will be expected unless you also specify --strategy=docker", input) input.ExpectToBuild = true } case input.ExpectToBuild && input.ResolvedMatch.IsTemplate(): // TODO: harder - break the template pieces and check if source code can be attached (look for a build config, build image, etc) errs = append(errs, fmt.Errorf("template with source code explicitly attached is not supported - you must either specify the template and source code separately or attach an image to the source code using the '[image]~[code]' form")) continue case input.ExpectToBuild && !input.ResolvedMatch.Builder && !input.Uses.IsDockerBuild(): if len(c.Strategy) == 0 { errs = append(errs, fmt.Errorf("none of the images that match %q can build source code - check whether this is the image you want to use, then use --strategy=source to build using source or --strategy=docker to treat this as a Docker base image and set up a layered Docker build", ref)) continue } } } return errors.NewAggregate(errs) } // searches on all references func (c *AppConfig) search(components app.ComponentReferences) error { errs := []error{} for _, ref := range components { if err := ref.Search(); err != nil { errs = append(errs, err) continue } } return errors.NewAggregate(errs) } // ensureHasSource ensure every builder component has source code associated with it. It takes a list of component references // that are builders and have not been associated with source, and a set of source repositories that have not been associated // with a builder func (c *AppConfig) ensureHasSource(components app.ComponentReferences, repositories app.SourceRepositories) error { if len(components) > 0 { switch { case len(repositories) > 1: if len(components) == 1 { component := components[0] suggestions := "" for _, repo := range repositories { suggestions += fmt.Sprintf("%s~%s\n", component, repo) } return fmt.Errorf("there are multiple code locations provided - use one of the following suggestions to declare which code goes with the image:\n%s", suggestions) } return fmt.Errorf("the following images require source code: %s\n"+ " and the following repositories are not used: %s\nUse '[image]~[repo]' to declare which code goes with which image", components, repositories) case len(repositories) == 1: glog.Infof("Using %q as the source for build", repositories[0]) for _, component := range components { component.Input().Use(repositories[0]) repositories[0].UsedBy(component) } default: for _, component := range components { component.Input().ExpectToBuild = false } } } return nil } // detectSource runs a code detector on the passed in repositories to obtain a SourceRepositoryInfo func (c *AppConfig) detectSource(repositories []*app.SourceRepository) error { errs := []error{} for _, repo := range repositories { err := repo.Detect(c.detector) if err != nil { errs = append(errs, err) continue } } return errors.NewAggregate(errs) } func ensureValidUniqueName(names map[string]int, name string) (string, error) { // Ensure that name meets length requirements if len(name) < 2 { return "", fmt.Errorf("invalid name: %s", name) } if len(name) > util.DNS1123SubdomainMaxLength { glog.V(4).Infof("Trimming %s to maximum allowable length (%d)\n", name, util.DNS1123SubdomainMaxLength) name = name[:util.DNS1123SubdomainMaxLength] } // Make all names lowercase name = strings.ToLower(name) count, existing := names[name] if !existing { names[name] = 0 return name, nil } count++ names[name] = count newName := namer.GetName(name, strconv.Itoa(count), util.DNS1123SubdomainMaxLength) return newName, nil } // buildPipelines converts a set of resolved, valid references into pipelines. func (c *AppConfig) buildPipelines(components app.ComponentReferences, environment app.Environment) (app.PipelineGroup, error) { pipelines := app.PipelineGroup{} names := map[string]int{} for _, group := range components.Group() { glog.V(2).Infof("found group: %#v", group) common := app.PipelineGroup{} for _, ref := range group { var pipeline *app.Pipeline var name string if ref.Input().ExpectToBuild { glog.V(2).Infof("will use %q as the base image for a source build of %q", ref, ref.Input().Uses) input, err := app.InputImageFromMatch(ref.Input().ResolvedMatch) if err != nil { return nil, fmt.Errorf("can't build %q: %v", ref.Input(), err) } if !input.AsImageStream { glog.Warningf("Could not find an image match for %q. Make sure that a Docker image with that tag is available on the node for the build to succeed.", ref.Input().ResolvedMatch.Value) } strategy, source, err := app.StrategyAndSourceForRepository(ref.Input().Uses, input) if err != nil { return nil, fmt.Errorf("can't build %q: %v", ref.Input(), err) } // Override resource names from the cli name = c.Name if len(name) == 0 { var ok bool name, ok = (app.NameSuggestions{source, input}).SuggestName() if !ok { return nil, fmt.Errorf("can't suggest a valid name, please specify a name with --name") } } name, err = ensureValidUniqueName(names, name) source.Name = name if err != nil { return nil, err } // Append any exposed ports from Dockerfile to input image if ref.Input().Uses.IsDockerBuild() { exposed, ok := ref.Input().Uses.Info().Dockerfile.GetDirective("EXPOSE") if ok { if input.Info == nil { input.Info = &imageapi.DockerImage{ Config: &imageapi.DockerConfig{}, } } input.Info.Config.ExposedPorts = map[string]struct{}{} for _, p := range exposed { input.Info.Config.ExposedPorts[p] = struct{}{} } } } if pipeline, err = app.NewBuildPipeline(ref.Input().String(), input, c.OutputDocker, strategy, source); err != nil { return nil, fmt.Errorf("can't build %q: %v", ref.Input(), err) } } else { glog.V(2).Infof("will include %q", ref) input, err := app.InputImageFromMatch(ref.Input().ResolvedMatch) if err != nil { return nil, fmt.Errorf("can't include %q: %v", ref.Input(), err) } name = c.Name if len(name) == 0 { var ok bool name, ok = input.SuggestName() if !ok { return nil, fmt.Errorf("can't suggest a valid name, please specify a name with --name") } } name, err = ensureValidUniqueName(names, name) if err != nil { return nil, err } input.ObjectName = name if pipeline, err = app.NewImagePipeline(ref.Input().String(), input); err != nil { return nil, fmt.Errorf("can't include %q: %v", ref.Input(), err) } } if err := pipeline.NeedsDeployment(environment, c.Labels, name); err != nil { return nil, fmt.Errorf("can't set up a deployment for %q: %v", ref.Input(), err) } common = append(common, pipeline) } if err := common.Reduce(); err != nil { return nil, fmt.Errorf("can't create a pipeline from %s: %v", common, err) } pipelines = append(pipelines, common...) } return pipelines, nil } // buildTemplates converts a set of resolved, valid references into references to template objects. func (c *AppConfig) buildTemplates(components app.ComponentReferences, environment app.Environment) ([]runtime.Object, error) { objects := []runtime.Object{} for _, ref := range components { tpl := ref.Input().ResolvedMatch.Template glog.V(4).Infof("processing template %s/%s", c.originNamespace, tpl.Name) for _, env := range environment.List() { // only set environment values that match what's expected by the template. if v := template.GetParameterByName(tpl, env.Name); v != nil { v.Value = env.Value v.Generate = "" template.AddParameter(tpl, *v) } else { return nil, fmt.Errorf("unexpected parameter name %q", env.Name) } } result, err := c.osclient.TemplateConfigs(c.originNamespace).Create(tpl) if err != nil { return nil, fmt.Errorf("error processing template %s/%s: %v", c.originNamespace, tpl.Name, err) } errs := runtime.DecodeList(result.Objects, kapi.Scheme) if len(errs) > 0 { err = errors.NewAggregate(errs) return nil, fmt.Errorf("error processing template %s/%s: %v", c.originNamespace, tpl.Name, errs) } objects = append(objects, result.Objects...) } return objects, nil } // ErrNoInputs is returned when no inputs are specified var ErrNoInputs = fmt.Errorf("no inputs provided") // AppResult contains the results of an application type AppResult struct { List *kapi.List Name string BuildNames []string HasSource bool Namespace string } // QueryResult contains the results of a query (search or list) type QueryResult struct { Matches app.ComponentMatches List *kapi.List } // RunAll executes the provided config to generate all objects. func (c *AppConfig) RunAll(out, errOut io.Writer) (*AppResult, error) { return c.run(out, errOut, app.Acceptors{app.NewAcceptUnique(c.typer), app.AcceptNew}) } // RunBuilds executes the provided config to generate just builds. func (c *AppConfig) RunBuilds(out, errOut io.Writer) (*AppResult, error) { bcAcceptor := app.NewAcceptBuildConfigs(c.typer) result, err := c.run(out, errOut, app.Acceptors{bcAcceptor, app.NewAcceptUnique(c.typer), app.AcceptNew}) if err != nil { return nil, err } return filterImageStreams(result), nil } func filterImageStreams(result *AppResult) *AppResult { // 1st pass to get images from all BuildConfigs imageStreams := map[string]bool{} for _, item := range result.List.Items { if bc, ok := item.(*buildapi.BuildConfig); ok { to := bc.Spec.Output.To if to != nil && to.Kind == "ImageStreamTag" { imageStreams[makeImageStreamKey(*to)] = true } switch bc.Spec.Strategy.Type { case buildapi.DockerBuildStrategyType: from := bc.Spec.Strategy.DockerStrategy.From if from != nil && from.Kind == "ImageStreamTag" { imageStreams[makeImageStreamKey(*from)] = true } case buildapi.SourceBuildStrategyType: from := bc.Spec.Strategy.SourceStrategy.From if from.Kind == "ImageStreamTag" { imageStreams[makeImageStreamKey(from)] = true } case buildapi.CustomBuildStrategyType: from := bc.Spec.Strategy.CustomStrategy.From if from.Kind == "ImageStreamTag" { imageStreams[makeImageStreamKey(from)] = true } } } } items := []runtime.Object{} // 2nd pass to remove ImageStreams not used by BuildConfigs for _, item := range result.List.Items { if is, ok := item.(*imageapi.ImageStream); ok { if _, ok := imageStreams[types.NamespacedName{is.Namespace, is.Name}.String()]; ok { items = append(items, is) } } else { items = append(items, item) } } result.List.Items = items return result } func makeImageStreamKey(ref kapi.ObjectReference) string { name, _, _ := imageapi.SplitImageStreamTag(ref.Name) return types.NamespacedName{ref.Namespace, name}.String() } // RunQuery executes the provided config and returns the result of the resolution. func (c *AppConfig) RunQuery(out, errOut io.Writer) (*QueryResult, error) { c.ensureDockerSearcher() repositories, err := c.individualSourceRepositories() if err != nil { return nil, err } if c.AsList { if c.AsSearch { return nil, fmt.Errorf("--list and --search can't be used together") } if c.HasArguments() { return nil, fmt.Errorf("--list can't be used with arguments") } c.Components.Set("*") } components, repositories, environment, parameters, err := c.validate() if err != nil { return nil, err } if len(components) == 0 && !c.AsList { return nil, ErrNoInputs } errs := []error{} if len(repositories) > 0 { errs = append(errs, fmt.Errorf("--search can't be used with source code")) } if len(environment) > 0 { errs = append(errs, fmt.Errorf("--search can't be used with --env")) } if len(parameters) > 0 { errs = append(errs, fmt.Errorf("--search can't be used with --param")) } if len(errs) > 0 { return nil, errors.NewAggregate(errs) } if err := c.search(components); err != nil { return nil, err } glog.V(4).Infof("Code %v", repositories) glog.V(4).Infof("Components %v", components) matches := app.ComponentMatches{} objects := app.Objects{} for _, ref := range components { for _, match := range ref.Input().SearchMatches { matches = append(matches, match) if match.IsTemplate() { objects = append(objects, match.Template) } else if match.IsImage() { if match.ImageStream != nil { objects = append(objects, match.ImageStream) } if match.Image != nil { objects = append(objects, match.Image) } } } } return &QueryResult{ Matches: matches, List: &kapi.List{Items: objects}, }, nil } // run executes the provided config applying provided acceptors. func (c *AppConfig) run(out, errOut io.Writer, acceptors app.Acceptors) (*AppResult, error) { c.ensureDockerSearcher() repositories, err := c.individualSourceRepositories() if err != nil { return nil, err } err = c.detectSource(repositories) if err != nil { return nil, err } components, repositories, environment, parameters, err := c.validate() if err != nil { return nil, err } if err := c.resolve(components); err != nil { return nil, err } // Couple source with resolved builder components if possible if err := c.ensureHasSource(components.NeedsSource(), repositories.NotUsed()); err != nil { return nil, err } // For source repos that are not yet coupled with a component, create components sourceComponents, err := c.componentsForRepos(repositories.NotUsed()) if err != nil { return nil, err } // resolve the source repo components if err := c.resolve(sourceComponents); err != nil { return nil, err } components = append(components, sourceComponents...) glog.V(4).Infof("Code %v", repositories) glog.V(4).Infof("Components %v", components) if len(repositories) == 0 && len(components) == 0 { return nil, ErrNoInputs } if len(components.ImageComponentRefs()) > 1 && len(c.Name) > 0 { return nil, fmt.Errorf("only one component or source repository can be used when specifying a name") } pipelines, err := c.buildPipelines(components.ImageComponentRefs(), app.Environment(environment)) if err != nil { return nil, err } objects := app.Objects{} accept := app.NewAcceptFirst() warned := make(map[string]struct{}) for _, p := range pipelines { accepted, err := p.Objects(accept, acceptors) if err != nil { return nil, fmt.Errorf("can't setup %q: %v", p.From, err) } if p.Image != nil && p.Image.HasEmptyDir { if _, ok := warned[p.Image.Name]; !ok { fmt.Fprintf(errOut, "NOTICE: Image %q uses an EmptyDir volume. Data in EmptyDir volumes is not persisted across deployments.\n", p.Image.Name) warned[p.Image.Name] = struct{}{} } } objects = append(objects, accepted...) } objects = app.AddServices(objects, false) templateObjects, err := c.buildTemplates(components.TemplateComponentRefs(), app.Environment(parameters)) if err != nil { return nil, err } objects = append(objects, templateObjects...) buildNames := []string{} for _, obj := range objects { switch t := obj.(type) { case *buildapi.BuildConfig: buildNames = append(buildNames, t.Name) } } name := c.Name if len(name) == 0 { for _, pipeline := range pipelines { if pipeline.Deployment != nil { name = pipeline.Deployment.Name break } } } return &AppResult{ List: &kapi.List{Items: objects}, Name: name, BuildNames: buildNames, HasSource: len(repositories) != 0, Namespace: c.originNamespace, }, nil } func (c *AppConfig) Querying() bool { return c.AsList || c.AsSearch } func (c *AppConfig) HasArguments() bool { return len(c.Components) > 0 || len(c.ImageStreams) > 0 || len(c.DockerImages) > 0 || len(c.Templates) > 0 || len(c.TemplateFiles) > 0 }