Repository: Azure/ARO-RP
Pull request #2215 (2022-06-29 04:41:40)

[M5] Initial storage of ClusterDocument in Hive
### Which issue this PR addresses:

Part of M5 work

### What this PR does / why we need it:

Proof of concept for adding ClusterDocument to trigger the installer, currently a WIP and proof of concept



github-actions[bot]: Please rebase pull request.
github-actions[bot]: Please rebase pull request.
github-actions[bot]: Please rebase pull request.
github-actions[bot]: Please rebase pull request.
m1kola: @hawkowl I'm fixing the [cluster doc patch issue](https://github.com/Azure/ARO-RP/pull/2215#discussion_r934123823) in https://github.com/Azure/ARO-RP/pull/2289. As we have a broken master (it is disabled in prod, but breaks development for me and probably others).

I'm not passing cluster UUID or sub id into the `clusterManager` struct becase as mentioned in [this comment](https://github.com/Azure/ARO-RP/pull/2215#discussion_r934560043) we can get them in CreateOrUpdate and other places by getting them from the method args.

Let's probably settle (at least for now) on:
* Adding AKS/Hive/RP related stuff in the `clusterManager` struct
* Passing anything related to the cluster/sub via args

github-actions[bot]: Please rebase pull request.
hawkowl: @m1kola With the upstream installer, we'll move to creating "extra manifests" that let us specify things like the InfraID. But, we still can't set the InfraID in the ClusterDocument, we pass it to the Installer via the extra manifests option and then it will spit it out for Hive. We're not doing this right now since the manifests are all configmaps, not secrets, but we'll need to do more to get the upstream installer working in any case so I'm not terribly fussed about tackling that separately.
hawkowl: /azp run ci
azure-pipelines[bot]: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
hawkowl: /azp run e2e
azure-pipelines[bot]: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>

Patch:
diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go
index 0e6d5decf..97a23ea1e 100644
--- a/pkg/cluster/cluster.go
+++ b/pkg/cluster/cluster.go
@@ -94,6 +94,7 @@ type manager struct {
 	arocli           aroclient.Interface
 	imageregistrycli imageregistryclient.Interface
 
+	installViaHive     bool
 	hiveClusterManager hive.ClusterManager
 
 	aroOperatorDeployer deploy.Operator
@@ -124,6 +125,11 @@ func New(ctx context.Context, log *logrus.Entry, _env env.Interface, db database
 
 	storage := storage.NewManager(_env, r.SubscriptionID, fpAuthorizer)
 
+	installViaHive, err := _env.LiveConfig().InstallViaHive(ctx)
+	if err != nil {
+		return nil, err
+	}
+
 	// TODO(hive): always set hiveClusterManager once we have Hive everywhere in prod and dev
 	var hr hive.ClusterManager
 	if hiveRestConfig != nil {
@@ -167,6 +173,7 @@ func New(ctx context.Context, log *logrus.Entry, _env env.Interface, db database
 		subnet:  subnet.NewManager(_env.Environment(), r.SubscriptionID, fpAuthorizer),
 		graph:   graph.NewManager(log, aead, storage),
 
+		installViaHive:     installViaHive,
 		hiveClusterManager: hr,
 	}, nil
 }
diff --git a/pkg/cluster/hive.go b/pkg/cluster/hive.go
index 32e0a91c9..74a0f5146 100644
--- a/pkg/cluster/hive.go
+++ b/pkg/cluster/hive.go
@@ -11,10 +11,12 @@ import (
 
 func (m *manager) hiveCreateNamespace(ctx context.Context) error {
 	m.log.Info("creating a namespace in the hive cluster")
-	if m.hiveClusterManager == nil {
-		// TODO(hive): remove this once we have Hive everywhere
-		m.log.Info("skipping: no hive cluster manager")
-		return nil
+	if !m.installViaHive {
+		if m.hiveClusterManager == nil {
+			// TODO(hive): remove this once we have Hive everywhere
+			m.log.Info("skipping: no hive cluster manager")
+			return nil
+		}
 	}
 
 	if m.doc.OpenShiftCluster.Properties.HiveProfile.Namespace != "" {
@@ -47,24 +49,33 @@ func (m *manager) hiveEnsureResources(ctx context.Context) error {
 
 func (m *manager) hiveClusterDeploymentReady(ctx context.Context) (bool, error) {
 	m.log.Info("waiting for cluster deployment to become ready")
-	if m.hiveClusterManager == nil {
-		// TODO(hive): remove this if once we have Hive everywhere
-		m.log.Info("skipping: no hive cluster manager")
-		return true, nil
+	if !m.installViaHive {
+		if m.hiveClusterManager == nil {
+			// TODO(hive): remove this if once we have Hive everywhere
+			m.log.Info("skipping: no hive cluster manager")
+			return true, nil
+		}
 	}
 
-	return m.hiveClusterManager.IsClusterDeploymentReady(ctx, m.doc.OpenShiftCluster.Properties.HiveProfile.Namespace)
+	return m.hiveClusterManager.IsClusterDeploymentReady(ctx, m.doc)
+}
+
+func (m *manager) hiveClusterInstallationComplete(ctx context.Context) (bool, error) {
+	m.log.Info("waiting for cluster installation to complete")
+	return m.hiveClusterManager.IsClusterInstallationComplete(ctx, m.doc)
 }
 
 func (m *manager) hiveResetCorrelationData(ctx context.Context) error {
 	m.log.Info("resetting correlation data for hive")
-	if m.hiveClusterManager == nil {
-		// TODO(hive): remove this if once we have Hive everywhere
-		m.log.Info("skipping: no hive cluster manager")
-		return nil
+	if !m.installViaHive {
+		if m.hiveClusterManager == nil {
+			// TODO(hive): remove this if once we have Hive everywhere
+			m.log.Info("skipping: no hive cluster manager")
+			return nil
+		}
 	}
 
-	return m.hiveClusterManager.ResetCorrelationData(ctx, m.doc.OpenShiftCluster.Properties.HiveProfile.Namespace)
+	return m.hiveClusterManager.ResetCorrelationData(ctx, m.doc)
 }
 
 func (m *manager) hiveDeleteResources(ctx context.Context) error {
@@ -81,5 +92,5 @@ func (m *manager) hiveDeleteResources(ctx context.Context) error {
 		return nil
 	}
 
-	return m.hiveClusterManager.Delete(ctx, namespace)
+	return m.hiveClusterManager.Delete(ctx, m.doc)
 }
diff --git a/pkg/cluster/install.go b/pkg/cluster/install.go
index 125d32e84..73476232c 100644
--- a/pkg/cluster/install.go
+++ b/pkg/cluster/install.go
@@ -5,6 +5,7 @@ package cluster
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"time"
 
@@ -141,54 +142,100 @@ func (m *manager) Update(ctx context.Context) error {
 	return m.runSteps(ctx, steps)
 }
 
-// callInstaller initialises and calls the Installer code. This will later be replaced with a call into Hive.
-func (m *manager) callInstaller(ctx context.Context) error {
+func (m *manager) runIntegratedInstaller(ctx context.Context) error {
 	i := installer.NewInstaller(m.log, m.env, m.doc.ID, m.doc.OpenShiftCluster, m.subscriptionDoc.Subscription, m.fpAuthorizer, m.deployments, m.graph)
 	return i.Install(ctx)
 }
 
-// Install installs an ARO cluster
-func (m *manager) Install(ctx context.Context) error {
-	steps := map[api.InstallPhase][]steps.Step{
-		api.InstallPhaseBootstrap: {
-			steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.validateResources)),
-			steps.Action(m.ensureACRToken),
-			steps.Action(m.ensureInfraID),
-			steps.Action(m.ensureSSHKey),
-			steps.Action(m.ensureStorageSuffix),
-			steps.Action(m.populateMTUSize),
-
-			steps.Action(m.createDNS),
-			steps.Action(m.initializeClusterSPClients), // must run before clusterSPObjectID
-			steps.Action(m.clusterSPObjectID),
-			steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.ensureResourceGroup)),
-			steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.enableServiceEndpoints)),
-			steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.setMasterSubnetPolicies)),
-			steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.deployStorageTemplate)),
-			steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.attachNSGs)),
-			steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.updateAPIIPEarly)),
-			steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.createOrUpdateRouterIPEarly)),
-			steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.ensureGatewayCreate)),
-			steps.Action(m.createAPIServerPrivateEndpoint),
-			steps.Action(m.createCertificates),
-
-			// Run installer. For M5/M6 we will persist the graph inside the
-			// installer code since it's easier, but in the future, this data
-			// should be collected from Hive's outputs where needed.
-			steps.Action(m.callInstaller),
+func (m *manager) runHiveInstaller(ctx context.Context) error {
+	// TODO: Load from M6 database
+	installerPullspec, err := m.env.LiveConfig().DefaultInstallerPullSpec(ctx)
+	if err != nil {
+		return err
+	}
+
+	v := &api.OpenShiftVersion{
+		Version:           version.InstallStream.Version.String(),
+		OpenShiftPullspec: version.InstallStream.PullSpec,
+		InstallerPullspec: installerPullspec,
+	}
+
+	// Run installer. For M5/M6 we will persist the graph inside the installer
+	// code since it's easier, but in the future, this data should be collected
+	// from Hive's outputs where needed.
+	return m.hiveClusterManager.Install(ctx, m.subscriptionDoc, m.doc, v)
+}
+
+func (m *manager) bootstrap() []steps.Step {
+	s := []steps.Step{
+		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.validateResources)),
+		steps.Action(m.ensureACRToken),
+		steps.Action(m.ensureInfraID),
+		steps.Action(m.ensureSSHKey),
+		steps.Action(m.ensureStorageSuffix),
+		steps.Action(m.populateMTUSize),
+
+		steps.Action(m.createDNS),
+		steps.Action(m.initializeClusterSPClients), // must run before clusterSPObjectID
+		steps.Action(m.clusterSPObjectID),
+		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.ensureResourceGroup)),
+		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.enableServiceEndpoints)),
+		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.setMasterSubnetPolicies)),
+		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.deployStorageTemplate)),
+		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.attachNSGs)),
+		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.updateAPIIPEarly)),
+		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.createOrUpdateRouterIPEarly)),
+		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.ensureGatewayCreate)),
+		steps.Action(m.createAPIServerPrivateEndpoint),
+		steps.Action(m.createCertificates),
+
+		// We will always need a Hive namespace, whether we are installing
+		// via Hive or adopting
+		steps.Action(m.hiveCreateNamespace),
+	}
+
+	if m.installViaHive {
+		s = append(s,
+			steps.Action(m.hiveCreateNamespace),
+			steps.Action(m.runHiveInstaller),
+			// Give Hive 60 minutes to install the cluster, since this includes
+			// all of bootstrapping being complete
+			steps.Condition(m.hiveClusterInstallationComplete, 60*time.Minute, true),
+			steps.Condition(m.hiveClusterDeploymentReady, 5*time.Minute, true),
+		)
+	} else {
+		s = append(s,
+			steps.Action(m.runIntegratedInstaller),
 			steps.Action(m.hiveCreateNamespace),
 			steps.Action(m.hiveEnsureResources),
 			steps.Condition(m.hiveClusterDeploymentReady, 5*time.Minute, true),
-			steps.Action(m.hiveResetCorrelationData),
+		)
+	}
 
-			steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.generateKubeconfigs)),
-			steps.Action(m.ensureBillingRecord),
-			steps.Action(m.initializeKubernetesClients),
-			steps.Action(m.initializeOperatorDeployer), // depends on kube clients
-			steps.Condition(m.apiServersReady, 30*time.Minute, true),
-			steps.Action(m.ensureAROOperator),
-			steps.Action(m.incrInstallPhase),
-		},
+	s = append(s,
+		// Reset correlation data whether adopting or installing via Hive
+		steps.Action(m.hiveResetCorrelationData),
+
+		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.generateKubeconfigs)),
+		steps.Action(m.ensureBillingRecord),
+		steps.Action(m.initializeKubernetesClients),
+		steps.Action(m.initializeOperatorDeployer), // depends on kube clients
+		steps.Condition(m.apiServersReady, 30*time.Minute, true),
+		steps.Action(m.ensureAROOperator),
+		steps.Action(m.incrInstallPhase),
+	)
+
+	return s
+}
+
+// Install installs an ARO cluster
+func (m *manager) Install(ctx context.Context) error {
+	if m.installViaHive && m.hiveClusterManager == nil {
+		return errors.New("installViaHive was requested but hiveClusterManager is unavailable")
+	}
+
+	steps := map[api.InstallPhase][]steps.Step{
+		api.InstallPhaseBootstrap: m.bootstrap(),
 		api.InstallPhaseRemoveBootstrap: {
 			steps.Action(m.initializeKubernetesClients),
 			steps.Action(m.initializeOperatorDeployer), // depends on kube clients
diff --git a/pkg/hive/install.go b/pkg/hive/install.go
new file mode 100644
index 000000000..503bb60e3
--- /dev/null
+++ b/pkg/hive/install.go
@@ -0,0 +1,200 @@
+package hive
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"context"
+	"encoding/json"
+	"io/ioutil"
+
+	hivev1 "github.com/openshift/hive/apis/hive/v1"
+	hivev1azure "github.com/openshift/hive/apis/hive/v1/azure"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	kruntime "k8s.io/apimachinery/pkg/runtime"
+
+	"github.com/Azure/ARO-RP/pkg/api"
+	"github.com/Azure/ARO-RP/pkg/util/dynamichelper"
+	utillog "github.com/Azure/ARO-RP/pkg/util/log"
+)
+
+const (
+	envSecretsName        = "aro-env-secret"
+	pullsecretSecretName  = "aro-pullsecret"
+	installConfigName     = "aro-installconfig"
+	installConfigTemplate = `apiVersion: v1
+platform:
+  azure:
+    region: "%s"
+`
+)
+
+func makeEnvSecret(name string) corev1.EnvVar {
+	return corev1.EnvVar{
+		Name: "ARO_" + name,
+		ValueFrom: &corev1.EnvVarSource{
+			SecretKeyRef: &corev1.SecretKeySelector{
+				LocalObjectReference: corev1.LocalObjectReference{
+					Name: envSecretsName,
+				},
+				Key: "ARO_" + name,
+			},
+		},
+	}
+}
+
+func (c *clusterManager) Install(ctx context.Context, sub *api.SubscriptionDocument, doc *api.OpenShiftClusterDocument, version *api.OpenShiftVersion) error {
+	sppSecret, err := servicePrincipalSecretForInstall(doc.OpenShiftCluster, sub, c.env.IsLocalDevelopmentMode())
+	if err != nil {
+		return err
+	}
+
+	psSecret, err := pullsecretSecret(doc.OpenShiftCluster.Properties.HiveProfile.Namespace, doc.OpenShiftCluster)
+	if err != nil {
+		return err
+	}
+
+	cd := c.clusterDeploymentForInstall(doc, version, c.env.IsLocalDevelopmentMode())
+
+	// Enrich the cluster deployment with the correlation data so that logs are
+	// properly annotated
+	err = utillog.EnrichHiveWithCorrelationData(cd, doc.CorrelationData)
+	if err != nil {
+		return err
+	}
+	err = utillog.EnrichHiveWithResourceID(cd, doc.OpenShiftCluster.ID)
+	if err != nil {
+		return err
+	}
+
+	resources := []kruntime.Object{
+		sppSecret,
+		envSecret(doc.OpenShiftCluster.Properties.HiveProfile.Namespace, c.env.IsLocalDevelopmentMode()),
+		psSecret,
+		installConfigCM(doc.OpenShiftCluster.Properties.HiveProfile.Namespace, doc.OpenShiftCluster.Location),
+		cd,
+	}
+
+	err = dynamichelper.Prepare(resources)
+	if err != nil {
+		return err
+	}
+
+	err = c.dh.Ensure(ctx, resources...)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func servicePrincipalSecretForInstall(oc *api.OpenShiftCluster, sub *api.SubscriptionDocument, isDevelopment bool) (*corev1.Secret, error) {
+	clusterSPBytes, err := clusterSPToBytes(sub, oc)
+	if err != nil {
+		return nil, err
+	}
+
+	enc, err := json.Marshal(oc)
+	if err != nil {
+		return nil, err
+	}
+
+	encSub, err := json.Marshal(sub)
+	if err != nil {
+		return nil, err
+	}
+
+	sppSecret := clusterServicePrincipalSecret(oc.Properties.HiveProfile.Namespace, clusterSPBytes)
+	sppSecret.Data["99_aro.json"] = enc
+	sppSecret.Data["99_sub.json"] = encSub
+
+	if isDevelopment {
+		// In development mode, load in the proxy certificates so that clusters
+		// can be accessed from a local (not in Azure) Hive
+		proxyCert, err := ioutil.ReadFile("secrets/proxy.crt")
+		if err != nil {
+			return nil, err
+		}
+
+		proxyClientCert, err := ioutil.ReadFile("secrets/proxy-client.crt")
+		if err != nil {
+			return nil, err
+		}
+
+		proxyClientKey, err := ioutil.ReadFile("secrets/proxy-client.key")
+		if err != nil {
+			return nil, err
+		}
+
+		sppSecret.Data["proxy.crt"] = proxyCert
+		sppSecret.Data["proxy-client.crt"] = proxyClientCert
+		sppSecret.Data["proxy-client.key"] = proxyClientKey
+	}
+
+	return sppSecret, nil
+}
+
+func (c *clusterManager) clusterDeploymentForInstall(doc *api.OpenShiftClusterDocument, version *api.OpenShiftVersion, isDevelopment bool) *hivev1.ClusterDeployment {
+	var envVars = []corev1.EnvVar{
+		{
+			Name:  "ARO_UUID",
+			Value: doc.ID,
+		},
+	}
+
+	if isDevelopment {
+		envVars = append(envVars, corev1.EnvVar{
+			Name:  "ARO_RP_MODE",
+			Value: "development",
+		})
+		for _, i := range devEnvVars {
+			envVars = append(envVars, makeEnvSecret(i))
+		}
+	} else {
+		for _, i := range prodEnvVars {
+			envVars = append(envVars, makeEnvSecret(i))
+		}
+	}
+
+	// Do not set InfraID here, as Hive wants to do that
+	return &hivev1.ClusterDeployment{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      ClusterDeploymentName,
+			Namespace: doc.OpenShiftCluster.Properties.HiveProfile.Namespace,
+			Labels: map[string]string{
+				"hive.openshift.io/cluster-platform": "azure",
+				"hive.openshift.io/cluster-region":   doc.OpenShiftCluster.Location,
+			},
+			Annotations: map[string]string{
+				"hive.openshift.io/try-install-once": "true",
+			},
+		},
+		Spec: hivev1.ClusterDeploymentSpec{
+			BaseDomain:  "",
+			ClusterName: doc.OpenShiftCluster.Name,
+			Platform: hivev1.Platform{
+				Azure: &hivev1azure.Platform{
+					BaseDomainResourceGroupName: doc.OpenShiftCluster.Properties.ClusterProfile.ResourceGroupID,
+					Region:                      doc.OpenShiftCluster.Location,
+					CredentialsSecretRef: corev1.LocalObjectReference{
+						Name: clusterServicePrincipalSecretName,
+					},
+				},
+			},
+			PullSecretRef: &corev1.LocalObjectReference{
+				Name: pullsecretSecretName,
+			},
+			Provisioning: &hivev1.Provisioning{
+				InstallerImageOverride: version.InstallerPullspec,
+				ReleaseImage:           version.OpenShiftPullspec,
+				InstallConfigSecretRef: &corev1.LocalObjectReference{
+					Name: installConfigName,
+				},
+				InstallerEnv: envVars,
+			},
+			PreserveOnDelete: true,
+			ManageDNS:        false,
+		},
+	}
+}
diff --git a/pkg/hive/manager.go b/pkg/hive/manager.go
index d5c43d35c..b093b61a6 100644
--- a/pkg/hive/manager.go
+++ b/pkg/hive/manager.go
@@ -25,10 +25,19 @@ import (
 
 type ClusterManager interface {
 	CreateNamespace(ctx context.Context) (*corev1.Namespace, error)
+
+	// CreateOrUpdate reconciles the ClusterDocument and related secrets for an
+	// existing cluster. This may adopt the cluster (Create) or amend the
+	// existing resources (Update).
 	CreateOrUpdate(ctx context.Context, sub *api.SubscriptionDocument, doc *api.OpenShiftClusterDocument) error
-	Delete(ctx context.Context, namespace string) error
-	IsClusterDeploymentReady(ctx context.Context, namespace string) (bool, error)
-	ResetCorrelationData(ctx context.Context, namespace string) error
+	// Delete removes the cluster from Hive.
+	Delete(ctx context.Context, doc *api.OpenShiftClusterDocument) error
+	// Install creates a ClusterDocument and related secrets for a new cluster
+	// so that it can be provisioned by Hive.
+	Install(ctx context.Context, sub *api.SubscriptionDocument, doc *api.OpenShiftClusterDocument, version *api.OpenShiftVersion) error
+	IsClusterDeploymentReady(ctx context.Context, doc *api.OpenShiftClusterDocument) (bool, error)
+	IsClusterInstallationComplete(ctx context.Context, doc *api.OpenShiftClusterDocument) (bool, error)
+	ResetCorrelationData(ctx context.Context, doc *api.OpenShiftClusterDocument) error
 }
 
 type clusterManager struct {
@@ -112,8 +121,8 @@ func (hr *clusterManager) CreateOrUpdate(ctx context.Context, sub *api.Subscript
 	return nil
 }
 
-func (hr *clusterManager) Delete(ctx context.Context, namespace string) error {
-	err := hr.kubernetescli.CoreV1().Namespaces().Delete(ctx, namespace, metav1.DeleteOptions{})
+func (hr *clusterManager) Delete(ctx context.Context, doc *api.OpenShiftClusterDocument) error {
+	err := hr.kubernetescli.CoreV1().Namespaces().Delete(ctx, doc.OpenShiftCluster.Properties.HiveProfile.Namespace, metav1.DeleteOptions{})
 	if err != nil && kerrors.IsNotFound(err) {
 		return nil
 	}
@@ -121,8 +130,8 @@ func (hr *clusterManager) Delete(ctx context.Context, namespace string) error {
 	return err
 }
 
-func (hr *clusterManager) IsClusterDeploymentReady(ctx context.Context, namespace string) (bool, error) {
-	cd, err := hr.hiveClientset.HiveV1().ClusterDeployments(namespace).Get(ctx, ClusterDeploymentName, metav1.GetOptions{})
+func (hr *clusterManager) IsClusterDeploymentReady(ctx context.Context, doc *api.OpenShiftClusterDocument) (bool, error) {
+	cd, err := hr.hiveClientset.HiveV1().ClusterDeployments(doc.OpenShiftCluster.Properties.HiveProfile.Namespace).Get(ctx, ClusterDeploymentName, metav1.GetOptions{})
 	if err == nil {
 		for _, cond := range cd.Status.Conditions {
 			if cond.Type == hivev1.ClusterReadyCondition && cond.Status == corev1.ConditionTrue {
@@ -133,9 +142,17 @@ func (hr *clusterManager) IsClusterDeploymentReady(ctx context.Context, namespac
 	return false, err
 }
 
-func (hr *clusterManager) ResetCorrelationData(ctx context.Context, namespace string) error {
+func (hr *clusterManager) IsClusterInstallationComplete(ctx context.Context, doc *api.OpenShiftClusterDocument) (bool, error) {
+	cd, err := hr.hiveClientset.HiveV1().ClusterDeployments(doc.OpenShiftCluster.Properties.HiveProfile.Namespace).Get(ctx, ClusterDeploymentName, metav1.GetOptions{})
+	if err == nil {
+		return cd.Spec.Installed, nil
+	}
+	return false, err
+}
+
+func (hr *clusterManager) ResetCorrelationData(ctx context.Context, doc *api.OpenShiftClusterDocument) error {
 	return retry.RetryOnConflict(retry.DefaultRetry, func() error {
-		cd, err := hr.hiveClientset.HiveV1().ClusterDeployments(namespace).Get(ctx, ClusterDeploymentName, metav1.GetOptions{})
+		cd, err := hr.hiveClientset.HiveV1().ClusterDeployments(doc.OpenShiftCluster.Properties.HiveProfile.Namespace).Get(ctx, ClusterDeploymentName, metav1.GetOptions{})
 		if err != nil {
 			return err
 		}
@@ -145,7 +162,7 @@ func (hr *clusterManager) ResetCorrelationData(ctx context.Context, namespace st
 			return err
 		}
 
-		_, err = hr.hiveClientset.HiveV1().ClusterDeployments(namespace).Update(ctx, cd, metav1.UpdateOptions{})
+		_, err = hr.hiveClientset.HiveV1().ClusterDeployments(doc.OpenShiftCluster.Properties.HiveProfile.Namespace).Update(ctx, cd, metav1.UpdateOptions{})
 		return err
 	})
 }
diff --git a/pkg/hive/resources.go b/pkg/hive/resources.go
index 9f3bfcb91..401e8f54e 100644
--- a/pkg/hive/resources.go
+++ b/pkg/hive/resources.go
@@ -4,6 +4,9 @@ package hive
 // Licensed under the Apache License 2.0.
 
 import (
+	"fmt"
+	"os"
+
 	hivev1 "github.com/openshift/hive/apis/hive/v1"
 	hivev1azure "github.com/openshift/hive/apis/hive/v1/azure"
 	corev1 "k8s.io/api/core/v1"
@@ -12,6 +15,7 @@ import (
 
 	"github.com/Azure/ARO-RP/pkg/api"
 	utillog "github.com/Azure/ARO-RP/pkg/util/log"
+	"github.com/Azure/ARO-RP/pkg/util/pullsecret"
 )
 
 // Changing values of these constants most likely would require
@@ -22,6 +26,33 @@ const (
 	clusterServicePrincipalSecretName = "cluster-service-principal-secret"
 )
 
+var (
+	devEnvVars = []string{
+		"AZURE_FP_CLIENT_ID",
+		"AZURE_RP_CLIENT_ID",
+		"AZURE_RP_CLIENT_SECRET",
+		"AZURE_SUBSCRIPTION_ID",
+		"AZURE_TENANT_ID",
+		"DOMAIN_NAME",
+		"KEYVAULT_PREFIX",
+		"LOCATION",
+		"PROXY_HOSTNAME",
+		"PULL_SECRET",
+		"RESOURCEGROUP",
+	}
+	prodEnvVars = []string{
+		"AZURE_FP_CLIENT_ID",
+		"CLUSTER_MDSD_ACCOUNT",
+		"CLUSTER_MDSD_CONFIG_VERSION",
+		"CLUSTER_MDSD_NAMESPACE",
+		"DOMAIN_NAME",
+		"GATEWAY_DOMAINS",
+		"GATEWAY_RESOURCEGROUP",
+		"KEYVAULT_PREFIX",
+		"MDSD_ENVIRONMENT",
+	}
+)
+
 func (hr *clusterManager) resources(sub *api.SubscriptionDocument, doc *api.OpenShiftClusterDocument) ([]kruntime.Object, error) {
 	namespace := doc.OpenShiftCluster.Properties.HiveProfile.Namespace
 	clusterSP, err := clusterSPToBytes(sub, doc.OpenShiftCluster)
@@ -29,7 +60,7 @@ func (hr *clusterManager) resources(sub *api.SubscriptionDocument, doc *api.Open
 		return nil, err
 	}
 
-	cd := clusterDeployment(
+	cd := adoptedClusterDeployment(
 		namespace,
 		doc.OpenShiftCluster.Name,
 		doc.ID,
@@ -79,7 +110,29 @@ func clusterServicePrincipalSecret(namespace string, secret []byte) *corev1.Secr
 	}
 }
 
-func clusterDeployment(namespace, clusterName, clusterID, infraID, location, APIServerPrivateEndpointIP string) *hivev1.ClusterDeployment {
+func envSecret(namespace string, isDevelopment bool) *corev1.Secret {
+	stringdata := map[string]string{}
+
+	if isDevelopment {
+		for _, i := range devEnvVars {
+			stringdata["ARO_"+i] = os.Getenv(i)
+		}
+	} else {
+		for _, i := range prodEnvVars {
+			stringdata["ARO_"+i] = os.Getenv(i)
+		}
+	}
+
+	return &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: namespace,
+			Name:      envSecretsName,
+		},
+		StringData: stringdata,
+	}
+}
+
+func adoptedClusterDeployment(namespace, clusterName, clusterID, infraID, location, APIServerPrivateEndpointIP string) *hivev1.ClusterDeployment {
 	return &hivev1.ClusterDeployment{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      ClusterDeploymentName,
@@ -113,3 +166,38 @@ func clusterDeployment(namespace, clusterName, clusterID, infraID, location, API
 		},
 	}
 }
+
+func pullsecretSecret(namespace string, oc *api.OpenShiftCluster) (*corev1.Secret, error) {
+	pullSecret, err := pullsecret.Build(oc, string(oc.Properties.ClusterProfile.PullSecret))
+	if err != nil {
+		return nil, err
+	}
+	for _, key := range []string{"cloud.openshift.com"} {
+		pullSecret, err = pullsecret.RemoveKey(pullSecret, key)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: namespace,
+			Name:      pullsecretSecretName,
+		},
+		StringData: map[string]string{
+			".dockerconfigjson": pullSecret,
+		},
+	}, nil
+}
+
+func installConfigCM(namespace string, location string) *corev1.Secret {
+	return &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: namespace,
+			Name:      installConfigName,
+		},
+		StringData: map[string]string{
+			"install-config.yaml": fmt.Sprintf(installConfigTemplate, location),
+		},
+	}
+}
diff --git a/pkg/util/liveconfig/dev.go b/pkg/util/liveconfig/dev.go
index 081638db3..58370d679 100644
--- a/pkg/util/liveconfig/dev.go
+++ b/pkg/util/liveconfig/dev.go
@@ -13,7 +13,9 @@ import (
 )
 
 const (
-	HIVE_ENV_VARIABLE = "HIVEKUBECONFIGPATH"
+	HIVE_ENV_VARIABLE               = "HIVEKUBECONFIGPATH"
+	HIVE_INSTALL_ENV_VARIABLE       = "ARO_INSTALL_VIA_HIVE"
+	HIVE_DEFAULT_INSTALLER_VARIABLE = "ARO_HIVE_DEFAULT_INSTALLER_PULLSPEC"
 )
 
 func (d *dev) HiveRestConfig(ctx context.Context, index int) (*rest.Config, error) {
@@ -35,3 +37,15 @@ func (d *dev) HiveRestConfig(ctx context.Context, index int) (*rest.Config, erro
 
 	return restConfig, nil
 }
+
+func (d *dev) InstallViaHive(ctx context.Context) (bool, error) {
+	installViaHive := os.Getenv(HIVE_INSTALL_ENV_VARIABLE)
+	if installViaHive != "" {
+		return true, nil
+	}
+	return false, nil
+}
+
+func (d *dev) DefaultInstallerPullSpec(ctx context.Context) (string, error) {
+	return os.Getenv(HIVE_DEFAULT_INSTALLER_VARIABLE), nil
+}
diff --git a/pkg/util/liveconfig/hive.go b/pkg/util/liveconfig/hive.go
index 65ac67a81..7276f6aeb 100644
--- a/pkg/util/liveconfig/hive.go
+++ b/pkg/util/liveconfig/hive.go
@@ -7,6 +7,7 @@ import (
 	"context"
 	"encoding/base64"
 	"fmt"
+	"os"
 
 	mgmtcontainerservice "github.com/Azure/azure-sdk-for-go/services/containerservice/mgmt/2021-10-01/containerservice"
 	"k8s.io/client-go/rest"
@@ -65,3 +66,17 @@ func (p *prod) HiveRestConfig(ctx context.Context, index int) (*rest.Config, err
 	p.cachedCredentials[index] = parsed
 	return rest.CopyConfig(parsed), nil
 }
+
+func (p *prod) InstallViaHive(ctx context.Context) (bool, error) {
+	// TODO: Replace with RP Live Service Config (KeyVault)
+	installViaHive := os.Getenv(HIVE_INSTALL_ENV_VARIABLE)
+	if installViaHive != "" {
+		return true, nil
+	}
+	return false, nil
+}
+
+func (p *prod) DefaultInstallerPullSpec(ctx context.Context) (string, error) {
+	// TODO: Replace with loading from M6's database always
+	return os.Getenv(HIVE_DEFAULT_INSTALLER_VARIABLE), nil
+}
diff --git a/pkg/util/liveconfig/manager.go b/pkg/util/liveconfig/manager.go
index 576a7e4c5..7a3770ccf 100644
--- a/pkg/util/liveconfig/manager.go
+++ b/pkg/util/liveconfig/manager.go
@@ -14,6 +14,10 @@ import (
 
 type Manager interface {
 	HiveRestConfig(context.Context, int) (*rest.Config, error)
+	InstallViaHive(context.Context) (bool, error)
+
+	// TEMPORARY: Default installation versions for pre-M6 APIs
+	DefaultInstallerPullSpec(context.Context) (string, error)
 }
 
 type dev struct{}
diff --git a/pkg/util/mocks/hive/hive.go b/pkg/util/mocks/hive/hive.go
index 75eb21cf0..e34891b7d 100644
--- a/pkg/util/mocks/hive/hive.go
+++ b/pkg/util/mocks/hive/hive.go
@@ -67,7 +67,7 @@ func (mr *MockClusterManagerMockRecorder) CreateOrUpdate(arg0, arg1, arg2 interf
 }
 
 // Delete mocks base method.
-func (m *MockClusterManager) Delete(arg0 context.Context, arg1 string) error {
+func (m *MockClusterManager) Delete(arg0 context.Context, arg1 *api.OpenShiftClusterDocument) error {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Delete", arg0, arg1)
 	ret0, _ := ret[0].(error)
@@ -80,8 +80,22 @@ func (mr *MockClusterManagerMockRecorder) Delete(arg0, arg1 interface{}) *gomock
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockClusterManager)(nil).Delete), arg0, arg1)
 }
 
+// Install mocks base method.
+func (m *MockClusterManager) Install(arg0 context.Context, arg1 *api.SubscriptionDocument, arg2 *api.OpenShiftClusterDocument, arg3 *api.OpenShiftVersion) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Install", arg0, arg1, arg2, arg3)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Install indicates an expected call of Install.
+func (mr *MockClusterManagerMockRecorder) Install(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Install", reflect.TypeOf((*MockClusterManager)(nil).Install), arg0, arg1, arg2, arg3)
+}
+
 // IsClusterDeploymentReady mocks base method.
-func (m *MockClusterManager) IsClusterDeploymentReady(arg0 context.Context, arg1 string) (bool, error) {
+func (m *MockClusterManager) IsClusterDeploymentReady(arg0 context.Context, arg1 *api.OpenShiftClusterDocument) (bool, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "IsClusterDeploymentReady", arg0, arg1)
 	ret0, _ := ret[0].(bool)
@@ -95,8 +109,23 @@ func (mr *MockClusterManagerMockRecorder) IsClusterDeploymentReady(arg0, arg1 in
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsClusterDeploymentReady", reflect.TypeOf((*MockClusterManager)(nil).IsClusterDeploymentReady), arg0, arg1)
 }
 
+// IsClusterInstallationComplete mocks base method.
+func (m *MockClusterManager) IsClusterInstallationComplete(arg0 context.Context, arg1 *api.OpenShiftClusterDocument) (bool, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "IsClusterInstallationComplete", arg0, arg1)
+	ret0, _ := ret[0].(bool)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// IsClusterInstallationComplete indicates an expected call of IsClusterInstallationComplete.
+func (mr *MockClusterManagerMockRecorder) IsClusterInstallationComplete(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsClusterInstallationComplete", reflect.TypeOf((*MockClusterManager)(nil).IsClusterInstallationComplete), arg0, arg1)
+}
+
 // ResetCorrelationData mocks base method.
-func (m *MockClusterManager) ResetCorrelationData(arg0 context.Context, arg1 string) error {
+func (m *MockClusterManager) ResetCorrelationData(arg0 context.Context, arg1 *api.OpenShiftClusterDocument) error {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "ResetCorrelationData", arg0, arg1)
 	ret0, _ := ret[0].(error)


Test patch:
diff --git a/pkg/cluster/hive_test.go b/pkg/cluster/hive_test.go
index 3a51eb5bf..c6842de3a 100644
--- a/pkg/cluster/hive_test.go
+++ b/pkg/cluster/hive_test.go
@@ -15,7 +15,6 @@ import (
 	corev1 "k8s.io/api/core/v1"
 
 	"github.com/Azure/ARO-RP/pkg/api"
-	"github.com/Azure/ARO-RP/pkg/hive"
 	mock_hive "github.com/Azure/ARO-RP/pkg/util/mocks/hive"
 	testdatabase "github.com/Azure/ARO-RP/test/database"
 )
@@ -25,7 +24,7 @@ func TestHiveClusterDeploymentReady(t *testing.T) {
 
 	for _, tt := range []struct {
 		name  string
-		mocks func(hiveMock *mock_hive.MockClusterManager)
+		mocks func(hiveMock *mock_hive.MockClusterManager, doc *api.OpenShiftClusterDocument)
 		// TODO(hive): remove wantSkip and all test cases with wantSkip=True once we have Hive everywhere
 		wantSkip   bool
 		wantResult bool
@@ -33,22 +32,22 @@ func TestHiveClusterDeploymentReady(t *testing.T) {
 	}{
 		{
 			name: "ready",
-			mocks: func(hiveMock *mock_hive.MockClusterManager) {
-				hiveMock.EXPECT().IsClusterDeploymentReady(gomock.Any(), fakeNamespace).Return(true, nil)
+			mocks: func(hiveMock *mock_hive.MockClusterManager, doc *api.OpenShiftClusterDocument) {
+				hiveMock.EXPECT().IsClusterDeploymentReady(gomock.Any(), doc).Return(true, nil)
 			},
 			wantResult: true,
 		},
 		{
 			name: "not ready",
-			mocks: func(hiveMock *mock_hive.MockClusterManager) {
-				hiveMock.EXPECT().IsClusterDeploymentReady(gomock.Any(), fakeNamespace).Return(false, nil)
+			mocks: func(hiveMock *mock_hive.MockClusterManager, doc *api.OpenShiftClusterDocument) {
+				hiveMock.EXPECT().IsClusterDeploymentReady(gomock.Any(), doc).Return(false, nil)
 			},
 			wantResult: false,
 		},
 		{
 			name: "error",
-			mocks: func(hiveMock *mock_hive.MockClusterManager) {
-				hiveMock.EXPECT().IsClusterDeploymentReady(gomock.Any(), fakeNamespace).Return(false, errors.New("fake err"))
+			mocks: func(hiveMock *mock_hive.MockClusterManager, doc *api.OpenShiftClusterDocument) {
+				hiveMock.EXPECT().IsClusterDeploymentReady(gomock.Any(), doc).Return(false, errors.New("fake err"))
 			},
 			wantResult: false,
 			wantErr:    "fake err",
@@ -63,27 +62,14 @@ func TestHiveClusterDeploymentReady(t *testing.T) {
 			controller := gomock.NewController(t)
 			defer controller.Finish()
 
-			var hiveClusterManager hive.ClusterManager
+			m := createManagerForTests(t, fakeNamespace)
+
 			if !tt.wantSkip {
 				hiveMock := mock_hive.NewMockClusterManager(controller)
-				hiveClusterManager = hiveMock
 				if tt.mocks != nil {
-					tt.mocks(hiveMock)
+					tt.mocks(hiveMock, m.doc)
 				}
-			}
-
-			m := &manager{
-				log: logrus.NewEntry(logrus.StandardLogger()),
-				doc: &api.OpenShiftClusterDocument{
-					OpenShiftCluster: &api.OpenShiftCluster{
-						Properties: api.OpenShiftClusterProperties{
-							HiveProfile: api.HiveProfile{
-								Namespace: fakeNamespace,
-							},
-						},
-					},
-				},
-				hiveClusterManager: hiveClusterManager,
+				m.hiveClusterManager = hiveMock
 			}
 
 			result, err := m.hiveClusterDeploymentReady(context.Background())
@@ -104,21 +90,21 @@ func TestHiveResetCorrelationData(t *testing.T) {
 
 	for _, tt := range []struct {
 		name  string
-		mocks func(hiveMock *mock_hive.MockClusterManager)
+		mocks func(hiveMock *mock_hive.MockClusterManager, doc *api.OpenShiftClusterDocument)
 		// TODO(hive): remove wantSkip and all test cases with wantSkip=True once we have Hive everywhere
 		wantSkip bool
 		wantErr  string
 	}{
 		{
 			name: "success",
-			mocks: func(hiveMock *mock_hive.MockClusterManager) {
-				hiveMock.EXPECT().ResetCorrelationData(gomock.Any(), fakeNamespace).Return(nil)
+			mocks: func(hiveMock *mock_hive.MockClusterManager, doc *api.OpenShiftClusterDocument) {
+				hiveMock.EXPECT().ResetCorrelationData(gomock.Any(), doc).Return(nil)
 			},
 		},
 		{
 			name: "error",
-			mocks: func(hiveMock *mock_hive.MockClusterManager) {
-				hiveMock.EXPECT().ResetCorrelationData(gomock.Any(), fakeNamespace).Return(errors.New("fake err"))
+			mocks: func(hiveMock *mock_hive.MockClusterManager, doc *api.OpenShiftClusterDocument) {
+				hiveMock.EXPECT().ResetCorrelationData(gomock.Any(), doc).Return(errors.New("fake err"))
 			},
 			wantErr: "fake err",
 		},
@@ -130,28 +116,14 @@ func TestHiveResetCorrelationData(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			controller := gomock.NewController(t)
 			defer controller.Finish()
+			m := createManagerForTests(t, fakeNamespace)
 
-			var hiveClusterManager hive.ClusterManager
 			if !tt.wantSkip {
 				hiveMock := mock_hive.NewMockClusterManager(controller)
-				hiveClusterManager = hiveMock
 				if tt.mocks != nil {
-					tt.mocks(hiveMock)
+					tt.mocks(hiveMock, m.doc)
 				}
-			}
-
-			m := &manager{
-				log: logrus.NewEntry(logrus.StandardLogger()),
-				doc: &api.OpenShiftClusterDocument{
-					OpenShiftCluster: &api.OpenShiftCluster{
-						Properties: api.OpenShiftClusterProperties{
-							HiveProfile: api.HiveProfile{
-								Namespace: fakeNamespace,
-							},
-						},
-					},
-				},
-				hiveClusterManager: hiveClusterManager,
+				m.hiveClusterManager = hiveMock
 			}
 
 			err := m.hiveResetCorrelationData(context.Background())
@@ -265,12 +237,11 @@ func createManagerForTests(t *testing.T, existingNamespaceName string) *manager
 		t.Fatal(err)
 	}
 
-	m := &manager{
+	return &manager{
 		log: logrus.NewEntry(logrus.StandardLogger()),
 		db:  fakeDb,
 		doc: doc,
 	}
-	return m
 }
 
 func TestHiveEnsureResources(t *testing.T) {
@@ -326,7 +297,7 @@ func TestHiveDeleteResources(t *testing.T) {
 	for _, tt := range []struct {
 		testName           string
 		namespace          string
-		clusterManagerMock func(mockCtrl *gomock.Controller, namespaceName string) *mock_hive.MockClusterManager
+		clusterManagerMock func(mockCtrl *gomock.Controller, doc *api.OpenShiftClusterDocument) *mock_hive.MockClusterManager
 		wantErr            string
 	}{
 		{
@@ -335,26 +306,26 @@ func TestHiveDeleteResources(t *testing.T) {
 		{
 			testName:  "deletes namespace if it exists",
 			namespace: "existing-namespace",
-			clusterManagerMock: func(mockCtrl *gomock.Controller, namespaceName string) *mock_hive.MockClusterManager {
+			clusterManagerMock: func(mockCtrl *gomock.Controller, doc *api.OpenShiftClusterDocument) *mock_hive.MockClusterManager {
 				mockClusterManager := mock_hive.NewMockClusterManager(mockCtrl)
-				mockClusterManager.EXPECT().Delete(ctx, namespaceName).Return(nil)
+				mockClusterManager.EXPECT().Delete(ctx, doc).Return(nil)
 				return mockClusterManager
 			},
 		},
 		{
 			testName: "doesn't attempt to delete namespace if it doesn't exist",
-			clusterManagerMock: func(mockCtrl *gomock.Controller, namespaceName string) *mock_hive.MockClusterManager {
+			clusterManagerMock: func(mockCtrl *gomock.Controller, doc *api.OpenShiftClusterDocument) *mock_hive.MockClusterManager {
 				mockClusterManager := mock_hive.NewMockClusterManager(mockCtrl)
-				mockClusterManager.EXPECT().Delete(ctx, namespaceName).Times(0)
+				mockClusterManager.EXPECT().Delete(ctx, doc).Times(0)
 				return mockClusterManager
 			},
 		},
 		{
 			testName:  "returns error if cluster manager returns error",
 			namespace: "existing-namespace",
-			clusterManagerMock: func(mockCtrl *gomock.Controller, namespaceName string) *mock_hive.MockClusterManager {
+			clusterManagerMock: func(mockCtrl *gomock.Controller, doc *api.OpenShiftClusterDocument) *mock_hive.MockClusterManager {
 				mockClusterManager := mock_hive.NewMockClusterManager(mockCtrl)
-				mockClusterManager.EXPECT().Delete(ctx, namespaceName).Return(fmt.Errorf("cluster manager error"))
+				mockClusterManager.EXPECT().Delete(ctx, doc).Return(fmt.Errorf("cluster manager error"))
 				return mockClusterManager
 			},
 			wantErr: "cluster manager error",
@@ -367,7 +338,7 @@ func TestHiveDeleteResources(t *testing.T) {
 			m := createManagerForTests(t, tt.namespace)
 
 			if tt.clusterManagerMock != nil {
-				m.hiveClusterManager = tt.clusterManagerMock(controller, m.doc.OpenShiftCluster.Properties.HiveProfile.Namespace)
+				m.hiveClusterManager = tt.clusterManagerMock(controller, m.doc)
 			}
 
 			err := m.hiveDeleteResources(ctx)
diff --git a/pkg/hive/manager_test.go b/pkg/hive/manager_test.go
index d144c1fcc..3ad95e3bc 100644
--- a/pkg/hive/manager_test.go
+++ b/pkg/hive/manager_test.go
@@ -15,6 +15,7 @@ import (
 	kruntime "k8s.io/apimachinery/pkg/runtime"
 	kubernetesfake "k8s.io/client-go/kubernetes/fake"
 
+	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/util/cmp"
 	"github.com/Azure/ARO-RP/pkg/util/uuid"
 	"github.com/Azure/ARO-RP/pkg/util/uuid/fake"
@@ -22,6 +23,15 @@ import (
 
 func TestIsClusterDeploymentReady(t *testing.T) {
 	fakeNamespace := "fake-namespace"
+	doc := &api.OpenShiftClusterDocument{
+		OpenShiftCluster: &api.OpenShiftCluster{
+			Properties: api.OpenShiftClusterProperties{
+				HiveProfile: api.HiveProfile{
+					Namespace: fakeNamespace,
+				},
+			},
+		},
+	}
 
 	for _, tt := range []struct {
 		name       string
@@ -90,7 +100,7 @@ func TestIsClusterDeploymentReady(t *testing.T) {
 				hiveClientset: fakeClientset,
 			}
 
-			result, err := c.IsClusterDeploymentReady(context.Background(), fakeNamespace)
+			result, err := c.IsClusterDeploymentReady(context.Background(), doc)
 			if err != nil && err.Error() != tt.wantErr ||
 				err == nil && tt.wantErr != "" {
 				t.Error(err)
@@ -105,6 +115,15 @@ func TestIsClusterDeploymentReady(t *testing.T) {
 
 func TestResetCorrelationData(t *testing.T) {
 	fakeNamespace := "fake-namespace"
+	doc := &api.OpenShiftClusterDocument{
+		OpenShiftCluster: &api.OpenShiftCluster{
+			Properties: api.OpenShiftClusterProperties{
+				HiveProfile: api.HiveProfile{
+					Namespace: fakeNamespace,
+				},
+			},
+		},
+	}
 
 	for _, tt := range []struct {
 		name            string
@@ -143,7 +162,7 @@ func TestResetCorrelationData(t *testing.T) {
 				hiveClientset: fakeClientset,
 			}
 
-			err := c.ResetCorrelationData(context.Background(), fakeNamespace)
+			err := c.ResetCorrelationData(context.Background(), doc)
 			if err != nil && err.Error() != tt.wantErr ||
 				err == nil && tt.wantErr != "" {
 				t.Error(err)
diff --git a/pkg/hive/resources_test.go b/pkg/hive/resources_test.go
new file mode 100644
index 000000000..5d0774291
--- /dev/null
+++ b/pkg/hive/resources_test.go
@@ -0,0 +1,20 @@
+package hive
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"testing"
+
+	"github.com/go-test/deep"
+)
+
+func TestInstallConfigMap(t *testing.T) {
+	var expected = map[string]string{"install-config.yaml": "apiVersion: v1\nplatform:\n  azure:\n    region: \"testLocation\"\n"}
+
+	r := installConfigCM("testNamespace", "testLocation")
+
+	for _, err := range deep.Equal(r.StringData, expected) {
+		t.Error(err)
+	}
+}
diff --git a/test/util/testliveconfig/liveconfig.go b/test/util/testliveconfig/liveconfig.go
index 3eb4c47eb..2e553fa2f 100644
--- a/test/util/testliveconfig/liveconfig.go
+++ b/test/util/testliveconfig/liveconfig.go
@@ -24,6 +24,18 @@ func (t *testLiveConfig) HiveRestConfig(ctx context.Context, shard int) (*rest.C
 	}
 }
 
+func (t *testLiveConfig) InstallViaHive(ctx context.Context) (bool, error) {
+	return t.hasHive, nil
+}
+
+func (t *testLiveConfig) DefaultInstallerPullSpec(ctx context.Context) (string, error) {
+	if t.hasHive {
+		return "example/pull:spec", nil
+	} else {
+		return "", nil
+	}
+}
+
 func NewTestLiveConfig(hasHive bool) liveconfig.Manager {
 	return &testLiveConfig{hasHive: hasHive}
 }