pkg/cmd/cli/cmd/startbuild.go
8c556b6d
 package cmd
 
 import (
4b7de05d
 	"bufio"
25189a16
 	"bytes"
4b7de05d
 	"compress/gzip"
e7387750
 	"encoding/json"
14f0c3cb
 	"fmt"
8c556b6d
 	"io"
e7387750
 	"io/ioutil"
25189a16
 	"net/http"
 	"net/url"
e7387750
 	"os"
4b7de05d
 	"path/filepath"
e7387750
 	"strings"
8c556b6d
 
e7387750
 	"github.com/golang/glog"
c431087f
 	"github.com/spf13/cobra"
 
83c702b4
 	kapi "k8s.io/kubernetes/pkg/api"
bc80045d
 	kerrors "k8s.io/kubernetes/pkg/api/errors"
cb5824a5
 	"k8s.io/kubernetes/pkg/api/meta"
f638b86d
 	"k8s.io/kubernetes/pkg/api/unversioned"
bfa9bb91
 	"k8s.io/kubernetes/pkg/client/restclient"
1ee2710c
 	kclientcmd "k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
818cc10e
 	"k8s.io/kubernetes/pkg/fields"
95ec120f
 	kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
f03483a6
 	"k8s.io/kubernetes/third_party/forked/golang/netutil"
25189a16
 
41de07a4
 	buildapi "github.com/openshift/origin/pkg/build/api"
260558d2
 	osclient "github.com/openshift/origin/pkg/client"
6267dded
 	"github.com/openshift/origin/pkg/cmd/templates"
95ec120f
 	cmdutil "github.com/openshift/origin/pkg/cmd/util"
cfe9d1ef
 	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
e7387750
 	"github.com/openshift/origin/pkg/generate/git"
bc80045d
 	oerrors "github.com/openshift/origin/pkg/util/errors"
4b7de05d
 	"github.com/openshift/source-to-image/pkg/tar"
8c556b6d
 )
 
6267dded
 var (
 	startBuildLong = templates.LongDesc(`
 		Start a build
8c556b6d
 
6267dded
 		This command starts a new build for the provided build config or copies an existing build using
 		--from-build=<name>. Pass the --follow flag to see output from the build.
8c556b6d
 
6267dded
 		In addition, you can pass a file, directory, or source code repository with the --from-file,
 		--from-dir, or --from-repo flags directly to the build. The contents will be streamed to the build
 		and override the current build source settings. When using --from-repo, the --commit flag can be
 		used to control which branch, tag, or commit is sent to the server. If you pass --from-file, the
 		file is placed in the root of an empty directory with the same filename. Note that builds
 		triggered from binary input will not preserve the source on the server, so rebuilds triggered by
 		base image changes will use the source specified on the build config.`)
0df64392
 
6267dded
 	startBuildExample = templates.Examples(`
 		# Starts build from build config "hello-world"
 	  %[1]s start-build hello-world
50f134ea
 
6267dded
 	  # Starts build from a previous build "hello-world-1"
 	  %[1]s start-build --from-build=hello-world-1
818cc10e
 
6267dded
 	  # Use the contents of a directory as build input
 	  %[1]s start-build hello-world --from-dir=src/
4b7de05d
 
6267dded
 	  # Send the contents of a Git repository to the server from tag 'v2'
 	  %[1]s start-build hello-world --from-repo=../hello-world --commit=v2
4b7de05d
 
6267dded
 	  # Start a new build for build config "hello-world" and watch the logs until the build
 	  # completes or fails.
 	  %[1]s start-build hello-world --follow
4b7de05d
 
6267dded
 	  # Start a new build for build config "hello-world" and wait until the build completes. It
 	  # exits with a non-zero return code if the build fails.
 	  %[1]s start-build hello-world --wait`)
1558f2d9
 )
0c5b8196
 
13eb76ee
 // NewCmdStartBuild implements the OpenShift cli start-build command
dd473fd9
 func NewCmdStartBuild(fullName string, f *clientcmd.Factory, in io.Reader, out, errout io.Writer) *cobra.Command {
1ee2710c
 	o := &StartBuildOptions{}
260558d2
 
0c5b8196
 	cmd := &cobra.Command{
b3e77d8d
 		Use:        "start-build (BUILDCONFIG | --from-build=BUILD)",
4b7de05d
 		Short:      "Start a new build",
b3e77d8d
 		Long:       startBuildLong,
 		Example:    fmt.Sprintf(startBuildExample, fullName),
 		SuggestFor: []string{"build", "builds"},
8c556b6d
 		Run: func(cmd *cobra.Command, args []string) {
dd473fd9
 			kcmdutil.CheckErr(o.Complete(f, in, out, errout, cmd, fullName, args))
1ee2710c
 			kcmdutil.CheckErr(o.Run())
af00209a
 		},
 	}
1ee2710c
 	cmd.Flags().StringVar(&o.LogLevel, "build-loglevel", o.LogLevel, "Specify the log level for the build log output")
 	cmd.Flags().StringSliceVarP(&o.Env, "env", "e", o.Env, "Specify key value pairs of environment variables to set for the build container.")
 	cmd.Flags().StringVar(&o.FromBuild, "from-build", o.FromBuild, "Specify the name of a build which should be re-run")
1b9f255f
 
555ce555
 	cmd.Flags().BoolVarP(&o.Follow, "follow", "F", o.Follow, "Start a build and watch its logs until it completes or fails")
 	cmd.Flags().BoolVarP(&o.WaitForComplete, "wait", "w", o.WaitForComplete, "Wait for a build to complete and exit with a non-zero return code if the build fails")
1b9f255f
 
1ee2710c
 	cmd.Flags().StringVar(&o.FromFile, "from-file", o.FromFile, "A file to use as the binary input for the build; example a pom.xml or Dockerfile. Will be the only file in the build source.")
 	cmd.Flags().StringVar(&o.FromDir, "from-dir", o.FromDir, "A directory to archive and use as the binary input for a build.")
 	cmd.Flags().StringVar(&o.FromRepo, "from-repo", o.FromRepo, "The path to a local source code repository to use as the binary input for a build.")
 	cmd.Flags().StringVar(&o.Commit, "commit", o.Commit, "Specify the source code commit identifier the build should use; requires a build based on a Git repository")
4b7de05d
 
1ee2710c
 	cmd.Flags().StringVar(&o.ListWebhooks, "list-webhooks", o.ListWebhooks, "List the webhooks for the specified build config or build; accepts 'all', 'generic', or 'github'")
 	cmd.Flags().StringVar(&o.FromWebhook, "from-webhook", o.FromWebhook, "Specify a webhook URL for an existing build config to trigger")
1b9f255f
 
1ee2710c
 	cmd.Flags().StringVar(&o.GitPostReceive, "git-post-receive", o.GitPostReceive, "The contents of the post-receive hook to trigger a build")
 	cmd.Flags().StringVar(&o.GitRepository, "git-repository", o.GitRepository, "The path to the git repository for post-receive; defaults to the current directory")
190d64b7
 
cb5824a5
 	kcmdutil.AddOutputFlagsForMutation(cmd)
af00209a
 	return cmd
 }
8c556b6d
 
1ee2710c
 type StartBuildOptions struct {
 	In          io.Reader
 	Out, ErrOut io.Writer
 	Git         git.Repository
 
 	FromBuild    string
 	FromWebhook  string
 	ListWebhooks string
 
 	Commit   string
 	FromFile string
 	FromDir  string
 	FromRepo string
 
 	Env []string
 
 	Follow          bool
 	WaitForComplete bool
 	LogLevel        string
 
 	GitRepository  string
 	GitPostReceive string
 
cb5824a5
 	Mapper       meta.RESTMapper
1ee2710c
 	Client       osclient.Interface
 	ClientConfig kclientcmd.ClientConfig
 
cb5824a5
 	AsBinary    bool
 	ShortOutput bool
 	EnvVar      []kapi.EnvVar
 	Name        string
 	Namespace   string
1ee2710c
 }
 
dd473fd9
 func (o *StartBuildOptions) Complete(f *clientcmd.Factory, in io.Reader, out, errout io.Writer, cmd *cobra.Command, cmdFullName string, args []string) error {
1ee2710c
 	o.In = in
 	o.Out = out
dd473fd9
 	o.ErrOut = errout
1ee2710c
 	o.Git = git.NewRepository()
 	o.ClientConfig = f.OpenShiftClientConfig
cb5824a5
 	o.Mapper, _ = f.Object(false)
1ee2710c
 
 	webhook := o.FromWebhook
 	buildName := o.FromBuild
 	fromFile := o.FromFile
 	fromDir := o.FromDir
 	fromRepo := o.FromRepo
 	buildLogLevel := o.LogLevel
260558d2
 
cb5824a5
 	outputFormat := kcmdutil.GetFlagString(cmd, "output")
 	if outputFormat != "name" && outputFormat != "" {
 		return kcmdutil.UsageError(cmd, "Unsupported output format: %s", outputFormat)
 	}
 	o.ShortOutput = outputFormat == "name"
 
260558d2
 	switch {
 	case len(webhook) > 0:
4b7de05d
 		if len(args) > 0 || len(buildName) > 0 || len(fromFile) > 0 || len(fromDir) > 0 || len(fromRepo) > 0 {
95ec120f
 			return kcmdutil.UsageError(cmd, "The '--from-webhook' flag is incompatible with arguments and all '--from-*' flags")
260558d2
 		}
1ee2710c
 		return nil
 
260558d2
 	case len(args) != 1 && len(buildName) == 0:
15f1ff87
 		return kcmdutil.UsageError(cmd, "Must pass a name of a build config or specify build name with '--from-build' flag.\nUse \"%s get bc\" to list all available build configs.", cmdFullName)
25189a16
 	}
 
bc80045d
 	if len(buildName) != 0 && (len(fromFile) != 0 || len(fromDir) != 0 || len(fromRepo) != 0) {
 		// TODO: we should support this, it should be possible to clone a build to run again with new uploaded artifacts.
 		// Doing so requires introducing a new clonebinary endpoint.
 		return kcmdutil.UsageError(cmd, "Cannot use '--from-build' flag with binary builds")
 	}
1ee2710c
 	o.AsBinary = len(fromFile) > 0 || len(fromDir) > 0 || len(fromRepo) > 0
 
e30dccdf
 	namespace, _, err := f.DefaultNamespace()
 	if err != nil {
 		return err
 	}
 
1ee2710c
 	client, _, err := f.Clients()
 	if err != nil {
 		return err
 	}
 	o.Client = client
 
e30dccdf
 	var (
 		name     = buildName
49ad7ccd
 		resource = buildapi.Resource("builds")
e30dccdf
 	)
 
 	if len(name) == 0 && len(args) > 0 && len(args[0]) > 0 {
59e6f9d2
 		mapper, _ := f.Object(false)
49ad7ccd
 		resource, name, err = cmdutil.ResolveResource(buildapi.Resource("buildconfigs"), args[0], mapper)
e30dccdf
 		if err != nil {
 			return err
 		}
 		switch resource {
49ad7ccd
 		case buildapi.Resource("buildconfigs"):
e30dccdf
 			// no special handling required
49ad7ccd
 		case buildapi.Resource("builds"):
1ee2710c
 			if len(o.ListWebhooks) == 0 {
 				return fmt.Errorf("use --from-build to rerun your builds")
 			}
e30dccdf
 		default:
49ad7ccd
 			return fmt.Errorf("invalid resource provided: %v", resource)
e30dccdf
 		}
 	}
1ee2710c
 	// when listing webhooks, allow --from-build to lookup a build config
 	if resource == buildapi.Resource("builds") && len(o.ListWebhooks) > 0 {
 		build, err := client.Builds(namespace).Get(name)
 		if err != nil {
 			return err
 		}
 		ref := build.Status.Config
 		if ref == nil {
 			return fmt.Errorf("the provided Build %q was not created from a BuildConfig and cannot have webhooks", name)
 		}
 		if len(ref.Namespace) > 0 {
 			namespace = ref.Namespace
 		}
 		name = ref.Name
260558d2
 	}
 
1ee2710c
 	if len(name) == 0 {
 		return fmt.Errorf("a resource name is required either as an argument or by using --from-build")
af00209a
 	}
8c556b6d
 
1ee2710c
 	o.Namespace = namespace
 	o.Name = name
14f0c3cb
 
1ee2710c
 	env, _, err := cmdutil.ParseEnv(o.Env, in)
d8f6eca3
 	if err != nil {
 		return err
 	}
 	if len(buildLogLevel) > 0 {
 		env = append(env, kapi.EnvVar{Name: "BUILD_LOGLEVEL", Value: buildLogLevel})
 	}
1ee2710c
 	o.EnvVar = env
d8f6eca3
 
1ee2710c
 	return nil
 }
 
 // Run contains all the necessary functionality for the OpenShift cli start-build command
 func (o *StartBuildOptions) Run() error {
 	if len(o.FromWebhook) > 0 {
 		return o.RunStartBuildWebHook()
 	}
 	if len(o.ListWebhooks) > 0 {
 		return o.RunListBuildWebHooks()
 	}
cb5824a5
 
9b0166a8
 	buildRequestCauses := []buildapi.BuildTriggerCause{}
260558d2
 	request := &buildapi.BuildRequest{
9b0166a8
 		TriggeredBy: append(buildRequestCauses,
 			buildapi.BuildTriggerCause{
 				Message: "Manually triggered",
 			},
 		),
1ee2710c
 		ObjectMeta: kapi.ObjectMeta{Name: o.Name},
260558d2
 	}
1ee2710c
 	if len(o.EnvVar) > 0 {
 		request.Env = o.EnvVar
d8f6eca3
 	}
1ee2710c
 	if len(o.Commit) > 0 {
e7c2142f
 		request.Revision = &buildapi.SourceRevision{
 			Git: &buildapi.GitSourceRevision{
1ee2710c
 				Commit: o.Commit,
e7c2142f
 			},
 		}
 	}
4b7de05d
 
1ee2710c
 	var err error
af00209a
 	var newBuild *buildapi.Build
4b7de05d
 	switch {
1ee2710c
 	case o.AsBinary:
4b7de05d
 		request := &buildapi.BinaryBuildRequestOptions{
 			ObjectMeta: kapi.ObjectMeta{
1ee2710c
 				Name:      o.Name,
 				Namespace: o.Namespace,
4b7de05d
 			},
1ee2710c
 			Commit: o.Commit,
4b7de05d
 		}
1ee2710c
 		if len(o.EnvVar) > 0 {
 			fmt.Fprintf(o.ErrOut, "WARNING: Specifying environment variables with binary builds is not supported.\n")
71992928
 		}
1ee2710c
 		if newBuild, err = streamPathToBuild(o.Git, o.In, o.ErrOut, o.Client.BuildConfigs(o.Namespace), o.FromDir, o.FromFile, o.FromRepo, request); err != nil {
4b7de05d
 			return err
 		}
1ee2710c
 	case len(o.FromBuild) > 0:
 		if newBuild, err = o.Client.Builds(o.Namespace).Clone(request); err != nil {
bc80045d
 			if isInvalidSourceInputsError(err) {
 				return fmt.Errorf("Build %s/%s has no valid source inputs and '--from-build' cannot be used for binary builds", o.Namespace, o.Name)
 			}
af00209a
 			return err
 		}
1ee2710c
 	default:
 		if newBuild, err = o.Client.BuildConfigs(o.Namespace).Instantiate(request); err != nil {
bc80045d
 			if isInvalidSourceInputsError(err) {
 				return fmt.Errorf("Build configuration %s/%s has no valid source inputs, if this is a binary build you must specify one of '--from-dir', '--from-repo', or '--from-file'", o.Namespace, o.Name)
 			}
af00209a
 			return err
 		}
 	}
4b7de05d
 
cb5824a5
 	kcmdutil.PrintSuccess(o.Mapper, o.ShortOutput, o.Out, "build", newBuild.Name, "started")
50f134ea
 
818cc10e
 	// Stream the logs from the build
1ee2710c
 	if o.Follow {
335e1c3f
 		opts := buildapi.BuildLogOptions{
 			Follow: true,
 			NoWait: false,
 		}
 		for {
 			rd, err := o.Client.BuildLogs(o.Namespace).Get(newBuild.Name, opts).Stream()
 			if err != nil {
 				// retry the connection to build logs when we hit the timeout.
 				if oerrors.IsTimeoutErr(err) {
 					fmt.Fprintf(o.ErrOut, "timed out getting logs, retrying\n")
 					continue
e57d9b77
 				}
335e1c3f
 				fmt.Fprintf(o.ErrOut, "error getting logs (%v), waiting for build to complete\n", err)
e57d9b77
 				break
818cc10e
 			}
335e1c3f
 			defer rd.Close()
 			if _, err = io.Copy(o.Out, rd); err != nil {
 				fmt.Fprintf(o.ErrOut, "error streaming logs (%v), waiting for build to complete\n", err)
 			}
 			break
 		}
818cc10e
 	}
 
335e1c3f
 	if o.Follow || o.WaitForComplete {
 		return WaitForBuildComplete(o.Client.Builds(o.Namespace), newBuild.Name)
 	}
818cc10e
 
335e1c3f
 	return nil
8c556b6d
 }
25189a16
 
260558d2
 // RunListBuildWebHooks prints the webhooks for the provided build config.
1ee2710c
 func (o *StartBuildOptions) RunListBuildWebHooks() error {
260558d2
 	generic, github := false, false
 	prefix := false
1ee2710c
 	switch o.ListWebhooks {
260558d2
 	case "all":
 		generic, github = true, true
 		prefix = true
 	case "generic":
 		generic = true
 	case "github":
 		github = true
 	default:
 		return fmt.Errorf("--list-webhooks must be 'all', 'generic', or 'github'")
 	}
1ee2710c
 	client := o.Client
e30dccdf
 
1ee2710c
 	config, err := client.BuildConfigs(o.Namespace).Get(o.Name)
260558d2
 	if err != nil {
 		return err
 	}
 
8933ecc1
 	for _, t := range config.Spec.Triggers {
260558d2
 		hookType := ""
 		switch {
 		case t.GenericWebHook != nil && generic:
 			if prefix {
 				hookType = "generic "
 			}
e65dbda2
 		case t.GitHubWebHook != nil && github:
260558d2
 			if prefix {
 				hookType = "github "
 			}
 		default:
 			continue
 		}
1ee2710c
 		url, err := client.BuildConfigs(o.Namespace).WebHookURL(o.Name, &t)
260558d2
 		if err != nil {
 			if err != osclient.ErrTriggerIsNotAWebHook {
1ee2710c
 				fmt.Fprintf(o.ErrOut, "error: unable to get webhook for %s: %v", o.Name, err)
260558d2
 			}
 			continue
 		}
1ee2710c
 		fmt.Fprintf(o.Out, "%s%s\n", hookType, url.String())
260558d2
 	}
 	return nil
 }
 
e43be323
 func streamPathToBuild(repo git.Repository, in io.Reader, out io.Writer, client osclient.BuildConfigInterface, fromDir, fromFile, fromRepo string, options *buildapi.BinaryBuildRequestOptions) (*buildapi.Build, error) {
4b7de05d
 	count := 0
 	asDir, asFile, asRepo := len(fromDir) > 0, len(fromFile) > 0, len(fromRepo) > 0
 	if asDir {
 		count++
 	}
 	if asFile {
 		count++
 	}
 	if asRepo {
 		count++
 	}
 	if count > 1 {
 		return nil, fmt.Errorf("only one of --from-file, --from-repo, or --from-dir may be specified")
 	}
 
e43be323
 	if asRepo && !git.IsGitInstalled() {
 		return nil, fmt.Errorf("cannot find git. Git is required to start a build from a repository. If git is not available, use --from-dir instead.")
 	}
 
4b7de05d
 	var r io.Reader
 	switch {
 	case fromFile == "-":
 		return nil, fmt.Errorf("--from-file=- is not supported")
 
 	case fromDir == "-":
 		br := bufio.NewReaderSize(in, 4096)
 		r = br
 		if !isArchive(br) {
 			fmt.Fprintf(out, "WARNING: the provided file may not be an archive (tar, tar.gz, or zip), use --from-file=- instead\n")
 		}
 		fmt.Fprintf(out, "Uploading archive file from STDIN as binary input for the build ...\n")
 
 	default:
 		var fromPath string
 		switch {
 		case asDir:
 			fromPath = fromDir
 		case asFile:
 			fromPath = fromFile
 		case asRepo:
 			fromPath = fromRepo
 		}
 
 		clean := filepath.Clean(fromPath)
 		path, err := filepath.Abs(fromPath)
 		if err != nil {
 			return nil, err
 		}
 
 		stat, err := os.Stat(path)
 		if err != nil {
 			return nil, err
 		}
 		if stat.IsDir() {
22e71df6
 			commit := "HEAD"
 			if len(options.Commit) > 0 {
 				commit = options.Commit
 			}
e43be323
 			info, gitErr := gitRefInfo(repo, clean, commit)
4b7de05d
 			if gitErr == nil {
 				options.Commit = info.GitSourceRevision.Commit
 				options.Message = info.GitSourceRevision.Message
 				options.AuthorName = info.GitSourceRevision.Author.Name
 				options.AuthorEmail = info.GitSourceRevision.Author.Email
 				options.CommitterName = info.GitSourceRevision.Committer.Name
 				options.CommitterEmail = info.GitSourceRevision.Committer.Email
 			} else {
 				glog.V(6).Infof("Unable to read Git info from %q: %v", clean, gitErr)
 			}
 
 			if asRepo {
98411fe2
 				fmt.Fprintf(out, "Uploading %q at commit %q as binary input for the build ...\n", clean, commit)
4b7de05d
 				if gitErr != nil {
 					return nil, fmt.Errorf("the directory %q is not a valid Git repository: %v", clean, gitErr)
 				}
 				pr, pw := io.Pipe()
 				go func() {
e43be323
 					if err := repo.Archive(clean, options.Commit, "tar.gz", pw); err != nil {
4b7de05d
 						pw.CloseWithError(fmt.Errorf("unable to create Git archive of %q for build: %v", clean, err))
 					} else {
 						pw.CloseWithError(io.EOF)
 					}
 				}()
 				r = pr
 
 			} else {
 				fmt.Fprintf(out, "Uploading directory %q as binary input for the build ...\n", clean)
 
 				pr, pw := io.Pipe()
 				go func() {
 					w := gzip.NewWriter(pw)
 					if err := tar.New().CreateTarStream(path, false, w); err != nil {
 						pw.CloseWithError(err)
 					} else {
 						w.Close()
 						pw.CloseWithError(io.EOF)
 					}
 				}()
 				r = pr
 			}
 		} else {
 			f, err := os.Open(path)
 			if err != nil {
 				return nil, err
 			}
 			defer f.Close()
 
 			r = f
 
 			if asFile {
 				options.AsFile = filepath.Base(path)
 				fmt.Fprintf(out, "Uploading file %q as binary input for the build ...\n", clean)
 			} else {
 				br := bufio.NewReaderSize(f, 4096)
 				r = br
 				if !isArchive(br) {
 					fmt.Fprintf(out, "WARNING: the provided file may not be an archive (tar, tar.gz, or zip), use --as-file\n")
 				}
 				fmt.Fprintf(out, "Uploading archive file %q as binary input for the build ...\n", clean)
 			}
 		}
 	}
 	return client.InstantiateBinary(options, r)
 }
 
 func isArchive(r *bufio.Reader) bool {
 	data, err := r.Peek(280)
 	if err != nil {
 		return false
 	}
 	for _, b := range [][]byte{
 		{0x50, 0x4B, 0x03, 0x04}, // zip
 		{0x1F, 0x9D},             // tar.z
 		{0x1F, 0xA0},             // tar.z
 		{0x42, 0x5A, 0x68},       // bz2
 		{0x1F, 0x8B, 0x08},       // gzip
 	} {
 		if bytes.HasPrefix(data, b) {
 			return true
 		}
 	}
 	switch {
1e911339
 	// Unified TAR files have this magic number
4b7de05d
 	case len(data) > 257+5 && bytes.Equal(data[257:257+5], []byte{0x75, 0x73, 0x74, 0x61, 0x72}):
 		return true
 	default:
 		return false
 	}
 }
 
25189a16
 // RunStartBuildWebHook tries to trigger the provided webhook. It will attempt to utilize the current client
 // configuration if the webhook has the same URL.
1ee2710c
 func (o *StartBuildOptions) RunStartBuildWebHook() error {
 	repo := o.Git
 	hook, err := url.Parse(o.FromWebhook)
e7387750
 	if err != nil {
 		return err
25189a16
 	}
 
1ee2710c
 	event, err := hookEventFromPostReceive(repo, o.GitRepository, o.GitPostReceive)
25189a16
 	if err != nil {
 		return err
 	}
e7387750
 
 	// TODO: should be a versioned struct
8ccb3959
 	var data []byte
 	if event != nil {
 		data, err = json.Marshal(event)
 		if err != nil {
 			return err
 		}
e7387750
 	}
 
25189a16
 	httpClient := http.DefaultClient
 	// when using HTTPS, try to reuse the local config transport if possible to get a client cert
 	// TODO: search all configs
 	if hook.Scheme == "https" {
1ee2710c
 		config, err := o.ClientConfig.ClientConfig()
25189a16
 		if err == nil {
bfa9bb91
 			if url, _, err := restclient.DefaultServerURL(config.Host, "", unversioned.GroupVersion{}, true); err == nil {
f03483a6
 				if netutil.CanonicalAddr(url) == netutil.CanonicalAddr(hook) && url.Scheme == hook.Scheme {
bfa9bb91
 					if rt, err := restclient.TransportFor(config); err == nil {
25189a16
 						httpClient = &http.Client{Transport: rt}
 					}
 				}
 			}
 		}
 	}
e7387750
 	glog.V(4).Infof("Triggering hook %s\n%s", hook, string(data))
 	resp, err := httpClient.Post(hook.String(), "application/json", bytes.NewBuffer(data))
 	if err != nil {
25189a16
 		return err
 	}
36c26f2e
 	defer resp.Body.Close()
e7387750
 	switch {
 	case resp.StatusCode == 301 || resp.StatusCode == 302:
 		// TODO: follow redirect and display output
 	case resp.StatusCode < 200 || resp.StatusCode >= 300:
 		body, _ := ioutil.ReadAll(resp.Body)
 		return fmt.Errorf("server rejected our request %d\nremote: %s", resp.StatusCode, string(body))
 	}
25189a16
 	return nil
 }
e7387750
 
 // hookEventFromPostReceive creates a GenericWebHookEvent from the provided git repository and
8ccb3959
 // post receive input. If no inputs are available, it will return nil.
e7387750
 func hookEventFromPostReceive(repo git.Repository, path, postReceivePath string) (*buildapi.GenericWebHookEvent, error) {
 	// TODO: support other types of refs
 	event := &buildapi.GenericWebHookEvent{
6d31fd27
 		Git: &buildapi.GitInfo{},
e7387750
 	}
 
 	// attempt to extract a post receive body
 	refs := []git.ChangedRef{}
 	switch receive := postReceivePath; {
 	case receive == "-":
 		r, err := git.ParsePostReceive(os.Stdin)
 		if err != nil {
 			return nil, err
 		}
 		refs = r
 	case len(receive) > 0:
 		file, err := os.Open(receive)
 		if err != nil {
 			return nil, fmt.Errorf("unable to open --git-post-receive argument as a file: %v", err)
 		}
 		defer file.Close()
 		r, err := git.ParsePostReceive(file)
 		if err != nil {
 			return nil, err
 		}
 		refs = r
 	}
8ccb3959
 	if len(refs) == 0 {
 		return nil, nil
 	}
e7387750
 	for _, ref := range refs {
 		if len(ref.New) == 0 || ref.New == ref.Old {
 			continue
 		}
 		info, err := gitRefInfo(repo, path, ref.New)
 		if err != nil {
 			glog.V(4).Infof("Could not retrieve info for %s:%s: %v", ref.Ref, ref.New, err)
 		}
 		info.Ref = ref.Ref
 		info.Commit = ref.New
 		event.Git.Refs = append(event.Git.Refs, info)
 	}
 	return event, nil
 }
 
 // gitRefInfo extracts a buildapi.GitRefInfo from the specified repository or returns
 // an error.
 func gitRefInfo(repo git.Repository, dir, ref string) (buildapi.GitRefInfo, error) {
 	info := buildapi.GitRefInfo{}
 	if repo == nil {
 		return info, nil
 	}
4b7de05d
 	out, err := repo.ShowFormat(dir, ref, "%H%n%an%n%ae%n%cn%n%ce%n%B")
e7387750
 	if err != nil {
 		return info, err
 	}
4b7de05d
 	lines := strings.SplitN(out, "\n", 6)
 	if len(lines) != 6 {
 		full := make([]string, 6)
e7387750
 		copy(full, lines)
 		lines = full
 	}
4b7de05d
 	info.Commit = lines[0]
 	info.Author.Name = lines[1]
 	info.Author.Email = lines[2]
 	info.Committer.Name = lines[3]
 	info.Committer.Email = lines[4]
 	info.Message = lines[5]
e7387750
 	return info, nil
 }
818cc10e
 
 // WaitForBuildComplete waits for a build identified by the name to complete
 func WaitForBuildComplete(c osclient.BuildInterface, name string) error {
 	isOK := func(b *buildapi.Build) bool {
 		return b.Status.Phase == buildapi.BuildPhaseComplete
 	}
 	isFailed := func(b *buildapi.Build) bool {
 		return b.Status.Phase == buildapi.BuildPhaseFailed ||
 			b.Status.Phase == buildapi.BuildPhaseCancelled ||
 			b.Status.Phase == buildapi.BuildPhaseError
 	}
 	for {
f638b86d
 		list, err := c.List(kapi.ListOptions{FieldSelector: fields.Set{"name": name}.AsSelector()})
818cc10e
 		if err != nil {
 			return err
 		}
 		for i := range list.Items {
 			if name == list.Items[i].Name && isOK(&list.Items[i]) {
 				return nil
 			}
 			if name != list.Items[i].Name || isFailed(&list.Items[i]) {
772b5fa0
 				return fmt.Errorf("the build %s/%s status is %q", list.Items[i].Namespace, list.Items[i].Name, list.Items[i].Status.Phase)
818cc10e
 			}
 		}
 
 		rv := list.ResourceVersion
f638b86d
 		w, err := c.Watch(kapi.ListOptions{FieldSelector: fields.Set{"name": name}.AsSelector(), ResourceVersion: rv})
818cc10e
 		if err != nil {
 			return err
 		}
 		defer w.Stop()
 
 		for {
 			val, ok := <-w.ResultChan()
 			if !ok {
 				// reget and re-watch
 				break
 			}
 			if e, ok := val.Object.(*buildapi.Build); ok {
 				if name == e.Name && isOK(e) {
 					return nil
 				}
 				if name != e.Name || isFailed(e) {
 					return fmt.Errorf("The build %s/%s status is %q", e.Namespace, name, e.Status.Phase)
 				}
 			}
 		}
 	}
 }
bc80045d
 
 func isInvalidSourceInputsError(err error) bool {
 	if err != nil {
 		if statusErr, ok := err.(*kerrors.StatusError); ok {
 			if kerrors.IsInvalid(statusErr) {
 				for _, cause := range statusErr.ErrStatus.Details.Causes {
 					if cause.Field == "spec.source" {
 						return true
 					}
 				}
 			}
 		}
 	}
 	return false
 }