Repository: knative/client
Pull request #835 (2020-05-13 15:01:12)

fix(e2e): Create next namespace for e2e if one exists
## Description
- If a namespace (kne2etestsN) is being created to run e2e tests and it exists, e2e execution fails. This patch tries creating the next namespace (kne2etestsN+1) if earlier exists, retries cap at 20.
- Tags plugins tests to run into serving or eventing scope but only in e2e scope.


knative-prow-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/knative/client/pull/835#" title="Author self-approved">navidshaikh</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=knative%2Fclient).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/knative/client/blob/master/OWNERS)~~ [navidshaikh]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
navidshaikh: /hold

added a test commit which creates `kne2etests0`, to verify the functionality 
navidshaikh: `kne2etests0` namespace was created before e2e started and it was skipped for e2e. ref: [log](https://prow.knative.dev/view/gcs/knative-prow/pr-logs/pull/knative_client/835/pull-knative-client-integration-tests/1260597247779803138#1:build-log.txt%3A530)
reverting the test commit
/unhold
navidshaikh: cc: @dsimansk 

Patch:
diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index af95da59..44f766e0 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 394463a5..deaf8377 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 73a6e17b..49c3504f 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
+}


Test patch:
diff --git a/lib/test/integration.go b/lib/test/integration.go
index 0e8574cf..a062774a 100644
--- a/lib/test/integration.go
+++ b/lib/test/integration.go
@@ -46,13 +46,22 @@ type KnTest struct {
 
 // NewKnTest creates a new KnTest object
 func NewKnTest() (*KnTest, error) {
-	ns := NextNamespace()
-
-	err := CreateNamespace(ns)
-	if err != nil {
-		return nil, err
+	ns := ""
+	// try next 20 namespace before giving up creating a namespace if it already exists
+	for i := 0; i < 20; i++ {
+		ns = NextNamespace()
+		err := CreateNamespace(ns)
+		if err == nil {
+			break
+		}
+		if strings.Contains(err.Error(), "AlreadyExists") {
+			continue
+		} else {
+			return nil, err
+		}
 	}
-	err = WaitForNamespaceCreated(ns)
+
+	err := WaitForNamespaceCreated(ns)
 	if err != nil {
 		return nil, err
 	}
diff --git a/lib/test/utils.go b/lib/test/utils.go
index 890b3078..93878ae6 100644
--- a/lib/test/utils.go
+++ b/lib/test/utils.go
@@ -14,7 +14,17 @@
 
 package test
 
-import "testing"
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+)
+
+const (
+	FileModeReadWrite  = 0666
+	FileModeExecutable = 0777
+)
 
 // GetResourceFieldsWithJSONPath returns output of given JSON path for given resource using kubectl and error if any
 func GetResourceFieldsWithJSONPath(t *testing.T, it *KnTest, resource, name, jsonpath string) (string, error) {
@@ -25,3 +35,10 @@ func GetResourceFieldsWithJSONPath(t *testing.T, it *KnTest, resource, name, jso
 
 	return out, nil
 }
+
+// CreateFile creates a file with given name, content, path, fileMode and returns absolute filepath and error if any
+func CreateFile(fileName, fileContent, filePath string, fileMode os.FileMode) (string, error) {
+	file := filepath.Join(filePath, fileName)
+	err := ioutil.WriteFile(file, []byte(fileContent), fileMode)
+	return file, err
+}
diff --git a/pkg/kn/commands/service/delete_mock_test.go b/pkg/kn/commands/service/delete_mock_test.go
index f901b9dd..8b5d918f 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 0f42f2b3..c3ec42b9 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/plugins_test.go b/test/e2e/plugins_test.go
index d893a3e0..53b7bff4 100644
--- a/test/e2e/plugins_test.go
+++ b/test/e2e/plugins_test.go
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 // +build e2e
+// +build !eventing
+// +build !serving
 
 package e2e
 
@@ -35,9 +37,6 @@ const (
 echo "Hello Knative, I'm a Kn plugin"
 echo "  My plugin file is $0"
 echo "  I received arguments: $1 $2 $3 $4"`
-
-	FileModeReadWrite  = 0666
-	FileModeExecutable = 0777
 )
 
 type pluginTestConfig struct {
@@ -53,27 +52,27 @@ func (pc *pluginTestConfig) setup() error {
 	}
 
 	pc.knPluginsDir = filepath.Join(pc.knConfigDir, "plugins")
-	err = os.MkdirAll(pc.knPluginsDir, FileModeExecutable)
+	err = os.MkdirAll(pc.knPluginsDir, test.FileModeExecutable)
 	if err != nil {
 		return err
 	}
 
 	pc.knPluginsDir2 = filepath.Join(pc.knConfigDir, "plugins2")
-	err = os.MkdirAll(pc.knPluginsDir2, FileModeExecutable)
+	err = os.MkdirAll(pc.knPluginsDir2, test.FileModeExecutable)
 	if err != nil {
 		return err
 	}
 
-	pc.knConfigPath, err = createPluginFile("config.yaml", "", pc.knConfigDir, FileModeReadWrite)
+	pc.knConfigPath, err = test.CreateFile("config.yaml", "", pc.knConfigDir, test.FileModeReadWrite)
 	if err != nil {
 		return err
 	}
 
-	pc.knPluginPath, err = createPluginFile("kn-helloe2e", TestPluginCode, pc.knPluginsDir, FileModeExecutable)
+	pc.knPluginPath, err = test.CreateFile("kn-helloe2e", TestPluginCode, pc.knPluginsDir, test.FileModeExecutable)
 	if err != nil {
 		return err
 	}
-	pc.knPluginPath2, err = createPluginFile("kn-hello2e2e", TestPluginCode, pc.knPluginsDir2, FileModeExecutable)
+	pc.knPluginPath2, err = test.CreateFile("kn-hello2e2e", TestPluginCode, pc.knPluginsDir2, test.FileModeExecutable)
 	if err != nil {
 		return err
 	}
@@ -84,12 +83,6 @@ func (pc *pluginTestConfig) teardown() {
 	os.RemoveAll(pc.knConfigDir)
 }
 
-func createPluginFile(fileName, fileContent, filePath string, fileMode os.FileMode) (string, error) {
-	file := filepath.Join(filePath, fileName)
-	err := ioutil.WriteFile(file, []byte(fileContent), fileMode)
-	return file, err
-}
-
 func TestPluginWithoutLookup(t *testing.T) {
 	t.Parallel()
 
diff --git a/test/e2e/service_test.go b/test/e2e/service_test.go
index 7efa05bf..ead29c71 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")
+}
diff --git a/test/e2e/sinkprefix_test.go b/test/e2e/sinkprefix_test.go
index 70204135..91d2d124 100644
--- a/test/e2e/sinkprefix_test.go
+++ b/test/e2e/sinkprefix_test.go
@@ -47,7 +47,7 @@ func (tc *sinkprefixTestConfig) setup() error {
 	if err != nil {
 		return err
 	}
-	tc.knConfigPath, err = createPluginFile("config.yaml", KnConfigContent, tc.knConfigDir, FileModeReadWrite)
+	tc.knConfigPath, err = test.CreateFile("config.yaml", KnConfigContent, tc.knConfigDir, test.FileModeReadWrite)
 	if err != nil {
 		return err
 	}