diff --git a/cmd/helm/status_test.go b/cmd/helm/status_test.go index d9a686dab..8aca8aefb 100644 --- a/cmd/helm/status_test.go +++ b/cmd/helm/status_test.go @@ -90,8 +90,18 @@ func TestStatusCmd(t *testing.T) { &release.Hook{ Name: "bar", Events: []release.HookEvent{release.HookTest}, + LastRun: release.HookExecution{ + StartedAt: mustParseTime("2006-01-02T15:04:05Z"), + CompletedAt: mustParseTime("2006-01-02T15:04:07Z"), + Successful: true, + }, }, ), }} runTestCmd(t, tests) } + +func mustParseTime(t string) time.Time { + res, _ := time.Parse(time.RFC3339, t) + return res +} diff --git a/cmd/helm/testdata/output/status-with-test-suite.txt b/cmd/helm/testdata/output/status-with-test-suite.txt index cc4be3460..6790ea5ea 100644 --- a/cmd/helm/testdata/output/status-with-test-suite.txt +++ b/cmd/helm/testdata/output/status-with-test-suite.txt @@ -3,13 +3,8 @@ LAST DEPLOYED: 2016-01-16 00:00:00 +0000 UTC NAMESPACE: default STATUS: deployed -TEST SUITE: foo -Last Started: 0001-01-01 00:00:00 +0000 UTC -Last Completed: 0001-01-01 00:00:00 +0000 UTC -Successful: false - TEST SUITE: bar -Last Started: 0001-01-01 00:00:00 +0000 UTC -Last Completed: 0001-01-01 00:00:00 +0000 UTC -Successful: false +Last Started: 2006-01-02 15:04:05 +0000 UTC +Last Completed: 2006-01-02 15:04:07 +0000 UTC +Successful: true diff --git a/pkg/action/printer.go b/pkg/action/printer.go index 006c3c24c..6fe3c6385 100644 --- a/pkg/action/printer.go +++ b/pkg/action/printer.go @@ -49,6 +49,10 @@ func PrintRelease(out io.Writer, rel *release.Release) { executions := executionsByHookEvent(rel) if tests, ok := executions[release.HookTest]; ok { for _, h := range tests { + // Don't print anything if hook has not been initiated + if h.LastRun.StartedAt.IsZero() { + continue + } fmt.Fprintf(out, "TEST SUITE: %s\n%s\n%s\n%s\n\n", h.Name, fmt.Sprintf("Last Started: %s", h.LastRun.StartedAt), diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go index bcfe94fe0..d416da6bb 100644 --- a/pkg/action/release_testing.go +++ b/pkg/action/release_testing.go @@ -53,5 +53,10 @@ func (r *ReleaseTesting) Run(name string) error { return err } - return r.cfg.execHook(rel, release.HookTest, r.Timeout) + if err := r.cfg.execHook(rel, release.HookTest, r.Timeout); err != nil { + r.cfg.Releases.Update(rel) + return err + } + + return r.cfg.Releases.Update(rel) } diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 6176ad0d0..93f090af2 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -235,6 +235,8 @@ func (c *Client) watchTimeout(t time.Duration) func(*resource.Info) error { // // - Jobs: A job is marked "Ready" when it has successfully completed. This is // ascertained by watching the Status fields in a job's output. +// - Pods: A pod is marked "Ready" when it has successfully completed. This is +// ascertained by watching the status.phase field in a pod's output. // // Handling for other kinds will be added as necessary. func (c *Client) WatchUntilReady(resources ResourceList, timeout time.Duration) error { @@ -383,8 +385,11 @@ func (c *Client) watchUntilReady(timeout time.Duration, info *resource.Info) err // the status go into a good state. For other types, like ReplicaSet // we don't really do anything to support these as hooks. c.Log("Add/Modify event for %s: %v", info.Name, e.Type) - if kind == "Job" { + switch kind { + case "Job": return c.waitForJob(obj, info.Name) + case "Pod": + return c.waitForPodSuccess(obj, info.Name) } return true, nil case watch.Deleted: @@ -422,6 +427,30 @@ func (c *Client) waitForJob(obj runtime.Object, name string) (bool, error) { return false, nil } +// waitForPodSuccess is a helper that waits for a pod to complete. +// +// This operates on an event returned from a watcher. +func (c *Client) waitForPodSuccess(obj runtime.Object, name string) (bool, error) { + o, ok := obj.(*v1.Pod) + if !ok { + return true, errors.Errorf("expected %s to be a *v1.Pod, got %T", name, obj) + } + + switch o.Status.Phase { + case v1.PodSucceeded: + fmt.Printf("Pod %s succeeded\n", o.Name) + return true, nil + case v1.PodFailed: + return true, errors.Errorf("pod %s failed", o.Name) + case v1.PodPending: + fmt.Printf("Pod %s pending\n", o.Name) + case v1.PodRunning: + fmt.Printf("Pod %s running\n", o.Name) + } + + return false, nil +} + // scrubValidationError removes kubectl info from the message. func scrubValidationError(err error) error { if err == nil { diff --git a/pkg/kube/interface.go b/pkg/kube/interface.go index 2069d8cdd..73dd42835 100644 --- a/pkg/kube/interface.go +++ b/pkg/kube/interface.go @@ -23,7 +23,7 @@ import ( v1 "k8s.io/api/core/v1" ) -// KubernetesClient represents a client capable of communicating with the Kubernetes API. +// Interface represents a client capable of communicating with the Kubernetes API. // // A KubernetesClient must be concurrency safe. type Interface interface { @@ -37,7 +37,8 @@ type Interface interface { // Watch the resource in reader until it is "ready". This method // - // For Jobs, "ready" means the job ran to completion (excited without error). + // For Jobs, "ready" means the Job ran to completion (exited without error). + // For Pods, "ready" means the Pod phase is marked "succeeded". // For all other kinds, it means the kind was created or modified without // error. WatchUntilReady(resources ResourceList, timeout time.Duration) error