Repository: fission/fission (language: go)

Issue:
Can not reapply specs containing HttpTrigger - false positive duplicate detection
This is a re-filing of #2658 since I cannot reopen that issue.

**Fission/Kubernetes version**

<!-- If you tested with other services, for example Istio, please also provide the version of service as well. -->

Fission: 1.20.5
Kubernetes: Minikube 1.34.0

**Kubernetes platform (e.g. Google Kubernetes Engine)** Minikube

**Describe the bug**
When using `fission spec apply`, route specs with multiple HTTP methods cause the `fission` CLI to detect a duplicate, and deployment fails. I have to delete the routes every time I deploy.

My hunch is that it is caused by the multiple HTTP methods, as I only started noticing this when  I added `OPTIONS` in addition to `GET` or `POST` in the spec.

**To Reproduce**

A spec like this should cause the error:

```yaml
apiVersion: fission.io/v1
kind: HTTPTrigger
metadata:
  creationTimestamp: null
  name: tasks-create
spec:
  createingress: false
  functionref:
    functionweights: null
    name: create-task
    type: name
  host: ""
  ingressconfig:
    annotations: null
    host: '*'
    path: /tasks/create
    tls: ""
  method: ""
  methods:
  - POST
  - OPTIONS
  prefix: ""
  relativeurl: /tasks/create

```

**Expected result**
Spec apply works fine.

**Actual result**

Spec apply fails with the error that a route with the same name/method/etc already exists.


Discussion / hints:
@ProjectMoon Unable to reproduce the issue.

Fission version: `v1.21.0`

Tried following steps and did not observe any issue:
- Create spec and test.
```
fission spec init
fission env create --name go --image ghcr.io/fission/go-env --builder ghcr.io/fission/go-builder --spec
fission pkg create --name test-pkg --src hello.go --env go --spec
fission fn create --name hello --env go --src hello.go --entrypoint Handler --spec
fission ht create --name hello --method POST --method OPTIONS --function hello --url hello --spec

fission spec apply

fission fn test --name hello
curl -X POST $FISSION_ROUTER/hello
```
- Edit the function package ref in `specs/function-hello.yaml` to `test-pkg` and run `fission spec apply`.
```
fission spec apply

fission fn test --name hello
curl -X POST $FISSION_ROUTER/hello
```
Hmm, I will try to create a minimal reproducible test case and instructions, because it happens to me with my little Fission test app no matter where I deploy, even on a completely fresh environment. And it's happened on two machines. But this was also on Fission 1.20.5, so I will try again on 1.21.0 (which I did not realize even existed until reading this post).
OK so this still happens on Fission 1.21.0. I will work on creating a reproducible test case repo.
OK here is a repo that SHOULD be able to reproduce this. It's a stripped down version of the build output I use to deploy to Minikube.

https://github.com/ProjectMoon/fission-bug

Instructions;
1. `fission spec apply --wait=true` All should be good.
2. Do it again, it should give the error. You can then delete the route, and run `fission spec apply` again and it'll work.
3. Then if you run it again, error.
4. And so on...

Let me know if it doesn't work.

Fix patch:
diff --git a/pkg/executor/executortype/poolmgr/gpm.go b/pkg/executor/executortype/poolmgr/gpm.go
index 1675f0877f..29289a91fb 100644
--- a/pkg/executor/executortype/poolmgr/gpm.go
+++ b/pkg/executor/executortype/poolmgr/gpm.go
@@ -382,7 +382,7 @@ func (gpm *GenericPoolManager) AdoptExistingResources(ctx context.Context) {
 			}
 
 			// create environment map for later use
-			key := fmt.Sprintf("%s/%s", env.ObjectMeta.Namespace, env.ObjectMeta.Name)
+			key := k8sCache.MetaObjectToName(&env.ObjectMeta).String()
 			envMap[key] = env
 		}
 	}
diff --git a/pkg/fission-cli/cmd/spec/apply.go b/pkg/fission-cli/cmd/spec/apply.go
index 17312aef95..b999f8bf3f 100644
--- a/pkg/fission-cli/cmd/spec/apply.go
+++ b/pkg/fission-cli/cmd/spec/apply.go
@@ -29,6 +29,7 @@ import (
 	"github.com/go-git/go-git/v5"
 	"github.com/pkg/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	k8sCache "k8s.io/client-go/tools/cache"
 
 	fv1 "github.com/fission/fission/pkg/apis/core/v1"
 	"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
@@ -449,10 +450,10 @@ func applyResources(input cli.Input, fclient cmd.Client, specDir string, fr *Fis
 		if f.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType == fv1.ExecutorTypeContainer {
 			continue
 		}
-		k := mapKey(&metav1.ObjectMeta{
+		k := k8sCache.MetaObjectToName(&metav1.ObjectMeta{
 			Namespace: f.Spec.Package.PackageRef.Namespace,
 			Name:      f.Spec.Package.PackageRef.Name,
-		})
+		}).String()
 		m, ok := pkgMeta[k]
 		if !ok {
 			// the function references a package that doesn't exist in the
@@ -599,10 +600,6 @@ func localArchiveFromSpec(ctx context.Context, specDir string, aus *spectypes.Ar
 	}
 }
 
-func mapKey(m *metav1.ObjectMeta) string {
-	return fmt.Sprintf("%v:%v", m.Namespace, m.Name)
-}
-
 func applyDeploymentConfig(m *metav1.ObjectMeta, fr *FissionResources) {
 	if m.Annotations == nil {
 		m.Annotations = make(map[string]string)
@@ -665,7 +662,7 @@ func applyPackages(ctx context.Context, fclient cmd.Client, fr *FissionResources
 	// index
 	existent := make(map[string]fv1.Package)
 	for _, obj := range objs {
-		existent[mapKey(&obj.ObjectMeta)] = obj
+		existent[k8sCache.MetaObjectToName(&obj.ObjectMeta).String()] = obj
 	}
 	metadataMap := make(map[string]metav1.ObjectMeta)
 
@@ -678,13 +675,13 @@ func applyPackages(ctx context.Context, fclient cmd.Client, fr *FissionResources
 	for _, o := range fr.Packages {
 		// apply deploymentConfig so we can find our objects on future apply invocations
 		applyDeploymentConfig(&o.ObjectMeta, fr)
-		console.Verbose(2, fmt.Sprintf("Package is here '%s','%s','%s','%s'", o.Namespace, o.Name, o.Spec.Environment.Namespace, o.Spec.Environment.Name))
+		console.Verbose(2, fmt.Sprintf("Package is here '%s','%s/%s'", k8sCache.MetaObjectToName(&o.ObjectMeta).String(), o.Spec.Environment.Namespace, o.Spec.Environment.Name))
 
 		// index desired state
-		desired[mapKey(&o.ObjectMeta)] = true
+		desired[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = true
 
 		// exists?
-		existingObj, ok := existent[mapKey(&o.ObjectMeta)]
+		existingObj, ok := existent[k8sCache.MetaObjectToName(&o.ObjectMeta).String()]
 		if ok {
 			// ok, a resource with the same name exists, is it the same?
 			keep := false
@@ -700,7 +697,7 @@ func applyPackages(ctx context.Context, fclient cmd.Client, fr *FissionResources
 
 			if keep && isObjectMetaEqual(existingObj.ObjectMeta, o.ObjectMeta) && existingObj.Status.BuildStatus == fv1.BuildStatusSucceeded {
 				// nothing to do on the server
-				metadataMap[mapKey(&o.ObjectMeta)] = existingObj.ObjectMeta
+				metadataMap[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = existingObj.ObjectMeta
 			} else {
 				// update
 				o.ObjectMeta.ResourceVersion = existingObj.ObjectMeta.ResourceVersion
@@ -726,7 +723,7 @@ func applyPackages(ctx context.Context, fclient cmd.Client, fr *FissionResources
 				}
 				ras.Updated = append(ras.Updated, &newmeta.ObjectMeta)
 				// keep track of metadata in case we need to create a reference to it
-				metadataMap[mapKey(&o.ObjectMeta)] = newmeta.ObjectMeta
+				metadataMap[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = newmeta.ObjectMeta
 			}
 		} else {
 
@@ -736,7 +733,7 @@ func applyPackages(ctx context.Context, fclient cmd.Client, fr *FissionResources
 				return nil, nil, err
 			}
 			ras.Created = append(ras.Created, &newmeta.ObjectMeta)
-			metadataMap[mapKey(&o.ObjectMeta)] = newmeta.ObjectMeta
+			metadataMap[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = newmeta.ObjectMeta
 		}
 	}
 
@@ -744,14 +741,14 @@ func applyPackages(ctx context.Context, fclient cmd.Client, fr *FissionResources
 	if delete {
 		// objs is already filtered with our UID
 		for _, o := range objs {
-			_, wanted := desired[mapKey(&o.ObjectMeta)]
+			_, wanted := desired[k8sCache.MetaObjectToName(&o.ObjectMeta).String()]
 			if !wanted {
 				err := fclient.FissionClientSet.CoreV1().Packages(o.ObjectMeta.Namespace).Delete(ctx, o.ObjectMeta.Name, metav1.DeleteOptions{})
 				if err != nil {
 					return nil, nil, err
 				}
 				ras.Deleted = append(ras.Deleted, &o.ObjectMeta)
-				fmt.Printf("Deleted %v %v/%v\n", o.TypeMeta.Kind, o.ObjectMeta.Namespace, o.ObjectMeta.Name)
+				fmt.Printf("Deleted %v %v\n", o.TypeMeta.Kind, k8sCache.MetaObjectToName(&o.ObjectMeta).String())
 			}
 		}
 	}
@@ -781,7 +778,7 @@ func applyFunctions(ctx context.Context, fclient cmd.Client, fr *FissionResource
 	// index
 	existent := make(map[string]fv1.Function)
 	for _, obj := range objs {
-		existent[mapKey(&obj.ObjectMeta)] = obj
+		existent[k8sCache.MetaObjectToName(&obj.ObjectMeta).String()] = obj
 	}
 	metadataMap := make(map[string]metav1.ObjectMeta)
 
@@ -796,15 +793,15 @@ func applyFunctions(ctx context.Context, fclient cmd.Client, fr *FissionResource
 		applyDeploymentConfig(&o.ObjectMeta, fr)
 
 		// index desired state
-		desired[mapKey(&o.ObjectMeta)] = true
+		desired[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = true
 
 		// exists?
-		existingObj, ok := existent[mapKey(&o.ObjectMeta)]
+		existingObj, ok := existent[k8sCache.MetaObjectToName(&o.ObjectMeta).String()]
 		if ok {
 			// ok, a resource with the same name exists, is it the same?
 			if isObjectMetaEqual(existingObj.ObjectMeta, o.ObjectMeta) && reflect.DeepEqual(existingObj.Spec, o.Spec) {
 				// nothing to do on the server
-				metadataMap[mapKey(&o.ObjectMeta)] = existingObj.ObjectMeta
+				metadataMap[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = existingObj.ObjectMeta
 			} else {
 				// update
 				o.ObjectMeta.ResourceVersion = existingObj.ObjectMeta.ResourceVersion
@@ -814,7 +811,7 @@ func applyFunctions(ctx context.Context, fclient cmd.Client, fr *FissionResource
 				}
 				ras.Updated = append(ras.Updated, &newmeta.ObjectMeta)
 				// keep track of metadata in case we need to create a reference to it
-				metadataMap[mapKey(&o.ObjectMeta)] = newmeta.ObjectMeta
+				metadataMap[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = newmeta.ObjectMeta
 			}
 		} else {
 			// create
@@ -823,7 +820,7 @@ func applyFunctions(ctx context.Context, fclient cmd.Client, fr *FissionResource
 				return nil, nil, err
 			}
 			ras.Created = append(ras.Created, &newmeta.ObjectMeta)
-			metadataMap[mapKey(&o.ObjectMeta)] = newmeta.ObjectMeta
+			metadataMap[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = newmeta.ObjectMeta
 		}
 	}
 
@@ -831,14 +828,14 @@ func applyFunctions(ctx context.Context, fclient cmd.Client, fr *FissionResource
 	if delete {
 		// objs is already filtered with our UID
 		for _, o := range objs {
-			_, wanted := desired[mapKey(&o.ObjectMeta)]
+			_, wanted := desired[k8sCache.MetaObjectToName(&o.ObjectMeta).String()]
 			if !wanted {
 				err := fclient.FissionClientSet.CoreV1().Functions(o.ObjectMeta.Namespace).Delete(ctx, o.ObjectMeta.Name, metav1.DeleteOptions{})
 				if err != nil {
 					return nil, nil, err
 				}
 				ras.Deleted = append(ras.Deleted, &o.ObjectMeta)
-				fmt.Printf("Deleted %v %v/%v\n", o.TypeMeta.Kind, o.ObjectMeta.Namespace, o.ObjectMeta.Name)
+				fmt.Printf("Deleted %v %v\n", o.TypeMeta.Kind, k8sCache.MetaObjectToName(&o.ObjectMeta).String())
 			}
 		}
 	}
@@ -868,7 +865,7 @@ func applyEnvironments(ctx context.Context, fclient cmd.Client, fr *FissionResou
 	// index
 	existent := make(map[string]fv1.Environment)
 	for _, obj := range objs {
-		existent[mapKey(&obj.ObjectMeta)] = obj
+		existent[k8sCache.MetaObjectToName(&obj.ObjectMeta).String()] = obj
 	}
 	metadataMap := make(map[string]metav1.ObjectMeta)
 
@@ -883,15 +880,15 @@ func applyEnvironments(ctx context.Context, fclient cmd.Client, fr *FissionResou
 		applyDeploymentConfig(&o.ObjectMeta, fr)
 
 		// index desired state
-		desired[mapKey(&o.ObjectMeta)] = true
+		desired[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = true
 
 		// exists?
-		existingObj, ok := existent[mapKey(&o.ObjectMeta)]
+		existingObj, ok := existent[k8sCache.MetaObjectToName(&o.ObjectMeta).String()]
 		if ok {
 			// ok, a resource with the same name exists, is it the same?
 			if isObjectMetaEqual(existingObj.ObjectMeta, o.ObjectMeta) && reflect.DeepEqual(existingObj.Spec, o.Spec) {
 				// nothing to do on the server
-				metadataMap[mapKey(&o.ObjectMeta)] = existingObj.ObjectMeta
+				metadataMap[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = existingObj.ObjectMeta
 			} else {
 				// update
 				o.ObjectMeta.ResourceVersion = existingObj.ObjectMeta.ResourceVersion
@@ -901,7 +898,7 @@ func applyEnvironments(ctx context.Context, fclient cmd.Client, fr *FissionResou
 				}
 				ras.Updated = append(ras.Updated, &newmeta.ObjectMeta)
 				// keep track of metadata in case we need to create a reference to it
-				metadataMap[mapKey(&o.ObjectMeta)] = newmeta.ObjectMeta
+				metadataMap[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = newmeta.ObjectMeta
 			}
 		} else {
 			// create
@@ -910,7 +907,7 @@ func applyEnvironments(ctx context.Context, fclient cmd.Client, fr *FissionResou
 				return nil, nil, err
 			}
 			ras.Created = append(ras.Created, &newmeta.ObjectMeta)
-			metadataMap[mapKey(&o.ObjectMeta)] = newmeta.ObjectMeta
+			metadataMap[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = newmeta.ObjectMeta
 		}
 	}
 
@@ -918,14 +915,14 @@ func applyEnvironments(ctx context.Context, fclient cmd.Client, fr *FissionResou
 	if delete {
 		// objs is already filtered with our UID
 		for _, o := range objs {
-			_, wanted := desired[mapKey(&o.ObjectMeta)]
+			_, wanted := desired[k8sCache.MetaObjectToName(&o.ObjectMeta).String()]
 			if !wanted {
 				err := fclient.FissionClientSet.CoreV1().Environments(o.ObjectMeta.Namespace).Delete(ctx, o.ObjectMeta.Name, metav1.DeleteOptions{})
 				if err != nil {
 					return nil, nil, err
 				}
 				ras.Deleted = append(ras.Deleted, &o.ObjectMeta)
-				fmt.Printf("Deleted %v %v/%v\n", o.TypeMeta.Kind, o.ObjectMeta.Namespace, o.ObjectMeta.Name)
+				fmt.Printf("Deleted %v %v\n", o.TypeMeta.Kind, k8sCache.MetaObjectToName(&o.ObjectMeta).String())
 			}
 		}
 	}
@@ -955,7 +952,7 @@ func applyHTTPTriggers(ctx context.Context, fclient cmd.Client, fr *FissionResou
 	// index
 	existent := make(map[string]fv1.HTTPTrigger)
 	for _, obj := range objs {
-		existent[mapKey(&obj.ObjectMeta)] = obj
+		existent[k8sCache.MetaObjectToName(&obj.ObjectMeta).String()] = obj
 	}
 	metadataMap := make(map[string]metav1.ObjectMeta)
 
@@ -970,15 +967,15 @@ func applyHTTPTriggers(ctx context.Context, fclient cmd.Client, fr *FissionResou
 		applyDeploymentConfig(&o.ObjectMeta, fr)
 
 		// index desired state
-		desired[mapKey(&o.ObjectMeta)] = true
+		desired[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = true
 
 		// exists?
-		existingObj, ok := existent[mapKey(&o.ObjectMeta)]
+		existingObj, ok := existent[k8sCache.MetaObjectToName(&o.ObjectMeta).String()]
 		if ok {
 			// ok, a resource with the same name exists, is it the same?
 			if isObjectMetaEqual(existingObj.ObjectMeta, o.ObjectMeta) && reflect.DeepEqual(existingObj.Spec, o.Spec) {
 				// nothing to do on the server
-				metadataMap[mapKey(&o.ObjectMeta)] = existingObj.ObjectMeta
+				metadataMap[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = existingObj.ObjectMeta
 			} else {
 
 				err := util.CheckHTTPTriggerDuplicates(ctx, fclient, &o)
@@ -993,7 +990,7 @@ func applyHTTPTriggers(ctx context.Context, fclient cmd.Client, fr *FissionResou
 				}
 				ras.Updated = append(ras.Updated, &newmeta.ObjectMeta)
 				// keep track of metadata in case we need to create a reference to it
-				metadataMap[mapKey(&o.ObjectMeta)] = newmeta.ObjectMeta
+				metadataMap[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = newmeta.ObjectMeta
 			}
 		} else {
 
@@ -1007,7 +1004,7 @@ func applyHTTPTriggers(ctx context.Context, fclient cmd.Client, fr *FissionResou
 				return nil, nil, err
 			}
 			ras.Created = append(ras.Created, &newmeta.ObjectMeta)
-			metadataMap[mapKey(&o.ObjectMeta)] = newmeta.ObjectMeta
+			metadataMap[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = newmeta.ObjectMeta
 		}
 	}
 
@@ -1015,14 +1012,14 @@ func applyHTTPTriggers(ctx context.Context, fclient cmd.Client, fr *FissionResou
 	if delete {
 		// objs is already filtered with our UID
 		for _, o := range objs {
-			_, wanted := desired[mapKey(&o.ObjectMeta)]
+			_, wanted := desired[k8sCache.MetaObjectToName(&o.ObjectMeta).String()]
 			if !wanted {
 				err := fclient.FissionClientSet.CoreV1().HTTPTriggers(o.ObjectMeta.Namespace).Delete(ctx, o.ObjectMeta.Name, metav1.DeleteOptions{})
 				if err != nil {
 					return nil, nil, err
 				}
 				ras.Deleted = append(ras.Deleted, &o.ObjectMeta)
-				fmt.Printf("Deleted %v %v/%v\n", o.TypeMeta.Kind, o.ObjectMeta.Namespace, o.ObjectMeta.Name)
+				fmt.Printf("Deleted %v %v\n", o.TypeMeta.Kind, k8sCache.MetaObjectToName(&o.ObjectMeta).String())
 			}
 		}
 	}
@@ -1052,7 +1049,7 @@ func applyKubernetesWatchTriggers(ctx context.Context, fclient cmd.Client, fr *F
 	// index
 	existent := make(map[string]fv1.KubernetesWatchTrigger)
 	for _, obj := range objs {
-		existent[mapKey(&obj.ObjectMeta)] = obj
+		existent[k8sCache.MetaObjectToName(&obj.ObjectMeta).String()] = obj
 	}
 	metadataMap := make(map[string]metav1.ObjectMeta)
 
@@ -1067,15 +1064,15 @@ func applyKubernetesWatchTriggers(ctx context.Context, fclient cmd.Client, fr *F
 		applyDeploymentConfig(&o.ObjectMeta, fr)
 
 		// index desired state
-		desired[mapKey(&o.ObjectMeta)] = true
+		desired[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = true
 
 		// exists?
-		existingObj, ok := existent[mapKey(&o.ObjectMeta)]
+		existingObj, ok := existent[k8sCache.MetaObjectToName(&o.ObjectMeta).String()]
 		if ok {
 			// ok, a resource with the same name exists, is it the same?
 			if isObjectMetaEqual(existingObj.ObjectMeta, o.ObjectMeta) && reflect.DeepEqual(existingObj.Spec, o.Spec) {
 				// nothing to do on the server
-				metadataMap[mapKey(&o.ObjectMeta)] = existingObj.ObjectMeta
+				metadataMap[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = existingObj.ObjectMeta
 			} else {
 				// update
 				o.ObjectMeta.ResourceVersion = existingObj.ObjectMeta.ResourceVersion
@@ -1085,7 +1082,7 @@ func applyKubernetesWatchTriggers(ctx context.Context, fclient cmd.Client, fr *F
 				}
 				ras.Updated = append(ras.Updated, &newmeta.ObjectMeta)
 				// keep track of metadata in case we need to create a reference to it
-				metadataMap[mapKey(&o.ObjectMeta)] = newmeta.ObjectMeta
+				metadataMap[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = newmeta.ObjectMeta
 			}
 		} else {
 			// create
@@ -1094,7 +1091,7 @@ func applyKubernetesWatchTriggers(ctx context.Context, fclient cmd.Client, fr *F
 				return nil, nil, err
 			}
 			ras.Created = append(ras.Created, &newmeta.ObjectMeta)
-			metadataMap[mapKey(&o.ObjectMeta)] = newmeta.ObjectMeta
+			metadataMap[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = newmeta.ObjectMeta
 		}
 	}
 
@@ -1102,14 +1099,14 @@ func applyKubernetesWatchTriggers(ctx context.Context, fclient cmd.Client, fr *F
 	if delete {
 		// objs is already filtered with our UID
 		for _, o := range objs {
-			_, wanted := desired[mapKey(&o.ObjectMeta)]
+			_, wanted := desired[k8sCache.MetaObjectToName(&o.ObjectMeta).String()]
 			if !wanted {
 				err := fclient.FissionClientSet.CoreV1().KubernetesWatchTriggers(o.ObjectMeta.Namespace).Delete(ctx, o.ObjectMeta.Name, metav1.DeleteOptions{})
 				if err != nil {
 					return nil, nil, err
 				}
 				ras.Deleted = append(ras.Deleted, &o.ObjectMeta)
-				fmt.Printf("Deleted %v %v/%v\n", o.TypeMeta.Kind, o.ObjectMeta.Namespace, o.ObjectMeta.Name)
+				fmt.Printf("Deleted %v %v\n", o.TypeMeta.Kind, k8sCache.MetaObjectToName(&o.ObjectMeta).String())
 			}
 		}
 	}
@@ -1139,7 +1136,7 @@ func applyTimeTriggers(ctx context.Context, fclient cmd.Client, fr *FissionResou
 	// index
 	existent := make(map[string]fv1.TimeTrigger)
 	for _, obj := range objs {
-		existent[mapKey(&obj.ObjectMeta)] = obj
+		existent[k8sCache.MetaObjectToName(&obj.ObjectMeta).String()] = obj
 	}
 	metadataMap := make(map[string]metav1.ObjectMeta)
 
@@ -1154,15 +1151,15 @@ func applyTimeTriggers(ctx context.Context, fclient cmd.Client, fr *FissionResou
 		applyDeploymentConfig(&o.ObjectMeta, fr)
 
 		// index desired state
-		desired[mapKey(&o.ObjectMeta)] = true
+		desired[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = true
 
 		// exists?
-		existingObj, ok := existent[mapKey(&o.ObjectMeta)]
+		existingObj, ok := existent[k8sCache.MetaObjectToName(&o.ObjectMeta).String()]
 		if ok {
 			// ok, a resource with the same name exists, is it the same?
 			if isObjectMetaEqual(existingObj.ObjectMeta, o.ObjectMeta) && reflect.DeepEqual(existingObj.Spec, o.Spec) {
 				// nothing to do on the server
-				metadataMap[mapKey(&o.ObjectMeta)] = existingObj.ObjectMeta
+				metadataMap[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = existingObj.ObjectMeta
 			} else {
 				// update
 				o.ObjectMeta.ResourceVersion = existingObj.ObjectMeta.ResourceVersion
@@ -1172,7 +1169,7 @@ func applyTimeTriggers(ctx context.Context, fclient cmd.Client, fr *FissionResou
 				}
 				ras.Updated = append(ras.Updated, &newmeta.ObjectMeta)
 				// keep track of metadata in case we need to create a reference to it
-				metadataMap[mapKey(&o.ObjectMeta)] = newmeta.ObjectMeta
+				metadataMap[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = newmeta.ObjectMeta
 			}
 		} else {
 			// create
@@ -1181,7 +1178,7 @@ func applyTimeTriggers(ctx context.Context, fclient cmd.Client, fr *FissionResou
 				return nil, nil, err
 			}
 			ras.Created = append(ras.Created, &newmeta.ObjectMeta)
-			metadataMap[mapKey(&o.ObjectMeta)] = newmeta.ObjectMeta
+			metadataMap[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = newmeta.ObjectMeta
 		}
 	}
 
@@ -1189,14 +1186,14 @@ func applyTimeTriggers(ctx context.Context, fclient cmd.Client, fr *FissionResou
 	if delete {
 		// objs is already filtered with our UID
 		for _, o := range objs {
-			_, wanted := desired[mapKey(&o.ObjectMeta)]
+			_, wanted := desired[k8sCache.MetaObjectToName(&o.ObjectMeta).String()]
 			if !wanted {
 				err := fclient.FissionClientSet.CoreV1().TimeTriggers(o.ObjectMeta.Namespace).Delete(ctx, o.ObjectMeta.Name, metav1.DeleteOptions{})
 				if err != nil {
 					return nil, nil, err
 				}
 				ras.Deleted = append(ras.Deleted, &o.ObjectMeta)
-				fmt.Printf("Deleted %v %v/%v\n", o.TypeMeta.Kind, o.ObjectMeta.Namespace, o.ObjectMeta.Name)
+				fmt.Printf("Deleted %v %v\n", o.TypeMeta.Kind, k8sCache.MetaObjectToName(&o.ObjectMeta).String())
 			}
 		}
 	}
@@ -1226,7 +1223,7 @@ func applyMessageQueueTriggers(ctx context.Context, fclient cmd.Client, fr *Fiss
 	// index
 	existent := make(map[string]fv1.MessageQueueTrigger)
 	for _, obj := range objs {
-		existent[mapKey(&obj.ObjectMeta)] = obj
+		existent[k8sCache.MetaObjectToName(&obj.ObjectMeta).String()] = obj
 	}
 	metadataMap := make(map[string]metav1.ObjectMeta)
 
@@ -1241,15 +1238,15 @@ func applyMessageQueueTriggers(ctx context.Context, fclient cmd.Client, fr *Fiss
 		applyDeploymentConfig(&o.ObjectMeta, fr)
 
 		// index desired state
-		desired[mapKey(&o.ObjectMeta)] = true
+		desired[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = true
 
 		// exists?
-		existingObj, ok := existent[mapKey(&o.ObjectMeta)]
+		existingObj, ok := existent[k8sCache.MetaObjectToName(&o.ObjectMeta).String()]
 		if ok {
 			// ok, a resource with the same name exists, is it the same?
 			if isObjectMetaEqual(existingObj.ObjectMeta, o.ObjectMeta) && reflect.DeepEqual(existingObj.Spec, o.Spec) {
 				// nothing to do on the server
-				metadataMap[mapKey(&o.ObjectMeta)] = existingObj.ObjectMeta
+				metadataMap[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = existingObj.ObjectMeta
 			} else {
 				// update
 				o.ObjectMeta.ResourceVersion = existingObj.ObjectMeta.ResourceVersion
@@ -1259,7 +1256,7 @@ func applyMessageQueueTriggers(ctx context.Context, fclient cmd.Client, fr *Fiss
 				}
 				ras.Updated = append(ras.Updated, &newmeta.ObjectMeta)
 				// keep track of metadata in case we need to create a reference to it
-				metadataMap[mapKey(&o.ObjectMeta)] = newmeta.ObjectMeta
+				metadataMap[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = newmeta.ObjectMeta
 			}
 		} else {
 			// create
@@ -1268,7 +1265,7 @@ func applyMessageQueueTriggers(ctx context.Context, fclient cmd.Client, fr *Fiss
 				return nil, nil, err
 			}
 			ras.Created = append(ras.Created, &newmeta.ObjectMeta)
-			metadataMap[mapKey(&o.ObjectMeta)] = newmeta.ObjectMeta
+			metadataMap[k8sCache.MetaObjectToName(&o.ObjectMeta).String()] = newmeta.ObjectMeta
 		}
 	}
 
@@ -1276,14 +1273,14 @@ func applyMessageQueueTriggers(ctx context.Context, fclient cmd.Client, fr *Fiss
 	if delete {
 		// objs is already filtered with our UID
 		for _, o := range objs {
-			_, wanted := desired[mapKey(&o.ObjectMeta)]
+			_, wanted := desired[k8sCache.MetaObjectToName(&o.ObjectMeta).String()]
 			if !wanted {
 				err := fclient.FissionClientSet.CoreV1().MessageQueueTriggers(o.ObjectMeta.Namespace).Delete(ctx, o.ObjectMeta.Name, metav1.DeleteOptions{})
 				if err != nil {
 					return nil, nil, err
 				}
 				ras.Deleted = append(ras.Deleted, &o.ObjectMeta)
-				fmt.Printf("Deleted %v %v/%v\n", o.TypeMeta.Kind, o.ObjectMeta.Namespace, o.ObjectMeta.Name)
+				fmt.Printf("Deleted %v %v\n", o.TypeMeta.Kind, k8sCache.MetaObjectToName(&o.ObjectMeta).String())
 			}
 		}
 	}
diff --git a/pkg/fission-cli/cmd/spec/buildwatch.go b/pkg/fission-cli/cmd/spec/buildwatch.go
index e752467d48..f97ee4edf6 100644
--- a/pkg/fission-cli/cmd/spec/buildwatch.go
+++ b/pkg/fission-cli/cmd/spec/buildwatch.go
@@ -23,10 +23,11 @@ import (
 	"time"
 
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	k8sCache "k8s.io/client-go/tools/cache"
 
 	fv1 "github.com/fission/fission/pkg/apis/core/v1"
 	"github.com/fission/fission/pkg/fission-cli/cmd"
-	"github.com/fission/fission/pkg/fission-cli/cmd/package/util"
+	pkgUtil "github.com/fission/fission/pkg/fission-cli/cmd/package/util"
 )
 
 type (
@@ -77,7 +78,7 @@ func (w *packageBuildWatcher) watch(ctx context.Context) {
 		keepWaiting := false
 		buildpkgs := make([]fv1.Package, 0)
 		for _, pkg := range pkgs.Items {
-			_, ok := w.pkgMeta[mapKey(&pkg.ObjectMeta)]
+			_, ok := w.pkgMeta[k8sCache.MetaObjectToName(&pkg.ObjectMeta).String()]
 			if !ok {
 				continue
 			}
@@ -101,7 +102,7 @@ func (w *packageBuildWatcher) watch(ctx context.Context) {
 				pkg.Status.BuildStatus == fv1.BuildStatusSucceeded {
 				w.finished[k] = true
 				fmt.Printf("------\n")
-				util.PrintPackageSummary(os.Stdout, &pkg)
+				pkgUtil.PrintPackageSummary(os.Stdout, &pkg)
 				fmt.Printf("------\n")
 			}
 			if pkg.Status.BuildStatus == fv1.BuildStatusFailed {
diff --git a/pkg/fission-cli/cmd/spec/destroy.go b/pkg/fission-cli/cmd/spec/destroy.go
index 9758312253..ee94e2e775 100644
--- a/pkg/fission-cli/cmd/spec/destroy.go
+++ b/pkg/fission-cli/cmd/spec/destroy.go
@@ -23,6 +23,7 @@ import (
 
 	"github.com/pkg/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	k8sCache "k8s.io/client-go/tools/cache"
 
 	fv1 "github.com/fission/fission/pkg/apis/core/v1"
 	"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
@@ -229,7 +230,7 @@ func destroyHTTPTriggers(ctx context.Context, fclient cmd.Client, fr *FissionRes
 		} else if err != nil {
 			return err
 		}
-		fmt.Printf("Deleted %s %s/%s\n", o.TypeMeta.Kind, o.ObjectMeta.Namespace, o.ObjectMeta.Name)
+		fmt.Printf("Deleted %s %s\n", o.TypeMeta.Kind, k8sCache.MetaObjectToName(&o.ObjectMeta).String())
 	}
 	return nil
 }
@@ -246,7 +247,7 @@ func destroyKubernetesWatchTriggers(ctx context.Context, fclient cmd.Client, fr
 		} else if err != nil {
 			return err
 		}
-		fmt.Printf("Deleted %s %s/%s\n", o.TypeMeta.Kind, o.ObjectMeta.Namespace, o.ObjectMeta.Name)
+		fmt.Printf("Deleted %s %s\n", o.TypeMeta.Kind, k8sCache.MetaObjectToName(&o.ObjectMeta).String())
 	}
 
 	return nil
@@ -264,7 +265,7 @@ func destroyTimeTriggers(ctx context.Context, fclient cmd.Client, fr *FissionRes
 		} else if err != nil {
 			return err
 		}
-		fmt.Printf("Deleted %s %s/%s\n", o.TypeMeta.Kind, o.ObjectMeta.Namespace, o.ObjectMeta.Name)
+		fmt.Printf("Deleted %s %s\n", o.TypeMeta.Kind, k8sCache.MetaObjectToName(&o.ObjectMeta).String())
 	}
 
 	return nil
@@ -282,7 +283,7 @@ func destroyMessageQueueTriggers(ctx context.Context, fclient cmd.Client, fr *Fi
 		} else if err != nil {
 			return err
 		}
-		fmt.Printf("Deleted %s %s/%s\n", o.TypeMeta.Kind, o.ObjectMeta.Namespace, o.ObjectMeta.Name)
+		fmt.Printf("Deleted %s %s\n", o.TypeMeta.Kind, k8sCache.MetaObjectToName(&o.ObjectMeta).String())
 	}
 
 	return nil
@@ -300,7 +301,7 @@ func destroyFunctions(ctx context.Context, fclient cmd.Client, fr *FissionResour
 		} else if err != nil {
 			return err
 		}
-		fmt.Printf("Deleted %s %s/%s\n", o.TypeMeta.Kind, o.ObjectMeta.Namespace, o.ObjectMeta.Name)
+		fmt.Printf("Deleted %s %s\n", o.TypeMeta.Kind, k8sCache.MetaObjectToName(&o.ObjectMeta).String())
 	}
 
 	return nil
@@ -318,7 +319,7 @@ func destroyPackages(ctx context.Context, fclient cmd.Client, fr *FissionResourc
 		} else if err != nil {
 			return err
 		}
-		fmt.Printf("Deleted %s %s/%s\n", o.TypeMeta.Kind, o.ObjectMeta.Namespace, o.ObjectMeta.Name)
+		fmt.Printf("Deleted %s %s\n", o.TypeMeta.Kind, k8sCache.MetaObjectToName(&o.ObjectMeta).String())
 	}
 
 	return nil
@@ -336,7 +337,7 @@ func destroyEnvironments(ctx context.Context, fclient cmd.Client, fr *FissionRes
 		} else if err != nil {
 			return err
 		}
-		fmt.Printf("Deleted %s %s/%s\n", o.TypeMeta.Kind, o.ObjectMeta.Namespace, o.ObjectMeta.Name)
+		fmt.Printf("Deleted %s %s\n", o.TypeMeta.Kind, k8sCache.MetaObjectToName(&o.ObjectMeta).String())
 	}
 
 	return nil
diff --git a/pkg/fission-cli/cmd/spec/spec.go b/pkg/fission-cli/cmd/spec/spec.go
index ff6b76d362..9120e42c88 100644
--- a/pkg/fission-cli/cmd/spec/spec.go
+++ b/pkg/fission-cli/cmd/spec/spec.go
@@ -27,6 +27,7 @@ import (
 	"github.com/pkg/errors"
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	k8sCache "k8s.io/client-go/tools/cache"
 	"sigs.k8s.io/yaml"
 
 	fv1 "github.com/fission/fission/pkg/apis/core/v1"
@@ -123,10 +124,6 @@ type (
 	}
 )
 
-func MapKey(m *metav1.ObjectMeta) string {
-	return fmt.Sprintf("%v:%v", m.Namespace, m.Name)
-}
-
 // save saves object encoded value to spec file under given spec directory
 func save(data []byte, specDir string, specFile string, truncate bool) error {
 	// verify
@@ -300,14 +297,14 @@ func (fr *FissionResources) validateFunctionReference(functions map[string]bool,
 			Namespace: namespace,
 			Name:      name,
 		}
-		if _, ok := functions[MapKey(m)]; !ok {
+		if _, ok := functions[k8sCache.MetaObjectToName(m).String()]; !ok {
 			return fmt.Errorf("%v: %v '%v' references unknown function '%v'",
 				fr.SourceMap.Locations[kind][meta.Namespace][meta.Name],
 				kind,
 				meta.Name,
 				name)
 		} else {
-			functions[MapKey(m)] = true
+			functions[k8sCache.MetaObjectToName(m).String()] = true
 		}
 	}
 	return nil
@@ -334,7 +331,7 @@ func (fr *FissionResources) Validate(input cli.Input, client cmd.Client) ([]stri
 	// index packages, check outgoing refs, mark archives that are referenced
 	packages := make(map[string]bool)
 	for _, p := range fr.Packages {
-		packages[MapKey(&p.ObjectMeta)] = false
+		packages[k8sCache.MetaObjectToName(&p.ObjectMeta).String()] = false
 
 		as := map[string]string{
 			"source":     p.Spec.Source.URL,
@@ -376,7 +373,7 @@ func (fr *FissionResources) Validate(input cli.Input, client cmd.Client) ([]stri
 	// index functions, check function package refs, mark referenced packages
 	functions := make(map[string]bool)
 	for _, f := range fr.Functions {
-		functions[MapKey(&f.ObjectMeta)] = false
+		functions[k8sCache.MetaObjectToName(&f.ObjectMeta).String()] = false
 
 		if f.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType != fv1.ExecutorTypeContainer {
 			pkgMeta := &metav1.ObjectMeta{
@@ -386,7 +383,7 @@ func (fr *FissionResources) Validate(input cli.Input, client cmd.Client) ([]stri
 
 			// check package ref from function
 			packageRefExists := func() bool {
-				_, ok := packages[MapKey(pkgMeta)]
+				_, ok := packages[k8sCache.MetaObjectToName(pkgMeta).String()]
 				return ok
 			}
 
@@ -410,7 +407,7 @@ func (fr *FissionResources) Validate(input cli.Input, client cmd.Client) ([]stri
 					pkgMeta.Namespace,
 					pkgMeta.Name))
 			} else {
-				packages[MapKey(pkgMeta)] = true
+				packages[k8sCache.MetaObjectToName(pkgMeta).String()] = true
 			}
 		}
 
@@ -434,8 +431,10 @@ func (fr *FissionResources) Validate(input cli.Input, client cmd.Client) ([]stri
 
 	// error on unreferenced packages
 	for key, referenced := range packages {
-		ks := strings.Split(key, ":")
-		namespace, name := ks[0], ks[1]
+		namespace, name, err := k8sCache.SplitMetaNamespaceKey(key)
+		if err != nil {
+			result = multierror.Append(result, fmt.Errorf("failed to check the reference for the package '%s'", key))
+		}
 		if !referenced {
 			warnings = append(warnings, fmt.Sprintf(
 				"%v: package '%v' is not used in any function",
diff --git a/pkg/fission-cli/util/util.go b/pkg/fission-cli/util/util.go
index 024b9a98fe..0c76ce7564 100644
--- a/pkg/fission-cli/util/util.go
+++ b/pkg/fission-cli/util/util.go
@@ -39,6 +39,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/client-go/kubernetes"
+	k8sCache "k8s.io/client-go/tools/cache"
 
 	fv1 "github.com/fission/fission/pkg/apis/core/v1"
 	"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
@@ -472,7 +473,7 @@ func CheckHTTPTriggerDuplicates(ctx context.Context, client cmd.Client, t *fv1.H
 		return err
 	}
 	for _, ht := range triggers.Items {
-		if ht.ObjectMeta.UID == t.ObjectMeta.UID {
+		if k8sCache.MetaObjectToName(&ht.ObjectMeta).String() == k8sCache.MetaObjectToName(&t.ObjectMeta).String() {
 			// Same resource. No need to check.
 			continue
 		}


Test patch: