Repository: kubernetes-sigs/cluster-api
Pull request #6392 (2022-04-07 17:22:43)

:book: Update controlplane example in book
Signed-off-by: killianmuldoon <kmuldoon@vmware.com>

Update a v1alpha3 example of a KubeadmControlPlane in the controllers part of the CAPI book



k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **neolit123** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @neolit123` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcluster-api).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/cluster-api/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["neolit123"]} -->

Patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index ea75a54b3..6a34b4201 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -6,11 +6,4 @@ updates:
       interval: "weekly"
   commit-message:
       prefix: ":seedling:"
-- package-ecosystem: "gomod"
-  directory: "/"
-  schedule:
-    interval: "weekly"
-  allow:
-    - dependency-name: "github.com/coredns/corefile-migration"
-  commit-message:
-    prefix: ":seedling:"
+
diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml
index b63e884a4..0ba26d9a5 100644
--- a/.github/workflows/dependabot.yml
+++ b/.github/workflows/dependabot.yml
@@ -15,13 +15,13 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - name: Set up Go 1.x
-      uses: actions/setup-go@v3
+      uses: actions/setup-go@v2
       with:
         go-version: '1.17'
       id: go
     - name: Check out code into the Go module directory
       uses: actions/checkout@v3
-    - uses: actions/cache@v3.0.2
+    - uses: actions/cache@v3.0.1
       name: Restore go cache
       with:
         path: |
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1310454dd..87e7155cf 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -18,7 +18,7 @@ jobs:
         with:
           fetch-depth: 0
       - name: Install go
-        uses: actions/setup-go@v3
+        uses: actions/setup-go@v2
         with:
           go-version: '^1.17'
       - name: generate release artifacts
diff --git a/api/v1beta1/machinedeployment_webhook.go b/api/v1beta1/machinedeployment_webhook.go
index 4d8f16379..0b8b611ae 100644
--- a/api/v1beta1/machinedeployment_webhook.go
+++ b/api/v1beta1/machinedeployment_webhook.go
@@ -76,18 +76,18 @@ func (m *MachineDeployment) ValidateDelete() error {
 
 func (m *MachineDeployment) validate(old *MachineDeployment) error {
 	var allErrs field.ErrorList
-	specPath := field.NewPath("spec")
 	selector, err := metav1.LabelSelectorAsSelector(&m.Spec.Selector)
 	if err != nil {
 		allErrs = append(
 			allErrs,
-			field.Invalid(specPath.Child("selector"), m.Spec.Selector, err.Error()),
+			field.Invalid(field.NewPath("spec", "selector"), m.Spec.Selector, err.Error()),
 		)
 	} else if !selector.Matches(labels.Set(m.Spec.Template.Labels)) {
 		allErrs = append(
 			allErrs,
-			field.Forbidden(
-				specPath.Child("template", "metadata", "labels"),
+			field.Invalid(
+				field.NewPath("spec", "template", "metadata", "labels"),
+				m.Spec.Template.ObjectMeta.Labels,
 				fmt.Sprintf("must match spec.selector %q", selector.String()),
 			),
 		)
@@ -96,10 +96,7 @@ func (m *MachineDeployment) validate(old *MachineDeployment) error {
 	if old != nil && old.Spec.ClusterName != m.Spec.ClusterName {
 		allErrs = append(
 			allErrs,
-			field.Forbidden(
-				specPath.Child("clusterName"),
-				"field is immutable",
-			),
+			field.Invalid(field.NewPath("spec", "clusterName"), m.Spec.ClusterName, "field is immutable"),
 		)
 	}
 
@@ -113,7 +110,7 @@ func (m *MachineDeployment) validate(old *MachineDeployment) error {
 			if _, err := intstr.GetScaledValueFromIntOrPercent(m.Spec.Strategy.RollingUpdate.MaxSurge, total, true); err != nil {
 				allErrs = append(
 					allErrs,
-					field.Invalid(specPath.Child("strategy", "rollingUpdate", "maxSurge"),
+					field.Invalid(field.NewPath("spec", "strategy", "rollingUpdate", "maxSurge"),
 						m.Spec.Strategy.RollingUpdate.MaxSurge, fmt.Sprintf("must be either an int or a percentage: %v", err.Error())),
 				)
 			}
@@ -123,7 +120,7 @@ func (m *MachineDeployment) validate(old *MachineDeployment) error {
 			if _, err := intstr.GetScaledValueFromIntOrPercent(m.Spec.Strategy.RollingUpdate.MaxUnavailable, total, true); err != nil {
 				allErrs = append(
 					allErrs,
-					field.Invalid(specPath.Child("strategy", "rollingUpdate", "maxUnavailable"),
+					field.Invalid(field.NewPath("spec", "strategy", "rollingUpdate", "maxUnavailable"),
 						m.Spec.Strategy.RollingUpdate.MaxUnavailable, fmt.Sprintf("must be either an int or a percentage: %v", err.Error())),
 				)
 			}
@@ -132,7 +129,7 @@ func (m *MachineDeployment) validate(old *MachineDeployment) error {
 
 	if m.Spec.Template.Spec.Version != nil {
 		if !version.KubeSemver.MatchString(*m.Spec.Template.Spec.Version) {
-			allErrs = append(allErrs, field.Invalid(specPath.Child("template", "spec", "version"), *m.Spec.Template.Spec.Version, "must be a valid semantic version"))
+			allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "template", "spec", "version"), *m.Spec.Template.Spec.Version, "must be a valid semantic version"))
 		}
 	}
 
diff --git a/bootstrap/kubeadm/api/v1alpha3/conversion.go b/bootstrap/kubeadm/api/v1alpha3/conversion.go
index a467fc6cc..349e48d6f 100644
--- a/bootstrap/kubeadm/api/v1alpha3/conversion.go
+++ b/bootstrap/kubeadm/api/v1alpha3/conversion.go
@@ -38,15 +38,6 @@ func (src *KubeadmConfig) ConvertTo(dstRaw conversion.Hub) error {
 		return err
 	}
 
-	dst.Spec.Users = restored.Spec.Users
-	if restored.Spec.Users != nil {
-		for i := range restored.Spec.Users {
-			if restored.Spec.Users[i].PasswdFrom != nil {
-				dst.Spec.Users[i].PasswdFrom = restored.Spec.Users[i].PasswdFrom
-			}
-		}
-	}
-
 	if restored.Spec.JoinConfiguration != nil && restored.Spec.JoinConfiguration.NodeRegistration.IgnorePreflightErrors != nil {
 		if dst.Spec.JoinConfiguration == nil {
 			dst.Spec.JoinConfiguration = &bootstrapv1.JoinConfiguration{}
@@ -120,15 +111,6 @@ func (src *KubeadmConfigTemplate) ConvertTo(dstRaw conversion.Hub) error {
 		return err
 	}
 
-	dst.Spec.Template.Spec.Users = restored.Spec.Template.Spec.Users
-	if restored.Spec.Template.Spec.Users != nil {
-		for i := range restored.Spec.Template.Spec.Users {
-			if restored.Spec.Template.Spec.Users[i].PasswdFrom != nil {
-				dst.Spec.Template.Spec.Users[i].PasswdFrom = restored.Spec.Template.Spec.Users[i].PasswdFrom
-			}
-		}
-	}
-
 	if restored.Spec.Template.Spec.JoinConfiguration != nil && restored.Spec.Template.Spec.JoinConfiguration.NodeRegistration.IgnorePreflightErrors != nil {
 		if dst.Spec.Template.Spec.JoinConfiguration == nil {
 			dst.Spec.Template.Spec.JoinConfiguration = &bootstrapv1.JoinConfiguration{}
@@ -233,8 +215,3 @@ func Convert_v1beta1_KubeadmConfigSpec_To_v1alpha3_KubeadmConfigSpec(in *bootstr
 	// KubeadmConfigSpec.Ignition does not exist in kubeadm v1alpha3 API.
 	return autoConvert_v1beta1_KubeadmConfigSpec_To_v1alpha3_KubeadmConfigSpec(in, out, s)
 }
-
-func Convert_v1beta1_User_To_v1alpha3_User(in *bootstrapv1.User, out *User, s apiconversion.Scope) error {
-	// User.PasswdFrom does not exist in kubeadm v1alpha3 API.
-	return autoConvert_v1beta1_User_To_v1alpha3_User(in, out, s)
-}
diff --git a/bootstrap/kubeadm/api/v1alpha3/zz_generated.conversion.go b/bootstrap/kubeadm/api/v1alpha3/zz_generated.conversion.go
index bf88f9a66..ea239590f 100644
--- a/bootstrap/kubeadm/api/v1alpha3/zz_generated.conversion.go
+++ b/bootstrap/kubeadm/api/v1alpha3/zz_generated.conversion.go
@@ -184,6 +184,11 @@ func RegisterConversions(s *runtime.Scheme) error {
 	}); err != nil {
 		return err
 	}
+	if err := s.AddGeneratedConversionFunc((*v1beta1.User)(nil), (*User)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_User_To_v1alpha3_User(a.(*v1beta1.User), b.(*User), scope)
+	}); err != nil {
+		return err
+	}
 	if err := s.AddConversionFunc((*upstreamv1beta1.ClusterConfiguration)(nil), (*v1beta1.ClusterConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
 		return Convert_upstreamv1beta1_ClusterConfiguration_To_v1beta1_ClusterConfiguration(a.(*upstreamv1beta1.ClusterConfiguration), b.(*v1beta1.ClusterConfiguration), scope)
 	}); err != nil {
@@ -224,11 +229,6 @@ func RegisterConversions(s *runtime.Scheme) error {
 	}); err != nil {
 		return err
 	}
-	if err := s.AddConversionFunc((*v1beta1.User)(nil), (*User)(nil), func(a, b interface{}, scope conversion.Scope) error {
-		return Convert_v1beta1_User_To_v1alpha3_User(a.(*v1beta1.User), b.(*User), scope)
-	}); err != nil {
-		return err
-	}
 	return nil
 }
 
@@ -447,17 +447,7 @@ func autoConvert_v1alpha3_KubeadmConfigSpec_To_v1beta1_KubeadmConfigSpec(in *Kub
 	out.Mounts = *(*[]v1beta1.MountPoints)(unsafe.Pointer(&in.Mounts))
 	out.PreKubeadmCommands = *(*[]string)(unsafe.Pointer(&in.PreKubeadmCommands))
 	out.PostKubeadmCommands = *(*[]string)(unsafe.Pointer(&in.PostKubeadmCommands))
-	if in.Users != nil {
-		in, out := &in.Users, &out.Users
-		*out = make([]v1beta1.User, len(*in))
-		for i := range *in {
-			if err := Convert_v1alpha3_User_To_v1beta1_User(&(*in)[i], &(*out)[i], s); err != nil {
-				return err
-			}
-		}
-	} else {
-		out.Users = nil
-	}
+	out.Users = *(*[]v1beta1.User)(unsafe.Pointer(&in.Users))
 	out.NTP = (*v1beta1.NTP)(unsafe.Pointer(in.NTP))
 	out.Format = v1beta1.Format(in.Format)
 	out.Verbosity = (*int32)(unsafe.Pointer(in.Verbosity))
@@ -503,17 +493,7 @@ func autoConvert_v1beta1_KubeadmConfigSpec_To_v1alpha3_KubeadmConfigSpec(in *v1b
 	out.Mounts = *(*[]MountPoints)(unsafe.Pointer(&in.Mounts))
 	out.PreKubeadmCommands = *(*[]string)(unsafe.Pointer(&in.PreKubeadmCommands))
 	out.PostKubeadmCommands = *(*[]string)(unsafe.Pointer(&in.PostKubeadmCommands))
-	if in.Users != nil {
-		in, out := &in.Users, &out.Users
-		*out = make([]User, len(*in))
-		for i := range *in {
-			if err := Convert_v1beta1_User_To_v1alpha3_User(&(*in)[i], &(*out)[i], s); err != nil {
-				return err
-			}
-		}
-	} else {
-		out.Users = nil
-	}
+	out.Users = *(*[]User)(unsafe.Pointer(&in.Users))
 	out.NTP = (*NTP)(unsafe.Pointer(in.NTP))
 	out.Format = Format(in.Format)
 	out.Verbosity = (*int32)(unsafe.Pointer(in.Verbosity))
@@ -782,10 +762,14 @@ func autoConvert_v1beta1_User_To_v1alpha3_User(in *v1beta1.User, out *User, s co
 	out.Inactive = (*bool)(unsafe.Pointer(in.Inactive))
 	out.Shell = (*string)(unsafe.Pointer(in.Shell))
 	out.Passwd = (*string)(unsafe.Pointer(in.Passwd))
-	// WARNING: in.PasswdFrom requires manual conversion: does not exist in peer-type
 	out.PrimaryGroup = (*string)(unsafe.Pointer(in.PrimaryGroup))
 	out.LockPassword = (*bool)(unsafe.Pointer(in.LockPassword))
 	out.Sudo = (*string)(unsafe.Pointer(in.Sudo))
 	out.SSHAuthorizedKeys = *(*[]string)(unsafe.Pointer(&in.SSHAuthorizedKeys))
 	return nil
 }
+
+// Convert_v1beta1_User_To_v1alpha3_User is an autogenerated conversion function.
+func Convert_v1beta1_User_To_v1alpha3_User(in *v1beta1.User, out *User, s conversion.Scope) error {
+	return autoConvert_v1beta1_User_To_v1alpha3_User(in, out, s)
+}
diff --git a/bootstrap/kubeadm/api/v1alpha4/conversion.go b/bootstrap/kubeadm/api/v1alpha4/conversion.go
index b2bbcb5c5..6da4b2da8 100644
--- a/bootstrap/kubeadm/api/v1alpha4/conversion.go
+++ b/bootstrap/kubeadm/api/v1alpha4/conversion.go
@@ -37,15 +37,6 @@ func (src *KubeadmConfig) ConvertTo(dstRaw conversion.Hub) error {
 		return err
 	}
 
-	dst.Spec.Users = restored.Spec.Users
-	if restored.Spec.Users != nil {
-		for i := range restored.Spec.Users {
-			if restored.Spec.Users[i].PasswdFrom != nil {
-				dst.Spec.Users[i].PasswdFrom = restored.Spec.Users[i].PasswdFrom
-			}
-		}
-	}
-
 	dst.Spec.Ignition = restored.Spec.Ignition
 	if restored.Spec.InitConfiguration != nil {
 		if dst.Spec.InitConfiguration == nil {
@@ -100,15 +91,6 @@ func (src *KubeadmConfigTemplate) ConvertTo(dstRaw conversion.Hub) error {
 		return err
 	}
 
-	dst.Spec.Template.Spec.Users = restored.Spec.Template.Spec.Users
-	if restored.Spec.Template.Spec.Users != nil {
-		for i := range restored.Spec.Template.Spec.Users {
-			if restored.Spec.Template.Spec.Users[i].PasswdFrom != nil {
-				dst.Spec.Template.Spec.Users[i].PasswdFrom = restored.Spec.Template.Spec.Users[i].PasswdFrom
-			}
-		}
-	}
-
 	dst.Spec.Template.Spec.Ignition = restored.Spec.Template.Spec.Ignition
 	if restored.Spec.Template.Spec.InitConfiguration != nil {
 		if dst.Spec.Template.Spec.InitConfiguration == nil {
@@ -165,8 +147,3 @@ func Convert_v1beta1_JoinConfiguration_To_v1alpha4_JoinConfiguration(in *bootstr
 	// InitConfiguration.Patches does not exist in kubeadm v1alpha4 API.
 	return autoConvert_v1beta1_JoinConfiguration_To_v1alpha4_JoinConfiguration(in, out, s)
 }
-
-func Convert_v1beta1_User_To_v1alpha4_User(in *bootstrapv1.User, out *User, s apiconversion.Scope) error {
-	// User.PasswdFrom does not exist in kubeadm v1alpha4 API.
-	return autoConvert_v1beta1_User_To_v1alpha4_User(in, out, s)
-}
diff --git a/bootstrap/kubeadm/api/v1alpha4/zz_generated.conversion.go b/bootstrap/kubeadm/api/v1alpha4/zz_generated.conversion.go
index 187a97828..cd0bff198 100644
--- a/bootstrap/kubeadm/api/v1alpha4/zz_generated.conversion.go
+++ b/bootstrap/kubeadm/api/v1alpha4/zz_generated.conversion.go
@@ -390,6 +390,11 @@ func RegisterConversions(s *runtime.Scheme) error {
 	}); err != nil {
 		return err
 	}
+	if err := s.AddGeneratedConversionFunc((*v1beta1.User)(nil), (*User)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_User_To_v1alpha4_User(a.(*v1beta1.User), b.(*User), scope)
+	}); err != nil {
+		return err
+	}
 	if err := s.AddConversionFunc((*v1beta1.InitConfiguration)(nil), (*InitConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
 		return Convert_v1beta1_InitConfiguration_To_v1alpha4_InitConfiguration(a.(*v1beta1.InitConfiguration), b.(*InitConfiguration), scope)
 	}); err != nil {
@@ -405,11 +410,6 @@ func RegisterConversions(s *runtime.Scheme) error {
 	}); err != nil {
 		return err
 	}
-	if err := s.AddConversionFunc((*v1beta1.User)(nil), (*User)(nil), func(a, b interface{}, scope conversion.Scope) error {
-		return Convert_v1beta1_User_To_v1alpha4_User(a.(*v1beta1.User), b.(*User), scope)
-	}); err != nil {
-		return err
-	}
 	return nil
 }
 
@@ -1108,17 +1108,7 @@ func autoConvert_v1alpha4_KubeadmConfigSpec_To_v1beta1_KubeadmConfigSpec(in *Kub
 	out.Mounts = *(*[]v1beta1.MountPoints)(unsafe.Pointer(&in.Mounts))
 	out.PreKubeadmCommands = *(*[]string)(unsafe.Pointer(&in.PreKubeadmCommands))
 	out.PostKubeadmCommands = *(*[]string)(unsafe.Pointer(&in.PostKubeadmCommands))
-	if in.Users != nil {
-		in, out := &in.Users, &out.Users
-		*out = make([]v1beta1.User, len(*in))
-		for i := range *in {
-			if err := Convert_v1alpha4_User_To_v1beta1_User(&(*in)[i], &(*out)[i], s); err != nil {
-				return err
-			}
-		}
-	} else {
-		out.Users = nil
-	}
+	out.Users = *(*[]v1beta1.User)(unsafe.Pointer(&in.Users))
 	out.NTP = (*v1beta1.NTP)(unsafe.Pointer(in.NTP))
 	out.Format = v1beta1.Format(in.Format)
 	out.Verbosity = (*int32)(unsafe.Pointer(in.Verbosity))
@@ -1156,17 +1146,7 @@ func autoConvert_v1beta1_KubeadmConfigSpec_To_v1alpha4_KubeadmConfigSpec(in *v1b
 	out.Mounts = *(*[]MountPoints)(unsafe.Pointer(&in.Mounts))
 	out.PreKubeadmCommands = *(*[]string)(unsafe.Pointer(&in.PreKubeadmCommands))
 	out.PostKubeadmCommands = *(*[]string)(unsafe.Pointer(&in.PostKubeadmCommands))
-	if in.Users != nil {
-		in, out := &in.Users, &out.Users
-		*out = make([]User, len(*in))
-		for i := range *in {
-			if err := Convert_v1beta1_User_To_v1alpha4_User(&(*in)[i], &(*out)[i], s); err != nil {
-				return err
-			}
-		}
-	} else {
-		out.Users = nil
-	}
+	out.Users = *(*[]User)(unsafe.Pointer(&in.Users))
 	out.NTP = (*NTP)(unsafe.Pointer(in.NTP))
 	out.Format = Format(in.Format)
 	out.Verbosity = (*int32)(unsafe.Pointer(in.Verbosity))
@@ -1523,10 +1503,14 @@ func autoConvert_v1beta1_User_To_v1alpha4_User(in *v1beta1.User, out *User, s co
 	out.Inactive = (*bool)(unsafe.Pointer(in.Inactive))
 	out.Shell = (*string)(unsafe.Pointer(in.Shell))
 	out.Passwd = (*string)(unsafe.Pointer(in.Passwd))
-	// WARNING: in.PasswdFrom requires manual conversion: does not exist in peer-type
 	out.PrimaryGroup = (*string)(unsafe.Pointer(in.PrimaryGroup))
 	out.LockPassword = (*bool)(unsafe.Pointer(in.LockPassword))
 	out.Sudo = (*string)(unsafe.Pointer(in.Sudo))
 	out.SSHAuthorizedKeys = *(*[]string)(unsafe.Pointer(&in.SSHAuthorizedKeys))
 	return nil
 }
+
+// Convert_v1beta1_User_To_v1alpha4_User is an autogenerated conversion function.
+func Convert_v1beta1_User_To_v1alpha4_User(in *v1beta1.User, out *User, s conversion.Scope) error {
+	return autoConvert_v1beta1_User_To_v1alpha4_User(in, out, s)
+}
diff --git a/bootstrap/kubeadm/api/v1beta1/kubeadmconfig_types.go b/bootstrap/kubeadm/api/v1beta1/kubeadmconfig_types.go
index 7d2c6704d..40bbe36a5 100644
--- a/bootstrap/kubeadm/api/v1beta1/kubeadmconfig_types.go
+++ b/bootstrap/kubeadm/api/v1beta1/kubeadmconfig_types.go
@@ -252,26 +252,6 @@ type SecretFileSource struct {
 	Key string `json:"key"`
 }
 
-// PasswdSource is a union of all possible external source types for passwd data.
-// Only one field may be populated in any given instance. Developers adding new
-// sources of data for target systems should add them here.
-type PasswdSource struct {
-	// Secret represents a secret that should populate this password.
-	Secret SecretPasswdSource `json:"secret"`
-}
-
-// SecretPasswdSource adapts a Secret into a PasswdSource.
-//
-// The contents of the target Secret's Data field will be presented
-// as passwd using the keys in the Data field as the file names.
-type SecretPasswdSource struct {
-	// Name of the secret in the KubeadmBootstrapConfig's namespace to use.
-	Name string `json:"name"`
-
-	// Key is the key in the secret's data map for this value.
-	Key string `json:"key"`
-}
-
 // User defines the input for a generated user in cloud-init.
 type User struct {
 	// Name specifies the user name
@@ -301,10 +281,6 @@ type User struct {
 	// +optional
 	Passwd *string `json:"passwd,omitempty"`
 
-	// PasswdFrom is a referenced source of passwd to populate the passwd.
-	// +optional
-	PasswdFrom *PasswdSource `json:"passwdFrom,omitempty"`
-
 	// PrimaryGroup specifies the primary group for the user
 	// +optional
 	PrimaryGroup *string `json:"primaryGroup,omitempty"`
diff --git a/bootstrap/kubeadm/api/v1beta1/kubeadmconfig_webhook.go b/bootstrap/kubeadm/api/v1beta1/kubeadmconfig_webhook.go
index a1284256a..bd5ce1d8a 100644
--- a/bootstrap/kubeadm/api/v1beta1/kubeadmconfig_webhook.go
+++ b/bootstrap/kubeadm/api/v1beta1/kubeadmconfig_webhook.go
@@ -31,7 +31,6 @@ import (
 var (
 	cannotUseWithIgnition                            = fmt.Sprintf("not supported when spec.format is set to %q", Ignition)
 	conflictingFileSourceMsg                         = "only one of content or contentFrom may be specified for a single file"
-	conflictingUserSourceMsg                         = "only one of passwd or passwdFrom may be specified for a single user"
 	kubeadmBootstrapFormatIgnitionFeatureDisabledMsg = "can be set only if the KubeadmBootstrapFormatIgnition feature gate is enabled"
 	missingSecretNameMsg                             = "secret file source must specify non-empty secret name"
 	missingSecretKeyMsg                              = "secret file source must specify non-empty secret key"
@@ -94,7 +93,6 @@ func (c *KubeadmConfigSpec) Validate(pathPrefix *field.Path) field.ErrorList {
 	var allErrs field.ErrorList
 
 	allErrs = append(allErrs, c.validateFiles(pathPrefix)...)
-	allErrs = append(allErrs, c.validateUsers(pathPrefix)...)
 	allErrs = append(allErrs, c.validateIgnition(pathPrefix)...)
 
 	return allErrs
@@ -157,49 +155,6 @@ func (c *KubeadmConfigSpec) validateFiles(pathPrefix *field.Path) field.ErrorLis
 	return allErrs
 }
 
-func (c *KubeadmConfigSpec) validateUsers(pathPrefix *field.Path) field.ErrorList {
-	var allErrs field.ErrorList
-
-	for i := range c.Users {
-		user := c.Users[i]
-		if user.Passwd != nil && user.PasswdFrom != nil {
-			allErrs = append(
-				allErrs,
-				field.Invalid(
-					pathPrefix.Child("users").Index(i),
-					user,
-					conflictingUserSourceMsg,
-				),
-			)
-		}
-		// n.b.: if we ever add types besides Secret as a PasswdFrom
-		// Source, we must add webhook validation here for one of the
-		// sources being non-nil.
-		if user.PasswdFrom != nil {
-			if user.PasswdFrom.Secret.Name == "" {
-				allErrs = append(
-					allErrs,
-					field.Required(
-						pathPrefix.Child("users").Index(i).Child("passwdFrom", "secret", "name"),
-						missingSecretNameMsg,
-					),
-				)
-			}
-			if user.PasswdFrom.Secret.Key == "" {
-				allErrs = append(
-					allErrs,
-					field.Required(
-						pathPrefix.Child("users").Index(i).Child("passwdFrom", "secret", "key"),
-						missingSecretKeyMsg,
-					),
-				)
-			}
-		}
-	}
-
-	return allErrs
-}
-
 func (c *KubeadmConfigSpec) validateIgnition(pathPrefix *field.Path) field.ErrorList {
 	var allErrs field.ErrorList
 
diff --git a/bootstrap/kubeadm/api/v1beta1/zz_generated.deepcopy.go b/bootstrap/kubeadm/api/v1beta1/zz_generated.deepcopy.go
index 403c76830..b3caa08cc 100644
--- a/bootstrap/kubeadm/api/v1beta1/zz_generated.deepcopy.go
+++ b/bootstrap/kubeadm/api/v1beta1/zz_generated.deepcopy.go
@@ -1021,22 +1021,6 @@ func (in *Partition) DeepCopy() *Partition {
 	return out
 }
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *PasswdSource) DeepCopyInto(out *PasswdSource) {
-	*out = *in
-	out.Secret = in.Secret
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PasswdSource.
-func (in *PasswdSource) DeepCopy() *PasswdSource {
-	if in == nil {
-		return nil
-	}
-	out := new(PasswdSource)
-	in.DeepCopyInto(out)
-	return out
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Patches) DeepCopyInto(out *Patches) {
 	*out = *in
@@ -1067,21 +1051,6 @@ func (in *SecretFileSource) DeepCopy() *SecretFileSource {
 	return out
 }
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *SecretPasswdSource) DeepCopyInto(out *SecretPasswdSource) {
-	*out = *in
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretPasswdSource.
-func (in *SecretPasswdSource) DeepCopy() *SecretPasswdSource {
-	if in == nil {
-		return nil
-	}
-	out := new(SecretPasswdSource)
-	in.DeepCopyInto(out)
-	return out
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *User) DeepCopyInto(out *User) {
 	*out = *in
@@ -1115,11 +1084,6 @@ func (in *User) DeepCopyInto(out *User) {
 		*out = new(string)
 		**out = **in
 	}
-	if in.PasswdFrom != nil {
-		in, out := &in.PasswdFrom, &out.PasswdFrom
-		*out = new(PasswdSource)
-		**out = **in
-	}
 	if in.PrimaryGroup != nil {
 		in, out := &in.PrimaryGroup, &out.PrimaryGroup
 		*out = new(string)
diff --git a/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigs.yaml b/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigs.yaml
index 87fd625ee..9aa0b56ea 100644
--- a/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigs.yaml
+++ b/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigs.yaml
@@ -2956,29 +2956,6 @@ spec:
                     passwd:
                       description: Passwd specifies a hashed password for the user
                       type: string
-                    passwdFrom:
-                      description: PasswdFrom is a referenced source of passwd to
-                        populate the passwd.
-                      properties:
-                        secret:
-                          description: Secret represents a secret that should populate
-                            this password.
-                          properties:
-                            key:
-                              description: Key is the key in the secret's data map
-                                for this value.
-                              type: string
-                            name:
-                              description: Name of the secret in the KubeadmBootstrapConfig's
-                                namespace to use.
-                              type: string
-                          required:
-                          - key
-                          - name
-                          type: object
-                      required:
-                      - secret
-                      type: object
                     primaryGroup:
                       description: PrimaryGroup specifies the primary group for the
                         user
diff --git a/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigtemplates.yaml b/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigtemplates.yaml
index f6a8ffaf6..fde62870a 100644
--- a/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigtemplates.yaml
+++ b/bootstrap/kubeadm/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigtemplates.yaml
@@ -3008,29 +3008,6 @@ spec:
                               description: Passwd specifies a hashed password for
                                 the user
                               type: string
-                            passwdFrom:
-                              description: PasswdFrom is a referenced source of passwd
-                                to populate the passwd.
-                              properties:
-                                secret:
-                                  description: Secret represents a secret that should
-                                    populate this password.
-                                  properties:
-                                    key:
-                                      description: Key is the key in the secret's
-                                        data map for this value.
-                                      type: string
-                                    name:
-                                      description: Name of the secret in the KubeadmBootstrapConfig's
-                                        namespace to use.
-                                      type: string
-                                  required:
-                                  - key
-                                  - name
-                                  type: object
-                              required:
-                              - secret
-                              type: object
                             primaryGroup:
                               description: PrimaryGroup specifies the primary group
                                 for the user
diff --git a/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go b/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go
index 8f6c0e6e3..7bb951f6f 100644
--- a/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go
+++ b/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go
@@ -447,19 +447,13 @@ func (r *KubeadmConfigReconciler) handleClusterNotInitialized(ctx context.Contex
 		return ctrl.Result{}, err
 	}
 
-	users, err := r.resolveUsers(ctx, scope.Config)
-	if err != nil {
-		conditions.MarkFalse(scope.Config, bootstrapv1.DataSecretAvailableCondition, bootstrapv1.DataSecretGenerationFailedReason, clusterv1.ConditionSeverityWarning, err.Error())
-		return ctrl.Result{}, err
-	}
-
 	controlPlaneInput := &cloudinit.ControlPlaneInput{
 		BaseUserData: cloudinit.BaseUserData{
 			AdditionalFiles:     files,
 			NTP:                 scope.Config.Spec.NTP,
 			PreKubeadmCommands:  scope.Config.Spec.PreKubeadmCommands,
 			PostKubeadmCommands: scope.Config.Spec.PostKubeadmCommands,
-			Users:               users,
+			Users:               scope.Config.Spec.Users,
 			Mounts:              scope.Config.Spec.Mounts,
 			DiskSetup:           scope.Config.Spec.DiskSetup,
 			KubeadmVerbosity:    verbosityFlag,
@@ -546,19 +540,13 @@ func (r *KubeadmConfigReconciler) joinWorker(ctx context.Context, scope *Scope)
 		return ctrl.Result{}, err
 	}
 
-	users, err := r.resolveUsers(ctx, scope.Config)
-	if err != nil {
-		conditions.MarkFalse(scope.Config, bootstrapv1.DataSecretAvailableCondition, bootstrapv1.DataSecretGenerationFailedReason, clusterv1.ConditionSeverityWarning, err.Error())
-		return ctrl.Result{}, err
-	}
-
 	nodeInput := &cloudinit.NodeInput{
 		BaseUserData: cloudinit.BaseUserData{
 			AdditionalFiles:      files,
 			NTP:                  scope.Config.Spec.NTP,
 			PreKubeadmCommands:   scope.Config.Spec.PreKubeadmCommands,
 			PostKubeadmCommands:  scope.Config.Spec.PostKubeadmCommands,
-			Users:                users,
+			Users:                scope.Config.Spec.Users,
 			Mounts:               scope.Config.Spec.Mounts,
 			DiskSetup:            scope.Config.Spec.DiskSetup,
 			KubeadmVerbosity:     verbosityFlag,
@@ -647,12 +635,6 @@ func (r *KubeadmConfigReconciler) joinControlplane(ctx context.Context, scope *S
 		return ctrl.Result{}, err
 	}
 
-	users, err := r.resolveUsers(ctx, scope.Config)
-	if err != nil {
-		conditions.MarkFalse(scope.Config, bootstrapv1.DataSecretAvailableCondition, bootstrapv1.DataSecretGenerationFailedReason, clusterv1.ConditionSeverityWarning, err.Error())
-		return ctrl.Result{}, err
-	}
-
 	controlPlaneJoinInput := &cloudinit.ControlPlaneJoinInput{
 		JoinConfiguration: joinData,
 		Certificates:      certificates,
@@ -661,7 +643,7 @@ func (r *KubeadmConfigReconciler) joinControlplane(ctx context.Context, scope *S
 			NTP:                  scope.Config.Spec.NTP,
 			PreKubeadmCommands:   scope.Config.Spec.PreKubeadmCommands,
 			PostKubeadmCommands:  scope.Config.Spec.PostKubeadmCommands,
-			Users:                users,
+			Users:                scope.Config.Spec.Users,
 			Mounts:               scope.Config.Spec.Mounts,
 			DiskSetup:            scope.Config.Spec.DiskSetup,
 			KubeadmVerbosity:     verbosityFlag,
@@ -731,45 +713,6 @@ func (r *KubeadmConfigReconciler) resolveSecretFileContent(ctx context.Context,
 	return data, nil
 }
 
-// resolveUsers maps .Spec.Users into cloudinit.Users, resolving any object references
-// along the way.
-func (r *KubeadmConfigReconciler) resolveUsers(ctx context.Context, cfg *bootstrapv1.KubeadmConfig) ([]bootstrapv1.User, error) {
-	collected := make([]bootstrapv1.User, 0, len(cfg.Spec.Users))
-
-	for i := range cfg.Spec.Users {
-		in := cfg.Spec.Users[i]
-		if in.PasswdFrom != nil {
-			data, err := r.resolveSecretPasswordContent(ctx, cfg.Namespace, in)
-			if err != nil {
-				return nil, errors.Wrapf(err, "failed to resolve passwd source")
-			}
-			in.PasswdFrom = nil
-			passwdContent := string(data)
-			in.Passwd = &passwdContent
-		}
-		collected = append(collected, in)
-	}
-
-	return collected, nil
-}
-
-// resolveSecretUserContent returns passwd fetched from a referenced secret object.
-func (r *KubeadmConfigReconciler) resolveSecretPasswordContent(ctx context.Context, ns string, source bootstrapv1.User) ([]byte, error) {
-	secret := &corev1.Secret{}
-	key := types.NamespacedName{Namespace: ns, Name: source.PasswdFrom.Secret.Name}
-	if err := r.Client.Get(ctx, key, secret); err != nil {
-		if apierrors.IsNotFound(err) {
-			return nil, errors.Wrapf(err, "secret not found: %s", key)
-		}
-		return nil, errors.Wrapf(err, "failed to retrieve Secret %q", key)
-	}
-	data, ok := secret.Data[source.PasswdFrom.Secret.Key]
-	if !ok {
-		return nil, errors.Errorf("secret references non-existent secret key: %q", source.PasswdFrom.Secret.Key)
-	}
-	return data, nil
-}
-
 // ClusterToKubeadmConfigs is a handler.ToRequestsFunc to be used to enqueue
 // requests for reconciliation of KubeadmConfigs.
 func (r *KubeadmConfigReconciler) ClusterToKubeadmConfigs(o client.Object) []ctrl.Request {
diff --git a/cmd/clusterctl/client/describe.go b/cmd/clusterctl/client/describe.go
index 2bdf14645..fa2512797 100644
--- a/cmd/clusterctl/client/describe.go
+++ b/cmd/clusterctl/client/describe.go
@@ -41,15 +41,6 @@ type DescribeClusterOptions struct {
 	// ShowMachineSets instructs the discovery process to include machine sets in the ObjectTree.
 	ShowMachineSets bool
 
-	// ShowClusterResourceSets instructs the discovery process to include cluster resource sets in the ObjectTree.
-	ShowClusterResourceSets bool
-
-	// ShowTemplates instructs the discovery process to include infrastructure and bootstrap config templates in the ObjectTree.
-	ShowTemplates bool
-
-	// AddTemplateVirtualNode instructs the discovery process to group template under a virtual node.
-	AddTemplateVirtualNode bool
-
 	// Echo displays MachineInfrastructure or BootstrapConfig objects if the object's ready condition is true
 	// or it has the same Status, Severity and Reason of the parent's object ready condition (it is an echo)
 	Echo bool
@@ -89,12 +80,9 @@ func (c *clusterctlClient) DescribeCluster(options DescribeClusterOptions) (*tre
 
 	// Gets the object tree representing the status of a Cluster API cluster.
 	return tree.Discovery(context.TODO(), client, options.Namespace, options.ClusterName, tree.DiscoverOptions{
-		ShowOtherConditions:     options.ShowOtherConditions,
-		ShowMachineSets:         options.ShowMachineSets,
-		ShowClusterResourceSets: options.ShowClusterResourceSets,
-		ShowTemplates:           options.ShowTemplates,
-		AddTemplateVirtualNode:  options.AddTemplateVirtualNode,
-		Echo:                    options.Echo,
-		Grouping:                options.Grouping,
+		ShowOtherConditions: options.ShowOtherConditions,
+		ShowMachineSets:     options.ShowMachineSets,
+		Echo:                options.Echo,
+		Grouping:            options.Grouping,
 	})
 }
diff --git a/cmd/clusterctl/client/tree/annotations.go b/cmd/clusterctl/client/tree/annotations.go
index 18a085bab..145d5d479 100644
--- a/cmd/clusterctl/client/tree/annotations.go
+++ b/cmd/clusterctl/client/tree/annotations.go
@@ -49,11 +49,6 @@ const (
 
 	// GroupItemsSeparator is the separator used in the GroupItemsAnnotation.
 	GroupItemsSeparator = ", "
-
-	// ObjectZOrderAnnotation contains an integer that defines the sorting of child objects when the object tree is printed.
-	// Objects are sorted by their z-order from highest to lowest, and then by their name in alphaebetical order if the
-	// z-order is the same. Objects with no z-order set are assumed to have a default z-order of 0.
-	ObjectZOrderAnnotation = "tree.cluster.x-k8s.io.io/z-order"
 )
 
 // GetMetaName returns the object meta name that should be used for the object in the presentation layer, if defined.
@@ -74,7 +69,7 @@ func IsGroupingObject(obj client.Object) bool {
 	return false
 }
 
-// IsGroupObject returns true if the object is the result of a grouping operation, and
+// IsGroupObject return true if the object is the result of a grouping operation, and
 // thus the object is representing group of sibling object, e.g. a group of machines.
 func IsGroupObject(obj client.Object) bool {
 	if val, ok := getBoolAnnotation(obj, GroupObjectAnnotation); ok {
@@ -83,7 +78,7 @@ func IsGroupObject(obj client.Object) bool {
 	return false
 }
 
-// GetGroupItems returns the list of names for the objects included in a group object.
+// GetGroupItems return the list of names for the objects included in a group object.
 func GetGroupItems(obj client.Object) string {
 	if val, ok := getAnnotation(obj, GroupItemsAnnotation); ok {
 		return val
@@ -91,17 +86,7 @@ func GetGroupItems(obj client.Object) string {
 	return ""
 }
 
-// GetZOrder return the zOrder of the object. Objects with no zOrder have a default zOrder of 0.
-func GetZOrder(obj client.Object) int {
-	if val, ok := getAnnotation(obj, ObjectZOrderAnnotation); ok {
-		if zOrder, err := strconv.ParseInt(val, 10, 0); err == nil {
-			return int(zOrder)
-		}
-	}
-	return 0
-}
-
-// IsVirtualObject returns true if the object does not correspond to any real object, but instead it is
+// IsVirtualObject return true if the object does not correspond to any real object, but instead it is
 // a virtual object introduced to provide a better representation of the cluster status.
 func IsVirtualObject(obj client.Object) bool {
 	if val, ok := getBoolAnnotation(obj, VirtualObjectAnnotation); ok {
diff --git a/cmd/clusterctl/client/tree/discovery.go b/cmd/clusterctl/client/tree/discovery.go
index 1f003e682..76405bfe0 100644
--- a/cmd/clusterctl/client/tree/discovery.go
+++ b/cmd/clusterctl/client/tree/discovery.go
@@ -19,15 +19,11 @@ package tree
 import (
 	"context"
 
-	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
 	"sigs.k8s.io/cluster-api/controllers/external"
-	addonsv1 "sigs.k8s.io/cluster-api/exp/addons/api/v1beta1"
-	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
 	"sigs.k8s.io/cluster-api/util"
 )
 
@@ -40,16 +36,8 @@ type DiscoverOptions struct {
 	// ShowMachineSets instructs the discovery process to include machine sets in the ObjectTree.
 	ShowMachineSets bool
 
-	// ShowClusterResourceSets instructs the discovery process to include cluster resource sets in the ObjectTree.
-	ShowClusterResourceSets bool
-
-	// ShowTemplates instructs the discovery process to include infrastructure and bootstrap config templates in the ObjectTree.
-	ShowTemplates bool
-
-	// AddTemplateVirtualNode instructs the discovery process to group template under a virtual node.
-	AddTemplateVirtualNode bool
-
 	// Echo displays MachineInfrastructure or BootstrapConfig objects if the object's ready condition is true
+	// or it has the same Status, Severity and Reason of the parent's object ready condition (it is an echo)
 	Echo bool
 
 	// Grouping groups machine objects in case the ready conditions
@@ -87,14 +75,10 @@ func Discovery(ctx context.Context, c client.Client, namespace, name string, opt
 		tree.Add(cluster, clusterInfra, ObjectMetaName("ClusterInfrastructure"))
 	}
 
-	if options.ShowClusterResourceSets {
-		addClusterResourceSetsToObjectTree(ctx, c, cluster, tree)
-	}
-
 	// Adds control plane
-	controlPlane, err := external.Get(ctx, c, cluster.Spec.ControlPlaneRef, cluster.Namespace)
+	controlPLane, err := external.Get(ctx, c, cluster.Spec.ControlPlaneRef, cluster.Namespace)
 	if err == nil {
-		addControlPlane(cluster, controlPlane, tree, options)
+		tree.Add(cluster, controlPLane, ObjectMetaName("ControlPlane"), GroupingObject(true))
 	}
 
 	// Adds control plane machines.
@@ -121,103 +105,25 @@ func Discovery(ctx context.Context, c client.Client, namespace, name string, opt
 	controlPlaneMachines := selectControlPlaneMachines(machinesList)
 	for i := range controlPlaneMachines {
 		cp := controlPlaneMachines[i]
-		addMachineFunc(controlPlane, cp)
+		addMachineFunc(controlPLane, cp)
 	}
 
-	machinePoolList, err := getMachinePoolsInCluster(ctx, c, cluster.Namespace, cluster.Name)
-	if err != nil {
-		return nil, err
+	if len(machinesList.Items) == len(controlPlaneMachines) {
+		return tree, nil
 	}
 
 	workers := VirtualObject(cluster.Namespace, "WorkerGroup", "Workers")
-	// Add WorkerGroup if there are MachineDeployments or MachinePools
-	if len(machinesList.Items) != len(controlPlaneMachines) || len(machinePoolList.Items) > 0 {
-		tree.Add(cluster, workers)
-	}
-
-	if len(machinesList.Items) != len(controlPlaneMachines) { // Add MachineDeployment objects
-		tree.Add(cluster, workers)
-		err = addMachineDeploymentToObjectTree(ctx, c, cluster, workers, machinesList, tree, options, addMachineFunc)
-		if err != nil {
-			return nil, err
-		}
-
-		// Handles orphan machines.
-		if len(machineMap) < len(machinesList.Items) {
-			other := VirtualObject(cluster.Namespace, "OtherGroup", "Other")
-			tree.Add(workers, other)
-
-			for i := range machinesList.Items {
-				m := &machinesList.Items[i]
-				if _, ok := machineMap[m.Name]; ok {
-					continue
-				}
-				addMachineFunc(other, m)
-			}
-		}
-	}
-
-	if len(machinePoolList.Items) > 0 { // Add MachinePool objects
-		tree.Add(cluster, workers)
-		addMachinePoolsToObjectTree(ctx, c, cluster.Namespace, workers, machinePoolList, tree)
-	}
-
-	return tree, nil
-}
+	tree.Add(cluster, workers)
 
-func addClusterResourceSetsToObjectTree(ctx context.Context, c client.Client, cluster *clusterv1.Cluster, tree *ObjectTree) {
-	if resourceSetBinding, err := getResourceSetBindingInCluster(ctx, c, cluster.Namespace, cluster.Name); err == nil {
-		resourceSetGroup := VirtualObject(cluster.Namespace, "ClusterResourceSetGroup", "ClusterResourceSets")
-		tree.Add(cluster, resourceSetGroup)
-
-		for _, binding := range resourceSetBinding.Spec.Bindings {
-			resourceSetRefObject := ObjectReferenceObject(&corev1.ObjectReference{
-				Kind:       "ClusterResourceSet",
-				Namespace:  cluster.Namespace,
-				Name:       binding.ClusterResourceSetName,
-				APIVersion: addonsv1.GroupVersion.String(),
-			})
-			tree.Add(resourceSetGroup, resourceSetRefObject)
-		}
-	}
-}
-
-func addControlPlane(cluster *clusterv1.Cluster, controlPlane *unstructured.Unstructured, tree *ObjectTree, options DiscoverOptions) {
-	tree.Add(cluster, controlPlane, ObjectMetaName("ControlPlane"), GroupingObject(true))
-
-	if options.ShowTemplates {
-		// Add control plane infrastructure ref using spec fields guaranteed in contract
-		infrastructureRef, found, err := unstructured.NestedMap(controlPlane.UnstructuredContent(), "spec", "machineTemplate", "infrastructureRef")
-		if err == nil && found {
-			infrastructureObjectRef := &corev1.ObjectReference{
-				Kind:       infrastructureRef["kind"].(string),
-				Namespace:  infrastructureRef["namespace"].(string),
-				Name:       infrastructureRef["name"].(string),
-				APIVersion: infrastructureRef["apiVersion"].(string),
-			}
-
-			machineTemplateRefObject := ObjectReferenceObject(infrastructureObjectRef)
-			var templateParent client.Object
-			if options.AddTemplateVirtualNode {
-				templateParent = addTemplateVirtualNode(tree, controlPlane, cluster.Namespace)
-			} else {
-				templateParent = controlPlane
-			}
-			tree.Add(templateParent, machineTemplateRefObject, ObjectMetaName("MachineInfrastructureTemplate"))
-		}
-	}
-}
-
-func addMachineDeploymentToObjectTree(ctx context.Context, c client.Client, cluster *clusterv1.Cluster, workers *unstructured.Unstructured, machinesList *clusterv1.MachineList, tree *ObjectTree, options DiscoverOptions, addMachineFunc func(parent client.Object, m *clusterv1.Machine)) error {
 	// Adds worker machines.
 	machinesDeploymentList, err := getMachineDeploymentsInCluster(ctx, c, cluster.Namespace, cluster.Name)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	machineSetList, err := getMachineSetsInCluster(ctx, c, cluster.Namespace, cluster.Name)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	for i := range machinesDeploymentList.Items {
@@ -228,21 +134,6 @@ func addMachineDeploymentToObjectTree(ctx context.Context, c client.Client, clus
 		}
 		tree.Add(workers, md, addOpts...)
 
-		if options.ShowTemplates {
-			var templateParent client.Object
-			if options.AddTemplateVirtualNode {
-				templateParent = addTemplateVirtualNode(tree, md, cluster.Namespace)
-			} else {
-				templateParent = md
-			}
-
-			bootstrapTemplateRefObject := ObjectReferenceObject(md.Spec.Template.Spec.Bootstrap.ConfigRef)
-			tree.Add(templateParent, bootstrapTemplateRefObject, ObjectMetaName("BootstrapConfigTemplate"))
-
-			machineTemplateRefObject := ObjectReferenceObject(&md.Spec.Template.Spec.InfrastructureRef)
-			tree.Add(templateParent, machineTemplateRefObject, ObjectMetaName("MachineInfrastructureTemplate"))
-		}
-
 		machineSets := selectMachinesSetsControlledBy(machineSetList, md)
 		for i := range machineSets {
 			ms := machineSets[i]
@@ -260,42 +151,21 @@ func addMachineDeploymentToObjectTree(ctx context.Context, c client.Client, clus
 		}
 	}
 
-	return nil
-}
-
-func addMachinePoolsToObjectTree(ctx context.Context, c client.Client, namespace string, workers *unstructured.Unstructured, machinePoolList *expv1.MachinePoolList, tree *ObjectTree) {
-	for i := range machinePoolList.Items {
-		mp := &machinePoolList.Items[i]
-		_, visible := tree.Add(workers, mp)
+	// Handles orphan machines.
+	if len(machineMap) < len(machinesList.Items) {
+		other := VirtualObject(cluster.Namespace, "OtherGroup", "Other")
+		tree.Add(workers, other)
 
-		if visible {
-			if machinePoolBootstrap, err := external.Get(ctx, c, mp.Spec.Template.Spec.Bootstrap.ConfigRef, namespace); err == nil {
-				tree.Add(mp, machinePoolBootstrap, ObjectMetaName("BootstrapConfig"), NoEcho(true))
-			}
-
-			if machinePoolInfra, err := external.Get(ctx, c, &mp.Spec.Template.Spec.InfrastructureRef, namespace); err == nil {
-				tree.Add(mp, machinePoolInfra, ObjectMetaName("MachineInfrastructure"), NoEcho(true))
+		for i := range machinesList.Items {
+			m := &machinesList.Items[i]
+			if _, ok := machineMap[m.Name]; ok {
+				continue
 			}
+			addMachineFunc(other, m)
 		}
 	}
-}
-
-func getResourceSetBindingInCluster(ctx context.Context, c client.Client, namespace string, name string) (*addonsv1.ClusterResourceSetBinding, error) {
-	if name == "" {
-		return nil, nil
-	}
-
-	resourceSetBinding := &addonsv1.ClusterResourceSetBinding{}
-	resourceSetBindingKey := client.ObjectKey{Namespace: namespace, Name: name}
-	if err := c.Get(ctx, resourceSetBindingKey, resourceSetBinding); err != nil {
-		return nil, err
-	}
-	resourceSetBinding.TypeMeta = metav1.TypeMeta{
-		Kind:       "ClusterResourceSetBinding",
-		APIVersion: addonsv1.GroupVersion.String(),
-	}
 
-	return resourceSetBinding, nil
+	return tree, nil
 }
 
 func getMachinesInCluster(ctx context.Context, c client.Client, namespace, name string) (*clusterv1.MachineList, error) {
@@ -343,21 +213,6 @@ func getMachineSetsInCluster(ctx context.Context, c client.Client, namespace, na
 	return machineSetList, nil
 }
 
-func getMachinePoolsInCluster(ctx context.Context, c client.Client, namespace, name string) (*expv1.MachinePoolList, error) {
-	if name == "" {
-		return nil, nil
-	}
-
-	machinePoolList := &expv1.MachinePoolList{}
-	labels := map[string]string{clusterv1.ClusterLabelName: name}
-
-	if err := c.List(ctx, machinePoolList, client.InNamespace(namespace), client.MatchingLabels(labels)); err != nil {
-		return nil, err
-	}
-
-	return machinePoolList, nil
-}
-
 func selectControlPlaneMachines(machineList *clusterv1.MachineList) []*clusterv1.Machine {
 	machines := []*clusterv1.Machine{}
 	for i := range machineList.Items {
@@ -390,14 +245,3 @@ func selectMachinesControlledBy(machineList *clusterv1.MachineList, controller c
 	}
 	return machines
 }
-
-func addTemplateVirtualNode(tree *ObjectTree, parent client.Object, namespace string) client.Object {
-	templateNode := VirtualObject(namespace, "TemplateGroup", parent.GetName())
-	addOpts := []AddObjectOption{
-		ZOrder(1),
-		ObjectMetaName("Templates"),
-	}
-	tree.Add(parent, templateNode, addOpts...)
-
-	return templateNode
-}
diff --git a/cmd/clusterctl/client/tree/options.go b/cmd/clusterctl/client/tree/options.go
index cb52a426d..5a93fdd65 100644
--- a/cmd/clusterctl/client/tree/options.go
+++ b/cmd/clusterctl/client/tree/options.go
@@ -25,7 +25,6 @@ type addObjectOptions struct {
 	MetaName       string
 	GroupingObject bool
 	NoEcho         bool
-	ZOrder         int
 }
 
 func (o *addObjectOptions) ApplyOptions(opts []AddObjectOption) *addObjectOptions {
@@ -61,13 +60,3 @@ type NoEcho bool
 func (n NoEcho) ApplyToAdd(options *addObjectOptions) {
 	options.NoEcho = bool(n)
 }
-
-// The ZOrder options defines the sorting of child objects when the tree is printed. Objects are sorted by their z-order
-// from highest to lowest, and then by their name in alphaebetical order if the z-order is the same. Objects with no
-// z-order set are assumed to have a default z-order of 0.
-type ZOrder int
-
-// ApplyToAdd applies the given options.
-func (z ZOrder) ApplyToAdd(options *addObjectOptions) {
-	options.ZOrder = int(z)
-}
diff --git a/cmd/clusterctl/client/tree/tree.go b/cmd/clusterctl/client/tree/tree.go
index 36e037f21..7040c2fa2 100644
--- a/cmd/clusterctl/client/tree/tree.go
+++ b/cmd/clusterctl/client/tree/tree.go
@@ -19,7 +19,6 @@ package tree
 import (
 	"fmt"
 	"sort"
-	"strconv"
 	"strings"
 
 	corev1 "k8s.io/api/core/v1"
@@ -41,15 +40,6 @@ type ObjectTreeOptions struct {
 	// ShowMachineSets instructs the discovery process to include machine sets in the ObjectTree.
 	ShowMachineSets bool
 
-	// ShowClusterResourceSets instructs the discovery process to include cluster resource sets in the ObjectTree.
-	ShowClusterResourceSets bool
-
-	// ShowTemplates instructs the discovery process to include infrastructure and bootstrap config templates in the ObjectTree.
-	ShowTemplates bool
-
-	// AddTemplateVirtualNode instructs the discovery process to group template under a virtual node.
-	AddTemplateVirtualNode bool
-
 	// Echo displays objects if the object's ready condition has the
 	// same Status, Severity and Reason of the parent's object ready condition (it is an echo)
 	Echo bool
@@ -115,20 +105,11 @@ func (od ObjectTree) Add(parent, obj client.Object, opts ...AddObjectOption) (ad
 		addAnnotation(obj, ObjectMetaNameAnnotation, addOpts.MetaName)
 	}
 
-	// Add the ObjectZOrderAnnotation to signal this to the presentation layer.
-	addAnnotation(obj, ObjectZOrderAnnotation, strconv.Itoa(addOpts.ZOrder))
-
 	// If it is requested that this object and its sibling should be grouped in case the ready condition
 	// has the same Status, Severity and Reason, process all the sibling nodes.
 	if IsGroupingObject(parent) {
 		siblings := od.GetObjectsByParent(parent.GetUID())
 
-		// The loop below will process the next node and decide if it belongs in a group. Since objects in the same group
-		// must have the same Kind, we sort by Kind so objects of the same Kind will be together in the list.
-		sort.Slice(siblings, func(i, j int) bool {
-			return siblings[i].GetObjectKind().GroupVersionKind().Kind < siblings[j].GetObjectKind().GroupVersionKind().Kind
-		})
-
 		for i := range siblings {
 			s := siblings[i]
 			sReady := GetReadyCondition(s)
@@ -139,26 +120,16 @@ func (od ObjectTree) Add(parent, obj client.Object, opts ...AddObjectOption) (ad
 				continue
 			}
 
-			// If the sibling node is already a group object
+			// If the sibling node is already a group object, upgrade it with the current object.
 			if IsGroupObject(s) {
-				// Check to see if the group object kind matches the object, i.e. group is MachineGroup and object is Machine.
-				// If so, upgrade it with the current object.
-				if s.GetObjectKind().GroupVersionKind().Kind == obj.GetObjectKind().GroupVersionKind().Kind+"Group" {
-					updateGroupNode(s, sReady, obj, objReady)
-					return true, false
-				}
-			} else if s.GetObjectKind().GroupVersionKind().Kind != obj.GetObjectKind().GroupVersionKind().Kind {
-				// If the sibling is not a group object, check if the sibling and the object are of the same kind. If not, move on.
-				continue
+				updateGroupNode(s, sReady, obj, objReady)
+				return true, false
 			}
 
 			// Otherwise the object and the current sibling should be merged in a group.
 
 			// Create virtual object for the group and add it to the object tree.
 			groupNode := createGroupNode(s, sReady, obj, objReady)
-			// By default, grouping objects should be sorted last.
-			addAnnotation(groupNode, ObjectZOrderAnnotation, strconv.Itoa(GetZOrder(obj)))
-
 			od.addInner(parent, groupNode)
 
 			// Remove the current sibling (now merged in the group).
diff --git a/cmd/clusterctl/client/tree/util.go b/cmd/clusterctl/client/tree/util.go
index eba344044..4f83ed2b6 100644
--- a/cmd/clusterctl/client/tree/util.go
+++ b/cmd/clusterctl/client/tree/util.go
@@ -20,7 +20,6 @@ import (
 	"fmt"
 	"sort"
 
-	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
@@ -90,7 +89,7 @@ func objToSetter(obj client.Object) conditions.Setter {
 	return setter
 }
 
-// VirtualObject returns a new virtual object.
+// VirtualObject return a new virtual object.
 func VirtualObject(namespace, kind, name string) *unstructured.Unstructured {
 	gk := "virtual.cluster.x-k8s.io/v1beta1"
 	return &unstructured.Unstructured{
@@ -103,22 +102,7 @@ func VirtualObject(namespace, kind, name string) *unstructured.Unstructured {
 				"annotations": map[string]interface{}{
 					VirtualObjectAnnotation: "True",
 				},
-				"uid": fmt.Sprintf("%s, Kind=%s, %s/%s", gk, kind, namespace, name),
-			},
-		},
-	}
-}
-
-// ObjectReferenceObject returns a new object referenced by the objectRef.
-func ObjectReferenceObject(objectRef *corev1.ObjectReference) *unstructured.Unstructured {
-	return &unstructured.Unstructured{
-		Object: map[string]interface{}{
-			"apiVersion": objectRef.APIVersion,
-			"kind":       objectRef.Kind,
-			"metadata": map[string]interface{}{
-				"namespace": objectRef.Namespace,
-				"name":      objectRef.Name,
-				"uid":       fmt.Sprintf("%s, Kind=%s, %s/%s", objectRef.APIVersion, objectRef.Kind, objectRef.Namespace, objectRef.Name),
+				"uid": fmt.Sprintf("%s, %s/%s", gk, namespace, name),
 			},
 		},
 	}
diff --git a/cmd/clusterctl/cmd/describe_cluster.go b/cmd/clusterctl/cmd/describe_cluster.go
index 71a81c661..888a8219e 100644
--- a/cmd/clusterctl/cmd/describe_cluster.go
+++ b/cmd/clusterctl/cmd/describe_cluster.go
@@ -52,17 +52,15 @@ var (
 )
 
 type describeClusterOptions struct {
-	kubeconfig              string
-	kubeconfigContext       string
-	namespace               string
-	showOtherConditions     string
-	showMachineSets         bool
-	showClusterResourceSets bool
-	showTemplates           bool
-	echo                    bool
-	disableNoEcho           bool
-	grouping                bool
-	disableGrouping         bool
+	kubeconfig          string
+	kubeconfigContext   string
+	namespace           string
+	showOtherConditions string
+	showMachineSets     bool
+	echo                bool
+	disableNoEcho       bool
+	grouping            bool
+	disableGrouping     bool
 }
 
 var dc = &describeClusterOptions{}
@@ -111,10 +109,6 @@ func init() {
 		"list of comma separated kind or kind/name for which the command should show all the object's conditions (use 'all' to show conditions for everything).")
 	describeClusterClusterCmd.Flags().BoolVar(&dc.showMachineSets, "show-machinesets", false,
 		"Show MachineSet objects.")
-	describeClusterClusterCmd.Flags().BoolVar(&dc.showClusterResourceSets, "show-resourcesets", false,
-		"Show cluster resource sets.")
-	describeClusterClusterCmd.Flags().BoolVar(&dc.showTemplates, "show-templates", false,
-		"Show infrastructure and bootstrap config templates associated with the cluster.")
 
 	describeClusterClusterCmd.Flags().BoolVar(&dc.echo, "echo", false, ""+
 		"Show MachineInfrastructure and BootstrapConfig when ready condition is true or it has the Status, Severity and Reason of the machine's object.")
@@ -148,16 +142,13 @@ func runDescribeCluster(name string) error {
 	}
 
 	tree, err := c.DescribeCluster(client.DescribeClusterOptions{
-		Kubeconfig:              client.Kubeconfig{Path: dc.kubeconfig, Context: dc.kubeconfigContext},
-		Namespace:               dc.namespace,
-		ClusterName:             name,
-		ShowOtherConditions:     dc.showOtherConditions,
-		ShowClusterResourceSets: dc.showClusterResourceSets,
-		ShowTemplates:           dc.showTemplates,
-		ShowMachineSets:         dc.showMachineSets,
-		AddTemplateVirtualNode:  true,
-		Echo:                    dc.echo || dc.disableNoEcho,
-		Grouping:                dc.grouping && !dc.disableGrouping,
+		Kubeconfig:          client.Kubeconfig{Path: dc.kubeconfig, Context: dc.kubeconfigContext},
+		Namespace:           dc.namespace,
+		ClusterName:         name,
+		ShowOtherConditions: dc.showOtherConditions,
+		ShowMachineSets:     dc.showMachineSets,
+		Echo:                dc.echo || dc.disableNoEcho,
+		Grouping:            dc.grouping && !dc.disableGrouping,
 	})
 	if err != nil {
 		return err
@@ -221,19 +212,11 @@ func addObjectRow(prefix string, tbl *uitable.Table, objectTree *tree.ObjectTree
 	}
 
 	// Add a row for each object's children, taking care of updating the tree view prefix.
+	// NOTE: Children objects are sorted by row name for better readability.
 	childrenObj := objectTree.GetObjectsByParent(obj.GetUID())
-
-	// printBefore returns true if children[i] should be printed before children[j]. Objects are sorted by z-order and
-	// row name such that objects with higher z-order are printed first, and objects with the same z-order are
-	// printed in alphabetical order.
-	printBefore := func(i, j int) bool {
-		if tree.GetZOrder(childrenObj[i]) == tree.GetZOrder(childrenObj[j]) {
-			return getRowName(childrenObj[i]) < getRowName(childrenObj[j])
-		}
-
-		return tree.GetZOrder(childrenObj[i]) > tree.GetZOrder(childrenObj[j])
-	}
-	sort.Slice(childrenObj, printBefore)
+	sort.Slice(childrenObj, func(i, j int) bool {
+		return getRowName(childrenObj[i]) < getRowName(childrenObj[j])
+	})
 
 	for i, child := range childrenObj {
 		addObjectRow(getChildPrefix(prefix, i, len(childrenObj)), tbl, objectTree, child)
@@ -287,7 +270,7 @@ func getChildPrefix(currentPrefix string, childIndex, childCount int) string {
 
 // getRowName returns the object name in the tree view according to following rules:
 // - group objects are represented as #of objects kind, e.g. 3 Machines...
-// - other virtual objects are represented using the object name, e.g. Workers, or meta name if provided.
+// - other virtual objects are represented using the object name, e.g. Workers
 // - objects with a meta name are represented as meta name - (kind/name), e.g. ClusterInfrastructure - DockerCluster/test1
 // - other objects are represented as kind/name, e.g.Machine/test1-md-0-779b87ff56-642vs
 // - if the object is being deleted, a prefix will be added.
@@ -299,9 +282,6 @@ func getRowName(obj ctrlclient.Object) string {
 	}
 
 	if tree.IsVirtualObject(obj) {
-		if metaName := tree.GetMetaName(obj); metaName != "" {
-			return metaName
-		}
 		return obj.GetName()
 	}
 
diff --git a/cmd/clusterctl/internal/scheme/scheme.go b/cmd/clusterctl/internal/scheme/scheme.go
index 531a3755f..4ecaec819 100644
--- a/cmd/clusterctl/internal/scheme/scheme.go
+++ b/cmd/clusterctl/internal/scheme/scheme.go
@@ -28,7 +28,6 @@ import (
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
 	clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
 	addonsv1 "sigs.k8s.io/cluster-api/exp/addons/api/v1beta1"
-	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
 )
 
 var (
@@ -45,5 +44,4 @@ func init() {
 	_ = admissionregistration.AddToScheme(Scheme)
 	_ = admissionregistrationv1beta1.AddToScheme(Scheme)
 	_ = addonsv1.AddToScheme(Scheme)
-	_ = expv1.AddToScheme(Scheme)
 }
diff --git a/controlplane/kubeadm/api/v1alpha3/conversion.go b/controlplane/kubeadm/api/v1alpha3/conversion.go
index 113e7a064..41047ace4 100644
--- a/controlplane/kubeadm/api/v1alpha3/conversion.go
+++ b/controlplane/kubeadm/api/v1alpha3/conversion.go
@@ -40,17 +40,8 @@ func (src *KubeadmControlPlane) ConvertTo(dstRaw conversion.Hub) error {
 
 	dst.Spec.MachineTemplate.ObjectMeta = restored.Spec.MachineTemplate.ObjectMeta
 	dst.Spec.MachineTemplate.NodeDeletionTimeout = restored.Spec.MachineTemplate.NodeDeletionTimeout
-	dst.Spec.KubeadmConfigSpec.Users = restored.Spec.KubeadmConfigSpec.Users
 	dst.Status.Version = restored.Status.Version
 
-	if restored.Spec.KubeadmConfigSpec.Users != nil {
-		for i := range restored.Spec.KubeadmConfigSpec.Users {
-			if restored.Spec.KubeadmConfigSpec.Users[i].PasswdFrom != nil {
-				dst.Spec.KubeadmConfigSpec.Users[i].PasswdFrom = restored.Spec.KubeadmConfigSpec.Users[i].PasswdFrom
-			}
-		}
-	}
-
 	if restored.Spec.KubeadmConfigSpec.JoinConfiguration != nil && restored.Spec.KubeadmConfigSpec.JoinConfiguration.NodeRegistration.IgnorePreflightErrors != nil {
 		if dst.Spec.KubeadmConfigSpec.JoinConfiguration == nil {
 			dst.Spec.KubeadmConfigSpec.JoinConfiguration = &bootstrapv1.JoinConfiguration{}
diff --git a/controlplane/kubeadm/api/v1alpha4/conversion.go b/controlplane/kubeadm/api/v1alpha4/conversion.go
index 664d1b4cc..f54388b1d 100644
--- a/controlplane/kubeadm/api/v1alpha4/conversion.go
+++ b/controlplane/kubeadm/api/v1alpha4/conversion.go
@@ -39,15 +39,6 @@ func (src *KubeadmControlPlane) ConvertTo(dstRaw conversion.Hub) error {
 		return err
 	}
 
-	dst.Spec.KubeadmConfigSpec.Users = restored.Spec.KubeadmConfigSpec.Users
-	if restored.Spec.KubeadmConfigSpec.Users != nil {
-		for i := range restored.Spec.KubeadmConfigSpec.Users {
-			if restored.Spec.KubeadmConfigSpec.Users[i].PasswdFrom != nil {
-				dst.Spec.KubeadmConfigSpec.Users[i].PasswdFrom = restored.Spec.KubeadmConfigSpec.Users[i].PasswdFrom
-			}
-		}
-	}
-
 	dst.Spec.KubeadmConfigSpec.Ignition = restored.Spec.KubeadmConfigSpec.Ignition
 	if restored.Spec.KubeadmConfigSpec.InitConfiguration != nil {
 		if dst.Spec.KubeadmConfigSpec.InitConfiguration == nil {
@@ -105,18 +96,8 @@ func (src *KubeadmControlPlaneTemplate) ConvertTo(dstRaw conversion.Hub) error {
 		return err
 	}
 
-	dst.Spec.Template.Spec.KubeadmConfigSpec.Users = restored.Spec.Template.Spec.KubeadmConfigSpec.Users
 	dst.Spec.Template.Spec.KubeadmConfigSpec.Ignition = restored.Spec.Template.Spec.KubeadmConfigSpec.Ignition
 	dst.Spec.Template.Spec.MachineTemplate = restored.Spec.Template.Spec.MachineTemplate
-
-	if restored.Spec.Template.Spec.KubeadmConfigSpec.Users != nil {
-		for i := range restored.Spec.Template.Spec.KubeadmConfigSpec.Users {
-			if restored.Spec.Template.Spec.KubeadmConfigSpec.Users[i].PasswdFrom != nil {
-				dst.Spec.Template.Spec.KubeadmConfigSpec.Users[i].PasswdFrom = restored.Spec.Template.Spec.KubeadmConfigSpec.Users[i].PasswdFrom
-			}
-		}
-	}
-
 	if restored.Spec.Template.Spec.KubeadmConfigSpec.InitConfiguration != nil {
 		if dst.Spec.Template.Spec.KubeadmConfigSpec.InitConfiguration == nil {
 			dst.Spec.Template.Spec.KubeadmConfigSpec.InitConfiguration = &bootstrapv1.InitConfiguration{}
diff --git a/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanes.yaml b/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanes.yaml
index 5211771ab..3dcbf7a79 100644
--- a/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanes.yaml
+++ b/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanes.yaml
@@ -3442,29 +3442,6 @@ spec:
                           description: Passwd specifies a hashed password for the
                             user
                           type: string
-                        passwdFrom:
-                          description: PasswdFrom is a referenced source of passwd
-                            to populate the passwd.
-                          properties:
-                            secret:
-                              description: Secret represents a secret that should
-                                populate this password.
-                              properties:
-                                key:
-                                  description: Key is the key in the secret's data
-                                    map for this value.
-                                  type: string
-                                name:
-                                  description: Name of the secret in the KubeadmBootstrapConfig's
-                                    namespace to use.
-                                  type: string
-                              required:
-                              - key
-                              - name
-                              type: object
-                          required:
-                          - secret
-                          type: object
                         primaryGroup:
                           description: PrimaryGroup specifies the primary group for
                             the user
diff --git a/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanetemplates.yaml b/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanetemplates.yaml
index 1c12482c7..ae33ad81f 100644
--- a/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanetemplates.yaml
+++ b/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanetemplates.yaml
@@ -2259,29 +2259,6 @@ spec:
                                   description: Passwd specifies a hashed password
                                     for the user
                                   type: string
-                                passwdFrom:
-                                  description: PasswdFrom is a referenced source of
-                                    passwd to populate the passwd.
-                                  properties:
-                                    secret:
-                                      description: Secret represents a secret that
-                                        should populate this password.
-                                      properties:
-                                        key:
-                                          description: Key is the key in the secret's
-                                            data map for this value.
-                                          type: string
-                                        name:
-                                          description: Name of the secret in the KubeadmBootstrapConfig's
-                                            namespace to use.
-                                          type: string
-                                      required:
-                                      - key
-                                      - name
-                                      type: object
-                                  required:
-                                  - secret
-                                  type: object
                                 primaryGroup:
                                   description: PrimaryGroup specifies the primary
                                     group for the user
diff --git a/docs/book/src/developer/architecture/controllers/control-plane.md b/docs/book/src/developer/architecture/controllers/control-plane.md
index ffc70ccb8..b6b941f28 100644
--- a/docs/book/src/developer/architecture/controllers/control-plane.md
+++ b/docs/book/src/developer/architecture/controllers/control-plane.md
@@ -229,19 +229,21 @@ The `status` object **may** define several fields:
 
 ## Example usage
 
-``` yaml
+```yaml
+apiVersion: controlplane.cluster.x-k8s.io/v1beta1
 kind: KubeadmControlPlane
-apiVersion: cluster.x-k8s.io/v1alpha3
 metadata:
   name: kcp-1
   namespace: default
 spec:
-  infrastructureTemplate:
-    name: kcp-infra-template
-    namespace: default
-  kubeadmConfigSpec:
-    clusterConfiguration:
-  version: v1.16.2
+  machineTemplate:
+    infrastructureRef:
+      apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+      kind: DockerMachineTemplate
+      name: docker-machine-template-1
+      namespace: default
+  replicas: 3
+  version: v1.21.2
 ```
 
 [scale]: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#scale-subresource
diff --git a/exp/api/v1beta1/machinepool_webhook.go b/exp/api/v1beta1/machinepool_webhook.go
index 717a42dfe..1532bcaf4 100644
--- a/exp/api/v1beta1/machinepool_webhook.go
+++ b/exp/api/v1beta1/machinepool_webhook.go
@@ -96,10 +96,9 @@ func (m *MachinePool) ValidateDelete() error {
 func (m *MachinePool) validate(old *MachinePool) error {
 	// NOTE: MachinePool is behind MachinePool feature gate flag; the web hook
 	// must prevent creating new objects new case the feature flag is disabled.
-	specPath := field.NewPath("spec")
 	if !feature.Gates.Enabled(feature.MachinePool) {
 		return field.Forbidden(
-			specPath,
+			field.NewPath("spec"),
 			"can be set only if the MachinePool feature flag is enabled",
 		)
 	}
@@ -108,7 +107,7 @@ func (m *MachinePool) validate(old *MachinePool) error {
 		allErrs = append(
 			allErrs,
 			field.Required(
-				specPath.Child("template", "spec", "bootstrap", "data"),
+				field.NewPath("spec", "template", "spec", "bootstrap", "data"),
 				"expected either spec.bootstrap.dataSecretName or spec.bootstrap.configRef to be populated",
 			),
 		)
@@ -118,7 +117,7 @@ func (m *MachinePool) validate(old *MachinePool) error {
 		allErrs = append(
 			allErrs,
 			field.Invalid(
-				specPath.Child("template", "spec", "bootstrap", "configRef", "namespace"),
+				field.NewPath("spec", "template", "spec", "bootstrap", "configRef", "namespace"),
 				m.Spec.Template.Spec.Bootstrap.ConfigRef.Namespace,
 				"must match metadata.namespace",
 			),
@@ -129,7 +128,7 @@ func (m *MachinePool) validate(old *MachinePool) error {
 		allErrs = append(
 			allErrs,
 			field.Invalid(
-				specPath.Child("infrastructureRef", "namespace"),
+				field.NewPath("spec", "infrastructureRef", "namespace"),
 				m.Spec.Template.Spec.InfrastructureRef.Namespace,
 				"must match metadata.namespace",
 			),
@@ -139,15 +138,13 @@ func (m *MachinePool) validate(old *MachinePool) error {
 	if old != nil && old.Spec.ClusterName != m.Spec.ClusterName {
 		allErrs = append(
 			allErrs,
-			field.Forbidden(
-				specPath.Child("clusterName"),
-				"field is immutable"),
+			field.Invalid(field.NewPath("spec", "clusterName"), m.Spec.ClusterName, "field is immutable"),
 		)
 	}
 
 	if m.Spec.Template.Spec.Version != nil {
 		if !version.KubeSemver.MatchString(*m.Spec.Template.Spec.Version) {
-			allErrs = append(allErrs, field.Invalid(specPath.Child("template", "spec", "version"), *m.Spec.Template.Spec.Version, "must be a valid semantic version"))
+			allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "template", "spec", "version"), *m.Spec.Template.Spec.Version, "must be a valid semantic version"))
 		}
 	}
 
diff --git a/go.mod b/go.mod
index d578d3768..8528e6421 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ require (
 	github.com/MakeNowJust/heredoc v1.0.0
 	github.com/Masterminds/sprig/v3 v3.2.2
 	github.com/blang/semver v3.5.1+incompatible
-	github.com/coredns/corefile-migration v1.0.15
+	github.com/coredns/corefile-migration v1.0.14
 	github.com/davecgh/go-spew v1.1.1
 	github.com/docker/distribution v2.8.0+incompatible
 	github.com/drone/envsubst/v2 v2.0.0-20210730161058-179042472c46
diff --git a/go.sum b/go.sum
index e4458333d..506456660 100644
--- a/go.sum
+++ b/go.sum
@@ -149,8 +149,8 @@ github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoC
 github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
 github.com/coredns/caddy v1.1.0 h1:ezvsPrT/tA/7pYDBZxu0cT0VmWk75AfIaf6GSYCNMf0=
 github.com/coredns/caddy v1.1.0/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=
-github.com/coredns/corefile-migration v1.0.15 h1:hrJMRDGAzkzps+Jk6CKdCt9x7wyuEYJeoIXEUo12p+E=
-github.com/coredns/corefile-migration v1.0.15/go.mod h1:XnhgULOEouimnzgn0t4WPuFDN2/PJQcTxdWKC5eXNGE=
+github.com/coredns/corefile-migration v1.0.14 h1:Tz3WZhoj2NdP8drrQH86NgnCng+VrPjNeg2Oe1ALKag=
+github.com/coredns/corefile-migration v1.0.14/go.mod h1:XnhgULOEouimnzgn0t4WPuFDN2/PJQcTxdWKC5eXNGE=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
diff --git a/hack/tools/go.sum b/hack/tools/go.sum
index a09aa5d7e..ca143d480 100644
--- a/hack/tools/go.sum
+++ b/hack/tools/go.sum
@@ -281,8 +281,8 @@ github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgU
 github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY=
 github.com/coredns/caddy v1.1.0 h1:ezvsPrT/tA/7pYDBZxu0cT0VmWk75AfIaf6GSYCNMf0=
 github.com/coredns/caddy v1.1.0/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=
-github.com/coredns/corefile-migration v1.0.15 h1:hrJMRDGAzkzps+Jk6CKdCt9x7wyuEYJeoIXEUo12p+E=
-github.com/coredns/corefile-migration v1.0.15/go.mod h1:XnhgULOEouimnzgn0t4WPuFDN2/PJQcTxdWKC5eXNGE=
+github.com/coredns/corefile-migration v1.0.14 h1:Tz3WZhoj2NdP8drrQH86NgnCng+VrPjNeg2Oe1ALKag=
+github.com/coredns/corefile-migration v1.0.14/go.mod h1:XnhgULOEouimnzgn0t4WPuFDN2/PJQcTxdWKC5eXNGE=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=


Test patch:
diff --git a/bootstrap/kubeadm/api/v1beta1/kubeadmconfig_webhook_test.go b/bootstrap/kubeadm/api/v1beta1/kubeadmconfig_webhook_test.go
index fb842d45f..e8ab19f45 100644
--- a/bootstrap/kubeadm/api/v1beta1/kubeadmconfig_webhook_test.go
+++ b/bootstrap/kubeadm/api/v1beta1/kubeadmconfig_webhook_test.go
@@ -178,100 +178,6 @@ func TestKubeadmConfigValidate(t *testing.T) {
 			},
 			expectErr: true,
 		},
-		"valid passwd": {
-			in: &KubeadmConfig{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "baz",
-					Namespace: metav1.NamespaceDefault,
-				},
-				Spec: KubeadmConfigSpec{
-					Users: []User{
-						{
-							Passwd: pointer.StringPtr("foo"),
-						},
-					},
-				},
-			},
-		},
-		"valid passwdFrom": {
-			in: &KubeadmConfig{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "baz",
-					Namespace: metav1.NamespaceDefault,
-				},
-				Spec: KubeadmConfigSpec{
-					Users: []User{
-						{
-							PasswdFrom: &PasswdSource{
-								Secret: SecretPasswdSource{
-									Name: "foo",
-									Key:  "bar",
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-		"invalid passwd and passwdFrom": {
-			in: &KubeadmConfig{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "baz",
-					Namespace: metav1.NamespaceDefault,
-				},
-				Spec: KubeadmConfigSpec{
-					Users: []User{
-						{
-							PasswdFrom: &PasswdSource{},
-							Passwd:     pointer.StringPtr("foo"),
-						},
-					},
-				},
-			},
-			expectErr: true,
-		},
-		"invalid passwdFrom without name": {
-			in: &KubeadmConfig{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "baz",
-					Namespace: metav1.NamespaceDefault,
-				},
-				Spec: KubeadmConfigSpec{
-					Users: []User{
-						{
-							PasswdFrom: &PasswdSource{
-								Secret: SecretPasswdSource{
-									Key: "bar",
-								},
-							},
-							Passwd: pointer.StringPtr("foo"),
-						},
-					},
-				},
-			},
-			expectErr: true,
-		},
-		"invalid passwdFrom without key": {
-			in: &KubeadmConfig{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "baz",
-					Namespace: metav1.NamespaceDefault,
-				},
-				Spec: KubeadmConfigSpec{
-					Users: []User{
-						{
-							PasswdFrom: &PasswdSource{
-								Secret: SecretPasswdSource{
-									Name: "foo",
-								},
-							},
-							Passwd: pointer.StringPtr("foo"),
-						},
-					},
-				},
-			},
-			expectErr: true,
-		},
 		"Ignition field is set, format is not Ignition": {
 			enableIgnitionFeature: true,
 			in: &KubeadmConfig{
diff --git a/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go b/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go
index cbc17189d..7d9109d93 100644
--- a/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go
+++ b/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go
@@ -1861,132 +1861,6 @@ func TestKubeadmConfigReconciler_ResolveFiles(t *testing.T) {
 	}
 }
 
-func TestKubeadmConfigReconciler_ResolveUsers(t *testing.T) {
-	fakePasswd := "bar"
-	testSecret := &corev1.Secret{
-		ObjectMeta: metav1.ObjectMeta{
-			Name: "source",
-		},
-		Data: map[string][]byte{
-			"key": []byte(fakePasswd),
-		},
-	}
-
-	cases := map[string]struct {
-		cfg     *bootstrapv1.KubeadmConfig
-		objects []client.Object
-		expect  []bootstrapv1.User
-	}{
-		"password should pass through": {
-			cfg: &bootstrapv1.KubeadmConfig{
-				Spec: bootstrapv1.KubeadmConfigSpec{
-					Users: []bootstrapv1.User{
-						{
-							Name:   "foo",
-							Passwd: &fakePasswd,
-						},
-					},
-				},
-			},
-			expect: []bootstrapv1.User{
-				{
-					Name:   "foo",
-					Passwd: &fakePasswd,
-				},
-			},
-		},
-		"passwdFrom should convert correctly": {
-			cfg: &bootstrapv1.KubeadmConfig{
-				Spec: bootstrapv1.KubeadmConfigSpec{
-					Users: []bootstrapv1.User{
-						{
-							Name: "foo",
-							PasswdFrom: &bootstrapv1.PasswdSource{
-								Secret: bootstrapv1.SecretPasswdSource{
-									Name: "source",
-									Key:  "key",
-								},
-							},
-						},
-					},
-				},
-			},
-			expect: []bootstrapv1.User{
-				{
-					Name:   "foo",
-					Passwd: &fakePasswd,
-				},
-			},
-			objects: []client.Object{testSecret},
-		},
-		"multiple users should work correctly": {
-			cfg: &bootstrapv1.KubeadmConfig{
-				Spec: bootstrapv1.KubeadmConfigSpec{
-					Users: []bootstrapv1.User{
-						{
-							Name:   "foo",
-							Passwd: &fakePasswd,
-						},
-						{
-							Name: "bar",
-							PasswdFrom: &bootstrapv1.PasswdSource{
-								Secret: bootstrapv1.SecretPasswdSource{
-									Name: "source",
-									Key:  "key",
-								},
-							},
-						},
-					},
-				},
-			},
-			expect: []bootstrapv1.User{
-				{
-					Name:   "foo",
-					Passwd: &fakePasswd,
-				},
-				{
-					Name:   "bar",
-					Passwd: &fakePasswd,
-				},
-			},
-			objects: []client.Object{testSecret},
-		},
-	}
-
-	for name, tc := range cases {
-		t.Run(name, func(t *testing.T) {
-			g := NewWithT(t)
-
-			myclient := fake.NewClientBuilder().WithObjects(tc.objects...).Build()
-			k := &KubeadmConfigReconciler{
-				Client:          myclient,
-				KubeadmInitLock: &myInitLocker{},
-			}
-
-			// make a list of password we expect to be sourced from secrets
-			// after we resolve users, assert that the original spec has
-			// not been mutated and all password we expected to be sourced
-			// from secret still are.
-			passwdFrom := map[string]bool{}
-			for _, user := range tc.cfg.Spec.Users {
-				if user.PasswdFrom != nil {
-					passwdFrom[user.Name] = true
-				}
-			}
-
-			users, err := k.resolveUsers(ctx, tc.cfg)
-			g.Expect(err).NotTo(HaveOccurred())
-			g.Expect(users).To(Equal(tc.expect))
-			for _, user := range tc.cfg.Spec.Users {
-				if passwdFrom[user.Name] {
-					g.Expect(user.PasswdFrom).NotTo(BeNil())
-					g.Expect(user.Passwd).To(BeNil())
-				}
-			}
-		})
-	}
-}
-
 // test utils.
 
 // newWorkerMachineForCluster returns a Machine with the passed Cluster's information and a pre-configured name.
diff --git a/cmd/clusterctl/client/tree/discovery_test.go b/cmd/clusterctl/client/tree/discovery_test.go
index 7509aec38..6cc5d2508 100644
--- a/cmd/clusterctl/client/tree/discovery_test.go
+++ b/cmd/clusterctl/client/tree/discovery_test.go
@@ -25,43 +25,9 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
-	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
 	"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test"
 )
 
-func clusterObjectsWithResourceSet() []client.Object {
-	namespace := "ns1"
-	clusterObjs := test.NewFakeCluster(namespace, "cluster1").
-		WithControlPlane(
-			test.NewFakeControlPlane("cp").
-				WithMachines(
-					test.NewFakeMachine("cp1"),
-				),
-		).
-		WithMachineDeployments(
-			test.NewFakeMachineDeployment("md1").
-				WithMachineSets(
-					test.NewFakeMachineSet("ms1").
-						WithMachines(
-							test.NewFakeMachine("m1"),
-							test.NewFakeMachine("m2"),
-						),
-				),
-		).
-		Objs()
-
-	var cluster *clusterv1.Cluster
-	for _, obj := range clusterObjs {
-		if obj.GetObjectKind().GroupVersionKind().Kind == "Cluster" {
-			cluster = obj.(*clusterv1.Cluster)
-		}
-	}
-	resourceSet := test.NewFakeClusterResourceSet(namespace, "crs1")
-	resourceSetObjs := resourceSet.ApplyToCluster(cluster).Objs()
-
-	return append(clusterObjs, resourceSetObjs...)
-}
-
 func Test_Discovery(t *testing.T) {
 	type nodeCheck func(*WithT, client.Object)
 	type args struct {
@@ -104,7 +70,7 @@ func Test_Discovery(t *testing.T) {
 				"cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": {
 					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1",
 					"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp",
-					"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers",
+					"virtual.cluster.x-k8s.io/v1beta1, ns1/Workers",
 				},
 				// InfrastructureCluster should be leaf
 				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": {},
@@ -115,12 +81,12 @@ func Test_Discovery(t *testing.T) {
 				// Machine should be leaf (no echo)
 				"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1": {},
 				// Workers should have a machine deployment
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": {
+				"virtual.cluster.x-k8s.io/v1beta1, ns1/Workers": {
 					"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1",
 				},
 				// Machine deployment should have a group of machines (grouping)
 				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": {
-					"virtual.cluster.x-k8s.io/v1beta1, Kind=MachineGroup, ns1/zzz_",
+					"virtual.cluster.x-k8s.io/v1beta1, ns1/zzz_",
 				},
 			},
 			wantNodeCheck: map[string]nodeCheck{
@@ -134,7 +100,7 @@ func Test_Discovery(t *testing.T) {
 					g.Expect(IsGroupingObject(obj)).To(BeTrue())
 				},
 				// Workers should be a virtual node
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": func(g *WithT, obj client.Object) {
+				"virtual.cluster.x-k8s.io/v1beta1, ns1/Workers": func(g *WithT, obj client.Object) {
 					g.Expect(IsVirtualObject(obj)).To(BeTrue())
 				},
 				// Machine deployment should be a grouping object
@@ -173,7 +139,7 @@ func Test_Discovery(t *testing.T) {
 				"cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": {
 					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1",
 					"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp",
-					"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers",
+					"virtual.cluster.x-k8s.io/v1beta1, ns1/Workers",
 				},
 				// InfrastructureCluster should be leaf
 				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": {},
@@ -182,7 +148,7 @@ func Test_Discovery(t *testing.T) {
 					"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1",
 				},
 				// Workers should have a machine deployment
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": {
+				"virtual.cluster.x-k8s.io/v1beta1, ns1/Workers": {
 					"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1",
 				},
 				// Machine deployment should have a group of machines
@@ -206,7 +172,7 @@ func Test_Discovery(t *testing.T) {
 					g.Expect(IsGroupingObject(obj)).To(BeFalse())
 				},
 				// Workers should be a virtual node
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": func(g *WithT, obj client.Object) {
+				"virtual.cluster.x-k8s.io/v1beta1, ns1/Workers": func(g *WithT, obj client.Object) {
 					g.Expect(IsVirtualObject(obj)).To(BeTrue())
 				},
 				// Machine deployment should NOT be a grouping object
@@ -245,7 +211,7 @@ func Test_Discovery(t *testing.T) {
 				"cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": {
 					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1",
 					"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp",
-					"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers",
+					"virtual.cluster.x-k8s.io/v1beta1, ns1/Workers",
 				},
 				// InfrastructureCluster should be leaf
 				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": {},
@@ -259,7 +225,7 @@ func Test_Discovery(t *testing.T) {
 					"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfig, ns1/cp1",
 				},
 				// Workers should have a machine deployment
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": {
+				"virtual.cluster.x-k8s.io/v1beta1, ns1/Workers": {
 					"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1",
 				},
 				// Machine deployment should have a group of machines
@@ -283,7 +249,7 @@ func Test_Discovery(t *testing.T) {
 					g.Expect(IsGroupingObject(obj)).To(BeFalse())
 				},
 				// Workers should be a virtual node
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": func(g *WithT, obj client.Object) {
+				"virtual.cluster.x-k8s.io/v1beta1, ns1/Workers": func(g *WithT, obj client.Object) {
 					g.Expect(IsVirtualObject(obj)).To(BeTrue())
 				},
 				// Machine deployment should NOT be a grouping object
@@ -305,434 +271,6 @@ func Test_Discovery(t *testing.T) {
 				},
 			},
 		},
-		{
-			name: "Discovery with cluster resource sets shown",
-			args: args{
-				discoverOptions: DiscoverOptions{
-					Grouping:                true,
-					ShowClusterResourceSets: true,
-				},
-				objs: clusterObjectsWithResourceSet(),
-			},
-			wantTree: map[string][]string{
-				// Cluster should be parent of InfrastructureCluster, ControlPlane, WorkerGroup, and ClusterResourceSetGroup
-				"cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": {
-					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1",
-					"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp",
-					"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers",
-					"virtual.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSetGroup, ns1/ClusterResourceSets",
-				},
-				// InfrastructureCluster should be leaf
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": {},
-				// ControlPlane should have a machine
-				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": {
-					"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1",
-				},
-				// Machine should be leaf (no echo)
-				"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1": {},
-				// Workers should have a machine deployment
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": {
-					"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1",
-				},
-				// Machine deployment should have a group of machines (grouping)
-				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": {
-					"virtual.cluster.x-k8s.io/v1beta1, Kind=MachineGroup, ns1/zzz_",
-				},
-				// ClusterResourceSetGroup should have a ClusterResourceSet
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSetGroup, ns1/ClusterResourceSets": {
-					"addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSet, ns1/crs1",
-				},
-				// ClusterResourceSet should be a leaf
-				"addons.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSet, ns1/crs1": {},
-			},
-			wantNodeCheck: map[string]nodeCheck{
-				// InfrastructureCluster should have a meta name
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": func(g *WithT, obj client.Object) {
-					g.Expect(GetMetaName(obj)).To(Equal("ClusterInfrastructure"))
-				},
-				// ControlPlane should have a meta name, be a grouping object
-				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": func(g *WithT, obj client.Object) {
-					g.Expect(GetMetaName(obj)).To(Equal("ControlPlane"))
-					g.Expect(IsGroupingObject(obj)).To(BeTrue())
-				},
-				// Workers should be a virtual node
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": func(g *WithT, obj client.Object) {
-					g.Expect(IsVirtualObject(obj)).To(BeTrue())
-				},
-				// ClusterResourceSetGroup should be a virtual node
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=ClusterResourceSetGroup, ns1/ClusterResourceSets": func(g *WithT, obj client.Object) {
-					g.Expect(IsVirtualObject(obj)).To(BeTrue())
-				},
-				// Machine deployment should be a grouping object
-				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": func(g *WithT, obj client.Object) {
-					g.Expect(IsGroupingObject(obj)).To(BeTrue())
-				},
-			},
-		},
-		{
-			name: "Discovery with templates shown with template virtual nodes",
-			args: args{
-				discoverOptions: DiscoverOptions{
-					Grouping:               true,
-					ShowTemplates:          true,
-					AddTemplateVirtualNode: true,
-				},
-				objs: test.NewFakeCluster("ns1", "cluster1").
-					WithControlPlane(
-						test.NewFakeControlPlane("cp").
-							WithMachines(
-								test.NewFakeMachine("cp1"),
-							),
-					).
-					WithMachineDeployments(
-						test.NewFakeMachineDeployment("md1").
-							WithMachineSets(
-								test.NewFakeMachineSet("ms1").
-									WithMachines(
-										test.NewFakeMachine("m1"),
-										test.NewFakeMachine("m2"),
-									),
-							).
-							WithInfrastructureTemplate(
-								test.NewFakeInfrastructureTemplate("md1"),
-							),
-					).
-					Objs(),
-			},
-			wantTree: map[string][]string{
-				// Cluster should be parent of InfrastructureCluster, ControlPlane, WorkerGroup, and ClusterResourceSetGroup
-				"cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": {
-					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1",
-					"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp",
-					"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers",
-				},
-				// InfrastructureCluster should be leaf
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": {},
-				// ControlPlane should have a machine and template group
-				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": {
-					"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1",
-					"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/cp",
-				},
-				// Machine should be leaf (no echo)
-				"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1": {},
-				// Workers should have a machine deployment
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": {
-					"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1",
-				},
-				// Machine deployment should have a group of machines (grouping) and templates group
-				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": {
-					"virtual.cluster.x-k8s.io/v1beta1, Kind=MachineGroup, ns1/zzz_",
-					"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md1",
-				},
-				// MachineDeployment TemplateGroup should have a BootstrapConfigRef and InfrastructureRef
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md1": {
-					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md1",
-					"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md1",
-				},
-				// MachineDeployment InfrastructureRef should be a leaf
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md1": {},
-				// MachineDeployment BootstrapConfigRef should be a leaf
-				"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md1": {},
-				// ControlPlane TemplateGroup should have a InfrastructureRef
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/cp": {
-					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/cp",
-				},
-				// ControlPlane InfrastructureRef should be a leaf
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/cp": {},
-			},
-			wantNodeCheck: map[string]nodeCheck{
-				// InfrastructureCluster should have a meta name
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": func(g *WithT, obj client.Object) {
-					g.Expect(GetMetaName(obj)).To(Equal("ClusterInfrastructure"))
-				},
-				// ControlPlane should have a meta name, be a grouping object
-				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": func(g *WithT, obj client.Object) {
-					g.Expect(GetMetaName(obj)).To(Equal("ControlPlane"))
-					g.Expect(IsGroupingObject(obj)).To(BeTrue())
-				},
-				// Workers should be a virtual node
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": func(g *WithT, obj client.Object) {
-					g.Expect(IsVirtualObject(obj)).To(BeTrue())
-				},
-				// Machine deployment should be a grouping object
-				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": func(g *WithT, obj client.Object) {
-					g.Expect(IsGroupingObject(obj)).To(BeTrue())
-				},
-				// ControlPlane TemplateGroup should be a virtual node
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/cp": func(g *WithT, obj client.Object) {
-					g.Expect(IsVirtualObject(obj)).To(BeTrue())
-				},
-				// MachineDeployment TemplateGroup should be a virtual node
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md1": func(g *WithT, obj client.Object) {
-					g.Expect(IsVirtualObject(obj)).To(BeTrue())
-				},
-				// MachineDeployment InfrastructureRef should have a meta name
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md1": func(g *WithT, obj client.Object) {
-					g.Expect(GetMetaName(obj)).To(Equal("MachineInfrastructureTemplate"))
-				},
-				// MachineDeployment BootstrapConfigRef should have a meta name
-				"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md1": func(g *WithT, obj client.Object) {
-					g.Expect(GetMetaName(obj)).To(Equal("BootstrapConfigTemplate"))
-				},
-				// ControlPlane InfrastructureRef should have a meta name
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/cp1": func(g *WithT, obj client.Object) {
-					g.Expect(GetMetaName(obj)).To(Equal("MachineInfrastructureTemplate"))
-				},
-			},
-		},
-		{
-			name: "Discovery with templates shown without template virtual nodes",
-			args: args{
-				discoverOptions: DiscoverOptions{
-					Grouping:               true,
-					ShowTemplates:          true,
-					AddTemplateVirtualNode: false,
-				},
-				objs: test.NewFakeCluster("ns1", "cluster1").
-					WithControlPlane(
-						test.NewFakeControlPlane("cp").
-							WithMachines(
-								test.NewFakeMachine("cp1"),
-							),
-					).
-					WithMachineDeployments(
-						test.NewFakeMachineDeployment("md1").
-							WithMachineSets(
-								test.NewFakeMachineSet("ms1").
-									WithMachines(
-										test.NewFakeMachine("m1"),
-										test.NewFakeMachine("m2"),
-									),
-							).
-							WithInfrastructureTemplate(
-								test.NewFakeInfrastructureTemplate("md1"),
-							),
-					).
-					Objs(),
-			},
-			wantTree: map[string][]string{
-				// Cluster should be parent of InfrastructureCluster, ControlPlane, WorkerGroup, and ClusterResourceSetGroup
-				"cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": {
-					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1",
-					"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp",
-					"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers",
-				},
-				// InfrastructureCluster should be leaf
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": {},
-				// ControlPlane should have a machine and template
-				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": {
-					"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1",
-					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/cp",
-				},
-				// Machine should be leaf (no echo)
-				"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1": {},
-				// Workers should have a machine deployment
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": {
-					"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1",
-				},
-				// Machine deployment should have a group of machines (grouping) and templates
-				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": {
-					"virtual.cluster.x-k8s.io/v1beta1, Kind=MachineGroup, ns1/zzz_",
-					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md1",
-					"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md1",
-				},
-				// MachineDeployment InfrastructureRef should be a leaf
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md1": {},
-				// MachineDeployment BootstrapConfigRef should be a leaf
-				"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md1": {},
-				// ControlPlane InfrastructureRef should be a leaf
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/cp": {},
-			},
-			wantNodeCheck: map[string]nodeCheck{
-				// InfrastructureCluster should have a meta name
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": func(g *WithT, obj client.Object) {
-					g.Expect(GetMetaName(obj)).To(Equal("ClusterInfrastructure"))
-				},
-				// ControlPlane should have a meta name, be a grouping object
-				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": func(g *WithT, obj client.Object) {
-					g.Expect(GetMetaName(obj)).To(Equal("ControlPlane"))
-					g.Expect(IsGroupingObject(obj)).To(BeTrue())
-				},
-				// Workers should be a virtual node
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": func(g *WithT, obj client.Object) {
-					g.Expect(IsVirtualObject(obj)).To(BeTrue())
-				},
-				// Machine deployment should be a grouping object
-				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": func(g *WithT, obj client.Object) {
-					g.Expect(IsGroupingObject(obj)).To(BeTrue())
-				},
-				// ControlPlane TemplateGroup should be a virtual node
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/cp": func(g *WithT, obj client.Object) {
-					g.Expect(IsVirtualObject(obj)).To(BeTrue())
-				},
-				// MachineDeployment TemplateGroup should be a virtual node
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md1": func(g *WithT, obj client.Object) {
-					g.Expect(IsVirtualObject(obj)).To(BeTrue())
-				},
-				// MachineDeployment InfrastructureRef should have a meta name
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md1": func(g *WithT, obj client.Object) {
-					g.Expect(GetMetaName(obj)).To(Equal("MachineInfrastructureTemplate"))
-				},
-				// MachineDeployment BootstrapConfigRef should have a meta name
-				"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md1": func(g *WithT, obj client.Object) {
-					g.Expect(GetMetaName(obj)).To(Equal("BootstrapConfigTemplate"))
-				},
-				// ControlPlane InfrastructureRef should have a meta name
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/cp1": func(g *WithT, obj client.Object) {
-					g.Expect(GetMetaName(obj)).To(Equal("MachineInfrastructureTemplate"))
-				},
-			},
-		},
-		{
-			name: "Discovery with multiple machine deployments does not cause template virtual nodes to collide",
-			args: args{
-				discoverOptions: DiscoverOptions{
-					Grouping:               true,
-					ShowTemplates:          true,
-					AddTemplateVirtualNode: true,
-				},
-				objs: test.NewFakeCluster("ns1", "cluster1").
-					WithControlPlane(
-						test.NewFakeControlPlane("cp").
-							WithMachines(
-								test.NewFakeMachine("cp1"),
-							),
-					).
-					WithMachineDeployments(
-						test.NewFakeMachineDeployment("md1").
-							WithMachineSets(
-								test.NewFakeMachineSet("ms1").
-									WithMachines(
-										test.NewFakeMachine("m1"),
-										test.NewFakeMachine("m2"),
-									),
-							).
-							WithInfrastructureTemplate(
-								test.NewFakeInfrastructureTemplate("md1"),
-							),
-						test.NewFakeMachineDeployment("md2").
-							WithMachineSets(
-								test.NewFakeMachineSet("ms2").
-									WithMachines(
-										test.NewFakeMachine("m3"),
-										test.NewFakeMachine("m4"),
-										test.NewFakeMachine("m5"),
-									),
-							).
-							WithInfrastructureTemplate(
-								test.NewFakeInfrastructureTemplate("md2"),
-							),
-					).
-					Objs(),
-			},
-			wantTree: map[string][]string{
-				// Cluster should be parent of InfrastructureCluster, ControlPlane, WorkerGroup, and ClusterResourceSetGroup
-				"cluster.x-k8s.io/v1beta1, Kind=Cluster, ns1/cluster1": {
-					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1",
-					"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp",
-					"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers",
-				},
-				// InfrastructureCluster should be leaf
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": {},
-				// ControlPlane should have a machine and template group
-				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": {
-					"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1",
-					"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/cp",
-				},
-				// ControlPlane TemplateGroup should have a InfrastructureRef
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/cp": {
-					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/cp",
-				},
-				// ControlPlane InfrastructureRef should be a leaf
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/cp": {},
-				// Machine should be leaf (no echo)
-				"cluster.x-k8s.io/v1beta1, Kind=Machine, ns1/cp1": {},
-				// Workers should have 2 machine deployments
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": {
-					"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1",
-					"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md2",
-				},
-				// Machine deployment 1 should have a group of machines (grouping) and templates group
-				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": {
-					"virtual.cluster.x-k8s.io/v1beta1, Kind=MachineGroup, ns1/zzz_",
-					"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md1",
-				},
-				// MachineDeployment 1 TemplateGroup should have a BootstrapConfigRef and InfrastructureRef
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md1": {
-					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md1",
-					"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md1",
-				},
-				// MachineDeployment 1 InfrastructureRef should be a leaf
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md1": {},
-				// MachineDeployment 1 BootstrapConfigRef should be a leaf
-				"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md1": {},
-				// Machine deployment 2 should have a group of machines (grouping) and templates group
-				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md2": {
-					"virtual.cluster.x-k8s.io/v1beta1, Kind=MachineGroup, ns1/zzz_",
-					"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md2",
-				},
-				// MachineDeployment 2 TemplateGroup should have a BootstrapConfigRef and InfrastructureRef
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md2": {
-					"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md2",
-					"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md2",
-				},
-				// MachineDeployment 2 InfrastructureRef should be a leaf
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md2": {},
-				// MachineDeployment 2 BootstrapConfigRef should be a leaf
-				"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md2": {},
-			},
-			wantNodeCheck: map[string]nodeCheck{
-				// InfrastructureCluster should have a meta name
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureCluster, ns1/cluster1": func(g *WithT, obj client.Object) {
-					g.Expect(GetMetaName(obj)).To(Equal("ClusterInfrastructure"))
-				},
-				// ControlPlane should have a meta name, be a grouping object
-				"controlplane.cluster.x-k8s.io/v1beta1, Kind=GenericControlPlane, ns1/cp": func(g *WithT, obj client.Object) {
-					g.Expect(GetMetaName(obj)).To(Equal("ControlPlane"))
-					g.Expect(IsGroupingObject(obj)).To(BeTrue())
-				},
-				// Workers should be a virtual node
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=WorkerGroup, ns1/Workers": func(g *WithT, obj client.Object) {
-					g.Expect(IsVirtualObject(obj)).To(BeTrue())
-				},
-				// Machine deployment should be a grouping object
-				"cluster.x-k8s.io/v1beta1, Kind=MachineDeployment, ns1/md1": func(g *WithT, obj client.Object) {
-					g.Expect(IsGroupingObject(obj)).To(BeTrue())
-				},
-				// ControlPlane TemplateGroup should be a virtual node
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/cp": func(g *WithT, obj client.Object) {
-					g.Expect(IsVirtualObject(obj)).To(BeTrue())
-				},
-				// ControlPlane InfrastructureRef should have a meta name
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/cp1": func(g *WithT, obj client.Object) {
-					g.Expect(GetMetaName(obj)).To(Equal("MachineInfrastructureTemplate"))
-				},
-				// MachineDeployment 1 TemplateGroup should be a virtual node
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md1": func(g *WithT, obj client.Object) {
-					g.Expect(IsVirtualObject(obj)).To(BeTrue())
-				},
-				// MachineDeployment 1 InfrastructureRef should have a meta name
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md1": func(g *WithT, obj client.Object) {
-					g.Expect(GetMetaName(obj)).To(Equal("MachineInfrastructureTemplate"))
-				},
-				// MachineDeployment 1 BootstrapConfigRef should have a meta name
-				"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md1": func(g *WithT, obj client.Object) {
-					g.Expect(GetMetaName(obj)).To(Equal("BootstrapConfigTemplate"))
-				},
-				// MachineDeployment 2 TemplateGroup should be a virtual node
-				"virtual.cluster.x-k8s.io/v1beta1, Kind=TemplateGroup, ns1/md2": func(g *WithT, obj client.Object) {
-					g.Expect(IsVirtualObject(obj)).To(BeTrue())
-				},
-				// MachineDeployment 2 InfrastructureRef should have a meta name
-				"infrastructure.cluster.x-k8s.io/v1beta1, Kind=GenericInfrastructureMachineTemplate, ns1/md2": func(g *WithT, obj client.Object) {
-					g.Expect(GetMetaName(obj)).To(Equal("MachineInfrastructureTemplate"))
-				},
-				// MachineDeployment 2 BootstrapConfigRef should have a meta name
-				"bootstrap.cluster.x-k8s.io/v1beta1, Kind=GenericBootstrapConfigTemplate, ns1/md2": func(g *WithT, obj client.Object) {
-					g.Expect(GetMetaName(obj)).To(Equal("BootstrapConfigTemplate"))
-				},
-			},
-		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -748,7 +286,7 @@ func Test_Discovery(t *testing.T) {
 
 			for parent, wantChildren := range tt.wantTree {
 				gotChildren := tree.GetObjectsByParent(types.UID(parent))
-				g.Expect(gotChildren).To(HaveLen(len(wantChildren)), "%q doesn't have the expected number of children nodes", parent)
+				g.Expect(wantChildren).To(HaveLen(len(gotChildren)), "%q doesn't have the expected number of children nodes", parent)
 
 				for _, gotChild := range gotChildren {
 					found := false
diff --git a/cmd/clusterctl/client/tree/tree_test.go b/cmd/clusterctl/client/tree/tree_test.go
index d92bb1caf..a39fedf5e 100644
--- a/cmd/clusterctl/client/tree/tree_test.go
+++ b/cmd/clusterctl/client/tree/tree_test.go
@@ -25,7 +25,6 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/types"
-	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
 	"sigs.k8s.io/cluster-api/util/conditions"
@@ -619,8 +618,8 @@ func Test_Add_Grouping(t *testing.T) {
 
 	type args struct {
 		addOptions []AddObjectOption
-		siblings   []client.Object
-		obj        client.Object
+		siblings   []*clusterv1.Machine
+		obj        *clusterv1.Machine
 	}
 	tests := []struct {
 		name            string
@@ -638,9 +637,9 @@ func Test_Add_Grouping(t *testing.T) {
 			wantVisible:     true,
 		},
 		{
-			name: "should group child node if it has same kind and conditions of an existing one",
+			name: "should group child node if it has same conditions of an existing one",
 			args: args{
-				siblings: []client.Object{
+				siblings: []*clusterv1.Machine{
 					fakeMachine("first-machine",
 						withMachineCondition(conditions.TrueCondition(clusterv1.ReadyCondition)),
 					),
@@ -654,9 +653,9 @@ func Test_Add_Grouping(t *testing.T) {
 			wantItems:       "first-machine, second-machine",
 		},
 		{
-			name: "should group child node if it has same kind and conditions of an existing group",
+			name: "should group child node if it has same conditions of an existing group",
 			args: args{
-				siblings: []client.Object{
+				siblings: []*clusterv1.Machine{
 					fakeMachine("first-machine",
 						withMachineCondition(conditions.TrueCondition(clusterv1.ReadyCondition)),
 					),
@@ -672,23 +671,6 @@ func Test_Add_Grouping(t *testing.T) {
 			wantVisible:     false,
 			wantItems:       "first-machine, second-machine, third-machine",
 		},
-		{
-			name: "should not group child node if it has different kind",
-			args: args{
-				siblings: []client.Object{
-					fakeMachine("first-machine",
-						withMachineCondition(conditions.TrueCondition(clusterv1.ReadyCondition)),
-					),
-					fakeMachine("second-machine",
-						withMachineCondition(conditions.TrueCondition(clusterv1.ReadyCondition)),
-					),
-				},
-				obj: VirtualObject("ns", "NotAMachine", "other-object"),
-			},
-			wantNodesPrefix: []string{"zz_True", "other-object"},
-			wantVisible:     true,
-			wantItems:       "first-machine, second-machine",
-		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
diff --git a/cmd/clusterctl/internal/test/fake_objects.go b/cmd/clusterctl/internal/test/fake_objects.go
index 92898ee64..911f64f6b 100644
--- a/cmd/clusterctl/internal/test/fake_objects.go
+++ b/cmd/clusterctl/internal/test/fake_objects.go
@@ -341,13 +341,11 @@ func (f *FakeControlPlane) Objs(cluster *clusterv1.Cluster) []client.Object {
 			},
 		},
 		Spec: fakecontrolplane.GenericControlPlaneSpec{
-			MachineTemplate: fakecontrolplane.GenericMachineTemplate{
-				InfrastructureRef: corev1.ObjectReference{
-					APIVersion: controlPlaneInfrastructure.APIVersion,
-					Kind:       controlPlaneInfrastructure.Kind,
-					Namespace:  controlPlaneInfrastructure.Namespace,
-					Name:       controlPlaneInfrastructure.Name,
-				},
+			InfrastructureTemplate: corev1.ObjectReference{
+				APIVersion: controlPlaneInfrastructure.APIVersion,
+				Kind:       controlPlaneInfrastructure.Kind,
+				Namespace:  controlPlaneInfrastructure.Namespace,
+				Name:       controlPlaneInfrastructure.Name,
 			},
 		},
 	}
diff --git a/cmd/clusterctl/internal/test/providers/controlplane/generic_types.go b/cmd/clusterctl/internal/test/providers/controlplane/generic_types.go
index 921510065..f4c8d80a7 100644
--- a/cmd/clusterctl/internal/test/providers/controlplane/generic_types.go
+++ b/cmd/clusterctl/internal/test/providers/controlplane/generic_types.go
@@ -21,14 +21,9 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
-// GenericMachineTemplate contains a generic control plane spec.
-type GenericMachineTemplate struct {
-	InfrastructureRef corev1.ObjectReference `json:"infrastructureRef"`
-}
-
 // GenericControlPlaneSpec contains a generic control plane spec.
 type GenericControlPlaneSpec struct {
-	MachineTemplate GenericMachineTemplate `json:"machineTemplate"`
+	InfrastructureTemplate corev1.ObjectReference `json:"infrastructureTemplate"`
 }
 
 // +kubebuilder:object:root=true
diff --git a/cmd/clusterctl/internal/test/providers/controlplane/zz_generated.deepcopy.go b/cmd/clusterctl/internal/test/providers/controlplane/zz_generated.deepcopy.go
index 84570bb55..d196e3f4c 100644
--- a/cmd/clusterctl/internal/test/providers/controlplane/zz_generated.deepcopy.go
+++ b/cmd/clusterctl/internal/test/providers/controlplane/zz_generated.deepcopy.go
@@ -86,7 +86,7 @@ func (in *GenericControlPlaneList) DeepCopyObject() runtime.Object {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *GenericControlPlaneSpec) DeepCopyInto(out *GenericControlPlaneSpec) {
 	*out = *in
-	out.MachineTemplate = in.MachineTemplate
+	out.InfrastructureTemplate = in.InfrastructureTemplate
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericControlPlaneSpec.
@@ -98,19 +98,3 @@ func (in *GenericControlPlaneSpec) DeepCopy() *GenericControlPlaneSpec {
 	in.DeepCopyInto(out)
 	return out
 }
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *GenericMachineTemplate) DeepCopyInto(out *GenericMachineTemplate) {
-	*out = *in
-	out.InfrastructureRef = in.InfrastructureRef
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericMachineTemplate.
-func (in *GenericMachineTemplate) DeepCopy() *GenericMachineTemplate {
-	if in == nil {
-		return nil
-	}
-	out := new(GenericMachineTemplate)
-	in.DeepCopyInto(out)
-	return out
-}
diff --git a/test/go.mod b/test/go.mod
index 58254b04e..617e2cf2a 100644
--- a/test/go.mod
+++ b/test/go.mod
@@ -45,7 +45,7 @@ require (
 	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 	github.com/containerd/containerd v1.5.9 // indirect
 	github.com/coredns/caddy v1.1.0 // indirect
-	github.com/coredns/corefile-migration v1.0.15 // indirect
+	github.com/coredns/corefile-migration v1.0.14 // indirect
 	github.com/coreos/go-semver v0.3.0 // indirect
 	github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
diff --git a/test/go.sum b/test/go.sum
index 9b337f412..ad1d87690 100644
--- a/test/go.sum
+++ b/test/go.sum
@@ -278,8 +278,8 @@ github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgU
 github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY=
 github.com/coredns/caddy v1.1.0 h1:ezvsPrT/tA/7pYDBZxu0cT0VmWk75AfIaf6GSYCNMf0=
 github.com/coredns/caddy v1.1.0/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=
-github.com/coredns/corefile-migration v1.0.15 h1:hrJMRDGAzkzps+Jk6CKdCt9x7wyuEYJeoIXEUo12p+E=
-github.com/coredns/corefile-migration v1.0.15/go.mod h1:XnhgULOEouimnzgn0t4WPuFDN2/PJQcTxdWKC5eXNGE=
+github.com/coredns/corefile-migration v1.0.14 h1:Tz3WZhoj2NdP8drrQH86NgnCng+VrPjNeg2Oe1ALKag=
+github.com/coredns/corefile-migration v1.0.14/go.mod h1:XnhgULOEouimnzgn0t4WPuFDN2/PJQcTxdWKC5eXNGE=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=