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

Issue:
E2E: Use kind cluster for clusterctl upgrade tests
Traditionally, the clusterctl upgrade tests created a workload cluster to be used as secondary management cluster, with the old versions deployed. From that cluster, another workload test cluster was created to check that the old versions worked, before upgrading and finally verifying that the upgraded versions worked.
Creating two workload clusters in this way is quite resource intensive compared to how all the other tests use kind  for management and only create a single test cluster. 

CAPI has now [made it possible](https://github.com/kubernetes-sigs/cluster-api/issues/7613) to use kind also for the secondary management cluster! We should adopt this as it will lower the resource requirement and potentially allow us to dump parallelism to 2 for our optional tests.


Discussion:
@k8s-triage-robot: The Kubernetes project currently lacks enough contributors to adequately respond to all issues.

This bot triages un-triaged issues according to the following rules:
- After 90d of inactivity, `lifecycle/stale` is applied
- After 30d of inactivity since `lifecycle/stale` was applied, `lifecycle/rotten` is applied
- After 30d of inactivity since `lifecycle/rotten` was applied, the issue is closed

You can:
- Mark this issue as fresh with `/remove-lifecycle stale`
- Close this issue with `/close`
- Offer to help out with [Issue Triage][1]

Please send feedback to sig-contributor-experience at [kubernetes/community](https://github.com/kubernetes/community).

/lifecycle stale

[1]: https://www.kubernetes.dev/docs/guide/issue-triage/@EmilienM: /remove-lifecycle rotten@k8s-triage-robot: The Kubernetes project currently lacks enough active contributors to adequately respond to all issues.

This bot triages un-triaged issues according to the following rules:
- After 90d of inactivity, `lifecycle/stale` is applied
- After 30d of inactivity since `lifecycle/stale` was applied, `lifecycle/rotten` is applied
- After 30d of inactivity since `lifecycle/rotten` was applied, the issue is closed

You can:
- Mark this issue as fresh with `/remove-lifecycle rotten`
- Close this issue with `/close`
- Offer to help out with [Issue Triage][1]

Please send feedback to sig-contributor-experience at [kubernetes/community](https://github.com/kubernetes/community).

/lifecycle rotten

[1]: https://www.kubernetes.dev/docs/guide/issue-triage/@lentzi90: /remove-lifecycle rotten

I'm back and hoping to work on this soonish@k8s-triage-robot: The Kubernetes project currently lacks enough contributors to adequately respond to all issues.

This bot triages un-triaged issues according to the following rules:
- After 90d of inactivity, `lifecycle/stale` is applied
- After 30d of inactivity since `lifecycle/stale` was applied, `lifecycle/rotten` is applied
- After 30d of inactivity since `lifecycle/rotten` was applied, the issue is closed

You can:
- Mark this issue as fresh with `/remove-lifecycle stale`
- Close this issue with `/close`
- Offer to help out with [Issue Triage][1]

Please send feedback to sig-contributor-experience at [kubernetes/community](https://github.com/kubernetes/community).

/lifecycle stale

[1]: https://www.kubernetes.dev/docs/guide/issue-triage/@EmilienM: /remove-lifecycle rotten@lentzi90: /remove-lifecycle stale
Soon ™ @k8s-triage-robot: The Kubernetes project currently lacks enough contributors to adequately respond to all issues.

This bot triages un-triaged issues according to the following rules:
- After 90d of inactivity, `lifecycle/stale` is applied
- After 30d of inactivity since `lifecycle/stale` was applied, `lifecycle/rotten` is applied
- After 30d of inactivity since `lifecycle/rotten` was applied, the issue is closed

You can:
- Mark this issue as fresh with `/remove-lifecycle stale`
- Close this issue with `/close`
- Offer to help out with [Issue Triage][1]

Please send feedback to sig-contributor-experience at [kubernetes/community](https://github.com/kubernetes/community).

/lifecycle stale

[1]: https://www.kubernetes.dev/docs/guide/issue-triage/@lentzi90: /remove-lifecycle stale@k8s-triage-robot: The Kubernetes project currently lacks enough contributors to adequately respond to all issues.

This bot triages un-triaged issues according to the following rules:
- After 90d of inactivity, `lifecycle/stale` is applied
- After 30d of inactivity since `lifecycle/stale` was applied, `lifecycle/rotten` is applied
- After 30d of inactivity since `lifecycle/rotten` was applied, the issue is closed

You can:
- Mark this issue as fresh with `/remove-lifecycle stale`
- Close this issue with `/close`
- Offer to help out with [Issue Triage][1]

Please send feedback to sig-contributor-experience at [kubernetes/community](https://github.com/kubernetes/community).

/lifecycle stale

[1]: https://www.kubernetes.dev/docs/guide/issue-triage/@lentzi90: /remove-lifecycle stale

Solution patch:
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 6f6043ec..c3ba8214 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -1,44 +1,118 @@
-name: release
+# This code is borrowed from https://github.com/kubernetes-sigs/cluster-api/blob/main/.github/workflows/release.yaml
+name: Create Release
 
 on:
   push:
-    # Sequence of patterns matched against refs/tags
-    tags:
-    - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
+    branches:
+    - main
+    paths:
+    - 'releasenotes/*.md'
 
-permissions:
-  contents: write # Allow to create a release.
+permissions: {}
 
 jobs:
-  build:
+  push_release_tags:
+    permissions:
+      contents: write # Allow to push tags
+    runs-on: ubuntu-latest
+    outputs:
+      release_tag: ${{ steps.release-version.outputs.release_version }}
+    if: github.repository == 'kubernetes-sigs/cluster-api-provider-openstack'
+    steps:
+    - name: Checkout code
+      uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # tag=v5.0.0
+      with:
+        fetch-depth: 0
+    - name: Get changed files
+      id: changed-files
+      uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v46.0.5
+    - name: Get release version
+      id: release-version
+      run: |
+        if [[ ${{ steps.changed-files.outputs.all_changed_files_count }} != 1 ]]; then
+          echo "1 release notes file should be changed to create a release tag, found ${{ steps.changed-files.outputs.all_changed_files_count }}"
+          exit 1
+        fi
+        for changed_file in ${{ steps.changed-files.outputs.all_changed_files }}; do
+          export RELEASE_VERSION=$(echo "${changed_file}" | grep -oP '(?<=/)[^/]+(?=\.md)')
+          echo "RELEASE_VERSION=${RELEASE_VERSION}" >> ${GITHUB_ENV}
+          echo "RELEASE_VERSION=${RELEASE_VERSION}" >> ${GITHUB_OUTPUT}
+          if [[ "${RELEASE_VERSION}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then
+            echo "Valid semver: ${RELEASE_VERSION}"
+          else
+            echo "Invalid semver: ${RELEASE_VERSION}"
+            exit 1
+          fi
+        done
+    - name: Determine the release branch to use
+      run: |
+        if [[ ${RELEASE_VERSION} =~ beta ]] || [[ ${RELEASE_VERSION} =~ alpha ]]; then
+          export RELEASE_BRANCH=main
+          echo "RELEASE_BRANCH=${RELEASE_BRANCH}" >> ${GITHUB_ENV}
+          echo "This is a beta or alpha release, will use release branch ${RELEASE_BRANCH}"
+        else
+          export RELEASE_BRANCH=release-$(echo ${RELEASE_VERSION} | sed -E 's/^v([0-9]+)\.([0-9]+)\..*$/\1.\2/')
+          echo "RELEASE_BRANCH=${RELEASE_BRANCH}" >> ${GITHUB_ENV}
+          echo "This is not a beta or alpha release, will use release branch ${RELEASE_BRANCH}"
+        fi
+    - name: Create or checkout release branch
+      run: |
+        if git show-ref --verify --quiet "refs/remotes/origin/${RELEASE_BRANCH}"; then
+          echo "Branch ${RELEASE_BRANCH} already exists"
+          git checkout "${RELEASE_BRANCH}"
+        else
+          git checkout -b "${RELEASE_BRANCH}"
+          git push origin "${RELEASE_BRANCH}"
+          echo "Created branch ${RELEASE_BRANCH}"
+        fi
+    - name: Validate tag does not already exist
+      run: |
+        if [[ -n "$(git tag -l "${RELEASE_VERSION}")" ]]; then
+          echo "Tag ${RELEASE_VERSION} already exists, exiting"
+          exit 1
+        fi
+    - name: Create Release Tag
+      run: |
+        git config user.name "${GITHUB_ACTOR}"
+        git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
+        git tag -a ${RELEASE_VERSION} -m ${RELEASE_VERSION}
+        git push origin ${RELEASE_VERSION}
+        echo "Created tags ${RELEASE_VERSION}"
+  release:
     name: create draft release
     runs-on: ubuntu-latest
+    needs: push_release_tags
+    permissions:
+      contents: write # Allow to create a release.
     steps:
-      - name: Set env
-        run:  echo "RELEASE_TAG=${GITHUB_REF:10}" >> $GITHUB_ENV
-      - name: checkout code
-        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # tag=v5.0.0
-        with:
-          fetch-depth: 0
-      - name: Calculate go version
-        run: echo "go_version=$(make go-version)" >> $GITHUB_ENV
-      - name: Set up Go
-        uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # tag=v6.0.0
-        with:
-          go-version: ${{ env.go_version }}
-      - name: generate release artifacts
-        run: |
-          make release
-      - name: generate release notes
-        # Ignore failures for release-notes generation so they could still get
-        # generated manually before publishing.
-        run: |
-          make generate-release-notes || echo "Failed to generate release notes" >> _releasenotes/${{ env.RELEASE_TAG }}.md
-        env:
-          GH_TOKEN: ${{ github.token }}
-      - name: Release
-        uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # tag=v2.4.1
-        with:
-          draft: true
-          files: out/*
-          body_path: _releasenotes/${{ env.RELEASE_TAG }}.md
+    - name: Set env
+      run: echo "RELEASE_TAG=${RELEASE_TAG}" >> ${GITHUB_ENV}
+      env:
+        RELEASE_TAG: ${{needs.push_release_tags.outputs.release_tag}}
+    - name: checkout code
+      uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # tag=v5.0.0
+      with:
+        fetch-depth: 0
+        ref: ${{ env.RELEASE_TAG }}
+    - name: Calculate go version
+      run: echo "go_version=$(make go-version)" >> ${GITHUB_ENV}
+    - name: Set up Go
+      uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # tag=v6.0.0
+      with:
+        go-version: ${{ env.go_version }}
+    - name: generate release artifacts
+      run: |
+        make release
+      env:
+        GH_TOKEN: ${{ github.token }}
+    - name: get release notes
+      run: |
+        curl -L "https://raw.githubusercontent.com/${{ github.repository }}/main/releasenotes/${{ env.RELEASE_TAG }}.md" \
+        -o "${{ env.RELEASE_TAG }}.md"
+    - name: Release
+      uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # tag=v2.4.1
+      with:
+        draft: true
+        files: out/*
+        body_path: ${{ env.RELEASE_TAG }}.md
+        tag_name: ${{ env.RELEASE_TAG }}
diff --git a/Makefile b/Makefile
index 6ccff05a..3358f5d2 100644
--- a/Makefile
+++ b/Makefile
@@ -448,14 +448,13 @@ staging-manifests:
 ## --------------------------------------
 ##@ Release
 ## --------------------------------------
-
 ifneq (,$(findstring -,$(RELEASE_TAG)))
     PRE_RELEASE=true
 endif
 PREVIOUS_TAG ?= $(shell git tag -l | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$$" | sort -V | grep -B1 $(RELEASE_TAG) | head -n 1 2>/dev/null)
 ## set by Prow, ref name of the base branch, e.g., main
 RELEASE_DIR := out
-RELEASE_NOTES_DIR := _releasenotes
+RELEASE_NOTES_DIR := releasenotes
 
 .PHONY: $(RELEASE_DIR)
 $(RELEASE_DIR):
@@ -478,7 +477,7 @@ list-image:
 	gcloud container images list-tags $(STAGING_REGISTRY)/$(IMAGE) --filter="tags=('$(RELEASE_TAG)')" --format=json
 
 .PHONY: release
-release: $(RELEASE_NOTES) clean-release $(RELEASE_DIR)  ## Builds and push container images using the latest git tag for the commit.
+release: $(RELEASE_NOTES) $(RELEASE_DIR)  ## Builds and push container images using the latest git tag for the commit.
 	@if [ -z "${RELEASE_TAG}" ]; then echo "RELEASE_TAG is not set"; exit 1; fi
 	@if ! [ -z "$$(git status --porcelain)" ]; then echo "Your local git repository contains uncommitted changes, use git clean before proceeding."; fi
 	git checkout "${RELEASE_TAG}"
@@ -486,6 +485,7 @@ release: $(RELEASE_NOTES) clean-release $(RELEASE_DIR)  ## Builds and push conta
 	$(MAKE) manifest-modification REGISTRY=$(PROD_REGISTRY)
 	$(MAKE) release-manifests
 	$(MAKE) release-templates
+	$(MAKE) generate-release-notes
 
 .PHONY: manifest-modification
 manifest-modification: # Set the manifest images to the staging/production bucket.



Test patch:
diff --git a/test/e2e/data/e2e_conf.yaml b/test/e2e/data/e2e_conf.yaml
index 9914f0eb..6970e192 100644
--- a/test/e2e/data/e2e_conf.yaml
+++ b/test/e2e/data/e2e_conf.yaml
@@ -218,6 +218,7 @@ variables:
   CNI: "../../data/cni/calico.yaml"
   CCM: "../../data/ccm/cloud-controller-manager.yaml"
   EXP_CLUSTER_RESOURCE_SET: "true"
+  IP_FAMILY: "ipv4"
   OPENSTACK_BASTION_IMAGE_NAME: "cirros-0.6.1-x86_64-disk"
   OPENSTACK_BASTION_IMAGE_URL: https://storage.googleapis.com/artifacts.k8s-staging-capi-openstack.appspot.com/test/cirros/2022-12-05/cirros-0.6.1-x86_64-disk.img
   OPENSTACK_BASTION_IMAGE_HASH: 0c839612eb3f2469420f2ccae990827f
diff --git a/test/e2e/suites/e2e/clusterctl_upgrade_test.go b/test/e2e/suites/e2e/clusterctl_upgrade_test.go
index 1af541d4..30be6748 100644
--- a/test/e2e/suites/e2e/clusterctl_upgrade_test.go
+++ b/test/e2e/suites/e2e/clusterctl_upgrade_test.go
@@ -68,6 +68,7 @@ var _ = Describe("When testing clusterctl upgrades for CAPO (v0.11=>current) and
 			WorkloadFlavor:                    shared.FlavorCapiV1Beta1,
 			InitWithKubernetesVersion:         e2eCtx.E2EConfig.MustGetVariable(shared.KubernetesVersion),
 			InitWithRuntimeExtensionProviders: []string{"openstack-resource-controller:v1.0.2"},
+			UseKindForManagementCluster:       true,
 		}
 	})
 })
@@ -102,6 +103,7 @@ var _ = Describe("When testing clusterctl upgrades for CAPO (v0.12=>current) and
 			WorkloadFlavor:                    shared.FlavorCapiV1Beta1,
 			InitWithKubernetesVersion:         e2eCtx.E2EConfig.MustGetVariable(shared.KubernetesVersion),
 			InitWithRuntimeExtensionProviders: []string{"openstack-resource-controller:v1.0.2"},
+			UseKindForManagementCluster:       true,
 		}
 	})
 })
@@ -136,6 +138,7 @@ var _ = Describe("When testing clusterctl upgrades for CAPO (v0.13=>current) and
 			WorkloadFlavor:                    shared.FlavorDefault,
 			InitWithKubernetesVersion:         e2eCtx.E2EConfig.MustGetVariable(shared.KubernetesVersion),
 			InitWithRuntimeExtensionProviders: []string{"openstack-resource-controller:v1.0.2"},
+			UseKindForManagementCluster:       true,
 		}
 	})
 })