diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index af95da597..44f766e04 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -16,6 +16,10 @@ |=== | | Description | PR +| 🎁 +| Add kn service delete --all +| https://github.com/knative/client/pull/836[#836] + | 🐛 | Skip LatestReadyRevisionName if Revision is Pending or Unknown | https://github.com/knative/client/pull/825[#825] diff --git a/docs/cmd/kn_service_delete.md b/docs/cmd/kn_service_delete.md index 394463a53..deaf83777 100644 --- a/docs/cmd/kn_service_delete.md +++ b/docs/cmd/kn_service_delete.md @@ -19,11 +19,15 @@ kn service delete NAME [flags] # Delete a service 'svc2' in 'ns1' namespace kn service delete svc2 -n ns1 + + # Delete all services in 'ns1' namespace + kn service delete --all -n ns1 ``` ### Options ``` + --all Delete all services in a namespace. --async DEPRECATED: please use --no-wait instead. Do not wait for 'service delete' operation to be completed. (default true) -h, --help help for delete -n, --namespace string Specify the namespace to operate in. diff --git a/pkg/kn/commands/service/delete.go b/pkg/kn/commands/service/delete.go index 73a6e17bc..49c3504f7 100644 --- a/pkg/kn/commands/service/delete.go +++ b/pkg/kn/commands/service/delete.go @@ -22,6 +22,7 @@ import ( "github.com/spf13/cobra" "knative.dev/client/pkg/kn/commands" + clientservingv1 "knative.dev/client/pkg/serving/v1" ) // NewServiceDeleteCommand represent 'service delete' command @@ -36,13 +37,26 @@ func NewServiceDeleteCommand(p *commands.KnParams) *cobra.Command { kn service delete svc1 # Delete a service 'svc2' in 'ns1' namespace - kn service delete svc2 -n ns1`, + kn service delete svc2 -n ns1 + + # Delete all services in 'ns1' namespace + kn service delete --all -n ns1`, RunE: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { + all, err := cmd.Flags().GetBool("all") + if err != nil { + return err + } + argsLen := len(args) + + if argsLen < 1 && !all { return errors.New("'service delete' requires the service name(s)") } + if argsLen > 0 && all { + return errors.New("'service delete' with --all flag requires no arguments") + } + namespace, err := p.GetNamespace(cmd) if err != nil { return err @@ -51,6 +65,18 @@ func NewServiceDeleteCommand(p *commands.KnParams) *cobra.Command { if err != nil { return err } + + if all { + args, err = getServiceNames(client) + if err != nil { + return err + } + if len(args) == 0 { + fmt.Fprintf(cmd.OutOrStdout(), "No services found.\n") + return nil + } + } + for _, name := range args { timeout := time.Duration(0) if waitFlags.Wait { @@ -66,7 +92,21 @@ func NewServiceDeleteCommand(p *commands.KnParams) *cobra.Command { return nil }, } + flags := serviceDeleteCommand.Flags() + flags.Bool("all", false, "Delete all services in a namespace.") commands.AddNamespaceFlags(serviceDeleteCommand.Flags(), false) waitFlags.AddConditionWaitFlags(serviceDeleteCommand, commands.WaitDefaultTimeout, "delete", "service", "deleted") return serviceDeleteCommand } + +func getServiceNames(client clientservingv1.KnServingClient) ([]string, error) { + serviceList, err := client.ListServices() + if err != nil { + return []string{}, err + } + serviceNames := []string{} + for _, service := range serviceList.Items { + serviceNames = append(serviceNames, service.Name) + } + return serviceNames, nil +} diff --git a/pkg/kn/commands/service/delete_mock_test.go b/pkg/kn/commands/service/delete_mock_test.go index f901b9ddb..8b5d918fa 100644 --- a/pkg/kn/commands/service/delete_mock_test.go +++ b/pkg/kn/commands/service/delete_mock_test.go @@ -22,6 +22,7 @@ import ( clientservingv1 "knative.dev/client/pkg/serving/v1" "knative.dev/client/pkg/util" "knative.dev/client/pkg/util/mock" + servingv1 "knative.dev/serving/pkg/apis/serving/v1" ) func TestServiceDeleteMock(t *testing.T) { @@ -77,6 +78,56 @@ func TestMultipleServiceDeleteMock(t *testing.T) { r.Validate() } +func TestServiceDeleteAllMock(t *testing.T) { + // New mock client + client := clientservingv1.NewMockKnServiceClient(t) + + // Recording: + r := client.Recorder() + + // Wait for delete event + r.DeleteService("foo", mock.Any(), nil) + r.DeleteService("bar", mock.Any(), nil) + r.DeleteService("baz", mock.Any(), nil) + + service1 := createMockServiceWithParams("foo", "default", "http://foo.default.example.com", "foo-xyz") + service2 := createMockServiceWithParams("bar", "default", "http://bar.default.example.com", "bar-xyz") + service3 := createMockServiceWithParams("baz", "default", "http://baz.default.example.com", "baz-xyz") + serviceList := &servingv1.ServiceList{Items: []servingv1.Service{*service1, *service2, *service3}} + r.ListServices(mock.Any(), serviceList, nil) + + output, err := executeServiceCommand(client, "delete", "--all") + assert.NilError(t, err) + assert.Assert(t, util.ContainsAll(output, "deleted", "foo", "bar", "baz", "default")) + + r.Validate() +} + +func TestServiceDeleteAllErrorFromArgMock(t *testing.T) { + // New mock client + client := clientservingv1.NewMockKnServiceClient(t) + + _, err := executeServiceCommand(client, "delete", "foo", "--all") + assert.Error(t, err, "'service delete' with --all flag requires no arguments") +} + +func TestServiceDeleteAllNoServicesMock(t *testing.T) { + // New mock client + client := clientservingv1.NewMockKnServiceClient(t) + + // Recording: + r := client.Recorder() + + serviceList := &servingv1.ServiceList{Items: []servingv1.Service{}} + r.ListServices(mock.Any(), serviceList, nil) + + output, err := executeServiceCommand(client, "delete", "--all") + assert.NilError(t, err) + assert.Assert(t, util.ContainsAll(output, "No", "services", "found")) + + r.Validate() +} + func TestServiceDeleteNoSvcNameMock(t *testing.T) { // New mock client client := clientservingv1.NewMockKnServiceClient(t) diff --git a/pkg/kn/commands/service/list_mock_test.go b/pkg/kn/commands/service/list_mock_test.go index 0f42f2b33..c3ec42b99 100644 --- a/pkg/kn/commands/service/list_mock_test.go +++ b/pkg/kn/commands/service/list_mock_test.go @@ -94,8 +94,8 @@ func TestServiceListDefaultOutputMock(t *testing.T) { r := client.Recorder() service1 := createMockServiceWithParams("foo", "default", "http://foo.default.example.com", "foo-xyz") - service3 := createMockServiceWithParams("sss", "default", "http://sss.default.example.com", "sss-xyz") service2 := createMockServiceWithParams("bar", "default", "http://bar.default.example.com", "bar-xyz") + service3 := createMockServiceWithParams("sss", "default", "http://sss.default.example.com", "sss-xyz") serviceList := &servingv1.ServiceList{Items: []servingv1.Service{*service1, *service2, *service3}} r.ListServices(mock.Any(), serviceList, nil) diff --git a/test/e2e/service_test.go b/test/e2e/service_test.go index 7efa05bfa..ead29c712 100644 --- a/test/e2e/service_test.go +++ b/test/e2e/service_test.go @@ -58,6 +58,12 @@ func TestService(t *testing.T) { t.Log("create service private and make public") serviceCreatePrivateUpdatePublic(r, "hello-private-public") + + t.Log("delete all services in a namespace") + test.ServiceCreate(r, "svc1") + test.ServiceCreate(r, "service2") + test.ServiceCreate(r, "ksvc3") + serviceDeleteAll(r, "svc1", "service2", "ksvc3") } func serviceCreatePrivate(r *test.KnRunResultCollector, serviceName string) { @@ -133,3 +139,19 @@ func serviceMultipleDelete(r *test.KnRunResultCollector, existService, nonexistS assert.Check(r.T(), strings.Contains(out.Stdout, expectedSuccess), "Failed to get 'successfully deleted' message") assert.Check(r.T(), strings.Contains(out.Stdout, expectedErr), "Failed to get 'not found' error") } + +func serviceDeleteAll(r *test.KnRunResultCollector, service1 string, service2 string, service3 string) { + out := r.KnTest().Kn().Run("service", "list") + r.AssertNoError(out) + assert.Check(r.T(), strings.Contains(out.Stdout, service1), "The service ", service1, " does not exist (but is expected to exist)") + assert.Check(r.T(), strings.Contains(out.Stdout, service2), "The service ", service2, " does not exist (but is expected to exist)") + assert.Check(r.T(), strings.Contains(out.Stdout, service3), "The service ", service3, " does not exist (but is expected to exist)") + + out = r.KnTest().Kn().Run("service", "delete", "--all") + r.AssertNoError(out) + + namespace := r.KnTest().Kn().Namespace() + expectedSuccess := fmt.Sprintf("Service '%s' successfully deleted in namespace '%s'.\nService '%s' successfully deleted in namespace '%s'.\nService '%s' successfully deleted in namespace '%s'.\n", + service3, namespace, service2, namespace, service1, namespace) + assert.Check(r.T(), strings.Contains(out.Stdout, expectedSuccess), "Failed to get 'successfully deleted' message") +}