Repository: kubernetes-sigs/cluster-api
Pull request #9340 (2023-08-30 08:53:25)

✨ ClusterClass: Introduce NamingStrategy and allow generating names using go templates
<!-- Thanks for sending a pull request! Here are some tips for you:
    1. If this is your first time, please read our contributor guidelines: https://github.com/kubernetes-sigs/cluster-api/blob/main/CONTRIBUTING.md#contributing-a-patch and developer guide https://github.com/kubernetes-sigs/cluster-api/blob/main/docs/book/src/developer/guide.md

    2. Please add an icon to the title of this PR (see https://sigs.k8s.io/cluster-api/CONTRIBUTING.md#contributing-a-patch), and delete this line and similar ones
    the icon will be either ⚠️ (:warning:, major or breaking changes), ✨ (:sparkles:, feature additions), 🐛 (:bug:, patch and bugfixes), 📖 (:book:, documentation or proposals), or 🌱 (:seedling:, minor or other) 
-->

**What this PR does / why we need it**:

Introduces the NamingStrategy struct at the ClusterClass level for:
* ControlPlane
* MachineDeployments
* MachinePools

Also introduces a new package which exposes functions and a interface to generate names.

TODO: improve this message here to have more details

TODO:

- [x] improve godocs
  - [x] add examples of which template data struct to expect on the godocs
  - [x] review all new godocs
- [x] unit tests

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Part of #9323

<!-- 
Please label this pull request according to what area(s) you are addressing. For reference on PR/issue labels, see: https://github.com/kubernetes-sigs/cluster-api/labels?q=area+

Area example:
/area clusterclass

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

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

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

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

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

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["vincepri"]} -->
chrischdi: /test help
k8s-ci-robot: @chrischdi: The specified target(s) for `/test` were not found.
The following commands are available to trigger required jobs:
* `/test pull-cluster-api-build-main`
* `/test pull-cluster-api-e2e-main`
* `/test pull-cluster-api-test-main`
* `/test pull-cluster-api-verify-main`

The following commands are available to trigger optional jobs:
* `/test pull-cluster-api-apidiff-main`
* `/test pull-cluster-api-e2e-full-dualstack-and-ipv6-main`
* `/test pull-cluster-api-e2e-full-main`
* `/test pull-cluster-api-e2e-informing-main`
* `/test pull-cluster-api-e2e-mink8s-main`
* `/test pull-cluster-api-e2e-scale-main-experimental`
* `/test pull-cluster-api-e2e-workload-upgrade-1-28-latest-main`
* `/test pull-cluster-api-test-mink8s-main`

Use `/test all` to run the following jobs that were automatically triggered:
* `pull-cluster-api-apidiff-main`
* `pull-cluster-api-build-main`
* `pull-cluster-api-e2e-informing-main`
* `pull-cluster-api-e2e-main`
* `pull-cluster-api-test-main`
* `pull-cluster-api-verify-main`



<details>

In response to [this](https://github.com/kubernetes-sigs/cluster-api/pull/9340#issuecomment-1698770920):

>/test help


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>
chrischdi: /test pull-cluster-api-e2e-full-main
k8s-ci-robot: @chrischdi: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-verify-main | cd17b468e8d634ea6513018cec47d35f91abb5f1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-verify-main/1696808336353136640) | true | `/test pull-cluster-api-verify-main`

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

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @chrischdi: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-verify-main | cd17b468e8d634ea6513018cec47d35f91abb5f1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-verify-main/1696808336353136640) | true | `/test pull-cluster-api-verify-main`
pull-cluster-api-e2e-informing-main | cd17b468e8d634ea6513018cec47d35f91abb5f1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-e2e-informing-main/1696808336491548672) | false | `/test pull-cluster-api-e2e-informing-main`

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

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @chrischdi: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-verify-main | cd17b468e8d634ea6513018cec47d35f91abb5f1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-verify-main/1696808336353136640) | true | `/test pull-cluster-api-verify-main`
pull-cluster-api-e2e-informing-main | cd17b468e8d634ea6513018cec47d35f91abb5f1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-e2e-informing-main/1696808336491548672) | false | `/test pull-cluster-api-e2e-informing-main`
pull-cluster-api-test-main | cd17b468e8d634ea6513018cec47d35f91abb5f1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-test-main/1696808336395079680) | true | `/test pull-cluster-api-test-main`

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

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
chrischdi: 
/test pull-cluster-api-e2e-full-main


k8s-ci-robot: @chrischdi: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-e2e-informing-main | cd17b468e8d634ea6513018cec47d35f91abb5f1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-e2e-informing-main/1696808336491548672) | false | `/test pull-cluster-api-e2e-informing-main`
pull-cluster-api-verify-main | bb5044622cedeccebcf3b251385613873e78bd5a | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-verify-main/1696851397116432384) | true | `/test pull-cluster-api-verify-main`

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

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @chrischdi: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-build-main | a5ada5c2172db89d7863f38f8eb461099007f311 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-build-main/1699081321294336000) | true | `/test pull-cluster-api-build-main`
pull-cluster-api-e2e-main | a5ada5c2172db89d7863f38f8eb461099007f311 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-e2e-main/1699081321495662592) | true | `/test pull-cluster-api-e2e-main`

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

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @chrischdi: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-e2e-main | a5ada5c2172db89d7863f38f8eb461099007f311 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-e2e-main/1699081321495662592) | true | `/test pull-cluster-api-e2e-main`
pull-cluster-api-build-main | 19c6b3ed6d227432c0b2310bdb6de21465c1c36c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-build-main/1699082362098290688) | true | `/test pull-cluster-api-build-main`

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

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @chrischdi: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-build-main | 19c6b3ed6d227432c0b2310bdb6de21465c1c36c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-build-main/1699082362098290688) | true | `/test pull-cluster-api-build-main`
pull-cluster-api-e2e-main | 19c6b3ed6d227432c0b2310bdb6de21465c1c36c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-e2e-main/1699082362303811584) | true | `/test pull-cluster-api-e2e-main`

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

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @chrischdi: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-e2e-main | 19c6b3ed6d227432c0b2310bdb6de21465c1c36c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-e2e-main/1699082362303811584) | true | `/test pull-cluster-api-e2e-main`
pull-cluster-api-verify-main | 19c6b3ed6d227432c0b2310bdb6de21465c1c36c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-verify-main/1699082362194759680) | true | `/test pull-cluster-api-verify-main`
pull-cluster-api-test-main | 1db01377021a85c4b8a0792b719cc31f8ce801b7 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-test-main/1699086088603176960) | true | `/test pull-cluster-api-test-main`

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

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @chrischdi: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-e2e-main | 19c6b3ed6d227432c0b2310bdb6de21465c1c36c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-e2e-main/1699082362303811584) | true | `/test pull-cluster-api-e2e-main`
pull-cluster-api-verify-main | 19c6b3ed6d227432c0b2310bdb6de21465c1c36c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-verify-main/1699082362194759680) | true | `/test pull-cluster-api-verify-main`
pull-cluster-api-test-main | db8bb003e413b88bc473443f77696b77e15471d6 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-test-main/1699088891266469888) | true | `/test pull-cluster-api-test-main`

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

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @chrischdi: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-e2e-main | 19c6b3ed6d227432c0b2310bdb6de21465c1c36c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-e2e-main/1699082362303811584) | true | `/test pull-cluster-api-e2e-main`
pull-cluster-api-test-main | db8bb003e413b88bc473443f77696b77e15471d6 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-test-main/1699088891266469888) | true | `/test pull-cluster-api-test-main`
pull-cluster-api-verify-main | db8bb003e413b88bc473443f77696b77e15471d6 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-verify-main/1699088891224526848) | true | `/test pull-cluster-api-verify-main`

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

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @chrischdi: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-e2e-main | 19c6b3ed6d227432c0b2310bdb6de21465c1c36c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-e2e-main/1699082362303811584) | true | `/test pull-cluster-api-e2e-main`
pull-cluster-api-verify-main | 47161867a4881615952c93d23cb2163340af4dbc | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-verify-main/1699092198873632768) | true | `/test pull-cluster-api-verify-main`

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

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
chrischdi: /test help
k8s-ci-robot: @chrischdi: The specified target(s) for `/test` were not found.
The following commands are available to trigger required jobs:
* `/test pull-cluster-api-build-main`
* `/test pull-cluster-api-e2e-main`
* `/test pull-cluster-api-test-main`
* `/test pull-cluster-api-verify-main`

The following commands are available to trigger optional jobs:
* `/test pull-cluster-api-apidiff-main`
* `/test pull-cluster-api-e2e-full-dualstack-and-ipv6-main`
* `/test pull-cluster-api-e2e-full-main`
* `/test pull-cluster-api-e2e-mink8s-main`
* `/test pull-cluster-api-e2e-scale-main-experimental`
* `/test pull-cluster-api-e2e-workload-upgrade-1-28-latest-main`
* `/test pull-cluster-api-test-mink8s-main`

Use `/test all` to run the following jobs that were automatically triggered:
* `pull-cluster-api-apidiff-main`
* `pull-cluster-api-build-main`
* `pull-cluster-api-e2e-main`
* `pull-cluster-api-test-main`
* `pull-cluster-api-verify-main`



<details>

In response to [this](https://github.com/kubernetes-sigs/cluster-api/pull/9340#issuecomment-1707787373):

>/test help


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>
chrischdi: /test pull-cluster-api-e2e-full-main

k8s-ci-robot: @chrischdi: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-e2e-full-main | 83fe2ea0d954e919d737baadf491d2d1e009cea5 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-e2e-full-main/1699318238392881152) | false | `/test pull-cluster-api-e2e-full-main`

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

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
chrischdi: 
/test pull-cluster-api-e2e-full-main


k8s-ci-robot: @chrischdi: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-e2e-full-main | 613e1b31e58c7ef861e53db4e722353eb3b208a3 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-e2e-full-main/1699355346293755904) | false | `/test pull-cluster-api-e2e-full-main`

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

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
chrischdi: 
/test pull-cluster-api-e2e-full-main


chrischdi: /test pull-cluster-api-e2e-full-main
k8s-ci-robot: @chrischdi: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-verify-main | ea07b5c5513b6dcc64b351719ef394ee41c2a281 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9340/pull-cluster-api-verify-main/1700030984876986368) | true | `/test pull-cluster-api-verify-main`

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

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
sbueringer: lgtm pending verify 
chrischdi: 
/test pull-cluster-api-e2e-full-main


sbueringer: Thank you very much!

/lgtm
k8s-ci-robot: LGTM label has been added.  <details>Git tree hash: 3ec77263de9f34ed2d91a80284a43af982fcaff1</details>
sbueringer: Quote from the issue:
> We would like to propose to cherry-pick this back into v1.5 (based on "Enhancements or additions to experimental Cluster API features"

/cherry-pick release-1.5
k8s-infra-cherrypick-robot: @sbueringer: once the present PR merges, I will cherry-pick it on top of release-1.5 in a new PR and assign it to you.

<details>

In response to [this](https://github.com/kubernetes-sigs/cluster-api/pull/9340#issuecomment-1711251592):

>Quote from the issue:
>> We would like to propose to cherry-pick this back into v1.5 (based on "Enhancements or additions to experimental Cluster API features"
>
>/cherry-pick release-1.5


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>
sbueringer: /assign @killianmuldoon @fabriziopandini 
k8s-ci-robot: New changes are detected. LGTM label has been removed.
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
**Once this PR has been reviewed and has the lgtm label**, please ask for approval from [fabriziopandini](https://github.com/fabriziopandini). For more information see [the Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process).

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

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

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

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["fabriziopandini"]} -->

Patch:
diff --git a/.github/workflows/pr-dependabot.yaml b/.github/workflows/pr-dependabot.yaml
index 7139a63cd..514858f58 100644
--- a/.github/workflows/pr-dependabot.yaml
+++ b/.github/workflows/pr-dependabot.yaml
@@ -27,7 +27,7 @@ jobs:
       uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # tag=v4.1.0
       with:
         go-version: ${{ steps.vars.outputs.go_version }}
-    - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # tag=v3.3.2
+    - uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # tag=v3.3.1
       name: Restore go cache
       with:
         path: |
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index cfa9857d8..20d04c270 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -22,7 +22,7 @@ jobs:
           fetch-depth: 0
       - name: Get changed files
         id: changed-files
-        uses: tj-actions/changed-files@246636f5fa148b5ad8e65ca4c57b18af3123e5f6 # tag=v39.0.1
+        uses: tj-actions/changed-files@48566bbcc22ceb7c5809ebdd27377309f2c3de8c # tag=v39.0.0
       - name: Get release version
         id: release-version
         run: |
diff --git a/Makefile b/Makefile
index d1d3befbe..e97647547 100644
--- a/Makefile
+++ b/Makefile
@@ -23,7 +23,7 @@ SHELL:=/usr/bin/env bash
 #
 # Go.
 #
-GO_VERSION ?= 1.20.8
+GO_VERSION ?= 1.20.7
 GO_CONTAINER_IMAGE ?= docker.io/library/golang:$(GO_VERSION)
 
 # Use GOPROXY environment variable if set
@@ -175,11 +175,6 @@ GOVULNCHECK_VER := v1.0.0
 GOVULNCHECK := $(abspath $(TOOLS_BIN_DIR)/$(GOVULNCHECK_BIN)-$(GOVULNCHECK_VER))
 GOVULNCHECK_PKG := golang.org/x/vuln/cmd/govulncheck
 
-IMPORT_BOSS_BIN := import-boss
-IMPORT_BOSS_VER := v0.28.1
-IMPORT_BOSS := $(abspath $(TOOLS_BIN_DIR)/$(IMPORT_BOSS_BIN))
-IMPORT_BOSS_PKG := k8s.io/code-generator/cmd/import-boss
-
 CONVERSION_VERIFIER_BIN := conversion-verifier
 CONVERSION_VERIFIER := $(abspath $(TOOLS_BIN_DIR)/$(CONVERSION_VERIFIER_BIN))
 
@@ -554,16 +549,16 @@ generate-e2e-templates-main: $(KUSTOMIZE)
 	$(KUSTOMIZE) build $(INMEMORY_TEMPLATES)/main/cluster-template --load-restrictor LoadRestrictionsNone > $(INMEMORY_TEMPLATES)/main/cluster-template.yaml
 
 .PHONY: generate-metrics-config
-generate-metrics-config: $(ENVSUBST_BIN) ## Generate ./config/metrics/crd-metrics-config.yaml
-	OUTPUT_FILE="./config/metrics/crd-metrics-config.yaml"; \
-	METRIC_TEMPLATES_DIR="./config/metrics/templates"; \
+generate-metrics-config: $(ENVSUBST_BIN) ## Generate ./hack/observability/kube-state-metrics/crd-config.yaml
+	OUTPUT_FILE="${OBSERVABILITY_DIR}/kube-state-metrics/crd-config.yaml"; \
+	METRICS_DIR="${OBSERVABILITY_DIR}/kube-state-metrics/metrics"; \
 	echo "# This file was auto-generated via: make generate-metrics-config" > "$${OUTPUT_FILE}"; \
-	cat "$${METRIC_TEMPLATES_DIR}/header.yaml" >> "$${OUTPUT_FILE}"; \
+	cat "$${METRICS_DIR}/header.yaml" >> "$${OUTPUT_FILE}"; \
 	for resource in clusterclass cluster kubeadmcontrolplane kubeadmconfig machine machinedeployment machinehealthcheck machineset machinepool; do \
-		cat "$${METRIC_TEMPLATES_DIR}/$${resource}.yaml"; \
-		RESOURCE="$${resource}" ${ENVSUBST_BIN} < "$${METRIC_TEMPLATES_DIR}/common_metrics.yaml"; \
+		cat "$${METRICS_DIR}/$${resource}.yaml"; \
+		RESOURCE="$${resource}" ${ENVSUBST_BIN} < "$${METRICS_DIR}/common_metrics.yaml"; \
 		if [[ "$${resource}" != "cluster" ]]; then \
-			cat "$${METRIC_TEMPLATES_DIR}/owner_metric.yaml"; \
+			cat "$${METRICS_DIR}/owner_metric.yaml"; \
 		fi \
 	done >> "$${OUTPUT_FILE}"; \
 
@@ -612,7 +607,7 @@ APIDIFF_OLD_COMMIT ?= $(shell git rev-parse origin/main)
 apidiff: $(GO_APIDIFF) ## Check for API differences
 	$(GO_APIDIFF) $(APIDIFF_OLD_COMMIT) --print-compatible
 
-ALL_VERIFY_CHECKS = licenses boilerplate shellcheck tiltfile modules gen conversions doctoc capi-book-summary diagrams import-restrictions
+ALL_VERIFY_CHECKS = licenses boilerplate shellcheck tiltfile modules gen conversions doctoc capi-book-summary diagrams
 
 .PHONY: verify
 verify: $(addprefix verify-,$(ALL_VERIFY_CHECKS)) lint-dockerfiles ## Run all verify-* targets
@@ -695,10 +690,6 @@ verify-security: ## Verify code and images for vulnerabilities
 		exit 1; \
 	fi
 
-.PHONY: verify-import-restrictions
-verify-import-restrictions: $(IMPORT_BOSS) ## Verify import restrictions with import-boss
-	./hack/verify-import-restrictions.sh
-
 ## --------------------------------------
 ## Binaries
 ## --------------------------------------
@@ -1314,9 +1305,6 @@ $(GOLANGCI_LINT_BIN): $(GOLANGCI_LINT) ## Build a local copy of golangci-lint.
 .PHONY: $(GOVULNCHECK_BIN)
 $(GOVULNCHECK_BIN): $(GOVULNCHECK) ## Build a local copy of govulncheck.
 
-.PHONY: $(IMPORT_BOSS_BIN)
-$(IMPORT_BOSS_BIN): $(IMPORT_BOSS)
-
 $(CONTROLLER_GEN): # Build controller-gen from tools folder.
 	GOBIN=$(TOOLS_BIN_DIR) $(GO_INSTALL) $(CONTROLLER_GEN_PKG) $(CONTROLLER_GEN_BIN) $(CONTROLLER_GEN_VER)
 
@@ -1371,9 +1359,6 @@ $(GOLANGCI_LINT): # Build golangci-lint from tools folder.
 $(GOVULNCHECK): # Build govulncheck.
 	GOBIN=$(TOOLS_BIN_DIR) $(GO_INSTALL) $(GOVULNCHECK_PKG) $(GOVULNCHECK_BIN) $(GOVULNCHECK_VER)
 
-$(IMPORT_BOSS): # Build import-boss
-	GOBIN=$(TOOLS_BIN_DIR) $(GO_INSTALL) $(IMPORT_BOSS_PKG) $(IMPORT_BOSS_BIN) $(IMPORT_BOSS_VER)
-
 ## --------------------------------------
 ## Helpers
 ## --------------------------------------
diff --git a/Tiltfile b/Tiltfile
index d2090f09d..463f8fbcf 100644
--- a/Tiltfile
+++ b/Tiltfile
@@ -184,7 +184,7 @@ def load_provider_tiltfiles():
 
 tilt_helper_dockerfile_header = """
 # Tilt image
-FROM golang:1.20.8 as tilt-helper
+FROM golang:1.20.7 as tilt-helper
 # Install delve. Note this should be kept in step with the Go release minor version.
 RUN go install github.com/go-delve/delve/cmd/dlv@v1.20
 # Support live reloading with Tilt
@@ -195,7 +195,7 @@ RUN wget --output-document /restart.sh --quiet https://raw.githubusercontent.com
 """
 
 tilt_dockerfile_header = """
-FROM golang:1.20.8 as tilt
+FROM golang:1.20.7 as tilt
 WORKDIR /
 COPY --from=tilt-helper /process.txt .
 COPY --from=tilt-helper /start.sh .
@@ -482,19 +482,6 @@ def deploy_observability():
             objects = ["capi-visualizer:serviceaccount"],
         )
 
-def deploy_kustomizations():
-    for name in settings.get("deploy_kustomizations", []):
-        yaml = read_file("./.tiltbuild/yaml/{}.kustomization.yaml".format(name))
-        k8s_yaml(yaml)
-        objs = decode_yaml_stream(yaml)
-        print("objects")
-        print(find_all_objects_names(objs))
-        k8s_resource(
-            new_name = name,
-            objects = find_all_objects_names(objs),
-            labels = ["kustomization"],
-        )
-
 def prepare_all():
     tools_arg = "--tools kustomize,envsubst,clusterctl "
     tilt_settings_file_arg = "--tilt-settings-file " + tilt_file
@@ -653,8 +640,6 @@ deploy_provider_crds()
 
 deploy_observability()
 
-deploy_kustomizations()
-
 enable_providers()
 
 cluster_templates()
diff --git a/api/.import-restrictions b/api/.import-restrictions
deleted file mode 100644
index f6f10b3ff..000000000
--- a/api/.import-restrictions
+++ /dev/null
@@ -1,5 +0,0 @@
-rules:
-  - selectorRegexp: sigs[.]k8s[.]io/controller-runtime
-    allowedPrefixes:
-      - "sigs.k8s.io/controller-runtime/pkg/conversion"
-    forbiddenPrefixes: []
diff --git a/api/v1alpha4/cluster_types.go b/api/v1alpha4/cluster_types.go
index d4ee0658e..ddf2ab509 100644
--- a/api/v1alpha4/cluster_types.go
+++ b/api/v1alpha4/cluster_types.go
@@ -400,7 +400,7 @@ type ClusterList struct {
 }
 
 func init() {
-	objectTypes = append(objectTypes, &Cluster{}, &ClusterList{})
+	SchemeBuilder.Register(&Cluster{}, &ClusterList{})
 }
 
 // FailureDomains is a slice of FailureDomains.
diff --git a/api/v1alpha4/clusterclass_types.go b/api/v1alpha4/clusterclass_types.go
index 62a6413b7..ddb7276ad 100644
--- a/api/v1alpha4/clusterclass_types.go
+++ b/api/v1alpha4/clusterclass_types.go
@@ -134,5 +134,5 @@ type ClusterClassList struct {
 }
 
 func init() {
-	objectTypes = append(objectTypes, &ClusterClass{}, &ClusterClassList{})
+	SchemeBuilder.Register(&ClusterClass{}, &ClusterClassList{})
 }
diff --git a/api/v1alpha4/conversion.go b/api/v1alpha4/conversion.go
index 5b1c45c25..368dff1b1 100644
--- a/api/v1alpha4/conversion.go
+++ b/api/v1alpha4/conversion.go
@@ -124,6 +124,7 @@ func (src *ClusterClass) ConvertTo(dstRaw conversion.Hub) error {
 	dst.Spec.Patches = restored.Spec.Patches
 	dst.Spec.Variables = restored.Spec.Variables
 	dst.Spec.ControlPlane.MachineHealthCheck = restored.Spec.ControlPlane.MachineHealthCheck
+	dst.Spec.ControlPlane.NamingStrategy = restored.Spec.ControlPlane.NamingStrategy
 	dst.Spec.ControlPlane.NodeDrainTimeout = restored.Spec.ControlPlane.NodeDrainTimeout
 	dst.Spec.ControlPlane.NodeVolumeDetachTimeout = restored.Spec.ControlPlane.NodeVolumeDetachTimeout
 	dst.Spec.ControlPlane.NodeDeletionTimeout = restored.Spec.ControlPlane.NodeDeletionTimeout
@@ -132,6 +133,7 @@ func (src *ClusterClass) ConvertTo(dstRaw conversion.Hub) error {
 	for i := range restored.Spec.Workers.MachineDeployments {
 		dst.Spec.Workers.MachineDeployments[i].MachineHealthCheck = restored.Spec.Workers.MachineDeployments[i].MachineHealthCheck
 		dst.Spec.Workers.MachineDeployments[i].FailureDomain = restored.Spec.Workers.MachineDeployments[i].FailureDomain
+		dst.Spec.Workers.MachineDeployments[i].NamingStrategy = restored.Spec.Workers.MachineDeployments[i].NamingStrategy
 		dst.Spec.Workers.MachineDeployments[i].NodeDrainTimeout = restored.Spec.Workers.MachineDeployments[i].NodeDrainTimeout
 		dst.Spec.Workers.MachineDeployments[i].NodeVolumeDetachTimeout = restored.Spec.Workers.MachineDeployments[i].NodeVolumeDetachTimeout
 		dst.Spec.Workers.MachineDeployments[i].NodeDeletionTimeout = restored.Spec.Workers.MachineDeployments[i].NodeDeletionTimeout
diff --git a/api/v1alpha4/groupversion_info.go b/api/v1alpha4/groupversion_info.go
index cd8adb96d..382657c78 100644
--- a/api/v1alpha4/groupversion_info.go
+++ b/api/v1alpha4/groupversion_info.go
@@ -20,9 +20,8 @@ limitations under the License.
 package v1alpha4
 
 import (
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
+	"sigs.k8s.io/controller-runtime/pkg/scheme"
 )
 
 var (
@@ -30,19 +29,10 @@ var (
 	GroupVersion = schema.GroupVersion{Group: "cluster.x-k8s.io", Version: "v1alpha4"}
 
 	// SchemeBuilder is used to add go types to the GroupVersionKind scheme.
-	schemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
+	SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
 
 	// AddToScheme adds the types in this group-version to the given scheme.
-	AddToScheme = schemeBuilder.AddToScheme
+	AddToScheme = SchemeBuilder.AddToScheme
 
-	objectTypes = []runtime.Object{}
-
-	// localSchemeBuilder is used for type conversions.
-	localSchemeBuilder = schemeBuilder
+	localSchemeBuilder = SchemeBuilder.SchemeBuilder
 )
-
-func addKnownTypes(scheme *runtime.Scheme) error {
-	scheme.AddKnownTypes(GroupVersion, objectTypes...)
-	metav1.AddToGroupVersion(scheme, GroupVersion)
-	return nil
-}
diff --git a/api/v1alpha4/machine_types.go b/api/v1alpha4/machine_types.go
index 6c9c7875c..6f5c7ba87 100644
--- a/api/v1alpha4/machine_types.go
+++ b/api/v1alpha4/machine_types.go
@@ -278,5 +278,5 @@ type MachineList struct {
 }
 
 func init() {
-	objectTypes = append(objectTypes, &Machine{}, &MachineList{})
+	SchemeBuilder.Register(&Machine{}, &MachineList{})
 }
diff --git a/api/v1alpha4/machinedeployment_types.go b/api/v1alpha4/machinedeployment_types.go
index dd71006fe..46e70f3f9 100644
--- a/api/v1alpha4/machinedeployment_types.go
+++ b/api/v1alpha4/machinedeployment_types.go
@@ -306,7 +306,7 @@ type MachineDeploymentList struct {
 }
 
 func init() {
-	objectTypes = append(objectTypes, &MachineDeployment{}, &MachineDeploymentList{})
+	SchemeBuilder.Register(&MachineDeployment{}, &MachineDeploymentList{})
 }
 
 // GetConditions returns the set of conditions for the machinedeployment.
diff --git a/api/v1alpha4/machinehealthcheck_types.go b/api/v1alpha4/machinehealthcheck_types.go
index 2933768ca..338970f6b 100644
--- a/api/v1alpha4/machinehealthcheck_types.go
+++ b/api/v1alpha4/machinehealthcheck_types.go
@@ -171,5 +171,5 @@ type MachineHealthCheckList struct {
 }
 
 func init() {
-	objectTypes = append(objectTypes, &MachineHealthCheck{}, &MachineHealthCheckList{})
+	SchemeBuilder.Register(&MachineHealthCheck{}, &MachineHealthCheckList{})
 }
diff --git a/api/v1alpha4/machineset_types.go b/api/v1alpha4/machineset_types.go
index 59c2bb7a1..dbfdffdb5 100644
--- a/api/v1alpha4/machineset_types.go
+++ b/api/v1alpha4/machineset_types.go
@@ -229,7 +229,7 @@ type MachineSetList struct {
 }
 
 func init() {
-	objectTypes = append(objectTypes, &MachineSet{}, &MachineSetList{})
+	SchemeBuilder.Register(&MachineSet{}, &MachineSetList{})
 }
 
 // GetConditions returns the set of conditions for the MachineSet.
diff --git a/api/v1alpha4/zz_generated.conversion.go b/api/v1alpha4/zz_generated.conversion.go
index cbdf0746e..e89720cc1 100644
--- a/api/v1alpha4/zz_generated.conversion.go
+++ b/api/v1alpha4/zz_generated.conversion.go
@@ -863,6 +863,7 @@ func autoConvert_v1beta1_ControlPlaneClass_To_v1alpha4_ControlPlaneClass(in *v1b
 	}
 	out.MachineInfrastructure = (*LocalObjectTemplate)(unsafe.Pointer(in.MachineInfrastructure))
 	// WARNING: in.MachineHealthCheck requires manual conversion: does not exist in peer-type
+	// WARNING: in.NamingStrategy requires manual conversion: does not exist in peer-type
 	// WARNING: in.NodeDrainTimeout requires manual conversion: does not exist in peer-type
 	// WARNING: in.NodeVolumeDetachTimeout requires manual conversion: does not exist in peer-type
 	// WARNING: in.NodeDeletionTimeout requires manual conversion: does not exist in peer-type
@@ -1042,6 +1043,7 @@ func autoConvert_v1beta1_MachineDeploymentClass_To_v1alpha4_MachineDeploymentCla
 	}
 	// WARNING: in.MachineHealthCheck requires manual conversion: does not exist in peer-type
 	// WARNING: in.FailureDomain requires manual conversion: does not exist in peer-type
+	// WARNING: in.NamingStrategy requires manual conversion: does not exist in peer-type
 	// WARNING: in.NodeDrainTimeout requires manual conversion: does not exist in peer-type
 	// WARNING: in.NodeVolumeDetachTimeout requires manual conversion: does not exist in peer-type
 	// WARNING: in.NodeDeletionTimeout requires manual conversion: does not exist in peer-type
diff --git a/api/v1beta1/.import-restrictions b/api/v1beta1/.import-restrictions
deleted file mode 100644
index a2e1dfd08..000000000
--- a/api/v1beta1/.import-restrictions
+++ /dev/null
@@ -1,5 +0,0 @@
-rules:
-  - selectorRegexp: sigs[.]k8s[.]io/controller-runtime
-    allowedPrefixes: []
-    forbiddenPrefixes:
-      - "sigs.k8s.io/controller-runtime"
diff --git a/api/v1beta1/clusterclass_types.go b/api/v1beta1/clusterclass_types.go
index f699df581..19cdad710 100644
--- a/api/v1beta1/clusterclass_types.go
+++ b/api/v1beta1/clusterclass_types.go
@@ -106,6 +106,10 @@ type ControlPlaneClass struct {
 	// +optional
 	MachineHealthCheck *MachineHealthCheckClass `json:"machineHealthCheck,omitempty"`
 
+	// NamingStrategy allows changing the naming pattern used when creating the control plane provider object.
+	// +optional
+	NamingStrategy *ControlPlaneClassNamingStrategy `json:"namingStrategy,omitempty"`
+
 	// NodeDrainTimeout is the total amount of time that the controller will spend on draining a node.
 	// The default value is 0, meaning that the node can be drained without any time limitations.
 	// NOTE: NodeDrainTimeout is different from `kubectl drain --timeout`
@@ -127,6 +131,19 @@ type ControlPlaneClass struct {
 	NodeDeletionTimeout *metav1.Duration `json:"nodeDeletionTimeout,omitempty"`
 }
 
+// ControlPlaneClassNamingStrategy defines the naming strategy for control plane objects.
+type ControlPlaneClassNamingStrategy struct {
+	// Template defines the template to use for generating the name of the ControlPlane object.
+	// If not defined, it will fallback to `{{ .cluster.name }}-{{ .random }}`.
+	// If the templated string exceeds 63 characters, it will be trimmed to 58 characters and will
+	// get concatenated with a random suffix of length 5.
+	// The templating mechanism provides the following arguments:
+	// * `.cluster.name`: The name of the cluster object.
+	// * `.random`: A random alphanumeric string, without vowels, of length 5.
+	// +optional
+	Template *string `json:"template,omitempty"`
+}
+
 // WorkersClass is a collection of deployment classes.
 type WorkersClass struct {
 	// MachineDeployments is a list of machine deployment classes that can be used to create
@@ -162,6 +179,10 @@ type MachineDeploymentClass struct {
 	// +optional
 	FailureDomain *string `json:"failureDomain,omitempty"`
 
+	// NamingStrategy allows changing the naming pattern used when creating the MachineDeployment.
+	// +optional
+	NamingStrategy *MachineDeploymentClassNamingStrategy `json:"namingStrategy,omitempty"`
+
 	// NodeDrainTimeout is the total amount of time that the controller will spend on draining a node.
 	// The default value is 0, meaning that the node can be drained without any time limitations.
 	// NOTE: NodeDrainTimeout is different from `kubectl drain --timeout`
@@ -212,6 +233,20 @@ type MachineDeploymentClassTemplate struct {
 	Infrastructure LocalObjectTemplate `json:"infrastructure"`
 }
 
+// MachineDeploymentClassNamingStrategy defines the naming strategy for machine deployment objects.
+type MachineDeploymentClassNamingStrategy struct {
+	// Template defines the template to use for generating the name of the MachineDeployment object.
+	// If not defined, it will fallback to `{{ .cluster.name }}-{{ .machineDeployment.topologyName }}-{{ .random }}`.
+	// If the templated string exceeds 63 characters, it will be trimmed to 58 characters and will
+	// get concatenated with a random suffix of length 5.
+	// The templating mechanism provides the following arguments:
+	// * `.cluster.name`: The name of the cluster object.
+	// * `.random`: A random alphanumeric string, without vowels, of length 5.
+	// * `.machineDeployment.topologyName`: The name of the MachineDeployment topology (Cluster.spec.topology.workers.machineDeployments[].name).
+	// +optional
+	Template *string `json:"template,omitempty"`
+}
+
 // MachineHealthCheckClass defines a MachineHealthCheck for a group of Machines.
 type MachineHealthCheckClass struct {
 	// UnhealthyConditions contains a list of the conditions that determine
@@ -267,6 +302,10 @@ type MachinePoolClass struct {
 	// +optional
 	FailureDomains []string `json:"failureDomains,omitempty"`
 
+	// NamingStrategy allows changing the naming pattern used when creating the MachinePool.
+	// +optional
+	NamingStrategy *MachinePoolClassNamingStrategy `json:"namingStrategy,omitempty"`
+
 	// NodeDrainTimeout is the total amount of time that the controller will spend on draining a node.
 	// The default value is 0, meaning that the node can be drained without any time limitations.
 	// NOTE: NodeDrainTimeout is different from `kubectl drain --timeout`
@@ -312,6 +351,20 @@ type MachinePoolClassTemplate struct {
 	Infrastructure LocalObjectTemplate `json:"infrastructure"`
 }
 
+// MachinePoolClassNamingStrategy defines the naming strategy for machine pool objects.
+type MachinePoolClassNamingStrategy struct {
+	// Template defines the template to use for generating the name of the MachinePool object.
+	// If not defined, it will fallback to `{{ .cluster.name }}-{{ .machinePool.topologyName }}-{{ .random }}`.
+	// If the templated string exceeds 63 characters, it will be trimmed to 58 characters and will
+	// get concatenated with a random suffix of length 5.
+	// The templating mechanism provides the following arguments:
+	// * `.cluster.name`: The name of the cluster object.
+	// * `.random`: A random alphanumeric string, without vowels, of length 5.
+	// * `.machinePool.topologyName`: The name of the MachinePool topology (Cluster.spec.topology.workers.machinePools[].name).
+	// +optional
+	Template *string `json:"template,omitempty"`
+}
+
 // IsZero returns true if none of the values of MachineHealthCheckClass are defined.
 func (m MachineHealthCheckClass) IsZero() bool {
 	return reflect.ValueOf(m).IsZero()
diff --git a/api/v1beta1/index/.import-restrictions b/api/v1beta1/index/.import-restrictions
deleted file mode 100644
index b3ee9a15d..000000000
--- a/api/v1beta1/index/.import-restrictions
+++ /dev/null
@@ -1,5 +0,0 @@
-rules:
-  - selectorRegexp: sigs[.]k8s[.]io/controller-runtime
-    allowedPrefixes:
-      - "sigs.k8s.io/controller-runtime"
-    forbiddenPrefixes: []
diff --git a/api/v1beta1/machinedeployment_types.go b/api/v1beta1/machinedeployment_types.go
index 13a023d07..88d43370b 100644
--- a/api/v1beta1/machinedeployment_types.go
+++ b/api/v1beta1/machinedeployment_types.go
@@ -152,8 +152,8 @@ type MachineDeploymentSpec struct {
 // MachineDeploymentStrategy describes how to replace existing machines
 // with new ones.
 type MachineDeploymentStrategy struct {
-	// Type of deployment. Allowed values are RollingUpdate and OnDelete.
-	// The default is RollingUpdate.
+	// Type of deployment.
+	// Default is RollingUpdate.
 	// +kubebuilder:validation:Enum=RollingUpdate;OnDelete
 	// +optional
 	Type MachineDeploymentStrategyType `json:"type,omitempty"`
diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go
index b9698d606..7eb964845 100644
--- a/api/v1beta1/zz_generated.deepcopy.go
+++ b/api/v1beta1/zz_generated.deepcopy.go
@@ -507,6 +507,11 @@ func (in *ControlPlaneClass) DeepCopyInto(out *ControlPlaneClass) {
 		*out = new(MachineHealthCheckClass)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.NamingStrategy != nil {
+		in, out := &in.NamingStrategy, &out.NamingStrategy
+		*out = new(ControlPlaneClassNamingStrategy)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.NodeDrainTimeout != nil {
 		in, out := &in.NodeDrainTimeout, &out.NodeDrainTimeout
 		*out = new(metav1.Duration)
@@ -534,6 +539,26 @@ func (in *ControlPlaneClass) DeepCopy() *ControlPlaneClass {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ControlPlaneClassNamingStrategy) DeepCopyInto(out *ControlPlaneClassNamingStrategy) {
+	*out = *in
+	if in.Template != nil {
+		in, out := &in.Template, &out.Template
+		*out = new(string)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControlPlaneClassNamingStrategy.
+func (in *ControlPlaneClassNamingStrategy) DeepCopy() *ControlPlaneClassNamingStrategy {
+	if in == nil {
+		return nil
+	}
+	out := new(ControlPlaneClassNamingStrategy)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ControlPlaneTopology) DeepCopyInto(out *ControlPlaneTopology) {
 	*out = *in
@@ -911,6 +936,11 @@ func (in *MachineDeploymentClass) DeepCopyInto(out *MachineDeploymentClass) {
 		*out = new(string)
 		**out = **in
 	}
+	if in.NamingStrategy != nil {
+		in, out := &in.NamingStrategy, &out.NamingStrategy
+		*out = new(MachineDeploymentClassNamingStrategy)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.NodeDrainTimeout != nil {
 		in, out := &in.NodeDrainTimeout, &out.NodeDrainTimeout
 		*out = new(metav1.Duration)
@@ -948,6 +978,26 @@ func (in *MachineDeploymentClass) DeepCopy() *MachineDeploymentClass {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *MachineDeploymentClassNamingStrategy) DeepCopyInto(out *MachineDeploymentClassNamingStrategy) {
+	*out = *in
+	if in.Template != nil {
+		in, out := &in.Template, &out.Template
+		*out = new(string)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineDeploymentClassNamingStrategy.
+func (in *MachineDeploymentClassNamingStrategy) DeepCopy() *MachineDeploymentClassNamingStrategy {
+	if in == nil {
+		return nil
+	}
+	out := new(MachineDeploymentClassNamingStrategy)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *MachineDeploymentClassTemplate) DeepCopyInto(out *MachineDeploymentClassTemplate) {
 	*out = *in
@@ -1398,6 +1448,11 @@ func (in *MachinePoolClass) DeepCopyInto(out *MachinePoolClass) {
 		*out = make([]string, len(*in))
 		copy(*out, *in)
 	}
+	if in.NamingStrategy != nil {
+		in, out := &in.NamingStrategy, &out.NamingStrategy
+		*out = new(MachinePoolClassNamingStrategy)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.NodeDrainTimeout != nil {
 		in, out := &in.NodeDrainTimeout, &out.NodeDrainTimeout
 		*out = new(metav1.Duration)
@@ -1430,6 +1485,26 @@ func (in *MachinePoolClass) DeepCopy() *MachinePoolClass {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *MachinePoolClassNamingStrategy) DeepCopyInto(out *MachinePoolClassNamingStrategy) {
+	*out = *in
+	if in.Template != nil {
+		in, out := &in.Template, &out.Template
+		*out = new(string)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachinePoolClassNamingStrategy.
+func (in *MachinePoolClassNamingStrategy) DeepCopy() *MachinePoolClassNamingStrategy {
+	if in == nil {
+		return nil
+	}
+	out := new(MachinePoolClassNamingStrategy)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *MachinePoolClassTemplate) DeepCopyInto(out *MachinePoolClassTemplate) {
 	*out = *in
diff --git a/api/v1beta1/zz_generated.openapi.go b/api/v1beta1/zz_generated.openapi.go
index be72a7122..51faf83d1 100644
--- a/api/v1beta1/zz_generated.openapi.go
+++ b/api/v1beta1/zz_generated.openapi.go
@@ -48,6 +48,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
 		"sigs.k8s.io/cluster-api/api/v1beta1.ClusterVariable":                          schema_sigsk8sio_cluster_api_api_v1beta1_ClusterVariable(ref),
 		"sigs.k8s.io/cluster-api/api/v1beta1.Condition":                                schema_sigsk8sio_cluster_api_api_v1beta1_Condition(ref),
 		"sigs.k8s.io/cluster-api/api/v1beta1.ControlPlaneClass":                        schema_sigsk8sio_cluster_api_api_v1beta1_ControlPlaneClass(ref),
+		"sigs.k8s.io/cluster-api/api/v1beta1.ControlPlaneClassNamingStrategy":          schema_sigsk8sio_cluster_api_api_v1beta1_ControlPlaneClassNamingStrategy(ref),
 		"sigs.k8s.io/cluster-api/api/v1beta1.ControlPlaneTopology":                     schema_sigsk8sio_cluster_api_api_v1beta1_ControlPlaneTopology(ref),
 		"sigs.k8s.io/cluster-api/api/v1beta1.ExternalPatchDefinition":                  schema_sigsk8sio_cluster_api_api_v1beta1_ExternalPatchDefinition(ref),
 		"sigs.k8s.io/cluster-api/api/v1beta1.FailureDomainSpec":                        schema_sigsk8sio_cluster_api_api_v1beta1_FailureDomainSpec(ref),
@@ -59,6 +60,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
 		"sigs.k8s.io/cluster-api/api/v1beta1.MachineAddress":                           schema_sigsk8sio_cluster_api_api_v1beta1_MachineAddress(ref),
 		"sigs.k8s.io/cluster-api/api/v1beta1.MachineDeployment":                        schema_sigsk8sio_cluster_api_api_v1beta1_MachineDeployment(ref),
 		"sigs.k8s.io/cluster-api/api/v1beta1.MachineDeploymentClass":                   schema_sigsk8sio_cluster_api_api_v1beta1_MachineDeploymentClass(ref),
+		"sigs.k8s.io/cluster-api/api/v1beta1.MachineDeploymentClassNamingStrategy":     schema_sigsk8sio_cluster_api_api_v1beta1_MachineDeploymentClassNamingStrategy(ref),
 		"sigs.k8s.io/cluster-api/api/v1beta1.MachineDeploymentClassTemplate":           schema_sigsk8sio_cluster_api_api_v1beta1_MachineDeploymentClassTemplate(ref),
 		"sigs.k8s.io/cluster-api/api/v1beta1.MachineDeploymentList":                    schema_sigsk8sio_cluster_api_api_v1beta1_MachineDeploymentList(ref),
 		"sigs.k8s.io/cluster-api/api/v1beta1.MachineDeploymentSpec":                    schema_sigsk8sio_cluster_api_api_v1beta1_MachineDeploymentSpec(ref),
@@ -74,6 +76,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
 		"sigs.k8s.io/cluster-api/api/v1beta1.MachineHealthCheckTopology":               schema_sigsk8sio_cluster_api_api_v1beta1_MachineHealthCheckTopology(ref),
 		"sigs.k8s.io/cluster-api/api/v1beta1.MachineList":                              schema_sigsk8sio_cluster_api_api_v1beta1_MachineList(ref),
 		"sigs.k8s.io/cluster-api/api/v1beta1.MachinePoolClass":                         schema_sigsk8sio_cluster_api_api_v1beta1_MachinePoolClass(ref),
+		"sigs.k8s.io/cluster-api/api/v1beta1.MachinePoolClassNamingStrategy":           schema_sigsk8sio_cluster_api_api_v1beta1_MachinePoolClassNamingStrategy(ref),
 		"sigs.k8s.io/cluster-api/api/v1beta1.MachinePoolClassTemplate":                 schema_sigsk8sio_cluster_api_api_v1beta1_MachinePoolClassTemplate(ref),
 		"sigs.k8s.io/cluster-api/api/v1beta1.MachinePoolTopology":                      schema_sigsk8sio_cluster_api_api_v1beta1_MachinePoolTopology(ref),
 		"sigs.k8s.io/cluster-api/api/v1beta1.MachinePoolVariables":                     schema_sigsk8sio_cluster_api_api_v1beta1_MachinePoolVariables(ref),
@@ -958,6 +961,12 @@ func schema_sigsk8sio_cluster_api_api_v1beta1_ControlPlaneClass(ref common.Refer
 							Ref:         ref("sigs.k8s.io/cluster-api/api/v1beta1.MachineHealthCheckClass"),
 						},
 					},
+					"namingStrategy": {
+						SchemaProps: spec.SchemaProps{
+							Description: "NamingStrategy allows changing the naming pattern used when creating the control plane provider object.",
+							Ref:         ref("sigs.k8s.io/cluster-api/api/v1beta1.ControlPlaneClassNamingStrategy"),
+						},
+					},
 					"nodeDrainTimeout": {
 						SchemaProps: spec.SchemaProps{
 							Description: "NodeDrainTimeout is the total amount of time that the controller will spend on draining a node. The default value is 0, meaning that the node can be drained without any time limitations. NOTE: NodeDrainTimeout is different from `kubectl drain --timeout` NOTE: This value can be overridden while defining a Cluster.Topology.",
@@ -981,7 +990,27 @@ func schema_sigsk8sio_cluster_api_api_v1beta1_ControlPlaneClass(ref common.Refer
 			},
 		},
 		Dependencies: []string{
-			"k8s.io/api/core/v1.ObjectReference", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration", "sigs.k8s.io/cluster-api/api/v1beta1.LocalObjectTemplate", "sigs.k8s.io/cluster-api/api/v1beta1.MachineHealthCheckClass", "sigs.k8s.io/cluster-api/api/v1beta1.ObjectMeta"},
+			"k8s.io/api/core/v1.ObjectReference", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration", "sigs.k8s.io/cluster-api/api/v1beta1.ControlPlaneClassNamingStrategy", "sigs.k8s.io/cluster-api/api/v1beta1.LocalObjectTemplate", "sigs.k8s.io/cluster-api/api/v1beta1.MachineHealthCheckClass", "sigs.k8s.io/cluster-api/api/v1beta1.ObjectMeta"},
+	}
+}
+
+func schema_sigsk8sio_cluster_api_api_v1beta1_ControlPlaneClassNamingStrategy(ref common.ReferenceCallback) common.OpenAPIDefinition {
+	return common.OpenAPIDefinition{
+		Schema: spec.Schema{
+			SchemaProps: spec.SchemaProps{
+				Description: "ControlPlaneClassNamingStrategy defines the naming strategy for control plane objects.",
+				Type:        []string{"object"},
+				Properties: map[string]spec.Schema{
+					"template": {
+						SchemaProps: spec.SchemaProps{
+							Description: "Template defines the template to use for generating the name of the ControlPlane object. If not defined, it will fallback to `{{ .cluster.name }}-{{ .random }}`. If the templated string exceeds 63 characters, it will be trimmed to 58 characters and will get concatenated with a random suffix of length 5. The templating mechanism provides the following arguments: * `.cluster.name`: The name of the cluster object. * `.random`: A random alphanumeric string, without vowels, of length 5.",
+							Type:        []string{"string"},
+							Format:      "",
+						},
+					},
+				},
+			},
+		},
 	}
 }
 
@@ -1559,6 +1588,12 @@ func schema_sigsk8sio_cluster_api_api_v1beta1_MachineDeploymentClass(ref common.
 							Format:      "",
 						},
 					},
+					"namingStrategy": {
+						SchemaProps: spec.SchemaProps{
+							Description: "NamingStrategy allows changing the naming pattern used when creating the MachineDeployment.",
+							Ref:         ref("sigs.k8s.io/cluster-api/api/v1beta1.MachineDeploymentClassNamingStrategy"),
+						},
+					},
 					"nodeDrainTimeout": {
 						SchemaProps: spec.SchemaProps{
 							Description: "NodeDrainTimeout is the total amount of time that the controller will spend on draining a node. The default value is 0, meaning that the node can be drained without any time limitations. NOTE: NodeDrainTimeout is different from `kubectl drain --timeout` NOTE: This value can be overridden while defining a Cluster.Topology using this MachineDeploymentClass.",
@@ -1595,7 +1630,27 @@ func schema_sigsk8sio_cluster_api_api_v1beta1_MachineDeploymentClass(ref common.
 			},
 		},
 		Dependencies: []string{
-			"k8s.io/apimachinery/pkg/apis/meta/v1.Duration", "sigs.k8s.io/cluster-api/api/v1beta1.MachineDeploymentClassTemplate", "sigs.k8s.io/cluster-api/api/v1beta1.MachineDeploymentStrategy", "sigs.k8s.io/cluster-api/api/v1beta1.MachineHealthCheckClass"},
+			"k8s.io/apimachinery/pkg/apis/meta/v1.Duration", "sigs.k8s.io/cluster-api/api/v1beta1.MachineDeploymentClassNamingStrategy", "sigs.k8s.io/cluster-api/api/v1beta1.MachineDeploymentClassTemplate", "sigs.k8s.io/cluster-api/api/v1beta1.MachineDeploymentStrategy", "sigs.k8s.io/cluster-api/api/v1beta1.MachineHealthCheckClass"},
+	}
+}
+
+func schema_sigsk8sio_cluster_api_api_v1beta1_MachineDeploymentClassNamingStrategy(ref common.ReferenceCallback) common.OpenAPIDefinition {
+	return common.OpenAPIDefinition{
+		Schema: spec.Schema{
+			SchemaProps: spec.SchemaProps{
+				Description: "MachineDeploymentClassNamingStrategy defines the naming strategy for machine deployment objects.",
+				Type:        []string{"object"},
+				Properties: map[string]spec.Schema{
+					"template": {
+						SchemaProps: spec.SchemaProps{
+							Description: "Template defines the template to use for generating the name of the MachineDeployment object. If not defined, it will fallback to `{{ .cluster.name }}-{{ .machineDeployment.topologyName }}-{{ .random }}`. If the templated string exceeds 63 characters, it will be trimmed to 58 characters and will get concatenated with a random suffix of length 5. The templating mechanism provides the following arguments: * `.cluster.name`: The name of the cluster object. * `.random`: A random alphanumeric string, without vowels, of length 5. * `.machineDeployment.topologyName`: The name of the MachineDeployment topology (Cluster.spec.topology.workers.machineDeployments[].name).",
+							Type:        []string{"string"},
+							Format:      "",
+						},
+					},
+				},
+			},
+		},
 	}
 }
 
@@ -1869,7 +1924,7 @@ func schema_sigsk8sio_cluster_api_api_v1beta1_MachineDeploymentStrategy(ref comm
 				Properties: map[string]spec.Schema{
 					"type": {
 						SchemaProps: spec.SchemaProps{
-							Description: "Type of deployment. Allowed values are RollingUpdate and OnDelete. The default is RollingUpdate.",
+							Description: "Type of deployment. Default is RollingUpdate.",
 							Type:        []string{"string"},
 							Format:      "",
 						},
@@ -2457,6 +2512,12 @@ func schema_sigsk8sio_cluster_api_api_v1beta1_MachinePoolClass(ref common.Refere
 							},
 						},
 					},
+					"namingStrategy": {
+						SchemaProps: spec.SchemaProps{
+							Description: "NamingStrategy allows changing the naming pattern used when creating the MachinePool.",
+							Ref:         ref("sigs.k8s.io/cluster-api/api/v1beta1.MachinePoolClassNamingStrategy"),
+						},
+					},
 					"nodeDrainTimeout": {
 						SchemaProps: spec.SchemaProps{
 							Description: "NodeDrainTimeout is the total amount of time that the controller will spend on draining a node. The default value is 0, meaning that the node can be drained without any time limitations. NOTE: NodeDrainTimeout is different from `kubectl drain --timeout` NOTE: This value can be overridden while defining a Cluster.Topology using this MachinePoolClass.",
@@ -2487,7 +2548,27 @@ func schema_sigsk8sio_cluster_api_api_v1beta1_MachinePoolClass(ref common.Refere
 			},
 		},
 		Dependencies: []string{
-			"k8s.io/apimachinery/pkg/apis/meta/v1.Duration", "sigs.k8s.io/cluster-api/api/v1beta1.MachinePoolClassTemplate"},
+			"k8s.io/apimachinery/pkg/apis/meta/v1.Duration", "sigs.k8s.io/cluster-api/api/v1beta1.MachinePoolClassNamingStrategy", "sigs.k8s.io/cluster-api/api/v1beta1.MachinePoolClassTemplate"},
+	}
+}
+
+func schema_sigsk8sio_cluster_api_api_v1beta1_MachinePoolClassNamingStrategy(ref common.ReferenceCallback) common.OpenAPIDefinition {
+	return common.OpenAPIDefinition{
+		Schema: spec.Schema{
+			SchemaProps: spec.SchemaProps{
+				Description: "MachinePoolClassNamingStrategy defines the naming strategy for machine pool objects.",
+				Type:        []string{"object"},
+				Properties: map[string]spec.Schema{
+					"template": {
+						SchemaProps: spec.SchemaProps{
+							Description: "Template defines the template to use for generating the name of the MachinePool object. If not defined, it will fallback to `{{ .cluster.name }}-{{ .machinePool.topologyName }}-{{ .random }}`. If the templated string exceeds 63 characters, it will be trimmed to 58 characters and will get concatenated with a random suffix of length 5. The templating mechanism provides the following arguments: * `.cluster.name`: The name of the cluster object. * `.random`: A random alphanumeric string, without vowels, of length 5. * `.machinePool.topologyName`: The name of the MachinePool topology (Cluster.spec.topology.workers.machinePools[].name).",
+							Type:        []string{"string"},
+							Format:      "",
+						},
+					},
+				},
+			},
+		},
 	}
 }
 
diff --git a/cmd/clusterctl/client/config/cert_manager_client.go b/cmd/clusterctl/client/config/cert_manager_client.go
index 36e97250c..0079ea0d9 100644
--- a/cmd/clusterctl/client/config/cert_manager_client.go
+++ b/cmd/clusterctl/client/config/cert_manager_client.go
@@ -29,7 +29,7 @@ const (
 	CertManagerConfigKey = "cert-manager"
 
 	// CertManagerDefaultVersion defines the default cert-manager version to be used by clusterctl.
-	CertManagerDefaultVersion = "v1.13.0"
+	CertManagerDefaultVersion = "v1.12.4"
 
 	// CertManagerDefaultURL defines the default cert-manager repository url to be used by clusterctl.
 	// NOTE: At runtime CertManagerDefaultVersion may be replaced with the
diff --git a/config/crd/bases/cluster.x-k8s.io_clusterclasses.yaml b/config/crd/bases/cluster.x-k8s.io_clusterclasses.yaml
index 026c7767a..579d02ad3 100644
--- a/config/crd/bases/cluster.x-k8s.io_clusterclasses.yaml
+++ b/config/crd/bases/cluster.x-k8s.io_clusterclasses.yaml
@@ -587,6 +587,22 @@ spec:
                           More info: http://kubernetes.io/docs/user-guide/labels'
                         type: object
                     type: object
+                  namingStrategy:
+                    description: NamingStrategy allows changing the naming pattern
+                      used when creating the control plane provider object.
+                    properties:
+                      template:
+                        description: 'Template defines the template to use for generating
+                          the name of the ControlPlane object. If not defined, it
+                          will fallback to `{{ .cluster.name }}-{{ .random }}`. If
+                          the templated string exceeds 63 characters, it will be trimmed
+                          to 58 characters and will get concatenated with a random
+                          suffix of length 5. The templating mechanism provides the
+                          following arguments: * `.cluster.name`: The name of the
+                          cluster object. * `.random`: A random alphanumeric string,
+                          without vowels, of length 5.'
+                        type: string
+                    type: object
                   nodeDeletionTimeout:
                     description: 'NodeDeletionTimeout defines how long the controller
                       will attempt to delete the Node that the Machine hosts after
@@ -1168,6 +1184,25 @@ spec:
                             using this MachineDeploymentClass.'
                           format: int32
                           type: integer
+                        namingStrategy:
+                          description: NamingStrategy allows changing the naming pattern
+                            used when creating the MachineDeployment.
+                          properties:
+                            template:
+                              description: 'Template defines the template to use for
+                                generating the name of the MachineDeployment object.
+                                If not defined, it will fallback to `{{ .cluster.name
+                                }}-{{ .machineDeployment.topologyName }}-{{ .random
+                                }}`. If the templated string exceeds 63 characters,
+                                it will be trimmed to 58 characters and will get concatenated
+                                with a random suffix of length 5. The templating mechanism
+                                provides the following arguments: * `.cluster.name`:
+                                The name of the cluster object. * `.random`: A random
+                                alphanumeric string, without vowels, of length 5.
+                                * `.machineDeployment.topologyName`: The name of the
+                                MachineDeployment topology (Cluster.spec.topology.workers.machineDeployments[].name).'
+                              type: string
+                          type: object
                         nodeDeletionTimeout:
                           description: 'NodeDeletionTimeout defines how long the controller
                             will attempt to delete the Node that the Machine hosts
@@ -1254,8 +1289,7 @@ spec:
                                   x-kubernetes-int-or-string: true
                               type: object
                             type:
-                              description: Type of deployment. Allowed values are
-                                RollingUpdate and OnDelete. The default is RollingUpdate.
+                              description: Type of deployment. Default is RollingUpdate.
                               enum:
                               - RollingUpdate
                               - OnDelete
@@ -1438,6 +1472,25 @@ spec:
                             using this MachinePoolClass.'
                           format: int32
                           type: integer
+                        namingStrategy:
+                          description: NamingStrategy allows changing the naming pattern
+                            used when creating the MachinePool.
+                          properties:
+                            template:
+                              description: 'Template defines the template to use for
+                                generating the name of the MachinePool object. If
+                                not defined, it will fallback to `{{ .cluster.name
+                                }}-{{ .machinePool.topologyName }}-{{ .random }}`.
+                                If the templated string exceeds 63 characters, it
+                                will be trimmed to 58 characters and will get concatenated
+                                with a random suffix of length 5. The templating mechanism
+                                provides the following arguments: * `.cluster.name`:
+                                The name of the cluster object. * `.random`: A random
+                                alphanumeric string, without vowels, of length 5.
+                                * `.machinePool.topologyName`: The name of the MachinePool
+                                topology (Cluster.spec.topology.workers.machinePools[].name).'
+                              type: string
+                          type: object
                         nodeDeletionTimeout:
                           description: 'NodeDeletionTimeout defines how long the controller
                             will attempt to delete the Node that the Machine hosts
diff --git a/config/crd/bases/cluster.x-k8s.io_clusters.yaml b/config/crd/bases/cluster.x-k8s.io_clusters.yaml
index f8bd0a426..a6d245e9d 100644
--- a/config/crd/bases/cluster.x-k8s.io_clusters.yaml
+++ b/config/crd/bases/cluster.x-k8s.io_clusters.yaml
@@ -1078,9 +1078,7 @@ spec:
                                       x-kubernetes-int-or-string: true
                                   type: object
                                 type:
-                                  description: Type of deployment. Allowed values
-                                    are RollingUpdate and OnDelete. The default is
-                                    RollingUpdate.
+                                  description: Type of deployment. Default is RollingUpdate.
                                   enum:
                                   - RollingUpdate
                                   - OnDelete
diff --git a/config/crd/bases/cluster.x-k8s.io_machinedeployments.yaml b/config/crd/bases/cluster.x-k8s.io_machinedeployments.yaml
index 8abaa5ec1..ec78b4705 100644
--- a/config/crd/bases/cluster.x-k8s.io_machinedeployments.yaml
+++ b/config/crd/bases/cluster.x-k8s.io_machinedeployments.yaml
@@ -698,8 +698,7 @@ spec:
                         x-kubernetes-int-or-string: true
                     type: object
                   type:
-                    description: Type of deployment. Allowed values are RollingUpdate
-                      and OnDelete. The default is RollingUpdate.
+                    description: Type of deployment. Default is RollingUpdate.
                     enum:
                     - RollingUpdate
                     - OnDelete
diff --git a/config/metrics/crd-clusterrole.yaml b/config/metrics/crd-clusterrole.yaml
deleted file mode 100644
index bcd5bde16..000000000
--- a/config/metrics/crd-clusterrole.yaml
+++ /dev/null
@@ -1,38 +0,0 @@
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  name: kube-state-metrics-custom-resource-capi
-  labels:
-    kube-state-metrics/aggregate-to-manager: "true"
-rules:
-- apiGroups:
-  - cluster.x-k8s.io
-  resources:
-  - clusterclasses
-  - clusters
-  - machinedeployments
-  - machinepools
-  - machinesets
-  - machines
-  - machinehealthchecks
-  verbs:
-  - get
-  - list
-  - watch
-- apiGroups:
-  - controlplane.cluster.x-k8s.io
-  resources:
-  - kubeadmcontrolplanes
-  verbs:
-  - get
-  - list
-  - watch
-- apiGroups:
-  - bootstrap.cluster.x-k8s.io
-  resources:
-  - kubeadmconfigs
-  verbs:
-  - get
-  - list
-  - watch
diff --git a/config/metrics/kustomization.yaml b/config/metrics/kustomization.yaml
deleted file mode 100644
index 3955c8d0d..000000000
--- a/config/metrics/kustomization.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-resources:
-  - ./crd-clusterrole.yaml
-
-namespace: observability
-
-configMapGenerator:
-- name: kube-state-metrics-crd-config-capi
-  files:
-  - capi.yaml=crd-metrics-config.yaml
-  options:
-    disableNameSuffixHash: true
-    labels:
-      kube-state-metrics/custom-resource: "true"
diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md
index 4cd2d6133..7b0d2a2ac 100644
--- a/docs/book/src/SUMMARY.md
+++ b/docs/book/src/SUMMARY.md
@@ -40,7 +40,6 @@
             - [Deploying Runtime Extensions](./tasks/experimental-features/runtime-sdk/deploy-runtime-extension.md)
         - [Ignition Bootstrap configuration](./tasks/experimental-features/ignition.md)
     - [Running multiple providers](./tasks/multiple-providers.md)
-    - [Verification of Container Images](./tasks/verify-container-images.md)
 - [Security Guidelines](./security/index.md)
     - [Pod Security Standards](./security/pod-security-standards.md)
 - [clusterctl CLI](./clusterctl/overview.md)
diff --git a/docs/book/src/clusterctl/commands/init.md b/docs/book/src/clusterctl/commands/init.md
index 309278c55..e18652d4d 100644
--- a/docs/book/src/clusterctl/commands/init.md
+++ b/docs/book/src/clusterctl/commands/init.md
@@ -190,7 +190,7 @@ If this happens, there are no guarantees about the proper functioning of `cluste
 Cluster API providers require a cert-manager version supporting the `cert-manager.io/v1` API to be installed in the cluster.
 
 While doing init, clusterctl checks if there is a version of cert-manager already installed. If not, clusterctl will
-install a default version (currently cert-manager v1.13.0). See [clusterctl configuration](../configuration.md) for
+install a default version (currently cert-manager v1.12.4). See [clusterctl configuration](../configuration.md) for
 available options to customize this operation.
 
 <aside class="note warning">
diff --git a/docs/book/src/developer/guide.md b/docs/book/src/developer/guide.md
index 3d9b54dc7..7e702adf9 100644
--- a/docs/book/src/developer/guide.md
+++ b/docs/book/src/developer/guide.md
@@ -81,7 +81,7 @@ The generated binary can be found at ./hack/tools/bin/envsubst
 You'll need to deploy [cert-manager] components on your [management cluster][mcluster], using `kubectl`
 
 ```bash
-kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml
+kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.4/cert-manager.yaml
 ```
 
 Ensure the cert-manager webhook service is ready before creating the Cluster API components.
diff --git a/docs/book/src/developer/tilt.md b/docs/book/src/developer/tilt.md
index 40d126270..958fcce2c 100644
--- a/docs/book/src/developer/tilt.md
+++ b/docs/book/src/developer/tilt.md
@@ -214,14 +214,6 @@ Supported values are:
 
 \*: Note: the UI will be accessible via a link in the tilt console
 
-**deploy_kustomizations** (map[string]string, default={}): If set, installs the additional kustomizations to the cluster.
-
-Example:
-```yaml
-deploy_kustomizations:
-  capv-metrics: ../cluster-api-provider-vsphere/config/metrics
-```
-
 **debug** (Map{string: Map} default{}): A map of named configurations for the provider. The key is the name of the provider.
 
 Supported settings:
diff --git a/docs/book/src/tasks/verify-container-images.md b/docs/book/src/tasks/verify-container-images.md
deleted file mode 100644
index 04c830fc3..000000000
--- a/docs/book/src/tasks/verify-container-images.md
+++ /dev/null
@@ -1,55 +0,0 @@
-# Verification of CAPI artifacts
-
-## Requirements 
-You will need to have the following tools installed:
-- cosign [(install guide)](https://docs.sigstore.dev/system_config/installation/)
-- jq [(download jq)](https://stedolan.github.io/jq/download/)
-
-## CAPI Images
-Each [release](https://github.com/kubernetes-sigs/cluster-api/releases) of the Cluster API project includes the following container images:
-- cluster-api-controller
-- kubeadm-bootstrap-controller
-- kubeadm-control-plane-controller
-- clusterctl
-
-## Verifying Image Signatures
-All of the four images are hosted by [registry.k8s.io](https://registry.k8s.io). In order to verify the authenticity of the images, you can use `cosign verify` command with the appropriate image name and version: 
-
-```bash
-$ cosign verify registry.k8s.io/cluster-api/cluster-api-controller:v1.5.0 --certificate-identity krel-trust@k8s-releng-prod.iam.gserviceaccount.com --certificate-oidc-issuer https://accounts.google.com | jq .
-```
-```text
-Verification for registry.k8s.io/cluster-api/cluster-api-controller:v1.5.0 --
-The following checks were performed on each of these signatures:
-  - The cosign claims were validated
-  - Existence of the claims in the transparency log was verified offline
-  - The code-signing certificate was verified using trusted certificate authority certificates
-[
-  {
-    "critical": {
-      "identity": {
-        "docker-reference": "registry.k8s.io/cluster-api/cluster-api-controller"
-      },
-      "image": {
-        "docker-manifest-digest": "sha256:f34016d3a494f9544a16137c9bba49d8756c574a0a1baf96257903409ef82f77"
-      },
-      "type": "cosign container image signature"
-    },
-    "optional": {
-      "1.3.6.1.4.1.57264.1.1": "https://accounts.google.com",
-      "Bundle": {
-        "SignedEntryTimestamp": "MEYCIQDtxr/v3uRl2QByVfYo1oopruADSaH3E4wThpmkibJs8gIhAIe0odbk99na5GBdYGjJ6IwpFzhlTlicgWOrsgxZH8LC",
-        "Payload": {
-          "body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIzMDMzNzY0MTQwZmI2OTE5ZjRmNDg2MDgwMDZjYzY1ODU2M2RkNjE0NWExMzVhMzE5MmQyYTAzNjE1OTRjMTRlIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJQ3RtcGdHN3RDcXNDYlk0VlpXNyt6Rm5tYWYzdjV4OTEwcWxlWGppdTFvbkFpQS9JUUVSSDErdit1a0hrTURSVnZnN1hPdXdqTTN4REFOdEZyS3NUMHFzaUE9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTTJha05EUVc1SFowRjNTVUpCWjBsVldqYzNUbGRSV1VacmQwNTVRMk13Y25GWWJIcHlXa3RyYURjMGQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcE5kMDU2U1RGTlZHTjNUa1JOTlZkb1kwNU5hazEzVG5wSk1VMVVZM2hPUkUwMVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZ4VEdveFJsSmhLM2RZTUVNd0sxYzFTVlZWUW14UmRsWkNWM2xLWTFRcmFWaERjV01LWTA4d1prVmpNV2s0TVUxSFQwRk1lVXB2UXpGNk5TdHVaRGxFUnpaSGNFSmpOV0ZJYXpoU1QxaDBOV2h6U21wa1VVdFBRMEZhUVhkblowZE5UVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlYxTVRoMENqWjVWMWxNVlU5RVR5dEVjek52VVU1RFNsYzNZMUJWZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDFGQldVUldVakJTUVZGSUwwSkVXWGRPU1VWNVlUTktiR0pETVRCamJsWjZaRVZDY2s5SVRYUmpiVlp6V2xjMWJreFlRbmxpTWxGMVlWZEdkQXBNYldSNldsaEtNbUZYVG14WlYwNXFZak5XZFdSRE5XcGlNakIzUzFGWlMwdDNXVUpDUVVkRWRucEJRa0ZSVVdKaFNGSXdZMGhOTmt4NU9XaFpNazUyQ21SWE5UQmplVFZ1WWpJNWJtSkhWWFZaTWpsMFRVTnpSME5wYzBkQlVWRkNaemM0ZDBGUlowVklVWGRpWVVoU01HTklUVFpNZVRsb1dUSk9kbVJYTlRBS1kzazFibUl5T1c1aVIxVjFXVEk1ZEUxSlIwdENaMjl5UW1kRlJVRmtXalZCWjFGRFFraDNSV1ZuUWpSQlNGbEJNMVF3ZDJGellraEZWRXBxUjFJMFl3cHRWMk16UVhGS1MxaHlhbVZRU3pNdmFEUndlV2RET0hBM2J6UkJRVUZIU21wblMxQmlkMEZCUWtGTlFWSjZRa1pCYVVKSmJXeGxTWEFyTm05WlpVWm9DbWRFTTI1Uk5sazBSV2g2U25SVmMxRTRSSEJrWTFGeU5FSk1XRE41ZDBsb1FVdFhkV05tYmxCUk9GaExPWGRZYkVwcVNWQTBZMFpFT0c1blpIazRkV29LYldreGN6RkRTamczTW1zclRVRnZSME5EY1VkVFRUUTVRa0ZOUkVFeVkwRk5SMUZEVFVoaU9YRjBSbGQxT1VGUU1FSXpaR3RKVkVZNGVrazRZVEkxVUFwb2IwbFBVVlJLVWxKeGFsVmlUMkUyVnpOMlRVZEJOWFpKTlZkVVJqQkZjREZwTWtGT2QwbDNSVko0TW5ocWVtWjNjbmRPYmxoUVpEQjRjbmd3WWxoRENtUmpOV0Z4WWxsWlVsRXdMMWhSVVdONFRFVnRkVGwzUnpGRlYydFNNWE01VEdaUGVHZDNVMjRLTFMwdExTMUZUa1FnUTBWU1ZFbEdTVU5CVkVVdExTMHRMUW89In19fX0=",
-          "integratedTime": 1690304684,
-          "logIndex": 28719030,
-          "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"
-        }
-      },
-      "Issuer": "https://accounts.google.com",
-      "Subject": "krel-trust@k8s-releng-prod.iam.gserviceaccount.com",
-      "org.kubernetes.kpromo.version": "kpromo-v4.0.3-5-ge99897c"
-    }
-  }
-]
-```
diff --git a/docs/release/OWNERS b/docs/release/OWNERS
deleted file mode 100644
index 774819574..000000000
--- a/docs/release/OWNERS
+++ /dev/null
@@ -1,7 +0,0 @@
-# See the OWNERS docs at https://go.k8s.io/owners
-
-approvers:
-  - cluster-api-release-lead
-
-reviewers:
-  - cluster-api-release-team
diff --git a/go.mod b/go.mod
index c2b4f4d96..ead1f09e8 100644
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@ require (
 	github.com/davecgh/go-spew v1.1.1
 	github.com/docker/distribution v2.8.2+incompatible
 	github.com/drone/envsubst/v2 v2.0.0-20210730161058-179042472c46
-	github.com/evanphx/json-patch/v5 v5.7.0
+	github.com/evanphx/json-patch/v5 v5.6.0
 	github.com/fatih/color v1.15.0
 	github.com/flatcar/container-linux-config-transpiler v0.9.4
 	github.com/flatcar/ignition v0.36.2
@@ -31,8 +31,8 @@ require (
 	github.com/valyala/fastjson v1.6.4
 	go.etcd.io/etcd/api/v3 v3.5.9
 	go.etcd.io/etcd/client/v3 v3.5.9
-	golang.org/x/oauth2 v0.12.0
-	golang.org/x/text v0.13.0
+	golang.org/x/oauth2 v0.11.0
+	golang.org/x/text v0.12.0
 	gomodules.xyz/jsonpatch/v2 v2.4.0
 	google.golang.org/grpc v1.55.1
 	k8s.io/api v0.28.1
@@ -138,12 +138,12 @@ require (
 	go.uber.org/multierr v1.11.0 // indirect
 	go.uber.org/zap v1.25.0 // indirect
 	go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
-	golang.org/x/crypto v0.13.0 // indirect
+	golang.org/x/crypto v0.12.0 // indirect
 	golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
-	golang.org/x/net v0.15.0 // indirect
+	golang.org/x/net v0.14.0 // indirect
 	golang.org/x/sync v0.3.0 // indirect
-	golang.org/x/sys v0.12.0 // indirect
-	golang.org/x/term v0.12.0 // indirect
+	golang.org/x/sys v0.11.0 // indirect
+	golang.org/x/term v0.11.0 // indirect
 	golang.org/x/time v0.3.0 // indirect
 	golang.org/x/tools v0.12.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
diff --git a/go.sum b/go.sum
index 019d42400..eda229239 100644
--- a/go.sum
+++ b/go.sum
@@ -140,8 +140,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
 github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
-github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc=
-github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
+github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
+github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
 github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
 github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
 github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
@@ -318,6 +318,7 @@ github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
@@ -568,8 +569,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
-golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
-golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
+golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -648,8 +649,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
-golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
-golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
+golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -659,8 +660,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
-golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
+golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
+golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -726,14 +727,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
-golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
+golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
-golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
-golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
+golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
+golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -743,8 +744,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
-golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
+golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
diff --git a/hack/observability/grafana/dashboards/cluster-api-performance.json b/hack/observability/grafana/dashboards/cluster-api-performance.json
index 3f40c62e7..366d6b7c1 100644
--- a/hack/observability/grafana/dashboards/cluster-api-performance.json
+++ b/hack/observability/grafana/dashboards/cluster-api-performance.json
@@ -156,7 +156,7 @@
             "uid": "prometheus"
           },
           "editorMode": "code",
-          "expr": "sum(capi_cluster_info{name=~\"${Cluster:pipe}\"})",
+          "expr": "sum(capi_cluster_info)",
           "legendFormat": "__auto",
           "range": true,
           "refId": "A"
@@ -221,7 +221,7 @@
             "uid": "prometheus"
           },
           "editorMode": "code",
-          "expr": "sum(capi_kubeadmcontrolplane_info{cluster_name=~\"${Cluster:pipe}\"})",
+          "expr": "sum(capi_kubeadmcontrolplane_info)",
           "legendFormat": "__auto",
           "range": true,
           "refId": "A"
@@ -286,7 +286,7 @@
             "uid": "prometheus"
           },
           "editorMode": "code",
-          "expr": "sum(capi_machinedeployment_info{cluster_name=~\"${Cluster:pipe}\"})",
+          "expr": "sum(capi_machinedeployment_info)",
           "legendFormat": "__auto",
           "range": true,
           "refId": "A"
@@ -351,7 +351,7 @@
             "uid": "prometheus"
           },
           "editorMode": "code",
-          "expr": "sum(capi_machineset_info{cluster_name=~\"${Cluster:pipe}\"})",
+          "expr": "sum(capi_machineset_info)",
           "legendFormat": "__auto",
           "range": true,
           "refId": "A"
@@ -416,7 +416,7 @@
             "uid": "prometheus"
           },
           "editorMode": "code",
-          "expr": "sum(capi_machinepool_info{cluster_name=~\"${Cluster:pipe}\"})",
+          "expr": "sum(capi_machinepool_info)",
           "hide": false,
           "legendFormat": "__auto",
           "range": true,
@@ -482,7 +482,7 @@
             "uid": "prometheus"
           },
           "editorMode": "code",
-          "expr": "sum(capi_machine_info{cluster_name=~\"${Cluster:pipe}\"})",
+          "expr": "sum(capi_machine_info)",
           "legendFormat": "__auto",
           "range": true,
           "refId": "A"
@@ -547,7 +547,7 @@
             "uid": "prometheus"
           },
           "editorMode": "code",
-          "expr": "sum(capi_machinehealthcheck_info{cluster_name=~\"${Cluster:pipe}\"})",
+          "expr": "sum(capi_machinehealthcheck_info)",
           "legendFormat": "__auto",
           "range": true,
           "refId": "A"
@@ -656,7 +656,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(capi_cluster_created{name=~\"${Cluster:pipe}\"}, \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\")) by (name)",
+              "expr": "avg(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(capi_cluster_created{}, \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\")) by (name)",
               "format": "time_series",
               "instant": true,
               "legendFormat": "__auto",
@@ -756,7 +756,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\",type=\"InfrastructureReady\"}[168h]) - on(name) group_left () capi_cluster_created{name=~\"${Cluster:pipe}\"}) by (name)",
+              "expr": "avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\",type=\"InfrastructureReady\"}[168h]) - on(name) group_left () capi_cluster_created{}) by (name)",
               "format": "time_series",
               "instant": true,
               "legendFormat": "__auto",
@@ -856,7 +856,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\",type=\"ControlPlaneReady\"}[168h]) - on(name) group_left () capi_cluster_created{name=~\"${Cluster:pipe}\"}) by (name)",
+              "expr": "avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\",type=\"ControlPlaneReady\"}[168h]) - on(name) group_left () capi_cluster_created{}) by (name)",
               "format": "time_series",
               "instant": true,
               "legendFormat": "__auto",
@@ -923,7 +923,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\",type=\"InfrastructureReady\"}[168h]) - on(name) group_left () capi_cluster_created{name=~\"${Cluster:pipe}\"})",
+              "expr": "avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\",type=\"InfrastructureReady\"}[168h]) - on(name) group_left () capi_cluster_created)",
               "legendFormat": "until InfrastructureReady since creationTimestamp",
               "range": true,
               "refId": "A"
@@ -934,7 +934,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\",type=\"ControlPlaneReady\"}[168h]) - on(name) group_left () capi_cluster_created{name=~\"${Cluster:pipe}\"})",
+              "expr": "avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\",type=\"ControlPlaneReady\"}[168h]) - on(name) group_left () capi_cluster_created{})",
               "hide": false,
               "legendFormat": "until ControlPlaneReady since creationTimestamp",
               "range": true,
@@ -946,7 +946,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(capi_cluster_created{name=~\"${Cluster:pipe}\"}, \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))",
+              "expr": "avg(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(capi_cluster_created{}, \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))",
               "hide": false,
               "legendFormat": "until last Machine NodeHealthy since creationTimestamp",
               "range": true,
@@ -1035,7 +1035,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\",type=\"InfrastructureReady\"}[168h]) - on(name) group_left () capi_cluster_created{name=~\"${Cluster:pipe}\"}) by (name)",
+              "expr": "avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\",type=\"InfrastructureReady\"}[168h]) - on(name) group_left () capi_cluster_created) by (name)",
               "format": "table",
               "instant": true,
               "legendFormat": "__auto",
@@ -1049,7 +1049,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\",type=\"ControlPlaneReady\"}[168h]) - on(name) group_left () capi_cluster_created{name=~\"${Cluster:pipe}\"}) by (name)",
+              "expr": "avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\",type=\"ControlPlaneReady\"}[168h]) - on(name) group_left () capi_cluster_created) by (name)",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -1064,7 +1064,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(capi_cluster_created{name=~\"${Cluster:pipe}\"}, \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\")) by (name)",
+              "expr": "avg(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(capi_cluster_created{}, \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\")) by (name)",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -1079,7 +1079,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "count(capi_cluster_info{name=~\"${Cluster:pipe}\"} * on(name) group_right () label_replace(capi_machine_info{control_plane_name!=\"\"}, \"name\", \"$0\", \"cluster_name\", \".*\")) by(name)",
+              "expr": "count(capi_cluster_info{} * on(name) group_right () label_replace(capi_machine_info{control_plane_name!=\"\"}, \"name\", \"$0\", \"cluster_name\", \".*\")) by(name)",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -1094,7 +1094,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "count(capi_cluster_info{name=~\"${Cluster:pipe}\"} * on(name) group_right () label_replace(capi_machine_info{control_plane_name=\"\"}, \"name\", \"$0\", \"cluster_name\", \".*\")) by(name)",
+              "expr": "count(capi_cluster_info{} * on(name) group_right () label_replace(capi_machine_info{control_plane_name=\"\"}, \"name\", \"$0\", \"cluster_name\", \".*\")) by(name)",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -1109,7 +1109,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(capi_cluster_created{name=~\"${Cluster:pipe}\"}) by (name) * 1000",
+              "expr": "avg(capi_cluster_created) by (name) * 1000",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -1258,7 +1258,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () capi_cluster_created{name=~\"${Cluster:pipe}\"}) by (type)",
+              "expr": "avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () capi_cluster_created) by (type)",
               "legendFormat": "{{type}}",
               "range": true,
               "refId": "A"
@@ -1356,7 +1356,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg((min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h])>= time() - 60*5) - on(name) group_left () capi_cluster_created{name=~\"${Cluster:pipe}\"}) by (type)",
+              "expr": "avg((min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h])>= time() - 60*5) - on(name) group_left () capi_cluster_created) by (type)",
               "legendFormat": "{{type}}",
               "range": true,
               "refId": "A"
@@ -1454,7 +1454,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(capi_cluster_created{name=~\"${Cluster:pipe}\"}, \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name))",
+              "expr": "avg(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(capi_cluster_created{}, \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name))",
               "legendFormat": "Average",
               "range": true,
               "refId": "A"
@@ -1567,7 +1567,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_kubeadmcontrolplane_status_condition_last_transition_time{status=\"True\",type=\"Ready\"}[168h]) - on(name) group_left () capi_kubeadmcontrolplane_created{cluster_name=~\"${Cluster:pipe}\"}) by (name)",
+              "expr": "avg(min_over_time(capi_kubeadmcontrolplane_status_condition_last_transition_time{status=\"True\",type=\"Ready\"}[168h]) - on(name) group_left () capi_kubeadmcontrolplane_created) by (name)",
               "format": "time_series",
               "instant": true,
               "legendFormat": "__auto",
@@ -1634,7 +1634,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg(min_over_time(capi_kubeadmcontrolplane_status_condition_last_transition_time{status=\"True\",type=\"Ready\"}[168h]) - on(name) group_left () capi_kubeadmcontrolplane_created{cluster_name=~\"${Cluster:pipe}\"})",
+              "expr": "avg(min_over_time(capi_kubeadmcontrolplane_status_condition_last_transition_time{status=\"True\",type=\"Ready\"}[168h]) - on(name) group_left () capi_kubeadmcontrolplane_created)",
               "legendFormat": "__auto",
               "range": true,
               "refId": "A"
@@ -1721,7 +1721,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_kubeadmcontrolplane_status_condition_last_transition_time{status=\"True\",type=\"Ready\"}[168h]) - on(name) group_left () capi_kubeadmcontrolplane_created{cluster_name=~\"${Cluster:pipe}\"}) by (name)",
+              "expr": "avg(min_over_time(capi_kubeadmcontrolplane_status_condition_last_transition_time{status=\"True\",type=\"Ready\"}[168h]) - on(name) group_left () capi_kubeadmcontrolplane_created) by (name)",
               "format": "table",
               "instant": true,
               "legendFormat": "__auto",
@@ -1735,7 +1735,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(capi_kubeadmcontrolplane_created{cluster_name=~\"${Cluster:pipe}\"}) by (name) * 1000",
+              "expr": "avg(capi_kubeadmcontrolplane_created) by (name) * 1000",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -1773,7 +1773,7 @@
                 },
                 "renameByName": {
                   "Value": "Time until Ready",
-                  "Value #A": "Time until Ready",
+                  "Value #A": "Time until InfrastructureReady",
                   "Value #B": "Creation Timestamp",
                   "Value #C": "Time until last Machine NodeHealthy",
                   "Value #D": "# CP",
@@ -1874,7 +1874,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg(min_over_time(capi_kubeadmcontrolplane_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () capi_kubeadmcontrolplane_created{cluster_name=~\"${Cluster:pipe}\"}) by (type)",
+              "expr": "avg(min_over_time(capi_kubeadmcontrolplane_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () capi_kubeadmcontrolplane_created) by (type)",
               "legendFormat": "{{type}}",
               "range": true,
               "refId": "A"
@@ -1972,7 +1972,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg((min_over_time(capi_kubeadmcontrolplane_status_condition_last_transition_time{status=\"True\"}[168h])>= time() - 60*5) - on(name) group_left () capi_kubeadmcontrolplane_created{cluster_name=~\"${Cluster:pipe}\"}) by (type)",
+              "expr": "avg((min_over_time(capi_kubeadmcontrolplane_status_condition_last_transition_time{status=\"True\"}[168h])>= time() - 60*5) - on(name) group_left () capi_kubeadmcontrolplane_created) by (type)",
               "legendFormat": "{{type}}",
               "range": true,
               "refId": "A"
@@ -2084,7 +2084,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h]) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name))) by (name)",
+              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name))) by (name)",
               "format": "time_series",
               "instant": true,
               "legendFormat": "__auto",
@@ -2184,7 +2184,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"BootstrapReady\"}[168h]) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name))) by (name)",
+              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"BootstrapReady\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name))) by (name)",
               "format": "time_series",
               "instant": true,
               "legendFormat": "__auto",
@@ -2284,7 +2284,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"InfrastructureReady\"}[168h]) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name))) by (name)",
+              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"InfrastructureReady\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name))) by (name)",
               "format": "time_series",
               "instant": true,
               "legendFormat": "__auto",
@@ -2351,7 +2351,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"BootstrapReady\"}[168h]) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name)))",
+              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"BootstrapReady\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name)))",
               "legendFormat": "until BootstrapReady since creationTimestamp",
               "range": true,
               "refId": "A"
@@ -2362,7 +2362,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"InfrastructureReady\"}[168h]) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name)))",
+              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"InfrastructureReady\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name)))",
               "hide": false,
               "legendFormat": "until InfrastructureReady since creationTimestamp",
               "range": true,
@@ -2374,7 +2374,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h]) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name)))",
+              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name)))",
               "hide": false,
               "legendFormat": "until NodeHealthy since creationTimestamp",
               "range": true,
@@ -2462,7 +2462,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"BootstrapReady\"}[168h]) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name))) by (name)",
+              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"BootstrapReady\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name))) by (name)",
               "format": "table",
               "instant": true,
               "legendFormat": "__auto",
@@ -2476,7 +2476,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"InfrastructureReady\"}[168h]) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name))) by (name)",
+              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"InfrastructureReady\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name))) by (name)",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -2491,7 +2491,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h]) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name))) by (name)",
+              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name))) by (name)",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -2506,7 +2506,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right capi_machine_info{control_plane_name!=\"\"}) by (name) * 1000",
+              "expr": "avg(capi_machine_created * on (name) group_right capi_machine_info{control_plane_name!=\"\"}) by (name) * 1000",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -2647,7 +2647,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name))) by (type)",
+              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name))) by (type)",
               "legendFormat": "{{type}}",
               "range": true,
               "refId": "A"
@@ -2745,7 +2745,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg((min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\"}[168h])>= time() - 60*5) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name))) by (type)",
+              "expr": "avg((min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\"}[168h])>= time() - 60*5) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name!=\"\"}) by (name))) by (type)",
               "legendFormat": "{{type}}",
               "range": true,
               "refId": "A"
@@ -2857,7 +2857,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h]) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name))) by (name)",
+              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name))) by (name)",
               "format": "time_series",
               "instant": true,
               "legendFormat": "__auto",
@@ -2957,7 +2957,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"BootstrapReady\"}[168h]) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name))) by (name)",
+              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"BootstrapReady\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name))) by (name)",
               "format": "time_series",
               "instant": true,
               "legendFormat": "__auto",
@@ -3057,7 +3057,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"InfrastructureReady\"}[168h]) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name))) by (name)",
+              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"InfrastructureReady\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name))) by (name)",
               "format": "time_series",
               "instant": true,
               "legendFormat": "__auto",
@@ -3124,7 +3124,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"BootstrapReady\"}[168h]) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name)))",
+              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"BootstrapReady\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name)))",
               "legendFormat": "until BootstrapReady since creationTimestamp",
               "range": true,
               "refId": "A"
@@ -3135,7 +3135,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"InfrastructureReady\"}[168h]) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name)))",
+              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"InfrastructureReady\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name)))",
               "hide": false,
               "legendFormat": "until InfrastructureReady since creationTimestamp",
               "range": true,
@@ -3147,7 +3147,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h]) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name)))",
+              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name)))",
               "hide": false,
               "legendFormat": "until NodeHealthy since creationTimestamp",
               "range": true,
@@ -3235,7 +3235,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"BootstrapReady\"}[168h]) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name))) by (name)",
+              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"BootstrapReady\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name))) by (name)",
               "format": "table",
               "instant": true,
               "legendFormat": "__auto",
@@ -3249,7 +3249,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"InfrastructureReady\"}[168h]) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name))) by (name)",
+              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"InfrastructureReady\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name))) by (name)",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -3264,7 +3264,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h]) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name))) by (name)",
+              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name))) by (name)",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -3279,7 +3279,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right capi_machine_info{control_plane_name=\"\"}) by (name) * 1000",
+              "expr": "avg(capi_machine_created * on (name) group_right capi_machine_info{control_plane_name=\"\"}) by (name) * 1000",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -3420,7 +3420,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name))) by (type)",
+              "expr": "avg(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name))) by (type)",
               "legendFormat": "{{type}}",
               "range": true,
               "refId": "A"
@@ -3518,7 +3518,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg((min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\"}[168h])>= time() - 60*5) - on(name) group_left () (capi_machine_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name))) by (type)",
+              "expr": "avg((min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\"}[168h])>= time() - 60*5) - on(name) group_left () (capi_machine_created * on (name) group_right avg(capi_machine_info{control_plane_name=\"\"}) by (name))) by (type)",
               "legendFormat": "{{type}}",
               "range": true,
               "refId": "A"
@@ -3630,7 +3630,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\",type=\"Ready\"}[168h]) - on(name) group_left () (capi_kubeadmconfig_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name!=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name)))) by (name)",
+              "expr": "avg(min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\",type=\"Ready\"}[168h]) - on(name) group_left () (capi_kubeadmconfig_created * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name!=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name)))) by (name)",
               "format": "time_series",
               "instant": true,
               "legendFormat": "__auto",
@@ -3697,7 +3697,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg(min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\",type=\"Ready\"}[168h]) - on(name) group_left () (capi_kubeadmconfig_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name!=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name))))",
+              "expr": "avg(min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\",type=\"Ready\"}[168h]) - on(name) group_left () (capi_kubeadmconfig_created * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name!=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name))))",
               "legendFormat": "__auto",
               "range": true,
               "refId": "A"
@@ -3784,7 +3784,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\",type=\"Ready\"}[168h]) - on(name) group_left () (capi_kubeadmconfig_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name!=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name)))) by (name)",
+              "expr": "avg(min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\",type=\"Ready\"}[168h]) - on(name) group_left () (capi_kubeadmconfig_created * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name!=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name)))) by (name)",
               "format": "table",
               "instant": true,
               "legendFormat": "__auto",
@@ -3798,7 +3798,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(capi_kubeadmconfig_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name!=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name))) by (name) * 1000",
+              "expr": "avg(capi_kubeadmconfig_created * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name!=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name))) by (name) * 1000",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -3835,7 +3835,7 @@
                 },
                 "renameByName": {
                   "Value": "Time until Ready",
-                  "Value #A": "Time until Ready",
+                  "Value #A": "Time until BootstrapReady",
                   "Value #B": "Creation Timestamp",
                   "Value #C": "Time until NodeHealthy",
                   "name": "KubeadmConfig Name"
@@ -3934,7 +3934,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg(min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () (capi_kubeadmconfig_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name!=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name)))) by (type)",
+              "expr": "avg(min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () (capi_kubeadmconfig_created * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name!=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name)))) by (type)",
               "legendFormat": "{{type}}",
               "range": true,
               "refId": "A"
@@ -4032,7 +4032,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg((min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\"}[168h])>= time() - 60*5) - on(name) group_left () (capi_kubeadmconfig_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name!=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name)))) by (type)",
+              "expr": "avg((min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\"}[168h])>= time() - 60*5) - on(name) group_left () (capi_kubeadmconfig_created * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name!=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name)))) by (type)",
               "legendFormat": "{{type}}",
               "range": true,
               "refId": "A"
@@ -4144,7 +4144,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\",type=\"Ready\"}[168h]) - on(name) group_left () (capi_kubeadmconfig_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name)))) by (name)",
+              "expr": "avg(min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\",type=\"Ready\"}[168h]) - on(name) group_left () (capi_kubeadmconfig_created * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name)))) by (name)",
               "format": "time_series",
               "instant": true,
               "legendFormat": "__auto",
@@ -4211,7 +4211,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg(min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\",type=\"Ready\"}[168h]) - on(name) group_left () (capi_kubeadmconfig_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name))))",
+              "expr": "avg(min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\",type=\"Ready\"}[168h]) - on(name) group_left () (capi_kubeadmconfig_created * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name))))",
               "legendFormat": "__auto",
               "range": true,
               "refId": "A"
@@ -4298,7 +4298,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\",type=\"Ready\"}[168h]) - on(name) group_left () (capi_kubeadmconfig_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name)))) by (name)",
+              "expr": "avg(min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\",type=\"Ready\"}[168h]) - on(name) group_left () (capi_kubeadmconfig_created * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name)))) by (name)",
               "format": "table",
               "instant": true,
               "legendFormat": "__auto",
@@ -4312,7 +4312,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(capi_kubeadmconfig_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name))) by (name) * 1000",
+              "expr": "avg(capi_kubeadmconfig_created * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name))) by (name) * 1000",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -4349,7 +4349,7 @@
                 },
                 "renameByName": {
                   "Value": "Time until Ready",
-                  "Value #A": "Time until Ready",
+                  "Value #A": "Time until BootstrapReady",
                   "Value #B": "Creation Timestamp",
                   "Value #C": "Time until NodeHealthy",
                   "name": "KubeadmConfig Name"
@@ -4448,7 +4448,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg(min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () (capi_kubeadmconfig_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name)))) by (type)",
+              "expr": "avg(min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () (capi_kubeadmconfig_created * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name)))) by (type)",
               "legendFormat": "{{type}}",
               "range": true,
               "refId": "A"
@@ -4546,7 +4546,7 @@
                 "uid": "prometheus"
               },
               "editorMode": "code",
-              "expr": "avg((min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\"}[168h])>= time() - 60*5) - on(name) group_left () (capi_kubeadmconfig_created{cluster_name=~\"${Cluster:pipe}\"} * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name)))) by (type)",
+              "expr": "avg((min_over_time(capi_kubeadmconfig_status_condition_last_transition_time{status=\"True\"}[168h])>= time() - 60*5) - on(name) group_left () (capi_kubeadmconfig_created * on (name) group_right ()  (avg(label_replace(capi_machine_info{control_plane_name=\"\"}, \"name\", \"$0\", \"bootstrap_reference_name\", \".*\")) by (name)))) by (type)",
               "legendFormat": "{{type}}",
               "range": true,
               "refId": "A"
@@ -4647,7 +4647,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "min(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () capi_cluster_created{name=~\"${Cluster:pipe}\"}) by (type)",
+              "expr": "min(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () capi_cluster_created) by (type)",
               "format": "table",
               "instant": true,
               "range": false,
@@ -4660,7 +4660,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "max(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () capi_cluster_created{name=~\"${Cluster:pipe}\"})  by (type)",
+              "expr": "max(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () capi_cluster_created)  by (type)",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -4674,7 +4674,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () capi_cluster_created{name=~\"${Cluster:pipe}\"})  by (type)",
+              "expr": "avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () capi_cluster_created)  by (type)",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -4688,7 +4688,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "quantile(0.5, min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () capi_cluster_created{name=~\"${Cluster:pipe}\"})  by (type)",
+              "expr": "quantile(0.5, min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () capi_cluster_created)  by (type)",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -4702,7 +4702,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "quantile(0.9, min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () capi_cluster_created{name=~\"${Cluster:pipe}\"}) by (type)",
+              "expr": "quantile(0.9, min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () capi_cluster_created) by (type)",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -4716,7 +4716,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "quantile(0.99, min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () capi_cluster_created{name=~\"${Cluster:pipe}\"}) by (type)",
+              "expr": "quantile(0.99, min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () capi_cluster_created) by (type)",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -4730,7 +4730,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "stddev(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () capi_cluster_created{name=~\"${Cluster:pipe}\"}) by (type)",
+              "expr": "stddev(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () capi_cluster_created) by (type)",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -4744,7 +4744,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () bottomk(10, avg(capi_cluster_created{name=~\"${Cluster:pipe}\"}) by (name))) by (type)",
+              "expr": "avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () bottomk(10, avg(capi_cluster_created) by (name))) by (type)",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -4758,7 +4758,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () topk(10, avg(capi_cluster_created{name=~\"${Cluster:pipe}\"}) by (name))) by (type)\n",
+              "expr": "avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () topk(10, avg(capi_cluster_created) by (name))) by (type)\n",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -4772,7 +4772,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "(avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () topk(10, avg(capi_cluster_created{name=~\"${Cluster:pipe}\"}) by (name))) by (type) - avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () bottomk(10, avg(capi_cluster_created) by (name))) by (type)) / avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () bottomk(10, avg(capi_cluster_created) by (name))) by (type)\n",
+              "expr": "(avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () topk(10, avg(capi_cluster_created) by (name))) by (type) - avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () bottomk(10, avg(capi_cluster_created) by (name))) by (type)) / avg(min_over_time(capi_cluster_status_condition_last_transition_time{status=\"True\"}[168h]) - on(name) group_left () bottomk(10, avg(capi_cluster_created) by (name))) by (type)\n",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -4904,7 +4904,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "min(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(capi_cluster_created{name=~\"${Cluster:pipe}\"}, \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))",
+              "expr": "min(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(capi_cluster_created{}, \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))",
               "format": "table",
               "instant": true,
               "range": false,
@@ -4917,7 +4917,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "max(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(capi_cluster_created{name=~\"${Cluster:pipe}\"}, \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))",
+              "expr": "max(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(capi_cluster_created{}, \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -4931,7 +4931,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(capi_cluster_created{name=~\"${Cluster:pipe}\"}, \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))",
+              "expr": "avg(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(capi_cluster_created{}, \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -4945,7 +4945,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "quantile(0.5, label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(capi_cluster_created{name=~\"${Cluster:pipe}\"}, \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))",
+              "expr": "quantile(0.5, label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(capi_cluster_created{}, \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -4959,7 +4959,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "quantile(0.9, label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(capi_cluster_created{name=~\"${Cluster:pipe}\"}, \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))",
+              "expr": "quantile(0.9, label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(capi_cluster_created{}, \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -4973,7 +4973,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "quantile(0.99, label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(capi_cluster_created{name=~\"${Cluster:pipe}\"}, \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))",
+              "expr": "quantile(0.99, label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(capi_cluster_created{}, \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -4987,7 +4987,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(bottomk(10, avg(capi_cluster_created{name=~\"${Cluster:pipe}\"}) by (name)), \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))",
+              "expr": "avg(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(bottomk(10, avg(capi_cluster_created) by (name)), \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -5001,7 +5001,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "avg(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(topk(10, avg(capi_cluster_created{name=~\"${Cluster:pipe}\"}) by (name)), \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))",
+              "expr": "avg(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(topk(10, avg(capi_cluster_created) by (name)), \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -5015,7 +5015,7 @@
               },
               "editorMode": "code",
               "exemplar": false,
-              "expr": "(avg(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(topk(10, avg(capi_cluster_created{name=~\"${Cluster:pipe}\"}) by (name)), \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\")) - avg(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(bottomk(10, avg(capi_cluster_created{name=~\"${Cluster:pipe}\"}) by (name)), \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))) / avg(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(bottomk(10, avg(capi_cluster_created{name=~\"${Cluster:pipe}\"}) by (name)), \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))",
+              "expr": "(avg(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(topk(10, avg(capi_cluster_created) by (name)), \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\")) - avg(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(bottomk(10, avg(capi_cluster_created) by (name)), \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))) / avg(label_replace(max(min_over_time(capi_machine_status_condition_last_transition_time{status=\"True\",type=\"NodeHealthy\"}[168h])) by (cluster_name) - on (cluster_name) label_replace(bottomk(10, avg(capi_cluster_created) by (name)), \"cluster_name\", \"$0\", \"name\", \".*\") unless count(capi_machine_status_condition_last_transition_time{status=\"False\",type=\"NodeHealthy\"}) by (cluster_name), \"name\", \"$0\", \"cluster_name\", \".*\"))",
               "format": "table",
               "hide": false,
               "instant": true,
@@ -5063,33 +5063,7 @@
   "style": "dark",
   "tags": [],
   "templating": {
-    "list": [
-      {
-        "allValue": ".*",
-        "current": {
-          "selected": false,
-          "text": ".*",
-          "value": ".*"
-        },
-        "description": "Cluster prefix to filter by",
-        "hide": 0,
-        "includeAll": true,
-        "label": "Cluster",
-        "multi": true,
-        "name": "Cluster",
-        "options": [
-          {
-            "selected": false,
-            "text": "All",
-            "value": "$__all"
-          }
-        ],
-        "query": "",
-        "queryValue": ".*",
-        "skipUrlSync": false,
-        "type": "custom"
-      }
-    ]
+    "list": []
   },
   "time": {
     "from": "now-30m",
diff --git a/hack/observability/kube-state-metrics/chart/kustomization.yaml b/hack/observability/kube-state-metrics/chart/kustomization.yaml
new file mode 100644
index 000000000..3555c80e7
--- /dev/null
+++ b/hack/observability/kube-state-metrics/chart/kustomization.yaml
@@ -0,0 +1,13 @@
+helmCharts:
+  - name: kube-state-metrics
+    repo: https://prometheus-community.github.io/helm-charts
+    namespace: observability
+    releaseName: kube-state-metrics
+    valuesFile: values.yaml
+    version: 5.12.1
+
+helmGlobals:
+    # Store chart in ".charts" folder instead of "charts".
+    # Otherwise "go mod tidy" picks up dependencies of go files contained in the Helm Chart.
+    # "go mod tidy" ignores folders that begin with ".": https://pkg.go.dev/cmd/go#hdr-Package_lists_and_patterns.
+    chartHome: .charts
diff --git a/hack/observability/kube-state-metrics/chart/values.yaml b/hack/observability/kube-state-metrics/chart/values.yaml
new file mode 100644
index 000000000..7413a5643
--- /dev/null
+++ b/hack/observability/kube-state-metrics/chart/values.yaml
@@ -0,0 +1,53 @@
+# Add the CR configuration from the config map.
+volumeMounts:
+ - mountPath: /etc/config
+   name: config-volume
+
+volumes:
+ - configMap:
+     name: kube-state-metrics-crd-config
+   name: config-volume
+
+extraArgs:
+- "--custom-resource-state-config-file=/etc/config/crd-config.yaml"
+
+rbac:
+  extraRules:
+  - apiGroups:
+    - apiextensions.k8s.io
+    resources:
+    - customresourcedefinitions
+    verbs:
+    - get
+    - list
+    - watch
+  - apiGroups:
+    - cluster.x-k8s.io
+    resources:
+    - clusterclasses
+    - clusters
+    - machinedeployments
+    - machinepools
+    - machinesets
+    - machines
+    - machinehealthchecks
+    verbs:
+    - get
+    - list
+    - watch
+  - apiGroups:
+    - controlplane.cluster.x-k8s.io
+    resources:
+    - kubeadmcontrolplanes
+    verbs:
+    - get
+    - list
+    - watch
+  - apiGroups:
+    - bootstrap.cluster.x-k8s.io
+    resources:
+    - kubeadmconfigs
+    verbs:
+    - get
+    - list
+    - watch
diff --git a/config/metrics/crd-metrics-config.yaml b/hack/observability/kube-state-metrics/crd-config.yaml
similarity index 100%
rename from config/metrics/crd-metrics-config.yaml
rename to hack/observability/kube-state-metrics/crd-config.yaml
diff --git a/hack/observability/kube-state-metrics/crd-sidecar-cm.yaml b/hack/observability/kube-state-metrics/crd-sidecar-cm.yaml
deleted file mode 100644
index 588ddabe0..000000000
--- a/hack/observability/kube-state-metrics/crd-sidecar-cm.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
-apiVersion: v1
-kind: ConfigMap
-metadata:
-  name: kube-state-metrics-crd-sidecar-script
-data:
-  compile.sh: |
-    #!/bin/sh
-    set -x
-    
-    SOURCE_DIR="/tmp"
-    TARGET_FILE="/etc/config/crd-metrics-config.yaml"
-    # This script will use a temporary file to only overwrite the target file once.
-    TARGET_FILE_TMP="${TARGET_FILE}.tmp"
-    # Create header
-    cat << EOF > "${TARGET_FILE_TMP}"
-    kind: CustomResourceStateMetrics
-    spec:
-      resources:
-    EOF
-    # Append custom resource config of all files but remove headers
-    for f in $(ls -1 ${SOURCE_DIR}/*.yaml); do
-      cat $f | grep -v -E -e '^(-|kind: CustomResourceStateMetrics|spec:| +resources:)' \
-        >> "${TARGET_FILE_TMP}"
-    done
-    # Overwrite target file
-    mv "${TARGET_FILE_TMP}" "${TARGET_FILE}"
diff --git a/hack/observability/kube-state-metrics/crd-sidecar-patch.yaml b/hack/observability/kube-state-metrics/crd-sidecar-patch.yaml
deleted file mode 100644
index 3690e92f4..000000000
--- a/hack/observability/kube-state-metrics/crd-sidecar-patch.yaml
+++ /dev/null
@@ -1,50 +0,0 @@
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  name: kube-state-metrics
-spec:
-  template:
-    spec:
-      containers:
-      - env:
-        - name: LABEL
-          value: kube-state-metrics/custom-resource
-        - name: FOLDER
-          value: /tmp
-        - name: RESOURCE
-          value: configmap
-        - name: NAMESPACE
-          value: observability
-        - name: SCRIPT
-          value: /script/compile.sh
-        # This image continuously collects config maps with the specified label and
-        # updates the configuration for kube-state-metrics using a script.
-        image: kiwigrid/k8s-sidecar:latest
-        name: crd-sidecar
-        volumeMounts:
-        - mountPath: /etc/config
-          name: config-volume
-        - mountPath: /script
-          name: compile-script
-      initContainers:
-      - command:
-        - /bin/sh
-        - -c
-        - |
-          cat << EOF > "/etc/config/crd-metrics-config.yaml"
-          kind: CustomResourceStateMetrics
-          spec:
-            resources: []
-          EOF
-        # This container initializes an empty configuration for kube-state-metrics
-        # to have a smooth start.
-        image: kiwigrid/k8s-sidecar:latest
-        name: init-crd-config
-        volumeMounts:
-        - mountPath: /etc/config
-          name: config-volume
-      volumes:
-      - configMap:
-          defaultMode: 511
-          name: kube-state-metrics-crd-sidecar-script
-        name: compile-script
\ No newline at end of file
diff --git a/hack/observability/kube-state-metrics/kustomization.yaml b/hack/observability/kube-state-metrics/kustomization.yaml
index e44d34102..cdcfcee5d 100644
--- a/hack/observability/kube-state-metrics/kustomization.yaml
+++ b/hack/observability/kube-state-metrics/kustomization.yaml
@@ -1,30 +1,15 @@
 resources:
   - ../namespace.yaml
-  # Aggregated ClusterRole and ClusterRoleBinding for kube-state-metrics crd resources.
-  - rbac-crd-aggregation.yaml
-  # Adds the configmap which contains the script to build the final crd configuration.
-  - crd-sidecar-cm.yaml
-  # Adds the core CAPI crd metrics configmap and CusterRole.
-  - ../../../config/metrics
+  # The kube-state-metrics helm chart will reference a configmap with name `kube-state-metrics-crd-config`.
+  # The configMapGenerator below will create the configmap and append a hash suffix calculated from its
+  # content to the name. Kustomize will append the suffix hash to all references in the helm chart, but
+  # only when the helm chart content is referenced in "resources".
+  # This would not work if the helm chart is configured in this file via the "helmCharts" option.
+  - ./chart
 
 namespace: observability
 
-helmCharts:
-  - name: kube-state-metrics
-    repo: https://prometheus-community.github.io/helm-charts
-    namespace: observability
-    releaseName: kube-state-metrics
-    valuesFile: values.yaml
-    version: 5.12.1
-
-helmGlobals:
-    # Store chart in ".charts" folder instead of "charts".
-    # Otherwise "go mod tidy" picks up dependencies of go files contained in the Helm Chart.
-    # "go mod tidy" ignores folders that begin with ".": https://pkg.go.dev/cmd/go#hdr-Package_lists_and_patterns.
-    chartHome: .charts
-
-patches:
-# Adds the sidecar container which assembles the final configuration for kube-state-metrics
-- path: crd-sidecar-patch.yaml
-  target:
-    kind: Deployment
+configMapGenerator:
+- name: kube-state-metrics-crd-config
+  files:
+  - crd-config.yaml
diff --git a/config/metrics/templates/README.md b/hack/observability/kube-state-metrics/metrics/README.md
similarity index 74%
rename from config/metrics/templates/README.md
rename to hack/observability/kube-state-metrics/metrics/README.md
index c52a76817..753ba822f 100644
--- a/config/metrics/templates/README.md
+++ b/hack/observability/kube-state-metrics/metrics/README.md
@@ -4,4 +4,4 @@
 
 The make target `generate-metrics-config` is used to generate a single file which contains the Cluster API specific custom resource configuration for kube-state-metrics.
 
-To regenerate the file `../crd-metrics-config.yaml`, execute the `make generate-metrics-config` command.
+To regenerate the file `../crd-config.yaml`, execute the `make generate-metrics-config` command.
diff --git a/config/metrics/templates/cluster.yaml b/hack/observability/kube-state-metrics/metrics/cluster.yaml
similarity index 100%
rename from config/metrics/templates/cluster.yaml
rename to hack/observability/kube-state-metrics/metrics/cluster.yaml
diff --git a/config/metrics/templates/clusterclass.yaml b/hack/observability/kube-state-metrics/metrics/clusterclass.yaml
similarity index 100%
rename from config/metrics/templates/clusterclass.yaml
rename to hack/observability/kube-state-metrics/metrics/clusterclass.yaml
diff --git a/config/metrics/templates/common_metrics.yaml b/hack/observability/kube-state-metrics/metrics/common_metrics.yaml
similarity index 100%
rename from config/metrics/templates/common_metrics.yaml
rename to hack/observability/kube-state-metrics/metrics/common_metrics.yaml
diff --git a/config/metrics/templates/header.yaml b/hack/observability/kube-state-metrics/metrics/header.yaml
similarity index 100%
rename from config/metrics/templates/header.yaml
rename to hack/observability/kube-state-metrics/metrics/header.yaml
diff --git a/config/metrics/templates/kubeadmconfig.yaml b/hack/observability/kube-state-metrics/metrics/kubeadmconfig.yaml
similarity index 100%
rename from config/metrics/templates/kubeadmconfig.yaml
rename to hack/observability/kube-state-metrics/metrics/kubeadmconfig.yaml
diff --git a/config/metrics/templates/kubeadmcontrolplane.yaml b/hack/observability/kube-state-metrics/metrics/kubeadmcontrolplane.yaml
similarity index 100%
rename from config/metrics/templates/kubeadmcontrolplane.yaml
rename to hack/observability/kube-state-metrics/metrics/kubeadmcontrolplane.yaml
diff --git a/config/metrics/templates/machine.yaml b/hack/observability/kube-state-metrics/metrics/machine.yaml
similarity index 100%
rename from config/metrics/templates/machine.yaml
rename to hack/observability/kube-state-metrics/metrics/machine.yaml
diff --git a/config/metrics/templates/machinedeployment.yaml b/hack/observability/kube-state-metrics/metrics/machinedeployment.yaml
similarity index 100%
rename from config/metrics/templates/machinedeployment.yaml
rename to hack/observability/kube-state-metrics/metrics/machinedeployment.yaml
diff --git a/config/metrics/templates/machinehealthcheck.yaml b/hack/observability/kube-state-metrics/metrics/machinehealthcheck.yaml
similarity index 100%
rename from config/metrics/templates/machinehealthcheck.yaml
rename to hack/observability/kube-state-metrics/metrics/machinehealthcheck.yaml
diff --git a/config/metrics/templates/machinepool.yaml b/hack/observability/kube-state-metrics/metrics/machinepool.yaml
similarity index 100%
rename from config/metrics/templates/machinepool.yaml
rename to hack/observability/kube-state-metrics/metrics/machinepool.yaml
diff --git a/config/metrics/templates/machineset.yaml b/hack/observability/kube-state-metrics/metrics/machineset.yaml
similarity index 100%
rename from config/metrics/templates/machineset.yaml
rename to hack/observability/kube-state-metrics/metrics/machineset.yaml
diff --git a/config/metrics/templates/owner_metric.yaml b/hack/observability/kube-state-metrics/metrics/owner_metric.yaml
similarity index 100%
rename from config/metrics/templates/owner_metric.yaml
rename to hack/observability/kube-state-metrics/metrics/owner_metric.yaml
diff --git a/hack/observability/kube-state-metrics/rbac-crd-aggregation.yaml b/hack/observability/kube-state-metrics/rbac-crd-aggregation.yaml
deleted file mode 100644
index a7c08eb24..000000000
--- a/hack/observability/kube-state-metrics/rbac-crd-aggregation.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
----
-# ClusterRole to aggregate other ClusterRoles for different Custom Resource Configurations
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  name: kube-state-metrics-aggregated-role
-aggregationRule:
-  clusterRoleSelectors:
-  - matchLabels:
-      kube-state-metrics/aggregate-to-manager: "true"
----
-# ClusterRoleBinding for the aggregation role
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRoleBinding
-metadata:
-  name: kube-state-metrics-custom-resource-rolebinding
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: ClusterRole
-  name: kube-state-metrics-aggregated-role
-subjects:
-- kind: ServiceAccount
-  name: kube-state-metrics
-  namespace: observability
diff --git a/hack/observability/kube-state-metrics/values.yaml b/hack/observability/kube-state-metrics/values.yaml
deleted file mode 100644
index b9dbfa508..000000000
--- a/hack/observability/kube-state-metrics/values.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
-# Add the CR configuration from the config map.
-volumeMounts:
-- mountPath: /etc/config
-  name: config-volume
-
-volumes:
-- emptyDir: {}
-  name: config-volume
-
-extraArgs:
-- "--custom-resource-state-config-file=/etc/config/crd-metrics-config.yaml"
-
-rbac:
-  extraRules:
-  - apiGroups:
-    - apiextensions.k8s.io
-    resources:
-    - customresourcedefinitions
-    verbs:
-    - get
-    - list
-    - watch
diff --git a/hack/tools/go.mod b/hack/tools/go.mod
index 140d5ee74..8a55e28bc 100644
--- a/hack/tools/go.mod
+++ b/hack/tools/go.mod
@@ -50,13 +50,13 @@ require (
 	github.com/coredns/corefile-migration v1.0.21 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/docker/distribution v2.8.2+incompatible // indirect
-	github.com/docker/docker v24.0.6+incompatible // indirect
+	github.com/docker/docker v24.0.5+incompatible // indirect
 	github.com/docker/go-connections v0.4.0 // indirect
 	github.com/docker/go-units v0.4.0 // indirect
 	github.com/drone/envsubst/v2 v2.0.0-20210730161058-179042472c46 // indirect
 	github.com/emicklei/go-restful/v3 v3.11.0 // indirect
 	github.com/evanphx/json-patch v5.6.0+incompatible // indirect
-	github.com/evanphx/json-patch/v5 v5.7.0 // indirect
+	github.com/evanphx/json-patch/v5 v5.6.0 // indirect
 	github.com/fsnotify/fsnotify v1.6.0 // indirect
 	github.com/go-errors/errors v1.4.2 // indirect
 	github.com/go-logr/logr v1.2.4 // indirect
@@ -107,15 +107,15 @@ require (
 	github.com/stoewer/go-strcase v1.2.0 // indirect
 	github.com/subosito/gotenv v1.4.2 // indirect
 	go.opencensus.io v0.24.0 // indirect
-	golang.org/x/crypto v0.13.0 // indirect
+	golang.org/x/crypto v0.12.0 // indirect
 	golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
 	golang.org/x/mod v0.12.0 // indirect
-	golang.org/x/net v0.15.0 // indirect
-	golang.org/x/oauth2 v0.12.0 // indirect
+	golang.org/x/net v0.14.0 // indirect
+	golang.org/x/oauth2 v0.11.0 // indirect
 	golang.org/x/sync v0.3.0 // indirect
-	golang.org/x/sys v0.12.0 // indirect
-	golang.org/x/term v0.12.0 // indirect
-	golang.org/x/text v0.13.0 // indirect
+	golang.org/x/sys v0.11.0 // indirect
+	golang.org/x/term v0.11.0 // indirect
+	golang.org/x/text v0.12.0 // indirect
 	golang.org/x/time v0.3.0 // indirect
 	golang.org/x/tools v0.12.0 // indirect
 	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
diff --git a/hack/tools/go.sum b/hack/tools/go.sum
index c70d8c7f6..6cfd8f75a 100644
--- a/hack/tools/go.sum
+++ b/hack/tools/go.sum
@@ -121,8 +121,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
 github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
-github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE=
-github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v24.0.5+incompatible h1:WmgcE4fxyI6EEXxBRxsHnZXrO1pQ3smi0k/jho4HLeY=
+github.com/docker/docker v24.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
 github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
 github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
@@ -140,8 +140,8 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
 github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
-github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc=
-github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
+github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
+github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
@@ -298,6 +298,7 @@ github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH
 github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
 github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
@@ -490,8 +491,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
-golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
-golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
+golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -569,8 +570,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
-golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
-golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
+golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -580,8 +581,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
-golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
+golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
+golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -643,13 +644,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
-golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
+golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
-golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
-golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
+golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
+golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -660,8 +661,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
-golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
+golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
diff --git a/hack/tools/internal/tilt-prepare/main.go b/hack/tools/internal/tilt-prepare/main.go
index ddd41ee95..5e7f4f7e6 100644
--- a/hack/tools/internal/tilt-prepare/main.go
+++ b/hack/tools/internal/tilt-prepare/main.go
@@ -104,14 +104,13 @@ var (
 // Types used to de-serialize the tilt-settings.yaml/json file from the Cluster API repository.
 
 type tiltSettings struct {
-	Debug                map[string]tiltSettingsDebugConfig `json:"debug,omitempty"`
-	ExtraArgs            map[string]tiltSettingsExtraArgs   `json:"extra_args,omitempty"`
-	DeployCertManager    *bool                              `json:"deploy_cert_manager,omitempty"`
-	DeployObservability  []string                           `json:"deploy_observability,omitempty"`
-	DeployKustomizations map[string]string                  `json:"deploy_kustomizations,omitempty"`
-	EnableProviders      []string                           `json:"enable_providers,omitempty"`
-	AllowedContexts      []string                           `json:"allowed_contexts,omitempty"`
-	ProviderRepos        []string                           `json:"provider_repos,omitempty"`
+	Debug               map[string]tiltSettingsDebugConfig `json:"debug,omitempty"`
+	ExtraArgs           map[string]tiltSettingsExtraArgs   `json:"extra_args,omitempty"`
+	DeployCertManager   *bool                              `json:"deploy_cert_manager,omitempty"`
+	DeployObservability []string                           `json:"deploy_observability,omitempty"`
+	EnableProviders     []string                           `json:"enable_providers,omitempty"`
+	AllowedContexts     []string                           `json:"allowed_contexts,omitempty"`
+	ProviderRepos       []string                           `json:"provider_repos,omitempty"`
 }
 
 type tiltSettingsDebugConfig struct {
@@ -309,13 +308,6 @@ func tiltResources(ctx context.Context, ts *tiltSettings) error {
 		)
 	}
 
-	for name, path := range ts.DeployKustomizations {
-		name := fmt.Sprintf("%s.kustomization", name)
-		tasks[name] = sequential(
-			kustomizeTask(path, fmt.Sprintf("%s.yaml", name)),
-		)
-	}
-
 	// Add read configurations from provider repos
 	for _, p := range ts.ProviderRepos {
 		tiltProviderConfigs, err := loadTiltProvider(p)
diff --git a/hack/verify-import-restrictions.sh b/hack/verify-import-restrictions.sh
deleted file mode 100755
index 818503a72..000000000
--- a/hack/verify-import-restrictions.sh
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/usr/bin/env bash
-
-# Copyright 2023 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.
-
-# This script checks import restrictions. The script looks for a file called
-# `.import-restrictions` in each directory, then all imports of the package are
-# checked against each "rule" in the file.
-# Usage: `hack/verify-import-restrictions.sh`.
-
-set -o errexit
-set -o nounset
-set -o pipefail
-
-sub_packages=(
-  "api"
-)
-
-packages=()
-visit() {
-  local count=0
-  for file in "$1"/* ; do
-    if [ -d "$file" ]; then
-      visit "$file"
-    elif [ -f "$file" ]; then
-      ((count += 1))
-    fi
-  done
-  if [ "$count" -gt 0 ]; then
-    # import-boss may not accept directories without any sources
-    packages+=("./$1")
-  fi
-}
-for d in "${sub_packages[@]}"; do
-  visit "$d"
-done
-
-INPUT_DIRS="$(IFS=, ; echo "${packages[*]}")"
-echo "Enforcing imports in source codes under the following directories: ${INPUT_DIRS}"
-
-# Make sure GOPATH is unset to avoid behavior inconsistency
-# as import-boss will go through the sources
-unset GOPATH
-import-boss --include-test-files=true --verify-only --input-dirs "${INPUT_DIRS}"
diff --git a/internal/controllers/topology/cluster/conditions.go b/internal/controllers/topology/cluster/conditions.go
index 44d6f9e10..79ed85b31 100644
--- a/internal/controllers/topology/cluster/conditions.go
+++ b/internal/controllers/topology/cluster/conditions.go
@@ -83,8 +83,7 @@ func (r *Reconciler) reconcileTopologyReconciledCondition(s *scope.Scope, cluste
 				clusterv1.TopologyReconciledCondition,
 				clusterv1.TopologyReconciledClusterClassNotReconciledReason,
 				clusterv1.ConditionSeverityInfo,
-				"ClusterClass not reconciled. If this condition persists please check ClusterClass status. A ClusterClass is reconciled if"+
-					".status.observedGeneration == .metadata.generation is true. If this is not the case either ClusterClass reconciliation failed or the ClusterClass is paused",
+				"ClusterClass not reconciled. If this condition persists please check ClusterClass status.",
 			),
 		)
 		return nil
diff --git a/internal/controllers/topology/cluster/desired_state.go b/internal/controllers/topology/cluster/desired_state.go
index 0aed86b5c..5a0c4aea2 100644
--- a/internal/controllers/topology/cluster/desired_state.go
+++ b/internal/controllers/topology/cluster/desired_state.go
@@ -25,7 +25,6 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime/schema"
-	"k8s.io/apiserver/pkg/storage/names"
 	"k8s.io/utils/pointer"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
@@ -39,6 +38,7 @@ import (
 	"sigs.k8s.io/cluster-api/internal/controllers/topology/cluster/scope"
 	"sigs.k8s.io/cluster-api/internal/hooks"
 	tlog "sigs.k8s.io/cluster-api/internal/log"
+	"sigs.k8s.io/cluster-api/internal/topology/names"
 	"sigs.k8s.io/cluster-api/internal/webhooks"
 	"sigs.k8s.io/cluster-api/util"
 )
@@ -161,7 +161,7 @@ func computeInfrastructureCluster(_ context.Context, s *scope.Scope) (*unstructu
 		template:              template,
 		templateClonedFromRef: templateClonedFromRef,
 		cluster:               cluster,
-		namePrefix:            fmt.Sprintf("%s-", cluster.Name),
+		nameGenerator:         names.SimpleNameGenerator(fmt.Sprintf("%s-", cluster.Name)),
 		currentObjectRef:      currentRef,
 		// Note: It is not possible to add an ownerRef to Cluster at this stage, otherwise the provisioning
 		// of the infrastructure cluster starts no matter of the object being actually referenced by the Cluster itself.
@@ -199,18 +199,17 @@ func computeControlPlaneInfrastructureMachineTemplate(_ context.Context, s *scop
 		}
 	}
 
-	controlPlaneInfrastructureMachineTemplate := templateToTemplate(templateToInput{
+	return templateToTemplate(templateToInput{
 		template:              template,
 		templateClonedFromRef: templateClonedFromRef,
 		cluster:               cluster,
-		namePrefix:            controlPlaneInfrastructureMachineTemplateNamePrefix(cluster.Name),
+		nameGenerator:         names.SimpleNameGenerator(controlPlaneInfrastructureMachineTemplateNamePrefix(cluster.Name)),
 		currentObjectRef:      currentRef,
 		// Note: we are adding an ownerRef to Cluster so the template will be automatically garbage collected
 		// in case of errors in between creating this template and updating the Cluster object
 		// with the reference to the ControlPlane object using this template.
 		ownerRef: ownerReferenceTo(s.Current.Cluster),
 	})
-	return controlPlaneInfrastructureMachineTemplate, nil
 }
 
 // computeControlPlane computes the desired state for the ControlPlane object starting from the
@@ -236,11 +235,16 @@ func (r *Reconciler) computeControlPlane(ctx context.Context, s *scope.Scope, in
 
 	controlPlaneAnnotations := util.MergeMap(topologyMetadata.Annotations, clusterClassMetadata.Annotations)
 
+	nameTemplate := "{{ .cluster.name }}-{{ .random }}"
+	if s.Blueprint.ClusterClass.Spec.ControlPlane.NamingStrategy != nil && s.Blueprint.ClusterClass.Spec.ControlPlane.NamingStrategy.Template != nil {
+		nameTemplate = *s.Blueprint.ClusterClass.Spec.ControlPlane.NamingStrategy.Template
+	}
+
 	controlPlane, err := templateToObject(templateToInput{
 		template:              template,
 		templateClonedFromRef: templateClonedFromRef,
 		cluster:               cluster,
-		namePrefix:            fmt.Sprintf("%s-", cluster.Name),
+		nameGenerator:         names.ControlPlaneNameGenerator(nameTemplate, cluster.Name),
 		currentObjectRef:      currentRef,
 		labels:                controlPlaneLabels,
 		annotations:           controlPlaneAnnotations,
@@ -609,17 +613,21 @@ func computeMachineDeployment(ctx context.Context, s *scope.Scope, machineDeploy
 	if currentMachineDeployment != nil && currentMachineDeployment.BootstrapTemplate != nil {
 		currentBootstrapTemplateRef = currentMachineDeployment.Object.Spec.Template.Spec.Bootstrap.ConfigRef
 	}
-	desiredMachineDeployment.BootstrapTemplate = templateToTemplate(templateToInput{
+	var err error
+	desiredMachineDeployment.BootstrapTemplate, err = templateToTemplate(templateToInput{
 		template:              machineDeploymentBlueprint.BootstrapTemplate,
 		templateClonedFromRef: contract.ObjToRef(machineDeploymentBlueprint.BootstrapTemplate),
 		cluster:               s.Current.Cluster,
-		namePrefix:            bootstrapTemplateNamePrefix(s.Current.Cluster.Name, machineDeploymentTopology.Name),
+		nameGenerator:         names.SimpleNameGenerator(bootstrapTemplateNamePrefix(s.Current.Cluster.Name, machineDeploymentTopology.Name)),
 		currentObjectRef:      currentBootstrapTemplateRef,
 		// Note: we are adding an ownerRef to Cluster so the template will be automatically garbage collected
 		// in case of errors in between creating this template and creating/updating the MachineDeployment object
 		// with the reference to the ControlPlane object using this template.
 		ownerRef: ownerReferenceTo(s.Current.Cluster),
 	})
+	if err != nil {
+		return nil, err
+	}
 
 	bootstrapTemplateLabels := desiredMachineDeployment.BootstrapTemplate.GetLabels()
 	if bootstrapTemplateLabels == nil {
@@ -634,17 +642,20 @@ func computeMachineDeployment(ctx context.Context, s *scope.Scope, machineDeploy
 	if currentMachineDeployment != nil && currentMachineDeployment.InfrastructureMachineTemplate != nil {
 		currentInfraMachineTemplateRef = &currentMachineDeployment.Object.Spec.Template.Spec.InfrastructureRef
 	}
-	desiredMachineDeployment.InfrastructureMachineTemplate = templateToTemplate(templateToInput{
+	desiredMachineDeployment.InfrastructureMachineTemplate, err = templateToTemplate(templateToInput{
 		template:              machineDeploymentBlueprint.InfrastructureMachineTemplate,
 		templateClonedFromRef: contract.ObjToRef(machineDeploymentBlueprint.InfrastructureMachineTemplate),
 		cluster:               s.Current.Cluster,
-		namePrefix:            infrastructureMachineTemplateNamePrefix(s.Current.Cluster.Name, machineDeploymentTopology.Name),
+		nameGenerator:         names.SimpleNameGenerator(infrastructureMachineTemplateNamePrefix(s.Current.Cluster.Name, machineDeploymentTopology.Name)),
 		currentObjectRef:      currentInfraMachineTemplateRef,
 		// Note: we are adding an ownerRef to Cluster so the template will be automatically garbage collected
 		// in case of errors in between creating this template and creating/updating the MachineDeployment object
 		// with the reference to the ControlPlane object using this template.
 		ownerRef: ownerReferenceTo(s.Current.Cluster),
 	})
+	if err != nil {
+		return nil, err
+	}
 
 	infraMachineTemplateLabels := desiredMachineDeployment.InfrastructureMachineTemplate.GetLabels()
 	if infraMachineTemplateLabels == nil {
@@ -696,13 +707,23 @@ func computeMachineDeployment(ctx context.Context, s *scope.Scope, machineDeploy
 		return nil, errors.Wrap(err, "failed to calculate desired infrastructure machine template ref")
 	}
 
+	nameTemplate := "{{ .cluster.name }}-{{ .machineDeployment.topologyName }}-{{ .random }}"
+	if machineDeploymentClass.NamingStrategy != nil && machineDeploymentClass.NamingStrategy.Template != nil {
+		nameTemplate = *machineDeploymentClass.NamingStrategy.Template
+	}
+
+	name, err := names.MachineDeploymentNameGenerator(nameTemplate, s.Current.Cluster.Name, machineDeploymentTopology.Name).GenerateName()
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to generate name for MachineDeployment")
+	}
+
 	desiredMachineDeploymentObj := &clusterv1.MachineDeployment{
 		TypeMeta: metav1.TypeMeta{
 			Kind:       clusterv1.GroupVersion.WithKind("MachineDeployment").Kind,
 			APIVersion: clusterv1.GroupVersion.String(),
 		},
 		ObjectMeta: metav1.ObjectMeta{
-			Name:      names.SimpleNameGenerator.GenerateName(fmt.Sprintf("%s-%s-", s.Current.Cluster.Name, machineDeploymentTopology.Name)),
+			Name:      name,
 			Namespace: s.Current.Cluster.Namespace,
 		},
 		Spec: clusterv1.MachineDeploymentSpec{
@@ -951,7 +972,7 @@ func computeMachinePool(_ context.Context, s *scope.Scope, machinePoolTopology c
 		template:              machinePoolBlueprint.BootstrapTemplate,
 		templateClonedFromRef: contract.ObjToRef(machinePoolBlueprint.BootstrapTemplate),
 		cluster:               s.Current.Cluster,
-		namePrefix:            bootstrapConfigNamePrefix(s.Current.Cluster.Name, machinePoolTopology.Name),
+		nameGenerator:         names.SimpleNameGenerator(bootstrapConfigNamePrefix(s.Current.Cluster.Name, machinePoolTopology.Name)),
 		currentObjectRef:      currentBootstrapConfigRef,
 	})
 	if err != nil {
@@ -975,7 +996,7 @@ func computeMachinePool(_ context.Context, s *scope.Scope, machinePoolTopology c
 		template:              machinePoolBlueprint.InfrastructureMachinePoolTemplate,
 		templateClonedFromRef: contract.ObjToRef(machinePoolBlueprint.InfrastructureMachinePoolTemplate),
 		cluster:               s.Current.Cluster,
-		namePrefix:            infrastructureMachinePoolNamePrefix(s.Current.Cluster.Name, machinePoolTopology.Name),
+		nameGenerator:         names.SimpleNameGenerator(infrastructureMachinePoolNamePrefix(s.Current.Cluster.Name, machinePoolTopology.Name)),
 		currentObjectRef:      currentInfraMachinePoolRef,
 	})
 	if err != nil {
@@ -1027,13 +1048,23 @@ func computeMachinePool(_ context.Context, s *scope.Scope, machinePoolTopology c
 		return nil, errors.Wrap(err, "failed to calculate desired infrastructure machine pool ref")
 	}
 
+	nameTemplate := "{{ .cluster.name }}-{{ .machinePool.topologyName }}-{{ .random }}"
+	if machinePoolClass.NamingStrategy != nil && machinePoolClass.NamingStrategy.Template != nil {
+		nameTemplate = *machinePoolClass.NamingStrategy.Template
+	}
+
+	name, err := names.MachinePoolNameGenerator(nameTemplate, s.Current.Cluster.Name, machinePoolTopology.Name).GenerateName()
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to generate name for MachinePool")
+	}
+
 	desiredMachinePoolObj := &expv1.MachinePool{
 		TypeMeta: metav1.TypeMeta{
 			Kind:       expv1.GroupVersion.WithKind("MachinePool").Kind,
 			APIVersion: expv1.GroupVersion.String(),
 		},
 		ObjectMeta: metav1.ObjectMeta{
-			Name:      names.SimpleNameGenerator.GenerateName(fmt.Sprintf("%s-%s-", s.Current.Cluster.Name, machinePoolTopology.Name)),
+			Name:      name,
 			Namespace: s.Current.Cluster.Namespace,
 		},
 		Spec: expv1.MachinePoolSpec{
@@ -1190,7 +1221,7 @@ type templateToInput struct {
 	template              *unstructured.Unstructured
 	templateClonedFromRef *corev1.ObjectReference
 	cluster               *clusterv1.Cluster
-	namePrefix            string
+	nameGenerator         names.NameGenerator
 	currentObjectRef      *corev1.ObjectReference
 	labels                map[string]string
 	annotations           map[string]string
@@ -1229,8 +1260,12 @@ func templateToObject(in templateToInput) (*unstructured.Unstructured, error) {
 
 	// Ensure the generated objects have a meaningful name.
 	// NOTE: In case there is already a ref to this object in the Cluster, re-use the same name
-	// in order to simplify compare at later stages of the reconcile process.
-	object.SetName(names.SimpleNameGenerator.GenerateName(in.namePrefix))
+	// in order to simplify comparison at later stages of the reconcile process.
+	name, err := in.nameGenerator.GenerateName()
+	if err != nil {
+		return nil, errors.Wrapf(err, "failed to generate name for %s", object.GetKind())
+	}
+	object.SetName(name)
 	if in.currentObjectRef != nil && len(in.currentObjectRef.Name) > 0 {
 		object.SetName(in.currentObjectRef.Name)
 	}
@@ -1243,7 +1278,7 @@ func templateToObject(in templateToInput) (*unstructured.Unstructured, error) {
 // and assigning a meaningful name (or reusing current reference name).
 // NOTE: We are creating a copy of the ClusterClass template for each cluster so
 // it is possible to add cluster specific information without affecting the original object.
-func templateToTemplate(in templateToInput) *unstructured.Unstructured {
+func templateToTemplate(in templateToInput) (*unstructured.Unstructured, error) {
 	template := &unstructured.Unstructured{}
 	in.template.DeepCopyInto(template)
 
@@ -1289,13 +1324,17 @@ func templateToTemplate(in templateToInput) *unstructured.Unstructured {
 
 	// Ensure the generated template gets a meaningful name.
 	// NOTE: In case there is already an object ref to this template, it is required to re-use the same name
-	// in order to simplify compare at later stages of the reconcile process.
-	template.SetName(names.SimpleNameGenerator.GenerateName(in.namePrefix))
+	// in order to simplify comparison at later stages of the reconcile process.
+	name, err := in.nameGenerator.GenerateName()
+	if err != nil {
+		return nil, errors.Wrapf(err, "failed to generate name for %s", template.GetKind())
+	}
+	template.SetName(name)
 	if in.currentObjectRef != nil && len(in.currentObjectRef.Name) > 0 {
 		template.SetName(in.currentObjectRef.Name)
 	}
 
-	return template
+	return template, nil
 }
 
 func ownerReferenceTo(obj client.Object) *metav1.OwnerReference {
diff --git a/internal/topology/names/names.go b/internal/topology/names/names.go
new file mode 100644
index 000000000..d4caf29ae
--- /dev/null
+++ b/internal/topology/names/names.go
@@ -0,0 +1,128 @@
+/*
+Copyright 2023 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 names implements name generators for managed topology.
+package names
+
+import (
+	"bytes"
+	"fmt"
+	"text/template"
+
+	"github.com/pkg/errors"
+	utilrand "k8s.io/apimachinery/pkg/util/rand"
+)
+
+// This is a copy of the constants at k8s.io/apiserver/pkg/storage/names.
+const (
+	maxNameLength          = 63
+	randomLength           = 5
+	maxGeneratedNameLength = maxNameLength - randomLength
+)
+
+type simpleNameGenerator struct {
+	base string
+}
+
+func (s *simpleNameGenerator) GenerateName() (string, error) {
+	base := s.base
+	if len(base) > maxGeneratedNameLength {
+		base = base[:maxGeneratedNameLength]
+	}
+	return fmt.Sprintf("%s%s", base, utilrand.String(randomLength)), nil
+}
+
+// NameGenerator generates names for objects.
+type NameGenerator interface {
+	// GenerateName generates a valid name. The generator is responsible for
+	// knowing the maximum valid name length.
+	GenerateName() (string, error)
+}
+
+// SimpleNameGenerator returns a NameGenerator which is based on
+// k8s.io/apiserver/pkg/storage/names.SimpleNameGenerator.
+func SimpleNameGenerator(base string) NameGenerator {
+	return &simpleNameGenerator{
+		base: base,
+	}
+}
+
+// ControlPlaneNameGenerator returns a generator for creating a control plane name.
+func ControlPlaneNameGenerator(templateString, clusterName string) NameGenerator {
+	return newTemplateGenerator(templateString, clusterName,
+		map[string]interface{}{})
+}
+
+// MachineDeploymentNameGenerator returns a generator for creating a machinedeployment name.
+func MachineDeploymentNameGenerator(templateString, clusterName, topologyName string) NameGenerator {
+	return newTemplateGenerator(templateString, clusterName,
+		map[string]interface{}{
+			"machineDeployment": map[string]interface{}{
+				"topologyName": topologyName,
+			},
+		})
+}
+
+// MachinePoolNameGenerator returns a generator for creating a machinepool name.
+func MachinePoolNameGenerator(templateString, clusterName, topologyName string) NameGenerator {
+	return newTemplateGenerator(templateString, clusterName,
+		map[string]interface{}{
+			"machinePool": map[string]interface{}{
+				"topologyName": topologyName,
+			},
+		})
+}
+
+// templateGenerator parses the template string as text/template and executes it using
+// the passed data to generate a name.
+type templateGenerator struct {
+	template string
+	data     map[string]interface{}
+}
+
+func newTemplateGenerator(template, clusterName string, data map[string]interface{}) NameGenerator {
+	data["cluster"] = map[string]interface{}{
+		"name": clusterName,
+	}
+	data["random"] = utilrand.String(randomLength)
+
+	return &templateGenerator{
+		template: template,
+		data:     data,
+	}
+}
+
+func (g *templateGenerator) GenerateName() (string, error) {
+	tpl, err := template.New("template name generator").Option("missingkey=error").Parse(g.template)
+	if err != nil {
+		return "", errors.Wrapf(err, "parsing template %q", g.template)
+	}
+
+	var buf bytes.Buffer
+	if err := tpl.Execute(&buf, g.data); err != nil {
+		return "", errors.Wrap(err, "rendering template")
+	}
+
+	name := buf.String()
+
+	// If the name exceeds the maxNameLength: trim to maxGeneratedNameLength and add
+	// a random suffix.
+	if len(name) > maxNameLength {
+		name = name[:maxGeneratedNameLength] + utilrand.String(randomLength)
+	}
+
+	return name, nil
+}
diff --git a/internal/webhooks/clusterclass.go b/internal/webhooks/clusterclass.go
index 78549f439..c259a1e40 100644
--- a/internal/webhooks/clusterclass.go
+++ b/internal/webhooks/clusterclass.go
@@ -27,6 +27,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/util/sets"
+	"k8s.io/apimachinery/pkg/util/validation"
 	"k8s.io/apimachinery/pkg/util/validation/field"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
@@ -37,6 +38,7 @@ import (
 	"sigs.k8s.io/cluster-api/api/v1beta1/index"
 	"sigs.k8s.io/cluster-api/feature"
 	"sigs.k8s.io/cluster-api/internal/topology/check"
+	"sigs.k8s.io/cluster-api/internal/topology/names"
 	"sigs.k8s.io/cluster-api/internal/topology/variables"
 )
 
@@ -157,6 +159,9 @@ func (webhook *ClusterClass) validate(ctx context.Context, oldClusterClass, newC
 	// Ensure MachineHealthChecks are valid.
 	allErrs = append(allErrs, validateMachineHealthCheckClasses(newClusterClass)...)
 
+	// Ensure NamingStrategies are valid.
+	allErrs = append(allErrs, validateNamingStrategies(newClusterClass)...)
+
 	// Validate variables.
 	allErrs = append(allErrs,
 		variables.ValidateClusterClassVariables(ctx, newClusterClass.Spec.Variables, field.NewPath("spec", "variables"))...,
@@ -410,6 +415,69 @@ func validateMachineHealthCheckClasses(clusterClass *clusterv1.ClusterClass) fie
 	return allErrs
 }
 
+func validateNamingStrategies(clusterClass *clusterv1.ClusterClass) field.ErrorList {
+	var allErrs field.ErrorList
+
+	if clusterClass.Spec.ControlPlane.NamingStrategy != nil && clusterClass.Spec.ControlPlane.NamingStrategy.Template != nil {
+		name, err := names.ControlPlaneNameGenerator(*clusterClass.Spec.ControlPlane.NamingStrategy.Template, "cluster").GenerateName()
+		templateFldPath := field.NewPath("spec", "controlPlane", "namingStrategy", "template")
+		if err != nil {
+			allErrs = append(allErrs,
+				field.Invalid(
+					templateFldPath,
+					*clusterClass.Spec.ControlPlane.NamingStrategy.Template,
+					fmt.Sprintf("invalid ControlPlane name template: %v", err),
+				))
+		} else {
+			for _, err := range validation.IsDNS1123Subdomain(name) {
+				allErrs = append(allErrs, field.Invalid(templateFldPath, *clusterClass.Spec.ControlPlane.NamingStrategy.Template, err))
+			}
+		}
+	}
+
+	for i, md := range clusterClass.Spec.Workers.MachineDeployments {
+		if md.NamingStrategy == nil || md.NamingStrategy.Template == nil {
+			continue
+		}
+		name, err := names.MachineDeploymentNameGenerator(*md.NamingStrategy.Template, "cluster", "mdTopology").GenerateName()
+		templateFldPath := field.NewPath("spec", "workers", "machineDeployments").Index(i).Child("namingStrategy", "template")
+		if err != nil {
+			allErrs = append(allErrs,
+				field.Invalid(
+					templateFldPath,
+					*md.NamingStrategy.Template,
+					fmt.Sprintf("invalid MachineDeployment name template: %v", err),
+				))
+		} else {
+			for _, err := range validation.IsDNS1123Subdomain(name) {
+				allErrs = append(allErrs, field.Invalid(templateFldPath, *md.NamingStrategy.Template, err))
+			}
+		}
+	}
+
+	for i, mp := range clusterClass.Spec.Workers.MachinePools {
+		if mp.NamingStrategy == nil || mp.NamingStrategy.Template == nil {
+			continue
+		}
+		name, err := names.MachinePoolNameGenerator(*mp.NamingStrategy.Template, "cluster", "mpTopology").GenerateName()
+		templateFldPath := field.NewPath("spec", "workers", "machinePools").Index(i).Child("namingStrategy", "template")
+		if err != nil {
+			allErrs = append(allErrs,
+				field.Invalid(
+					templateFldPath,
+					*mp.NamingStrategy.Template,
+					fmt.Sprintf("invalid MachinePool name template: %v", err),
+				))
+		} else {
+			for _, err := range validation.IsDNS1123Subdomain(name) {
+				allErrs = append(allErrs, field.Invalid(templateFldPath, *mp.NamingStrategy.Template, err))
+			}
+		}
+	}
+
+	return allErrs
+}
+
 // validateMachineHealthCheckClass validates the MachineHealthCheckSpec fields defined in a MachineHealthCheckClass.
 func validateMachineHealthCheckClass(fldPath *field.Path, namepace string, m *clusterv1.MachineHealthCheckClass) field.ErrorList {
 	mhc := clusterv1.MachineHealthCheck{


Test patch:
diff --git a/internal/controllers/topology/cluster/conditions_test.go b/internal/controllers/topology/cluster/conditions_test.go
index 9113f5497..d2b21aa3a 100644
--- a/internal/controllers/topology/cluster/conditions_test.go
+++ b/internal/controllers/topology/cluster/conditions_test.go
@@ -76,11 +76,10 @@ func TestReconcileTopologyReconciledCondition(t *testing.T) {
 					},
 				},
 			},
-			wantConditionStatus: corev1.ConditionFalse,
-			wantConditionReason: clusterv1.TopologyReconciledClusterClassNotReconciledReason,
-			wantConditionMessage: "ClusterClass not reconciled. If this condition persists please check ClusterClass status. A ClusterClass is reconciled if" +
-				".status.observedGeneration == .metadata.generation is true. If this is not the case either ClusterClass reconciliation failed or the ClusterClass is paused",
-			wantErr: false,
+			wantConditionStatus:  corev1.ConditionFalse,
+			wantConditionReason:  clusterv1.TopologyReconciledClusterClassNotReconciledReason,
+			wantConditionMessage: "ClusterClass not reconciled. If this condition persists please check ClusterClass status.",
+			wantErr:              false,
 		},
 		{
 			name:         "should set the condition to false if the there is a blocking hook",
diff --git a/internal/controllers/topology/cluster/desired_state_test.go b/internal/controllers/topology/cluster/desired_state_test.go
index b2edb1d7b..94feb132d 100644
--- a/internal/controllers/topology/cluster/desired_state_test.go
+++ b/internal/controllers/topology/cluster/desired_state_test.go
@@ -41,6 +41,7 @@ import (
 	"sigs.k8s.io/cluster-api/internal/hooks"
 	fakeruntimeclient "sigs.k8s.io/cluster-api/internal/runtime/client/fake"
 	"sigs.k8s.io/cluster-api/internal/test/builder"
+	"sigs.k8s.io/cluster-api/internal/topology/names"
 	"sigs.k8s.io/cluster-api/util"
 )
 
@@ -1995,7 +1996,7 @@ func TestTemplateToObject(t *testing.T) {
 			template:              template,
 			templateClonedFromRef: fakeRef1,
 			cluster:               cluster,
-			namePrefix:            cluster.Name,
+			nameGenerator:         names.SimpleNameGenerator(cluster.Name),
 			currentObjectRef:      nil,
 		})
 		g.Expect(err).ToNot(HaveOccurred())
@@ -2015,7 +2016,7 @@ func TestTemplateToObject(t *testing.T) {
 			template:              template,
 			templateClonedFromRef: fakeRef1,
 			cluster:               cluster,
-			namePrefix:            cluster.Name,
+			nameGenerator:         names.SimpleNameGenerator(cluster.Name),
 			currentObjectRef:      fakeRef2,
 		})
 		g.Expect(err).ToNot(HaveOccurred())
@@ -2052,13 +2053,14 @@ func TestTemplateToTemplate(t *testing.T) {
 
 	t.Run("Generates a template from a template", func(t *testing.T) {
 		g := NewWithT(t)
-		obj := templateToTemplate(templateToInput{
+		obj, err := templateToTemplate(templateToInput{
 			template:              template,
 			templateClonedFromRef: fakeRef1,
 			cluster:               cluster,
-			namePrefix:            cluster.Name,
+			nameGenerator:         names.SimpleNameGenerator(cluster.Name),
 			currentObjectRef:      nil,
 		})
+		g.Expect(err).ToNot(HaveOccurred())
 		g.Expect(obj).ToNot(BeNil())
 		assertTemplateToTemplate(g, assertTemplateInput{
 			cluster:     cluster,
@@ -2070,13 +2072,14 @@ func TestTemplateToTemplate(t *testing.T) {
 	})
 	t.Run("Overrides the generated name if there is already a reference", func(t *testing.T) {
 		g := NewWithT(t)
-		obj := templateToTemplate(templateToInput{
+		obj, err := templateToTemplate(templateToInput{
 			template:              template,
 			templateClonedFromRef: fakeRef1,
 			cluster:               cluster,
-			namePrefix:            cluster.Name,
+			nameGenerator:         names.SimpleNameGenerator(cluster.Name),
 			currentObjectRef:      fakeRef2,
 		})
+		g.Expect(err).ToNot(HaveOccurred())
 		g.Expect(obj).ToNot(BeNil())
 		assertTemplateToTemplate(g, assertTemplateInput{
 			cluster:     cluster,
diff --git a/internal/controllers/topology/cluster/patches/variables/variables_test.go b/internal/controllers/topology/cluster/patches/variables/variables_test.go
index 775ef609e..3086bcec7 100644
--- a/internal/controllers/topology/cluster/patches/variables/variables_test.go
+++ b/internal/controllers/topology/cluster/patches/variables/variables_test.go
@@ -28,7 +28,6 @@ import (
 	"k8s.io/utils/pointer"
 
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
-	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
 	runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
 	"sigs.k8s.io/cluster-api/internal/test/builder"
 )
@@ -854,373 +853,6 @@ func TestMachineDeployment(t *testing.T) {
 	}
 }
 
-func TestMachinePool(t *testing.T) {
-	tests := []struct {
-		name                        string
-		mpTopology                  *clusterv1.MachinePoolTopology
-		forPatch                    string
-		variableDefinitionsForPatch map[string]bool
-		mp                          *expv1.MachinePool
-		mpBootstrapConfig           *unstructured.Unstructured
-		mpInfrastructureMachinePool *unstructured.Unstructured
-		want                        []runtimehooksv1.Variable
-	}{
-		{
-			name:                        "Should calculate MachinePool variables",
-			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
-			forPatch:                    "patch1",
-			mpTopology: &clusterv1.MachinePoolTopology{
-				Replicas: pointer.Int32(3),
-				Name:     "mp-topology",
-				Class:    "mp-class",
-				Variables: &clusterv1.MachinePoolVariables{
-					Overrides: []clusterv1.ClusterVariable{
-						{
-							Name:  "location",
-							Value: toJSON("\"us-central\""),
-						},
-						{
-							Name:  "cpu",
-							Value: toJSON("8"),
-						},
-					},
-				},
-			},
-			mp: builder.MachinePool(metav1.NamespaceDefault, "mp1").
-				WithReplicas(3).
-				WithVersion("v1.21.1").
-				Build(),
-			want: []runtimehooksv1.Variable{
-				{
-					Name:  "location",
-					Value: toJSON("\"us-central\""),
-				},
-				{
-					Name:  "cpu",
-					Value: toJSON("8"),
-				},
-				{
-					Name: BuiltinsName,
-					Value: toJSONCompact(`{
-					"machinePool":{
-						"version": "v1.21.1",
-						"class": "mp-class",
-						"name": "mp1",
-						"topologyName": "mp-topology",
-						"replicas":3
-					}}`),
-				},
-			},
-		},
-		{
-			name:     "Should calculate MachinePool variables for a given patch name",
-			forPatch: "patch1",
-			variableDefinitionsForPatch: map[string]bool{
-				"location": true,
-				"cpu":      true,
-			},
-			mpTopology: &clusterv1.MachinePoolTopology{
-				Replicas: pointer.Int32(3),
-				Name:     "mp-topology",
-				Class:    "mp-class",
-				Variables: &clusterv1.MachinePoolVariables{
-					Overrides: []clusterv1.ClusterVariable{
-						{
-							Name:           "location",
-							Value:          toJSON("\"us-central\""),
-							DefinitionFrom: "patch1",
-						},
-						{
-							Name:  "location",
-							Value: toJSON("\"us-east\""),
-							// This variable should be excluded because it is defined for a different patch.
-							DefinitionFrom: "anotherPatch",
-						},
-
-						{
-							Name:  "http-proxy",
-							Value: toJSON("\"internal.proxy.com\""),
-							// This variable should be excluded because it is not in variableDefinitionsForPatch.
-							DefinitionFrom: "",
-						},
-						{
-							Name:  "cpu",
-							Value: toJSON("8"),
-							// This variable should be included because it is defined for all patches.
-						},
-					},
-				},
-			},
-			mp: builder.MachinePool(metav1.NamespaceDefault, "mp1").
-				WithReplicas(3).
-				WithVersion("v1.21.1").
-				Build(),
-			want: []runtimehooksv1.Variable{
-				{
-					Name:  "location",
-					Value: toJSON("\"us-central\""),
-				},
-				{
-					Name:  "cpu",
-					Value: toJSON("8"),
-				},
-				{
-					Name: BuiltinsName,
-					Value: toJSONCompact(`{
-					"machinePool":{
-						"version": "v1.21.1",
-						"class": "mp-class",
-						"name": "mp1",
-						"topologyName": "mp-topology",
-						"replicas":3
-					}}`),
-				},
-			},
-		},
-		{
-			name:                        "Should calculate MachinePool variables (without overrides)",
-			forPatch:                    "patch1",
-			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
-			mpTopology: &clusterv1.MachinePoolTopology{
-				Replicas: pointer.Int32(3),
-				Name:     "mp-topology",
-				Class:    "mp-class",
-			},
-			mp: builder.MachinePool(metav1.NamespaceDefault, "mp1").
-				WithReplicas(3).
-				WithVersion("v1.21.1").
-				Build(),
-			want: []runtimehooksv1.Variable{
-				{
-					Name: BuiltinsName,
-					Value: toJSONCompact(`{
-					"machinePool":{
-						"version": "v1.21.1",
-						"class": "mp-class",
-						"name": "mp1",
-						"topologyName": "mp-topology",
-						"replicas":3
-					}}`),
-				},
-			},
-		},
-		{
-			name:                        "Should calculate MachinePool variables, replicas not set",
-			forPatch:                    "patch1",
-			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
-			mpTopology: &clusterv1.MachinePoolTopology{
-				Name:  "mp-topology",
-				Class: "mp-class",
-				Variables: &clusterv1.MachinePoolVariables{
-					Overrides: []clusterv1.ClusterVariable{
-						{
-							Name:  "location",
-							Value: toJSON("\"us-central\""),
-						},
-						{
-							Name:  "cpu",
-							Value: toJSON("8"),
-						},
-					},
-				},
-			},
-			mp: builder.MachinePool(metav1.NamespaceDefault, "mp1").
-				WithVersion("v1.21.1").
-				Build(),
-			want: []runtimehooksv1.Variable{
-				{
-					Name:  "location",
-					Value: toJSON("\"us-central\""),
-				},
-				{
-					Name:  "cpu",
-					Value: toJSON("8"),
-				},
-				{
-					Name: BuiltinsName,
-					Value: toJSONCompact(`{
-					"machinePool":{
-						"version": "v1.21.1",
-						"class": "mp-class",
-						"name": "mp1",
-						"topologyName": "mp-topology"
-					}}`),
-				},
-			},
-		},
-		{
-			name:                        "Should calculate MachinePool variables with BoostrapConfig",
-			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
-			forPatch:                    "patch1",
-			mpTopology: &clusterv1.MachinePoolTopology{
-				Replicas: pointer.Int32(3),
-				Name:     "mp-topology",
-				Class:    "mp-class",
-				Variables: &clusterv1.MachinePoolVariables{
-					Overrides: []clusterv1.ClusterVariable{
-						{
-							Name:  "location",
-							Value: toJSON("\"us-central\""),
-						},
-						{
-							Name:  "cpu",
-							Value: toJSON("8"),
-						},
-					},
-				},
-			},
-			mp: builder.MachinePool(metav1.NamespaceDefault, "mp1").
-				WithReplicas(3).
-				WithVersion("v1.21.1").
-				Build(),
-			mpBootstrapConfig: builder.BootstrapConfig(metav1.NamespaceDefault, "mpBC1").Build(),
-			want: []runtimehooksv1.Variable{
-				{
-					Name:  "location",
-					Value: toJSON("\"us-central\""),
-				},
-				{
-					Name:  "cpu",
-					Value: toJSON("8"),
-				},
-				{
-					Name: BuiltinsName,
-					Value: toJSONCompact(`{
-					"machinePool":{
-						"version": "v1.21.1",
-						"class": "mp-class",
-						"name": "mp1",
-						"topologyName": "mp-topology",
-						"replicas":3,
-						"bootstrap":{
-							"configRef":{
-								"name": "mpBC1"
-							}
-						}
-					}}`),
-				},
-			},
-		},
-		{
-			name:                        "Should calculate MachinePool variables with InfrastructureMachinePool",
-			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
-			forPatch:                    "patch1",
-			mpTopology: &clusterv1.MachinePoolTopology{
-				Replicas: pointer.Int32(3),
-				Name:     "mp-topology",
-				Class:    "mp-class",
-				Variables: &clusterv1.MachinePoolVariables{
-					Overrides: []clusterv1.ClusterVariable{
-						{
-							Name:  "location",
-							Value: toJSON("\"us-central\""),
-						},
-						{
-							Name:  "cpu",
-							Value: toJSON("8"),
-						},
-					},
-				},
-			},
-			mp: builder.MachinePool(metav1.NamespaceDefault, "mp1").
-				WithReplicas(3).
-				WithVersion("v1.21.1").
-				Build(),
-			mpInfrastructureMachinePool: builder.InfrastructureMachinePool(metav1.NamespaceDefault, "mpIMP1").Build(),
-			want: []runtimehooksv1.Variable{
-				{
-					Name:  "location",
-					Value: toJSON("\"us-central\""),
-				},
-				{
-					Name:  "cpu",
-					Value: toJSON("8"),
-				},
-				{
-					Name: BuiltinsName,
-					Value: toJSONCompact(`{
-					"machinePool":{
-						"version": "v1.21.1",
-						"class": "mp-class",
-						"name": "mp1",
-						"topologyName": "mp-topology",
-						"replicas":3,
-						"infrastructureRef":{
-							"name": "mpIMP1"
-						}
-					}}`),
-				},
-			},
-		},
-		{
-			name:                        "Should calculate MachinePool variables with BootstrapConfig and InfrastructureMachinePool",
-			variableDefinitionsForPatch: map[string]bool{"location": true, "cpu": true},
-			forPatch:                    "patch1",
-			mpTopology: &clusterv1.MachinePoolTopology{
-				Replicas: pointer.Int32(3),
-				Name:     "mp-topology",
-				Class:    "mp-class",
-				Variables: &clusterv1.MachinePoolVariables{
-					Overrides: []clusterv1.ClusterVariable{
-						{
-							Name:  "location",
-							Value: toJSON("\"us-central\""),
-						},
-						{
-							Name:  "cpu",
-							Value: toJSON("8"),
-						},
-					},
-				},
-			},
-			mp: builder.MachinePool(metav1.NamespaceDefault, "mp1").
-				WithReplicas(3).
-				WithVersion("v1.21.1").
-				Build(),
-			mpBootstrapConfig:           builder.BootstrapConfig(metav1.NamespaceDefault, "mpBC1").Build(),
-			mpInfrastructureMachinePool: builder.InfrastructureMachinePool(metav1.NamespaceDefault, "mpIMP1").Build(),
-			want: []runtimehooksv1.Variable{
-				{
-					Name:  "location",
-					Value: toJSON("\"us-central\""),
-				},
-				{
-					Name:  "cpu",
-					Value: toJSON("8"),
-				},
-				{
-					Name: BuiltinsName,
-					Value: toJSONCompact(`{
-					"machinePool":{
-						"version": "v1.21.1",
-						"class": "mp-class",
-						"name": "mp1",
-						"topologyName": "mp-topology",
-						"replicas":3,
-						"bootstrap":{
-							"configRef":{
-								"name": "mpBC1"
-							}
-						},
-						"infrastructureRef":{
-							"name": "mpIMP1"
-						}
-					}}`),
-				},
-			},
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			g := NewWithT(t)
-
-			got, err := MachinePool(tt.mpTopology, tt.mp, tt.mpBootstrapConfig, tt.mpInfrastructureMachinePool, tt.forPatch, tt.variableDefinitionsForPatch)
-			g.Expect(err).ToNot(HaveOccurred())
-			g.Expect(got).To(BeComparableTo(tt.want))
-		})
-	}
-}
-
 func toJSON(value string) apiextensionsv1.JSON {
 	return apiextensionsv1.JSON{Raw: []byte(value)}
 }
diff --git a/internal/test/builder/builders.go b/internal/test/builder/builders.go
index 92037392a..9074e7c63 100644
--- a/internal/test/builder/builders.go
+++ b/internal/test/builder/builders.go
@@ -314,6 +314,7 @@ type ClusterClassBuilder struct {
 	controlPlaneNodeDrainTimeout              *metav1.Duration
 	controlPlaneNodeVolumeDetachTimeout       *metav1.Duration
 	controlPlaneNodeDeletionTimeout           *metav1.Duration
+	controlPlaneNamingStrategy                *clusterv1.ControlPlaneClassNamingStrategy
 	machineDeploymentClasses                  []clusterv1.MachineDeploymentClass
 	machinePoolClasses                        []clusterv1.MachinePoolClass
 	variables                                 []clusterv1.ClusterClassVariable
@@ -380,6 +381,12 @@ func (c *ClusterClassBuilder) WithControlPlaneNodeDeletionTimeout(t *metav1.Dura
 	return c
 }
 
+// WithControlPlaneNamingStrategy sets the NamingStrategy for the ControlPlane to the ClusterClassBuilder.
+func (c *ClusterClassBuilder) WithControlPlaneNamingStrategy(n *clusterv1.ControlPlaneClassNamingStrategy) *ClusterClassBuilder {
+	c.controlPlaneNamingStrategy = n
+	return c
+}
+
 // WithVariables adds the Variables to the ClusterClassBuilder.
 func (c *ClusterClassBuilder) WithVariables(vars ...clusterv1.ClusterClassVariable) *ClusterClassBuilder {
 	c.variables = vars
@@ -465,6 +472,9 @@ func (c *ClusterClassBuilder) Build() *clusterv1.ClusterClass {
 			Ref: objToRef(c.controlPlaneInfrastructureMachineTemplate),
 		}
 	}
+	if c.controlPlaneNamingStrategy != nil {
+		obj.Spec.ControlPlane.NamingStrategy = c.controlPlaneNamingStrategy
+	}
 
 	obj.Spec.Workers.MachineDeployments = c.machineDeploymentClasses
 	obj.Spec.Workers.MachinePools = c.machinePoolClasses
@@ -485,6 +495,7 @@ type MachineDeploymentClassBuilder struct {
 	nodeDeletionTimeout           *metav1.Duration
 	minReadySeconds               *int32
 	strategy                      *clusterv1.MachineDeploymentStrategy
+	namingStrategy                *clusterv1.MachineDeploymentClassNamingStrategy
 }
 
 // MachineDeploymentClass returns a MachineDeploymentClassBuilder with the given name and namespace.
@@ -560,6 +571,12 @@ func (m *MachineDeploymentClassBuilder) WithStrategy(s *clusterv1.MachineDeploym
 	return m
 }
 
+// WithNamingStrategy sets the NamingStrategy for the MachineDeploymentClassBuilder.
+func (m *MachineDeploymentClassBuilder) WithNamingStrategy(n *clusterv1.MachineDeploymentClassNamingStrategy) *MachineDeploymentClassBuilder {
+	m.namingStrategy = n
+	return m
+}
+
 // Build creates a full MachineDeploymentClass object with the variables passed to the MachineDeploymentClassBuilder.
 func (m *MachineDeploymentClassBuilder) Build() *clusterv1.MachineDeploymentClass {
 	obj := &clusterv1.MachineDeploymentClass{
@@ -598,6 +615,9 @@ func (m *MachineDeploymentClassBuilder) Build() *clusterv1.MachineDeploymentClas
 	if m.strategy != nil {
 		obj.Strategy = m.strategy
 	}
+	if m.namingStrategy != nil {
+		obj.NamingStrategy = m.namingStrategy
+	}
 	return obj
 }
 
@@ -613,6 +633,7 @@ type MachinePoolClassBuilder struct {
 	nodeVolumeDetachTimeout           *metav1.Duration
 	nodeDeletionTimeout               *metav1.Duration
 	minReadySeconds                   *int32
+	namingStrategy                    *clusterv1.MachinePoolClassNamingStrategy
 }
 
 // MachinePoolClass returns a MachinePoolClassBuilder with the given name and namespace.
@@ -676,6 +697,12 @@ func (m *MachinePoolClassBuilder) WithMinReadySeconds(t *int32) *MachinePoolClas
 	return m
 }
 
+// WithNamingStrategy sets the NamingStrategy for the MachinePoolClassBuilder.
+func (m *MachinePoolClassBuilder) WithNamingStrategy(n *clusterv1.MachinePoolClassNamingStrategy) *MachinePoolClassBuilder {
+	m.namingStrategy = n
+	return m
+}
+
 // Build creates a full MachinePoolClass object with the variables passed to the MachinePoolClassBuilder.
 func (m *MachinePoolClassBuilder) Build() *clusterv1.MachinePoolClass {
 	obj := &clusterv1.MachinePoolClass{
@@ -708,6 +735,9 @@ func (m *MachinePoolClassBuilder) Build() *clusterv1.MachinePoolClass {
 	if m.minReadySeconds != nil {
 		obj.MinReadySeconds = m.minReadySeconds
 	}
+	if m.namingStrategy != nil {
+		obj.NamingStrategy = m.namingStrategy
+	}
 	return obj
 }
 
diff --git a/internal/test/builder/zz_generated.deepcopy.go b/internal/test/builder/zz_generated.deepcopy.go
index c6a1e986c..1c9f41ec3 100644
--- a/internal/test/builder/zz_generated.deepcopy.go
+++ b/internal/test/builder/zz_generated.deepcopy.go
@@ -152,6 +152,11 @@ func (in *ClusterClassBuilder) DeepCopyInto(out *ClusterClassBuilder) {
 		*out = new(v1.Duration)
 		**out = **in
 	}
+	if in.controlPlaneNamingStrategy != nil {
+		in, out := &in.controlPlaneNamingStrategy, &out.controlPlaneNamingStrategy
+		*out = new(v1beta1.ControlPlaneClassNamingStrategy)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.machineDeploymentClasses != nil {
 		in, out := &in.machineDeploymentClasses, &out.machineDeploymentClasses
 		*out = make([]v1beta1.MachineDeploymentClass, len(*in))
@@ -510,6 +515,11 @@ func (in *MachineDeploymentClassBuilder) DeepCopyInto(out *MachineDeploymentClas
 		*out = new(v1beta1.MachineDeploymentStrategy)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.namingStrategy != nil {
+		in, out := &in.namingStrategy, &out.namingStrategy
+		*out = new(v1beta1.MachineDeploymentClassNamingStrategy)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineDeploymentClassBuilder.
@@ -682,6 +692,11 @@ func (in *MachinePoolClassBuilder) DeepCopyInto(out *MachinePoolClassBuilder) {
 		*out = new(int32)
 		**out = **in
 	}
+	if in.namingStrategy != nil {
+		in, out := &in.namingStrategy, &out.namingStrategy
+		*out = new(v1beta1.MachinePoolClassNamingStrategy)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachinePoolClassBuilder.
diff --git a/internal/test/envtest/environment.go b/internal/test/envtest/environment.go
index e6028ce1f..6b298414d 100644
--- a/internal/test/envtest/environment.go
+++ b/internal/test/envtest/environment.go
@@ -143,11 +143,11 @@ func Run(ctx context.Context, input RunInput) int {
 
 	if input.MinK8sVersion != "" {
 		if err := version.CheckKubernetesVersion(env.Config, input.MinK8sVersion); err != nil {
-			fmt.Printf("[ERROR] Cannot run tests after failing version check: %v\n", err)
+			fmt.Printf("[IMPORTANT] skipping tests after failing version check: %v\n", err)
 			if err := env.stop(); err != nil {
 				fmt.Println("[WARNING] Failed to stop the test environment")
 			}
-			return 1
+			return 0
 		}
 	}
 
diff --git a/internal/topology/names/names_test.go b/internal/topology/names/names_test.go
new file mode 100644
index 000000000..2a804687b
--- /dev/null
+++ b/internal/topology/names/names_test.go
@@ -0,0 +1,92 @@
+/*
+Copyright 2023 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 names
+
+import (
+	"fmt"
+	"testing"
+
+	. "github.com/onsi/gomega"
+	"github.com/onsi/gomega/types"
+)
+
+func Test_templateGenerator_GenerateName(t *testing.T) {
+	tests := []struct {
+		name     string
+		template string
+		data     map[string]interface{}
+		want     []types.GomegaMatcher
+		wantErr  bool
+	}{
+		{
+			name:     "simple template",
+			template: "some-simple-{{ .test }}",
+			data: map[string]interface{}{
+				"test": "testdata",
+			},
+			want: []types.GomegaMatcher{
+				Equal("some-simple-testdata"),
+			},
+		},
+		{
+			name:     "name which gets trimmed and added a random suffix with 5 characters",
+			template: fmt.Sprintf("%064d", 0),
+			want: []types.GomegaMatcher{
+				HavePrefix(fmt.Sprintf("%058d", 0)),
+				Not(HaveSuffix("00000")),
+			},
+		},
+		{
+			name:     "name which does not get trimmed",
+			template: fmt.Sprintf("%063d", 0),
+			want: []types.GomegaMatcher{
+				Equal(fmt.Sprintf("%063d", 0)),
+			},
+		},
+		{
+			name:     "error on parsing template",
+			template: "some-hardcoded-name-{{ .doesnotexistindata",
+			wantErr:  true,
+		},
+		{
+			name:     "error on due to missing key in data",
+			template: "some-hardcoded-name-{{ .doesnotexistindata }}",
+			data:     nil,
+			wantErr:  true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			g := NewWithT(t)
+			generator := &templateGenerator{
+				template: tt.template,
+				data:     tt.data,
+			}
+			got, err := generator.GenerateName()
+			if (err != nil) != tt.wantErr {
+				t.Errorf("templateGenerator.GenerateName() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if len(got) > maxNameLength {
+				t.Errorf("generated name should never be longer than %d, got %d", maxNameLength, len(got))
+			}
+			for _, matcher := range tt.want {
+				g.Expect(got).To(matcher)
+			}
+		})
+	}
+}
diff --git a/internal/webhooks/clusterclass_test.go b/internal/webhooks/clusterclass_test.go
index 702d453f2..330365dfe 100644
--- a/internal/webhooks/clusterclass_test.go
+++ b/internal/webhooks/clusterclass_test.go
@@ -1165,6 +1165,155 @@ func TestClusterClassValidation(t *testing.T) {
 				Build(),
 			expectErr: true,
 		},
+		{
+			name: "should not return error for valid namingStrategy.template",
+			in: builder.ClusterClass(metav1.NamespaceDefault, "class1").
+				WithInfrastructureClusterTemplate(
+					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
+				WithControlPlaneTemplate(
+					builder.ControlPlaneTemplate(metav1.NamespaceDefault, "cp1").
+						Build()).
+				WithControlPlaneNamingStrategy(&clusterv1.ControlPlaneClassNamingStrategy{Template: pointer.String("template-cp-{{ .cluster.name }}")}).
+				WithControlPlaneInfrastructureMachineTemplate(
+					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpInfra1").
+						Build()).
+				WithWorkerMachineDeploymentClasses(
+					*builder.MachineDeploymentClass("aa").
+						WithInfrastructureTemplate(
+							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
+						WithBootstrapTemplate(
+							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
+						WithNamingStrategy(&clusterv1.MachineDeploymentClassNamingStrategy{Template: pointer.String("template-md-{{ .cluster.name }}")}).
+						Build()).
+				WithWorkerMachinePoolClasses(
+					*builder.MachinePoolClass("bb").
+						WithInfrastructureTemplate(
+							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra2").Build()).
+						WithBootstrapTemplate(
+							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap2").Build()).
+						WithNamingStrategy(&clusterv1.MachinePoolClassNamingStrategy{Template: pointer.String("template-mp-{{ .cluster.name }}")}).
+						Build()).
+				Build(),
+			expectErr: false,
+		},
+		{
+			name: "should return error for invalid ControlPlane namingStrategy.template",
+			in: builder.ClusterClass(metav1.NamespaceDefault, "class1").
+				WithInfrastructureClusterTemplate(
+					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
+				WithControlPlaneTemplate(
+					builder.ControlPlaneTemplate(metav1.NamespaceDefault, "cp1").
+						Build()).
+				WithControlPlaneNamingStrategy(&clusterv1.ControlPlaneClassNamingStrategy{Template: pointer.String("template-cp-{{ .invalidkey }}")}).
+				WithControlPlaneInfrastructureMachineTemplate(
+					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpInfra1").
+						Build()).
+				Build(),
+			expectErr: true,
+		},
+		{
+			name: "should return error for ControlPlane namingStrategy.template when the generated name does not conform to RFC 1123",
+			in: builder.ClusterClass(metav1.NamespaceDefault, "class1").
+				WithInfrastructureClusterTemplate(
+					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
+				WithControlPlaneTemplate(
+					builder.ControlPlaneTemplate(metav1.NamespaceDefault, "cp1").
+						Build()).
+				WithControlPlaneNamingStrategy(&clusterv1.ControlPlaneClassNamingStrategy{Template: pointer.String("template-cp-{{ .cluster.name }}-")}).
+				WithControlPlaneInfrastructureMachineTemplate(
+					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpInfra1").
+						Build()).
+				Build(),
+			expectErr: true,
+		},
+		{
+			name: "should return error for invalid MachineDeployment namingStrategy.template",
+			in: builder.ClusterClass(metav1.NamespaceDefault, "class1").
+				WithInfrastructureClusterTemplate(
+					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
+				WithControlPlaneTemplate(
+					builder.ControlPlaneTemplate(metav1.NamespaceDefault, "cp1").
+						Build()).
+				WithControlPlaneInfrastructureMachineTemplate(
+					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpInfra1").
+						Build()).
+				WithWorkerMachineDeploymentClasses(
+					*builder.MachineDeploymentClass("aa").
+						WithInfrastructureTemplate(
+							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
+						WithBootstrapTemplate(
+							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
+						WithNamingStrategy(&clusterv1.MachineDeploymentClassNamingStrategy{Template: pointer.String("template-md-{{ .cluster.name")}).
+						Build()).
+				Build(),
+			expectErr: true,
+		},
+		{
+			name: "should return error for invalid MachineDeployment namingStrategy.template when the generated name does not conform to RFC 1123",
+			in: builder.ClusterClass(metav1.NamespaceDefault, "class1").
+				WithInfrastructureClusterTemplate(
+					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
+				WithControlPlaneTemplate(
+					builder.ControlPlaneTemplate(metav1.NamespaceDefault, "cp1").
+						Build()).
+				WithControlPlaneInfrastructureMachineTemplate(
+					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpInfra1").
+						Build()).
+				WithWorkerMachineDeploymentClasses(
+					*builder.MachineDeploymentClass("aa").
+						WithInfrastructureTemplate(
+							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
+						WithBootstrapTemplate(
+							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
+						WithNamingStrategy(&clusterv1.MachineDeploymentClassNamingStrategy{Template: pointer.String("template-md-{{ .cluster.name }}-")}).
+						Build()).
+				Build(),
+			expectErr: true,
+		},
+		{
+			name: "should return error for invalid MachinePool namingStrategy.template",
+			in: builder.ClusterClass(metav1.NamespaceDefault, "class1").
+				WithInfrastructureClusterTemplate(
+					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
+				WithControlPlaneTemplate(
+					builder.ControlPlaneTemplate(metav1.NamespaceDefault, "cp1").
+						Build()).
+				WithControlPlaneInfrastructureMachineTemplate(
+					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpInfra1").
+						Build()).
+				WithWorkerMachinePoolClasses(
+					*builder.MachinePoolClass("bb").
+						WithInfrastructureTemplate(
+							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra2").Build()).
+						WithBootstrapTemplate(
+							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap2").Build()).
+						WithNamingStrategy(&clusterv1.MachinePoolClassNamingStrategy{Template: pointer.String("template-mp-{{ .cluster.name")}).
+						Build()).
+				Build(),
+			expectErr: true,
+		},
+		{
+			name: "should return error for invalid MachinePool namingStrategy.template when the generated name does not conform to RFC 1123",
+			in: builder.ClusterClass(metav1.NamespaceDefault, "class1").
+				WithInfrastructureClusterTemplate(
+					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
+				WithControlPlaneTemplate(
+					builder.ControlPlaneTemplate(metav1.NamespaceDefault, "cp1").
+						Build()).
+				WithControlPlaneInfrastructureMachineTemplate(
+					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpInfra1").
+						Build()).
+				WithWorkerMachinePoolClasses(
+					*builder.MachinePoolClass("bb").
+						WithInfrastructureTemplate(
+							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra2").Build()).
+						WithBootstrapTemplate(
+							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap2").Build()).
+						WithNamingStrategy(&clusterv1.MachinePoolClassNamingStrategy{Template: pointer.String("template-mp-{{ .cluster.name }}-")}).
+						Build()).
+				Build(),
+			expectErr: true,
+		},
 	}
 
 	for _, tt := range tests {
diff --git a/scripts/ci-e2e-lib.sh b/scripts/ci-e2e-lib.sh
index e14a70b98..e1c595be4 100644
--- a/scripts/ci-e2e-lib.sh
+++ b/scripts/ci-e2e-lib.sh
@@ -204,9 +204,9 @@ EOL
 # the actual test run less sensible to the network speed.
 kind:prepullAdditionalImages () {
   # Pulling cert manager images so we can pre-load in kind nodes
-  kind::prepullImage "quay.io/jetstack/cert-manager-cainjector:v1.13.0"
-  kind::prepullImage "quay.io/jetstack/cert-manager-webhook:v1.13.0"
-  kind::prepullImage "quay.io/jetstack/cert-manager-controller:v1.13.0"
+  kind::prepullImage "quay.io/jetstack/cert-manager-cainjector:v1.12.4"
+  kind::prepullImage "quay.io/jetstack/cert-manager-webhook:v1.12.4"
+  kind::prepullImage "quay.io/jetstack/cert-manager-controller:v1.12.4"
 }
 
 # kind:prepullImage pre-pull a docker image if no already present locally.
diff --git a/test/e2e/config/docker.yaml b/test/e2e/config/docker.yaml
index cf1aafd76..a976ca29f 100644
--- a/test/e2e/config/docker.yaml
+++ b/test/e2e/config/docker.yaml
@@ -23,11 +23,11 @@ images:
   loadBehavior: tryLoad
 - name: gcr.io/k8s-staging-cluster-api/test-extension-{ARCH}:dev
   loadBehavior: tryLoad
-- name: quay.io/jetstack/cert-manager-cainjector:v1.13.0
+- name: quay.io/jetstack/cert-manager-cainjector:v1.12.4
   loadBehavior: tryLoad
-- name: quay.io/jetstack/cert-manager-webhook:v1.13.0
+- name: quay.io/jetstack/cert-manager-webhook:v1.12.4
   loadBehavior: tryLoad
-- name: quay.io/jetstack/cert-manager-controller:v1.13.0
+- name: quay.io/jetstack/cert-manager-controller:v1.12.4
   loadBehavior: tryLoad
 
 providers:
diff --git a/test/e2e/data/infrastructure-docker/main/clusterclass-quick-start-runtimesdk.yaml b/test/e2e/data/infrastructure-docker/main/clusterclass-quick-start-runtimesdk.yaml
index e6ac76b57..9a4ee30ea 100644
--- a/test/e2e/data/infrastructure-docker/main/clusterclass-quick-start-runtimesdk.yaml
+++ b/test/e2e/data/infrastructure-docker/main/clusterclass-quick-start-runtimesdk.yaml
@@ -13,6 +13,8 @@ spec:
         kind: DockerMachineTemplate
         apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
         name: quick-start-control-plane
+    namingStrategy:
+      template: "{{ .cluster.name }}-cp-{{ .random }}"
   infrastructure:
     ref:
       apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
@@ -21,6 +23,8 @@ spec:
   workers:
     machineDeployments:
     - class: default-worker
+      namingStrategy:
+        template: "{{ .cluster.name }}-md-{{ .machineDeployment.topologyName }}-{{ .random }}"
       template:
         bootstrap:
           ref:
diff --git a/test/go.mod b/test/go.mod
index 72fde0e6e..cf019734c 100644
--- a/test/go.mod
+++ b/test/go.mod
@@ -6,10 +6,10 @@ replace sigs.k8s.io/cluster-api => ../
 
 require (
 	github.com/blang/semver/v4 v4.0.0
-	github.com/docker/docker v24.0.6+incompatible
+	github.com/docker/docker v24.0.5+incompatible
 	github.com/docker/go-connections v0.4.0
 	github.com/emicklei/go-restful/v3 v3.11.0
-	github.com/evanphx/json-patch/v5 v5.7.0
+	github.com/evanphx/json-patch/v5 v5.6.0
 	github.com/flatcar/ignition v0.36.2
 	github.com/go-logr/logr v1.2.4
 	github.com/google/go-cmp v0.5.9
@@ -21,7 +21,7 @@ require (
 	github.com/vincent-petithory/dataurl v1.0.0
 	go.etcd.io/etcd/api/v3 v3.5.9
 	go.etcd.io/etcd/client/v3 v3.5.9
-	golang.org/x/net v0.15.0
+	golang.org/x/net v0.14.0
 	google.golang.org/grpc v1.55.1
 	k8s.io/api v0.28.1
 	k8s.io/apiextensions-apiserver v0.28.1
@@ -133,13 +133,13 @@ require (
 	go.opentelemetry.io/proto/otlp v0.19.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	go.uber.org/zap v1.25.0 // indirect
-	golang.org/x/crypto v0.13.0 // indirect
+	golang.org/x/crypto v0.12.0 // indirect
 	golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
-	golang.org/x/oauth2 v0.12.0 // indirect
+	golang.org/x/oauth2 v0.11.0 // indirect
 	golang.org/x/sync v0.3.0 // indirect
-	golang.org/x/sys v0.12.0 // indirect
-	golang.org/x/term v0.12.0 // indirect
-	golang.org/x/text v0.13.0 // indirect
+	golang.org/x/sys v0.11.0 // indirect
+	golang.org/x/term v0.11.0 // indirect
+	golang.org/x/text v0.12.0 // indirect
 	golang.org/x/time v0.3.0 // indirect
 	golang.org/x/tools v0.12.0 // indirect
 	gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
diff --git a/test/go.sum b/test/go.sum
index 7bb101f9a..8c294d3b1 100644
--- a/test/go.sum
+++ b/test/go.sum
@@ -138,8 +138,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
 github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
-github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE=
-github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v24.0.5+incompatible h1:WmgcE4fxyI6EEXxBRxsHnZXrO1pQ3smi0k/jho4HLeY=
+github.com/docker/docker v24.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
 github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
 github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
@@ -159,9 +159,8 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
 github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
 github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
-github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc=
-github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
 github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
@@ -594,8 +593,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
-golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
-golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
+golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -674,8 +673,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
-golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
-golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
+golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -686,8 +685,8 @@ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
-golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
+golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
+golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -754,13 +753,13 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
-golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
+golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
-golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
-golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
+golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
+golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -771,8 +770,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
-golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
+golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=