Repository: kubernetes-sigs/cluster-api
Pull request #4721 (2021-06-03 21:33:52)

:seedling: Update controller-runtime and ginkgo dependencies
Signed-off-by: Vince Prignano <vincepri@vmware.com>

<!-- please add a icon to the title of this PR (see https://sigs.k8s.io/cluster-api/VERSIONING.md), and delete this line and similar ones -->
<!-- the icon will be either ⚠️ (:warning:, major or breaking changes), ✨ (:sparkles:, feature additions), 🐛 (:bug:, patch and bugfixes), 📖 (:book:, documentation or proposals), or 🌱 (:seedling:, minor or other) -->

**What this PR does / why we need it**:

Rev update

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Potentially related to #4720 



Patch:
diff --git a/cmd/clusterctl/api/v1alpha3/labels.go b/cmd/clusterctl/api/v1alpha3/labels.go
index be962651d..96b761a1d 100644
--- a/cmd/clusterctl/api/v1alpha3/labels.go
+++ b/cmd/clusterctl/api/v1alpha3/labels.go
@@ -25,11 +25,23 @@ const (
 	// ClusterctlCoreLabelName is applied to all the core objects managed by clusterctl.
 	ClusterctlCoreLabelName = "clusterctl.cluster.x-k8s.io/core"
 
-	// ClusterctlMoveLabelName can be set on CRDs that providers wish to move but that are not part of a Cluster.
+	// ClusterctlResourceLifecyleLabelName describes the lifecyle for a specific resource.
+	//
+	// Example: resources shared between instances of the same provider:  CRDs,
+	// ValidatingWebhookConfiguration, MutatingWebhookConfiguration, and so on.
+	ClusterctlResourceLifecyleLabelName = "clusterctl.cluster.x-k8s.io/lifecycle"
+
+	// ClusterctlMoveLabelName can be set on CRDs that providers wish to move that are not part of a cluster.
 	ClusterctlMoveLabelName = "clusterctl.cluster.x-k8s.io/move"
+)
 
-	// ClusterctlMoveHierarchyLabelName can be set on CRDs that providers wish to move with their entire hierarchy, but that are not part of a Cluster.
-	ClusterctlMoveHierarchyLabelName = "clusterctl.cluster.x-k8s.io/move-hierarchy"
+// ResourceLifecycle configures the lifecycle of a resource.
+type ResourceLifecycle string
+
+const (
+	// ResourceLifecycleShared is used to indicate that a resource is shared between
+	// multiple instances of a provider.
+	ResourceLifecycleShared = ResourceLifecycle("shared")
 )
 
 // ManifestLabel returns the cluster.x-k8s.io/provider label value for a provider/type.
diff --git a/cmd/clusterctl/client/cluster/components.go b/cmd/clusterctl/client/cluster/components.go
index ce4788691..6b89de337 100644
--- a/cmd/clusterctl/client/cluster/components.go
+++ b/cmd/clusterctl/client/cluster/components.go
@@ -29,19 +29,13 @@ import (
 	"k8s.io/apimachinery/pkg/util/sets"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha4"
 	clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
+	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
 	"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/util"
 	logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
-const (
-	namespaceKind                      = "Namespace"
-	validatingWebhookConfigurationKind = "ValidatingWebhookConfiguration"
-	mutatingWebhookConfigurationKind   = "MutatingWebhookConfiguration"
-	customResourceDefinitionKind       = "CustomResourceDefinition"
-)
-
-// DeleteOptions holds options for ComponentsClient.Delete func.
+// DeleteOptions defines delete options.
 type DeleteOptions struct {
 	Provider         clusterctlv1.Provider
 	IncludeNamespace bool
@@ -129,13 +123,22 @@ func (p *providerComponents) Delete(options DeleteOptions) error {
 	log.Info("Deleting", "Provider", options.Provider.Name, "Version", options.Provider.Version, "TargetNamespace", options.Provider.Namespace)
 
 	// Fetch all the components belonging to a provider.
-	// We want that the delete operation is able to clean-up everything.
+	// We want that the delete operation is able to clean-up everything in a the most common use case that is
+	// single-tenant management clusters. However, the downside of this is that this operation might be destructive
+	// in multi-tenant scenario, because a single operation could delete both instance specific and shared CRDs/web-hook components.
+	// This is considered acceptable because we are considering the multi-tenant scenario an advanced use case, and the assumption
+	// is that user in this case understand the potential impacts of this operation.
+	// TODO: in future we can eventually block delete --IncludeCRDs in case more than one instance of a provider exists
 	labels := map[string]string{
 		clusterctlv1.ClusterctlLabelName: "",
 		clusterv1.ProviderLabelName:      options.Provider.ManifestLabel(),
 	}
 
 	namespaces := []string{options.Provider.Namespace}
+	if options.IncludeCRDs {
+		namespaces = append(namespaces, repository.WebhookNamespaceName)
+	}
+
 	resources, err := p.proxy.ListResources(labels, namespaces...)
 	if err != nil {
 		return err
@@ -146,15 +149,15 @@ func (p *providerComponents) Delete(options DeleteOptions) error {
 	namespacesToDelete := sets.NewString()
 	instanceNamespacePrefix := fmt.Sprintf("%s-", options.Provider.Namespace)
 	for _, obj := range resources {
-		// If the CRDs should NOT be deleted, skip it;
+		// If the CRDs (and by extensions, all the shared resources) should NOT be deleted, skip it;
 		// NB. Skipping CRDs deletion ensures that also the objects of Kind defined in the CRDs Kind are not deleted.
-		isCRD := obj.GroupVersionKind().Kind == customResourceDefinitionKind
-		if !options.IncludeCRDs && isCRD {
+		isSharedResource := util.IsSharedResource(obj)
+		if !options.IncludeCRDs && isSharedResource {
 			continue
 		}
 
 		// If the resource is a namespace
-		isNamespace := obj.GroupVersionKind().Kind == namespaceKind
+		isNamespace := obj.GroupVersionKind().Kind == "Namespace"
 		if isNamespace {
 			// Skip all the namespaces not related to the provider instance being processed.
 			if obj.GetName() != options.Provider.Namespace {
@@ -168,17 +171,17 @@ func (p *providerComponents) Delete(options DeleteOptions) error {
 			namespacesToDelete.Insert(obj.GetName())
 		}
 
-		// If the resource is a cluster resource, skip it if the resource name does not start with the instance prefix.
-		// This is required because there are cluster resources like e.g. ClusterRoles and ClusterRoleBinding, which are instance specific;
-		// During the installation, clusterctl adds the instance namespace prefix to such resources (see fixRBAC), and so we can rely
-		// on that for deleting only the global resources belonging the the instance we are processing.
-		// NOTE: namespace and CRD are special case managed above; webhook instead goes hand by hand with the controller they
-		// should always be deleted.
-		isWebhook := obj.GroupVersionKind().Kind == validatingWebhookConfigurationKind || obj.GroupVersionKind().Kind == mutatingWebhookConfigurationKind
-		if util.IsClusterResource(obj.GetKind()) &&
-			!isNamespace && !isCRD && !isWebhook &&
-			!strings.HasPrefix(obj.GetName(), instanceNamespacePrefix) {
-			continue
+		// If not a shared resource or not a namespace
+		if !isSharedResource && !isNamespace {
+			// If the resource is a cluster resource, skip it if the resource name does not start with the instance prefix.
+			// This is required because there are cluster resources like e.g. ClusterRoles and ClusterRoleBinding, which are instance specific;
+			// During the installation, clusterctl adds the instance namespace prefix to such resources (see fixRBAC), and so we can rely
+			// on that for deleting only the global resources belonging the the instance we are processing.
+			if util.IsClusterResource(obj.GetKind()) {
+				if !strings.HasPrefix(obj.GetName(), instanceNamespacePrefix) {
+					continue
+				}
+			}
 		}
 
 		resourcesToDelete = append(resourcesToDelete, obj)
@@ -216,22 +219,20 @@ func (p *providerComponents) Delete(options DeleteOptions) error {
 }
 
 func (p *providerComponents) DeleteWebhookNamespace() error {
-	const webhookNamespaceName = "capi-webhook-system"
-
 	log := logf.Log
-	log.V(5).Info("Deleting", "namespace", webhookNamespaceName)
+	log.V(5).Info("Deleting", "namespace", repository.WebhookNamespaceName)
 
 	c, err := p.proxy.NewClient()
 	if err != nil {
 		return err
 	}
 
-	coreProviderWebhookNs := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: webhookNamespaceName}}
+	coreProviderWebhookNs := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: repository.WebhookNamespaceName}}
 	if err := c.Delete(ctx, coreProviderWebhookNs); err != nil {
 		if apierrors.IsNotFound(err) {
 			return nil
 		}
-		return errors.Wrapf(err, "failed to delete namespace %s", webhookNamespaceName)
+		return errors.Wrapf(err, "failed to delete namespace %s", repository.WebhookNamespaceName)
 	}
 
 	return nil
diff --git a/cmd/clusterctl/client/cluster/installer.go b/cmd/clusterctl/client/cluster/installer.go
index 5f2dd0ea9..d1a04c0ce 100644
--- a/cmd/clusterctl/client/cluster/installer.go
+++ b/cmd/clusterctl/client/cluster/installer.go
@@ -81,8 +81,35 @@ func installComponentsAndUpdateInventory(components repository.Components, provi
 
 	inventoryObject := components.InventoryObject()
 
-	log.V(1).Info("Creating objects", "Provider", components.ManifestLabel(), "Version", components.Version(), "TargetNamespace", components.TargetNamespace())
-	if err := providerComponents.Create(components.Objs()); err != nil {
+	// Check the list of providers currently in the cluster and decide if to install shared components (CRDs, web-hooks) or not.
+	// We are required to install shared components in two cases:
+	// - when this is the first instance of the provider being installed.
+	// - when the version of the provider being installed is newer than the max version already installed in the cluster.
+	// Nb. this assumes the newer version of shared components are fully retro-compatible.
+	providerList, err := providerInventory.List()
+	if err != nil {
+		return err
+	}
+
+	installSharedComponents, err := shouldInstallSharedComponents(providerList, inventoryObject)
+	if err != nil {
+		return err
+	}
+	if installSharedComponents {
+		log.V(1).Info("Creating shared objects", "Provider", components.ManifestLabel(), "Version", components.Version())
+		// TODO: currently shared components overrides existing shared components. As a future improvement we should
+		//  consider if to delete (preserving CRDs) before installing so there will be no left-overs in case the list of resources changes
+		if err := providerComponents.Create(components.SharedObjs()); err != nil {
+			return err
+		}
+	} else {
+		log.V(1).Info("Shared objects already up to date", "Provider", components.ManifestLabel())
+	}
+
+	// Then always install the instance specific objects and the then inventory item for the provider
+
+	log.V(1).Info("Creating instance objects", "Provider", components.ManifestLabel(), "Version", components.Version(), "TargetNamespace", components.TargetNamespace())
+	if err := providerComponents.Create(components.InstanceObjs()); err != nil {
 		return err
 	}
 
@@ -90,6 +117,40 @@ func installComponentsAndUpdateInventory(components repository.Components, provi
 	return providerInventory.Create(inventoryObject)
 }
 
+// shouldInstallSharedComponents checks if it is required to install shared components for a provider.
+func shouldInstallSharedComponents(providerList *clusterctlv1.ProviderList, provider clusterctlv1.Provider) (bool, error) {
+	// Get the max version of the provider already installed in the cluster.
+	var maxVersion *version.Version
+	for _, other := range providerList.FilterByProviderNameAndType(provider.ProviderName, provider.GetProviderType()) {
+		otherVersion, err := version.ParseSemantic(other.Version)
+		if err != nil {
+			return false, errors.Wrapf(err, "failed to parse version for the %s provider", other.InstanceName())
+		}
+		if maxVersion == nil || otherVersion.AtLeast(maxVersion) {
+			maxVersion = otherVersion
+		}
+	}
+	// If there is no max version, this is the first instance of the provider being installed, so it is required
+	// to install the shared components.
+	if maxVersion == nil {
+		return true, nil
+	}
+
+	// If the installed version is newer or equal than than the version of the provider being installed,
+	// return false because we should not down grade the shared components.
+	providerVersion, err := version.ParseSemantic(provider.Version)
+	if err != nil {
+		return false, errors.Wrapf(err, "failed to parse version for the %s provider", provider.InstanceName())
+	}
+	if maxVersion.AtLeast(providerVersion) {
+		return false, nil
+	}
+
+	// Otherwise, the version of the provider being installed is newer that the current max version, so it is
+	// required to install also the new version of shared components.
+	return true, nil
+}
+
 func (i *providerInstaller) Validate() error {
 	// Get the list of providers currently in the cluster.
 	providerList, err := i.providerInventory.List()
diff --git a/cmd/clusterctl/client/cluster/mover.go b/cmd/clusterctl/client/cluster/mover.go
index 1131b93f8..07671d776 100644
--- a/cmd/clusterctl/client/cluster/mover.go
+++ b/cmd/clusterctl/client/cluster/mover.go
@@ -561,28 +561,24 @@ func (o *objectMover) createTargetObject(nodeToCreate *node, toProxy Proxy) erro
 				obj.GroupVersionKind(), obj.GetNamespace(), obj.GetName())
 		}
 
-		// If the object already exists, try to update it if it is node a global object / something belonging to a global object hierarchy (e.g. a secrets owned by a global identity object).
-		if nodeToCreate.isGlobal || nodeToCreate.isGlobalHierarchy {
-			log.V(5).Info("Object already exists, skipping upgrade because it is global/it is owned by a global object", nodeToCreate.identity.Kind, nodeToCreate.identity.Name, "Namespace", nodeToCreate.identity.Namespace)
-		} else {
-			// Nb. This should not happen, but it is supported to make move more resilient to unexpected interrupt/restarts of the move process.
-			log.V(5).Info("Object already exists, updating", nodeToCreate.identity.Kind, nodeToCreate.identity.Name, "Namespace", nodeToCreate.identity.Namespace)
-
-			// Retrieve the UID and the resource version for the update.
-			existingTargetObj := &unstructured.Unstructured{}
-			existingTargetObj.SetAPIVersion(obj.GetAPIVersion())
-			existingTargetObj.SetKind(obj.GetKind())
-			if err := cTo.Get(ctx, objKey, existingTargetObj); err != nil {
-				return errors.Wrapf(err, "error reading resource for %q %s/%s",
-					existingTargetObj.GroupVersionKind(), existingTargetObj.GetNamespace(), existingTargetObj.GetName())
-			}
+		// If the object already exists, try to update it.
+		// Nb. This should not happen, but it is supported to make move more resilient to unexpected interrupt/restarts of the move process.
+		log.V(5).Info("Object already exists, updating", nodeToCreate.identity.Kind, nodeToCreate.identity.Name, "Namespace", nodeToCreate.identity.Namespace)
+
+		// Retrieve the UID and the resource version for the update.
+		existingTargetObj := &unstructured.Unstructured{}
+		existingTargetObj.SetAPIVersion(obj.GetAPIVersion())
+		existingTargetObj.SetKind(obj.GetKind())
+		if err := cTo.Get(ctx, objKey, existingTargetObj); err != nil {
+			return errors.Wrapf(err, "error reading resource for %q %s/%s",
+				existingTargetObj.GroupVersionKind(), existingTargetObj.GetNamespace(), existingTargetObj.GetName())
+		}
 
-			obj.SetUID(existingTargetObj.GetUID())
-			obj.SetResourceVersion(existingTargetObj.GetResourceVersion())
-			if err := cTo.Update(ctx, obj); err != nil {
-				return errors.Wrapf(err, "error updating %q %s/%s",
-					obj.GroupVersionKind(), obj.GetNamespace(), obj.GetName())
-			}
+		obj.SetUID(existingTargetObj.GetUID())
+		obj.SetResourceVersion(existingTargetObj.GetResourceVersion())
+		if err := cTo.Update(ctx, obj); err != nil {
+			return errors.Wrapf(err, "error updating %q %s/%s",
+				obj.GroupVersionKind(), obj.GetNamespace(), obj.GetName())
 		}
 	}
 
@@ -599,6 +595,11 @@ func (o *objectMover) deleteGroup(group moveGroup) error {
 	for i := range group {
 		nodeToDelete := group[i]
 
+		// Don't delete cluster-wide nodes
+		if nodeToDelete.isGlobal {
+			continue
+		}
+
 		// Delete the Kubernetes object corresponding to the current node.
 		// Nb. The operation is wrapped in a retry loop to make move more resilient to unexpected conditions.
 		err := retryWithExponentialBackoff(deleteSourceObjectBackoff, func() error {
@@ -620,11 +621,6 @@ var (
 // deleteSourceObject deletes the Kubernetes object corresponding to the node from the source management cluster, taking care of removing all the finalizers so
 // the objects gets immediately deleted (force delete).
 func (o *objectMover) deleteSourceObject(nodeToDelete *node) error {
-	// Don't delete cluster-wide nodes or nodes that are below a hierarchy that starts with a global object (e.g. a secrets owned by a global identity object).
-	if nodeToDelete.isGlobal || nodeToDelete.isGlobalHierarchy {
-		return nil
-	}
-
 	log := logf.Log
 	log.V(1).Info("Deleting", nodeToDelete.identity.Kind, nodeToDelete.identity.Name, "Namespace", nodeToDelete.identity.Namespace)
 
diff --git a/cmd/clusterctl/client/cluster/objectgraph.go b/cmd/clusterctl/client/cluster/objectgraph.go
index 61aa820e4..c635b314d 100644
--- a/cmd/clusterctl/client/cluster/objectgraph.go
+++ b/cmd/clusterctl/client/cluster/objectgraph.go
@@ -57,34 +57,27 @@ type node struct {
 	// This ensures the node is moved, regardless of its owner refs.
 	forceMove bool
 
-	// forceMoveHierarchy is set to true if the CRD of this object has the "move-hierarchy" label attached.
-	// This ensures the node and it's entire hierarchy of dependants (via owner ref chain) is moved.
-	forceMoveHierarchy bool
-
 	// isGlobal gets set to true if this object is a global resource (no namespace).
 	isGlobal bool
 
-	// isGlobalHierarchy gets set to true if this object is part of a hierarchy of a global resource e.g.
-	// a secrets holding credentials for a global identity object.
-	// When this flag is true the object should not be deleted from the source cluster.
-	isGlobalHierarchy bool
-
 	// virtual records if this node was discovered indirectly, e.g. by processing an OwnerRef, but not yet observed as a concrete object.
 	virtual bool
 
 	// newID stores the new UID the objects gets once created in the target cluster.
 	newUID types.UID
 
-	// tenant define the list of objects which are tenant for the node, no matter if the node has a direct OwnerReference to the object or if
-	// the node is linked to a object indirectly in the OwnerReference chain.
-	tenant map[*node]empty
+	// tenantClusters define the list of Clusters which are tenant for the node, no matter if the node has a direct OwnerReference to the Cluster or if
+	// the node is linked to a Cluster indirectly in the OwnerReference chain.
+	tenantClusters map[*node]empty
+
+	// tenantCRSs define the list of ClusterResourceSet which are tenant for the node, no matter if the node has a direct OwnerReference to the ClusterResourceSet or if
+	// the node is linked to a ClusterResourceSet indirectly in the OwnerReference chain.
+	tenantCRSs map[*node]empty
 }
 
 type discoveryTypeInfo struct {
-	typeMeta           metav1.TypeMeta
-	forceMove          bool
-	forceMoveHierarchy bool
-	scope              apiextensionsv1.ResourceScope
+	typeMeta  metav1.TypeMeta
+	forceMove bool
 }
 
 // markObserved marks the fact that a node was observed as a concrete object.
@@ -136,7 +129,7 @@ func (o *objectGraph) addObj(obj *unstructured.Unstructured) {
 	for _, ownerReference := range obj.GetOwnerReferences() {
 		ownerNode, ok := o.uidToNode[ownerReference.UID]
 		if !ok {
-			ownerNode = o.ownerToVirtualNode(ownerReference)
+			ownerNode = o.ownerToVirtualNode(ownerReference, newNode.identity.Namespace)
 		}
 
 		newNode.addOwner(ownerNode, ownerReferenceAttributes{
@@ -148,20 +141,27 @@ func (o *objectGraph) addObj(obj *unstructured.Unstructured) {
 
 // ownerToVirtualNode creates a virtual node as a placeholder for the Kubernetes owner object received in input.
 // The virtual node will be eventually converted to an actual node when the node will be visited during discovery.
-func (o *objectGraph) ownerToVirtualNode(owner metav1.OwnerReference) *node {
+func (o *objectGraph) ownerToVirtualNode(owner metav1.OwnerReference, namespace string) *node {
+	isGlobal := false
+	if namespace == "" {
+		isGlobal = true
+	}
+
 	ownerNode := &node{
 		identity: corev1.ObjectReference{
 			APIVersion: owner.APIVersion,
 			Kind:       owner.Kind,
 			Name:       owner.Name,
 			UID:        owner.UID,
-			// NOTE: deferring initialization of fields derived from object meta to when the node reference is actually processed.
+			Namespace:  namespace,
 		},
-		owners:     make(map[*node]ownerReferenceAttributes),
-		softOwners: make(map[*node]empty),
-		tenant:     make(map[*node]empty),
-		virtual:    true,
-		// NOTE: deferring initialization of fields derived from object meta to when the node reference is actually processed.
+		owners:         make(map[*node]ownerReferenceAttributes),
+		softOwners:     make(map[*node]empty),
+		tenantClusters: make(map[*node]empty),
+		tenantCRSs:     make(map[*node]empty),
+		virtual:        true,
+		forceMove:      o.getForceMove(owner.Kind, owner.APIVersion, nil),
+		isGlobal:       isGlobal,
 	}
 
 	o.uidToNode[ownerNode.identity.UID] = ownerNode
@@ -177,12 +177,17 @@ func (o *objectGraph) objToNode(obj *unstructured.Unstructured) *node {
 		existingNode.markObserved()
 
 		// In order to compensate the lack of labels when adding a virtual node,
-		// it is required to re-compute the forceMove and forceMoveHierarchy field when the real node is processed.
-		// Without this, there is the risk that those fields could report false negatives depending on the discovery order.
-		o.objMetaToNode(obj, existingNode)
+		// it is required to re-compute the forceMove flag when the real node is processed
+		// Without this, there is the risk that, forceMove will report false negatives depending on the discovery order
+		existingNode.forceMove = o.getForceMove(obj.GetKind(), obj.GetAPIVersion(), obj.GetLabels())
 		return existingNode
 	}
 
+	isGlobal := false
+	if obj.GetNamespace() == "" {
+		isGlobal = true
+	}
+
 	newNode := &node{
 		identity: corev1.ObjectReference{
 			APIVersion: obj.GetAPIVersion(),
@@ -191,40 +196,30 @@ func (o *objectGraph) objToNode(obj *unstructured.Unstructured) *node {
 			Name:       obj.GetName(),
 			Namespace:  obj.GetNamespace(),
 		},
-		owners:     make(map[*node]ownerReferenceAttributes),
-		softOwners: make(map[*node]empty),
-		tenant:     make(map[*node]empty),
-		virtual:    false,
+		owners:         make(map[*node]ownerReferenceAttributes),
+		softOwners:     make(map[*node]empty),
+		tenantClusters: make(map[*node]empty),
+		tenantCRSs:     make(map[*node]empty),
+		virtual:        false,
+		forceMove:      o.getForceMove(obj.GetKind(), obj.GetAPIVersion(), obj.GetLabels()),
+		isGlobal:       isGlobal,
 	}
-	o.objMetaToNode(obj, newNode)
 
 	o.uidToNode[newNode.identity.UID] = newNode
 	return newNode
 }
 
-func (o *objectGraph) objMetaToNode(obj *unstructured.Unstructured, n *node) {
-	n.identity.Namespace = obj.GetNamespace()
-	if _, ok := obj.GetLabels()[clusterctlv1.ClusterctlMoveLabelName]; ok {
-		n.forceMove = true
-	}
-	if _, ok := obj.GetLabels()[clusterctlv1.ClusterctlMoveHierarchyLabelName]; ok {
-		n.forceMoveHierarchy = true
+func (o *objectGraph) getForceMove(kind, apiVersion string, labels map[string]string) bool {
+	if _, ok := labels[clusterctlv1.ClusterctlMoveLabelName]; ok {
+		return true
 	}
 
-	kindAPIStr := getKindAPIString(metav1.TypeMeta{Kind: obj.GetKind(), APIVersion: obj.GetAPIVersion()})
-	if discoveryType, ok := o.types[kindAPIStr]; ok {
-		if !n.forceMove && discoveryType.forceMove {
-			n.forceMove = true
-		}
+	kindAPIStr := getKindAPIString(metav1.TypeMeta{Kind: kind, APIVersion: apiVersion})
 
-		if !n.forceMoveHierarchy && discoveryType.forceMoveHierarchy {
-			n.forceMoveHierarchy = true
-		}
-
-		if discoveryType.scope == apiextensionsv1.ClusterScoped {
-			n.isGlobal = true
-		}
+	if discoveryType, ok := o.types[kindAPIStr]; ok {
+		return discoveryType.forceMove
 	}
+	return false
 }
 
 // getDiscoveryTypes returns the list of TypeMeta to be considered for the the move discovery phase.
@@ -246,23 +241,7 @@ func (o *objectGraph) getDiscoveryTypes() error {
 				continue
 			}
 
-			// If a CRD is labeled with force move-hierarchy, keep track of this so all the objects of this kind could be moved
-			// together with their descendants identified via the owner chain.
-			// NOTE: Cluster and ClusterResourceSet are automatically considered as force move-hierarchy.
-			forceMoveHierarchy := false
-			if crd.Spec.Group == clusterv1.GroupVersion.Group && crd.Spec.Names.Kind == "Cluster" {
-				forceMoveHierarchy = true
-			}
-			if crd.Spec.Group == addonsv1.GroupVersion.Group && crd.Spec.Names.Kind == "ClusterResourceSet" {
-				forceMoveHierarchy = true
-			}
-			if _, ok := crd.Labels[clusterctlv1.ClusterctlMoveHierarchyLabelName]; ok {
-				forceMoveHierarchy = true
-			}
-
-			// If a CRD is with as force move, keep track of this so all the objects of this type could be moved.
-			// NOTE: if a kind is set for force move-hierarchy, it is also automatically force moved.
-			forceMove := forceMoveHierarchy
+			forceMove := false
 			if _, ok := crd.Labels[clusterctlv1.ClusterctlMoveLabelName]; ok {
 				forceMove = true
 			}
@@ -276,10 +255,8 @@ func (o *objectGraph) getDiscoveryTypes() error {
 			}
 
 			o.types[getKindAPIString(typeMeta)] = &discoveryTypeInfo{
-				typeMeta:           typeMeta,
-				forceMove:          forceMove,
-				forceMoveHierarchy: forceMoveHierarchy,
-				scope:              crd.Spec.Scope,
+				typeMeta:  typeMeta,
+				forceMove: forceMove,
 			}
 		}
 	}
@@ -371,8 +348,11 @@ func (o *objectGraph) Discovery(namespace string) error {
 	// by a naming convention (without any explicit OwnerReference).
 	o.setSoftOwnership()
 
-	// Completes the graph by setting for each node the list of tenants the node belongs to.
-	o.setTenants()
+	// Completes the graph by setting for each node the list of Clusters the node belong to.
+	o.setClusterTenants()
+
+	// Completes the graph by setting for each node the list of ClusterResourceSet the node belong to.
+	o.setCRSTenants()
 
 	return nil
 }
@@ -437,12 +417,12 @@ func (o *objectGraph) getCRSs() []*node {
 	return clusters
 }
 
-// getMoveNodes returns the list of nodes existing in the object graph that belong at least to one tenant (e.g Cluster or to a ClusterResourceSet)
-// or it is labeled for force move (at object level or at CRD level).
+// getMoveNodes returns the list of nodes existing in the object graph that belong at least to one Cluster or to a ClusterResourceSet
+// or to a CRD containing the "move" label.
 func (o *objectGraph) getMoveNodes() []*node {
 	nodes := []*node{}
 	for _, node := range o.uidToNode {
-		if len(node.tenant) > 0 || node.forceMove {
+		if len(node.tenantClusters) > 0 || len(node.tenantCRSs) > 0 || node.forceMove {
 			nodes = append(nodes, node)
 		}
 	}
@@ -487,23 +467,36 @@ func (o *objectGraph) setSoftOwnership() {
 	}
 }
 
-// setTenants identifies all the nodes linked to a parent with forceMoveHierarchy = true (e.g. Clusters or ClusterResourceSet)
-// via the owner ref chain.
-func (o *objectGraph) setTenants() {
-	for _, node := range o.getNodes() {
-		if node.forceMoveHierarchy {
-			o.setTenant(node, node, node.isGlobal)
-		}
+// setClusterTenants sets the cluster tenants for the clusters itself and all their dependent object tree.
+func (o *objectGraph) setClusterTenants() {
+	for _, cluster := range o.getClusters() {
+		o.setClusterTenant(cluster, cluster)
 	}
 }
 
-// setTenant sets a tenant for a node and for its own dependents/sofDependents.
-func (o *objectGraph) setTenant(node, tenant *node, isGlobalHierarchy bool) {
-	node.tenant[tenant] = empty{}
-	node.isGlobalHierarchy = node.isGlobalHierarchy || isGlobalHierarchy
+// setNodeTenant sets a cluster tenant for a node and for its own dependents/sofDependents.
+func (o *objectGraph) setClusterTenant(node, tenant *node) {
+	node.tenantClusters[tenant] = empty{}
 	for _, other := range o.getNodes() {
 		if other.isOwnedBy(node) || other.isSoftOwnedBy(node) {
-			o.setTenant(other, tenant, isGlobalHierarchy)
+			o.setClusterTenant(other, tenant)
+		}
+	}
+}
+
+// setClusterTenants sets the ClusterResourceSet tenants for the ClusterResourceSet itself and all their dependent object tree.
+func (o *objectGraph) setCRSTenants() {
+	for _, crs := range o.getCRSs() {
+		o.setCRSTenant(crs, crs)
+	}
+}
+
+// setCRSTenant sets a ClusterResourceSet tenant for a node and for its own dependents/sofDependents.
+func (o *objectGraph) setCRSTenant(node, tenant *node) {
+	node.tenantCRSs[tenant] = empty{}
+	for _, other := range o.getNodes() {
+		if other.isOwnedBy(node) {
+			o.setCRSTenant(other, tenant)
 		}
 	}
 }
diff --git a/cmd/clusterctl/client/delete.go b/cmd/clusterctl/client/delete.go
index 73067f756..1a4a3bbd2 100644
--- a/cmd/clusterctl/client/delete.go
+++ b/cmd/clusterctl/client/delete.go
@@ -52,6 +52,7 @@ type DeleteOptions struct {
 	IncludeNamespace bool
 
 	// IncludeCRDs forces the deletion of the provider's CRDs (and of all the related objects).
+	// By Extension, this forces the deletion of all the resources shared among provider instances, like e.g. web-hooks.
 	IncludeCRDs bool
 }
 
diff --git a/cmd/clusterctl/client/repository/components.go b/cmd/clusterctl/client/repository/components.go
index 0c725247d..4cfd81077 100644
--- a/cmd/clusterctl/client/repository/components.go
+++ b/cmd/clusterctl/client/repository/components.go
@@ -33,10 +33,13 @@ import (
 )
 
 const (
-	namespaceKind          = "Namespace"
-	clusterRoleKind        = "ClusterRole"
-	clusterRoleBindingKind = "ClusterRoleBinding"
-	roleBindingKind        = "RoleBinding"
+	namespaceKind                      = "Namespace"
+	clusterRoleKind                    = "ClusterRole"
+	clusterRoleBindingKind             = "ClusterRoleBinding"
+	roleBindingKind                    = "RoleBinding"
+	validatingWebhookConfigurationKind = "ValidatingWebhookConfiguration"
+	mutatingWebhookConfigurationKind   = "MutatingWebhookConfiguration"
+	customResourceDefinitionKind       = "CustomResourceDefinition"
 )
 
 const (
@@ -79,8 +82,11 @@ type Components interface {
 	// Yaml return the provider components in the form of a YAML file.
 	Yaml() ([]byte, error)
 
-	// Objs return the components in the form of a list of Unstructured objects.
-	Objs() []unstructured.Unstructured
+	// InstanceObjs return the instance specific components in the form of a list of Unstructured objects.
+	InstanceObjs() []unstructured.Unstructured
+
+	// SharedObjs returns CRDs, web-hooks and all the components shared across instances in the form of a list of Unstructured objects.
+	SharedObjs() []unstructured.Unstructured
 }
 
 // components implement Components.
@@ -90,7 +96,8 @@ type components struct {
 	variables       []string
 	images          []string
 	targetNamespace string
-	objs            []unstructured.Unstructured
+	instanceObjs    []unstructured.Unstructured
+	sharedObjs      []unstructured.Unstructured
 }
 
 // ensure components implement Components.
@@ -132,12 +139,20 @@ func (c *components) InventoryObject() clusterctlv1.Provider {
 	}
 }
 
-func (c *components) Objs() []unstructured.Unstructured {
-	return c.objs
+func (c *components) InstanceObjs() []unstructured.Unstructured {
+	return c.instanceObjs
+}
+
+func (c *components) SharedObjs() []unstructured.Unstructured {
+	return c.sharedObjs
 }
 
 func (c *components) Yaml() ([]byte, error) {
-	return utilyaml.FromUnstructured(c.objs)
+	objs := []unstructured.Unstructured{}
+	objs = append(objs, c.sharedObjs...)
+	objs = append(objs, c.instanceObjs...)
+
+	return utilyaml.FromUnstructured(objs)
 }
 
 // ComponentsOptions represents specific inputs that are passed in to
@@ -201,9 +216,17 @@ func NewComponents(input ComponentsInput) (Components, error) {
 		return nil, errors.Wrap(err, "failed to detect required images")
 	}
 
+	// splits the component resources from the shared resources.
+	// This is required because a component yaml is designed for allowing users to create a single instance of a provider
+	// by running kubectl apply, while multi-tenant installations requires manual modifications to the yaml file.
+	// clusterctl does such modification for the user, and in order to do so, it is required to split objects in two sets;
+	// components resources are processed in order to make instance specific modifications, while identifying labels
+	// are applied to shared resources.
+	instanceObjs, sharedObjs := splitInstanceAndSharedResources(objs)
+
 	// inspect the list of objects for the default target namespace
 	// the default target namespace is the namespace object defined in the component yaml read from the repository, if any
-	defaultTargetNamespace, err := inspectTargetNamespace(objs)
+	defaultTargetNamespace, err := inspectTargetNamespace(instanceObjs)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to detect default target namespace")
 	}
@@ -221,21 +244,27 @@ func NewComponents(input ComponentsInput) (Components, error) {
 	}
 
 	// add a Namespace object if missing (ensure the targetNamespace will be created)
-	objs = addNamespaceIfMissing(objs, input.Options.TargetNamespace)
+	instanceObjs = addNamespaceIfMissing(instanceObjs, input.Options.TargetNamespace)
 
 	// fix Namespace name in all the objects
-	objs = fixTargetNamespace(objs, input.Options.TargetNamespace)
+	instanceObjs = fixTargetNamespace(instanceObjs, input.Options.TargetNamespace)
 
 	// ensures all the ClusterRole and ClusterRoleBinding have the name prefixed with the namespace name and that
 	// all the clusterRole/clusterRoleBinding namespaced subjects refers to targetNamespace
 	// Nb. Making all the RBAC rules "namespaced" is required for supporting multi-tenancy
-	objs, err = fixRBAC(objs, input.Options.TargetNamespace)
+	instanceObjs, err = fixRBAC(instanceObjs, input.Options.TargetNamespace)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to fix ClusterRoleBinding names")
 	}
 
-	// Add common labels.
-	objs = addCommonLabels(objs, input.Provider)
+	// Add common labels to both the obj groups.
+	instanceObjs = addCommonLabels(instanceObjs, input.Provider)
+	sharedObjs = addCommonLabels(sharedObjs, input.Provider)
+
+	// Add an identifying label to shared components so next invocation of init, clusterctl delete and clusterctl upgrade can act accordingly.
+	// Additionally, the capi-webhook-system namespace gets detached from any provider, so we prevent that deleting
+	// a provider can delete all the web-hooks.
+	sharedObjs = fixSharedLabels(sharedObjs)
 
 	return &components{
 		Provider:        input.Provider,
@@ -243,10 +272,43 @@ func NewComponents(input ComponentsInput) (Components, error) {
 		variables:       variables,
 		images:          images,
 		targetNamespace: input.Options.TargetNamespace,
-		objs:            objs,
+		instanceObjs:    instanceObjs,
+		sharedObjs:      sharedObjs,
 	}, nil
 }
 
+// splitInstanceAndSharedResources divides the objects contained in the component yaml into two sets, instance specific objects
+// and objects shared across many instances.
+func splitInstanceAndSharedResources(objs []unstructured.Unstructured) (instanceObjs []unstructured.Unstructured, sharedObjs []unstructured.Unstructured) {
+	for _, o := range objs {
+		// CRDs, and web-hook objects are shared among instances.
+		if o.GetKind() == customResourceDefinitionKind ||
+			o.GetKind() == mutatingWebhookConfigurationKind ||
+			o.GetKind() == validatingWebhookConfigurationKind {
+			sharedObjs = append(sharedObjs, o)
+			continue
+		}
+
+		// Web-hook objects are backed by a controller handling the web-hook calls; byt definition this
+		// controller and everything releted to it (eg. services, certificates) it is expected to be deployed in well
+		// know namespace named capi-webhook-system.
+		// So this namespace and all the objected belonging to it are considered shared resources.
+		if o.GetKind() == namespaceKind && o.GetName() == WebhookNamespaceName {
+			sharedObjs = append(sharedObjs, o)
+			continue
+		}
+
+		if util.IsResourceNamespaced(o.GetKind()) && o.GetNamespace() == WebhookNamespaceName {
+			sharedObjs = append(sharedObjs, o)
+			continue
+		}
+
+		// Everything else is considered an instance specific object.
+		instanceObjs = append(instanceObjs, o)
+	}
+	return
+}
+
 // inspectTargetNamespace identifies the name of the namespace object contained in the components YAML, if any.
 // In case more than one Namespace object is identified, an error is returned.
 func inspectTargetNamespace(objs []unstructured.Unstructured) (string, error) {
@@ -335,9 +397,11 @@ func fixRBAC(objs []unstructured.Unstructured, targetNamespace string) ([]unstru
 			// assign a namespaced name
 			b.Name = fmt.Sprintf("%s-%s", targetNamespace, b.Name)
 
-			// ensure that namespaced subjects refers to targetNamespace
+			// ensure that namespaced subjects refers to targetNamespace; the only exception
+			// for this rule are the namespaced subjects located in the capi-webhook-system, which are
+			// not affected by the targetNamespace value
 			for k := range b.Subjects {
-				if b.Subjects[k].Namespace != "" {
+				if b.Subjects[k].Namespace != "" && b.Subjects[k].Namespace != WebhookNamespaceName {
 					b.Subjects[k].Namespace = targetNamespace
 				}
 			}
@@ -360,9 +424,11 @@ func fixRBAC(objs []unstructured.Unstructured, targetNamespace string) ([]unstru
 				return nil, err
 			}
 
-			// ensure that namespaced subjects refers to targetNamespace
+			// ensure that namespaced subjects refers to targetNamespace; the only exception
+			// for this rule are the namespaced subjects located in the capi-webhook-system, which are
+			// not affected by the targetNamespace value
 			for k := range b.Subjects {
-				if b.Subjects[k].Namespace != "" {
+				if b.Subjects[k].Namespace != "" && b.Subjects[k].Namespace != WebhookNamespaceName {
 					b.Subjects[k].Namespace = targetNamespace
 				}
 			}
@@ -400,3 +466,20 @@ func getCommonLabels(provider config.Provider) map[string]string {
 		clusterv1.ProviderLabelName:      provider.ManifestLabel(),
 	}
 }
+
+// fixSharedLabels ensures all the shared components have an identifying label so next invocation of init, clusterctl delete
+// and clusterctl upgrade can act accordingly.
+func fixSharedLabels(objs []unstructured.Unstructured) []unstructured.Unstructured {
+	for _, o := range objs {
+		labels := o.GetLabels()
+		labels[clusterctlv1.ClusterctlResourceLifecyleLabelName] = string(clusterctlv1.ResourceLifecycleShared)
+
+		// the capi-webhook-system namespace is shared among many providers, so removing the ProviderLabelName label.
+		if o.GetKind() == namespaceKind && o.GetName() == WebhookNamespaceName {
+			delete(labels, clusterv1.ProviderLabelName)
+		}
+		o.SetLabels(labels)
+	}
+
+	return objs
+}
diff --git a/cmd/clusterctl/internal/util/objs.go b/cmd/clusterctl/internal/util/objs.go
index da8d5e72f..431448d02 100644
--- a/cmd/clusterctl/internal/util/objs.go
+++ b/cmd/clusterctl/internal/util/objs.go
@@ -21,6 +21,7 @@ import (
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
 	"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/scheme"
 )
 
@@ -100,6 +101,18 @@ func IsResourceNamespaced(kind string) bool {
 	}
 }
 
+// IsSharedResource returns true if the resource lifecycle is shared.
+func IsSharedResource(o unstructured.Unstructured) bool {
+	lifecycle, ok := o.GetLabels()[clusterctlv1.ClusterctlResourceLifecyleLabelName]
+	if !ok {
+		return false
+	}
+	if lifecycle == string(clusterctlv1.ResourceLifecycleShared) {
+		return true
+	}
+	return false
+}
+
 // FixImages alters images using the give alter func
 // NB. The implemented approach is specific for the provider components YAML & for the cert-manager manifest; it is not
 // intended to cover all the possible objects used to deploy containers existing in Kubernetes.
diff --git a/docs/book/src/clusterctl/provider-contract.md b/docs/book/src/clusterctl/provider-contract.md
index 9d4a8503e..7a7f3d93b 100644
--- a/docs/book/src/clusterctl/provider-contract.md
+++ b/docs/book/src/clusterctl/provider-contract.md
@@ -107,6 +107,22 @@ It is strongly recommended that:
 * Bootstrap providers release a file called ` bootstrap-components.yaml`
 * Control plane providers release a file called `control-plane-components.yaml`
 
+#### Shared and instance components
+
+The objects contained in a component YAML file can be divided in two sets:
+
+* Instance specific objects, like the Deployment for the controller, the ServiceAccount used for running the controller
+  and the related RBAC rules.
+* The objects that are shared among all the provider instances, like e.g. CRDs, ValidatingWebhookConfiguration or the
+  Deployment implementing the web-hook servers and related Service and Certificates.
+
+As per the Cluster API contract, all the shared objects are expected to be deployed in a namespace named `capi-webhook-system`
+(if applicable).
+
+clusterctl implements a different lifecycle for shared resources e.g.
+* Ensuring that the version of the shared objects for each provider matches the latest version installed in the cluster.
+* Ensuring that deleting an instance of a provider does not destroy shared resources unless explicitly requested by the user.
+
 #### Target namespace
 
 The instance components should contain one Namespace object, which will be used as the default target namespace
@@ -295,35 +311,18 @@ functioning of `clusterctl` when using non-compliant component YAML or cluster t
 
 Provider authors should be aware that `clusterctl move` command implements a discovery mechanism that considers:
 
-* All the Kind defined in one of the CRDs installed by clusterctl using `clusterctl init` (identified via the `clusterctl.cluster.x-k8s.io label`); 
-  For each CRD, discovery collects:
-  * All the objects from the namespace being moved only if the CRD scope is `Namespaced`.
-  * All the objects if the CRD scope is `Cluster`.
-* All the `ConfigMap` objects from the namespace being moved.
-* All the `Secret` objects from the namespace being moved and from the namespaces where infrastructure providers are installed.
-
-After completing discovery, `clusterctl move` moves to the target cluster only the objects discovered in the previous phase
-that are compliant with one of the following rules:
-  * The object is directly or indirectly linked to a `Cluster` object (linked through the `OwnerReference` chain).
-  * The object is a secret containing a user provided certificate (linked to a `Cluster` object via a naming convention).
-  * The object is directly or indirectly linked to a `ClusterResourceSet` object (through the `OwnerReference` chain).
-  * The object is directly or indirectly linked to another object with the `clusterctl.cluster.x-k8s.io/move-hierarchy` 
-    label, e.g. the infrastructure Provider ClusterIdentity objects (linked through the `OwnerReference` chain).
-  * The object hase the `clusterctl.cluster.x-k8s.io/move` label or the `clusterctl.cluster.x-k8s.io/move-hierarchy` label,
-    e.g. the CPI config secret. 
-    
-Note. `clusterctl.cluster.x-k8s.io/move` and `clusterctl.cluster.x-k8s.io/move-hierarchy` labels could be applied 
-to single objects or at the CRD level (the label applies to all the objects).
-    
-Please note that during move:
-  * Namespaced objects, if not existing in the target cluster, are created.
-  * Namespaced objects, if already existing in the target cluster, are updated.  
-  * Namespaced objects are removed from the source cluster.
-  * Global objects, if not existing in the target cluster, are created.
-  * Global objects, if already existing in the target cluster, are not updated.
-  * Global objects are not removed from the source cluster.
-  * Namespaced objects which are part of an owner chain that starts with a global object (e.g. a secret containing 
-    credentials for an infrastructure Provider ClusterIdentity) are treated as Global objects.
+* All the objects of Kind defined in one of the CRDs installed by clusterctl using `clusterctl init` from the namespace being moved.
+* `ConfigMap` objects from the namespace being moved.
+* `Secret` objects from the namespace being moved and from the namespaces where infrastructure providers are installed.
+
+`clusterctl move` does NOT consider any objects:
+
+* Not included in the set of objects defined above.
+* Included in the set of objects defined above, but not:
+  * Directly or indirectly linked to a `Cluster` object through the `OwnerReference` chain.
+  * Directly or indirectly linked to a `ClusterResourceSet` object through the `OwnerReference` chain.
+  * Explicitly required to move via the "move" label (`clusterctl.cluster.x-k8s.io/move`) attached to the object or to
+    the CRD definition.
 
 <aside class="note warning">
 
diff --git a/go.mod b/go.mod
index 0ae928c2e..5c2b32771 100644
--- a/go.mod
+++ b/go.mod
@@ -17,7 +17,7 @@ require (
 	github.com/google/go-github/v33 v33.0.0
 	github.com/google/gofuzz v1.2.0
 	github.com/gosuri/uitable v0.0.4
-	github.com/onsi/ginkgo v1.16.2
+	github.com/onsi/ginkgo v1.16.3
 	github.com/onsi/gomega v1.12.0
 	github.com/pkg/errors v0.9.1
 	github.com/spf13/cobra v1.1.3
@@ -35,7 +35,7 @@ require (
 	k8s.io/component-base v0.21.1
 	k8s.io/klog/v2 v2.8.0
 	k8s.io/kubectl v0.21.1
-	k8s.io/utils v0.0.0-20210305010621-2afb4311ab10
-	sigs.k8s.io/controller-runtime v0.9.0-beta.5
+	k8s.io/utils v0.0.0-20210517184530-5a248b5acedc
+	sigs.k8s.io/controller-runtime v0.9.0-beta.6
 	sigs.k8s.io/yaml v1.2.0
 )
diff --git a/go.sum b/go.sum
index 2274b1287..c15891214 100644
--- a/go.sum
+++ b/go.sum
@@ -153,6 +153,7 @@ github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT
 github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
 github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=
 github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
@@ -241,7 +242,6 @@ github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFG
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
@@ -284,6 +284,7 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-github/v33 v33.0.0 h1:qAf9yP0qc54ufQxzwv+u9H0tiVOnPJxo0lI/JXqw3ZM=
@@ -309,8 +310,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
-github.com/googleapis/gnostic v0.5.4 h1:ynbQIWjLw7iv6HAFdixb30U7Uvcmx+f4KlLJpmhkTK0=
-github.com/googleapis/gnostic v0.5.4/go.mod h1:TRWw1s4gxBGjSe301Dai3c7wXJAZy57+/6tawkOvqHQ=
+github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=
+github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
@@ -362,11 +363,12 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
 github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
-github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
-github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
+github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jimstudt/http-authentication v0.0.0-20140401203705-3eca13d6893a/go.mod h1:wK6yTYYcgjHE1Z1QtXACPDjcFJyBskHEdagmnq3vsP8=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
@@ -384,7 +386,6 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
-github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
@@ -486,16 +487,15 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
 github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
-github.com/onsi/ginkgo v1.16.2 h1:HFB2fbVIlhIfCfOW81bZFbiC/RvnpXSdhbF2/DJr134=
 github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
+github.com/onsi/ginkgo v1.16.3 h1:3s86PZkI1ApJh6HFIzC1gXby/mIyZqfE5zxSvtoBSsM=
+github.com/onsi/ginkgo v1.16.3/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
 github.com/onsi/gomega v1.12.0 h1:p4oGGk2M2UJc0wWN4lHFvIB71lxsh0T/UiKCCgFADY8=
 github.com/onsi/gomega v1.12.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
@@ -533,8 +533,8 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
-github.com/prometheus/client_golang v1.9.0 h1:Rrch9mh17XcxvEu9D9DEpb4isxjGBtcevQjKvxPRQIU=
-github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU=
+github.com/prometheus/client_golang v1.10.0 h1:/o0BDeWzLWXNZ+4q5gXltUvaMpJqckTa+jTNoB+z4cg=
+github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -548,16 +548,17 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
-github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tyspaKM=
-github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
+github.com/prometheus/common v0.18.0 h1:WCVKW7aL6LEe1uryfI9dnEc2ZqNB1Fn0ok930v0iL1Y=
+github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
-github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4=
 github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
+github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
+github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
@@ -752,7 +753,6 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
 golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
@@ -769,6 +769,7 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -814,10 +815,11 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
@@ -841,7 +843,6 @@ golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxb
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -892,8 +893,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-gomodules.xyz/jsonpatch/v2 v2.1.0 h1:Phva6wqu+xR//Njw6iorylFFgn/z547tw5Ne3HZPQ+k=
-gomodules.xyz/jsonpatch/v2 v2.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=
+gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY=
+gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY=
 google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@@ -1007,27 +1008,20 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU=
 k8s.io/api v0.21.1 h1:94bbZ5NTjdINJEdzOkpS4vdPhkb1VFpTYC9zh43f75c=
 k8s.io/api v0.21.1/go.mod h1:FstGROTmsSHBarKc8bylzXih8BLNYTiS3TZcsoEDg2s=
-k8s.io/apiextensions-apiserver v0.21.0/go.mod h1:gsQGNtGkc/YoDG9loKI0V+oLZM4ljRPjc/sql5tmvzc=
 k8s.io/apiextensions-apiserver v0.21.1 h1:AA+cnsb6w7SZ1vD32Z+zdgfXdXY8X9uGX5bN6EoPEIo=
 k8s.io/apiextensions-apiserver v0.21.1/go.mod h1:KESQFCGjqVcVsZ9g0xX5bacMjyX5emuWcS2arzdEouA=
-k8s.io/apimachinery v0.21.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY=
 k8s.io/apimachinery v0.21.1 h1:Q6XuHGlj2xc+hlMCvqyYfbv3H7SRGn2c8NycxJquDVs=
 k8s.io/apimachinery v0.21.1/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY=
-k8s.io/apiserver v0.21.0/go.mod h1:w2YSn4/WIwYuxG5zJmcqtRdtqgW/J2JRgFAqps3bBpg=
 k8s.io/apiserver v0.21.1 h1:wTRcid53IhxhbFt4KTrFSw8tAncfr01EP91lzfcygVg=
 k8s.io/apiserver v0.21.1/go.mod h1:nLLYZvMWn35glJ4/FZRhzLG/3MPxAaZTgV4FJZdr+tY=
 k8s.io/cli-runtime v0.21.1/go.mod h1:TI9Bvl8lQWZB2KqE91QLCp9AZE4l29zNFnj/x4IX4Fw=
-k8s.io/client-go v0.21.0/go.mod h1:nNBytTF9qPFDEhoqgEPaarobC8QPae13bElIVHzIglA=
 k8s.io/client-go v0.21.1 h1:bhblWYLZKUu+pm50plvQF8WpY6TXdRRtcS/K9WauOj4=
 k8s.io/client-go v0.21.1/go.mod h1:/kEw4RgW+3xnBGzvp9IWxKSNA+lXn3A7AuH3gdOAzLs=
 k8s.io/cluster-bootstrap v0.21.1 h1:wrk2jHNrXK7LKwLfmgZRkk0Od3LJtBmf2t5+cFHxdOE=
 k8s.io/cluster-bootstrap v0.21.1/go.mod h1:izdXmPTfqI9gkjQLf9PQ1Y9/DSqG54zU2UByEzgdajs=
-k8s.io/code-generator v0.21.0/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q=
 k8s.io/code-generator v0.21.1/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q=
-k8s.io/component-base v0.21.0/go.mod h1:qvtjz6X0USWXbgmbfXR+Agik4RZ3jv2Bgr5QnZzdPYw=
 k8s.io/component-base v0.21.1 h1:iLpj2btXbR326s/xNQWmPNGu0gaYSjzn7IN/5i28nQw=
 k8s.io/component-base v0.21.1/go.mod h1:NgzFZ2qu4m1juby4TnrmpR8adRk6ka62YdH5DkIIyKA=
 k8s.io/component-helpers v0.21.1/go.mod h1:FtC1flbiQlosHQrLrRUulnKxE4ajgWCGy/67fT2GRlQ=
@@ -1043,15 +1037,14 @@ k8s.io/kubectl v0.21.1 h1:ySEusoeSgSDSiSBncDMsNrthSa3OSlXqT4R2rf1VFTw=
 k8s.io/kubectl v0.21.1/go.mod h1:PMYR88MqESuysBM/MX+Vu4JbX/50nY4d4kny+SPEI2U=
 k8s.io/metrics v0.21.1/go.mod h1:pyDVLsLe++FIGDBFU80NcW4xMFsuiVTWL8Zfi7+PpNo=
 k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
-k8s.io/utils v0.0.0-20210111153108-fddb29f9d009/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
-k8s.io/utils v0.0.0-20210305010621-2afb4311ab10 h1:u5rPykqiCpL+LBfjRkXvnK71gOgIdmq3eHUEkPrbeTI=
-k8s.io/utils v0.0.0-20210305010621-2afb4311ab10/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
+k8s.io/utils v0.0.0-20210517184530-5a248b5acedc h1:cIS13bDBZaWqngldgGuDypv4z+zjcYgTKv72k6bMAn0=
+k8s.io/utils v0.0.0-20210517184530-5a248b5acedc/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
-sigs.k8s.io/controller-runtime v0.9.0-beta.5 h1:yteq8am8lUFvlI/bfIgySMp/MqAPXiAghxX+XrMCifE=
-sigs.k8s.io/controller-runtime v0.9.0-beta.5/go.mod h1:ufPDuvefw2Y1KnBgHQrLdOjueYlj+XJV2AszbT+WTxs=
+sigs.k8s.io/controller-runtime v0.9.0-beta.6 h1:ac786Z1lZ2sgFIw98veqp+9YNXG5v/BxUBvEgiOVmDU=
+sigs.k8s.io/controller-runtime v0.9.0-beta.6/go.mod h1:rgf+cBz72pYlKXDRNhI1WFQv/S86EMUV4/ySmsEYgHk=
 sigs.k8s.io/kustomize/api v0.8.8/go.mod h1:He1zoK0nk43Pc6NlV085xDXDXTNprtcyKZVm3swsdNY=
 sigs.k8s.io/kustomize/cmd/config v0.9.10/go.mod h1:Mrby0WnRH7hA6OwOYnYpfpiY0WJIMgYrEDfwOeFdMK0=
 sigs.k8s.io/kustomize/kustomize/v4 v4.1.2/go.mod h1:PxBvo4WGYlCLeRPL+ziT64wBXqbgfcalOS/SXa/tcyo=
diff --git a/hack/tools/go.mod b/hack/tools/go.mod
index 9f0e58e31..abb6bcf38 100644
--- a/hack/tools/go.mod
+++ b/hack/tools/go.mod
@@ -6,14 +6,14 @@ require (
 	github.com/blang/semver v3.5.1+incompatible
 	github.com/drone/envsubst/v2 v2.0.0-20210305151453-490366e43a3c
 	github.com/joelanford/go-apidiff v0.1.0
-	github.com/onsi/ginkgo v1.16.1
+	github.com/onsi/ginkgo v1.16.3
 	github.com/sergi/go-diff v1.1.0 // indirect
 	github.com/stretchr/testify v1.7.0 // indirect
 	golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 // indirect
 	golang.org/x/text v0.3.5 // indirect
 	golang.org/x/tools v0.1.2-0.20210512205948-8287d5da45e4
 	gotest.tools/gotestsum v1.6.3
-	k8s.io/code-generator v0.21.0
+	k8s.io/code-generator v0.21.1
 	sigs.k8s.io/controller-tools v0.6.0-beta.0
 	sigs.k8s.io/kubebuilder/docs/book/utils v0.0.0-20200226075303-ed8438ec10a4
 )
diff --git a/hack/tools/go.sum b/hack/tools/go.sum
index b9fb3ff80..649918b17 100644
--- a/hack/tools/go.sum
+++ b/hack/tools/go.sum
@@ -308,8 +308,9 @@ github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.16.1 h1:foqVmeWDD6yYpK+Yz3fHyNIxFYNxswxqNFjSKe+vI54=
 github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
+github.com/onsi/ginkgo v1.16.3 h1:3s86PZkI1ApJh6HFIzC1gXby/mIyZqfE5zxSvtoBSsM=
+github.com/onsi/ginkgo v1.16.3/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
@@ -740,8 +741,9 @@ k8s.io/apimachinery v0.21.0 h1:3Fx+41if+IRavNcKOz09FwEXDBG6ORh6iMsTSelhkMA=
 k8s.io/apimachinery v0.21.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY=
 k8s.io/apiserver v0.21.0/go.mod h1:w2YSn4/WIwYuxG5zJmcqtRdtqgW/J2JRgFAqps3bBpg=
 k8s.io/client-go v0.21.0/go.mod h1:nNBytTF9qPFDEhoqgEPaarobC8QPae13bElIVHzIglA=
-k8s.io/code-generator v0.21.0 h1:LGWJOvkbBNpuRBqBRXUjzfvymUh7F/iR2KDpwLnqCM4=
 k8s.io/code-generator v0.21.0/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q=
+k8s.io/code-generator v0.21.1 h1:jvcxHpVu5dm/LMXr3GOj/jroiP8+v2YnJE9i2OVRenk=
+k8s.io/code-generator v0.21.1/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q=
 k8s.io/component-base v0.21.0/go.mod h1:qvtjz6X0USWXbgmbfXR+Agik4RZ3jv2Bgr5QnZzdPYw=
 k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027 h1:Uusb3oh8XcdzDF/ndlI4ToKTYVlkCSJP39SRY2mfRAw=


Test patch:
diff --git a/cmd/clusterctl/client/cluster/components_test.go b/cmd/clusterctl/client/cluster/components_test.go
index cda8af22c..c2b7ed85f 100644
--- a/cmd/clusterctl/client/cluster/components_test.go
+++ b/cmd/clusterctl/client/cluster/components_test.go
@@ -28,6 +28,7 @@ import (
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha4"
 	clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
+	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
 	"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
@@ -36,18 +37,22 @@ func Test_providerComponents_Delete(t *testing.T) {
 	labels := map[string]string{
 		clusterv1.ProviderLabelName: "infrastructure-infra",
 	}
+	sharedLabels := map[string]string{
+		clusterv1.ProviderLabelName:                      "infrastructure-infra",
+		clusterctlv1.ClusterctlResourceLifecyleLabelName: string(clusterctlv1.ResourceLifecycleShared),
+	}
 
 	crd := unstructured.Unstructured{}
 	crd.SetAPIVersion("apiextensions.k8s.io/v1beta1")
 	crd.SetKind("CustomResourceDefinition")
 	crd.SetName("crd1")
-	crd.SetLabels(labels)
+	crd.SetLabels(sharedLabels)
 
 	mutatingWebhook := unstructured.Unstructured{}
 	mutatingWebhook.SetAPIVersion("admissionregistration.k8s.io/v1beta1")
 	mutatingWebhook.SetKind("MutatingWebhookConfiguration")
 	mutatingWebhook.SetName("mwh1")
-	mutatingWebhook.SetLabels(labels)
+	mutatingWebhook.SetLabels(sharedLabels)
 
 	initObjs := []client.Object{
 		// Namespace (should be deleted only if includeNamespace)
@@ -84,6 +89,28 @@ func Test_providerComponents_Delete(t *testing.T) {
 		// CRDs (should be deleted only if includeCRD)
 		&crd,
 		&mutatingWebhook,
+		&corev1.Namespace{
+			TypeMeta: metav1.TypeMeta{
+				Kind: "Namespace",
+			},
+			ObjectMeta: metav1.ObjectMeta{
+				Name: repository.WebhookNamespaceName,
+				Labels: map[string]string{
+					clusterctlv1.ClusterctlResourceLifecyleLabelName: string(clusterctlv1.ResourceLifecycleShared),
+					// NB. the capi-webhook-system namespace doe not have a provider label (see fixSharedLabels)
+				},
+			},
+		},
+		&corev1.Pod{
+			TypeMeta: metav1.TypeMeta{
+				Kind: "Pod",
+			},
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: repository.WebhookNamespaceName,
+				Name:      "podx",
+				Labels:    sharedLabels,
+			},
+		},
 		// A cluster-wide provider component (should always be deleted)
 		&rbacv1.ClusterRole{
 			TypeMeta: metav1.TypeMeta{
@@ -100,7 +127,8 @@ func Test_providerComponents_Delete(t *testing.T) {
 				Kind: "ClusterRole",
 			},
 			ObjectMeta: metav1.ObjectMeta{
-				Name: "some-cluster-role",
+				Name:   "some-cluster-role",
+				Labels: labels,
 			},
 		},
 		// Another object out of the provider namespace (should never be deleted)
@@ -140,14 +168,16 @@ func Test_providerComponents_Delete(t *testing.T) {
 				includeCRD:       false,
 			},
 			wantDiff: []wantDiff{
-				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Namespace", Name: "ns1"}, deleted: false},                                                      // namespace should be preserved
-				{object: corev1.ObjectReference{APIVersion: "apiextensions.k8s.io/v1beta1", Kind: "CustomResourceDefinition", Name: "crd1"}, deleted: false},            // crd should be preserved
-				{object: corev1.ObjectReference{APIVersion: "admissionregistration.k8s.io/v1beta1", Kind: "MutatingWebhookConfiguration", Name: "mwh1"}, deleted: true}, // MutatingWebhookConfiguration goes away with the controller
-				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod1"}, deleted: true},                                          // provider components should be deleted
-				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod2"}, deleted: false},                                         // other objects in the namespace should not be deleted
-				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns2", Name: "pod3"}, deleted: false},                                         // this object is in another namespace, and should never be touched by delete
-				{object: corev1.ObjectReference{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole", Name: "ns1-cluster-role"}, deleted: true},              // cluster-wide provider components should be deleted
-				{object: corev1.ObjectReference{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole", Name: "some-cluster-role"}, deleted: false},            // other cluster-wide objects should be preserved
+				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Namespace", Name: "ns1"}, deleted: false},                                                       // namespace should be preserved
+				{object: corev1.ObjectReference{APIVersion: "apiextensions.k8s.io/v1beta1", Kind: "CustomResourceDefinition", Name: "crd1"}, deleted: false},             // crd should be preserved
+				{object: corev1.ObjectReference{APIVersion: "admissionregistration.k8s.io/v1beta1", Kind: "MutatingWebhookConfiguration", Name: "mwh1"}, deleted: false}, // MutatingWebhookConfiguration should be preserved
+				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Namespace", Name: repository.WebhookNamespaceName}, deleted: false},                             // capi-webhook-system namespace should never be deleted
+				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: repository.WebhookNamespaceName, Name: "podx"}, deleted: false},                // provider objects in the capi-webhook-system namespace should be preserved
+				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod1"}, deleted: true},                                           // provider components should be deleted
+				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod2"}, deleted: false},                                          // other objects in the namespace should not be deleted
+				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns2", Name: "pod3"}, deleted: false},                                          // this object is in another namespace, and should never be touched by delete
+				{object: corev1.ObjectReference{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole", Name: "ns1-cluster-role"}, deleted: true},               // cluster-wide provider components should be deleted
+				{object: corev1.ObjectReference{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole", Name: "some-cluster-role"}, deleted: false},             // other cluster-wide objects should be preserved
 			},
 			wantErr: false,
 		},
@@ -159,14 +189,16 @@ func Test_providerComponents_Delete(t *testing.T) {
 				includeCRD:       false,
 			},
 			wantDiff: []wantDiff{
-				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Namespace", Name: "ns1"}, deleted: true},                                                       // namespace should be deleted
-				{object: corev1.ObjectReference{APIVersion: "apiextensions.k8s.io/v1beta1", Kind: "CustomResourceDefinition", Name: "crd1"}, deleted: false},            // crd should be preserved
-				{object: corev1.ObjectReference{APIVersion: "admissionregistration.k8s.io/v1beta1", Kind: "MutatingWebhookConfiguration", Name: "mwh1"}, deleted: true}, // MutatingWebhookConfiguration goes away with the controller
-				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod1"}, deleted: true},                                          // provider components should be deleted
-				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod2"}, deleted: true},                                          // other objects in the namespace goes away when deleting the namespace
-				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns2", Name: "pod3"}, deleted: false},                                         // this object is in another namespace, and should never be touched by delete
-				{object: corev1.ObjectReference{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole", Name: "ns1-cluster-role"}, deleted: true},              // cluster-wide provider components should be deleted
-				{object: corev1.ObjectReference{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole", Name: "some-cluster-role"}, deleted: false},            // other cluster-wide objects should be preserved
+				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Namespace", Name: "ns1"}, deleted: true},                                                        // namespace should be deleted
+				{object: corev1.ObjectReference{APIVersion: "apiextensions.k8s.io/v1beta1", Kind: "CustomResourceDefinition", Name: "crd1"}, deleted: false},             // crd should be preserved
+				{object: corev1.ObjectReference{APIVersion: "admissionregistration.k8s.io/v1beta1", Kind: "MutatingWebhookConfiguration", Name: "mwh1"}, deleted: false}, // MutatingWebhookConfiguration should be preserved
+				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Namespace", Name: repository.WebhookNamespaceName}, deleted: false},                             // capi-webhook-system namespace should never be deleted
+				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: repository.WebhookNamespaceName, Name: "podx"}, deleted: false},                // provider objects in the capi-webhook-system namespace should be preserved
+				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod1"}, deleted: true},                                           // provider components should be deleted
+				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod2"}, deleted: true},                                           // other objects in the namespace goes away when deleting the namespace
+				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns2", Name: "pod3"}, deleted: false},                                          // this object is in another namespace, and should never be touched by delete
+				{object: corev1.ObjectReference{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole", Name: "ns1-cluster-role"}, deleted: true},               // cluster-wide provider components should be deleted
+				{object: corev1.ObjectReference{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole", Name: "some-cluster-role"}, deleted: false},             // other cluster-wide objects should be preserved
 			},
 			wantErr: false,
 		},
@@ -181,6 +213,8 @@ func Test_providerComponents_Delete(t *testing.T) {
 				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Namespace", Name: "ns1"}, deleted: false},                                                      // namespace should be preserved
 				{object: corev1.ObjectReference{APIVersion: "apiextensions.k8s.io/v1beta1", Kind: "CustomResourceDefinition", Name: "crd1"}, deleted: true},             // crd should be deleted
 				{object: corev1.ObjectReference{APIVersion: "admissionregistration.k8s.io/v1beta1", Kind: "MutatingWebhookConfiguration", Name: "mwh1"}, deleted: true}, // MutatingWebhookConfiguration should be deleted
+				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Namespace", Name: repository.WebhookNamespaceName}, deleted: false},                            // capi-webhook-system namespace should never be deleted
+				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: repository.WebhookNamespaceName, Name: "podx"}, deleted: true},                // provider objects in the capi-webhook-system namespace should be deleted
 				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod1"}, deleted: true},                                          // provider components should be deleted
 				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod2"}, deleted: false},                                         // other objects in the namespace should not be deleted
 				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns2", Name: "pod3"}, deleted: false},                                         // this object is in another namespace, and should never be touched by delete
@@ -200,6 +234,8 @@ func Test_providerComponents_Delete(t *testing.T) {
 				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Namespace", Name: "ns1"}, deleted: true},                                                       // namespace should be deleted
 				{object: corev1.ObjectReference{APIVersion: "apiextensions.k8s.io/v1beta1", Kind: "CustomResourceDefinition", Name: "crd1"}, deleted: true},             // crd should be deleted
 				{object: corev1.ObjectReference{APIVersion: "admissionregistration.k8s.io/v1beta1", Kind: "MutatingWebhookConfiguration", Name: "mwh1"}, deleted: true}, // MutatingWebhookConfiguration should be deleted
+				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Namespace", Name: repository.WebhookNamespaceName}, deleted: false},                            // capi-webhook-namespace should never be deleted
+				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: repository.WebhookNamespaceName, Name: "podx"}, deleted: true},                // provider objects in the capi-webhook-namespace should be deleted
 				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod1"}, deleted: true},                                          // provider components should be deleted
 				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns1", Name: "pod2"}, deleted: true},                                          // other objects in the namespace goes away when deleting the namespace
 				{object: corev1.ObjectReference{APIVersion: "v1", Kind: "Pod", Namespace: "ns2", Name: "pod3"}, deleted: false},                                         // this object is in another namespace, and should never be touched by delete
diff --git a/cmd/clusterctl/client/cluster/installer_test.go b/cmd/clusterctl/client/cluster/installer_test.go
index 7ee159e20..f48a54084 100644
--- a/cmd/clusterctl/client/cluster/installer_test.go
+++ b/cmd/clusterctl/client/cluster/installer_test.go
@@ -282,7 +282,11 @@ func (c *fakeComponents) InventoryObject() clusterctlv1.Provider {
 	return c.inventoryObject
 }
 
-func (c *fakeComponents) Objs() []unstructured.Unstructured {
+func (c *fakeComponents) InstanceObjs() []unstructured.Unstructured {
+	panic("not implemented")
+}
+
+func (c *fakeComponents) SharedObjs() []unstructured.Unstructured {
 	panic("not implemented")
 }
 
@@ -297,3 +301,72 @@ func newFakeComponents(name string, providerType clusterctlv1.ProviderType, vers
 		inventoryObject: inventoryObject,
 	}
 }
+
+func Test_shouldInstallSharedComponents(t *testing.T) {
+	type args struct {
+		providerList *clusterctlv1.ProviderList
+		provider     clusterctlv1.Provider
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    bool
+		wantErr bool
+	}{
+		{
+			name: "First instance of the provider, must install shared components",
+			args: args{
+				providerList: &clusterctlv1.ProviderList{Items: []clusterctlv1.Provider{}}, // no core provider installed
+				provider:     fakeProvider("core", clusterctlv1.CoreProviderType, "v2.0.0", ""),
+			},
+			want:    true,
+			wantErr: false,
+		},
+		{
+			name: "Second instance of the provider, same version, must NOT install shared components",
+			args: args{
+				providerList: &clusterctlv1.ProviderList{Items: []clusterctlv1.Provider{
+					fakeProvider("core", clusterctlv1.CoreProviderType, "v2.0.0", ""),
+				}},
+				provider: fakeProvider("core", clusterctlv1.CoreProviderType, "v2.0.0", ""),
+			},
+			want:    false,
+			wantErr: false,
+		},
+		{
+			name: "Second instance of the provider, older version, must NOT install shared components",
+			args: args{
+				providerList: &clusterctlv1.ProviderList{Items: []clusterctlv1.Provider{
+					fakeProvider("core", clusterctlv1.CoreProviderType, "v2.0.0", ""),
+				}},
+				provider: fakeProvider("core", clusterctlv1.CoreProviderType, "v1.0.0", ""),
+			},
+			want:    false,
+			wantErr: false,
+		},
+		{
+			name: "Second instance of the provider, newer version, must install shared components",
+			args: args{
+				providerList: &clusterctlv1.ProviderList{Items: []clusterctlv1.Provider{
+					fakeProvider("core", clusterctlv1.CoreProviderType, "v2.0.0", ""),
+				}},
+				provider: fakeProvider("core", clusterctlv1.CoreProviderType, "v3.0.0", ""),
+			},
+			want:    true,
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			g := NewWithT(t)
+
+			got, err := shouldInstallSharedComponents(tt.args.providerList, tt.args.provider)
+			if tt.wantErr {
+				g.Expect(err).To(HaveOccurred())
+				return
+			}
+			g.Expect(err).NotTo(HaveOccurred())
+			g.Expect(got).To(Equal(tt.want))
+		})
+	}
+}
diff --git a/cmd/clusterctl/client/cluster/mover_test.go b/cmd/clusterctl/client/cluster/mover_test.go
index 93adfb0bd..c5b32c8c1 100644
--- a/cmd/clusterctl/client/cluster/mover_test.go
+++ b/cmd/clusterctl/client/cluster/mover_test.go
@@ -24,11 +24,9 @@ import (
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/utils/pointer"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha4"
 	clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
 	"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test"
-	"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test/providers/infrastructure"
 	"sigs.k8s.io/cluster-api/util/conditions"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
@@ -62,7 +60,7 @@ var moveTests = []struct {
 		wantErr: false,
 	},
 	{
-		name: "Cluster with cloud config secret with the force move label",
+		name: "Cluster with external objects marked with move label",
 		fields: moveTestsFields{
 			objs: test.NewFakeCluster("ns1", "foo").WithCloudConfigSecret().Objs(),
 		},
@@ -415,69 +413,31 @@ var moveTests = []struct {
 		},
 	},
 	{
-		// NOTE: External objects are CRD types installed by clusterctl, but not directly related with the CAPI hierarchy of objects. e.g. IPAM claims.
-		name: "Namespaced External Objects with force move label",
+		name: "Cluster and global + namespaced external objects with force-move label",
 		fields: moveTestsFields{
-			objs: test.NewFakeExternalObject("ns1", "externalObject1").Objs(),
-		},
-		wantMoveGroups: [][]string{
-			{ // group1
-				"external.cluster.x-k8s.io/v1alpha4, Kind=GenericExternalObject, ns1/externalObject1",
-			},
-		},
-		wantErr: false,
-	},
-	{
-		// NOTE: External objects are CRD types installed by clusterctl, but not directly related with the CAPI hierarchy of objects. e.g. IPAM claims.
-		name: "Global External Objects with force move label",
-		fields: moveTestsFields{
-			objs: test.NewFakeClusterExternalObject("externalObject1").Objs(),
+			func() []client.Object {
+				objs := []client.Object{}
+				objs = append(objs, test.NewFakeCluster("ns1", "foo").Objs()...)
+				objs = append(objs, test.NewFakeExternalObject("ns1", "externalTest1").Objs()...)
+				objs = append(objs, test.NewFakeExternalObject("", "externalTest2").Objs()...)
+				return objs
+			}(),
 		},
 		wantMoveGroups: [][]string{
 			{ // group1
-				"external.cluster.x-k8s.io/v1alpha4, Kind=GenericClusterExternalObject, /externalObject1",
-			},
-		},
-		wantErr: false,
-	},
-	{
-		name: "Cluster owning a secret with infrastructure credentials",
-		fields: moveTestsFields{
-			objs: test.NewFakeCluster("ns1", "foo").
-				WithCredentialSecret().Objs(),
-		},
-		wantMoveGroups: [][]string{
-			{ // group 1
 				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/foo",
+				"external.cluster.x-k8s.io/v1alpha4, Kind=GenericExternalObject, ns1/externalTest1",
+				"external.cluster.x-k8s.io/v1alpha4, Kind=GenericExternalObject, /externalTest2",
 			},
 			{ // group 2 (objects with ownerReferences in group 1)
 				// owned by Clusters
 				"/v1, Kind=Secret, ns1/foo-ca",
-				"/v1, Kind=Secret, ns1/foo-credentials",
 				"/v1, Kind=Secret, ns1/foo-kubeconfig",
 				"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericInfrastructureCluster, ns1/foo",
 			},
 		},
 		wantErr: false,
 	},
-	{
-		name: "A global identity for an infrastructure provider owning a Secret with credentials in the provider's namespace",
-		fields: moveTestsFields{
-			objs: test.NewFakeClusterInfrastructureIdentity("infra1-identity").
-				WithSecretIn("infra1-system"). // a secret in infra1-system namespace, where an infrastructure provider is installed
-				Objs(),
-		},
-		wantMoveGroups: [][]string{
-			{ // group 1
-				"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericClusterInfrastructureIdentity, /infra1-identity",
-			},
-			{ // group 2 (objects with ownerReferences in group 1)
-				// owned by Clusters
-				"/v1, Kind=Secret, infra1-system/infra1-identity-credentials",
-			},
-		},
-		wantErr: false,
-	},
 }
 
 func Test_getMoveSequence(t *testing.T) {
@@ -638,7 +598,7 @@ func Test_objectMover_move(t *testing.T) {
 
 				err := csFrom.Get(ctx, key, oFrom)
 				if err == nil {
-					if !node.isGlobal && !node.isGlobalHierarchy {
+					if oFrom.GetNamespace() != "" {
 						t.Errorf("%v not deleted in source cluster", key)
 						continue
 					}
@@ -1050,7 +1010,7 @@ func Test_objectMoverService_ensureNamespaces(t *testing.T) {
 
 	cluster1 := test.NewFakeCluster("namespace-1", "cluster-1")
 	cluster2 := test.NewFakeCluster("namespace-2", "cluster-2")
-	globalObj := test.NewFakeClusterExternalObject("eo-1")
+	globalObj := test.NewFakeExternalObject("", "eo-1")
 
 	clustersObjs := append(cluster1.Objs(), cluster2.Objs()...)
 
@@ -1154,364 +1114,3 @@ func Test_objectMoverService_ensureNamespaces(t *testing.T) {
 		})
 	}
 }
-
-func Test_createTargetObject(t *testing.T) {
-	type args struct {
-		fromProxy Proxy
-		toProxy   Proxy
-		node      *node
-	}
-
-	tests := []struct {
-		name    string
-		args    args
-		want    func(*WithT, client.Client)
-		wantErr bool
-	}{
-		{
-			name: "fails if the object is missing from the source",
-			args: args{
-				fromProxy: test.NewFakeProxy(),
-				toProxy: test.NewFakeProxy().WithObjs(
-					&clusterv1.Cluster{
-						ObjectMeta: metav1.ObjectMeta{
-							Name:      "foo",
-							Namespace: "ns1",
-						},
-					},
-				),
-				node: &node{
-					identity: corev1.ObjectReference{
-						Kind:       "Cluster",
-						Namespace:  "ns1",
-						Name:       "foo",
-						APIVersion: "cluster.x-k8s.io/v1alpha4",
-					},
-				},
-			},
-			wantErr: true,
-		},
-		{
-			name: "creates the object with owner references if not exists",
-			args: args{
-				fromProxy: test.NewFakeProxy().WithObjs(
-					&clusterv1.Cluster{
-						ObjectMeta: metav1.ObjectMeta{
-							Name:      "foo",
-							Namespace: "ns1",
-						},
-					},
-				),
-				toProxy: test.NewFakeProxy(),
-				node: &node{
-					identity: corev1.ObjectReference{
-						Kind:       "Cluster",
-						Namespace:  "ns1",
-						Name:       "foo",
-						APIVersion: "cluster.x-k8s.io/v1alpha4",
-					},
-					owners: map[*node]ownerReferenceAttributes{
-						{
-							identity: corev1.ObjectReference{
-								Kind:       "Something",
-								Namespace:  "ns1",
-								Name:       "bar",
-								APIVersion: "cluster.x-k8s.io/v1alpha4",
-							},
-						}: {
-							Controller: pointer.BoolPtr(true),
-						},
-					},
-				},
-			},
-			want: func(g *WithT, toClient client.Client) {
-				c := &clusterv1.Cluster{}
-				key := client.ObjectKey{
-					Namespace: "ns1",
-					Name:      "foo",
-				}
-				g.Expect(toClient.Get(ctx, key, c)).ToNot(HaveOccurred())
-				g.Expect(c.OwnerReferences).To(HaveLen(1))
-				g.Expect(c.OwnerReferences[0].Controller).To(Equal(pointer.BoolPtr(true)))
-			},
-		},
-		{
-			name: "updates the object if it already exists and the object is not Global/GlobalHierarchy",
-			args: args{
-				fromProxy: test.NewFakeProxy().WithObjs(
-					&clusterv1.Cluster{
-						ObjectMeta: metav1.ObjectMeta{
-							Name:      "foo",
-							Namespace: "ns1",
-						},
-					},
-				),
-				toProxy: test.NewFakeProxy().WithObjs(
-					&clusterv1.Cluster{
-						ObjectMeta: metav1.ObjectMeta{
-							Name:        "foo",
-							Namespace:   "ns1",
-							Annotations: map[string]string{"foo": "bar"},
-						},
-					},
-				),
-				node: &node{
-					identity: corev1.ObjectReference{
-						Kind:       "Cluster",
-						Namespace:  "ns1",
-						Name:       "foo",
-						APIVersion: "cluster.x-k8s.io/v1alpha4",
-					},
-				},
-			},
-			want: func(g *WithT, toClient client.Client) {
-				c := &clusterv1.Cluster{}
-				key := client.ObjectKey{
-					Namespace: "ns1",
-					Name:      "foo",
-				}
-				g.Expect(toClient.Get(ctx, key, c)).ToNot(HaveOccurred())
-				g.Expect(c.Annotations).To(BeEmpty())
-			},
-		},
-		{
-			name: "should not update Global objects",
-			args: args{
-				fromProxy: test.NewFakeProxy().WithObjs(
-					&infrastructure.GenericClusterInfrastructureIdentity{
-						ObjectMeta: metav1.ObjectMeta{
-							Name: "foo",
-						},
-					},
-				),
-				toProxy: test.NewFakeProxy().WithObjs(
-					&infrastructure.GenericClusterInfrastructureIdentity{
-						ObjectMeta: metav1.ObjectMeta{
-							Name:        "foo",
-							Annotations: map[string]string{"foo": "bar"},
-						},
-					},
-				),
-				node: &node{
-					identity: corev1.ObjectReference{
-						Kind:       "GenericClusterInfrastructureIdentity",
-						Name:       "foo",
-						APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha4",
-					},
-					isGlobal: true,
-				},
-			},
-			want: func(g *WithT, toClient client.Client) {
-				c := &infrastructure.GenericClusterInfrastructureIdentity{}
-				key := client.ObjectKey{
-					Name: "foo",
-				}
-				g.Expect(toClient.Get(ctx, key, c)).ToNot(HaveOccurred())
-				g.Expect(c.Annotations).ToNot(BeEmpty())
-			},
-		},
-		{
-			name: "should not update Global Hierarchy objects",
-			args: args{
-				fromProxy: test.NewFakeProxy().WithObjs(
-					&corev1.Secret{
-						ObjectMeta: metav1.ObjectMeta{
-							Name:      "foo",
-							Namespace: "ns1",
-						},
-					},
-				),
-				toProxy: test.NewFakeProxy().WithObjs(
-					&corev1.Secret{
-						ObjectMeta: metav1.ObjectMeta{
-							Name:        "foo",
-							Namespace:   "ns1",
-							Annotations: map[string]string{"foo": "bar"},
-						},
-					},
-				),
-				node: &node{
-					identity: corev1.ObjectReference{
-						Kind:       "Secret",
-						Namespace:  "ns1",
-						Name:       "foo",
-						APIVersion: "v1",
-					},
-					isGlobalHierarchy: true,
-				},
-			},
-			want: func(g *WithT, toClient client.Client) {
-				c := &corev1.Secret{}
-				key := client.ObjectKey{
-					Namespace: "ns1",
-					Name:      "foo",
-				}
-				g.Expect(toClient.Get(ctx, key, c)).ToNot(HaveOccurred())
-				g.Expect(c.Annotations).ToNot(BeEmpty())
-			},
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			g := NewWithT(t)
-
-			mover := objectMover{
-				fromProxy: tt.args.fromProxy,
-			}
-
-			err := mover.createTargetObject(tt.args.node, tt.args.toProxy)
-			if tt.wantErr {
-				g.Expect(err).To(HaveOccurred())
-				return
-			}
-			g.Expect(err).NotTo(HaveOccurred())
-
-			toClient, err := tt.args.toProxy.NewClient()
-			g.Expect(err).NotTo(HaveOccurred())
-
-			tt.want(g, toClient)
-		})
-	}
-}
-
-func Test_deleteSourceObject(t *testing.T) {
-	type args struct {
-		fromProxy Proxy
-		node      *node
-	}
-
-	tests := []struct {
-		name string
-		args args
-		want func(*WithT, client.Client)
-	}{
-		{
-			name: "no op if the object is already deleted from source",
-			args: args{
-				fromProxy: test.NewFakeProxy(),
-				node: &node{
-					identity: corev1.ObjectReference{
-						Kind:       "Cluster",
-						Namespace:  "ns1",
-						Name:       "foo",
-						APIVersion: "cluster.x-k8s.io/v1alpha4",
-					},
-				},
-			},
-			want: func(g *WithT, toClient client.Client) {
-				c := &clusterv1.Cluster{}
-				key := client.ObjectKey{
-					Namespace: "ns1",
-					Name:      "foo",
-				}
-				g.Expect(apierrors.IsNotFound(toClient.Get(ctx, key, c))).To(BeTrue())
-			},
-		},
-		{
-			name: "deletes from source if the object is not is not Global/GlobalHierarchy",
-			args: args{
-				fromProxy: test.NewFakeProxy().WithObjs(
-					&clusterv1.Cluster{
-						ObjectMeta: metav1.ObjectMeta{
-							Name:      "foo",
-							Namespace: "ns1",
-						},
-					},
-				),
-				node: &node{
-					identity: corev1.ObjectReference{
-						Kind:       "Cluster",
-						Namespace:  "ns1",
-						Name:       "foo",
-						APIVersion: "cluster.x-k8s.io/v1alpha4",
-					},
-				},
-			},
-			want: func(g *WithT, toClient client.Client) {
-				c := &clusterv1.Cluster{}
-				key := client.ObjectKey{
-					Namespace: "ns1",
-					Name:      "foo",
-				}
-				g.Expect(apierrors.IsNotFound(toClient.Get(ctx, key, c))).To(BeTrue())
-			},
-		},
-		{
-			name: "does not delete from source if the object is not is Global",
-			args: args{
-				fromProxy: test.NewFakeProxy().WithObjs(
-					&infrastructure.GenericClusterInfrastructureIdentity{
-						ObjectMeta: metav1.ObjectMeta{
-							Name: "foo",
-						},
-					},
-				),
-				node: &node{
-					identity: corev1.ObjectReference{
-						Kind:       "GenericClusterInfrastructureIdentity",
-						Name:       "foo",
-						APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha4",
-					},
-					isGlobal: true,
-				},
-			},
-			want: func(g *WithT, toClient client.Client) {
-				c := &clusterv1.Cluster{}
-				key := client.ObjectKey{
-					Namespace: "ns1",
-					Name:      "foo",
-				}
-				g.Expect(apierrors.IsNotFound(toClient.Get(ctx, key, c))).To(BeTrue())
-			},
-		},
-		{
-			name: "does not delete from source if the object is not is Global Hierarchy",
-			args: args{
-				fromProxy: test.NewFakeProxy().WithObjs(
-					&corev1.Secret{
-						ObjectMeta: metav1.ObjectMeta{
-							Name:      "foo",
-							Namespace: "ns1",
-						},
-					},
-				),
-				node: &node{
-					identity: corev1.ObjectReference{
-						Kind:       "Secret",
-						Namespace:  "ns1",
-						Name:       "foo",
-						APIVersion: "v1",
-					},
-					isGlobalHierarchy: true,
-				},
-			},
-			want: func(g *WithT, toClient client.Client) {
-				c := &clusterv1.Cluster{}
-				key := client.ObjectKey{
-					Namespace: "ns1",
-					Name:      "foo",
-				}
-				g.Expect(apierrors.IsNotFound(toClient.Get(ctx, key, c))).To(BeTrue())
-			},
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			g := NewWithT(t)
-
-			mover := objectMover{
-				fromProxy: tt.args.fromProxy,
-			}
-
-			err := mover.deleteSourceObject(tt.args.node)
-			g.Expect(err).NotTo(HaveOccurred())
-
-			fromClient, err := tt.args.fromProxy.NewClient()
-			g.Expect(err).NotTo(HaveOccurred())
-
-			tt.want(g, fromClient)
-		})
-	}
-}
diff --git a/cmd/clusterctl/client/cluster/objectgraph_test.go b/cmd/clusterctl/client/cluster/objectgraph_test.go
index f481cb114..dcf29769a 100644
--- a/cmd/clusterctl/client/cluster/objectgraph_test.go
+++ b/cmd/clusterctl/client/cluster/objectgraph_test.go
@@ -38,7 +38,7 @@ func TestObjectGraph_getDiscoveryTypeMetaList(t *testing.T) {
 	tests := []struct {
 		name    string
 		fields  fields
-		want    map[string]*discoveryTypeInfo
+		want    []metav1.TypeMeta
 		wantErr bool
 	}{
 		{
@@ -46,170 +46,15 @@ func TestObjectGraph_getDiscoveryTypeMetaList(t *testing.T) {
 			fields: fields{
 				proxy: test.NewFakeProxy().
 					WithObjs(
-						test.FakeNamespacedCustomResourceDefinition("foo", "Bar", "v2", "v1"), // NB. foo/v1 Bar is not a storage version, so it should be ignored
-						test.FakeNamespacedCustomResourceDefinition("foo", "Baz", "v1"),
+						test.FakeCustomResourceDefinition("foo", "Bar", "v2", "v1"), // NB. foo/v1 Bar is not a storage version, so it should be ignored
+						test.FakeCustomResourceDefinition("foo", "Baz", "v1"),
 					),
 			},
-			want: map[string]*discoveryTypeInfo{
-				"bars.foo": {
-					typeMeta:           metav1.TypeMeta{Kind: "Bar", APIVersion: "foo/v2"},
-					forceMove:          false,
-					forceMoveHierarchy: false,
-					scope:              "Namespaced",
-				},
-				"bazs.foo": {
-					typeMeta:           metav1.TypeMeta{Kind: "Baz", APIVersion: "foo/v1"},
-					forceMove:          false,
-					forceMoveHierarchy: false,
-					scope:              "Namespaced",
-				},
-				"secrets.v1": {
-					typeMeta:           metav1.TypeMeta{Kind: "Secret", APIVersion: "v1"},
-					forceMove:          false,
-					forceMoveHierarchy: false,
-					scope:              "",
-				},
-				"configmaps.v1": {
-					typeMeta:           metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
-					forceMove:          false,
-					forceMoveHierarchy: false,
-					scope:              "",
-				},
-			},
-			wantErr: false,
-		},
-		{
-			name: "Enforce force move for Cluster and ClusterResourceSet",
-			fields: fields{
-				proxy: test.NewFakeProxy().
-					WithObjs(
-						test.FakeNamespacedCustomResourceDefinition("cluster.x-k8s.io", "Cluster", "v1alpha4"),
-						test.FakeNamespacedCustomResourceDefinition("addons.cluster.x-k8s.io", "ClusterResourceSet", "v1alpha4"),
-					),
-			},
-			want: map[string]*discoveryTypeInfo{
-				"clusters.cluster.x-k8s.io": {
-					typeMeta:           metav1.TypeMeta{Kind: "Cluster", APIVersion: "cluster.x-k8s.io/v1alpha4"},
-					forceMove:          true,
-					forceMoveHierarchy: true,
-					scope:              "Namespaced",
-				},
-				"clusterresourcesets.addons.cluster.x-k8s.io": {
-					typeMeta:           metav1.TypeMeta{Kind: "ClusterResourceSet", APIVersion: "addons.cluster.x-k8s.io/v1alpha4"},
-					forceMove:          true,
-					forceMoveHierarchy: true,
-					scope:              "Namespaced",
-				},
-				"secrets.v1": {
-					typeMeta:           metav1.TypeMeta{Kind: "Secret", APIVersion: "v1"},
-					forceMove:          false,
-					forceMoveHierarchy: false,
-					scope:              "",
-				},
-				"configmaps.v1": {
-					typeMeta:           metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
-					forceMove:          false,
-					forceMoveHierarchy: false,
-					scope:              "",
-				},
-			},
-			wantErr: false,
-		},
-		{
-			name: "Identified Cluster scoped types",
-			fields: fields{
-				proxy: test.NewFakeProxy().
-					WithObjs(
-						test.FakeClusterCustomResourceDefinition("infrastructure.cluster.x-k8s.io", "GenericClusterInfrastructureIdentity", "v1alpha4"),
-					),
-			},
-			want: map[string]*discoveryTypeInfo{
-				"genericclusterinfrastructureidentitys.infrastructure.cluster.x-k8s.io": {
-					typeMeta:           metav1.TypeMeta{Kind: "GenericClusterInfrastructureIdentity", APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha4"},
-					forceMove:          false,
-					forceMoveHierarchy: false,
-					scope:              "Cluster",
-				},
-				"secrets.v1": {
-					typeMeta:           metav1.TypeMeta{Kind: "Secret", APIVersion: "v1"},
-					forceMove:          false,
-					forceMoveHierarchy: false,
-					scope:              "",
-				},
-				"configmaps.v1": {
-					typeMeta:           metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
-					forceMove:          false,
-					forceMoveHierarchy: false,
-					scope:              "",
-				},
-			},
-			wantErr: false,
-		},
-		{
-			name: "Identified force move label",
-			fields: fields{
-				proxy: test.NewFakeProxy().
-					WithObjs(
-						func() client.Object {
-							crd := test.FakeNamespacedCustomResourceDefinition("foo", "Bar", "v1")
-							crd.Labels[clusterctlv1.ClusterctlMoveLabelName] = ""
-							return crd
-						}(),
-					),
-			},
-			want: map[string]*discoveryTypeInfo{
-				"bars.foo": {
-					typeMeta:           metav1.TypeMeta{Kind: "Bar", APIVersion: "foo/v1"},
-					forceMove:          true,
-					forceMoveHierarchy: false,
-					scope:              "Namespaced",
-				},
-				"secrets.v1": {
-					typeMeta:           metav1.TypeMeta{Kind: "Secret", APIVersion: "v1"},
-					forceMove:          false,
-					forceMoveHierarchy: false,
-					scope:              "",
-				},
-				"configmaps.v1": {
-					typeMeta:           metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
-					forceMove:          false,
-					forceMoveHierarchy: false,
-					scope:              "",
-				},
-			},
-			wantErr: false,
-		},
-		{
-			name: "Identified force move hierarchy label",
-			fields: fields{
-				proxy: test.NewFakeProxy().
-					WithObjs(
-						func() client.Object {
-							crd := test.FakeNamespacedCustomResourceDefinition("foo", "Bar", "v1")
-							crd.Labels[clusterctlv1.ClusterctlMoveHierarchyLabelName] = ""
-							return crd
-						}(),
-					),
-			},
-			want: map[string]*discoveryTypeInfo{
-				"bars.foo": {
-					typeMeta:           metav1.TypeMeta{Kind: "Bar", APIVersion: "foo/v1"},
-					forceMove:          true, // force move is implicit when there is forceMoveHierarchy
-					forceMoveHierarchy: true,
-					scope:              "Namespaced",
-				},
-				"secrets.v1": {
-					typeMeta:           metav1.TypeMeta{Kind: "Secret", APIVersion: "v1"},
-					forceMove:          false,
-					forceMoveHierarchy: false,
-					scope:              "",
-				},
-				"configmaps.v1": {
-					typeMeta:           metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
-					forceMove:          false,
-					forceMoveHierarchy: false,
-					scope:              "",
-				},
+			want: []metav1.TypeMeta{
+				{APIVersion: "foo/v2", Kind: "Bar"},
+				{APIVersion: "foo/v1", Kind: "Baz"},
+				{APIVersion: "v1", Kind: "Secret"},
+				{APIVersion: "v1", Kind: "ConfigMap"},
 			},
 			wantErr: false,
 		},
@@ -226,18 +71,20 @@ func TestObjectGraph_getDiscoveryTypeMetaList(t *testing.T) {
 			}
 
 			g.Expect(err).NotTo(HaveOccurred())
-			g.Expect(graph.types).To(Equal(tt.want))
+
+			discoveryTypeMetas := []metav1.TypeMeta{}
+			for _, discoveryType := range graph.types {
+				discoveryTypeMetas = append(discoveryTypeMetas, discoveryType.typeMeta)
+			}
+			g.Expect(discoveryTypeMetas).To(ConsistOf(tt.want))
 		})
 	}
 }
 
 type wantGraphItem struct {
-	virtual            bool
-	isGlobal           bool
-	forceMove          bool
-	forceMoveHierarchy bool
-	owners             []string
-	softOwners         []string
+	virtual    bool
+	owners     []string
+	softOwners []string
 }
 
 type wantGraph struct {
@@ -253,9 +100,6 @@ func assertGraph(t *testing.T, got *objectGraph, want wantGraph) {
 		gotNode, ok := got.uidToNode[types.UID(uid)]
 		g.Expect(ok).To(BeTrue(), "node %q not found", uid)
 		g.Expect(gotNode.virtual).To(Equal(wantNode.virtual), "node %q.virtual does not have the expected value", uid)
-		g.Expect(gotNode.isGlobal).To(Equal(wantNode.isGlobal), "node %q.isGlobal does not have the expected value", uid)
-		g.Expect(gotNode.forceMove).To(Equal(wantNode.forceMove), "node %q.forceMove does not have the expected value", uid)
-		g.Expect(gotNode.forceMoveHierarchy).To(Equal(wantNode.forceMoveHierarchy), "node %q.forceMoveHierarchy does not have the expected value", uid)
 		g.Expect(gotNode.owners).To(HaveLen(len(wantNode.owners)), "node %q.owner does not have the expected length", uid)
 
 		for _, wantOwner := range wantNode.owners {
@@ -486,10 +330,7 @@ var objectGraphsTests = []struct {
 		},
 		want: wantGraph{
 			nodes: map[string]wantGraphItem{
-				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {
-					forceMove:          true,
-					forceMoveHierarchy: true,
-				},
+				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {},
 				"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericInfrastructureCluster, ns1/cluster1": {
 					owners: []string{
 						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
@@ -509,17 +350,14 @@ var objectGraphsTests = []struct {
 		},
 	},
 	{
-		name: "Cluster with cloud config secret with the force move label",
+		name: "Cluster with force move label",
 		args: objectGraphTestArgs{
 			objs: test.NewFakeCluster("ns1", "cluster1").
 				WithCloudConfigSecret().Objs(),
 		},
 		want: wantGraph{
 			nodes: map[string]wantGraphItem{
-				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {
-					forceMove:          true,
-					forceMoveHierarchy: true,
-				},
+				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {},
 				"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericInfrastructureCluster, ns1/cluster1": {
 					owners: []string{
 						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
@@ -535,9 +373,7 @@ var objectGraphsTests = []struct {
 						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
 					},
 				},
-				"/v1, Kind=Secret, ns1/cluster1-cloud-config": {
-					forceMove: true,
-				},
+				"/v1, Kind=Secret, ns1/cluster1-cloud-config": {},
 			},
 		},
 	},
@@ -553,10 +389,7 @@ var objectGraphsTests = []struct {
 		},
 		want: wantGraph{
 			nodes: map[string]wantGraphItem{
-				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {
-					forceMove:          true,
-					forceMoveHierarchy: true,
-				},
+				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {},
 				"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericInfrastructureCluster, ns1/cluster1": {
 					owners: []string{
 						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
@@ -572,10 +405,7 @@ var objectGraphsTests = []struct {
 						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
 					},
 				},
-				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster2": {
-					forceMove:          true,
-					forceMoveHierarchy: true,
-				},
+				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster2": {},
 				"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericInfrastructureCluster, ns1/cluster2": {
 					owners: []string{
 						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster2",
@@ -604,10 +434,7 @@ var objectGraphsTests = []struct {
 		},
 		want: wantGraph{
 			nodes: map[string]wantGraphItem{
-				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {
-					forceMove:          true,
-					forceMoveHierarchy: true,
-				},
+				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {},
 				"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericInfrastructureCluster, ns1/cluster1": {
 					owners: []string{
 						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
@@ -663,10 +490,7 @@ var objectGraphsTests = []struct {
 		},
 		want: wantGraph{
 			nodes: map[string]wantGraphItem{
-				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {
-					forceMove:          true,
-					forceMoveHierarchy: true,
-				},
+				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {},
 				"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericInfrastructureCluster, ns1/cluster1": {
 					owners: []string{
 						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
@@ -738,10 +562,7 @@ var objectGraphsTests = []struct {
 		},
 		want: wantGraph{
 			nodes: map[string]wantGraphItem{
-				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {
-					forceMove:          true,
-					forceMoveHierarchy: true,
-				},
+				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {},
 				"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericInfrastructureCluster, ns1/cluster1": {
 					owners: []string{
 						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
@@ -816,10 +637,7 @@ var objectGraphsTests = []struct {
 		},
 		want: wantGraph{
 			nodes: map[string]wantGraphItem{
-				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {
-					forceMove:          true,
-					forceMoveHierarchy: true,
-				},
+				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {},
 				"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericInfrastructureCluster, ns1/cluster1": {
 					owners: []string{
 						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
@@ -885,10 +703,7 @@ var objectGraphsTests = []struct {
 		},
 		want: wantGraph{
 			nodes: map[string]wantGraphItem{
-				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {
-					forceMove:          true,
-					forceMoveHierarchy: true,
-				},
+				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {},
 				"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericInfrastructureCluster, ns1/cluster1": {
 					owners: []string{
 						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
@@ -964,10 +779,7 @@ var objectGraphsTests = []struct {
 					},
 				},
 
-				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {
-					forceMove:          true,
-					forceMoveHierarchy: true,
-				},
+				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {},
 				"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericInfrastructureCluster, ns1/cluster1": {
 					owners: []string{
 						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
@@ -1015,10 +827,7 @@ var objectGraphsTests = []struct {
 						"bootstrap.cluster.x-k8s.io/v1alpha4, Kind=GenericBootstrapConfig, ns1/cluster1-m1",
 					},
 				},
-				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster2": {
-					forceMove:          true,
-					forceMoveHierarchy: true,
-				},
+				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster2": {},
 				"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericInfrastructureCluster, ns1/cluster2": {
 					owners: []string{
 						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster2",
@@ -1087,10 +896,7 @@ var objectGraphsTests = []struct {
 		},
 		want: wantGraph{
 			nodes: map[string]wantGraphItem{
-				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {
-					forceMove:          true,
-					forceMoveHierarchy: true,
-				},
+				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {},
 				"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericInfrastructureCluster, ns1/cluster1": {
 					owners: []string{
 						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
@@ -1106,10 +912,7 @@ var objectGraphsTests = []struct {
 						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
 					},
 				},
-				"addons.cluster.x-k8s.io/v1alpha4, Kind=ClusterResourceSet, ns1/crs1": {
-					forceMove:          true,
-					forceMoveHierarchy: true,
-				},
+				"addons.cluster.x-k8s.io/v1alpha4, Kind=ClusterResourceSet, ns1/crs1": {},
 				"addons.cluster.x-k8s.io/v1alpha4, Kind=ClusterResourceSetBinding, ns1/cluster1": {
 					owners: []string{
 						"addons.cluster.x-k8s.io/v1alpha4, Kind=ClusterResourceSet, ns1/crs1",
@@ -1149,10 +952,7 @@ var objectGraphsTests = []struct {
 		},
 		want: wantGraph{
 			nodes: map[string]wantGraphItem{
-				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {
-					forceMove:          true,
-					forceMoveHierarchy: true,
-				},
+				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {},
 				"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericInfrastructureCluster, ns1/cluster1": {
 					owners: []string{
 						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
@@ -1168,10 +968,7 @@ var objectGraphsTests = []struct {
 						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
 					},
 				},
-				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster2": {
-					forceMove:          true,
-					forceMoveHierarchy: true,
-				},
+				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster2": {},
 				"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericInfrastructureCluster, ns1/cluster2": {
 					owners: []string{
 						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster2",
@@ -1187,10 +984,7 @@ var objectGraphsTests = []struct {
 						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster2",
 					},
 				},
-				"addons.cluster.x-k8s.io/v1alpha4, Kind=ClusterResourceSet, ns1/crs1": {
-					forceMove:          true,
-					forceMoveHierarchy: true,
-				},
+				"addons.cluster.x-k8s.io/v1alpha4, Kind=ClusterResourceSet, ns1/crs1": {},
 				"addons.cluster.x-k8s.io/v1alpha4, Kind=ClusterResourceSetBinding, ns1/cluster1": {
 					owners: []string{
 						"addons.cluster.x-k8s.io/v1alpha4, Kind=ClusterResourceSet, ns1/crs1",
@@ -1224,9 +1018,7 @@ var objectGraphsTests = []struct {
 		},
 		want: wantGraph{
 			nodes: map[string]wantGraphItem{
-				"external.cluster.x-k8s.io/v1alpha4, Kind=GenericExternalObject, ns1/externalObject1": {
-					forceMove: true,
-				},
+				"external.cluster.x-k8s.io/v1alpha4, Kind=GenericExternalObject, ns1/externalObject1": {},
 			},
 		},
 	},
@@ -1234,14 +1026,11 @@ var objectGraphsTests = []struct {
 		// NOTE: External objects are CRD types installed by clusterctl, but not directly related with the CAPI hierarchy of objects. e.g. IPAM claims.
 		name: "Global External Objects with force move label",
 		args: objectGraphTestArgs{
-			objs: test.NewFakeClusterExternalObject("externalObject1").Objs(),
+			objs: test.NewFakeExternalObject("", "externalObject1").Objs(),
 		},
 		want: wantGraph{
 			nodes: map[string]wantGraphItem{
-				"external.cluster.x-k8s.io/v1alpha4, Kind=GenericClusterExternalObject, /externalObject1": {
-					forceMove: true,
-					isGlobal:  true,
-				},
+				"external.cluster.x-k8s.io/v1alpha4, Kind=GenericExternalObject, /externalObject1": {},
 			},
 		},
 	},
@@ -1250,69 +1039,12 @@ var objectGraphsTests = []struct {
 		name: "Secrets from provider's namespace",
 		args: objectGraphTestArgs{
 			objs: []client.Object{
-				test.NewSecret("infra-system", "credentials"),
+				test.NewSecret("infra1", "credentials"),
 			},
 		},
 		want: wantGraph{
 			nodes: map[string]wantGraphItem{
-				"/v1, Kind=Secret, infra-system/credentials": {},
-			},
-		},
-	},
-	{
-		name: "Cluster owning a secret with infrastructure credentials",
-		args: objectGraphTestArgs{
-			objs: test.NewFakeCluster("ns1", "cluster1").
-				WithCredentialSecret().Objs(),
-		},
-		want: wantGraph{
-			nodes: map[string]wantGraphItem{
-				"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {
-					forceMove:          true,
-					forceMoveHierarchy: true,
-				},
-				"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericInfrastructureCluster, ns1/cluster1": {
-					owners: []string{
-						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
-					},
-				},
-				"/v1, Kind=Secret, ns1/cluster1-ca": {
-					softOwners: []string{
-						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1", // NB. this secret is not linked to the cluster through owner ref
-					},
-				},
-				"/v1, Kind=Secret, ns1/cluster1-kubeconfig": {
-					owners: []string{
-						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
-					},
-				},
-				"/v1, Kind=Secret, ns1/cluster1-credentials": {
-					owners: []string{
-						"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
-					},
-				},
-			},
-		},
-	},
-	{
-		name: "A global identity for an infrastructure provider owning a Secret with credentials in the provider's namespace",
-		args: objectGraphTestArgs{
-			objs: test.NewFakeClusterInfrastructureIdentity("infra1-identity").
-				WithSecretIn("infra1-system"). // a secret in infra1-system namespace, where an infrastructure provider is installed
-				Objs(),
-		},
-		want: wantGraph{
-			nodes: map[string]wantGraphItem{
-				"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericClusterInfrastructureIdentity, /infra1-identity": {
-					isGlobal:           true,
-					forceMove:          true,
-					forceMoveHierarchy: true,
-				},
-				"/v1, Kind=Secret, infra1-system/infra1-identity-credentials": {
-					owners: []string{
-						"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericClusterInfrastructureIdentity, /infra1-identity",
-					},
-				},
+				"/v1, Kind=Secret, infra1/credentials": {},
 			},
 		},
 	},
@@ -1327,24 +1059,6 @@ func getDetachedObjectGraphWihObjs(objs []client.Object) (*objectGraph, error) {
 		}
 		graph.addObj(u)
 	}
-
-	// given that we are not relying on discovery while testing in "detached mode (without a fake client)" it is required to:
-	for _, node := range graph.getNodes() {
-		// enforce forceMoveHierarchy for Clusters, ClusterResourceSets, GenericClusterInfrastructureIdentity
-		if node.identity.Kind == "Cluster" || node.identity.Kind == "ClusterResourceSet" || node.identity.Kind == "GenericClusterInfrastructureIdentity" {
-			node.forceMove = true
-			node.forceMoveHierarchy = true
-		}
-		// enforce forceMove for GenericExternalObject, GenericClusterExternalObject
-		if node.identity.Kind == "GenericExternalObject" || node.identity.Kind == "GenericClusterExternalObject" {
-			node.forceMove = true
-		}
-		// enforce isGlobal for GenericClusterInfrastructureIdentity and GenericClusterExternalObject
-		if node.identity.Kind == "GenericClusterInfrastructureIdentity" || node.identity.Kind == "GenericClusterExternalObject" {
-			node.isGlobal = true
-		}
-	}
-
 	return graph, nil
 }
 
@@ -1372,7 +1086,7 @@ func getObjectGraphWithObjs(objs []client.Object) *objectGraph {
 		fromProxy.WithObjs(o)
 	}
 
-	fromProxy.WithProviderInventory("infra1", clusterctlv1.InfrastructureProviderType, "v1.2.3", "infra1-system")
+	fromProxy.WithProviderInventory("infra1", clusterctlv1.InfrastructureProviderType, "v1.2.3", "infra1")
 	inventory := newInventoryClient(fromProxy, fakePollImmediateWaiter)
 
 	return newObjectGraph(fromProxy, inventory)
@@ -1448,10 +1162,7 @@ func TestObjectGraph_DiscoveryByNamespace(t *testing.T) {
 			},
 			want: wantGraph{
 				nodes: map[string]wantGraphItem{
-					"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {
-						forceMove:          true,
-						forceMoveHierarchy: true,
-					},
+					"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {},
 					"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericInfrastructureCluster, ns1/cluster1": {
 						owners: []string{
 							"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
@@ -1467,10 +1178,7 @@ func TestObjectGraph_DiscoveryByNamespace(t *testing.T) {
 							"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
 						},
 					},
-					"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns2/cluster1": {
-						forceMove:          true,
-						forceMoveHierarchy: true,
-					},
+					"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns2/cluster1": {},
 					"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericInfrastructureCluster, ns2/cluster1": {
 						owners: []string{
 							"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns2/cluster1",
@@ -1502,10 +1210,7 @@ func TestObjectGraph_DiscoveryByNamespace(t *testing.T) {
 			},
 			want: wantGraph{
 				nodes: map[string]wantGraphItem{
-					"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {
-						forceMove:          true,
-						forceMoveHierarchy: true,
-					},
+					"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1": {},
 					"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericInfrastructureCluster, ns1/cluster1": {
 						owners: []string{
 							"cluster.x-k8s.io/v1alpha4, Kind=Cluster, ns1/cluster1",
@@ -1533,9 +1238,7 @@ func TestObjectGraph_DiscoveryByNamespace(t *testing.T) {
 			},
 			want: wantGraph{
 				nodes: map[string]wantGraphItem{
-					"external.cluster.x-k8s.io/v1alpha4, Kind=GenericExternalObject, ns1/externalObject1": {
-						forceMove: true,
-					},
+					"external.cluster.x-k8s.io/v1alpha4, Kind=GenericExternalObject, ns1/externalObject1": {},
 				},
 			},
 		},
@@ -1545,12 +1248,12 @@ func TestObjectGraph_DiscoveryByNamespace(t *testing.T) {
 			args: args{
 				namespace: "ns1", // read only from ns1
 				objs: []client.Object{
-					test.NewSecret("infra1-system", "infra1-credentials"), // a secret in infra1-system namespace, where an infrastructure provider is installed
+					test.NewSecret("infra1", "credentials"), // a secret in infra1 namespace, where an infrastructure provider is installed
 				},
 			},
 			want: wantGraph{
 				nodes: map[string]wantGraphItem{
-					"/v1, Kind=Secret, infra1-system/infra1-credentials": {},
+					"/v1, Kind=Secret, infra1/credentials": {},
 				},
 			},
 		},
@@ -1840,8 +1543,8 @@ func Test_objectGraph_setClusterTenants(t *testing.T) {
 			// we want to check that soft dependent nodes are considered part of the cluster, so we make sure to call SetSoftDependants before SetClusterTenants
 			gb.setSoftOwnership()
 
-			// finally test SetTenants
-			gb.setTenants()
+			// finally test SetClusterTenants
+			gb.setClusterTenants()
 
 			gotClusters := gb.getClusters()
 			sort.Slice(gotClusters, func(i, j int) bool {
@@ -1856,10 +1559,9 @@ func Test_objectGraph_setClusterTenants(t *testing.T) {
 
 				gotTenants := []string{}
 				for _, node := range gb.uidToNode {
-					for c := range node.tenant {
+					for c := range node.tenantClusters {
 						if c.identity.UID == cluster.identity.UID {
 							gotTenants = append(gotTenants, string(node.identity.UID))
-							g.Expect(node.isGlobalHierarchy).To(BeFalse()) // We should make sure that everything below a Cluster is not considered global
 						}
 					}
 				}
@@ -1940,7 +1642,7 @@ func Test_objectGraph_setCRSTenants(t *testing.T) {
 			gb, err := getDetachedObjectGraphWihObjs(tt.fields.objs)
 			g.Expect(err).NotTo(HaveOccurred())
 
-			gb.setTenants()
+			gb.setCRSTenants()
 
 			gotCRSs := gb.getCRSs()
 			sort.Slice(gotCRSs, func(i, j int) bool {
@@ -1955,74 +1657,9 @@ func Test_objectGraph_setCRSTenants(t *testing.T) {
 
 				gotTenants := []string{}
 				for _, node := range gb.uidToNode {
-					for c := range node.tenant {
+					for c := range node.tenantCRSs {
 						if c.identity.UID == crs.identity.UID {
 							gotTenants = append(gotTenants, string(node.identity.UID))
-							g.Expect(node.isGlobalHierarchy).To(BeFalse()) // We should make sure that everything below a CRS is not considered global
-						}
-					}
-				}
-
-				g.Expect(gotTenants).To(ConsistOf(wantTenants))
-			}
-		})
-	}
-}
-
-func Test_objectGraph_setGlobalIdentityTenants(t *testing.T) {
-	type fields struct {
-		objs []client.Object
-	}
-	tests := []struct {
-		name         string
-		fields       fields
-		wantIdentity map[string][]string
-	}{
-		{
-			name: "A global identity for an infrastructure provider owning a Secret with credentials in the provider's namespace",
-			fields: fields{
-				objs: test.NewFakeClusterInfrastructureIdentity("infra1-identity").
-					WithSecretIn("infra1-system"). // a secret in infra1-system namespace, where an infrastructure provider is installed
-					Objs(),
-			},
-			wantIdentity: map[string][]string{ // wantCRDs is a map[ClusterResourceSet.UID] --> list of UIDs
-				"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericClusterInfrastructureIdentity, /infra1-identity": {
-					"infrastructure.cluster.x-k8s.io/v1alpha4, Kind=GenericClusterInfrastructureIdentity, /infra1-identity", // the global identity should be tenant of itself
-					"/v1, Kind=Secret, infra1-system/infra1-identity-credentials",
-				},
-			},
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			g := NewWithT(t)
-
-			gb, err := getDetachedObjectGraphWihObjs(tt.fields.objs)
-			g.Expect(err).NotTo(HaveOccurred())
-
-			gb.setTenants()
-
-			gotIdentity := []*node{}
-			for _, n := range gb.getNodes() {
-				if n.forceMoveHierarchy {
-					gotIdentity = append(gotIdentity, n)
-				}
-			}
-			sort.Slice(gotIdentity, func(i, j int) bool {
-				return gotIdentity[i].identity.UID < gotIdentity[j].identity.UID
-			})
-			g.Expect(gotIdentity).To(HaveLen(len(tt.wantIdentity)))
-
-			for _, i := range gotIdentity {
-				wantTenants, ok := tt.wantIdentity[string(i.identity.UID)]
-				g.Expect(ok).To(BeTrue())
-
-				gotTenants := []string{}
-				for _, node := range gb.uidToNode {
-					for c := range node.tenant {
-						if c.identity.UID == i.identity.UID {
-							gotTenants = append(gotTenants, string(node.identity.UID))
-							g.Expect(node.isGlobalHierarchy).To(BeTrue()) // We should make sure that everything below a global object is considered global
 						}
 					}
 				}
diff --git a/cmd/clusterctl/client/repository/components_client_test.go b/cmd/clusterctl/client/repository/components_client_test.go
index f78a92c0c..1b83ab9df 100644
--- a/cmd/clusterctl/client/repository/components_client_test.go
+++ b/cmd/clusterctl/client/repository/components_client_test.go
@@ -299,11 +299,17 @@ func Test_componentsClient_Get(t *testing.T) {
 				}
 			}
 
-			for _, o := range got.Objs() {
+			for _, o := range got.InstanceObjs() {
 				for _, v := range []string{clusterctlv1.ClusterctlLabelName, clusterv1.ProviderLabelName} {
 					gs.Expect(o.GetLabels()).To(HaveKey(v))
 				}
 			}
+
+			for _, o := range got.SharedObjs() {
+				for _, v := range []string{clusterctlv1.ClusterctlLabelName, clusterv1.ProviderLabelName, clusterctlv1.ClusterctlResourceLifecyleLabelName} {
+					gs.Expect(o.GetLabels()).To(HaveKey(v))
+				}
+			}
 		})
 	}
 }
diff --git a/cmd/clusterctl/client/repository/components_test.go b/cmd/clusterctl/client/repository/components_test.go
index 5c08414a8..3427beb31 100644
--- a/cmd/clusterctl/client/repository/components_test.go
+++ b/cmd/clusterctl/client/repository/components_test.go
@@ -485,6 +485,134 @@ func Test_fixRBAC(t *testing.T) {
 			},
 			wantErr: false,
 		},
+		{
+			name: "ClusterRoleBinding with subjects IN capi-webhook-system get fixed (without changing the subject namespace)",
+			args: args{
+				objs: []unstructured.Unstructured{
+					{
+						Object: map[string]interface{}{
+							"kind":       "ClusterRoleBinding",
+							"apiVersion": "rbac.authorization.k8s.io/v1",
+							"metadata": map[string]interface{}{
+								"name": "foo",
+							},
+							"roleRef": map[string]interface{}{
+								"apiGroup": "",
+								"kind":     "",
+								"name":     "bar",
+							},
+							"subjects": []interface{}{
+								map[string]interface{}{
+									"kind":      "ServiceAccount",
+									"name":      "baz",
+									"namespace": "capi-webhook-system",
+								},
+							},
+						},
+					},
+					{
+						Object: map[string]interface{}{
+							"kind":       "ClusterRole",
+							"apiVersion": "rbac.authorization.k8s.io/v1",
+							"metadata": map[string]interface{}{
+								"name": "bar",
+							},
+						},
+					},
+				},
+				targetNamespace: "target",
+			},
+			want: []unstructured.Unstructured{
+				{
+					Object: map[string]interface{}{
+						"kind":       "ClusterRoleBinding",
+						"apiVersion": "rbac.authorization.k8s.io/v1",
+						"metadata": map[string]interface{}{
+							"name":              "target-foo", // ClusterRoleBinding name fixed!
+							"creationTimestamp": nil,
+						},
+						"roleRef": map[string]interface{}{
+							"apiGroup": "",
+							"kind":     "",
+							"name":     "target-bar", // ClusterRole name fixed!
+						},
+						"subjects": []interface{}{
+							map[string]interface{}{
+								"kind":      "ServiceAccount",
+								"name":      "baz",
+								"namespace": "capi-webhook-system", // Subjects namespace get preserved!
+							},
+						},
+					},
+				},
+				{
+					Object: map[string]interface{}{
+						"kind":       "ClusterRole",
+						"apiVersion": "rbac.authorization.k8s.io/v1",
+						"metadata": map[string]interface{}{
+							"name": "target-bar", // ClusterRole fixed!
+						},
+					},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "RoleBinding with subjects IN capi-webhook-system get fixed (without changing the subject namespace)",
+			args: args{
+				objs: []unstructured.Unstructured{
+					{
+						Object: map[string]interface{}{
+							"kind":       "RoleBinding",
+							"apiVersion": "rbac.authorization.k8s.io/v1",
+							"metadata": map[string]interface{}{
+								"name":      "foo",
+								"namespace": "target",
+							},
+							"roleRef": map[string]interface{}{
+								"apiGroup": "",
+								"kind":     "",
+								"name":     "bar",
+							},
+							"subjects": []interface{}{
+								map[string]interface{}{
+									"kind":      "ServiceAccount",
+									"name":      "baz",
+									"namespace": "capi-webhook-system",
+								},
+							},
+						},
+					},
+				},
+				targetNamespace: "target",
+			},
+			want: []unstructured.Unstructured{
+				{
+					Object: map[string]interface{}{
+						"kind":       "RoleBinding",
+						"apiVersion": "rbac.authorization.k8s.io/v1",
+						"metadata": map[string]interface{}{
+							"name":              "foo",
+							"namespace":         "target",
+							"creationTimestamp": nil,
+						},
+						"roleRef": map[string]interface{}{
+							"apiGroup": "",
+							"kind":     "",
+							"name":     "bar",
+						},
+						"subjects": []interface{}{
+							map[string]interface{}{
+								"kind":      "ServiceAccount",
+								"name":      "baz",
+								"namespace": "capi-webhook-system", // Subjects namespace get preserved!
+							},
+						},
+					},
+				},
+			},
+			wantErr: false,
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -550,3 +678,154 @@ func Test_addCommonLabels(t *testing.T) {
 		})
 	}
 }
+
+func Test_splitInstanceAndSharedResources(t *testing.T) {
+	type args struct {
+		objs []unstructured.Unstructured
+	}
+	tests := []struct {
+		name             string
+		args             args
+		wantInstanceObjs []unstructured.Unstructured
+		wantSharedObjs   []unstructured.Unstructured
+	}{
+		{
+			name: "objects are split in two sets",
+			args: args{
+				objs: []unstructured.Unstructured{
+					// Instance objs
+					{
+						Object: map[string]interface{}{
+							"kind": "Namespace",
+							"metadata": map[string]interface{}{
+								"name": "capi-system",
+							},
+						},
+					},
+					{
+						Object: map[string]interface{}{
+							"kind": "Deployment",
+							"metadata": map[string]interface{}{
+								"name":      "capi-controller-manager",
+								"namespace": "capi-system",
+							},
+						},
+					},
+					// Shared objs
+					{
+						Object: map[string]interface{}{
+							"kind": "Namespace",
+							"metadata": map[string]interface{}{
+								"name": "capi-webhook-system",
+							},
+						},
+					},
+					{
+						Object: map[string]interface{}{
+							"kind": "Deployment",
+							"metadata": map[string]interface{}{
+								"name":      "capi-controller-manager",
+								"namespace": "capi-webhook-system",
+							},
+						},
+					},
+					{
+						Object: map[string]interface{}{
+							"kind": "CustomResourceDefinition",
+							"metadata": map[string]interface{}{
+								"name": "clusters.cluster.x-k8s.io",
+							},
+						},
+					},
+					{
+						Object: map[string]interface{}{
+							"kind": "MutatingWebhookConfiguration",
+							"metadata": map[string]interface{}{
+								"name": "capi-mutating-webhook-configuration",
+							},
+						},
+					},
+					{
+						Object: map[string]interface{}{
+							"kind": "ValidatingWebhookConfiguration",
+							"metadata": map[string]interface{}{
+								"name": "capi-validating-webhook-configuration",
+							},
+						},
+					},
+				},
+			},
+			wantInstanceObjs: []unstructured.Unstructured{
+				{
+					Object: map[string]interface{}{
+						"kind": "Namespace",
+						"metadata": map[string]interface{}{
+							"name": "capi-system",
+						},
+					},
+				},
+				{
+					Object: map[string]interface{}{
+						"kind": "Deployment",
+						"metadata": map[string]interface{}{
+							"name":      "capi-controller-manager",
+							"namespace": "capi-system",
+						},
+					},
+				},
+			},
+			wantSharedObjs: []unstructured.Unstructured{
+				{
+					Object: map[string]interface{}{
+						"kind": "Namespace",
+						"metadata": map[string]interface{}{
+							"name": "capi-webhook-system",
+						},
+					},
+				},
+				{
+					Object: map[string]interface{}{
+						"kind": "Deployment",
+						"metadata": map[string]interface{}{
+							"name":      "capi-controller-manager",
+							"namespace": "capi-webhook-system",
+						},
+					},
+				},
+				{
+					Object: map[string]interface{}{
+						"kind": "CustomResourceDefinition",
+						"metadata": map[string]interface{}{
+							"name": "clusters.cluster.x-k8s.io",
+						},
+					},
+				},
+				{
+					Object: map[string]interface{}{
+						"kind": "MutatingWebhookConfiguration",
+						"metadata": map[string]interface{}{
+							"name": "capi-mutating-webhook-configuration",
+						},
+					},
+				},
+				{
+					Object: map[string]interface{}{
+						"kind": "ValidatingWebhookConfiguration",
+						"metadata": map[string]interface{}{
+							"name": "capi-validating-webhook-configuration",
+						},
+					},
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			g := NewWithT(t)
+
+			gotInstanceObjs, gotWebHookObjs := splitInstanceAndSharedResources(tt.args.objs)
+			g.Expect(gotInstanceObjs).To(ConsistOf(tt.wantInstanceObjs))
+			g.Expect(gotWebHookObjs).To(ConsistOf(tt.wantSharedObjs))
+		})
+	}
+}
diff --git a/cmd/clusterctl/internal/test/fake_objects.go b/cmd/clusterctl/internal/test/fake_objects.go
index 9f840f585..f86293a99 100644
--- a/cmd/clusterctl/internal/test/fake_objects.go
+++ b/cmd/clusterctl/internal/test/fake_objects.go
@@ -45,7 +45,6 @@ type FakeCluster struct {
 	machineSets           []*FakeMachineSet
 	machines              []*FakeMachine
 	withCloudConfigSecret bool
-	withCredentialSecret  bool
 }
 
 // NewFakeCluster return a FakeCluster that can generate a cluster object, all its own ancillary objects:
@@ -76,11 +75,6 @@ func (f *FakeCluster) WithCloudConfigSecret() *FakeCluster {
 	return f
 }
 
-func (f *FakeCluster) WithCredentialSecret() *FakeCluster {
-	f.withCredentialSecret = true
-	return f
-}
-
 func (f *FakeCluster) WithMachineDeployments(fakeMachineDeployment ...*FakeMachineDeployment) *FakeCluster {
 	f.machineDeployments = append(f.machineDeployments, fakeMachineDeployment...)
 	return f
@@ -180,28 +174,6 @@ func (f *FakeCluster) Objs() []client.Object {
 		objs = append(objs, cloudSecret)
 	}
 
-	if f.withCredentialSecret {
-		credentialSecret := &corev1.Secret{ // provided by the user -- ** NOT RECONCILED **
-			TypeMeta: metav1.TypeMeta{
-				Kind:       "Secret",
-				APIVersion: "v1",
-			},
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      f.name + "-credentials",
-				Namespace: f.namespace,
-			},
-		}
-		credentialSecret.SetOwnerReferences([]metav1.OwnerReference{
-			{
-				APIVersion: cluster.APIVersion,
-				Kind:       cluster.Kind,
-				Name:       cluster.Name,
-				UID:        cluster.UID,
-			},
-		})
-		objs = append(objs, credentialSecret)
-	}
-
 	// if the cluster has a control plane object
 	if f.controlPlane != nil {
 		// Adds the objects for the controlPlane
@@ -1147,7 +1119,6 @@ type FakeExternalObject struct {
 	namespace string
 }
 
-// NewFakeExternalObject generates a new external object (a CR not related to the Cluster).
 func NewFakeExternalObject(namespace, name string) *FakeExternalObject {
 	return &FakeExternalObject{
 		name:      name,
@@ -1166,83 +1137,12 @@ func (f *FakeExternalObject) Objs() []client.Object {
 			Namespace: f.namespace,
 		},
 	}
-	setUID(externalObj)
-
-	return []client.Object{externalObj}
-}
-
-type FakeClusterExternalObject struct {
-	name string
-}
-
-// NewFakeClusterExternalObject generates a new global external object (a CR not related to the Cluster).
-func NewFakeClusterExternalObject(name string) *FakeClusterExternalObject {
-	return &FakeClusterExternalObject{
-		name: name,
-	}
-}
 
-func (f *FakeClusterExternalObject) Objs() []client.Object {
-	externalObj := &fakeexternal.GenericClusterExternalObject{
-		TypeMeta: metav1.TypeMeta{
-			APIVersion: fakeexternal.GroupVersion.String(),
-			Kind:       "GenericClusterExternalObject",
-		},
-		ObjectMeta: metav1.ObjectMeta{
-			Name: f.name,
-		},
-	}
 	setUID(externalObj)
 
 	return []client.Object{externalObj}
 }
 
-type FakeClusterInfrastructureIdentity struct {
-	name            string
-	secretNamespace string
-}
-
-// NewFakeClusterInfrastructureIdentity generates a new global cluster identity object.
-func NewFakeClusterInfrastructureIdentity(name string) *FakeClusterInfrastructureIdentity {
-	return &FakeClusterInfrastructureIdentity{
-		name: name,
-	}
-}
-
-func (f *FakeClusterInfrastructureIdentity) WithSecretIn(namespace string) *FakeClusterInfrastructureIdentity {
-	f.secretNamespace = namespace
-	return f
-}
-
-func (f *FakeClusterInfrastructureIdentity) Objs() []client.Object {
-	identityObj := &fakeinfrastructure.GenericClusterInfrastructureIdentity{
-		TypeMeta: metav1.TypeMeta{
-			APIVersion: fakeinfrastructure.GroupVersion.String(),
-			Kind:       "GenericClusterInfrastructureIdentity",
-		},
-		ObjectMeta: metav1.ObjectMeta{
-			Name: f.name,
-		},
-	}
-	setUID(identityObj)
-	objs := []client.Object{identityObj}
-
-	if f.secretNamespace != "" {
-		secret := NewSecret(f.secretNamespace, fmt.Sprintf("%s-credentials", f.name))
-		setUID(secret)
-
-		secret.SetOwnerReferences(append(secret.OwnerReferences, metav1.OwnerReference{
-			APIVersion: identityObj.APIVersion,
-			Kind:       identityObj.Kind,
-			Name:       identityObj.Name,
-			UID:        identityObj.UID,
-		}))
-		objs = append(objs, secret)
-	}
-
-	return objs
-}
-
 // NewSecret generates a new secret with the given namespace and name.
 func NewSecret(namespace, name string) *corev1.Secret {
 	s := &corev1.Secret{
@@ -1292,21 +1192,8 @@ func setUID(obj client.Object) {
 	accessor.SetUID(types.UID(uid))
 }
 
-// FakeClusterCustomResourceDefinition returns a fake CRD object for the given group/versions/kind.
-func FakeClusterCustomResourceDefinition(group string, kind string, versions ...string) *apiextensionsv1.CustomResourceDefinition {
-	crd := fakeCRD(group, kind, versions)
-	crd.Spec.Scope = apiextensionsv1.ClusterScoped
-	return crd
-}
-
-// FakeNamespacedCustomResourceDefinition returns a fake CRD object for the given group/versions/kind.
-func FakeNamespacedCustomResourceDefinition(group string, kind string, versions ...string) *apiextensionsv1.CustomResourceDefinition {
-	crd := fakeCRD(group, kind, versions)
-	crd.Spec.Scope = apiextensionsv1.NamespaceScoped
-	return crd
-}
-
-func fakeCRD(group string, kind string, versions []string) *apiextensionsv1.CustomResourceDefinition {
+// FakeCustomResourceDefinition returns a fake CRD object for the given group/versions/kind.
+func FakeCustomResourceDefinition(group string, kind string, versions ...string) *apiextensionsv1.CustomResourceDefinition {
 	crd := &apiextensionsv1.CustomResourceDefinition{
 		TypeMeta: metav1.TypeMeta{
 			Kind:       apiextensionsv1.SchemeGroupVersion.String(),
@@ -1334,40 +1221,32 @@ func fakeCRD(group string, kind string, versions []string) *apiextensionsv1.Cust
 		}
 		crd.Spec.Versions = append(crd.Spec.Versions, versionObj)
 	}
+
 	return crd
 }
 
 // FakeCRDList returns FakeCustomResourceDefinitions for all the Types used in the test object graph.
 func FakeCRDList() []*apiextensionsv1.CustomResourceDefinition {
-	version := clusterv1.GroupVersion.Version
+	version := "v1alpha4"
 
-	// Ensure CRD for external objects is set as for "force move"
-	externalCRD := FakeNamespacedCustomResourceDefinition(fakeexternal.GroupVersion.Group, "GenericExternalObject", version)
+	// Ensure external objects are of a CRD type with the "force move" label
+	externalCRD := FakeCustomResourceDefinition(fakeexternal.GroupVersion.Group, "GenericExternalObject", version)
 	externalCRD.Labels[clusterctlv1.ClusterctlMoveLabelName] = ""
 
-	clusterExternalCRD := FakeClusterCustomResourceDefinition(fakeexternal.GroupVersion.Group, "GenericClusterExternalObject", version)
-	clusterExternalCRD.Labels[clusterctlv1.ClusterctlMoveLabelName] = ""
-
-	// Ensure CRD for GenericClusterInfrastructureIdentity is set for "force move hierarchy"
-	clusterInfrastructureIdentityCRD := FakeClusterCustomResourceDefinition(fakeinfrastructure.GroupVersion.Group, "GenericClusterInfrastructureIdentity", version)
-	clusterInfrastructureIdentityCRD.Labels[clusterctlv1.ClusterctlMoveHierarchyLabelName] = ""
-
 	return []*apiextensionsv1.CustomResourceDefinition{
-		FakeNamespacedCustomResourceDefinition(clusterv1.GroupVersion.Group, "Cluster", version),
-		FakeNamespacedCustomResourceDefinition(clusterv1.GroupVersion.Group, "Machine", version),
-		FakeNamespacedCustomResourceDefinition(clusterv1.GroupVersion.Group, "MachineDeployment", version),
-		FakeNamespacedCustomResourceDefinition(clusterv1.GroupVersion.Group, "MachineSet", version),
-		FakeNamespacedCustomResourceDefinition(expv1.GroupVersion.Group, "MachinePool", version),
-		FakeNamespacedCustomResourceDefinition(addonsv1.GroupVersion.Group, "ClusterResourceSet", version),
-		FakeNamespacedCustomResourceDefinition(addonsv1.GroupVersion.Group, "ClusterResourceSetBinding", version),
-		FakeNamespacedCustomResourceDefinition(fakecontrolplane.GroupVersion.Group, "GenericControlPlane", version),
-		FakeNamespacedCustomResourceDefinition(fakeinfrastructure.GroupVersion.Group, "GenericInfrastructureCluster", version),
-		FakeNamespacedCustomResourceDefinition(fakeinfrastructure.GroupVersion.Group, "GenericInfrastructureMachine", version),
-		FakeNamespacedCustomResourceDefinition(fakeinfrastructure.GroupVersion.Group, "GenericInfrastructureMachineTemplate", version),
-		FakeNamespacedCustomResourceDefinition(fakebootstrap.GroupVersion.Group, "GenericBootstrapConfig", version),
-		FakeNamespacedCustomResourceDefinition(fakebootstrap.GroupVersion.Group, "GenericBootstrapConfigTemplate", version),
+		FakeCustomResourceDefinition(clusterv1.GroupVersion.Group, "Cluster", version),
+		FakeCustomResourceDefinition(clusterv1.GroupVersion.Group, "Machine", version),
+		FakeCustomResourceDefinition(clusterv1.GroupVersion.Group, "MachineDeployment", version),
+		FakeCustomResourceDefinition(clusterv1.GroupVersion.Group, "MachineSet", version),
+		FakeCustomResourceDefinition(expv1.GroupVersion.Group, "MachinePool", version),
+		FakeCustomResourceDefinition(addonsv1.GroupVersion.Group, "ClusterResourceSet", version),
+		FakeCustomResourceDefinition(addonsv1.GroupVersion.Group, "ClusterResourceSetBinding", version),
+		FakeCustomResourceDefinition(fakecontrolplane.GroupVersion.Group, "GenericControlPlane", version),
+		FakeCustomResourceDefinition(fakeinfrastructure.GroupVersion.Group, "GenericInfrastructureCluster", version),
+		FakeCustomResourceDefinition(fakeinfrastructure.GroupVersion.Group, "GenericInfrastructureMachine", version),
+		FakeCustomResourceDefinition(fakeinfrastructure.GroupVersion.Group, "GenericInfrastructureMachineTemplate", version),
+		FakeCustomResourceDefinition(fakebootstrap.GroupVersion.Group, "GenericBootstrapConfig", version),
+		FakeCustomResourceDefinition(fakebootstrap.GroupVersion.Group, "GenericBootstrapConfigTemplate", version),
 		externalCRD,
-		clusterExternalCRD,
-		clusterInfrastructureIdentityCRD,
 	}
 }
diff --git a/cmd/clusterctl/internal/test/providers/external/generic_types.go b/cmd/clusterctl/internal/test/providers/external/generic_types.go
index cfb581008..18d380fc0 100644
--- a/cmd/clusterctl/internal/test/providers/external/generic_types.go
+++ b/cmd/clusterctl/internal/test/providers/external/generic_types.go
@@ -36,25 +36,8 @@ type GenericExternalObjectList struct {
 	Items           []GenericExternalObject `json:"items"`
 }
 
-// GenericClusterExternalObject is an object which is not actually managed by CAPI, but we wish to move with clusterctl
-// using the "move" label on the resource.
-// +kubebuilder:object:root=true
-type GenericClusterExternalObject struct {
-	metav1.TypeMeta   `json:",inline"`
-	metav1.ObjectMeta `json:"metadata,omitempty"`
-}
-
-// +kubebuilder:object:root=true
-
-type GenericClusterExternalObjectList struct {
-	metav1.TypeMeta `json:",inline"`
-	metav1.ListMeta `json:"metadata,omitempty"`
-	Items           []GenericExternalObject `json:"items"`
-}
-
 func init() {
 	SchemeBuilder.Register(
 		&GenericExternalObject{}, &GenericExternalObjectList{},
-		&GenericClusterExternalObject{}, &GenericClusterExternalObjectList{},
 	)
 }
diff --git a/cmd/clusterctl/internal/test/providers/external/zz_generated.deepcopy.go b/cmd/clusterctl/internal/test/providers/external/zz_generated.deepcopy.go
index 1acca4bd4..48af38224 100644
--- a/cmd/clusterctl/internal/test/providers/external/zz_generated.deepcopy.go
+++ b/cmd/clusterctl/internal/test/providers/external/zz_generated.deepcopy.go
@@ -24,63 +24,6 @@ import (
 	runtime "k8s.io/apimachinery/pkg/runtime"
 )
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *GenericClusterExternalObject) DeepCopyInto(out *GenericClusterExternalObject) {
-	*out = *in
-	out.TypeMeta = in.TypeMeta
-	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericClusterExternalObject.
-func (in *GenericClusterExternalObject) DeepCopy() *GenericClusterExternalObject {
-	if in == nil {
-		return nil
-	}
-	out := new(GenericClusterExternalObject)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *GenericClusterExternalObject) DeepCopyObject() runtime.Object {
-	if c := in.DeepCopy(); c != nil {
-		return c
-	}
-	return nil
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *GenericClusterExternalObjectList) DeepCopyInto(out *GenericClusterExternalObjectList) {
-	*out = *in
-	out.TypeMeta = in.TypeMeta
-	in.ListMeta.DeepCopyInto(&out.ListMeta)
-	if in.Items != nil {
-		in, out := &in.Items, &out.Items
-		*out = make([]GenericExternalObject, len(*in))
-		for i := range *in {
-			(*in)[i].DeepCopyInto(&(*out)[i])
-		}
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericClusterExternalObjectList.
-func (in *GenericClusterExternalObjectList) DeepCopy() *GenericClusterExternalObjectList {
-	if in == nil {
-		return nil
-	}
-	out := new(GenericClusterExternalObjectList)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *GenericClusterExternalObjectList) DeepCopyObject() runtime.Object {
-	if c := in.DeepCopy(); c != nil {
-		return c
-	}
-	return nil
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *GenericExternalObject) DeepCopyInto(out *GenericExternalObject) {
 	*out = *in
diff --git a/cmd/clusterctl/internal/test/providers/infrastructure/generic_types.go b/cmd/clusterctl/internal/test/providers/infrastructure/generic_types.go
index 6f7123e62..b6660863b 100644
--- a/cmd/clusterctl/internal/test/providers/infrastructure/generic_types.go
+++ b/cmd/clusterctl/internal/test/providers/infrastructure/generic_types.go
@@ -65,26 +65,10 @@ type GenericInfrastructureMachineTemplateList struct {
 	Items           []GenericInfrastructureMachineTemplate `json:"items"`
 }
 
-// +kubebuilder:object:root=true
-
-type GenericClusterInfrastructureIdentity struct {
-	metav1.TypeMeta   `json:",inline"`
-	metav1.ObjectMeta `json:"metadata,omitempty"`
-}
-
-// +kubebuilder:object:root=true
-
-type GenericClusterInfrastructureIdentityList struct {
-	metav1.TypeMeta `json:",inline"`
-	metav1.ListMeta `json:"metadata,omitempty"`
-	Items           []GenericClusterInfrastructureIdentity `json:"items"`
-}
-
 func init() {
 	SchemeBuilder.Register(
 		&GenericInfrastructureCluster{}, &GenericInfrastructureClusterList{},
 		&GenericInfrastructureMachine{}, &GenericInfrastructureMachineList{},
 		&GenericInfrastructureMachineTemplate{}, &GenericInfrastructureMachineTemplateList{},
-		&GenericClusterInfrastructureIdentity{}, &GenericClusterInfrastructureIdentityList{},
 	)
 }
diff --git a/cmd/clusterctl/internal/test/providers/infrastructure/zz_generated.deepcopy.go b/cmd/clusterctl/internal/test/providers/infrastructure/zz_generated.deepcopy.go
index b00b28316..732483575 100644
--- a/cmd/clusterctl/internal/test/providers/infrastructure/zz_generated.deepcopy.go
+++ b/cmd/clusterctl/internal/test/providers/infrastructure/zz_generated.deepcopy.go
@@ -24,63 +24,6 @@ import (
 	runtime "k8s.io/apimachinery/pkg/runtime"
 )
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *GenericClusterInfrastructureIdentity) DeepCopyInto(out *GenericClusterInfrastructureIdentity) {
-	*out = *in
-	out.TypeMeta = in.TypeMeta
-	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericClusterInfrastructureIdentity.
-func (in *GenericClusterInfrastructureIdentity) DeepCopy() *GenericClusterInfrastructureIdentity {
-	if in == nil {
-		return nil
-	}
-	out := new(GenericClusterInfrastructureIdentity)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *GenericClusterInfrastructureIdentity) DeepCopyObject() runtime.Object {
-	if c := in.DeepCopy(); c != nil {
-		return c
-	}
-	return nil
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *GenericClusterInfrastructureIdentityList) DeepCopyInto(out *GenericClusterInfrastructureIdentityList) {
-	*out = *in
-	out.TypeMeta = in.TypeMeta
-	in.ListMeta.DeepCopyInto(&out.ListMeta)
-	if in.Items != nil {
-		in, out := &in.Items, &out.Items
-		*out = make([]GenericClusterInfrastructureIdentity, len(*in))
-		for i := range *in {
-			(*in)[i].DeepCopyInto(&(*out)[i])
-		}
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericClusterInfrastructureIdentityList.
-func (in *GenericClusterInfrastructureIdentityList) DeepCopy() *GenericClusterInfrastructureIdentityList {
-	if in == nil {
-		return nil
-	}
-	out := new(GenericClusterInfrastructureIdentityList)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *GenericClusterInfrastructureIdentityList) DeepCopyObject() runtime.Object {
-	if c := in.DeepCopy(); c != nil {
-		return c
-	}
-	return nil
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *GenericInfrastructureCluster) DeepCopyInto(out *GenericInfrastructureCluster) {
 	*out = *in
diff --git a/test/go.mod b/test/go.mod
index 2f03c49a4..aae88aa26 100644
--- a/test/go.mod
+++ b/test/go.mod
@@ -8,7 +8,7 @@ require (
 	github.com/blang/semver v3.5.1+incompatible
 	github.com/go-logr/logr v0.4.0
 	github.com/onsi/ginkgo v1.16.3
-	github.com/onsi/gomega v1.13.0
+	github.com/onsi/gomega v1.12.0
 	github.com/pkg/errors v0.9.1
 	github.com/spf13/pflag v1.0.5
 	k8s.io/api v0.21.1
@@ -19,7 +19,7 @@ require (
 	k8s.io/klog/v2 v2.9.0
 	k8s.io/utils v0.0.0-20210527160623-6fdb442a123b
 	sigs.k8s.io/cluster-api v0.0.0-00010101000000-000000000000
-	sigs.k8s.io/controller-runtime v0.9.0-beta.5
+	sigs.k8s.io/controller-runtime v0.9.0-beta.6
 	sigs.k8s.io/kind v0.11.1
 	sigs.k8s.io/yaml v1.2.0
 )
diff --git a/test/go.sum b/test/go.sum
index c1ef718cc..bdc00c6d9 100644
--- a/test/go.sum
+++ b/test/go.sum
@@ -139,6 +139,7 @@ github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT
 github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
 github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=
 github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
@@ -269,6 +270,7 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-github/v33 v33.0.0 h1:qAf9yP0qc54ufQxzwv+u9H0tiVOnPJxo0lI/JXqw3ZM=
@@ -294,8 +296,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
-github.com/googleapis/gnostic v0.5.4 h1:ynbQIWjLw7iv6HAFdixb30U7Uvcmx+f4KlLJpmhkTK0=
-github.com/googleapis/gnostic v0.5.4/go.mod h1:TRWw1s4gxBGjSe301Dai3c7wXJAZy57+/6tawkOvqHQ=
+github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=
+github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
@@ -342,8 +344,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
 github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
-github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
-github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
+github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
@@ -461,7 +463,6 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
 github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
 github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
 github.com/onsi/ginkgo v1.16.3 h1:3s86PZkI1ApJh6HFIzC1gXby/mIyZqfE5zxSvtoBSsM=
 github.com/onsi/ginkgo v1.16.3/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
@@ -471,10 +472,8 @@ github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
+github.com/onsi/gomega v1.12.0 h1:p4oGGk2M2UJc0wWN4lHFvIB71lxsh0T/UiKCCgFADY8=
 github.com/onsi/gomega v1.12.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
-github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
-github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
@@ -511,8 +510,8 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
-github.com/prometheus/client_golang v1.9.0 h1:Rrch9mh17XcxvEu9D9DEpb4isxjGBtcevQjKvxPRQIU=
-github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU=
+github.com/prometheus/client_golang v1.10.0 h1:/o0BDeWzLWXNZ+4q5gXltUvaMpJqckTa+jTNoB+z4cg=
+github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -526,16 +525,17 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
-github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tyspaKM=
-github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
+github.com/prometheus/common v0.18.0 h1:WCVKW7aL6LEe1uryfI9dnEc2ZqNB1Fn0ok930v0iL1Y=
+github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
-github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4=
 github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
+github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
+github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
@@ -721,7 +721,6 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
 golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
@@ -738,6 +737,7 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -784,11 +784,11 @@ golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
@@ -863,8 +863,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-gomodules.xyz/jsonpatch/v2 v2.1.0 h1:Phva6wqu+xR//Njw6iorylFFgn/z547tw5Ne3HZPQ+k=
-gomodules.xyz/jsonpatch/v2 v2.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=
+gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY=
+gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY=
 google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@@ -975,28 +975,21 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU=
 k8s.io/api v0.21.1 h1:94bbZ5NTjdINJEdzOkpS4vdPhkb1VFpTYC9zh43f75c=
 k8s.io/api v0.21.1/go.mod h1:FstGROTmsSHBarKc8bylzXih8BLNYTiS3TZcsoEDg2s=
-k8s.io/apiextensions-apiserver v0.21.0/go.mod h1:gsQGNtGkc/YoDG9loKI0V+oLZM4ljRPjc/sql5tmvzc=
 k8s.io/apiextensions-apiserver v0.21.1 h1:AA+cnsb6w7SZ1vD32Z+zdgfXdXY8X9uGX5bN6EoPEIo=
 k8s.io/apiextensions-apiserver v0.21.1/go.mod h1:KESQFCGjqVcVsZ9g0xX5bacMjyX5emuWcS2arzdEouA=
 k8s.io/apimachinery v0.20.2/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
-k8s.io/apimachinery v0.21.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY=
 k8s.io/apimachinery v0.21.1 h1:Q6XuHGlj2xc+hlMCvqyYfbv3H7SRGn2c8NycxJquDVs=
 k8s.io/apimachinery v0.21.1/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY=
-k8s.io/apiserver v0.21.0/go.mod h1:w2YSn4/WIwYuxG5zJmcqtRdtqgW/J2JRgFAqps3bBpg=
 k8s.io/apiserver v0.21.1 h1:wTRcid53IhxhbFt4KTrFSw8tAncfr01EP91lzfcygVg=
 k8s.io/apiserver v0.21.1/go.mod h1:nLLYZvMWn35glJ4/FZRhzLG/3MPxAaZTgV4FJZdr+tY=
 k8s.io/cli-runtime v0.21.1/go.mod h1:TI9Bvl8lQWZB2KqE91QLCp9AZE4l29zNFnj/x4IX4Fw=
-k8s.io/client-go v0.21.0/go.mod h1:nNBytTF9qPFDEhoqgEPaarobC8QPae13bElIVHzIglA=
 k8s.io/client-go v0.21.1 h1:bhblWYLZKUu+pm50plvQF8WpY6TXdRRtcS/K9WauOj4=
 k8s.io/client-go v0.21.1/go.mod h1:/kEw4RgW+3xnBGzvp9IWxKSNA+lXn3A7AuH3gdOAzLs=
 k8s.io/cluster-bootstrap v0.21.1 h1:wrk2jHNrXK7LKwLfmgZRkk0Od3LJtBmf2t5+cFHxdOE=
 k8s.io/cluster-bootstrap v0.21.1/go.mod h1:izdXmPTfqI9gkjQLf9PQ1Y9/DSqG54zU2UByEzgdajs=
-k8s.io/code-generator v0.21.0/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q=
 k8s.io/code-generator v0.21.1/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q=
-k8s.io/component-base v0.21.0/go.mod h1:qvtjz6X0USWXbgmbfXR+Agik4RZ3jv2Bgr5QnZzdPYw=
 k8s.io/component-base v0.21.1 h1:iLpj2btXbR326s/xNQWmPNGu0gaYSjzn7IN/5i28nQw=
 k8s.io/component-base v0.21.1/go.mod h1:NgzFZ2qu4m1juby4TnrmpR8adRk6ka62YdH5DkIIyKA=
 k8s.io/component-helpers v0.21.1/go.mod h1:FtC1flbiQlosHQrLrRUulnKxE4ajgWCGy/67fT2GRlQ=
@@ -1014,16 +1007,15 @@ k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iL
 k8s.io/kubectl v0.21.1/go.mod h1:PMYR88MqESuysBM/MX+Vu4JbX/50nY4d4kny+SPEI2U=
 k8s.io/metrics v0.21.1/go.mod h1:pyDVLsLe++FIGDBFU80NcW4xMFsuiVTWL8Zfi7+PpNo=
 k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
-k8s.io/utils v0.0.0-20210111153108-fddb29f9d009/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
-k8s.io/utils v0.0.0-20210305010621-2afb4311ab10/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
+k8s.io/utils v0.0.0-20210517184530-5a248b5acedc/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
 k8s.io/utils v0.0.0-20210527160623-6fdb442a123b h1:MSqsVQ3pZvPGTqCjptfimO2WjG7A9un2zcpiHkA6M/s=
 k8s.io/utils v0.0.0-20210527160623-6fdb442a123b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
-sigs.k8s.io/controller-runtime v0.9.0-beta.5 h1:yteq8am8lUFvlI/bfIgySMp/MqAPXiAghxX+XrMCifE=
-sigs.k8s.io/controller-runtime v0.9.0-beta.5/go.mod h1:ufPDuvefw2Y1KnBgHQrLdOjueYlj+XJV2AszbT+WTxs=
+sigs.k8s.io/controller-runtime v0.9.0-beta.6 h1:ac786Z1lZ2sgFIw98veqp+9YNXG5v/BxUBvEgiOVmDU=
+sigs.k8s.io/controller-runtime v0.9.0-beta.6/go.mod h1:rgf+cBz72pYlKXDRNhI1WFQv/S86EMUV4/ySmsEYgHk=
 sigs.k8s.io/kind v0.11.1 h1:pVzOkhUwMBrCB0Q/WllQDO3v14Y+o2V0tFgjTqIUjwA=
 sigs.k8s.io/kind v0.11.1/go.mod h1:fRpgVhtqAWrtLB9ED7zQahUimpUXuG/iHT88xYqEGIA=
 sigs.k8s.io/kustomize/api v0.8.8/go.mod h1:He1zoK0nk43Pc6NlV085xDXDXTNprtcyKZVm3swsdNY=