Repository: Azure/ARO-RP
Pull request #1845 (2021-11-18 06:55:56)

Managed Upgrade Operator
### Which issue this PR addresses:

https://msazure.visualstudio.com/AzureRedHatOpenShift/_workitems/edit/10430647

### What this PR does / why we need it:

Implements the Managed Upgrade Operator deployment in the ARO Operator.

### Test plan for issue:

Unit tests exist.

To verify, build the operator and push it to a cluster, and then perform an AdminUpdate with this:

```
{
  "properties": {
    "maintenanceTask": "OperatorUpdate",
    "operatorFlags": {
      "rh.srep.muo.enabled": "true",
      "rh.srep.muo.managed": "true",
      "rh.srep.muo.deploy.pullspec": "quay.io/app-sre/managed-upgrade-operator@sha256:054895ba677031579ed346ad9df7ae7f9c6b5ed9ca106f34fb028bbc2aee4ff4"
    }
  }
}
```

Then apply this config manifest (will be included in a separate PR with configuration options):

```
apiVersion: v1
kind: ConfigMap
metadata:
  name: managed-upgrade-operator-config
  namespace:  openshift-managed-upgrade-operator
data:
  config.yaml: |
    configManager:
      source: LOCAL
      localConfigName: managed-upgrade-config
      watchInterval: 1
    maintenance:
      controlPlaneTime: 90
      ignoredAlerts:
        controlPlaneCriticals:
        - ClusterOperatorDown
        - ClusterOperatorDegraded
    upgradeWindow:
      delayTrigger: 30
      timeOut: 120
    nodeDrain:
      timeOut: 45
      expectedNodeDrainTime: 8
    scale:
      timeOut: 30
    healthCheck:
      ignoredCriticals:
      - PrometheusRuleFailures
      - CannotRetrieveUpdates
      - FluentdNodeDown
      ignoredNamespaces:
      - openshift-logging
      - openshift-redhat-marketplace
      - openshift-operators
      - openshift-user-workload-monitoring
      - openshift-pipelines

```

You may need to kill the MUO pod for it to pick up the config.

You can then schedule an upgrade:

```
apiVersion: upgrade.managed.openshift.io/v1alpha1
kind: UpgradeConfig
metadata:
  name: managed-upgrade-config
  namespace: openshift-managed-upgrade-operator
spec:
  type: "ARO"
  upgradeAt: "2022-02-08T03:20:00Z"
  PDBForceDrainTimeout: 60
  capacityReservation: false
  desired:
    image: "<IMAGEHASH HERE>"

```

### Is there any documentation that needs to be updated for this PR?

Customer-facing and SRE-facing documentation TBA.

github-actions[bot]: Please rebase pull request.
github-actions[bot]: Please rebase pull request.
hawkowl: I re-verified the instructions today and it works :)
github-actions[bot]: Please rebase pull request.
hawkowl: I ran out of time to add a test for the appsv1.Deployment changes in DynamicHelper since it adds a bunch of default stuff and I'm not 100% sure how I can make the existing test happy with that without copy-pasting a bunch of defaults that aren't actually relevant to the test. Added tests for the other items in it, though, and fixed a potential crash (yay for tests!).

Patch:
diff --git a/cmd/aro/operator.go b/cmd/aro/operator.go
index 07b85de50..3eb3dc6cf 100644
--- a/cmd/aro/operator.go
+++ b/cmd/aro/operator.go
@@ -33,6 +33,7 @@ import (
 	"github.com/Azure/ARO-RP/pkg/operator/controllers/machine"
 	"github.com/Azure/ARO-RP/pkg/operator/controllers/machineset"
 	"github.com/Azure/ARO-RP/pkg/operator/controllers/monitoring"
+	"github.com/Azure/ARO-RP/pkg/operator/controllers/muo"
 	"github.com/Azure/ARO-RP/pkg/operator/controllers/node"
 	"github.com/Azure/ARO-RP/pkg/operator/controllers/pullsecret"
 	"github.com/Azure/ARO-RP/pkg/operator/controllers/rbac"
@@ -202,6 +203,11 @@ func operator(ctx context.Context, log *logrus.Entry) error {
 			arocli, maocli, kubernetescli, imageregistrycli)).SetupWithManager(mgr); err != nil {
 			return fmt.Errorf("unable to create controller %s: %v", controllers.StorageAccountsControllerName, err)
 		}
+		if err = (muo.NewReconciler(
+			log.WithField("controller", controllers.ManagedUpgradeOperatorControllerName),
+			arocli, kubernetescli, dh)).SetupWithManager(mgr); err != nil {
+			return fmt.Errorf("unable to create controller %s: %v", controllers.ManagedUpgradeOperatorControllerName, err)
+		}
 	}
 
 	if err = (checker.NewReconciler(
diff --git a/hack/validate-imports/validate-imports.go b/hack/validate-imports/validate-imports.go
index 9b67d75be..440b29a2f 100644
--- a/hack/validate-imports/validate-imports.go
+++ b/hack/validate-imports/validate-imports.go
@@ -72,6 +72,11 @@ func acceptableNames(path string) []string {
 		return []string{"mock_" + m[1]}
 	}
 
+	m = regexp.MustCompile(`^github\.com/Azure/ARO-RP/pkg/operator/mocks/(?:.+/)?([^/]+)$`).FindStringSubmatch(path)
+	if m != nil {
+		return []string{"mock_" + m[1]}
+	}
+
 	m = regexp.MustCompile(`^github\.com/Azure/azure-sdk-for-go/services/(?:preview/)?(?:[^/]+)/mgmt/(?:[^/]+)/([^/]+)$`).FindStringSubmatch(path)
 	if m != nil {
 		return []string{"mgmt" + m[1]}
@@ -209,6 +214,7 @@ func validateImports(path string, fset *token.FileSet, f *ast.File) (errs []erro
 		"pkg/database/cosmosdb/zz_generated_",
 		"pkg/operator/apis",
 		"pkg/operator/clientset",
+		"pkg/operator/mocks/",
 		"pkg/util/mocks/",
 	} {
 		if strings.HasPrefix(path, prefix) {
diff --git a/pkg/operator/apis/aro.openshift.io/v1alpha1/cluster_types.go b/pkg/operator/apis/aro.openshift.io/v1alpha1/cluster_types.go
index daead04a8..17e82c1b2 100644
--- a/pkg/operator/apis/aro.openshift.io/v1alpha1/cluster_types.go
+++ b/pkg/operator/apis/aro.openshift.io/v1alpha1/cluster_types.go
@@ -22,12 +22,14 @@ const (
 	InternetReachableFromWorker = "InternetReachableFromWorker"
 	MachineValid                = "MachineValid"
 	ServicePrincipalValid       = "ServicePrincipalValid"
+
+	ManagedUpgradeOperatorStatus = "ManagedUpgradeOperatorStatus"
 )
 
 // AllConditionTypes is a operator conditions currently in use, any condition not in this list is not
 // added to the operator.status.conditions list
 func AllConditionTypes() []string {
-	return []string{InternetReachableFromMaster, InternetReachableFromWorker, MachineValid, ServicePrincipalValid}
+	return []string{InternetReachableFromMaster, InternetReachableFromWorker, MachineValid, ServicePrincipalValid, ManagedUpgradeOperatorStatus}
 }
 
 // ClusterChecksTypes represents checks performed on the cluster to verify basic functionality
diff --git a/pkg/operator/controllers/const.go b/pkg/operator/controllers/const.go
index b48287305..58974552a 100644
--- a/pkg/operator/controllers/const.go
+++ b/pkg/operator/controllers/const.go
@@ -23,4 +23,5 @@ const (
 	MachineSetControllerName               = "MachineSet"
 	ImageConfigControllerName              = "ImageConfig"
 	StorageAccountsControllerName          = "StorageAccounts"
+	ManagedUpgradeOperatorControllerName   = "ManagedUpgradeOperator"
 )
diff --git a/pkg/operator/controllers/muo/bindata.go b/pkg/operator/controllers/muo/bindata.go
new file mode 100644
index 000000000..b2e39b399
--- /dev/null
+++ b/pkg/operator/controllers/muo/bindata.go
@@ -0,0 +1,566 @@
+// Code generated for package muo by go-bindata DO NOT EDIT. (@generated)
+// sources:
+// staticresources/cluster_role.yaml
+// staticresources/cluster_rolebinding.yaml
+// staticresources/custom_resource_definition.yaml
+// staticresources/deployment.yaml
+// staticresources/managed_upgrade_role.yaml
+// staticresources/managed_upgrade_rolebinding.yaml
+// staticresources/monitoring_reader_role.yaml
+// staticresources/monitoring_reader_rolebinding.yaml
+// staticresources/namespace.yaml
+// staticresources/prometheus_role.yaml
+// staticresources/prometheus_rolebinding.yaml
+// staticresources/pullsecret_reader_role.yaml
+// staticresources/pullsecret_reader_rolebinding.yaml
+// staticresources/service_account.yaml
+// staticresources/trusted_ca_bundle_configmap.yaml
+package muo
+
+import (
+	"bytes"
+	"compress/gzip"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+)
+
+func bindataRead(data []byte, name string) ([]byte, error) {
+	gz, err := gzip.NewReader(bytes.NewBuffer(data))
+	if err != nil {
+		return nil, fmt.Errorf("Read %q: %v", name, err)
+	}
+
+	var buf bytes.Buffer
+	_, err = io.Copy(&buf, gz)
+	clErr := gz.Close()
+
+	if err != nil {
+		return nil, fmt.Errorf("Read %q: %v", name, err)
+	}
+	if clErr != nil {
+		return nil, err
+	}
+
+	return buf.Bytes(), nil
+}
+
+type asset struct {
+	bytes []byte
+	info  os.FileInfo
+}
+
+type bindataFileInfo struct {
+	name    string
+	size    int64
+	mode    os.FileMode
+	modTime time.Time
+}
+
+// Name return file name
+func (fi bindataFileInfo) Name() string {
+	return fi.name
+}
+
+// Size return file size
+func (fi bindataFileInfo) Size() int64 {
+	return fi.size
+}
+
+// Mode return file mode
+func (fi bindataFileInfo) Mode() os.FileMode {
+	return fi.mode
+}
+
+// Mode return file modify time
+func (fi bindataFileInfo) ModTime() time.Time {
+	return fi.modTime
+}
+
+// IsDir return file whether a directory
+func (fi bindataFileInfo) IsDir() bool {
+	return fi.mode&os.ModeDir != 0
+}
+
+// Sys return file is sys mode
+func (fi bindataFileInfo) Sys() interface{} {
+	return nil
+}
+
+var _cluster_roleYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x54\xc1\x8e\xd4\x30\x0c\xbd\xf7\x2b\xa2\xbd\xac\x84\x34\x1d\x71\x43\xbd\x72\xe0\x8e\x10\x77\x4f\xe2\xe9\x58\x9b\xc6\x91\xed\x14\xb1\x5f\x8f\xa6\xc9\xae\x0a\x9d\x32\xbb\x48\x9c\xe2\xbe\xda\x7e\x2f\xcf\x6e\x21\xd3\x77\x14\x25\x4e\x83\x93\x13\xf8\x1e\x8a\x5d\x58\xe8\x19\x8c\x38\xf5\x4f\x9f\xb4\x27\x3e\xce\x1f\xbb\x27\x4a\x61\x70\x9f\x63\x51\x43\xf9\xca\x11\xbb\x09\x0d\x02\x18\x0c\x9d\x73\x5e\x70\x29\xf8\x46\x13\xaa\xc1\x94\x07\x97\x4a\x8c\x9d\x73\x09\x26\x1c\xdc\x04\x09\x46\x0c\x87\x92\x47\x81\x80\x07\xce\x28\x60\x2c\x9d\x94\x88\x3a\x74\x07\x07\x99\xbe\x08\x97\xac\xd7\x76\x07\xf7\xf0\xd0\x39\x27\xa8\x5c\xc4\x63\xc3\x32\x07\x7d\x0d\x8e\x67\x4a\x10\xe9\x19\xe5\x8a\xcd\x28\xa7\x96\xb5\x48\xc1\x25\x0c\x18\xb1\x85\x23\xda\x72\x46\xd2\x1a\x64\x30\x7f\x59\xa2\x92\xc3\x4b\xc1\x8f\x05\x7c\x93\x18\x9c\x31\x59\x95\x93\x38\xe0\x1f\x22\x36\x74\x3b\x9d\x9b\x1f\x7d\xf3\xa7\xe7\x8c\x49\x2f\x74\xb6\x9e\x78\xcb\xd9\xb2\x3d\xa7\x33\x8d\xba\x86\x6e\xbc\x3e\xaa\x81\x95\xff\x6f\x4e\xa5\xbb\xa3\xdc\xd7\xbd\x99\xeb\xaa\xe9\x1a\x7b\x59\x85\x7b\x0e\xbe\x47\xd3\x04\xfe\x42\x09\xef\x88\x6a\x59\xfa\xdb\x03\xda\xfb\x2d\xdb\xe3\xaf\xd6\x14\xa9\xdf\xd2\x9b\xc4\xd4\x92\xcc\x1c\xff\x71\xa3\x5e\xed\xec\x3d\x0b\xf2\xf5\x98\xb6\x6c\x5a\x4e\xea\x85\xb2\xb5\x69\xac\x88\x1e\x3f\x3c\xde\xb8\x10\x27\x32\x16\x4a\xe3\xdf\xdb\xa2\xcc\xe4\xb1\x65\xef\x1b\xb9\x9a\xe0\xce\x9c\x37\x0a\x32\x47\xf2\x3f\x6f\xfe\x14\x02\xa9\x94\xe5\x2a\xa7\x12\xc6\xcd\x00\x77\x9c\xfb\x15\x00\x00\xff\xff\xa3\xcc\x1c\xe4\xfa\x04\x00\x00")
+
+func cluster_roleYamlBytes() ([]byte, error) {
+	return bindataRead(
+		_cluster_roleYaml,
+		"cluster_role.yaml",
+	)
+}
+
+func cluster_roleYaml() (*asset, error) {
+	bytes, err := cluster_roleYamlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "cluster_role.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+	a := &asset{bytes: bytes, info: info}
+	return a, nil
+}
+
+var _cluster_rolebindingYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x90\xbd\x6e\xc3\x30\x0c\x84\x77\x3d\x85\x5e\x40\x2e\xba\x15\xda\xda\x0e\xdd\x5d\x20\x3b\x2d\xd1\x36\x63\x9b\x14\x28\xca\x43\x9e\x3e\x08\x3c\x06\xf9\xd9\x3f\xdc\x77\x77\x0b\x71\x8e\xfe\x77\x6d\xd5\x50\x7b\x59\xf1\x87\x38\x13\x4f\x0e\x0a\x9d\x50\x2b\x09\x47\xaf\x03\xa4\x0e\x9a\xcd\xa2\x74\x01\x23\xe1\x6e\xf9\xaa\x1d\xc9\xc7\xfe\xe9\x36\x34\xc8\x60\x10\x9d\xf7\x0c\x1b\x46\xbf\x01\xc3\x84\x39\xb4\x32\x29\x64\x0c\x52\x50\xc1\x44\x5d\x6d\xc3\x19\x93\xd5\xe8\x82\x3f\xc4\xff\xa8\x3b\x25\xfc\x4e\x49\x1a\xdb\xeb\x84\x03\xa8\x05\x12\x46\x2f\x05\xb9\xce\x34\x5a\x78\xc8\xab\xac\xd8\xe3\x78\xeb\x76\xb7\xf4\x1d\x1b\x14\xfa\x53\x69\xe5\xc9\x07\xee\x1a\x00\x00\xff\xff\xc4\x6f\x81\xf4\x43\x01\x00\x00")
+
+func cluster_rolebindingYamlBytes() ([]byte, error) {
+	return bindataRead(
+		_cluster_rolebindingYaml,
+		"cluster_rolebinding.yaml",
+	)
+}
+
+func cluster_rolebindingYaml() (*asset, error) {
+	bytes, err := cluster_rolebindingYamlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "cluster_rolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+	a := &asset{bytes: bytes, info: info}
+	return a, nil
+}
+
+var _custom_resource_definitionYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x59\x4b\x8f\xdb\x38\x12\xbe\xfb\x57\x14\x66\x0f\xd9\x05\x62\x75\x67\x73\x59\xf8\x96\x71\x6f\x30\xc1\xce\x24\x8d\x74\x4f\x2e\x83\xc1\xa2\x2c\x96\x2c\x4e\x53\xa4\x96\x0f\x77\x3c\x8b\xfd\xef\x8b\xa2\x28\xcb\x92\xa9\xb6\x1b\xc9\x3c\x78\x13\x1f\xf5\xae\xaf\x8a\xd4\x62\xb9\x5c\x2e\xb0\x95\x9f\xc8\x3a\x69\xf4\x0a\xb0\x95\xf4\xd9\x93\xe6\x2f\x57\x3c\xfc\xc3\x15\xd2\x5c\xed\x5e\x2d\x1e\xa4\x16\x2b\x58\x07\xe7\x4d\xf3\x91\x9c\x09\xb6\xa4\x1b\xaa\xa4\x96\x5e\x1a\xbd\x68\xc8\xa3\x40\x8f\xab\x05\x00\x6a\x6d\x3c\xf2\xb4\xe3\x4f\x80\xd2\x68\x6f\x8d\x52\x64\x97\x5b\xd2\xc5\x43\xd8\xd0\x26\x48\x25\xc8\x46\xe2\x3d\xeb\xdd\x75\xf1\xba\xb8\x5e\x00\x94\x96\xe2\xf1\x7b\xd9\x90\xf3\xd8\xb4\x2b\xd0\x41\xa9\x05\x80\xc6\x86\x56\x10\xda\xad\x45\x41\xa5\xd1\x95\xdc\xba\x22\x7d\x16\x0d\x6a\xdc\x92\x28\x4c\x4b\xda\xd5\xb2\xf2\x85\x34\x0b\xd7\x52\xc9\x52\x6c\xad\x09\xed\xe1\x68\x7e\x6f\x47\x3f\x09\xdd\x29\xfc\x63\xb7\x7f\x1d\x59\xc5\x79\x25\x9d\xff\xd7\xe9\xda\xf7\xd2\xf9\xb8\xde\xaa\x60\x51\x4d\x85\x8c\x4b\xae\x36\xd6\xbf\x1f\x58\x2c\xfb\x4d\xdd\xaa\xd4\xdb\xa0\xd0\x4e\x8e\x2e\x00\x5c\x69\x5a\x5a\x41\x3c\xd9\x62\x49\x62\x01\x90\xac\x16\x29\x2d\x01\x85\x88\x7e\x40\x75\x6b\xa5\xf6\x64\xd7\x46\x85\x46\x1f\xf8\xfc\xe2\x8c\xbe\x45\x5f\xaf\xa0\x60\x83\x14\x82\x9c\xb4\x24\x8a\x44\x25\xee\xea\xad\x9b\xd6\xfe\x3d\x5e\xf3\x7b\x16\xc1\x79\x2b\xf5\x36\x43\xd4\xa3\x0f\xae\xa8\xa5\xf3\xc6\xee\x7f\xba\xfe\xb9\x68\x6b\x74\x34\xa2\x7b\x3c\xf3\x5c\x6a\xa5\xd1\x9d\x82\x8e\xbf\xf8\xf4\x88\xb4\xf3\xb8\xfd\x3a\xa4\xbb\xf5\x29\xf1\x61\xea\xcb\xa8\x5b\x42\x37\xb1\xf6\x68\xea\xcb\xa8\x37\xe4\xdc\x60\x87\x8e\xfc\x78\xee\x84\x7e\xb7\x69\xf7\x0a\x55\x5b\xe3\xab\x2e\x0c\xcb\x9a\x1a\x5c\xa5\x13\x9c\x1f\x6f\x6e\xdf\x7d\x7a\x7d\x37\x9a\x06\x8e\x92\xd2\xca\xd6\xc7\xcc\x1d\x65\x02\x48\x07\xbe\x26\xe8\x4e\x40\x65\x6c\xfc\x1c\xe7\x03\xbc\xb9\x7d\x77\xa0\xd5\x5a\xd3\x92\xf5\xb2\xcf\x8b\x6e\x1c\xa1\xd2\xd1\xec\x84\xf3\x0b\x16\xae\xdb\x05\x82\xe1\x88\x3a\xe6\x29\x78\x49\x24\x7d\xc0\x54\xe0\x6b\xe9\xc0\x52\x6b\xc9\x91\xee\x00\x6a\x44\x18\x78\x13\x6a\x30\x9b\x5f\xa8\xf4\x05\xdc\x91\x65\x32\x9c\xb6\x41\x09\x46\xb1\x1d\x59\x0f\x96\x4a\xb3\xd5\xf2\xd7\x03\x6d\x07\xde\x44\xa6\x0a\x3d\x25\x14\x18\x46\x4c\x47\x8d\x0a\x76\xa8\x02\xbd\x04\xd4\x02\x1a\xdc\x83\x25\xe6\x02\x41\x1f\xd1\x8b\x5b\x5c\x01\x3f\x18\x4b\x20\x75\x65\x56\x50\x7b\xdf\xba\xd5\xd5\xd5\x56\xfa\x1e\x8d\x4b\xd3\x34\x41\x4b\xbf\xbf\x8a\xc0\x2a\x37\xc1\x1b\xeb\xae\x04\xed\x48\x5d\x39\xb9\x5d\xa2\x2d\x6b\xe9\xa9\xf4\xc1\xd2\x15\xb6\x72\x19\x45\xd7\x31\x50\x8a\x46\xfc\xc5\x26\xfc\x76\x2f\x46\xb2\x9e\x44\x47\x37\x22\x10\x3e\xe1\x01\x06\x43\xf6\x39\xa6\xa3\x9d\x16\x83\xa1\x79\x8a\xad\xf3\xf1\x9f\x77\xf7\xd0\xb3\x8e\xce\x98\x5a\x3f\xda\x7d\x38\xe8\x06\x17\xb0\xc1\xa4\xae\xc8\x76\x4e\xac\xac\x69\x22\x4d\xd2\xa2\x35\x52\xfb\xf8\x51\x2a\x49\x7a\x6a\x7e\x17\x36\x8d\xf4\xec\xf7\xff\x04\x72\x9e\x7d\x55\xc0\x3a\x96\x28\xd8\x70\x54\x0a\xf4\x24\x0a\x78\xa7\x61\x8d\x0d\xa9\x35\x3a\xfa\xcd\x1d\xc0\x96\x76\x4b\x36\xec\x65\x2e\x38\xae\xae\xd3\xcd\x9d\xd5\x8e\x16\xfa\x8a\x37\xe3\xaf\x51\xae\xde\xb5\x54\x8e\xf2\x26\x81\x7f\x04\x3d\xe2\x7c\x38\x2d\x80\xc3\xe0\x50\x4e\x69\x0d\x8f\x52\x0b\xf3\x18\xa7\x2a\x4b\xf4\x6b\x3f\x33\x3a\x92\x4f\x74\x1e\xb7\x37\xdf\xbe\x35\xdc\x52\x58\x94\xb1\xee\x9b\xe0\xa7\x7b\x26\x8a\xdc\xd7\x04\x0d\x7e\x96\x4d\x68\x60\x6b\xb1\x24\x68\xc9\x4a\x23\xf8\x43\x7b\x12\x9c\x95\x08\xda\xb0\x70\xb5\x71\x04\x82\x69\x9f\xd0\x04\x8e\xdd\x8d\x32\xe5\x03\x09\xd8\xec\x01\xe1\xd6\x08\xb8\x91\xce\x86\xc8\x08\xbe\x0d\x62\x4b\xfe\x25\x6c\xa8\xe2\xa8\xf0\x35\xfa\x8e\x14\x1f\xac\x58\x68\x51\x64\xa8\xfe\x40\xe8\x02\x9b\x52\x6a\x68\xa4\x0e\x9e\x13\x3b\xca\x2c\x75\x94\x19\xcb\x92\x5a\xdf\x67\x3d\x13\xbb\x8e\xe6\x93\x3a\x97\x1c\x3c\x4a\x74\x04\xd2\xc3\xa3\x54\x0a\xbc\x95\xdb\x2d\xd9\x4e\x82\x24\x10\x56\x9e\x3a\xb4\xa5\xcf\x2d\x95\x9e\xc4\x7b\x23\x06\x9b\x66\x48\x2a\x6c\x5d\x4e\xfe\xca\xd8\x06\xfd\x8a\xd1\xeb\xf5\xdf\x4f\x56\x93\x0a\x2b\xb8\x3e\x59\xea\x82\x92\x41\x6f\x4b\x76\x31\x95\xbf\xc5\x52\xfa\xfd\x47\x72\x64\x77\x11\x80\xcf\xf8\x98\xe3\x53\x56\x7b\x90\x15\xb8\x12\x15\x43\x49\x68\x19\xa3\xe9\xb3\xb7\xc9\xb9\x5c\x60\x7a\xca\x8c\x2f\x3d\xe9\x8c\xb6\xc9\x85\x7d\xcc\x3a\x8f\xd6\x3b\xb6\xbc\x26\x12\xb1\xad\xca\x29\xb3\x31\x46\x11\x4e\xe9\xa5\x4c\xb9\x50\x81\xe3\xdc\xfa\xd0\x92\xbe\xe3\x9e\x13\x2c\x29\x1a\x9a\xa2\x61\xcc\xe7\x49\x34\x63\x8d\x5a\x93\xca\x2d\x4d\xb8\xaf\xbb\x9d\x10\x1c\x89\x68\xa7\xa4\x79\x2e\xb8\x66\xc1\xa7\x1f\xb2\xc1\x2d\x5d\xc0\xf4\x1d\xef\x03\x4b\x15\x59\xd2\x25\x7d\x1d\xe6\xbb\x5c\x27\x90\x65\xdf\x77\x03\xa6\x82\x43\x73\x3f\x6b\xe8\xb3\x9c\x67\x30\xf6\xb0\x74\x06\xa2\xf6\x2d\x97\x11\x21\x4b\xee\x0c\x62\x10\xac\x55\x70\x9e\x6c\x82\x55\x0b\xb2\x69\x15\x35\x7d\x3f\x02\xde\x64\x64\x0c\x8e\x18\xcc\x5a\xb2\x9c\x96\x1c\xfe\x7d\x04\xc7\xae\x86\x2b\x5f\x24\x7a\x72\x94\x74\x68\x4e\x45\x5c\xc2\x87\xbb\x9b\xcc\xec\x9b\x8f\x1f\x66\xf4\xcf\x1a\x27\xc9\xf0\xe6\x1c\x4e\x1f\xa7\xc0\x28\xf3\xc0\xe7\x30\x69\x96\x23\x57\xf0\xd3\x8c\x5b\x66\x4b\xc7\x64\x4b\xca\xbc\xc9\xec\xd1\x35\xa2\x9f\x3a\xe8\x74\x51\xa1\x8d\xfd\xf8\xc5\xa5\x36\xee\x1e\x15\x5b\xb3\x61\xb0\xba\xac\xda\xce\x43\x42\xba\x0f\x9c\xad\x96\xb1\xf9\x2d\x8d\x15\xfd\x09\x66\x48\x3b\xb2\xfb\xd1\x35\xf4\x78\x48\x4f\x4d\x16\x82\x72\x6a\x7e\x97\xa8\x9e\x32\x99\x23\x7f\x0e\xe8\x00\x4a\xc3\xe9\xe1\x89\xdd\x9a\xdf\x31\x94\x2a\x6e\xe4\x96\xd9\x90\xea\xc6\x19\x84\x89\xcf\x14\xe9\x36\x35\xc7\x6a\x0c\xae\x87\xed\xa9\xfb\x25\xcf\xca\x1e\xa6\x41\x6a\xe7\x51\x97\xe4\x72\xed\x01\x3c\x65\xde\x0c\xb7\x21\x32\x12\xf5\xda\x04\x47\x0e\x2a\x49\x4a\xb8\xd4\x95\xc4\x03\x9b\x39\x03\x44\x23\xd4\x34\x44\x1b\xea\x9e\x2c\x48\x5d\xaa\x20\xb8\xbe\xf6\xbd\xe6\x9c\xd0\xe7\x9d\xd6\x5b\xf3\xbc\xeb\x32\x7a\xae\xd3\xb1\x08\x0e\x87\x5b\xdb\xc1\x35\xf3\x52\xc1\x33\x42\x21\xd9\xe2\x5c\x40\x74\x43\xa1\xf3\xb7\xd6\x6c\x9e\xab\xc9\xf7\xe8\x3a\x88\xeb\x20\xfa\xe0\xb8\x47\x74\x50\xd6\xc4\xfd\xe6\x1f\xa4\xcd\xbd\x45\xed\x64\xff\xc2\xf6\x15\x54\xf2\x1d\xc1\xee\x5e\x66\xf4\xd3\x82\xf6\xc0\x19\xfb\x73\x6d\x7c\x4d\xf6\x0f\xb0\x43\x7a\x18\x79\x86\xf2\xdf\x85\x06\x35\x58\x42\x81\x1b\x45\x3d\x81\xbe\xbc\x73\xea\x08\xf2\x28\x55\xbe\xbf\x19\x06\x6e\x4c\xf0\xd1\x0f\xbd\xdd\xce\xc6\xf5\x85\x3a\x75\x6f\x49\xcf\x50\xe9\xaf\x1b\x2b\xa9\xfa\x5b\x3a\x78\x78\xaa\x39\x38\xf6\x85\xfb\x4d\xe4\x8c\xd5\xff\x99\xa1\x77\x77\xe8\x18\xfe\x14\xa0\x90\xab\xfd\xe7\xe4\xe7\x98\x37\xd5\x20\xf6\x4b\x4e\x15\x9e\xb9\xb7\x81\x5e\xc2\x5b\x54\x8e\x5e\x9e\x09\x9e\x1f\xf5\x83\x36\x8f\xb9\xbb\xcd\xb3\x55\xc8\xf7\xaf\xb3\x0a\xc4\x5e\x76\x28\xe6\x83\x1a\x5f\x2e\x4b\xbe\xb3\x3b\x1e\xcb\xf1\x2b\x6c\x6e\xc3\x49\x37\x77\x2a\x46\xb6\x8d\x1f\x6f\x41\x6b\x71\x9f\xdd\x11\x9f\xaf\x2f\x6a\x0c\x62\xab\xd5\xd7\xe2\x43\xc5\xed\x9c\x7f\xdc\x04\xb7\xd6\x94\xe4\xe6\x74\xca\xf7\xee\xbd\xb6\xef\xe9\x71\x76\xed\x96\xb4\x98\x37\xf7\x32\xd5\xfc\xf3\x3b\x32\x17\xe2\x7e\xc3\x5b\x94\x6a\x76\xf9\xac\xcf\xcf\xe6\xff\x57\x6b\xe9\x9e\xbc\x36\x4e\xdc\x76\x93\xae\xe7\xbb\xe1\x02\x19\x61\x66\xbe\x7b\xbd\x48\x84\x47\x63\x1f\xc8\xae\x7f\xcf\x46\xb6\x63\x79\xf7\x3b\x59\xf9\xa9\xec\x5d\x8e\xfe\xfa\x9c\x12\x9d\xc9\xc8\xb9\x5c\xcc\x1e\x3a\x99\xec\x2e\x55\x2b\xf0\x36\xa4\x3f\x6b\xde\x58\xae\xf5\x47\x33\x61\x73\x78\x04\xef\xe5\x4e\x90\x0e\xff\xfd\xdf\x62\x40\xf7\xfe\x91\xee\xfd\xf4\xc7\xe0\x37\xdf\xc4\x8f\xfe\x6f\x5f\xfc\x3c\xba\x40\xc0\x4f\x3f\x2f\x3a\xc6\x24\x3e\xf5\xbf\xea\x78\xf2\xff\x01\x00\x00\xff\xff\x31\x0f\xfe\x9b\x71\x1d\x00\x00")
+
+func custom_resource_definitionYamlBytes() ([]byte, error) {
+	return bindataRead(
+		_custom_resource_definitionYaml,
+		"custom_resource_definition.yaml",
+	)
+}
+
+func custom_resource_definitionYaml() (*asset, error) {
+	bytes, err := custom_resource_definitionYamlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "custom_resource_definition.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+	a := &asset{bytes: bytes, info: info}
+	return a, nil
+}
+
+var _deploymentYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x55\x4d\x6f\xe3\x36\x10\xbd\xfb\x57\x0c\xd2\xb3\xa2\x24\x68\x2f\xbc\x19\x1b\xb7\x5d\xa0\x8e\x0d\xc7\x68\x8f\xc5\x98\x1c\x49\x84\xf9\x55\x72\xe8\x8d\xfe\x7d\x21\xc9\x71\x64\x39\xc6\x1a\x05\x8a\xf5\xc9\x98\x37\x7c\xf3\xf8\x38\x33\xc2\xa0\xff\xa4\x98\xb4\x77\x02\x30\x84\x54\x1e\x1e\x67\x7b\xed\x94\x80\x67\x0a\xc6\xb7\x96\x1c\xcf\x2c\x31\x2a\x64\x14\x33\x00\x87\x96\x04\x58\x74\x58\x93\x2a\x72\xa8\x23\x2a\x2a\x7c\xa0\x88\xec\xe3\x31\x21\x05\x94\x24\xc0\x07\x72\xa9\xd1\x15\x17\x57\xf3\x53\x20\xd9\xd1\x46\x0a\x46\x4b\x4c\x02\x1e\x67\x00\x89\x0c\x49\xf6\xb1\x43\x00\x2c\xb2\x6c\xfe\xc0\x1d\x99\x34\x04\x6e\x50\xc1\x64\x83\x41\xa6\x23\xc3\xe8\x02\xdd\xcf\x9c\x91\xdd\x40\x07\xf0\x2e\xb4\xff\x4f\xf1\xa0\x25\xcd\xa5\xf4\xd9\xf1\xcb\xf7\x0f\x03\x60\x55\x69\xa7\xb9\x1d\xd5\xf4\x8a\xe6\x17\xd1\xce\x89\x7f\xb2\x8e\xa4\x9e\x73\xd4\xae\x7e\x95\x0d\xa9\x6c\xb4\xab\xbf\xd6\xce\x9f\xc2\x8b\x37\x92\x99\xbb\x57\x1b\x9d\x1c\x38\x5f\x8f\xde\x6d\x29\xda\x74\x0e\x17\x83\x95\x8b\xb7\x10\x29\x75\x6f\x3e\xc1\xbb\x8c\x3d\xb5\xa2\xe7\x29\xa2\x37\x74\xbf\xcf\x3b\x8a\x8e\x98\xd2\xbd\xf6\xa5\xc5\xc4\x14\x27\x67\x00\xde\xef\x2a\xe0\xab\xbb\x00\x0f\x68\x32\x5d\x14\xea\x4a\xdd\xdd\x1d\x83\xec\x4d\x47\x30\x16\x54\x00\x55\x15\x49\x16\xf0\xe2\x8f\x1e\xd0\x89\xe2\x76\x8d\x63\x9a\xc1\xb3\xff\xc2\x72\xf0\x26\x5b\x1a\x69\x93\xde\x55\xba\x5e\x62\x18\x5f\x4b\x51\x85\xd9\xf0\xd2\x2b\x12\xf0\xf3\xd3\xc3\x08\xd2\x4c\x97\x6f\xd1\xd7\x97\x58\xec\xb2\x53\x86\xee\x65\xe4\x89\x47\x01\xb9\x11\xc0\x26\x15\x1f\x59\x81\xec\x28\x6b\xe8\x5b\x8e\x39\x31\xa9\x8f\xac\xd9\x6d\xb8\xf4\x8e\x51\x3b\x8a\x23\x69\xc5\x2d\xb3\x30\xfc\x7e\x82\x0d\x05\x83\x92\x80\x1b\x9d\xe0\x9b\xe6\x06\xb8\x21\xd8\x65\x6d\x18\xb4\xc5\x9a\x7a\xb2\xb3\x23\xdb\x21\xd5\x18\xa8\x89\xfb\xa9\x47\x49\x0a\xbc\x03\xd5\xef\x1b\xd8\xb5\x50\x36\x28\xf7\x65\x4d\xae\x2b\xf9\x51\xfa\x64\x41\x3b\x76\xb6\x2b\x23\xe0\xb7\xc5\xcb\x62\x33\xdf\x2e\x9e\x47\x90\xf4\xd6\xa2\x53\x63\xdb\x8b\x5b\xee\xd5\x53\xae\xb3\x31\x6b\x6f\xb4\x6c\x05\xcc\xcd\x37\x6c\xd3\xd9\x88\x26\x9f\xa3\x9c\x76\x75\x37\xb9\x94\xf8\xa2\xd7\x65\xc8\x02\x9e\x1e\xec\x24\x6c\xc9\xfa\xd8\x0a\x78\x7c\x78\x58\xea\x33\xcc\x68\xab\xaf\xd0\xfc\x72\x95\xe6\x69\x42\x43\xee\x30\xed\xb8\xe1\x69\xff\x9a\x6f\xbf\xfc\xfe\xf7\xcb\x7c\xb9\x78\x5d\xcf\xbf\x2c\x26\x74\xfd\xb8\xfe\x1a\xbd\xbd\x9c\xd8\x4a\x93\x51\x1b\xaa\x2e\x91\x23\xb6\xee\x1b\xf6\x7d\xd9\xde\x9f\xbe\x04\x9f\xca\x58\xaf\x9e\x7b\x11\xff\x6f\xfd\x4f\x4b\xaf\xd6\x5d\xb3\xac\x36\x3f\xde\x84\x33\x25\x9f\x89\x10\x70\x77\xad\x65\xef\x46\xf9\xc3\x86\x5a\x76\x9f\xa3\x34\x69\xf8\x2e\x36\x68\x2a\x89\x65\x19\xf6\xba\x94\x58\xf4\x3b\xa1\xa4\x37\x8e\x28\x99\x54\x79\xbe\x57\xbe\xbf\x59\xa0\xef\x77\x54\x2b\x67\xda\x3e\x8f\x66\xff\x06\x00\x00\xff\xff\xb7\xcc\xef\x39\x4b\x08\x00\x00")
+
+func deploymentYamlBytes() ([]byte, error) {
+	return bindataRead(
+		_deploymentYaml,
+		"deployment.yaml",
+	)
+}
+
+func deploymentYaml() (*asset, error) {
+	bytes, err := deploymentYamlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "deployment.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+	a := &asset{bytes: bytes, info: info}
+	return a, nil
+}
+
+var _managed_upgrade_roleYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x84\x8f\xb1\x6e\x03\x21\x10\x44\xfb\xfb\x0a\xe4\x1e\x5b\xe9\x22\x7e\x20\x7d\x8a\xf4\x6b\x18\x9f\x57\xe6\xd8\xd5\x02\x17\xc5\x5f\x1f\xf9\x7c\x27\xd9\x89\xa2\x54\xbc\x19\x8d\xe0\x41\xca\x1f\xb0\xca\x52\x82\xb3\x23\xc5\x3d\xf5\x76\x16\xe3\x2b\x35\x96\xb2\xbf\xbc\xd6\x3d\xcb\x61\x7e\x19\x2e\x5c\x52\x70\xef\x92\x31\x4c\x68\x94\xa8\x51\x18\x9c\x2b\x34\x21\xb8\x89\x0a\x8d\x48\xbe\xeb\x68\x94\xe0\x45\x61\xd4\xc4\xd6\x41\x55\x8a\x08\x4e\x14\xa5\x9e\xf9\xd4\xfc\x9f\x7b\xeb\x19\x35\x0c\xde\x91\xf2\x9b\x49\xd7\x7a\x7b\xc4\xbb\xdd\x6e\x70\xce\x50\xa5\x5b\xc4\xda\x45\x29\x27\x1e\x27\xd2\xba\xc4\x8a\x68\x68\x1b\xdb\xcc\x11\xcf\xe1\x70\xe2\x42\x99\xaf\xb0\x5b\x3f\xc3\x8e\xdb\x45\x06\x6a\x58\x30\x21\x63\xc5\x11\x6d\x39\x33\xd7\x3b\x74\x4d\xdb\xec\x93\x5a\x3c\xff\xb6\x24\x5d\x5c\x7e\x78\x26\x68\x96\xaf\x09\x65\x95\x7b\xc8\xcf\x4a\xde\x19\x34\x73\xa4\x7a\xff\xc7\x83\xe2\xff\x32\xdf\x01\x00\x00\xff\xff\x4e\x92\x80\x9e\xc9\x01\x00\x00")
+
+func managed_upgrade_roleYamlBytes() ([]byte, error) {
+	return bindataRead(
+		_managed_upgrade_roleYaml,
+		"managed_upgrade_role.yaml",
+	)
+}
+
+func managed_upgrade_roleYaml() (*asset, error) {
+	bytes, err := managed_upgrade_roleYamlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "managed_upgrade_role.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+	a := &asset{bytes: bytes, info: info}
+	return a, nil
+}
+
+var _managed_upgrade_rolebindingYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x8e\x31\xce\xc2\x30\x0c\x46\xf7\x9c\x22\x17\x48\x7f\xfd\x1b\xca\x06\x47\x28\x12\xbb\x9b\xb8\xad\x69\x6b\x47\x8e\xd3\x81\xd3\x23\xa4\x4a\x4c\x88\x85\xfd\x7d\xdf\x7b\x50\xe8\x86\x5a\x49\x38\x7a\x1d\x20\x75\xd0\x6c\x16\xa5\x07\x18\x09\x77\xcb\xa9\x76\x24\x7f\xfb\xbf\x5b\x88\x73\xf4\xbd\xac\x78\x21\xce\xc4\x93\xdb\xd0\x20\x83\x41\x74\xde\x33\x6c\x18\xfd\x06\x0c\x13\xe6\xd0\xca\xa4\x90\x31\x48\x41\x05\x13\x3d\x80\x5a\x20\x61\xf4\x52\x90\xeb\x4c\xa3\x85\x8f\xbc\xca\x8a\x3d\x8e\xaf\xe7\xb7\xf7\xbb\xa6\xb6\xe1\x8e\xc9\x6a\x74\xe1\xd8\x5d\x51\x77\x4a\x78\x4e\x49\x1a\xdb\xef\x43\x9f\x01\x00\x00\xff\xff\x45\xec\x1c\xea\x3f\x01\x00\x00")
+
+func managed_upgrade_rolebindingYamlBytes() ([]byte, error) {
+	return bindataRead(
+		_managed_upgrade_rolebindingYaml,
+		"managed_upgrade_rolebinding.yaml",
+	)
+}
+
+func managed_upgrade_rolebindingYaml() (*asset, error) {
+	bytes, err := managed_upgrade_rolebindingYamlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "managed_upgrade_rolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+	a := &asset{bytes: bytes, info: info}
+	return a, nil
+}
+
+var _monitoring_reader_roleYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x8e\xb1\x6a\x03\x41\x0c\x44\xfb\xfd\x0a\xe1\xfe\x36\xa4\x0b\xfb\x03\xe9\x53\xa4\x97\xd7\xf2\x9d\xf0\xad\xb4\x48\xda\x0b\xe4\xeb\x83\x2f\x87\x09\xb8\x49\xa5\xd1\xcc\x30\x3c\xec\xfc\x49\xe6\xac\x52\xc0\xce\x58\x33\x8e\x58\xd4\xf8\x1b\x83\x55\xf2\xed\xcd\x33\xeb\xcb\xf6\x9a\x6e\x2c\x97\x02\x1f\xba\x52\x6a\x14\x78\xc1\xc0\x92\x00\x04\x1b\x15\x68\x43\xa7\xa6\xc2\xa1\xc6\x32\x4f\x46\x78\x21\x3b\x52\xef\x58\xa9\x80\x76\x12\x5f\xf8\x1a\x7f\x8a\xc9\xc6\x4a\x5e\xd2\x04\xd8\xf9\xdd\x74\x74\xbf\x6f\x4e\x70\x3a\x25\x00\x23\xd7\x61\x95\x0e\xaf\xaa\x5c\x79\x6e\xd8\x1d\x60\x37\x9c\x6c\xe3\x4a\x58\xab\x0e\x09\x3f\xbc\x6a\xf4\xd0\x7b\x7e\x7f\x36\xb2\xf3\x31\x33\x53\xec\x77\x65\xff\x15\x5f\x18\x75\x79\x46\x30\x1d\x41\xf9\x41\x9d\x59\x9f\x91\xf6\xce\xff\xf6\x7f\x02\x00\x00\xff\xff\x98\x29\x94\x73\x68\x01\x00\x00")
+
+func monitoring_reader_roleYamlBytes() ([]byte, error) {
+	return bindataRead(
+		_monitoring_reader_roleYaml,
+		"monitoring_reader_role.yaml",
+	)
+}
+
+func monitoring_reader_roleYaml() (*asset, error) {
+	bytes, err := monitoring_reader_roleYamlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "monitoring_reader_role.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+	a := &asset{bytes: bytes, info: info}
+	return a, nil
+}
+
+var _monitoring_reader_rolebindingYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x84\x8e\x31\x4e\x83\x31\x0c\x46\xf7\x9c\x22\x17\xc8\x8f\xd8\x50\x36\x38\x42\x91\xd8\xdd\xe4\x6b\x6a\xda\xd8\x91\xe3\x74\xe0\xf4\x08\xa9\x52\x59\x7e\x75\xf6\x7b\xcf\x1f\x0d\xfe\x82\x4d\x56\xc9\xd1\x8e\x54\x36\x5a\x7e\x56\xe3\x1f\x72\x56\xd9\x2e\x6f\x73\x63\x7d\xb9\xbd\x86\x0b\x4b\xcd\xf1\xa0\x57\x7c\xb0\x54\x96\x16\x3a\x9c\x2a\x39\xe5\x10\xa3\x50\x47\x8e\x7d\x69\xea\x2a\xec\x6a\x2c\x2d\x19\xa8\xc2\xee\xd7\x39\xa8\x20\x47\x1d\x90\x79\xe6\x93\xff\x03\x83\xe9\x15\x07\x9c\xfe\x42\x8f\x37\x4f\xaa\x73\x1d\xbf\x51\x7c\xe6\x90\xee\xd2\x27\xec\xc6\x05\xef\xa5\xe8\x12\x7f\xe8\x24\xd4\x50\xd3\x1a\xcd\xa8\x22\xe9\x80\x91\xeb\xfe\xae\x3d\xfe\x37\x00\x00\xff\xff\xd9\xce\xa4\x95\x2b\x01\x00\x00")
+
+func monitoring_reader_rolebindingYamlBytes() ([]byte, error) {
+	return bindataRead(
+		_monitoring_reader_rolebindingYaml,
+		"monitoring_reader_rolebinding.yaml",
+	)
+}
+
+func monitoring_reader_rolebindingYaml() (*asset, error) {
+	bytes, err := monitoring_reader_rolebindingYamlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "monitoring_reader_rolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+	a := &asset{bytes: bytes, info: info}
+	return a, nil
+}
+
+var _namespaceYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x44\xcb\x31\x92\x42\x21\x0c\x06\xe0\x9e\x53\x64\x5e\xcf\xee\xd8\x72\x08\x4b\xfb\xf8\xf8\xc5\x8c\x90\x30\x21\x78\x7e\xc7\xca\x03\x7c\x2f\xd1\x5a\xe8\xca\x03\x6b\xf2\x89\xc4\x53\x6e\xf0\x25\xa6\x85\xde\x97\x34\x10\x5c\x39\xb8\x24\x22\xe5\x81\x42\x36\xa1\xeb\x29\x8f\xc8\x83\x95\x1b\x6a\xde\xb3\x39\x57\x64\x9b\x70\x0e\xf3\x44\xd4\xf9\x8e\xbe\xbe\x88\x7e\xe0\x4f\xec\xff\xec\x7b\x05\x3c\x0f\x53\x09\x73\xd1\x56\xe8\x08\xdf\x38\xd2\x27\x00\x00\xff\xff\x15\x8c\x3a\x9e\x8a\x00\x00\x00")
+
+func namespaceYamlBytes() ([]byte, error) {
+	return bindataRead(
+		_namespaceYaml,
+		"namespace.yaml",
+	)
+}
+
+func namespaceYaml() (*asset, error) {
+	bytes, err := namespaceYamlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "namespace.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+	a := &asset{bytes: bytes, info: info}
+	return a, nil
+}
+
+var _prometheus_roleYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\x8c\x31\x6e\xc3\x30\x0c\x45\x77\x9d\x82\xc8\x2e\x17\xdd\x02\x5d\xa0\x7b\x87\xee\x8c\xc4\xda\x84\x6d\x91\x20\x29\x17\xe8\xe9\x83\xc4\x9e\xf8\xde\x03\xf1\x51\xf9\x87\xcc\x59\x7a\x01\x7b\x60\x9d\x70\xc4\x22\xc6\xff\x18\x2c\x7d\x5a\xef\x3e\xb1\x7c\x1c\x9f\x69\xe5\xde\x0a\x7c\xcb\x46\x69\xa7\xc0\x86\x81\x25\x01\x74\xdc\xa9\x80\x9a\xec\x14\x0b\x0d\xcf\xeb\xdd\xaf\xec\x8a\x95\x0a\x88\x52\xf7\x85\x7f\x23\xef\xd8\x71\xa6\x96\x87\xce\x86\x8d\xb2\x28\x19\x86\x58\xb2\xb1\x91\x97\x94\x01\x95\xbf\x4c\x86\xfa\x6b\x3a\xc3\xed\x96\x00\x8c\x5c\x86\x55\xba\x9a\x93\x1d\x5c\xc9\xdf\x42\xbd\xa9\x70\x8f\xd3\x54\xda\x0b\x0e\xb2\xc7\xf5\x3c\x53\xbc\xef\xc6\x7e\xc2\x1f\x46\x5d\xd2\x33\x00\x00\xff\xff\xab\xd8\x23\x94\xf5\x00\x00\x00")
+
+func prometheus_roleYamlBytes() ([]byte, error) {
+	return bindataRead(
+		_prometheus_roleYaml,
+		"prometheus_role.yaml",
+	)
+}
+
+func prometheus_roleYaml() (*asset, error) {
+	bytes, err := prometheus_roleYamlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "prometheus_role.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+	a := &asset{bytes: bytes, info: info}
+	return a, nil
+}
+
+var _prometheus_rolebindingYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x94\x8f\x31\x6e\x03\x31\x0c\x04\x7b\xbd\x42\x1f\xd0\x05\xe9\x0c\x75\xc9\x13\x1c\x20\x3d\x2d\xad\xef\x98\xf3\x91\x02\x49\xb9\xc8\xeb\x03\x03\x06\x52\xa5\x48\xbb\x58\xcc\xee\xd0\xe0\x4f\x98\xb3\x4a\xcd\x76\xa1\xb6\xd0\x8c\x4d\x8d\xbf\x29\x58\x65\xd9\x4f\xbe\xb0\xbe\xdc\x5f\xd3\xce\xd2\x6b\x3e\xeb\x0d\xef\x2c\x9d\x65\x4d\x07\x82\x3a\x05\xd5\x94\xb3\xd0\x81\x9a\x87\xe9\x81\xd8\x30\xbd\xec\x27\x7f\xc6\x3e\xa8\xa1\x66\x1d\x10\xdf\xf8\x1a\xe5\x20\xa1\x15\xbd\xcc\xb1\x1a\x75\x14\x1d\x30\x0a\xb5\x64\x7a\xc3\x19\xd7\x07\xef\x77\xed\x2f\xb8\xcf\xcb\x17\x5a\x78\x4d\xe5\xd9\xfe\x80\xdd\xb9\xe1\xad\x35\x9d\x12\xff\x3d\xa5\xc2\xa1\xf6\xf0\xfa\x09\x00\x00\xff\xff\xdf\x2c\x62\x3f\x13\x01\x00\x00")
+
+func prometheus_rolebindingYamlBytes() ([]byte, error) {
+	return bindataRead(
+		_prometheus_rolebindingYaml,
+		"prometheus_rolebinding.yaml",
+	)
+}
+
+func prometheus_rolebindingYaml() (*asset, error) {
+	bytes, err := prometheus_rolebindingYamlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "prometheus_rolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+	a := &asset{bytes: bytes, info: info}
+	return a, nil
+}
+
+var _pullsecret_reader_roleYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\x8c\xb1\x6a\xc5\x30\x0c\x45\x77\x7d\x85\x78\xbb\x53\xba\x15\xff\x40\xf7\x0e\xdd\xf5\x1c\x25\x11\x71\x2c\x23\xc9\x29\xf4\xeb\x4b\x5f\x32\xdd\xcb\x39\x70\xa8\xcb\x37\x9b\x8b\xb6\x8c\xf6\xa4\x32\xd1\x88\x4d\x4d\x7e\x29\x44\xdb\xb4\x7f\xf8\x24\xfa\x76\xbe\xc3\x2e\x6d\xce\xf8\xa5\x95\xe1\xe0\xa0\x99\x82\x32\x20\x36\x3a\x38\xe3\x31\x34\xf5\x51\xab\x73\x31\x8e\x64\x4c\x33\xdb\x6d\xbd\x53\xe1\x8c\xda\xb9\xf9\x26\x4b\xa4\xa2\x6d\x91\x15\x6c\x54\xf6\x0c\x09\xa9\xcb\xa7\xe9\xe8\xfe\xdf\x4b\xf8\x78\x00\xa2\xb1\xeb\xb0\xc2\x37\xbb\xba\x0e\x88\x27\xdb\xf3\x86\x2b\xc7\x6b\xab\xf8\x75\x7e\x28\xca\x06\x7f\x01\x00\x00\xff\xff\xb2\xe9\x84\x3f\xd2\x00\x00\x00")
+
+func pullsecret_reader_roleYamlBytes() ([]byte, error) {
+	return bindataRead(
+		_pullsecret_reader_roleYaml,
+		"pullsecret_reader_role.yaml",
+	)
+}
+
+func pullsecret_reader_roleYaml() (*asset, error) {
+	bytes, err := pullsecret_reader_roleYamlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "pullsecret_reader_role.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+	a := &asset{bytes: bytes, info: info}
+	return a, nil
+}
+
+var _pullsecret_reader_rolebindingYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x84\x8e\x31\x6e\x03\x31\x0c\x04\x7b\xbd\x42\x1f\xd0\x05\xe9\x02\x75\xc9\x13\x1c\x20\x3d\x4d\xed\x9d\x19\xdf\x91\x02\x45\xb9\xc8\xeb\x83\x00\x06\x5c\x19\xa9\x77\x67\x76\xa9\xcb\x17\x7c\x88\x69\xcd\x7e\x26\x5e\x68\xc6\xc5\x5c\x7e\x28\xc4\x74\xb9\xbe\x8d\x45\xec\xe5\xf6\x9a\xae\xa2\xad\xe6\x93\xed\xf8\x10\x6d\xa2\x5b\x3a\x10\xd4\x28\xa8\xa6\x9c\x95\x0e\xd4\x7c\x4c\x2b\x7d\xee\xfb\x00\x3b\xa2\x38\xa8\xc1\xef\xe9\xe8\xc4\xa8\xd9\x3a\x74\x5c\x64\x8d\xc2\xa6\xab\x6c\xc9\x6d\xc7\x09\xeb\x9f\xe4\x31\xf1\x8f\x71\xcc\xf3\x37\x38\x46\x4d\xe5\x0e\x7d\xc2\x6f\xc2\x78\x67\xb6\xa9\xf1\xc0\x49\x69\x43\x2b\xb3\x6f\x4e\x0d\xc5\x3a\x9c\xc2\x9e\x7e\x7a\xda\xff\x0d\x00\x00\xff\xff\x43\xf5\x74\xa0\x27\x01\x00\x00")
+
+func pullsecret_reader_rolebindingYamlBytes() ([]byte, error) {
+	return bindataRead(
+		_pullsecret_reader_rolebindingYaml,
+		"pullsecret_reader_rolebinding.yaml",
+	)
+}
+
+func pullsecret_reader_rolebindingYaml() (*asset, error) {
+	bytes, err := pullsecret_reader_rolebindingYamlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "pullsecret_reader_rolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+	a := &asset{bytes: bytes, info: info}
+	return a, nil
+}
+
+var _service_accountYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x74\xca\xb1\x0d\x02\x31\x0c\x05\xd0\x3e\x53\x78\x81\x14\xb4\xe9\x98\x01\x89\xfe\x2b\xf9\x1c\x11\x8a\x6d\x39\xbe\x9b\x9f\x86\x96\xfe\xc1\xe7\x93\xb1\xa7\x69\x93\xeb\x56\x3e\x53\x47\x93\x07\xe3\x9a\x9d\xf7\xde\xed\xd4\x2c\x8b\x89\x81\x44\x2b\x22\x8a\xc5\x26\x0b\x8a\x83\xa3\x9e\x7e\x04\x06\xab\x39\x03\x69\xf1\x03\xdb\xd1\xd9\xc4\x9c\xba\xdf\xf3\x95\xf5\xaf\xff\x06\x00\x00\xff\xff\xfc\xc6\x66\xb9\x7f\x00\x00\x00")
+
+func service_accountYamlBytes() ([]byte, error) {
+	return bindataRead(
+		_service_accountYaml,
+		"service_account.yaml",
+	)
+}
+
+func service_accountYaml() (*asset, error) {
+	bytes, err := service_accountYamlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "service_account.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+	a := &asset{bytes: bytes, info: info}
+	return a, nil
+}
+
+var _trusted_ca_bundle_configmapYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x44\xcd\xbd\x12\xc2\x20\x10\xc4\xf1\x9e\xa7\xb8\x49\x4f\x1c\x5b\x5a\x6b\x5b\xfb\x0b\x6c\xe2\x69\x38\x18\x38\x7c\x7e\xc7\x8f\xd1\x7e\xff\xbf\xe5\x2a\x17\xb4\x2e\x45\x03\x3d\x8e\xee\x2e\x9a\x02\x9d\x8a\xae\xb2\x9d\xb9\xba\x0c\xe3\xc4\xc6\xc1\x11\x29\x67\xf4\xca\x11\x81\x4a\x85\xf6\xab\xac\xe6\x33\x2b\x6f\x48\x7e\xd4\xad\x71\x82\x2f\x15\x8d\xad\xb4\xef\x3e\x90\xb5\xd1\x0d\xc9\x47\xf6\xcb\xd0\xb4\xc3\x11\xed\xbc\x60\xef\x2f\x93\x28\xbe\xbf\xe6\x9f\x38\x4b\x39\x88\xde\x10\xcd\xff\xd3\x4f\x19\x68\xb2\x36\x30\xb9\x67\x00\x00\x00\xff\xff\x96\x4c\x4f\xe4\xb5\x00\x00\x00")
+
+func trusted_ca_bundle_configmapYamlBytes() ([]byte, error) {
+	return bindataRead(
+		_trusted_ca_bundle_configmapYaml,
+		"trusted_ca_bundle_configmap.yaml",
+	)
+}
+
+func trusted_ca_bundle_configmapYaml() (*asset, error) {
+	bytes, err := trusted_ca_bundle_configmapYamlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "trusted_ca_bundle_configmap.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+	a := &asset{bytes: bytes, info: info}
+	return a, nil
+}
+
+// Asset loads and returns the asset for the given name.
+// It returns an error if the asset could not be found or
+// could not be loaded.
+func Asset(name string) ([]byte, error) {
+	cannonicalName := strings.Replace(name, "\\", "/", -1)
+	if f, ok := _bindata[cannonicalName]; ok {
+		a, err := f()
+		if err != nil {
+			return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
+		}
+		return a.bytes, nil
+	}
+	return nil, fmt.Errorf("Asset %s not found", name)
+}
+
+// MustAsset is like Asset but panics when Asset would return an error.
+// It simplifies safe initialization of global variables.
+func MustAsset(name string) []byte {
+	a, err := Asset(name)
+	if err != nil {
+		panic("asset: Asset(" + name + "): " + err.Error())
+	}
+
+	return a
+}
+
+// AssetInfo loads and returns the asset info for the given name.
+// It returns an error if the asset could not be found or
+// could not be loaded.
+func AssetInfo(name string) (os.FileInfo, error) {
+	cannonicalName := strings.Replace(name, "\\", "/", -1)
+	if f, ok := _bindata[cannonicalName]; ok {
+		a, err := f()
+		if err != nil {
+			return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
+		}
+		return a.info, nil
+	}
+	return nil, fmt.Errorf("AssetInfo %s not found", name)
+}
+
+// AssetNames returns the names of the assets.
+func AssetNames() []string {
+	names := make([]string, 0, len(_bindata))
+	for name := range _bindata {
+		names = append(names, name)
+	}
+	return names
+}
+
+// _bindata is a table, holding each asset generator, mapped to its name.
+var _bindata = map[string]func() (*asset, error){
+	"cluster_role.yaml":                  cluster_roleYaml,
+	"cluster_rolebinding.yaml":           cluster_rolebindingYaml,
+	"custom_resource_definition.yaml":    custom_resource_definitionYaml,
+	"deployment.yaml":                    deploymentYaml,
+	"managed_upgrade_role.yaml":          managed_upgrade_roleYaml,
+	"managed_upgrade_rolebinding.yaml":   managed_upgrade_rolebindingYaml,
+	"monitoring_reader_role.yaml":        monitoring_reader_roleYaml,
+	"monitoring_reader_rolebinding.yaml": monitoring_reader_rolebindingYaml,
+	"namespace.yaml":                     namespaceYaml,
+	"prometheus_role.yaml":               prometheus_roleYaml,
+	"prometheus_rolebinding.yaml":        prometheus_rolebindingYaml,
+	"pullsecret_reader_role.yaml":        pullsecret_reader_roleYaml,
+	"pullsecret_reader_rolebinding.yaml": pullsecret_reader_rolebindingYaml,
+	"service_account.yaml":               service_accountYaml,
+	"trusted_ca_bundle_configmap.yaml":   trusted_ca_bundle_configmapYaml,
+}
+
+// AssetDir returns the file names below a certain
+// directory embedded in the file by go-bindata.
+// For example if you run go-bindata on data/... and data contains the
+// following hierarchy:
+//     data/
+//       foo.txt
+//       img/
+//         a.png
+//         b.png
+// then AssetDir("data") would return []string{"foo.txt", "img"}
+// AssetDir("data/img") would return []string{"a.png", "b.png"}
+// AssetDir("foo.txt") and AssetDir("notexist") would return an error
+// AssetDir("") will return []string{"data"}.
+func AssetDir(name string) ([]string, error) {
+	node := _bintree
+	if len(name) != 0 {
+		cannonicalName := strings.Replace(name, "\\", "/", -1)
+		pathList := strings.Split(cannonicalName, "/")
+		for _, p := range pathList {
+			node = node.Children[p]
+			if node == nil {
+				return nil, fmt.Errorf("Asset %s not found", name)
+			}
+		}
+	}
+	if node.Func != nil {
+		return nil, fmt.Errorf("Asset %s not found", name)
+	}
+	rv := make([]string, 0, len(node.Children))
+	for childName := range node.Children {
+		rv = append(rv, childName)
+	}
+	return rv, nil
+}
+
+type bintree struct {
+	Func     func() (*asset, error)
+	Children map[string]*bintree
+}
+
+var _bintree = &bintree{nil, map[string]*bintree{
+	"cluster_role.yaml":                  {cluster_roleYaml, map[string]*bintree{}},
+	"cluster_rolebinding.yaml":           {cluster_rolebindingYaml, map[string]*bintree{}},
+	"custom_resource_definition.yaml":    {custom_resource_definitionYaml, map[string]*bintree{}},
+	"deployment.yaml":                    {deploymentYaml, map[string]*bintree{}},
+	"managed_upgrade_role.yaml":          {managed_upgrade_roleYaml, map[string]*bintree{}},
+	"managed_upgrade_rolebinding.yaml":   {managed_upgrade_rolebindingYaml, map[string]*bintree{}},
+	"monitoring_reader_role.yaml":        {monitoring_reader_roleYaml, map[string]*bintree{}},
+	"monitoring_reader_rolebinding.yaml": {monitoring_reader_rolebindingYaml, map[string]*bintree{}},
+	"namespace.yaml":                     {namespaceYaml, map[string]*bintree{}},
+	"prometheus_role.yaml":               {prometheus_roleYaml, map[string]*bintree{}},
+	"prometheus_rolebinding.yaml":        {prometheus_rolebindingYaml, map[string]*bintree{}},
+	"pullsecret_reader_role.yaml":        {pullsecret_reader_roleYaml, map[string]*bintree{}},
+	"pullsecret_reader_rolebinding.yaml": {pullsecret_reader_rolebindingYaml, map[string]*bintree{}},
+	"service_account.yaml":               {service_accountYaml, map[string]*bintree{}},
+	"trusted_ca_bundle_configmap.yaml":   {trusted_ca_bundle_configmapYaml, map[string]*bintree{}},
+}}
+
+// RestoreAsset restores an asset under the given directory
+func RestoreAsset(dir, name string) error {
+	data, err := Asset(name)
+	if err != nil {
+		return err
+	}
+	info, err := AssetInfo(name)
+	if err != nil {
+		return err
+	}
+	err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
+	if err != nil {
+		return err
+	}
+	err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
+	if err != nil {
+		return err
+	}
+	err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// RestoreAssets restores an asset under the given directory recursively
+func RestoreAssets(dir, name string) error {
+	children, err := AssetDir(name)
+	// File
+	if err != nil {
+		return RestoreAsset(dir, name)
+	}
+	// Dir
+	for _, child := range children {
+		err = RestoreAssets(dir, filepath.Join(name, child))
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func _filePath(dir, name string) string {
+	cannonicalName := strings.Replace(name, "\\", "/", -1)
+	return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
+}
diff --git a/pkg/operator/controllers/muo/deploy.go b/pkg/operator/controllers/muo/deploy.go
new file mode 100644
index 000000000..3a93e47ad
--- /dev/null
+++ b/pkg/operator/controllers/muo/deploy.go
@@ -0,0 +1,121 @@
+package muo
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"context"
+	"errors"
+	"strings"
+
+	appsv1 "k8s.io/api/apps/v1"
+	kruntime "k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/kubernetes/scheme"
+
+	arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
+	"github.com/Azure/ARO-RP/pkg/util/dynamichelper"
+	"github.com/Azure/ARO-RP/pkg/util/ready"
+)
+
+type Deployer interface {
+	CreateOrUpdate(context.Context, *arov1alpha1.Cluster) error
+	Remove(context.Context) error
+	IsReady(ctx context.Context) (bool, error)
+	Resources(string) ([]kruntime.Object, error)
+}
+
+type deployer struct {
+	kubernetescli kubernetes.Interface
+	dh            dynamichelper.Interface
+}
+
+func newDeployer(kubernetescli kubernetes.Interface, dh dynamichelper.Interface) Deployer {
+	return &deployer{
+		kubernetescli: kubernetescli,
+		dh:            dh,
+	}
+}
+
+func (o *deployer) Resources(pullspec string) ([]kruntime.Object, error) {
+	results := []kruntime.Object{}
+	for _, assetName := range AssetNames() {
+		b, err := Asset(assetName)
+		if err != nil {
+			return nil, err
+		}
+
+		obj, _, err := scheme.Codecs.UniversalDeserializer().Decode(b, nil, nil)
+		if err != nil {
+			return nil, err
+		}
+
+		// set the image for the deployments
+		if d, ok := obj.(*appsv1.Deployment); ok {
+			for i := range d.Spec.Template.Spec.Containers {
+				d.Spec.Template.Spec.Containers[i].Image = pullspec
+			}
+		}
+
+		results = append(results, obj)
+	}
+
+	return results, nil
+}
+
+func (o *deployer) CreateOrUpdate(ctx context.Context, cluster *arov1alpha1.Cluster) error {
+	imagePullspec, ext := cluster.Spec.OperatorFlags[pullSpec]
+	if !ext {
+		return errors.New("missing pullspec")
+	}
+
+	resources, err := o.Resources(imagePullspec)
+	if err != nil {
+		return err
+	}
+
+	err = dynamichelper.SetControllerReferences(resources, cluster)
+	if err != nil {
+		return err
+	}
+
+	err = dynamichelper.Prepare(resources)
+	if err != nil {
+		return err
+	}
+
+	return o.dh.Ensure(ctx, resources...)
+}
+
+func (o *deployer) Remove(ctx context.Context) error {
+	resources, err := o.Resources("")
+	if err != nil {
+		return err
+	}
+
+	var errs []error
+	for _, obj := range resources {
+		// delete any deployments we have
+		if d, ok := obj.(*appsv1.Deployment); ok {
+			err := o.dh.EnsureDeleted(ctx, "Deployment", d.Namespace, d.Name)
+			// Don't error out because then we might delete some resources and not others
+			if err != nil {
+				errs = append(errs, err)
+			}
+		}
+	}
+
+	if len(errs) != 0 {
+		errContent := []string{"error removing MUO:"}
+		for _, err := range errs {
+			errContent = append(errContent, err.Error())
+		}
+		return errors.New(strings.Join(errContent, "\n"))
+	}
+
+	return nil
+}
+
+func (o *deployer) IsReady(ctx context.Context) (bool, error) {
+	return ready.CheckDeploymentIsReady(ctx, o.kubernetescli.AppsV1().Deployments("openshift-managed-upgrade-operator"), "managed-upgrade-operator")()
+}
diff --git a/pkg/operator/controllers/muo/generate.go b/pkg/operator/controllers/muo/generate.go
new file mode 100644
index 000000000..2d682e94c
--- /dev/null
+++ b/pkg/operator/controllers/muo/generate.go
@@ -0,0 +1,12 @@
+package muo
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+// bindata for the above yaml files
+//go:generate go run ../../../../vendor/github.com/go-bindata/go-bindata/go-bindata -nometadata -pkg muo -prefix staticresources/ -o bindata.go staticresources/...
+//go:generate gofmt -s -l -w bindata.go
+
+//go:generate rm -rf ../../mocks/$GOPACKAGE
+//go:generate go run ../../../../vendor/github.com/golang/mock/mockgen -destination=../../mocks/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/operator/controllers/$GOPACKAGE Deployer
+//go:generate go run ../../../../vendor/golang.org/x/tools/cmd/goimports -local=github.com/Azure/ARO-RP -e -w ../../mocks/$GOPACKAGE/$GOPACKAGE.go
diff --git a/pkg/operator/controllers/muo/muo_controller.go b/pkg/operator/controllers/muo/muo_controller.go
new file mode 100644
index 000000000..b8ab2b82a
--- /dev/null
+++ b/pkg/operator/controllers/muo/muo_controller.go
@@ -0,0 +1,119 @@
+package muo
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"context"
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/sirupsen/logrus"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/wait"
+	"k8s.io/client-go/kubernetes"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/builder"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/predicate"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
+	aroclient "github.com/Azure/ARO-RP/pkg/operator/clientset/versioned"
+	"github.com/Azure/ARO-RP/pkg/operator/controllers"
+	"github.com/Azure/ARO-RP/pkg/util/dynamichelper"
+)
+
+const (
+	enabled  string = "rh.srep.muo.enabled"
+	managed  string = "rh.srep.muo.managed"
+	pullSpec string = "rh.srep.muo.deploy.pullspec"
+)
+
+type Reconciler struct {
+	arocli   aroclient.Interface
+	deployer Deployer
+
+	readinessPollTime time.Duration
+	readinessTimeout  time.Duration
+}
+
+func NewReconciler(log *logrus.Entry, arocli aroclient.Interface, kubernetescli kubernetes.Interface, dh dynamichelper.Interface) *Reconciler {
+	return &Reconciler{
+		arocli:   arocli,
+		deployer: newDeployer(kubernetescli, dh),
+
+		readinessPollTime: 10 * time.Second,
+		readinessTimeout:  5 * time.Minute,
+	}
+}
+
+func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) {
+	instance, err := r.arocli.AroV1alpha1().Clusters().Get(ctx, arov1alpha1.SingletonClusterName, metav1.GetOptions{})
+	if err != nil {
+		return reconcile.Result{}, err
+	}
+
+	if !instance.Spec.OperatorFlags.GetSimpleBoolean(enabled) {
+		// controller is disabled
+		return reconcile.Result{}, nil
+	}
+
+	managed := instance.Spec.OperatorFlags.GetWithDefault(managed, "")
+
+	// If enabled and managed=true, install MUO
+	// If enabled and managed=false, remove the MUO deployment
+	// If enabled and managed is missing, do nothing
+	if strings.EqualFold(managed, "true") {
+		err = r.deployer.CreateOrUpdate(ctx, instance)
+		if err != nil {
+			return reconcile.Result{}, err
+		}
+
+		// Check that MUO has become ready, wait up to readinessTimeout (default 5min)
+		timeoutCtx, cancel := context.WithTimeout(ctx, r.readinessTimeout)
+		defer cancel()
+
+		err := wait.PollImmediateUntil(r.readinessPollTime, func() (bool, error) {
+			return r.deployer.IsReady(ctx)
+		}, timeoutCtx.Done())
+		if err != nil {
+			return reconcile.Result{}, fmt.Errorf("Managed Upgrade Operator deployment timed out on Ready: %w", err)
+		}
+	} else if strings.EqualFold(managed, "false") {
+		err := r.deployer.Remove(ctx)
+		if err != nil {
+			return reconcile.Result{}, err
+		}
+	}
+
+	return reconcile.Result{}, nil
+}
+
+// SetupWithManager setup our manager
+func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
+	aroClusterPredicate := predicate.NewPredicateFuncs(func(o client.Object) bool {
+		return o.GetName() == arov1alpha1.SingletonClusterName
+	})
+
+	builder := ctrl.NewControllerManagedBy(mgr).
+		For(&arov1alpha1.Cluster{}, builder.WithPredicates(aroClusterPredicate))
+
+	resources, err := r.deployer.Resources("")
+	if err != nil {
+		return err
+	}
+
+	for _, i := range resources {
+		o, ok := i.(client.Object)
+		if ok {
+			builder.Owns(o)
+		}
+	}
+
+	return builder.
+		WithEventFilter(predicate.Or(predicate.GenerationChangedPredicate{}, predicate.AnnotationChangedPredicate{}, predicate.LabelChangedPredicate{})).
+		Named(controllers.ManagedUpgradeOperatorControllerName).
+		Complete(r)
+}
diff --git a/pkg/operator/controllers/muo/staticresources/cluster_role.yaml b/pkg/operator/controllers/muo/staticresources/cluster_role.yaml
new file mode 100644
index 000000000..5268624bf
--- /dev/null
+++ b/pkg/operator/controllers/muo/staticresources/cluster_role.yaml
@@ -0,0 +1,95 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  creationTimestamp: null
+  name: managed-upgrade-operator
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - pods
+  - pods/finalizers
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - ""
+  resources:
+  - events
+  - nodes
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - upgrade.managed.openshift.io
+  resources:
+  - upgradeconfigs
+  - upgrade
+  - upgradeconfigs/status
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - config.openshift.io
+  resources:
+  - clusterversions
+  - clusteroperators
+  verbs:
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - machine.openshift.io
+  resources:
+  - machines
+  - machinesets
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+- apiGroups:
+  - machineconfiguration.openshift.io
+  resources:
+  - machineconfigpools
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - operators.coreos.com
+  resources:
+  - subscriptions
+  verbs:
+  - '*'
+- apiGroups:
+  - monitoring.coreos.com
+  resources:
+  - servicemonitors
+  verbs:
+  - create
+  - update
+  - get
+  - list
+  - patch
+- apiGroups:
+  - policy
+  resources:
+  - poddisruptionbudgets
+  verbs:
+  - get
+  - list
+  - watch
diff --git a/pkg/operator/controllers/muo/staticresources/cluster_rolebinding.yaml b/pkg/operator/controllers/muo/staticresources/cluster_rolebinding.yaml
new file mode 100644
index 000000000..873149df9
--- /dev/null
+++ b/pkg/operator/controllers/muo/staticresources/cluster_rolebinding.yaml
@@ -0,0 +1,12 @@
+kind: ClusterRoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: managed-upgrade-operator
+subjects:
+- kind: ServiceAccount
+  name: managed-upgrade-operator
+  namespace: openshift-managed-upgrade-operator
+roleRef:
+  kind: ClusterRole
+  name: managed-upgrade-operator
+  apiGroup: rbac.authorization.k8s.io
diff --git a/pkg/operator/controllers/muo/staticresources/custom_resource_definition.yaml b/pkg/operator/controllers/muo/staticresources/custom_resource_definition.yaml
new file mode 100644
index 000000000..cb9bd74a6
--- /dev/null
+++ b/pkg/operator/controllers/muo/staticresources/custom_resource_definition.yaml
@@ -0,0 +1,192 @@
+
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.3.0
+  creationTimestamp: null
+  name: upgradeconfigs.upgrade.managed.openshift.io
+spec:
+  group: upgrade.managed.openshift.io
+  names:
+    kind: UpgradeConfig
+    listKind: UpgradeConfigList
+    plural: upgradeconfigs
+    shortNames:
+    - upgrade
+    singular: upgradeconfig
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .spec.desired.version
+      name: desired_version
+      type: string
+    - jsonPath: .status.history[0].phase
+      name: phase
+      type: string
+    - jsonPath: .status.history[0].conditions[0].type
+      name: stage
+      type: string
+    - jsonPath: .status.history[0].conditions[0].status
+      name: status
+      type: string
+    - jsonPath: .status.history[0].conditions[0].reason
+      name: reason
+      type: string
+    - jsonPath: .status.history[0].conditions[0].message
+      name: message
+      type: string
+    name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        description: UpgradeConfig is the Schema for the upgradeconfigs API
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: UpgradeConfigSpec defines the desired state of UpgradeConfig
+              and upgrade window and freeze window
+            properties:
+              PDBForceDrainTimeout:
+                description: The maximum grace period granted to a node whose drain
+                  is blocked by a Pod Disruption Budget, before that drain is forced.
+                  Measured in minutes. The minimum accepted value is 0 and in this
+                  case it will trigger force drain after the expectedNodeDrainTime
+                  lapsed.
+                format: int32
+                minimum: 0
+                type: integer
+              capacityReservation:
+                description: Specify if scaling up an extra node for capacity reservation
+                  before upgrade starts is needed
+                type: boolean
+              desired:
+                description: Specify the desired OpenShift release
+                properties:
+                  channel:
+                    description: Channel used for upgrades
+                    type: string
+                  image:
+                    description: Image reference used for upgrades
+                    type: string
+                  version:
+                    description: Version of openshift release
+                    type: string
+                type: object
+              type:
+                description: Type indicates the ClusterUpgrader implementation to
+                  use to perform an upgrade of the cluster
+                enum:
+                - OSD
+                - ARO
+                type: string
+              upgradeAt:
+                description: Specify the upgrade start time
+                type: string
+            required:
+            - PDBForceDrainTimeout
+            - desired
+            - type
+            - upgradeAt
+            type: object
+          status:
+            description: UpgradeConfigStatus defines the observed state of UpgradeConfig
+            properties:
+              history:
+                description: This record history of every upgrade
+                items:
+                  description: UpgradeHistory record history of upgrade
+                  properties:
+                    completeTime:
+                      format: date-time
+                      type: string
+                    conditions:
+                      description: Conditions is a set of Condition instances.
+                      items:
+                        description: UpgradeCondition houses fields that describe
+                          the state of an Upgrade including metadata.
+                        properties:
+                          completeTime:
+                            description: Complete time of this condition.
+                            format: date-time
+                            type: string
+                          lastProbeTime:
+                            description: Last time the condition was checked.
+                            format: date-time
+                            type: string
+                          lastTransitionTime:
+                            description: Last time the condition transit from one
+                              status to another.
+                            format: date-time
+                            type: string
+                          message:
+                            description: Human readable message indicating details
+                              about last transition.
+                            type: string
+                          reason:
+                            description: (brief) reason for the condition's last transition.
+                            type: string
+                          startTime:
+                            description: Start time of this condition.
+                            format: date-time
+                            type: string
+                          status:
+                            description: Status of condition, one of True, False,
+                              Unknown
+                            type: string
+                          type:
+                            description: Type of upgrade condition
+                            type: string
+                        required:
+                        - status
+                        - type
+                        type: object
+                      type: array
+                    phase:
+                      description: This describe the status of the upgrade process
+                      enum:
+                      - New
+                      - Pending
+                      - Upgrading
+                      - Upgraded
+                      - Failed
+                      type: string
+                    startTime:
+                      format: date-time
+                      type: string
+                    version:
+                      description: Desired version of this upgrade
+                      type: string
+                    workerCompleteTime:
+                      format: date-time
+                      type: string
+                    workerStartTime:
+                      format: date-time
+                      type: string
+                  required:
+                  - phase
+                  type: object
+                type: array
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+status:
+  acceptedNames:
+    kind: ""
+    plural: ""
+  conditions: []
+  storedVersions: []
diff --git a/pkg/operator/controllers/muo/staticresources/deployment.yaml b/pkg/operator/controllers/muo/staticresources/deployment.yaml
new file mode 100644
index 000000000..19e682ca8
--- /dev/null
+++ b/pkg/operator/controllers/muo/staticresources/deployment.yaml
@@ -0,0 +1,72 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: managed-upgrade-operator
+  namespace: openshift-managed-upgrade-operator
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      name: managed-upgrade-operator
+  template:
+    metadata:
+      labels:
+        name: managed-upgrade-operator
+    spec:
+      serviceAccountName: managed-upgrade-operator
+      affinity:
+        nodeAffinity:
+          requiredDuringSchedulingIgnoredDuringExecution:
+            nodeSelectorTerms:
+            - matchExpressions:
+              - key: node-role.kubernetes.io/master
+                operator: In
+                values:
+                - ""
+      tolerations:
+      - effect: NoSchedule
+        key: node-role.kubernetes.io/master
+      - effect: NoExecute
+        key: node-role.kubernetes.io/master
+      volumes:
+      - configMap:
+          defaultMode: 420
+          items:
+            - key: ca-bundle.crt
+              path: tls-ca-bundle.pem
+          name: trusted-ca-bundle
+        name: trusted-ca-bundle
+      containers:
+        - name: managed-upgrade-operator
+          # Replace this with the built image name
+          # This will get replaced on deploy by /hack/generate-operator-bundle.py
+          image: GENERATED
+          command:
+          - managed-upgrade-operator
+          imagePullPolicy: Always
+          resources:
+            requests:
+              cpu: 20m
+              memory: 100Mi
+            limits:
+              cpu: 50m
+              memory: 200Mi
+          env:
+            - name: WATCH_NAMESPACE
+              valueFrom:
+                fieldRef:
+                  fieldPath: metadata.namespace
+            - name: POD_NAME
+              valueFrom:
+                fieldRef:
+                  fieldPath: metadata.name
+            - name: OPERATOR_NAMESPACE
+              valueFrom:
+                fieldRef:
+                  fieldPath: metadata.namespace
+            - name: OPERATOR_NAME
+              value: "managed-upgrade-operator"
+          volumeMounts:
+          - mountPath: /etc/pki/ca-trust/extracted/pem
+            name: trusted-ca-bundle
+            readOnly: true
diff --git a/pkg/operator/controllers/muo/staticresources/managed_upgrade_role.yaml b/pkg/operator/controllers/muo/staticresources/managed_upgrade_role.yaml
new file mode 100644
index 000000000..07be90917
--- /dev/null
+++ b/pkg/operator/controllers/muo/staticresources/managed_upgrade_role.yaml
@@ -0,0 +1,31 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: managed-upgrade-operator
+  namespace: openshift-managed-upgrade-operator
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - configmaps
+  - secrets
+  - services
+  - services/finalizers
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - update
+  - watch
+- apiGroups:
+  - apps
+  resources:
+  - deployments
+  - deployments/finalizers
+  - replicasets
+  verbs:
+  - get
+  - list
+  - update
+  - watch
diff --git a/pkg/operator/controllers/muo/staticresources/managed_upgrade_rolebinding.yaml b/pkg/operator/controllers/muo/staticresources/managed_upgrade_rolebinding.yaml
new file mode 100644
index 000000000..71eaf7f5d
--- /dev/null
+++ b/pkg/operator/controllers/muo/staticresources/managed_upgrade_rolebinding.yaml
@@ -0,0 +1,12 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: managed-upgrade-operator
+  namespace: openshift-managed-upgrade-operator
+roleRef:
+  kind: Role
+  name: managed-upgrade-operator
+subjects:
+- kind: ServiceAccount
+  name: managed-upgrade-operator
+  namespace: openshift-managed-upgrade-operator
diff --git a/pkg/operator/controllers/muo/staticresources/monitoring_reader_role.yaml b/pkg/operator/controllers/muo/staticresources/monitoring_reader_role.yaml
new file mode 100644
index 000000000..8b8bf2c57
--- /dev/null
+++ b/pkg/operator/controllers/muo/staticresources/monitoring_reader_role.yaml
@@ -0,0 +1,25 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: muo-monitoring-reader
+  namespace: openshift-monitoring
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - configmaps  
+  - serviceaccounts
+  - secrets
+  - services
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - route.openshift.io
+  resources:
+  - routes
+  verbs:
+  - get
+  - list
+  - watch
diff --git a/pkg/operator/controllers/muo/staticresources/monitoring_reader_rolebinding.yaml b/pkg/operator/controllers/muo/staticresources/monitoring_reader_rolebinding.yaml
new file mode 100644
index 000000000..437363036
--- /dev/null
+++ b/pkg/operator/controllers/muo/staticresources/monitoring_reader_rolebinding.yaml
@@ -0,0 +1,12 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: muo-monitoring-reader
+  namespace: openshift-monitoring
+roleRef:
+  kind: Role
+  name: muo-monitoring-reader
+subjects:
+- kind: ServiceAccount
+  name: managed-upgrade-operator
+  namespace: openshift-managed-upgrade-operator
diff --git a/pkg/operator/controllers/muo/staticresources/namespace.yaml b/pkg/operator/controllers/muo/staticresources/namespace.yaml
new file mode 100644
index 000000000..a028d166e
--- /dev/null
+++ b/pkg/operator/controllers/muo/staticresources/namespace.yaml
@@ -0,0 +1,6 @@
+kind: Namespace
+apiVersion: v1
+metadata:
+  name: openshift-managed-upgrade-operator
+  labels:
+    openshift.io/cluster-monitoring: "true"
diff --git a/pkg/operator/controllers/muo/staticresources/prometheus_role.yaml b/pkg/operator/controllers/muo/staticresources/prometheus_role.yaml
new file mode 100644
index 000000000..9b770b987
--- /dev/null
+++ b/pkg/operator/controllers/muo/staticresources/prometheus_role.yaml
@@ -0,0 +1,16 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: prometheus-k8s
+  namespace: openshift-managed-upgrade-operator
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - services
+  - endpoints
+  - pods
+  verbs:
+  - get
+  - list
+  - watch
diff --git a/pkg/operator/controllers/muo/staticresources/prometheus_rolebinding.yaml b/pkg/operator/controllers/muo/staticresources/prometheus_rolebinding.yaml
new file mode 100644
index 000000000..130b266ed
--- /dev/null
+++ b/pkg/operator/controllers/muo/staticresources/prometheus_rolebinding.yaml
@@ -0,0 +1,12 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: prometheus-k8s
+  namespace: openshift-managed-upgrade-operator
+roleRef:
+  kind: Role
+  name: prometheus-k8s
+subjects:
+- kind: ServiceAccount
+  name: prometheus-k8s
+  namespace: openshift-monitoring
diff --git a/pkg/operator/controllers/muo/staticresources/pullsecret_reader_role.yaml b/pkg/operator/controllers/muo/staticresources/pullsecret_reader_role.yaml
new file mode 100644
index 000000000..9824d2f56
--- /dev/null
+++ b/pkg/operator/controllers/muo/staticresources/pullsecret_reader_role.yaml
@@ -0,0 +1,14 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: muo-pullsecret-reader
+  namespace: openshift-config
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - secrets
+  verbs:
+  - get
+  - list
+  - watch
diff --git a/pkg/operator/controllers/muo/staticresources/pullsecret_reader_rolebinding.yaml b/pkg/operator/controllers/muo/staticresources/pullsecret_reader_rolebinding.yaml
new file mode 100644
index 000000000..73eddb30e
--- /dev/null
+++ b/pkg/operator/controllers/muo/staticresources/pullsecret_reader_rolebinding.yaml
@@ -0,0 +1,12 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: muo-pullsecret-reader
+  namespace: openshift-config
+roleRef:
+  kind: Role
+  name: muo-pullsecret-reader
+subjects:
+- kind: ServiceAccount
+  name: managed-upgrade-operator
+  namespace: openshift-managed-upgrade-operator
diff --git a/pkg/operator/controllers/muo/staticresources/service_account.yaml b/pkg/operator/controllers/muo/staticresources/service_account.yaml
new file mode 100644
index 000000000..8a6ad583d
--- /dev/null
+++ b/pkg/operator/controllers/muo/staticresources/service_account.yaml
@@ -0,0 +1,5 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: managed-upgrade-operator
+  namespace: openshift-managed-upgrade-operator
diff --git a/pkg/operator/controllers/muo/staticresources/trusted_ca_bundle_configmap.yaml b/pkg/operator/controllers/muo/staticresources/trusted_ca_bundle_configmap.yaml
new file mode 100644
index 000000000..b4b9e3eef
--- /dev/null
+++ b/pkg/operator/controllers/muo/staticresources/trusted_ca_bundle_configmap.yaml
@@ -0,0 +1,7 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  namespace: openshift-managed-upgrade-operator
+  name: trusted-ca-bundle
+  labels:
+    config.openshift.io/inject-trusted-cabundle: "true"
diff --git a/pkg/operator/mocks/muo/muo.go b/pkg/operator/mocks/muo/muo.go
new file mode 100644
index 000000000..ddb062ae0
--- /dev/null
+++ b/pkg/operator/mocks/muo/muo.go
@@ -0,0 +1,96 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: github.com/Azure/ARO-RP/pkg/operator/controllers/muo (interfaces: Deployer)
+
+// Package mock_muo is a generated GoMock package.
+package mock_muo
+
+import (
+	context "context"
+	reflect "reflect"
+
+	gomock "github.com/golang/mock/gomock"
+	runtime "k8s.io/apimachinery/pkg/runtime"
+
+	v1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
+)
+
+// MockDeployer is a mock of Deployer interface.
+type MockDeployer struct {
+	ctrl     *gomock.Controller
+	recorder *MockDeployerMockRecorder
+}
+
+// MockDeployerMockRecorder is the mock recorder for MockDeployer.
+type MockDeployerMockRecorder struct {
+	mock *MockDeployer
+}
+
+// NewMockDeployer creates a new mock instance.
+func NewMockDeployer(ctrl *gomock.Controller) *MockDeployer {
+	mock := &MockDeployer{ctrl: ctrl}
+	mock.recorder = &MockDeployerMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockDeployer) EXPECT() *MockDeployerMockRecorder {
+	return m.recorder
+}
+
+// CreateOrUpdate mocks base method.
+func (m *MockDeployer) CreateOrUpdate(arg0 context.Context, arg1 *v1alpha1.Cluster) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "CreateOrUpdate", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// CreateOrUpdate indicates an expected call of CreateOrUpdate.
+func (mr *MockDeployerMockRecorder) CreateOrUpdate(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdate", reflect.TypeOf((*MockDeployer)(nil).CreateOrUpdate), arg0, arg1)
+}
+
+// IsReady mocks base method.
+func (m *MockDeployer) IsReady(arg0 context.Context) (bool, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "IsReady", arg0)
+	ret0, _ := ret[0].(bool)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// IsReady indicates an expected call of IsReady.
+func (mr *MockDeployerMockRecorder) IsReady(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsReady", reflect.TypeOf((*MockDeployer)(nil).IsReady), arg0)
+}
+
+// Remove mocks base method.
+func (m *MockDeployer) Remove(arg0 context.Context) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Remove", arg0)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Remove indicates an expected call of Remove.
+func (mr *MockDeployerMockRecorder) Remove(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockDeployer)(nil).Remove), arg0)
+}
+
+// Resources mocks base method.
+func (m *MockDeployer) Resources(arg0 string) ([]runtime.Object, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Resources", arg0)
+	ret0, _ := ret[0].([]runtime.Object)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Resources indicates an expected call of Resources.
+func (mr *MockDeployerMockRecorder) Resources(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Resources", reflect.TypeOf((*MockDeployer)(nil).Resources), arg0)
+}
diff --git a/pkg/util/dynamichelper/dynamichelper.go b/pkg/util/dynamichelper/dynamichelper.go
index 15be5e6bb..c9c213eac 100644
--- a/pkg/util/dynamichelper/dynamichelper.go
+++ b/pkg/util/dynamichelper/dynamichelper.go
@@ -7,6 +7,7 @@ import (
 	"context"
 	"fmt"
 	"reflect"
+	"strings"
 
 	mcv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1"
 	"github.com/sirupsen/logrus"
@@ -173,6 +174,12 @@ func merge(old, new kruntime.Object) (kruntime.Object, bool, string, error) {
 		} {
 			copyAnnotation(&new.ObjectMeta, &old.ObjectMeta, name)
 		}
+		// Copy OLM label
+		for k := range old.Labels {
+			if strings.HasPrefix(k, "olm.operatorgroup.uid/") {
+				copyLabel(&new.ObjectMeta, &old.ObjectMeta, k)
+			}
+		}
 		new.Spec.Finalizers = old.Spec.Finalizers
 		new.Status = old.Status
 
@@ -193,6 +200,12 @@ func merge(old, new kruntime.Object) (kruntime.Object, bool, string, error) {
 	case *appsv1.Deployment:
 		old, new := old.(*appsv1.Deployment), new.(*appsv1.Deployment)
 		copyAnnotation(&new.ObjectMeta, &old.ObjectMeta, "deployment.kubernetes.io/revision")
+
+		// populated automatically by the Kubernetes API (observed on 4.9)
+		if old.Spec.Template.Spec.DeprecatedServiceAccount != "" {
+			new.Spec.Template.Spec.DeprecatedServiceAccount = old.Spec.Template.Spec.DeprecatedServiceAccount
+		}
+
 		new.Status = old.Status
 
 	case *mcv1.KubeletConfig:
@@ -210,6 +223,17 @@ func merge(old, new kruntime.Object) (kruntime.Object, bool, string, error) {
 	case *arov1alpha1.Cluster:
 		old, new := old.(*arov1alpha1.Cluster), new.(*arov1alpha1.Cluster)
 		new.Status = old.Status
+
+	case *corev1.ConfigMap:
+		old, new := old.(*corev1.ConfigMap), new.(*corev1.ConfigMap)
+
+		_, injectTrustBundle := new.ObjectMeta.Labels["config.openshift.io/inject-trusted-cabundle"]
+		if injectTrustBundle {
+			caBundle, ext := old.Data["ca-bundle.crt"]
+			if ext {
+				new.Data["ca-bundle.crt"] = caBundle
+			}
+		}
 	}
 
 	var diff string
@@ -229,6 +253,15 @@ func copyAnnotation(dst, src *metav1.ObjectMeta, name string) {
 	}
 }
 
+func copyLabel(dst, src *metav1.ObjectMeta, name string) {
+	if _, found := src.Labels[name]; found {
+		if dst.Labels == nil {
+			dst.Labels = map[string]string{}
+		}
+		dst.Labels[name] = src.Labels[name]
+	}
+}
+
 func makeURLSegments(gvr *schema.GroupVersionResource, namespace, name string) (url []string) {
 	if gvr.Group == "" {
 		url = append(url, "api")


Test patch:
diff --git a/pkg/operator/controllers/muo/deploy_test.go b/pkg/operator/controllers/muo/deploy_test.go
new file mode 100644
index 000000000..2b6832775
--- /dev/null
+++ b/pkg/operator/controllers/muo/deploy_test.go
@@ -0,0 +1,226 @@
+package muo
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"context"
+	"errors"
+	"testing"
+
+	"github.com/go-test/deep"
+	"github.com/golang/mock/gomock"
+	appsv1 "k8s.io/api/apps/v1"
+	"k8s.io/apimachinery/pkg/api/meta"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	kruntime "k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/client-go/kubernetes/fake"
+
+	arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
+	mock_dynamichelper "github.com/Azure/ARO-RP/pkg/util/mocks/dynamichelper"
+)
+
+func TestDeployCreateOrUpdateCorrectKinds(t *testing.T) {
+	controller := gomock.NewController(t)
+	defer controller.Finish()
+
+	setPullSpec := "MyMUOPullSpec"
+	cluster := &arov1alpha1.Cluster{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: arov1alpha1.SingletonClusterName,
+		},
+		Spec: arov1alpha1.ClusterSpec{
+			OperatorFlags: arov1alpha1.OperatorFlags{
+				pullSpec: setPullSpec,
+			},
+		},
+	}
+
+	k8scli := fake.NewSimpleClientset()
+	dh := mock_dynamichelper.NewMockInterface(controller)
+
+	// When the DynamicHelper is called, count the number of objects it creates
+	// and capture any deployments so that we can check the pull secret
+	var deployments []*appsv1.Deployment
+	deployedObjects := make(map[string]int)
+	check := func(ctx context.Context, objs ...kruntime.Object) error {
+		m := meta.NewAccessor()
+		for _, i := range objs {
+			kind, err := m.Kind(i)
+			if err != nil {
+				return err
+			}
+			deployedObjects[kind] = deployedObjects[kind] + 1
+		}
+		return nil
+	}
+	dh.EXPECT().Ensure(gomock.Any(), gomock.Any()).Do(check).Return(nil)
+
+	deployer := newDeployer(k8scli, dh)
+	err := deployer.CreateOrUpdate(context.Background(), cluster)
+	if err != nil {
+		t.Error(err)
+	}
+
+	// We expect these numbers of resources to be created
+	expectedKinds := map[string]int{
+		"ClusterRole":              1,
+		"ConfigMap":                1,
+		"ClusterRoleBinding":       1,
+		"CustomResourceDefinition": 1,
+		"Deployment":               1,
+		"Namespace":                1,
+		"Role":                     4,
+		"RoleBinding":              4,
+		"ServiceAccount":           1,
+	}
+	errs := deep.Equal(deployedObjects, expectedKinds)
+	for _, e := range errs {
+		t.Error(e)
+	}
+
+	// Ensure we have set the pullspec set on the containers
+	for _, d := range deployments {
+		for _, c := range d.Spec.Template.Spec.Containers {
+			if c.Image != setPullSpec {
+				t.Errorf("expected %s, got %s for pullspec", setPullSpec, c.Image)
+			}
+		}
+	}
+}
+
+func TestDeployCreateOrUpdateSetsOwnerReferences(t *testing.T) {
+	controller := gomock.NewController(t)
+	defer controller.Finish()
+
+	setPullSpec := "MyMUOPullSpec"
+	cluster := &arov1alpha1.Cluster{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: arov1alpha1.SingletonClusterName,
+		},
+		Spec: arov1alpha1.ClusterSpec{
+			OperatorFlags: arov1alpha1.OperatorFlags{
+				pullSpec: setPullSpec,
+			},
+		},
+	}
+
+	k8scli := fake.NewSimpleClientset()
+	dh := mock_dynamichelper.NewMockInterface(controller)
+
+	// the OwnerReference that we expect to be set on each object we Ensure
+	pointerToTrueForSomeReason := bool(true)
+	expectedOwner := metav1.OwnerReference{
+		APIVersion:         "aro.openshift.io/v1alpha1",
+		Kind:               "Cluster",
+		Name:               arov1alpha1.SingletonClusterName,
+		UID:                cluster.UID,
+		BlockOwnerDeletion: &pointerToTrueForSomeReason,
+		Controller:         &pointerToTrueForSomeReason,
+	}
+
+	// save the list of OwnerReferences on each of the Ensured objects
+	var ownerReferences [][]metav1.OwnerReference
+	check := func(ctx context.Context, objs ...kruntime.Object) error {
+		for _, i := range objs {
+			obj, err := meta.Accessor(i)
+			if err != nil {
+				return err
+			}
+			ownerReferences = append(ownerReferences, obj.GetOwnerReferences())
+		}
+		return nil
+	}
+	dh.EXPECT().Ensure(gomock.Any(), gomock.Any()).Do(check).Return(nil)
+
+	deployer := newDeployer(k8scli, dh)
+	err := deployer.CreateOrUpdate(context.Background(), cluster)
+	if err != nil {
+		t.Error(err)
+	}
+
+	// Check that each list of OwnerReferences contains our controller
+	for _, references := range ownerReferences {
+		errs := deep.Equal([]metav1.OwnerReference{expectedOwner}, references)
+		for _, e := range errs {
+			t.Error(e)
+		}
+	}
+}
+
+func TestDeployDelete(t *testing.T) {
+	controller := gomock.NewController(t)
+	defer controller.Finish()
+
+	k8scli := fake.NewSimpleClientset()
+	dh := mock_dynamichelper.NewMockInterface(controller)
+	dh.EXPECT().EnsureDeleted(gomock.Any(), "Deployment", "openshift-managed-upgrade-operator", "managed-upgrade-operator").Return(nil)
+
+	deployer := newDeployer(k8scli, dh)
+	err := deployer.Remove(context.Background())
+	if err != nil {
+		t.Error(err)
+	}
+}
+
+func TestDeployDeleteFailure(t *testing.T) {
+	controller := gomock.NewController(t)
+	defer controller.Finish()
+
+	k8scli := fake.NewSimpleClientset()
+	dh := mock_dynamichelper.NewMockInterface(controller)
+	dh.EXPECT().EnsureDeleted(gomock.Any(), "Deployment", "openshift-managed-upgrade-operator", "managed-upgrade-operator").Return(errors.New("fail"))
+
+	deployer := newDeployer(k8scli, dh)
+	err := deployer.Remove(context.Background())
+	if err == nil {
+		t.Error(err)
+	}
+	if err.Error() != "error removing MUO:\nfail" {
+		t.Error(err)
+	}
+}
+
+func TestDeployIsReady(t *testing.T) {
+	specReplicas := int32(1)
+	k8scli := fake.NewSimpleClientset(&appsv1.Deployment{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:       "managed-upgrade-operator",
+			Namespace:  "openshift-managed-upgrade-operator",
+			Generation: 1234,
+		},
+		Spec: appsv1.DeploymentSpec{
+			Replicas: &specReplicas,
+		},
+		Status: appsv1.DeploymentStatus{
+			ObservedGeneration:  1234,
+			Replicas:            1,
+			ReadyReplicas:       1,
+			UpdatedReplicas:     1,
+			AvailableReplicas:   1,
+			UnavailableReplicas: 0,
+		},
+	})
+
+	deployer := newDeployer(k8scli, nil)
+	ready, err := deployer.IsReady(context.Background())
+	if err != nil {
+		t.Error(err)
+	}
+	if !ready {
+		t.Error("deployment is not seen as ready")
+	}
+}
+
+func TestDeployIsReadyMissing(t *testing.T) {
+	k8scli := fake.NewSimpleClientset()
+	deployer := newDeployer(k8scli, nil)
+	ready, err := deployer.IsReady(context.Background())
+	if err != nil {
+		t.Error(err)
+	}
+	if ready {
+		t.Error("deployment is wrongly seen as ready")
+	}
+
+}
diff --git a/pkg/operator/controllers/muo/muo_controller_test.go b/pkg/operator/controllers/muo/muo_controller_test.go
new file mode 100644
index 000000000..42d68b105
--- /dev/null
+++ b/pkg/operator/controllers/muo/muo_controller_test.go
@@ -0,0 +1,131 @@
+package muo
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"context"
+	"errors"
+	"strconv"
+	"testing"
+	"time"
+
+	"github.com/golang/mock/gomock"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
+	arofake "github.com/Azure/ARO-RP/pkg/operator/clientset/versioned/fake"
+	mock_muo "github.com/Azure/ARO-RP/pkg/operator/mocks/muo"
+)
+
+func TestMUOReconciler(t *testing.T) {
+	tests := []struct {
+		name  string
+		mocks func(*mock_muo.MockDeployer, *arov1alpha1.Cluster)
+		// feature flag options
+		enabled bool
+		managed string
+		// errors
+		wantErr string
+	}{
+		{
+			name:    "disabled",
+			enabled: false,
+			managed: "false",
+		},
+		{
+			name:    "managed",
+			enabled: true,
+			managed: "true",
+			mocks: func(md *mock_muo.MockDeployer, cluster *arov1alpha1.Cluster) {
+				md.EXPECT().CreateOrUpdate(gomock.Any(), gomock.Eq(cluster)).Return(nil)
+				md.EXPECT().IsReady(gomock.Any()).Return(true, nil)
+			},
+		},
+		{
+			name:    "managed, MUO does not become ready",
+			enabled: true,
+			managed: "true",
+			mocks: func(md *mock_muo.MockDeployer, cluster *arov1alpha1.Cluster) {
+				md.EXPECT().CreateOrUpdate(gomock.Any(), gomock.Eq(cluster)).Return(nil)
+				md.EXPECT().IsReady(gomock.Any()).Return(false, nil)
+			},
+			wantErr: "Managed Upgrade Operator deployment timed out on Ready: timed out waiting for the condition",
+		},
+		{
+			name:    "managed, CreateOrUpdate() fails",
+			enabled: true,
+			managed: "true",
+			mocks: func(md *mock_muo.MockDeployer, cluster *arov1alpha1.Cluster) {
+				md.EXPECT().CreateOrUpdate(gomock.Any(), gomock.Eq(cluster)).Return(errors.New("failed ensure"))
+			},
+			wantErr: "failed ensure",
+		},
+		{
+			name:    "managed=false (removal)",
+			enabled: true,
+			managed: "false",
+			mocks: func(md *mock_muo.MockDeployer, cluster *arov1alpha1.Cluster) {
+				md.EXPECT().Remove(gomock.Any()).Return(nil)
+			},
+		},
+		{
+			name:    "managed=false (removal), Remove() fails",
+			enabled: true,
+			managed: "false",
+			mocks: func(md *mock_muo.MockDeployer, cluster *arov1alpha1.Cluster) {
+				md.EXPECT().Remove(gomock.Any()).Return(errors.New("failed delete"))
+			},
+			wantErr: "failed delete",
+		},
+		{
+			name:    "managed=blank (no action)",
+			enabled: true,
+			managed: "",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			controller := gomock.NewController(t)
+			defer controller.Finish()
+
+			cluster := &arov1alpha1.Cluster{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: arov1alpha1.SingletonClusterName,
+				},
+				Spec: arov1alpha1.ClusterSpec{
+					OperatorFlags: arov1alpha1.OperatorFlags{
+						enabled:  strconv.FormatBool(tt.enabled),
+						pullSpec: "wonderfulPullspec",
+					},
+				},
+			}
+			// nil and empty string are valid values, be careful to preserve them
+			if tt.managed != "" {
+				cluster.Spec.OperatorFlags[managed] = tt.managed
+			}
+			arocli := arofake.NewSimpleClientset(cluster)
+			deployer := mock_muo.NewMockDeployer(controller)
+
+			if tt.mocks != nil {
+				tt.mocks(deployer, cluster)
+			}
+
+			r := &Reconciler{
+				arocli:            arocli,
+				deployer:          deployer,
+				readinessTimeout:  0 * time.Second,
+				readinessPollTime: 1 * time.Second,
+			}
+			_, err := r.Reconcile(context.Background(), reconcile.Request{})
+			if err == nil && tt.wantErr != "" {
+				t.Error(err)
+			} else if err != nil {
+				if err.Error() != tt.wantErr {
+					t.Errorf("wanted '%v', got '%v'", tt.wantErr, err)
+				}
+			}
+		})
+	}
+}
diff --git a/pkg/util/dynamichelper/dynamichelper_test.go b/pkg/util/dynamichelper/dynamichelper_test.go
index ecd7ffd75..b5e1791b1 100644
--- a/pkg/util/dynamichelper/dynamichelper_test.go
+++ b/pkg/util/dynamichelper/dynamichelper_test.go
@@ -104,6 +104,9 @@ func TestMerge(t *testing.T) {
 						"openshift.io/sa.scc.supplemental-groups": "groups",
 						"openshift.io/sa.scc.uid-range":           "uids",
 					},
+					Labels: map[string]string{
+						"olm.operatorgroup.uid/jdfgbdfgdfhg": "test",
+					},
 				},
 				Spec: corev1.NamespaceSpec{
 					Finalizers: []corev1.FinalizerName{
@@ -122,6 +125,9 @@ func TestMerge(t *testing.T) {
 						"openshift.io/sa.scc.supplemental-groups": "groups",
 						"openshift.io/sa.scc.uid-range":           "uids",
 					},
+					Labels: map[string]string{
+						"olm.operatorgroup.uid/jdfgbdfgdfhg": "test",
+					},
 				},
 				Spec: corev1.NamespaceSpec{
 					Finalizers: []corev1.FinalizerName{
@@ -163,6 +169,65 @@ func TestMerge(t *testing.T) {
 			},
 			wantEmptyDiff: true,
 		},
+		{
+			name: "ConfigMap with injected ca bundle label and bundle, no changes",
+			old: &corev1.ConfigMap{
+				ObjectMeta: metav1.ObjectMeta{
+					Labels: map[string]string{
+						"config.openshift.io/inject-trusted-cabundle": "",
+					},
+				},
+				Data: map[string]string{
+					"ca-bundle.crt": "bundlehere",
+				},
+			},
+			new: &corev1.ConfigMap{
+				ObjectMeta: metav1.ObjectMeta{
+					Labels: map[string]string{
+						"config.openshift.io/inject-trusted-cabundle": "",
+					},
+				},
+			},
+			want: &corev1.ConfigMap{
+				ObjectMeta: metav1.ObjectMeta{
+					Labels: map[string]string{
+						"config.openshift.io/inject-trusted-cabundle": "",
+					},
+				},
+				Data: map[string]string{
+					"ca-bundle.crt": "bundlehere",
+				},
+			},
+			wantEmptyDiff: true,
+		},
+		{
+			name: "ConfigMap with injected ca bundle label and no bundle, no changes",
+			old: &corev1.ConfigMap{
+				ObjectMeta: metav1.ObjectMeta{
+					Labels: map[string]string{
+						"config.openshift.io/inject-trusted-cabundle": "",
+					},
+				},
+				Data: map[string]string{},
+			},
+			new: &corev1.ConfigMap{
+				ObjectMeta: metav1.ObjectMeta{
+					Labels: map[string]string{
+						"config.openshift.io/inject-trusted-cabundle": "",
+					},
+				},
+				Data: map[string]string{},
+			},
+			want: &corev1.ConfigMap{
+				ObjectMeta: metav1.ObjectMeta{
+					Labels: map[string]string{
+						"config.openshift.io/inject-trusted-cabundle": "",
+					},
+				},
+				Data: map[string]string{},
+			},
+			wantEmptyDiff: true,
+		},
 		{
 			name: "Service no changes",
 			old: &corev1.Service{