diff --git a/cmd/deploy/deploy.go b/cmd/deploy/deploy.go index 426d597a..5726756c 100644 --- a/cmd/deploy/deploy.go +++ b/cmd/deploy/deploy.go @@ -31,10 +31,11 @@ func main() { } func doDeploy() int { - var context, namespace string + var context, namespace, timeout string const ( contextUsage = "override the context for default environment deployment target" namespaceUsage = "override the namespace for default environment deployment target" + timeoutUsage = "override the default deployment timeout (2 minutes). 0 means forever, all other values should contain a corresponding time unit (e.g. 1s, 2m, 3h)" ) set := flag.NewFlagSet("deploy", flag.ExitOnError) set.Usage = func() { @@ -45,6 +46,8 @@ func doDeploy() int { set.StringVar(&context, "c", "", contextUsage+" (shorthand)") set.StringVar(&namespace, "namespace", "", namespaceUsage) set.StringVar(&namespace, "n", "", namespaceUsage+" (shorthand)") + set.StringVar(&timeout, "timeout", "", timeoutUsage) + set.StringVar(&timeout, "t", "", timeoutUsage+" (shorthand)") _ = set.Parse(os.Args[1:]) if set.NArg() < 1 { set.Usage() @@ -67,6 +70,9 @@ func doDeploy() int { if namespace != "" { env.Namespace = namespace } + if timeout == "" { + timeout = "2m" + } currentCI := cfg.CurrentCI() if !ci.IsValid(currentCI) { _, _ = fmt.Println(tml.Sprintf("Commit and/or branch information is missing. Perhaps your not in a Git repository or forgot to set environment variables?")) @@ -76,7 +82,7 @@ func doDeploy() int { tstamp := time.Now().Format(time.RFC3339) client := kubectl.New(env, os.Stdout, os.Stderr) defer client.Cleanup() - if err := deploy.Deploy(dir, currentCI.Commit(), currentCI.BuildName(), tstamp, environment, client, os.Stdout, os.Stderr); err != nil { + if err := deploy.Deploy(dir, currentCI.Commit(), currentCI.BuildName(), tstamp, environment, client, os.Stdout, os.Stderr, timeout); err != nil { fmt.Println(err.Error()) return -4 } diff --git a/cmd/deploy/deploy_test.go b/cmd/deploy/deploy_test.go index 1900cf82..4bd6573a 100644 --- a/cmd/deploy/deploy_test.go +++ b/cmd/deploy/deploy_test.go @@ -163,3 +163,30 @@ environments: os.Args = []string{"deploy", "-c", "other", "-n", "dev", "dummy"} main() } + +func TestDeploy_Timeout(t *testing.T) { + exitFunc = func(code int) { + assert.Equal(t, -4, code) + } + + defer pkg.SetEnv("CI_COMMIT_SHA", "abc123")() + defer pkg.SetEnv("CI_PROJECT_NAME", "dummy")() + defer pkg.SetEnv("CI_COMMIT_REF_NAME", "master")() + oldPwd, _ := os.Getwd() + name, _ := ioutil.TempDir(os.TempDir(), "build-tools") + defer func() { _ = os.RemoveAll(name) }() + yaml := ` +environments: + dummy: + context: missing + namespace: none +` + _ = ioutil.WriteFile(filepath.Join(name, ".buildtools.yaml"), []byte(yaml), 0777) + + err := os.Chdir(name) + assert.NoError(t, err) + defer func() { _ = os.Chdir(oldPwd) }() + + os.Args = []string{"deploy", "-t", "20s", "dummy"} + main() +} diff --git a/pkg/deploy/deploy.go b/pkg/deploy/deploy.go index d671429c..a7152f5c 100644 --- a/pkg/deploy/deploy.go +++ b/pkg/deploy/deploy.go @@ -11,14 +11,14 @@ import ( "strings" ) -func Deploy(dir, commit, buildName, timestamp, targetEnvironment string, client kubectl.Kubectl, out, eout io.Writer) error { +func Deploy(dir, commit, buildName, timestamp, targetEnvironment string, client kubectl.Kubectl, out, eout io.Writer, timeout string) error { deploymentFiles := filepath.Join(dir, "k8s") if err := processDir(deploymentFiles, commit, timestamp, targetEnvironment, client, out, eout); err != nil { return err } if client.DeploymentExists(buildName) { - if !client.RolloutStatus(buildName) { + if !client.RolloutStatus(buildName, timeout) { _, _ = fmt.Fprint(out, "Rollout failed. Fetching events.") _, _ = fmt.Fprint(out, client.DeploymentEvents(buildName)) _, _ = fmt.Fprint(out, client.PodEvents(buildName)) diff --git a/pkg/deploy/deploy_test.go b/pkg/deploy/deploy_test.go index bcf13c8c..0bc74832 100644 --- a/pkg/deploy/deploy_test.go +++ b/pkg/deploy/deploy_test.go @@ -20,7 +20,7 @@ func TestDeploy_MissingDeploymentFilesDir(t *testing.T) { out := &bytes.Buffer{} eout := &bytes.Buffer{} - err := Deploy(".", "abc123", "image", "20190513-17:22:36", "test", client, out, eout) + err := Deploy(".", "abc123", "image", "20190513-17:22:36", "test", client, out, eout, "2m") assert.EqualError(t, err, "open k8s: no such file or directory") assert.Equal(t, "", out.String()) @@ -38,7 +38,7 @@ func TestDeploy_NoFiles(t *testing.T) { out := &bytes.Buffer{} eout := &bytes.Buffer{} - err := Deploy(name, "abc123", "image", "20190513-17:22:36", "test", client, out, eout) + err := Deploy(name, "abc123", "image", "20190513-17:22:36", "test", client, out, eout, "2m") assert.NoError(t, err) assert.Equal(t, 0, len(client.Inputs)) @@ -65,7 +65,7 @@ metadata: out := &bytes.Buffer{} eout := &bytes.Buffer{} - err := Deploy(name, "abc123", "image", "20190513-17:22:36", "test", client, out, eout) + err := Deploy(name, "abc123", "image", "20190513-17:22:36", "test", client, out, eout, "2m") assert.NoError(t, err) assert.Equal(t, 1, len(client.Inputs)) @@ -85,7 +85,7 @@ func TestDeploy_UnreadableFile(t *testing.T) { out := &bytes.Buffer{} eout := &bytes.Buffer{} - err := Deploy(name, "abc123", "image", "20190513-17:22:36", "test", client, out, eout) + err := Deploy(name, "abc123", "image", "20190513-17:22:36", "test", client, out, eout, "2m") assert.EqualError(t, err, fmt.Sprintf("read %s/k8s/deploy.yaml: is a directory", name)) assert.Equal(t, "", out.String()) @@ -107,7 +107,7 @@ func TestDeploy_FileBrokenSymlink(t *testing.T) { out := &bytes.Buffer{} eout := &bytes.Buffer{} - err := Deploy(name, "abc123", "image", "20190513-17:22:36", "test", client, out, eout) + err := Deploy(name, "abc123", "image", "20190513-17:22:36", "test", client, out, eout, "2m") assert.EqualError(t, err, fmt.Sprintf("open %s/k8s/deploy.yaml: no such file or directory", name)) assert.Equal(t, "", out.String()) @@ -133,7 +133,7 @@ metadata: out := &bytes.Buffer{} eout := &bytes.Buffer{} - err := Deploy(name, "abc123", "image", "20190513-17:22:36", "dummy", client, out, eout) + err := Deploy(name, "abc123", "image", "20190513-17:22:36", "dummy", client, out, eout, "2m") assert.NoError(t, err) assert.Equal(t, 1, len(client.Inputs)) @@ -157,7 +157,7 @@ func TestDeploy_EnvSpecificFiles(t *testing.T) { out := &bytes.Buffer{} eout := &bytes.Buffer{} - err := Deploy(name, "abc123", "image", "20190513-17:22:36", "prod", client, out, eout) + err := Deploy(name, "abc123", "image", "20190513-17:22:36", "prod", client, out, eout, "2m") assert.NoError(t, err) assert.Equal(t, 1, len(client.Inputs)) @@ -180,7 +180,7 @@ echo "Prod-script with suffix"` out := &bytes.Buffer{} eout := &bytes.Buffer{} - err := Deploy(name, "abc123", "image", "20190513-17:22:36", "prod", client, out, eout) + err := Deploy(name, "abc123", "image", "20190513-17:22:36", "prod", client, out, eout, "2m") assert.NoError(t, err) assert.Equal(t, 0, len(client.Inputs)) @@ -202,7 +202,7 @@ echo "Script without suffix should not execute"` out := &bytes.Buffer{} eout := &bytes.Buffer{} - err := Deploy(name, "abc123", "image", "20190513-17:22:36", "prod", client, out, eout) + err := Deploy(name, "abc123", "image", "20190513-17:22:36", "prod", client, out, eout, "2m") assert.NoError(t, err) assert.Equal(t, 0, len(client.Inputs)) @@ -225,7 +225,7 @@ echo "Prod-script with suffix"` out := &bytes.Buffer{} eout := &bytes.Buffer{} - err := Deploy(name, "abc123", "image", "20190513-17:22:36", "prod", client, out, eout) + err := Deploy(name, "abc123", "image", "20190513-17:22:36", "prod", client, out, eout, "2m") assert.EqualError(t, err, fmt.Sprintf("fork/exec %s: permission denied", scriptName)) } @@ -249,7 +249,7 @@ metadata: out := &bytes.Buffer{} eout := &bytes.Buffer{} - err := Deploy(name, "abc123", "image", "20190513-17:22:36", "test", client, out, eout) + err := Deploy(name, "abc123", "image", "20190513-17:22:36", "test", client, out, eout, "2m") assert.EqualError(t, err, "apply failed") assert.Equal(t, "", out.String()) @@ -277,7 +277,7 @@ metadata: out := &bytes.Buffer{} eout := &bytes.Buffer{} - err := Deploy(name, "abc123", "image", "2019-05-13T17:22:36Z01:00", "test", client, out, eout) + err := Deploy(name, "abc123", "image", "2019-05-13T17:22:36Z01:00", "test", client, out, eout, "2m") assert.NoError(t, err) assert.Equal(t, 1, len(client.Inputs)) @@ -314,7 +314,7 @@ metadata: out := &bytes.Buffer{} eout := &bytes.Buffer{} - err := Deploy(name, "abc123", "image", "20190513-17:22:36", "test", client, out, eout) + err := Deploy(name, "abc123", "image", "20190513-17:22:36", "test", client, out, eout, "2m") assert.EqualError(t, err, "failed to rollout") assert.Equal(t, 1, len(client.Inputs)) @@ -344,7 +344,7 @@ metadata: out := &bytes.Buffer{} eout := &bytes.Buffer{} - err := Deploy(name, "abc123", "image", "20190513-17:22:36", "test", client, out, eout) + err := Deploy(name, "abc123", "image", "20190513-17:22:36", "test", client, out, eout, "2m") assert.EqualError(t, err, "failed to rollout") assert.Equal(t, 1, len(client.Inputs)) diff --git a/pkg/kubectl/kubectl.go b/pkg/kubectl/kubectl.go index b9f267da..70faa992 100644 --- a/pkg/kubectl/kubectl.go +++ b/pkg/kubectl/kubectl.go @@ -23,7 +23,7 @@ type Kubectl interface { Apply(input string) error Cleanup() DeploymentExists(name string) bool - RolloutStatus(name string) bool + RolloutStatus(name, timeout string) bool DeploymentEvents(name string) string PodEvents(name string) string } @@ -114,9 +114,9 @@ func (k kubectl) DeploymentExists(name string) bool { return c.Execute() == nil } -func (k kubectl) RolloutStatus(name string) bool { +func (k kubectl) RolloutStatus(name, timeout string) bool { args := k.defaultArgs() - args = append(args, "rollout", "status", "deployment", "--timeout=2m", name) + args = append(args, "rollout", "status", "deployment", fmt.Sprintf("--timeout=%s", timeout), name) _, _ = fmt.Fprintf(k.out, "kubectl %s\n", strings.Join(args, " ")) c := newKubectlCmd(os.Stdin, os.Stdout, os.Stderr) c.SetArgs(args) diff --git a/pkg/kubectl/kubectl_test.go b/pkg/kubectl/kubectl_test.go index ae9ff899..669f9034 100644 --- a/pkg/kubectl/kubectl_test.go +++ b/pkg/kubectl/kubectl_test.go @@ -147,7 +147,7 @@ func TestKubectl_RolloutStatusSuccess(t *testing.T) { k := New(&config.Environment{Context: "missing", Namespace: "default"}, out, eout) - result := k.RolloutStatus("image") + result := k.RolloutStatus("image", "2m") assert.True(t, result) assert.Equal(t, 1, len(calls)) assert.Equal(t, []string{"rollout", "status", "deployment", "image", "--context", "missing", "--namespace", "default", "--timeout", "2m0s"}, calls[0]) @@ -165,7 +165,7 @@ func TestKubectl_RolloutStatusFailure(t *testing.T) { k := New(&config.Environment{Context: "missing", Namespace: "default"}, out, eout) - result := k.RolloutStatus("image") + result := k.RolloutStatus("image", "2m") assert.False(t, result) assert.Equal(t, 1, len(calls)) assert.Equal(t, []string{"rollout", "status", "deployment", "image", "--context", "missing", "--namespace", "default", "--timeout", "2m0s"}, calls[0]) @@ -186,11 +186,11 @@ func TestKubectl_RolloutStatusFatal(t *testing.T) { k := New(&config.Environment{Context: "missing", Namespace: "default"}, out, eout) - result := k.RolloutStatus("image") + result := k.RolloutStatus("image", "3m") assert.False(t, result) assert.Equal(t, 1, len(calls)) - assert.Equal(t, []string{"rollout", "status", "deployment", "image", "--context", "missing", "--namespace", "default", "--timeout", "2m0s"}, calls[0]) - assert.Equal(t, "kubectl --context missing --namespace default rollout status deployment --timeout=2m image\n", out.String()) + assert.Equal(t, []string{"rollout", "status", "deployment", "image", "--context", "missing", "--namespace", "default", "--timeout", "3m0s"}, calls[0]) + assert.Equal(t, "kubectl --context missing --namespace default rollout status deployment --timeout=3m image\n", out.String()) assert.Equal(t, "failed to get kubeconfig from environment\n", eout.String()) } diff --git a/pkg/kubectl/testing.go b/pkg/kubectl/testing.go index 5427c9e9..6125de13 100644 --- a/pkg/kubectl/testing.go +++ b/pkg/kubectl/testing.go @@ -21,7 +21,7 @@ func (m *MockKubectl) DeploymentExists(name string) bool { return m.Deployment } -func (m *MockKubectl) RolloutStatus(name string) bool { +func (m *MockKubectl) RolloutStatus(name, timeout string) bool { return m.Status } diff --git a/www/content/commands.md b/www/content/commands.md index 819fae9b..0270ad69 100644 --- a/www/content/commands.md +++ b/www/content/commands.md @@ -38,10 +38,11 @@ $ push -f docker/Dockerfile.build Deploys the built application to a Kubernetes cluster. Normal usage `deploy `, but additional flags can be used to override: -| Flag | Description | -| :--------------------------------- | :------------------------------------------------------------ | -| `-c/--context` | Use a different context than the one found in configuration | -| `-n/--namespace` | Use a different namespace than the one found in configuration | +| Flag | Description | +| :--------------------------------- | :-------------------------------------------------------------------------------| +| `-c/--context` | Use a different context than the one found in configuration | +| `-n/--namespace` | Use a different namespace than the one found in configuration | +| `-t/--timeout` | Override the default deployment waiting time for completion (default 2 minutes). 0 means forever, all other values should contain a corresponding time unit (e.g. 1s, 2m, 3h) | ```sh $ deploy -n testing_namespace local