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

Issue:
FlavorID exposed in OpenstackMachine but not propagated to OpenstackServer
/kind bug

**What steps did you take and what happened:**
I created an OpenstackMachine specifying FlavorID (instead of Flavor) which should be valid according to OpenstackMachineSpec CEL rule:
`// +kubebuilder:validation:XValidation:message="at least one of flavor or flavorID must be set",rule=(has(self.flavor) || has(self.flavorID))`

**What did you expect to happen:**
Machine reconciled successfully and created an OpenstackServer. Instead, machine controller did not try to set FlavorID (nor Flavor as it was empty) and Server validating webhook rejected the creation
```log
capo-controller-manager-5f8d7bd987-hvmnn manager E0516 16:19:14.533105       1 controller.go:316] "Reconciler error" err="failed to create machine server: OpenStackServer.infrastructure.cluster.x-k8s.io \"workload-openstack-01-jhl6h\" is invalid: spec: Invalid value: \"object\": at least one of flavor or flavorID must be set" controller="openstackmachine" controllerGroup="infrastructure.cluster.x-k8s.io" controllerKind="OpenStackMachine" OpenStackMachine="c-workload-openstack-01/workload-openstack-01-jhl6h" namespace="c-workload-openstack-01" name="workload-openstack-01-jhl6h" reconcileID="31564326-d26e-4c47-a617-a95bfedbce75"
```

**Anything else you would like to add:**
OpenstackMachineController should set 

```go
FlavorID:                          openStackMachineSpec.FlavorID,
```
 when creating OpenstackServer:
```go
// openStackMachineSpecToOpenStackServerSpec converts an OpenStackMachineSpec to an OpenStackServerSpec.
// It returns the OpenStackServerSpec object and an error if there is any.
func openStackMachineSpecToOpenStackServerSpec(openStackMachineSpec *infrav1.OpenStackMachineSpec, identityRef infrav1.OpenStackIdentityReference, tags []string, failureDomain string, userDataRef *corev1.LocalObjectReference, defaultSecGroup *string, defaultNetworkID string) *infrav1alpha1.OpenStackServerSpec {
	openStackServerSpec := &infrav1alpha1.OpenStackServerSpec{
		AdditionalBlockDevices:            openStackMachineSpec.AdditionalBlockDevices,
		ConfigDrive:                       openStackMachineSpec.ConfigDrive,
		Flavor:                            openStackMachineSpec.Flavor,
		IdentityRef:                       identityRef,
		Image:                             openStackMachineSpec.Image,
		RootVolume:                        openStackMachineSpec.RootVolume,
		ServerMetadata:                    openStackMachineSpec.ServerMetadata,
		SSHKeyName:                        openStackMachineSpec.SSHKeyName,
		ServerGroup:                       openStackMachineSpec.ServerGroup,
		SchedulerHintAdditionalProperties: openStackMachineSpec.SchedulerHintAdditionalProperties,
	}
```



**Environment:**

- Cluster API Provider OpenStack version (Or `git rev-parse HEAD` if manually built): `v0.12.2`
- Cluster-API version: `v1.9.3`
- OpenStack version: `N/A`
- Minikube/KIND version: `kind version 0.27.0`
- Kubernetes version (use `kubectl version`):  `v1.32.2`
- OS (e.g. from `/etc/os-release`): `N/A`


Discussion:
@lentzi90: /assign @SiiriKemppainen@k8s-ci-robot: @lentzi90: GitHub didn't allow me to assign the following users: SiiriKemppainen.

Note that only [kubernetes-sigs members](https://github.com/orgs/kubernetes-sigs/people) with read permissions, repo collaborators and people who have commented on this issue/PR can be assigned. Additionally, issues/PRs can only have 10 assignees at the same time.
For more information please see [the contributor guide](https://git.k8s.io/community/contributors/guide/first-contribution.md#issue-assignment-in-github)

<details>

In response to [this](https://github.com/kubernetes-sigs/cluster-api-provider-openstack/issues/2550#issuecomment-2903512774):

>/assign @SiiriKemppainen


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-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>@sirpaleet: /assign

Solution patch:
diff --git a/controllers/openstackmachine_controller.go b/controllers/openstackmachine_controller.go
index 5e70dc76..e3ac95ec 100644
--- a/controllers/openstackmachine_controller.go
+++ b/controllers/openstackmachine_controller.go
@@ -484,6 +484,7 @@ func openStackMachineSpecToOpenStackServerSpec(openStackMachineSpec *infrav1.Ope
 		AdditionalBlockDevices:            openStackMachineSpec.AdditionalBlockDevices,
 		ConfigDrive:                       openStackMachineSpec.ConfigDrive,
 		Flavor:                            openStackMachineSpec.Flavor,
+		FlavorID:                          openStackMachineSpec.FlavorID,
 		IdentityRef:                       identityRef,
 		Image:                             openStackMachineSpec.Image,
 		RootVolume:                        openStackMachineSpec.RootVolume,



Test patch:
diff --git a/controllers/openstackmachine_controller_test.go b/controllers/openstackmachine_controller_test.go
index 2cace254..e66e9dc2 100644
--- a/controllers/openstackmachine_controller_test.go
+++ b/controllers/openstackmachine_controller_test.go
@@ -117,7 +117,7 @@ func TestOpenStackMachineSpecToOpenStackServerSpec(t *testing.T) {
 			},
 		},
 		{
-			name: "Test a OpenStackMachineSpec to OpenStackServerSpec conversion with an additional security group",
+			name: "Test an OpenStackMachineSpec to OpenStackServerSpec conversion with an additional security group",
 			spec: &infrav1.OpenStackMachineSpec{
 				Flavor:     ptr.To(flavorName),
 				Image:      image,
@@ -138,6 +138,42 @@ func TestOpenStackMachineSpecToOpenStackServerSpec(t *testing.T) {
 				UserDataRef: userData,
 			},
 		},
+		{
+			name: "Test an OpenStackMachineSpec to OpenStackServerSpec conversion with flavor and flavorID specified",
+			spec: &infrav1.OpenStackMachineSpec{
+				Flavor:     ptr.To(flavorName),
+				FlavorID:   ptr.To(flavorUUID),
+				Image:      image,
+				SSHKeyName: sshKeyName,
+			},
+			want: &infrav1alpha1.OpenStackServerSpec{
+				Flavor:      ptr.To(flavorName),
+				FlavorID:    ptr.To(flavorUUID),
+				IdentityRef: identityRef,
+				Image:       image,
+				SSHKeyName:  sshKeyName,
+				Ports:       portOpts,
+				Tags:        tags,
+				UserDataRef: userData,
+			},
+		},
+		{
+			name: "Test an OpenStackMachineSpec to OpenStackServerSpec conversion with flavorID specified but not flavor",
+			spec: &infrav1.OpenStackMachineSpec{
+				FlavorID:   ptr.To(flavorUUID),
+				Image:      image,
+				SSHKeyName: sshKeyName,
+			},
+			want: &infrav1alpha1.OpenStackServerSpec{
+				FlavorID:    ptr.To(flavorUUID),
+				IdentityRef: identityRef,
+				Image:       image,
+				SSHKeyName:  sshKeyName,
+				Ports:       portOpts,
+				Tags:        tags,
+				UserDataRef: userData,
+			},
+		},
 	}
 	for i := range tests {
 		tt := tests[i]
diff --git a/test/e2e/data/ccm/cloud-controller-manager.yaml b/test/e2e/data/ccm/cloud-controller-manager.yaml
index 032a16cb..90ef0035 100644
--- a/test/e2e/data/ccm/cloud-controller-manager.yaml
+++ b/test/e2e/data/ccm/cloud-controller-manager.yaml
@@ -40,18 +40,23 @@ spec:
       serviceAccountName: cloud-controller-manager
       containers:
         - name: openstack-cloud-controller-manager
-          image: docker.io/k8scloudprovider/openstack-cloud-controller-manager:v1.23.1
+          image: >-
+            registry.k8s.io/provider-os/openstack-cloud-controller-manager:v1.32.0
           args:
             - /bin/openstack-cloud-controller-manager
             - --v=1
+            - --cluster-name=$(CLUSTER_NAME)
             - --cloud-config=$(CLOUD_CONFIG)
             - --cloud-provider=openstack
-            - --use-service-account-credentials=true
+            - --use-service-account-credentials=false
             - --bind-address=127.0.0.1
           volumeMounts:
             - mountPath: /etc/kubernetes
               name: k8s
               readOnly: true
+            - mountPath: /etc/kubernetes/pki
+              name: k8s-certs
+              readOnly: true
             - mountPath: /etc/ssl/certs
               name: ca-certs
               readOnly: true
@@ -61,18 +66,24 @@ spec:
           env:
             - name: CLOUD_CONFIG
               value: /etc/kubernetes/cloud.conf
+            - name: CLUSTER_NAME
+              value: kubernetes
       hostNetwork: true
       volumes:
         - hostPath:
             path: /etc/kubernetes
             type: DirectoryOrCreate
           name: k8s
+        - hostPath:
+            path: /etc/kubernetes/pki
+            type: DirectoryOrCreate
+          name: k8s-certs
         - hostPath:
             path: /etc/ssl/certs
             type: DirectoryOrCreate
           name: ca-certs
 ---
-# From: https://raw.githubusercontent.com/kubernetes/cloud-provider-openstack/master/cluster/addons/rbac/cloud-controller-manager-role-bindings.yaml
+# https://raw.githubusercontent.com/kubernetes/cloud-provider-openstack/refs/heads/master/manifests/controller-manager/cloud-controller-manager-role-bindings.yaml
 apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRoleBinding
 metadata:
@@ -99,7 +110,7 @@ subjects:
   name: cloud-controller-manager
   namespace: kube-system
 ---
-# From: https://raw.githubusercontent.com/kubernetes/cloud-provider-openstack/master/cluster/addons/rbac/cloud-controller-manager-roles.yaml
+# https://raw.githubusercontent.com/kubernetes/cloud-provider-openstack/refs/heads/master/manifests/controller-manager/cloud-controller-manager-roles.yaml
 apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
 metadata: