Repository: Azure/ARO-RP
Pull request #2009 (2022-03-17 02:18:44)

Add the MUO configmap deployment to the Operator
### Which issue this PR addresses:

Fixes https://msazure.visualstudio.com/AzureRedHatOpenShift/_workitems/edit/10430654

### What this PR does / why we need it:
Deploys the MUO configmap as well as allowing "connected" MUO to be enabled if the 'allow MUO' flag is available and there is an OCM-related pullsecret in the cluster.

### Test plan for issue:
Unit tests are provided for the new functionality

### Is there any documentation that needs to be updated for this PR?

No change needed to SOP, scripts can be simplified though


Patch:
diff --git a/pkg/operator/controllers/muo/bindata.go b/pkg/operator/controllers/muo/bindata.go
index 991352527..3d2cfe213 100644
--- a/pkg/operator/controllers/muo/bindata.go
+++ b/pkg/operator/controllers/muo/bindata.go
@@ -2,6 +2,7 @@
 // sources:
 // staticresources/cluster_role.yaml
 // staticresources/cluster_rolebinding.yaml
+// staticresources/config.yaml
 // staticresources/custom_resource_definition.yaml
 // staticresources/deployment.yaml
 // staticresources/managed_upgrade_role.yaml
@@ -131,6 +132,26 @@ func cluster_rolebindingYaml() (*asset, error) {
 	return a, nil
 }
 
+var _configYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x7c\x52\xdd\x6a\x1b\x3d\x10\xbd\xf7\x53\xe8\x05\xf6\xe3\x73\x7f\xa0\xd5\x5d\xd8\x10\x28\x24\x76\x08\x69\x7b\x3d\x48\x27\xbb\xc2\xda\x91\x18\x8d\xec\xa6\xf4\xe1\x8b\xd7\xbb\x8e\xd3\x6c\xab\xcb\xf3\x33\xe7\x68\x18\xca\xe1\x1b\xa4\x84\xc4\xd6\xec\xd7\xab\x5d\x60\x6f\x4d\x9b\xf8\x29\x74\x77\x94\x57\x03\x94\x3c\x29\xd9\x95\x31\x4c\x03\xac\x19\x88\xa9\x83\x6f\x6a\xee\x84\x3c\x9a\x94\x21\xa4\x49\x1a\x37\x9a\x26\x5d\xc9\xe4\x60\x8d\x49\x19\x5c\xfa\xf0\xa4\xcd\xdf\x7c\xab\x79\xfc\xc9\xff\xdf\x33\x0d\xd1\x9a\x5f\x2b\x63\x66\xe8\x6e\x74\x8a\x1d\x21\x63\x4a\xaa\x72\x9c\x7d\xbb\x6d\xaf\x6e\x27\x2c\x26\x47\xf1\xd4\x7a\xb3\xd8\xf2\x5c\xee\xf8\x0e\xa4\xae\xff\xc2\x0a\xd9\x53\xb4\x66\x3d\xc2\x03\x05\x56\x30\xb1\xc3\x9c\xe4\x12\xab\xa4\x78\x1f\x89\xf1\x18\x8e\x63\x3f\xff\x3f\x51\xa1\xe3\x24\xf0\x57\x11\xa2\x65\xd6\xbf\x76\xb4\x12\x34\x38\x8a\x17\x74\x63\xda\x58\x8b\x42\xb6\xd3\xe7\xaf\xd3\x81\xff\xc1\x62\x2c\xef\x47\xc5\xf4\x93\xef\x81\x7d\x3a\xcc\x23\x3d\x22\x3d\x3f\x4a\xe8\x8e\xfb\x31\xef\xe7\x76\x1a\x06\x6c\xab\x5a\xb3\x7e\x77\x82\x38\x79\x5c\x0b\x05\xb6\x7f\x2a\x3e\x7c\x9c\x10\xfc\xc8\x70\x0a\xbf\x99\xa5\xa7\x1f\x7f\x1a\xe9\xe2\x28\xe2\x8d\x77\xca\xeb\x41\x51\xfb\xb6\x87\xdb\xd9\xd7\xeb\x79\xb3\x82\xc6\xdc\x4b\x1a\xa0\x3d\x6a\x79\xa8\x11\x37\x14\x62\x15\x94\x33\xdd\x12\x73\xd2\x07\xa8\x04\xec\xf1\x35\x7b\xd2\x0b\xf6\x26\x56\xb0\x9e\x2a\xbe\x6c\x6e\x0a\xdb\xcc\x67\x77\x91\xf6\x72\x7e\x31\x75\x5d\xe0\x6e\x81\x11\xf8\x9e\x8e\xf7\x29\x3b\x68\x8e\xe4\xb0\x20\x9a\xaf\xb5\x2c\x70\xb5\x40\x9a\x43\x92\x5d\x4c\xe4\x9b\x21\x71\xd0\x24\xcb\x59\x39\x64\xc4\xc0\x58\x1a\x43\x3f\xab\xe0\xdc\xf3\x77\x00\x00\x00\xff\xff\xd6\xce\xc1\x4a\x98\x03\x00\x00")
+
+func configYamlBytes() ([]byte, error) {
+	return bindataRead(
+		_configYaml,
+		"config.yaml",
+	)
+}
+
+func configYaml() (*asset, error) {
+	bytes, err := configYamlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "config.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) {
@@ -445,6 +466,7 @@ func AssetNames() []string {
 var _bindata = map[string]func() (*asset, error){
 	"cluster_role.yaml":                  cluster_roleYaml,
 	"cluster_rolebinding.yaml":           cluster_rolebindingYaml,
+	"config.yaml":                        configYaml,
 	"custom_resource_definition.yaml":    custom_resource_definitionYaml,
 	"deployment.yaml":                    deploymentYaml,
 	"managed_upgrade_role.yaml":          managed_upgrade_roleYaml,
@@ -503,6 +525,7 @@ type bintree struct {
 var _bintree = &bintree{nil, map[string]*bintree{
 	"cluster_role.yaml":                  {cluster_roleYaml, map[string]*bintree{}},
 	"cluster_rolebinding.yaml":           {cluster_rolebindingYaml, map[string]*bintree{}},
+	"config.yaml":                        {configYaml, 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{}},
diff --git a/pkg/operator/controllers/muo/config/config.go b/pkg/operator/controllers/muo/config/config.go
new file mode 100644
index 000000000..f4819502c
--- /dev/null
+++ b/pkg/operator/controllers/muo/config/config.go
@@ -0,0 +1,10 @@
+package config
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+type MUODeploymentConfig struct {
+	Pullspec        string
+	EnableConnected bool
+	OCMBaseURL      string
+}
diff --git a/pkg/operator/controllers/muo/deploy.go b/pkg/operator/controllers/muo/deploy.go
index e4fb08ffb..0aa0643b9 100644
--- a/pkg/operator/controllers/muo/deploy.go
+++ b/pkg/operator/controllers/muo/deploy.go
@@ -8,36 +8,55 @@ import (
 	"errors"
 	"strings"
 
+	"github.com/ghodss/yaml"
+	"github.com/ugorji/go/codec"
 	appsv1 "k8s.io/api/apps/v1"
+	corev1 "k8s.io/api/core/v1"
 	kruntime "k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/kubernetes/scheme"
 
+	"github.com/Azure/ARO-RP/pkg/api"
 	arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
+	"github.com/Azure/ARO-RP/pkg/operator/controllers/muo/config"
 	"github.com/Azure/ARO-RP/pkg/util/dynamichelper"
 	"github.com/Azure/ARO-RP/pkg/util/ready"
 )
 
+type muoConfig struct {
+	api.MissingFields
+	ConfigManager struct {
+		api.MissingFields
+		Source          string `json:"source,omitempty"`
+		OcmBaseUrl      string `json:"ocmBaseUrl,omitempty"`
+		LocalConfigName string `json:"localConfigName,omitempty"`
+	} `json:"configManager,omitempty"`
+}
+
 type Deployer interface {
-	CreateOrUpdate(context.Context, *arov1alpha1.Cluster) error
+	CreateOrUpdate(context.Context, *arov1alpha1.Cluster, *config.MUODeploymentConfig) error
 	Remove(context.Context) error
 	IsReady(ctx context.Context) (bool, error)
-	Resources(string) ([]kruntime.Object, error)
+	Resources(*config.MUODeploymentConfig) ([]kruntime.Object, error)
 }
 
 type deployer struct {
 	kubernetescli kubernetes.Interface
 	dh            dynamichelper.Interface
+
+	jsonHandle *codec.JsonHandle
 }
 
 func newDeployer(kubernetescli kubernetes.Interface, dh dynamichelper.Interface) Deployer {
 	return &deployer{
 		kubernetescli: kubernetescli,
 		dh:            dh,
+
+		jsonHandle: new(codec.JsonHandle),
 	}
 }
 
-func (o *deployer) Resources(pullspec string) ([]kruntime.Object, error) {
+func (o *deployer) Resources(config *config.MUODeploymentConfig) ([]kruntime.Object, error) {
 	results := []kruntime.Object{}
 	for _, assetName := range AssetNames() {
 		b, err := Asset(assetName)
@@ -53,7 +72,46 @@ func (o *deployer) Resources(pullspec string) ([]kruntime.Object, error) {
 		// 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
+				d.Spec.Template.Spec.Containers[i].Image = config.Pullspec
+			}
+		}
+
+		if cm, ok := obj.(*corev1.ConfigMap); ok {
+			if cm.Name == "managed-upgrade-operator-config" && cm.Namespace == "openshift-managed-upgrade-operator" {
+				// read the config.yaml from the MUO ConfigMap which stores defaults
+				configDataJSON, err := yaml.YAMLToJSON([]byte(cm.Data["config.yaml"]))
+				if err != nil {
+					return nil, err
+				}
+
+				var configData muoConfig
+				err = codec.NewDecoderBytes(configDataJSON, o.jsonHandle).Decode(&configData)
+				if err != nil {
+					return nil, err
+				}
+
+				if config.EnableConnected {
+					configData.ConfigManager.Source = "OCM"
+					configData.ConfigManager.OcmBaseUrl = config.OCMBaseURL
+					configData.ConfigManager.LocalConfigName = ""
+				} else {
+					configData.ConfigManager.Source = "LOCAL"
+					configData.ConfigManager.LocalConfigName = "managed-upgrade-config"
+					configData.ConfigManager.OcmBaseUrl = ""
+				}
+
+				// Write the yaml back into the ConfigMap
+				var b []byte
+				err = codec.NewEncoderBytes(&b, o.jsonHandle).Encode(configData)
+				if err != nil {
+					return nil, err
+				}
+
+				cmYaml, err := yaml.JSONToYAML(b)
+				if err != nil {
+					return nil, err
+				}
+				cm.Data["config.yaml"] = string(cmYaml)
 			}
 		}
 
@@ -63,13 +121,8 @@ func (o *deployer) Resources(pullspec string) ([]kruntime.Object, error) {
 	return results, nil
 }
 
-func (o *deployer) CreateOrUpdate(ctx context.Context, cluster *arov1alpha1.Cluster) error {
-	imagePullspec, ext := cluster.Spec.OperatorFlags[controllerPullSpec]
-	if !ext {
-		return errors.New("missing pullspec")
-	}
-
-	resources, err := o.Resources(imagePullspec)
+func (o *deployer) CreateOrUpdate(ctx context.Context, cluster *arov1alpha1.Cluster, config *config.MUODeploymentConfig) error {
+	resources, err := o.Resources(config)
 	if err != nil {
 		return err
 	}
@@ -88,7 +141,7 @@ func (o *deployer) CreateOrUpdate(ctx context.Context, cluster *arov1alpha1.Clus
 }
 
 func (o *deployer) Remove(ctx context.Context) error {
-	resources, err := o.Resources("")
+	resources, err := o.Resources(&config.MUODeploymentConfig{})
 	if err != nil {
 		return err
 	}
diff --git a/pkg/operator/controllers/muo/muo_controller.go b/pkg/operator/controllers/muo/muo_controller.go
index 0f8ca2531..c3190f73c 100644
--- a/pkg/operator/controllers/muo/muo_controller.go
+++ b/pkg/operator/controllers/muo/muo_controller.go
@@ -5,11 +5,14 @@ package muo
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"strings"
 	"time"
 
+	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/client-go/kubernetes"
 	ctrl "sigs.k8s.io/controller-runtime"
@@ -20,20 +23,36 @@ import (
 
 	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/muo/config"
 	"github.com/Azure/ARO-RP/pkg/util/dynamichelper"
+	"github.com/Azure/ARO-RP/pkg/util/pullsecret"
 )
 
 const (
 	ControllerName = "ManagedUpgradeOperator"
 
-	controllerEnabled  = "rh.srep.muo.enabled"
-	controllerManaged  = "rh.srep.muo.managed"
-	controllerPullSpec = "rh.srep.muo.deploy.pullspec"
+	controllerEnabled                = "rh.srep.muo.enabled"
+	controllerManaged                = "rh.srep.muo.managed"
+	controllerPullSpec               = "rh.srep.muo.deploy.pullspec"
+	controllerAllowOCM               = "rh.srep.muo.deploy.allowOCM"
+	controllerOcmBaseURL             = "rh.srep.muo.deploy.ocmBaseUrl"
+	controllerOcmBaseURLDefaultValue = "https://api.openshift.com"
+
+	pullSecretOCMKey = "cloud.redhat.com"
 )
 
+var pullSecretName = types.NamespacedName{Name: "pull-secret", Namespace: "openshift-config"}
+
+type MUODeploymentConfig struct {
+	Pullspec     string
+	ConnectToOCM bool
+	OCMBaseURL   string
+}
+
 type Reconciler struct {
-	arocli   aroclient.Interface
-	deployer Deployer
+	arocli        aroclient.Interface
+	kubernetescli kubernetes.Interface
+	deployer      Deployer
 
 	readinessPollTime time.Duration
 	readinessTimeout  time.Duration
@@ -41,8 +60,9 @@ type Reconciler struct {
 
 func NewReconciler(arocli aroclient.Interface, kubernetescli kubernetes.Interface, dh dynamichelper.Interface) *Reconciler {
 	return &Reconciler{
-		arocli:   arocli,
-		deployer: newDeployer(kubernetescli, dh),
+		arocli:        arocli,
+		kubernetescli: kubernetescli,
+		deployer:      newDeployer(kubernetescli, dh),
 
 		readinessPollTime: 10 * time.Second,
 		readinessTimeout:  5 * time.Minute,
@@ -66,7 +86,48 @@ func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.
 	// 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)
+		imagePullspec, ext := instance.Spec.OperatorFlags[controllerPullSpec]
+		if !ext {
+			return reconcile.Result{}, errors.New("missing pullspec")
+		}
+
+		config := &config.MUODeploymentConfig{
+			Pullspec: imagePullspec,
+		}
+
+		allowOCM := instance.Spec.OperatorFlags.GetSimpleBoolean(controllerAllowOCM)
+		if allowOCM {
+			useOCM := func() bool {
+				var userSecret *corev1.Secret
+
+				userSecret, err = r.kubernetescli.CoreV1().Secrets(pullSecretName.Namespace).Get(ctx, pullSecretName.Name, metav1.GetOptions{})
+				if err != nil {
+					// if a pullsecret doesn't exist/etc, fallback to local
+					return false
+				}
+
+				parsedKeys, err := pullsecret.UnmarshalSecretData(userSecret)
+				if err != nil {
+					// if we can't parse the pullsecret, fallback to local
+					return false
+				}
+
+				// check for the key that connects the cluster to OCM (since
+				// clusters may have a RH registry pull secret but not the OCM
+				// one if they choose)
+				_, foundKey := parsedKeys[pullSecretOCMKey]
+				return foundKey
+			}()
+
+			// if we have a valid pullsecret, enable connected MUO
+			if useOCM {
+				config.EnableConnected = true
+				config.OCMBaseURL = instance.Spec.OperatorFlags.GetWithDefault(controllerOcmBaseURL, controllerOcmBaseURLDefaultValue)
+			}
+		}
+
+		// Deploy the MUO manifests and config
+		err = r.deployer.CreateOrUpdate(ctx, instance, config)
 		if err != nil {
 			return reconcile.Result{}, err
 		}
@@ -100,7 +161,7 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
 	builder := ctrl.NewControllerManagedBy(mgr).
 		For(&arov1alpha1.Cluster{}, builder.WithPredicates(aroClusterPredicate))
 
-	resources, err := r.deployer.Resources("")
+	resources, err := r.deployer.Resources(&config.MUODeploymentConfig{})
 	if err != nil {
 		return err
 	}
diff --git a/pkg/operator/controllers/muo/staticresources/config.yaml b/pkg/operator/controllers/muo/staticresources/config.yaml
new file mode 100644
index 000000000..3a95b6280
--- /dev/null
+++ b/pkg/operator/controllers/muo/staticresources/config.yaml
@@ -0,0 +1,37 @@
+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
+      - openshift-azure-logging
diff --git a/pkg/operator/mocks/muo/muo.go b/pkg/operator/mocks/muo/muo.go
index ddb062ae0..17d3f141a 100644
--- a/pkg/operator/mocks/muo/muo.go
+++ b/pkg/operator/mocks/muo/muo.go
@@ -12,6 +12,7 @@ import (
 	runtime "k8s.io/apimachinery/pkg/runtime"
 
 	v1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
+	config "github.com/Azure/ARO-RP/pkg/operator/controllers/muo/config"
 )
 
 // MockDeployer is a mock of Deployer interface.
@@ -38,17 +39,17 @@ func (m *MockDeployer) EXPECT() *MockDeployerMockRecorder {
 }
 
 // CreateOrUpdate mocks base method.
-func (m *MockDeployer) CreateOrUpdate(arg0 context.Context, arg1 *v1alpha1.Cluster) error {
+func (m *MockDeployer) CreateOrUpdate(arg0 context.Context, arg1 *v1alpha1.Cluster, arg2 *config.MUODeploymentConfig) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "CreateOrUpdate", arg0, arg1)
+	ret := m.ctrl.Call(m, "CreateOrUpdate", arg0, arg1, arg2)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
 // CreateOrUpdate indicates an expected call of CreateOrUpdate.
-func (mr *MockDeployerMockRecorder) CreateOrUpdate(arg0, arg1 interface{}) *gomock.Call {
+func (mr *MockDeployerMockRecorder) CreateOrUpdate(arg0, arg1, arg2 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdate", reflect.TypeOf((*MockDeployer)(nil).CreateOrUpdate), arg0, arg1)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdate", reflect.TypeOf((*MockDeployer)(nil).CreateOrUpdate), arg0, arg1, arg2)
 }
 
 // IsReady mocks base method.
@@ -81,7 +82,7 @@ func (mr *MockDeployerMockRecorder) Remove(arg0 interface{}) *gomock.Call {
 }
 
 // Resources mocks base method.
-func (m *MockDeployer) Resources(arg0 string) ([]runtime.Object, error) {
+func (m *MockDeployer) Resources(arg0 *config.MUODeploymentConfig) ([]runtime.Object, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Resources", arg0)
 	ret0, _ := ret[0].([]runtime.Object)


Test patch:
diff --git a/pkg/operator/controllers/muo/deploy_test.go b/pkg/operator/controllers/muo/deploy_test.go
index 400cfd7e6..621688b5d 100644
--- a/pkg/operator/controllers/muo/deploy_test.go
+++ b/pkg/operator/controllers/muo/deploy_test.go
@@ -6,17 +6,20 @@ package muo
 import (
 	"context"
 	"errors"
+	"strings"
 	"testing"
 
 	"github.com/go-test/deep"
 	"github.com/golang/mock/gomock"
 	appsv1 "k8s.io/api/apps/v1"
+	corev1 "k8s.io/api/core/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"
+	"github.com/Azure/ARO-RP/pkg/operator/controllers/muo/config"
 	mock_dynamichelper "github.com/Azure/ARO-RP/pkg/util/mocks/dynamichelper"
 )
 
@@ -29,18 +32,13 @@ func TestDeployCreateOrUpdateCorrectKinds(t *testing.T) {
 		ObjectMeta: metav1.ObjectMeta{
 			Name: arov1alpha1.SingletonClusterName,
 		},
-		Spec: arov1alpha1.ClusterSpec{
-			OperatorFlags: arov1alpha1.OperatorFlags{
-				controllerPullSpec: 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
+	// and capture any deployments so that we can check the pullspec
 	var deployments []*appsv1.Deployment
 	deployedObjects := make(map[string]int)
 	check := func(ctx context.Context, objs ...kruntime.Object) error {
@@ -50,6 +48,9 @@ func TestDeployCreateOrUpdateCorrectKinds(t *testing.T) {
 			if err != nil {
 				return err
 			}
+			if d, ok := i.(*appsv1.Deployment); ok {
+				deployments = append(deployments, d)
+			}
 			deployedObjects[kind] = deployedObjects[kind] + 1
 		}
 		return nil
@@ -57,7 +58,7 @@ func TestDeployCreateOrUpdateCorrectKinds(t *testing.T) {
 	dh.EXPECT().Ensure(gomock.Any(), gomock.Any()).Do(check).Return(nil)
 
 	deployer := newDeployer(k8scli, dh)
-	err := deployer.CreateOrUpdate(context.Background(), cluster)
+	err := deployer.CreateOrUpdate(context.Background(), cluster, &config.MUODeploymentConfig{Pullspec: setPullSpec})
 	if err != nil {
 		t.Error(err)
 	}
@@ -65,7 +66,7 @@ func TestDeployCreateOrUpdateCorrectKinds(t *testing.T) {
 	// We expect these numbers of resources to be created
 	expectedKinds := map[string]int{
 		"ClusterRole":              1,
-		"ConfigMap":                1,
+		"ConfigMap":                2,
 		"ClusterRoleBinding":       1,
 		"CustomResourceDefinition": 1,
 		"Deployment":               1,
@@ -98,11 +99,6 @@ func TestDeployCreateOrUpdateSetsOwnerReferences(t *testing.T) {
 		ObjectMeta: metav1.ObjectMeta{
 			Name: arov1alpha1.SingletonClusterName,
 		},
-		Spec: arov1alpha1.ClusterSpec{
-			OperatorFlags: arov1alpha1.OperatorFlags{
-				controllerPullSpec: setPullSpec,
-			},
-		},
 	}
 
 	k8scli := fake.NewSimpleClientset()
@@ -134,7 +130,7 @@ func TestDeployCreateOrUpdateSetsOwnerReferences(t *testing.T) {
 	dh.EXPECT().Ensure(gomock.Any(), gomock.Any()).Do(check).Return(nil)
 
 	deployer := newDeployer(k8scli, dh)
-	err := deployer.CreateOrUpdate(context.Background(), cluster)
+	err := deployer.CreateOrUpdate(context.Background(), cluster, &config.MUODeploymentConfig{Pullspec: setPullSpec})
 	if err != nil {
 		t.Error(err)
 	}
@@ -224,3 +220,133 @@ func TestDeployIsReadyMissing(t *testing.T) {
 	}
 
 }
+
+func TestDeployConfig(t *testing.T) {
+	controller := gomock.NewController(t)
+	defer controller.Finish()
+
+	cluster := &arov1alpha1.Cluster{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: arov1alpha1.SingletonClusterName,
+		},
+	}
+
+	tests := []struct {
+		name             string
+		deploymentConfig *config.MUODeploymentConfig
+		expected         []string
+	}{
+		{
+			name:             "local",
+			deploymentConfig: &config.MUODeploymentConfig{EnableConnected: false},
+			expected: []string{
+				"configManager:",
+				"  localConfigName: managed-upgrade-config",
+				"  source: LOCAL",
+				"  watchInterval: 1",
+				"healthCheck:",
+				"  ignoredCriticals:",
+				"  - PrometheusRuleFailures",
+				"  - CannotRetrieveUpdates",
+				"  - FluentdNodeDown",
+				"  ignoredNamespaces:",
+				"  - openshift-logging",
+				"  - openshift-redhat-marketplace",
+				"  - openshift-operators",
+				"  - openshift-user-workload-monitoring",
+				"  - openshift-pipelines",
+				"  - openshift-azure-logging",
+				"maintenance:",
+				"  controlPlaneTime: 90",
+				"  ignoredAlerts:",
+				"    controlPlaneCriticals:",
+				"    - ClusterOperatorDown",
+				"    - ClusterOperatorDegraded",
+				"nodeDrain:",
+				"  expectedNodeDrainTime: 8",
+				"  timeOut: 45",
+				"scale:",
+				"  timeOut: 30",
+				"upgradeWindow:",
+				"  delayTrigger: 30",
+				"  timeOut: 120",
+				"",
+			},
+		},
+		{
+			name:             "connected",
+			deploymentConfig: &config.MUODeploymentConfig{EnableConnected: true, OCMBaseURL: "https://example.com"},
+			expected: []string{
+				"configManager:",
+				"  ocmBaseUrl: https://example.com",
+				"  source: OCM",
+				"  watchInterval: 1",
+				"healthCheck:",
+				"  ignoredCriticals:",
+				"  - PrometheusRuleFailures",
+				"  - CannotRetrieveUpdates",
+				"  - FluentdNodeDown",
+				"  ignoredNamespaces:",
+				"  - openshift-logging",
+				"  - openshift-redhat-marketplace",
+				"  - openshift-operators",
+				"  - openshift-user-workload-monitoring",
+				"  - openshift-pipelines",
+				"  - openshift-azure-logging",
+				"maintenance:",
+				"  controlPlaneTime: 90",
+				"  ignoredAlerts:",
+				"    controlPlaneCriticals:",
+				"    - ClusterOperatorDown",
+				"    - ClusterOperatorDegraded",
+				"nodeDrain:",
+				"  expectedNodeDrainTime: 8",
+				"  timeOut: 45",
+				"scale:",
+				"  timeOut: 30",
+				"upgradeWindow:",
+				"  delayTrigger: 30",
+				"  timeOut: 120",
+				"",
+			},
+		},
+	}
+	for _, tt := range tests {
+		k8scli := fake.NewSimpleClientset()
+		dh := mock_dynamichelper.NewMockInterface(controller)
+
+		// When the DynamicHelper is called, capture configmaps to inspect them
+		var configs []*corev1.ConfigMap
+		check := func(ctx context.Context, objs ...kruntime.Object) error {
+			for _, i := range objs {
+				if cm, ok := i.(*corev1.ConfigMap); ok {
+					configs = append(configs, cm)
+				}
+
+			}
+			return nil
+		}
+		dh.EXPECT().Ensure(gomock.Any(), gomock.Any()).Do(check).Return(nil)
+
+		deployer := newDeployer(k8scli, dh)
+		err := deployer.CreateOrUpdate(context.Background(), cluster, tt.deploymentConfig)
+		if err != nil {
+			t.Error(err)
+		}
+
+		foundConfig := false
+		for _, cms := range configs {
+			if cms.Name == "managed-upgrade-operator-config" && cms.Namespace == "openshift-managed-upgrade-operator" {
+				foundConfig = true
+				errs := deep.Equal(tt.expected, strings.Split(cms.Data["config.yaml"], "\n"))
+				for _, e := range errs {
+					t.Error(e)
+				}
+			}
+		}
+
+		if !foundConfig {
+			t.Error("MUO config was not found")
+		}
+	}
+}
diff --git a/pkg/operator/controllers/muo/muo_controller_test.go b/pkg/operator/controllers/muo/muo_controller_test.go
index 404e5323b..c5e6d07f9 100644
--- a/pkg/operator/controllers/muo/muo_controller_test.go
+++ b/pkg/operator/controllers/muo/muo_controller_test.go
@@ -6,16 +6,18 @@ package muo
 import (
 	"context"
 	"errors"
-	"strconv"
 	"testing"
 	"time"
 
 	"github.com/golang/mock/gomock"
+	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/kubernetes/fake"
 	"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"
+	"github.com/Azure/ARO-RP/pkg/operator/controllers/muo/config"
 	mock_muo "github.com/Azure/ARO-RP/pkg/operator/mocks/muo"
 )
 
@@ -23,66 +25,176 @@ func TestMUOReconciler(t *testing.T) {
 	tests := []struct {
 		name  string
 		mocks func(*mock_muo.MockDeployer, *arov1alpha1.Cluster)
-		// feature flag options
-		enabled bool
-		managed string
+		flags arov1alpha1.OperatorFlags
+		// connected MUO -- cluster pullsecret
+		pullsecret string
 		// errors
 		wantErr string
 	}{
 		{
-			name:    "disabled",
-			enabled: false,
-			managed: "false",
+			name: "disabled",
+			flags: arov1alpha1.OperatorFlags{
+				controllerEnabled:  "false",
+				controllerManaged:  "false",
+				controllerPullSpec: "wonderfulPullspec",
+			},
+		},
+		{
+			name: "managed",
+			flags: arov1alpha1.OperatorFlags{
+				controllerEnabled:  "true",
+				controllerManaged:  "true",
+				controllerPullSpec: "wonderfulPullspec",
+			},
+			mocks: func(md *mock_muo.MockDeployer, cluster *arov1alpha1.Cluster) {
+				expectedConfig := &config.MUODeploymentConfig{
+					Pullspec: "wonderfulPullspec",
+				}
+				md.EXPECT().CreateOrUpdate(gomock.Any(), cluster, expectedConfig).Return(nil)
+				md.EXPECT().IsReady(gomock.Any()).Return(true, nil)
+			},
+		},
+		{
+			name: "managed, no pullspec",
+			flags: arov1alpha1.OperatorFlags{
+				controllerEnabled: "true",
+				controllerManaged: "true",
+			},
+			wantErr: "missing pullspec",
+		},
+		{
+			name: "managed, OCM allowed but no pull secret",
+			flags: arov1alpha1.OperatorFlags{
+				controllerEnabled:  "true",
+				controllerManaged:  "true",
+				controllerAllowOCM: "true",
+				controllerPullSpec: "wonderfulPullspec",
+			},
+			pullsecret: "{\"auths\": {}}",
+			mocks: func(md *mock_muo.MockDeployer, cluster *arov1alpha1.Cluster) {
+				expectedConfig := &config.MUODeploymentConfig{
+					Pullspec:        "wonderfulPullspec",
+					EnableConnected: false,
+				}
+				md.EXPECT().CreateOrUpdate(gomock.Any(), cluster, expectedConfig).Return(nil)
+				md.EXPECT().IsReady(gomock.Any()).Return(true, nil)
+			},
+		},
+		{
+			name: "managed, OCM allowed but mangled pullsecret",
+			flags: arov1alpha1.OperatorFlags{
+				controllerEnabled:  "true",
+				controllerManaged:  "true",
+				controllerAllowOCM: "true",
+				controllerPullSpec: "wonderfulPullspec",
+			},
+			pullsecret: "i'm a little json, short and stout",
+			mocks: func(md *mock_muo.MockDeployer, cluster *arov1alpha1.Cluster) {
+				expectedConfig := &config.MUODeploymentConfig{
+					Pullspec:        "wonderfulPullspec",
+					EnableConnected: false,
+				}
+				md.EXPECT().CreateOrUpdate(gomock.Any(), cluster, expectedConfig).Return(nil)
+				md.EXPECT().IsReady(gomock.Any()).Return(true, nil)
+			},
+		},
+		{
+			name: "managed, OCM connected mode",
+			flags: arov1alpha1.OperatorFlags{
+				controllerEnabled:  "true",
+				controllerManaged:  "true",
+				controllerAllowOCM: "true",
+				controllerPullSpec: "wonderfulPullspec",
+			},
+			pullsecret: "{\"auths\": {\"" + pullSecretOCMKey + "\": {\"auth\": \"secret value\"}}}",
+			mocks: func(md *mock_muo.MockDeployer, cluster *arov1alpha1.Cluster) {
+				expectedConfig := &config.MUODeploymentConfig{
+					Pullspec:        "wonderfulPullspec",
+					EnableConnected: true,
+					OCMBaseURL:      "https://api.openshift.com",
+				}
+				md.EXPECT().CreateOrUpdate(gomock.Any(), cluster, expectedConfig).Return(nil)
+				md.EXPECT().IsReady(gomock.Any()).Return(true, nil)
+			},
 		},
 		{
-			name:    "managed",
-			enabled: true,
-			managed: "true",
+			name: "managed, OCM connected mode, custom OCM URL",
+			flags: arov1alpha1.OperatorFlags{
+				controllerEnabled:    "true",
+				controllerManaged:    "true",
+				controllerAllowOCM:   "true",
+				controllerOcmBaseURL: "https://example.com",
+				controllerPullSpec:   "wonderfulPullspec",
+			},
+			pullsecret: "{\"auths\": {\"" + pullSecretOCMKey + "\": {\"auth\": \"secret value\"}}}",
 			mocks: func(md *mock_muo.MockDeployer, cluster *arov1alpha1.Cluster) {
-				md.EXPECT().CreateOrUpdate(gomock.Any(), cluster).Return(nil)
+				expectedConfig := &config.MUODeploymentConfig{
+					Pullspec:        "wonderfulPullspec",
+					EnableConnected: true,
+					OCMBaseURL:      "https://example.com",
+				}
+				md.EXPECT().CreateOrUpdate(gomock.Any(), cluster, expectedConfig).Return(nil)
 				md.EXPECT().IsReady(gomock.Any()).Return(true, nil)
 			},
 		},
 		{
-			name:    "managed, MUO does not become ready",
-			enabled: true,
-			managed: "true",
+			name: "managed, MUO does not become ready",
+			flags: arov1alpha1.OperatorFlags{
+				controllerEnabled:  "true",
+				controllerManaged:  "true",
+				controllerPullSpec: "wonderfulPullspec",
+			},
 			mocks: func(md *mock_muo.MockDeployer, cluster *arov1alpha1.Cluster) {
-				md.EXPECT().CreateOrUpdate(gomock.Any(), cluster).Return(nil)
+				expectedConfig := &config.MUODeploymentConfig{
+					Pullspec: "wonderfulPullspec",
+				}
+				md.EXPECT().CreateOrUpdate(gomock.Any(), cluster, expectedConfig).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",
+			name: "managed, CreateOrUpdate() fails",
+			flags: arov1alpha1.OperatorFlags{
+				controllerEnabled:  "true",
+				controllerManaged:  "true",
+				controllerPullSpec: "wonderfulPullspec",
+			},
 			mocks: func(md *mock_muo.MockDeployer, cluster *arov1alpha1.Cluster) {
-				md.EXPECT().CreateOrUpdate(gomock.Any(), cluster).Return(errors.New("failed ensure"))
+				md.EXPECT().CreateOrUpdate(gomock.Any(), cluster, gomock.AssignableToTypeOf(&config.MUODeploymentConfig{})).Return(errors.New("failed ensure"))
 			},
 			wantErr: "failed ensure",
 		},
 		{
-			name:    "managed=false (removal)",
-			enabled: true,
-			managed: "false",
+			name: "managed=false (removal)",
+			flags: arov1alpha1.OperatorFlags{
+				controllerEnabled:  "true",
+				controllerManaged:  "false",
+				controllerPullSpec: "wonderfulPullspec",
+			},
 			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",
+			name: "managed=false (removal), Remove() fails",
+			flags: arov1alpha1.OperatorFlags{
+				controllerEnabled:  "true",
+				controllerManaged:  "false",
+				controllerPullSpec: "wonderfulPullspec",
+			},
 			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: "",
+			name: "managed=blank (no action)",
+			flags: arov1alpha1.OperatorFlags{
+				controllerEnabled:  "true",
+				controllerManaged:  "",
+				controllerPullSpec: "wonderfulPullspec",
+			},
 		},
 	}
 	for _, tt := range tests {
@@ -95,25 +207,35 @@ func TestMUOReconciler(t *testing.T) {
 					Name: arov1alpha1.SingletonClusterName,
 				},
 				Spec: arov1alpha1.ClusterSpec{
-					OperatorFlags: arov1alpha1.OperatorFlags{
-						controllerEnabled:  strconv.FormatBool(tt.enabled),
-						controllerPullSpec: "wonderfulPullspec",
-					},
+					OperatorFlags: tt.flags,
 				},
 			}
-			// nil and empty string are valid values, be careful to preserve them
-			if tt.managed != "" {
-				cluster.Spec.OperatorFlags[controllerManaged] = tt.managed
-			}
 			arocli := arofake.NewSimpleClientset(cluster)
+			kubecli := fake.NewSimpleClientset()
 			deployer := mock_muo.NewMockDeployer(controller)
 
+			if tt.pullsecret != "" {
+				_, err := kubecli.CoreV1().Secrets(pullSecretName.Namespace).Create(context.Background(),
+					&corev1.Secret{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:      pullSecretName.Name,
+							Namespace: pullSecretName.Namespace,
+						},
+						Data: map[string][]byte{corev1.DockerConfigJsonKey: []byte(tt.pullsecret)},
+					},
+					metav1.CreateOptions{})
+				if err != nil {
+					t.Fatal(err)
+				}
+			}
+
 			if tt.mocks != nil {
 				tt.mocks(deployer, cluster)
 			}
 
 			r := &Reconciler{
 				arocli:            arocli,
+				kubernetescli:     kubecli,
 				deployer:          deployer,
 				readinessTimeout:  0 * time.Second,
 				readinessPollTime: 1 * time.Second,