Repository: kubernetes-sigs/cluster-api
Pull request #4373 (2021-03-24 18:37:35)

🐛 Fix annotations.AddAnnotations (if annotations has been nil before)
**What this PR does / why we need it**:

Previously the `AddAnnotations` func only added the annotations in a local var in case the annotations map in the object has been nil before calling the func. 

As far as I could see this func is used in machine and machinepool controller when adding annotations to node resources. I suppose we didn't hit this bug because in those cases the annotation map is never nil.


**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Fixes #


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

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

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

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

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

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["neolit123"]} -->
k8s-ci-robot: @sbueringer: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-test-main | b172b790ed99a6e831dd38c86a2d6b266ca11895 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/4373/pull-cluster-api-test-main/1374792532310888448) | `/test pull-cluster-api-test-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=4373). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asbueringer). 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 -->

Patch:
diff --git a/Dockerfile b/Dockerfile
index 8a413769b..5ca18d8ef 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -15,7 +15,7 @@
 # limitations under the License.
 
 # Build the manager binary
-FROM golang:1.16.2 as builder
+FROM golang:1.16.0 as builder
 WORKDIR /workspace
 
 # Run this with docker build --build_arg goproxy=$(go env GOPROXY) to override the goproxy
diff --git a/Makefile b/Makefile
index b22aba5c9..7310f2d7c 100644
--- a/Makefile
+++ b/Makefile
@@ -356,7 +356,7 @@ modules: ## Runs go mod to ensure modules are up to date.
 .PHONY: docker-pull-prerequisites
 docker-pull-prerequisites:
 	docker pull docker.io/docker/dockerfile:1.1-experimental
-	docker pull docker.io/library/golang:1.16.2
+	docker pull docker.io/library/golang:1.16.0
 	docker pull gcr.io/distroless/static:latest
 
 .PHONY: docker-build
@@ -525,7 +525,7 @@ release-binary: $(RELEASE_DIR)
 		-e GOARCH=$(GOARCH) \
 		-v "$$(pwd):/workspace$(DOCKER_VOL_OPTS)" \
 		-w /workspace \
-		golang:1.16.2 \
+		golang:1.16.0 \
 		go build -a -ldflags "$(LDFLAGS) -extldflags '-static'" \
 		-o $(RELEASE_DIR)/$(notdir $(RELEASE_BINARY))-$(GOOS)-$(GOARCH) $(RELEASE_BINARY)
 
diff --git a/Tiltfile b/Tiltfile
index bddceb24a..cb66b6ee0 100644
--- a/Tiltfile
+++ b/Tiltfile
@@ -126,7 +126,7 @@ def load_provider_tiltfiles():
 
 tilt_helper_dockerfile_header = """
 # Tilt image
-FROM golang:1.16.2 as tilt-helper
+FROM golang:1.16.0 as tilt-helper
 # Support live reloading with Tilt
 RUN wget --output-document /restart.sh --quiet https://raw.githubusercontent.com/windmilleng/rerun-process-wrapper/master/restart.sh  && \
     wget --output-document /start.sh --quiet https://raw.githubusercontent.com/windmilleng/rerun-process-wrapper/master/start.sh && \
diff --git a/api/v1alpha3/common_types.go b/api/v1alpha3/common_types.go
index b63af3eb3..bcace3cc3 100644
--- a/api/v1alpha3/common_types.go
+++ b/api/v1alpha3/common_types.go
@@ -105,8 +105,6 @@ type ObjectMeta struct {
 	// Cannot be updated.
 	// More info: http://kubernetes.io/docs/user-guide/identifiers#names
 	// +optional
-	//
-	// Deprecated: This field has no function and is going to be removed in a next release.
 	Name string `json:"name,omitempty"`
 
 	// GenerateName is an optional prefix, used by the server, to generate a unique
@@ -125,8 +123,6 @@ type ObjectMeta struct {
 	// Applied only if Name is not specified.
 	// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency
 	// +optional
-	//
-	// Deprecated: This field has no function and is going to be removed in a next release.
 	GenerateName string `json:"generateName,omitempty"`
 
 	// Namespace defines the space within each name must be unique. An empty namespace is
@@ -138,8 +134,6 @@ type ObjectMeta struct {
 	// Cannot be updated.
 	// More info: http://kubernetes.io/docs/user-guide/namespaces
 	// +optional
-	//
-	// Deprecated: This field has no function and is going to be removed in a next release.
 	Namespace string `json:"namespace,omitempty"`
 
 	// Map of string keys and values that can be used to organize and categorize
@@ -163,7 +157,5 @@ type ObjectMeta struct {
 	// +optional
 	// +patchMergeKey=uid
 	// +patchStrategy=merge
-	//
-	// Deprecated: This field has no function and is going to be removed in a next release.
 	OwnerReferences []metav1.OwnerReference `json:"ownerReferences,omitempty" patchStrategy:"merge" patchMergeKey:"uid"`
 }
diff --git a/api/v1alpha3/conversion.go b/api/v1alpha3/conversion.go
index 62f251e2e..c693abd72 100644
--- a/api/v1alpha3/conversion.go
+++ b/api/v1alpha3/conversion.go
@@ -233,7 +233,3 @@ func Convert_v1alpha4_MachineHealthCheckSpec_To_v1alpha3_MachineHealthCheckSpec(
 func Convert_v1alpha3_ClusterStatus_To_v1alpha4_ClusterStatus(in *ClusterStatus, out *v1alpha4.ClusterStatus, s apiconversion.Scope) error {
 	return autoConvert_v1alpha3_ClusterStatus_To_v1alpha4_ClusterStatus(in, out, s)
 }
-
-func Convert_v1alpha3_ObjectMeta_To_v1alpha4_ObjectMeta(in *ObjectMeta, out *v1alpha4.ObjectMeta, s apiconversion.Scope) error {
-	return autoConvert_v1alpha3_ObjectMeta_To_v1alpha4_ObjectMeta(in, out, s)
-}
diff --git a/api/v1alpha3/zz_generated.conversion.go b/api/v1alpha3/zz_generated.conversion.go
index 5ea7422da..8122dd24a 100644
--- a/api/v1alpha3/zz_generated.conversion.go
+++ b/api/v1alpha3/zz_generated.conversion.go
@@ -319,6 +319,11 @@ func RegisterConversions(s *runtime.Scheme) error {
 	}); err != nil {
 		return err
 	}
+	if err := s.AddGeneratedConversionFunc((*ObjectMeta)(nil), (*v1alpha4.ObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha3_ObjectMeta_To_v1alpha4_ObjectMeta(a.(*ObjectMeta), b.(*v1alpha4.ObjectMeta), scope)
+	}); err != nil {
+		return err
+	}
 	if err := s.AddGeneratedConversionFunc((*v1alpha4.ObjectMeta)(nil), (*ObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error {
 		return Convert_v1alpha4_ObjectMeta_To_v1alpha3_ObjectMeta(a.(*v1alpha4.ObjectMeta), b.(*ObjectMeta), scope)
 	}); err != nil {
@@ -344,11 +349,6 @@ func RegisterConversions(s *runtime.Scheme) error {
 	}); err != nil {
 		return err
 	}
-	if err := s.AddConversionFunc((*ObjectMeta)(nil), (*v1alpha4.ObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error {
-		return Convert_v1alpha3_ObjectMeta_To_v1alpha4_ObjectMeta(a.(*ObjectMeta), b.(*v1alpha4.ObjectMeta), scope)
-	}); err != nil {
-		return err
-	}
 	if err := s.AddConversionFunc((*v1alpha4.MachineHealthCheckSpec)(nil), (*MachineHealthCheckSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
 		return Convert_v1alpha4_MachineHealthCheckSpec_To_v1alpha3_MachineHealthCheckSpec(a.(*v1alpha4.MachineHealthCheckSpec), b.(*MachineHealthCheckSpec), scope)
 	}); err != nil {
@@ -1331,18 +1331,27 @@ func Convert_v1alpha4_NetworkRanges_To_v1alpha3_NetworkRanges(in *v1alpha4.Netwo
 }
 
 func autoConvert_v1alpha3_ObjectMeta_To_v1alpha4_ObjectMeta(in *ObjectMeta, out *v1alpha4.ObjectMeta, s conversion.Scope) error {
-	// WARNING: in.Name requires manual conversion: does not exist in peer-type
-	// WARNING: in.GenerateName requires manual conversion: does not exist in peer-type
-	// WARNING: in.Namespace requires manual conversion: does not exist in peer-type
+	out.Name = in.Name
+	out.GenerateName = in.GenerateName
+	out.Namespace = in.Namespace
 	out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels))
 	out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations))
-	// WARNING: in.OwnerReferences requires manual conversion: does not exist in peer-type
+	out.OwnerReferences = *(*[]metav1.OwnerReference)(unsafe.Pointer(&in.OwnerReferences))
 	return nil
 }
 
+// Convert_v1alpha3_ObjectMeta_To_v1alpha4_ObjectMeta is an autogenerated conversion function.
+func Convert_v1alpha3_ObjectMeta_To_v1alpha4_ObjectMeta(in *ObjectMeta, out *v1alpha4.ObjectMeta, s conversion.Scope) error {
+	return autoConvert_v1alpha3_ObjectMeta_To_v1alpha4_ObjectMeta(in, out, s)
+}
+
 func autoConvert_v1alpha4_ObjectMeta_To_v1alpha3_ObjectMeta(in *v1alpha4.ObjectMeta, out *ObjectMeta, s conversion.Scope) error {
+	out.Name = in.Name
+	out.GenerateName = in.GenerateName
+	out.Namespace = in.Namespace
 	out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels))
 	out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations))
+	out.OwnerReferences = *(*[]metav1.OwnerReference)(unsafe.Pointer(&in.OwnerReferences))
 	return nil
 }
 
diff --git a/api/v1alpha4/common_types.go b/api/v1alpha4/common_types.go
index 8083f0e50..576c1d312 100644
--- a/api/v1alpha4/common_types.go
+++ b/api/v1alpha4/common_types.go
@@ -18,6 +18,7 @@ package v1alpha4
 
 import (
 	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 const (
@@ -128,6 +129,44 @@ type MachineAddresses []MachineAddress
 // In future versions, controller-tools@v2 might allow overriding the type and validation for embedded
 // types. When that happens, this hack should be revisited.
 type ObjectMeta struct {
+	// Name must be unique within a namespace. Is required when creating resources, although
+	// some resources may allow a client to request the generation of an appropriate name
+	// automatically. Name is primarily intended for creation idempotence and configuration
+	// definition.
+	// Cannot be updated.
+	// More info: http://kubernetes.io/docs/user-guide/identifiers#names
+	// +optional
+	Name string `json:"name,omitempty"`
+
+	// GenerateName is an optional prefix, used by the server, to generate a unique
+	// name ONLY IF the Name field has not been provided.
+	// If this field is used, the name returned to the client will be different
+	// than the name passed. This value will also be combined with a unique suffix.
+	// The provided value has the same validation rules as the Name field,
+	// and may be truncated by the length of the suffix required to make the value
+	// unique on the server.
+	//
+	// If this field is specified and the generated name exists, the server will
+	// NOT return a 409 - instead, it will either return 201 Created or 500 with Reason
+	// ServerTimeout indicating a unique name could not be found in the time allotted, and the client
+	// should retry (optionally after the time indicated in the Retry-After header).
+	//
+	// Applied only if Name is not specified.
+	// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency
+	// +optional
+	GenerateName string `json:"generateName,omitempty"`
+
+	// Namespace defines the space within each name must be unique. An empty namespace is
+	// equivalent to the "default" namespace, but "default" is the canonical representation.
+	// Not all objects are required to be scoped to a namespace - the value of this field for
+	// those objects will be empty.
+	//
+	// Must be a DNS_LABEL.
+	// Cannot be updated.
+	// More info: http://kubernetes.io/docs/user-guide/namespaces
+	// +optional
+	Namespace string `json:"namespace,omitempty"`
+
 	// Map of string keys and values that can be used to organize and categorize
 	// (scope and select) objects. May match selectors of replication controllers
 	// and services.
@@ -141,4 +180,13 @@ type ObjectMeta struct {
 	// More info: http://kubernetes.io/docs/user-guide/annotations
 	// +optional
 	Annotations map[string]string `json:"annotations,omitempty"`
+
+	// List of objects depended by this object. If ALL objects in the list have
+	// been deleted, this object will be garbage collected. If this object is managed by a controller,
+	// then an entry in this list will point to this controller, with the controller field set to true.
+	// There cannot be more than one managing controller.
+	// +optional
+	// +patchMergeKey=uid
+	// +patchStrategy=merge
+	OwnerReferences []metav1.OwnerReference `json:"ownerReferences,omitempty" patchStrategy:"merge" patchMergeKey:"uid"`
 }
diff --git a/api/v1alpha4/zz_generated.deepcopy.go b/api/v1alpha4/zz_generated.deepcopy.go
index eeaa36285..93bf447a5 100644
--- a/api/v1alpha4/zz_generated.deepcopy.go
+++ b/api/v1alpha4/zz_generated.deepcopy.go
@@ -941,6 +941,13 @@ func (in *ObjectMeta) DeepCopyInto(out *ObjectMeta) {
 			(*out)[key] = val
 		}
 	}
+	if in.OwnerReferences != nil {
+		in, out := &in.OwnerReferences, &out.OwnerReferences
+		*out = make([]metav1.OwnerReference, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectMeta.
diff --git a/config/crd/bases/cluster.x-k8s.io_machinedeployments.yaml b/config/crd/bases/cluster.x-k8s.io_machinedeployments.yaml
index 24ab4b4b0..1262c7104 100644
--- a/config/crd/bases/cluster.x-k8s.io_machinedeployments.yaml
+++ b/config/crd/bases/cluster.x-k8s.io_machinedeployments.yaml
@@ -145,7 +145,7 @@ spec:
                         description: 'Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations'
                         type: object
                       generateName:
-                        description: "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. \n If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). \n Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency \n Deprecated: This field has no function and is going to be removed in a next release."
+                        description: "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. \n If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). \n Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency"
                         type: string
                       labels:
                         additionalProperties:
@@ -153,13 +153,13 @@ spec:
                         description: 'Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels'
                         type: object
                       name:
-                        description: "Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names \n Deprecated: This field has no function and is going to be removed in a next release."
+                        description: 'Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names'
                         type: string
                       namespace:
-                        description: "Namespace defines the space within each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. \n Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces \n Deprecated: This field has no function and is going to be removed in a next release."
+                        description: "Namespace defines the space within each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. \n Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces"
                         type: string
                       ownerReferences:
-                        description: "List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller. \n Deprecated: This field has no function and is going to be removed in a next release."
+                        description: List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.
                         items:
                           description: OwnerReference contains enough information to let you identify an owning object. An owning object must be in the same namespace as the dependent, or be cluster-scoped, so there is no namespace field.
                           properties:
@@ -454,11 +454,50 @@ spec:
                           type: string
                         description: 'Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations'
                         type: object
+                      generateName:
+                        description: "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. \n If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). \n Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency"
+                        type: string
                       labels:
                         additionalProperties:
                           type: string
                         description: 'Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels'
                         type: object
+                      name:
+                        description: 'Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names'
+                        type: string
+                      namespace:
+                        description: "Namespace defines the space within each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. \n Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces"
+                        type: string
+                      ownerReferences:
+                        description: List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.
+                        items:
+                          description: OwnerReference contains enough information to let you identify an owning object. An owning object must be in the same namespace as the dependent, or be cluster-scoped, so there is no namespace field.
+                          properties:
+                            apiVersion:
+                              description: API version of the referent.
+                              type: string
+                            blockOwnerDeletion:
+                              description: If true, AND if the owner has the "foregroundDeletion" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.
+                              type: boolean
+                            controller:
+                              description: If true, this reference points to the managing controller.
+                              type: boolean
+                            kind:
+                              description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+                              type: string
+                            name:
+                              description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names'
+                              type: string
+                            uid:
+                              description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids'
+                              type: string
+                          required:
+                          - apiVersion
+                          - kind
+                          - name
+                          - uid
+                          type: object
+                        type: array
                     type: object
                   spec:
                     description: 'Specification of the desired behavior of the machine. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status'
diff --git a/config/crd/bases/cluster.x-k8s.io_machinesets.yaml b/config/crd/bases/cluster.x-k8s.io_machinesets.yaml
index fd632ff7e..0190bad92 100644
--- a/config/crd/bases/cluster.x-k8s.io_machinesets.yaml
+++ b/config/crd/bases/cluster.x-k8s.io_machinesets.yaml
@@ -110,7 +110,7 @@ spec:
                         description: 'Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations'
                         type: object
                       generateName:
-                        description: "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. \n If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). \n Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency \n Deprecated: This field has no function and is going to be removed in a next release."
+                        description: "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. \n If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). \n Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency"
                         type: string
                       labels:
                         additionalProperties:
@@ -118,13 +118,13 @@ spec:
                         description: 'Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels'
                         type: object
                       name:
-                        description: "Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names \n Deprecated: This field has no function and is going to be removed in a next release."
+                        description: 'Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names'
                         type: string
                       namespace:
-                        description: "Namespace defines the space within each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. \n Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces \n Deprecated: This field has no function and is going to be removed in a next release."
+                        description: "Namespace defines the space within each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. \n Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces"
                         type: string
                       ownerReferences:
-                        description: "List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller. \n Deprecated: This field has no function and is going to be removed in a next release."
+                        description: List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.
                         items:
                           description: OwnerReference contains enough information to let you identify an owning object. An owning object must be in the same namespace as the dependent, or be cluster-scoped, so there is no namespace field.
                           properties:
@@ -374,11 +374,50 @@ spec:
                           type: string
                         description: 'Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations'
                         type: object
+                      generateName:
+                        description: "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. \n If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). \n Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency"
+                        type: string
                       labels:
                         additionalProperties:
                           type: string
                         description: 'Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels'
                         type: object
+                      name:
+                        description: 'Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names'
+                        type: string
+                      namespace:
+                        description: "Namespace defines the space within each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. \n Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces"
+                        type: string
+                      ownerReferences:
+                        description: List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.
+                        items:
+                          description: OwnerReference contains enough information to let you identify an owning object. An owning object must be in the same namespace as the dependent, or be cluster-scoped, so there is no namespace field.
+                          properties:
+                            apiVersion:
+                              description: API version of the referent.
+                              type: string
+                            blockOwnerDeletion:
+                              description: If true, AND if the owner has the "foregroundDeletion" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.
+                              type: boolean
+                            controller:
+                              description: If true, this reference points to the managing controller.
+                              type: boolean
+                            kind:
+                              description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+                              type: string
+                            name:
+                              description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names'
+                              type: string
+                            uid:
+                              description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids'
+                              type: string
+                          required:
+                          - apiVersion
+                          - kind
+                          - name
+                          - uid
+                          type: object
+                        type: array
                     type: object
                   spec:
                     description: 'Specification of the desired behavior of the machine. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status'
diff --git a/config/crd/bases/exp.cluster.x-k8s.io_machinepools.yaml b/config/crd/bases/exp.cluster.x-k8s.io_machinepools.yaml
index 3c6bbf09f..1426046e2 100644
--- a/config/crd/bases/exp.cluster.x-k8s.io_machinepools.yaml
+++ b/config/crd/bases/exp.cluster.x-k8s.io_machinepools.yaml
@@ -106,7 +106,7 @@ spec:
                         description: 'Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations'
                         type: object
                       generateName:
-                        description: "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. \n If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). \n Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency \n Deprecated: This field has no function and is going to be removed in a next release."
+                        description: "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. \n If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). \n Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency"
                         type: string
                       labels:
                         additionalProperties:
@@ -114,13 +114,13 @@ spec:
                         description: 'Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels'
                         type: object
                       name:
-                        description: "Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names \n Deprecated: This field has no function and is going to be removed in a next release."
+                        description: 'Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names'
                         type: string
                       namespace:
-                        description: "Namespace defines the space within each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. \n Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces \n Deprecated: This field has no function and is going to be removed in a next release."
+                        description: "Namespace defines the space within each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. \n Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces"
                         type: string
                       ownerReferences:
-                        description: "List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller. \n Deprecated: This field has no function and is going to be removed in a next release."
+                        description: List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.
                         items:
                           description: OwnerReference contains enough information to let you identify an owning object. An owning object must be in the same namespace as the dependent, or be cluster-scoped, so there is no namespace field.
                           properties:
@@ -405,11 +405,50 @@ spec:
                           type: string
                         description: 'Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations'
                         type: object
+                      generateName:
+                        description: "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. \n If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). \n Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency"
+                        type: string
                       labels:
                         additionalProperties:
                           type: string
                         description: 'Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels'
                         type: object
+                      name:
+                        description: 'Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names'
+                        type: string
+                      namespace:
+                        description: "Namespace defines the space within each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. \n Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces"
+                        type: string
+                      ownerReferences:
+                        description: List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.
+                        items:
+                          description: OwnerReference contains enough information to let you identify an owning object. An owning object must be in the same namespace as the dependent, or be cluster-scoped, so there is no namespace field.
+                          properties:
+                            apiVersion:
+                              description: API version of the referent.
+                              type: string
+                            blockOwnerDeletion:
+                              description: If true, AND if the owner has the "foregroundDeletion" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.
+                              type: boolean
+                            controller:
+                              description: If true, this reference points to the managing controller.
+                              type: boolean
+                            kind:
+                              description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+                              type: string
+                            name:
+                              description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names'
+                              type: string
+                            uid:
+                              description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids'
+                              type: string
+                          required:
+                          - apiVersion
+                          - kind
+                          - name
+                          - uid
+                          type: object
+                        type: array
                     type: object
                   spec:
                     description: 'Specification of the desired behavior of the machine. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status'
diff --git a/controlplane/kubeadm/api/v1alpha4/kubeadm_control_plane_webhook.go b/controlplane/kubeadm/api/v1alpha4/kubeadm_control_plane_webhook.go
index 6791c6a19..e3e77475d 100644
--- a/controlplane/kubeadm/api/v1alpha4/kubeadm_control_plane_webhook.go
+++ b/controlplane/kubeadm/api/v1alpha4/kubeadm_control_plane_webhook.go
@@ -43,7 +43,7 @@ func (in *KubeadmControlPlane) SetupWebhookWithManager(mgr ctrl.Manager) error {
 }
 
 // +kubebuilder:webhook:verbs=create;update,path=/mutate-controlplane-cluster-x-k8s-io-v1alpha4-kubeadmcontrolplane,mutating=true,failurePolicy=fail,matchPolicy=Equivalent,groups=controlplane.cluster.x-k8s.io,resources=kubeadmcontrolplanes,versions=v1alpha4,name=default.kubeadmcontrolplane.controlplane.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1beta1
-// +kubebuilder:webhook:verbs=create;update,path=/validate-controlplane-cluster-x-k8s-io-v1alpha4-kubeadmcontrolplane,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=controlplane.cluster.x-k8s.io,resources=kubeadmcontrolplanes;kubeadmcontrolplanes/scale,versions=v1alpha4,name=validation.kubeadmcontrolplane.controlplane.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1beta1
+// +kubebuilder:webhook:verbs=create;update,path=/validate-controlplane-cluster-x-k8s-io-v1alpha4-kubeadmcontrolplane,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=controlplane.cluster.x-k8s.io,resources=kubeadmcontrolplanes,versions=v1alpha4,name=validation.kubeadmcontrolplane.controlplane.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1beta1
 
 var _ webhook.Defaulter = &KubeadmControlPlane{}
 var _ webhook.Validator = &KubeadmControlPlane{}
diff --git a/controlplane/kubeadm/config/webhook/manifests.yaml b/controlplane/kubeadm/config/webhook/manifests.yaml
index 3d765257f..32b42f54e 100644
--- a/controlplane/kubeadm/config/webhook/manifests.yaml
+++ b/controlplane/kubeadm/config/webhook/manifests.yaml
@@ -55,5 +55,4 @@ webhooks:
     - UPDATE
     resources:
     - kubeadmcontrolplanes
-    - kubeadmcontrolplanes/scale
   sideEffects: None
diff --git a/go.mod b/go.mod
index 22921f30f..cf77c44cd 100644
--- a/go.mod
+++ b/go.mod
@@ -36,7 +36,7 @@ require (
 	k8s.io/klog/v2 v2.8.0
 	k8s.io/kubectl v0.21.0-beta.1
 	k8s.io/utils v0.0.0-20210305010621-2afb4311ab10
-	sigs.k8s.io/controller-runtime v0.9.0-alpha.1
+	sigs.k8s.io/controller-runtime v0.9.0-alpha.0
 	sigs.k8s.io/kind v0.9.0
 	sigs.k8s.io/yaml v1.2.0
 )
diff --git a/go.sum b/go.sum
index 86af74bef..bb9336805 100644
--- a/go.sum
+++ b/go.sum
@@ -1067,8 +1067,8 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
-sigs.k8s.io/controller-runtime v0.9.0-alpha.1 h1:yIYTxDHQfcrYWO1hjZvHhjkGY1fYFo1k07FzlTono4E=
-sigs.k8s.io/controller-runtime v0.9.0-alpha.1/go.mod h1:BARxVvgj+8Ihw9modUvYh7/OJmjxuBtLK8P36jdf7rY=
+sigs.k8s.io/controller-runtime v0.9.0-alpha.0 h1:ZCkLH+1PU56b3IUDYJhZKyoq4CCXfrhyYtJIRlW3RoI=
+sigs.k8s.io/controller-runtime v0.9.0-alpha.0/go.mod h1:BARxVvgj+8Ihw9modUvYh7/OJmjxuBtLK8P36jdf7rY=
 sigs.k8s.io/kind v0.9.0 h1:SoDlXq6pEc7dGagHULNRCCBYrLH6xOi7lqXTRXeAlg4=
 sigs.k8s.io/kind v0.9.0/go.mod h1:cxKQWwmbtRDzQ+RNKnR6gZG6fjbeTtItp5cGf+ww+1Y=
 sigs.k8s.io/kustomize/api v0.8.5/go.mod h1:M377apnKT5ZHJS++6H4rQoCHmWtt6qTpp3mbe7p6OLY=
diff --git a/util/annotations/helpers.go b/util/annotations/helpers.go
index 71b96b330..2880cf803 100644
--- a/util/annotations/helpers.go
+++ b/util/annotations/helpers.go
@@ -52,9 +52,13 @@ func HasWithPrefix(prefix string, annotations map[string]string) bool {
 
 // AddAnnotations sets the desired annotations on the object and returns true if the annotations have changed.
 func AddAnnotations(o metav1.Object, desired map[string]string) bool {
+	if len(desired) == 0 {
+		return false
+	}
 	annotations := o.GetAnnotations()
 	if annotations == nil {
 		annotations = make(map[string]string)
+		o.SetAnnotations(annotations)
 	}
 	hasChanged := false
 	for k, v := range desired {


Test patch:
diff --git a/api/v1alpha3/conversion_test.go b/api/v1alpha3/conversion_test.go
index b911d997b..c55680620 100644
--- a/api/v1alpha3/conversion_test.go
+++ b/api/v1alpha3/conversion_test.go
@@ -54,14 +54,14 @@ func TestFuzzyConversion(t *testing.T) {
 		Scheme:      scheme,
 		Hub:         &v1alpha4.MachineSet{},
 		Spoke:       &MachineSet{},
-		FuzzerFuncs: []fuzzer.FuzzerFuncs{BootstrapFuzzFuncs, CustomObjectMetaFuzzFunc},
+		FuzzerFuncs: []fuzzer.FuzzerFuncs{BootstrapFuzzFuncs},
 	}))
 
 	t.Run("for MachineDeployment", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{
 		Scheme:      scheme,
 		Hub:         &v1alpha4.MachineDeployment{},
 		Spoke:       &MachineDeployment{},
-		FuzzerFuncs: []fuzzer.FuzzerFuncs{BootstrapFuzzFuncs, CustomObjectMetaFuzzFunc},
+		FuzzerFuncs: []fuzzer.FuzzerFuncs{BootstrapFuzzFuncs},
 	}))
 
 	t.Run("for MachineHealthCheckSpec", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{
@@ -71,23 +71,6 @@ func TestFuzzyConversion(t *testing.T) {
 	}))
 }
 
-func CustomObjectMetaFuzzFunc(_ runtimeserializer.CodecFactory) []interface{} {
-	return []interface{}{
-		CustomObjectMetaFuzzer,
-	}
-}
-
-func CustomObjectMetaFuzzer(in *ObjectMeta, c fuzz.Continue) {
-	c.FuzzNoCustom(in)
-
-	// These fields have been removed in v1alpha4
-	// data is going to be lost, so we're forcing zero values here.
-	in.Name = ""
-	in.GenerateName = ""
-	in.Namespace = ""
-	in.OwnerReferences = nil
-}
-
 func BootstrapFuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} {
 	return []interface{}{
 		BootstrapFuzzer,
diff --git a/controllers/mdutil/util_test.go b/controllers/mdutil/util_test.go
index 15923d42d..3e92fb687 100644
--- a/controllers/mdutil/util_test.go
+++ b/controllers/mdutil/util_test.go
@@ -88,9 +88,10 @@ func generateDeployment(image string) clusterv1.MachineDeployment {
 	}
 }
 
-func generateMachineTemplateSpec(annotations, labels map[string]string) clusterv1.MachineTemplateSpec {
+func generateMachineTemplateSpec(name string, annotations, labels map[string]string) clusterv1.MachineTemplateSpec {
 	return clusterv1.MachineTemplateSpec{
 		ObjectMeta: clusterv1.ObjectMeta{
+			Name:        name,
 			Annotations: annotations,
 			Labels:      labels,
 		},
@@ -106,68 +107,68 @@ func TestEqualMachineTemplate(t *testing.T) {
 	}{
 		{
 			Name:     "Same spec, same labels",
-			Former:   generateMachineTemplateSpec(map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-1", "something": "else"}),
-			Latter:   generateMachineTemplateSpec(map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-1", "something": "else"}),
+			Former:   generateMachineTemplateSpec("foo", map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-1", "something": "else"}),
+			Latter:   generateMachineTemplateSpec("foo", map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-1", "something": "else"}),
 			Expected: true,
 		},
 		{
 			Name:     "Same spec, only machine-template-hash label value is different",
-			Former:   generateMachineTemplateSpec(map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-1", "something": "else"}),
-			Latter:   generateMachineTemplateSpec(map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-2", "something": "else"}),
+			Former:   generateMachineTemplateSpec("foo", map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-1", "something": "else"}),
+			Latter:   generateMachineTemplateSpec("foo", map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-2", "something": "else"}),
 			Expected: true,
 		},
 		{
 			Name:     "Same spec, the former doesn't have machine-template-hash label",
-			Former:   generateMachineTemplateSpec(map[string]string{}, map[string]string{"something": "else"}),
-			Latter:   generateMachineTemplateSpec(map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-2", "something": "else"}),
+			Former:   generateMachineTemplateSpec("foo", map[string]string{}, map[string]string{"something": "else"}),
+			Latter:   generateMachineTemplateSpec("foo", map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-2", "something": "else"}),
 			Expected: true,
 		},
 		{
 			Name:     "Same spec, the former doesn't have machine-template-hash label",
-			Former:   generateMachineTemplateSpec(map[string]string{}, map[string]string{"something": "else"}),
-			Latter:   generateMachineTemplateSpec(map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-2", "something": "else"}),
+			Former:   generateMachineTemplateSpec("foo", map[string]string{}, map[string]string{"something": "else"}),
+			Latter:   generateMachineTemplateSpec("foo", map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-2", "something": "else"}),
 			Expected: true,
 		},
 		{
 			Name:     "Same spec, the label is different, the former doesn't have machine-template-hash label, same number of labels",
-			Former:   generateMachineTemplateSpec(map[string]string{}, map[string]string{"something": "else"}),
-			Latter:   generateMachineTemplateSpec(map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-2"}),
+			Former:   generateMachineTemplateSpec("foo", map[string]string{}, map[string]string{"something": "else"}),
+			Latter:   generateMachineTemplateSpec("foo", map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-2"}),
 			Expected: false,
 		},
 		{
 			Name:     "Same spec, the label is different, the latter doesn't have machine-template-hash label, same number of labels",
-			Former:   generateMachineTemplateSpec(map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-1"}),
-			Latter:   generateMachineTemplateSpec(map[string]string{}, map[string]string{"something": "else"}),
+			Former:   generateMachineTemplateSpec("foo", map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-1"}),
+			Latter:   generateMachineTemplateSpec("foo", map[string]string{}, map[string]string{"something": "else"}),
 			Expected: false,
 		},
 		{
 			Name:     "Same spec, the label is different, and the machine-template-hash label value is the same",
-			Former:   generateMachineTemplateSpec(map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-1"}),
-			Latter:   generateMachineTemplateSpec(map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-1", "something": "else"}),
+			Former:   generateMachineTemplateSpec("foo", map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-1"}),
+			Latter:   generateMachineTemplateSpec("foo", map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-1", "something": "else"}),
 			Expected: false,
 		},
 		{
 			Name:     "Different spec, same labels",
-			Former:   generateMachineTemplateSpec(map[string]string{"former": "value"}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-1", "something": "else"}),
-			Latter:   generateMachineTemplateSpec(map[string]string{"latter": "value"}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-1", "something": "else"}),
+			Former:   generateMachineTemplateSpec("foo", map[string]string{"former": "value"}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-1", "something": "else"}),
+			Latter:   generateMachineTemplateSpec("foo", map[string]string{"latter": "value"}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-1", "something": "else"}),
 			Expected: false,
 		},
 		{
 			Name:     "Different spec, different machine-template-hash label value",
-			Former:   generateMachineTemplateSpec(map[string]string{"x": ""}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-1", "something": "else"}),
-			Latter:   generateMachineTemplateSpec(map[string]string{"x": "1"}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-2", "something": "else"}),
+			Former:   generateMachineTemplateSpec("foo-1", map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-1", "something": "else"}),
+			Latter:   generateMachineTemplateSpec("foo-2", map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-2", "something": "else"}),
 			Expected: false,
 		},
 		{
 			Name:     "Different spec, the former doesn't have machine-template-hash label",
-			Former:   generateMachineTemplateSpec(map[string]string{"x": ""}, map[string]string{"something": "else"}),
-			Latter:   generateMachineTemplateSpec(map[string]string{"x": "1"}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-2", "something": "else"}),
+			Former:   generateMachineTemplateSpec("foo-1", map[string]string{}, map[string]string{"something": "else"}),
+			Latter:   generateMachineTemplateSpec("foo-2", map[string]string{}, map[string]string{DefaultMachineDeploymentUniqueLabelKey: "value-2", "something": "else"}),
 			Expected: false,
 		},
 		{
 			Name:     "Different spec, different labels",
-			Former:   generateMachineTemplateSpec(map[string]string{}, map[string]string{"something": "else"}),
-			Latter:   generateMachineTemplateSpec(map[string]string{}, map[string]string{"nothing": "else"}),
+			Former:   generateMachineTemplateSpec("foo", map[string]string{}, map[string]string{"something": "else"}),
+			Latter:   generateMachineTemplateSpec("foo", map[string]string{}, map[string]string{"nothing": "else"}),
 			Expected: false,
 		},
 		{
@@ -281,10 +282,8 @@ func TestFindNewMachineSet(t *testing.T) {
 	newMSDup.CreationTimestamp = now
 
 	oldDeployment := generateDeployment("nginx")
+	oldDeployment.Spec.Template.Name = "nginx-old-1"
 	oldMS := generateMS(oldDeployment)
-	oldMS.Spec.Template.Annotations = map[string]string{
-		"old": "true",
-	}
 	oldMS.Status.FullyLabeledReplicas = *(oldMS.Spec.Replicas)
 
 	tests := []struct {
@@ -339,10 +338,8 @@ func TestFindOldMachineSets(t *testing.T) {
 	newMSDup.CreationTimestamp = now
 
 	oldDeployment := generateDeployment("nginx")
+	oldDeployment.Spec.Template.Name = "nginx-old-1"
 	oldMS := generateMS(oldDeployment)
-	oldMS.Spec.Template.Annotations = map[string]string{
-		"old": "true",
-	}
 	oldMS.Status.FullyLabeledReplicas = *(oldMS.Spec.Replicas)
 	oldMS.CreationTimestamp = before
 
diff --git a/exp/addons/controllers/clusterresourceset_controller_test.go b/exp/addons/controllers/clusterresourceset_controller_test.go
index 1cb3e233b..76411c49b 100644
--- a/exp/addons/controllers/clusterresourceset_controller_test.go
+++ b/exp/addons/controllers/clusterresourceset_controller_test.go
@@ -22,7 +22,6 @@ import (
 
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
-	"github.com/pkg/errors"
 
 	corev1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -308,16 +307,17 @@ metadata:
 		}, timeout).Should(BeTrue())
 
 		// When the ConfigMap resource is created, CRS should get reconciled immediately.
-		Eventually(func() error {
+		Eventually(func() bool {
 			binding := &addonsv1.ClusterResourceSetBinding{}
-			if err := testEnv.Get(ctx, clusterResourceSetBindingKey, binding); err != nil {
-				return err
-			}
-			if len(binding.Spec.Bindings[0].Resources) > 0 && binding.Spec.Bindings[0].Resources[0].Name == newCMName {
-				return nil
+
+			err := testEnv.Get(ctx, clusterResourceSetBindingKey, binding)
+			if err == nil {
+				if len(binding.Spec.Bindings[0].Resources) > 0 && binding.Spec.Bindings[0].Resources[0].Name == newCMName {
+					return true
+				}
 			}
-			return errors.Errorf("ClusterResourceSet binding does not have any resources matching %q: %v", newCMName, binding.Spec.Bindings)
-		}, timeout).Should(Succeed())
+			return false
+		}, timeout).Should(BeTrue())
 		Expect(testEnv.Delete(ctx, testConfigmap)).To(Succeed())
 	})
 	It("Should delete ClusterResourceSet from the bindings list when ClusterResourceSet is deleted", func() {
diff --git a/test/framework/cluster_proxy.go b/test/framework/cluster_proxy.go
index 0da6512c2..6ca701706 100644
--- a/test/framework/cluster_proxy.go
+++ b/test/framework/cluster_proxy.go
@@ -64,7 +64,7 @@ type ClusterProxy interface {
 	GetRESTConfig() *rest.Config
 
 	// Apply to apply YAML to the Kubernetes cluster, `kubectl apply`.
-	Apply(ctx context.Context, resources []byte, args ...string) error
+	Apply(context.Context, []byte) error
 
 	// GetWorkloadCluster returns a proxy to a workload cluster defined in the Kubernetes cluster.
 	GetWorkloadCluster(ctx context.Context, namespace, name string) ClusterProxy
@@ -179,12 +179,20 @@ func (p *clusterProxy) GetClientSet() *kubernetes.Clientset {
 	return cs
 }
 
+// Apply wraps `kubectl apply` and prints the output so we can see what gets applied to the cluster.
+func (p *clusterProxy) Apply(ctx context.Context, resources []byte) error {
+	Expect(ctx).NotTo(BeNil(), "ctx is required for Apply")
+	Expect(resources).NotTo(BeNil(), "resources is required for Apply")
+
+	return exec.KubectlApply(ctx, p.kubeconfigPath, resources)
+}
+
 // Apply wraps `kubectl apply ...` and prints the output so we can see what gets applied to the cluster.
-func (p *clusterProxy) Apply(ctx context.Context, resources []byte, args ...string) error {
+func (p *clusterProxy) ApplyWithArgs(ctx context.Context, resources []byte, args ...string) error {
 	Expect(ctx).NotTo(BeNil(), "ctx is required for Apply")
 	Expect(resources).NotTo(BeNil(), "resources is required for Apply")
 
-	return exec.KubectlApply(ctx, p.kubeconfigPath, resources, args...)
+	return exec.KubectlApplyWithArgs(ctx, p.kubeconfigPath, resources, args...)
 }
 
 func (p *clusterProxy) GetRESTConfig() *rest.Config {
diff --git a/test/framework/clusterctl/clusterctl_helpers.go b/test/framework/clusterctl/clusterctl_helpers.go
index b8ccdc81a..721365c53 100644
--- a/test/framework/clusterctl/clusterctl_helpers.go
+++ b/test/framework/clusterctl/clusterctl_helpers.go
@@ -111,7 +111,6 @@ type ApplyClusterTemplateAndWaitInput struct {
 	WaitForControlPlaneIntervals []interface{}
 	WaitForMachineDeployments    []interface{}
 	WaitForMachinePools          []interface{}
-	Args                         []string // extra args to be used during `kubectl apply`
 }
 
 type ApplyClusterTemplateAndWaitResult struct {
@@ -154,7 +153,7 @@ func ApplyClusterTemplateAndWait(ctx context.Context, input ApplyClusterTemplate
 	Expect(workloadClusterTemplate).ToNot(BeNil(), "Failed to get the cluster template")
 
 	log.Logf("Applying the cluster template yaml to the cluster")
-	Expect(input.ClusterProxy.Apply(ctx, workloadClusterTemplate, input.Args...)).To(Succeed())
+	Expect(input.ClusterProxy.Apply(ctx, workloadClusterTemplate)).To(Succeed())
 
 	log.Logf("Waiting for the cluster infrastructure to be provisioned")
 	result.Cluster = framework.DiscoveryAndWaitForCluster(ctx, framework.DiscoveryAndWaitForClusterInput{
diff --git a/test/framework/exec/kubectl.go b/test/framework/exec/kubectl.go
index 7080b4dbe..e6e5b6d7b 100644
--- a/test/framework/exec/kubectl.go
+++ b/test/framework/exec/kubectl.go
@@ -23,7 +23,11 @@ import (
 )
 
 // TODO: Remove this usage of kubectl and replace with a function from apply.go using the controller-runtime client.
-func KubectlApply(ctx context.Context, kubeconfigPath string, resources []byte, args ...string) error {
+func KubectlApply(ctx context.Context, kubeconfigPath string, resources []byte) error {
+	return KubectlApplyWithArgs(ctx, kubeconfigPath, resources)
+}
+
+func KubectlApplyWithArgs(ctx context.Context, kubeconfigPath string, resources []byte, args ...string) error {
 	aargs := append([]string{"apply", "--kubeconfig", kubeconfigPath, "-f", "-"}, args...)
 	rbytes := bytes.NewReader(resources)
 	applyCmd := NewCommand(
diff --git a/test/infrastructure/docker/Dockerfile b/test/infrastructure/docker/Dockerfile
index 52eee88d0..d3fbf114c 100644
--- a/test/infrastructure/docker/Dockerfile
+++ b/test/infrastructure/docker/Dockerfile
@@ -14,7 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-FROM golang:1.16.2 as builder
+FROM golang:1.16.0 as builder
 
 # Run this with docker build --build_arg goproxy=$(go env GOPROXY) to override the goproxy
 ARG goproxy=https://proxy.golang.org
diff --git a/test/infrastructure/docker/Dockerfile.dev b/test/infrastructure/docker/Dockerfile.dev
index 7fcc8946c..d25f5479f 100644
--- a/test/infrastructure/docker/Dockerfile.dev
+++ b/test/infrastructure/docker/Dockerfile.dev
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-FROM golang:1.16.2
+FROM golang:1.16.0
 
 # ALERT ################################################################
 # This is an unusual dockerfile. The expected build context is all of  #
diff --git a/test/infrastructure/docker/Makefile b/test/infrastructure/docker/Makefile
index 3c0541b7a..d37f87c17 100644
--- a/test/infrastructure/docker/Makefile
+++ b/test/infrastructure/docker/Makefile
@@ -159,7 +159,7 @@ modules: ## Runs go mod to ensure modules are up to date.
 .PHONY: docker-pull-prerequisites
 docker-pull-prerequisites:
 	docker pull docker.io/docker/dockerfile:1.1-experimental
-	docker pull docker.io/library/golang:1.16.2
+	docker pull docker.io/library/golang:1.16.0
 	docker pull gcr.io/distroless/static:latest
 
 .PHONY: docker-build
diff --git a/test/infrastructure/docker/go.mod b/test/infrastructure/docker/go.mod
index 9168e7346..a16ca799d 100644
--- a/test/infrastructure/docker/go.mod
+++ b/test/infrastructure/docker/go.mod
@@ -14,7 +14,7 @@ require (
 	k8s.io/klog/v2 v2.8.0
 	k8s.io/utils v0.0.0-20210305010621-2afb4311ab10
 	sigs.k8s.io/cluster-api v0.3.3
-	sigs.k8s.io/controller-runtime v0.9.0-alpha.1
+	sigs.k8s.io/controller-runtime v0.9.0-alpha.0
 	sigs.k8s.io/kind v0.9.0
 	sigs.k8s.io/yaml v1.2.0
 )
diff --git a/test/infrastructure/docker/go.sum b/test/infrastructure/docker/go.sum
index 6c7bd2610..53d7f4e76 100644
--- a/test/infrastructure/docker/go.sum
+++ b/test/infrastructure/docker/go.sum
@@ -996,8 +996,8 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
 sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
-sigs.k8s.io/controller-runtime v0.9.0-alpha.1 h1:yIYTxDHQfcrYWO1hjZvHhjkGY1fYFo1k07FzlTono4E=
-sigs.k8s.io/controller-runtime v0.9.0-alpha.1/go.mod h1:BARxVvgj+8Ihw9modUvYh7/OJmjxuBtLK8P36jdf7rY=
+sigs.k8s.io/controller-runtime v0.9.0-alpha.0 h1:ZCkLH+1PU56b3IUDYJhZKyoq4CCXfrhyYtJIRlW3RoI=
+sigs.k8s.io/controller-runtime v0.9.0-alpha.0/go.mod h1:BARxVvgj+8Ihw9modUvYh7/OJmjxuBtLK8P36jdf7rY=
 sigs.k8s.io/kind v0.9.0 h1:SoDlXq6pEc7dGagHULNRCCBYrLH6xOi7lqXTRXeAlg4=
 sigs.k8s.io/kind v0.9.0/go.mod h1:cxKQWwmbtRDzQ+RNKnR6gZG6fjbeTtItp5cGf+ww+1Y=
 sigs.k8s.io/kustomize/api v0.8.5/go.mod h1:M377apnKT5ZHJS++6H4rQoCHmWtt6qTpp3mbe7p6OLY=
diff --git a/util/annotations/helpers_test.go b/util/annotations/helpers_test.go
index 533d1b282..67376ef0a 100644
--- a/util/annotations/helpers_test.go
+++ b/util/annotations/helpers_test.go
@@ -69,6 +69,19 @@ func TestAddAnnotations(t *testing.T) {
 			},
 			changed: false,
 		},
+		{
+			name: "should do nothing if no annotations are provided and have been nil before",
+			obj: &corev1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: nil,
+				},
+				Spec:   corev1.NodeSpec{},
+				Status: corev1.NodeStatus{},
+			},
+			input:    map[string]string{},
+			expected: nil,
+			changed:  false,
+		},
 		{
 			name: "should return true if annotations are added",
 			obj: &corev1.Node{
@@ -110,6 +123,23 @@ func TestAddAnnotations(t *testing.T) {
 			},
 			changed: true,
 		},
+		{
+			name: "should return true if annotations are changed and have been nil before",
+			obj: &corev1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: nil,
+				},
+				Spec:   corev1.NodeSpec{},
+				Status: corev1.NodeStatus{},
+			},
+			input: map[string]string{
+				"foo": "buzz",
+			},
+			expected: map[string]string{
+				"foo": "buzz",
+			},
+			changed: true,
+		},
 	}
 
 	for _, tc := range testcases {