Repository: kubernetes-sigs/cluster-api-provider-gcp
Pull request #1131 (2024-02-06 10:35:25)

docs: basic netlify configuration for book
**What type of PR is this?**

/kind documentation

**What this PR does / why we need it**:

This PR adds the basic `netlify` configuration (plus minimal make tools for building the book) so that we can [request a `netlify` site](https://github.com/kubernetes/community/blob/c5243d80e565a3292f446f6f54c82e57ea7b1602/github-management/subproject-site-requests.md#requesting-a-netlify-site) and a [project subdomain](https://github.com/kubernetes/community/blob/c5243d80e565a3292f446f6f54c82e57ea7b1602/github-management/subproject-site-requests.md#requesting-a-subproject-domain).

Together with this PR, I'm opening the necessary requests in the https://github.com/kubernetes/k8s.io/ repository.

Then, when the site is up and running, I'll create a separate PR to add the base structure/contents of the book, so that we can later iterate over this and add new/improve existing topics.

**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 #

**Special notes for your reviewer**:

This the initial PR on an effort to sort the CAPG docs into a book https://github.com/kubernetes-sigs/cluster-api-provider-gcp/issues/490.

**TODOs**:

- [x] squashed commits
- [ ] includes documentation
- [ ] adds unit tests

**Release note**:
```release-note
Add Netlify configuration for book
```


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

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/cluster-api-provider-gcp/pull/1131#" title="Author self-approved">salasberryfin</a>*
**Once this PR has been reviewed and has the lgtm label**, please assign [fabriziopandini](https://github.com/fabriziopandini) for approval. 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-provider-gcp).

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

- **[OWNERS](https://github.com/kubernetes-sigs/cluster-api-provider-gcp/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":["fabriziopandini"]} -->
k8s-ci-robot: Hi @salasberryfin. Thanks for your PR.

I'm waiting for a [kubernetes-sigs](https://github.com/orgs/kubernetes-sigs/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://git.k8s.io/community/community-membership.md#member) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcluster-api-provider-gcp).

<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>

richardcase: /ok-to-tesr
salasberryfin: @richardcase I think there's a typo in the to-test label and prow didn't start the checks
k8s-ci-robot: @salasberryfin: 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-provider-gcp-verify | d6578e382c5db38484baf280d81434f610c93e1e | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-gcp/1131/pull-cluster-api-provider-gcp-verify/1754861275491340288) | true | `/test pull-cluster-api-provider-gcp-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-gcp&pr=1131). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asalasberryfin). 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-provider-gcp/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 -->
salasberryfin: Fixed tests and it is now ready to be reviewed.

Patch:
diff --git a/Makefile b/Makefile
index 5399484c..7a9efb85 100644
--- a/Makefile
+++ b/Makefile
@@ -324,6 +324,12 @@ generate-go: $(CONTROLLER_GEN) $(CONVERSION_GEN) ## Runs Go related generate tar
 		paths=./... \
 		paths=./$(EXP_DIR)/api/... \
 		object:headerFile=./hack/boilerplate/boilerplate.generatego.txt
+	$(CONVERSION_GEN) \
+		--input-dirs=./api/v1alpha4 \
+		--build-tag=ignore_autogenerated_core_v1alpha4 \
+		--extra-peer-dirs=sigs.k8s.io/cluster-api/api/v1alpha4 \
+		--output-file-base=zz_generated.conversion \
+		--go-header-file=./hack/boilerplate/boilerplate.generatego.txt $(OUTPUT_BASE)
 	go generate ./...
 
 .PHONY: generate-manifests
diff --git a/api/v1alpha4/doc.go b/api/v1alpha4/doc.go
new file mode 100644
index 00000000..87b008ee
--- /dev/null
+++ b/api/v1alpha4/doc.go
@@ -0,0 +1,22 @@
+/*
+Copyright 2021 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.
+*/
+
+// Package v1alpha4 contains the v1alpha4 API implementation.
+//
+// Deprecated: This package will be removed in one of the next releases.
+package v1alpha4
+
+// +k8s:conversion-gen=sigs.k8s.io/cluster-api-provider-gcp/api/v1beta1
diff --git a/api/v1alpha4/gcpcluster_conversion.go b/api/v1alpha4/gcpcluster_conversion.go
new file mode 100644
index 00000000..7ceb3655
--- /dev/null
+++ b/api/v1alpha4/gcpcluster_conversion.go
@@ -0,0 +1,98 @@
+/*
+Copyright 2021 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.
+*/
+
+package v1alpha4
+
+import (
+	apiconversion "k8s.io/apimachinery/pkg/conversion"
+	infrav1beta1 "sigs.k8s.io/cluster-api-provider-gcp/api/v1beta1"
+	v1beta1 "sigs.k8s.io/cluster-api-provider-gcp/api/v1beta1"
+	utilconversion "sigs.k8s.io/cluster-api/util/conversion"
+	"sigs.k8s.io/controller-runtime/pkg/conversion"
+)
+
+// ConvertTo converts this GCPCluster to the Hub version (v1beta1).
+func (src *GCPCluster) ConvertTo(dstRaw conversion.Hub) error { // nolint
+	dst := dstRaw.(*infrav1beta1.GCPCluster)
+
+	if err := Convert_v1alpha4_GCPCluster_To_v1beta1_GCPCluster(src, dst, nil); err != nil {
+		return err
+	}
+
+	// Manually restore data.
+	restored := &infrav1beta1.GCPCluster{}
+	if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok {
+		return err
+	}
+
+	for _, restoredSubnet := range restored.Spec.Network.Subnets {
+		for i, dstSubnet := range dst.Spec.Network.Subnets {
+			if dstSubnet.Name != restoredSubnet.Name {
+				continue
+			}
+			dst.Spec.Network.Subnets[i].Purpose = restoredSubnet.Purpose
+
+			break
+		}
+	}
+	if restored.Spec.CredentialsRef != nil {
+		dst.Spec.CredentialsRef = restored.Spec.CredentialsRef.DeepCopy()
+	}
+
+	for _, restoredTag := range restored.Spec.ResourceManagerTags {
+		dst.Spec.ResourceManagerTags = append(dst.Spec.ResourceManagerTags, *restoredTag.DeepCopy())
+	}
+
+	return nil
+}
+
+// ConvertFrom converts from the Hub version (v1beta1) to this version.
+func (dst *GCPCluster) ConvertFrom(srcRaw conversion.Hub) error { // nolint
+	src := srcRaw.(*infrav1beta1.GCPCluster)
+
+	if err := Convert_v1beta1_GCPCluster_To_v1alpha4_GCPCluster(src, dst, nil); err != nil {
+		return err
+	}
+
+	// Preserve Hub data on down-conversion.
+	if err := utilconversion.MarshalData(src, dst); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ConvertTo converts this GCPClusterList to the Hub version (v1beta1).
+func (src *GCPClusterList) ConvertTo(dstRaw conversion.Hub) error { // nolint
+	dst := dstRaw.(*infrav1beta1.GCPClusterList)
+	return Convert_v1alpha4_GCPClusterList_To_v1beta1_GCPClusterList(src, dst, nil)
+}
+
+// ConvertFrom converts from the Hub version (v1beta1) to this version.
+func (dst *GCPClusterList) ConvertFrom(srcRaw conversion.Hub) error { // nolint
+	src := srcRaw.(*infrav1beta1.GCPClusterList)
+	return Convert_v1beta1_GCPClusterList_To_v1alpha4_GCPClusterList(src, dst, nil)
+}
+
+// Convert_v1beta1_SubnetSpec_To_v1alpha4_SubnetSpec is an autogenerated conversion function.
+func Convert_v1beta1_SubnetSpec_To_v1alpha4_SubnetSpec(in *infrav1beta1.SubnetSpec, out *SubnetSpec, s apiconversion.Scope) error {
+	return autoConvert_v1beta1_SubnetSpec_To_v1alpha4_SubnetSpec(in, out, s)
+}
+
+// Convert_v1beta1_GCPClusterSpec_To_v1alpha4_GCPClusterSpec is an autogenerated conversion function.
+func Convert_v1beta1_GCPClusterSpec_To_v1alpha4_GCPClusterSpec(in *v1beta1.GCPClusterSpec, out *GCPClusterSpec, s apiconversion.Scope) error {
+	return autoConvert_v1beta1_GCPClusterSpec_To_v1alpha4_GCPClusterSpec(in, out, s)
+}
diff --git a/api/v1alpha4/gcpcluster_types.go b/api/v1alpha4/gcpcluster_types.go
new file mode 100644
index 00000000..9657a4c0
--- /dev/null
+++ b/api/v1alpha4/gcpcluster_types.go
@@ -0,0 +1,95 @@
+/*
+Copyright 2021 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.
+*/
+
+package v1alpha4
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha4" //nolint: staticcheck
+)
+
+const (
+	// ClusterFinalizer allows ReconcileGCPCluster to clean up GCP resources associated with GCPCluster before
+	// removing it from the apiserver.
+	ClusterFinalizer = "gcpcluster.infrastructure.cluster.x-k8s.io"
+)
+
+// GCPClusterSpec defines the desired state of GCPCluster.
+type GCPClusterSpec struct {
+	// Project is the name of the project to deploy the cluster to.
+	Project string `json:"project"`
+
+	// The GCP Region the cluster lives in.
+	Region string `json:"region"`
+
+	// ControlPlaneEndpoint represents the endpoint used to communicate with the control plane.
+	// +optional
+	ControlPlaneEndpoint clusterv1.APIEndpoint `json:"controlPlaneEndpoint"`
+
+	// NetworkSpec encapsulates all things related to GCP network.
+	// +optional
+	Network NetworkSpec `json:"network"`
+
+	// FailureDomains is an optional field which is used to assign selected availability zones to a cluster
+	// FailureDomains if empty, defaults to all the zones in the selected region and if specified would override
+	// the default zones.
+	// +optional
+	FailureDomains []string `json:"failureDomains,omitempty"`
+
+	// AdditionalLabels is an optional set of tags to add to GCP resources managed by the GCP provider, in addition to the
+	// ones added by default.
+	// +optional
+	AdditionalLabels Labels `json:"additionalLabels,omitempty"`
+}
+
+// GCPClusterStatus defines the observed state of GCPCluster.
+type GCPClusterStatus struct {
+	FailureDomains clusterv1.FailureDomains `json:"failureDomains,omitempty"`
+	Network        Network                  `json:"network,omitempty"`
+
+	// Bastion Instance `json:"bastion,omitempty"`
+	Ready bool `json:"ready"`
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:resource:path=gcpclusters,scope=Namespaced,categories=cluster-api
+// +kubebuilder:subresource:status
+// +kubebuilder:printcolumn:name="Cluster",type="string",JSONPath=".metadata.labels.cluster\\.x-k8s\\.io/cluster-name",description="Cluster to which this GCPCluster belongs"
+// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.ready",description="Cluster infrastructure is ready for GCE instances"
+// +kubebuilder:printcolumn:name="Network",type="string",JSONPath=".spec.network.name",description="GCP network the cluster is using"
+// +kubebuilder:printcolumn:name="Endpoint",type="string",JSONPath=".status.apiEndpoints[0]",description="API Endpoint",priority=1
+
+// GCPCluster is the Schema for the gcpclusters API.
+type GCPCluster struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec   GCPClusterSpec   `json:"spec,omitempty"`
+	Status GCPClusterStatus `json:"status,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// GCPClusterList contains a list of GCPCluster.
+type GCPClusterList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []GCPCluster `json:"items"`
+}
+
+func init() {
+	SchemeBuilder.Register(&GCPCluster{}, &GCPClusterList{})
+}
diff --git a/api/v1alpha4/gcpclustertemplate_conversion.go b/api/v1alpha4/gcpclustertemplate_conversion.go
new file mode 100644
index 00000000..a4fae1a3
--- /dev/null
+++ b/api/v1alpha4/gcpclustertemplate_conversion.go
@@ -0,0 +1,94 @@
+/*
+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.
+*/
+
+package v1alpha4
+
+import (
+	apiconversion "k8s.io/apimachinery/pkg/conversion"
+	infrav1beta1 "sigs.k8s.io/cluster-api-provider-gcp/api/v1beta1"
+	utilconversion "sigs.k8s.io/cluster-api/util/conversion"
+	"sigs.k8s.io/controller-runtime/pkg/conversion"
+)
+
+// ConvertTo converts this GCPClusterTemplate to the Hub version (v1beta1).
+func (src *GCPClusterTemplate) ConvertTo(dstRaw conversion.Hub) error { // nolint
+	dst := dstRaw.(*infrav1beta1.GCPClusterTemplate)
+
+	if err := Convert_v1alpha4_GCPClusterTemplate_To_v1beta1_GCPClusterTemplate(src, dst, nil); err != nil {
+		return err
+	}
+
+	// Manually restore data.
+	restored := &infrav1beta1.GCPClusterTemplate{}
+	if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok {
+		return err
+	}
+
+	dst.Spec.Template.ObjectMeta = restored.Spec.Template.ObjectMeta
+
+	for _, restoredSubnet := range restored.Spec.Template.Spec.Network.Subnets {
+		for i, dstSubnet := range dst.Spec.Template.Spec.Network.Subnets {
+			if dstSubnet.Name != restoredSubnet.Name {
+				continue
+			}
+			dst.Spec.Template.Spec.Network.Subnets[i].Purpose = restoredSubnet.Purpose
+
+			break
+		}
+	}
+
+	if restored.Spec.Template.Spec.CredentialsRef != nil {
+		dst.Spec.Template.Spec.CredentialsRef = restored.Spec.Template.Spec.CredentialsRef.DeepCopy()
+	}
+
+	for _, restoredTag := range restored.Spec.Template.Spec.ResourceManagerTags {
+		dst.Spec.Template.Spec.ResourceManagerTags = append(dst.Spec.Template.Spec.ResourceManagerTags, *restoredTag.DeepCopy())
+	}
+
+	return nil
+}
+
+// ConvertFrom converts from the Hub version (v1beta1) to this version.
+func (dst *GCPClusterTemplate) ConvertFrom(srcRaw conversion.Hub) error { // nolint
+	src := srcRaw.(*infrav1beta1.GCPClusterTemplate)
+	if err := Convert_v1beta1_GCPClusterTemplate_To_v1alpha4_GCPClusterTemplate(src, dst, nil); err != nil {
+		return err
+	}
+
+	// Preserve Hub data on down-conversion.
+	if err := utilconversion.MarshalData(src, dst); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ConvertTo converts this GCPClusterTemplateList to the Hub version (v1beta1).
+func (src *GCPClusterTemplateList) ConvertTo(dstRaw conversion.Hub) error { // nolint
+	dst := dstRaw.(*infrav1beta1.GCPClusterTemplateList)
+	return Convert_v1alpha4_GCPClusterTemplateList_To_v1beta1_GCPClusterTemplateList(src, dst, nil)
+}
+
+// ConvertFrom converts from the Hub version (v1beta1) to this version.
+func (dst *GCPClusterTemplateList) ConvertFrom(srcRaw conversion.Hub) error { // nolint
+	src := srcRaw.(*infrav1beta1.GCPClusterTemplateList)
+	return Convert_v1beta1_GCPClusterTemplateList_To_v1alpha4_GCPClusterTemplateList(src, dst, nil)
+}
+
+func Convert_v1beta1_GCPClusterTemplateResource_To_v1alpha4_GCPClusterTemplateResource(in *infrav1beta1.GCPClusterTemplateResource, out *GCPClusterTemplateResource, s apiconversion.Scope) error {
+	// NOTE: custom conversion func is required because spec.template.metadata has been added in v1beta1.
+	return autoConvert_v1beta1_GCPClusterTemplateResource_To_v1alpha4_GCPClusterTemplateResource(in, out, s)
+}
diff --git a/api/v1alpha4/gcpclustertemplate_types.go b/api/v1alpha4/gcpclustertemplate_types.go
new file mode 100644
index 00000000..4ca8f8dd
--- /dev/null
+++ b/api/v1alpha4/gcpclustertemplate_types.go
@@ -0,0 +1,55 @@
+/*
+Copyright 2021 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.
+*/
+
+package v1alpha4
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// GCPClusterTemplateSpec defines the desired state of GCPClusterTemplate.
+type GCPClusterTemplateSpec struct {
+	Template GCPClusterTemplateResource `json:"template"`
+}
+
+// GCPClusterTemplateResource contains spec for GCPClusterSpec.
+type GCPClusterTemplateResource struct {
+	Spec GCPClusterSpec `json:"spec"`
+}
+
+//+kubebuilder:object:root=true
+//+kubebuilder:resource:path=gcpclustertemplates,scope=Namespaced,categories=cluster-api,shortName=gcpct
+
+// GCPClusterTemplate is the Schema for the gcpclustertemplates API.
+type GCPClusterTemplate struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec GCPClusterTemplateSpec `json:"spec,omitempty"`
+}
+
+//+kubebuilder:object:root=true
+
+// GCPClusterTemplateList contains a list of GCPClusterTemplate.
+type GCPClusterTemplateList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []GCPClusterTemplate `json:"items"`
+}
+
+func init() {
+	SchemeBuilder.Register(&GCPClusterTemplate{}, &GCPClusterTemplateList{})
+}
diff --git a/api/v1alpha4/gcpmachine_conversion.go b/api/v1alpha4/gcpmachine_conversion.go
new file mode 100644
index 00000000..24c4c237
--- /dev/null
+++ b/api/v1alpha4/gcpmachine_conversion.go
@@ -0,0 +1,92 @@
+/*
+Copyright 2021 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.
+*/
+
+package v1alpha4
+
+import (
+	apiconversion "k8s.io/apimachinery/pkg/conversion"
+	v1beta1 "sigs.k8s.io/cluster-api-provider-gcp/api/v1beta1"
+	utilconversion "sigs.k8s.io/cluster-api/util/conversion"
+	"sigs.k8s.io/controller-runtime/pkg/conversion"
+)
+
+// ConvertTo converts this GCPMachine to the Hub version (v1beta1).
+func (src *GCPMachine) ConvertTo(dstRaw conversion.Hub) error { // nolint
+	dst := dstRaw.(*v1beta1.GCPMachine)
+
+	if err := Convert_v1alpha4_GCPMachine_To_v1beta1_GCPMachine(src, dst, nil); err != nil {
+		return err
+	}
+
+	// Manually restore data.
+	restored := &v1beta1.GCPMachine{}
+	if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok {
+		return err
+	}
+
+	if restored.Spec.IPForwarding != nil {
+		dst.Spec.IPForwarding = restored.Spec.IPForwarding
+	}
+
+	if restored.Spec.ShieldedInstanceConfig != nil {
+		dst.Spec.ShieldedInstanceConfig = restored.Spec.ShieldedInstanceConfig
+	}
+
+	if restored.Spec.OnHostMaintenance != nil {
+		dst.Spec.OnHostMaintenance = restored.Spec.OnHostMaintenance
+	}
+
+	if restored.Spec.ConfidentialCompute != nil {
+		dst.Spec.ConfidentialCompute = restored.Spec.ConfidentialCompute
+	}
+
+	if restored.Spec.ResourceManagerTags != nil {
+		dst.Spec.ResourceManagerTags = restored.Spec.ResourceManagerTags
+	}
+
+	return nil
+}
+
+// ConvertFrom converts from the Hub version (v1beta1) to this version.
+func (dst *GCPMachine) ConvertFrom(srcRaw conversion.Hub) error { // nolint
+	src := srcRaw.(*v1beta1.GCPMachine)
+	if err := Convert_v1beta1_GCPMachine_To_v1alpha4_GCPMachine(src, dst, nil); err != nil {
+		return err
+	}
+
+	// Preserve Hub data on down-conversion.
+	if err := utilconversion.MarshalData(src, dst); err != nil {
+		return err
+	}
+	return nil
+}
+
+// ConvertTo converts this GCPMachineList to the Hub version (v1beta1).
+func (src *GCPMachineList) ConvertTo(dstRaw conversion.Hub) error { // nolint
+	dst := dstRaw.(*v1beta1.GCPMachineList)
+	return Convert_v1alpha4_GCPMachineList_To_v1beta1_GCPMachineList(src, dst, nil)
+}
+
+// ConvertFrom converts from the Hub version (v1beta1) to this version.
+func (dst *GCPMachineList) ConvertFrom(srcRaw conversion.Hub) error { // nolint
+	src := srcRaw.(*v1beta1.GCPMachineList)
+	return Convert_v1beta1_GCPMachineList_To_v1alpha4_GCPMachineList(src, dst, nil)
+}
+
+// Convert_v1beta1_GCPMachineSpec_To_v1alpha4_GCPMachineSpec is an autogenerated conversion function.
+func Convert_v1beta1_GCPMachineSpec_To_v1alpha4_GCPMachineSpec(in *v1beta1.GCPMachineSpec, out *GCPMachineSpec, s apiconversion.Scope) error {
+	return autoConvert_v1beta1_GCPMachineSpec_To_v1alpha4_GCPMachineSpec(in, out, s)
+}
diff --git a/api/v1alpha4/gcpmachine_types.go b/api/v1alpha4/gcpmachine_types.go
new file mode 100644
index 00000000..ac3d84bd
--- /dev/null
+++ b/api/v1alpha4/gcpmachine_types.go
@@ -0,0 +1,226 @@
+/*
+Copyright 2021 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.
+*/
+
+package v1alpha4
+
+import (
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"sigs.k8s.io/cluster-api/errors"
+)
+
+const (
+	// MachineFinalizer allows ReconcileGCPMachine to clean up GCP resources associated with GCPMachine before
+	// removing it from the apiserver.
+	MachineFinalizer = "gcpmachine.infrastructure.cluster.x-k8s.io"
+)
+
+// DiskType is a type to use to define with disk type will be used.
+type DiskType string
+
+const (
+	// PdStandardDiskType defines the name for the standard disk.
+	PdStandardDiskType DiskType = "pd-standard"
+	// PdSsdDiskType defines the name for the ssd disk.
+	PdSsdDiskType DiskType = "pd-ssd"
+	// LocalSsdDiskType defines the name for the local ssd disk.
+	LocalSsdDiskType DiskType = "local-ssd"
+)
+
+// AttachedDiskSpec degined GCP machine disk.
+type AttachedDiskSpec struct {
+	// DeviceType is a device type of the attached disk.
+	// Supported types of non-root attached volumes:
+	// 1. "pd-standard" - Standard (HDD) persistent disk
+	// 2. "pd-ssd" - SSD persistent disk
+	// 3. "local-ssd" - Local SSD disk (https://cloud.google.com/compute/docs/disks/local-ssd).
+	// Default is "pd-standard".
+	// +optional
+	DeviceType *DiskType `json:"deviceType,omitempty"`
+	// Size is the size of the disk in GBs.
+	// Defaults to 30GB. For "local-ssd" size is always 375GB.
+	// +optional
+	Size *int64 `json:"size,omitempty"`
+}
+
+// GCPMachineSpec defines the desired state of GCPMachine.
+type GCPMachineSpec struct {
+	// InstanceType is the type of instance to create. Example: n1.standard-2
+	InstanceType string `json:"instanceType"`
+
+	// Subnet is a reference to the subnetwork to use for this instance. If not specified,
+	// the first subnetwork retrieved from the Cluster Region and Network is picked.
+	// +optional
+	Subnet *string `json:"subnet,omitempty"`
+
+	// ProviderID is the unique identifier as specified by the cloud provider.
+	// +optional
+	ProviderID *string `json:"providerID,omitempty"`
+
+	// ImageFamily is the full reference to a valid image family to be used for this machine.
+	// +optional
+	ImageFamily *string `json:"imageFamily,omitempty"`
+
+	// Image is the full reference to a valid image to be used for this machine.
+	// Takes precedence over ImageFamily.
+	// +optional
+	Image *string `json:"image,omitempty"`
+
+	// AdditionalLabels is an optional set of tags to add to an instance, in addition to the ones added by default by the
+	// GCP provider. If both the GCPCluster and the GCPMachine specify the same tag name with different values, the
+	// GCPMachine's value takes precedence.
+	// +optional
+	AdditionalLabels Labels `json:"additionalLabels,omitempty"`
+
+	// AdditionalMetadata is an optional set of metadata to add to an instance, in addition to the ones added by default by the
+	// GCP provider.
+	// +listType=map
+	// +listMapKey=key
+	// +optional
+	AdditionalMetadata []MetadataItem `json:"additionalMetadata,omitempty"`
+
+	// IAMInstanceProfile is a name of an IAM instance profile to assign to the instance
+	// +optional
+	// IAMInstanceProfile string `json:"iamInstanceProfile,omitempty"`
+
+	// PublicIP specifies whether the instance should get a public IP.
+	// Set this to true if you don't have a NAT instances or Cloud Nat setup.
+	// +optional
+	PublicIP *bool `json:"publicIP,omitempty"`
+
+	// AdditionalNetworkTags is a list of network tags that should be applied to the
+	// instance. These tags are set in addition to any network tags defined
+	// at the cluster level or in the actuator.
+	// +optional
+	AdditionalNetworkTags []string `json:"additionalNetworkTags,omitempty"`
+
+	// RootDeviceSize is the size of the root volume in GB.
+	// Defaults to 30.
+	// +optional
+	RootDeviceSize int64 `json:"rootDeviceSize,omitempty"`
+
+	// RootDeviceType is the type of the root volume.
+	// Supported types of root volumes:
+	// 1. "pd-standard" - Standard (HDD) persistent disk
+	// 2. "pd-ssd" - SSD persistent disk
+	// Default is "pd-standard".
+	// +optional
+	RootDeviceType *DiskType `json:"rootDeviceType,omitempty"`
+
+	// AdditionalDisks are optional non-boot attached disks.
+	// +optional
+	AdditionalDisks []AttachedDiskSpec `json:"additionalDisks,omitempty"`
+
+	// ServiceAccount specifies the service account email and which scopes to assign to the machine.
+	// Defaults to: email: "default", scope: []{compute.CloudPlatformScope}
+	// +optional
+	ServiceAccount *ServiceAccount `json:"serviceAccounts,omitempty"`
+
+	// Preemptible defines if instance is preemptible
+	// +optional
+	Preemptible bool `json:"preemptible,omitempty"`
+}
+
+// MetadataItem defines a single piece of metadata associated with an instance.
+type MetadataItem struct {
+	// Key is the identifier for the metadata entry.
+	Key string `json:"key"`
+	// Value is the value of the metadata entry.
+	Value *string `json:"value,omitempty"`
+}
+
+// GCPMachineStatus defines the observed state of GCPMachine.
+type GCPMachineStatus struct {
+	// Ready is true when the provider resource is ready.
+	// +optional
+	Ready bool `json:"ready"`
+
+	// Addresses contains the GCP instance associated addresses.
+	Addresses []corev1.NodeAddress `json:"addresses,omitempty"`
+
+	// InstanceStatus is the status of the GCP instance for this machine.
+	// +optional
+	InstanceStatus *InstanceStatus `json:"instanceState,omitempty"`
+
+	// FailureReason will be set in the event that there is a terminal problem
+	// reconciling the Machine and will contain a succinct value suitable
+	// for machine interpretation.
+	//
+	// This field should not be set for transitive errors that a controller
+	// faces that are expected to be fixed automatically over
+	// time (like service outages), but instead indicate that something is
+	// fundamentally wrong with the Machine's spec or the configuration of
+	// the controller, and that manual intervention is required. Examples
+	// of terminal errors would be invalid combinations of settings in the
+	// spec, values that are unsupported by the controller, or the
+	// responsible controller itself being critically misconfigured.
+	//
+	// Any transient errors that occur during the reconciliation of Machines
+	// can be added as events to the Machine object and/or logged in the
+	// controller's output.
+	// +optional
+	FailureReason *errors.MachineStatusError `json:"failureReason,omitempty"`
+
+	// FailureMessage will be set in the event that there is a terminal problem
+	// reconciling the Machine and will contain a more verbose string suitable
+	// for logging and human consumption.
+	//
+	// This field should not be set for transitive errors that a controller
+	// faces that are expected to be fixed automatically over
+	// time (like service outages), but instead indicate that something is
+	// fundamentally wrong with the Machine's spec or the configuration of
+	// the controller, and that manual intervention is required. Examples
+	// of terminal errors would be invalid combinations of settings in the
+	// spec, values that are unsupported by the controller, or the
+	// responsible controller itself being critically misconfigured.
+	//
+	// Any transient errors that occur during the reconciliation of Machines
+	// can be added as events to the Machine object and/or logged in the
+	// controller's output.
+	// +optional
+	FailureMessage *string `json:"failureMessage,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:resource:path=gcpmachines,scope=Namespaced,categories=cluster-api
+// +kubebuilder:subresource:status
+// +kubebuilder:printcolumn:name="Cluster",type="string",JSONPath=".metadata.labels.cluster\\.x-k8s\\.io/cluster-name",description="Cluster to which this GCPMachine belongs"
+// +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.instanceState",description="GCE instance state"
+// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.ready",description="Machine ready status"
+// +kubebuilder:printcolumn:name="InstanceID",type="string",JSONPath=".spec.providerID",description="GCE instance ID"
+// +kubebuilder:printcolumn:name="Machine",type="string",JSONPath=".metadata.ownerReferences[?(@.kind==\"Machine\")].name",description="Machine object which owns with this GCPMachine"
+
+// GCPMachine is the Schema for the gcpmachines API.
+type GCPMachine struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec   GCPMachineSpec   `json:"spec,omitempty"`
+	Status GCPMachineStatus `json:"status,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// GCPMachineList contains a list of GCPMachine.
+type GCPMachineList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []GCPMachine `json:"items"`
+}
+
+func init() {
+	SchemeBuilder.Register(&GCPMachine{}, &GCPMachineList{})
+}
diff --git a/api/v1alpha4/gcpmachinetemplate_conversion.go b/api/v1alpha4/gcpmachinetemplate_conversion.go
new file mode 100644
index 00000000..a12cf517
--- /dev/null
+++ b/api/v1alpha4/gcpmachinetemplate_conversion.go
@@ -0,0 +1,94 @@
+/*
+Copyright 2021 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.
+*/
+
+package v1alpha4
+
+import (
+	apiconversion "k8s.io/apimachinery/pkg/conversion"
+	infrav1beta1 "sigs.k8s.io/cluster-api-provider-gcp/api/v1beta1"
+	utilconversion "sigs.k8s.io/cluster-api/util/conversion"
+	"sigs.k8s.io/controller-runtime/pkg/conversion"
+)
+
+// ConvertTo converts this GCPMachineTemplate to the Hub version (v1beta1).
+func (src *GCPMachineTemplate) ConvertTo(dstRaw conversion.Hub) error { // nolint
+	dst := dstRaw.(*infrav1beta1.GCPMachineTemplate)
+
+	if err := Convert_v1alpha4_GCPMachineTemplate_To_v1beta1_GCPMachineTemplate(src, dst, nil); err != nil {
+		return err
+	}
+
+	// Manually restore data.
+	restored := &infrav1beta1.GCPMachineTemplate{}
+	if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok {
+		return err
+	}
+
+	dst.Spec.Template.ObjectMeta = restored.Spec.Template.ObjectMeta
+
+	if restored.Spec.Template.Spec.IPForwarding != nil {
+		dst.Spec.Template.Spec.IPForwarding = restored.Spec.Template.Spec.IPForwarding
+	}
+
+	if restored.Spec.Template.Spec.ShieldedInstanceConfig != nil {
+		dst.Spec.Template.Spec.ShieldedInstanceConfig = restored.Spec.Template.Spec.ShieldedInstanceConfig
+	}
+	if restored.Spec.Template.Spec.OnHostMaintenance != nil {
+		dst.Spec.Template.Spec.OnHostMaintenance = restored.Spec.Template.Spec.OnHostMaintenance
+	}
+
+	if restored.Spec.Template.Spec.ConfidentialCompute != nil {
+		dst.Spec.Template.Spec.ConfidentialCompute = restored.Spec.Template.Spec.ConfidentialCompute
+	}
+
+	if restored.Spec.Template.Spec.ResourceManagerTags != nil {
+		dst.Spec.Template.Spec.ResourceManagerTags = restored.Spec.Template.Spec.ResourceManagerTags
+	}
+
+	return nil
+}
+
+// ConvertFrom converts from the Hub version (v1beta1) to this version.
+func (dst *GCPMachineTemplate) ConvertFrom(srcRaw conversion.Hub) error { // nolint
+	src := srcRaw.(*infrav1beta1.GCPMachineTemplate)
+	if err := Convert_v1beta1_GCPMachineTemplate_To_v1alpha4_GCPMachineTemplate(src, dst, nil); err != nil {
+		return err
+	}
+
+	// Preserve Hub data on down-conversion.
+	if err := utilconversion.MarshalData(src, dst); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ConvertTo converts this GCPMachineTemplateList to the Hub version (v1beta1).
+func (src *GCPMachineTemplateList) ConvertTo(dstRaw conversion.Hub) error { // nolint
+	dst := dstRaw.(*infrav1beta1.GCPMachineTemplateList)
+	return Convert_v1alpha4_GCPMachineTemplateList_To_v1beta1_GCPMachineTemplateList(src, dst, nil)
+}
+
+// ConvertFrom converts from the Hub version (v1beta1) to this version.
+func (dst *GCPMachineTemplateList) ConvertFrom(srcRaw conversion.Hub) error { // nolint
+	src := srcRaw.(*infrav1beta1.GCPMachineTemplateList)
+	return Convert_v1beta1_GCPMachineTemplateList_To_v1alpha4_GCPMachineTemplateList(src, dst, nil)
+}
+
+func Convert_v1beta1_GCPMachineTemplateResource_To_v1alpha4_GCPMachineTemplateResource(in *infrav1beta1.GCPMachineTemplateResource, out *GCPMachineTemplateResource, s apiconversion.Scope) error {
+	// NOTE: custom conversion func is required because spec.template.metadata has been added in v1beta1.
+	return autoConvert_v1beta1_GCPMachineTemplateResource_To_v1alpha4_GCPMachineTemplateResource(in, out, s)
+}
diff --git a/api/v1alpha4/gcpmachinetemplate_types.go b/api/v1alpha4/gcpmachinetemplate_types.go
new file mode 100644
index 00000000..b5d62d48
--- /dev/null
+++ b/api/v1alpha4/gcpmachinetemplate_types.go
@@ -0,0 +1,50 @@
+/*
+Copyright 2021 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.
+*/
+
+package v1alpha4
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// GCPMachineTemplateSpec defines the desired state of GCPMachineTemplate.
+type GCPMachineTemplateSpec struct {
+	Template GCPMachineTemplateResource `json:"template"`
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:resource:path=gcpmachinetemplates,scope=Namespaced,categories=cluster-api
+
+// GCPMachineTemplate is the Schema for the gcpmachinetemplates API.
+type GCPMachineTemplate struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec GCPMachineTemplateSpec `json:"spec,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// GCPMachineTemplateList contains a list of GCPMachineTemplate.
+type GCPMachineTemplateList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []GCPMachineTemplate `json:"items"`
+}
+
+func init() {
+	SchemeBuilder.Register(&GCPMachineTemplate{}, &GCPMachineTemplateList{})
+}
diff --git a/api/v1alpha4/groupversion_info.go b/api/v1alpha4/groupversion_info.go
new file mode 100644
index 00000000..728422c8
--- /dev/null
+++ b/api/v1alpha4/groupversion_info.go
@@ -0,0 +1,39 @@
+/*
+Copyright 2021 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.
+*/
+
+// Package v1alpha4 contains API Schema definitions for the infrastructure v1alpha4 API group
+// +kubebuilder:object:generate=true
+// +groupName=infrastructure.cluster.x-k8s.io
+package v1alpha4
+
+import (
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"sigs.k8s.io/controller-runtime/pkg/scheme"
+)
+
+var (
+	// GroupVersion is group version used to register these objects.
+	GroupVersion = schema.GroupVersion{Group: "infrastructure.cluster.x-k8s.io", Version: "v1alpha4"}
+
+	// SchemeBuilder is used to add go types to the GroupVersionKind scheme.
+	SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
+
+	// AddToScheme adds the types in this group-version to the given scheme.
+	AddToScheme = SchemeBuilder.AddToScheme
+
+	// localSchemeBuilder is used for type conversions.
+	localSchemeBuilder = SchemeBuilder.SchemeBuilder
+)
diff --git a/api/v1alpha4/labels.go b/api/v1alpha4/labels.go
new file mode 100644
index 00000000..fa08dcaa
--- /dev/null
+++ b/api/v1alpha4/labels.go
@@ -0,0 +1,159 @@
+/*
+Copyright 2021 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.
+*/
+
+package v1alpha4
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+)
+
+// Labels defines a map of tags.
+type Labels map[string]string
+
+// Equals returns true if the tags are equal.
+func (in Labels) Equals(other Labels) bool {
+	return reflect.DeepEqual(in, other)
+}
+
+// HasOwned returns true if the tags contains a tag that marks the resource as owned by the cluster from the perspective of this management tooling.
+func (in Labels) HasOwned(cluster string) bool {
+	value, ok := in[ClusterTagKey(cluster)]
+
+	return ok && ResourceLifecycle(value) == ResourceLifecycleOwned
+}
+
+// // HasOwned returns true if the tags contains a tag that marks the resource as owned by the cluster from the perspective of the in-tree cloud provider.
+// func (in Labels) HasGCPCloudProviderOwned(cluster string) bool {
+// 	value, ok := t[ClusterGCPCloudProviderTagKey(cluster)]
+// 	return ok && ResourceLifecycle(value) == ResourceLifecycleOwned
+// }
+
+// GetRole returns the Cluster API role for the tagged resource.
+func (in Labels) GetRole() string {
+	return in[NameGCPClusterAPIRole]
+}
+
+// ToComputeFilter returns the string representation of the labels as a filter
+// to be used in google compute sdk calls.
+func (in Labels) ToComputeFilter() string {
+	var builder strings.Builder
+	for k, v := range in {
+		builder.WriteString(fmt.Sprintf("(labels.%s = %q) ", k, v))
+	}
+
+	return builder.String()
+}
+
+// Difference returns the difference between this map of tags and the other map of tags.
+// Items are considered equals if key and value are equals.
+func (in Labels) Difference(other Labels) Labels {
+	res := make(Labels, len(in))
+
+	for key, value := range in {
+		if otherValue, ok := other[key]; ok && value == otherValue {
+			continue
+		}
+		res[key] = value
+	}
+
+	return res
+}
+
+// AddLabels adds (and overwrites) the current labels with the ones passed in.
+func (in Labels) AddLabels(other Labels) Labels {
+	for key, value := range other {
+		if in == nil {
+			in = make(map[string]string, len(other))
+		}
+		in[key] = value
+	}
+
+	return in
+}
+
+// ResourceLifecycle configures the lifecycle of a resource.
+type ResourceLifecycle string
+
+const (
+	// ResourceLifecycleOwned is the value we use when tagging resources to indicate
+	// that the resource is considered owned and managed by the cluster,
+	// and in particular that the lifecycle is tied to the lifecycle of the cluster.
+	ResourceLifecycleOwned = ResourceLifecycle("owned")
+
+	// NameGCPProviderPrefix is the tag prefix we use to differentiate
+	// cluster-api-provider-gcp owned components from other tooling that
+	// uses NameKubernetesClusterPrefix.
+	NameGCPProviderPrefix = "capg-"
+
+	// NameGCPProviderOwned is the tag name we use to differentiate
+	// cluster-api-provider-gcp owned components from other tooling that
+	// uses NameKubernetesClusterPrefix.
+	NameGCPProviderOwned = NameGCPProviderPrefix + "cluster-"
+
+	// NameGCPClusterAPIRole is the tag name we use to mark roles for resources
+	// dedicated to this cluster api provider implementation.
+	NameGCPClusterAPIRole = NameGCPProviderPrefix + "role"
+
+	// APIServerRoleTagValue describes the value for the apiserver role.
+	APIServerRoleTagValue = "apiserver"
+)
+
+// ClusterTagKey generates the key for resources associated with a cluster.
+func ClusterTagKey(name string) string {
+	return fmt.Sprintf("%s%s", NameGCPProviderOwned, name)
+}
+
+// ClusterGCPCloudProviderTagKey generates the key for resources associated a cluster's GCP cloud provider.
+// func ClusterGCPCloudProviderTagKey(name string) string {
+// return fmt.Sprintf("%s%s", NameKubernetesGCPCloudProviderPrefix, name)
+// }
+
+// BuildParams is used to build tags around an gcp resource.
+type BuildParams struct {
+	// Lifecycle determines the resource lifecycle.
+	Lifecycle ResourceLifecycle
+
+	// ClusterName is the cluster associated with the resource.
+	ClusterName string
+
+	// ResourceID is the unique identifier of the resource to be tagged.
+	ResourceID string
+
+	// Role is the role associated to the resource.
+	// +optional
+	Role *string
+
+	// Any additional tags to be added to the resource.
+	// +optional
+	Additional Labels
+}
+
+// Build builds tags including the cluster tag and returns them in map form.
+func Build(params BuildParams) Labels {
+	tags := make(Labels)
+	for k, v := range params.Additional {
+		tags[strings.ToLower(k)] = strings.ToLower(v)
+	}
+
+	tags[ClusterTagKey(params.ClusterName)] = string(params.Lifecycle)
+	if params.Role != nil {
+		tags[NameGCPClusterAPIRole] = strings.ToLower(*params.Role)
+	}
+
+	return tags
+}
diff --git a/api/v1alpha4/types.go b/api/v1alpha4/types.go
new file mode 100644
index 00000000..c85215cc
--- /dev/null
+++ b/api/v1alpha4/types.go
@@ -0,0 +1,231 @@
+/*
+Copyright 2021 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.
+*/
+
+package v1alpha4
+
+import (
+	"fmt"
+)
+
+// GCPMachineTemplateResource describes the data needed to create am GCPMachine from a template.
+type GCPMachineTemplateResource struct {
+	// Spec is the specification of the desired behavior of the machine.
+	Spec GCPMachineSpec `json:"spec"`
+}
+
+// Filter is a filter used to identify an GCP resource.
+type Filter struct {
+	// Name of the filter. Filter names are case-sensitive.
+	Name string `json:"name"`
+
+	// Values includes one or more filter values. Filter values are case-sensitive.
+	Values []string `json:"values"`
+}
+
+// Network encapsulates GCP networking resources.
+type Network struct {
+	// SelfLink is the link to the Network used for this cluster.
+	SelfLink *string `json:"selfLink,omitempty"`
+
+	// FirewallRules is a map from the name of the rule to its full reference.
+	// +optional
+	FirewallRules map[string]string `json:"firewallRules,omitempty"`
+
+	// Router is the full reference to the router created within the network
+	// it'll contain the cloud nat gateway
+	// +optional
+	Router *string `json:"router,omitempty"`
+
+	// APIServerAddress is the IPV4 global address assigned to the load balancer
+	// created for the API Server.
+	// +optional
+	APIServerAddress *string `json:"apiServerIpAddress,omitempty"`
+
+	// APIServerHealthCheck is the full reference to the health check
+	// created for the API Server.
+	// +optional
+	APIServerHealthCheck *string `json:"apiServerHealthCheck,omitempty"`
+
+	// APIServerInstanceGroups is a map from zone to the full reference
+	// to the instance groups created for the control plane nodes created in the same zone.
+	// +optional
+	APIServerInstanceGroups map[string]string `json:"apiServerInstanceGroups,omitempty"`
+
+	// APIServerBackendService is the full reference to the backend service
+	// created for the API Server.
+	// +optional
+	APIServerBackendService *string `json:"apiServerBackendService,omitempty"`
+
+	// APIServerTargetProxy is the full reference to the target proxy
+	// created for the API Server.
+	// +optional
+	APIServerTargetProxy *string `json:"apiServerTargetProxy,omitempty"`
+
+	// APIServerForwardingRule is the full reference to the forwarding rule
+	// created for the API Server.
+	// +optional
+	APIServerForwardingRule *string `json:"apiServerForwardingRule,omitempty"`
+}
+
+// NetworkSpec encapsulates all things related to a GCP network.
+type NetworkSpec struct {
+	// Name is the name of the network to be used.
+	// +optional
+	Name *string `json:"name,omitempty"`
+
+	// AutoCreateSubnetworks: When set to true, the VPC network is created
+	// in "auto" mode. When set to false, the VPC network is created in
+	// "custom" mode.
+	//
+	// An auto mode VPC network starts with one subnet per region. Each
+	// subnet has a predetermined range as described in Auto mode VPC
+	// network IP ranges.
+	//
+	// Defaults to true.
+	// +optional
+	AutoCreateSubnetworks *bool `json:"autoCreateSubnetworks,omitempty"`
+
+	// Subnets configuration.
+	// +optional
+	Subnets Subnets `json:"subnets,omitempty"`
+
+	// Allow for configuration of load balancer backend (useful for changing apiserver port)
+	// +optional
+	LoadBalancerBackendPort *int32 `json:"loadBalancerBackendPort,omitempty"`
+}
+
+// SubnetSpec configures an GCP Subnet.
+type SubnetSpec struct {
+	// Name defines a unique identifier to reference this resource.
+	Name string `json:"name,omitempty"`
+
+	// CidrBlock is the range of internal addresses that are owned by this
+	// subnetwork. Provide this property when you create the subnetwork. For
+	// example, 10.0.0.0/8 or 192.168.0.0/16. Ranges must be unique and
+	// non-overlapping within a network. Only IPv4 is supported. This field
+	// can be set only at resource creation time.
+	CidrBlock string `json:"cidrBlock,omitempty"`
+
+	// Description is an optional description associated with the resource.
+	// +optional
+	Description *string `json:"description,omitempty"`
+
+	// SecondaryCidrBlocks defines secondary CIDR ranges,
+	// from which secondary IP ranges of a VM may be allocated
+	// +optional
+	SecondaryCidrBlocks map[string]string `json:"secondaryCidrBlocks,omitempty"`
+
+	// Region is the name of the region where the Subnetwork resides.
+	Region string `json:"region,omitempty"`
+
+	// PrivateGoogleAccess defines whether VMs in this subnet can access
+	// Google services without assigning external IP addresses
+	// +optional
+	PrivateGoogleAccess *bool `json:"privateGoogleAccess,omitempty"`
+
+	// EnableFlowLogs: Whether to enable flow logging for this subnetwork.
+	// If this field is not explicitly set, it will not appear in get
+	// listings. If not set the default behavior is to disable flow logging.
+	// +optional
+	EnableFlowLogs *bool `json:"routeTableId"`
+}
+
+// String returns a string representation of the subnet.
+func (s *SubnetSpec) String() string {
+	return fmt.Sprintf("name=%s/region=%s", s.Name, s.Region)
+}
+
+// Subnets is a slice of Subnet.
+type Subnets []SubnetSpec
+
+// ToMap returns a map from name to subnet.
+func (s Subnets) ToMap() map[string]*SubnetSpec {
+	res := make(map[string]*SubnetSpec)
+	for i := range s {
+		x := s[i]
+		res[x.Name] = &x
+	}
+
+	return res
+}
+
+// FindByName returns a single subnet matching the given name or nil.
+func (s Subnets) FindByName(name string) *SubnetSpec {
+	for _, x := range s {
+		if x.Name == name {
+			return &x
+		}
+	}
+
+	return nil
+}
+
+// FilterByRegion returns a slice containing all subnets that live in the specified region.
+func (s Subnets) FilterByRegion(region string) (res Subnets) {
+	for _, x := range s {
+		if x.Region == region {
+			res = append(res, x)
+		}
+	}
+
+	return
+}
+
+// InstanceStatus describes the state of an GCP instance.
+type InstanceStatus string
+
+var (
+	// InstanceStatusProvisioning is the string representing an instance in a provisioning state.
+	InstanceStatusProvisioning = InstanceStatus("PROVISIONING")
+
+	// InstanceStatusRepairing is the string representing an instance in a repairing state.
+	InstanceStatusRepairing = InstanceStatus("REPAIRING")
+
+	// InstanceStatusRunning is the string representing an instance in a pending state.
+	InstanceStatusRunning = InstanceStatus("RUNNING")
+
+	// InstanceStatusStaging is the string representing an instance in a staging state.
+	InstanceStatusStaging = InstanceStatus("STAGING")
+
+	// InstanceStatusStopped is the string representing an instance
+	// that has been stopped and can be restarted.
+	InstanceStatusStopped = InstanceStatus("STOPPED")
+
+	// InstanceStatusStopping is the string representing an instance
+	// that is in the process of being stopped and can be restarted.
+	InstanceStatusStopping = InstanceStatus("STOPPING")
+
+	// InstanceStatusSuspended is the string representing an instance
+	// that is suspended.
+	InstanceStatusSuspended = InstanceStatus("SUSPENDED")
+
+	// InstanceStatusSuspending is the string representing an instance
+	// that is in the process of being suspended.
+	InstanceStatusSuspending = InstanceStatus("SUSPENDING")
+
+	// InstanceStatusTerminated is the string representing an instance that has been terminated.
+	InstanceStatusTerminated = InstanceStatus("TERMINATED")
+)
+
+// ServiceAccount describes compute.serviceAccount.
+type ServiceAccount struct {
+	// Email: Email address of the service account.
+	Email string `json:"email,omitempty"`
+
+	// Scopes: The list of scopes to be made available for this service
+	// account.
+	Scopes []string `json:"scopes,omitempty"`
+}
diff --git a/api/v1alpha4/zz_generated.conversion.go b/api/v1alpha4/zz_generated.conversion.go
new file mode 100644
index 00000000..053f1529
--- /dev/null
+++ b/api/v1alpha4/zz_generated.conversion.go
@@ -0,0 +1,1043 @@
+//go:build !ignore_autogenerated_core_v1alpha4
+// +build !ignore_autogenerated_core_v1alpha4
+
+/*
+Copyright 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.
+*/
+
+// Code generated by conversion-gen-v0.28.6. DO NOT EDIT.
+
+package v1alpha4
+
+import (
+	unsafe "unsafe"
+
+	v1 "k8s.io/api/core/v1"
+	conversion "k8s.io/apimachinery/pkg/conversion"
+	runtime "k8s.io/apimachinery/pkg/runtime"
+	v1beta1 "sigs.k8s.io/cluster-api-provider-gcp/api/v1beta1"
+	apiv1alpha4 "sigs.k8s.io/cluster-api/api/v1alpha4"
+	apiv1beta1 "sigs.k8s.io/cluster-api/api/v1beta1"
+	errors "sigs.k8s.io/cluster-api/errors"
+)
+
+func init() {
+	localSchemeBuilder.Register(RegisterConversions)
+}
+
+// RegisterConversions adds conversion functions to the given scheme.
+// Public to allow building arbitrary schemes.
+func RegisterConversions(s *runtime.Scheme) error {
+	if err := s.AddGeneratedConversionFunc((*AttachedDiskSpec)(nil), (*v1beta1.AttachedDiskSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_AttachedDiskSpec_To_v1beta1_AttachedDiskSpec(a.(*AttachedDiskSpec), b.(*v1beta1.AttachedDiskSpec), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*v1beta1.AttachedDiskSpec)(nil), (*AttachedDiskSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_AttachedDiskSpec_To_v1alpha4_AttachedDiskSpec(a.(*v1beta1.AttachedDiskSpec), b.(*AttachedDiskSpec), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*BuildParams)(nil), (*v1beta1.BuildParams)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_BuildParams_To_v1beta1_BuildParams(a.(*BuildParams), b.(*v1beta1.BuildParams), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*v1beta1.BuildParams)(nil), (*BuildParams)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_BuildParams_To_v1alpha4_BuildParams(a.(*v1beta1.BuildParams), b.(*BuildParams), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*Filter)(nil), (*v1beta1.Filter)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_Filter_To_v1beta1_Filter(a.(*Filter), b.(*v1beta1.Filter), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*v1beta1.Filter)(nil), (*Filter)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_Filter_To_v1alpha4_Filter(a.(*v1beta1.Filter), b.(*Filter), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*GCPCluster)(nil), (*v1beta1.GCPCluster)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_GCPCluster_To_v1beta1_GCPCluster(a.(*GCPCluster), b.(*v1beta1.GCPCluster), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*v1beta1.GCPCluster)(nil), (*GCPCluster)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_GCPCluster_To_v1alpha4_GCPCluster(a.(*v1beta1.GCPCluster), b.(*GCPCluster), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*GCPClusterList)(nil), (*v1beta1.GCPClusterList)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_GCPClusterList_To_v1beta1_GCPClusterList(a.(*GCPClusterList), b.(*v1beta1.GCPClusterList), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*v1beta1.GCPClusterList)(nil), (*GCPClusterList)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_GCPClusterList_To_v1alpha4_GCPClusterList(a.(*v1beta1.GCPClusterList), b.(*GCPClusterList), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*GCPClusterSpec)(nil), (*v1beta1.GCPClusterSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_GCPClusterSpec_To_v1beta1_GCPClusterSpec(a.(*GCPClusterSpec), b.(*v1beta1.GCPClusterSpec), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*GCPClusterStatus)(nil), (*v1beta1.GCPClusterStatus)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_GCPClusterStatus_To_v1beta1_GCPClusterStatus(a.(*GCPClusterStatus), b.(*v1beta1.GCPClusterStatus), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*v1beta1.GCPClusterStatus)(nil), (*GCPClusterStatus)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_GCPClusterStatus_To_v1alpha4_GCPClusterStatus(a.(*v1beta1.GCPClusterStatus), b.(*GCPClusterStatus), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*GCPClusterTemplate)(nil), (*v1beta1.GCPClusterTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_GCPClusterTemplate_To_v1beta1_GCPClusterTemplate(a.(*GCPClusterTemplate), b.(*v1beta1.GCPClusterTemplate), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*v1beta1.GCPClusterTemplate)(nil), (*GCPClusterTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_GCPClusterTemplate_To_v1alpha4_GCPClusterTemplate(a.(*v1beta1.GCPClusterTemplate), b.(*GCPClusterTemplate), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*GCPClusterTemplateList)(nil), (*v1beta1.GCPClusterTemplateList)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_GCPClusterTemplateList_To_v1beta1_GCPClusterTemplateList(a.(*GCPClusterTemplateList), b.(*v1beta1.GCPClusterTemplateList), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*v1beta1.GCPClusterTemplateList)(nil), (*GCPClusterTemplateList)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_GCPClusterTemplateList_To_v1alpha4_GCPClusterTemplateList(a.(*v1beta1.GCPClusterTemplateList), b.(*GCPClusterTemplateList), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*GCPClusterTemplateResource)(nil), (*v1beta1.GCPClusterTemplateResource)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_GCPClusterTemplateResource_To_v1beta1_GCPClusterTemplateResource(a.(*GCPClusterTemplateResource), b.(*v1beta1.GCPClusterTemplateResource), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*GCPClusterTemplateSpec)(nil), (*v1beta1.GCPClusterTemplateSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_GCPClusterTemplateSpec_To_v1beta1_GCPClusterTemplateSpec(a.(*GCPClusterTemplateSpec), b.(*v1beta1.GCPClusterTemplateSpec), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*v1beta1.GCPClusterTemplateSpec)(nil), (*GCPClusterTemplateSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_GCPClusterTemplateSpec_To_v1alpha4_GCPClusterTemplateSpec(a.(*v1beta1.GCPClusterTemplateSpec), b.(*GCPClusterTemplateSpec), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*GCPMachine)(nil), (*v1beta1.GCPMachine)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_GCPMachine_To_v1beta1_GCPMachine(a.(*GCPMachine), b.(*v1beta1.GCPMachine), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*v1beta1.GCPMachine)(nil), (*GCPMachine)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_GCPMachine_To_v1alpha4_GCPMachine(a.(*v1beta1.GCPMachine), b.(*GCPMachine), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*GCPMachineList)(nil), (*v1beta1.GCPMachineList)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_GCPMachineList_To_v1beta1_GCPMachineList(a.(*GCPMachineList), b.(*v1beta1.GCPMachineList), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*v1beta1.GCPMachineList)(nil), (*GCPMachineList)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_GCPMachineList_To_v1alpha4_GCPMachineList(a.(*v1beta1.GCPMachineList), b.(*GCPMachineList), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*GCPMachineSpec)(nil), (*v1beta1.GCPMachineSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_GCPMachineSpec_To_v1beta1_GCPMachineSpec(a.(*GCPMachineSpec), b.(*v1beta1.GCPMachineSpec), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*GCPMachineStatus)(nil), (*v1beta1.GCPMachineStatus)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_GCPMachineStatus_To_v1beta1_GCPMachineStatus(a.(*GCPMachineStatus), b.(*v1beta1.GCPMachineStatus), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*v1beta1.GCPMachineStatus)(nil), (*GCPMachineStatus)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_GCPMachineStatus_To_v1alpha4_GCPMachineStatus(a.(*v1beta1.GCPMachineStatus), b.(*GCPMachineStatus), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*GCPMachineTemplate)(nil), (*v1beta1.GCPMachineTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_GCPMachineTemplate_To_v1beta1_GCPMachineTemplate(a.(*GCPMachineTemplate), b.(*v1beta1.GCPMachineTemplate), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*v1beta1.GCPMachineTemplate)(nil), (*GCPMachineTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_GCPMachineTemplate_To_v1alpha4_GCPMachineTemplate(a.(*v1beta1.GCPMachineTemplate), b.(*GCPMachineTemplate), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*GCPMachineTemplateList)(nil), (*v1beta1.GCPMachineTemplateList)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_GCPMachineTemplateList_To_v1beta1_GCPMachineTemplateList(a.(*GCPMachineTemplateList), b.(*v1beta1.GCPMachineTemplateList), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*v1beta1.GCPMachineTemplateList)(nil), (*GCPMachineTemplateList)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_GCPMachineTemplateList_To_v1alpha4_GCPMachineTemplateList(a.(*v1beta1.GCPMachineTemplateList), b.(*GCPMachineTemplateList), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*GCPMachineTemplateResource)(nil), (*v1beta1.GCPMachineTemplateResource)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_GCPMachineTemplateResource_To_v1beta1_GCPMachineTemplateResource(a.(*GCPMachineTemplateResource), b.(*v1beta1.GCPMachineTemplateResource), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*GCPMachineTemplateSpec)(nil), (*v1beta1.GCPMachineTemplateSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_GCPMachineTemplateSpec_To_v1beta1_GCPMachineTemplateSpec(a.(*GCPMachineTemplateSpec), b.(*v1beta1.GCPMachineTemplateSpec), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*v1beta1.GCPMachineTemplateSpec)(nil), (*GCPMachineTemplateSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_GCPMachineTemplateSpec_To_v1alpha4_GCPMachineTemplateSpec(a.(*v1beta1.GCPMachineTemplateSpec), b.(*GCPMachineTemplateSpec), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*MetadataItem)(nil), (*v1beta1.MetadataItem)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_MetadataItem_To_v1beta1_MetadataItem(a.(*MetadataItem), b.(*v1beta1.MetadataItem), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*v1beta1.MetadataItem)(nil), (*MetadataItem)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_MetadataItem_To_v1alpha4_MetadataItem(a.(*v1beta1.MetadataItem), b.(*MetadataItem), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*Network)(nil), (*v1beta1.Network)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_Network_To_v1beta1_Network(a.(*Network), b.(*v1beta1.Network), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*v1beta1.Network)(nil), (*Network)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_Network_To_v1alpha4_Network(a.(*v1beta1.Network), b.(*Network), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*NetworkSpec)(nil), (*v1beta1.NetworkSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_NetworkSpec_To_v1beta1_NetworkSpec(a.(*NetworkSpec), b.(*v1beta1.NetworkSpec), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*v1beta1.NetworkSpec)(nil), (*NetworkSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_NetworkSpec_To_v1alpha4_NetworkSpec(a.(*v1beta1.NetworkSpec), b.(*NetworkSpec), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*ServiceAccount)(nil), (*v1beta1.ServiceAccount)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_ServiceAccount_To_v1beta1_ServiceAccount(a.(*ServiceAccount), b.(*v1beta1.ServiceAccount), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*v1beta1.ServiceAccount)(nil), (*ServiceAccount)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_ServiceAccount_To_v1alpha4_ServiceAccount(a.(*v1beta1.ServiceAccount), b.(*ServiceAccount), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*SubnetSpec)(nil), (*v1beta1.SubnetSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha4_SubnetSpec_To_v1beta1_SubnetSpec(a.(*SubnetSpec), b.(*v1beta1.SubnetSpec), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddConversionFunc((*v1beta1.GCPClusterSpec)(nil), (*GCPClusterSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_GCPClusterSpec_To_v1alpha4_GCPClusterSpec(a.(*v1beta1.GCPClusterSpec), b.(*GCPClusterSpec), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddConversionFunc((*v1beta1.GCPClusterTemplateResource)(nil), (*GCPClusterTemplateResource)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_GCPClusterTemplateResource_To_v1alpha4_GCPClusterTemplateResource(a.(*v1beta1.GCPClusterTemplateResource), b.(*GCPClusterTemplateResource), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddConversionFunc((*v1beta1.GCPMachineSpec)(nil), (*GCPMachineSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_GCPMachineSpec_To_v1alpha4_GCPMachineSpec(a.(*v1beta1.GCPMachineSpec), b.(*GCPMachineSpec), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddConversionFunc((*v1beta1.GCPMachineTemplateResource)(nil), (*GCPMachineTemplateResource)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_GCPMachineTemplateResource_To_v1alpha4_GCPMachineTemplateResource(a.(*v1beta1.GCPMachineTemplateResource), b.(*GCPMachineTemplateResource), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddConversionFunc((*v1beta1.SubnetSpec)(nil), (*SubnetSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_SubnetSpec_To_v1alpha4_SubnetSpec(a.(*v1beta1.SubnetSpec), b.(*SubnetSpec), scope)
+	}); err != nil {
+		return err
+	}
+	return nil
+}
+
+func autoConvert_v1alpha4_AttachedDiskSpec_To_v1beta1_AttachedDiskSpec(in *AttachedDiskSpec, out *v1beta1.AttachedDiskSpec, s conversion.Scope) error {
+	out.DeviceType = (*v1beta1.DiskType)(unsafe.Pointer(in.DeviceType))
+	out.Size = (*int64)(unsafe.Pointer(in.Size))
+	return nil
+}
+
+// Convert_v1alpha4_AttachedDiskSpec_To_v1beta1_AttachedDiskSpec is an autogenerated conversion function.
+func Convert_v1alpha4_AttachedDiskSpec_To_v1beta1_AttachedDiskSpec(in *AttachedDiskSpec, out *v1beta1.AttachedDiskSpec, s conversion.Scope) error {
+	return autoConvert_v1alpha4_AttachedDiskSpec_To_v1beta1_AttachedDiskSpec(in, out, s)
+}
+
+func autoConvert_v1beta1_AttachedDiskSpec_To_v1alpha4_AttachedDiskSpec(in *v1beta1.AttachedDiskSpec, out *AttachedDiskSpec, s conversion.Scope) error {
+	out.DeviceType = (*DiskType)(unsafe.Pointer(in.DeviceType))
+	out.Size = (*int64)(unsafe.Pointer(in.Size))
+	return nil
+}
+
+// Convert_v1beta1_AttachedDiskSpec_To_v1alpha4_AttachedDiskSpec is an autogenerated conversion function.
+func Convert_v1beta1_AttachedDiskSpec_To_v1alpha4_AttachedDiskSpec(in *v1beta1.AttachedDiskSpec, out *AttachedDiskSpec, s conversion.Scope) error {
+	return autoConvert_v1beta1_AttachedDiskSpec_To_v1alpha4_AttachedDiskSpec(in, out, s)
+}
+
+func autoConvert_v1alpha4_BuildParams_To_v1beta1_BuildParams(in *BuildParams, out *v1beta1.BuildParams, s conversion.Scope) error {
+	out.Lifecycle = v1beta1.ResourceLifecycle(in.Lifecycle)
+	out.ClusterName = in.ClusterName
+	out.ResourceID = in.ResourceID
+	out.Role = (*string)(unsafe.Pointer(in.Role))
+	out.Additional = *(*v1beta1.Labels)(unsafe.Pointer(&in.Additional))
+	return nil
+}
+
+// Convert_v1alpha4_BuildParams_To_v1beta1_BuildParams is an autogenerated conversion function.
+func Convert_v1alpha4_BuildParams_To_v1beta1_BuildParams(in *BuildParams, out *v1beta1.BuildParams, s conversion.Scope) error {
+	return autoConvert_v1alpha4_BuildParams_To_v1beta1_BuildParams(in, out, s)
+}
+
+func autoConvert_v1beta1_BuildParams_To_v1alpha4_BuildParams(in *v1beta1.BuildParams, out *BuildParams, s conversion.Scope) error {
+	out.Lifecycle = ResourceLifecycle(in.Lifecycle)
+	out.ClusterName = in.ClusterName
+	out.ResourceID = in.ResourceID
+	out.Role = (*string)(unsafe.Pointer(in.Role))
+	out.Additional = *(*Labels)(unsafe.Pointer(&in.Additional))
+	return nil
+}
+
+// Convert_v1beta1_BuildParams_To_v1alpha4_BuildParams is an autogenerated conversion function.
+func Convert_v1beta1_BuildParams_To_v1alpha4_BuildParams(in *v1beta1.BuildParams, out *BuildParams, s conversion.Scope) error {
+	return autoConvert_v1beta1_BuildParams_To_v1alpha4_BuildParams(in, out, s)
+}
+
+func autoConvert_v1alpha4_Filter_To_v1beta1_Filter(in *Filter, out *v1beta1.Filter, s conversion.Scope) error {
+	out.Name = in.Name
+	out.Values = *(*[]string)(unsafe.Pointer(&in.Values))
+	return nil
+}
+
+// Convert_v1alpha4_Filter_To_v1beta1_Filter is an autogenerated conversion function.
+func Convert_v1alpha4_Filter_To_v1beta1_Filter(in *Filter, out *v1beta1.Filter, s conversion.Scope) error {
+	return autoConvert_v1alpha4_Filter_To_v1beta1_Filter(in, out, s)
+}
+
+func autoConvert_v1beta1_Filter_To_v1alpha4_Filter(in *v1beta1.Filter, out *Filter, s conversion.Scope) error {
+	out.Name = in.Name
+	out.Values = *(*[]string)(unsafe.Pointer(&in.Values))
+	return nil
+}
+
+// Convert_v1beta1_Filter_To_v1alpha4_Filter is an autogenerated conversion function.
+func Convert_v1beta1_Filter_To_v1alpha4_Filter(in *v1beta1.Filter, out *Filter, s conversion.Scope) error {
+	return autoConvert_v1beta1_Filter_To_v1alpha4_Filter(in, out, s)
+}
+
+func autoConvert_v1alpha4_GCPCluster_To_v1beta1_GCPCluster(in *GCPCluster, out *v1beta1.GCPCluster, s conversion.Scope) error {
+	out.ObjectMeta = in.ObjectMeta
+	if err := Convert_v1alpha4_GCPClusterSpec_To_v1beta1_GCPClusterSpec(&in.Spec, &out.Spec, s); err != nil {
+		return err
+	}
+	if err := Convert_v1alpha4_GCPClusterStatus_To_v1beta1_GCPClusterStatus(&in.Status, &out.Status, s); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Convert_v1alpha4_GCPCluster_To_v1beta1_GCPCluster is an autogenerated conversion function.
+func Convert_v1alpha4_GCPCluster_To_v1beta1_GCPCluster(in *GCPCluster, out *v1beta1.GCPCluster, s conversion.Scope) error {
+	return autoConvert_v1alpha4_GCPCluster_To_v1beta1_GCPCluster(in, out, s)
+}
+
+func autoConvert_v1beta1_GCPCluster_To_v1alpha4_GCPCluster(in *v1beta1.GCPCluster, out *GCPCluster, s conversion.Scope) error {
+	out.ObjectMeta = in.ObjectMeta
+	if err := Convert_v1beta1_GCPClusterSpec_To_v1alpha4_GCPClusterSpec(&in.Spec, &out.Spec, s); err != nil {
+		return err
+	}
+	if err := Convert_v1beta1_GCPClusterStatus_To_v1alpha4_GCPClusterStatus(&in.Status, &out.Status, s); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Convert_v1beta1_GCPCluster_To_v1alpha4_GCPCluster is an autogenerated conversion function.
+func Convert_v1beta1_GCPCluster_To_v1alpha4_GCPCluster(in *v1beta1.GCPCluster, out *GCPCluster, s conversion.Scope) error {
+	return autoConvert_v1beta1_GCPCluster_To_v1alpha4_GCPCluster(in, out, s)
+}
+
+func autoConvert_v1alpha4_GCPClusterList_To_v1beta1_GCPClusterList(in *GCPClusterList, out *v1beta1.GCPClusterList, s conversion.Scope) error {
+	out.ListMeta = in.ListMeta
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]v1beta1.GCPCluster, len(*in))
+		for i := range *in {
+			if err := Convert_v1alpha4_GCPCluster_To_v1beta1_GCPCluster(&(*in)[i], &(*out)[i], s); err != nil {
+				return err
+			}
+		}
+	} else {
+		out.Items = nil
+	}
+	return nil
+}
+
+// Convert_v1alpha4_GCPClusterList_To_v1beta1_GCPClusterList is an autogenerated conversion function.
+func Convert_v1alpha4_GCPClusterList_To_v1beta1_GCPClusterList(in *GCPClusterList, out *v1beta1.GCPClusterList, s conversion.Scope) error {
+	return autoConvert_v1alpha4_GCPClusterList_To_v1beta1_GCPClusterList(in, out, s)
+}
+
+func autoConvert_v1beta1_GCPClusterList_To_v1alpha4_GCPClusterList(in *v1beta1.GCPClusterList, out *GCPClusterList, s conversion.Scope) error {
+	out.ListMeta = in.ListMeta
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]GCPCluster, len(*in))
+		for i := range *in {
+			if err := Convert_v1beta1_GCPCluster_To_v1alpha4_GCPCluster(&(*in)[i], &(*out)[i], s); err != nil {
+				return err
+			}
+		}
+	} else {
+		out.Items = nil
+	}
+	return nil
+}
+
+// Convert_v1beta1_GCPClusterList_To_v1alpha4_GCPClusterList is an autogenerated conversion function.
+func Convert_v1beta1_GCPClusterList_To_v1alpha4_GCPClusterList(in *v1beta1.GCPClusterList, out *GCPClusterList, s conversion.Scope) error {
+	return autoConvert_v1beta1_GCPClusterList_To_v1alpha4_GCPClusterList(in, out, s)
+}
+
+func autoConvert_v1alpha4_GCPClusterSpec_To_v1beta1_GCPClusterSpec(in *GCPClusterSpec, out *v1beta1.GCPClusterSpec, s conversion.Scope) error {
+	out.Project = in.Project
+	out.Region = in.Region
+	if err := apiv1alpha4.Convert_v1alpha4_APIEndpoint_To_v1beta1_APIEndpoint(&in.ControlPlaneEndpoint, &out.ControlPlaneEndpoint, s); err != nil {
+		return err
+	}
+	if err := Convert_v1alpha4_NetworkSpec_To_v1beta1_NetworkSpec(&in.Network, &out.Network, s); err != nil {
+		return err
+	}
+	out.FailureDomains = *(*[]string)(unsafe.Pointer(&in.FailureDomains))
+	out.AdditionalLabels = *(*v1beta1.Labels)(unsafe.Pointer(&in.AdditionalLabels))
+	return nil
+}
+
+// Convert_v1alpha4_GCPClusterSpec_To_v1beta1_GCPClusterSpec is an autogenerated conversion function.
+func Convert_v1alpha4_GCPClusterSpec_To_v1beta1_GCPClusterSpec(in *GCPClusterSpec, out *v1beta1.GCPClusterSpec, s conversion.Scope) error {
+	return autoConvert_v1alpha4_GCPClusterSpec_To_v1beta1_GCPClusterSpec(in, out, s)
+}
+
+func autoConvert_v1beta1_GCPClusterSpec_To_v1alpha4_GCPClusterSpec(in *v1beta1.GCPClusterSpec, out *GCPClusterSpec, s conversion.Scope) error {
+	out.Project = in.Project
+	out.Region = in.Region
+	if err := apiv1alpha4.Convert_v1beta1_APIEndpoint_To_v1alpha4_APIEndpoint(&in.ControlPlaneEndpoint, &out.ControlPlaneEndpoint, s); err != nil {
+		return err
+	}
+	if err := Convert_v1beta1_NetworkSpec_To_v1alpha4_NetworkSpec(&in.Network, &out.Network, s); err != nil {
+		return err
+	}
+	out.FailureDomains = *(*[]string)(unsafe.Pointer(&in.FailureDomains))
+	out.AdditionalLabels = *(*Labels)(unsafe.Pointer(&in.AdditionalLabels))
+	// WARNING: in.ResourceManagerTags requires manual conversion: does not exist in peer-type
+	// WARNING: in.CredentialsRef requires manual conversion: does not exist in peer-type
+	return nil
+}
+
+func autoConvert_v1alpha4_GCPClusterStatus_To_v1beta1_GCPClusterStatus(in *GCPClusterStatus, out *v1beta1.GCPClusterStatus, s conversion.Scope) error {
+	if in.FailureDomains != nil {
+		in, out := &in.FailureDomains, &out.FailureDomains
+		*out = make(apiv1beta1.FailureDomains, len(*in))
+		for key, val := range *in {
+			newVal := new(apiv1beta1.FailureDomainSpec)
+			if err := apiv1alpha4.Convert_v1alpha4_FailureDomainSpec_To_v1beta1_FailureDomainSpec(&val, newVal, s); err != nil {
+				return err
+			}
+			(*out)[key] = *newVal
+		}
+	} else {
+		out.FailureDomains = nil
+	}
+	if err := Convert_v1alpha4_Network_To_v1beta1_Network(&in.Network, &out.Network, s); err != nil {
+		return err
+	}
+	out.Ready = in.Ready
+	return nil
+}
+
+// Convert_v1alpha4_GCPClusterStatus_To_v1beta1_GCPClusterStatus is an autogenerated conversion function.
+func Convert_v1alpha4_GCPClusterStatus_To_v1beta1_GCPClusterStatus(in *GCPClusterStatus, out *v1beta1.GCPClusterStatus, s conversion.Scope) error {
+	return autoConvert_v1alpha4_GCPClusterStatus_To_v1beta1_GCPClusterStatus(in, out, s)
+}
+
+func autoConvert_v1beta1_GCPClusterStatus_To_v1alpha4_GCPClusterStatus(in *v1beta1.GCPClusterStatus, out *GCPClusterStatus, s conversion.Scope) error {
+	if in.FailureDomains != nil {
+		in, out := &in.FailureDomains, &out.FailureDomains
+		*out = make(apiv1alpha4.FailureDomains, len(*in))
+		for key, val := range *in {
+			newVal := new(apiv1alpha4.FailureDomainSpec)
+			if err := apiv1alpha4.Convert_v1beta1_FailureDomainSpec_To_v1alpha4_FailureDomainSpec(&val, newVal, s); err != nil {
+				return err
+			}
+			(*out)[key] = *newVal
+		}
+	} else {
+		out.FailureDomains = nil
+	}
+	if err := Convert_v1beta1_Network_To_v1alpha4_Network(&in.Network, &out.Network, s); err != nil {
+		return err
+	}
+	out.Ready = in.Ready
+	return nil
+}
+
+// Convert_v1beta1_GCPClusterStatus_To_v1alpha4_GCPClusterStatus is an autogenerated conversion function.
+func Convert_v1beta1_GCPClusterStatus_To_v1alpha4_GCPClusterStatus(in *v1beta1.GCPClusterStatus, out *GCPClusterStatus, s conversion.Scope) error {
+	return autoConvert_v1beta1_GCPClusterStatus_To_v1alpha4_GCPClusterStatus(in, out, s)
+}
+
+func autoConvert_v1alpha4_GCPClusterTemplate_To_v1beta1_GCPClusterTemplate(in *GCPClusterTemplate, out *v1beta1.GCPClusterTemplate, s conversion.Scope) error {
+	out.ObjectMeta = in.ObjectMeta
+	if err := Convert_v1alpha4_GCPClusterTemplateSpec_To_v1beta1_GCPClusterTemplateSpec(&in.Spec, &out.Spec, s); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Convert_v1alpha4_GCPClusterTemplate_To_v1beta1_GCPClusterTemplate is an autogenerated conversion function.
+func Convert_v1alpha4_GCPClusterTemplate_To_v1beta1_GCPClusterTemplate(in *GCPClusterTemplate, out *v1beta1.GCPClusterTemplate, s conversion.Scope) error {
+	return autoConvert_v1alpha4_GCPClusterTemplate_To_v1beta1_GCPClusterTemplate(in, out, s)
+}
+
+func autoConvert_v1beta1_GCPClusterTemplate_To_v1alpha4_GCPClusterTemplate(in *v1beta1.GCPClusterTemplate, out *GCPClusterTemplate, s conversion.Scope) error {
+	out.ObjectMeta = in.ObjectMeta
+	if err := Convert_v1beta1_GCPClusterTemplateSpec_To_v1alpha4_GCPClusterTemplateSpec(&in.Spec, &out.Spec, s); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Convert_v1beta1_GCPClusterTemplate_To_v1alpha4_GCPClusterTemplate is an autogenerated conversion function.
+func Convert_v1beta1_GCPClusterTemplate_To_v1alpha4_GCPClusterTemplate(in *v1beta1.GCPClusterTemplate, out *GCPClusterTemplate, s conversion.Scope) error {
+	return autoConvert_v1beta1_GCPClusterTemplate_To_v1alpha4_GCPClusterTemplate(in, out, s)
+}
+
+func autoConvert_v1alpha4_GCPClusterTemplateList_To_v1beta1_GCPClusterTemplateList(in *GCPClusterTemplateList, out *v1beta1.GCPClusterTemplateList, s conversion.Scope) error {
+	out.ListMeta = in.ListMeta
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]v1beta1.GCPClusterTemplate, len(*in))
+		for i := range *in {
+			if err := Convert_v1alpha4_GCPClusterTemplate_To_v1beta1_GCPClusterTemplate(&(*in)[i], &(*out)[i], s); err != nil {
+				return err
+			}
+		}
+	} else {
+		out.Items = nil
+	}
+	return nil
+}
+
+// Convert_v1alpha4_GCPClusterTemplateList_To_v1beta1_GCPClusterTemplateList is an autogenerated conversion function.
+func Convert_v1alpha4_GCPClusterTemplateList_To_v1beta1_GCPClusterTemplateList(in *GCPClusterTemplateList, out *v1beta1.GCPClusterTemplateList, s conversion.Scope) error {
+	return autoConvert_v1alpha4_GCPClusterTemplateList_To_v1beta1_GCPClusterTemplateList(in, out, s)
+}
+
+func autoConvert_v1beta1_GCPClusterTemplateList_To_v1alpha4_GCPClusterTemplateList(in *v1beta1.GCPClusterTemplateList, out *GCPClusterTemplateList, s conversion.Scope) error {
+	out.ListMeta = in.ListMeta
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]GCPClusterTemplate, len(*in))
+		for i := range *in {
+			if err := Convert_v1beta1_GCPClusterTemplate_To_v1alpha4_GCPClusterTemplate(&(*in)[i], &(*out)[i], s); err != nil {
+				return err
+			}
+		}
+	} else {
+		out.Items = nil
+	}
+	return nil
+}
+
+// Convert_v1beta1_GCPClusterTemplateList_To_v1alpha4_GCPClusterTemplateList is an autogenerated conversion function.
+func Convert_v1beta1_GCPClusterTemplateList_To_v1alpha4_GCPClusterTemplateList(in *v1beta1.GCPClusterTemplateList, out *GCPClusterTemplateList, s conversion.Scope) error {
+	return autoConvert_v1beta1_GCPClusterTemplateList_To_v1alpha4_GCPClusterTemplateList(in, out, s)
+}
+
+func autoConvert_v1alpha4_GCPClusterTemplateResource_To_v1beta1_GCPClusterTemplateResource(in *GCPClusterTemplateResource, out *v1beta1.GCPClusterTemplateResource, s conversion.Scope) error {
+	if err := Convert_v1alpha4_GCPClusterSpec_To_v1beta1_GCPClusterSpec(&in.Spec, &out.Spec, s); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Convert_v1alpha4_GCPClusterTemplateResource_To_v1beta1_GCPClusterTemplateResource is an autogenerated conversion function.
+func Convert_v1alpha4_GCPClusterTemplateResource_To_v1beta1_GCPClusterTemplateResource(in *GCPClusterTemplateResource, out *v1beta1.GCPClusterTemplateResource, s conversion.Scope) error {
+	return autoConvert_v1alpha4_GCPClusterTemplateResource_To_v1beta1_GCPClusterTemplateResource(in, out, s)
+}
+
+func autoConvert_v1beta1_GCPClusterTemplateResource_To_v1alpha4_GCPClusterTemplateResource(in *v1beta1.GCPClusterTemplateResource, out *GCPClusterTemplateResource, s conversion.Scope) error {
+	// WARNING: in.ObjectMeta requires manual conversion: does not exist in peer-type
+	if err := Convert_v1beta1_GCPClusterSpec_To_v1alpha4_GCPClusterSpec(&in.Spec, &out.Spec, s); err != nil {
+		return err
+	}
+	return nil
+}
+
+func autoConvert_v1alpha4_GCPClusterTemplateSpec_To_v1beta1_GCPClusterTemplateSpec(in *GCPClusterTemplateSpec, out *v1beta1.GCPClusterTemplateSpec, s conversion.Scope) error {
+	if err := Convert_v1alpha4_GCPClusterTemplateResource_To_v1beta1_GCPClusterTemplateResource(&in.Template, &out.Template, s); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Convert_v1alpha4_GCPClusterTemplateSpec_To_v1beta1_GCPClusterTemplateSpec is an autogenerated conversion function.
+func Convert_v1alpha4_GCPClusterTemplateSpec_To_v1beta1_GCPClusterTemplateSpec(in *GCPClusterTemplateSpec, out *v1beta1.GCPClusterTemplateSpec, s conversion.Scope) error {
+	return autoConvert_v1alpha4_GCPClusterTemplateSpec_To_v1beta1_GCPClusterTemplateSpec(in, out, s)
+}
+
+func autoConvert_v1beta1_GCPClusterTemplateSpec_To_v1alpha4_GCPClusterTemplateSpec(in *v1beta1.GCPClusterTemplateSpec, out *GCPClusterTemplateSpec, s conversion.Scope) error {
+	if err := Convert_v1beta1_GCPClusterTemplateResource_To_v1alpha4_GCPClusterTemplateResource(&in.Template, &out.Template, s); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Convert_v1beta1_GCPClusterTemplateSpec_To_v1alpha4_GCPClusterTemplateSpec is an autogenerated conversion function.
+func Convert_v1beta1_GCPClusterTemplateSpec_To_v1alpha4_GCPClusterTemplateSpec(in *v1beta1.GCPClusterTemplateSpec, out *GCPClusterTemplateSpec, s conversion.Scope) error {
+	return autoConvert_v1beta1_GCPClusterTemplateSpec_To_v1alpha4_GCPClusterTemplateSpec(in, out, s)
+}
+
+func autoConvert_v1alpha4_GCPMachine_To_v1beta1_GCPMachine(in *GCPMachine, out *v1beta1.GCPMachine, s conversion.Scope) error {
+	out.ObjectMeta = in.ObjectMeta
+	if err := Convert_v1alpha4_GCPMachineSpec_To_v1beta1_GCPMachineSpec(&in.Spec, &out.Spec, s); err != nil {
+		return err
+	}
+	if err := Convert_v1alpha4_GCPMachineStatus_To_v1beta1_GCPMachineStatus(&in.Status, &out.Status, s); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Convert_v1alpha4_GCPMachine_To_v1beta1_GCPMachine is an autogenerated conversion function.
+func Convert_v1alpha4_GCPMachine_To_v1beta1_GCPMachine(in *GCPMachine, out *v1beta1.GCPMachine, s conversion.Scope) error {
+	return autoConvert_v1alpha4_GCPMachine_To_v1beta1_GCPMachine(in, out, s)
+}
+
+func autoConvert_v1beta1_GCPMachine_To_v1alpha4_GCPMachine(in *v1beta1.GCPMachine, out *GCPMachine, s conversion.Scope) error {
+	out.ObjectMeta = in.ObjectMeta
+	if err := Convert_v1beta1_GCPMachineSpec_To_v1alpha4_GCPMachineSpec(&in.Spec, &out.Spec, s); err != nil {
+		return err
+	}
+	if err := Convert_v1beta1_GCPMachineStatus_To_v1alpha4_GCPMachineStatus(&in.Status, &out.Status, s); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Convert_v1beta1_GCPMachine_To_v1alpha4_GCPMachine is an autogenerated conversion function.
+func Convert_v1beta1_GCPMachine_To_v1alpha4_GCPMachine(in *v1beta1.GCPMachine, out *GCPMachine, s conversion.Scope) error {
+	return autoConvert_v1beta1_GCPMachine_To_v1alpha4_GCPMachine(in, out, s)
+}
+
+func autoConvert_v1alpha4_GCPMachineList_To_v1beta1_GCPMachineList(in *GCPMachineList, out *v1beta1.GCPMachineList, s conversion.Scope) error {
+	out.ListMeta = in.ListMeta
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]v1beta1.GCPMachine, len(*in))
+		for i := range *in {
+			if err := Convert_v1alpha4_GCPMachine_To_v1beta1_GCPMachine(&(*in)[i], &(*out)[i], s); err != nil {
+				return err
+			}
+		}
+	} else {
+		out.Items = nil
+	}
+	return nil
+}
+
+// Convert_v1alpha4_GCPMachineList_To_v1beta1_GCPMachineList is an autogenerated conversion function.
+func Convert_v1alpha4_GCPMachineList_To_v1beta1_GCPMachineList(in *GCPMachineList, out *v1beta1.GCPMachineList, s conversion.Scope) error {
+	return autoConvert_v1alpha4_GCPMachineList_To_v1beta1_GCPMachineList(in, out, s)
+}
+
+func autoConvert_v1beta1_GCPMachineList_To_v1alpha4_GCPMachineList(in *v1beta1.GCPMachineList, out *GCPMachineList, s conversion.Scope) error {
+	out.ListMeta = in.ListMeta
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]GCPMachine, len(*in))
+		for i := range *in {
+			if err := Convert_v1beta1_GCPMachine_To_v1alpha4_GCPMachine(&(*in)[i], &(*out)[i], s); err != nil {
+				return err
+			}
+		}
+	} else {
+		out.Items = nil
+	}
+	return nil
+}
+
+// Convert_v1beta1_GCPMachineList_To_v1alpha4_GCPMachineList is an autogenerated conversion function.
+func Convert_v1beta1_GCPMachineList_To_v1alpha4_GCPMachineList(in *v1beta1.GCPMachineList, out *GCPMachineList, s conversion.Scope) error {
+	return autoConvert_v1beta1_GCPMachineList_To_v1alpha4_GCPMachineList(in, out, s)
+}
+
+func autoConvert_v1alpha4_GCPMachineSpec_To_v1beta1_GCPMachineSpec(in *GCPMachineSpec, out *v1beta1.GCPMachineSpec, s conversion.Scope) error {
+	out.InstanceType = in.InstanceType
+	out.Subnet = (*string)(unsafe.Pointer(in.Subnet))
+	out.ProviderID = (*string)(unsafe.Pointer(in.ProviderID))
+	out.ImageFamily = (*string)(unsafe.Pointer(in.ImageFamily))
+	out.Image = (*string)(unsafe.Pointer(in.Image))
+	out.AdditionalLabels = *(*v1beta1.Labels)(unsafe.Pointer(&in.AdditionalLabels))
+	out.AdditionalMetadata = *(*[]v1beta1.MetadataItem)(unsafe.Pointer(&in.AdditionalMetadata))
+	out.PublicIP = (*bool)(unsafe.Pointer(in.PublicIP))
+	out.AdditionalNetworkTags = *(*[]string)(unsafe.Pointer(&in.AdditionalNetworkTags))
+	out.RootDeviceSize = in.RootDeviceSize
+	out.RootDeviceType = (*v1beta1.DiskType)(unsafe.Pointer(in.RootDeviceType))
+	out.AdditionalDisks = *(*[]v1beta1.AttachedDiskSpec)(unsafe.Pointer(&in.AdditionalDisks))
+	out.ServiceAccount = (*v1beta1.ServiceAccount)(unsafe.Pointer(in.ServiceAccount))
+	out.Preemptible = in.Preemptible
+	return nil
+}
+
+// Convert_v1alpha4_GCPMachineSpec_To_v1beta1_GCPMachineSpec is an autogenerated conversion function.
+func Convert_v1alpha4_GCPMachineSpec_To_v1beta1_GCPMachineSpec(in *GCPMachineSpec, out *v1beta1.GCPMachineSpec, s conversion.Scope) error {
+	return autoConvert_v1alpha4_GCPMachineSpec_To_v1beta1_GCPMachineSpec(in, out, s)
+}
+
+func autoConvert_v1beta1_GCPMachineSpec_To_v1alpha4_GCPMachineSpec(in *v1beta1.GCPMachineSpec, out *GCPMachineSpec, s conversion.Scope) error {
+	out.InstanceType = in.InstanceType
+	out.Subnet = (*string)(unsafe.Pointer(in.Subnet))
+	out.ProviderID = (*string)(unsafe.Pointer(in.ProviderID))
+	out.ImageFamily = (*string)(unsafe.Pointer(in.ImageFamily))
+	out.Image = (*string)(unsafe.Pointer(in.Image))
+	out.AdditionalLabels = *(*Labels)(unsafe.Pointer(&in.AdditionalLabels))
+	out.AdditionalMetadata = *(*[]MetadataItem)(unsafe.Pointer(&in.AdditionalMetadata))
+	out.PublicIP = (*bool)(unsafe.Pointer(in.PublicIP))
+	out.AdditionalNetworkTags = *(*[]string)(unsafe.Pointer(&in.AdditionalNetworkTags))
+	// WARNING: in.ResourceManagerTags requires manual conversion: does not exist in peer-type
+	out.RootDeviceSize = in.RootDeviceSize
+	out.RootDeviceType = (*DiskType)(unsafe.Pointer(in.RootDeviceType))
+	out.AdditionalDisks = *(*[]AttachedDiskSpec)(unsafe.Pointer(&in.AdditionalDisks))
+	out.ServiceAccount = (*ServiceAccount)(unsafe.Pointer(in.ServiceAccount))
+	out.Preemptible = in.Preemptible
+	// WARNING: in.IPForwarding requires manual conversion: does not exist in peer-type
+	// WARNING: in.ShieldedInstanceConfig requires manual conversion: does not exist in peer-type
+	// WARNING: in.OnHostMaintenance requires manual conversion: does not exist in peer-type
+	// WARNING: in.ConfidentialCompute requires manual conversion: does not exist in peer-type
+	return nil
+}
+
+func autoConvert_v1alpha4_GCPMachineStatus_To_v1beta1_GCPMachineStatus(in *GCPMachineStatus, out *v1beta1.GCPMachineStatus, s conversion.Scope) error {
+	out.Ready = in.Ready
+	out.Addresses = *(*[]v1.NodeAddress)(unsafe.Pointer(&in.Addresses))
+	out.InstanceStatus = (*v1beta1.InstanceStatus)(unsafe.Pointer(in.InstanceStatus))
+	out.FailureReason = (*errors.MachineStatusError)(unsafe.Pointer(in.FailureReason))
+	out.FailureMessage = (*string)(unsafe.Pointer(in.FailureMessage))
+	return nil
+}
+
+// Convert_v1alpha4_GCPMachineStatus_To_v1beta1_GCPMachineStatus is an autogenerated conversion function.
+func Convert_v1alpha4_GCPMachineStatus_To_v1beta1_GCPMachineStatus(in *GCPMachineStatus, out *v1beta1.GCPMachineStatus, s conversion.Scope) error {
+	return autoConvert_v1alpha4_GCPMachineStatus_To_v1beta1_GCPMachineStatus(in, out, s)
+}
+
+func autoConvert_v1beta1_GCPMachineStatus_To_v1alpha4_GCPMachineStatus(in *v1beta1.GCPMachineStatus, out *GCPMachineStatus, s conversion.Scope) error {
+	out.Ready = in.Ready
+	out.Addresses = *(*[]v1.NodeAddress)(unsafe.Pointer(&in.Addresses))
+	out.InstanceStatus = (*InstanceStatus)(unsafe.Pointer(in.InstanceStatus))
+	out.FailureReason = (*errors.MachineStatusError)(unsafe.Pointer(in.FailureReason))
+	out.FailureMessage = (*string)(unsafe.Pointer(in.FailureMessage))
+	return nil
+}
+
+// Convert_v1beta1_GCPMachineStatus_To_v1alpha4_GCPMachineStatus is an autogenerated conversion function.
+func Convert_v1beta1_GCPMachineStatus_To_v1alpha4_GCPMachineStatus(in *v1beta1.GCPMachineStatus, out *GCPMachineStatus, s conversion.Scope) error {
+	return autoConvert_v1beta1_GCPMachineStatus_To_v1alpha4_GCPMachineStatus(in, out, s)
+}
+
+func autoConvert_v1alpha4_GCPMachineTemplate_To_v1beta1_GCPMachineTemplate(in *GCPMachineTemplate, out *v1beta1.GCPMachineTemplate, s conversion.Scope) error {
+	out.ObjectMeta = in.ObjectMeta
+	if err := Convert_v1alpha4_GCPMachineTemplateSpec_To_v1beta1_GCPMachineTemplateSpec(&in.Spec, &out.Spec, s); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Convert_v1alpha4_GCPMachineTemplate_To_v1beta1_GCPMachineTemplate is an autogenerated conversion function.
+func Convert_v1alpha4_GCPMachineTemplate_To_v1beta1_GCPMachineTemplate(in *GCPMachineTemplate, out *v1beta1.GCPMachineTemplate, s conversion.Scope) error {
+	return autoConvert_v1alpha4_GCPMachineTemplate_To_v1beta1_GCPMachineTemplate(in, out, s)
+}
+
+func autoConvert_v1beta1_GCPMachineTemplate_To_v1alpha4_GCPMachineTemplate(in *v1beta1.GCPMachineTemplate, out *GCPMachineTemplate, s conversion.Scope) error {
+	out.ObjectMeta = in.ObjectMeta
+	if err := Convert_v1beta1_GCPMachineTemplateSpec_To_v1alpha4_GCPMachineTemplateSpec(&in.Spec, &out.Spec, s); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Convert_v1beta1_GCPMachineTemplate_To_v1alpha4_GCPMachineTemplate is an autogenerated conversion function.
+func Convert_v1beta1_GCPMachineTemplate_To_v1alpha4_GCPMachineTemplate(in *v1beta1.GCPMachineTemplate, out *GCPMachineTemplate, s conversion.Scope) error {
+	return autoConvert_v1beta1_GCPMachineTemplate_To_v1alpha4_GCPMachineTemplate(in, out, s)
+}
+
+func autoConvert_v1alpha4_GCPMachineTemplateList_To_v1beta1_GCPMachineTemplateList(in *GCPMachineTemplateList, out *v1beta1.GCPMachineTemplateList, s conversion.Scope) error {
+	out.ListMeta = in.ListMeta
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]v1beta1.GCPMachineTemplate, len(*in))
+		for i := range *in {
+			if err := Convert_v1alpha4_GCPMachineTemplate_To_v1beta1_GCPMachineTemplate(&(*in)[i], &(*out)[i], s); err != nil {
+				return err
+			}
+		}
+	} else {
+		out.Items = nil
+	}
+	return nil
+}
+
+// Convert_v1alpha4_GCPMachineTemplateList_To_v1beta1_GCPMachineTemplateList is an autogenerated conversion function.
+func Convert_v1alpha4_GCPMachineTemplateList_To_v1beta1_GCPMachineTemplateList(in *GCPMachineTemplateList, out *v1beta1.GCPMachineTemplateList, s conversion.Scope) error {
+	return autoConvert_v1alpha4_GCPMachineTemplateList_To_v1beta1_GCPMachineTemplateList(in, out, s)
+}
+
+func autoConvert_v1beta1_GCPMachineTemplateList_To_v1alpha4_GCPMachineTemplateList(in *v1beta1.GCPMachineTemplateList, out *GCPMachineTemplateList, s conversion.Scope) error {
+	out.ListMeta = in.ListMeta
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]GCPMachineTemplate, len(*in))
+		for i := range *in {
+			if err := Convert_v1beta1_GCPMachineTemplate_To_v1alpha4_GCPMachineTemplate(&(*in)[i], &(*out)[i], s); err != nil {
+				return err
+			}
+		}
+	} else {
+		out.Items = nil
+	}
+	return nil
+}
+
+// Convert_v1beta1_GCPMachineTemplateList_To_v1alpha4_GCPMachineTemplateList is an autogenerated conversion function.
+func Convert_v1beta1_GCPMachineTemplateList_To_v1alpha4_GCPMachineTemplateList(in *v1beta1.GCPMachineTemplateList, out *GCPMachineTemplateList, s conversion.Scope) error {
+	return autoConvert_v1beta1_GCPMachineTemplateList_To_v1alpha4_GCPMachineTemplateList(in, out, s)
+}
+
+func autoConvert_v1alpha4_GCPMachineTemplateResource_To_v1beta1_GCPMachineTemplateResource(in *GCPMachineTemplateResource, out *v1beta1.GCPMachineTemplateResource, s conversion.Scope) error {
+	if err := Convert_v1alpha4_GCPMachineSpec_To_v1beta1_GCPMachineSpec(&in.Spec, &out.Spec, s); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Convert_v1alpha4_GCPMachineTemplateResource_To_v1beta1_GCPMachineTemplateResource is an autogenerated conversion function.
+func Convert_v1alpha4_GCPMachineTemplateResource_To_v1beta1_GCPMachineTemplateResource(in *GCPMachineTemplateResource, out *v1beta1.GCPMachineTemplateResource, s conversion.Scope) error {
+	return autoConvert_v1alpha4_GCPMachineTemplateResource_To_v1beta1_GCPMachineTemplateResource(in, out, s)
+}
+
+func autoConvert_v1beta1_GCPMachineTemplateResource_To_v1alpha4_GCPMachineTemplateResource(in *v1beta1.GCPMachineTemplateResource, out *GCPMachineTemplateResource, s conversion.Scope) error {
+	// WARNING: in.ObjectMeta requires manual conversion: does not exist in peer-type
+	if err := Convert_v1beta1_GCPMachineSpec_To_v1alpha4_GCPMachineSpec(&in.Spec, &out.Spec, s); err != nil {
+		return err
+	}
+	return nil
+}
+
+func autoConvert_v1alpha4_GCPMachineTemplateSpec_To_v1beta1_GCPMachineTemplateSpec(in *GCPMachineTemplateSpec, out *v1beta1.GCPMachineTemplateSpec, s conversion.Scope) error {
+	if err := Convert_v1alpha4_GCPMachineTemplateResource_To_v1beta1_GCPMachineTemplateResource(&in.Template, &out.Template, s); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Convert_v1alpha4_GCPMachineTemplateSpec_To_v1beta1_GCPMachineTemplateSpec is an autogenerated conversion function.
+func Convert_v1alpha4_GCPMachineTemplateSpec_To_v1beta1_GCPMachineTemplateSpec(in *GCPMachineTemplateSpec, out *v1beta1.GCPMachineTemplateSpec, s conversion.Scope) error {
+	return autoConvert_v1alpha4_GCPMachineTemplateSpec_To_v1beta1_GCPMachineTemplateSpec(in, out, s)
+}
+
+func autoConvert_v1beta1_GCPMachineTemplateSpec_To_v1alpha4_GCPMachineTemplateSpec(in *v1beta1.GCPMachineTemplateSpec, out *GCPMachineTemplateSpec, s conversion.Scope) error {
+	if err := Convert_v1beta1_GCPMachineTemplateResource_To_v1alpha4_GCPMachineTemplateResource(&in.Template, &out.Template, s); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Convert_v1beta1_GCPMachineTemplateSpec_To_v1alpha4_GCPMachineTemplateSpec is an autogenerated conversion function.
+func Convert_v1beta1_GCPMachineTemplateSpec_To_v1alpha4_GCPMachineTemplateSpec(in *v1beta1.GCPMachineTemplateSpec, out *GCPMachineTemplateSpec, s conversion.Scope) error {
+	return autoConvert_v1beta1_GCPMachineTemplateSpec_To_v1alpha4_GCPMachineTemplateSpec(in, out, s)
+}
+
+func autoConvert_v1alpha4_MetadataItem_To_v1beta1_MetadataItem(in *MetadataItem, out *v1beta1.MetadataItem, s conversion.Scope) error {
+	out.Key = in.Key
+	out.Value = (*string)(unsafe.Pointer(in.Value))
+	return nil
+}
+
+// Convert_v1alpha4_MetadataItem_To_v1beta1_MetadataItem is an autogenerated conversion function.
+func Convert_v1alpha4_MetadataItem_To_v1beta1_MetadataItem(in *MetadataItem, out *v1beta1.MetadataItem, s conversion.Scope) error {
+	return autoConvert_v1alpha4_MetadataItem_To_v1beta1_MetadataItem(in, out, s)
+}
+
+func autoConvert_v1beta1_MetadataItem_To_v1alpha4_MetadataItem(in *v1beta1.MetadataItem, out *MetadataItem, s conversion.Scope) error {
+	out.Key = in.Key
+	out.Value = (*string)(unsafe.Pointer(in.Value))
+	return nil
+}
+
+// Convert_v1beta1_MetadataItem_To_v1alpha4_MetadataItem is an autogenerated conversion function.
+func Convert_v1beta1_MetadataItem_To_v1alpha4_MetadataItem(in *v1beta1.MetadataItem, out *MetadataItem, s conversion.Scope) error {
+	return autoConvert_v1beta1_MetadataItem_To_v1alpha4_MetadataItem(in, out, s)
+}
+
+func autoConvert_v1alpha4_Network_To_v1beta1_Network(in *Network, out *v1beta1.Network, s conversion.Scope) error {
+	out.SelfLink = (*string)(unsafe.Pointer(in.SelfLink))
+	out.FirewallRules = *(*map[string]string)(unsafe.Pointer(&in.FirewallRules))
+	out.Router = (*string)(unsafe.Pointer(in.Router))
+	out.APIServerAddress = (*string)(unsafe.Pointer(in.APIServerAddress))
+	out.APIServerHealthCheck = (*string)(unsafe.Pointer(in.APIServerHealthCheck))
+	out.APIServerInstanceGroups = *(*map[string]string)(unsafe.Pointer(&in.APIServerInstanceGroups))
+	out.APIServerBackendService = (*string)(unsafe.Pointer(in.APIServerBackendService))
+	out.APIServerTargetProxy = (*string)(unsafe.Pointer(in.APIServerTargetProxy))
+	out.APIServerForwardingRule = (*string)(unsafe.Pointer(in.APIServerForwardingRule))
+	return nil
+}
+
+// Convert_v1alpha4_Network_To_v1beta1_Network is an autogenerated conversion function.
+func Convert_v1alpha4_Network_To_v1beta1_Network(in *Network, out *v1beta1.Network, s conversion.Scope) error {
+	return autoConvert_v1alpha4_Network_To_v1beta1_Network(in, out, s)
+}
+
+func autoConvert_v1beta1_Network_To_v1alpha4_Network(in *v1beta1.Network, out *Network, s conversion.Scope) error {
+	out.SelfLink = (*string)(unsafe.Pointer(in.SelfLink))
+	out.FirewallRules = *(*map[string]string)(unsafe.Pointer(&in.FirewallRules))
+	out.Router = (*string)(unsafe.Pointer(in.Router))
+	out.APIServerAddress = (*string)(unsafe.Pointer(in.APIServerAddress))
+	out.APIServerHealthCheck = (*string)(unsafe.Pointer(in.APIServerHealthCheck))
+	out.APIServerInstanceGroups = *(*map[string]string)(unsafe.Pointer(&in.APIServerInstanceGroups))
+	out.APIServerBackendService = (*string)(unsafe.Pointer(in.APIServerBackendService))
+	out.APIServerTargetProxy = (*string)(unsafe.Pointer(in.APIServerTargetProxy))
+	out.APIServerForwardingRule = (*string)(unsafe.Pointer(in.APIServerForwardingRule))
+	return nil
+}
+
+// Convert_v1beta1_Network_To_v1alpha4_Network is an autogenerated conversion function.
+func Convert_v1beta1_Network_To_v1alpha4_Network(in *v1beta1.Network, out *Network, s conversion.Scope) error {
+	return autoConvert_v1beta1_Network_To_v1alpha4_Network(in, out, s)
+}
+
+func autoConvert_v1alpha4_NetworkSpec_To_v1beta1_NetworkSpec(in *NetworkSpec, out *v1beta1.NetworkSpec, s conversion.Scope) error {
+	out.Name = (*string)(unsafe.Pointer(in.Name))
+	out.AutoCreateSubnetworks = (*bool)(unsafe.Pointer(in.AutoCreateSubnetworks))
+	if in.Subnets != nil {
+		in, out := &in.Subnets, &out.Subnets
+		*out = make(v1beta1.Subnets, len(*in))
+		for i := range *in {
+			if err := Convert_v1alpha4_SubnetSpec_To_v1beta1_SubnetSpec(&(*in)[i], &(*out)[i], s); err != nil {
+				return err
+			}
+		}
+	} else {
+		out.Subnets = nil
+	}
+	out.LoadBalancerBackendPort = (*int32)(unsafe.Pointer(in.LoadBalancerBackendPort))
+	return nil
+}
+
+// Convert_v1alpha4_NetworkSpec_To_v1beta1_NetworkSpec is an autogenerated conversion function.
+func Convert_v1alpha4_NetworkSpec_To_v1beta1_NetworkSpec(in *NetworkSpec, out *v1beta1.NetworkSpec, s conversion.Scope) error {
+	return autoConvert_v1alpha4_NetworkSpec_To_v1beta1_NetworkSpec(in, out, s)
+}
+
+func autoConvert_v1beta1_NetworkSpec_To_v1alpha4_NetworkSpec(in *v1beta1.NetworkSpec, out *NetworkSpec, s conversion.Scope) error {
+	out.Name = (*string)(unsafe.Pointer(in.Name))
+	out.AutoCreateSubnetworks = (*bool)(unsafe.Pointer(in.AutoCreateSubnetworks))
+	if in.Subnets != nil {
+		in, out := &in.Subnets, &out.Subnets
+		*out = make(Subnets, len(*in))
+		for i := range *in {
+			if err := Convert_v1beta1_SubnetSpec_To_v1alpha4_SubnetSpec(&(*in)[i], &(*out)[i], s); err != nil {
+				return err
+			}
+		}
+	} else {
+		out.Subnets = nil
+	}
+	out.LoadBalancerBackendPort = (*int32)(unsafe.Pointer(in.LoadBalancerBackendPort))
+	return nil
+}
+
+// Convert_v1beta1_NetworkSpec_To_v1alpha4_NetworkSpec is an autogenerated conversion function.
+func Convert_v1beta1_NetworkSpec_To_v1alpha4_NetworkSpec(in *v1beta1.NetworkSpec, out *NetworkSpec, s conversion.Scope) error {
+	return autoConvert_v1beta1_NetworkSpec_To_v1alpha4_NetworkSpec(in, out, s)
+}
+
+func autoConvert_v1alpha4_ServiceAccount_To_v1beta1_ServiceAccount(in *ServiceAccount, out *v1beta1.ServiceAccount, s conversion.Scope) error {
+	out.Email = in.Email
+	out.Scopes = *(*[]string)(unsafe.Pointer(&in.Scopes))
+	return nil
+}
+
+// Convert_v1alpha4_ServiceAccount_To_v1beta1_ServiceAccount is an autogenerated conversion function.
+func Convert_v1alpha4_ServiceAccount_To_v1beta1_ServiceAccount(in *ServiceAccount, out *v1beta1.ServiceAccount, s conversion.Scope) error {
+	return autoConvert_v1alpha4_ServiceAccount_To_v1beta1_ServiceAccount(in, out, s)
+}
+
+func autoConvert_v1beta1_ServiceAccount_To_v1alpha4_ServiceAccount(in *v1beta1.ServiceAccount, out *ServiceAccount, s conversion.Scope) error {
+	out.Email = in.Email
+	out.Scopes = *(*[]string)(unsafe.Pointer(&in.Scopes))
+	return nil
+}
+
+// Convert_v1beta1_ServiceAccount_To_v1alpha4_ServiceAccount is an autogenerated conversion function.
+func Convert_v1beta1_ServiceAccount_To_v1alpha4_ServiceAccount(in *v1beta1.ServiceAccount, out *ServiceAccount, s conversion.Scope) error {
+	return autoConvert_v1beta1_ServiceAccount_To_v1alpha4_ServiceAccount(in, out, s)
+}
+
+func autoConvert_v1alpha4_SubnetSpec_To_v1beta1_SubnetSpec(in *SubnetSpec, out *v1beta1.SubnetSpec, s conversion.Scope) error {
+	out.Name = in.Name
+	out.CidrBlock = in.CidrBlock
+	out.Description = (*string)(unsafe.Pointer(in.Description))
+	out.SecondaryCidrBlocks = *(*map[string]string)(unsafe.Pointer(&in.SecondaryCidrBlocks))
+	out.Region = in.Region
+	out.PrivateGoogleAccess = (*bool)(unsafe.Pointer(in.PrivateGoogleAccess))
+	out.EnableFlowLogs = (*bool)(unsafe.Pointer(in.EnableFlowLogs))
+	return nil
+}
+
+// Convert_v1alpha4_SubnetSpec_To_v1beta1_SubnetSpec is an autogenerated conversion function.
+func Convert_v1alpha4_SubnetSpec_To_v1beta1_SubnetSpec(in *SubnetSpec, out *v1beta1.SubnetSpec, s conversion.Scope) error {
+	return autoConvert_v1alpha4_SubnetSpec_To_v1beta1_SubnetSpec(in, out, s)
+}
+
+func autoConvert_v1beta1_SubnetSpec_To_v1alpha4_SubnetSpec(in *v1beta1.SubnetSpec, out *SubnetSpec, s conversion.Scope) error {
+	out.Name = in.Name
+	out.CidrBlock = in.CidrBlock
+	out.Description = (*string)(unsafe.Pointer(in.Description))
+	out.SecondaryCidrBlocks = *(*map[string]string)(unsafe.Pointer(&in.SecondaryCidrBlocks))
+	out.Region = in.Region
+	out.PrivateGoogleAccess = (*bool)(unsafe.Pointer(in.PrivateGoogleAccess))
+	out.EnableFlowLogs = (*bool)(unsafe.Pointer(in.EnableFlowLogs))
+	// WARNING: in.Purpose requires manual conversion: does not exist in peer-type
+	return nil
+}
diff --git a/api/v1alpha4/zz_generated.deepcopy.go b/api/v1alpha4/zz_generated.deepcopy.go
new file mode 100644
index 00000000..264bf26b
--- /dev/null
+++ b/api/v1alpha4/zz_generated.deepcopy.go
@@ -0,0 +1,781 @@
+//go:build !ignore_autogenerated
+
+/*
+Copyright 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.
+*/
+
+// Code generated by controller-gen. DO NOT EDIT.
+
+package v1alpha4
+
+import (
+	"k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	apiv1alpha4 "sigs.k8s.io/cluster-api/api/v1alpha4"
+	"sigs.k8s.io/cluster-api/errors"
+)
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AttachedDiskSpec) DeepCopyInto(out *AttachedDiskSpec) {
+	*out = *in
+	if in.DeviceType != nil {
+		in, out := &in.DeviceType, &out.DeviceType
+		*out = new(DiskType)
+		**out = **in
+	}
+	if in.Size != nil {
+		in, out := &in.Size, &out.Size
+		*out = new(int64)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AttachedDiskSpec.
+func (in *AttachedDiskSpec) DeepCopy() *AttachedDiskSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(AttachedDiskSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *BuildParams) DeepCopyInto(out *BuildParams) {
+	*out = *in
+	if in.Role != nil {
+		in, out := &in.Role, &out.Role
+		*out = new(string)
+		**out = **in
+	}
+	if in.Additional != nil {
+		in, out := &in.Additional, &out.Additional
+		*out = make(Labels, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BuildParams.
+func (in *BuildParams) DeepCopy() *BuildParams {
+	if in == nil {
+		return nil
+	}
+	out := new(BuildParams)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Filter) DeepCopyInto(out *Filter) {
+	*out = *in
+	if in.Values != nil {
+		in, out := &in.Values, &out.Values
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Filter.
+func (in *Filter) DeepCopy() *Filter {
+	if in == nil {
+		return nil
+	}
+	out := new(Filter)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCPCluster) DeepCopyInto(out *GCPCluster) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPCluster.
+func (in *GCPCluster) DeepCopy() *GCPCluster {
+	if in == nil {
+		return nil
+	}
+	out := new(GCPCluster)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *GCPCluster) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCPClusterList) DeepCopyInto(out *GCPClusterList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]GCPCluster, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPClusterList.
+func (in *GCPClusterList) DeepCopy() *GCPClusterList {
+	if in == nil {
+		return nil
+	}
+	out := new(GCPClusterList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *GCPClusterList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCPClusterSpec) DeepCopyInto(out *GCPClusterSpec) {
+	*out = *in
+	out.ControlPlaneEndpoint = in.ControlPlaneEndpoint
+	in.Network.DeepCopyInto(&out.Network)
+	if in.FailureDomains != nil {
+		in, out := &in.FailureDomains, &out.FailureDomains
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	if in.AdditionalLabels != nil {
+		in, out := &in.AdditionalLabels, &out.AdditionalLabels
+		*out = make(Labels, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPClusterSpec.
+func (in *GCPClusterSpec) DeepCopy() *GCPClusterSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(GCPClusterSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCPClusterStatus) DeepCopyInto(out *GCPClusterStatus) {
+	*out = *in
+	if in.FailureDomains != nil {
+		in, out := &in.FailureDomains, &out.FailureDomains
+		*out = make(apiv1alpha4.FailureDomains, len(*in))
+		for key, val := range *in {
+			(*out)[key] = *val.DeepCopy()
+		}
+	}
+	in.Network.DeepCopyInto(&out.Network)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPClusterStatus.
+func (in *GCPClusterStatus) DeepCopy() *GCPClusterStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(GCPClusterStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCPClusterTemplate) DeepCopyInto(out *GCPClusterTemplate) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPClusterTemplate.
+func (in *GCPClusterTemplate) DeepCopy() *GCPClusterTemplate {
+	if in == nil {
+		return nil
+	}
+	out := new(GCPClusterTemplate)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *GCPClusterTemplate) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCPClusterTemplateList) DeepCopyInto(out *GCPClusterTemplateList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]GCPClusterTemplate, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPClusterTemplateList.
+func (in *GCPClusterTemplateList) DeepCopy() *GCPClusterTemplateList {
+	if in == nil {
+		return nil
+	}
+	out := new(GCPClusterTemplateList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *GCPClusterTemplateList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCPClusterTemplateResource) DeepCopyInto(out *GCPClusterTemplateResource) {
+	*out = *in
+	in.Spec.DeepCopyInto(&out.Spec)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPClusterTemplateResource.
+func (in *GCPClusterTemplateResource) DeepCopy() *GCPClusterTemplateResource {
+	if in == nil {
+		return nil
+	}
+	out := new(GCPClusterTemplateResource)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCPClusterTemplateSpec) DeepCopyInto(out *GCPClusterTemplateSpec) {
+	*out = *in
+	in.Template.DeepCopyInto(&out.Template)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPClusterTemplateSpec.
+func (in *GCPClusterTemplateSpec) DeepCopy() *GCPClusterTemplateSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(GCPClusterTemplateSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCPMachine) DeepCopyInto(out *GCPMachine) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPMachine.
+func (in *GCPMachine) DeepCopy() *GCPMachine {
+	if in == nil {
+		return nil
+	}
+	out := new(GCPMachine)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *GCPMachine) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCPMachineList) DeepCopyInto(out *GCPMachineList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]GCPMachine, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPMachineList.
+func (in *GCPMachineList) DeepCopy() *GCPMachineList {
+	if in == nil {
+		return nil
+	}
+	out := new(GCPMachineList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *GCPMachineList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCPMachineSpec) DeepCopyInto(out *GCPMachineSpec) {
+	*out = *in
+	if in.Subnet != nil {
+		in, out := &in.Subnet, &out.Subnet
+		*out = new(string)
+		**out = **in
+	}
+	if in.ProviderID != nil {
+		in, out := &in.ProviderID, &out.ProviderID
+		*out = new(string)
+		**out = **in
+	}
+	if in.ImageFamily != nil {
+		in, out := &in.ImageFamily, &out.ImageFamily
+		*out = new(string)
+		**out = **in
+	}
+	if in.Image != nil {
+		in, out := &in.Image, &out.Image
+		*out = new(string)
+		**out = **in
+	}
+	if in.AdditionalLabels != nil {
+		in, out := &in.AdditionalLabels, &out.AdditionalLabels
+		*out = make(Labels, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+	if in.AdditionalMetadata != nil {
+		in, out := &in.AdditionalMetadata, &out.AdditionalMetadata
+		*out = make([]MetadataItem, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	if in.PublicIP != nil {
+		in, out := &in.PublicIP, &out.PublicIP
+		*out = new(bool)
+		**out = **in
+	}
+	if in.AdditionalNetworkTags != nil {
+		in, out := &in.AdditionalNetworkTags, &out.AdditionalNetworkTags
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	if in.RootDeviceType != nil {
+		in, out := &in.RootDeviceType, &out.RootDeviceType
+		*out = new(DiskType)
+		**out = **in
+	}
+	if in.AdditionalDisks != nil {
+		in, out := &in.AdditionalDisks, &out.AdditionalDisks
+		*out = make([]AttachedDiskSpec, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	if in.ServiceAccount != nil {
+		in, out := &in.ServiceAccount, &out.ServiceAccount
+		*out = new(ServiceAccount)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPMachineSpec.
+func (in *GCPMachineSpec) DeepCopy() *GCPMachineSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(GCPMachineSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCPMachineStatus) DeepCopyInto(out *GCPMachineStatus) {
+	*out = *in
+	if in.Addresses != nil {
+		in, out := &in.Addresses, &out.Addresses
+		*out = make([]v1.NodeAddress, len(*in))
+		copy(*out, *in)
+	}
+	if in.InstanceStatus != nil {
+		in, out := &in.InstanceStatus, &out.InstanceStatus
+		*out = new(InstanceStatus)
+		**out = **in
+	}
+	if in.FailureReason != nil {
+		in, out := &in.FailureReason, &out.FailureReason
+		*out = new(errors.MachineStatusError)
+		**out = **in
+	}
+	if in.FailureMessage != nil {
+		in, out := &in.FailureMessage, &out.FailureMessage
+		*out = new(string)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPMachineStatus.
+func (in *GCPMachineStatus) DeepCopy() *GCPMachineStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(GCPMachineStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCPMachineTemplate) DeepCopyInto(out *GCPMachineTemplate) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPMachineTemplate.
+func (in *GCPMachineTemplate) DeepCopy() *GCPMachineTemplate {
+	if in == nil {
+		return nil
+	}
+	out := new(GCPMachineTemplate)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *GCPMachineTemplate) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCPMachineTemplateList) DeepCopyInto(out *GCPMachineTemplateList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]GCPMachineTemplate, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPMachineTemplateList.
+func (in *GCPMachineTemplateList) DeepCopy() *GCPMachineTemplateList {
+	if in == nil {
+		return nil
+	}
+	out := new(GCPMachineTemplateList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *GCPMachineTemplateList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCPMachineTemplateResource) DeepCopyInto(out *GCPMachineTemplateResource) {
+	*out = *in
+	in.Spec.DeepCopyInto(&out.Spec)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPMachineTemplateResource.
+func (in *GCPMachineTemplateResource) DeepCopy() *GCPMachineTemplateResource {
+	if in == nil {
+		return nil
+	}
+	out := new(GCPMachineTemplateResource)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GCPMachineTemplateSpec) DeepCopyInto(out *GCPMachineTemplateSpec) {
+	*out = *in
+	in.Template.DeepCopyInto(&out.Template)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPMachineTemplateSpec.
+func (in *GCPMachineTemplateSpec) DeepCopy() *GCPMachineTemplateSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(GCPMachineTemplateSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in Labels) DeepCopyInto(out *Labels) {
+	{
+		in := &in
+		*out = make(Labels, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Labels.
+func (in Labels) DeepCopy() Labels {
+	if in == nil {
+		return nil
+	}
+	out := new(Labels)
+	in.DeepCopyInto(out)
+	return *out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *MetadataItem) DeepCopyInto(out *MetadataItem) {
+	*out = *in
+	if in.Value != nil {
+		in, out := &in.Value, &out.Value
+		*out = new(string)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetadataItem.
+func (in *MetadataItem) DeepCopy() *MetadataItem {
+	if in == nil {
+		return nil
+	}
+	out := new(MetadataItem)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Network) DeepCopyInto(out *Network) {
+	*out = *in
+	if in.SelfLink != nil {
+		in, out := &in.SelfLink, &out.SelfLink
+		*out = new(string)
+		**out = **in
+	}
+	if in.FirewallRules != nil {
+		in, out := &in.FirewallRules, &out.FirewallRules
+		*out = make(map[string]string, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+	if in.Router != nil {
+		in, out := &in.Router, &out.Router
+		*out = new(string)
+		**out = **in
+	}
+	if in.APIServerAddress != nil {
+		in, out := &in.APIServerAddress, &out.APIServerAddress
+		*out = new(string)
+		**out = **in
+	}
+	if in.APIServerHealthCheck != nil {
+		in, out := &in.APIServerHealthCheck, &out.APIServerHealthCheck
+		*out = new(string)
+		**out = **in
+	}
+	if in.APIServerInstanceGroups != nil {
+		in, out := &in.APIServerInstanceGroups, &out.APIServerInstanceGroups
+		*out = make(map[string]string, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+	if in.APIServerBackendService != nil {
+		in, out := &in.APIServerBackendService, &out.APIServerBackendService
+		*out = new(string)
+		**out = **in
+	}
+	if in.APIServerTargetProxy != nil {
+		in, out := &in.APIServerTargetProxy, &out.APIServerTargetProxy
+		*out = new(string)
+		**out = **in
+	}
+	if in.APIServerForwardingRule != nil {
+		in, out := &in.APIServerForwardingRule, &out.APIServerForwardingRule
+		*out = new(string)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Network.
+func (in *Network) DeepCopy() *Network {
+	if in == nil {
+		return nil
+	}
+	out := new(Network)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *NetworkSpec) DeepCopyInto(out *NetworkSpec) {
+	*out = *in
+	if in.Name != nil {
+		in, out := &in.Name, &out.Name
+		*out = new(string)
+		**out = **in
+	}
+	if in.AutoCreateSubnetworks != nil {
+		in, out := &in.AutoCreateSubnetworks, &out.AutoCreateSubnetworks
+		*out = new(bool)
+		**out = **in
+	}
+	if in.Subnets != nil {
+		in, out := &in.Subnets, &out.Subnets
+		*out = make(Subnets, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	if in.LoadBalancerBackendPort != nil {
+		in, out := &in.LoadBalancerBackendPort, &out.LoadBalancerBackendPort
+		*out = new(int32)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkSpec.
+func (in *NetworkSpec) DeepCopy() *NetworkSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(NetworkSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ServiceAccount) DeepCopyInto(out *ServiceAccount) {
+	*out = *in
+	if in.Scopes != nil {
+		in, out := &in.Scopes, &out.Scopes
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccount.
+func (in *ServiceAccount) DeepCopy() *ServiceAccount {
+	if in == nil {
+		return nil
+	}
+	out := new(ServiceAccount)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SubnetSpec) DeepCopyInto(out *SubnetSpec) {
+	*out = *in
+	if in.Description != nil {
+		in, out := &in.Description, &out.Description
+		*out = new(string)
+		**out = **in
+	}
+	if in.SecondaryCidrBlocks != nil {
+		in, out := &in.SecondaryCidrBlocks, &out.SecondaryCidrBlocks
+		*out = make(map[string]string, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+	if in.PrivateGoogleAccess != nil {
+		in, out := &in.PrivateGoogleAccess, &out.PrivateGoogleAccess
+		*out = new(bool)
+		**out = **in
+	}
+	if in.EnableFlowLogs != nil {
+		in, out := &in.EnableFlowLogs, &out.EnableFlowLogs
+		*out = new(bool)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubnetSpec.
+func (in *SubnetSpec) DeepCopy() *SubnetSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(SubnetSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in Subnets) DeepCopyInto(out *Subnets) {
+	{
+		in := &in
+		*out = make(Subnets, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Subnets.
+func (in Subnets) DeepCopy() Subnets {
+	if in == nil {
+		return nil
+	}
+	out := new(Subnets)
+	in.DeepCopyInto(out)
+	return *out
+}
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpclusters.yaml
index a75541dd..7b9e8a93 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpclusters.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpclusters.yaml
@@ -16,6 +16,228 @@ spec:
     singular: gcpcluster
   scope: Namespaced
   versions:
+  - additionalPrinterColumns:
+    - description: Cluster to which this GCPCluster belongs
+      jsonPath: .metadata.labels.cluster\.x-k8s\.io/cluster-name
+      name: Cluster
+      type: string
+    - description: Cluster infrastructure is ready for GCE instances
+      jsonPath: .status.ready
+      name: Ready
+      type: string
+    - description: GCP network the cluster is using
+      jsonPath: .spec.network.name
+      name: Network
+      type: string
+    - description: API Endpoint
+      jsonPath: .status.apiEndpoints[0]
+      name: Endpoint
+      priority: 1
+      type: string
+    name: v1alpha4
+    schema:
+      openAPIV3Schema:
+        description: GCPCluster is the Schema for the gcpclusters API.
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: GCPClusterSpec defines the desired state of GCPCluster.
+            properties:
+              additionalLabels:
+                additionalProperties:
+                  type: string
+                description: AdditionalLabels is an optional set of tags to add to
+                  GCP resources managed by the GCP provider, in addition to the ones
+                  added by default.
+                type: object
+              controlPlaneEndpoint:
+                description: ControlPlaneEndpoint represents the endpoint used to
+                  communicate with the control plane.
+                properties:
+                  host:
+                    description: The hostname on which the API server is serving.
+                    type: string
+                  port:
+                    description: The port on which the API server is serving.
+                    format: int32
+                    type: integer
+                required:
+                - host
+                - port
+                type: object
+              failureDomains:
+                description: FailureDomains is an optional field which is used to
+                  assign selected availability zones to a cluster FailureDomains if
+                  empty, defaults to all the zones in the selected region and if specified
+                  would override the default zones.
+                items:
+                  type: string
+                type: array
+              network:
+                description: NetworkSpec encapsulates all things related to GCP network.
+                properties:
+                  autoCreateSubnetworks:
+                    description: "AutoCreateSubnetworks: When set to true, the VPC
+                      network is created in \"auto\" mode. When set to false, the
+                      VPC network is created in \"custom\" mode. \n An auto mode VPC
+                      network starts with one subnet per region. Each subnet has a
+                      predetermined range as described in Auto mode VPC network IP
+                      ranges. \n Defaults to true."
+                    type: boolean
+                  loadBalancerBackendPort:
+                    description: Allow for configuration of load balancer backend
+                      (useful for changing apiserver port)
+                    format: int32
+                    type: integer
+                  name:
+                    description: Name is the name of the network to be used.
+                    type: string
+                  subnets:
+                    description: Subnets configuration.
+                    items:
+                      description: SubnetSpec configures an GCP Subnet.
+                      properties:
+                        cidrBlock:
+                          description: CidrBlock is the range of internal addresses
+                            that are owned by this subnetwork. Provide this property
+                            when you create the subnetwork. For example, 10.0.0.0/8
+                            or 192.168.0.0/16. Ranges must be unique and non-overlapping
+                            within a network. Only IPv4 is supported. This field can
+                            be set only at resource creation time.
+                          type: string
+                        description:
+                          description: Description is an optional description associated
+                            with the resource.
+                          type: string
+                        name:
+                          description: Name defines a unique identifier to reference
+                            this resource.
+                          type: string
+                        privateGoogleAccess:
+                          description: PrivateGoogleAccess defines whether VMs in
+                            this subnet can access Google services without assigning
+                            external IP addresses
+                          type: boolean
+                        region:
+                          description: Region is the name of the region where the
+                            Subnetwork resides.
+                          type: string
+                        routeTableId:
+                          description: 'EnableFlowLogs: Whether to enable flow logging
+                            for this subnetwork. If this field is not explicitly set,
+                            it will not appear in get listings. If not set the default
+                            behavior is to disable flow logging.'
+                          type: boolean
+                        secondaryCidrBlocks:
+                          additionalProperties:
+                            type: string
+                          description: SecondaryCidrBlocks defines secondary CIDR
+                            ranges, from which secondary IP ranges of a VM may be
+                            allocated
+                          type: object
+                      type: object
+                    type: array
+                type: object
+              project:
+                description: Project is the name of the project to deploy the cluster
+                  to.
+                type: string
+              region:
+                description: The GCP Region the cluster lives in.
+                type: string
+            required:
+            - project
+            - region
+            type: object
+          status:
+            description: GCPClusterStatus defines the observed state of GCPCluster.
+            properties:
+              failureDomains:
+                additionalProperties:
+                  description: FailureDomainSpec is the Schema for Cluster API failure
+                    domains. It allows controllers to understand how many failure
+                    domains a cluster can optionally span across.
+                  properties:
+                    attributes:
+                      additionalProperties:
+                        type: string
+                      description: Attributes is a free form map of attributes an
+                        infrastructure provider might use or require.
+                      type: object
+                    controlPlane:
+                      description: ControlPlane determines if this failure domain
+                        is suitable for use by control plane machines.
+                      type: boolean
+                  type: object
+                description: FailureDomains is a slice of FailureDomains.
+                type: object
+              network:
+                description: Network encapsulates GCP networking resources.
+                properties:
+                  apiServerBackendService:
+                    description: APIServerBackendService is the full reference to
+                      the backend service created for the API Server.
+                    type: string
+                  apiServerForwardingRule:
+                    description: APIServerForwardingRule is the full reference to
+                      the forwarding rule created for the API Server.
+                    type: string
+                  apiServerHealthCheck:
+                    description: APIServerHealthCheck is the full reference to the
+                      health check created for the API Server.
+                    type: string
+                  apiServerInstanceGroups:
+                    additionalProperties:
+                      type: string
+                    description: APIServerInstanceGroups is a map from zone to the
+                      full reference to the instance groups created for the control
+                      plane nodes created in the same zone.
+                    type: object
+                  apiServerIpAddress:
+                    description: APIServerAddress is the IPV4 global address assigned
+                      to the load balancer created for the API Server.
+                    type: string
+                  apiServerTargetProxy:
+                    description: APIServerTargetProxy is the full reference to the
+                      target proxy created for the API Server.
+                    type: string
+                  firewallRules:
+                    additionalProperties:
+                      type: string
+                    description: FirewallRules is a map from the name of the rule
+                      to its full reference.
+                    type: object
+                  router:
+                    description: Router is the full reference to the router created
+                      within the network it'll contain the cloud nat gateway
+                    type: string
+                  selfLink:
+                    description: SelfLink is the link to the Network used for this
+                      cluster.
+                    type: string
+                type: object
+              ready:
+                description: Bastion Instance `json:"bastion,omitempty"`
+                type: boolean
+            required:
+            - ready
+            type: object
+        type: object
+    served: true
+    storage: false
+    subresources:
+      status: {}
   - additionalPrinterColumns:
     - description: Cluster to which this GCPCluster belongs
       jsonPath: .metadata.labels.cluster\.x-k8s\.io/cluster-name
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpclustertemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpclustertemplates.yaml
index 8b1a67f4..290b0483 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpclustertemplates.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpclustertemplates.yaml
@@ -18,6 +18,152 @@ spec:
     singular: gcpclustertemplate
   scope: Namespaced
   versions:
+  - name: v1alpha4
+    schema:
+      openAPIV3Schema:
+        description: GCPClusterTemplate is the Schema for the gcpclustertemplates
+          API.
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: GCPClusterTemplateSpec defines the desired state of GCPClusterTemplate.
+            properties:
+              template:
+                description: GCPClusterTemplateResource contains spec for GCPClusterSpec.
+                properties:
+                  spec:
+                    description: GCPClusterSpec defines the desired state of GCPCluster.
+                    properties:
+                      additionalLabels:
+                        additionalProperties:
+                          type: string
+                        description: AdditionalLabels is an optional set of tags to
+                          add to GCP resources managed by the GCP provider, in addition
+                          to the ones added by default.
+                        type: object
+                      controlPlaneEndpoint:
+                        description: ControlPlaneEndpoint represents the endpoint
+                          used to communicate with the control plane.
+                        properties:
+                          host:
+                            description: The hostname on which the API server is serving.
+                            type: string
+                          port:
+                            description: The port on which the API server is serving.
+                            format: int32
+                            type: integer
+                        required:
+                        - host
+                        - port
+                        type: object
+                      failureDomains:
+                        description: FailureDomains is an optional field which is
+                          used to assign selected availability zones to a cluster
+                          FailureDomains if empty, defaults to all the zones in the
+                          selected region and if specified would override the default
+                          zones.
+                        items:
+                          type: string
+                        type: array
+                      network:
+                        description: NetworkSpec encapsulates all things related to
+                          GCP network.
+                        properties:
+                          autoCreateSubnetworks:
+                            description: "AutoCreateSubnetworks: When set to true,
+                              the VPC network is created in \"auto\" mode. When set
+                              to false, the VPC network is created in \"custom\" mode.
+                              \n An auto mode VPC network starts with one subnet per
+                              region. Each subnet has a predetermined range as described
+                              in Auto mode VPC network IP ranges. \n Defaults to true."
+                            type: boolean
+                          loadBalancerBackendPort:
+                            description: Allow for configuration of load balancer
+                              backend (useful for changing apiserver port)
+                            format: int32
+                            type: integer
+                          name:
+                            description: Name is the name of the network to be used.
+                            type: string
+                          subnets:
+                            description: Subnets configuration.
+                            items:
+                              description: SubnetSpec configures an GCP Subnet.
+                              properties:
+                                cidrBlock:
+                                  description: CidrBlock is the range of internal
+                                    addresses that are owned by this subnetwork. Provide
+                                    this property when you create the subnetwork.
+                                    For example, 10.0.0.0/8 or 192.168.0.0/16. Ranges
+                                    must be unique and non-overlapping within a network.
+                                    Only IPv4 is supported. This field can be set
+                                    only at resource creation time.
+                                  type: string
+                                description:
+                                  description: Description is an optional description
+                                    associated with the resource.
+                                  type: string
+                                name:
+                                  description: Name defines a unique identifier to
+                                    reference this resource.
+                                  type: string
+                                privateGoogleAccess:
+                                  description: PrivateGoogleAccess defines whether
+                                    VMs in this subnet can access Google services
+                                    without assigning external IP addresses
+                                  type: boolean
+                                region:
+                                  description: Region is the name of the region where
+                                    the Subnetwork resides.
+                                  type: string
+                                routeTableId:
+                                  description: 'EnableFlowLogs: Whether to enable
+                                    flow logging for this subnetwork. If this field
+                                    is not explicitly set, it will not appear in get
+                                    listings. If not set the default behavior is to
+                                    disable flow logging.'
+                                  type: boolean
+                                secondaryCidrBlocks:
+                                  additionalProperties:
+                                    type: string
+                                  description: SecondaryCidrBlocks defines secondary
+                                    CIDR ranges, from which secondary IP ranges of
+                                    a VM may be allocated
+                                  type: object
+                              type: object
+                            type: array
+                        type: object
+                      project:
+                        description: Project is the name of the project to deploy
+                          the cluster to.
+                        type: string
+                      region:
+                        description: The GCP Region the cluster lives in.
+                        type: string
+                    required:
+                    - project
+                    - region
+                    type: object
+                required:
+                - spec
+                type: object
+            required:
+            - template
+            type: object
+        type: object
+    served: true
+    storage: false
   - name: v1beta1
     schema:
       openAPIV3Schema:
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachines.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachines.yaml
index c965b68b..2ff8b015 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachines.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachines.yaml
@@ -16,6 +16,223 @@ spec:
     singular: gcpmachine
   scope: Namespaced
   versions:
+  - additionalPrinterColumns:
+    - description: Cluster to which this GCPMachine belongs
+      jsonPath: .metadata.labels.cluster\.x-k8s\.io/cluster-name
+      name: Cluster
+      type: string
+    - description: GCE instance state
+      jsonPath: .status.instanceState
+      name: State
+      type: string
+    - description: Machine ready status
+      jsonPath: .status.ready
+      name: Ready
+      type: string
+    - description: GCE instance ID
+      jsonPath: .spec.providerID
+      name: InstanceID
+      type: string
+    - description: Machine object which owns with this GCPMachine
+      jsonPath: .metadata.ownerReferences[?(@.kind=="Machine")].name
+      name: Machine
+      type: string
+    name: v1alpha4
+    schema:
+      openAPIV3Schema:
+        description: GCPMachine is the Schema for the gcpmachines API.
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: GCPMachineSpec defines the desired state of GCPMachine.
+            properties:
+              additionalDisks:
+                description: AdditionalDisks are optional non-boot attached disks.
+                items:
+                  description: AttachedDiskSpec degined GCP machine disk.
+                  properties:
+                    deviceType:
+                      description: 'DeviceType is a device type of the attached disk.
+                        Supported types of non-root attached volumes: 1. "pd-standard"
+                        - Standard (HDD) persistent disk 2. "pd-ssd" - SSD persistent
+                        disk 3. "local-ssd" - Local SSD disk (https://cloud.google.com/compute/docs/disks/local-ssd).
+                        Default is "pd-standard".'
+                      type: string
+                    size:
+                      description: Size is the size of the disk in GBs. Defaults to
+                        30GB. For "local-ssd" size is always 375GB.
+                      format: int64
+                      type: integer
+                  type: object
+                type: array
+              additionalLabels:
+                additionalProperties:
+                  type: string
+                description: AdditionalLabels is an optional set of tags to add to
+                  an instance, in addition to the ones added by default by the GCP
+                  provider. If both the GCPCluster and the GCPMachine specify the
+                  same tag name with different values, the GCPMachine's value takes
+                  precedence.
+                type: object
+              additionalMetadata:
+                description: AdditionalMetadata is an optional set of metadata to
+                  add to an instance, in addition to the ones added by default by
+                  the GCP provider.
+                items:
+                  description: MetadataItem defines a single piece of metadata associated
+                    with an instance.
+                  properties:
+                    key:
+                      description: Key is the identifier for the metadata entry.
+                      type: string
+                    value:
+                      description: Value is the value of the metadata entry.
+                      type: string
+                  required:
+                  - key
+                  type: object
+                type: array
+                x-kubernetes-list-map-keys:
+                - key
+                x-kubernetes-list-type: map
+              additionalNetworkTags:
+                description: AdditionalNetworkTags is a list of network tags that
+                  should be applied to the instance. These tags are set in addition
+                  to any network tags defined at the cluster level or in the actuator.
+                items:
+                  type: string
+                type: array
+              image:
+                description: Image is the full reference to a valid image to be used
+                  for this machine. Takes precedence over ImageFamily.
+                type: string
+              imageFamily:
+                description: ImageFamily is the full reference to a valid image family
+                  to be used for this machine.
+                type: string
+              instanceType:
+                description: 'InstanceType is the type of instance to create. Example:
+                  n1.standard-2'
+                type: string
+              preemptible:
+                description: Preemptible defines if instance is preemptible
+                type: boolean
+              providerID:
+                description: ProviderID is the unique identifier as specified by the
+                  cloud provider.
+                type: string
+              publicIP:
+                description: PublicIP specifies whether the instance should get a
+                  public IP. Set this to true if you don't have a NAT instances or
+                  Cloud Nat setup.
+                type: boolean
+              rootDeviceSize:
+                description: RootDeviceSize is the size of the root volume in GB.
+                  Defaults to 30.
+                format: int64
+                type: integer
+              rootDeviceType:
+                description: 'RootDeviceType is the type of the root volume. Supported
+                  types of root volumes: 1. "pd-standard" - Standard (HDD) persistent
+                  disk 2. "pd-ssd" - SSD persistent disk Default is "pd-standard".'
+                type: string
+              serviceAccounts:
+                description: 'ServiceAccount specifies the service account email and
+                  which scopes to assign to the machine. Defaults to: email: "default",
+                  scope: []{compute.CloudPlatformScope}'
+                properties:
+                  email:
+                    description: 'Email: Email address of the service account.'
+                    type: string
+                  scopes:
+                    description: 'Scopes: The list of scopes to be made available
+                      for this service account.'
+                    items:
+                      type: string
+                    type: array
+                type: object
+              subnet:
+                description: Subnet is a reference to the subnetwork to use for this
+                  instance. If not specified, the first subnetwork retrieved from
+                  the Cluster Region and Network is picked.
+                type: string
+            required:
+            - instanceType
+            type: object
+          status:
+            description: GCPMachineStatus defines the observed state of GCPMachine.
+            properties:
+              addresses:
+                description: Addresses contains the GCP instance associated addresses.
+                items:
+                  description: NodeAddress contains information for the node's address.
+                  properties:
+                    address:
+                      description: The node address.
+                      type: string
+                    type:
+                      description: Node address type, one of Hostname, ExternalIP
+                        or InternalIP.
+                      type: string
+                  required:
+                  - address
+                  - type
+                  type: object
+                type: array
+              failureMessage:
+                description: "FailureMessage will be set in the event that there is
+                  a terminal problem reconciling the Machine and will contain a more
+                  verbose string suitable for logging and human consumption. \n This
+                  field should not be set for transitive errors that a controller
+                  faces that are expected to be fixed automatically over time (like
+                  service outages), but instead indicate that something is fundamentally
+                  wrong with the Machine's spec or the configuration of the controller,
+                  and that manual intervention is required. Examples of terminal errors
+                  would be invalid combinations of settings in the spec, values that
+                  are unsupported by the controller, or the responsible controller
+                  itself being critically misconfigured. \n Any transient errors that
+                  occur during the reconciliation of Machines can be added as events
+                  to the Machine object and/or logged in the controller's output."
+                type: string
+              failureReason:
+                description: "FailureReason will be set in the event that there is
+                  a terminal problem reconciling the Machine and will contain a succinct
+                  value suitable for machine interpretation. \n This field should
+                  not be set for transitive errors that a controller faces that are
+                  expected to be fixed automatically over time (like service outages),
+                  but instead indicate that something is fundamentally wrong with
+                  the Machine's spec or the configuration of the controller, and that
+                  manual intervention is required. Examples of terminal errors would
+                  be invalid combinations of settings in the spec, values that are
+                  unsupported by the controller, or the responsible controller itself
+                  being critically misconfigured. \n Any transient errors that occur
+                  during the reconciliation of Machines can be added as events to
+                  the Machine object and/or logged in the controller's output."
+                type: string
+              instanceState:
+                description: InstanceStatus is the status of the GCP instance for
+                  this machine.
+                type: string
+              ready:
+                description: Ready is true when the provider resource is ready.
+                type: boolean
+            type: object
+        type: object
+    served: true
+    storage: false
+    subresources:
+      status: {}
   - additionalPrinterColumns:
     - description: Cluster to which this GCPMachine belongs
       jsonPath: .metadata.labels.cluster\.x-k8s\.io/cluster-name
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachinetemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachinetemplates.yaml
index ceacc3c0..dbe40197 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachinetemplates.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachinetemplates.yaml
@@ -16,6 +16,162 @@ spec:
     singular: gcpmachinetemplate
   scope: Namespaced
   versions:
+  - name: v1alpha4
+    schema:
+      openAPIV3Schema:
+        description: GCPMachineTemplate is the Schema for the gcpmachinetemplates
+          API.
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: GCPMachineTemplateSpec defines the desired state of GCPMachineTemplate.
+            properties:
+              template:
+                description: GCPMachineTemplateResource describes the data needed
+                  to create am GCPMachine from a template.
+                properties:
+                  spec:
+                    description: Spec is the specification of the desired behavior
+                      of the machine.
+                    properties:
+                      additionalDisks:
+                        description: AdditionalDisks are optional non-boot attached
+                          disks.
+                        items:
+                          description: AttachedDiskSpec degined GCP machine disk.
+                          properties:
+                            deviceType:
+                              description: 'DeviceType is a device type of the attached
+                                disk. Supported types of non-root attached volumes:
+                                1. "pd-standard" - Standard (HDD) persistent disk
+                                2. "pd-ssd" - SSD persistent disk 3. "local-ssd" -
+                                Local SSD disk (https://cloud.google.com/compute/docs/disks/local-ssd).
+                                Default is "pd-standard".'
+                              type: string
+                            size:
+                              description: Size is the size of the disk in GBs. Defaults
+                                to 30GB. For "local-ssd" size is always 375GB.
+                              format: int64
+                              type: integer
+                          type: object
+                        type: array
+                      additionalLabels:
+                        additionalProperties:
+                          type: string
+                        description: AdditionalLabels is an optional set of tags to
+                          add to an instance, in addition to the ones added by default
+                          by the GCP provider. If both the GCPCluster and the GCPMachine
+                          specify the same tag name with different values, the GCPMachine's
+                          value takes precedence.
+                        type: object
+                      additionalMetadata:
+                        description: AdditionalMetadata is an optional set of metadata
+                          to add to an instance, in addition to the ones added by
+                          default by the GCP provider.
+                        items:
+                          description: MetadataItem defines a single piece of metadata
+                            associated with an instance.
+                          properties:
+                            key:
+                              description: Key is the identifier for the metadata
+                                entry.
+                              type: string
+                            value:
+                              description: Value is the value of the metadata entry.
+                              type: string
+                          required:
+                          - key
+                          type: object
+                        type: array
+                        x-kubernetes-list-map-keys:
+                        - key
+                        x-kubernetes-list-type: map
+                      additionalNetworkTags:
+                        description: AdditionalNetworkTags is a list of network tags
+                          that should be applied to the instance. These tags are set
+                          in addition to any network tags defined at the cluster level
+                          or in the actuator.
+                        items:
+                          type: string
+                        type: array
+                      image:
+                        description: Image is the full reference to a valid image
+                          to be used for this machine. Takes precedence over ImageFamily.
+                        type: string
+                      imageFamily:
+                        description: ImageFamily is the full reference to a valid
+                          image family to be used for this machine.
+                        type: string
+                      instanceType:
+                        description: 'InstanceType is the type of instance to create.
+                          Example: n1.standard-2'
+                        type: string
+                      preemptible:
+                        description: Preemptible defines if instance is preemptible
+                        type: boolean
+                      providerID:
+                        description: ProviderID is the unique identifier as specified
+                          by the cloud provider.
+                        type: string
+                      publicIP:
+                        description: PublicIP specifies whether the instance should
+                          get a public IP. Set this to true if you don't have a NAT
+                          instances or Cloud Nat setup.
+                        type: boolean
+                      rootDeviceSize:
+                        description: RootDeviceSize is the size of the root volume
+                          in GB. Defaults to 30.
+                        format: int64
+                        type: integer
+                      rootDeviceType:
+                        description: 'RootDeviceType is the type of the root volume.
+                          Supported types of root volumes: 1. "pd-standard" - Standard
+                          (HDD) persistent disk 2. "pd-ssd" - SSD persistent disk
+                          Default is "pd-standard".'
+                        type: string
+                      serviceAccounts:
+                        description: 'ServiceAccount specifies the service account
+                          email and which scopes to assign to the machine. Defaults
+                          to: email: "default", scope: []{compute.CloudPlatformScope}'
+                        properties:
+                          email:
+                            description: 'Email: Email address of the service account.'
+                            type: string
+                          scopes:
+                            description: 'Scopes: The list of scopes to be made available
+                              for this service account.'
+                            items:
+                              type: string
+                            type: array
+                        type: object
+                      subnet:
+                        description: Subnet is a reference to the subnetwork to use
+                          for this instance. If not specified, the first subnetwork
+                          retrieved from the Cluster Region and Network is picked.
+                        type: string
+                    required:
+                    - instanceType
+                    type: object
+                required:
+                - spec
+                type: object
+            required:
+            - template
+            type: object
+        type: object
+    served: true
+    storage: false
   - name: v1beta1
     schema:
       openAPIV3Schema:
diff --git a/docs/book/Makefile b/docs/book/Makefile
new file mode 100644
index 00000000..8ad60923
--- /dev/null
+++ b/docs/book/Makefile
@@ -0,0 +1,43 @@
+# Copyright 2024 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.
+
+# Directories.
+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)
+TABULATE := $(TOOLS_BIN_DIR)/mdbook-tabulate
+EMBED := $(TOOLS_BIN_DIR)/mdbook-embed
+RELEASELINK := $(TOOLS_BIN_DIR)/mdbook-releaselink
+MDBOOK := $(TOOLS_BIN_DIR)/mdbook
+
+export PATH := $(TOOLS_BIN_DIR):$(PATH)
+
+BOOK_DEPS := $(MDBOOK) $(TABULATE) $(EMBED) $(RELEASELINK)
+
+$(TOOLS_BIN_DIR)/%: $(TOOLS_DIR_DEPS)
+	make -C $(TOOLS_DIR) $(subst $(TOOLS_DIR)/,,$@)
+
+.PHONY: serve
+serve: $(BOOK_DEPS) ## Run a local web server with the compiled book
+	$(MDBOOK) serve
+
+.PHONY: build
+build: $(BOOK_DEPS) ## Build the book
+	$(MDBOOK) build
+
+.PHONY: clean
+clean:
+	rm -rf book
diff --git a/go.mod b/go.mod
index 4f59ce61..511027f7 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,7 @@ replace (
 
 require (
 	cloud.google.com/go/compute v1.23.4
-	cloud.google.com/go/container v1.30.1
+	cloud.google.com/go/container v1.30.0
 	cloud.google.com/go/iam v1.1.6
 	cloud.google.com/go/resourcemanager v1.9.5
 	github.com/GoogleCloudPlatform/k8s-cloud-provider v1.24.0
@@ -166,7 +166,7 @@ require (
 	google.golang.org/appengine v1.6.8 // indirect
 	google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20240122161410-6c6643bf1457 // indirect
 	google.golang.org/protobuf v1.32.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
diff --git a/go.sum b/go.sum
index 5ab1f04c..876de94d 100644
--- a/go.sum
+++ b/go.sum
@@ -29,8 +29,8 @@ cloud.google.com/go/compute v1.23.4 h1:EBT9Nw4q3zyE7G45Wvv3MzolIrCJEuHys5muLY0wv
 cloud.google.com/go/compute v1.23.4/go.mod h1:/EJMj55asU6kAFnuZET8zqgwgJ9FvXWXOkkfQZa4ioI=
 cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
 cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
-cloud.google.com/go/container v1.30.1 h1:DbEwg6d9FggyNeSb+AiW6142m2YVPTSENzGx2INDv58=
-cloud.google.com/go/container v1.30.1/go.mod h1:vkbfX0EnAKL/vgVECs5BZn24e1cJROzgszJirRKQ4Bg=
+cloud.google.com/go/container v1.30.0 h1:gutCEjI7UvFx16r+BVXV1QBTfwELbHmO7UBXyqyUq/E=
+cloud.google.com/go/container v1.30.0/go.mod h1:PIUTv9oovS/zd4BBooN1dUlRB74kefTYMlTrs8SfR0E=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
 cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc=
@@ -786,8 +786,8 @@ google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac h1:ZL/Teoy/ZGnzyrq
 google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k=
 google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe h1:0poefMBYvYbs7g5UkjS6HcxBPaTRAmznle9jnxYoAI8=
 google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe h1:bQnxqljG/wqi4NTXu2+DJ3n7APcEA882QZ1JvhQAq9o=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240122161410-6c6643bf1457 h1:6Bi3wdn5Ed9baJn7P0gOhjwA98wOr6uSPjKagPHOVsE=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240122161410-6c6643bf1457/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
diff --git a/hack/tools/Makefile b/hack/tools/Makefile
new file mode 100644
index 00000000..879b093c
--- /dev/null
+++ b/hack/tools/Makefile
@@ -0,0 +1,69 @@
+# Copyright 2024 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.
+
+ROOT_DIR_RELATIVE := ../..
+
+UNAME := $(shell uname -s)
+
+OS := $(shell go env GOOS)
+RUST_TARGET := unknown-$(OS)-gnu
+
+ifeq ($(OS), darwin)
+	RUST_TARGET := apple-darwin
+	GH_ARCH_SUFFIX := macOS_amd64
+	GTAR := gtar
+endif
+
+# Directories.
+BIN_DIR := bin
+BIN_DIR_ABS := $(abspath $(BIN_DIR))
+SHARE_DIR := share
+
+MDBOOK_EXTRACT_COMMAND := tar xfvz $(SHARE_DIR)/mdbook.tar.gz -C bin
+MDBOOK_ARCHIVE_EXT := .tar.gz
+
+MDBOOK_VERSION := v0.4.5
+
+# Tooling binaries.
+$(BIN_DIR):
+	mkdir -p $@
+
+$(SHARE_DIR):
+	mkdir -p $@
+
+# Binaries.
+MDBOOK := $(BIN_DIR)/mdbook
+MDBOOK_SHARE := $(SHARE_DIR)/mdbook$(MDBOOK_ARCHIVE_EXT)
+$(MDBOOK): $(BIN_DIR) $(SHARE_DIR)
+	curl -sL -o $(MDBOOK_SHARE) "https://github.com/rust-lang/mdBook/releases/download/$(MDBOOK_VERSION)/mdBook-$(MDBOOK_VERSION)-x86_64-$(RUST_TARGET)$(MDBOOK_ARCHIVE_EXT)"
+	$(MDBOOK_EXTRACT_COMMAND)
+	chmod +x $@
+	touch -m $@
+
+MDBOOK_EMBED := $(BIN_DIR)/mdbook-embed
+$(MDBOOK_EMBED): $(BIN_DIR) go.mod go.sum
+	go build -tags=tools -o $(BIN_DIR)/mdbook-embed sigs.k8s.io/cluster-api/hack/tools/mdbook/embed
+
+MDBOOK_RELEASELINK := $(BIN_DIR)/mdbook-releaselink
+$(MDBOOK_RELEASELINK): $(BIN_DIR) go.mod go.sum
+	go build -tags=tools -o $(BIN_DIR)/mdbook-releaselink sigs.k8s.io/cluster-api/hack/tools/mdbook/releaselink
+
+MDBOOK_TABULATE := $(BIN_DIR)/mdbook-tabulate
+$(MDBOOK_TABULATE): $(BIN_DIR) go.mod go.sum
+	go build -tags=tools -o $(BIN_DIR)/mdbook-tabulate sigs.k8s.io/cluster-api/hack/tools/mdbook/tabulate
+
+.PHONY: clean
+clean: ## Remove all tools
+	rm -rf bin
+	rm -rf share
diff --git a/hack/tools/go.mod b/hack/tools/go.mod
index 53ba5feb..dfc1e8f5 100644
--- a/hack/tools/go.mod
+++ b/hack/tools/go.mod
@@ -4,9 +4,12 @@ go 1.21
 
 toolchain go1.21.6
 
+replace sigs.k8s.io/cluster-api => sigs.k8s.io/cluster-api v1.6.1
+
 require sigs.k8s.io/cluster-api/hack/tools v0.0.0-20240116064735-bfe8d0d16ff3
 
 require (
+	github.com/blang/semver/v4 v4.0.0 // indirect
 	github.com/go-logr/logr v1.4.1 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
@@ -26,7 +29,9 @@ require (
 	k8s.io/apimachinery v0.28.5 // indirect
 	k8s.io/klog/v2 v2.100.1 // indirect
 	k8s.io/utils v0.0.0-20231127182322-b307cd553661 // indirect
+	sigs.k8s.io/cluster-api v0.0.0-00010101000000-000000000000 // indirect
 	sigs.k8s.io/controller-tools v0.13.0 // indirect
 	sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
+	sigs.k8s.io/kubebuilder/docs/book/utils v0.0.0-20211028165026-57688c578b5d // indirect
 	sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
 )
diff --git a/hack/tools/go.sum b/hack/tools/go.sum
index 60529384..eb98c8ed 100644
--- a/hack/tools/go.sum
+++ b/hack/tools/go.sum
@@ -1,3 +1,5 @@
+github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
+github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -106,12 +108,16 @@ k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
 k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
 k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI=
 k8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+sigs.k8s.io/cluster-api v1.6.1 h1:I34p/fwgRlEhs+o9cUhKXDwNNfPS3no0yJsd2bJyQVc=
+sigs.k8s.io/cluster-api v1.6.1/go.mod h1:DaxwruDvSaEYq5q6FREDaGzX6UsAVUCA99Sp8vfMHyQ=
 sigs.k8s.io/cluster-api/hack/tools v0.0.0-20240116064735-bfe8d0d16ff3 h1:pbVMlqGtkJvCBYntRFIEsvOIGzoTTJtjW1hBEoZMSYk=
 sigs.k8s.io/cluster-api/hack/tools v0.0.0-20240116064735-bfe8d0d16ff3/go.mod h1:1p1NPNOaoJmhqeNiXzNvRMOEFSdJhOwSJtSyfwigFMA=
 sigs.k8s.io/controller-tools v0.13.0 h1:NfrvuZ4bxyolhDBt/rCZhDnx3M2hzlhgo5n3Iv2RykI=
 sigs.k8s.io/controller-tools v0.13.0/go.mod h1:5vw3En2NazbejQGCeWKRrE7q4P+CW8/klfVqP8QZkgA=
 sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
 sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
+sigs.k8s.io/kubebuilder/docs/book/utils v0.0.0-20211028165026-57688c578b5d h1:KLiQzLW3RZJR19+j4pw2h5iioyAyqCkDBEAFdnGa3N8=
+sigs.k8s.io/kubebuilder/docs/book/utils v0.0.0-20211028165026-57688c578b5d/go.mod h1:NRdZafr4zSCseLQggdvIMXa7umxf+Q+PJzrj3wFwiGE=
 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
 sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
 sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
diff --git a/hack/tools/tools.go b/hack/tools/tools.go
index 7379189f..40a452f3 100644
--- a/hack/tools/tools.go
+++ b/hack/tools/tools.go
@@ -19,4 +19,6 @@ package tools
 
 import (
 	_ "sigs.k8s.io/cluster-api/hack/tools/conversion-verifier"
+	_ "sigs.k8s.io/cluster-api/hack/tools/mdbook/embed"
+	_ "sigs.k8s.io/cluster-api/hack/tools/mdbook/releaselink"
 )
diff --git a/main.go b/main.go
index b875c364..3d15c6ef 100644
--- a/main.go
+++ b/main.go
@@ -32,6 +32,7 @@ import (
 	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
 	cgrecord "k8s.io/client-go/tools/record"
 	"k8s.io/klog/v2"
+	infrav1alpha4 "sigs.k8s.io/cluster-api-provider-gcp/api/v1alpha4" //nolint: staticcheck
 	infrav1beta1 "sigs.k8s.io/cluster-api-provider-gcp/api/v1beta1"
 	"sigs.k8s.io/cluster-api-provider-gcp/controllers"
 	infrav1exp "sigs.k8s.io/cluster-api-provider-gcp/exp/api/v1beta1"
@@ -59,6 +60,7 @@ func init() {
 	klog.InitFlags(nil)
 
 	_ = clientgoscheme.AddToScheme(scheme)
+	_ = infrav1alpha4.AddToScheme(scheme)
 	_ = infrav1beta1.AddToScheme(scheme)
 	_ = clusterv1.AddToScheme(scheme)
 	_ = expclusterv1.AddToScheme(scheme)
diff --git a/netlify.toml b/netlify.toml
new file mode 100644
index 00000000..c39e6d2c
--- /dev/null
+++ b/netlify.toml
@@ -0,0 +1,27 @@
+# Netlify build instructions
+[build]
+    command = "make -C docs/book build"
+    publish = "docs/book/book"
+
+[build.environment]
+    GO_VERSION = "1.21"
+
+# Standard Netlify redirects
+[[redirects]]
+    from = "https://main--cluster-api-gcp.netlify.com/*"
+    to = "https://main.cluster-api-gcp.sigs.k8s.io/:splat"
+    status = 301
+    force = true
+
+# HTTP-to-HTTPS rules
+[[redirects]]
+    from = "http://main.cluster-api-gcp.sigs.k8s.io/*"
+    to = "https://main.cluster-api-gcp.sigs.k8s.io/:splat"
+    status = 301
+    force = true
+
+[[redirects]]
+    from = "http://main--cluster-api-gcp.netlify.com/*"
+    to = "http://main.cluster-api-gcp.sigs.k8s.io/:splat"
+    status = 301
+    force = true


Test patch:
diff --git a/api/v1alpha4/conversion_test.go b/api/v1alpha4/conversion_test.go
new file mode 100644
index 00000000..3317fb80
--- /dev/null
+++ b/api/v1alpha4/conversion_test.go
@@ -0,0 +1,46 @@
+/*
+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.
+*/
+
+package v1alpha4
+
+import (
+	"testing"
+
+	"sigs.k8s.io/cluster-api-provider-gcp/api/v1beta1"
+	utilconversion "sigs.k8s.io/cluster-api/util/conversion"
+)
+
+func TestFuzzyConversion(t *testing.T) {
+	t.Run("for GCPCluster", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{
+		Hub:   &v1beta1.GCPCluster{},
+		Spoke: &GCPCluster{},
+	}))
+
+	t.Run("for GCPClusterTemplate", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{
+		Hub:   &v1beta1.GCPClusterTemplate{},
+		Spoke: &GCPClusterTemplate{},
+	}))
+
+	t.Run("for GCPMachine", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{
+		Hub:   &v1beta1.GCPMachine{},
+		Spoke: &GCPMachine{},
+	}))
+
+	t.Run("for GCPMachineTemplate", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{
+		Hub:   &v1beta1.GCPMachineTemplate{},
+		Spoke: &GCPMachineTemplate{},
+	}))
+}