diff --git a/cmd/helm/release_testing.go b/cmd/helm/release_testing.go index a49816305..7190ec736 100644 --- a/cmd/helm/release_testing.go +++ b/cmd/helm/release_testing.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "fmt" "io" "time" @@ -36,7 +37,8 @@ The tests to be run are defined in the chart that was installed. func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client := action.NewReleaseTesting(cfg) - var outfmt output.Format + var outfmt = output.Table + var outputLogs bool cmd := &cobra.Command{ Use: "test [RELEASE]", @@ -44,17 +46,34 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command Long: releaseTestHelp, Args: require.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - rel, err := client.Run(args[0]) - if err != nil { + client.Namespace = settings.Namespace() + rel, runErr := client.Run(args[0]) + // We only return an error if we weren't even able to get the + // release, otherwise we keep going so we can print status and logs + // if requested + if runErr != nil && rel == nil { + return runErr + } + + if err := outfmt.Write(out, &statusPrinter{rel, settings.Debug}); err != nil { return err } - return outfmt.Write(out, &statusPrinter{rel, settings.Debug}) + if outputLogs { + // Print a newline to stdout to separate the output + fmt.Fprintln(out) + if err := client.GetPodLogs(out, rel); err != nil { + return err + } + } + + return runErr }, } f := cmd.Flags() f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") + f.BoolVar(&outputLogs, "logs", false, "Dump the logs from test pods (this runs after all tests are complete, but before any cleanup)") return cmd } diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go index fbd36bef8..b7a1da757 100644 --- a/pkg/action/release_testing.go +++ b/pkg/action/release_testing.go @@ -17,9 +17,12 @@ limitations under the License. package action import ( + "fmt" + "io" "time" "github.com/pkg/errors" + v1 "k8s.io/api/core/v1" "helm.sh/helm/v3/pkg/release" ) @@ -30,6 +33,8 @@ import ( type ReleaseTesting struct { cfg *Configuration Timeout time.Duration + // Used for fetching logs from test pods + Namespace string } // NewReleaseTesting creates a new ReleaseTesting object with the given configuration. @@ -62,3 +67,33 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) { return rel, r.cfg.Releases.Update(rel) } + +// GetPodLogs will write the logs for all test pods in the given release into +// the given writer. These can be immediately output to the user or captured for +// other uses +func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error { + client, err := r.cfg.KubernetesClientSet() + if err != nil { + return errors.Wrap(err, "unable to get kubernetes client to fetch pod logs") + } + + for _, h := range rel.Hooks { + for _, e := range h.Events { + if e == release.HookTest { + req := client.CoreV1().Pods(r.Namespace).GetLogs(h.Name, &v1.PodLogOptions{}) + logReader, err := req.Stream() + if err != nil { + return errors.Wrapf(err, "unable to get pod logs for %s", h.Name) + } + + fmt.Fprintf(out, "POD LOGS: %s\n", h.Name) + _, err = io.Copy(out, logReader) + fmt.Fprintln(out) + if err != nil { + return errors.Wrapf(err, "unable to write pod logs for %s", h.Name) + } + } + } + } + return nil +}