@@ -38,6 +38,7 @@ import (
3838 "sigs.k8s.io/controller-runtime/pkg/client"
3939 "sigs.k8s.io/controller-runtime/pkg/reconcile"
4040
41+ infrav1alpha1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha1"
4142 infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1"
4243 "sigs.k8s.io/cluster-api-provider-openstack/pkg/cloud/services/networking"
4344 "sigs.k8s.io/cluster-api-provider-openstack/pkg/scope"
@@ -147,6 +148,134 @@ var _ = Describe("OpenStackCluster controller", func() {
147148 framework .DeleteNamespace (ctx , input )
148149 })
149150
151+ It ("should create OpenStackClusterIdentity (CRD present)" , func () {
152+ err := k8sClient .Create (ctx , testCluster )
153+ Expect (err ).To (BeNil ())
154+ err = k8sClient .Create (ctx , capiCluster )
155+ Expect (err ).To (BeNil ())
156+
157+ id := & infrav1alpha1.OpenStackClusterIdentity {
158+ ObjectMeta : metav1.ObjectMeta {
159+ Name : fmt .Sprintf ("id-%d" , GinkgoRandomSeed ()),
160+ },
161+ Spec : infrav1alpha1.OpenStackClusterIdentitySpec {
162+ SecretRef : infrav1alpha1.OpenStackCredentialSecretReference {
163+ Name : "creds" ,
164+ Namespace : "capo-system" ,
165+ },
166+ },
167+ }
168+ Expect (k8sClient .Create (ctx , id )).To (Succeed ())
169+
170+ // Cleanup cluster-scoped resource since it won't be deleted with namespace
171+ DeferCleanup (func () {
172+ Expect (k8sClient .Delete (ctx , id )).To (Succeed ())
173+ })
174+ })
175+
176+ It ("should successfully create OpenStackCluster with valid identityRef" , func () {
177+ err := k8sClient .Create (ctx , testCluster )
178+ Expect (err ).To (BeNil ())
179+ err = k8sClient .Create (ctx , capiCluster )
180+ Expect (err ).To (BeNil ())
181+
182+ c := & infrav1.OpenStackCluster {
183+ ObjectMeta : metav1.ObjectMeta {
184+ Name : "cluster-valid-identity" ,
185+ Namespace : testNamespace ,
186+ },
187+ Spec : infrav1.OpenStackClusterSpec {
188+ IdentityRef : infrav1.OpenStackIdentityReference {
189+ Name : "creds" ,
190+ CloudName : "openstack" ,
191+ // Type should default to "Secret"
192+ },
193+ },
194+ }
195+ err = k8sClient .Create (ctx , c )
196+ Expect (err ).To (Succeed ())
197+
198+ // Verify the object was created and Type was defaulted
199+ created := & infrav1.OpenStackCluster {}
200+ err = k8sClient .Get (ctx , client.ObjectKey {Name : c .Name , Namespace : c .Namespace }, created )
201+ Expect (err ).To (Succeed ())
202+ Expect (created .Spec .IdentityRef .Type ).To (Equal ("Secret" ))
203+ Expect (created .Spec .IdentityRef .Name ).To (Equal ("creds" ))
204+ Expect (created .Spec .IdentityRef .CloudName ).To (Equal ("openstack" ))
205+ })
206+
207+ It ("should successfully create OpenStackCluster with ClusterIdentity type" , func () {
208+ err := k8sClient .Create (ctx , testCluster )
209+ Expect (err ).To (BeNil ())
210+ err = k8sClient .Create (ctx , capiCluster )
211+ Expect (err ).To (BeNil ())
212+
213+ c := & infrav1.OpenStackCluster {
214+ ObjectMeta : metav1.ObjectMeta {
215+ Name : "cluster-clusteridentity-type" ,
216+ Namespace : testNamespace ,
217+ },
218+ Spec : infrav1.OpenStackClusterSpec {
219+ IdentityRef : infrav1.OpenStackIdentityReference {
220+ Type : "ClusterIdentity" ,
221+ Name : "global-creds" ,
222+ CloudName : "openstack" ,
223+ Region : "RegionOne" ,
224+ },
225+ },
226+ }
227+ err = k8sClient .Create (ctx , c )
228+ Expect (err ).To (Succeed ())
229+
230+ // Verify all fields are preserved
231+ created := & infrav1.OpenStackCluster {}
232+ err = k8sClient .Get (ctx , client.ObjectKey {Name : c .Name , Namespace : c .Namespace }, created )
233+ Expect (err ).To (Succeed ())
234+ Expect (created .Spec .IdentityRef .Type ).To (Equal ("ClusterIdentity" ))
235+ Expect (created .Spec .IdentityRef .Name ).To (Equal ("global-creds" ))
236+ Expect (created .Spec .IdentityRef .CloudName ).To (Equal ("openstack" ))
237+ Expect (created .Spec .IdentityRef .Region ).To (Equal ("RegionOne" ))
238+ })
239+
240+ It ("should accept cluster and default identityRef.type to Secret" , func () {
241+ testCluster .Spec = infrav1.OpenStackClusterSpec {
242+ IdentityRef : infrav1.OpenStackIdentityReference {
243+ Name : "creds" ,
244+ CloudName : "openstack" ,
245+ // Type omitted -> should default to Secret
246+ },
247+ }
248+ err := k8sClient .Create (ctx , testCluster )
249+ Expect (err ).To (BeNil ())
250+ err = k8sClient .Create (ctx , capiCluster )
251+ Expect (err ).To (BeNil ())
252+
253+ fetched := & infrav1.OpenStackCluster {}
254+ Expect (k8sClient .Get (ctx , types.NamespacedName {Name : testClusterName , Namespace : testNamespace }, fetched )).To (Succeed ())
255+ Expect (fetched .Spec .IdentityRef .Type ).To (Equal ("Secret" ))
256+ })
257+
258+ It ("should reject updates that modify identityRef.region (immutable)" , func () {
259+ testCluster .Spec = infrav1.OpenStackClusterSpec {
260+ IdentityRef : infrav1.OpenStackIdentityReference {
261+ Type : "Secret" ,
262+ Name : "creds" ,
263+ CloudName : "openstack" ,
264+ Region : "RegionOne" ,
265+ },
266+ }
267+ err := k8sClient .Create (ctx , testCluster )
268+ Expect (err ).To (BeNil ())
269+ err = k8sClient .Create (ctx , capiCluster )
270+ Expect (err ).To (BeNil ())
271+
272+ // Try to update region
273+ fetched := & infrav1.OpenStackCluster {}
274+ Expect (k8sClient .Get (ctx , types.NamespacedName {Name : testClusterName , Namespace : testNamespace }, fetched )).To (Succeed ())
275+ fetched .Spec .IdentityRef .Region = "RegionTwo"
276+ Expect (k8sClient .Update (ctx , fetched )).ToNot (Succeed ())
277+ })
278+
150279 It ("should do nothing when owner is missing" , func () {
151280 testCluster .SetName ("missing-owner" )
152281 testCluster .SetOwnerReferences ([]metav1.OwnerReference {})
0 commit comments