Repository: kubernetes-sigs/cluster-api
Pull request #7087 (2022-08-18 13:52:54)

🌱  Logs/update type casing
Bring more of our logs into line with the logging guidelines by capitalising the Kinds in the key/value pairs.

/kind cleanup

k8s-ci-robot: @killianmuldoon: This issue is currently awaiting triage.

If CAPI contributors determines this is a relevant issue, they will accept it by applying the `triage/accepted` label and provide further guidance.

The `triage/accepted` label can be added by org members by writing `/triage accepted` in a comment.


<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
killianmuldoon: /hold
This PR includes https://github.com/kubernetes-sigs/cluster-api/pull/7075 so we should wait for that to merge first.

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

This pull-request has been approved by:
**Once this PR has been reviewed and has the lgtm label**, please assign justinsb for approval by writing `/assign @justinsb` in a comment. For more information see:[The Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process).

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":["justinsb"]} -->
k8s-ci-robot: @killianmuldoon: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-apidiff-main | bab509ec13657d09d74f3f7db7a8db69812e74ac | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/7087/pull-cluster-api-apidiff-main/1560263489249873920) | false | `/test pull-cluster-api-apidiff-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=7087). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Akillianmuldoon). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
sbueringer: /lgtm

cc @ykakarap 
killianmuldoon: /hold remove
killianmuldoon: /unhold
fabriziopandini: lgtm pending last round of nits

Patch:
diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml
index 784d45034..094afc1ea 100644
--- a/.github/workflows/dependabot.yml
+++ b/.github/workflows/dependabot.yml
@@ -24,7 +24,7 @@ jobs:
       id: go
     - name: Check out code into the Go module directory
       uses: actions/checkout@v3
-    - uses: actions/cache@v3.0.8
+    - uses: actions/cache@v3.0.7
       name: Restore go cache
       with:
         path: |
diff --git a/Tiltfile b/Tiltfile
index a9a700bea..0a9616073 100644
--- a/Tiltfile
+++ b/Tiltfile
@@ -13,6 +13,7 @@ load("ext://uibutton", "cmd_button", "location", "text_input")
 version_settings(True, ">=0.22.2")
 
 settings = {
+    "deploy_cert_manager": True,
     "enable_providers": ["docker"],
     "kind_cluster_name": os.getenv("CAPI_KIND_CLUSTER_NAME", "capi-test"),
     "debug": {},
diff --git a/api/v1alpha4/conversion.go b/api/v1alpha4/conversion.go
index ed788a78e..f3446e9af 100644
--- a/api/v1alpha4/conversion.go
+++ b/api/v1alpha4/conversion.go
@@ -47,10 +47,6 @@ func (src *Cluster) ConvertTo(dstRaw conversion.Hub) error {
 			dst.Spec.Topology.ControlPlane.NodeDrainTimeout = restored.Spec.Topology.ControlPlane.NodeDrainTimeout
 		}
 
-		if restored.Spec.Topology.ControlPlane.NodeDeletionTimeout != nil {
-			dst.Spec.Topology.ControlPlane.NodeDeletionTimeout = restored.Spec.Topology.ControlPlane.NodeDeletionTimeout
-		}
-
 		if restored.Spec.Topology.Workers != nil {
 			if dst.Spec.Topology.Workers == nil {
 				dst.Spec.Topology.Workers = &clusterv1.WorkersTopology{}
@@ -59,7 +55,6 @@ func (src *Cluster) ConvertTo(dstRaw conversion.Hub) error {
 				dst.Spec.Topology.Workers.MachineDeployments[i].FailureDomain = restored.Spec.Topology.Workers.MachineDeployments[i].FailureDomain
 				dst.Spec.Topology.Workers.MachineDeployments[i].Variables = restored.Spec.Topology.Workers.MachineDeployments[i].Variables
 				dst.Spec.Topology.Workers.MachineDeployments[i].NodeDrainTimeout = restored.Spec.Topology.Workers.MachineDeployments[i].NodeDrainTimeout
-				dst.Spec.Topology.Workers.MachineDeployments[i].NodeDeletionTimeout = restored.Spec.Topology.Workers.MachineDeployments[i].NodeDeletionTimeout
 			}
 		}
 	}
diff --git a/api/v1alpha4/zz_generated.conversion.go b/api/v1alpha4/zz_generated.conversion.go
index b065ce6bb..4672f0d61 100644
--- a/api/v1alpha4/zz_generated.conversion.go
+++ b/api/v1alpha4/zz_generated.conversion.go
@@ -889,7 +889,6 @@ func autoConvert_v1beta1_ControlPlaneTopology_To_v1alpha4_ControlPlaneTopology(i
 	}
 	out.Replicas = (*int32)(unsafe.Pointer(in.Replicas))
 	// WARNING: in.NodeDrainTimeout requires manual conversion: does not exist in peer-type
-	// WARNING: in.NodeDeletionTimeout requires manual conversion: does not exist in peer-type
 	return nil
 }
 
@@ -1243,7 +1242,6 @@ func autoConvert_v1beta1_MachineDeploymentTopology_To_v1alpha4_MachineDeployment
 	// WARNING: in.FailureDomain requires manual conversion: does not exist in peer-type
 	out.Replicas = (*int32)(unsafe.Pointer(in.Replicas))
 	// WARNING: in.NodeDrainTimeout requires manual conversion: does not exist in peer-type
-	// WARNING: in.NodeDeletionTimeout requires manual conversion: does not exist in peer-type
 	// WARNING: in.Variables requires manual conversion: does not exist in peer-type
 	return nil
 }
diff --git a/api/v1beta1/cluster_types.go b/api/v1beta1/cluster_types.go
index 0d9ac13cd..d7971b85c 100644
--- a/api/v1beta1/cluster_types.go
+++ b/api/v1beta1/cluster_types.go
@@ -121,12 +121,6 @@ type ControlPlaneTopology struct {
 	// NOTE: NodeDrainTimeout is different from `kubectl drain --timeout`
 	// +optional
 	NodeDrainTimeout *metav1.Duration `json:"nodeDrainTimeout,omitempty"`
-
-	// NodeDeletionTimeout defines how long the controller will attempt to delete the Node that the Machine
-	// hosts after the Machine is marked for deletion. A duration of 0 will retry deletion indefinitely.
-	// Defaults to 10 seconds.
-	// +optional
-	NodeDeletionTimeout *metav1.Duration `json:"nodeDeletionTimeout,omitempty"`
 }
 
 // WorkersTopology represents the different sets of worker nodes in the cluster.
@@ -173,12 +167,6 @@ type MachineDeploymentTopology struct {
 	// +optional
 	NodeDrainTimeout *metav1.Duration `json:"nodeDrainTimeout,omitempty"`
 
-	// NodeDeletionTimeout defines how long the controller will attempt to delete the Node that the Machine
-	// hosts after the Machine is marked for deletion. A duration of 0 will retry deletion indefinitely.
-	// Defaults to 10 seconds.
-	// +optional
-	NodeDeletionTimeout *metav1.Duration `json:"nodeDeletionTimeout,omitempty"`
-
 	// Variables can be used to customize the MachineDeployment through patches.
 	// +optional
 	Variables *MachineDeploymentVariables `json:"variables,omitempty"`
diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go
index 2c7ce3814..db145ed1c 100644
--- a/api/v1beta1/zz_generated.deepcopy.go
+++ b/api/v1beta1/zz_generated.deepcopy.go
@@ -466,11 +466,6 @@ func (in *ControlPlaneTopology) DeepCopyInto(out *ControlPlaneTopology) {
 		*out = new(metav1.Duration)
 		**out = **in
 	}
-	if in.NodeDeletionTimeout != nil {
-		in, out := &in.NodeDeletionTimeout, &out.NodeDeletionTimeout
-		*out = new(metav1.Duration)
-		**out = **in
-	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControlPlaneTopology.
@@ -967,11 +962,6 @@ func (in *MachineDeploymentTopology) DeepCopyInto(out *MachineDeploymentTopology
 		*out = new(metav1.Duration)
 		**out = **in
 	}
-	if in.NodeDeletionTimeout != nil {
-		in, out := &in.NodeDeletionTimeout, &out.NodeDeletionTimeout
-		*out = new(metav1.Duration)
-		**out = **in
-	}
 	if in.Variables != nil {
 		in, out := &in.Variables, &out.Variables
 		*out = new(MachineDeploymentVariables)
diff --git a/api/v1beta1/zz_generated.openapi.go b/api/v1beta1/zz_generated.openapi.go
index f4fb30539..01c418b08 100644
--- a/api/v1beta1/zz_generated.openapi.go
+++ b/api/v1beta1/zz_generated.openapi.go
@@ -837,12 +837,6 @@ func schema_sigsk8sio_cluster_api_api_v1beta1_ControlPlaneTopology(ref common.Re
 							Ref:         ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"),
 						},
 					},
-					"nodeDeletionTimeout": {
-						SchemaProps: spec.SchemaProps{
-							Description: "NodeDeletionTimeout defines how long the controller will attempt to delete the Node that the Machine hosts after the Machine is marked for deletion. A duration of 0 will retry deletion indefinitely. Defaults to 10 seconds.",
-							Ref:         ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"),
-						},
-					},
 				},
 			},
 		},
@@ -1677,12 +1671,6 @@ func schema_sigsk8sio_cluster_api_api_v1beta1_MachineDeploymentTopology(ref comm
 							Ref:         ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"),
 						},
 					},
-					"nodeDeletionTimeout": {
-						SchemaProps: spec.SchemaProps{
-							Description: "NodeDeletionTimeout defines how long the controller will attempt to delete the Node that the Machine hosts after the Machine is marked for deletion. A duration of 0 will retry deletion indefinitely. Defaults to 10 seconds.",
-							Ref:         ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"),
-						},
-					},
 					"variables": {
 						SchemaProps: spec.SchemaProps{
 							Description: "Variables can be used to customize the MachineDeployment through patches.",
diff --git a/config/crd/bases/cluster.x-k8s.io_clusters.yaml b/config/crd/bases/cluster.x-k8s.io_clusters.yaml
index 8106a7226..15086f0b3 100644
--- a/config/crd/bases/cluster.x-k8s.io_clusters.yaml
+++ b/config/crd/bases/cluster.x-k8s.io_clusters.yaml
@@ -863,12 +863,6 @@ spec:
                               More info: http://kubernetes.io/docs/user-guide/labels'
                             type: object
                         type: object
-                      nodeDeletionTimeout:
-                        description: NodeDeletionTimeout defines how long the controller
-                          will attempt to delete the Node that the Machine hosts after
-                          the Machine is marked for deletion. A duration of 0 will
-                          retry deletion indefinitely. Defaults to 10 seconds.
-                        type: string
                       nodeDrainTimeout:
                         description: 'NodeDrainTimeout is the total amount of time
                           that the controller will spend on draining a node. The default
@@ -978,13 +972,6 @@ spec:
                                 is greater than the allowed maximum length, the values
                                 are hashed together.
                               type: string
-                            nodeDeletionTimeout:
-                              description: NodeDeletionTimeout defines how long the
-                                controller will attempt to delete the Node that the
-                                Machine hosts after the Machine is marked for deletion.
-                                A duration of 0 will retry deletion indefinitely.
-                                Defaults to 10 seconds.
-                              type: string
                             nodeDrainTimeout:
                               description: 'NodeDrainTimeout is the total amount of
                                 time that the controller will spend on draining a
diff --git a/controlplane/kubeadm/internal/controllers/remediation.go b/controlplane/kubeadm/internal/controllers/remediation.go
index 55340ae04..429f8a2e9 100644
--- a/controlplane/kubeadm/internal/controllers/remediation.go
+++ b/controlplane/kubeadm/internal/controllers/remediation.go
@@ -98,7 +98,7 @@ func (r *KubeadmControlPlaneReconciler) reconcileUnhealthyMachines(ctx context.C
 		if err := patchHelper.Patch(ctx, machineToBeRemediated, patch.WithOwnedConditions{Conditions: []clusterv1.ConditionType{
 			clusterv1.MachineOwnerRemediatedCondition,
 		}}); err != nil {
-			log.Error(err, "Failed to patch control plane Machine", "machine", machineToBeRemediated.Name)
+			log.Error(err, "Failed to patch control plane Machine", "Machine", machineToBeRemediated.Name)
 			if retErr == nil {
 				retErr = errors.Wrapf(err, "failed to patch control plane Machine %s", machineToBeRemediated.Name)
 			}
diff --git a/docs/book/Makefile b/docs/book/Makefile
index a6d0d75c5..afcb67b9e 100644
--- a/docs/book/Makefile
+++ b/docs/book/Makefile
@@ -17,7 +17,7 @@ ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
 TOOLS_DIR := $(realpath ../../hack/tools)
 TOOLS_BIN_DIR := $(TOOLS_DIR)/bin
 BIN_DIR := bin
-MDBOOK_INSTALL := $(realpath ../../scripts/ci-install-mdbook.sh)
+CRATE_INSTALL := $(realpath ../../third_party/japaric-trust/crate_install.sh)
 
 export PATH := $(TOOLS_BIN_DIR):$(PATH)
 
@@ -34,10 +34,9 @@ RELEASELINK := $(TOOLS_BIN_DIR)/mdbook-releaselink
 $(RELEASELINK): $(TOOLS_DIR)/go.mod
 	cd $(TOOLS_DIR); go build -tags=tools -o $(BIN_DIR)/mdbook-releaselink ./mdbook/releaselink
 
-# Only set MDBOOK if it's not set as an environment variable
-MDBOOK ?= $(TOOLS_BIN_DIR)/mdbook
+MDBOOK := $(TOOLS_BIN_DIR)/mdbook
 $(MDBOOK):
-	$(MDBOOK_INSTALL) v0.4.11 $(TOOLS_BIN_DIR)
+	$(CRATE_INSTALL) --git rust-lang/mdBook --tag v0.4.11 --to $(TOOLS_BIN_DIR) --force
 
 RUNTIME_OPENAPI_GEN_BIN := runtime-openapi-gen
 RUNTIME_OPENAPI_GEN := $(abspath $(TOOLS_BIN_DIR)/$(RUNTIME_OPENAPI_GEN_BIN))
diff --git a/docs/book/src/developer/providers/implementers-guide/overview.md b/docs/book/src/developer/providers/implementers-guide/overview.md
index 75d9135dc..ed3299aa1 100644
--- a/docs/book/src/developer/providers/implementers-guide/overview.md
+++ b/docs/book/src/developer/providers/implementers-guide/overview.md
@@ -27,9 +27,6 @@ brew install kubernetes-cli
 
 # Install kustomize
 brew install kustomize
-
-# Install Kubebuilder
-brew install kubebuilder
 ```
 {{#/tab }}
 {{#tab Linux}}
@@ -42,15 +39,17 @@ curl -fLO https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl
 # Install kustomize
 curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"  | bash
 chmod +x ./kustomize && sudo mv ./kustomize /usr/local/bin/kustomize
+```
+
+{{#/tab }}
+{{#/tabs }}
 
+```bash
 # Install Kubebuilder
 curl -sLo kubebuilder https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)
 chmod +x ./kubebuilder && sudo mv ./kubebuilder /usr/local/bin/kubebuilder
 ```
 
-{{#/tab }}
-{{#/tabs }}
-
 [kubebuilder-book]: https://book.kubebuilder.io/
 [kubectl-install]: http://kubernetes.io/docs/user-guide/prereqs/
 [install-kustomize]: https://kubectl.docs.kubernetes.io/installation/kustomize/
diff --git a/docs/book/src/developer/providers/v1.2-to-v1.3.md b/docs/book/src/developer/providers/v1.2-to-v1.3.md
index c64b269ee..fc92cbee9 100644
--- a/docs/book/src/developer/providers/v1.2-to-v1.3.md
+++ b/docs/book/src/developer/providers/v1.2-to-v1.3.md
@@ -10,7 +10,7 @@ maintainers of providers and consumers of our Go API.
 ## Dependencies
 
 **Note**: Only the most relevant dependencies are listed, `k8s.io/` and `ginkgo`/`gomega` dependencies
-in Cluster API are kept in sync with the versions used by `sigs.k8s.io/controller-runtime`.
+in Cluster API are kept in sync with the versions used by `sigs.k8s.io/controller-runtime`. 
 
 -
 
@@ -31,7 +31,4 @@ in Cluster API are kept in sync with the versions used by `sigs.k8s.io/controlle
 
 ### Other
 
-- e2e tests are upgraded to use Ginkgo v2 (v2.1.4) and Gomega v1.20.0. Providers who use the test framework from this release will also need to upgrade, because Ginkgo v2 can't be imported alongside v1. Please see the [Ginkgo upgrade guide](https://onsi.github.io/ginkgo/MIGRATING_TO_V2), and note:
-  * the default test timeout has been [changed to 1h](https://onsi.github.io/ginkgo/MIGRATING_TO_V2#timeout-behavior)
-  * the `--junit-report` argument [replaces JUnit custom reporter](https://onsi.github.io/ginkgo/MIGRATING_TO_V2#improved-reporting-infrastructure) code
-  * see the ["Update tests to Ginkgo v2" PR](https://github.com/kubernetes-sigs/cluster-api/pull/6906) for a reference example
+-
\ No newline at end of file
diff --git a/exp/runtime/internal/controllers/warmup.go b/exp/runtime/internal/controllers/warmup.go
index e120d3c81..72b2860bd 100644
--- a/exp/runtime/internal/controllers/warmup.go
+++ b/exp/runtime/internal/controllers/warmup.go
@@ -103,7 +103,7 @@ func warmupRegistry(ctx context.Context, client client.Client, reader client.Rea
 		extensionConfig := &extensionConfigList.Items[i]
 		original := extensionConfig.DeepCopy()
 
-		log := log.WithValues("extensionConfig", klog.KObj(extensionConfig), "name", extensionConfig.Name, "namespace", extensionConfig.Namespace)
+		log := log.WithValues("ExtensionConfig", klog.KObj(extensionConfig))
 		ctx := ctrl.LoggerInto(ctx, log)
 
 		// Inject CABundle from secret if annotation is set. Otherwise https calls may fail.
diff --git a/exp/util/util.go b/exp/util/util.go
index 214fec3b2..b8f96c24d 100644
--- a/exp/util/util.go
+++ b/exp/util/util.go
@@ -24,6 +24,7 @@ import (
 	"github.com/pkg/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/klog/v2"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/handler"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
@@ -63,23 +64,23 @@ func GetMachinePoolByName(ctx context.Context, c client.Client, namespace, name
 func MachinePoolToInfrastructureMapFunc(gvk schema.GroupVersionKind, log logr.Logger) handler.MapFunc {
 	log = log.WithValues("machine-pool-to-infra-map-func", gvk.String())
 	return func(o client.Object) []reconcile.Request {
-		log := log.WithValues("namespace", o.GetNamespace(), "name", o.GetName())
 		m, ok := o.(*expv1.MachinePool)
 		if !ok {
-			log.V(4).Info("not a machine pool")
+			log.V(4).Info("Not a machine pool", "Object", klog.KObj(o))
 			return nil
 		}
+		log := log.WithValues("MachinePool", klog.KObj(o))
 
 		gk := gvk.GroupKind()
 		ref := m.Spec.Template.Spec.InfrastructureRef
 		// Return early if the GroupKind doesn't match what we expect.
 		infraGK := ref.GroupVersionKind().GroupKind()
 		if gk != infraGK {
-			log.V(4).Info("infra kind doesn't match filter group kind", "infrastructureGroupKind", infraGK.String())
+			log.V(4).Info("Infra kind doesn't match filter group kind", "infrastructureGroupKind", infraGK.String())
 			return nil
 		}
 
-		log.V(4).Info("projecting object", "namespace", m.Namespace, "name", ref.Name)
+		log.V(4).Info("Projecting object")
 		return []reconcile.Request{
 			{
 				NamespacedName: client.ObjectKey{
diff --git a/go.mod b/go.mod
index c2275f2ec..ce3a6bd5f 100644
--- a/go.mod
+++ b/go.mod
@@ -20,8 +20,8 @@ require (
 	github.com/google/go-github/v45 v45.2.0
 	github.com/google/gofuzz v1.2.0
 	github.com/mattn/go-runewidth v0.0.13 // indirect
-	github.com/onsi/ginkgo/v2 v2.1.4
-	github.com/onsi/gomega v1.20.0
+	github.com/onsi/ginkgo v1.16.5
+	github.com/onsi/gomega v1.18.1
 	github.com/pkg/errors v0.9.1
 	github.com/spf13/cobra v1.5.0
 	github.com/spf13/pflag v1.0.5
@@ -29,7 +29,7 @@ require (
 	github.com/valyala/fastjson v1.6.3
 	go.etcd.io/etcd/api/v3 v3.5.4
 	go.etcd.io/etcd/client/v3 v3.5.4
-	golang.org/x/net v0.0.0-20220812174116-3211cb980234
+	golang.org/x/net v0.0.0-20220617184016-355a448f1bc9
 	golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb
 	google.golang.org/grpc v1.47.0
 	inet.af/netaddr v0.0.0-20220617031823-097006376321
@@ -109,6 +109,7 @@ require (
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
+	github.com/nxadm/tail v1.4.8 // indirect
 	github.com/olekukonko/tablewriter v0.0.5
 	github.com/opencontainers/go-digest v1.0.0 // indirect
 	github.com/pelletier/go-toml v1.9.5 // indirect
@@ -136,7 +137,7 @@ require (
 	go.uber.org/zap v1.19.1 // indirect
 	go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
 	golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
-	golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
+	golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
 	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
 	golang.org/x/text v0.3.7
 	golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
@@ -146,8 +147,9 @@ require (
 	google.golang.org/protobuf v1.28.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/ini.v1 v1.66.4 // indirect
+	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
-	gopkg.in/yaml.v3 v3.0.1 // indirect
+	gopkg.in/yaml.v3 v3.0.0 // indirect
 	k8s.io/cli-runtime v0.24.0 // indirect
 	k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42
 	sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
diff --git a/go.sum b/go.sum
index 59724c6eb..a7fd9e789 100644
--- a/go.sum
+++ b/go.sum
@@ -567,18 +567,14 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k
 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
 github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
 github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
+github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ=
 github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
-github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
-github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY=
-github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
 github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
-github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
-github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q=
-github.com/onsi/gomega v1.20.0/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
 github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@@ -906,8 +902,8 @@ golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su
 golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
-golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 h1:Yqz/iviulwKwAREEeUd3nbBFn0XuyJqkoft2IlrvOhc=
+golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1030,14 +1026,11 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1122,7 +1115,6 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
-golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1342,9 +1334,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
 gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
 gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
 gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
diff --git a/hack/tools/go.mod b/hack/tools/go.mod
index db95448a7..40d3dde18 100644
--- a/hack/tools/go.mod
+++ b/hack/tools/go.mod
@@ -13,8 +13,8 @@ require (
 	github.com/pkg/errors v0.9.1
 	github.com/spf13/pflag v1.0.5
 	github.com/valyala/fastjson v1.6.3
-	golang.org/x/net v0.0.0-20220812174116-3211cb980234
-	golang.org/x/tools v0.1.10
+	golang.org/x/net v0.0.0-20220617184016-355a448f1bc9
+	golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717
 	google.golang.org/api v0.81.0
 	k8s.io/api v0.24.2
 	k8s.io/apiextensions-apiserver v0.24.2
@@ -88,7 +88,7 @@ require (
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
-	github.com/onsi/gomega v1.20.0 // indirect
+	github.com/onsi/gomega v1.18.1 // indirect
 	github.com/opencontainers/go-digest v1.0.0 // indirect
 	github.com/opencontainers/image-spec v1.0.2 // indirect
 	github.com/pelletier/go-toml v1.9.5 // indirect
@@ -109,7 +109,7 @@ require (
 	golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
 	golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
 	golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb // indirect
-	golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
+	golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
 	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
@@ -123,7 +123,7 @@ require (
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/ini.v1 v1.66.4 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
-	gopkg.in/yaml.v3 v3.0.1 // indirect
+	gopkg.in/yaml.v3 v3.0.0 // indirect
 	k8s.io/apiserver v0.24.2 // indirect
 	k8s.io/cluster-bootstrap v0.24.0 // indirect
 	k8s.io/component-base v0.24.2 // indirect
diff --git a/hack/tools/go.sum b/hack/tools/go.sum
index b46f5d81f..501570d8e 100644
--- a/hack/tools/go.sum
+++ b/hack/tools/go.sum
@@ -560,18 +560,14 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k
 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
 github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
 github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
+github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ=
 github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
-github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
-github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY=
-github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
 github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
-github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
-github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q=
-github.com/onsi/gomega v1.20.0/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
 github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
@@ -893,8 +889,8 @@ golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su
 golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
-golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 h1:Yqz/iviulwKwAREEeUd3nbBFn0XuyJqkoft2IlrvOhc=
+golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1017,14 +1013,11 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1108,9 +1101,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717 h1:hI3jKY4Hpf63ns040onEbB3dAkR/H/P83hw1TG8dD3Y=
 golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
-golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
-golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1335,9 +1327,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
 gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
 gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
 gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
diff --git a/hack/tools/tilt-prepare/main.go b/hack/tools/tilt-prepare/main.go
index 0003aa769..a1f143410 100644
--- a/hack/tools/tilt-prepare/main.go
+++ b/hack/tools/tilt-prepare/main.go
@@ -70,7 +70,7 @@ var (
 type tiltSettings struct {
 	Debug               map[string]debugConfig `json:"debug,omitempty"`
 	ExtraArgs           map[string]extraArgs   `json:"extra_args,omitempty"`
-	DeployCertManager   *bool                  `json:"deploy_cert_manager,omitempty"`
+	DeployCertManager   bool                   `json:"deploy_cert_manager,omitempty"`
 	DeployObservability []string               `json:"deploy_observability,omitempty"`
 	EnableProviders     []string               `json:"enable_providers,omitempty"`
 	AllowedContexts     []string               `json:"allowed_contexts,omitempty"`
@@ -161,16 +161,12 @@ func readTiltSettings(path string) (*tiltSettings, error) {
 		return nil, errors.Wrap(err, "failed to unmarshal tilt-settings content")
 	}
 
-	setDefaults(ts)
+	setDebugDefaults(ts)
 	return ts, nil
 }
 
-// setDefaults sets default values for debug related fields in tiltSettings.
-func setDefaults(ts *tiltSettings) {
-	if ts.DeployCertManager == nil {
-		ts.DeployCertManager = pointer.BoolPtr(true)
-	}
-
+// setDebugDefaults sets default values for debug related fields in tiltSettings.
+func setDebugDefaults(ts *tiltSettings) {
 	for k := range ts.Debug {
 		p := ts.Debug[k]
 		if p.Continue == nil {
@@ -238,7 +234,7 @@ func tiltResources(ctx context.Context, ts *tiltSettings) error {
 	// If required, all the task to install cert manager.
 	// NOTE: strictly speaking cert-manager is not a resource, however it is a dependency for most of the actual resources
 	// and running this is the same task group of the kustomize/provider tasks gives the maximum benefits in terms of reducing the total elapsed time.
-	if ts.DeployCertManager == nil || *ts.DeployCertManager {
+	if ts.DeployCertManager {
 		tasks["cert-manager-cainjector"] = preLoadImageTask(fmt.Sprintf("quay.io/jetstack/cert-manager-cainjector:%s", config.CertManagerDefaultVersion))
 		tasks["cert-manager-webhook"] = preLoadImageTask(fmt.Sprintf("quay.io/jetstack/cert-manager-webhook:%s", config.CertManagerDefaultVersion))
 		tasks["cert-manager-controller"] = preLoadImageTask(fmt.Sprintf("quay.io/jetstack/cert-manager-controller:%s", config.CertManagerDefaultVersion))
diff --git a/internal/contract/controlplane.go b/internal/contract/controlplane.go
index edabf8eee..7ebcd2cbf 100644
--- a/internal/contract/controlplane.go
+++ b/internal/contract/controlplane.go
@@ -226,10 +226,3 @@ func (c *ControlPlaneMachineTemplate) NodeDrainTimeout() *Duration {
 		path: Path{"spec", "machineTemplate", "nodeDrainTimeout"},
 	}
 }
-
-// NodeDeletionTimeout provides access to the nodeDeletionTimeout of a MachineTemplate.
-func (c *ControlPlaneMachineTemplate) NodeDeletionTimeout() *Duration {
-	return &Duration{
-		path: Path{"spec", "machineTemplate", "nodeDeletionTimeout"},
-	}
-}
diff --git a/internal/controllers/cluster/cluster_controller_phases.go b/internal/controllers/cluster/cluster_controller_phases.go
index 589c8a0a9..fea026e6d 100644
--- a/internal/controllers/cluster/cluster_controller_phases.go
+++ b/internal/controllers/cluster/cluster_controller_phases.go
@@ -274,7 +274,7 @@ func (r *Reconciler) reconcileKubeconfig(ctx context.Context, cluster *clusterv1
 	case apierrors.IsNotFound(err):
 		if err := kubeconfig.CreateSecret(ctx, r.Client, cluster); err != nil {
 			if err == kubeconfig.ErrDependentCertificateNotFound {
-				log.Info("could not find secret for cluster, requeuing", "secret", secret.ClusterCA)
+				log.Info("Could not find secret for cluster, requeuing", "Secret", secret.ClusterCA)
 				return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
 			}
 			return ctrl.Result{}, err
diff --git a/internal/controllers/machine/machine_controller_node_labels.go b/internal/controllers/machine/machine_controller_node_labels.go
index b0b0c8b12..21888a382 100644
--- a/internal/controllers/machine/machine_controller_node_labels.go
+++ b/internal/controllers/machine/machine_controller_node_labels.go
@@ -21,6 +21,7 @@ import (
 
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/klog/v2"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
@@ -48,7 +49,7 @@ func (r *Reconciler) reconcileInterruptibleNodeLabel(ctx context.Context, cluste
 	// Get interruptible instance status from the infrastructure provider.
 	interruptible, _, err := unstructured.NestedBool(infra.Object, "status", "interruptible")
 	if err != nil {
-		log.V(1).Error(err, "Failed to get interruptible status from infrastructure provider", "machinename", machine.Name)
+		log.V(1).Error(err, "Failed to get interruptible status from infrastructure provider", "Machine", klog.KObj(machine))
 		return ctrl.Result{}, nil
 	}
 	if !interruptible {
diff --git a/internal/controllers/machine/machine_controller_phases.go b/internal/controllers/machine/machine_controller_phases.go
index aa8e668b4..b393390be 100644
--- a/internal/controllers/machine/machine_controller_phases.go
+++ b/internal/controllers/machine/machine_controller_phases.go
@@ -232,7 +232,7 @@ func (r *Reconciler) reconcileBootstrap(ctx context.Context, cluster *clusterv1.
 	}
 	m.Spec.Bootstrap.DataSecretName = pointer.StringPtr(secretName)
 	if !m.Status.BootstrapReady {
-		log.Info("Bootstrap provider generated data secret and reports status.ready", bootstrapConfig.GetKind(), klog.KObj(bootstrapConfig), "secret", klog.KRef(m.Namespace, secretName))
+		log.Info("Bootstrap provider generated data secret and reports status.ready", bootstrapConfig.GetKind(), klog.KObj(bootstrapConfig), "Secret", klog.KRef(m.Namespace, secretName))
 	}
 	m.Status.BootstrapReady = true
 	return ctrl.Result{}, nil
diff --git a/internal/controllers/machinedeployment/machinedeployment_controller.go b/internal/controllers/machinedeployment/machinedeployment_controller.go
index c3e19efe5..4118c422e 100644
--- a/internal/controllers/machinedeployment/machinedeployment_controller.go
+++ b/internal/controllers/machinedeployment/machinedeployment_controller.go
@@ -314,7 +314,7 @@ func (r *Reconciler) getMachineDeploymentsForMachineSet(ctx context.Context, ms
 	log := ctrl.LoggerFrom(ctx)
 
 	if len(ms.Labels) == 0 {
-		log.V(2).Info("No MachineDeployments found for MachineSet because it has no labels", "machineset", ms.Name)
+		log.V(2).Info("No MachineDeployments found for MachineSet because it has no labels", "MachineSet", klog.KObj(ms))
 		return nil
 	}
 
diff --git a/internal/controllers/machinedeployment/machinedeployment_sync.go b/internal/controllers/machinedeployment/machinedeployment_sync.go
index 8d1aa19fd..241f27c7f 100644
--- a/internal/controllers/machinedeployment/machinedeployment_sync.go
+++ b/internal/controllers/machinedeployment/machinedeployment_sync.go
@@ -29,6 +29,7 @@ import (
 	apirand "k8s.io/apimachinery/pkg/util/rand"
 	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/client-go/util/retry"
+	"k8s.io/klog/v2"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -236,13 +237,13 @@ func (r *Reconciler) getNewMachineSet(ctx context.Context, d *clusterv1.MachineD
 
 		return nil, err
 	case err != nil:
-		log.Error(err, "Failed to create new machine set", "machineset", newMS.Name)
+		log.Error(err, "Failed to create new MachineSet", "MachineSet", klog.KObj(&newMS))
 		r.recorder.Eventf(d, corev1.EventTypeWarning, "FailedCreate", "Failed to create MachineSet %q: %v", newMS.Name, err)
 		return nil, err
 	}
 
 	if !alreadyExists {
-		log.V(4).Info("Created new machine set", "machineset", createdMS.Name)
+		log.V(4).Info("Created new MachineSet", "MachineSet", klog.KObj(createdMS))
 		r.recorder.Eventf(d, corev1.EventTypeNormal, "SuccessfulCreate", "Created MachineSet %q", newMS.Name)
 	}
 
diff --git a/internal/controllers/machineset/machineset_controller.go b/internal/controllers/machineset/machineset_controller.go
index a8a11283e..e9ab72f2f 100644
--- a/internal/controllers/machineset/machineset_controller.go
+++ b/internal/controllers/machineset/machineset_controller.go
@@ -660,7 +660,7 @@ func (r *Reconciler) updateStatus(ctx context.Context, cluster *clusterv1.Cluste
 	templateLabel := labels.Set(ms.Spec.Template.Labels).AsSelectorPreValidated()
 
 	for _, machine := range filteredMachines {
-		log = log.WithValues("machine", klog.KObj(machine))
+		log = log.WithValues("Machine", klog.KObj(machine))
 
 		if templateLabel.Matches(labels.Set(machine.Labels)) {
 			fullyLabeledReplicasCount++
diff --git a/internal/controllers/topology/cluster/desired_state.go b/internal/controllers/topology/cluster/desired_state.go
index c655395c8..b3086d517 100644
--- a/internal/controllers/topology/cluster/desired_state.go
+++ b/internal/controllers/topology/cluster/desired_state.go
@@ -242,13 +242,6 @@ func (r *Reconciler) computeControlPlane(ctx context.Context, s *scope.Scope, in
 		}
 	}
 
-	// If it is required to manage the NodeDeletionTimeout for the control plane, set the corresponding field.
-	if s.Blueprint.Topology.ControlPlane.NodeDeletionTimeout != nil {
-		if err := contract.ControlPlane().MachineTemplate().NodeDeletionTimeout().Set(controlPlane, *s.Blueprint.Topology.ControlPlane.NodeDeletionTimeout); err != nil {
-			return nil, errors.Wrap(err, "failed to set spec.machineTemplate.nodeDeletionTimeout in the ControlPlane object")
-		}
-	}
-
 	// Sets the desired Kubernetes version for the control plane.
 	version, err := r.computeControlPlaneVersion(ctx, s)
 	if err != nil {
@@ -538,13 +531,12 @@ func computeMachineDeployment(_ context.Context, s *scope.Scope, desiredControlP
 					Annotations: mergeMap(machineDeploymentTopology.Metadata.Annotations, machineDeploymentBlueprint.Metadata.Annotations),
 				},
 				Spec: clusterv1.MachineSpec{
-					ClusterName:         s.Current.Cluster.Name,
-					Version:             pointer.String(version),
-					Bootstrap:           clusterv1.Bootstrap{ConfigRef: contract.ObjToRef(desiredMachineDeployment.BootstrapTemplate)},
-					InfrastructureRef:   *contract.ObjToRef(desiredMachineDeployment.InfrastructureMachineTemplate),
-					FailureDomain:       machineDeploymentTopology.FailureDomain,
-					NodeDrainTimeout:    machineDeploymentTopology.NodeDrainTimeout,
-					NodeDeletionTimeout: machineDeploymentTopology.NodeDeletionTimeout,
+					ClusterName:       s.Current.Cluster.Name,
+					Version:           pointer.String(version),
+					Bootstrap:         clusterv1.Bootstrap{ConfigRef: contract.ObjToRef(desiredMachineDeployment.BootstrapTemplate)},
+					InfrastructureRef: *contract.ObjToRef(desiredMachineDeployment.InfrastructureMachineTemplate),
+					FailureDomain:     machineDeploymentTopology.FailureDomain,
+					NodeDrainTimeout:  machineDeploymentTopology.NodeDrainTimeout,
 				},
 			},
 		},
diff --git a/internal/controllers/topology/cluster/patches/engine.go b/internal/controllers/topology/cluster/patches/engine.go
index 17515fcbd..66685c09d 100644
--- a/internal/controllers/topology/cluster/patches/engine.go
+++ b/internal/controllers/topology/cluster/patches/engine.go
@@ -371,7 +371,6 @@ func updateDesiredState(ctx context.Context, req *runtimehooksv1.GeneratePatches
 		contract.ControlPlane().MachineTemplate().Metadata().Path(),
 		contract.ControlPlane().MachineTemplate().InfrastructureRef().Path(),
 		contract.ControlPlane().MachineTemplate().NodeDrainTimeout().Path(),
-		contract.ControlPlane().MachineTemplate().NodeDeletionTimeout().Path(),
 		contract.ControlPlane().Replicas().Path(),
 		contract.ControlPlane().Version().Path(),
 	}); err != nil {
diff --git a/scripts/ci-install-mdbook.sh b/scripts/ci-install-mdbook.sh
deleted file mode 100755
index 5720e1c6f..000000000
--- a/scripts/ci-install-mdbook.sh
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/bin/bash
-
-# Copyright 2022 The Kubernetes Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -o errexit
-set -o nounset
-set -o pipefail
-
-VERSION=${1}
-OUTPUT_PATH=${2}
-
-# Ensure the output folder exists
-mkdir -p "${OUTPUT_PATH}"
-
-# Get what release to download
-RELEASE_NAME=""
-case "$OSTYPE" in
-  darwin*) RELEASE_NAME="x86_64-apple-darwin.tar.gz"  ;;
-  linux*)  RELEASE_NAME="x86_64-unknown-linux-gnu.tar.gz" ;;
-#  msys*)    echo "WINDOWS" ;;
-  *)        echo "No mdBook release available for: $OSTYPE" && exit 1;;
-esac
-
-# Download and extract the mdBook release
-curl -L "https://github.com/rust-lang/mdBook/releases/download/${VERSION}/mdbook-${VERSION}-${RELEASE_NAME}" | tar -xvz -C "${OUTPUT_PATH}"
diff --git a/third_party/README.md b/third_party/README.md
new file mode 100644
index 000000000..af9d996b0
--- /dev/null
+++ b/third_party/README.md
@@ -0,0 +1,2 @@
+This directory is used to copy code from other projects in-tree. This
+directory is excluded from linters.
diff --git a/third_party/japaric-trust/LICENSE-APACHE b/third_party/japaric-trust/LICENSE-APACHE
new file mode 100644
index 000000000..16fe87b06
--- /dev/null
+++ b/third_party/japaric-trust/LICENSE-APACHE
@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+   To apply the Apache License to your work, attach the following
+   boilerplate notice, with the fields enclosed by brackets "[]"
+   replaced with your own identifying information. (Don't include
+   the brackets!)  The text should be enclosed in the appropriate
+   comment syntax for the file format. We also recommend that a
+   file or class name and description of purpose be included on the
+   same "printed page" as the copyright notice for easier
+   identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/third_party/japaric-trust/crate_install.sh b/third_party/japaric-trust/crate_install.sh
new file mode 100755
index 000000000..4cf6d4d22
--- /dev/null
+++ b/third_party/japaric-trust/crate_install.sh
@@ -0,0 +1,164 @@
+#!/bin/sh
+
+# From https://github.com/japaric/trust/blob/2d08818370e618108b55fae9b243330640554dce/install.sh
+
+set -e
+
+help() {
+    cat <<'EOF'
+Install a binary release of a Rust crate hosted on GitHub
+
+Usage:
+    install.sh [options]
+
+Options:
+    -h, --help      Display this message
+    --git SLUG      Get the crate from "https://github/$SLUG"
+    -f, --force     Force overwriting an existing binary
+    --crate NAME    Name of the crate to install (default <repository name>)
+    --tag TAG       Tag (version) of the crate to install (default <latest release>)
+    --target TARGET Install the release compiled for $TARGET (default <`rustc` host>)
+    --to LOCATION   Where to install the binary (default ~/.cargo/bin)
+EOF
+}
+
+say() {
+    echo "install.sh: $1"
+}
+
+say_err() {
+    say "$1" >&2
+}
+
+err() {
+    if [ ! -z $td ]; then
+        rm -rf $td
+    fi
+
+    say_err "ERROR $1"
+    exit 1
+}
+
+need() {
+    if ! command -v $1 > /dev/null 2>&1; then
+        err "need $1 (command not found)"
+    fi
+}
+
+force=false
+while test $# -gt 0; do
+    case $1 in
+        --crate)
+            crate=$2
+            shift
+            ;;
+        --force | -f)
+            force=true
+            ;;
+        --git)
+            git=$2
+            shift
+            ;;
+        --help | -h)
+            help
+            exit 0
+            ;;
+        --tag)
+            tag=$2
+            shift
+            ;;
+        --target)
+            target=$2
+            shift
+            ;;
+        --to)
+            dest=$2
+            shift
+            ;;
+        *)
+            ;;
+    esac
+    shift
+done
+
+# Dependencies
+need basename
+need curl
+need install
+need mkdir
+need mktemp
+need tar
+
+# Optional dependencies
+if [ -z $crate ] || [ -z $tag ] || [ -z $target ]; then
+    need cut
+fi
+
+if [ -z $tag ]; then
+    need rev
+fi
+
+if [ -z $git ]; then
+    err 'must specify a git repository using `--git`. Example: `install.sh --git japaric/cross`'
+fi
+
+url="https://github.com/$git"
+say_err "GitHub repository: $url"
+
+if [ -z $crate ]; then
+    crate=$(echo $git | cut -d'/' -f2)
+fi
+
+say_err "Crate: $crate"
+
+url="$url/releases"
+
+if [ -z $tag ]; then
+    tag=$(curl -s "$url/latest" | cut -d'"' -f2 | rev | cut -d'/' -f1 | rev)
+    say_err "Tag: latest ($tag)"
+else
+    say_err "Tag: $tag"
+fi
+
+if [ -z $target ]; then
+    kernel="$(uname -s)"
+    case $kernel in
+        "Darwin")
+            platform="apple-darwin"
+            ;;
+        "Linux")
+            platform="unknown-linux-gnu"
+            ;;
+        *)
+            err "unknown kernel $kernel"
+            ;;
+    esac
+
+    target="$(uname -m)-$platform"
+fi
+
+say_err "Target: $target"
+
+if [ -z $dest ]; then
+    dest="$HOME/.cargo/bin"
+fi
+
+say_err "Installing to: $dest"
+
+url="$url/download/$tag/$crate-$tag-$target.tar.gz"
+
+td=$(mktemp -d || mktemp -d -t tmp)
+curl -sL $url | tar -C $td -xz
+
+for f in $(ls $td); do
+    test -x $td/$f || continue
+
+    if [ -e "$dest/$f" ] && [ $force = false ]; then
+        err "$f already exists in $dest"
+    else
+        mkdir -p $dest
+        install -m 755 $td/$f $dest
+    fi
+done
+
+rm -rf $td


Test patch:
diff --git a/internal/contract/controlplane_test.go b/internal/contract/controlplane_test.go
index 027f860f5..114796883 100644
--- a/internal/contract/controlplane_test.go
+++ b/internal/contract/controlplane_test.go
@@ -170,28 +170,6 @@ func TestControlPlane(t *testing.T) {
 		g.Expect(found).To(BeTrue())
 		g.Expect(durationString).To(Equal(expectedDurationString))
 	})
-
-	t.Run("Manages spec.machineTemplate.nodeDeletionTimeout", func(t *testing.T) {
-		g := NewWithT(t)
-
-		duration := metav1.Duration{Duration: 2*time.Minute + 5*time.Second}
-		expectedDurationString := "2m5s"
-		g.Expect(ControlPlane().MachineTemplate().NodeDeletionTimeout().Path()).To(Equal(Path{"spec", "machineTemplate", "nodeDeletionTimeout"}))
-
-		err := ControlPlane().MachineTemplate().NodeDeletionTimeout().Set(obj, duration)
-		g.Expect(err).ToNot(HaveOccurred())
-
-		got, err := ControlPlane().MachineTemplate().NodeDeletionTimeout().Get(obj)
-		g.Expect(err).ToNot(HaveOccurred())
-		g.Expect(got).ToNot(BeNil())
-		g.Expect(*got).To(Equal(duration))
-
-		// Check that the literal string value of the duration is correctly formatted.
-		durationString, found, err := unstructured.NestedString(obj.UnstructuredContent(), "spec", "machineTemplate", "nodeDeletionTimeout")
-		g.Expect(err).ToNot(HaveOccurred())
-		g.Expect(found).To(BeTrue())
-		g.Expect(durationString).To(Equal(expectedDurationString))
-	})
 }
 
 func TestControlPlaneIsUpgrading(t *testing.T) {
diff --git a/internal/controllers/topology/cluster/desired_state_test.go b/internal/controllers/topology/cluster/desired_state_test.go
index 00d7fae18..eed9411e2 100644
--- a/internal/controllers/topology/cluster/desired_state_test.go
+++ b/internal/controllers/topology/cluster/desired_state_test.go
@@ -263,7 +263,6 @@ func TestComputeControlPlane(t *testing.T) {
 	replicas := int32(3)
 	duration := 10 * time.Second
 	nodeDrainTimeout := metav1.Duration{Duration: duration}
-	nodeDeletionTimeout := metav1.Duration{Duration: duration}
 	cluster := &clusterv1.Cluster{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      "cluster1",
@@ -277,9 +276,8 @@ func TestComputeControlPlane(t *testing.T) {
 						Labels:      map[string]string{"l2": ""},
 						Annotations: map[string]string{"a2": ""},
 					},
-					Replicas:            &replicas,
-					NodeDrainTimeout:    &nodeDrainTimeout,
-					NodeDeletionTimeout: &nodeDeletionTimeout,
+					Replicas:         &replicas,
+					NodeDrainTimeout: &nodeDrainTimeout,
 				},
 			},
 		},
@@ -317,7 +315,6 @@ func TestComputeControlPlane(t *testing.T) {
 		assertNestedField(g, obj, version, contract.ControlPlane().Version().Path()...)
 		assertNestedField(g, obj, int64(replicas), contract.ControlPlane().Replicas().Path()...)
 		assertNestedField(g, obj, duration.String(), contract.ControlPlane().MachineTemplate().NodeDrainTimeout().Path()...)
-		assertNestedField(g, obj, duration.String(), contract.ControlPlane().MachineTemplate().NodeDeletionTimeout().Path()...)
 		assertNestedFieldUnset(g, obj, contract.ControlPlane().MachineTemplate().InfrastructureRef().Path()...)
 
 		// Ensure no ownership is added to generated ControlPlane.
@@ -1321,17 +1318,15 @@ func TestComputeMachineDeployment(t *testing.T) {
 	replicas := int32(5)
 	failureDomain := "always-up-region"
 	nodeDrainTimeout := metav1.Duration{Duration: 10 * time.Second}
-	nodeDeletionTimeout := metav1.Duration{Duration: 10 * time.Second}
 	mdTopology := clusterv1.MachineDeploymentTopology{
 		Metadata: clusterv1.ObjectMeta{
 			Labels: map[string]string{"foo": "baz"},
 		},
-		Class:               "linux-worker",
-		Name:                "big-pool-of-machines",
-		Replicas:            &replicas,
-		FailureDomain:       &failureDomain,
-		NodeDrainTimeout:    &nodeDrainTimeout,
-		NodeDeletionTimeout: &nodeDeletionTimeout,
+		Class:            "linux-worker",
+		Name:             "big-pool-of-machines",
+		Replicas:         &replicas,
+		FailureDomain:    &failureDomain,
+		NodeDrainTimeout: &nodeDrainTimeout,
 	}
 
 	t.Run("Generates the machine deployment and the referenced templates", func(t *testing.T) {
@@ -1360,7 +1355,6 @@ func TestComputeMachineDeployment(t *testing.T) {
 		g.Expect(*actualMd.Spec.Replicas).To(Equal(replicas))
 		g.Expect(*actualMd.Spec.Template.Spec.FailureDomain).To(Equal(failureDomain))
 		g.Expect(*actualMd.Spec.Template.Spec.NodeDrainTimeout).To(Equal(nodeDrainTimeout))
-		g.Expect(*actualMd.Spec.Template.Spec.NodeDeletionTimeout).To(Equal(nodeDeletionTimeout))
 		g.Expect(actualMd.Spec.ClusterName).To(Equal("cluster1"))
 		g.Expect(actualMd.Name).To(ContainSubstring("cluster1"))
 		g.Expect(actualMd.Name).To(ContainSubstring("big-pool-of-machines"))
diff --git a/internal/test/envtest/environment.go b/internal/test/envtest/environment.go
index 075e48b0c..065a191c9 100644
--- a/internal/test/envtest/environment.go
+++ b/internal/test/envtest/environment.go
@@ -29,7 +29,7 @@ import (
 	"testing"
 	"time"
 
-	"github.com/onsi/ginkgo/v2"
+	"github.com/onsi/ginkgo"
 	"github.com/pkg/errors"
 	admissionv1 "k8s.io/api/admissionregistration/v1"
 	corev1 "k8s.io/api/core/v1"
diff --git a/scripts/ci-e2e.sh b/scripts/ci-e2e.sh
index a05721ff5..903c7e78e 100755
--- a/scripts/ci-e2e.sh
+++ b/scripts/ci-e2e.sh
@@ -53,7 +53,7 @@ kind:prepullAdditionalImages
 # Configure e2e tests
 export GINKGO_NODES=3
 export GINKGO_NOCOLOR=true
-export GINKGO_ARGS="--fail-fast" # Other ginkgo args that need to be appended to the command.
+export GINKGO_ARGS="--failFast" # Other ginkgo args that need to be appended to the command.
 export E2E_CONF_FILE="${REPO_ROOT}/test/e2e/config/docker.yaml"
 export ARTIFACTS="${ARTIFACTS:-${REPO_ROOT}/_artifacts}"
 export SKIP_RESOURCE_CLEANUP=false
diff --git a/test/e2e/Makefile b/test/e2e/Makefile
index f6f87c733..6f24dfd94 100644
--- a/test/e2e/Makefile
+++ b/test/e2e/Makefile
@@ -37,10 +37,10 @@ help:  ## Display this help
 TOOLS_DIR := $(REPO_ROOT)/hack/tools
 BIN_DIR := bin
 TOOLS_BIN_DIR := $(TOOLS_DIR)/$(BIN_DIR)
-GINGKO_VER := v2.1.4
+GINGKO_VER := v1.16.5
 GINKGO_BIN := ginkgo
 GINKGO := $(abspath $(TOOLS_BIN_DIR)/$(GINKGO_BIN)-$(GINGKO_VER))
-GINKGO_PKG := github.com/onsi/ginkgo/v2/ginkgo
+GINKGO_PKG := github.com/onsi/ginkgo/ginkgo
 GO_INSTALL := $(REPO_ROOT)/scripts/go_install.sh
 KUSTOMIZE_BIN := kustomize
 KUSTOMIZE_VER := $(shell grep "^KUSTOMIZE_VER" $(REPO_ROOT)/Makefile | awk '{print $$NF}')
@@ -100,6 +100,7 @@ cluster-templates-v1beta1: $(KUSTOMIZE) ## Generate cluster templates for v1beta
 	$(KUSTOMIZE) build $(DOCKER_TEMPLATES)/v1beta1/cluster-template-kcp-scale-in --load-restrictor LoadRestrictionsNone > $(DOCKER_TEMPLATES)/v1beta1/cluster-template-kcp-scale-in.yaml
 	$(KUSTOMIZE) build $(DOCKER_TEMPLATES)/v1beta1/cluster-template-ipv6 --load-restrictor LoadRestrictionsNone > $(DOCKER_TEMPLATES)/v1beta1/cluster-template-ipv6.yaml
 	$(KUSTOMIZE) build $(DOCKER_TEMPLATES)/v1beta1/cluster-template-topology --load-restrictor LoadRestrictionsNone > $(DOCKER_TEMPLATES)/v1beta1/cluster-template-topology.yaml
+	$(KUSTOMIZE) build $(DOCKER_TEMPLATES)/v1beta1/cluster-template-topology-changes --load-restrictor LoadRestrictionsNone > $(DOCKER_TEMPLATES)/v1beta1/cluster-template-topology-changes.yaml
 	$(KUSTOMIZE) build $(DOCKER_TEMPLATES)/v1beta1/cluster-template-ignition --load-restrictor LoadRestrictionsNone > $(DOCKER_TEMPLATES)/v1beta1/cluster-template-ignition.yaml
 
 test-extension-deployment: $(KUSTOMIZE) ## Generate deployment for test extension
@@ -110,10 +111,10 @@ test-extension-deployment: $(KUSTOMIZE) ## Generate deployment for test extensio
 ## Testing
 ## --------------------------------------
 
-GINKGO_FOCUS ?=
+GINKGO_FOCUS  ?=
 GINKGO_SKIP ?=
-GINKGO_NODES ?= 1
-E2E_CONF_FILE ?= ${REPO_ROOT}/test/e2e/config/docker.yaml
+GINKGO_NODES  ?= 1
+E2E_CONF_FILE  ?= ${REPO_ROOT}/test/e2e/config/docker.yaml
 ARTIFACTS ?= ${REPO_ROOT}/_artifacts
 SKIP_RESOURCE_CLEANUP ?= false
 USE_EXISTING_CLUSTER ?= false
@@ -126,7 +127,7 @@ endif
 
 .PHONY: run
 run: $(GINKGO) cluster-templates test-extension-deployment ## Run the end-to-end tests
-	$(GINKGO) -v --trace --tags=e2e --focus="$(GINKGO_FOCUS)" $(_SKIP_ARGS) --nodes=$(GINKGO_NODES) --no-color=$(GINKGO_NOCOLOR) --output-dir="$(ARTIFACTS)" --junit-report="junit.e2e_suite.1.xml" $(GINKGO_ARGS) . -- \
+	$(GINKGO) -v -trace -tags=e2e -focus="$(GINKGO_FOCUS)" $(_SKIP_ARGS) -nodes=$(GINKGO_NODES) --noColor=$(GINKGO_NOCOLOR) $(GINKGO_ARGS) . -- \
 	    -e2e.artifacts-folder="$(ARTIFACTS)" \
 	    -e2e.config="$(E2E_CONF_FILE)" \
 	    -e2e.skip-resource-cleanup=$(SKIP_RESOURCE_CLEANUP) -e2e.use-existing-cluster=$(USE_EXISTING_CLUSTER)
diff --git a/test/e2e/cluster_upgrade.go b/test/e2e/cluster_upgrade.go
index 973766713..3682776d3 100644
--- a/test/e2e/cluster_upgrade.go
+++ b/test/e2e/cluster_upgrade.go
@@ -22,7 +22,7 @@ import (
 	"os"
 	"path/filepath"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/utils/pointer"
diff --git a/test/e2e/cluster_upgrade_runtimesdk.go b/test/e2e/cluster_upgrade_runtimesdk.go
index 46b49bc49..4453c7d63 100644
--- a/test/e2e/cluster_upgrade_runtimesdk.go
+++ b/test/e2e/cluster_upgrade_runtimesdk.go
@@ -24,7 +24,7 @@ import (
 	"strings"
 	"time"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	"github.com/pkg/errors"
 	appsv1 "k8s.io/api/apps/v1"
diff --git a/test/e2e/cluster_upgrade_runtimesdk_test.go b/test/e2e/cluster_upgrade_runtimesdk_test.go
index 0ce1d79dd..f28ee288f 100644
--- a/test/e2e/cluster_upgrade_runtimesdk_test.go
+++ b/test/e2e/cluster_upgrade_runtimesdk_test.go
@@ -21,7 +21,7 @@ package e2e
 
 import (
 	"github.com/blang/semver"
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	"k8s.io/utils/pointer"
 )
diff --git a/test/e2e/cluster_upgrade_test.go b/test/e2e/cluster_upgrade_test.go
index 31d177f53..4b565cf7d 100644
--- a/test/e2e/cluster_upgrade_test.go
+++ b/test/e2e/cluster_upgrade_test.go
@@ -21,7 +21,7 @@ package e2e
 
 import (
 	"github.com/blang/semver"
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	"k8s.io/utils/pointer"
 )
diff --git a/test/e2e/clusterclass_changes.go b/test/e2e/clusterclass_changes.go
index c32e644e2..003961c35 100644
--- a/test/e2e/clusterclass_changes.go
+++ b/test/e2e/clusterclass_changes.go
@@ -23,7 +23,7 @@ import (
 	"path/filepath"
 	"strings"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	"github.com/pkg/errors"
 	corev1 "k8s.io/api/core/v1"
diff --git a/test/e2e/clusterclass_changes_test.go b/test/e2e/clusterclass_changes_test.go
index 824204f08..81a79c12c 100644
--- a/test/e2e/clusterclass_changes_test.go
+++ b/test/e2e/clusterclass_changes_test.go
@@ -20,7 +20,7 @@ limitations under the License.
 package e2e
 
 import (
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 )
 
 var _ = Describe("When testing ClusterClass changes [ClusterClass]", func() {
@@ -31,7 +31,7 @@ var _ = Describe("When testing ClusterClass changes [ClusterClass]", func() {
 			BootstrapClusterProxy: bootstrapClusterProxy,
 			ArtifactFolder:        artifactFolder,
 			SkipCleanup:           skipCleanup,
-			Flavor:                "topology",
+			Flavor:                "topology-changes",
 			// ModifyControlPlaneFields are the ControlPlane fields which will be set on the
 			// ControlPlaneTemplate of the ClusterClass after the initial Cluster creation.
 			// The test verifies that these fields are rolled out to the ControlPlane.
diff --git a/test/e2e/clusterctl_upgrade.go b/test/e2e/clusterctl_upgrade.go
index 8eec62783..38ffde9d6 100644
--- a/test/e2e/clusterctl_upgrade.go
+++ b/test/e2e/clusterctl_upgrade.go
@@ -26,7 +26,7 @@ import (
 	"runtime"
 	"strings"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
diff --git a/test/e2e/clusterctl_upgrade_test.go b/test/e2e/clusterctl_upgrade_test.go
index 77e163a43..0ece757f3 100644
--- a/test/e2e/clusterctl_upgrade_test.go
+++ b/test/e2e/clusterctl_upgrade_test.go
@@ -20,7 +20,7 @@ limitations under the License.
 package e2e
 
 import (
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 )
 
 var _ = Describe("When testing clusterctl upgrades [clusterctl-Upgrade]", func() {
diff --git a/test/e2e/common.go b/test/e2e/common.go
index fb95c909a..fba0a28ab 100644
--- a/test/e2e/common.go
+++ b/test/e2e/common.go
@@ -22,7 +22,7 @@ import (
 	"path/filepath"
 
 	"github.com/blang/semver"
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	"github.com/onsi/gomega/types"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/klog/v2"
diff --git a/test/e2e/config/docker.yaml b/test/e2e/config/docker.yaml
index 36a89ddaf..86daf1bfc 100644
--- a/test/e2e/config/docker.yaml
+++ b/test/e2e/config/docker.yaml
@@ -195,6 +195,7 @@ providers:
     - sourcePath: "../data/infrastructure-docker/v1beta1/cluster-template-kcp-scale-in.yaml"
     - sourcePath: "../data/infrastructure-docker/v1beta1/cluster-template-ipv6.yaml"
     - sourcePath: "../data/infrastructure-docker/v1beta1/cluster-template-topology.yaml"
+    - sourcePath: "../data/infrastructure-docker/v1beta1/cluster-template-topology-changes.yaml"
     - sourcePath: "../data/infrastructure-docker/v1beta1/cluster-template-ignition.yaml"
     - sourcePath: "../data/infrastructure-docker/v1beta1/clusterclass-quick-start.yaml"
     - sourcePath: "../data/infrastructure-docker/v1beta1/clusterclass-quick-start-runtimesdk.yaml"
diff --git a/test/e2e/data/infrastructure-docker/v1beta1/bases/cluster-with-topology.yaml b/test/e2e/data/infrastructure-docker/v1beta1/bases/cluster-with-topology.yaml
index 0498dc57f..678e70505 100644
--- a/test/e2e/data/infrastructure-docker/v1beta1/bases/cluster-with-topology.yaml
+++ b/test/e2e/data/infrastructure-docker/v1beta1/bases/cluster-with-topology.yaml
@@ -17,13 +17,13 @@ spec:
     version: "${KUBERNETES_VERSION}"
     controlPlane:
       metadata: {}
-      nodeDeletionTimeout: "30s"
+      nodeDrainTimeout: "30s"
       replicas: ${CONTROL_PLANE_MACHINE_COUNT}
     workers:
       machineDeployments:
       - class: "default-worker"
         name: "md-0"
-        nodeDeletionTimeout: "30s"
+        nodeDrainTimeout: "30s"
         replicas: ${WORKER_MACHINE_COUNT}
         failureDomain: fd4
     variables:
diff --git a/test/e2e/data/infrastructure-docker/v1beta1/cluster-template-topology-changes/kustomization.yaml b/test/e2e/data/infrastructure-docker/v1beta1/cluster-template-topology-changes/kustomization.yaml
new file mode 100644
index 000000000..0eba38315
--- /dev/null
+++ b/test/e2e/data/infrastructure-docker/v1beta1/cluster-template-topology-changes/kustomization.yaml
@@ -0,0 +1,10 @@
+resources:
+  - ../bases/cluster-with-topology.yaml
+  - ../bases/crs.yaml
+
+patches:
+  - path: ./node-drain-timeout.yaml
+    target:
+      group: cluster.x-k8s.io
+      version: v1beta1
+      kind: Cluster
\ No newline at end of file
diff --git a/test/e2e/data/infrastructure-docker/v1beta1/cluster-template-topology-changes/node-drain-timeout.yaml b/test/e2e/data/infrastructure-docker/v1beta1/cluster-template-topology-changes/node-drain-timeout.yaml
new file mode 100644
index 000000000..4491541da
--- /dev/null
+++ b/test/e2e/data/infrastructure-docker/v1beta1/cluster-template-topology-changes/node-drain-timeout.yaml
@@ -0,0 +1,2 @@
+- op: remove
+  path: /spec/topology/controlPlane/nodeDrainTimeout
\ No newline at end of file
diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go
index 492d67462..b519dd70b 100644
--- a/test/e2e/e2e_suite_test.go
+++ b/test/e2e/e2e_suite_test.go
@@ -27,7 +27,7 @@ import (
 	"strings"
 	"testing"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
@@ -110,7 +110,8 @@ func TestE2E(t *testing.T) {
 		defer w.Close()
 	}
 
-	RunSpecs(t, "capi-e2e")
+	junitReporter := framework.CreateJUnitReporterForProw(artifactFolder)
+	RunSpecsWithDefaultAndCustomReporters(t, "capi-e2e", []Reporter{junitReporter})
 }
 
 // Using a SynchronizedBeforeSuite for controlling how to create resources shared across ParallelNodes (~ginkgo threads).
diff --git a/test/e2e/internal/log/log.go b/test/e2e/internal/log/log.go
index 77bbe6c0a..c672fcb41 100644
--- a/test/e2e/internal/log/log.go
+++ b/test/e2e/internal/log/log.go
@@ -19,7 +19,7 @@ package log
 import (
 	"fmt"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 )
 
 func Logf(format string, a ...interface{}) {
diff --git a/test/e2e/k8s_conformance.go b/test/e2e/k8s_conformance.go
index 27ebb5569..4aa63d0d6 100644
--- a/test/e2e/k8s_conformance.go
+++ b/test/e2e/k8s_conformance.go
@@ -22,7 +22,7 @@ import (
 	"os"
 	"path/filepath"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/utils/pointer"
diff --git a/test/e2e/k8s_conformance_test.go b/test/e2e/k8s_conformance_test.go
index 1a06d1ee9..6f618316e 100644
--- a/test/e2e/k8s_conformance_test.go
+++ b/test/e2e/k8s_conformance_test.go
@@ -20,7 +20,7 @@ limitations under the License.
 package e2e
 
 import (
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 )
 
 var _ = Describe("When testing K8S conformance [Conformance]", func() {
diff --git a/test/e2e/kcp_adoption.go b/test/e2e/kcp_adoption.go
index 5fa2152f4..5318408d4 100644
--- a/test/e2e/kcp_adoption.go
+++ b/test/e2e/kcp_adoption.go
@@ -24,7 +24,7 @@ import (
 	"strings"
 	"time"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/labels"
diff --git a/test/e2e/kcp_adoption_test.go b/test/e2e/kcp_adoption_test.go
index 7b82f64ad..acdd961af 100644
--- a/test/e2e/kcp_adoption_test.go
+++ b/test/e2e/kcp_adoption_test.go
@@ -20,7 +20,7 @@ limitations under the License.
 package e2e
 
 import (
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 )
 
 var _ = Describe("When testing KCP adoption", func() {
diff --git a/test/e2e/machine_pool.go b/test/e2e/machine_pool.go
index 9ed245901..6bc020bd4 100644
--- a/test/e2e/machine_pool.go
+++ b/test/e2e/machine_pool.go
@@ -25,7 +25,7 @@ import (
 	"os"
 	"path/filepath"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/utils/pointer"
diff --git a/test/e2e/machine_pool_test.go b/test/e2e/machine_pool_test.go
index c903c57e5..71cf604a6 100644
--- a/test/e2e/machine_pool_test.go
+++ b/test/e2e/machine_pool_test.go
@@ -20,7 +20,7 @@ limitations under the License.
 package e2e
 
 import (
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 )
 
 var _ = Describe("When testing MachinePools", func() {
diff --git a/test/e2e/md_rollout.go b/test/e2e/md_rollout.go
index 2182d3e38..9288a30b5 100644
--- a/test/e2e/md_rollout.go
+++ b/test/e2e/md_rollout.go
@@ -22,7 +22,7 @@ import (
 	"os"
 	"path/filepath"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/utils/pointer"
diff --git a/test/e2e/md_rollout_test.go b/test/e2e/md_rollout_test.go
index b8ceb49ec..aa0937f7f 100644
--- a/test/e2e/md_rollout_test.go
+++ b/test/e2e/md_rollout_test.go
@@ -20,7 +20,7 @@ limitations under the License.
 package e2e
 
 import (
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 )
 
 var _ = Describe("When testing MachineDeployment rolling upgrades", func() {
diff --git a/test/e2e/md_scale.go b/test/e2e/md_scale.go
index dfd2e7888..da00056e4 100644
--- a/test/e2e/md_scale.go
+++ b/test/e2e/md_scale.go
@@ -22,7 +22,7 @@ import (
 	"os"
 	"path/filepath"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/utils/pointer"
diff --git a/test/e2e/md_scale_test.go b/test/e2e/md_scale_test.go
index ec493d105..690d092c9 100644
--- a/test/e2e/md_scale_test.go
+++ b/test/e2e/md_scale_test.go
@@ -20,7 +20,7 @@ limitations under the License.
 package e2e
 
 import (
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 )
 
 var _ = Describe("When testing MachineDeployment scale out/in", func() {
diff --git a/test/e2e/mhc_remediations.go b/test/e2e/mhc_remediations.go
index a190c0f43..37a5eb2c3 100644
--- a/test/e2e/mhc_remediations.go
+++ b/test/e2e/mhc_remediations.go
@@ -22,7 +22,7 @@ import (
 	"os"
 	"path/filepath"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/utils/pointer"
diff --git a/test/e2e/mhc_remediations_test.go b/test/e2e/mhc_remediations_test.go
index 66471901d..93738a19a 100644
--- a/test/e2e/mhc_remediations_test.go
+++ b/test/e2e/mhc_remediations_test.go
@@ -20,7 +20,7 @@ limitations under the License.
 package e2e
 
 import (
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 )
 
 var _ = Describe("When testing unhealthy machines remediation", func() {
diff --git a/test/e2e/node_drain_timeout.go b/test/e2e/node_drain_timeout.go
index 54e1c4164..a7026099f 100644
--- a/test/e2e/node_drain_timeout.go
+++ b/test/e2e/node_drain_timeout.go
@@ -23,7 +23,7 @@ import (
 	"path/filepath"
 	"time"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/test/e2e/node_drain_timeout_test.go b/test/e2e/node_drain_timeout_test.go
index 9d74efe50..79a99a2a7 100644
--- a/test/e2e/node_drain_timeout_test.go
+++ b/test/e2e/node_drain_timeout_test.go
@@ -20,7 +20,7 @@ limitations under the License.
 package e2e
 
 import (
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 )
 
 var _ = Describe("When testing node drain timeout", func() {
diff --git a/test/e2e/quick_start.go b/test/e2e/quick_start.go
index d19f83106..037d4806d 100644
--- a/test/e2e/quick_start.go
+++ b/test/e2e/quick_start.go
@@ -22,7 +22,7 @@ import (
 	"os"
 	"path/filepath"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/utils/pointer"
diff --git a/test/e2e/quick_start_test.go b/test/e2e/quick_start_test.go
index bb79feee9..4dba2c525 100644
--- a/test/e2e/quick_start_test.go
+++ b/test/e2e/quick_start_test.go
@@ -20,7 +20,7 @@ limitations under the License.
 package e2e
 
 import (
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	"k8s.io/utils/pointer"
 )
 
diff --git a/test/e2e/self_hosted.go b/test/e2e/self_hosted.go
index a5f149bdc..9f8d4b6e5 100644
--- a/test/e2e/self_hosted.go
+++ b/test/e2e/self_hosted.go
@@ -23,7 +23,7 @@ import (
 	"os"
 	"path/filepath"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
diff --git a/test/e2e/self_hosted_test.go b/test/e2e/self_hosted_test.go
index cb0c042b6..ef4aa1f87 100644
--- a/test/e2e/self_hosted_test.go
+++ b/test/e2e/self_hosted_test.go
@@ -20,7 +20,7 @@ limitations under the License.
 package e2e
 
 import (
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 )
 
 var _ = Describe("When testing Cluster API working on self-hosted clusters", func() {
diff --git a/test/framework/alltypes_helpers.go b/test/framework/alltypes_helpers.go
index b28f3e356..4a05e2637 100644
--- a/test/framework/alltypes_helpers.go
+++ b/test/framework/alltypes_helpers.go
@@ -24,7 +24,7 @@ import (
 	"path"
 	"path/filepath"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
diff --git a/test/framework/cluster_helpers.go b/test/framework/cluster_helpers.go
index bdd628b2a..ca287487c 100644
--- a/test/framework/cluster_helpers.go
+++ b/test/framework/cluster_helpers.go
@@ -19,7 +19,7 @@ package framework
 import (
 	"context"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
diff --git a/test/framework/clusterctl/client.go b/test/framework/clusterctl/client.go
index 28fbd0a1f..55bae04d8 100644
--- a/test/framework/clusterctl/client.go
+++ b/test/framework/clusterctl/client.go
@@ -24,7 +24,7 @@ import (
 	"path/filepath"
 	"strings"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 
 	clusterctlclient "sigs.k8s.io/cluster-api/cmd/clusterctl/client"
diff --git a/test/framework/clusterresourceset_helpers.go b/test/framework/clusterresourceset_helpers.go
index 2cadcf6ad..6ef51f1b9 100644
--- a/test/framework/clusterresourceset_helpers.go
+++ b/test/framework/clusterresourceset_helpers.go
@@ -20,7 +20,7 @@ import (
 	"context"
 	"fmt"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/types"
diff --git a/test/framework/control_plane.go b/test/framework/control_plane.go
index fa0f23a5c..e10e48a2e 100644
--- a/test/framework/control_plane.go
+++ b/test/framework/control_plane.go
@@ -19,7 +19,7 @@ package framework
 import (
 	"context"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
diff --git a/test/framework/controlplane_helpers.go b/test/framework/controlplane_helpers.go
index baacc92c8..c3f5e191c 100644
--- a/test/framework/controlplane_helpers.go
+++ b/test/framework/controlplane_helpers.go
@@ -20,7 +20,7 @@ import (
 	"context"
 	"fmt"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	. "github.com/onsi/gomega/gstruct"
 	"github.com/pkg/errors"
diff --git a/test/framework/daemonset_helpers.go b/test/framework/daemonset_helpers.go
index a6fe02d27..97f973c66 100644
--- a/test/framework/daemonset_helpers.go
+++ b/test/framework/daemonset_helpers.go
@@ -20,7 +20,7 @@ import (
 	"context"
 
 	"github.com/blang/semver"
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	appsv1 "k8s.io/api/apps/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/test/framework/deployment_helpers.go b/test/framework/deployment_helpers.go
index 717908671..213dbeccb 100644
--- a/test/framework/deployment_helpers.go
+++ b/test/framework/deployment_helpers.go
@@ -28,7 +28,7 @@ import (
 	"strings"
 	"time"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
diff --git a/test/framework/ginkgoextensions/output.go b/test/framework/ginkgoextensions/output.go
index 530dc2f85..1dfc19046 100644
--- a/test/framework/ginkgoextensions/output.go
+++ b/test/framework/ginkgoextensions/output.go
@@ -23,7 +23,7 @@ import (
 	"os"
 	"time"
 
-	"github.com/onsi/ginkgo/v2"
+	"github.com/onsi/ginkgo"
 	"github.com/pkg/errors"
 )
 
@@ -35,6 +35,10 @@ func Byf(format string, a ...interface{}) {
 	ginkgo.By(fmt.Sprintf(format, a...))
 }
 
+type writerRedirecter interface {
+	AndRedirectTo(writer io.Writer)
+}
+
 // EnableFileLogging enables additional file logging.
 // Logs are written to the given path with timestamps.
 func EnableFileLogging(path string) (io.WriteCloser, error) {
@@ -43,7 +47,12 @@ func EnableFileLogging(path string) (io.WriteCloser, error) {
 		return nil, errors.Wrapf(err, "failed to create fileWriter")
 	}
 
-	ginkgo.GinkgoWriter.TeeTo(w)
+	ginkgoWriter, ok := ginkgo.GinkgoWriter.(writerRedirecter)
+	if !ok {
+		return nil, errors.Errorf("GinkgoWriter does not have an AndRedirectTo method")
+	}
+
+	ginkgoWriter.AndRedirectTo(w)
 
 	return w, nil
 }
diff --git a/test/framework/internal/log/log.go b/test/framework/internal/log/log.go
index f909ff0e3..9ee0fe7ca 100644
--- a/test/framework/internal/log/log.go
+++ b/test/framework/internal/log/log.go
@@ -20,7 +20,7 @@ package log
 import (
 	"fmt"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 )
 
 func Logf(format string, a ...interface{}) {
diff --git a/test/framework/kubetest/run.go b/test/framework/kubetest/run.go
index 16339b475..eb3a3044a 100644
--- a/test/framework/kubetest/run.go
+++ b/test/framework/kubetest/run.go
@@ -27,7 +27,7 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/onsi/ginkgo/v2"
+	"github.com/onsi/ginkgo"
 	"github.com/pkg/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/discovery"
diff --git a/test/framework/machinedeployment_helpers.go b/test/framework/machinedeployment_helpers.go
index 345f4bc28..29531fa0f 100644
--- a/test/framework/machinedeployment_helpers.go
+++ b/test/framework/machinedeployment_helpers.go
@@ -20,7 +20,7 @@ import (
 	"context"
 	"fmt"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	"github.com/pkg/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/test/framework/machinehealthcheck_helpers.go b/test/framework/machinehealthcheck_helpers.go
index 53a2fe8d9..3cac54ecc 100644
--- a/test/framework/machinehealthcheck_helpers.go
+++ b/test/framework/machinehealthcheck_helpers.go
@@ -21,7 +21,7 @@ import (
 	"fmt"
 	"time"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/test/framework/machinepool_helpers.go b/test/framework/machinepool_helpers.go
index 0735917e1..3123707aa 100644
--- a/test/framework/machinepool_helpers.go
+++ b/test/framework/machinepool_helpers.go
@@ -22,7 +22,7 @@ import (
 	"strings"
 
 	"github.com/blang/semver"
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	"github.com/pkg/errors"
 	corev1 "k8s.io/api/core/v1"
diff --git a/test/framework/machines.go b/test/framework/machines.go
index 53df58910..eb9a1e5d8 100644
--- a/test/framework/machines.go
+++ b/test/framework/machines.go
@@ -19,7 +19,7 @@ package framework
 import (
 	"context"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/klog/v2"
diff --git a/test/framework/namespace_helpers.go b/test/framework/namespace_helpers.go
index 0806139cd..0446cbb29 100644
--- a/test/framework/namespace_helpers.go
+++ b/test/framework/namespace_helpers.go
@@ -24,7 +24,7 @@ import (
 	"path/filepath"
 	"time"
 
-	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
diff --git a/test/framework/suite_helpers.go b/test/framework/suite_helpers.go
index 4db07dc7f..7d5a82289 100644
--- a/test/framework/suite_helpers.go
+++ b/test/framework/suite_helpers.go
@@ -17,12 +17,16 @@ limitations under the License.
 package framework
 
 import (
+	"fmt"
 	"os"
 	"os/exec"
 	"path"
 	"path/filepath"
 	"strings"
 
+	"github.com/onsi/ginkgo/config"
+	"github.com/onsi/ginkgo/reporters"
+
 	. "sigs.k8s.io/cluster-api/test/framework/ginkgoextensions"
 )
 
@@ -72,6 +76,14 @@ func ResolveArtifactsDirectory(input string) string {
 	return path.Join(rootDir, "_artifacts")
 }
 
+// CreateJUnitReporterForProw sets up Ginkgo to create JUnit outputs compatible
+// with Prow.
+func CreateJUnitReporterForProw(artifactsDirectory string) *reporters.JUnitReporter {
+	junitPath := filepath.Join(artifactsDirectory, fmt.Sprintf("junit.e2e_suite.%d.xml", config.GinkgoConfig.ParallelNode))
+
+	return reporters.NewJUnitReporter(junitPath)
+}
+
 // CompleteCommand prints a command before running it. Acts as a helper function.
 // privateArgs when true will not print arguments.
 func CompleteCommand(cmd *exec.Cmd, desc string, privateArgs bool) *exec.Cmd {
diff --git a/test/go.mod b/test/go.mod
index 73f080031..e7e101b08 100644
--- a/test/go.mod
+++ b/test/go.mod
@@ -10,8 +10,8 @@ require (
 	github.com/docker/go-connections v0.4.0
 	github.com/flatcar-linux/ignition v0.36.1
 	github.com/go-logr/logr v1.2.0
-	github.com/onsi/ginkgo/v2 v2.1.4
-	github.com/onsi/gomega v1.20.0
+	github.com/onsi/ginkgo v1.16.5
+	github.com/onsi/gomega v1.18.1
 	github.com/pkg/errors v0.9.1
 	github.com/spf13/pflag v1.0.5
 	github.com/vincent-petithory/dataurl v1.0.0
@@ -88,6 +88,7 @@ require (
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/morikuni/aec v1.0.0 // indirect
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+	github.com/nxadm/tail v1.4.8 // indirect
 	github.com/opencontainers/go-digest v1.0.0 // indirect
 	github.com/opencontainers/image-spec v1.0.2 // indirect
 	github.com/pelletier/go-toml v1.9.5 // indirect
@@ -110,9 +111,9 @@ require (
 	go.uber.org/multierr v1.6.0 // indirect
 	go.uber.org/zap v1.19.1 // indirect
 	golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
-	golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect
+	golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 // indirect
 	golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb // indirect
-	golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
+	golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
 	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
@@ -121,8 +122,9 @@ require (
 	google.golang.org/protobuf v1.28.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/ini.v1 v1.66.4 // indirect
+	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
-	gopkg.in/yaml.v3 v3.0.1 // indirect
+	gopkg.in/yaml.v3 v3.0.0 // indirect
 	k8s.io/apiserver v0.24.2 // indirect
 	k8s.io/cluster-bootstrap v0.24.0 // indirect
 	k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect
diff --git a/test/go.sum b/test/go.sum
index 145ff6e2c..c18af6d99 100644
--- a/test/go.sum
+++ b/test/go.sum
@@ -552,18 +552,14 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k
 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
 github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
 github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
+github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ=
 github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
-github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
-github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY=
-github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
 github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
-github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
-github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q=
-github.com/onsi/gomega v1.20.0/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
 github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
@@ -885,8 +881,8 @@ golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su
 golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
-golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 h1:Yqz/iviulwKwAREEeUd3nbBFn0XuyJqkoft2IlrvOhc=
+golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1009,14 +1005,11 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1101,7 +1094,6 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
-golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1320,9 +1312,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
 gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
 gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
 gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=