8c556b6d |
package cmd
import ( |
25189a16 |
"bytes" |
e7387750 |
"encoding/json" |
14f0c3cb |
"fmt" |
8c556b6d |
"io" |
e7387750 |
"io/ioutil" |
25189a16 |
"net/http"
"net/url" |
e7387750 |
"os"
"strings" |
8c556b6d |
|
e7387750 |
"github.com/golang/glog" |
c431087f |
"github.com/spf13/cobra"
|
83c702b4 |
kapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/util" |
25189a16 |
|
41de07a4 |
buildapi "github.com/openshift/origin/pkg/build/api" |
260558d2 |
osclient "github.com/openshift/origin/pkg/client" |
cfe9d1ef |
"github.com/openshift/origin/pkg/cmd/util/clientcmd" |
e7387750 |
"github.com/openshift/origin/pkg/generate/git" |
8c556b6d |
)
|
1558f2d9 |
const ( |
c124965f |
startBuildLong = `
Start a build |
8c556b6d |
|
821ddfce |
This command starts a build for the provided BuildConfig or re-runs an existing build using |
1558f2d9 |
--from-build=<name>. You may pass the --follow flag to see output from the build.` |
8c556b6d |
|
821ddfce |
startBuildExample = ` // Starts build from BuildConfig matching the name "3bd2ug53b" |
14c0fea1 |
$ %[1]s start-build 3bd2ug53b |
0df64392 |
|
1558f2d9 |
// Starts build from build matching the name "3bd2ug53b" |
14c0fea1 |
$ %[1]s start-build --from-build=3bd2ug53b |
50f134ea |
|
821ddfce |
// Starts build from BuildConfig matching the name "3bd2ug53b" and watches the logs until the build |
1558f2d9 |
// completes or fails
$ %[1]s start-build 3bd2ug53b --follow`
) |
0c5b8196 |
|
13eb76ee |
// NewCmdStartBuild implements the OpenShift cli start-build command |
0c5b8196 |
func NewCmdStartBuild(fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command { |
260558d2 |
webhooks := util.StringFlag{}
webhooks.Default("none")
|
0c5b8196 |
cmd := &cobra.Command{ |
1558f2d9 |
Use: "start-build (BUILDCONFIG | --from-build=BUILD)", |
ebf8e4ef |
Short: "Starts a new build", |
c36413f1 |
Long: startBuildLong,
Example: fmt.Sprintf(startBuildExample, fullName), |
8c556b6d |
Run: func(cmd *cobra.Command, args []string) { |
260558d2 |
err := RunStartBuild(f, out, cmd, args, webhooks) |
af00209a |
cmdutil.CheckErr(err)
},
}
cmd.Flags().String("from-build", "", "Specify the name of a build which should be re-run")
cmd.Flags().Bool("follow", false, "Start a build and watch its logs until it completes or fails") |
821ddfce |
cmd.Flags().Var(&webhooks, "list-webhooks", "List the webhooks for the specified BuildConfig or build; accepts 'all', 'generic', or 'github'")
cmd.Flags().String("from-webhook", "", "Specify a webhook URL for an existing BuildConfign to trigger") |
25189a16 |
cmd.Flags().String("git-post-receive", "", "The contents of the post-receive hook to trigger a build") |
e7387750 |
cmd.Flags().String("git-repository", "", "The path to the git repository for post-receive; defaults to the current directory") |
af00209a |
return cmd
} |
8c556b6d |
|
13eb76ee |
// RunStartBuild contains all the necessary functionality for the OpenShift cli start-build command |
260558d2 |
func RunStartBuild(f *clientcmd.Factory, out io.Writer, cmd *cobra.Command, args []string, webhooks util.StringFlag) error { |
25189a16 |
webhook := cmdutil.GetFlagString(cmd, "from-webhook") |
260558d2 |
buildName := cmdutil.GetFlagString(cmd, "from-build")
follow := cmdutil.GetFlagBool(cmd, "follow")
switch {
case len(webhook) > 0:
if len(args) > 0 || len(buildName) > 0 {
return cmdutil.UsageError(cmd, "The '--from-webhook' flag is incompatible with arguments or '--from-build'")
} |
e7387750 |
path := cmdutil.GetFlagString(cmd, "git-repository")
postReceivePath := cmdutil.GetFlagString(cmd, "git-post-receive")
repo := git.NewRepository()
return RunStartBuildWebHook(f, out, webhook, path, postReceivePath, repo) |
260558d2 |
case len(args) != 1 && len(buildName) == 0: |
821ddfce |
return cmdutil.UsageError(cmd, "Must pass a name of a BuildConfig or specify build name with '--from-build' flag") |
25189a16 |
}
|
260558d2 |
name := buildName
isBuild := true
if len(name) == 0 {
name = args[0]
isBuild = false
}
if webhooks.Provided() {
return RunListBuildWebHooks(f, out, cmd.Out(), name, isBuild, webhooks.String()) |
af00209a |
} |
8c556b6d |
|
af00209a |
client, _, err := f.Clients()
if err != nil {
return err
} |
14f0c3cb |
|
ab7d732c |
namespace, _, err := f.DefaultNamespace() |
af00209a |
if err != nil {
return err
} |
0f50aa4f |
|
260558d2 |
request := &buildapi.BuildRequest{
ObjectMeta: kapi.ObjectMeta{Name: name},
} |
af00209a |
var newBuild *buildapi.Build |
260558d2 |
if isBuild {
if newBuild, err = client.Builds(namespace).Clone(request); err != nil { |
af00209a |
return err
}
} else { |
260558d2 |
if newBuild, err = client.BuildConfigs(namespace).Instantiate(request); err != nil { |
af00209a |
return err
}
} |
5ce468b4 |
fmt.Fprintf(out, "%s\n", newBuild.Name) |
50f134ea |
|
af00209a |
if follow { |
5ce468b4 |
opts := buildapi.BuildLogOptions{
Follow: true,
NoWait: false,
}
rd, err := client.BuildLogs(namespace).Get(newBuild.Name, opts).Stream() |
af00209a |
if err != nil { |
5ce468b4 |
return fmt.Errorf("error getting logs: %v", err) |
af00209a |
} |
5ce468b4 |
defer rd.Close()
_, err = io.Copy(out, rd)
if err != nil {
return fmt.Errorf("error streaming logs: %v", err) |
af00209a |
} |
8c556b6d |
} |
af00209a |
return nil |
8c556b6d |
} |
25189a16 |
|
260558d2 |
// RunListBuildWebHooks prints the webhooks for the provided build config.
func RunListBuildWebHooks(f *clientcmd.Factory, out, errOut io.Writer, name string, isBuild bool, webhookFilter string) error {
generic, github := false, false
prefix := false
switch webhookFilter {
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'")
}
client, _, err := f.Clients()
if err != nil {
return err
} |
ab7d732c |
namespace, _, err := f.DefaultNamespace() |
260558d2 |
if err != nil {
return err
}
if isBuild {
build, err := client.Builds(namespace).Get(name)
if err != nil {
return err
} |
8933ecc1 |
ref := build.Status.Config |
260558d2 |
if ref == nil { |
821ddfce |
return fmt.Errorf("the provided Build %q was not created from a BuildConfig and cannot have webhooks", name) |
260558d2 |
}
if len(ref.Namespace) > 0 {
namespace = ref.Namespace
}
name = ref.Name
}
config, err := client.BuildConfigs(namespace).Get(name)
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
}
url, err := client.BuildConfigs(namespace).WebHookURL(name, &t)
if err != nil {
if err != osclient.ErrTriggerIsNotAWebHook {
fmt.Fprintf(errOut, "error: unable to get webhook for %s: %v", name, err)
}
continue
}
fmt.Fprintf(out, "%s%s\n", hookType, url.String())
}
return nil
}
|
25189a16 |
// RunStartBuildWebHook tries to trigger the provided webhook. It will attempt to utilize the current client
// configuration if the webhook has the same URL. |
e7387750 |
func RunStartBuildWebHook(f *clientcmd.Factory, out io.Writer, webhook string, path, postReceivePath string, repo git.Repository) error {
hook, err := url.Parse(webhook)
if err != nil {
return err |
25189a16 |
}
|
e7387750 |
event, err := hookEventFromPostReceive(repo, path, postReceivePath) |
25189a16 |
if err != nil {
return err
} |
e7387750 |
// TODO: should be a versioned struct
data, err := json.Marshal(event)
if err != nil {
return err
}
|
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" {
config, err := f.OpenShiftClientConfig.ClientConfig()
if err == nil {
if url, err := client.DefaultServerURL(config.Host, "", "test", true); err == nil {
if url.Host == hook.Host && url.Scheme == hook.Scheme {
if rt, err := client.TransportFor(config); err == nil {
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
} |
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
// post receive input. If no inputs are available will return an empty event.
func hookEventFromPostReceive(repo git.Repository, path, postReceivePath string) (*buildapi.GenericWebHookEvent, error) {
// TODO: support other types of refs
event := &buildapi.GenericWebHookEvent{
Type: buildapi.BuildSourceGit,
Git: &buildapi.GitInfo{},
}
// 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
}
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
}
out, err := repo.ShowFormat(dir, ref, "%an%n%ae%n%cn%n%ce%n%B")
if err != nil {
return info, err
}
lines := strings.SplitN(out, "\n", 5)
if len(lines) != 5 {
full := make([]string, 5)
copy(full, lines)
lines = full
}
info.Author.Name = lines[0]
info.Author.Email = lines[1]
info.Committer.Name = lines[2]
info.Committer.Email = lines[3]
info.Message = lines[4]
return info, nil
} |