diff --git a/cmd/helm/release_testing.go b/cmd/helm/release_testing.go index e4e09ef3b..fbf0dd112 100644 --- a/cmd/helm/release_testing.go +++ b/cmd/helm/release_testing.go @@ -19,6 +19,8 @@ package main import ( "fmt" "io" + "regexp" + "strings" "time" "github.com/spf13/cobra" @@ -39,6 +41,7 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command client := action.NewReleaseTesting(cfg) var outfmt = output.Table var outputLogs bool + var filter []string cmd := &cobra.Command{ Use: "test [RELEASE]", @@ -53,6 +56,14 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command }, RunE: func(cmd *cobra.Command, args []string) error { client.Namespace = settings.Namespace() + notName := regexp.MustCompile(`^!\s?name=`) + for _, f := range filter { + if strings.HasPrefix(f, "name=") { + client.Filters["name"] = append(client.Filters["name"], strings.TrimPrefix(f, "name=")) + } else if notName.MatchString(f) { + client.Filters["!name"] = append(client.Filters["!name"], notName.ReplaceAllLiteralString(f, "")) + } + } 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 @@ -80,6 +91,7 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command 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)") + f.StringSliceVar(&filter, "filter", []string{}, "specify tests by attribute (currently \"name\") using attribute=value syntax or '!attribute=value' to exclude a test (can specify multiple or separate values with commas: name=test1,name=test2)") return cmd } diff --git a/cmd/helm/testdata/helmhome/helm/repository/test-name-index.yaml b/cmd/helm/testdata/helmhome/helm/repository/test-name-index.yaml index 895e79d39..d5ab620ad 100644 --- a/cmd/helm/testdata/helmhome/helm/repository/test-name-index.yaml +++ b/cmd/helm/testdata/helmhome/helm/repository/test-name-index.yaml @@ -1,3 +1,3 @@ apiVersion: v1 entries: {} -generated: "2020-06-23T10:01:59.2530763-07:00" +generated: "2020-09-09T19:50:50.198347916-04:00" diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go index 2f6f5cfce..ecaeaf59f 100644 --- a/pkg/action/release_testing.go +++ b/pkg/action/release_testing.go @@ -37,12 +37,14 @@ type ReleaseTesting struct { Timeout time.Duration // Used for fetching logs from test pods Namespace string + Filters map[string][]string } // NewReleaseTesting creates a new ReleaseTesting object with the given configuration. func NewReleaseTesting(cfg *Configuration) *ReleaseTesting { return &ReleaseTesting{ - cfg: cfg, + cfg: cfg, + Filters: map[string][]string{}, } } @@ -62,11 +64,37 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) { return rel, err } + skippedHooks := []*release.Hook{} + executingHooks := []*release.Hook{} + if len(r.Filters["!name"]) != 0 { + for _, h := range rel.Hooks { + if contains(r.Filters["!name"], h.Name) { + skippedHooks = append(skippedHooks, h) + } else { + executingHooks = append(executingHooks, h) + } + } + rel.Hooks = executingHooks + } + if len(r.Filters["name"]) != 0 { + executingHooks = nil + for _, h := range rel.Hooks { + if contains(r.Filters["name"], h.Name) { + executingHooks = append(executingHooks, h) + } else { + skippedHooks = append(skippedHooks, h) + } + } + rel.Hooks = executingHooks + } + if err := r.cfg.execHook(rel, release.HookTest, r.Timeout); err != nil { + rel.Hooks = append(skippedHooks, rel.Hooks...) r.cfg.Releases.Update(rel) return rel, err } + rel.Hooks = append(skippedHooks, rel.Hooks...) return rel, r.cfg.Releases.Update(rel) } @@ -99,3 +127,12 @@ func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error { } return nil } + +func contains(arr []string, value string) bool { + for _, item := range arr { + if item == value { + return true + } + } + return false +}