Repository: kubernetes-sigs/cluster-api
Pull request #9116 (2023-08-03 15:00:34)

🌱 Add separate concurrency flag for cluster cache tracker
<!-- 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**:

Adds a separate flag `--clustercache-tracker-concurrency` to allow adjusting the concurrency of the created cluster cache tracker to all relevant executables: CAPI, CAPBK, KCP, CAPD.

This came up while adding the clustercache tracker to CAPV in [this discussion](https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/pull/2132#discussion_r1282677508).

Before this PR the other concurrency flags got re-used to also set the concurrency for the clustercache tracker.

Example from @sbueringer why this change makes sense:

> Let's make an example. You have a mgmt cluster with 2k+ workload clusters. The cluster controller is doing some heavy lifting so you would want to use more workers (for 2k 10 should be still fine but it's just an example :D).
>
> So if you want to increase the concurrency of the cluster controller you don't want the side effect that the cluster controller of the cluster cache tracker also gets more workers.


**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Fixes #


k8s-ci-robot: @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-test-main | 022f1fbeab46ee85aa96562f2b243cfc07a463b7 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/9116/pull-cluster-api-test-main/1687116279019737088) | true | `/test pull-cluster-api-test-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=9116). [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: cc @vincepri @killianmuldoon 
chrischdi: With marking it as deprecated, the old flag won't be visible anymore on `-h`. Defining it does not prevent the controller to run, instead the following is printed at the start:
```
❯ go run ./bootstrap/kubeadm --cluster-concurrency 1
Flag --cluster-concurrency has been deprecated, This flag has no function anymore and is going to be removed in a next release. Use "--clustercachetracker-concurrency" insteaed.
I0804 08:32:11.242317   50353 listener.go:44] "controller-runtime/metrics: Metrics server is starting to listen" addr="localhost:8080"
I0804 08:32:11.248101   50353 webhook.go:158] "controller-runtime/builder: Registering a mutating webhook" GVK="bootstrap.cluster.x-k8s.io/v1beta1, Kind=KubeadmConfig" path="/mutate-bootstrap-cluster-x-k8s-io-v1beta1-kubeadmconfig"
...
```
chrischdi: > One nit/question, otherwise looks good to me, thanks

Sorry about forgetting to push my commit 🤦 
sbueringer: Restarted the linter

Patch:
diff --git a/.github/dependabot.yaml b/.github/dependabot.yml
similarity index 100%
rename from .github/dependabot.yaml
rename to .github/dependabot.yml
diff --git a/.github/workflows/pr-dependabot.yaml b/.github/workflows/dependabot.yml
similarity index 88%
rename from .github/workflows/pr-dependabot.yaml
rename to .github/workflows/dependabot.yml
index d3b2f1d63..8b55c95d9 100644
--- a/.github/workflows/pr-dependabot.yaml
+++ b/.github/workflows/dependabot.yml
@@ -1,6 +1,5 @@
-name: PR dependabot code generation and go modules fix
+name: dependabot
 
-# This action runs on other PRs opened by dependabot. It updates modules and generated code on PRs opened by dependabot.
 on:
   pull_request:
     branches:
diff --git a/.github/workflows/pr-golangci-lint.yaml b/.github/workflows/golangci-lint.yml
similarity index 97%
rename from .github/workflows/pr-golangci-lint.yaml
rename to .github/workflows/golangci-lint.yml
index b5dbbaa98..dc8c2f1ae 100644
--- a/.github/workflows/pr-golangci-lint.yaml
+++ b/.github/workflows/golangci-lint.yml
@@ -1,4 +1,4 @@
-name: PR golangci-lint
+name: golangci-lint
 
 on:
   pull_request:
diff --git a/.github/workflows/pr-md-link-check.yaml b/.github/workflows/lint-docs-pr.yaml
similarity index 95%
rename from .github/workflows/pr-md-link-check.yaml
rename to .github/workflows/lint-docs-pr.yaml
index 87154aaca..93a5b16ef 100644
--- a/.github/workflows/pr-md-link-check.yaml
+++ b/.github/workflows/lint-docs-pr.yaml
@@ -1,4 +1,4 @@
-name: PR check Markdown links
+name: Check PR Markdown links
 
 on:
   pull_request:
diff --git a/.github/workflows/weekly-md-link-check.yaml b/.github/workflows/lint-docs-weekly.yml
similarity index 93%
rename from .github/workflows/weekly-md-link-check.yaml
rename to .github/workflows/lint-docs-weekly.yml
index 2594366b2..7323b58a3 100644
--- a/.github/workflows/weekly-md-link-check.yaml
+++ b/.github/workflows/lint-docs-weekly.yml
@@ -14,7 +14,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        branch: [ main, release-1.5, release-1.4 ]
+        branch: [ main, release-1.4, release-1.3 ]
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # tag=v3.5.3
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yml
similarity index 100%
rename from .github/workflows/release.yaml
rename to .github/workflows/release.yml
diff --git a/.github/workflows/weekly-image-scan.yaml b/.github/workflows/scan.yml
similarity index 91%
rename from .github/workflows/weekly-image-scan.yaml
rename to .github/workflows/scan.yml
index cac026712..5d727684c 100644
--- a/.github/workflows/weekly-image-scan.yaml
+++ b/.github/workflows/scan.yml
@@ -1,4 +1,4 @@
-name: Weekly image scan
+name: scan-images
 
 on:
   schedule:
@@ -13,7 +13,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        branch: [ main, release-1.5, release-1.4 ]
+        branch: [ main, release-1.4, release-1.3 ]
     name: Trivy
     runs-on: ubuntu-latest
     steps:
diff --git a/.github/workflows/pr-verify.yaml b/.github/workflows/verify.yml
similarity index 96%
rename from .github/workflows/pr-verify.yaml
rename to .github/workflows/verify.yml
index 0f476079a..40a09e150 100644
--- a/.github/workflows/pr-verify.yaml
+++ b/.github/workflows/verify.yml
@@ -1,4 +1,4 @@
-name: PR verify
+name: verify
 
 on:
   pull_request_target:
diff --git a/Makefile b/Makefile
index c34dfb202..0a98e6d7e 100644
--- a/Makefile
+++ b/Makefile
@@ -160,7 +160,7 @@ GINKGO := $(abspath $(TOOLS_BIN_DIR)/$(GINKGO_BIN)-$(GINGKO_VER))
 GINKGO_PKG := github.com/onsi/ginkgo/v2/ginkgo
 
 GOLANGCI_LINT_BIN := golangci-lint
-GOLANGCI_LINT_VER := $(shell cat .github/workflows/pr-golangci-lint.yaml | grep [[:space:]]version: | sed 's/.*version: //')
+GOLANGCI_LINT_VER := $(shell cat .github/workflows/golangci-lint.yml | grep [[:space:]]version: | sed 's/.*version: //')
 GOLANGCI_LINT := $(abspath $(TOOLS_BIN_DIR)/$(GOLANGCI_LINT_BIN)-$(GOLANGCI_LINT_VER))
 GOLANGCI_LINT_PKG := github.com/golangci/golangci-lint/cmd/golangci-lint
 
diff --git a/bootstrap/kubeadm/main.go b/bootstrap/kubeadm/main.go
index 819a5c31c..a32ed8ff3 100644
--- a/bootstrap/kubeadm/main.go
+++ b/bootstrap/kubeadm/main.go
@@ -76,26 +76,27 @@ func init() {
 }
 
 var (
-	metricsBindAddr             string
-	enableLeaderElection        bool
-	leaderElectionLeaseDuration time.Duration
-	leaderElectionRenewDeadline time.Duration
-	leaderElectionRetryPeriod   time.Duration
-	watchFilterValue            string
-	watchNamespace              string
-	profilerAddress             string
-	enableContentionProfiling   bool
-	clusterConcurrency          int
-	kubeadmConfigConcurrency    int
-	syncPeriod                  time.Duration
-	restConfigQPS               float32
-	restConfigBurst             int
-	webhookPort                 int
-	webhookCertDir              string
-	healthAddr                  string
-	tokenTTL                    time.Duration
-	tlsOptions                  = flags.TLSOptions{}
-	logOptions                  = logs.NewOptions()
+	metricsBindAddr                string
+	enableLeaderElection           bool
+	leaderElectionLeaseDuration    time.Duration
+	leaderElectionRenewDeadline    time.Duration
+	leaderElectionRetryPeriod      time.Duration
+	watchFilterValue               string
+	watchNamespace                 string
+	profilerAddress                string
+	enableContentionProfiling      bool
+	clusterConcurrency             int
+	clusterCacheTrackerConcurrency int
+	kubeadmConfigConcurrency       int
+	syncPeriod                     time.Duration
+	restConfigQPS                  float32
+	restConfigBurst                int
+	webhookPort                    int
+	webhookCertDir                 string
+	healthAddr                     string
+	tokenTTL                       time.Duration
+	tlsOptions                     = flags.TLSOptions{}
+	logOptions                     = logs.NewOptions()
 )
 
 // InitFlags initializes this manager's flags.
@@ -128,6 +129,10 @@ func InitFlags(fs *pflag.FlagSet) {
 
 	fs.IntVar(&clusterConcurrency, "cluster-concurrency", 10,
 		"Number of clusters to process simultaneously")
+	_ = fs.MarkDeprecated("cluster-concurrency", "This flag has no function anymore and is going to be removed in a next release. Use \"--clustercachetracker-concurrency\" instead.")
+
+	fs.IntVar(&clusterCacheTrackerConcurrency, "clustercachetracker-concurrency", 10,
+		"Number of clusters to process simultaneously")
 
 	fs.IntVar(&kubeadmConfigConcurrency, "kubeadmconfig-concurrency", 10,
 		"Number of kubeadm configs to process simultaneously")
@@ -307,7 +312,7 @@ func setupReconcilers(ctx context.Context, mgr ctrl.Manager) {
 		Client:           mgr.GetClient(),
 		Tracker:          tracker,
 		WatchFilterValue: watchFilterValue,
-	}).SetupWithManager(ctx, mgr, concurrency(clusterConcurrency)); err != nil {
+	}).SetupWithManager(ctx, mgr, concurrency(clusterCacheTrackerConcurrency)); err != nil {
 		setupLog.Error(err, "unable to create controller", "controller", "ClusterCacheReconciler")
 		os.Exit(1)
 	}
diff --git a/controlplane/kubeadm/main.go b/controlplane/kubeadm/main.go
index f9a6bfab1..fdce09785 100644
--- a/controlplane/kubeadm/main.go
+++ b/controlplane/kubeadm/main.go
@@ -91,6 +91,7 @@ var (
 	profilerAddress                string
 	enableContentionProfiling      bool
 	kubeadmControlPlaneConcurrency int
+	clusterCacheTrackerConcurrency int
 	syncPeriod                     time.Duration
 	restConfigQPS                  float32
 	restConfigBurst                int
@@ -134,6 +135,9 @@ func InitFlags(fs *pflag.FlagSet) {
 	fs.IntVar(&kubeadmControlPlaneConcurrency, "kubeadmcontrolplane-concurrency", 10,
 		"Number of kubeadm control planes to process simultaneously")
 
+	fs.IntVar(&clusterCacheTrackerConcurrency, "clustercachetracker-concurrency", 10,
+		"Number of clusters to process simultaneously")
+
 	fs.DurationVar(&syncPeriod, "sync-period", 10*time.Minute,
 		"The minimum interval at which watched resources are reconciled (e.g. 15m)")
 
@@ -320,7 +324,7 @@ func setupReconcilers(ctx context.Context, mgr ctrl.Manager) {
 		Client:           mgr.GetClient(),
 		Tracker:          tracker,
 		WatchFilterValue: watchFilterValue,
-	}).SetupWithManager(ctx, mgr, concurrency(kubeadmControlPlaneConcurrency)); err != nil {
+	}).SetupWithManager(ctx, mgr, concurrency(clusterCacheTrackerConcurrency)); err != nil {
 		setupLog.Error(err, "unable to create controller", "controller", "ClusterCacheReconciler")
 		os.Exit(1)
 	}
diff --git a/docs/book/src/reference/jobs.md b/docs/book/src/reference/jobs.md
index ba4bb9ca4..8be7f50ef 100644
--- a/docs/book/src/reference/jobs.md
+++ b/docs/book/src/reference/jobs.md
@@ -37,27 +37,23 @@ Prow Presubmits:
   * [pull-cluster-api-e2e-scale-main-experimental] `./scripts/ci-e2e-scale.sh`
 
 GitHub Presubmit Workflows:
-* PR golangci-lint: golangci/golangci-lint-action
+* golangci-lint: golangci/golangci-lint-action
   * Runs golangci-lint. Can be run locally via `make lint`.
-* PR verify: kubernetes-sigs/kubebuilder-release-tools verifier
+* verify: kubernetes-sigs/kubebuilder-release-tools verifier
   * Verifies the PR titles have a valid format, i.e. contains one of the valid icons.
   * Verifies the PR description is valid, i.e. is long enough.
-* PR check Markdown links (run when markdown files changed)
-  * Checks markdown modified in PR for broken links.
-* PR dependabot (run on dependabot PRs)
+* Check PR Markdown links (run when markdown files changed)
+  * Checks markdown for broken links.
+* dependabot (run on dependabot PRs)
   * Regenerates Go modules and code.
+* release (run on tags)
+  * Creates a GitHub release with release notes for the tag.
 
 GitHub Weekly Workflows:
-* Weekly check all Markdown links
-  * Checks markdown across the repo for broken links.
-* Weekly image scan:
+* golangci-lint: golangci/golangci-lint-action
+  * Weekly check all Markdown links
+* scan-images:
   * Scan all images for vulnerabilities. Can be run locally via `make verify-container-images`
-* Weekly release test:
-  * Test the the `release` make target is working without errors.
-  
-Other Github workflows
-* release (runs when tags are pushed)
-  * Creates a GitHub release with release notes for the tag.
 
 ### Postsubmits
 
diff --git a/docs/release/release-tasks.md b/docs/release/release-tasks.md
index 4fd90f92d..b3f0a3067 100644
--- a/docs/release/release-tasks.md
+++ b/docs/release/release-tasks.md
@@ -426,7 +426,7 @@ While we add test coverage for the new release branch we will also drop the test
 3. Remove tests for old release branches according to our policy documented in [Support and guarantees](../../CONTRIBUTING.md#support-and-guarantees)
    For example, let's assume we just created tests for v1.4, then we can now drop test coverage for the release-1.1 branch.
 4. Verify the jobs and dashboards a day later by taking a look at: `https://testgrid.k8s.io/sig-cluster-lifecycle-cluster-api-1.4`
-5. Update `.github/workflows/weekly-image-scan.yaml` - to setup Trivy scanning - `.github/workflows/weekly-md-link-check.yaml` - to setup link checking in the CAPI book - and `.github/workflows/weekly-test-release.yaml` - to verify the release target is working - for the currently supported branches.
+5. Update `.github/workflows/scan.yml` - to setup Trivy scanning - `.github/workflows/lint-docs-weekly.yml` - to setup link checking in the CAPI book - and `.github/workflows/test-release-weekly.yml` - to verify the release target is working - for the currently supported branches.
 
 
 Prior art:
diff --git a/go.mod b/go.mod
index 9828f5038..16c67aadf 100644
--- a/go.mod
+++ b/go.mod
@@ -30,7 +30,7 @@ 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/net v0.13.0 // indirect
+	golang.org/x/net v0.12.0 // indirect
 	golang.org/x/oauth2 v0.10.0
 	google.golang.org/grpc v1.55.0
 	k8s.io/api v0.27.2
diff --git a/go.sum b/go.sum
index e212348e3..1d257153d 100644
--- a/go.sum
+++ b/go.sum
@@ -664,8 +664,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
 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.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY=
-golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
+golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
+golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
 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=
diff --git a/hack/tools/go.mod b/hack/tools/go.mod
index 1cc680381..f6a849624 100644
--- a/hack/tools/go.mod
+++ b/hack/tools/go.mod
@@ -113,7 +113,7 @@ require (
 	go.opencensus.io v0.24.0 // indirect
 	golang.org/x/crypto v0.11.0 // indirect
 	golang.org/x/mod v0.10.0 // indirect
-	golang.org/x/net v0.13.0 // indirect
+	golang.org/x/net v0.12.0 // indirect
 	golang.org/x/oauth2 v0.10.0 // indirect
 	golang.org/x/sys v0.10.0 // indirect
 	golang.org/x/term v0.10.0 // indirect
diff --git a/hack/tools/go.sum b/hack/tools/go.sum
index 6d797bc92..6ca381d5a 100644
--- a/hack/tools/go.sum
+++ b/hack/tools/go.sum
@@ -586,8 +586,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
 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.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY=
-golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
+golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
+golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
 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=
diff --git a/hack/tools/release/notes.go b/hack/tools/release/notes.go
index 889b0be38..83fa935a3 100644
--- a/hack/tools/release/notes.go
+++ b/hack/tools/release/notes.go
@@ -434,15 +434,6 @@ type releaseNoteEntry struct {
 	prNumber string
 }
 
-func modifyEntryTitle(title string, prefixes []string) string {
-	entryWithoutTag := title
-	for _, prefix := range prefixes {
-		entryWithoutTag = strings.TrimLeft(strings.TrimPrefix(entryWithoutTag, prefix), " ")
-	}
-
-	return strings.ToUpper(string(entryWithoutTag[0])) + entryWithoutTag[1:]
-}
-
 // generateReleaseNoteEntry processes a commit into a PR line item for the release notes.
 func generateReleaseNoteEntry(c *commit) (*releaseNoteEntry, error) {
 	entry := &releaseNoteEntry{}
@@ -461,22 +452,27 @@ func generateReleaseNoteEntry(c *commit) (*releaseNoteEntry, error) {
 	switch {
 	case strings.HasPrefix(entry.title, ":sparkles:"), strings.HasPrefix(entry.title, "✨"):
 		entry.section = features
-		entry.title = modifyEntryTitle(entry.title, []string{":sparkles:", "✨"})
+		entry.title = strings.TrimPrefix(entry.title, ":sparkles:")
+		entry.title = strings.TrimPrefix(entry.title, "✨")
 	case strings.HasPrefix(entry.title, ":bug:"), strings.HasPrefix(entry.title, "🐛"):
 		entry.section = bugs
-		entry.title = modifyEntryTitle(entry.title, []string{":bug:", "🐛"})
+		entry.title = strings.TrimPrefix(entry.title, ":bug:")
+		entry.title = strings.TrimPrefix(entry.title, "🐛")
 	case strings.HasPrefix(entry.title, ":book:"), strings.HasPrefix(entry.title, "📖"):
 		entry.section = documentation
-		entry.title = modifyEntryTitle(entry.title, []string{":book:", "📖"})
+		entry.title = strings.TrimPrefix(entry.title, ":book:")
+		entry.title = strings.TrimPrefix(entry.title, "📖")
 		if strings.Contains(entry.title, "CAEP") || strings.Contains(entry.title, "proposal") {
 			entry.section = proposals
 		}
 	case strings.HasPrefix(entry.title, ":seedling:"), strings.HasPrefix(entry.title, "🌱"):
 		entry.section = other
-		entry.title = modifyEntryTitle(entry.title, []string{":seedling:", "🌱"})
+		entry.title = strings.TrimPrefix(entry.title, ":seedling:")
+		entry.title = strings.TrimPrefix(entry.title, "🌱")
 	case strings.HasPrefix(entry.title, ":warning:"), strings.HasPrefix(entry.title, "⚠️"):
 		entry.section = warning
-		entry.title = modifyEntryTitle(entry.title, []string{":warning:", "⚠️"})
+		entry.title = strings.TrimPrefix(entry.title, ":warning:")
+		entry.title = strings.TrimPrefix(entry.title, "⚠️")
 	default:
 		entry.section = unknown
 	}
diff --git a/main.go b/main.go
index 7cb437958..2890605f7 100644
--- a/main.go
+++ b/main.go
@@ -82,34 +82,35 @@ var (
 	controllerName = "cluster-api-controller-manager"
 
 	// flags.
-	metricsBindAddr               string
-	enableLeaderElection          bool
-	leaderElectionLeaseDuration   time.Duration
-	leaderElectionRenewDeadline   time.Duration
-	leaderElectionRetryPeriod     time.Duration
-	watchNamespace                string
-	watchFilterValue              string
-	profilerAddress               string
-	enableContentionProfiling     bool
-	clusterTopologyConcurrency    int
-	clusterClassConcurrency       int
-	clusterConcurrency            int
-	extensionConfigConcurrency    int
-	machineConcurrency            int
-	machineSetConcurrency         int
-	machineDeploymentConcurrency  int
-	machinePoolConcurrency        int
-	clusterResourceSetConcurrency int
-	machineHealthCheckConcurrency int
-	syncPeriod                    time.Duration
-	restConfigQPS                 float32
-	restConfigBurst               int
-	nodeDrainClientTimeout        time.Duration
-	webhookPort                   int
-	webhookCertDir                string
-	healthAddr                    string
-	tlsOptions                    = flags.TLSOptions{}
-	logOptions                    = logs.NewOptions()
+	metricsBindAddr                string
+	enableLeaderElection           bool
+	leaderElectionLeaseDuration    time.Duration
+	leaderElectionRenewDeadline    time.Duration
+	leaderElectionRetryPeriod      time.Duration
+	watchNamespace                 string
+	watchFilterValue               string
+	profilerAddress                string
+	enableContentionProfiling      bool
+	clusterTopologyConcurrency     int
+	clusterCacheTrackerConcurrency int
+	clusterClassConcurrency        int
+	clusterConcurrency             int
+	extensionConfigConcurrency     int
+	machineConcurrency             int
+	machineSetConcurrency          int
+	machineDeploymentConcurrency   int
+	machinePoolConcurrency         int
+	clusterResourceSetConcurrency  int
+	machineHealthCheckConcurrency  int
+	syncPeriod                     time.Duration
+	restConfigQPS                  float32
+	restConfigBurst                int
+	nodeDrainClientTimeout         time.Duration
+	webhookPort                    int
+	webhookCertDir                 string
+	healthAddr                     string
+	tlsOptions                     = flags.TLSOptions{}
+	logOptions                     = logs.NewOptions()
 )
 
 func init() {
@@ -177,6 +178,9 @@ func InitFlags(fs *pflag.FlagSet) {
 	fs.IntVar(&clusterConcurrency, "cluster-concurrency", 10,
 		"Number of clusters to process simultaneously")
 
+	fs.IntVar(&clusterCacheTrackerConcurrency, "clustercachetracker-concurrency", 10,
+		"Number of clusters to process simultaneously")
+
 	fs.IntVar(&extensionConfigConcurrency, "extensionconfig-concurrency", 10,
 		"Number of extension configs to process simultaneously")
 
@@ -394,7 +398,7 @@ func setupReconcilers(ctx context.Context, mgr ctrl.Manager) {
 		Client:           mgr.GetClient(),
 		Tracker:          tracker,
 		WatchFilterValue: watchFilterValue,
-	}).SetupWithManager(ctx, mgr, concurrency(clusterConcurrency)); err != nil {
+	}).SetupWithManager(ctx, mgr, concurrency(clusterCacheTrackerConcurrency)); err != nil {
 		setupLog.Error(err, "unable to create controller", "controller", "ClusterCacheReconciler")
 		os.Exit(1)
 	}


Test patch:
diff --git a/.github/workflows/weekly-test-release.yaml b/.github/workflows/test-release-weekly.yml
similarity index 83%
rename from .github/workflows/weekly-test-release.yaml
rename to .github/workflows/test-release-weekly.yml
index 0c4c6f662..8b6ec50d8 100644
--- a/.github/workflows/weekly-test-release.yaml
+++ b/.github/workflows/test-release-weekly.yml
@@ -1,7 +1,7 @@
-name: Weekly release test
+name: Release test
 
-# Note: This workflow does not build for releases. It attempts to build release binaries periodically to ensure the repo
-# release machinery is in a good state.
+# Note: This workflow does not build for releases. It attempts to build release binaries daily to ensure the repo
+# is in a good state.
 
 on:
   schedule:
@@ -17,7 +17,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        branch: [ main, release-1.5, release-1.4 ]
+        branch: [ main, release-1.4, release-1.3 ]
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # tag=v3.5.3
diff --git a/test/go.mod b/test/go.mod
index 39d961b3c..9bb2d61dd 100644
--- a/test/go.mod
+++ b/test/go.mod
@@ -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.13.0
+	golang.org/x/net v0.12.0
 	google.golang.org/grpc v1.55.0
 	k8s.io/api v0.27.2
 	k8s.io/apiextensions-apiserver v0.27.2
diff --git a/test/go.sum b/test/go.sum
index b8b2b5b4c..5c165e3a4 100644
--- a/test/go.sum
+++ b/test/go.sum
@@ -681,8 +681,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
 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.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY=
-golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
+golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
+golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
 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=
diff --git a/test/infrastructure/docker/main.go b/test/infrastructure/docker/main.go
index f87d8c777..9896dd04b 100644
--- a/test/infrastructure/docker/main.go
+++ b/test/infrastructure/docker/main.go
@@ -68,24 +68,25 @@ var (
 	controllerName = "cluster-api-docker-controller-manager"
 
 	// flags.
-	metricsBindAddr             string
-	enableLeaderElection        bool
-	leaderElectionLeaseDuration time.Duration
-	leaderElectionRenewDeadline time.Duration
-	leaderElectionRetryPeriod   time.Duration
-	watchNamespace              string
-	watchFilterValue            string
-	profilerAddress             string
-	enableContentionProfiling   bool
-	concurrency                 int
-	syncPeriod                  time.Duration
-	restConfigQPS               float32
-	restConfigBurst             int
-	webhookPort                 int
-	webhookCertDir              string
-	healthAddr                  string
-	tlsOptions                  = flags.TLSOptions{}
-	logOptions                  = logs.NewOptions()
+	metricsBindAddr                string
+	enableLeaderElection           bool
+	leaderElectionLeaseDuration    time.Duration
+	leaderElectionRenewDeadline    time.Duration
+	leaderElectionRetryPeriod      time.Duration
+	watchNamespace                 string
+	watchFilterValue               string
+	profilerAddress                string
+	enableContentionProfiling      bool
+	concurrency                    int
+	clusterCacheTrackerConcurrency int
+	syncPeriod                     time.Duration
+	restConfigQPS                  float32
+	restConfigBurst                int
+	webhookPort                    int
+	webhookCertDir                 string
+	healthAddr                     string
+	tlsOptions                     = flags.TLSOptions{}
+	logOptions                     = logs.NewOptions()
 )
 
 func init() {
@@ -135,6 +136,9 @@ func initFlags(fs *pflag.FlagSet) {
 	fs.IntVar(&concurrency, "concurrency", 10,
 		"The number of docker machines to process simultaneously")
 
+	fs.IntVar(&clusterCacheTrackerConcurrency, "clustercachetracker-concurrency", 10,
+		"Number of clusters to process simultaneously")
+
 	fs.DurationVar(&syncPeriod, "sync-period", 10*time.Minute,
 		"The minimum interval at which watched resources are reconciled (e.g. 15m)")
 
@@ -316,7 +320,7 @@ func setupReconcilers(ctx context.Context, mgr ctrl.Manager) {
 		Tracker:          tracker,
 		WatchFilterValue: watchFilterValue,
 	}).SetupWithManager(ctx, mgr, controller.Options{
-		MaxConcurrentReconciles: concurrency,
+		MaxConcurrentReconciles: clusterCacheTrackerConcurrency,
 	}); err != nil {
 		setupLog.Error(err, "unable to create controller", "controller", "ClusterCacheReconciler")
 		os.Exit(1)