Repository: kubernetes-sigs/cluster-api-provider-openstack

Issue:
Add/update kustomizations to CAPI v1beta2
We have already updated the e2e tests to use the v1beta2 contract in all tests except for clusterctl-upgrade. However, our [kustomizations](https://github.com/kubernetes-sigs/cluster-api-provider-openstack/tree/main/kustomize/v1beta1) are still v1beta1. I think we should already update them to v1beta2, and add one specific v1beta1 kustomization for clusterctl-upgrade.

The differences are quite well documented at https://cluster-api.sigs.k8s.io/developer/providers/migrations/v1.10-to-v1.11.

Discussion:

Solution patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 91cd0caf..9c969474 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -12,7 +12,7 @@ updates:
   target-branch: main
   groups:
     all-github-actions:
-      patterns: [ "*" ]
+      patterns: ["*"]
   commit-message:
     prefix: ":seedling:"
     include: scope
@@ -30,8 +30,8 @@ updates:
   target-branch: main
   groups:
     all-go-mod-patch-and-minor:
-      patterns: [ "*" ]
-      update-types: [ "patch", "minor" ]
+      patterns: ["*"]
+      update-types: ["patch", "minor"]
   commit-message:
     prefix: ":seedling:"
     include: scope
@@ -45,8 +45,8 @@ updates:
   - dependency-name: "sigs.k8s.io/controller-tools"
     update-types: ["version-update:semver-major", "version-update:semver-minor"]
   labels:
-    - "area/dependency"
-    - "ok-to-test"
+  - "area/dependency"
+  - "ok-to-test"
 ## main branch config ends here
 ## release-0.13 branch config starts here
 # github-actions
@@ -58,7 +58,7 @@ updates:
   target-branch: release-0.13
   groups:
     all-github-actions:
-      patterns: [ "*" ]
+      patterns: ["*"]
   commit-message:
     prefix: ":seedling:"
     include: scope
@@ -76,8 +76,8 @@ updates:
   target-branch: release-0.13
   groups:
     all-go-mod-patch-and-minor:
-      patterns: [ "*" ]
-      update-types: [ "patch", "minor" ]
+      patterns: ["*"]
+      update-types: ["patch", "minor"]
   commit-message:
     prefix: ":seedling:"
     include: scope
@@ -94,8 +94,8 @@ updates:
   - dependency-name: "sigs.k8s.io/controller-tools"
     update-types: ["version-update:semver-major", "version-update:semver-minor"]
   labels:
-    - "area/dependency"
-    - "ok-to-test"
+  - "area/dependency"
+  - "ok-to-test"
 ## release-0.13 branch config ends here
 ## release-0.12 branch config starts here
 # github-actions
@@ -107,7 +107,7 @@ updates:
   target-branch: release-0.12
   groups:
     all-github-actions:
-      patterns: [ "*" ]
+      patterns: ["*"]
   commit-message:
     prefix: ":seedling:"
     include: scope
@@ -125,8 +125,8 @@ updates:
   target-branch: release-0.12
   groups:
     all-go-mod-patch-and-minor:
-      patterns: [ "*" ]
-      update-types: [ "patch", "minor" ]
+      patterns: ["*"]
+      update-types: ["patch", "minor"]
   commit-message:
     prefix: ":seedling:"
     include: scope
@@ -147,9 +147,10 @@ updates:
   # These dependencies are skipped because they require a newer version of go:
   - dependency-name: "github.com/a8m/envsubst"
   - dependency-name: "github.com/onsi/gomega"
+  - dependency-name: "golang.org/x/crypto"
   labels:
-    - "area/dependency"
-    - "ok-to-test"
+  - "area/dependency"
+  - "ok-to-test"
 ## release-0.12 branch config ends here
 ## release-0.11 branch config starts here
 # github-actions
@@ -161,7 +162,7 @@ updates:
   target-branch: release-0.11
   groups:
     all-github-actions:
-      patterns: [ "*" ]
+      patterns: ["*"]
   commit-message:
     prefix: ":seedling:"
     include: scope
@@ -180,8 +181,8 @@ updates:
   target-branch: release-0.11
   groups:
     all-go-mod-patch-and-minor:
-      patterns: [ "*" ]
-      update-types: [ "patch", "minor" ]
+      patterns: ["*"]
+      update-types: ["patch", "minor"]
   commit-message:
     prefix: ":seedling:"
     include: scope
@@ -208,6 +209,6 @@ updates:
   - dependency-name: "golang.org/x/crypto"
   - dependency-name: "golang.org/x/text"
   labels:
-    - "area/dependency"
-    - "ok-to-test"
+  - "area/dependency"
+  - "ok-to-test"
 ## release-0.11 branch config ends here
diff --git a/.github/workflows/pr-verifer.yml b/.github/workflows/pr-verifer.yml
new file mode 100644
index 00000000..6f4a254f
--- /dev/null
+++ b/.github/workflows/pr-verifer.yml
@@ -0,0 +1,53 @@
+name: Check PR Title
+permissions: {}
+
+on:
+  pull_request_target:
+    types: [opened, edited, reopened, synchronize, ready_for_review]
+
+jobs:
+  check-title:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check out repository
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+
+      - name: Validate PR Title
+        env:
+          PR_TITLE: ${{ github.event.pull_request.title }}
+        run: |
+          WIP_REGEX='^\W?WIP\W'
+          TAG_REGEX='^\[[[:alnum:]\._-]*\]'
+
+          # Trim WIP and tags from title
+          trimmed_title=$(echo "${PR_TITLE}" | sed -E "s/${WIP_REGEX}//" | sed -E "s/${TAG_REGEX}//" | xargs)
+
+          # Normalize common emojis in text form to actual emojis
+          trimmed_title=$(echo "$trimmed_title" | sed -E "s/:warning:/⚠/g")
+          trimmed_title=$(echo "$trimmed_title" | sed -E "s/:sparkles:/✨/g")
+          trimmed_title=$(echo "$trimmed_title" | sed -E "s/:bug:/🐛/g")
+          trimmed_title=$(echo "$trimmed_title" | sed -E "s/:book:/📖/g")
+          trimmed_title=$(echo "$trimmed_title" | sed -E "s/:rocket:/🚀/g")
+          trimmed_title=$(echo "$trimmed_title" | sed -E "s/:seedling:/🌱/g")
+
+          # Check PR type prefix
+          if [[ "${trimmed_title}" =~ ^(⚠|✨|🐛|📖|🚀|🌱) ]]; then
+              echo "PR title is valid: $trimmed_title"
+          else
+              echo "Error: No matching PR type indicator found in title."
+              echo "You need to have one of these as the prefix of your PR title:"
+              echo "- Breaking change: ⚠ (:warning:)"
+              echo "- Non-breaking feature: ✨ (:sparkles:)"
+              echo "- Patch fix: 🐛 (:bug:)"
+              echo "- Docs: 📖 (:book:)"
+              echo "- Release: 🚀 (:rocket:)"
+              echo "- Infra/Tests/Other: 🌱 (:seedling:)"
+              exit 1
+          fi
+
+          # Check that PR title does not contain Issue or PR number
+          if [[ "${trimmed_title}" =~ \#[0-9]+ ]]; then
+              echo "Error: PR title should not contain issue or PR number."
+              echo "Issue numbers belong in the PR body as either \"Fixes #XYZ\" (if it closes the issue or PR), or something like \"Related to #XYZ\" (if it's just related)."
+              exit 1
+          fi
diff --git a/.github/workflows/update-golangci-lint.yaml b/.github/workflows/update-golangci-lint.yaml
index baf3bcdd..1d68ad05 100644
--- a/.github/workflows/update-golangci-lint.yaml
+++ b/.github/workflows/update-golangci-lint.yaml
@@ -16,7 +16,7 @@ jobs:
       current_version: ${{ steps.check_version.outputs.current_version }}
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v5
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
         with:
           fetch-depth: 0
       - name: Get latest golangci-lint version
diff --git a/Makefile b/Makefile
index 8bc8b927..a4fe8203 100644
--- a/Makefile
+++ b/Makefile
@@ -184,10 +184,11 @@ e2e-templates: $(addprefix $(E2E_NO_ARTIFACT_TEMPLATES_DIR)/, \
 		 cluster-template-without-lb.yaml \
 		 cluster-template.yaml \
 		 cluster-template-flatcar.yaml \
-                 cluster-template-k8s-upgrade.yaml \
+		 cluster-template-k8s-upgrade.yaml \
 		 cluster-template-flatcar-sysext.yaml \
 		 cluster-template-no-bastion.yaml \
-		 cluster-template-health-monitor.yaml)
+		 cluster-template-health-monitor.yaml \
+		 cluster-template-capi-v1beta1.yaml)
 # Currently no templates that require CI artifacts
 # $(addprefix $(E2E_TEMPLATES_DIR)/, add-templates-here.yaml) \
 
@@ -486,7 +487,8 @@ templates: ## Generate cluster templates
 templates: templates/cluster-template.yaml \
 	templates/cluster-template-without-lb.yaml \
 	templates/cluster-template-flatcar.yaml \
-	templates/cluster-template-flatcar-sysext.yaml
+	templates/cluster-template-flatcar-sysext.yaml \
+	templates/cluster-template-capi-v1beta1.yaml
 
 templates/cluster-template.yaml: kustomize/v1beta1/default $(KUSTOMIZE) FORCE
 	$(KUSTOMIZE) build "$<" > "$@"
diff --git a/kustomize/v1beta1/capi-v1beta1/cluster-template.yaml b/kustomize/v1beta1/capi-v1beta1/cluster-template.yaml
new file mode 100644
index 00000000..545dad1d
--- /dev/null
+++ b/kustomize/v1beta1/capi-v1beta1/cluster-template.yaml
@@ -0,0 +1,160 @@
+apiVersion: cluster.x-k8s.io/v1beta1
+kind: Cluster
+metadata:
+  name: ${CLUSTER_NAME}
+spec:
+  clusterNetwork:
+    pods:
+      cidrBlocks: ["192.168.0.0/16"] # CIDR block used by Calico.
+    serviceDomain: "cluster.local"
+  infrastructureRef:
+    apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+    kind: OpenStackCluster
+    name: ${CLUSTER_NAME}
+  controlPlaneRef:
+    kind: KubeadmControlPlane
+    apiVersion: controlplane.cluster.x-k8s.io/v1beta1
+    name: ${CLUSTER_NAME}-control-plane
+---
+apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+kind: OpenStackCluster
+metadata:
+  name: ${CLUSTER_NAME}
+spec:
+  identityRef:
+    name: ${CLUSTER_NAME}-cloud-config
+    cloudName: ${OPENSTACK_CLOUD}
+  apiServerLoadBalancer:
+    enabled: true
+  managedSubnets:
+  - cidr: 10.6.0.0/24
+    dnsNameservers:
+    - ${OPENSTACK_DNS_NAMESERVERS}
+  managedSecurityGroups:
+    allNodesSecurityGroupRules:
+    - description: Created by cluster-api-provider-openstack - BGP (calico)
+      direction: ingress
+      etherType: IPv4
+      name: BGP (Calico)
+      portRangeMax: 179
+      portRangeMin: 179
+      protocol: tcp
+      remoteManagedGroups:
+      - controlplane
+      - worker
+    - description: Created by cluster-api-provider-openstack - IP-in-IP (calico)
+      direction: ingress
+      etherType: IPv4
+      name: IP-in-IP (calico)
+      protocol: "4"
+      remoteManagedGroups:
+      - controlplane
+      - worker
+  externalNetwork:
+    id: ${OPENSTACK_EXTERNAL_NETWORK_ID}
+---
+kind: KubeadmControlPlane
+apiVersion: controlplane.cluster.x-k8s.io/v1beta1
+metadata:
+  name: "${CLUSTER_NAME}-control-plane"
+spec:
+  replicas: ${CONTROL_PLANE_MACHINE_COUNT}
+  machineTemplate:
+    infrastructureRef:
+      kind: OpenStackMachineTemplate
+      apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+      name: "${CLUSTER_NAME}-control-plane"
+  kubeadmConfigSpec:
+    initConfiguration:
+      nodeRegistration:
+        name: '{{ local_hostname }}'
+        kubeletExtraArgs:
+          cloud-provider: external
+          provider-id: "openstack:///'{{ instance_id }}'"
+    clusterConfiguration:
+      controllerManager:
+        extraArgs:
+          cloud-provider: external
+    joinConfiguration:
+      nodeRegistration:
+        name: '{{ local_hostname }}'
+        kubeletExtraArgs:
+          cloud-provider: external
+          provider-id: "openstack:///'{{ instance_id }}'"
+    files: []
+  version: "${KUBERNETES_VERSION}"
+---
+apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+kind: OpenStackMachineTemplate
+metadata:
+  name: ${CLUSTER_NAME}-control-plane
+spec:
+  template:
+    spec:
+      flavor: ${OPENSTACK_CONTROL_PLANE_MACHINE_FLAVOR}
+      image:
+        filter:
+          name: ${OPENSTACK_IMAGE_NAME}
+      sshKeyName: ${OPENSTACK_SSH_KEY_NAME}
+---
+apiVersion: cluster.x-k8s.io/v1beta1
+kind: MachineDeployment
+metadata:
+  name: "${CLUSTER_NAME}-md-0"
+spec:
+  clusterName: "${CLUSTER_NAME}"
+  replicas: ${WORKER_MACHINE_COUNT}
+  selector:
+    matchLabels:
+  template:
+    spec:
+      clusterName: "${CLUSTER_NAME}"
+      version: "${KUBERNETES_VERSION}"
+      failureDomain: ${OPENSTACK_FAILURE_DOMAIN}
+      bootstrap:
+        configRef:
+          name: "${CLUSTER_NAME}-md-0"
+          apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+          kind: KubeadmConfigTemplate
+      infrastructureRef:
+        name: "${CLUSTER_NAME}-md-0"
+        apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+        kind: OpenStackMachineTemplate
+---
+apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+kind: OpenStackMachineTemplate
+metadata:
+  name: ${CLUSTER_NAME}-md-0
+spec:
+  template:
+    spec:
+      flavor: ${OPENSTACK_NODE_MACHINE_FLAVOR}
+      image:
+        filter:
+          name: ${OPENSTACK_IMAGE_NAME}
+      sshKeyName: ${OPENSTACK_SSH_KEY_NAME}
+---
+apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+kind: KubeadmConfigTemplate
+metadata:
+  name: ${CLUSTER_NAME}-md-0
+spec:
+  template:
+    spec:
+      files: []
+      joinConfiguration:
+        nodeRegistration:
+          name: '{{ local_hostname }}'
+          kubeletExtraArgs:
+            cloud-provider: external
+            provider-id: "openstack:///'{{ instance_id }}'"
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: ${CLUSTER_NAME}-cloud-config
+  labels:
+    clusterctl.cluster.x-k8s.io/move: "true"
+data:
+  clouds.yaml: ${OPENSTACK_CLOUD_YAML_B64}
+  cacert: ${OPENSTACK_CLOUD_CACERT_B64}
diff --git a/kustomize/v1beta1/capi-v1beta1/kustomization.yaml b/kustomize/v1beta1/capi-v1beta1/kustomization.yaml
new file mode 100644
index 00000000..bc7ab04c
--- /dev/null
+++ b/kustomize/v1beta1/capi-v1beta1/kustomization.yaml
@@ -0,0 +1,4 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+resources:
+- cluster-template.yaml
diff --git a/kustomize/v1beta1/default/cluster-template.yaml b/kustomize/v1beta1/default/cluster-template.yaml
index d27f558a..8f60382a 100644
--- a/kustomize/v1beta1/default/cluster-template.yaml
+++ b/kustomize/v1beta1/default/cluster-template.yaml
@@ -1,5 +1,4 @@
----
-apiVersion: cluster.x-k8s.io/v1beta1
+apiVersion: cluster.x-k8s.io/v1beta2
 kind: Cluster
 metadata:
   name: ${CLUSTER_NAME}
@@ -9,12 +8,12 @@ spec:
       cidrBlocks: ["192.168.0.0/16"] # CIDR block used by Calico.
     serviceDomain: "cluster.local"
   infrastructureRef:
-    apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+    apiGroup: infrastructure.cluster.x-k8s.io
     kind: OpenStackCluster
     name: ${CLUSTER_NAME}
   controlPlaneRef:
+    apiGroup: controlplane.cluster.x-k8s.io
     kind: KubeadmControlPlane
-    apiVersion: controlplane.cluster.x-k8s.io/v1beta1
     name: ${CLUSTER_NAME}-control-plane
 ---
 apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
@@ -54,35 +53,40 @@ spec:
   externalNetwork:
     id: ${OPENSTACK_EXTERNAL_NETWORK_ID}
 ---
+apiVersion: controlplane.cluster.x-k8s.io/v1beta2
 kind: KubeadmControlPlane
-apiVersion: controlplane.cluster.x-k8s.io/v1beta1
 metadata:
   name: "${CLUSTER_NAME}-control-plane"
 spec:
   replicas: ${CONTROL_PLANE_MACHINE_COUNT}
   machineTemplate:
-    infrastructureRef:
-      kind: OpenStackMachineTemplate
-      apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
-      name: "${CLUSTER_NAME}-control-plane"
+    spec:
+      infrastructureRef:
+        kind: OpenStackMachineTemplate
+        apiGroup: infrastructure.cluster.x-k8s.io
+        name: "${CLUSTER_NAME}-control-plane"
   kubeadmConfigSpec:
     initConfiguration:
       nodeRegistration:
         name: '{{ local_hostname }}'
         kubeletExtraArgs:
-          cloud-provider: external
-          provider-id: "openstack:///'{{ instance_id }}'"
+        - name: cloud-provider
+          value: external
+        - name: provider-id
+          value: "openstack:///'{{ instance_id }}'"
     clusterConfiguration:
       controllerManager:
         extraArgs:
-          cloud-provider: external
+        - name: cloud-provider
+          value: external
     joinConfiguration:
       nodeRegistration:
         name: '{{ local_hostname }}'
         kubeletExtraArgs:
-          cloud-provider: external
-          provider-id: "openstack:///'{{ instance_id }}'"
-    files: []
+        - name: cloud-provider
+          value: external
+        - name: provider-id
+          value: "openstack:///'{{ instance_id }}'"
   version: "${KUBERNETES_VERSION}"
 ---
 apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
@@ -98,7 +102,7 @@ spec:
           name: ${OPENSTACK_IMAGE_NAME}
       sshKeyName: ${OPENSTACK_SSH_KEY_NAME}
 ---
-apiVersion: cluster.x-k8s.io/v1beta1
+apiVersion: cluster.x-k8s.io/v1beta2
 kind: MachineDeployment
 metadata:
   name: "${CLUSTER_NAME}-md-0"
@@ -115,11 +119,11 @@ spec:
       bootstrap:
         configRef:
           name: "${CLUSTER_NAME}-md-0"
-          apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+          apiGroup: bootstrap.cluster.x-k8s.io
           kind: KubeadmConfigTemplate
       infrastructureRef:
         name: "${CLUSTER_NAME}-md-0"
-        apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+        apiGroup: infrastructure.cluster.x-k8s.io
         kind: OpenStackMachineTemplate
 ---
 apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
@@ -135,20 +139,21 @@ spec:
           name: ${OPENSTACK_IMAGE_NAME}
       sshKeyName: ${OPENSTACK_SSH_KEY_NAME}
 ---
-apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+apiVersion: bootstrap.cluster.x-k8s.io/v1beta2
 kind: KubeadmConfigTemplate
 metadata:
   name: ${CLUSTER_NAME}-md-0
 spec:
   template:
     spec:
-      files: []
       joinConfiguration:
         nodeRegistration:
           name: '{{ local_hostname }}'
           kubeletExtraArgs:
-            cloud-provider: external
-            provider-id: "openstack:///'{{ instance_id }}'"
+          - name: cloud-provider
+            value: external
+          - name: provider-id
+            value: "openstack:///'{{ instance_id }}'"
 ---
 apiVersion: v1
 kind: Secret
diff --git a/kustomize/v1beta1/flatcar-sysext/patch-flatcar.yaml b/kustomize/v1beta1/flatcar-sysext/patch-flatcar.yaml
index 89113fb0..d2b78d87 100644
--- a/kustomize/v1beta1/flatcar-sysext/patch-flatcar.yaml
+++ b/kustomize/v1beta1/flatcar-sysext/patch-flatcar.yaml
@@ -1,4 +1,3 @@
----
 apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
 kind: OpenStackCluster
 metadata:
@@ -7,7 +6,7 @@ spec:
   apiServerLoadBalancer:
     $patch: delete
 ---
-apiVersion: controlplane.cluster.x-k8s.io/v1beta1
+apiVersion: controlplane.cluster.x-k8s.io/v1beta2
 kind: KubeadmControlPlane
 metadata:
   name: "${CLUSTER_NAME}-control-plane"
@@ -18,12 +17,18 @@ spec:
       nodeRegistration:
         name: $${COREOS_OPENSTACK_HOSTNAME}
         kubeletExtraArgs:
-          provider-id: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
+        - name: cloud-provider
+          value: external
+        - name: provider-id
+          value: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
     initConfiguration:
       nodeRegistration:
         name: $${COREOS_OPENSTACK_HOSTNAME}
         kubeletExtraArgs:
-          provider-id: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
+        - name: cloud-provider
+          value: external
+        - name: provider-id
+          value: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
     format: ignition
     ignition:
       containerLinuxConfig:
@@ -87,7 +92,7 @@ spec:
     - envsubst < /etc/kubeadm.yml > /etc/kubeadm.yml.tmp
     - mv /etc/kubeadm.yml.tmp /etc/kubeadm.yml
 ---
-apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+apiVersion: bootstrap.cluster.x-k8s.io/v1beta2
 kind: KubeadmConfigTemplate
 metadata:
   name: ${CLUSTER_NAME}-md-0
@@ -98,7 +103,10 @@ spec:
         nodeRegistration:
           name: $${COREOS_OPENSTACK_HOSTNAME}
           kubeletExtraArgs:
-            provider-id: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
+          - name: cloud-provider
+            value: external
+          - name: provider-id
+            value: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
       preKubeadmCommands:
       - export COREOS_OPENSTACK_HOSTNAME=$${COREOS_OPENSTACK_HOSTNAME%.*}
       - export COREOS_OPENSTACK_INSTANCE_UUID=$${COREOS_OPENSTACK_INSTANCE_UUID}
diff --git a/kustomize/v1beta1/flatcar/patch-flatcar.yaml b/kustomize/v1beta1/flatcar/patch-flatcar.yaml
index a7024296..35ea2511 100644
--- a/kustomize/v1beta1/flatcar/patch-flatcar.yaml
+++ b/kustomize/v1beta1/flatcar/patch-flatcar.yaml
@@ -1,4 +1,3 @@
----
 apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
 kind: OpenStackCluster
 metadata:
@@ -7,7 +6,7 @@ spec:
   apiServerLoadBalancer:
     $patch: delete
 ---
-apiVersion: controlplane.cluster.x-k8s.io/v1beta1
+apiVersion: controlplane.cluster.x-k8s.io/v1beta2
 kind: KubeadmControlPlane
 metadata:
   name: "${CLUSTER_NAME}-control-plane"
@@ -18,12 +17,18 @@ spec:
       nodeRegistration:
         name: $${COREOS_OPENSTACK_HOSTNAME}
         kubeletExtraArgs:
-          provider-id: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
+        - name: cloud-provider
+          value: external
+        - name: provider-id
+          value: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
     initConfiguration:
       nodeRegistration:
         name: $${COREOS_OPENSTACK_HOSTNAME}
         kubeletExtraArgs:
-          provider-id: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
+        - name: cloud-provider
+          value: external
+        - name: provider-id
+          value: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
     format: ignition
     ignition:
       containerLinuxConfig:
@@ -44,12 +49,12 @@ spec:
                   [Service]
                   EnvironmentFile=/run/metadata/flatcar
     preKubeadmCommands:
-      - export COREOS_OPENSTACK_HOSTNAME=$${COREOS_OPENSTACK_HOSTNAME%.*}
-      - export COREOS_OPENSTACK_INSTANCE_UUID=$${COREOS_OPENSTACK_INSTANCE_UUID}
-      - envsubst < /etc/kubeadm.yml > /etc/kubeadm.yml.tmp
-      - mv /etc/kubeadm.yml.tmp /etc/kubeadm.yml
+    - export COREOS_OPENSTACK_HOSTNAME=$${COREOS_OPENSTACK_HOSTNAME%.*}
+    - export COREOS_OPENSTACK_INSTANCE_UUID=$${COREOS_OPENSTACK_INSTANCE_UUID}
+    - envsubst < /etc/kubeadm.yml > /etc/kubeadm.yml.tmp
+    - mv /etc/kubeadm.yml.tmp /etc/kubeadm.yml
 ---
-apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+apiVersion: bootstrap.cluster.x-k8s.io/v1beta2
 kind: KubeadmConfigTemplate
 metadata:
   name: ${CLUSTER_NAME}-md-0
@@ -60,12 +65,15 @@ spec:
         nodeRegistration:
           name: $${COREOS_OPENSTACK_HOSTNAME}
           kubeletExtraArgs:
-            provider-id: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
+          - name: cloud-provider
+            value: external
+          - name: provider-id
+            value: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
       preKubeadmCommands:
-        - export COREOS_OPENSTACK_HOSTNAME=$${COREOS_OPENSTACK_HOSTNAME%.*}
-        - export COREOS_OPENSTACK_INSTANCE_UUID=$${COREOS_OPENSTACK_INSTANCE_UUID}
-        - envsubst < /etc/kubeadm.yml > /etc/kubeadm.yml.tmp
-        - mv /etc/kubeadm.yml.tmp /etc/kubeadm.yml
+      - export COREOS_OPENSTACK_HOSTNAME=$${COREOS_OPENSTACK_HOSTNAME%.*}
+      - export COREOS_OPENSTACK_INSTANCE_UUID=$${COREOS_OPENSTACK_INSTANCE_UUID}
+      - envsubst < /etc/kubeadm.yml > /etc/kubeadm.yml.tmp
+      - mv /etc/kubeadm.yml.tmp /etc/kubeadm.yml
       format: ignition
       ignition:
         containerLinuxConfig:
diff --git a/kustomize/v1beta1/without-lb/patch-without-lb.yaml b/kustomize/v1beta1/without-lb/patch-without-lb.yaml
index 96932f5a..36945b92 100644
--- a/kustomize/v1beta1/without-lb/patch-without-lb.yaml
+++ b/kustomize/v1beta1/without-lb/patch-without-lb.yaml
@@ -1,4 +1,3 @@
----
 apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
 kind: OpenStackCluster
 metadata:
@@ -7,7 +6,7 @@ spec:
   apiServerLoadBalancer:
     $patch: delete
 ---
-apiVersion: controlplane.cluster.x-k8s.io/v1beta1
+apiVersion: controlplane.cluster.x-k8s.io/v1beta2
 kind: KubeadmControlPlane
 metadata:
   name: "${CLUSTER_NAME}-control-plane"
diff --git a/templates/cluster-template-capi-v1beta1.yaml b/templates/cluster-template-capi-v1beta1.yaml
new file mode 100644
index 00000000..d419a7c3
--- /dev/null
+++ b/templates/cluster-template-capi-v1beta1.yaml
@@ -0,0 +1,161 @@
+apiVersion: v1
+data:
+  cacert: ${OPENSTACK_CLOUD_CACERT_B64}
+  clouds.yaml: ${OPENSTACK_CLOUD_YAML_B64}
+kind: Secret
+metadata:
+  labels:
+    clusterctl.cluster.x-k8s.io/move: "true"
+  name: ${CLUSTER_NAME}-cloud-config
+---
+apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+kind: KubeadmConfigTemplate
+metadata:
+  name: ${CLUSTER_NAME}-md-0
+spec:
+  template:
+    spec:
+      files: []
+      joinConfiguration:
+        nodeRegistration:
+          kubeletExtraArgs:
+            cloud-provider: external
+            provider-id: openstack:///'{{ instance_id }}'
+          name: '{{ local_hostname }}'
+---
+apiVersion: cluster.x-k8s.io/v1beta1
+kind: Cluster
+metadata:
+  name: ${CLUSTER_NAME}
+spec:
+  clusterNetwork:
+    pods:
+      cidrBlocks:
+      - 192.168.0.0/16
+    serviceDomain: cluster.local
+  controlPlaneRef:
+    apiVersion: controlplane.cluster.x-k8s.io/v1beta1
+    kind: KubeadmControlPlane
+    name: ${CLUSTER_NAME}-control-plane
+  infrastructureRef:
+    apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+    kind: OpenStackCluster
+    name: ${CLUSTER_NAME}
+---
+apiVersion: cluster.x-k8s.io/v1beta1
+kind: MachineDeployment
+metadata:
+  name: ${CLUSTER_NAME}-md-0
+spec:
+  clusterName: ${CLUSTER_NAME}
+  replicas: ${WORKER_MACHINE_COUNT}
+  selector:
+    matchLabels: null
+  template:
+    spec:
+      bootstrap:
+        configRef:
+          apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+          kind: KubeadmConfigTemplate
+          name: ${CLUSTER_NAME}-md-0
+      clusterName: ${CLUSTER_NAME}
+      failureDomain: ${OPENSTACK_FAILURE_DOMAIN}
+      infrastructureRef:
+        apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+        kind: OpenStackMachineTemplate
+        name: ${CLUSTER_NAME}-md-0
+      version: ${KUBERNETES_VERSION}
+---
+apiVersion: controlplane.cluster.x-k8s.io/v1beta1
+kind: KubeadmControlPlane
+metadata:
+  name: ${CLUSTER_NAME}-control-plane
+spec:
+  kubeadmConfigSpec:
+    clusterConfiguration:
+      controllerManager:
+        extraArgs:
+          cloud-provider: external
+    files: []
+    initConfiguration:
+      nodeRegistration:
+        kubeletExtraArgs:
+          cloud-provider: external
+          provider-id: openstack:///'{{ instance_id }}'
+        name: '{{ local_hostname }}'
+    joinConfiguration:
+      nodeRegistration:
+        kubeletExtraArgs:
+          cloud-provider: external
+          provider-id: openstack:///'{{ instance_id }}'
+        name: '{{ local_hostname }}'
+  machineTemplate:
+    infrastructureRef:
+      apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+      kind: OpenStackMachineTemplate
+      name: ${CLUSTER_NAME}-control-plane
+  replicas: ${CONTROL_PLANE_MACHINE_COUNT}
+  version: ${KUBERNETES_VERSION}
+---
+apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+kind: OpenStackCluster
+metadata:
+  name: ${CLUSTER_NAME}
+spec:
+  apiServerLoadBalancer:
+    enabled: true
+  externalNetwork:
+    id: ${OPENSTACK_EXTERNAL_NETWORK_ID}
+  identityRef:
+    cloudName: ${OPENSTACK_CLOUD}
+    name: ${CLUSTER_NAME}-cloud-config
+  managedSecurityGroups:
+    allNodesSecurityGroupRules:
+    - description: Created by cluster-api-provider-openstack - BGP (calico)
+      direction: ingress
+      etherType: IPv4
+      name: BGP (Calico)
+      portRangeMax: 179
+      portRangeMin: 179
+      protocol: tcp
+      remoteManagedGroups:
+      - controlplane
+      - worker
+    - description: Created by cluster-api-provider-openstack - IP-in-IP (calico)
+      direction: ingress
+      etherType: IPv4
+      name: IP-in-IP (calico)
+      protocol: "4"
+      remoteManagedGroups:
+      - controlplane
+      - worker
+  managedSubnets:
+  - cidr: 10.6.0.0/24
+    dnsNameservers:
+    - ${OPENSTACK_DNS_NAMESERVERS}
+---
+apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+kind: OpenStackMachineTemplate
+metadata:
+  name: ${CLUSTER_NAME}-control-plane
+spec:
+  template:
+    spec:
+      flavor: ${OPENSTACK_CONTROL_PLANE_MACHINE_FLAVOR}
+      image:
+        filter:
+          name: ${OPENSTACK_IMAGE_NAME}
+      sshKeyName: ${OPENSTACK_SSH_KEY_NAME}
+---
+apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+kind: OpenStackMachineTemplate
+metadata:
+  name: ${CLUSTER_NAME}-md-0
+spec:
+  template:
+    spec:
+      flavor: ${OPENSTACK_NODE_MACHINE_FLAVOR}
+      image:
+        filter:
+          name: ${OPENSTACK_IMAGE_NAME}
+      sshKeyName: ${OPENSTACK_SSH_KEY_NAME}
diff --git a/templates/cluster-template-development.yaml b/templates/cluster-template-development.yaml
index d00233b8..df62668d 100644
--- a/templates/cluster-template-development.yaml
+++ b/templates/cluster-template-development.yaml
@@ -24,3 +24,14 @@ spec:
       value: ${ADD_IMAGE_VERSION:=false}
     - name: injectIgnitionSysext
       value: ${INJECT_IGNITION_SYSEXT:=true}
+    - name: allowedCIDRs
+      value: ${OPENSTACK_API_SERVER_ALLOWED_CIDRS:=[]}
+    - name: bastion
+      value:
+        enabled: ${OPENSTACK_BASTION_ENABLED:=false}
+        spec:
+          flavor: ${OPENSTACK_BASTION_FLAVOR:=m1.small}
+          image:
+            filter:
+              name: ${OPENSTACK_BASTION_IMAGE_NAME:=ubuntu-24.04}
+          sshKeyName: ${OPENSTACK_SSH_KEY_NAME:=""}
diff --git a/templates/cluster-template-flatcar-sysext.yaml b/templates/cluster-template-flatcar-sysext.yaml
index 32af9f00..1cf966bb 100644
--- a/templates/cluster-template-flatcar-sysext.yaml
+++ b/templates/cluster-template-flatcar-sysext.yaml
@@ -8,14 +8,13 @@ metadata:
     clusterctl.cluster.x-k8s.io/move: "true"
   name: ${CLUSTER_NAME}-cloud-config
 ---
-apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+apiVersion: bootstrap.cluster.x-k8s.io/v1beta2
 kind: KubeadmConfigTemplate
 metadata:
   name: ${CLUSTER_NAME}-md-0
 spec:
   template:
     spec:
-      files: []
       format: ignition
       ignition:
         containerLinuxConfig:
@@ -76,8 +75,10 @@ spec:
       joinConfiguration:
         nodeRegistration:
           kubeletExtraArgs:
-            cloud-provider: external
-            provider-id: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
+          - name: cloud-provider
+            value: external
+          - name: provider-id
+            value: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
           name: $${COREOS_OPENSTACK_HOSTNAME}
       preKubeadmCommands:
       - export COREOS_OPENSTACK_HOSTNAME=$${COREOS_OPENSTACK_HOSTNAME%.*}
@@ -85,7 +86,7 @@ spec:
       - envsubst < /etc/kubeadm.yml > /etc/kubeadm.yml.tmp
       - mv /etc/kubeadm.yml.tmp /etc/kubeadm.yml
 ---
-apiVersion: cluster.x-k8s.io/v1beta1
+apiVersion: cluster.x-k8s.io/v1beta2
 kind: Cluster
 metadata:
   name: ${CLUSTER_NAME}
@@ -96,15 +97,15 @@ spec:
       - 192.168.0.0/16
     serviceDomain: cluster.local
   controlPlaneRef:
-    apiVersion: controlplane.cluster.x-k8s.io/v1beta1
+    apiGroup: controlplane.cluster.x-k8s.io
     kind: KubeadmControlPlane
     name: ${CLUSTER_NAME}-control-plane
   infrastructureRef:
-    apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+    apiGroup: infrastructure.cluster.x-k8s.io
     kind: OpenStackCluster
     name: ${CLUSTER_NAME}
 ---
-apiVersion: cluster.x-k8s.io/v1beta1
+apiVersion: cluster.x-k8s.io/v1beta2
 kind: MachineDeployment
 metadata:
   name: ${CLUSTER_NAME}-md-0
@@ -117,18 +118,18 @@ spec:
     spec:
       bootstrap:
         configRef:
-          apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+          apiGroup: bootstrap.cluster.x-k8s.io
           kind: KubeadmConfigTemplate
           name: ${CLUSTER_NAME}-md-0
       clusterName: ${CLUSTER_NAME}
       failureDomain: ${OPENSTACK_FAILURE_DOMAIN}
       infrastructureRef:
-        apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+        apiGroup: infrastructure.cluster.x-k8s.io
         kind: OpenStackMachineTemplate
         name: ${CLUSTER_NAME}-md-0
       version: ${KUBERNETES_VERSION}
 ---
-apiVersion: controlplane.cluster.x-k8s.io/v1beta1
+apiVersion: controlplane.cluster.x-k8s.io/v1beta2
 kind: KubeadmControlPlane
 metadata:
   name: ${CLUSTER_NAME}-control-plane
@@ -137,8 +138,8 @@ spec:
     clusterConfiguration:
       controllerManager:
         extraArgs:
-          cloud-provider: external
-    files: []
+        - name: cloud-provider
+          value: external
     format: ignition
     ignition:
       containerLinuxConfig:
@@ -199,14 +200,18 @@ spec:
     initConfiguration:
       nodeRegistration:
         kubeletExtraArgs:
-          cloud-provider: external
-          provider-id: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
+        - name: cloud-provider
+          value: external
+        - name: provider-id
+          value: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
         name: $${COREOS_OPENSTACK_HOSTNAME}
     joinConfiguration:
       nodeRegistration:
         kubeletExtraArgs:
-          cloud-provider: external
-          provider-id: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
+        - name: cloud-provider
+          value: external
+        - name: provider-id
+          value: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
         name: $${COREOS_OPENSTACK_HOSTNAME}
     preKubeadmCommands:
     - export COREOS_OPENSTACK_HOSTNAME=$${COREOS_OPENSTACK_HOSTNAME%.*}
@@ -214,10 +219,11 @@ spec:
     - envsubst < /etc/kubeadm.yml > /etc/kubeadm.yml.tmp
     - mv /etc/kubeadm.yml.tmp /etc/kubeadm.yml
   machineTemplate:
-    infrastructureRef:
-      apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
-      kind: OpenStackMachineTemplate
-      name: ${CLUSTER_NAME}-control-plane
+    spec:
+      infrastructureRef:
+        apiGroup: infrastructure.cluster.x-k8s.io
+        kind: OpenStackMachineTemplate
+        name: ${CLUSTER_NAME}-control-plane
   replicas: ${CONTROL_PLANE_MACHINE_COUNT}
   version: ${KUBERNETES_VERSION}
 ---
diff --git a/templates/cluster-template-flatcar.yaml b/templates/cluster-template-flatcar.yaml
index bc48e499..6f685eaf 100644
--- a/templates/cluster-template-flatcar.yaml
+++ b/templates/cluster-template-flatcar.yaml
@@ -8,14 +8,13 @@ metadata:
     clusterctl.cluster.x-k8s.io/move: "true"
   name: ${CLUSTER_NAME}-cloud-config
 ---
-apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+apiVersion: bootstrap.cluster.x-k8s.io/v1beta2
 kind: KubeadmConfigTemplate
 metadata:
   name: ${CLUSTER_NAME}-md-0
 spec:
   template:
     spec:
-      files: []
       format: ignition
       ignition:
         containerLinuxConfig:
@@ -38,8 +37,10 @@ spec:
       joinConfiguration:
         nodeRegistration:
           kubeletExtraArgs:
-            cloud-provider: external
-            provider-id: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
+          - name: cloud-provider
+            value: external
+          - name: provider-id
+            value: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
           name: $${COREOS_OPENSTACK_HOSTNAME}
       preKubeadmCommands:
       - export COREOS_OPENSTACK_HOSTNAME=$${COREOS_OPENSTACK_HOSTNAME%.*}
@@ -47,7 +48,7 @@ spec:
       - envsubst < /etc/kubeadm.yml > /etc/kubeadm.yml.tmp
       - mv /etc/kubeadm.yml.tmp /etc/kubeadm.yml
 ---
-apiVersion: cluster.x-k8s.io/v1beta1
+apiVersion: cluster.x-k8s.io/v1beta2
 kind: Cluster
 metadata:
   name: ${CLUSTER_NAME}
@@ -58,15 +59,15 @@ spec:
       - 192.168.0.0/16
     serviceDomain: cluster.local
   controlPlaneRef:
-    apiVersion: controlplane.cluster.x-k8s.io/v1beta1
+    apiGroup: controlplane.cluster.x-k8s.io
     kind: KubeadmControlPlane
     name: ${CLUSTER_NAME}-control-plane
   infrastructureRef:
-    apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+    apiGroup: infrastructure.cluster.x-k8s.io
     kind: OpenStackCluster
     name: ${CLUSTER_NAME}
 ---
-apiVersion: cluster.x-k8s.io/v1beta1
+apiVersion: cluster.x-k8s.io/v1beta2
 kind: MachineDeployment
 metadata:
   name: ${CLUSTER_NAME}-md-0
@@ -79,18 +80,18 @@ spec:
     spec:
       bootstrap:
         configRef:
-          apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+          apiGroup: bootstrap.cluster.x-k8s.io
           kind: KubeadmConfigTemplate
           name: ${CLUSTER_NAME}-md-0
       clusterName: ${CLUSTER_NAME}
       failureDomain: ${OPENSTACK_FAILURE_DOMAIN}
       infrastructureRef:
-        apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+        apiGroup: infrastructure.cluster.x-k8s.io
         kind: OpenStackMachineTemplate
         name: ${CLUSTER_NAME}-md-0
       version: ${KUBERNETES_VERSION}
 ---
-apiVersion: controlplane.cluster.x-k8s.io/v1beta1
+apiVersion: controlplane.cluster.x-k8s.io/v1beta2
 kind: KubeadmControlPlane
 metadata:
   name: ${CLUSTER_NAME}-control-plane
@@ -99,8 +100,8 @@ spec:
     clusterConfiguration:
       controllerManager:
         extraArgs:
-          cloud-provider: external
-    files: []
+        - name: cloud-provider
+          value: external
     format: ignition
     ignition:
       containerLinuxConfig:
@@ -123,14 +124,18 @@ spec:
     initConfiguration:
       nodeRegistration:
         kubeletExtraArgs:
-          cloud-provider: external
-          provider-id: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
+        - name: cloud-provider
+          value: external
+        - name: provider-id
+          value: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
         name: $${COREOS_OPENSTACK_HOSTNAME}
     joinConfiguration:
       nodeRegistration:
         kubeletExtraArgs:
-          cloud-provider: external
-          provider-id: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
+        - name: cloud-provider
+          value: external
+        - name: provider-id
+          value: openstack:///$${COREOS_OPENSTACK_INSTANCE_UUID}
         name: $${COREOS_OPENSTACK_HOSTNAME}
     preKubeadmCommands:
     - export COREOS_OPENSTACK_HOSTNAME=$${COREOS_OPENSTACK_HOSTNAME%.*}
@@ -138,10 +143,11 @@ spec:
     - envsubst < /etc/kubeadm.yml > /etc/kubeadm.yml.tmp
     - mv /etc/kubeadm.yml.tmp /etc/kubeadm.yml
   machineTemplate:
-    infrastructureRef:
-      apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
-      kind: OpenStackMachineTemplate
-      name: ${CLUSTER_NAME}-control-plane
+    spec:
+      infrastructureRef:
+        apiGroup: infrastructure.cluster.x-k8s.io
+        kind: OpenStackMachineTemplate
+        name: ${CLUSTER_NAME}-control-plane
   replicas: ${CONTROL_PLANE_MACHINE_COUNT}
   version: ${KUBERNETES_VERSION}
 ---
diff --git a/templates/cluster-template-without-lb.yaml b/templates/cluster-template-without-lb.yaml
index d6bf52a0..5ff44c56 100644
--- a/templates/cluster-template-without-lb.yaml
+++ b/templates/cluster-template-without-lb.yaml
@@ -8,22 +8,23 @@ metadata:
     clusterctl.cluster.x-k8s.io/move: "true"
   name: ${CLUSTER_NAME}-cloud-config
 ---
-apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+apiVersion: bootstrap.cluster.x-k8s.io/v1beta2
 kind: KubeadmConfigTemplate
 metadata:
   name: ${CLUSTER_NAME}-md-0
 spec:
   template:
     spec:
-      files: []
       joinConfiguration:
         nodeRegistration:
           kubeletExtraArgs:
-            cloud-provider: external
-            provider-id: openstack:///'{{ instance_id }}'
+          - name: cloud-provider
+            value: external
+          - name: provider-id
+            value: openstack:///'{{ instance_id }}'
           name: '{{ local_hostname }}'
 ---
-apiVersion: cluster.x-k8s.io/v1beta1
+apiVersion: cluster.x-k8s.io/v1beta2
 kind: Cluster
 metadata:
   name: ${CLUSTER_NAME}
@@ -34,15 +35,15 @@ spec:
       - 192.168.0.0/16
     serviceDomain: cluster.local
   controlPlaneRef:
-    apiVersion: controlplane.cluster.x-k8s.io/v1beta1
+    apiGroup: controlplane.cluster.x-k8s.io
     kind: KubeadmControlPlane
     name: ${CLUSTER_NAME}-control-plane
   infrastructureRef:
-    apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+    apiGroup: infrastructure.cluster.x-k8s.io
     kind: OpenStackCluster
     name: ${CLUSTER_NAME}
 ---
-apiVersion: cluster.x-k8s.io/v1beta1
+apiVersion: cluster.x-k8s.io/v1beta2
 kind: MachineDeployment
 metadata:
   name: ${CLUSTER_NAME}-md-0
@@ -55,18 +56,18 @@ spec:
     spec:
       bootstrap:
         configRef:
-          apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+          apiGroup: bootstrap.cluster.x-k8s.io
           kind: KubeadmConfigTemplate
           name: ${CLUSTER_NAME}-md-0
       clusterName: ${CLUSTER_NAME}
       failureDomain: ${OPENSTACK_FAILURE_DOMAIN}
       infrastructureRef:
-        apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+        apiGroup: infrastructure.cluster.x-k8s.io
         kind: OpenStackMachineTemplate
         name: ${CLUSTER_NAME}-md-0
       version: ${KUBERNETES_VERSION}
 ---
-apiVersion: controlplane.cluster.x-k8s.io/v1beta1
+apiVersion: controlplane.cluster.x-k8s.io/v1beta2
 kind: KubeadmControlPlane
 metadata:
   name: ${CLUSTER_NAME}-control-plane
@@ -75,25 +76,30 @@ spec:
     clusterConfiguration:
       controllerManager:
         extraArgs:
-          cloud-provider: external
-    files: []
+        - name: cloud-provider
+          value: external
     initConfiguration:
       nodeRegistration:
         kubeletExtraArgs:
-          cloud-provider: external
-          provider-id: openstack:///'{{ instance_id }}'
+        - name: cloud-provider
+          value: external
+        - name: provider-id
+          value: openstack:///'{{ instance_id }}'
         name: '{{ local_hostname }}'
     joinConfiguration:
       nodeRegistration:
         kubeletExtraArgs:
-          cloud-provider: external
-          provider-id: openstack:///'{{ instance_id }}'
+        - name: cloud-provider
+          value: external
+        - name: provider-id
+          value: openstack:///'{{ instance_id }}'
         name: '{{ local_hostname }}'
   machineTemplate:
-    infrastructureRef:
-      apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
-      kind: OpenStackMachineTemplate
-      name: ${CLUSTER_NAME}-control-plane
+    spec:
+      infrastructureRef:
+        apiGroup: infrastructure.cluster.x-k8s.io
+        kind: OpenStackMachineTemplate
+        name: ${CLUSTER_NAME}-control-plane
   replicas: 1
   version: ${KUBERNETES_VERSION}
 ---
diff --git a/templates/cluster-template.yaml b/templates/cluster-template.yaml
index d419a7c3..fd3c11c8 100644
--- a/templates/cluster-template.yaml
+++ b/templates/cluster-template.yaml
@@ -8,22 +8,23 @@ metadata:
     clusterctl.cluster.x-k8s.io/move: "true"
   name: ${CLUSTER_NAME}-cloud-config
 ---
-apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+apiVersion: bootstrap.cluster.x-k8s.io/v1beta2
 kind: KubeadmConfigTemplate
 metadata:
   name: ${CLUSTER_NAME}-md-0
 spec:
   template:
     spec:
-      files: []
       joinConfiguration:
         nodeRegistration:
           kubeletExtraArgs:
-            cloud-provider: external
-            provider-id: openstack:///'{{ instance_id }}'
+          - name: cloud-provider
+            value: external
+          - name: provider-id
+            value: openstack:///'{{ instance_id }}'
           name: '{{ local_hostname }}'
 ---
-apiVersion: cluster.x-k8s.io/v1beta1
+apiVersion: cluster.x-k8s.io/v1beta2
 kind: Cluster
 metadata:
   name: ${CLUSTER_NAME}
@@ -34,15 +35,15 @@ spec:
       - 192.168.0.0/16
     serviceDomain: cluster.local
   controlPlaneRef:
-    apiVersion: controlplane.cluster.x-k8s.io/v1beta1
+    apiGroup: controlplane.cluster.x-k8s.io
     kind: KubeadmControlPlane
     name: ${CLUSTER_NAME}-control-plane
   infrastructureRef:
-    apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+    apiGroup: infrastructure.cluster.x-k8s.io
     kind: OpenStackCluster
     name: ${CLUSTER_NAME}
 ---
-apiVersion: cluster.x-k8s.io/v1beta1
+apiVersion: cluster.x-k8s.io/v1beta2
 kind: MachineDeployment
 metadata:
   name: ${CLUSTER_NAME}-md-0
@@ -55,18 +56,18 @@ spec:
     spec:
       bootstrap:
         configRef:
-          apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+          apiGroup: bootstrap.cluster.x-k8s.io
           kind: KubeadmConfigTemplate
           name: ${CLUSTER_NAME}-md-0
       clusterName: ${CLUSTER_NAME}
       failureDomain: ${OPENSTACK_FAILURE_DOMAIN}
       infrastructureRef:
-        apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+        apiGroup: infrastructure.cluster.x-k8s.io
         kind: OpenStackMachineTemplate
         name: ${CLUSTER_NAME}-md-0
       version: ${KUBERNETES_VERSION}
 ---
-apiVersion: controlplane.cluster.x-k8s.io/v1beta1
+apiVersion: controlplane.cluster.x-k8s.io/v1beta2
 kind: KubeadmControlPlane
 metadata:
   name: ${CLUSTER_NAME}-control-plane
@@ -75,25 +76,30 @@ spec:
     clusterConfiguration:
       controllerManager:
         extraArgs:
-          cloud-provider: external
-    files: []
+        - name: cloud-provider
+          value: external
     initConfiguration:
       nodeRegistration:
         kubeletExtraArgs:
-          cloud-provider: external
-          provider-id: openstack:///'{{ instance_id }}'
+        - name: cloud-provider
+          value: external
+        - name: provider-id
+          value: openstack:///'{{ instance_id }}'
         name: '{{ local_hostname }}'
     joinConfiguration:
       nodeRegistration:
         kubeletExtraArgs:
-          cloud-provider: external
-          provider-id: openstack:///'{{ instance_id }}'
+        - name: cloud-provider
+          value: external
+        - name: provider-id
+          value: openstack:///'{{ instance_id }}'
         name: '{{ local_hostname }}'
   machineTemplate:
-    infrastructureRef:
-      apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
-      kind: OpenStackMachineTemplate
-      name: ${CLUSTER_NAME}-control-plane
+    spec:
+      infrastructureRef:
+        apiGroup: infrastructure.cluster.x-k8s.io
+        kind: OpenStackMachineTemplate
+        name: ${CLUSTER_NAME}-control-plane
   replicas: ${CONTROL_PLANE_MACHINE_COUNT}
   version: ${KUBERNETES_VERSION}
 ---
diff --git a/templates/clusterclass-dev-test.yaml b/templates/clusterclass-dev-test.yaml
index 345eb80e..eff4f62a 100644
--- a/templates/clusterclass-dev-test.yaml
+++ b/templates/clusterclass-dev-test.yaml
@@ -1,37 +1,36 @@
-apiVersion: cluster.x-k8s.io/v1beta1
+apiVersion: cluster.x-k8s.io/v1beta2
 kind: ClusterClass
 metadata:
   name: dev-test
 spec:
   controlPlane:
-    ref:
+    templateRef:
       apiVersion: controlplane.cluster.x-k8s.io/v1beta1
       kind: KubeadmControlPlaneTemplate
       name: dev-test-control-plane
     machineInfrastructure:
-      ref:
+      templateRef:
         apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
         kind: OpenStackMachineTemplate
         name: dev-test-control-plane-machine
   infrastructure:
-    ref:
+    templateRef:
       apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
       kind: OpenStackClusterTemplate
       name: dev-test-openstackcluster
   workers:
     machineDeployments:
     - class: default-worker
-      template:
-        bootstrap:
-          ref:
-            apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
-            kind: KubeadmConfigTemplate
-            name: dev-test-default-worker-bootstraptemplate
-        infrastructure:
-          ref:
-            apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
-            kind: OpenStackMachineTemplate
-            name: dev-test-default-worker-machine
+      bootstrap:
+        templateRef:
+          apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+          kind: KubeadmConfigTemplate
+          name: dev-test-default-worker-bootstraptemplate
+      infrastructure:
+        templateRef:
+          apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+          kind: OpenStackMachineTemplate
+          name: dev-test-default-worker-machine
   variables:
   - name: identityRef
     required: false
@@ -73,6 +72,53 @@ spec:
           Use a sysext overlay to add the Kubernetes components to the image.
           This is for use with flatcar and similar images.
         default: false
+  - name: allowedCIDRs
+    required: false
+    schema:
+      openAPIV3Schema:
+        type: array
+        items:
+          type: string
+        description: |
+          List of CIDR ranges allowed to access the API server load balancer.
+          If not specified, all traffic will be allowed.
+        default: []
+  - name: bastion
+    required: false
+    schema:
+      openAPIV3Schema:
+        type: object
+        properties:
+          enabled:
+            type: boolean
+            description: "Enable or disable the bastion host"
+            default: false
+          spec:
+            type: object
+            description: "OpenStackMachineSpec for the bastion host"
+            properties:
+              flavor:
+                type: string
+                description: "OpenStack flavor for the bastion"
+              image:
+                type: object
+                properties:
+                  filter:
+                    type: object
+                    properties:
+                      name:
+                        type: string
+                        description: "Name of the image to use for bastion"
+              sshKeyName:
+                type: string
+                description: "SSH key pair name for bastion access"
+          floatingIP:
+            type: string
+            format: ipv4
+            description: "Floating IP address to assign to the bastion (optional)"
+          availabilityZone:
+            type: string
+            description: "Availability zone for the bastion host"
   patches:
   - name: image
     description: "Sets the OpenStack image that is used for creating the servers."
@@ -114,6 +160,34 @@ spec:
         path: /spec/template/spec/identityRef
         valueFrom:
           variable: identityRef
+  - name: allowedCIDRs
+    description: "Sets the allowed CIDRs for the API server load balancer."
+    enabledIf: "{{ if .allowedCIDRs }}{{ gt (len .allowedCIDRs) 0 }}{{ end }}"
+    definitions:
+    - selector:
+        apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+        kind: OpenStackClusterTemplate
+        matchResources:
+          infrastructureCluster: true
+      jsonPatches:
+      - op: add
+        path: /spec/template/spec/apiServerLoadBalancer/allowedCIDRs
+        valueFrom:
+          variable: allowedCIDRs
+  - name: bastion
+    description: "Sets the bastion host configuration."
+    enabledIf: "{{ if .bastion }}{{ .bastion.enabled }}{{ end }}"
+    definitions:
+    - selector:
+        apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+        kind: OpenStackClusterTemplate
+        matchResources:
+          infrastructureCluster: true
+      jsonPatches:
+      - op: add
+        path: /spec/template/spec/bastion
+        valueFrom:
+          variable: bastion
   - name: ignitionSysext
     description: "Add the necessary ignition configuration for kube components through sysext."
     enabledIf: "{{ .injectIgnitionSysext }}"
@@ -284,7 +358,7 @@ spec:
                               [Service]
                               EnvironmentFile=/run/metadata/flatcar
 ---
-apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+apiVersion: bootstrap.cluster.x-k8s.io/v1beta2
 kind: KubeadmConfigTemplate
 metadata:
   name: dev-test-default-worker-bootstraptemplate
@@ -295,11 +369,13 @@ spec:
       joinConfiguration:
         nodeRegistration:
           kubeletExtraArgs:
-            cloud-provider: external
-            provider-id: "openstack:///{{ v1.instance_id }}"
+          - name: cloud-provider
+            value: external
+          - name: provider-id
+            value: "openstack:///{{ v1.instance_id }}"
           name: "{{ v1.local_hostname }}"
 ---
-apiVersion: controlplane.cluster.x-k8s.io/v1beta1
+apiVersion: controlplane.cluster.x-k8s.io/v1beta2
 kind: KubeadmControlPlaneTemplate
 metadata:
   name: dev-test-control-plane
@@ -310,18 +386,23 @@ spec:
         clusterConfiguration:
           controllerManager:
             extraArgs:
-              cloud-provider: external
+            - name: cloud-provider
+              value: external
         initConfiguration:
           nodeRegistration:
             kubeletExtraArgs:
-              cloud-provider: external
-              provider-id: "openstack:///{{ v1.instance_id }}"
+            - name: cloud-provider
+              value: external
+            - name: provider-id
+              value: "openstack:///{{ v1.instance_id }}"
             name: "{{ v1.local_hostname }}"
         joinConfiguration:
           nodeRegistration:
             kubeletExtraArgs:
-              cloud-provider: external
-              provider-id: "openstack:///{{ v1.instance_id }}"
+            - name: cloud-provider
+              value: external
+            - name: provider-id
+              value: "openstack:///{{ v1.instance_id }}"
             name: "{{ v1.local_hostname }}"
 ---
 apiVersion: infrastructure.cluster.x-k8s.io/v1beta1



Test patch:
diff --git a/test/e2e/data/kustomize/common-patches/ccm/ccm.yaml b/test/e2e/data/kustomize/capi-v1beta1/ccm.yaml
similarity index 98%
rename from test/e2e/data/kustomize/common-patches/ccm/ccm.yaml
rename to test/e2e/data/kustomize/capi-v1beta1/ccm.yaml
index a06a2804..9a0d6276 100644
--- a/test/e2e/data/kustomize/common-patches/ccm/ccm.yaml
+++ b/test/e2e/data/kustomize/capi-v1beta1/ccm.yaml
@@ -1,4 +1,3 @@
----
 apiVersion: v1
 kind: ConfigMap
 metadata:
diff --git a/test/e2e/data/kustomize/common-patches/cni/cni.yaml b/test/e2e/data/kustomize/capi-v1beta1/cni.yaml
similarity index 98%
rename from test/e2e/data/kustomize/common-patches/cni/cni.yaml
rename to test/e2e/data/kustomize/capi-v1beta1/cni.yaml
index bd93c022..6ad6df4e 100644
--- a/test/e2e/data/kustomize/common-patches/cni/cni.yaml
+++ b/test/e2e/data/kustomize/capi-v1beta1/cni.yaml
@@ -1,4 +1,3 @@
----
 apiVersion: v1
 kind: ConfigMap
 metadata:
diff --git a/test/e2e/data/kustomize/capi-v1beta1/kustomization.yaml b/test/e2e/data/kustomize/capi-v1beta1/kustomization.yaml
new file mode 100644
index 00000000..a371f58c
--- /dev/null
+++ b/test/e2e/data/kustomize/capi-v1beta1/kustomization.yaml
@@ -0,0 +1,18 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+
+resources:
+- ../../../../../kustomize/v1beta1/capi-v1beta1
+- ccm.yaml
+- cni.yaml
+# We cannot use the common cluster-resource-sets here since they are v1beta2.
+components:
+- ../components/common
+
+patches:
+- target:
+    kind: KubeadmControlPlane
+    name: \${CLUSTER_NAME}-control-plane
+  path: patch-ccm-cloud-config.yaml
+- path: patch-ccm.yaml
+- path: patch-cni.yaml
diff --git a/test/e2e/data/kustomize/common-patches/ccm/patch-ccm-cloud-config.yaml b/test/e2e/data/kustomize/capi-v1beta1/patch-ccm-cloud-config.yaml
similarity index 86%
rename from test/e2e/data/kustomize/common-patches/ccm/patch-ccm-cloud-config.yaml
rename to test/e2e/data/kustomize/capi-v1beta1/patch-ccm-cloud-config.yaml
index fb537ce4..b88d9eda 100644
--- a/test/e2e/data/kustomize/common-patches/ccm/patch-ccm-cloud-config.yaml
+++ b/test/e2e/data/kustomize/capi-v1beta1/patch-ccm-cloud-config.yaml
@@ -1,3 +1,6 @@
+- op: add
+  path: /spec/kubeadmConfigSpec/files
+  value: []
 - op: add
   path: /spec/kubeadmConfigSpec/files/-
   value:
diff --git a/test/e2e/data/kustomize/common-patches/ccm/patch-ccm.yaml b/test/e2e/data/kustomize/capi-v1beta1/patch-ccm.yaml
similarity index 96%
rename from test/e2e/data/kustomize/common-patches/ccm/patch-ccm.yaml
rename to test/e2e/data/kustomize/capi-v1beta1/patch-ccm.yaml
index 9d355659..345f3606 100644
--- a/test/e2e/data/kustomize/common-patches/ccm/patch-ccm.yaml
+++ b/test/e2e/data/kustomize/capi-v1beta1/patch-ccm.yaml
@@ -1,4 +1,3 @@
----
 apiVersion: cluster.x-k8s.io/v1beta1
 kind: Cluster
 metadata:
diff --git a/test/e2e/data/kustomize/common-patches/cni/patch-cni.yaml b/test/e2e/data/kustomize/capi-v1beta1/patch-cni.yaml
similarity index 96%
rename from test/e2e/data/kustomize/common-patches/cni/patch-cni.yaml
rename to test/e2e/data/kustomize/capi-v1beta1/patch-cni.yaml
index a92fe96d..f73463f3 100644
--- a/test/e2e/data/kustomize/common-patches/cni/patch-cni.yaml
+++ b/test/e2e/data/kustomize/capi-v1beta1/patch-cni.yaml
@@ -1,4 +1,3 @@
----
 apiVersion: cluster.x-k8s.io/v1beta1
 kind: Cluster
 metadata:
diff --git a/test/e2e/data/kustomize/common-patches/cluster-prev1beta1/kustomization.yaml b/test/e2e/data/kustomize/common-patches/cluster-prev1beta1/kustomization.yaml
deleted file mode 100644
index 7136e960..00000000
--- a/test/e2e/data/kustomize/common-patches/cluster-prev1beta1/kustomization.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-# Modifications to release templates common to all e2e test scenarios
----
-apiVersion: kustomize.config.k8s.io/v1alpha1
-kind: Component
-
-patches:
-- target:
-    kind: OpenStackCluster
-    name: \${CLUSTER_NAME}
-  path: patch-cluster.yaml
diff --git a/test/e2e/data/kustomize/common-patches/cluster-prev1beta1/patch-cluster.yaml b/test/e2e/data/kustomize/common-patches/cluster-prev1beta1/patch-cluster.yaml
deleted file mode 100644
index 23bc8f4f..00000000
--- a/test/e2e/data/kustomize/common-patches/cluster-prev1beta1/patch-cluster.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
----
-- op: add
-  path: /spec/bastion
-  value:
-    enabled: true
-    spec:
-      flavor: ${OPENSTACK_BASTION_MACHINE_FLAVOR}
-      image:
-        name: ${OPENSTACK_BASTION_IMAGE_NAME}
-      sshKeyName: ${OPENSTACK_SSH_KEY_NAME}
-- op: add
-  path: /spec/controlPlaneAvailabilityZones
-  value:
-  - ${OPENSTACK_FAILURE_DOMAIN}
diff --git a/test/e2e/data/kustomize/common-patches/cluster/kustomization.yaml b/test/e2e/data/kustomize/common-patches/cluster/kustomization.yaml
deleted file mode 100644
index 7136e960..00000000
--- a/test/e2e/data/kustomize/common-patches/cluster/kustomization.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-# Modifications to release templates common to all e2e test scenarios
----
-apiVersion: kustomize.config.k8s.io/v1alpha1
-kind: Component
-
-patches:
-- target:
-    kind: OpenStackCluster
-    name: \${CLUSTER_NAME}
-  path: patch-cluster.yaml
diff --git a/test/e2e/data/kustomize/common-patches/cni/kustomization.yaml b/test/e2e/data/kustomize/common-patches/cni/kustomization.yaml
deleted file mode 100644
index 0f10084b..00000000
--- a/test/e2e/data/kustomize/common-patches/cni/kustomization.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-# Modifications to release templates common to all e2e test scenarios
----
-apiVersion: kustomize.config.k8s.io/v1alpha1
-kind: Component
-
-resources:
-- cni.yaml
-
-patches:
-- path: patch-cni.yaml
diff --git a/test/e2e/data/kustomize/common-patches/externalNetworkByName/kustomization.yaml b/test/e2e/data/kustomize/common-patches/externalNetworkByName/kustomization.yaml
deleted file mode 100644
index 6c89e0eb..00000000
--- a/test/e2e/data/kustomize/common-patches/externalNetworkByName/kustomization.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
----
-apiVersion: kustomize.config.k8s.io/v1alpha1
-kind: Component
-
-patches:
-- patch: |-
-    - op: "remove"
-      path: "/spec/externalNetwork"
-    - op: "add"
-      path: "/spec/externalNetwork"
-      value:
-        filter:
-          name: "${OPENSTACK_EXTERNAL_NETWORK_NAME}"
-  target:
-    kind: OpenStackCluster
diff --git a/test/e2e/data/kustomize/common-patches/images-without-ref/kustomization.yaml b/test/e2e/data/kustomize/common-patches/images-without-ref/kustomization.yaml
deleted file mode 100644
index 32e862b8..00000000
--- a/test/e2e/data/kustomize/common-patches/images-without-ref/kustomization.yaml
+++ /dev/null
@@ -1,28 +0,0 @@
----
-apiVersion: kustomize.config.k8s.io/v1alpha1
-kind: Component
-
-resources:
-- images.yaml
-
-patches:
-- target:
-    group: infrastructure.cluster.x-k8s.io
-    version: v1beta1
-    kind: OpenStackMachineTemplate
-  patch: |-
-    - op: replace
-      path: /spec/template/spec/image
-      value:
-        filter:
-          name: ${OPENSTACK_IMAGE_NAME}
-- target:
-    group: infrastructure.cluster.x-k8s.io
-    version: v1beta1
-    kind: OpenStackCluster
-  patch: |-
-    - op: replace
-      path: /spec/bastion/spec/image
-      value:
-        filter:
-          name: ${OPENSTACK_BASTION_IMAGE_NAME}
diff --git a/test/e2e/data/kustomize/common-patches/images/images.yaml b/test/e2e/data/kustomize/common-patches/images/images.yaml
deleted file mode 100644
index 9fc007bd..00000000
--- a/test/e2e/data/kustomize/common-patches/images/images.yaml
+++ /dev/null
@@ -1,44 +0,0 @@
-apiVersion: openstack.k-orc.cloud/v1alpha1
-kind: Image
-metadata:
-  name: node-image
-  labels:
-    clusterctl.cluster.x-k8s.io/move: ""
-spec:
-  resource:
-    name: ${OPENSTACK_IMAGE_NAME}
-    content:
-      containerFormat: bare
-      diskFormat: qcow2
-      download:
-        url: ${OPENSTACK_IMAGE_URL}
-  managementPolicy: managed
-  managedOptions:
-    onDelete: detach
-  cloudCredentialsRef:
-    secretName: ${CLUSTER_NAME}-cloud-config
-    cloudName: ${OPENSTACK_CLOUD}
----
-apiVersion: openstack.k-orc.cloud/v1alpha1
-kind: Image
-metadata:
-  name: bastion-image
-  labels:
-    clusterctl.cluster.x-k8s.io/move: ""
-spec:
-  resource:
-    name: ${OPENSTACK_BASTION_IMAGE_NAME}
-    content:
-      containerFormat: bare
-      diskFormat: qcow2
-      download:
-        url: ${OPENSTACK_BASTION_IMAGE_URL}
-        hash:
-          algorithm: ${OPENSTACK_BASTION_IMAGE_HASH_ALGORITHM}
-          value: ${OPENSTACK_BASTION_IMAGE_HASH}
-  managementPolicy: managed
-  managedOptions:
-    onDelete: detach
-  cloudCredentialsRef:
-    secretName: ${CLUSTER_NAME}-cloud-config
-    cloudName: ${OPENSTACK_CLOUD}
diff --git a/test/e2e/data/kustomize/components/cluster-resource-sets/ccm.yaml b/test/e2e/data/kustomize/components/cluster-resource-sets/ccm.yaml
new file mode 100644
index 00000000..f567cb5a
--- /dev/null
+++ b/test/e2e/data/kustomize/components/cluster-resource-sets/ccm.yaml
@@ -0,0 +1,18 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: ccm-${CLUSTER_NAME}-crs-1
+data: ${CCM_RESOURCES}
+---
+apiVersion: addons.cluster.x-k8s.io/v1beta2
+kind: ClusterResourceSet
+metadata:
+  name: ${CLUSTER_NAME}-crs-1
+spec:
+  clusterSelector:
+    matchLabels:
+      ccm: ${CLUSTER_NAME}-crs-1
+  resources:
+  - kind: ConfigMap
+    name: ccm-${CLUSTER_NAME}-crs-1
+  strategy: ApplyOnce
diff --git a/test/e2e/data/kustomize/components/cluster-resource-sets/cni.yaml b/test/e2e/data/kustomize/components/cluster-resource-sets/cni.yaml
new file mode 100644
index 00000000..fc04e816
--- /dev/null
+++ b/test/e2e/data/kustomize/components/cluster-resource-sets/cni.yaml
@@ -0,0 +1,18 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: cni-${CLUSTER_NAME}-crs-0
+data: ${CNI_RESOURCES}
+---
+apiVersion: addons.cluster.x-k8s.io/v1beta2
+kind: ClusterResourceSet
+metadata:
+  name: ${CLUSTER_NAME}-crs-0
+spec:
+  clusterSelector:
+    matchLabels:
+      cni: ${CLUSTER_NAME}-crs-0
+  resources:
+  - kind: ConfigMap
+    name: cni-${CLUSTER_NAME}-crs-0
+  strategy: ApplyOnce
diff --git a/test/e2e/data/kustomize/common-patches/ccm/kustomization.yaml b/test/e2e/data/kustomize/components/cluster-resource-sets/kustomization.yaml
similarity index 75%
rename from test/e2e/data/kustomize/common-patches/ccm/kustomization.yaml
rename to test/e2e/data/kustomize/components/cluster-resource-sets/kustomization.yaml
index ee38329d..0c716409 100644
--- a/test/e2e/data/kustomize/common-patches/ccm/kustomization.yaml
+++ b/test/e2e/data/kustomize/components/cluster-resource-sets/kustomization.yaml
@@ -1,10 +1,8 @@
-# Modifications to release templates common to all e2e test scenarios
----
 apiVersion: kustomize.config.k8s.io/v1alpha1
 kind: Component
-
 resources:
 - ccm.yaml
+- cni.yaml
 
 patches:
 - target:
@@ -12,3 +10,4 @@ patches:
     name: \${CLUSTER_NAME}-control-plane
   path: patch-ccm-cloud-config.yaml
 - path: patch-ccm.yaml
+- path: patch-cni.yaml
diff --git a/test/e2e/data/kustomize/components/cluster-resource-sets/patch-ccm-cloud-config.yaml b/test/e2e/data/kustomize/components/cluster-resource-sets/patch-ccm-cloud-config.yaml
new file mode 100644
index 00000000..b88d9eda
--- /dev/null
+++ b/test/e2e/data/kustomize/components/cluster-resource-sets/patch-ccm-cloud-config.yaml
@@ -0,0 +1,19 @@
+- op: add
+  path: /spec/kubeadmConfigSpec/files
+  value: []
+- op: add
+  path: /spec/kubeadmConfigSpec/files/-
+  value:
+    content: ${OPENSTACK_CLOUD_PROVIDER_CONF_B64}
+    encoding: base64
+    owner: root
+    path: /etc/kubernetes/cloud.conf
+    permissions: "0600"
+- op: add
+  path: /spec/kubeadmConfigSpec/files/-
+  value:
+    content: ${OPENSTACK_CLOUD_CACERT_B64}
+    encoding: base64
+    owner: root
+    path: /etc/certs/cacert
+    permissions: "0600"
diff --git a/test/e2e/data/kustomize/components/cluster-resource-sets/patch-ccm.yaml b/test/e2e/data/kustomize/components/cluster-resource-sets/patch-ccm.yaml
new file mode 100644
index 00000000..bcbb2683
--- /dev/null
+++ b/test/e2e/data/kustomize/components/cluster-resource-sets/patch-ccm.yaml
@@ -0,0 +1,6 @@
+apiVersion: cluster.x-k8s.io/v1beta2
+kind: Cluster
+metadata:
+  name: ${CLUSTER_NAME}
+  labels:
+    ccm: ${CLUSTER_NAME}-crs-1
diff --git a/test/e2e/data/kustomize/components/cluster-resource-sets/patch-cni.yaml b/test/e2e/data/kustomize/components/cluster-resource-sets/patch-cni.yaml
new file mode 100644
index 00000000..2bca6f48
--- /dev/null
+++ b/test/e2e/data/kustomize/components/cluster-resource-sets/patch-cni.yaml
@@ -0,0 +1,6 @@
+apiVersion: cluster.x-k8s.io/v1beta2
+kind: Cluster
+metadata:
+  name: ${CLUSTER_NAME}
+  labels:
+    cni: ${CLUSTER_NAME}-crs-0
diff --git a/test/e2e/data/kustomize/common-patches/images-without-ref/images.yaml b/test/e2e/data/kustomize/components/common/images.yaml
similarity index 100%
rename from test/e2e/data/kustomize/common-patches/images-without-ref/images.yaml
rename to test/e2e/data/kustomize/components/common/images.yaml
diff --git a/test/e2e/data/kustomize/common-patches/images/kustomization.yaml b/test/e2e/data/kustomize/components/common/kustomization.yaml
similarity index 56%
rename from test/e2e/data/kustomize/common-patches/images/kustomization.yaml
rename to test/e2e/data/kustomize/components/common/kustomization.yaml
index 6e238392..211dc0e4 100644
--- a/test/e2e/data/kustomize/common-patches/images/kustomization.yaml
+++ b/test/e2e/data/kustomize/components/common/kustomization.yaml
@@ -1,11 +1,25 @@
----
 apiVersion: kustomize.config.k8s.io/v1alpha1
 kind: Component
-
 resources:
 - images.yaml
-
 patches:
+# Set AZ and enable bastion
+- target:
+    kind: OpenStackCluster
+    name: \${CLUSTER_NAME}
+  path: patch-cluster.yaml
+# Use network name instead of ID for external network
+- patch: |-
+    - op: "remove"
+      path: "/spec/externalNetwork"
+    - op: "add"
+      path: "/spec/externalNetwork"
+      value:
+        filter:
+          name: "${OPENSTACK_EXTERNAL_NETWORK_NAME}"
+  target:
+    kind: OpenStackCluster
+# Use ORC image references
 - target:
     group: infrastructure.cluster.x-k8s.io
     version: v1beta1
diff --git a/test/e2e/data/kustomize/common-patches/cluster/patch-cluster.yaml b/test/e2e/data/kustomize/components/common/patch-cluster.yaml
similarity index 100%
rename from test/e2e/data/kustomize/common-patches/cluster/patch-cluster.yaml
rename to test/e2e/data/kustomize/components/common/patch-cluster.yaml
diff --git a/test/e2e/data/kustomize/upgrade-patches/ci-hack-kcp.yaml b/test/e2e/data/kustomize/components/upgrade-patches/ci-hack-kcp.yaml
similarity index 99%
rename from test/e2e/data/kustomize/upgrade-patches/ci-hack-kcp.yaml
rename to test/e2e/data/kustomize/components/upgrade-patches/ci-hack-kcp.yaml
index 45fe9a39..d685dc33 100644
--- a/test/e2e/data/kustomize/upgrade-patches/ci-hack-kcp.yaml
+++ b/test/e2e/data/kustomize/components/upgrade-patches/ci-hack-kcp.yaml
@@ -1,4 +1,3 @@
----
 # Workaround for https://github.com/kubernetes-sigs/cluster-api/issues/7457
 # There is a small but important difference between these two:
 # path: /a/b/c
diff --git a/test/e2e/data/kustomize/upgrade-patches/ci-hack-kct.yaml b/test/e2e/data/kustomize/components/upgrade-patches/ci-hack-kct.yaml
similarity index 95%
rename from test/e2e/data/kustomize/upgrade-patches/ci-hack-kct.yaml
rename to test/e2e/data/kustomize/components/upgrade-patches/ci-hack-kct.yaml
index 2429b30a..e7d5690d 100644
--- a/test/e2e/data/kustomize/upgrade-patches/ci-hack-kct.yaml
+++ b/test/e2e/data/kustomize/components/upgrade-patches/ci-hack-kct.yaml
@@ -1,4 +1,3 @@
----
 # Workaround for https://github.com/kubernetes-sigs/cluster-api/issues/7457
 # There is a small but important difference between these two:
 # path: /a/b/c
@@ -12,6 +11,9 @@
   path: /spec/template/spec/postKubeadmCommands
   value:
   - /usr/local/bin/ci-artifacts-openstack.sh
+- op: add
+  path: /spec/template/spec/files
+  value: []
 - op: add
   path: /spec/template/spec/files/-
   value:
diff --git a/test/e2e/data/kustomize/upgrade-patches/kustomization.yaml b/test/e2e/data/kustomize/components/upgrade-patches/kustomization.yaml
similarity index 100%
rename from test/e2e/data/kustomize/upgrade-patches/kustomization.yaml
rename to test/e2e/data/kustomize/components/upgrade-patches/kustomization.yaml
diff --git a/test/e2e/data/kustomize/default/kustomization.yaml b/test/e2e/data/kustomize/default/kustomization.yaml
index 059fb0e7..f2d25440 100644
--- a/test/e2e/data/kustomize/default/kustomization.yaml
+++ b/test/e2e/data/kustomize/default/kustomization.yaml
@@ -5,9 +5,6 @@ resources:
 - ../../../../../kustomize/v1beta1/default
 
 components:
-- ../common-patches/cluster
-- ../common-patches/cni
-- ../upgrade-patches
-- ../common-patches/ccm
-- ../common-patches/externalNetworkByName
-- ../common-patches/images
+- ../components/cluster-resource-sets
+- ../components/common
+- ../components/upgrade-patches
diff --git a/test/e2e/data/kustomize/flatcar-sysext/kustomization.yaml b/test/e2e/data/kustomize/flatcar-sysext/kustomization.yaml
index 3550c5f5..86043c42 100644
--- a/test/e2e/data/kustomize/flatcar-sysext/kustomization.yaml
+++ b/test/e2e/data/kustomize/flatcar-sysext/kustomization.yaml
@@ -2,11 +2,8 @@ resources:
 - ../../../../../kustomize/v1beta1/flatcar-sysext
 
 components:
-- ../common-patches/cluster
-- ../common-patches/cni
-- ../common-patches/ccm
-- ../common-patches/externalNetworkByName
-- ../common-patches/images
+- ../components/cluster-resource-sets
+- ../components/common
 
 patches:
 - target:
diff --git a/test/e2e/data/kustomize/flatcar/kustomization.yaml b/test/e2e/data/kustomize/flatcar/kustomization.yaml
index 674f00cc..f7288946 100644
--- a/test/e2e/data/kustomize/flatcar/kustomization.yaml
+++ b/test/e2e/data/kustomize/flatcar/kustomization.yaml
@@ -5,11 +5,8 @@ resources:
 - ../../../../../kustomize/v1beta1/flatcar
 
 components:
-- ../common-patches/cluster
-- ../common-patches/cni
-- ../common-patches/ccm
-- ../common-patches/externalNetworkByName
-- ../common-patches/images
+- ../components/cluster-resource-sets
+- ../components/common
 
 patches:
 - target:
diff --git a/test/e2e/data/kustomize/health-monitor/kustomization.yaml b/test/e2e/data/kustomize/health-monitor/kustomization.yaml
index d4e15eaf..1865ffca 100644
--- a/test/e2e/data/kustomize/health-monitor/kustomization.yaml
+++ b/test/e2e/data/kustomize/health-monitor/kustomization.yaml
@@ -4,5 +4,5 @@ kind: Kustomization
 resources:
 - ../default
 
-patchesStrategicMerge:
-- patch-cluster-health-monitor.yaml
\ No newline at end of file
+patches:
+- path: patch-cluster-health-monitor.yaml
diff --git a/test/e2e/data/kustomize/k8s-upgrade/kcp-patch.yaml b/test/e2e/data/kustomize/k8s-upgrade/kcp-patch.yaml
index 859ff0c6..eb1c31a4 100644
--- a/test/e2e/data/kustomize/k8s-upgrade/kcp-patch.yaml
+++ b/test/e2e/data/kustomize/k8s-upgrade/kcp-patch.yaml
@@ -1,10 +1,11 @@
-apiVersion: controlplane.cluster.x-k8s.io/v1beta1
+apiVersion: controlplane.cluster.x-k8s.io/v1beta2
 kind: KubeadmControlPlane
 metadata:
   name: ${CLUSTER_NAME}-control-plane
 spec:
   machineTemplate:
-    infrastructureRef:
-      apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
-      kind: OpenStackMachineTemplate
-      name: ${CLUSTER_NAME}-upgrade-from-control-plane
+    spec:
+      infrastructureRef:
+        apiGroup: infrastructure.cluster.x-k8s.io
+        kind: OpenStackMachineTemplate
+        name: ${CLUSTER_NAME}-upgrade-from-control-plane
diff --git a/test/e2e/data/kustomize/k8s-upgrade/md-patch.yaml b/test/e2e/data/kustomize/k8s-upgrade/md-patch.yaml
index a79427f8..d0bb966a 100644
--- a/test/e2e/data/kustomize/k8s-upgrade/md-patch.yaml
+++ b/test/e2e/data/kustomize/k8s-upgrade/md-patch.yaml
@@ -1,4 +1,4 @@
-apiVersion: cluster.x-k8s.io/v1beta1
+apiVersion: cluster.x-k8s.io/v1beta2
 kind: MachineDeployment
 metadata:
   name: ${CLUSTER_NAME}-md-0
@@ -6,6 +6,6 @@ spec:
   template:
     spec:
       infrastructureRef:
-        apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+        apiGroup: infrastructure.cluster.x-k8s.io
         kind: OpenStackMachineTemplate
         name: ${CLUSTER_NAME}-upgrade-from-md-0
diff --git a/test/e2e/data/kustomize/kcp-remediation/mhc.yaml b/test/e2e/data/kustomize/kcp-remediation/mhc.yaml
index 3ed3e0a9..4f8f343c 100644
--- a/test/e2e/data/kustomize/kcp-remediation/mhc.yaml
+++ b/test/e2e/data/kustomize/kcp-remediation/mhc.yaml
@@ -1,18 +1,20 @@
----
 # MachineHealthCheck object with
 # - a selector that targets all the machines with label cluster.x-k8s.io/control-plane=""
-# - unhealthyConditions triggering remediation after 10s the condition is set
-apiVersion: cluster.x-k8s.io/v1beta1
+# - unhealthyNodeConditions triggering remediation after 10s the condition is set
+apiVersion: cluster.x-k8s.io/v1beta2
 kind: MachineHealthCheck
 metadata:
   name: "${CLUSTER_NAME}-mhc-0"
 spec:
   clusterName: "${CLUSTER_NAME}"
-  maxUnhealthy: 100%
+  remediation:
+    triggerIf:
+      unhealthyLessThanOrEqualTo: 100%
   selector:
     matchLabels:
       cluster.x-k8s.io/control-plane: ""
-  unhealthyConditions:
+  checks:
+    unhealthyNodeConditions:
     - type: e2e.remediation.condition
       status: "False"
-      timeout: 10s
+      timeoutSeconds: 10
diff --git a/test/e2e/data/kustomize/md-remediation/md.yaml b/test/e2e/data/kustomize/md-remediation/md.yaml
index 13968556..d317bf32 100644
--- a/test/e2e/data/kustomize/md-remediation/md.yaml
+++ b/test/e2e/data/kustomize/md-remediation/md.yaml
@@ -1,4 +1,4 @@
-apiVersion: cluster.x-k8s.io/v1beta1
+apiVersion: cluster.x-k8s.io/v1beta2
 kind: MachineDeployment
 metadata:
   name: "${CLUSTER_NAME}-md-0"
diff --git a/test/e2e/data/kustomize/md-remediation/mhc.yaml b/test/e2e/data/kustomize/md-remediation/mhc.yaml
index c1072259..cb344948 100644
--- a/test/e2e/data/kustomize/md-remediation/mhc.yaml
+++ b/test/e2e/data/kustomize/md-remediation/mhc.yaml
@@ -1,18 +1,20 @@
----
 # MachineHealthCheck object with
 # - a selector that targets all the machines with label e2e.remediation.label=""
-# - unhealthyConditions triggering remediation after 10s the condition is set
-apiVersion: cluster.x-k8s.io/v1beta1
+# - unhealthyNodeConditions triggering remediation after 10s the condition is set
+apiVersion: cluster.x-k8s.io/v1beta2
 kind: MachineHealthCheck
 metadata:
   name: "${CLUSTER_NAME}-mhc-0"
 spec:
   clusterName: "${CLUSTER_NAME}"
-  maxUnhealthy: 100%
+  remediation:
+    triggerIf:
+      unhealthyLessThanOrEqualTo: 100%
   selector:
     matchLabels:
       e2e.remediation.label: ""
-  unhealthyConditions:
+  checks:
+    unhealthyNodeConditions:
     - type: e2e.remediation.condition
       status: "False"
-      timeout: 10s
+      timeoutSeconds: 10
diff --git a/test/e2e/data/kustomize/without-lb/kustomization.yaml b/test/e2e/data/kustomize/without-lb/kustomization.yaml
index 7582ad3f..a42e9b8d 100644
--- a/test/e2e/data/kustomize/without-lb/kustomization.yaml
+++ b/test/e2e/data/kustomize/without-lb/kustomization.yaml
@@ -4,8 +4,5 @@ resources:
 - ../../../../../kustomize/v1beta1/without-lb
 
 components:
-- ../common-patches/cluster
-- ../common-patches/cni
-- ../common-patches/ccm
-- ../common-patches/externalNetworkByName
-- ../common-patches/images
+- ../components/cluster-resource-sets
+- ../components/common
diff --git a/test/e2e/shared/defaults.go b/test/e2e/shared/defaults.go
index f5d2c868..ab57acd1 100644
--- a/test/e2e/shared/defaults.go
+++ b/test/e2e/shared/defaults.go
@@ -62,6 +62,7 @@ const (
 	FlavorKubernetesUpgrade    = "k8s-upgrade"
 	FlavorFlatcarSysext        = "flatcar-sysext"
 	FlavorHealthMonitor        = "health-monitor"
+	FlavorCapiV1Beta1          = "capi-v1beta1"
 )
 
 // DefaultScheme returns the default scheme to use for testing.
diff --git a/test/e2e/suites/e2e/clusterctl_upgrade_test.go b/test/e2e/suites/e2e/clusterctl_upgrade_test.go
index 6e1b0ba7..a7b94c9d 100644
--- a/test/e2e/suites/e2e/clusterctl_upgrade_test.go
+++ b/test/e2e/suites/e2e/clusterctl_upgrade_test.go
@@ -63,7 +63,7 @@ var _ = Describe("When testing clusterctl upgrades for CAPO (v0.11=>current) and
 			InitWithBootstrapProviders:        []string{"kubeadm:" + capiRelease110},
 			InitWithControlPlaneProviders:     []string{"kubeadm:" + capiRelease110},
 			MgmtFlavor:                        shared.FlavorDefault,
-			WorkloadFlavor:                    shared.FlavorDefault,
+			WorkloadFlavor:                    shared.FlavorCapiV1Beta1,
 			InitWithKubernetesVersion:         e2eCtx.E2EConfig.MustGetVariable(shared.KubernetesVersion),
 			InitWithRuntimeExtensionProviders: []string{"openstack-resource-controller:v1.0.2"},
 		}
@@ -97,7 +97,7 @@ var _ = Describe("When testing clusterctl upgrades for CAPO (v0.12=>current) and
 			InitWithBootstrapProviders:        []string{"kubeadm:" + capiRelease110},
 			InitWithControlPlaneProviders:     []string{"kubeadm:" + capiRelease110},
 			MgmtFlavor:                        shared.FlavorDefault,
-			WorkloadFlavor:                    shared.FlavorDefault,
+			WorkloadFlavor:                    shared.FlavorCapiV1Beta1,
 			InitWithKubernetesVersion:         e2eCtx.E2EConfig.MustGetVariable(shared.KubernetesVersion),
 			InitWithRuntimeExtensionProviders: []string{"openstack-resource-controller:v1.0.2"},
 		}