From e27f24e29f8425a1feadf08153436eac2b382324 Mon Sep 17 00:00:00 2001 From: Pradeep Kumar Date: Tue, 31 Mar 2020 16:05:56 +0530 Subject: [PATCH] v1beta1 support for clustertask delete Signed-off-by: Pradeep Kumar --- pkg/cmd/clustertask/delete.go | 8 +- pkg/cmd/clustertask/delete_test.go | 233 +++++++++++++++++++++++++++-- pkg/cmd/clustertask/list.go | 29 +++- 3 files changed, 245 insertions(+), 25 deletions(-) diff --git a/pkg/cmd/clustertask/delete.go b/pkg/cmd/clustertask/delete.go index e52efdc03..703475467 100644 --- a/pkg/cmd/clustertask/delete.go +++ b/pkg/cmd/clustertask/delete.go @@ -18,10 +18,12 @@ import ( "fmt" "github.com/spf13/cobra" + ctaction "github.com/tektoncd/cli/pkg/actions/delete" "github.com/tektoncd/cli/pkg/cli" "github.com/tektoncd/cli/pkg/deleter" "github.com/tektoncd/cli/pkg/options" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" cliopts "k8s.io/cli-runtime/pkg/genericclioptions" ) @@ -69,12 +71,14 @@ or } func deleteClusterTasks(s *cli.Stream, p cli.Params, tNames []string, deleteAll bool) error { + ctGroupResource := schema.GroupVersionResource{Group: "tekton.dev", Resource: "clustertasks"} + cs, err := p.Clients() if err != nil { return fmt.Errorf("Failed to create tekton client") } d := deleter.New("ClusterTask", func(taskName string) error { - return cs.Tekton.TektonV1alpha1().ClusterTasks().Delete(taskName, &metav1.DeleteOptions{}) + return ctaction.Delete(ctGroupResource, cs, taskName, "", &metav1.DeleteOptions{}) }) if deleteAll { cts, err := allClusterTaskNames(cs) @@ -97,7 +101,7 @@ func deleteClusterTasks(s *cli.Stream, p cli.Params, tNames []string, deleteAll } func allClusterTaskNames(cs *cli.Clients) ([]string, error) { - clusterTasks, err := cs.Tekton.TektonV1alpha1().ClusterTasks().List(metav1.ListOptions{}) + clusterTasks, err := List(cs, metav1.ListOptions{}) if err != nil { return nil, err } diff --git a/pkg/cmd/clustertask/delete_test.go b/pkg/cmd/clustertask/delete_test.go index 6752e99ab..a6791329a 100644 --- a/pkg/cmd/clustertask/delete_test.go +++ b/pkg/cmd/clustertask/delete_test.go @@ -23,15 +23,23 @@ import ( "github.com/jonboulle/clockwork" "github.com/tektoncd/cli/pkg/test" cb "github.com/tektoncd/cli/pkg/test/builder" + testDynamic "github.com/tektoncd/cli/pkg/test/dynamic" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" pipelinetest "github.com/tektoncd/pipeline/test" tb "github.com/tektoncd/pipeline/test/builder" + "k8s.io/client-go/dynamic" ) func TestClusterTaskDelete(t *testing.T) { + version := "v1alpha1" clock := clockwork.NewFakeClock() - seeds := make([]pipelinetest.Clients, 0) + type clients struct { + pipelineClient pipelinetest.Clients + dynamicClient dynamic.Interface + } + + seeds := make([]clients, 0) for i := 0; i < 5; i++ { clustertasks := []*v1alpha1.ClusterTask{ tb.ClusterTask("tomatoes", cb.ClusterTaskCreationTime(clock.Now().Add(-1*time.Minute))), @@ -39,12 +47,195 @@ func TestClusterTaskDelete(t *testing.T) { tb.ClusterTask("tomatoes3", cb.ClusterTaskCreationTime(clock.Now().Add(-1*time.Minute))), } cs, _ := test.SeedTestData(t, pipelinetest.Data{ClusterTasks: clustertasks}) - seeds = append(seeds, cs) + cs.Pipeline.Resources = cb.APIResourceList(version, []string{"clustertask"}) + dc, err := testDynamic.Client( + cb.UnstructuredCT(clustertasks[0], version), + cb.UnstructuredCT(clustertasks[1], version), + cb.UnstructuredCT(clustertasks[2], version), + ) + if err != nil { + t.Errorf("unable to create dynamic clinet: %v", err) + } + seeds = append(seeds, clients{cs, dc}) + } + + testParams := []struct { + name string + command []string + dynamic dynamic.Interface + input pipelinetest.Clients + inputStream io.Reader + wantError bool + want string + }{ + { + name: "With force delete flag (shorthand)", + command: []string{"rm", "tomatoes", "-f"}, + dynamic: seeds[0].dynamicClient, + input: seeds[0].pipelineClient, + inputStream: nil, + wantError: false, + want: "ClusterTasks deleted: \"tomatoes\"\n", + }, + { + name: "With force delete flag", + command: []string{"rm", "tomatoes", "--force"}, + dynamic: seeds[1].dynamicClient, + input: seeds[1].pipelineClient, + inputStream: nil, + wantError: false, + want: "ClusterTasks deleted: \"tomatoes\"\n", + }, + { + name: "Without force delete flag, reply no", + command: []string{"rm", "tomatoes"}, + dynamic: seeds[2].dynamicClient, + input: seeds[2].pipelineClient, + inputStream: strings.NewReader("n"), + wantError: true, + want: "canceled deleting clustertask \"tomatoes\"", + }, + { + name: "Without force delete flag, reply yes", + command: []string{"rm", "tomatoes"}, + dynamic: seeds[3].dynamicClient, + input: seeds[3].pipelineClient, + inputStream: strings.NewReader("y"), + wantError: false, + want: "Are you sure you want to delete clustertask \"tomatoes\" (y/n): ClusterTasks deleted: \"tomatoes\"\n", + }, + { + name: "Remove non existent resource", + command: []string{"rm", "nonexistent"}, + dynamic: seeds[2].dynamicClient, + input: seeds[2].pipelineClient, + inputStream: strings.NewReader("y"), + wantError: true, + want: "failed to delete clustertask \"nonexistent\": clustertasks.tekton.dev \"nonexistent\" not found", + }, + { + name: "Remove multiple non existent resources", + command: []string{"rm", "nonexistent", "nonexistent2", "-n", "ns"}, + dynamic: seeds[2].dynamicClient, + input: seeds[2].pipelineClient, + inputStream: strings.NewReader("y"), + wantError: true, + want: "failed to delete clustertask \"nonexistent\": clustertasks.tekton.dev \"nonexistent\" not found; failed to delete clustertask \"nonexistent2\": clustertasks.tekton.dev \"nonexistent2\" not found", + }, + { + name: "With force delete flag, reply yes, multiple clustertasks", + command: []string{"rm", "tomatoes2", "tomatoes3", "-f"}, + dynamic: seeds[1].dynamicClient, + input: seeds[1].pipelineClient, + inputStream: strings.NewReader("y"), + wantError: false, + want: "ClusterTasks deleted: \"tomatoes2\", \"tomatoes3\"\n", + }, + { + name: "Without force delete flag, reply yes, multiple clustertasks", + command: []string{"rm", "tomatoes2", "tomatoes3"}, + dynamic: seeds[2].dynamicClient, + input: seeds[2].pipelineClient, + inputStream: strings.NewReader("y"), + wantError: false, + want: "Are you sure you want to delete clustertask \"tomatoes2\", \"tomatoes3\" (y/n): ClusterTasks deleted: \"tomatoes2\", \"tomatoes3\"\n", + }, + { + name: "Delete all with prompt", + command: []string{"delete", "--all"}, + dynamic: seeds[3].dynamicClient, + input: seeds[3].pipelineClient, + inputStream: strings.NewReader("y"), + wantError: false, + want: "Are you sure you want to delete all clustertasks (y/n): All ClusterTasks deleted\n", + }, + { + name: "Delete all with -f", + command: []string{"delete", "--all", "-f"}, + dynamic: seeds[4].dynamicClient, + input: seeds[4].pipelineClient, + inputStream: nil, + wantError: false, + want: "All ClusterTasks deleted\n", + }, + { + name: "Error from using clustertask name with --all", + command: []string{"delete", "ct", "--all"}, + dynamic: seeds[4].dynamicClient, + input: seeds[4].pipelineClient, + inputStream: nil, + wantError: true, + want: "--all flag should not have any arguments or flags specified with it", + }, + { + name: "Error from using clustertask delete with no names or --all", + command: []string{"delete"}, + dynamic: seeds[4].dynamicClient, + input: seeds[4].pipelineClient, + inputStream: nil, + wantError: true, + want: "must provide clustertask name(s) or use --all flag with delete", + }, + } + + for _, tp := range testParams { + t.Run(tp.name, func(t *testing.T) { + p := &test.Params{Tekton: tp.input.Pipeline, Dynamic: tp.dynamic} + clustertask := Command(p) + + if tp.inputStream != nil { + clustertask.SetIn(tp.inputStream) + } + + out, err := test.ExecuteCommand(clustertask, tp.command...) + if tp.wantError { + if err == nil { + t.Errorf("Error expected here") + } + test.AssertOutput(t, tp.want, err.Error()) + } else { + if err != nil { + t.Errorf("Unexpected Error") + } + test.AssertOutput(t, tp.want, out) + } + }) + } +} + +func TestClusterTaskDelete_v1beta1(t *testing.T) { + version := "v1beta1" + clock := clockwork.NewFakeClock() + + type clients struct { + pipelineClient pipelinetest.Clients + dynamicClient dynamic.Interface + } + + seeds := make([]clients, 0) + for i := 0; i < 5; i++ { + clustertasks := []*v1alpha1.ClusterTask{ + tb.ClusterTask("tomatoes", cb.ClusterTaskCreationTime(clock.Now().Add(-1*time.Minute))), + tb.ClusterTask("tomatoes2", cb.ClusterTaskCreationTime(clock.Now().Add(-1*time.Minute))), + tb.ClusterTask("tomatoes3", cb.ClusterTaskCreationTime(clock.Now().Add(-1*time.Minute))), + } + cs, _ := test.SeedTestData(t, pipelinetest.Data{ClusterTasks: clustertasks}) + cs.Pipeline.Resources = cb.APIResourceList(version, []string{"clustertask"}) + dc, err := testDynamic.Client( + cb.UnstructuredCT(clustertasks[0], version), + cb.UnstructuredCT(clustertasks[1], version), + cb.UnstructuredCT(clustertasks[2], version), + ) + if err != nil { + t.Errorf("unable to create dynamic clinet: %v", err) + } + seeds = append(seeds, clients{cs, dc}) } testParams := []struct { name string command []string + dynamic dynamic.Interface input pipelinetest.Clients inputStream io.Reader wantError bool @@ -53,7 +244,8 @@ func TestClusterTaskDelete(t *testing.T) { { name: "With force delete flag (shorthand)", command: []string{"rm", "tomatoes", "-f"}, - input: seeds[0], + dynamic: seeds[0].dynamicClient, + input: seeds[0].pipelineClient, inputStream: nil, wantError: false, want: "ClusterTasks deleted: \"tomatoes\"\n", @@ -61,7 +253,8 @@ func TestClusterTaskDelete(t *testing.T) { { name: "With force delete flag", command: []string{"rm", "tomatoes", "--force"}, - input: seeds[1], + dynamic: seeds[1].dynamicClient, + input: seeds[1].pipelineClient, inputStream: nil, wantError: false, want: "ClusterTasks deleted: \"tomatoes\"\n", @@ -69,7 +262,8 @@ func TestClusterTaskDelete(t *testing.T) { { name: "Without force delete flag, reply no", command: []string{"rm", "tomatoes"}, - input: seeds[2], + dynamic: seeds[2].dynamicClient, + input: seeds[2].pipelineClient, inputStream: strings.NewReader("n"), wantError: true, want: "canceled deleting clustertask \"tomatoes\"", @@ -77,7 +271,8 @@ func TestClusterTaskDelete(t *testing.T) { { name: "Without force delete flag, reply yes", command: []string{"rm", "tomatoes"}, - input: seeds[2], + dynamic: seeds[3].dynamicClient, + input: seeds[3].pipelineClient, inputStream: strings.NewReader("y"), wantError: false, want: "Are you sure you want to delete clustertask \"tomatoes\" (y/n): ClusterTasks deleted: \"tomatoes\"\n", @@ -85,7 +280,8 @@ func TestClusterTaskDelete(t *testing.T) { { name: "Remove non existent resource", command: []string{"rm", "nonexistent"}, - input: seeds[2], + dynamic: seeds[2].dynamicClient, + input: seeds[2].pipelineClient, inputStream: strings.NewReader("y"), wantError: true, want: "failed to delete clustertask \"nonexistent\": clustertasks.tekton.dev \"nonexistent\" not found", @@ -93,7 +289,8 @@ func TestClusterTaskDelete(t *testing.T) { { name: "Remove multiple non existent resources", command: []string{"rm", "nonexistent", "nonexistent2", "-n", "ns"}, - input: seeds[2], + dynamic: seeds[2].dynamicClient, + input: seeds[2].pipelineClient, inputStream: strings.NewReader("y"), wantError: true, want: "failed to delete clustertask \"nonexistent\": clustertasks.tekton.dev \"nonexistent\" not found; failed to delete clustertask \"nonexistent2\": clustertasks.tekton.dev \"nonexistent2\" not found", @@ -101,7 +298,8 @@ func TestClusterTaskDelete(t *testing.T) { { name: "With force delete flag, reply yes, multiple clustertasks", command: []string{"rm", "tomatoes2", "tomatoes3", "-f"}, - input: seeds[1], + dynamic: seeds[1].dynamicClient, + input: seeds[1].pipelineClient, inputStream: strings.NewReader("y"), wantError: false, want: "ClusterTasks deleted: \"tomatoes2\", \"tomatoes3\"\n", @@ -109,7 +307,8 @@ func TestClusterTaskDelete(t *testing.T) { { name: "Without force delete flag, reply yes, multiple clustertasks", command: []string{"rm", "tomatoes2", "tomatoes3"}, - input: seeds[2], + dynamic: seeds[2].dynamicClient, + input: seeds[2].pipelineClient, inputStream: strings.NewReader("y"), wantError: false, want: "Are you sure you want to delete clustertask \"tomatoes2\", \"tomatoes3\" (y/n): ClusterTasks deleted: \"tomatoes2\", \"tomatoes3\"\n", @@ -117,7 +316,8 @@ func TestClusterTaskDelete(t *testing.T) { { name: "Delete all with prompt", command: []string{"delete", "--all"}, - input: seeds[3], + dynamic: seeds[3].dynamicClient, + input: seeds[3].pipelineClient, inputStream: strings.NewReader("y"), wantError: false, want: "Are you sure you want to delete all clustertasks (y/n): All ClusterTasks deleted\n", @@ -125,7 +325,8 @@ func TestClusterTaskDelete(t *testing.T) { { name: "Delete all with -f", command: []string{"delete", "--all", "-f"}, - input: seeds[4], + dynamic: seeds[4].dynamicClient, + input: seeds[4].pipelineClient, inputStream: nil, wantError: false, want: "All ClusterTasks deleted\n", @@ -133,7 +334,8 @@ func TestClusterTaskDelete(t *testing.T) { { name: "Error from using clustertask name with --all", command: []string{"delete", "ct", "--all"}, - input: seeds[4], + dynamic: seeds[4].dynamicClient, + input: seeds[4].pipelineClient, inputStream: nil, wantError: true, want: "--all flag should not have any arguments or flags specified with it", @@ -141,7 +343,8 @@ func TestClusterTaskDelete(t *testing.T) { { name: "Error from using clustertask delete with no names or --all", command: []string{"delete"}, - input: seeds[4], + dynamic: seeds[4].dynamicClient, + input: seeds[4].pipelineClient, inputStream: nil, wantError: true, want: "must provide clustertask name(s) or use --all flag with delete", @@ -150,7 +353,7 @@ func TestClusterTaskDelete(t *testing.T) { for _, tp := range testParams { t.Run(tp.name, func(t *testing.T) { - p := &test.Params{Tekton: tp.input.Pipeline} + p := &test.Params{Tekton: tp.input.Pipeline, Dynamic: tp.dynamic} clustertask := Command(p) if tp.inputStream != nil { diff --git a/pkg/cmd/clustertask/list.go b/pkg/cmd/clustertask/list.go index f8eab72ec..fddd4051e 100644 --- a/pkg/cmd/clustertask/list.go +++ b/pkg/cmd/clustertask/list.go @@ -77,14 +77,7 @@ func printClusterTaskDetails(s *cli.Stream, p cli.Params) error { return err } - unstructuredCT, err := list.AllObjecs(schema.GroupVersionResource{Group: "tekton.dev", Resource: "clustertasks"}, cs, "", metav1.ListOptions{}) - if err != nil { - return err - } - var clustertasks v1beta1.ClusterTaskList - if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredCT.UnstructuredContent(), &clustertasks); err != nil { - return err - } + clustertasks, err := List(cs, metav1.ListOptions{}) if err != nil { fmt.Fprintf(os.Stderr, "failed to list clustertasks\n") return err @@ -107,3 +100,23 @@ func printClusterTaskDetails(s *cli.Stream, p cli.Params) error { } return w.Flush() } + +func List(c *cli.Clients, opts metav1.ListOptions) (*v1beta1.ClusterTaskList, error) { + + ctGroupResource := schema.GroupVersionResource{Group: "tekton.dev", Resource: "clustertasks"} + unstructuredCT, err := list.AllObjecs(ctGroupResource, c, "", opts) + if err != nil { + return nil, err + } + + var cts *v1beta1.ClusterTaskList + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredCT.UnstructuredContent(), &cts); err != nil { + return nil, err + } + if err != nil { + fmt.Fprintln(os.Stderr, "failed to list clustertasks") + return nil, err + } + + return cts, nil +}