Repository: Azure/ARO-RP
Pull request #2327 (2022-08-16 13:53:01)

Test hive create namespace

### Which issue this PR addresses:
https://msazure.visualstudio.com/AzureRedHatOpenShift/_workitems/edit/14962382/

### What this PR does / why we need it:
Unit tests for pkg/hive/manager.go, CreateNamespace func.

### Is there any documentation that needs to be updated for this PR?
No



Patch:
diff --git a/.github/workflows/ci-go.yml b/.github/workflows/ci-go.yml
new file mode 100644
index 000000000..2a7897e73
--- /dev/null
+++ b/.github/workflows/ci-go.yml
@@ -0,0 +1,25 @@
+name: ci-go
+
+on:
+  push:
+    tags:
+      - v*
+    branches:
+      - master
+  pull_request:
+permissions:
+  contents: read
+
+jobs:
+  ci-from-docker:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+      - name: build from buildah
+        uses: redhat-actions/buildah-build@v2
+        with:
+          image: test-image
+          tags: ${{ github.sha }}
+          context: .
+          containerfiles: ./Dockerfile.ci
diff --git a/Dockerfile.ci b/Dockerfile.ci
new file mode 100644
index 000000000..68e3e99ce
--- /dev/null
+++ b/Dockerfile.ci
@@ -0,0 +1,36 @@
+FROM registry.access.redhat.com/ubi8/go-toolset:1.17.7 
+
+USER root
+RUN mkdir -p /root/go/src/github.com/Azure/ARO-RP 
+WORKDIR /root/go/src/github.com/Azure/ARO-RP 
+ENV GOPATH=/root/go
+
+#we have multiple steps for copy so we can make use of caching
+COPY vendor/ vendor
+
+COPY docs/ docs
+COPY hack/ hack
+
+COPY swagger/ swagger
+COPY test/ test
+COPY python/ python
+COPY portal/ portal
+
+COPY cmd/ cmd
+COPY pkg/ pkg
+
+#COPY all files with an extension (directories not copied)
+COPY ?*.* .
+COPY Makefile LICENSE .
+
+COPY .git .git
+COPY .gitignore .gitignore
+COPY .pipelines .pipelines
+COPY .gdn .gdn
+COPY .github .github
+COPY .env .env
+COPY .sha256sum .sha256sum
+COPY .config .config
+
+RUN hack/ci-utils/build.sh
+
diff --git a/hack/ci-utils/build.sh b/hack/ci-utils/build.sh
new file mode 100755
index 000000000..37e07be76
--- /dev/null
+++ b/hack/ci-utils/build.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+isClean() {
+    if [[ ! -z "$(git status -s)" ]]
+    then
+        echo "there are some modified files"
+        git status
+        exit 1 
+    fi
+}
+    
+
+set -xe 
+
+make generate
+isClean
+make build-all
+isClean
+make unit-test-go
+isClean
diff --git a/pkg/cluster/storageclass.go b/pkg/cluster/storageclass.go
index 44b88513c..4d7f27d7f 100644
--- a/pkg/cluster/storageclass.go
+++ b/pkg/cluster/storageclass.go
@@ -9,6 +9,7 @@ import (
 	"github.com/Azure/go-autorest/autorest/to"
 	corev1 "k8s.io/api/core/v1"
 	storagev1 "k8s.io/api/storage/v1"
+	kerrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/util/retry"
 )
@@ -42,7 +43,7 @@ func (m *manager) configureDefaultStorageClass(ctx context.Context) error {
 
 		encryptedSC := newEncryptedStorageClass(m.doc.OpenShiftCluster.Properties.WorkerProfiles[0].DiskEncryptionSetID)
 		_, err = m.kubernetescli.StorageV1().StorageClasses().Create(ctx, encryptedSC, metav1.CreateOptions{})
-		if err != nil {
+		if err != nil && !kerrors.IsAlreadyExists(err) {
 			return err
 		}
 
diff --git a/pkg/deploy/config.go b/pkg/deploy/config.go
index 55d10a802..eb5e1f3fc 100644
--- a/pkg/deploy/config.go
+++ b/pkg/deploy/config.go
@@ -6,13 +6,14 @@ package deploy
 import (
 	"crypto/rand"
 	"crypto/rsa"
-	"crypto/x509"
 	"fmt"
 	"io/ioutil"
 	"reflect"
 	"strings"
 
+	"github.com/Azure/go-autorest/autorest/to"
 	"github.com/ghodss/yaml"
+	"golang.org/x/crypto/ssh"
 )
 
 // NOTICE: when modifying the config definition here, don't forget to update
@@ -156,9 +157,12 @@ func (conf *RPConfig) validate() error {
 		if err != nil {
 			return err
 		}
-		pubKey := x509.MarshalPKCS1PublicKey(&key.PublicKey)
-		publicKey := string(pubKey)
-		conf.Configuration.SSHPublicKey = (&publicKey)
+		publicRsaKey, err := ssh.NewPublicKey(&key.PublicKey)
+		if err != nil {
+			return err
+		}
+		publicKeyBytes := ssh.MarshalAuthorizedKey(publicRsaKey)
+		conf.Configuration.SSHPublicKey = to.StringPtr(string(publicKeyBytes))
 	}
 
 	for i := 0; i < v.NumField(); i++ {
diff --git a/pkg/frontend/admin_openshiftcluster_redeployvm.go b/pkg/frontend/admin_openshiftcluster_redeployvm.go
index 1e6fe6a68..f1a1ed864 100644
--- a/pkg/frontend/admin_openshiftcluster_redeployvm.go
+++ b/pkg/frontend/admin_openshiftcluster_redeployvm.go
@@ -12,8 +12,6 @@ import (
 	"github.com/gorilla/mux"
 	"github.com/sirupsen/logrus"
 
-	"github.com/Azure/ARO-RP/pkg/api"
-	"github.com/Azure/ARO-RP/pkg/database/cosmosdb"
 	"github.com/Azure/ARO-RP/pkg/frontend/middleware"
 )
 
@@ -21,40 +19,16 @@ func (f *frontend) postAdminOpenShiftClusterRedeployVM(w http.ResponseWriter, r
 	ctx := r.Context()
 	log := ctx.Value(middleware.ContextKeyLog).(*logrus.Entry)
 	r.URL.Path = filepath.Dir(r.URL.Path)
-
-	err := f._postAdminOpenShiftClusterRedeployVM(ctx, r, log)
-
+	err := f._postAdminOpenShiftClusterRedeployVM(log, ctx, r)
 	adminReply(log, w, nil, nil, err)
 }
 
-func (f *frontend) _postAdminOpenShiftClusterRedeployVM(ctx context.Context, r *http.Request, log *logrus.Entry) error {
+func (f *frontend) _postAdminOpenShiftClusterRedeployVM(log *logrus.Entry, ctx context.Context, r *http.Request) error {
 	vars := mux.Vars(r)
-
 	vmName := r.URL.Query().Get("vmName")
-	err := validateAdminVMName(vmName)
-	if err != nil {
-		return err
-	}
-
-	resourceID := strings.TrimPrefix(r.URL.Path, "/admin")
-
-	doc, err := f.dbOpenShiftClusters.Get(ctx, resourceID)
-	switch {
-	case cosmosdb.IsErrorStatusCode(err, http.StatusNotFound):
-		return api.NewCloudError(http.StatusNotFound, api.CloudErrorCodeResourceNotFound, "", "The Resource '%s/%s' under resource group '%s' was not found.", vars["resourceType"], vars["resourceName"], vars["resourceGroupName"])
-	case err != nil:
-		return err
-	}
-
-	subscriptionDoc, err := f.getSubscriptionDocument(ctx, doc.Key)
-	if err != nil {
-		return err
-	}
-
-	a, err := f.azureActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc)
+	azActionsWrapper, err := f.newAzureActionsWrapper(log, ctx, vmName, strings.TrimPrefix(r.URL.Path, "/admin"), vars)
 	if err != nil {
 		return err
 	}
-
-	return a.VMRedeployAndWait(ctx, vmName)
+	return f.adminAction.VMRedeployAndWait(ctx, azActionsWrapper.vmName)
 }
diff --git a/pkg/frontend/admin_openshiftcluster_startvm.go b/pkg/frontend/admin_openshiftcluster_startvm.go
index be43f2383..57c20ab89 100644
--- a/pkg/frontend/admin_openshiftcluster_startvm.go
+++ b/pkg/frontend/admin_openshiftcluster_startvm.go
@@ -12,8 +12,6 @@ import (
 	"github.com/gorilla/mux"
 	"github.com/sirupsen/logrus"
 
-	"github.com/Azure/ARO-RP/pkg/api"
-	"github.com/Azure/ARO-RP/pkg/database/cosmosdb"
 	"github.com/Azure/ARO-RP/pkg/frontend/middleware"
 )
 
@@ -21,40 +19,17 @@ func (f *frontend) postAdminOpenShiftClusterStartVM(w http.ResponseWriter, r *ht
 	ctx := r.Context()
 	log := ctx.Value(middleware.ContextKeyLog).(*logrus.Entry)
 	r.URL.Path = filepath.Dir(r.URL.Path)
-
-	err := f._postAdminOpenShiftClusterStartVM(ctx, r, log)
-
+	err := f._postAdminOpenShiftClusterStartVM(log, ctx, r)
 	adminReply(log, w, nil, nil, err)
 }
 
-func (f *frontend) _postAdminOpenShiftClusterStartVM(ctx context.Context, r *http.Request, log *logrus.Entry) error {
+func (f *frontend) _postAdminOpenShiftClusterStartVM(log *logrus.Entry, ctx context.Context, r *http.Request) error {
 	vars := mux.Vars(r)
-
 	vmName := r.URL.Query().Get("vmName")
-	err := validateAdminVMName(vmName)
-	if err != nil {
-		return err
-	}
-
-	resourceID := strings.TrimPrefix(r.URL.Path, "/admin")
-
-	doc, err := f.dbOpenShiftClusters.Get(ctx, resourceID)
-	switch {
-	case cosmosdb.IsErrorStatusCode(err, http.StatusNotFound):
-		return api.NewCloudError(http.StatusNotFound, api.CloudErrorCodeResourceNotFound, "", "The Resource '%s/%s' under resource group '%s' was not found.", vars["resourceType"], vars["resourceName"], vars["resourceGroupName"])
-	case err != nil:
-		return err
-	}
-
-	subscriptionDoc, err := f.getSubscriptionDocument(ctx, doc.Key)
-	if err != nil {
-		return err
-	}
-
-	a, err := f.azureActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc)
+	azActionsWrapper, err := f.newAzureActionsWrapper(log, ctx, vmName, strings.TrimPrefix(r.URL.Path, "/admin"), vars)
 	if err != nil {
 		return err
 	}
 
-	return a.VMStartAndWait(ctx, vmName)
+	return f.adminAction.VMStartAndWait(ctx, azActionsWrapper.vmName)
 }
diff --git a/pkg/frontend/admin_openshiftcluster_stopvm.go b/pkg/frontend/admin_openshiftcluster_stopvm.go
index e2a013979..76b41ec92 100644
--- a/pkg/frontend/admin_openshiftcluster_stopvm.go
+++ b/pkg/frontend/admin_openshiftcluster_stopvm.go
@@ -12,8 +12,6 @@ import (
 	"github.com/gorilla/mux"
 	"github.com/sirupsen/logrus"
 
-	"github.com/Azure/ARO-RP/pkg/api"
-	"github.com/Azure/ARO-RP/pkg/database/cosmosdb"
 	"github.com/Azure/ARO-RP/pkg/frontend/middleware"
 )
 
@@ -21,40 +19,17 @@ func (f *frontend) postAdminOpenShiftClusterStopVM(w http.ResponseWriter, r *htt
 	ctx := r.Context()
 	log := ctx.Value(middleware.ContextKeyLog).(*logrus.Entry)
 	r.URL.Path = filepath.Dir(r.URL.Path)
-
-	err := f._postAdminOpenShiftClusterStopVM(ctx, r, log)
-
+	err := f._postAdminOpenShiftClusterStopVM(log, ctx, r)
 	adminReply(log, w, nil, nil, err)
 }
 
-func (f *frontend) _postAdminOpenShiftClusterStopVM(ctx context.Context, r *http.Request, log *logrus.Entry) error {
+func (f *frontend) _postAdminOpenShiftClusterStopVM(log *logrus.Entry, ctx context.Context, r *http.Request) error {
 	vars := mux.Vars(r)
-
 	vmName := r.URL.Query().Get("vmName")
-	err := validateAdminVMName(vmName)
-	if err != nil {
-		return err
-	}
-
-	resourceID := strings.TrimPrefix(r.URL.Path, "/admin")
-
-	doc, err := f.dbOpenShiftClusters.Get(ctx, resourceID)
-	switch {
-	case cosmosdb.IsErrorStatusCode(err, http.StatusNotFound):
-		return api.NewCloudError(http.StatusNotFound, api.CloudErrorCodeResourceNotFound, "", "The Resource '%s/%s' under resource group '%s' was not found.", vars["resourceType"], vars["resourceName"], vars["resourceGroupName"])
-	case err != nil:
-		return err
-	}
-
-	subscriptionDoc, err := f.getSubscriptionDocument(ctx, doc.Key)
-	if err != nil {
-		return err
-	}
-
-	a, err := f.azureActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc)
+	azActionsWrapper, err := f.newAzureActionsWrapper(log, ctx, vmName, strings.TrimPrefix(r.URL.Path, "/admin"), vars)
 	if err != nil {
 		return err
 	}
 
-	return a.VMStopAndWait(ctx, vmName)
+	return f.adminAction.VMStopAndWait(ctx, azActionsWrapper.vmName)
 }
diff --git a/pkg/frontend/admin_openshiftcluster_vmresize.go b/pkg/frontend/admin_openshiftcluster_vmresize.go
index e04a7e08b..64c957a70 100644
--- a/pkg/frontend/admin_openshiftcluster_vmresize.go
+++ b/pkg/frontend/admin_openshiftcluster_vmresize.go
@@ -17,7 +17,6 @@ import (
 	kruntime "k8s.io/apimachinery/pkg/runtime"
 
 	"github.com/Azure/ARO-RP/pkg/api"
-	"github.com/Azure/ARO-RP/pkg/database/cosmosdb"
 	"github.com/Azure/ARO-RP/pkg/frontend/middleware"
 )
 
@@ -25,34 +24,14 @@ func (f *frontend) postAdminOpenShiftClusterVMResize(w http.ResponseWriter, r *h
 	ctx := r.Context()
 	log := ctx.Value(middleware.ContextKeyLog).(*logrus.Entry)
 	r.URL.Path = filepath.Dir(r.URL.Path)
-
-	err := f._postAdminOpenShiftClusterVMResize(ctx, r, log)
-
+	err := f._postAdminOpenShiftClusterVMResize(log, ctx, r)
 	adminReply(log, w, nil, nil, err)
 }
 
-func (f *frontend) _postAdminOpenShiftClusterVMResize(ctx context.Context, r *http.Request, log *logrus.Entry) error {
+func (f *frontend) _postAdminOpenShiftClusterVMResize(log *logrus.Entry, ctx context.Context, r *http.Request) error {
 	vars := mux.Vars(r)
-
 	vmName := r.URL.Query().Get("vmName")
-	err := validateAdminVMName(vmName)
-	if err != nil {
-		return err
-	}
-
-	resourceID := strings.TrimPrefix(r.URL.Path, "/admin")
-
-	doc, err := f.dbOpenShiftClusters.Get(ctx, resourceID)
-	switch {
-	case cosmosdb.IsErrorStatusCode(err, http.StatusNotFound):
-		return api.NewCloudError(http.StatusNotFound, api.CloudErrorCodeResourceNotFound, "",
-			"The Resource '%s/%s' under resource group '%s' was not found.",
-			vars["resourceType"], vars["resourceName"], vars["resourceGroupName"])
-	case err != nil:
-		return err
-	}
-
-	subscriptionDoc, err := f.getSubscriptionDocument(ctx, doc.Key)
+	azActionsWrapper, err := f.newAzureActionsWrapper(log, ctx, vmName, strings.TrimPrefix(r.URL.Path, "/admin"), vars)
 	if err != nil {
 		return err
 	}
@@ -63,12 +42,7 @@ func (f *frontend) _postAdminOpenShiftClusterVMResize(ctx context.Context, r *ht
 		return err
 	}
 
-	a, err := f.azureActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc)
-	if err != nil {
-		return err
-	}
-
-	k, err := f.kubeActionsFactory(log, f.env, doc.OpenShiftCluster)
+	k, err := f.kubeActionsFactory(log, f.env, azActionsWrapper.doc.OpenShiftCluster)
 	if err != nil {
 		return err
 	}
@@ -95,7 +69,7 @@ func (f *frontend) _postAdminOpenShiftClusterVMResize(ctx context.Context, r *ht
 			continue
 		}
 
-		if strings.EqualFold(vmName, node.ObjectMeta.Name) {
+		if strings.EqualFold(azActionsWrapper.vmName, node.ObjectMeta.Name) {
 			nodeExists = true
 			break
 		}
@@ -104,8 +78,8 @@ func (f *frontend) _postAdminOpenShiftClusterVMResize(ctx context.Context, r *ht
 	if !nodeExists {
 		return api.NewCloudError(http.StatusNotFound, api.CloudErrorCodeNotFound, "",
 			`"The master node '%s' under resource group '%s' was not found."`,
-			vmName, vars["resourceGroupName"])
+			azActionsWrapper.vmName, vars["resourceGroupName"])
 	}
 
-	return a.VMResize(ctx, vmName, vmSize)
+	return f.adminAction.VMResize(ctx, azActionsWrapper.vmName, vmSize)
 }
diff --git a/pkg/frontend/admin_openshiftcluster_vmsizelist.go b/pkg/frontend/admin_openshiftcluster_vmsizelist.go
index 5a712b363..1bc9651d2 100644
--- a/pkg/frontend/admin_openshiftcluster_vmsizelist.go
+++ b/pkg/frontend/admin_openshiftcluster_vmsizelist.go
@@ -24,9 +24,7 @@ func (f *frontend) getAdminOpenShiftClusterVMResizeOptions(w http.ResponseWriter
 	ctx := r.Context()
 	log := ctx.Value(middleware.ContextKeyLog).(*logrus.Entry)
 	r.URL.Path = filepath.Dir(r.URL.Path)
-
 	b, err := f._getAdminOpenShiftClusterVMResizeOptions(ctx, r, log)
-
 	adminReply(log, w, nil, b, err)
 }
 
diff --git a/pkg/frontend/admin_vm_actions.go b/pkg/frontend/admin_vm_actions.go
new file mode 100644
index 000000000..de7e1af32
--- /dev/null
+++ b/pkg/frontend/admin_vm_actions.go
@@ -0,0 +1,53 @@
+package frontend
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"context"
+	"net/http"
+
+	"github.com/sirupsen/logrus"
+
+	"github.com/Azure/ARO-RP/pkg/api"
+	"github.com/Azure/ARO-RP/pkg/database/cosmosdb"
+)
+
+type azVmActionsWrapper struct {
+	vmName string
+	doc    *api.OpenShiftClusterDocument
+}
+
+func (f *frontend) newAzureActionsWrapper(log *logrus.Entry, ctx context.Context, vmName, resourceID string, vars map[string]string) (azVmActionsWrapper, error) {
+	err := validateAdminVMName(vmName)
+	if err != nil {
+		return azVmActionsWrapper{}, err
+	}
+	if err != nil {
+		return azVmActionsWrapper{}, err
+	}
+
+	doc, err := f.dbOpenShiftClusters.Get(ctx, resourceID)
+	switch {
+	case cosmosdb.IsErrorStatusCode(err, http.StatusNotFound):
+		return azVmActionsWrapper{}, api.NewCloudError(http.StatusNotFound, api.CloudErrorCodeResourceNotFound, "",
+			"The Resource '%s/%s' under resource group '%s' was not found.",
+			vars["resourceType"], vars["resourceName"], vars["resourceGroupName"])
+	case err != nil:
+		return azVmActionsWrapper{}, err
+	}
+
+	subscriptionDoc, err := f.getSubscriptionDocument(ctx, doc.Key)
+	if err != nil {
+		return azVmActionsWrapper{}, err
+	}
+
+	f.adminAction, err = f.azureActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc)
+	if err != nil {
+		return azVmActionsWrapper{}, err
+	}
+	return azVmActionsWrapper{
+		vmName: vmName,
+		doc:    doc,
+	}, nil
+}
diff --git a/pkg/frontend/frontend.go b/pkg/frontend/frontend.go
index 0dad8a02c..a8a8af9e6 100644
--- a/pkg/frontend/frontend.go
+++ b/pkg/frontend/frontend.go
@@ -60,6 +60,7 @@ type frontend struct {
 	kubeActionsFactory  kubeActionsFactory
 	azureActionsFactory azureActionsFactory
 	ocEnricherFactory   ocEnricherFactory
+	adminAction         adminactions.AzureActions
 
 	l net.Listener
 	s *http.Server
diff --git a/pkg/operator/controllers/genevalogging/const.go b/pkg/operator/controllers/genevalogging/const.go
index 4dd5d10a5..d180c0981 100644
--- a/pkg/operator/controllers/genevalogging/const.go
+++ b/pkg/operator/controllers/genevalogging/const.go
@@ -73,7 +73,7 @@ const (
 [FILTER]
 	Name grep
 	Match containers
-	Regex NAMESPACE ^(?:default|kube-.*|openshift|openshift-.*)$
+	Regex NAMESPACE ^(?:default|kube-.*|openshift|(?!openshift-(logging|gitops|user-workload-monitoring))(openshift-.*))$
 
 [FILTER]
 	Name nest
diff --git a/pkg/operator/deploy/deploy.go b/pkg/operator/deploy/deploy.go
index a3806e179..420eaa20b 100644
--- a/pkg/operator/deploy/deploy.go
+++ b/pkg/operator/deploy/deploy.go
@@ -89,9 +89,7 @@ func New(log *logrus.Entry, env env.Interface, oc *api.OpenShiftCluster, arocli
 type deploymentData struct {
 	Image              string
 	Version            string
-	GitCommit          string
 	IsLocalDevelopment bool
-	HasVersion         bool
 }
 
 func templateManifests(data deploymentData) ([][]byte, error) {
@@ -125,18 +123,24 @@ func templateManifests(data deploymentData) ([][]byte, error) {
 
 func (o *operator) createDeploymentData() deploymentData {
 	image := o.env.AROOperatorImage()
-	hasVersion := false
+
+	// HACK: Override for ARO_IMAGE env variable setup in local-dev mode
+	version := "latest"
+	if strings.Contains(image, ":") {
+		str := strings.Split(image, ":")
+		version = str[len(str)-1]
+	}
+
+	// Set version correctly if it's overridden
 	if o.oc.Properties.OperatorVersion != "" {
-		image = fmt.Sprintf("%s/aro", o.env.ACRDomain())
-		hasVersion = true
+		version = o.oc.Properties.OperatorVersion
+		image = fmt.Sprintf("%s/aro:%s", o.env.ACRDomain(), version)
 	}
 
 	return deploymentData{
 		IsLocalDevelopment: o.env.IsLocalDevelopmentMode(),
 		Image:              image,
-		Version:            o.oc.Properties.OperatorVersion,
-		GitCommit:          version.GitCommit,
-		HasVersion:         hasVersion,
+		Version:            version,
 	}
 }
 
diff --git a/pkg/operator/deploy/staticresources/master/deployment.yaml.tmpl b/pkg/operator/deploy/staticresources/master/deployment.yaml.tmpl
index 844adf473..03781edc0 100644
--- a/pkg/operator/deploy/staticresources/master/deployment.yaml.tmpl
+++ b/pkg/operator/deploy/staticresources/master/deployment.yaml.tmpl
@@ -2,12 +2,8 @@ apiVersion: apps/v1
 kind: Deployment
 metadata:
   labels:
-    app: aro-operator-master 
-    {{ if .HasVersion}}
-    version: {{ .Version }} 
-    {{ else }}
-    version: unknown
-    {{end}}
+    app: aro-operator-master
+    version: {{ .Version }}
   name: aro-operator-master
   namespace: openshift-azure-operator
 spec:
@@ -30,12 +26,12 @@ spec:
         args:
         - operator
         - master
-        image: "{{ .Image }}{{ if .HasVersion}}:{{ if .IsLocalDevelopment }}{{ .GitCommit }}{{ else }}{{ .Version }}{{end}}{{end}}"
+        image: "{{ .Image }}"
         name: aro-operator
         {{ if .IsLocalDevelopment}}
-        env: 
+        env:
         - name: "RP_MODE"
-          value: "development" 
+          value: "development"
         {{ end }}
         ports:
         - containerPort: 8080
diff --git a/pkg/operator/deploy/staticresources/worker/deployment.yaml.tmpl b/pkg/operator/deploy/staticresources/worker/deployment.yaml.tmpl
index ada7882f4..48ba0c700 100644
--- a/pkg/operator/deploy/staticresources/worker/deployment.yaml.tmpl
+++ b/pkg/operator/deploy/staticresources/worker/deployment.yaml.tmpl
@@ -2,12 +2,8 @@ apiVersion: apps/v1
 kind: Deployment
 metadata:
   labels:
-    app: aro-operator-worker 
-    {{ if .HasVersion}}
-    version: {{ .Version }} 
-    {{ else }}
-    version: unknown
-    {{end}}
+    app: aro-operator-worker
+    version: {{ .Version }}
   name: aro-operator-worker
   namespace: openshift-azure-operator
 spec:
@@ -30,13 +26,13 @@ spec:
         args:
         - operator
         - worker
-        image: "{{ .Image }}{{ if .HasVersion}}:{{ if .IsLocalDevelopment }}{{ .GitCommit }}{{ else }}{{ .Version }}{{end}}{{end}}"
+        image: "{{ .Image }}"
+        name: aro-operator
         {{ if .IsLocalDevelopment}}
-        env: 
+        env:
         - name: "RP_MODE"
-          value: "development" 
+          value: "development"
         {{ end }}
-        name: aro-operator
         livenessProbe:
           httpGet:
             path: /healthz/ready
diff --git a/pkg/util/uuid/fake/fake_uuid.go b/pkg/util/uuid/fake/fake_uuid.go
new file mode 100644
index 000000000..7a581a624
--- /dev/null
+++ b/pkg/util/uuid/fake/fake_uuid.go
@@ -0,0 +1,31 @@
+package fake
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
+)
+
+type fakeGenerator struct {
+	words      []string
+	currentPos int
+}
+
+func NewGenerator(predefinedWords []string) uuid.Generator {
+	return &fakeGenerator{
+		words: predefinedWords,
+	}
+}
+
+func (f *fakeGenerator) movePos() {
+	f.currentPos++
+}
+
+func (f fakeGenerator) Generate() string {
+	defer f.movePos()
+	if len(f.words) < f.currentPos {
+		return ""
+	}
+	return f.words[f.currentPos]
+}


Test patch:
diff --git a/.pipelines/e2e.yml b/.pipelines/e2e.yml
index ca4d1797f..0a6cb5963 100644
--- a/.pipelines/e2e.yml
+++ b/.pipelines/e2e.yml
@@ -43,7 +43,9 @@ jobs:
 
       set -x
       . ./hack/e2e/run-rp-and-e2e.sh
-      trap 'set +e; kill_rp; clean_e2e_db' EXIT
+      trap 'set +e; kill_rp; clean_e2e_db; kill_vpn' EXIT
+
+      run_vpn
 
       deploy_e2e_db
 
diff --git a/hack/e2e/run-rp-and-e2e.sh b/hack/e2e/run-rp-and-e2e.sh
index 44d6c8de6..e6e3cdfe5 100755
--- a/hack/e2e/run-rp-and-e2e.sh
+++ b/hack/e2e/run-rp-and-e2e.sh
@@ -37,6 +37,15 @@ kill_rp(){
     wait $rppid
 }
 
+run_vpn() {
+    sudo openvpn --config secrets/$VPN --daemon --writepid vpnpid
+    sleep 10
+}
+
+kill_vpn() {
+    while read pid; do sudo kill $pid; done < vpnpid
+}
+
 deploy_e2e_db() {
     echo "########## 📦 Creating new DB $DATABASE_NAME in $DATABASE_ACCOUNT_NAME ##########"
 
diff --git a/pkg/hive/manager_test.go b/pkg/hive/manager_test.go
index 6a93d8c9d..d144c1fcc 100644
--- a/pkg/hive/manager_test.go
+++ b/pkg/hive/manager_test.go
@@ -13,8 +13,11 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	kruntime "k8s.io/apimachinery/pkg/runtime"
+	kubernetesfake "k8s.io/client-go/kubernetes/fake"
 
 	"github.com/Azure/ARO-RP/pkg/util/cmp"
+	"github.com/Azure/ARO-RP/pkg/util/uuid"
+	"github.com/Azure/ARO-RP/pkg/util/uuid/fake"
 )
 
 func TestIsClusterDeploymentReady(t *testing.T) {
@@ -158,3 +161,63 @@ func TestResetCorrelationData(t *testing.T) {
 		})
 	}
 }
+
+func TestCreateNamespace(t *testing.T) {
+	for _, tc := range []struct {
+		name             string
+		nsNames          []string
+		useFakeGenerator bool
+		shouldFail       bool
+	}{
+		{
+			name:             "not conflict names and real generator",
+			nsNames:          []string{"namespace1", "namespace2"},
+			useFakeGenerator: false,
+			shouldFail:       false,
+		},
+		{
+			name:             "conflict names and real generator",
+			nsNames:          []string{"namespace", "namespace"},
+			useFakeGenerator: false,
+			shouldFail:       false,
+		},
+		{
+			name:             "not conflict names and fake generator",
+			nsNames:          []string{"namespace1", "namespace2"},
+			useFakeGenerator: true,
+			shouldFail:       false,
+		},
+		{
+			name:             "conflict names and fake generator",
+			nsNames:          []string{"namespace", "namespace"},
+			useFakeGenerator: true,
+			shouldFail:       true,
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			fakeClientset := kubernetesfake.NewSimpleClientset()
+			c := clusterManager{
+				kubernetescli: fakeClientset,
+			}
+
+			if tc.useFakeGenerator {
+				uuid.DefaultGenerator = fake.NewGenerator(tc.nsNames)
+			}
+
+			ns, err := c.CreateNamespace(context.Background())
+			if err != nil && !tc.shouldFail {
+				t.Error(err)
+			}
+
+			if err == nil {
+				res, err := fakeClientset.CoreV1().Namespaces().Get(context.Background(), ns.Name, metav1.GetOptions{})
+				if err != nil {
+					t.Error(err)
+				}
+				if !reflect.DeepEqual(ns, res) {
+					t.Errorf("results are not equal: \n wanted: %+v \n got %+v", ns, res)
+				}
+			}
+		})
+	}
+}
diff --git a/pkg/operator/deploy/deploy_test.go b/pkg/operator/deploy/deploy_test.go
index c4d494c19..5bdb8ac7f 100644
--- a/pkg/operator/deploy/deploy_test.go
+++ b/pkg/operator/deploy/deploy_test.go
@@ -5,6 +5,7 @@ package deploy
 
 import (
 	"errors"
+	"reflect"
 	"testing"
 
 	"github.com/golang/mock/gomock"
@@ -13,7 +14,6 @@ import (
 	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/util/cmp"
 	mock_env "github.com/Azure/ARO-RP/pkg/util/mocks/env"
-	"github.com/Azure/ARO-RP/pkg/util/version"
 )
 
 func TestCheckIngressIP(t *testing.T) {
@@ -130,6 +130,79 @@ func TestCheckIngressIP(t *testing.T) {
 	}
 }
 
+func TestCreateDeploymentData(t *testing.T) {
+	operatorImageTag := "v20071110"
+	operatorImageUntagged := "arosvc.azurecr.io/aro"
+	operatorImageWithTag := operatorImageUntagged + ":" + operatorImageTag
+
+	for _, tt := range []struct {
+		name                    string
+		mock                    func(*mock_env.MockInterface, *api.OpenShiftCluster)
+		operatorVersionOverride string
+		expected                deploymentData
+	}{
+		{
+			name: "no image override, use default",
+			mock: func(env *mock_env.MockInterface, oc *api.OpenShiftCluster) {
+				env.EXPECT().
+					AROOperatorImage().
+					Return(operatorImageWithTag)
+			},
+			expected: deploymentData{
+				Image:   operatorImageWithTag,
+				Version: operatorImageTag},
+		},
+		{
+			name: "no image tag, use latest version",
+			mock: func(env *mock_env.MockInterface, oc *api.OpenShiftCluster) {
+				env.EXPECT().
+					AROOperatorImage().
+					Return(operatorImageUntagged)
+			},
+			expected: deploymentData{
+				Image:   operatorImageUntagged,
+				Version: "latest"},
+		},
+		{
+			name: "OperatorVersion override set",
+			mock: func(env *mock_env.MockInterface, oc *api.OpenShiftCluster) {
+				env.EXPECT().
+					AROOperatorImage().
+					Return(operatorImageUntagged)
+				env.EXPECT().
+					ACRDomain().
+					Return("docker.io")
+
+				oc.Properties.OperatorVersion = "override"
+			},
+			expected: deploymentData{
+				Image:   "docker.io/aro:override",
+				Version: "override"},
+		},
+	} {
+		t.Run(tt.name, func(t *testing.T) {
+			controller := gomock.NewController(t)
+			defer controller.Finish()
+
+			env := mock_env.NewMockInterface(controller)
+			env.EXPECT().IsLocalDevelopmentMode().Return(tt.expected.IsLocalDevelopment).AnyTimes()
+
+			oc := &api.OpenShiftCluster{Properties: api.OpenShiftClusterProperties{}}
+			tt.mock(env, oc)
+
+			o := operator{
+				oc:  oc,
+				env: env,
+			}
+
+			deploymentData := o.createDeploymentData()
+			if !reflect.DeepEqual(deploymentData, tt.expected) {
+				t.Errorf("actual deployment: %v, expected %v", deploymentData, tt.expected)
+			}
+		})
+	}
+}
+
 func TestOperatorVersion(t *testing.T) {
 	type test struct {
 		name         string
@@ -144,7 +217,7 @@ func TestOperatorVersion(t *testing.T) {
 			oc: func() *api.OpenShiftClusterProperties {
 				return &api.OpenShiftClusterProperties{}
 			},
-			wantVersion:  version.GitCommit,
+			wantVersion:  "latest",
 			wantPullspec: "defaultaroimagefromenv",
 		},
 		{