pkg/cmd/cli/cmd/newbuild.go
277a3984
 package cmd
 
 import (
fe7039fe
 	"bytes"
277a3984
 	"fmt"
 	"io"
1b9f255f
 	"io/ioutil"
277a3984
 	"os"
 
b36f0965
 	"github.com/MakeNowJust/heredoc"
277a3984
 	"github.com/spf13/cobra"
e8bc19d6
 
95ec120f
 	kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
04990ffb
 	"k8s.io/kubernetes/pkg/runtime"
83c702b4
 	"k8s.io/kubernetes/pkg/util/errors"
277a3984
 
 	buildapi "github.com/openshift/origin/pkg/build/api"
6267dded
 	"github.com/openshift/origin/pkg/cmd/templates"
95ec120f
 	cmdutil "github.com/openshift/origin/pkg/cmd/util"
277a3984
 	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
93f4af33
 	configcmd "github.com/openshift/origin/pkg/config/cmd"
98cd35ba
 	newapp "github.com/openshift/origin/pkg/generate/app"
277a3984
 	newcmd "github.com/openshift/origin/pkg/generate/app/cmd"
 )
 
64fed1a6
 // NewBuildRecommendedCommandName is the recommended command name.
 const NewBuildRecommendedCommandName = "new-build"
 
6267dded
 var (
 	newBuildLong = templates.LongDesc(`
 		Create a new build by specifying source code
277a3984
 
6267dded
 		This command will try to create a build configuration for your application using images and
 		code that has a public repository. It will lookup the images on the local Docker installation
 		(if available), a Docker registry, or an image stream.
c3e1dbba
 
6267dded
 		If you specify a source code URL, it will set up a build that takes your source code and converts
 		it into an image that can run inside of a pod. Local source must be in a git repository that has a
 		remote repository that the server can see.
277a3984
 
6267dded
 		Once the build configuration is created a new build will be automatically triggered.
 		You can use '%[1]s status' to check the progress.`)
277a3984
 
6267dded
 	newBuildExample = templates.Examples(`
 	  # Create a build config based on the source code in the current git repository (with a public
 	  # remote) and a Docker image
 	  %[1]s %[2]s . --docker-image=repo/langimage
277a3984
 
6267dded
 	  # Create a NodeJS build config based on the provided [image]~[source code] combination
 	  %[1]s %[2]s openshift/nodejs-010-centos7~https://github.com/openshift/nodejs-ex.git
277a3984
 
6267dded
 	  # Create a build config from a remote repository using its beta2 branch
 	  %[1]s %[2]s https://github.com/openshift/ruby-hello-world#beta2
4ef01b69
 
6267dded
 	  # Create a build config using a Dockerfile specified as an argument
 	  %[1]s %[2]s -D $'FROM centos:7\nRUN yum install -y httpd'
1b9f255f
 
6267dded
 	  # Create a build config from a remote repository and add custom environment variables
 	  %[1]s %[2]s https://github.com/openshift/ruby-hello-world RACK_ENV=development
04e96a3a
 
6267dded
 	  # Create a build config from a remote repository and inject the npmrc into a build
 	  %[1]s %[2]s https://github.com/openshift/ruby-hello-world --build-secret npmrc:.npmrc
a166f955
 
6267dded
 	  # Create a build config that gets its input from a remote repository and another Docker image
 	  %[1]s %[2]s https://github.com/openshift/ruby-hello-world --source-image=openshift/jenkins-1-centos7 --source-image-path=/var/lib/jenkins:tmp`)
98cd35ba
 
 	newBuildNoInput = `You must specify one or more images, image streams, or source code locations to create a build.
 
 To build from an existing image stream tag or Docker image, provide the name of the image and
 the source code location:
 
64fed1a6
   %[1]s %[2]s openshift/nodejs-010-centos7~https://github.com/openshift/nodejs-ex.git
98cd35ba
 
 If you only specify the source repository location (local or remote), the command will look at
 the repo to determine the type, and then look for a matching image on your server or on the
 default Docker registry.
 
64fed1a6
   %[1]s %[2]s https://github.com/openshift/nodejs-ex.git
98cd35ba
 
 will look for an image called "nodejs" in your current project, the 'openshift' project, or
 on the Docker Hub.
 `
277a3984
 )
 
04990ffb
 type NewBuildOptions struct {
9cf65112
 	Action configcmd.BulkAction
04990ffb
 	Config *newcmd.AppConfig
 
64fed1a6
 	BaseName    string
04990ffb
 	CommandPath string
 	CommandName string
 
 	Out, ErrOut   io.Writer
 	Output        string
 	PrintObject   func(obj runtime.Object) error
 	LogsForObject LogsForObjectFunc
 }
 
277a3984
 // NewCmdNewBuild implements the OpenShift cli new-build command
dd473fd9
 func NewCmdNewBuild(name, baseName string, f *clientcmd.Factory, in io.Reader, out, errout io.Writer) *cobra.Command {
1d678d0c
 	config := newcmd.NewAppConfig()
f58fde40
 	config.ExpectToBuild = true
04990ffb
 	config.AddEnvironmentToBuild = true
64fed1a6
 	o := &NewBuildOptions{Config: config}
277a3984
 
 	cmd := &cobra.Command{
64fed1a6
 		Use:        fmt.Sprintf("%s (IMAGE | IMAGESTREAM | PATH | URL ...)", name),
b3e77d8d
 		Short:      "Create a new build configuration",
64fed1a6
 		Long:       fmt.Sprintf(newBuildLong, baseName, name),
 		Example:    fmt.Sprintf(newBuildExample, baseName, name),
b3e77d8d
 		SuggestFor: []string{"build", "builds"},
277a3984
 		Run: func(c *cobra.Command, args []string) {
dd473fd9
 			kcmdutil.CheckErr(o.Complete(baseName, name, f, c, args, out, errout, in))
64fed1a6
 			err := o.RunNewBuild()
95ec120f
 			if err == cmdutil.ErrExit {
277a3984
 				os.Exit(1)
 			}
95ec120f
 			kcmdutil.CheckErr(err)
277a3984
 		},
 	}
 
5997bb61
 	cmd.Flags().StringSliceVar(&config.SourceRepositories, "code", config.SourceRepositories, "Source code in the build configuration.")
6517d767
 	cmd.Flags().StringSliceVarP(&config.ImageStreams, "image", "", config.ImageStreams, "Name of an image stream to to use as a builder. (deprecated)")
 	cmd.Flags().MarkDeprecated("image", "use --image-stream instead")
 	cmd.Flags().StringSliceVarP(&config.ImageStreams, "image-stream", "i", config.ImageStreams, "Name of an image stream to to use as a builder.")
5997bb61
 	cmd.Flags().StringSliceVar(&config.DockerImages, "docker-image", config.DockerImages, "Name of a Docker image to use as a builder.")
04e96a3a
 	cmd.Flags().StringSliceVar(&config.Secrets, "build-secret", config.Secrets, "Secret and destination to use as an input for the build.")
57747fd5
 	cmd.Flags().StringVar(&config.Name, "name", "", "Set name to use for generated build artifacts.")
 	cmd.Flags().StringVar(&config.To, "to", "", "Push built images to this image stream tag (or Docker image repository if --to-docker is set).")
 	cmd.Flags().BoolVar(&config.OutputDocker, "to-docker", false, "Have the build output push to a Docker repository.")
5997bb61
 	cmd.Flags().StringSliceVarP(&config.Environment, "env", "e", config.Environment, "Specify key value pairs of environment variables to set into resulting image.")
4b35f1a0
 	cmd.Flags().StringVar(&config.Strategy, "strategy", "", "Specify the build strategy to use if you don't want to detect (docker|source).")
1b9f255f
 	cmd.Flags().StringVarP(&config.Dockerfile, "dockerfile", "D", "", "Specify the contents of a Dockerfile to build directly, implies --strategy=docker. Pass '-' to read from STDIN.")
f58fde40
 	cmd.Flags().BoolVar(&config.BinaryBuild, "binary", false, "Instead of expecting a source URL, set the build to expect binary contents. Will disable triggers.")
277a3984
 	cmd.Flags().StringP("labels", "l", "", "Label to set in all generated resources.")
c6bc5ea9
 	cmd.Flags().BoolVar(&config.AllowMissingImages, "allow-missing-images", false, "If true, indicates that referenced Docker images that cannot be found locally or in a registry should still be used.")
0fd621f4
 	cmd.Flags().BoolVar(&config.AllowMissingImageStreamTags, "allow-missing-imagestream-tags", false, "If true, indicates that image stream tags that don't exist should still be used.")
ce45941e
 	cmd.Flags().StringVar(&config.ContextDir, "context-dir", "", "Context directory to be used for the build.")
96fd8c26
 	cmd.Flags().BoolVar(&config.NoOutput, "no-output", false, "If true, the build output will not be pushed anywhere.")
fecf2d71
 	cmd.Flags().StringVar(&config.SourceImage, "source-image", "", "Specify an image to use as source for the build.  You must also specify --source-image-path.")
 	cmd.Flags().StringVar(&config.SourceImagePath, "source-image-path", "", "Specify the file or directory to copy from the source image and its destination in the build directory. Format: [source]:[destination-dir].")
9cf65112
 
64fed1a6
 	o.Action.BindForOutput(cmd.Flags())
9cf65112
 	cmd.Flags().String("output-version", "", "The preferred API versions of the output objects")
277a3984
 
 	return cmd
 }
 
04990ffb
 // Complete sets any default behavior for the command
dd473fd9
 func (o *NewBuildOptions) Complete(baseName, commandName string, f *clientcmd.Factory, c *cobra.Command, args []string, out, errout io.Writer, in io.Reader) error {
04990ffb
 	o.Out = out
dd473fd9
 	o.ErrOut = errout
04990ffb
 	o.Output = kcmdutil.GetFlagString(c, "output")
 	// Only output="" should print descriptions of intermediate steps. Everything
 	// else should print only some specific output (json, yaml, go-template, ...)
 	if len(o.Output) == 0 {
 		o.Config.Out = o.Out
 	} else {
 		o.Config.Out = ioutil.Discard
 	}
 	o.Config.ErrOut = o.ErrOut
a680572a
 
9cf65112
 	o.Action.Out, o.Action.ErrOut = o.Out, o.ErrOut
 	o.Action.Bulk.Mapper = clientcmd.ResourceMapper(f)
 	o.Action.Bulk.Op = configcmd.Create
 	// Retry is used to support previous versions of the API server that will
 	// consider the presence of an unknown trigger type to be an error.
 	o.Action.Bulk.Retry = retryBuildConfig
 
 	o.Config.DryRun = o.Action.DryRun
 
64fed1a6
 	o.BaseName = baseName
04990ffb
 	o.CommandPath = c.CommandPath()
64fed1a6
 	o.CommandName = commandName
 
59e6f9d2
 	mapper, _ := f.Object(false)
 	o.PrintObject = cmdutil.VersionedPrintObject(f.PrintObject, c, mapper, out)
04990ffb
 	o.LogsForObject = f.LogsForObject
 	if err := CompleteAppConfig(o.Config, f, c, args); err != nil {
 		return err
 	}
 	if o.Config.Dockerfile == "-" {
1b9f255f
 		data, err := ioutil.ReadAll(in)
 		if err != nil {
 			return err
 		}
04990ffb
 		o.Config.Dockerfile = string(data)
1b9f255f
 	}
04990ffb
 	if err := setAppConfigLabels(c, o.Config); err != nil {
277a3984
 		return err
 	}
04990ffb
 	return nil
 }
 
64fed1a6
 // RunNewBuild contains all the necessary functionality for the OpenShift cli new-build command
 func (o *NewBuildOptions) RunNewBuild() error {
04990ffb
 	config := o.Config
 	out := o.Out
277a3984
 
0b6008ee
 	checkGitInstalled(out)
 
93f4af33
 	result, err := config.Run()
277a3984
 	if err != nil {
64fed1a6
 		return handleBuildError(err, o.BaseName, o.CommandName, o.CommandPath)
277a3984
 	}
93f4af33
 
 	if len(config.Labels) == 0 && len(result.Name) > 0 {
 		config.Labels = map[string]string{"build": result.Name}
 	}
 
d27713ef
 	if err := setLabels(config.Labels, result); err != nil {
277a3984
 		return err
 	}
7aa66f3e
 	if err := setAnnotations(map[string]string{newcmd.GeneratedByNamespace: newcmd.GeneratedByNewBuild}, result); err != nil {
 		return err
 	}
93f4af33
 
9cf65112
 	if o.Action.ShouldPrint() {
04990ffb
 		return o.PrintObject(result.List)
93f4af33
 	}
 
9cf65112
 	if errs := o.Action.WithMessage(configcmd.CreateMessage(config.Labels), "created").Run(result.List, result.Namespace); len(errs) > 0 {
 		return cmdutil.ErrExit
277a3984
 	}
 
9cf65112
 	if !o.Action.Verbose() || o.Action.DryRun {
93f4af33
 		return nil
 	}
 
9cf65112
 	indent := o.Action.DefaultIndent()
277a3984
 	for _, item := range result.List.Items {
 		switch t := item.(type) {
 		case *buildapi.BuildConfig:
5bf62487
 			if len(t.Spec.Triggers) > 0 && t.Spec.Source.Binary == nil {
 				fmt.Fprintf(out, "%sBuild configuration %q created and build triggered.\n", indent, t.Name)
04990ffb
 				fmt.Fprintf(out, "%sRun '%s logs -f bc/%s' to stream the build progress.\n", indent, o.CommandName, t.Name)
5bf62487
 			}
277a3984
 		}
 	}
 
 	return nil
 }
98cd35ba
 
64fed1a6
 func handleBuildError(err error, baseName, commandName, commandPath string) error {
98cd35ba
 	if err == nil {
 		return nil
 	}
fe7039fe
 	errs := []error{err}
 	if agg, ok := err.(errors.Aggregate); ok {
 		errs = agg.Errors()
 	}
 	groups := errorGroups{}
 	for _, err := range errs {
64fed1a6
 		transformBuildError(err, baseName, commandName, commandPath, groups)
fe7039fe
 	}
 	buf := &bytes.Buffer{}
 	for _, group := range groups {
95ec120f
 		fmt.Fprint(buf, kcmdutil.MultipleErrors("error: ", group.errs))
fe7039fe
 		if len(group.suggestion) > 0 {
 			fmt.Fprintln(buf)
98cd35ba
 		}
fe7039fe
 		fmt.Fprint(buf, group.suggestion)
98cd35ba
 	}
fe7039fe
 	return fmt.Errorf(buf.String())
 }
 
64fed1a6
 func transformBuildError(err error, baseName, commandName, commandPath string, groups errorGroups) {
98cd35ba
 	switch t := err.(type) {
 	case newapp.ErrNoMatch:
fe7039fe
 		groups.Add(
 			"no-matches",
b36f0965
 			heredoc.Docf(`
fe7039fe
 				The '%[1]s' command will match arguments to the following types:
 
 				  1. Images tagged into image streams in the current project or the 'openshift' project
 				     - if you don't specify a tag, we'll add ':latest'
 				  2. Images in the Docker Hub, on remote registries, or on the local Docker engine
 				  3. Git repository URLs or local paths that point to Git repositories
 
bf2eaf23
 				--allow-missing-images can be used to force the use of an image that was not matched
fe7039fe
 
04990ffb
 				See '%[1]s -h' for examples.`, commandPath,
fe7039fe
 			),
 			t,
 			t.Errs...,
 		)
 		return
 	}
1acc3c75
 	switch err {
 	case newcmd.ErrNoInputs:
64fed1a6
 		groups.Add("", "", usageError(commandPath, newBuildNoInput, baseName, commandName))
1acc3c75
 		return
 	}
64fed1a6
 	transformError(err, baseName, commandName, commandPath, groups)
98cd35ba
 }