Repository: kubernetes-sigs/cluster-api-provider-azure
Pull request #1401 (2021-05-31 14:59:17)

Save subnet details back to scope
 <!-- If this is your first PR, welcome! Please make sure you read the [contributing guidelines](../CONTRIBUTING.md). -->

 <!-- Please label this pull request according to what type of issue you are addressing (see ../CONTRIBUTING.md) -->
**What type of PR is this?**

<!--
Add one of the following kinds:
/kind feature
/kind bug
/kind api-change
/kind cleanup
/kind deprecation
/kind design
/kind documentation
/kind failing-test
/kind flake
/kind other
-->
/kind bug

**What this PR does / why we need it**:

When `SubnetSpec` was changed to not be a pointer in #1144 we were not saving the Subnet details in the scope.


**TODOs**:
<!-- Put an "X" character inside the brackets of each completed task. Some may be optional depending on the PR. -->

- [X] squashed commits
- [ ] includes documentation
- [X] adds unit tests

**Release note**:
<!--  Write your release note:
1. Enter your extended release note in the below block. If the PR requires additional action from users switching to the new release, include the string "action required".
2. If no release note is required, just write "NONE".
-->
```release-note
Save subnet details back to scope when reconciling already existing subnets
```


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

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

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

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

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

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["neolit123"]} -->
k8s-ci-robot: Hi @fiunchinho. 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-azure).

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

nader-ziada: /ok-to-test
k8s-ci-robot: @fiunchinho: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-provider-azure-apidiff | 290e7573409c04496a676468271376c18de45bba | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-azure/1401/pull-cluster-api-provider-azure-apidiff/1399438516437389312) | `/test pull-cluster-api-provider-azure-apidiff`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-azure&pr=1401). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Afiunchinho). 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-azure/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 -->
k8s-ci-robot: @fiunchinho: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-provider-azure-apidiff | 290e7573409c04496a676468271376c18de45bba | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-azure/1401/pull-cluster-api-provider-azure-apidiff/1399438516437389312) | `/test pull-cluster-api-provider-azure-apidiff`
pull-cluster-api-provider-azure-test | 290e7573409c04496a676468271376c18de45bba | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-azure/1401/pull-cluster-api-provider-azure-test/1399438516282200064) | `/test pull-cluster-api-provider-azure-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-azure&pr=1401). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Afiunchinho). 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-azure/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 -->
nader-ziada: the unit tests are failing on the lint because `routetables_test.go` is not `gofmt`-ed with `-s` (gofmt)
k8s-ci-robot: @fiunchinho: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-provider-azure-test | 290e7573409c04496a676468271376c18de45bba | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-azure/1401/pull-cluster-api-provider-azure-test/1399438516282200064) | `/test pull-cluster-api-provider-azure-test`
pull-cluster-api-provider-azure-apidiff | 796639ae0c353a4f6af2b0bc7591e9b42b2585d4 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-azure/1401/pull-cluster-api-provider-azure-apidiff/1399638089382498304) | `/test pull-cluster-api-provider-azure-apidiff`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-azure&pr=1401). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Afiunchinho). 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-azure/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 -->
fiunchinho: Fixed
CecileRobertMichon: Please add a release note, this is a user facing change
fiunchinho: Done
k8s-ci-robot: @fiunchinho: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-provider-azure-apidiff | 7fedf7b9abffea3bbb041cd033dc7d8309c87b13 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-azure/1401/pull-cluster-api-provider-azure-apidiff/1400081854664544256) | `/test pull-cluster-api-provider-azure-apidiff`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-azure&pr=1401). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Afiunchinho). 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-azure/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 -->
k8s-ci-robot: @fiunchinho: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-provider-azure-apidiff | 7fedf7b9abffea3bbb041cd033dc7d8309c87b13 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-azure/1401/pull-cluster-api-provider-azure-apidiff/1400081854664544256) | `/test pull-cluster-api-provider-azure-apidiff`
pull-cluster-api-provider-azure-test | 7fedf7b9abffea3bbb041cd033dc7d8309c87b13 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-azure/1401/pull-cluster-api-provider-azure-test/1400081854538715136) | `/test pull-cluster-api-provider-azure-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-azure&pr=1401). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Afiunchinho). 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-azure/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 -->
k8s-ci-robot: @fiunchinho: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-provider-azure-test | 7fedf7b9abffea3bbb041cd033dc7d8309c87b13 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-azure/1401/pull-cluster-api-provider-azure-test/1400081854538715136) | `/test pull-cluster-api-provider-azure-test`
pull-cluster-api-provider-azure-apidiff | 16830744b758280a59ba80e45b403c6b869f6b6c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-azure/1401/pull-cluster-api-provider-azure-apidiff/1400088623721746432) | `/test pull-cluster-api-provider-azure-apidiff`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-azure&pr=1401). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Afiunchinho). 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-azure/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 -->
nader-ziada: /retest
k8s-ci-robot: @fiunchinho: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-provider-azure-apidiff | 16830744b758280a59ba80e45b403c6b869f6b6c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-azure/1401/pull-cluster-api-provider-azure-apidiff/1400095432528367616) | `/test pull-cluster-api-provider-azure-apidiff`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-azure&pr=1401). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Afiunchinho). 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-azure/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 -->
k8s-ci-robot: @fiunchinho: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-provider-azure-apidiff | 88c1a674da08f5bf4fefdc1278fba63473fbcc1f | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-azure/1401/pull-cluster-api-provider-azure-apidiff/1400354756752314368) | `/test pull-cluster-api-provider-azure-apidiff`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-azure&pr=1401). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Afiunchinho). 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-azure/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 -->
k8s-ci-robot: @fiunchinho: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-provider-azure-apidiff | 88c1a674da08f5bf4fefdc1278fba63473fbcc1f | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-azure/1401/pull-cluster-api-provider-azure-apidiff/1400354756752314368) | `/test pull-cluster-api-provider-azure-apidiff`
pull-cluster-api-provider-azure-test | 88c1a674da08f5bf4fefdc1278fba63473fbcc1f | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-azure/1401/pull-cluster-api-provider-azure-test/1400354756618096640) | `/test pull-cluster-api-provider-azure-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-azure&pr=1401). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Afiunchinho). 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-azure/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 -->
k8s-ci-robot: @fiunchinho: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-provider-azure-apidiff | 88c1a674da08f5bf4fefdc1278fba63473fbcc1f | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-azure/1401/pull-cluster-api-provider-azure-apidiff/1400354756752314368) | `/test pull-cluster-api-provider-azure-apidiff`
pull-cluster-api-provider-azure-test | 88c1a674da08f5bf4fefdc1278fba63473fbcc1f | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-azure/1401/pull-cluster-api-provider-azure-test/1400354756618096640) | `/test pull-cluster-api-provider-azure-test`
pull-cluster-api-provider-azure-e2e | 88c1a674da08f5bf4fefdc1278fba63473fbcc1f | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-azure/1401/pull-cluster-api-provider-azure-e2e/1400354756660039680) | `/test pull-cluster-api-provider-azure-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-azure&pr=1401). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Afiunchinho). 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-azure/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 -->
k8s-ci-robot: @fiunchinho: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-provider-azure-test | 88c1a674da08f5bf4fefdc1278fba63473fbcc1f | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-azure/1401/pull-cluster-api-provider-azure-test/1400354756618096640) | `/test pull-cluster-api-provider-azure-test`
pull-cluster-api-provider-azure-e2e | 88c1a674da08f5bf4fefdc1278fba63473fbcc1f | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-azure/1401/pull-cluster-api-provider-azure-e2e/1400354756660039680) | `/test pull-cluster-api-provider-azure-e2e`
pull-cluster-api-provider-azure-apidiff | d1ce6c3828b34a599c34e7bf9574b0cd5d701228 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-azure/1401/pull-cluster-api-provider-azure-apidiff/1400404177204547584) | `/test pull-cluster-api-provider-azure-apidiff`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-azure&pr=1401). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Afiunchinho). 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-azure/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 -->
CecileRobertMichon: @fiunchinho please squash

lgtm

Patch:
diff --git a/Tiltfile b/Tiltfile
index 5525e69f2..b706da81c 100644
--- a/Tiltfile
+++ b/Tiltfile
@@ -19,7 +19,7 @@ settings = {
     "capi_version": "nightly_master_20210526",
     "cert_manager_version": "v1.1.0",
     "kubernetes_version": "v1.19.7",
-    "aks_kubernetes_version": "v1.20.5"
+    "aks_kubernetes_version": "v1.18.8"
 }
 
 keys = ["AZURE_SUBSCRIPTION_ID_B64", "AZURE_TENANT_ID_B64", "AZURE_CLIENT_SECRET_B64", "AZURE_CLIENT_ID_B64"]
@@ -96,7 +96,7 @@ def fixup_yaml_empty_arrays(yaml_str):
 
 def validate_auth():
     substitutions = settings.get("kustomize_substitutions", {})
-    missing = [k for k in keys if k not in substitutions and not os.environ.get(k)]
+    missing = [k for k in keys if k not in substitutions]
     if missing:
         fail("missing kustomize_substitutions keys {} in tilt-setting.json".format(missing))
 
@@ -195,7 +195,7 @@ def flavors():
     substitutions = settings.get("kustomize_substitutions", {})
     for key in keys:
         if key[-4:] == "_B64":
-            os.environ[key[:-4]] = base64_decode(os.environ[key])
+            substitutions[key[:-4]] = base64_decode(substitutions[key])
 
     ssh_pub_key_B64 = "AZURE_SSH_PUBLIC_KEY_B64"
     ssh_pub_key_path = "$HOME/.ssh/id_rsa.pub"
diff --git a/api/v1alpha3/azurecluster_conversion.go b/api/v1alpha3/azurecluster_conversion.go
index 924166975..59e514a22 100644
--- a/api/v1alpha3/azurecluster_conversion.go
+++ b/api/v1alpha3/azurecluster_conversion.go
@@ -54,7 +54,6 @@ func (src *AzureCluster) ConvertTo(dstRaw conversion.Hub) error { // nolint
 	dst.Spec.NetworkSpec.APIServerLB.FrontendIPsCount = restored.Spec.NetworkSpec.APIServerLB.FrontendIPsCount
 	dst.Spec.NetworkSpec.NodeOutboundLB = restored.Spec.NetworkSpec.NodeOutboundLB
 	dst.Spec.CloudProviderConfigOverrides = restored.Spec.CloudProviderConfigOverrides
-	dst.Spec.BastionSpec = restored.Spec.BastionSpec
 
 	// Here we manually restore outbound security rules. Since v1alpha3 only supports ingress ("Inbound") rules, all v1alpha4 outbound rules are dropped when an AzureCluster
 	// is converted to v1alpha3. We loop through all security group rules. For all previously existing outbound rules we restore the full rule.
@@ -96,6 +95,11 @@ func (dst *AzureCluster) ConvertFrom(srcRaw conversion.Hub) error { // nolint
 		return err
 	}
 
+	// Preserve Hub data on down-conversion.
+	if err := utilconversion.MarshalData(src, dst); err != nil {
+		return err
+	}
+
 	return nil
 }
 
diff --git a/api/v1alpha3/azuremachine_conversion.go b/api/v1alpha3/azuremachine_conversion.go
index 801101994..475155dd3 100644
--- a/api/v1alpha3/azuremachine_conversion.go
+++ b/api/v1alpha3/azuremachine_conversion.go
@@ -113,9 +113,7 @@ func Convert_v1alpha4_AzureMachineStatus_To_v1alpha3_AzureMachineStatus(in *v1al
 // Convert_v1alpha3_OSDisk_To_v1alpha4_OSDisk converts this OSDisk to the Hub version (v1alpha4).
 func Convert_v1alpha3_OSDisk_To_v1alpha4_OSDisk(in *OSDisk, out *v1alpha4.OSDisk, s apiconversion.Scope) error { // nolint
 	out.OSType = in.OSType
-	if in.DiskSizeGB != 0 {
-		out.DiskSizeGB = &in.DiskSizeGB
-	}
+	out.DiskSizeGB = in.DiskSizeGB
 	out.DiffDiskSettings = (*v1alpha4.DiffDiskSettings)(in.DiffDiskSettings)
 	out.CachingType = in.CachingType
 	out.ManagedDisk = &v1alpha4.ManagedDiskParameters{}
@@ -130,9 +128,7 @@ func Convert_v1alpha3_OSDisk_To_v1alpha4_OSDisk(in *OSDisk, out *v1alpha4.OSDisk
 // Convert_v1alpha4_OSDisk_To_v1alpha3_OSDisk converts from the Hub version (v1alpha4) of the AzureMachineStatus to this version.
 func Convert_v1alpha4_OSDisk_To_v1alpha3_OSDisk(in *v1alpha4.OSDisk, out *OSDisk, s apiconversion.Scope) error { // nolint
 	out.OSType = in.OSType
-	if in.DiskSizeGB != nil {
-		out.DiskSizeGB = *in.DiskSizeGB
-	}
+	out.DiskSizeGB = in.DiskSizeGB
 	out.DiffDiskSettings = (*DiffDiskSettings)(in.DiffDiskSettings)
 	out.CachingType = in.CachingType
 
diff --git a/api/v1alpha3/zz_generated.conversion.go b/api/v1alpha3/zz_generated.conversion.go
index e342656f4..40e28a267 100644
--- a/api/v1alpha3/zz_generated.conversion.go
+++ b/api/v1alpha3/zz_generated.conversion.go
@@ -691,7 +691,6 @@ func autoConvert_v1alpha4_AzureClusterSpec_To_v1alpha3_AzureClusterSpec(in *v1al
 	out.AdditionalTags = *(*Tags)(unsafe.Pointer(&in.AdditionalTags))
 	out.IdentityRef = (*v1.ObjectReference)(unsafe.Pointer(in.IdentityRef))
 	// WARNING: in.AzureEnvironment requires manual conversion: does not exist in peer-type
-	// WARNING: in.BastionSpec requires manual conversion: does not exist in peer-type
 	// WARNING: in.CloudProviderConfigOverrides requires manual conversion: does not exist in peer-type
 	return nil
 }
diff --git a/api/v1alpha4/azurecluster_default.go b/api/v1alpha4/azurecluster_default.go
index 25da7cef1..11d5f9aef 100644
--- a/api/v1alpha4/azurecluster_default.go
+++ b/api/v1alpha4/azurecluster_default.go
@@ -29,10 +29,6 @@ const (
 	DefaultControlPlaneSubnetCIDR = "10.0.0.0/16"
 	// DefaultNodeSubnetCIDR is the default Node Subnet CIDR
 	DefaultNodeSubnetCIDR = "10.1.0.0/16"
-	// DefaultAzureBastionSubnetCIDR is the default Subnet CIDR for AzureBastion
-	DefaultAzureBastionSubnetCIDR = "10.255.255.224/27"
-	// DefaultAzureBastionSubnetName is the default Subnet Name for AzureBastion
-	DefaultAzureBastionSubnetName = "AzureBastionSubnet"
 	// DefaultInternalLBIPAddress is the default internal load balancer ip address
 	DefaultInternalLBIPAddress = "10.0.0.100"
 	// DefaultAzureCloud is the public cloud that will be used by most users
@@ -47,7 +43,6 @@ func (c *AzureCluster) setDefaults() {
 
 func (c *AzureCluster) setNetworkSpecDefaults() {
 	c.setVnetDefaults()
-	c.setBastionDefaults()
 	c.setSubnetDefaults()
 	c.setAPIServerLBDefaults()
 	c.setNodeOutboundLBDefaults()
@@ -209,29 +204,6 @@ func (c *AzureCluster) setNodeOutboundLBDefaults() {
 	}
 }
 
-func (c *AzureCluster) setBastionDefaults() {
-	if c.Spec.BastionSpec.AzureBastion != nil {
-		if c.Spec.BastionSpec.AzureBastion.Name == "" {
-			c.Spec.BastionSpec.AzureBastion.Name = generateAzureBastionName(c.ObjectMeta.Name)
-		}
-		// Ensure defaults for the Subnet settings.
-		{
-			if c.Spec.BastionSpec.AzureBastion.Subnet.Name == "" {
-				c.Spec.BastionSpec.AzureBastion.Subnet.Name = DefaultAzureBastionSubnetName
-			}
-			if len(c.Spec.BastionSpec.AzureBastion.Subnet.CIDRBlocks) == 0 {
-				c.Spec.BastionSpec.AzureBastion.Subnet.CIDRBlocks = []string{DefaultAzureBastionSubnetCIDR}
-			}
-		}
-		// Ensure defaults for the PublicIP settings.
-		{
-			if c.Spec.BastionSpec.AzureBastion.PublicIP.Name == "" {
-				c.Spec.BastionSpec.AzureBastion.PublicIP.Name = generateAzureBastionPublicIPName(c.ObjectMeta.Name)
-			}
-		}
-	}
-}
-
 // generateVnetName generates a virtual network name, based on the cluster name.
 func generateVnetName(clusterName string) string {
 	return fmt.Sprintf("%s-%s", clusterName, "vnet")
@@ -247,16 +219,6 @@ func generateNodeSubnetName(clusterName string) string {
 	return fmt.Sprintf("%s-%s", clusterName, "node-subnet")
 }
 
-// generateAzureBastionName generates an azure bastion name.
-func generateAzureBastionName(clusterName string) string {
-	return fmt.Sprintf("%s-azure-bastion", clusterName)
-}
-
-// generateAzureBastionPublicIPName generates an azure bastion public ip name.
-func generateAzureBastionPublicIPName(clusterName string) string {
-	return fmt.Sprintf("%s-azure-bastion-pip", clusterName)
-}
-
 // generateControlPlaneSecurityGroupName generates a control plane security group name, based on the cluster name.
 func generateControlPlaneSecurityGroupName(clusterName string) string {
 	return fmt.Sprintf("%s-%s", clusterName, "controlplane-nsg")
diff --git a/api/v1alpha4/azurecluster_types.go b/api/v1alpha4/azurecluster_types.go
index 72a455764..fb057682e 100644
--- a/api/v1alpha4/azurecluster_types.go
+++ b/api/v1alpha4/azurecluster_types.go
@@ -66,10 +66,6 @@ type AzureClusterSpec struct {
 	// +optional
 	AzureEnvironment string `json:"azureEnvironment,omitempty"`
 
-	// BastionSpec encapsulates all things related to the Bastions in the cluster.
-	// +optional
-	BastionSpec BastionSpec `json:"bastionSpec,omitempty"`
-
 	// CloudProviderConfigOverrides is an optional set of configuration values that can be overridden in azure cloud provider config.
 	// This is only a subset of options that are available in azure cloud provider config.
 	// Some values for the cloud provider config are inferred from other parts of cluster api provider azure spec, and may not be available for overrides.
diff --git a/api/v1alpha4/azurecluster_webhook.go b/api/v1alpha4/azurecluster_webhook.go
index 24b6a9bc1..69b718929 100644
--- a/api/v1alpha4/azurecluster_webhook.go
+++ b/api/v1alpha4/azurecluster_webhook.go
@@ -98,14 +98,6 @@ func (c *AzureCluster) ValidateUpdate(oldRaw runtime.Object) error {
 		)
 	}
 
-	// Allow enabling azure bastion but avoid disabling it.
-	if old.Spec.BastionSpec.AzureBastion != nil && !reflect.DeepEqual(old.Spec.BastionSpec.AzureBastion, c.Spec.BastionSpec.AzureBastion) {
-		allErrs = append(allErrs,
-			field.Invalid(field.NewPath("spec", "BastionSpec", "AzureBastion"),
-				c.Spec.BastionSpec.AzureBastion, "azure bastion cannot be removed from a cluster"),
-		)
-	}
-
 	if len(allErrs) == 0 {
 		return c.validateCluster(old)
 	}
diff --git a/api/v1alpha4/azuremachine_validation.go b/api/v1alpha4/azuremachine_validation.go
index d41efebd6..5b5ff7aa7 100644
--- a/api/v1alpha4/azuremachine_validation.go
+++ b/api/v1alpha4/azuremachine_validation.go
@@ -122,10 +122,8 @@ func ValidateDataDisks(dataDisks []DataDisk, fieldPath *field.Path) field.ErrorL
 func ValidateOSDisk(osDisk OSDisk, fieldPath *field.Path) field.ErrorList {
 	allErrs := field.ErrorList{}
 
-	if osDisk.DiskSizeGB != nil {
-		if *osDisk.DiskSizeGB <= 0 || *osDisk.DiskSizeGB > 2048 {
-			allErrs = append(allErrs, field.Invalid(fieldPath.Child("DiskSizeGB"), "", "the Disk size should be a value between 1 and 2048"))
-		}
+	if osDisk.DiskSizeGB <= 0 || osDisk.DiskSizeGB > 2048 {
+		allErrs = append(allErrs, field.Invalid(fieldPath.Child("DiskSizeGB"), "", "the Disk size should be a value between 1 and 2048"))
 	}
 
 	if osDisk.OSType == "" {
diff --git a/api/v1alpha4/types.go b/api/v1alpha4/types.go
index adb47f08f..b63270657 100644
--- a/api/v1alpha4/types.go
+++ b/api/v1alpha4/types.go
@@ -367,11 +367,8 @@ const (
 // conversion-gen where the warning message generated uses a relative directory import rather than the fully
 // qualified import when generating outside of the GOPATH.
 type OSDisk struct {
-	OSType string `json:"osType"`
-	// DiskSizeGB is the size in GB to assign to the OS disk.
-	// Will have a default of 30GB if not provided
-	// +optional
-	DiskSizeGB *int32 `json:"diskSizeGB,omitempty"`
+	OSType     string `json:"osType"`
+	DiskSizeGB int32  `json:"diskSizeGB"`
 	// ManagedDisk specifies the Managed Disk parameters for the OS disk.
 	// +optional
 	ManagedDisk      *ManagedDiskParameters `json:"managedDisk,omitempty"`
@@ -571,19 +568,3 @@ const (
 	// AvailabilitySetRateLimit ...
 	AvailabilitySetRateLimit = "availabilitySetRateLimit"
 )
-
-// BastionSpec specifies how the Bastion feature should be set up for the cluster.
-type BastionSpec struct {
-	// +optional
-	AzureBastion *AzureBastion `json:"azureBastion,omitempty"`
-}
-
-// AzureBastion specifies how the Azure Bastion cloud component should be configured.
-type AzureBastion struct {
-	// +optional
-	Name string `json:"name,omitempty"`
-	// +optional
-	Subnet SubnetSpec `json:"subnet,omitempty"`
-	// +optional
-	PublicIP PublicIPSpec `json:"publicIP,omitempty"`
-}
diff --git a/api/v1alpha4/zz_generated.deepcopy.go b/api/v1alpha4/zz_generated.deepcopy.go
index 11d6182a9..d3d39bfbe 100644
--- a/api/v1alpha4/zz_generated.deepcopy.go
+++ b/api/v1alpha4/zz_generated.deepcopy.go
@@ -68,23 +68,6 @@ func (in *AllowedNamespaces) DeepCopy() *AllowedNamespaces {
 	return out
 }
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *AzureBastion) DeepCopyInto(out *AzureBastion) {
-	*out = *in
-	in.Subnet.DeepCopyInto(&out.Subnet)
-	out.PublicIP = in.PublicIP
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureBastion.
-func (in *AzureBastion) DeepCopy() *AzureBastion {
-	if in == nil {
-		return nil
-	}
-	out := new(AzureBastion)
-	in.DeepCopyInto(out)
-	return out
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *AzureCluster) DeepCopyInto(out *AzureCluster) {
 	*out = *in
@@ -263,7 +246,6 @@ func (in *AzureClusterSpec) DeepCopyInto(out *AzureClusterSpec) {
 		*out = new(v1.ObjectReference)
 		**out = **in
 	}
-	in.BastionSpec.DeepCopyInto(&out.BastionSpec)
 	if in.CloudProviderConfigOverrides != nil {
 		in, out := &in.CloudProviderConfigOverrides, &out.CloudProviderConfigOverrides
 		*out = new(CloudProviderConfigOverrides)
@@ -596,26 +578,6 @@ func (in *AzureSharedGalleryImage) DeepCopy() *AzureSharedGalleryImage {
 	return out
 }
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *BastionSpec) DeepCopyInto(out *BastionSpec) {
-	*out = *in
-	if in.AzureBastion != nil {
-		in, out := &in.AzureBastion, &out.AzureBastion
-		*out = new(AzureBastion)
-		(*in).DeepCopyInto(*out)
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BastionSpec.
-func (in *BastionSpec) DeepCopy() *BastionSpec {
-	if in == nil {
-		return nil
-	}
-	out := new(BastionSpec)
-	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
@@ -869,11 +831,6 @@ func (in *NetworkSpec) DeepCopy() *NetworkSpec {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *OSDisk) DeepCopyInto(out *OSDisk) {
 	*out = *in
-	if in.DiskSizeGB != nil {
-		in, out := &in.DiskSizeGB, &out.DiskSizeGB
-		*out = new(int32)
-		**out = **in
-	}
 	if in.ManagedDisk != nil {
 		in, out := &in.ManagedDisk, &out.ManagedDisk
 		*out = new(ManagedDiskParameters)
diff --git a/azure/errors.go b/azure/errors.go
index 777acec75..0a4c699c4 100644
--- a/azure/errors.go
+++ b/azure/errors.go
@@ -22,7 +22,6 @@ import (
 	"time"
 
 	"github.com/Azure/go-autorest/autorest"
-	"github.com/Azure/go-autorest/autorest/azure"
 
 	infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1alpha4"
 )
@@ -30,15 +29,6 @@ import (
 // ErrNotOwned is returned when a resource can't be deleted because it isn't owned.
 var ErrNotOwned = errors.New("resource is not managed and cannot be deleted")
 
-const codeResourceGroupNotFound = "ResourceGroupNotFound"
-
-// ResourceGroupNotFound parses the error to check if it's a resource group not found error.
-func ResourceGroupNotFound(err error) bool {
-	derr := autorest.DetailedError{}
-	serr := &azure.ServiceError{}
-	return errors.As(err, &derr) && errors.As(derr.Original, &serr) && serr.Code == codeResourceGroupNotFound
-}
-
 // ResourceNotFound parses the error to check if it's a resource not found error.
 func ResourceNotFound(err error) bool {
 	derr := autorest.DetailedError{}
diff --git a/azure/interfaces.go b/azure/interfaces.go
index 3c87b39aa..41ef5e535 100644
--- a/azure/interfaces.go
+++ b/azure/interfaces.go
@@ -64,6 +64,7 @@ type NetworkDescriber interface {
 	IsVnetManaged() bool
 	NodeSubnet() infrav1.SubnetSpec
 	ControlPlaneSubnet() infrav1.SubnetSpec
+	SetSubnet(infrav1.SubnetSpec)
 	IsIPv6Enabled() bool
 	NodeRouteTable() infrav1.RouteTable
 	ControlPlaneRouteTable() infrav1.RouteTable
diff --git a/azure/mocks/service_mock.go b/azure/mocks/service_mock.go
index c802f7e47..e5b18d180 100644
--- a/azure/mocks/service_mock.go
+++ b/azure/mocks/service_mock.go
@@ -523,6 +523,18 @@ func (mr *MockNetworkDescriberMockRecorder) OutboundPoolName(arg0 interface{}) *
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OutboundPoolName", reflect.TypeOf((*MockNetworkDescriber)(nil).OutboundPoolName), arg0)
 }
 
+// SetSubnet mocks base method.
+func (m *MockNetworkDescriber) SetSubnet(arg0 v1alpha4.SubnetSpec) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "SetSubnet", arg0)
+}
+
+// SetSubnet indicates an expected call of SetSubnet.
+func (mr *MockNetworkDescriberMockRecorder) SetSubnet(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnet", reflect.TypeOf((*MockNetworkDescriber)(nil).SetSubnet), arg0)
+}
+
 // Vnet mocks base method.
 func (m *MockNetworkDescriber) Vnet() *v1alpha4.VnetSpec {
 	m.ctrl.T.Helper()
@@ -1115,6 +1127,18 @@ func (mr *MockClusterScoperMockRecorder) ResourceGroup() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResourceGroup", reflect.TypeOf((*MockClusterScoper)(nil).ResourceGroup))
 }
 
+// SetSubnet mocks base method.
+func (m *MockClusterScoper) SetSubnet(arg0 v1alpha4.SubnetSpec) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "SetSubnet", arg0)
+}
+
+// SetSubnet indicates an expected call of SetSubnet.
+func (mr *MockClusterScoperMockRecorder) SetSubnet(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnet", reflect.TypeOf((*MockClusterScoper)(nil).SetSubnet), arg0)
+}
+
 // SubscriptionID mocks base method.
 func (m *MockClusterScoper) SubscriptionID() string {
 	m.ctrl.T.Helper()
diff --git a/azure/scope/clients.go b/azure/scope/clients.go
index 3c416217d..dc11cf83e 100644
--- a/azure/scope/clients.go
+++ b/azure/scope/clients.go
@@ -93,9 +93,7 @@ func (c *AzureClients) setCredentials(subscriptionID, environmentName string) er
 	c.Values[auth.SubscriptionID] = strings.TrimSuffix(subscriptionID, "\n")
 	c.Values[auth.TenantID] = strings.TrimSuffix(c.Values[auth.TenantID], "\n")
 
-	if c.Authorizer == nil {
-		c.Authorizer, err = c.GetAuthorizer()
-	}
+	c.Authorizer, err = c.GetAuthorizer()
 	return err
 }
 
diff --git a/azure/scope/cluster.go b/azure/scope/cluster.go
index 4a5e18e29..bb822a0dc 100644
--- a/azure/scope/cluster.go
+++ b/azure/scope/cluster.go
@@ -153,15 +153,6 @@ func (s *ClusterScope) PublicIPSpecs() []azure.PublicIPSpec {
 
 	publicIPSpecs = append(publicIPSpecs, nodeOutboundIPSpecs...)
 
-	if s.AzureCluster.Spec.BastionSpec.AzureBastion != nil {
-		// public IP for Azure Bastion.
-		azureBastionPublicIP := azure.PublicIPSpec{
-			Name:    s.AzureCluster.Spec.BastionSpec.AzureBastion.PublicIP.Name,
-			DNSName: s.AzureCluster.Spec.BastionSpec.AzureBastion.PublicIP.DNSName,
-		}
-		publicIPSpecs = append(publicIPSpecs, azureBastionPublicIP)
-	}
-
 	return publicIPSpecs
 }
 
@@ -245,7 +236,7 @@ func (s *ClusterScope) NSGSpecs() []azure.NSGSpec {
 
 // SubnetSpecs returns the subnets specs.
 func (s *ClusterScope) SubnetSpecs() []azure.SubnetSpec {
-	subnetSpecs := []azure.SubnetSpec{
+	return []azure.SubnetSpec{
 		{
 			Name:              s.ControlPlaneSubnet().Name,
 			CIDRs:             s.ControlPlaneSubnet().CIDRBlocks,
@@ -263,20 +254,6 @@ func (s *ClusterScope) SubnetSpecs() []azure.SubnetSpec {
 			Role:              s.NodeSubnet().Role,
 		},
 	}
-
-	if s.AzureCluster.Spec.BastionSpec.AzureBastion != nil {
-		azureBastionSubnet := s.AzureCluster.Spec.BastionSpec.AzureBastion.Subnet
-		subnetSpecs = append(subnetSpecs, azure.SubnetSpec{
-			Name:              azureBastionSubnet.Name,
-			CIDRs:             azureBastionSubnet.CIDRBlocks,
-			VNetName:          s.Vnet().Name,
-			SecurityGroupName: azureBastionSubnet.SecurityGroup.Name,
-			RouteTableName:    azureBastionSubnet.RouteTable.Name,
-			Role:              azureBastionSubnet.Role,
-		})
-	}
-
-	return subnetSpecs
 }
 
 // VNetSpec returns the virtual network spec.
@@ -308,21 +285,6 @@ func (s *ClusterScope) PrivateDNSSpec() *azure.PrivateDNSSpec {
 	return spec
 }
 
-// BastionSpec returns the bastion spec.
-func (s *ClusterScope) BastionSpec() azure.BastionSpec {
-	var ret azure.BastionSpec
-	if s.AzureCluster.Spec.BastionSpec.AzureBastion != nil {
-		ret.AzureBastion = &azure.AzureBastionSpec{
-			Name:         s.AzureCluster.Spec.BastionSpec.AzureBastion.Name,
-			SubnetSpec:   s.AzureCluster.Spec.BastionSpec.AzureBastion.Subnet,
-			PublicIPName: s.AzureCluster.Spec.BastionSpec.AzureBastion.PublicIP.Name,
-			VNetName:     s.Vnet().Name,
-		}
-	}
-
-	return ret
-}
-
 // Vnet returns the cluster Vnet.
 func (s *ClusterScope) Vnet() *infrav1.VnetSpec {
 	return &s.AzureCluster.Spec.NetworkSpec.Vnet
@@ -360,6 +322,16 @@ func (s *ClusterScope) NodeSubnet() infrav1.SubnetSpec {
 	return subnet
 }
 
+// SetSubnet sets the subnet spec for the subnet with the same role.
+func (s *ClusterScope) SetSubnet(subnetSpec infrav1.SubnetSpec) {
+	for i, sn := range s.AzureCluster.Spec.NetworkSpec.Subnets {
+		if sn.Role == subnetSpec.Role {
+			s.AzureCluster.Spec.NetworkSpec.Subnets[i] = subnetSpec
+			return
+		}
+	}
+}
+
 // ControlPlaneRouteTable returns the cluster controlplane routetable.
 func (s *ClusterScope) ControlPlaneRouteTable() infrav1.RouteTable {
 	subnet, _ := s.AzureCluster.Spec.NetworkSpec.GetControlPlaneSubnet()
diff --git a/azure/scope/machine.go b/azure/scope/machine.go
index ee4d4a562..9ca584a56 100644
--- a/azure/scope/machine.go
+++ b/azure/scope/machine.go
@@ -207,6 +207,17 @@ func (m *MachineScope) DiskSpecs() []azure.DiskSpec {
 	return disks
 }
 
+// BastionSpecs returns the bastion specs.
+func (m *MachineScope) BastionSpecs() []azure.BastionSpec {
+	spec := azure.BastionSpec{
+		Name:         azure.GenerateOSDiskName(m.Name()),
+		SubnetName:   m.Subnet().Name,
+		PublicIPName: azure.GenerateNodePublicIPName(azure.GenerateNICName(m.Name())),
+		VNetName:     m.Vnet().Name,
+	}
+	return []azure.BastionSpec{spec}
+}
+
 // RoleAssignmentSpecs returns the role assignment specs.
 func (m *MachineScope) RoleAssignmentSpecs() []azure.RoleAssignmentSpec {
 	if m.AzureMachine.Spec.Identity == infrav1.VMIdentitySystemAssigned {
diff --git a/azure/scope/managedcontrolplane.go b/azure/scope/managedcontrolplane.go
index 8f377e254..039e2ed9b 100644
--- a/azure/scope/managedcontrolplane.go
+++ b/azure/scope/managedcontrolplane.go
@@ -198,6 +198,12 @@ func (s *ManagedControlPlaneScope) NodeSubnet() infrav1.SubnetSpec {
 	}
 }
 
+// SetSubnet sets the passed subnet spec into the scope.
+// This is not used when using a managed control plane.
+func (s *ManagedControlPlaneScope) SetSubnet(subnetSpec infrav1.SubnetSpec) {
+	// no-op
+}
+
 // ControlPlaneSubnet returns the cluster control plane subnet.
 func (s *ManagedControlPlaneScope) ControlPlaneSubnet() infrav1.SubnetSpec {
 	return infrav1.SubnetSpec{}
diff --git a/azure/services/bastionhosts/azurebastion.go b/azure/services/bastionhosts/azurebastion.go
deleted file mode 100644
index 8b143498e..000000000
--- a/azure/services/bastionhosts/azurebastion.go
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
-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 bastionhosts
-
-import (
-	"context"
-	"fmt"
-	"strings"
-
-	"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
-	"github.com/Azure/go-autorest/autorest/to"
-	"github.com/pkg/errors"
-
-	infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1alpha4"
-	"sigs.k8s.io/cluster-api-provider-azure/azure"
-	"sigs.k8s.io/cluster-api-provider-azure/azure/converters"
-)
-
-func (s *Service) ensureAzureBastion(ctx context.Context, azureBastionSpec azure.AzureBastionSpec) error {
-	s.Scope.V(2).Info("getting azure bastion public IP", "publicIP", azureBastionSpec.PublicIPName)
-	publicIP, err := s.publicIPsClient.Get(ctx, s.Scope.ResourceGroup(), azureBastionSpec.PublicIPName)
-	if err != nil {
-		return errors.Wrap(err, "failed to get public IP for azure bastion")
-	}
-
-	s.Scope.V(2).Info("getting azure bastion subnet", "subnet", azureBastionSpec.SubnetSpec)
-	subnet, err := s.subnetsClient.Get(ctx, s.Scope.ResourceGroup(), azureBastionSpec.VNetName, azureBastionSpec.SubnetSpec.Name)
-	if err != nil {
-		return errors.Wrap(err, "failed to get subnet for azure bastion")
-	}
-
-	s.Scope.V(2).Info("creating bastion host", "bastion", azureBastionSpec.Name)
-	bastionHostIPConfigName := fmt.Sprintf("%s-%s", azureBastionSpec.Name, "bastionIP")
-	err = s.client.CreateOrUpdate(
-		ctx,
-		s.Scope.ResourceGroup(),
-		azureBastionSpec.Name,
-		network.BastionHost{
-			Name:     to.StringPtr(azureBastionSpec.Name),
-			Location: to.StringPtr(s.Scope.Location()),
-			Tags: converters.TagsToMap(infrav1.Build(infrav1.BuildParams{
-				ClusterName: s.Scope.ClusterName(),
-				Lifecycle:   infrav1.ResourceLifecycleOwned,
-				Name:        to.StringPtr(azureBastionSpec.Name),
-				Role:        to.StringPtr("Bastion"),
-			})),
-			BastionHostPropertiesFormat: &network.BastionHostPropertiesFormat{
-				DNSName: to.StringPtr(fmt.Sprintf("%s-bastion", strings.ToLower(azureBastionSpec.Name))),
-				IPConfigurations: &[]network.BastionHostIPConfiguration{
-					{
-						Name: to.StringPtr(bastionHostIPConfigName),
-						BastionHostIPConfigurationPropertiesFormat: &network.BastionHostIPConfigurationPropertiesFormat{
-							Subnet: &network.SubResource{
-								ID: subnet.ID,
-							},
-							PublicIPAddress: &network.SubResource{
-								ID: publicIP.ID,
-							},
-							PrivateIPAllocationMethod: network.Dynamic,
-						},
-					},
-				},
-			},
-		},
-	)
-	if err != nil {
-		return errors.Wrap(err, "cannot create Azure Bastion")
-	}
-
-	s.Scope.V(2).Info("successfully created bastion host", "bastion", azureBastionSpec.Name)
-	return nil
-}
-
-func (s *Service) ensureAzureBastionDeleted(ctx context.Context, azureBastionSpec azure.AzureBastionSpec) error {
-	s.Scope.V(2).Info("deleting bastion host", "bastion", azureBastionSpec.Name)
-
-	err := s.client.Delete(ctx, s.Scope.ResourceGroup(), azureBastionSpec.Name)
-	if err != nil && azure.ResourceNotFound(err) {
-		// Resource already deleted, all good.
-	} else if err != nil {
-		return errors.Wrapf(err, "failed to delete Azure Bastion %s in resource group %s", azureBastionSpec.Name, s.Scope.ResourceGroup())
-	}
-
-	s.Scope.V(2).Info("successfully deleted bastion host", "bastion", azureBastionSpec.Name)
-
-	return nil
-}
diff --git a/azure/services/bastionhosts/bastionhosts.go b/azure/services/bastionhosts/bastionhosts.go
index 1effaef83..4b87eaca1 100644
--- a/azure/services/bastionhosts/bastionhosts.go
+++ b/azure/services/bastionhosts/bastionhosts.go
@@ -18,12 +18,18 @@ package bastionhosts
 
 import (
 	"context"
+	"fmt"
+	"strings"
 
+	"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
+	"github.com/Azure/go-autorest/autorest/to"
 	"github.com/go-logr/logr"
 	"github.com/pkg/errors"
 
+	infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1alpha4"
 	"sigs.k8s.io/cluster-api-provider-azure/azure"
 
+	"sigs.k8s.io/cluster-api-provider-azure/azure/converters"
 	"sigs.k8s.io/cluster-api-provider-azure/azure/services/publicips"
 	"sigs.k8s.io/cluster-api-provider-azure/azure/services/subnets"
 	"sigs.k8s.io/cluster-api-provider-azure/util/tele"
@@ -34,7 +40,7 @@ type BastionScope interface {
 	logr.Logger
 	azure.ClusterDescriber
 	azure.NetworkDescriber
-	BastionSpec() azure.BastionSpec
+	BastionSpecs() []azure.BastionSpec
 }
 
 // Service provides operations on azure resources
@@ -60,14 +66,71 @@ func (s *Service) Reconcile(ctx context.Context) error {
 	ctx, span := tele.Tracer().Start(ctx, "bastionhosts.Service.Reconcile")
 	defer span.End()
 
-	azureBastionSpec := s.Scope.BastionSpec().AzureBastion
-	if azureBastionSpec != nil {
-		err := s.ensureAzureBastion(ctx, *azureBastionSpec)
+	for _, bastionSpec := range s.Scope.BastionSpecs() {
+		s.Scope.V(2).Info("getting subnet in vnet", "subnet", bastionSpec.SubnetName, "vNet", bastionSpec.VNetName)
+		subnet, err := s.subnetsClient.Get(ctx, s.Scope.ResourceGroup(), bastionSpec.VNetName, bastionSpec.SubnetName)
 		if err != nil {
-			return errors.Wrap(err, "error creating Azure Bastion")
+			return errors.Wrap(err, "failed to get subnet")
+		}
+		s.Scope.V(2).Info("successfully got subnet in vnet", "subnet", bastionSpec.SubnetName, "vNet", bastionSpec.VNetName)
+
+		s.Scope.V(2).Info("checking if public ip exist otherwise will try to create", "publicIP", bastionSpec.PublicIPName)
+		publicIP, err := s.publicIPsClient.Get(ctx, s.Scope.ResourceGroup(), bastionSpec.PublicIPName)
+		if err != nil && azure.ResourceNotFound(err) {
+			iperr := s.createBastionPublicIP(ctx, bastionSpec.PublicIPName)
+			if iperr != nil {
+				return errors.Wrap(iperr, "failed to create bastion publicIP")
+			}
+			var errPublicIP error
+			publicIP, errPublicIP = s.publicIPsClient.Get(ctx, s.Scope.ResourceGroup(), bastionSpec.PublicIPName)
+			if errPublicIP != nil {
+				return errors.Wrap(errPublicIP, "failed to get created publicIP")
+			}
+		} else if err != nil {
+			return errors.Wrap(err, "failed to get existing publicIP")
+		}
+		s.Scope.V(2).Info("successfully got public ip", "publicIP", bastionSpec.PublicIPName)
+
+		s.Scope.V(2).Info("creating bastion host", "bastion", bastionSpec.Name)
+		bastionHostIPConfigName := fmt.Sprintf("%s-%s", bastionSpec.Name, "bastionIP")
+		err = s.client.CreateOrUpdate(
+			ctx,
+			s.Scope.ResourceGroup(),
+			bastionSpec.Name,
+			network.BastionHost{
+				Name:     to.StringPtr(bastionSpec.Name),
+				Location: to.StringPtr(s.Scope.Location()),
+				Tags: converters.TagsToMap(infrav1.Build(infrav1.BuildParams{
+					ClusterName: s.Scope.ClusterName(),
+					Lifecycle:   infrav1.ResourceLifecycleOwned,
+					Name:        to.StringPtr(bastionSpec.Name),
+					Role:        to.StringPtr("Bastion"),
+				})),
+				BastionHostPropertiesFormat: &network.BastionHostPropertiesFormat{
+					DNSName: to.StringPtr(fmt.Sprintf("%s-bastion", strings.ToLower(bastionSpec.Name))),
+					IPConfigurations: &[]network.BastionHostIPConfiguration{
+						{
+							Name: to.StringPtr(bastionHostIPConfigName),
+							BastionHostIPConfigurationPropertiesFormat: &network.BastionHostIPConfigurationPropertiesFormat{
+								Subnet: &network.SubResource{
+									ID: subnet.ID,
+								},
+								PublicIPAddress: &network.SubResource{
+									ID: publicIP.ID,
+								},
+								PrivateIPAllocationMethod: network.Static,
+							},
+						},
+					},
+				},
+			},
+		)
+		if err != nil {
+			return errors.Wrap(err, "cannot create bastion host")
 		}
-	}
 
+		s.Scope.V(2).Info("successfully created bastion host", "bastion", bastionSpec.Name)
+	}
 	return nil
 }
 
@@ -76,12 +139,44 @@ func (s *Service) Delete(ctx context.Context) error {
 	ctx, span := tele.Tracer().Start(ctx, "bastionhosts.Service.Delete")
 	defer span.End()
 
-	azureBastionSpec := s.Scope.BastionSpec().AzureBastion
-	if azureBastionSpec != nil {
-		err := s.ensureAzureBastionDeleted(ctx, *azureBastionSpec)
+	for _, bastionSpec := range s.Scope.BastionSpecs() {
+
+		s.Scope.V(2).Info("deleting bastion host", "bastion", bastionSpec.Name)
+
+		err := s.client.Delete(ctx, s.Scope.ResourceGroup(), bastionSpec.Name)
+		if err != nil && azure.ResourceNotFound(err) {
+			// already deleted
+			continue
+		}
 		if err != nil {
-			return errors.Wrap(err, "error deleting Azure Bastion")
+			return errors.Wrapf(err, "failed to delete Bastion Host %s in resource group %s", bastionSpec.Name, s.Scope.ResourceGroup())
 		}
+
+		s.Scope.V(2).Info("successfully deleted bastion host", "bastion", bastionSpec.Name)
 	}
 	return nil
 }
+
+func (s *Service) createBastionPublicIP(ctx context.Context, ipName string) error {
+	ctx, span := tele.Tracer().Start(ctx, "bastionhosts.Service.createBastionPublicIP")
+	defer span.End()
+
+	s.Scope.V(2).Info("creating bastion public IP", "public IP", ipName)
+	return s.publicIPsClient.CreateOrUpdate(
+		ctx,
+		s.Scope.ResourceGroup(),
+		ipName,
+		network.PublicIPAddress{
+			Sku:      &network.PublicIPAddressSku{Name: network.PublicIPAddressSkuNameStandard},
+			Name:     to.StringPtr(ipName),
+			Location: to.StringPtr(s.Scope.Location()),
+			PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
+				PublicIPAddressVersion:   network.IPv4,
+				PublicIPAllocationMethod: network.Static,
+				DNSSettings: &network.PublicIPAddressDNSSettings{
+					DomainNameLabel: to.StringPtr(strings.ToLower(ipName)),
+				},
+			},
+		},
+	)
+}
diff --git a/azure/services/bastionhosts/mocks_bastionhosts/bastionhosts_mock.go b/azure/services/bastionhosts/mocks_bastionhosts/bastionhosts_mock.go
index c230ca094..33c8ce2b4 100644
--- a/azure/services/bastionhosts/mocks_bastionhosts/bastionhosts_mock.go
+++ b/azure/services/bastionhosts/mocks_bastionhosts/bastionhosts_mock.go
@@ -137,18 +137,18 @@ func (mr *MockBastionScopeMockRecorder) BaseURI() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BaseURI", reflect.TypeOf((*MockBastionScope)(nil).BaseURI))
 }
 
-// BastionSpec mocks base method.
-func (m *MockBastionScope) BastionSpec() azure.BastionSpec {
+// BastionSpecs mocks base method.
+func (m *MockBastionScope) BastionSpecs() []azure.BastionSpec {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "BastionSpec")
-	ret0, _ := ret[0].(azure.BastionSpec)
+	ret := m.ctrl.Call(m, "BastionSpecs")
+	ret0, _ := ret[0].([]azure.BastionSpec)
 	return ret0
 }
 
-// BastionSpec indicates an expected call of BastionSpec.
-func (mr *MockBastionScopeMockRecorder) BastionSpec() *gomock.Call {
+// BastionSpecs indicates an expected call of BastionSpecs.
+func (mr *MockBastionScopeMockRecorder) BastionSpecs() *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BastionSpec", reflect.TypeOf((*MockBastionScope)(nil).BastionSpec))
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BastionSpecs", reflect.TypeOf((*MockBastionScope)(nil).BastionSpecs))
 }
 
 // ClientID mocks base method.
@@ -451,6 +451,18 @@ func (mr *MockBastionScopeMockRecorder) ResourceGroup() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResourceGroup", reflect.TypeOf((*MockBastionScope)(nil).ResourceGroup))
 }
 
+// SetSubnet mocks base method.
+func (m *MockBastionScope) SetSubnet(arg0 v1alpha4.SubnetSpec) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "SetSubnet", arg0)
+}
+
+// SetSubnet indicates an expected call of SetSubnet.
+func (mr *MockBastionScopeMockRecorder) SetSubnet(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnet", reflect.TypeOf((*MockBastionScope)(nil).SetSubnet), arg0)
+}
+
 // SubscriptionID mocks base method.
 func (m *MockBastionScope) SubscriptionID() string {
 	m.ctrl.T.Helper()
diff --git a/azure/services/loadbalancers/mock_loadbalancers/loadbalancers_mock.go b/azure/services/loadbalancers/mock_loadbalancers/loadbalancers_mock.go
index bb1bc218f..2e75250c5 100644
--- a/azure/services/loadbalancers/mock_loadbalancers/loadbalancers_mock.go
+++ b/azure/services/loadbalancers/mock_loadbalancers/loadbalancers_mock.go
@@ -451,6 +451,18 @@ func (mr *MockLBScopeMockRecorder) ResourceGroup() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResourceGroup", reflect.TypeOf((*MockLBScope)(nil).ResourceGroup))
 }
 
+// SetSubnet mocks base method.
+func (m *MockLBScope) SetSubnet(arg0 v1alpha4.SubnetSpec) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "SetSubnet", arg0)
+}
+
+// SetSubnet indicates an expected call of SetSubnet.
+func (mr *MockLBScopeMockRecorder) SetSubnet(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnet", reflect.TypeOf((*MockLBScope)(nil).SetSubnet), arg0)
+}
+
 // SubscriptionID mocks base method.
 func (m *MockLBScope) SubscriptionID() string {
 	m.ctrl.T.Helper()
diff --git a/azure/services/managedclusters/client.go b/azure/services/managedclusters/client.go
index b98e03213..357796aaf 100644
--- a/azure/services/managedclusters/client.go
+++ b/azure/services/managedclusters/client.go
@@ -101,9 +101,6 @@ func (ac *AzureClient) Delete(ctx context.Context, resourceGroupName, name strin
 
 	future, err := ac.managedclusters.Delete(ctx, resourceGroupName, name)
 	if err != nil {
-		if azure.ResourceGroupNotFound(err) || azure.ResourceNotFound(err) {
-			return nil
-		}
 		return errors.Wrap(err, "failed to begin operation")
 	}
 	if err := future.WaitForCompletionRef(ctx, ac.managedclusters.Client); err != nil {
diff --git a/azure/services/routetables/mock_routetables/routetables_mock.go b/azure/services/routetables/mock_routetables/routetables_mock.go
index 28e632905..05f297c7d 100644
--- a/azure/services/routetables/mock_routetables/routetables_mock.go
+++ b/azure/services/routetables/mock_routetables/routetables_mock.go
@@ -451,6 +451,18 @@ func (mr *MockRouteTableScopeMockRecorder) RouteTableSpecs() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RouteTableSpecs", reflect.TypeOf((*MockRouteTableScope)(nil).RouteTableSpecs))
 }
 
+// SetSubnet mocks base method.
+func (m *MockRouteTableScope) SetSubnet(arg0 v1alpha4.SubnetSpec) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "SetSubnet", arg0)
+}
+
+// SetSubnet indicates an expected call of SetSubnet.
+func (mr *MockRouteTableScopeMockRecorder) SetSubnet(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnet", reflect.TypeOf((*MockRouteTableScope)(nil).SetSubnet), arg0)
+}
+
 // SubscriptionID mocks base method.
 func (m *MockRouteTableScope) SubscriptionID() string {
 	m.ctrl.T.Helper()
diff --git a/azure/services/routetables/routetables.go b/azure/services/routetables/routetables.go
index a38469c18..d67c7d1dc 100644
--- a/azure/services/routetables/routetables.go
+++ b/azure/services/routetables/routetables.go
@@ -72,6 +72,7 @@ func (s *Service) Reconcile(ctx context.Context) error {
 			// currently don't support specifying your own routes via spec
 			routeTableSpec.Subnet.RouteTable.Name = to.String(existingRouteTable.Name)
 			routeTableSpec.Subnet.RouteTable.ID = to.String(existingRouteTable.ID)
+			s.Scope.SetSubnet(routeTableSpec.Subnet)
 
 			continue
 		}
diff --git a/azure/services/scalesets/scalesets.go b/azure/services/scalesets/scalesets.go
index 3def1bd13..191b7c23d 100644
--- a/azure/services/scalesets/scalesets.go
+++ b/azure/services/scalesets/scalesets.go
@@ -548,7 +548,7 @@ func (s *Service) generateStorageProfile(vmssSpec azure.ScaleSetSpec, sku resour
 		OsDisk: &compute.VirtualMachineScaleSetOSDisk{
 			OsType:       compute.OperatingSystemTypes(vmssSpec.OSDisk.OSType),
 			CreateOption: compute.DiskCreateOptionTypesFromImage,
-			DiskSizeGB:   vmssSpec.OSDisk.DiskSizeGB,
+			DiskSizeGB:   to.Int32Ptr(vmssSpec.OSDisk.DiskSizeGB),
 		},
 	}
 
diff --git a/azure/services/securitygroups/mock_securitygroups/securitygroups_mock.go b/azure/services/securitygroups/mock_securitygroups/securitygroups_mock.go
index 16e5e89f4..441599a59 100644
--- a/azure/services/securitygroups/mock_securitygroups/securitygroups_mock.go
+++ b/azure/services/securitygroups/mock_securitygroups/securitygroups_mock.go
@@ -451,6 +451,18 @@ func (mr *MockNSGScopeMockRecorder) ResourceGroup() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResourceGroup", reflect.TypeOf((*MockNSGScope)(nil).ResourceGroup))
 }
 
+// SetSubnet mocks base method.
+func (m *MockNSGScope) SetSubnet(arg0 v1alpha4.SubnetSpec) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "SetSubnet", arg0)
+}
+
+// SetSubnet indicates an expected call of SetSubnet.
+func (mr *MockNSGScopeMockRecorder) SetSubnet(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnet", reflect.TypeOf((*MockNSGScope)(nil).SetSubnet), arg0)
+}
+
 // SubscriptionID mocks base method.
 func (m *MockNSGScope) SubscriptionID() string {
 	m.ctrl.T.Helper()
diff --git a/azure/services/subnets/mock_subnets/subnets_mock.go b/azure/services/subnets/mock_subnets/subnets_mock.go
index 6c9a7bd79..4fa17a5e4 100644
--- a/azure/services/subnets/mock_subnets/subnets_mock.go
+++ b/azure/services/subnets/mock_subnets/subnets_mock.go
@@ -437,6 +437,18 @@ func (mr *MockSubnetScopeMockRecorder) ResourceGroup() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResourceGroup", reflect.TypeOf((*MockSubnetScope)(nil).ResourceGroup))
 }
 
+// SetSubnet mocks base method.
+func (m *MockSubnetScope) SetSubnet(arg0 v1alpha4.SubnetSpec) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "SetSubnet", arg0)
+}
+
+// SetSubnet indicates an expected call of SetSubnet.
+func (mr *MockSubnetScopeMockRecorder) SetSubnet(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnet", reflect.TypeOf((*MockSubnetScope)(nil).SetSubnet), arg0)
+}
+
 // SubnetSpecs mocks base method.
 func (m *MockSubnetScope) SubnetSpecs() []azure.SubnetSpec {
 	m.ctrl.T.Helper()
diff --git a/azure/services/subnets/subnets.go b/azure/services/subnets/subnets.go
index 38c1c8b5f..498743386 100644
--- a/azure/services/subnets/subnets.go
+++ b/azure/services/subnets/subnets.go
@@ -72,10 +72,10 @@ func (s *Service) Reconcile(ctx context.Context) error {
 				continue
 			}
 
-			subnet.Role = subnetSpec.Role
-			subnet.Name = existingSubnet.Name
-			subnet.CIDRBlocks = existingSubnet.CIDRBlocks
 			subnet.ID = existingSubnet.ID
+			subnet.CIDRBlocks = existingSubnet.CIDRBlocks
+
+			s.Scope.SetSubnet(subnet)
 
 		case !s.Scope.IsVnetManaged():
 			return fmt.Errorf("vnet was provided but subnet %s is missing", subnetSpec.Name)
diff --git a/azure/services/virtualmachines/virtualmachines.go b/azure/services/virtualmachines/virtualmachines.go
index 78c002bdb..457bc963c 100644
--- a/azure/services/virtualmachines/virtualmachines.go
+++ b/azure/services/virtualmachines/virtualmachines.go
@@ -350,7 +350,7 @@ func (s *Service) generateStorageProfile(ctx context.Context, vmSpec azure.VMSpe
 			Name:         to.StringPtr(azure.GenerateOSDiskName(vmSpec.Name)),
 			OsType:       compute.OperatingSystemTypes(vmSpec.OSDisk.OSType),
 			CreateOption: compute.DiskCreateOptionTypesFromImage,
-			DiskSizeGB:   vmSpec.OSDisk.DiskSizeGB,
+			DiskSizeGB:   to.Int32Ptr(vmSpec.OSDisk.DiskSizeGB),
 			Caching:      compute.CachingTypes(vmSpec.OSDisk.CachingType),
 		},
 	}
diff --git a/azure/services/virtualnetworks/virtualnetworks.go b/azure/services/virtualnetworks/virtualnetworks.go
index f27d255fe..ee5ceeec9 100644
--- a/azure/services/virtualnetworks/virtualnetworks.go
+++ b/azure/services/virtualnetworks/virtualnetworks.go
@@ -120,10 +120,9 @@ func (s *Service) Delete(ctx context.Context) error {
 
 	s.Scope.V(2).Info("deleting VNet", "VNet", vnetSpec.Name)
 	err := s.Client.Delete(ctx, vnetSpec.ResourceGroup, vnetSpec.Name)
-	if err != nil {
-		if azure.ResourceGroupNotFound(err) || azure.ResourceNotFound(err) {
-			return nil
-		}
+	if err != nil && azure.ResourceNotFound(err) {
+		// already deleted
+		return nil
 	}
 	if err != nil {
 		return errors.Wrapf(err, "failed to delete VNet %s in resource group %s", vnetSpec.Name, vnetSpec.ResourceGroup)
diff --git a/azure/types.go b/azure/types.go
index 81573a318..aaeb080d1 100644
--- a/azure/types.go
+++ b/azure/types.go
@@ -138,15 +138,10 @@ type VMSpec struct {
 	SecurityProfile        *infrav1.SecurityProfile
 }
 
-// BastionSpec defines the specification for the generic bastion feature.
+// BastionSpec defines the specification for bastion host.
 type BastionSpec struct {
-	AzureBastion *AzureBastionSpec
-}
-
-// AzureBastionSpec defines the specification for azure bastion feature.
-type AzureBastionSpec struct { //nolint
 	Name         string
-	SubnetSpec   infrav1.SubnetSpec
+	SubnetName   string
 	PublicIPName string
 	VNetName     string
 }
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclusters.yaml
index 5621d2d40..0e28d3df0 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclusters.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_azureclusters.yaml
@@ -370,115 +370,6 @@ spec:
               azureEnvironment:
                 description: 'AzureEnvironment is the name of the AzureCloud to be used. The default value that would be used by most users is "AzurePublicCloud", other values are: - ChinaCloud: "AzureChinaCloud" - GermanCloud: "AzureGermanCloud" - PublicCloud: "AzurePublicCloud" - USGovernmentCloud: "AzureUSGovernmentCloud"'
                 type: string
-              bastionSpec:
-                description: BastionSpec encapsulates all things related to the Bastions in the cluster.
-                properties:
-                  azureBastion:
-                    description: AzureBastion specifies how the Azure Bastion cloud component should be configured.
-                    properties:
-                      name:
-                        type: string
-                      publicIP:
-                        description: PublicIPSpec defines the inputs to create an Azure public IP address.
-                        properties:
-                          dnsName:
-                            type: string
-                          name:
-                            type: string
-                        required:
-                        - name
-                        type: object
-                      subnet:
-                        description: SubnetSpec configures an Azure subnet.
-                        properties:
-                          cidrBlocks:
-                            description: CIDRBlocks defines the subnet's address space, specified as one or more address prefixes in CIDR notation.
-                            items:
-                              type: string
-                            type: array
-                          id:
-                            description: ID defines a unique identifier to reference this resource.
-                            type: string
-                          name:
-                            description: Name defines a name for the subnet resource.
-                            type: string
-                          role:
-                            description: Role defines the subnet role (eg. Node, ControlPlane)
-                            type: string
-                          routeTable:
-                            description: RouteTable defines the route table that should be attached to this subnet.
-                            properties:
-                              id:
-                                type: string
-                              name:
-                                type: string
-                            type: object
-                          securityGroup:
-                            description: SecurityGroup defines the NSG (network security group) that should be attached to this subnet.
-                            properties:
-                              id:
-                                type: string
-                              name:
-                                type: string
-                              securityRules:
-                                description: SecurityRules is a slice of Azure security rules for security groups.
-                                items:
-                                  description: SecurityRule defines an Azure security rule for security groups.
-                                  properties:
-                                    description:
-                                      description: A description for this rule. Restricted to 140 chars.
-                                      type: string
-                                    destination:
-                                      description: Destination is the destination address prefix. CIDR or destination IP range. Asterix '*' can also be used to match all source IPs. Default tags such as 'VirtualNetwork', 'AzureLoadBalancer' and 'Internet' can also be used.
-                                      type: string
-                                    destinationPorts:
-                                      description: DestinationPorts specifies the destination port or range. Integer or range between 0 and 65535. Asterix '*' can also be used to match all ports.
-                                      type: string
-                                    direction:
-                                      description: Direction indicates whether the rule applies to inbound, or outbound traffic. "Inbound" or "Outbound".
-                                      enum:
-                                      - Inbound
-                                      - Outbound
-                                      type: string
-                                    name:
-                                      description: Name is a unique name within the network security group.
-                                      type: string
-                                    priority:
-                                      description: Priority is a number between 100 and 4096. Each rule should have a unique value for priority. Rules are processed in priority order, with lower numbers processed before higher numbers. Once traffic matches a rule, processing stops.
-                                      format: int32
-                                      type: integer
-                                    protocol:
-                                      description: Protocol specifies the protocol type. "Tcp", "Udp", "Icmp", or "*".
-                                      enum:
-                                      - Tcp
-                                      - Udp
-                                      - Icmp
-                                      - '*'
-                                      type: string
-                                    source:
-                                      description: Source specifies the CIDR or source IP range. Asterix '*' can also be used to match all source IPs. Default tags such as 'VirtualNetwork', 'AzureLoadBalancer' and 'Internet' can also be used. If this is an ingress rule, specifies where network traffic originates from.
-                                      type: string
-                                    sourcePorts:
-                                      description: SourcePorts specifies source port or range. Integer or range between 0 and 65535. Asterix '*' can also be used to match all ports.
-                                      type: string
-                                  required:
-                                  - description
-                                  - direction
-                                  - name
-                                  - protocol
-                                  type: object
-                                type: array
-                              tags:
-                                additionalProperties:
-                                  type: string
-                                description: Tags defines a map of tags.
-                                type: object
-                            type: object
-                        required:
-                        - name
-                        type: object
-                    type: object
-                type: object
               cloudProviderConfigOverrides:
                 description: 'CloudProviderConfigOverrides is an optional set of configuration values that can be overridden in azure cloud provider config. This is only a subset of options that are available in azure cloud provider config. Some values for the cloud provider config are inferred from other parts of cluster api provider azure spec, and may not be available for overrides. See: https://kubernetes-sigs.github.io/cloud-provider-azure/install/configs Note: All cloud provider config values can be customized by creating the secret beforehand. CloudProviderConfigOverrides is only used when the secret is managed by the Azure Provider.'
                 properties:
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachinepools.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachinepools.yaml
index b5caa4a7d..24e7fca3a 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachinepools.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachinepools.yaml
@@ -608,7 +608,6 @@ spec:
                         - option
                         type: object
                       diskSizeGB:
-                        description: DiskSizeGB is the size in GB to assign to the OS disk. Will have a default of 30GB if not provided
                         format: int32
                         type: integer
                       managedDisk:
@@ -627,6 +626,7 @@ spec:
                       osType:
                         type: string
                     required:
+                    - diskSizeGB
                     - osType
                     type: object
                   securityProfile:
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachines.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachines.yaml
index 91b5baf76..152859a96 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachines.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachines.yaml
@@ -555,7 +555,6 @@ spec:
                     - option
                     type: object
                   diskSizeGB:
-                    description: DiskSizeGB is the size in GB to assign to the OS disk. Will have a default of 30GB if not provided
                     format: int32
                     type: integer
                   managedDisk:
@@ -574,6 +573,7 @@ spec:
                   osType:
                     type: string
                 required:
+                - diskSizeGB
                 - osType
                 type: object
               providerID:
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachinetemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachinetemplates.yaml
index 8282788ae..ba504acdc 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachinetemplates.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_azuremachinetemplates.yaml
@@ -452,7 +452,6 @@ spec:
                             - option
                             type: object
                           diskSizeGB:
-                            description: DiskSizeGB is the size in GB to assign to the OS disk. Will have a default of 30GB if not provided
                             format: int32
                             type: integer
                           managedDisk:
@@ -471,6 +470,7 @@ spec:
                           osType:
                             type: string
                         required:
+                        - diskSizeGB
                         - osType
                         type: object
                       providerID:
diff --git a/controllers/azurecluster_reconciler.go b/controllers/azurecluster_reconciler.go
index 7bf1ece88..436e78201 100644
--- a/controllers/azurecluster_reconciler.go
+++ b/controllers/azurecluster_reconciler.go
@@ -24,7 +24,6 @@ import (
 
 	"sigs.k8s.io/cluster-api-provider-azure/azure"
 	"sigs.k8s.io/cluster-api-provider-azure/azure/scope"
-	"sigs.k8s.io/cluster-api-provider-azure/azure/services/bastionhosts"
 	"sigs.k8s.io/cluster-api-provider-azure/azure/services/groups"
 	"sigs.k8s.io/cluster-api-provider-azure/azure/services/loadbalancers"
 	"sigs.k8s.io/cluster-api-provider-azure/azure/services/privatedns"
@@ -48,7 +47,6 @@ type azureClusterService struct {
 	publicIPSvc      azure.Reconciler
 	loadBalancerSvc  azure.Reconciler
 	privateDNSSvc    azure.Reconciler
-	bastionSvc       azure.Reconciler
 	skuCache         *resourceskus.Cache
 }
 
@@ -69,7 +67,6 @@ func newAzureClusterService(scope *scope.ClusterScope) (*azureClusterService, er
 		publicIPSvc:      publicips.New(scope),
 		loadBalancerSvc:  loadbalancers.New(scope),
 		privateDNSSvc:    privatedns.New(scope),
-		bastionSvc:       bastionhosts.New(scope),
 		skuCache:         skuCache,
 	}, nil
 }
@@ -120,10 +117,6 @@ func (s *azureClusterService) Reconcile(ctx context.Context) error {
 		return errors.Wrap(err, "failed to reconcile private dns")
 	}
 
-	if err := s.bastionSvc.Reconcile(ctx); err != nil {
-		return errors.Wrap(err, "failed to reconcile bastion")
-	}
-
 	return nil
 }
 
@@ -162,10 +155,6 @@ func (s *azureClusterService) Delete(ctx context.Context) error {
 				return errors.Wrap(err, "failed to delete virtual network")
 			}
 
-			if err := s.bastionSvc.Delete(ctx); err != nil {
-				return errors.Wrap(err, "failed to delete bastion")
-			}
-
 		} else {
 			return errors.Wrap(err, "failed to delete resource group")
 		}
diff --git a/controllers/helpers.go b/controllers/helpers.go
index e5405e8cf..788fc613f 100644
--- a/controllers/helpers.go
+++ b/controllers/helpers.go
@@ -187,8 +187,6 @@ func GetCloudProviderSecret(d azure.ClusterScoper, namespace, name string, owner
 	secret.Data = map[string][]byte{
 		"control-plane-azure.json": controlPlaneData,
 		"worker-node-azure.json":   workerNodeData,
-		// added for backwards compatibility
-		"azure.json": controlPlaneData,
 	}
 
 	return secret, nil
diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md
index 631f0c178..8e05c1d38 100644
--- a/docs/book/src/SUMMARY.md
+++ b/docs/book/src/SUMMARY.md
@@ -25,7 +25,6 @@
     - [Spot Virtual Machines](./topics/spot-vms.md)
     - [Virtual Networks](./topics/custom-vnet.md)
     - [Windows](./topics/windows.md)
-    - [SSH Access to nodes](./topics/ssh-access.md)
 - [Development](./developers/development.md)
     - [Kubernetes Developers](./developers/kubernetes-developers.md)
     - [Releasing](./developers/releasing.md)
diff --git a/docs/book/src/topics/cloud-provider-config.md b/docs/book/src/topics/cloud-provider-config.md
index 63c997e9b..60e494681 100644
--- a/docs/book/src/topics/cloud-provider-config.md
+++ b/docs/book/src/topics/cloud-provider-config.md
@@ -4,16 +4,7 @@ The [Azure cloud provider](https://github.com/kubernetes-sigs/cloud-provider-azu
 
 CAPZ automatically generates this file based on user-provided values in AzureMachineTemplate and AzureMachine. All AzureMachines in the same MachineDeployment or control plane will all share a single cloud provider secret, while AzureMachines created inidividually will have their own secret.
 
-For AzureMachineTemplate and standalone AzureMachines, the generated secret will have the name "${RESOURCE}-azure-json", where "${RESOURCE}" is the name of either the AzureMachineTemplate or AzureMachine. The secret will have two data fields: `control-plane-azure.json` and `worker-node-azure.json`, with the raw content for that file containing the control plane and worker node data respectively. When the secret `${RESOURCE}-azure-json` already exists in the same namespace as an AzureCluster and does not have the label `"${CLUSTER_NAME}": "owned"`, CAPZ will not generate the default described above. Instead it will directly use whatever the user provides in that secret.
-
-<aside class="note warning">
-
-<h1> Warning </h1>
-
-For backwards compatibility, the generated secret will also have the `azure.json` field with the control plane data.
-But, this is deprecated and will be removed in capz `v0.6.x`. It is recommended to use the `control-plane-azure.json` and `worker-node-azure.json` fields instead.
-
-</aside>
+For AzureMachineTemplate and standalone AzureMachines, the generated secret will have the name "${RESOURCE}-azure-json", where "${RESOURCE}" is the name of either the AzureMachineTemplate or AzureMachine. The secret will have one data field, `azure.json`, with the raw content for that file. When the secret `${RESOURCE}-azure-json` already exists in the same namespace as an AzureCluster and does not have the label `"${CLUSTER_NAME}": "owned"`, CAPZ will not generate the default described above. Instead it will directly use whatever the user provides in that secret.
 
 ### Overriding Cloud Provider Config
 
diff --git a/docs/book/src/topics/os-disk.md b/docs/book/src/topics/os-disk.md
index 30c9fce5b..1562d2b7b 100644
--- a/docs/book/src/topics/os-disk.md
+++ b/docs/book/src/topics/os-disk.md
@@ -21,8 +21,6 @@ See [Azure documentation on disk types](https://docs.microsoft.com/en-us/azure/v
 
 See [Introduction to Azure managed disks](https://docs.microsoft.com/en-us/azure/virtual-machines/managed-disks-overview) for more information on managed disks.
 
-If the optional field `diskSizeGB` is not provided, it will default to 30GB.
-
 ## Ephemeral OS
 
 Ephemeral OS uses local VM storage for changes to the OS disk.
diff --git a/docs/book/src/topics/ssh-access.md b/docs/book/src/topics/ssh-access.md
deleted file mode 100644
index 6d970bdc8..000000000
--- a/docs/book/src/topics/ssh-access.md
+++ /dev/null
@@ -1,171 +0,0 @@
-# SSH access to nodes
-
-This document describes how to get SSH access to virtual machines being part of a CAPZ cluster.
-
-In order to get SSH access to a Virtual Machine on Azure, two requirements have to be met:
-
-- get network-level access to the SSH service;
-- get authentication sorted.
-
-This documents describe some possible strategies to fulfil both requirements.
-
-## Network Access
-
-### Default behavior
-
-By default, `control plane` VMs have SSH access allowed from any source in their `Network Security Group`s. Also by default,
-VMs don't have a public IP address assigned. 
-
-To get SSH access to one of the `Control Plane` VMs you can use the `API Load Balancer`'s IP, because by default an `Inbound NAT Rule`
-is created to route traffic coming to the load balancer on TCP port 22 (the SSH port) to one of the nodes with role `master` in the workload cluster.
-
-This of course works only for clusters that are using a `Public` Load Balancer.
-
-In order to reach all other VMs, you can use the NATted control plane VM as a bastion host and use the private IP
-address for the other nodes.
-
-For example, let's consider this CAPZ cluster (using a Public Load Balancer) with two nodes:
-
-```
-NAME                        STATUS   ROLES    AGE    VERSION    INTERNAL-IP   EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION     CONTAINER-RUNTIME
-test1-control-plane-cn9lm   Ready    master   111m   v1.18.16   10.0.0.4      <none>        Ubuntu 18.04.5 LTS   5.4.0-1039-azure   containerd://1.4.3
-test1-md-0-scctm            Ready    <none>   109m   v1.18.16   10.1.0.4      <none>        Ubuntu 18.04.5 LTS   5.4.0-1039-azure   containerd://1.4.3
-```
-
-You can SSH to the control plane node using the load balancer's public DNS name:
-
-```
-$ kubectl get azurecluster test1 -o json| jq .spec.networkSpec.apiServerLB.frontendIPs[0].publicIP.dnsName
-test1-21192f78.eastus.cloudapp.azure.com
-
-$ ssh username@test1-21192f78.eastus.cloudapp.azure.com hostname
-test1-control-plane-cn9lm
-```
-
-As you can see, the Load Balancer routed the request to node `test1-control-plane-cn9lm` that is the only node with role `master` in this workload cluster.
-
-In order to SSH to node 'test1-md-0-scctm', you can use the other node as a bastion:
-
-```
-$ ssh -J username@test1-21192f78.eastus.cloudapp.azure.com username@10.1.0.4 hostname
-test1-md-0-scctm
-```
-
-Clusters using an `Internal` Load Balancer (private clusters) can't use this approach. Network-level SSH access to those clusters have to be made on the private IP address of VMs
-by first getting access to the Virtual Network. How to do that is out of the scope of this document.
-A possible alternative that works for private clusters as well is described in the next paragraph.
-
-### Azure Bastion
-
-A possible alternative to the process described above is to use the [`Azure Bastion`](https://azure.microsoft.com/en-us/services/azure-bastion/) feature.
-This approach works the same way for workload clusters using either type of `Load Balancers`.
-
-In order to enable `Azure Bastion` on a CAPZ workload cluster, edit the `AzureCluster` CR and set the `spec/bastionSpec/azureBastion` field.
-It is enough to set the field's value to the empty object `{}` and the default configuration settings will be used while deploying the `Azure Bastion`.
-
-For example this is an `AzureCluster` CR with the `Azure Bastion` feature enabled:
-
-```
-apiVersion: infrastructure.cluster.x-k8s.io/v1alpha4
-kind: AzureCluster
-metadata:
-  name: test1
-  namespace: default
-spec:
-  bastionSpec:
-    azureBastion: {}
-  ...
-```
-
-Once the `Azure Bastion` is deployed, it will be possible to SSH to any of the cluster VMs through the
-`Azure Portal`. Please follow the [official documentation](https://docs.microsoft.com/en-us/azure/bastion/bastion-overview)
-for a deeper explanation on how to do that.
-
-#### Advanced settings
-
-When the `AzureBastion` feature is enabled in a CAPZ cluster, 3 new resources will be deployed in the resource group:
-
-- The `Azure Bastion` resource;
-- A subnet named `AzureBastionSubnet` (the name is mandatory and can't be changed);
-- A public `IP address`.
-
-The default values for the new resources should work for most use cases, but if you need to customize them you can 
-provide your own values. Here is a detailed example:
-
-```
-apiVersion: infrastructure.cluster.x-k8s.io/v1alpha4
-kind: AzureCluster
-metadata:
-  name: test1
-  namespace: default
-spec:
-  bastionSpec:
-    azureBastion:
-      name: "..." // The name of the Azure Bastion, defaults to '<cluster name>-azure-bastion'
-      subnet:
-        name: "..." // The name of the Subnet. The only supported name is `AzureBastionSubnet` (this is an Azure limitation).
-        securityGroup: {} // No security group is assigned by default. You can choose to have one created and assigned by defining it. 
-      publicIP:
-        "name": "..." // The name of the Public IP, defaults to '<cluster name>-azure-bastion-pip'.
-```
-
-If you specify a security group to be associated with the Azure Bastion subnet, it needs to have some networking rules defined or
-the `Azure Bastion` resource creation will fail. Please refer to [the documentation](https://docs.microsoft.com/en-us/azure/bastion/bastion-nsg) for more details.
-
-## Authentication
-
-With the networking part sorted, we still have to work out a way of authenticating to the VMs via SSH.
-
-### Provisioning SSH keys using Machine Templates
-
-In order to add an SSH authorized key for user `username` and provide `sudo` access to the `control plane` VMs, you can adjust the `KubeadmControlPlane` CR
-as in the following example:
-
-```
-apiVersion: controlplane.cluster.x-k8s.io/v1alpha3
-kind: KubeadmControlPlane
-...
-spec:
-  ...
-  kubeadmConfigSpec:
-    ...
-    users:
-    - name: username
-      sshAuthorizedKeys:
-      - "ssh-rsa AAAA..."
-    files:
-    - content: "username ALL = (ALL) NOPASSWD: ALL"
-      owner: root:root
-      path: /etc/sudoers.d/username
-      permissions: "0440"
-    ...
-```
-
-Similarly, you can achieve the same result for `Machine Deployments` by customizing the `KubeadmConfigTemplate` CR: 
-
-```
-apiVersion: bootstrap.cluster.x-k8s.io/v1alpha3
-kind: KubeadmConfigTemplate
-metadata:
-  name: test1-md-0
-  namespace: default
-spec:
-  template:
-    spec:
-      files:
-      ...
-      - content: "username ALL = (ALL) NOPASSWD: ALL"
-        owner: root:root
-        path: /etc/sudoers.d/username
-        permissions: "0440"
-      ...
-      users:
-      - name: username
-        sshAuthorizedKeys:
-        - "ssh-rsa AAAA..."
-```
-
-### Setting SSH keys or passwords using the Azure Portal
-
-An alternative way of gaining SSH access to VMs on Azure is to set the `password` or `authorized key` via the `Azure Portal`.
-In the Portal, navigate to the `Virtual Machine` details page and find the `Reset password` function in the left pane.
diff --git a/docs/development.md b/docs/development.md
index 57d40eadb..65a62c798 100644
--- a/docs/development.md
+++ b/docs/development.md
@@ -109,7 +109,8 @@ Makefile targets and scripts are offered to work with go modules:
 
 ### Setting up the environment
 
-Your environment must have the Azure credentials as outlined in the [getting started prerequisites](https://capz.sigs.k8s.io/topics/getting-started.html) section.
+Your environment must have the Azure credentials as outlined in the [getting
+started prerequisites](./getting-started.md#Prerequisites) section.
 
 ### Using Tilt
 
diff --git a/exp/controllers/azuremanagedcluster_controller.go b/exp/controllers/azuremanagedcluster_controller.go
index 05c38f042..216dd09fc 100644
--- a/exp/controllers/azuremanagedcluster_controller.go
+++ b/exp/controllers/azuremanagedcluster_controller.go
@@ -57,22 +57,10 @@ type AzureManagedClusterReconciler struct {
 func (r *AzureManagedClusterReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
 	log := r.Log.WithValues("controller", "AzureManagedCluster")
 	azManagedCluster := &infrav1exp.AzureManagedCluster{}
-
-	// create mapper to transform incoming AzureManagedControlPlanes into AzureManagedCluster requests
-	azureManagedControlPlaneMapper, err := AzureManagedControlPlaneToAzureManagedClusterMapper(ctx, r.Client, log)
-	if err != nil {
-		return errors.Wrap(err, "failed to create AzureManagedControlPlane to AzureManagedClusters mapper")
-	}
-
 	c, err := ctrl.NewControllerManagedBy(mgr).
 		WithOptions(options).
 		For(azManagedCluster).
 		WithEventFilter(predicates.ResourceNotPausedAndHasFilterLabel(ctrl.LoggerFrom(ctx), r.WatchFilterValue)).
-		// watch AzureManagedControlPlane resources
-		Watches(
-			&source.Kind{Type: &infrav1exp.AzureManagedControlPlane{}},
-			handler.EnqueueRequestsFromMapFunc(azureManagedControlPlaneMapper),
-		).
 		Build(r)
 	if err != nil {
 		return errors.Wrap(err, "error creating controller")
@@ -153,9 +141,9 @@ func (r *AzureManagedClusterReconciler) Reconcile(ctx context.Context, req ctrl.
 		return reconcile.Result{}, errors.Wrap(err, "failed to init patch helper")
 	}
 
-	// Infrastructure must be ready before control plane. We should also enqueue
-	// requests from control plane to infra cluster to keep control plane endpoint accurate.
-	aksCluster.Status.Ready = true
+	// Match whatever the control plane says. We should also enqueue
+	// requests from control plane to infra cluster to keep this accurate
+	aksCluster.Status.Ready = controlPlane.Status.Ready
 	aksCluster.Spec.ControlPlaneEndpoint = controlPlane.Spec.ControlPlaneEndpoint
 
 	if err := patchhelper.Patch(ctx, aksCluster); err != nil {
diff --git a/exp/controllers/azuremanagedmachinepool_controller.go b/exp/controllers/azuremanagedmachinepool_controller.go
index d1c44e709..7d860fde0 100644
--- a/exp/controllers/azuremanagedmachinepool_controller.go
+++ b/exp/controllers/azuremanagedmachinepool_controller.go
@@ -78,10 +78,10 @@ func NewAzureManagedMachinePoolReconciler(client client.Client, log logr.Logger,
 func (r *AzureManagedMachinePoolReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
 	log := r.Log.WithValues("controller", "AzureManagedMachinePool")
 	azManagedMachinePool := &infrav1exp.AzureManagedMachinePool{}
-	// create mapper to transform incoming AzureManagedControlPlanes into AzureManagedMachinePool requests
-	azureManagedControlPlaneMapper, err := AzureManagedControlPlaneToAzureManagedMachinePoolsMapper(ctx, r.Client, mgr.GetScheme(), log)
+	// create mapper to transform incoming AzureManagedClusters into AzureManagedMachinePool requests
+	azureManagedClusterMapper, err := AzureManagedClusterToAzureManagedMachinePoolsMapper(ctx, r.Client, mgr.GetScheme(), log)
 	if err != nil {
-		return errors.Wrap(err, "failed to create AzureManagedControlPlane to AzureManagedMachinePools mapper")
+		return errors.Wrap(err, "failed to create AzureManagedCluster to AzureManagedMachinePools mapper")
 	}
 
 	c, err := ctrl.NewControllerManagedBy(mgr).
@@ -93,10 +93,10 @@ func (r *AzureManagedMachinePoolReconciler) SetupWithManager(ctx context.Context
 			&source.Kind{Type: &clusterv1exp.MachinePool{}},
 			handler.EnqueueRequestsFromMapFunc(util.MachineToInfrastructureMapFunc(infrav1exp.GroupVersion.WithKind("AzureManagedMachinePool"))),
 		).
-		// watch for changes in AzureManagedControlPlanes
+		// watch for changes in AzureManagedClusters
 		Watches(
-			&source.Kind{Type: &infrav1exp.AzureManagedControlPlane{}},
-			handler.EnqueueRequestsFromMapFunc(azureManagedControlPlaneMapper),
+			&source.Kind{Type: &infrav1exp.AzureManagedCluster{}},
+			handler.EnqueueRequestsFromMapFunc(azureManagedClusterMapper),
 		).
 		Build(r)
 	if err != nil {
@@ -255,18 +255,13 @@ func (r *AzureManagedMachinePoolReconciler) reconcileDelete(ctx context.Context,
 
 	scope.Logger.Info("Reconciling AzureManagedMachinePool delete")
 
-	if !scope.Cluster.DeletionTimestamp.IsZero() {
-		// Cluster was deleted, skip machine pool deletion and let AKS delete the whole cluster.
-		// So, remove the finalizer.
-		controllerutil.RemoveFinalizer(scope.InfraMachinePool, infrav1.ClusterFinalizer)
-	} else {
-		if err := r.createAzureManagedMachinePoolService(scope).Delete(ctx, scope); err != nil {
-			return reconcile.Result{}, errors.Wrapf(err, "error deleting AzureManagedMachinePool %s/%s", scope.InfraMachinePool.Namespace, scope.InfraMachinePool.Name)
-		}
-		// Machine pool successfully deleted, remove the finalizer.
-		controllerutil.RemoveFinalizer(scope.InfraMachinePool, infrav1.ClusterFinalizer)
+	if err := r.createAzureManagedMachinePoolService(scope).Delete(ctx, scope); err != nil {
+		return reconcile.Result{}, errors.Wrapf(err, "error deleting AzureManagedMachinePool %s/%s", scope.InfraMachinePool.Namespace, scope.InfraMachinePool.Name)
 	}
 
+	// Cluster is deleted so remove the finalizer.
+	controllerutil.RemoveFinalizer(scope.InfraMachinePool, infrav1.ClusterFinalizer)
+
 	if err := scope.PatchObject(ctx); err != nil {
 		return reconcile.Result{}, err
 	}
diff --git a/exp/controllers/azuremanagedcontrolplane_controller.go b/exp/controllers/azuremangedcontrolplane_controller.go
similarity index 88%
rename from exp/controllers/azuremanagedcontrolplane_controller.go
rename to exp/controllers/azuremangedcontrolplane_controller.go
index 07b6a0cb1..8cedd256c 100644
--- a/exp/controllers/azuremanagedcontrolplane_controller.go
+++ b/exp/controllers/azuremangedcontrolplane_controller.go
@@ -27,7 +27,6 @@ import (
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/client-go/tools/record"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha4"
-	clusterv1exp "sigs.k8s.io/cluster-api/exp/api/v1alpha4"
 	"sigs.k8s.io/cluster-api/util"
 	"sigs.k8s.io/cluster-api/util/annotations"
 	"sigs.k8s.io/cluster-api/util/predicates"
@@ -66,9 +65,6 @@ func (r *AzureManagedControlPlaneReconciler) SetupWithManager(ctx context.Contex
 		return errors.Wrap(err, "failed to create AzureManagedCluster to AzureManagedControlPlane mapper")
 	}
 
-	// map requests for machine pools corresponding to AzureManagedControlPlane's defaultPool back to the corresponding AzureManagedControlPlane.
-	azureManagedMachinePoolMapper := MachinePoolToAzureManagedControlPlaneMapFunc(ctx, r.Client, infrav1exp.GroupVersion.WithKind("AzureManagedControlPlane"), log)
-
 	c, err := ctrl.NewControllerManagedBy(mgr).
 		WithOptions(options).
 		For(azManagedControlPlane).
@@ -78,11 +74,6 @@ func (r *AzureManagedControlPlaneReconciler) SetupWithManager(ctx context.Contex
 			&source.Kind{Type: &infrav1exp.AzureManagedCluster{}},
 			handler.EnqueueRequestsFromMapFunc(azureManagedClusterMapper),
 		).
-		// watch MachinePool resources
-		Watches(
-			&source.Kind{Type: &clusterv1exp.MachinePool{}},
-			handler.EnqueueRequestsFromMapFunc(azureManagedMachinePoolMapper),
-		).
 		Build(r)
 	if err != nil {
 		return errors.Wrap(err, "error creating controller")
@@ -146,28 +137,6 @@ func (r *AzureManagedControlPlaneReconciler) Reconcile(ctx context.Context, req
 		return ctrl.Result{}, nil
 	}
 
-	// Handle deleted clusters
-	// needs to happen before trying to fetch the default pool to avoid circular deletion dependencies
-	if !azureControlPlane.DeletionTimestamp.IsZero() {
-		// Create the scope.
-		mcpScope, err := scope.NewManagedControlPlaneScope(scope.ManagedControlPlaneScopeParams{
-			Client:       r.Client,
-			Logger:       log,
-			Cluster:      cluster,
-			ControlPlane: azureControlPlane,
-			PatchTarget:  azureControlPlane,
-		})
-		if err != nil {
-			return reconcile.Result{}, errors.Errorf("failed to create scope: %+v", err)
-		}
-		defer func() {
-			if err := mcpScope.PatchObject(ctx); err != nil && reterr == nil {
-				reterr = err
-			}
-		}()
-		return r.reconcileDelete(ctx, mcpScope)
-	}
-
 	// fetch default pool
 	defaultPoolKey := client.ObjectKey{
 		Name:      azureControlPlane.Spec.DefaultPoolRef.Name,
@@ -213,6 +182,11 @@ func (r *AzureManagedControlPlaneReconciler) Reconcile(ctx context.Context, req
 		}
 	}()
 
+	// Handle deleted clusters
+	if !azureControlPlane.DeletionTimestamp.IsZero() {
+		return r.reconcileDelete(ctx, mcpScope)
+	}
+
 	// Handle non-deleted clusters
 	return r.reconcileNormal(ctx, mcpScope)
 }
diff --git a/exp/controllers/azuremangedcontrolplane_reconciler.go b/exp/controllers/azuremangedcontrolplane_reconciler.go
index 5dab2649d..d345c2001 100644
--- a/exp/controllers/azuremangedcontrolplane_reconciler.go
+++ b/exp/controllers/azuremangedcontrolplane_reconciler.go
@@ -147,11 +147,6 @@ func (r *azureManagedControlPlaneReconciler) Delete(ctx context.Context, scope *
 		return errors.Wrapf(err, "failed to delete managed cluster %s", scope.ControlPlane.Name)
 	}
 
-	scope.V(2).Info("Deleting virtual network")
-	if err := r.vnetSvc.Delete(ctx); err != nil {
-		return errors.Wrap(err, "failed to delete virtual network")
-	}
-
 	scope.V(2).Info("Deleting managed cluster resource group")
 	if err := r.groupsSvc.Delete(ctx); err != nil {
 		return errors.Wrap(err, "failed to delete managed cluster resource group")
diff --git a/exp/controllers/helpers.go b/exp/controllers/helpers.go
index d7fc8c7ec..9701c26a1 100644
--- a/exp/controllers/helpers.go
+++ b/exp/controllers/helpers.go
@@ -112,9 +112,9 @@ func AzureManagedClusterToAzureManagedMachinePoolsMapper(ctx context.Context, c
 			return nil
 		}
 
-		log = log.WithValues("AzureManagedCluster", azCluster.Name, "Namespace", azCluster.Namespace)
+		log = log.WithValues("AzureCluster", azCluster.Name, "Namespace", azCluster.Namespace)
 
-		// Don't handle deleted AzureManagedClusters
+		// Don't handle deleted AzureClusters
 		if !azCluster.ObjectMeta.DeletionTimestamp.IsZero() {
 			log.V(4).Info("AzureManagedCluster has a deletion timestamp, skipping mapping.")
 			return nil
@@ -145,59 +145,6 @@ func AzureManagedClusterToAzureManagedMachinePoolsMapper(ctx context.Context, c
 	}, nil
 }
 
-// AzureManagedControlPlaneToAzureManagedMachinePoolsMapper creates a mapping handler to transform AzureManagedControlPlanes into
-// AzureManagedMachinePools. The transform requires AzureManagedControlPlane to map to the owning Cluster, then from the
-// Cluster, collect the MachinePools belonging to the cluster, then finally projecting the infrastructure reference
-// to the AzureManagedMachinePools.
-func AzureManagedControlPlaneToAzureManagedMachinePoolsMapper(ctx context.Context, c client.Client, scheme *runtime.Scheme, log logr.Logger) (handler.MapFunc, error) {
-	gvk, err := apiutil.GVKForObject(new(infrav1exp.AzureManagedMachinePool), scheme)
-	if err != nil {
-		return nil, errors.Wrap(err, "failed to find GVK for AzureManagedMachinePool")
-	}
-
-	return func(o client.Object) []ctrl.Request {
-		ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultMappingTimeout)
-		defer cancel()
-
-		azControlPlane, ok := o.(*infrav1exp.AzureManagedControlPlane)
-		if !ok {
-			log.Error(errors.Errorf("expected an AzureManagedControlPlane, got %T instead", o.GetObjectKind()), "failed to map AzureManagedControlPlane")
-			return nil
-		}
-
-		log = log.WithValues("AzureManagedControlPlane", azControlPlane.Name, "Namespace", azControlPlane.Namespace)
-
-		// Don't handle deleted AzureManagedControlPlanes
-		if !azControlPlane.ObjectMeta.DeletionTimestamp.IsZero() {
-			log.V(4).Info("AzureManagedControlPlane has a deletion timestamp, skipping mapping.")
-			return nil
-		}
-
-		clusterName, ok := controllers.GetOwnerClusterName(azControlPlane.ObjectMeta)
-		if !ok {
-			log.Info("unable to get the owner cluster")
-			return nil
-		}
-
-		machineList := &clusterv1exp.MachinePoolList{}
-		machineList.SetGroupVersionKind(gvk)
-		// list all of the requested objects within the cluster namespace with the cluster name label
-		if err := c.List(ctx, machineList, client.InNamespace(azControlPlane.Namespace), client.MatchingLabels{clusterv1.ClusterLabelName: clusterName}); err != nil {
-			return nil
-		}
-
-		mapFunc := MachinePoolToInfrastructureMapFunc(gvk, log)
-		var results []ctrl.Request
-		for _, machine := range machineList.Items {
-			m := machine
-			azureMachines := mapFunc(&m)
-			results = append(results, azureMachines...)
-		}
-
-		return results
-	}, nil
-}
-
 // AzureManagedClusterToAzureManagedControlPlaneMapper creates a mapping handler to transform AzureManagedClusters into
 // AzureManagedControlPlane. The transform requires AzureManagedCluster to map to the owning Cluster, then from the
 // Cluster, collect the control plane infrastructure reference.
@@ -212,9 +159,9 @@ func AzureManagedClusterToAzureManagedControlPlaneMapper(ctx context.Context, c
 			return nil
 		}
 
-		log = log.WithValues("AzureManagedCluster", azCluster.Name, "Namespace", azCluster.Namespace)
+		log = log.WithValues("AzureCluster", azCluster.Name, "Namespace", azCluster.Namespace)
 
-		// Don't handle deleted AzureManagedClusters
+		// Don't handle deleted AzureClusters
 		if !azCluster.ObjectMeta.DeletionTimestamp.IsZero() {
 			log.V(4).Info("AzureManagedCluster has a deletion timestamp, skipping mapping.")
 			return nil
@@ -247,139 +194,6 @@ func AzureManagedClusterToAzureManagedControlPlaneMapper(ctx context.Context, c
 	}, nil
 }
 
-// AzureManagedControlPlaneToAzureManagedClusterMapper creates a mapping handler to transform AzureManagedClusters into
-// AzureManagedControlPlane. The transform requires AzureManagedCluster to map to the owning Cluster, then from the
-// Cluster, collect the control plane infrastructure reference.
-func AzureManagedControlPlaneToAzureManagedClusterMapper(ctx context.Context, c client.Client, log logr.Logger) (handler.MapFunc, error) {
-	return func(o client.Object) []ctrl.Request {
-		ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultMappingTimeout)
-		defer cancel()
-
-		azManagedControlPlane, ok := o.(*infrav1exp.AzureManagedControlPlane)
-		if !ok {
-			log.Error(errors.Errorf("expected an AzureManagedControlPlane, got %T instead", o), "failed to map AzureManagedControlPlane")
-			return nil
-		}
-
-		log = log.WithValues("AzureManagedControlPlane", azManagedControlPlane.Name, "Namespace", azManagedControlPlane.Namespace)
-
-		// Don't handle deleted AzureManagedControlPlanes
-		if !azManagedControlPlane.ObjectMeta.DeletionTimestamp.IsZero() {
-			log.V(4).Info("AzureManagedControlPlane has a deletion timestamp, skipping mapping.")
-			return nil
-		}
-
-		cluster, err := util.GetOwnerCluster(ctx, c, azManagedControlPlane.ObjectMeta)
-		if err != nil {
-			log.Error(err, "failed to get the owning cluster")
-			return nil
-		}
-
-		if cluster == nil {
-			log.Error(err, "cluster has not set owner ref yet")
-			return nil
-		}
-
-		ref := cluster.Spec.InfrastructureRef
-		if ref == nil || ref.Name == "" {
-			return nil
-		}
-
-		return []ctrl.Request{
-			{
-				NamespacedName: types.NamespacedName{
-					Namespace: ref.Namespace,
-					Name:      ref.Name,
-				},
-			},
-		}
-	}, nil
-}
-
-// MachinePoolToAzureManagedControlPlaneMapFunc returns a handler.MapFunc that watches for
-// MachinePool events and returns reconciliation requests for a control plane object.
-func MachinePoolToAzureManagedControlPlaneMapFunc(ctx context.Context, c client.Client, gvk schema.GroupVersionKind, log logr.Logger) handler.MapFunc {
-	return func(o client.Object) []reconcile.Request {
-		ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultMappingTimeout)
-		defer cancel()
-
-		machinePool, ok := o.(*clusterv1exp.MachinePool)
-		if !ok {
-			log.Info("expected a MachinePool, got wrong type", "type", fmt.Sprintf("%T", o))
-			return nil
-		}
-
-		cluster, err := util.GetClusterByName(ctx, c, machinePool.ObjectMeta.Namespace, machinePool.Spec.ClusterName)
-		if err != nil {
-			log.Error(err, "failed to get the owning cluster")
-			return nil
-		}
-
-		gk := gvk.GroupKind()
-		ref := cluster.Spec.ControlPlaneRef
-		// Return early if the GroupKind doesn't match what we expect.
-		controlPlaneGK := ref.GroupVersionKind().GroupKind()
-		if gk != controlPlaneGK {
-			log.Info("gk does not match", "gk", gk, "controlPlaneGK", controlPlaneGK)
-			return nil
-		}
-
-		controlPlaneKey := client.ObjectKey{
-			Name:      ref.Name,
-			Namespace: ref.Namespace,
-		}
-		controlPlane := &infrav1exp.AzureManagedControlPlane{}
-		if err := c.Get(ctx, controlPlaneKey, controlPlane); err != nil {
-			log.Error(err, "failed to fetch default pool reference")
-			// If we get here, we might want to reconcile but aren't sure.
-			// Do it anyway to be safe. Worst case we reconcile a few extra times with no-ops.
-			return []reconcile.Request{
-				{
-					NamespacedName: client.ObjectKey{
-						Namespace: ref.Namespace,
-						Name:      ref.Name,
-					},
-				},
-			}
-		}
-
-		infraMachinePoolRef := machinePool.Spec.Template.Spec.InfrastructureRef
-
-		gv, err := schema.ParseGroupVersion(infraMachinePoolRef.APIVersion)
-		if err != nil {
-			log.Error(err, "failed to parse group version")
-			// If we get here, we might want to reconcile but aren't sure.
-			// Do it anyway to be safe. Worst case we reconcile a few extra times with no-ops.
-			return []reconcile.Request{
-				{
-					NamespacedName: client.ObjectKey{
-						Namespace: ref.Namespace,
-						Name:      ref.Name,
-					},
-				},
-			}
-		}
-
-		nameMatches := controlPlane.Spec.DefaultPoolRef.Name == infraMachinePoolRef.Name
-		kindMatches := infraMachinePoolRef.Kind == "AzureManagedMachinePool"
-		groupMatches := controlPlaneGK.Group == gv.Group
-
-		if groupMatches && kindMatches && nameMatches {
-			return []reconcile.Request{
-				{
-					NamespacedName: client.ObjectKey{
-						Namespace: ref.Namespace,
-						Name:      ref.Name,
-					},
-				},
-			}
-		}
-
-		// By default, return nothing for a machine pool which is not the default pool for a control plane.
-		return nil
-	}
-}
-
 // MachinePoolToInfrastructureMapFunc returns a handler.MapFunc that watches for
 // MachinePool events and returns reconciliation requests for an infrastructure provider object.
 func MachinePoolToInfrastructureMapFunc(gvk schema.GroupVersionKind, log logr.Logger) handler.MapFunc {
diff --git a/templates/cluster-template-azure-bastion.yaml b/templates/cluster-template-azure-bastion.yaml
deleted file mode 100644
index aff6f076b..000000000
--- a/templates/cluster-template-azure-bastion.yaml
+++ /dev/null
@@ -1,197 +0,0 @@
-apiVersion: cluster.x-k8s.io/v1alpha4
-kind: Cluster
-metadata:
-  labels:
-    cni: calico
-  name: ${CLUSTER_NAME}
-  namespace: default
-spec:
-  clusterNetwork:
-    pods:
-      cidrBlocks:
-      - 192.168.0.0/16
-  controlPlaneRef:
-    apiVersion: controlplane.cluster.x-k8s.io/v1alpha4
-    kind: KubeadmControlPlane
-    name: ${CLUSTER_NAME}-control-plane
-  infrastructureRef:
-    apiVersion: infrastructure.cluster.x-k8s.io/v1alpha4
-    kind: AzureCluster
-    name: ${CLUSTER_NAME}
----
-apiVersion: infrastructure.cluster.x-k8s.io/v1alpha4
-kind: AzureCluster
-metadata:
-  name: ${CLUSTER_NAME}
-  namespace: default
-spec:
-  bastionSpec:
-    azureBastion: {}
-  location: ${AZURE_LOCATION}
-  networkSpec:
-    vnet:
-      name: ${AZURE_VNET_NAME:=${CLUSTER_NAME}-vnet}
-  resourceGroup: ${AZURE_RESOURCE_GROUP:=${CLUSTER_NAME}}
-  subscriptionID: ${AZURE_SUBSCRIPTION_ID}
----
-apiVersion: controlplane.cluster.x-k8s.io/v1alpha4
-kind: KubeadmControlPlane
-metadata:
-  name: ${CLUSTER_NAME}-control-plane
-  namespace: default
-spec:
-  kubeadmConfigSpec:
-    clusterConfiguration:
-      apiServer:
-        extraArgs:
-          cloud-config: /etc/kubernetes/azure.json
-          cloud-provider: azure
-        extraVolumes:
-        - hostPath: /etc/kubernetes/azure.json
-          mountPath: /etc/kubernetes/azure.json
-          name: cloud-config
-          readOnly: true
-        timeoutForControlPlane: 20m
-      controllerManager:
-        extraArgs:
-          allocate-node-cidrs: "false"
-          cloud-config: /etc/kubernetes/azure.json
-          cloud-provider: azure
-          cluster-name: ${CLUSTER_NAME}
-        extraVolumes:
-        - hostPath: /etc/kubernetes/azure.json
-          mountPath: /etc/kubernetes/azure.json
-          name: cloud-config
-          readOnly: true
-      etcd:
-        local:
-          dataDir: /var/lib/etcddisk/etcd
-    diskSetup:
-      filesystems:
-      - device: /dev/disk/azure/scsi1/lun0
-        extraOpts:
-        - -E
-        - lazy_itable_init=1,lazy_journal_init=1
-        filesystem: ext4
-        label: etcd_disk
-      - device: ephemeral0.1
-        filesystem: ext4
-        label: ephemeral0
-        replaceFS: ntfs
-      partitions:
-      - device: /dev/disk/azure/scsi1/lun0
-        layout: true
-        overwrite: false
-        tableType: gpt
-    files:
-    - contentFrom:
-        secret:
-          key: control-plane-azure.json
-          name: ${CLUSTER_NAME}-control-plane-azure-json
-      owner: root:root
-      path: /etc/kubernetes/azure.json
-      permissions: "0644"
-    initConfiguration:
-      nodeRegistration:
-        kubeletExtraArgs:
-          azure-container-registry-config: /etc/kubernetes/azure.json
-          cloud-config: /etc/kubernetes/azure.json
-          cloud-provider: azure
-        name: '{{ ds.meta_data["local_hostname"] }}'
-    joinConfiguration:
-      nodeRegistration:
-        kubeletExtraArgs:
-          azure-container-registry-config: /etc/kubernetes/azure.json
-          cloud-config: /etc/kubernetes/azure.json
-          cloud-provider: azure
-        name: '{{ ds.meta_data["local_hostname"] }}'
-    mounts:
-    - - LABEL=etcd_disk
-      - /var/lib/etcddisk
-  machineTemplate:
-    infrastructureRef:
-      apiVersion: infrastructure.cluster.x-k8s.io/v1alpha4
-      kind: AzureMachineTemplate
-      name: ${CLUSTER_NAME}-control-plane
-  replicas: ${CONTROL_PLANE_MACHINE_COUNT}
-  version: ${KUBERNETES_VERSION}
----
-apiVersion: infrastructure.cluster.x-k8s.io/v1alpha4
-kind: AzureMachineTemplate
-metadata:
-  name: ${CLUSTER_NAME}-control-plane
-  namespace: default
-spec:
-  template:
-    spec:
-      dataDisks:
-      - diskSizeGB: 256
-        lun: 0
-        nameSuffix: etcddisk
-      osDisk:
-        diskSizeGB: 128
-        osType: Linux
-      sshPublicKey: ${AZURE_SSH_PUBLIC_KEY_B64:=""}
-      vmSize: ${AZURE_CONTROL_PLANE_MACHINE_TYPE}
----
-apiVersion: cluster.x-k8s.io/v1alpha4
-kind: MachineDeployment
-metadata:
-  name: ${CLUSTER_NAME}-md-0
-  namespace: default
-spec:
-  clusterName: ${CLUSTER_NAME}
-  replicas: ${WORKER_MACHINE_COUNT}
-  selector:
-    matchLabels: null
-  template:
-    spec:
-      bootstrap:
-        configRef:
-          apiVersion: bootstrap.cluster.x-k8s.io/v1alpha4
-          kind: KubeadmConfigTemplate
-          name: ${CLUSTER_NAME}-md-0
-      clusterName: ${CLUSTER_NAME}
-      infrastructureRef:
-        apiVersion: infrastructure.cluster.x-k8s.io/v1alpha4
-        kind: AzureMachineTemplate
-        name: ${CLUSTER_NAME}-md-0
-      version: ${KUBERNETES_VERSION}
----
-apiVersion: infrastructure.cluster.x-k8s.io/v1alpha4
-kind: AzureMachineTemplate
-metadata:
-  name: ${CLUSTER_NAME}-md-0
-  namespace: default
-spec:
-  template:
-    spec:
-      osDisk:
-        diskSizeGB: 128
-        osType: Linux
-      sshPublicKey: ${AZURE_SSH_PUBLIC_KEY_B64:=""}
-      vmSize: ${AZURE_NODE_MACHINE_TYPE}
----
-apiVersion: bootstrap.cluster.x-k8s.io/v1alpha4
-kind: KubeadmConfigTemplate
-metadata:
-  name: ${CLUSTER_NAME}-md-0
-  namespace: default
-spec:
-  template:
-    spec:
-      files:
-      - contentFrom:
-          secret:
-            key: worker-node-azure.json
-            name: ${CLUSTER_NAME}-md-0-azure-json
-        owner: root:root
-        path: /etc/kubernetes/azure.json
-        permissions: "0644"
-      joinConfiguration:
-        nodeRegistration:
-          kubeletExtraArgs:
-            azure-container-registry-config: /etc/kubernetes/azure.json
-            cloud-config: /etc/kubernetes/azure.json
-            cloud-provider: azure
-          name: '{{ ds.meta_data["local_hostname"] }}'
diff --git a/templates/cluster-template-private.yaml b/templates/cluster-template-private.yaml
index 8bddb487c..cb42bc4ac 100644
--- a/templates/cluster-template-private.yaml
+++ b/templates/cluster-template-private.yaml
@@ -25,8 +25,6 @@ metadata:
   name: ${CLUSTER_NAME}
   namespace: default
 spec:
-  bastionSpec:
-    azureBastion: {}
   location: ${AZURE_LOCATION}
   networkSpec:
     apiServerLB:
diff --git a/templates/flavors/azure-bastion/kustomization.yaml b/templates/flavors/azure-bastion/kustomization.yaml
deleted file mode 100644
index f016f77b3..000000000
--- a/templates/flavors/azure-bastion/kustomization.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace: default
-resources:
-  - ../default
-patchesStrategicMerge:
-  - patches/azure-cluster.yaml
diff --git a/templates/flavors/azure-bastion/patches/azure-cluster.yaml b/templates/flavors/azure-bastion/patches/azure-cluster.yaml
deleted file mode 100644
index 03a0eda1f..000000000
--- a/templates/flavors/azure-bastion/patches/azure-cluster.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-apiVersion: infrastructure.cluster.x-k8s.io/v1alpha4
-kind: AzureCluster
-metadata:
-  name: ${CLUSTER_NAME}
-spec:
-  bastionSpec:
-    azureBastion: {}
diff --git a/templates/flavors/private/kustomization.yaml b/templates/flavors/private/kustomization.yaml
index 34e80105c..2489830e2 100644
--- a/templates/flavors/private/kustomization.yaml
+++ b/templates/flavors/private/kustomization.yaml
@@ -4,5 +4,4 @@ resources:
 patchesStrategicMerge:
   - patches/private-lb.yaml
   - patches/apiserver-host-dns.yaml
-  - patches/azure-bastion.yaml
 
diff --git a/templates/flavors/private/patches/azure-bastion.yaml b/templates/flavors/private/patches/azure-bastion.yaml
deleted file mode 100644
index 03a0eda1f..000000000
--- a/templates/flavors/private/patches/azure-bastion.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-apiVersion: infrastructure.cluster.x-k8s.io/v1alpha4
-kind: AzureCluster
-metadata:
-  name: ${CLUSTER_NAME}
-spec:
-  bastionSpec:
-    azureBastion: {}
diff --git a/tilt-provider.json b/tilt-provider.json
index f9df3b2bf..4684dc5d6 100644
--- a/tilt-provider.json
+++ b/tilt-provider.json
@@ -7,7 +7,7 @@
       "go.mod",
       "go.sum",
       "api",
-      "azure",
+      "cloud",
       "controllers",
       "pkg",
       "exp"


Test patch:
diff --git a/api/v1alpha4/azurecluster_default_test.go b/api/v1alpha4/azurecluster_default_test.go
index 8c40aab8e..830997383 100644
--- a/api/v1alpha4/azurecluster_default_test.go
+++ b/api/v1alpha4/azurecluster_default_test.go
@@ -910,206 +910,3 @@ func TestNodeOutboundLBDefaults(t *testing.T) {
 		})
 	}
 }
-
-func TestBastionDefault(t *testing.T) {
-	cases := map[string]struct {
-		cluster *AzureCluster
-		output  *AzureCluster
-	}{
-		"no bastion set": {
-			cluster: &AzureCluster{
-				ObjectMeta: metav1.ObjectMeta{
-					Name: "foo",
-				},
-				Spec: AzureClusterSpec{},
-			},
-			output: &AzureCluster{
-				ObjectMeta: metav1.ObjectMeta{
-					Name: "foo",
-				},
-				Spec: AzureClusterSpec{},
-			},
-		},
-		"azure bastion enabled with no settings": {
-			cluster: &AzureCluster{
-				ObjectMeta: metav1.ObjectMeta{
-					Name: "foo",
-				},
-				Spec: AzureClusterSpec{
-					BastionSpec: BastionSpec{
-						AzureBastion: &AzureBastion{},
-					},
-				},
-			},
-			output: &AzureCluster{
-				ObjectMeta: metav1.ObjectMeta{
-					Name: "foo",
-				},
-				Spec: AzureClusterSpec{
-					BastionSpec: BastionSpec{
-						AzureBastion: &AzureBastion{
-							Name: "foo-azure-bastion",
-							Subnet: SubnetSpec{
-								Name:       "AzureBastionSubnet",
-								CIDRBlocks: []string{DefaultAzureBastionSubnetCIDR},
-							},
-							PublicIP: PublicIPSpec{
-								Name: "foo-azure-bastion-pip",
-							},
-						},
-					},
-				},
-			},
-		},
-		"azure bastion enabled with name set": {
-			cluster: &AzureCluster{
-				ObjectMeta: metav1.ObjectMeta{
-					Name: "foo",
-				},
-				Spec: AzureClusterSpec{
-					BastionSpec: BastionSpec{
-						AzureBastion: &AzureBastion{
-							Name: "my-fancy-name",
-						},
-					},
-				},
-			},
-			output: &AzureCluster{
-				ObjectMeta: metav1.ObjectMeta{
-					Name: "foo",
-				},
-				Spec: AzureClusterSpec{
-					BastionSpec: BastionSpec{
-						AzureBastion: &AzureBastion{
-							Name: "my-fancy-name",
-							Subnet: SubnetSpec{
-								Name:       "AzureBastionSubnet",
-								CIDRBlocks: []string{DefaultAzureBastionSubnetCIDR},
-							},
-							PublicIP: PublicIPSpec{
-								Name: "foo-azure-bastion-pip",
-							},
-						},
-					},
-				},
-			},
-		},
-		"azure bastion enabled with subnet partially set": {
-			cluster: &AzureCluster{
-				ObjectMeta: metav1.ObjectMeta{
-					Name: "foo",
-				},
-				Spec: AzureClusterSpec{
-					BastionSpec: BastionSpec{
-						AzureBastion: &AzureBastion{
-							Subnet: SubnetSpec{},
-						},
-					},
-				},
-			},
-			output: &AzureCluster{
-				ObjectMeta: metav1.ObjectMeta{
-					Name: "foo",
-				},
-				Spec: AzureClusterSpec{
-					BastionSpec: BastionSpec{
-						AzureBastion: &AzureBastion{
-							Name: "foo-azure-bastion",
-							Subnet: SubnetSpec{
-								Name:       "AzureBastionSubnet",
-								CIDRBlocks: []string{DefaultAzureBastionSubnetCIDR},
-							},
-							PublicIP: PublicIPSpec{
-								Name: "foo-azure-bastion-pip",
-							},
-						},
-					},
-				},
-			},
-		},
-		"azure bastion enabled with subnet fully set": {
-			cluster: &AzureCluster{
-				ObjectMeta: metav1.ObjectMeta{
-					Name: "foo",
-				},
-				Spec: AzureClusterSpec{
-					BastionSpec: BastionSpec{
-						AzureBastion: &AzureBastion{
-							Subnet: SubnetSpec{
-								Name:       "my-superfancy-name",
-								CIDRBlocks: []string{"10.10.0.0/16"},
-							},
-						},
-					},
-				},
-			},
-			output: &AzureCluster{
-				ObjectMeta: metav1.ObjectMeta{
-					Name: "foo",
-				},
-				Spec: AzureClusterSpec{
-					BastionSpec: BastionSpec{
-						AzureBastion: &AzureBastion{
-							Name: "foo-azure-bastion",
-							Subnet: SubnetSpec{
-								Name:       "my-superfancy-name",
-								CIDRBlocks: []string{"10.10.0.0/16"},
-							},
-							PublicIP: PublicIPSpec{
-								Name: "foo-azure-bastion-pip",
-							},
-						},
-					},
-				},
-			},
-		},
-		"azure bastion enabled with public IP name set": {
-			cluster: &AzureCluster{
-				ObjectMeta: metav1.ObjectMeta{
-					Name: "foo",
-				},
-				Spec: AzureClusterSpec{
-					BastionSpec: BastionSpec{
-						AzureBastion: &AzureBastion{
-							PublicIP: PublicIPSpec{
-								Name: "my-ultrafancy-pip-name",
-							},
-						},
-					},
-				},
-			},
-			output: &AzureCluster{
-				ObjectMeta: metav1.ObjectMeta{
-					Name: "foo",
-				},
-				Spec: AzureClusterSpec{
-					BastionSpec: BastionSpec{
-						AzureBastion: &AzureBastion{
-							Name: "foo-azure-bastion",
-							Subnet: SubnetSpec{
-								Name:       "AzureBastionSubnet",
-								CIDRBlocks: []string{DefaultAzureBastionSubnetCIDR},
-							},
-							PublicIP: PublicIPSpec{
-								Name: "my-ultrafancy-pip-name",
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-
-	for name := range cases {
-		c := cases[name]
-		t.Run(name, func(t *testing.T) {
-			t.Parallel()
-			c.cluster.setBastionDefaults()
-			if !reflect.DeepEqual(c.cluster, c.output) {
-				expected, _ := json.MarshalIndent(c.output, "", "\t")
-				actual, _ := json.MarshalIndent(c.cluster, "", "\t")
-				t.Errorf("Expected %s, got %s", string(expected), string(actual))
-			}
-		})
-	}
-}
diff --git a/api/v1alpha4/azuremachine_validation_test.go b/api/v1alpha4/azuremachine_validation_test.go
index 1461e914e..a2b2be9cd 100644
--- a/api/v1alpha4/azuremachine_validation_test.go
+++ b/api/v1alpha4/azuremachine_validation_test.go
@@ -103,7 +103,7 @@ func TestAzureMachine_ValidateOSDisk(t *testing.T) {
 			name:    "valid ephemeral os disk spec",
 			wantErr: false,
 			osDisk: OSDisk{
-				DiskSizeGB:  to.Int32Ptr(30),
+				DiskSizeGB:  30,
 				CachingType: "None",
 				OSType:      "blah",
 				DiffDiskSettings: &DiffDiskSettings{
@@ -118,7 +118,7 @@ func TestAzureMachine_ValidateOSDisk(t *testing.T) {
 			name:    "byoc encryption with ephemeral os disk spec",
 			wantErr: true,
 			osDisk: OSDisk{
-				DiskSizeGB:  to.Int32Ptr(30),
+				DiskSizeGB:  30,
 				CachingType: "None",
 				OSType:      "blah",
 				DiffDiskSettings: &DiffDiskSettings{
@@ -154,42 +154,42 @@ func generateNegativeTestCases() []osDiskTestInput {
 	invalidDiskSpecs := []OSDisk{
 		{},
 		{
-			DiskSizeGB: to.Int32Ptr(0),
+			DiskSizeGB: 0,
 			OSType:     "blah",
 		},
 		{
-			DiskSizeGB: to.Int32Ptr(-10),
+			DiskSizeGB: -10,
 			OSType:     "blah",
 		},
 		{
-			DiskSizeGB: to.Int32Ptr(2050),
+			DiskSizeGB: 2050,
 			OSType:     "blah",
 		},
 		{
-			DiskSizeGB: to.Int32Ptr(20),
+			DiskSizeGB: 20,
 			OSType:     "",
 		},
 		{
-			DiskSizeGB:  to.Int32Ptr(30),
+			DiskSizeGB:  30,
 			OSType:      "blah",
 			ManagedDisk: &ManagedDiskParameters{},
 		},
 		{
-			DiskSizeGB: to.Int32Ptr(30),
+			DiskSizeGB: 30,
 			OSType:     "blah",
 			ManagedDisk: &ManagedDiskParameters{
 				StorageAccountType: "",
 			},
 		},
 		{
-			DiskSizeGB: to.Int32Ptr(30),
+			DiskSizeGB: 30,
 			OSType:     "blah",
 			ManagedDisk: &ManagedDiskParameters{
 				StorageAccountType: "invalid_type",
 			},
 		},
 		{
-			DiskSizeGB: to.Int32Ptr(30),
+			DiskSizeGB: 30,
 			OSType:     "blah",
 			ManagedDisk: &ManagedDiskParameters{
 				StorageAccountType: "Premium_LRS",
@@ -213,7 +213,7 @@ func generateNegativeTestCases() []osDiskTestInput {
 
 func generateValidOSDisk() OSDisk {
 	return OSDisk{
-		DiskSizeGB: to.Int32Ptr(30),
+		DiskSizeGB: 30,
 		OSType:     "Linux",
 		ManagedDisk: &ManagedDiskParameters{
 			StorageAccountType: "Premium_LRS",
diff --git a/api/v1alpha4/azuremachinetemplate_webhook_test.go b/api/v1alpha4/azuremachinetemplate_webhook_test.go
index 72ee3b66d..01201e03c 100644
--- a/api/v1alpha4/azuremachinetemplate_webhook_test.go
+++ b/api/v1alpha4/azuremachinetemplate_webhook_test.go
@@ -19,7 +19,6 @@ package v1alpha4
 import (
 	"testing"
 
-	"github.com/Azure/go-autorest/autorest/to"
 	. "github.com/onsi/gomega"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
@@ -44,7 +43,7 @@ func TestAzureMachineTemplate_ValidateUpdate(t *testing.T) {
 							FailureDomain: &failureDomain,
 							OSDisk: OSDisk{
 								OSType:     "type",
-								DiskSizeGB: to.Int32Ptr(11),
+								DiskSizeGB: 11,
 							},
 							DataDisks:    []DataDisk{},
 							SSHPublicKey: "",
@@ -60,7 +59,7 @@ func TestAzureMachineTemplate_ValidateUpdate(t *testing.T) {
 							FailureDomain: &failureDomain,
 							OSDisk: OSDisk{
 								OSType:     "type",
-								DiskSizeGB: to.Int32Ptr(11),
+								DiskSizeGB: 11,
 							},
 							DataDisks:    []DataDisk{},
 							SSHPublicKey: "",
@@ -80,7 +79,7 @@ func TestAzureMachineTemplate_ValidateUpdate(t *testing.T) {
 							FailureDomain: &failureDomain,
 							OSDisk: OSDisk{
 								OSType:     "type",
-								DiskSizeGB: to.Int32Ptr(11),
+								DiskSizeGB: 11,
 							},
 							DataDisks:    []DataDisk{},
 							SSHPublicKey: "",
@@ -99,7 +98,7 @@ func TestAzureMachineTemplate_ValidateUpdate(t *testing.T) {
 							FailureDomain: &failureDomain,
 							OSDisk: OSDisk{
 								OSType:     "type",
-								DiskSizeGB: to.Int32Ptr(11),
+								DiskSizeGB: 11,
 							},
 							DataDisks:    []DataDisk{},
 							SSHPublicKey: "",
diff --git a/azure/scope/clients_test.go b/azure/scope/clients_test.go
index 977fe23fb..8df97f8f8 100644
--- a/azure/scope/clients_test.go
+++ b/azure/scope/clients_test.go
@@ -19,8 +19,6 @@ package scope
 import (
 	"testing"
 
-	"github.com/Azure/go-autorest/autorest"
-
 	. "github.com/onsi/gomega"
 )
 
@@ -68,9 +66,7 @@ func TestGettingEnvironment(t *testing.T) {
 		}}
 	for name, test := range tests {
 		t.Run(name, func(t *testing.T) {
-			c := AzureClients{
-				Authorizer: autorest.NullAuthorizer{},
-			}
+			c := AzureClients{}
 			err := c.setCredentials("1234", test.azureEnv)
 			if test.expectedError {
 				g.Expect(err).To(HaveOccurred())
diff --git a/azure/services/bastionhosts/bastionhosts_test.go b/azure/services/bastionhosts/bastionhosts_test.go
index 645e642a6..3a5b56db7 100644
--- a/azure/services/bastionhosts/bastionhosts_test.go
+++ b/azure/services/bastionhosts/bastionhosts_test.go
@@ -22,8 +22,6 @@ import (
 	"testing"
 
 	. "github.com/onsi/gomega"
-
-	"sigs.k8s.io/cluster-api-provider-azure/api/v1alpha4"
 	"sigs.k8s.io/cluster-api-provider-azure/azure"
 	mock_bastionhosts "sigs.k8s.io/cluster-api-provider-azure/azure/services/bastionhosts/mocks_bastionhosts"
 	"sigs.k8s.io/cluster-api-provider-azure/azure/services/publicips/mock_publicips"
@@ -60,13 +58,11 @@ func TestReconcileBastionHosts(t *testing.T) {
 				mSubnet *mock_subnets.MockClientMockRecorder,
 				mPublicIP *mock_publicips.MockClientMockRecorder) {
 				s.V(gomock.AssignableToTypeOf(2)).AnyTimes().Return(klogr.New())
-				s.BastionSpec().Return(azure.BastionSpec{
-					AzureBastion: &azure.AzureBastionSpec{
-						Name:     "my-bastion",
-						VNetName: "my-vnet",
-						SubnetSpec: v1alpha4.SubnetSpec{
-							Name: "my-subnet",
-						},
+				s.BastionSpecs().Return([]azure.BastionSpec{
+					{
+						Name:         "my-bastion",
+						VNetName:     "my-vnet",
+						SubnetName:   "my-subnet",
 						PublicIPName: "my-publicip",
 					},
 				})
@@ -83,13 +79,11 @@ func TestReconcileBastionHosts(t *testing.T) {
 				mSubnet *mock_subnets.MockClientMockRecorder,
 				mPublicIP *mock_publicips.MockClientMockRecorder) {
 				s.V(gomock.AssignableToTypeOf(2)).AnyTimes().Return(klogr.New())
-				s.BastionSpec().Return(azure.BastionSpec{
-					AzureBastion: &azure.AzureBastionSpec{
-						Name:     "my-bastion",
-						VNetName: "my-vnet",
-						SubnetSpec: v1alpha4.SubnetSpec{
-							Name: "my-subnet",
-						},
+				s.BastionSpecs().Return([]azure.BastionSpec{
+					{
+						Name:         "my-bastion",
+						VNetName:     "my-vnet",
+						SubnetName:   "my-subnet",
 						PublicIPName: "my-publicip",
 					},
 				})
@@ -108,13 +102,11 @@ func TestReconcileBastionHosts(t *testing.T) {
 				mSubnet *mock_subnets.MockClientMockRecorder,
 				mPublicIP *mock_publicips.MockClientMockRecorder) {
 				s.V(gomock.AssignableToTypeOf(2)).AnyTimes().Return(klogr.New())
-				s.BastionSpec().Return(azure.BastionSpec{
-					AzureBastion: &azure.AzureBastionSpec{
-						Name:     "my-bastion",
-						VNetName: "my-vnet",
-						SubnetSpec: v1alpha4.SubnetSpec{
-							Name: "my-subnet",
-						},
+				s.BastionSpecs().Return([]azure.BastionSpec{
+					{
+						Name:         "my-bastion",
+						VNetName:     "my-vnet",
+						SubnetName:   "my-subnet",
 						PublicIPName: "my-publicip",
 					},
 				})
@@ -135,13 +127,11 @@ func TestReconcileBastionHosts(t *testing.T) {
 				mSubnet *mock_subnets.MockClientMockRecorder,
 				mPublicIP *mock_publicips.MockClientMockRecorder) {
 				s.V(gomock.AssignableToTypeOf(2)).AnyTimes().Return(klogr.New())
-				s.BastionSpec().Return(azure.BastionSpec{
-					AzureBastion: &azure.AzureBastionSpec{
-						Name:     "my-bastion",
-						VNetName: "my-vnet",
-						SubnetSpec: v1alpha4.SubnetSpec{
-							Name: "my-subnet",
-						},
+				s.BastionSpecs().Return([]azure.BastionSpec{
+					{
+						Name:         "my-bastion",
+						VNetName:     "my-vnet",
+						SubnetName:   "my-subnet",
 						PublicIPName: "my-publicip",
 					},
 				})
@@ -161,13 +151,11 @@ func TestReconcileBastionHosts(t *testing.T) {
 				mSubnet *mock_subnets.MockClientMockRecorder,
 				mPublicIP *mock_publicips.MockClientMockRecorder) {
 				s.V(gomock.AssignableToTypeOf(2)).AnyTimes().Return(klogr.New())
-				s.BastionSpec().Return(azure.BastionSpec{
-					AzureBastion: &azure.AzureBastionSpec{
-						Name:     "my-bastion",
-						VNetName: "my-vnet",
-						SubnetSpec: v1alpha4.SubnetSpec{
-							Name: "my-subnet",
-						},
+				s.BastionSpecs().Return([]azure.BastionSpec{
+					{
+						Name:         "my-bastion",
+						VNetName:     "my-vnet",
+						SubnetName:   "my-subnet",
 						PublicIPName: "my-publicip",
 					},
 				})
@@ -191,13 +179,11 @@ func TestReconcileBastionHosts(t *testing.T) {
 				mSubnet *mock_subnets.MockClientMockRecorder,
 				mPublicIP *mock_publicips.MockClientMockRecorder) {
 				s.V(gomock.AssignableToTypeOf(2)).AnyTimes().Return(klogr.New())
-				s.BastionSpec().Return(azure.BastionSpec{
-					AzureBastion: &azure.AzureBastionSpec{
-						Name:     "my-bastion",
-						VNetName: "my-vnet",
-						SubnetSpec: v1alpha4.SubnetSpec{
-							Name: "my-subnet",
-						},
+				s.BastionSpecs().Return([]azure.BastionSpec{
+					{
+						Name:         "my-bastion",
+						VNetName:     "my-vnet",
+						SubnetName:   "my-subnet",
 						PublicIPName: "my-publicip",
 					},
 				})
@@ -213,19 +199,17 @@ func TestReconcileBastionHosts(t *testing.T) {
 		},
 		{
 			name:          "fail to create a bastion",
-			expectedError: "error creating Azure Bastion: cannot create Azure Bastion: #: Internal Server Error: StatusCode=500",
+			expectedError: "cannot create bastion host: #: Internal Server Error: StatusCode=500",
 			expect: func(s *mock_bastionhosts.MockBastionScopeMockRecorder,
 				m *mock_bastionhosts.MockclientMockRecorder,
 				mSubnet *mock_subnets.MockClientMockRecorder,
 				mPublicIP *mock_publicips.MockClientMockRecorder) {
 				s.V(gomock.AssignableToTypeOf(2)).AnyTimes().Return(klogr.New())
-				s.BastionSpec().Return(azure.BastionSpec{
-					AzureBastion: &azure.AzureBastionSpec{
-						Name:     "my-bastion",
-						VNetName: "my-vnet",
-						SubnetSpec: v1alpha4.SubnetSpec{
-							Name: "my-subnet",
-						},
+				s.BastionSpecs().Return([]azure.BastionSpec{
+					{
+						Name:         "my-bastion",
+						VNetName:     "my-vnet",
+						SubnetName:   "my-subnet",
 						PublicIPName: "my-publicip",
 					},
 				})
@@ -233,8 +217,8 @@ func TestReconcileBastionHosts(t *testing.T) {
 				s.Location().AnyTimes().Return("fake-location")
 				s.ClusterName().AnyTimes().Return("fake-cluster")
 				gomock.InOrder(
-					mPublicIP.Get(gomockinternal.AContext(), "my-rg", "my-publicip").Return(network.PublicIPAddress{}, nil),
 					mSubnet.Get(gomockinternal.AContext(), "my-rg", "my-vnet", "my-subnet").Return(network.Subnet{}, nil),
+					mPublicIP.Get(gomockinternal.AContext(), "my-rg", "my-publicip").Return(network.PublicIPAddress{}, nil),
 					m.CreateOrUpdate(gomockinternal.AContext(), "my-rg", "my-bastion", gomock.AssignableToTypeOf(network.BastionHost{})).Return(autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 500}, "Internal Server Error")),
 				)
 			},
@@ -292,18 +276,23 @@ func TestDeleteBastionHost(t *testing.T) {
 				mSubnet *mock_subnets.MockClientMockRecorder,
 				mPublicIP *mock_publicips.MockClientMockRecorder) {
 				s.V(gomock.AssignableToTypeOf(2)).AnyTimes().Return(klogr.New())
-				s.BastionSpec().Return(azure.BastionSpec{
-					AzureBastion: &azure.AzureBastionSpec{
-						Name:     "my-bastionhost",
-						VNetName: "my-vnet",
-						SubnetSpec: v1alpha4.SubnetSpec{
-							Name: "my-subnet",
-						},
+				s.BastionSpecs().Return([]azure.BastionSpec{
+					{
+						Name:         "my-bastionhost",
+						VNetName:     "my-vnet",
+						SubnetName:   "my-subnet",
+						PublicIPName: "my-publicip",
+					},
+					{
+						Name:         "my-bastionhost1",
+						VNetName:     "my-vnet",
+						SubnetName:   "my-subnet",
 						PublicIPName: "my-publicip",
 					},
 				})
 				s.ResourceGroup().AnyTimes().Return("my-rg")
 				m.Delete(gomockinternal.AContext(), "my-rg", "my-bastionhost")
+				m.Delete(gomockinternal.AContext(), "my-rg", "my-bastionhost1")
 			},
 		},
 		{
@@ -314,13 +303,17 @@ func TestDeleteBastionHost(t *testing.T) {
 				mSubnet *mock_subnets.MockClientMockRecorder,
 				mPublicIP *mock_publicips.MockClientMockRecorder) {
 				s.V(gomock.AssignableToTypeOf(2)).AnyTimes().Return(klogr.New())
-				s.BastionSpec().Return(azure.BastionSpec{
-					AzureBastion: &azure.AzureBastionSpec{
-						Name:     "my-bastionhost",
-						VNetName: "my-vnet",
-						SubnetSpec: v1alpha4.SubnetSpec{
-							Name: "my-subnet",
-						},
+				s.BastionSpecs().Return([]azure.BastionSpec{
+					{
+						Name:         "my-bastionhost",
+						VNetName:     "my-vnet",
+						SubnetName:   "my-subnet",
+						PublicIPName: "my-publicip",
+					},
+					{
+						Name:         "my-bastionhost1",
+						VNetName:     "my-vnet",
+						SubnetName:   "my-subnet",
 						PublicIPName: "my-publicip",
 					},
 				})
@@ -332,19 +325,17 @@ func TestDeleteBastionHost(t *testing.T) {
 		},
 		{
 			name:          "bastion host deletion fails",
-			expectedError: "error deleting Azure Bastion: failed to delete Azure Bastion my-bastionhost in resource group my-rg: #: Internal Server Error: StatusCode=500",
+			expectedError: "failed to delete Bastion Host my-bastionhost in resource group my-rg: #: Internal Server Error: StatusCode=500",
 			expect: func(s *mock_bastionhosts.MockBastionScopeMockRecorder,
 				m *mock_bastionhosts.MockclientMockRecorder,
 				mSubnet *mock_subnets.MockClientMockRecorder,
 				mPublicIP *mock_publicips.MockClientMockRecorder) {
 				s.V(gomock.AssignableToTypeOf(2)).AnyTimes().Return(klogr.New())
-				s.BastionSpec().Return(azure.BastionSpec{
-					AzureBastion: &azure.AzureBastionSpec{
-						Name:     "my-bastionhost",
-						VNetName: "my-vnet",
-						SubnetSpec: v1alpha4.SubnetSpec{
-							Name: "my-subnet",
-						},
+				s.BastionSpecs().Return([]azure.BastionSpec{
+					{
+						Name:         "my-bastionhost",
+						VNetName:     "my-vnet",
+						SubnetName:   "my-subnet",
 						PublicIPName: "my-publicip",
 					},
 				})
diff --git a/azure/services/disks/disks_test.go b/azure/services/disks/disks_test.go
index 65e1a5edc..810ce3208 100644
--- a/azure/services/disks/disks_test.go
+++ b/azure/services/disks/disks_test.go
@@ -22,7 +22,6 @@ import (
 	"testing"
 
 	"github.com/Azure/go-autorest/autorest"
-	"github.com/Azure/go-autorest/autorest/to"
 	"github.com/golang/mock/gomock"
 	. "github.com/onsi/gomega"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -211,7 +210,7 @@ func TestDiskSpecs(t *testing.T) {
 				},
 				Spec: infrav1.AzureMachineSpec{
 					OSDisk: infrav1.OSDisk{
-						DiskSizeGB: to.Int32Ptr(30),
+						DiskSizeGB: 30,
 						OSType:     "Linux",
 					},
 				},
diff --git a/azure/services/routetables/routetables_test.go b/azure/services/routetables/routetables_test.go
index 970886354..989c95f91 100644
--- a/azure/services/routetables/routetables_test.go
+++ b/azure/services/routetables/routetables_test.go
@@ -142,6 +142,14 @@ func TestReconcileRouteTables(t *testing.T) {
 					Name: to.StringPtr("my-cp-routetable"),
 					ID:   to.StringPtr("1"),
 				}, nil)
+				s.SetSubnet(infrav1.SubnetSpec{
+					Name: "control-plane-subnet",
+					Role: infrav1.SubnetControlPlane,
+					RouteTable: infrav1.RouteTable{
+						ID:   "1",
+						Name: "my-cp-routetable",
+					},
+				}).Times(1)
 				s.NodeSubnet().AnyTimes().Return(infrav1.SubnetSpec{Name: "node-subnet", Role: infrav1.SubnetNode})
 				s.NodeRouteTable().AnyTimes().Return(infrav1.RouteTable{Name: "my-node-routetable"})
 				s.ResourceGroup().AnyTimes().Return("my-rg")
@@ -149,6 +157,14 @@ func TestReconcileRouteTables(t *testing.T) {
 					Name: to.StringPtr("my-node-routetable"),
 					ID:   to.StringPtr("2"),
 				}, nil)
+				s.SetSubnet(infrav1.SubnetSpec{
+					Name: "node-subnet",
+					Role: infrav1.SubnetNode,
+					RouteTable: infrav1.RouteTable{
+						ID:   "2",
+						Name: "my-node-routetable",
+					},
+				}).Times(1)
 			},
 		},
 		{
diff --git a/azure/services/scalesets/scalesets_test.go b/azure/services/scalesets/scalesets_test.go
index 57345fca0..f031e8f37 100644
--- a/azure/services/scalesets/scalesets_test.go
+++ b/azure/services/scalesets/scalesets_test.go
@@ -807,7 +807,7 @@ func newDefaultVMSSSpec() azure.ScaleSetSpec {
 		SSHKeyData: "ZmFrZXNzaGtleQo=",
 		OSDisk: infrav1.OSDisk{
 			OSType:     "Linux",
-			DiskSizeGB: to.Int32Ptr(120),
+			DiskSizeGB: 120,
 			ManagedDisk: &infrav1.ManagedDiskParameters{
 				StorageAccountType: "Premium_LRS",
 			},
diff --git a/azure/services/subnets/subnets_test.go b/azure/services/subnets/subnets_test.go
index a300897bd..3b45f4609 100644
--- a/azure/services/subnets/subnets_test.go
+++ b/azure/services/subnets/subnets_test.go
@@ -236,6 +236,12 @@ func TestReconcileSubnets(t *testing.T) {
 							},
 						},
 					}, nil)
+				s.SetSubnet(infrav1.SubnetSpec{
+					ID:         "subnet-id",
+					Name:       "my-subnet",
+					Role:       infrav1.SubnetNode,
+					CIDRBlocks: []string{"10.0.0.0/16"},
+				}).Times(1)
 				m.Get(gomockinternal.AContext(), "", "my-vnet", "my-subnet-1").
 					Return(network.Subnet{
 						ID:   to.StringPtr("subnet-id-1"),
@@ -252,6 +258,12 @@ func TestReconcileSubnets(t *testing.T) {
 							},
 						},
 					}, nil)
+				s.SetSubnet(infrav1.SubnetSpec{
+					ID:         "subnet-id-1",
+					Name:       "my-subnet-1",
+					Role:       infrav1.SubnetControlPlane,
+					CIDRBlocks: []string{"10.2.0.0/16"},
+				}).Times(1)
 			},
 		},
 		{
@@ -309,6 +321,12 @@ func TestReconcileSubnets(t *testing.T) {
 							},
 						},
 					}, nil)
+				s.SetSubnet(infrav1.SubnetSpec{
+					ID:         "subnet-id",
+					Name:       "my-subnet",
+					Role:       infrav1.SubnetNode,
+					CIDRBlocks: []string{"10.0.0.0/16", "2001:1234:5678:9abd::/64"},
+				}).Times(1)
 				m.Get(gomockinternal.AContext(), "", "my-vnet", "my-ipv6-subnet-cp").
 					Return(network.Subnet{
 						ID:   to.StringPtr("subnet-id-1"),
@@ -328,6 +346,12 @@ func TestReconcileSubnets(t *testing.T) {
 							},
 						},
 					}, nil)
+				s.SetSubnet(infrav1.SubnetSpec{
+					ID:         "subnet-id-1",
+					Name:       "my-subnet-1",
+					Role:       infrav1.SubnetControlPlane,
+					CIDRBlocks: []string{"10.2.0.0/16", "2001:1234:5678:9abc::/64"},
+				}).Times(1)
 			},
 		},
 	}
diff --git a/azure/services/virtualmachines/virtualmachines_test.go b/azure/services/virtualmachines/virtualmachines_test.go
index f62d43ce6..ac37fb2de 100644
--- a/azure/services/virtualmachines/virtualmachines_test.go
+++ b/azure/services/virtualmachines/virtualmachines_test.go
@@ -296,7 +296,7 @@ func TestReconcileVM(t *testing.T) {
 					Identity:   infrav1.VMIdentityNone,
 					OSDisk: infrav1.OSDisk{
 						OSType:     "Linux",
-						DiskSizeGB: to.Int32Ptr(128),
+						DiskSizeGB: 128,
 						ManagedDisk: &infrav1.ManagedDiskParameters{
 							StorageAccountType: "Premium_LRS",
 						},
@@ -709,7 +709,7 @@ func TestReconcileVM(t *testing.T) {
 					Identity:   infrav1.VMIdentityNone,
 					OSDisk: infrav1.OSDisk{
 						OSType:     "Windows",
-						DiskSizeGB: to.Int32Ptr(128),
+						DiskSizeGB: 128,
 						ManagedDisk: &infrav1.ManagedDiskParameters{
 							StorageAccountType: "Premium_LRS",
 						},
@@ -947,7 +947,7 @@ func TestReconcileVM(t *testing.T) {
 					Identity:   infrav1.VMIdentityNone,
 					OSDisk: infrav1.OSDisk{
 						OSType:     "Linux",
-						DiskSizeGB: to.Int32Ptr(128),
+						DiskSizeGB: 128,
 						ManagedDisk: &infrav1.ManagedDiskParameters{
 							StorageAccountType: "Premium_LRS",
 						},
@@ -1230,7 +1230,7 @@ func TestReconcileVM(t *testing.T) {
 					Identity:   infrav1.VMIdentityNone,
 					OSDisk: infrav1.OSDisk{
 						OSType:     "Linux",
-						DiskSizeGB: to.Int32Ptr(128),
+						DiskSizeGB: 128,
 						ManagedDisk: &infrav1.ManagedDiskParameters{
 							StorageAccountType: "Premium_LRS",
 						},
@@ -1297,7 +1297,7 @@ func TestReconcileVM(t *testing.T) {
 					Identity:   infrav1.VMIdentityNone,
 					OSDisk: infrav1.OSDisk{
 						OSType:     "Linux",
-						DiskSizeGB: to.Int32Ptr(128),
+						DiskSizeGB: 128,
 						ManagedDisk: &infrav1.ManagedDiskParameters{
 							StorageAccountType: "Premium_LRS",
 						},
@@ -1364,7 +1364,7 @@ func TestReconcileVM(t *testing.T) {
 					Identity:   infrav1.VMIdentityNone,
 					OSDisk: infrav1.OSDisk{
 						OSType:     "Linux",
-						DiskSizeGB: to.Int32Ptr(128),
+						DiskSizeGB: 128,
 						ManagedDisk: &infrav1.ManagedDiskParameters{
 							StorageAccountType: "Premium_LRS",
 						},
@@ -1438,7 +1438,7 @@ func TestReconcileVM(t *testing.T) {
 					Identity:   infrav1.VMIdentityNone,
 					OSDisk: infrav1.OSDisk{
 						OSType:     "Linux",
-						DiskSizeGB: to.Int32Ptr(128),
+						DiskSizeGB: 128,
 						ManagedDisk: &infrav1.ManagedDiskParameters{
 							StorageAccountType: "Premium_LRS",
 						},
@@ -1603,7 +1603,7 @@ func TestReconcileVM(t *testing.T) {
 					Identity:   infrav1.VMIdentityNone,
 					OSDisk: infrav1.OSDisk{
 						OSType:     "Linux",
-						DiskSizeGB: to.Int32Ptr(128),
+						DiskSizeGB: 128,
 						ManagedDisk: &infrav1.ManagedDiskParameters{
 							StorageAccountType: "Premium_LRS",
 						},
diff --git a/controllers/azurecluster_reconciler_test.go b/controllers/azurecluster_reconciler_test.go
index 45af03e1f..f4e807388 100644
--- a/controllers/azurecluster_reconciler_test.go
+++ b/controllers/azurecluster_reconciler_test.go
@@ -33,7 +33,7 @@ import (
 	gomockinternal "sigs.k8s.io/cluster-api-provider-azure/internal/test/matchers/gomock"
 )
 
-type expect func(grp *mocks.MockReconcilerMockRecorder, vnet *mocks.MockReconcilerMockRecorder, sg *mocks.MockReconcilerMockRecorder, rt *mocks.MockReconcilerMockRecorder, sn *mocks.MockReconcilerMockRecorder, pip *mocks.MockReconcilerMockRecorder, lb *mocks.MockReconcilerMockRecorder, dns *mocks.MockReconcilerMockRecorder, bastion *mocks.MockReconcilerMockRecorder)
+type expect func(grp *mocks.MockReconcilerMockRecorder, vnet *mocks.MockReconcilerMockRecorder, sg *mocks.MockReconcilerMockRecorder, rt *mocks.MockReconcilerMockRecorder, sn *mocks.MockReconcilerMockRecorder, pip *mocks.MockReconcilerMockRecorder, lb *mocks.MockReconcilerMockRecorder, dns *mocks.MockReconcilerMockRecorder)
 
 func TestAzureClusterReconcilerDelete(t *testing.T) {
 	cases := map[string]struct {
@@ -42,21 +42,21 @@ func TestAzureClusterReconcilerDelete(t *testing.T) {
 	}{
 		"Resource Group is deleted successfully": {
 			expectedError: "",
-			expect: func(grp *mocks.MockReconcilerMockRecorder, vnet *mocks.MockReconcilerMockRecorder, sg *mocks.MockReconcilerMockRecorder, rt *mocks.MockReconcilerMockRecorder, sn *mocks.MockReconcilerMockRecorder, pip *mocks.MockReconcilerMockRecorder, lb *mocks.MockReconcilerMockRecorder, dns *mocks.MockReconcilerMockRecorder, bastion *mocks.MockReconcilerMockRecorder) {
+			expect: func(grp *mocks.MockReconcilerMockRecorder, vnet *mocks.MockReconcilerMockRecorder, sg *mocks.MockReconcilerMockRecorder, rt *mocks.MockReconcilerMockRecorder, sn *mocks.MockReconcilerMockRecorder, pip *mocks.MockReconcilerMockRecorder, lb *mocks.MockReconcilerMockRecorder, dns *mocks.MockReconcilerMockRecorder) {
 				gomock.InOrder(
 					grp.Delete(gomockinternal.AContext()).Return(nil))
 			},
 		},
 		"Resource Group delete fails": {
 			expectedError: "failed to delete resource group: internal error",
-			expect: func(grp *mocks.MockReconcilerMockRecorder, vnet *mocks.MockReconcilerMockRecorder, sg *mocks.MockReconcilerMockRecorder, rt *mocks.MockReconcilerMockRecorder, sn *mocks.MockReconcilerMockRecorder, pip *mocks.MockReconcilerMockRecorder, lb *mocks.MockReconcilerMockRecorder, dns *mocks.MockReconcilerMockRecorder, bastion *mocks.MockReconcilerMockRecorder) {
+			expect: func(grp *mocks.MockReconcilerMockRecorder, vnet *mocks.MockReconcilerMockRecorder, sg *mocks.MockReconcilerMockRecorder, rt *mocks.MockReconcilerMockRecorder, sn *mocks.MockReconcilerMockRecorder, pip *mocks.MockReconcilerMockRecorder, lb *mocks.MockReconcilerMockRecorder, dns *mocks.MockReconcilerMockRecorder) {
 				gomock.InOrder(
 					grp.Delete(gomockinternal.AContext()).Return(errors.New("internal error")))
 			},
 		},
 		"Resource Group not owned by cluster": {
 			expectedError: "",
-			expect: func(grp *mocks.MockReconcilerMockRecorder, vnet *mocks.MockReconcilerMockRecorder, sg *mocks.MockReconcilerMockRecorder, rt *mocks.MockReconcilerMockRecorder, sn *mocks.MockReconcilerMockRecorder, pip *mocks.MockReconcilerMockRecorder, lb *mocks.MockReconcilerMockRecorder, dns *mocks.MockReconcilerMockRecorder, bastion *mocks.MockReconcilerMockRecorder) {
+			expect: func(grp *mocks.MockReconcilerMockRecorder, vnet *mocks.MockReconcilerMockRecorder, sg *mocks.MockReconcilerMockRecorder, rt *mocks.MockReconcilerMockRecorder, sn *mocks.MockReconcilerMockRecorder, pip *mocks.MockReconcilerMockRecorder, lb *mocks.MockReconcilerMockRecorder, dns *mocks.MockReconcilerMockRecorder) {
 				gomock.InOrder(
 					grp.Delete(gomockinternal.AContext()).Return(azure.ErrNotOwned),
 					dns.Delete(gomockinternal.AContext()),
@@ -66,13 +66,12 @@ func TestAzureClusterReconcilerDelete(t *testing.T) {
 					rt.Delete(gomockinternal.AContext()),
 					sg.Delete(gomockinternal.AContext()),
 					vnet.Delete(gomockinternal.AContext()),
-					bastion.Delete(gomockinternal.AContext()),
 				)
 			},
 		},
 		"Load Balancer delete fails": {
 			expectedError: "failed to delete load balancer: some error happened",
-			expect: func(grp *mocks.MockReconcilerMockRecorder, vnet *mocks.MockReconcilerMockRecorder, sg *mocks.MockReconcilerMockRecorder, rt *mocks.MockReconcilerMockRecorder, sn *mocks.MockReconcilerMockRecorder, pip *mocks.MockReconcilerMockRecorder, lb *mocks.MockReconcilerMockRecorder, dns *mocks.MockReconcilerMockRecorder, bastion *mocks.MockReconcilerMockRecorder) {
+			expect: func(grp *mocks.MockReconcilerMockRecorder, vnet *mocks.MockReconcilerMockRecorder, sg *mocks.MockReconcilerMockRecorder, rt *mocks.MockReconcilerMockRecorder, sn *mocks.MockReconcilerMockRecorder, pip *mocks.MockReconcilerMockRecorder, lb *mocks.MockReconcilerMockRecorder, dns *mocks.MockReconcilerMockRecorder) {
 				gomock.InOrder(
 					grp.Delete(gomockinternal.AContext()).Return(azure.ErrNotOwned),
 					dns.Delete(gomockinternal.AContext()),
@@ -82,7 +81,7 @@ func TestAzureClusterReconcilerDelete(t *testing.T) {
 		},
 		"Route table delete fails": {
 			expectedError: "failed to delete route table: some error happened",
-			expect: func(grp *mocks.MockReconcilerMockRecorder, vnet *mocks.MockReconcilerMockRecorder, sg *mocks.MockReconcilerMockRecorder, rt *mocks.MockReconcilerMockRecorder, sn *mocks.MockReconcilerMockRecorder, pip *mocks.MockReconcilerMockRecorder, lb *mocks.MockReconcilerMockRecorder, dns *mocks.MockReconcilerMockRecorder, bastion *mocks.MockReconcilerMockRecorder) {
+			expect: func(grp *mocks.MockReconcilerMockRecorder, vnet *mocks.MockReconcilerMockRecorder, sg *mocks.MockReconcilerMockRecorder, rt *mocks.MockReconcilerMockRecorder, sn *mocks.MockReconcilerMockRecorder, pip *mocks.MockReconcilerMockRecorder, lb *mocks.MockReconcilerMockRecorder, dns *mocks.MockReconcilerMockRecorder) {
 				gomock.InOrder(
 					grp.Delete(gomockinternal.AContext()).Return(azure.ErrNotOwned),
 					dns.Delete(gomockinternal.AContext()),
@@ -111,9 +110,8 @@ func TestAzureClusterReconcilerDelete(t *testing.T) {
 			publicIPMock := mocks.NewMockReconciler(mockCtrl)
 			lbMock := mocks.NewMockReconciler(mockCtrl)
 			dnsMock := mocks.NewMockReconciler(mockCtrl)
-			bastionMock := mocks.NewMockReconciler(mockCtrl)
 
-			tc.expect(groupsMock.EXPECT(), vnetMock.EXPECT(), sgMock.EXPECT(), rtMock.EXPECT(), subnetsMock.EXPECT(), publicIPMock.EXPECT(), lbMock.EXPECT(), dnsMock.EXPECT(), bastionMock.EXPECT())
+			tc.expect(groupsMock.EXPECT(), vnetMock.EXPECT(), sgMock.EXPECT(), rtMock.EXPECT(), subnetsMock.EXPECT(), publicIPMock.EXPECT(), lbMock.EXPECT(), dnsMock.EXPECT())
 
 			s := &azureClusterService{
 				scope: &scope.ClusterScope{
@@ -127,7 +125,6 @@ func TestAzureClusterReconcilerDelete(t *testing.T) {
 				publicIPSvc:      publicIPMock,
 				loadBalancerSvc:  lbMock,
 				privateDNSSvc:    dnsMock,
-				bastionSvc:       bastionMock,
 				skuCache:         resourceskus.NewStaticCache([]compute.ResourceSku{}, ""),
 			}
 
diff --git a/controllers/azurejson_machine_controller_test.go b/controllers/azurejson_machine_controller_test.go
index 7e1080bb9..9a6d74644 100644
--- a/controllers/azurejson_machine_controller_test.go
+++ b/controllers/azurejson_machine_controller_test.go
@@ -18,11 +18,8 @@ package controllers
 
 import (
 	"context"
-	"os"
 	"testing"
 
-	"github.com/Azure/go-autorest/autorest/azure/auth"
-
 	"github.com/google/go-cmp/cmp"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -169,10 +166,6 @@ func TestAzureJSONMachineReconciler(t *testing.T) {
 		},
 	}
 
-	os.Setenv(auth.ClientID, "fooClient")
-	os.Setenv(auth.ClientSecret, "fooSecret")
-	os.Setenv(auth.TenantID, "fooTenant")
-
 	for name, tc := range cases {
 		t.Run(name, func(t *testing.T) {
 			client := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(tc.objects...).Build()
diff --git a/controllers/azurejson_machinepool_controller_test.go b/controllers/azurejson_machinepool_controller_test.go
index 3c13a0816..f4bd8620e 100644
--- a/controllers/azurejson_machinepool_controller_test.go
+++ b/controllers/azurejson_machinepool_controller_test.go
@@ -18,11 +18,8 @@ package controllers
 
 import (
 	"context"
-	"os"
 	"testing"
 
-	"github.com/Azure/go-autorest/autorest/azure/auth"
-
 	"github.com/google/go-cmp/cmp"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -140,10 +137,6 @@ func TestAzureJSONPoolReconciler(t *testing.T) {
 		},
 	}
 
-	os.Setenv(auth.ClientID, "fooClient")
-	os.Setenv(auth.ClientSecret, "fooSecret")
-	os.Setenv(auth.TenantID, "fooTenant")
-
 	for name, tc := range cases {
 		t.Run(name, func(t *testing.T) {
 			client := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(tc.objects...).Build()
diff --git a/controllers/azurejson_machinetemplate_controller_test.go b/controllers/azurejson_machinetemplate_controller_test.go
index b7c021ee6..376e51e46 100644
--- a/controllers/azurejson_machinetemplate_controller_test.go
+++ b/controllers/azurejson_machinetemplate_controller_test.go
@@ -18,11 +18,8 @@ package controllers
 
 import (
 	"context"
-	"os"
 	"testing"
 
-	"github.com/Azure/go-autorest/autorest/azure/auth"
-
 	"github.com/google/go-cmp/cmp"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -107,10 +104,6 @@ func TestAzureJSONTemplateReconciler(t *testing.T) {
 		},
 	}
 
-	os.Setenv(auth.ClientID, "fooClient")
-	os.Setenv(auth.ClientSecret, "fooSecret")
-	os.Setenv(auth.TenantID, "fooTenant")
-
 	for name, tc := range cases {
 		t.Run(name, func(t *testing.T) {
 			client := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(tc.objects...).Build()
diff --git a/controllers/helpers_test.go b/controllers/helpers_test.go
index 80a334751..c91977558 100644
--- a/controllers/helpers_test.go
+++ b/controllers/helpers_test.go
@@ -170,9 +170,6 @@ func TestGetCloudProviderConfig(t *testing.T) {
 			if diff := cmp.Diff(tc.expectedWorkerNodeConfig, string(cloudConfig.Data["worker-node-azure.json"])); diff != "" {
 				t.Errorf(diff)
 			}
-			if diff := cmp.Diff(tc.expectedControlPlaneConfig, string(cloudConfig.Data["azure.json"])); diff != "" {
-				t.Errorf(diff)
-			}
 		})
 	}
 }
@@ -205,6 +202,10 @@ func TestReconcileAzureSecret(t *testing.T) {
 	ctrl.SetLogger(zap.New(zap.UseDevMode(true)))
 	testLog := ctrl.Log.WithName("reconcileAzureSecret")
 
+	os.Setenv(auth.ClientID, "fooClient")
+	os.Setenv(auth.ClientSecret, "fooSecret")
+	os.Setenv(auth.TenantID, "fooTenant")
+
 	cluster := newCluster("foo")
 	azureCluster := newAzureCluster("foo", "bar")
 
diff --git a/exp/controllers/helpers_test.go b/exp/controllers/helpers_test.go
index fc555b0ad..bd77944ae 100644
--- a/exp/controllers/helpers_test.go
+++ b/exp/controllers/helpers_test.go
@@ -39,14 +39,10 @@ import (
 	"sigs.k8s.io/cluster-api-provider-azure/internal/test/mock_log"
 )
 
-var (
-	cpName      = "my-managed-cp"
-	clusterName = "my-cluster"
-)
-
 func TestAzureClusterToAzureMachinePoolsMapper(t *testing.T) {
 	g := NewWithT(t)
 	scheme := newScheme(g)
+	clusterName := "my-cluster"
 	initObjects := []runtime.Object{
 		newCluster(clusterName),
 		// Create two Machines with an infrastructure ref and one without.
@@ -81,6 +77,7 @@ func TestAzureClusterToAzureMachinePoolsMapper(t *testing.T) {
 func TestAzureManagedClusterToAzureManagedMachinePoolsMapper(t *testing.T) {
 	g := NewWithT(t)
 	scheme := newScheme(g)
+	clusterName := "my-cluster"
 	initObjects := []runtime.Object{
 		newCluster(clusterName),
 		// Create two Machines with an infrastructure ref and one without.
@@ -92,7 +89,7 @@ func TestAzureManagedClusterToAzureManagedMachinePoolsMapper(t *testing.T) {
 	fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(initObjects...).Build()
 
 	log := mock_log.NewMockLogger(gomock.NewController(t))
-	log.EXPECT().WithValues("AzureManagedCluster", "my-cluster", "Namespace", "default").Return(log)
+	log.EXPECT().WithValues("AzureCluster", "my-cluster", "Namespace", "default").Return(log)
 	log.EXPECT().Info("gk does not match", "gk", gomock.Any(), "infraGK", gomock.Any())
 	mapper, err := AzureManagedClusterToAzureManagedMachinePoolsMapper(context.Background(), fakeClient, scheme, log)
 	g.Expect(err).NotTo(HaveOccurred())
@@ -132,168 +129,10 @@ func TestAzureManagedClusterToAzureManagedMachinePoolsMapper(t *testing.T) {
 	}))
 }
 
-func TestAzureManagedControlPlaneToAzureManagedMachinePoolsMapper(t *testing.T) {
-	g := NewWithT(t)
-	scheme := newScheme(g)
-	cluster := newCluster("my-cluster")
-	cluster.Spec.ControlPlaneRef = &corev1.ObjectReference{
-		APIVersion: infrav1exp.GroupVersion.String(),
-		Kind:       "AzureManagedControlPlane",
-		Name:       cpName,
-		Namespace:  cluster.Namespace,
-	}
-	initObjects := []runtime.Object{
-		cluster,
-		newAzureManagedControlPlane(cpName),
-		// Create two Machines with an infrastructure ref and one without.
-		newManagedMachinePoolInfraReference(clusterName, "my-mmp-0"),
-		newManagedMachinePoolInfraReference(clusterName, "my-mmp-1"),
-		newManagedMachinePoolInfraReference(clusterName, "my-mmp-2"),
-		newMachinePool(clusterName, "my-machine-2"),
-	}
-	fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(initObjects...).Build()
-
-	log := mock_log.NewMockLogger(gomock.NewController(t))
-	log.EXPECT().WithValues("AzureManagedControlPlane", cpName, "Namespace", cluster.Namespace).Return(log)
-	log.EXPECT().Info("gk does not match", "gk", gomock.Any(), "infraGK", gomock.Any())
-	mapper, err := AzureManagedControlPlaneToAzureManagedMachinePoolsMapper(context.Background(), fakeClient, scheme, log)
-	g.Expect(err).NotTo(HaveOccurred())
-
-	requests := mapper(&infrav1exp.AzureManagedControlPlane{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      cpName,
-			Namespace: cluster.Namespace,
-			OwnerReferences: []metav1.OwnerReference{
-				{
-					Name:       cluster.Name,
-					Kind:       "Cluster",
-					APIVersion: clusterv1.GroupVersion.String(),
-				},
-			},
-		},
-	})
-	g.Expect(requests).To(ConsistOf([]reconcile.Request{
-		{
-			NamespacedName: types.NamespacedName{
-				Name:      "azuremy-mmp-0",
-				Namespace: "default",
-			},
-		},
-		{
-			NamespacedName: types.NamespacedName{
-				Name:      "azuremy-mmp-1",
-				Namespace: "default",
-			},
-		},
-		{
-			NamespacedName: types.NamespacedName{
-				Name:      "azuremy-mmp-2",
-				Namespace: "default",
-			},
-		},
-	}))
-}
-
-func TestMachinePoolToAzureManagedControlPlaneMapFuncSuccess(t *testing.T) {
-	g := NewWithT(t)
-	scheme := newScheme(g)
-	cluster := newCluster(clusterName)
-	controlPlane := newAzureManagedControlPlane(cpName)
-	cluster.Spec.ControlPlaneRef = &corev1.ObjectReference{
-		APIVersion: infrav1exp.GroupVersion.String(),
-		Kind:       "AzureManagedControlPlane",
-		Name:       cpName,
-		Namespace:  cluster.Namespace,
-	}
-	controlPlane.Spec.DefaultPoolRef.Name = "azuremy-mmp-0"
-	managedMachinePool := newManagedMachinePoolInfraReference(clusterName, "my-mmp-0")
-	managedMachinePool.Spec.ClusterName = clusterName
-	initObjects := []runtime.Object{
-		cluster,
-		controlPlane,
-		managedMachinePool,
-		// Create two Machines with an infrastructure ref and one without.
-		newManagedMachinePoolInfraReference(clusterName, "my-mmp-1"),
-	}
-	fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(initObjects...).Build()
-
-	log := mock_log.NewMockLogger(gomock.NewController(t))
-	log.EXPECT().WithValues("AzureManagedControlPlane", cpName, "Namespace", cluster.Namespace).Return(log)
-	log.EXPECT().Info("expected a MachinePool, got wrong type", "type", gomock.Any())
-	mapper := MachinePoolToAzureManagedControlPlaneMapFunc(context.Background(), fakeClient, infrav1exp.GroupVersion.WithKind("AzureManagedControlPlane"), log)
-
-	// default pool should trigger
-	requests := mapper(newManagedMachinePoolInfraReference(clusterName, "my-mmp-0"))
-	g.Expect(requests).To(ConsistOf([]reconcile.Request{
-		{
-			NamespacedName: types.NamespacedName{
-				Name:      "my-managed-cp",
-				Namespace: "default",
-			},
-		},
-	}))
-
-	// any other pool should not trigger
-	requests = mapper(newManagedMachinePoolInfraReference(clusterName, "my-mmp-1"))
-	g.Expect(requests).To(BeNil())
-}
-
-func TestMachinePoolToAzureManagedControlPlaneMapFuncFailure(t *testing.T) {
-	g := NewWithT(t)
-	scheme := newScheme(g)
-	cluster := newCluster(clusterName)
-	controlPlane := newAzureManagedControlPlane(cpName)
-	cluster.Spec.ControlPlaneRef = &corev1.ObjectReference{
-		APIVersion: infrav1exp.GroupVersion.String(),
-		Kind:       "AzureManagedControlPlane",
-		Name:       cpName,
-		Namespace:  cluster.Namespace,
-	}
-	controlPlane.Spec.DefaultPoolRef.Name = "azuremy-mmp-0"
-	managedMachinePool := newManagedMachinePoolInfraReference(clusterName, "my-mmp-0")
-	managedMachinePool.Spec.ClusterName = clusterName
-	initObjects := []runtime.Object{
-		cluster,
-		managedMachinePool,
-		// Create two Machines with an infrastructure ref and one without.
-		newManagedMachinePoolInfraReference(clusterName, "my-mmp-1"),
-	}
-	fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(initObjects...).Build()
-
-	log := mock_log.NewMockLogger(gomock.NewController(t))
-	log.EXPECT().WithValues("AzureManagedControlPlane", cpName, "Namespace", cluster.Namespace).Return(log)
-	log.EXPECT().Info("expected a MachinePool, got wrong type", "type", gomock.Any())
-	log.EXPECT().Error(gomock.Any(), "failed to fetch default pool reference")
-	log.EXPECT().Error(gomock.Any(), "failed to fetch default pool reference") // twice because we are testing two calls
-
-	mapper := MachinePoolToAzureManagedControlPlaneMapFunc(context.Background(), fakeClient, infrav1exp.GroupVersion.WithKind("AzureManagedControlPlane"), log)
-
-	// default pool should trigger if owned cluster could not be fetched
-	requests := mapper(newManagedMachinePoolInfraReference(clusterName, "my-mmp-0"))
-	g.Expect(requests).To(ConsistOf([]reconcile.Request{
-		{
-			NamespacedName: types.NamespacedName{
-				Name:      "my-managed-cp",
-				Namespace: "default",
-			},
-		},
-	}))
-
-	// any other pool should also trigger if owned cluster could not be fetched
-	requests = mapper(newManagedMachinePoolInfraReference(clusterName, "my-mmp-1"))
-	g.Expect(requests).To(ConsistOf([]reconcile.Request{
-		{
-			NamespacedName: types.NamespacedName{
-				Name:      "my-managed-cp",
-				Namespace: "default",
-			},
-		},
-	}))
-}
-
 func TestAzureManagedClusterToAzureManagedControlPlaneMapper(t *testing.T) {
 	g := NewWithT(t)
 	scheme := newScheme(g)
+	cpName := "my-managed-cp"
 	cluster := newCluster("my-cluster")
 	cluster.Spec.ControlPlaneRef = &corev1.ObjectReference{
 		APIVersion: infrav1exp.GroupVersion.String(),
@@ -309,7 +148,7 @@ func TestAzureManagedClusterToAzureManagedControlPlaneMapper(t *testing.T) {
 	fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(initObjects...).Build()
 
 	log := mock_log.NewMockLogger(gomock.NewController(t))
-	log.EXPECT().WithValues("AzureManagedCluster", "az-"+cluster.Name, "Namespace", "default")
+	log.EXPECT().WithValues("AzureCluster", "az-"+cluster.Name, "Namespace", "default")
 
 	mapper, err := AzureManagedClusterToAzureManagedControlPlaneMapper(context.Background(), fakeClient, log)
 	g.Expect(err).NotTo(HaveOccurred())
@@ -337,73 +176,6 @@ func TestAzureManagedClusterToAzureManagedControlPlaneMapper(t *testing.T) {
 	}))
 }
 
-func TestAzureManagedControlPlaneToAzureManagedClusterMapper(t *testing.T) {
-	g := NewWithT(t)
-	scheme := newScheme(g)
-	cluster := newCluster("my-cluster")
-	azManagedCluster := &infrav1exp.AzureManagedCluster{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "az-" + cluster.Name,
-			Namespace: "default",
-			OwnerReferences: []metav1.OwnerReference{
-				{
-					Name:       cluster.Name,
-					Kind:       "Cluster",
-					APIVersion: clusterv1.GroupVersion.String(),
-				},
-			},
-		},
-	}
-
-	cluster.Spec.ControlPlaneRef = &corev1.ObjectReference{
-		APIVersion: infrav1exp.GroupVersion.String(),
-		Kind:       "AzureManagedControlPlane",
-		Name:       cpName,
-		Namespace:  cluster.Namespace,
-	}
-	cluster.Spec.InfrastructureRef = &corev1.ObjectReference{
-		APIVersion: infrav1exp.GroupVersion.String(),
-		Kind:       "AzureManagedCluster",
-		Name:       azManagedCluster.Name,
-		Namespace:  azManagedCluster.Namespace,
-	}
-
-	initObjects := []runtime.Object{
-		cluster,
-		newAzureManagedControlPlane(cpName),
-		azManagedCluster,
-	}
-	fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(initObjects...).Build()
-
-	log := mock_log.NewMockLogger(gomock.NewController(t))
-	log.EXPECT().WithValues("AzureManagedControlPlane", cpName, "Namespace", cluster.Namespace)
-
-	mapper, err := AzureManagedControlPlaneToAzureManagedClusterMapper(context.Background(), fakeClient, log)
-	g.Expect(err).NotTo(HaveOccurred())
-	requests := mapper(&infrav1exp.AzureManagedControlPlane{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      cpName,
-			Namespace: cluster.Namespace,
-			OwnerReferences: []metav1.OwnerReference{
-				{
-					Name:       cluster.Name,
-					Kind:       "Cluster",
-					APIVersion: clusterv1.GroupVersion.String(),
-				},
-			},
-		},
-	})
-	g.Expect(requests).To(HaveLen(1))
-	g.Expect(requests).To(Equal([]reconcile.Request{
-		{
-			NamespacedName: types.NamespacedName{
-				Name:      azManagedCluster.Name,
-				Namespace: azManagedCluster.Namespace,
-			},
-		},
-	}))
-}
-
 func Test_MachinePoolToInfrastructureMapFunc(t *testing.T) {
 	cases := []struct {
 		Name             string
@@ -620,7 +392,6 @@ func newAzureManagedControlPlane(cpName string) *infrav1exp.AzureManagedControlP
 
 func newManagedMachinePoolInfraReference(clusterName, poolName string) *clusterv1exp.MachinePool {
 	m := newMachinePool(clusterName, poolName)
-	m.Spec.ClusterName = clusterName
 	m.Spec.Template.Spec.InfrastructureRef = corev1.ObjectReference{
 		Kind:       "AzureManagedMachinePool",
 		Namespace:  m.Namespace,
diff --git a/templates/test/ci/cluster-template-prow-private.yaml b/templates/test/ci/cluster-template-prow-private.yaml
index a0d3f6217..fdc7ce234 100644
--- a/templates/test/ci/cluster-template-prow-private.yaml
+++ b/templates/test/ci/cluster-template-prow-private.yaml
@@ -29,8 +29,6 @@ spec:
     buildProvenance: ${BUILD_PROVENANCE}
     creationTimestamp: ${TIMESTAMP}
     jobName: ${JOB_NAME}
-  bastionSpec:
-    azureBastion: {}
   location: ${AZURE_LOCATION}
   networkSpec:
     apiServerLB:
diff --git a/test/e2e/azure_privatecluster.go b/test/e2e/azure_privatecluster.go
index f72284e13..1e92b83ce 100644
--- a/test/e2e/azure_privatecluster.go
+++ b/test/e2e/azure_privatecluster.go
@@ -29,11 +29,8 @@ import (
 	"github.com/Azure/go-autorest/autorest/azure/auth"
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
-	"github.com/pkg/errors"
 	corev1 "k8s.io/api/core/v1"
-	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/utils/pointer"
-	"sigs.k8s.io/cluster-api-provider-azure/api/v1alpha4"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha4"
 	capi_e2e "sigs.k8s.io/cluster-api/test/e2e"
 	"sigs.k8s.io/cluster-api/test/framework"
@@ -122,45 +119,6 @@ func AzurePrivateClusterSpec(ctx context.Context, inputGetter func() AzurePrivat
 	cluster = result.Cluster
 
 	Expect(cluster).ToNot(BeNil())
-
-	// Check that azure bastion is provisioned successfully.
-	{
-		settings, err := auth.GetSettingsFromEnvironment()
-		Expect(err).To(BeNil())
-
-		azureBastionClient := network.NewBastionHostsClient(settings.GetSubscriptionID())
-		azureBastionClient.Authorizer, err = settings.GetAuthorizer()
-		Expect(err).To(BeNil())
-
-		groupName := os.Getenv(AzureResourceGroup)
-		azureBastionName := fmt.Sprintf("%s-azure-bastion", clusterName)
-
-		backoff := wait.Backoff{
-			Duration: retryBackoffInitialDuration,
-			Factor:   retryBackoffFactor,
-			Jitter:   retryBackoffJitter,
-			Steps:    retryBackoffSteps,
-		}
-		retryFn := func() (bool, error) {
-			bastion, err := azureBastionClient.Get(ctx, groupName, azureBastionName)
-			if err != nil {
-				return false, err
-			}
-
-			switch bastion.ProvisioningState {
-			case network.Succeeded:
-				return true, nil
-			case network.Updating:
-				// Wait for operation to complete.
-				return false, nil
-			default:
-				return false, errors.New(fmt.Sprintf("Azure Bastion provisioning failed with state: %q", bastion.ProvisioningState))
-			}
-		}
-		err = wait.ExponentialBackoff(backoff, retryFn)
-
-		Expect(err).To(BeNil())
-	}
 }
 
 // SetupExistingVNet creates a resource group and a VNet to be used by a workload cluster.
@@ -253,14 +211,6 @@ func SetupExistingVNet(ctx context.Context, vnetCidr string, cpSubnetCidrs, node
 		})
 	}
 
-	// Create the AzureBastion subnet.
-	subnets = append(subnets, network.Subnet{
-		SubnetPropertiesFormat: &network.SubnetPropertiesFormat{
-			AddressPrefix: pointer.StringPtr(v1alpha4.DefaultAzureBastionSubnetCIDR),
-		},
-		Name: pointer.StringPtr(v1alpha4.DefaultAzureBastionSubnetName),
-	})
-
 	vnetFuture, err := vnetClient.CreateOrUpdate(ctx, groupName, os.Getenv(AzureVNetName), network.VirtualNetwork{
 		Location: pointer.StringPtr(os.Getenv(AzureLocation)),
 		VirtualNetworkPropertiesFormat: &network.VirtualNetworkPropertiesFormat{