@@ -74,8 +74,8 @@ func (r *orcImageReconciler) reconcileNormal(ctx context.Context, orcImage *orcv
74
74
log := ctrl .LoggerFrom (ctx )
75
75
log .V (3 ).Info ("Reconciling image" )
76
76
77
- if ! controllerutil .ContainsFinalizer (orcImage , orcv1alpha1 . ImageControllerFinalizer ) {
78
- return ctrl.Result {}, r .updateObject (ctx , orcImage )
77
+ if ! controllerutil .ContainsFinalizer (orcImage , Finalizer ) {
78
+ return ctrl.Result {}, r .setFinalizer (ctx , orcImage )
79
79
}
80
80
81
81
var statusOpts []updateStatusOpt
@@ -90,41 +90,81 @@ func (r *orcImageReconciler) reconcileNormal(ctx context.Context, orcImage *orcv
90
90
}
91
91
92
92
err = errors .Join (err , r .updateStatus (ctx , orcImage , statusOpts ... ))
93
+
94
+ var terminalError * capoerrors.TerminalError
95
+ if errors .As (err , & terminalError ) {
96
+ log .Error (err , "not scheduling further reconciles for terminal error" )
97
+ err = nil
98
+ }
93
99
}()
94
100
95
101
imageClient , err := r .getImageClient (ctx , orcImage )
96
102
if err != nil {
97
103
return ctrl.Result {}, err
98
104
}
99
105
100
- var glanceImage * images.Image
101
- glanceImage , err = getGlanceImage (ctx , orcImage , imageClient )
102
- if err != nil {
103
- if capoerrors .IsNotFound (err ) {
104
- // An image we previously created has been deleted unexpected. We can't recover from this.
105
- err = capoerrors .Terminal (orcv1alpha1 .OpenStackConditionReasonUnrecoverableError , "image has been deleted from glance" )
106
+ glanceImage , err := func () (* images.Image , error ) {
107
+ if orcImage .Status .ID != nil {
108
+ log .V (4 ).Info ("Fetching existing glance image" , "ID" , * orcImage .Status .ID )
109
+
110
+ image , err := imageClient .GetImage (* orcImage .Status .ID )
111
+ if capoerrors .IsNotFound (err ) {
112
+ // An image we previously referenced has been deleted unexpectedly. We can't recover from this.
113
+ err = capoerrors .Terminal (orcv1alpha1 .OpenStackConditionReasonUnrecoverableError , "image has been deleted from glance" )
114
+ }
115
+ return image , err
116
+ }
117
+
118
+ if orcImage .Spec .Import != nil {
119
+ log .V (4 ).Info ("Importing existing glance image" )
120
+
121
+ if orcImage .Spec .Import .ID != nil {
122
+ image , err := imageClient .GetImage (* orcImage .Spec .Import .ID )
123
+ if capoerrors .IsNotFound (err ) {
124
+ // We assume that an image imported by ID must already exist. It's a terminal error if it doesn't.
125
+ err = capoerrors .Terminal (orcv1alpha1 .OpenStackConditionReasonUnrecoverableError , "referenced image does not exist in glance" )
126
+ }
127
+ return image , err
128
+ }
129
+
130
+ listOpts := listOptsFromImportFilter (orcImage .Spec .Import .Filter )
131
+ return getGlanceImageFromList (ctx , listOpts , imageClient )
132
+
133
+ // TODO: When we support 'import and manage' we need to implement
134
+ // setting spec.resource from the discovered glance image here.
106
135
}
107
- return ctrl.Result {}, err
108
- }
109
136
110
- if orcImage .GetControllerOptions ().GetOnCreate () == orcv1alpha1 .ControllerOptionsOnCreateAdopt && glanceImage == nil {
111
- log .V (3 ).Info ("Image does not yet exist" , "onCreate" , orcv1alpha1 .ControllerOptionsOnCreateAdopt )
112
- addStatus (withProgressMessage ("Waiting for glance image to be created externally" ))
137
+ log .V (4 ).Info ("Checking for previously created image" )
113
138
114
- return ctrl.Result {
115
- RequeueAfter : waitForGlanceImageStatusUpdate ,
116
- }, err
139
+ listOpts := listOptsFromCreation (orcImage )
140
+ return getGlanceImageFromList (ctx , listOpts , imageClient )
141
+ }()
142
+ if err != nil {
143
+ return ctrl.Result {}, err
117
144
}
118
145
119
146
if glanceImage == nil {
147
+ if orcImage .Spec .Import != nil {
148
+ log .V (3 ).Info ("Image does not yet exist" )
149
+ addStatus (withProgressMessage ("Waiting for glance image to be created externally" ))
150
+
151
+ return ctrl.Result {RequeueAfter : waitForGlanceImageStatusUpdate }, err
152
+ }
153
+
120
154
glanceImage , err = createImage (ctx , orcImage , imageClient )
121
155
if err != nil {
122
156
return ctrl.Result {}, err
123
157
}
124
158
}
125
159
addStatus (withGlanceImage (glanceImage ))
126
160
127
- log = log .WithValues ("imageID" , glanceImage .ID )
161
+ if orcImage .Status .ID == nil {
162
+ if err := r .setStatusID (ctx , orcImage , glanceImage .ID ); err != nil {
163
+ return ctrl.Result {}, err
164
+ }
165
+ }
166
+
167
+ log = log .WithValues ("ID" , glanceImage .ID )
128
168
ctx = ctrl .LoggerInto (ctx , log )
129
169
130
170
log .V (4 ).Info ("Got glance image" , "status" , glanceImage .Status )
@@ -143,8 +183,8 @@ func (r *orcImageReconciler) reconcileNormal(ctx context.Context, orcImage *orcv
143
183
144
184
// Newly created image, waiting for upload, or... previous upload was interrupted and has now reset
145
185
case images .ImageStatusQueued :
146
- // Don't attempt image creation if we're only adopting
147
- if orcImage .GetControllerOptions (). GetOnCreate () == orcv1alpha1 .ControllerOptionsOnCreateAdopt {
186
+ // Don't attempt image creation if we're not managing the image
187
+ if orcImage .Spec . ManagementPolicy == orcv1alpha1 .ManagementPolicyUnmanaged {
148
188
addStatus (withProgressMessage ("Waiting for glance image content to be uploaded externally" ))
149
189
150
190
return ctrl.Result {
@@ -213,7 +253,14 @@ func (r *orcImageReconciler) reconcileDelete(ctx context.Context, orcImage *orcv
213
253
}
214
254
}()
215
255
216
- if orcImage .GetControllerOptions ().GetOnDelete () == orcv1alpha1 .ControllerOptionsOnDeleteDelete {
256
+ // We won't delete the resource for an unmanaged object, or if deletePolicy is detach
257
+ if orcImage .Spec .ManagementPolicy == orcv1alpha1 .ManagementPolicyUnmanaged || orcImage .Spec .ManagedOptions .GetDeletePolicy () == orcv1alpha1 .DeletePolicyDetach {
258
+ logPolicy := []any {"managementPolicy" , orcImage .Spec .ManagementPolicy }
259
+ if orcImage .Spec .ManagementPolicy == orcv1alpha1 .ManagementPolicyManaged {
260
+ logPolicy = append (logPolicy , "deletePolicy" , orcImage .Spec .ManagedOptions .GetDeletePolicy ())
261
+ }
262
+ log .V (4 ).Info ("Not deleting Glance image due to policy" , logPolicy ... )
263
+ } else {
217
264
imageClient , err := r .getImageClient (ctx , orcImage )
218
265
if err != nil {
219
266
return ctrl.Result {}, err
@@ -235,14 +282,15 @@ func (r *orcImageReconciler) reconcileDelete(ctx context.Context, orcImage *orcv
235
282
}
236
283
return ctrl.Result {RequeueAfter : 5 * time .Second }, nil
237
284
}
285
+
286
+ log .V (4 ).Info ("Image is deleted" )
238
287
}
239
288
240
289
deleted = true
241
- log .V (4 ).Info ("Image is deleted" )
242
290
243
- // Clear owned fields on the base resource, including the finalizer
291
+ // Clear the finalizer
244
292
applyConfig := orcapplyconfigv1alpha1 .Image (orcImage .Name , orcImage .Namespace ).WithUID (orcImage .UID )
245
- return ctrl.Result {}, r .client .Patch (ctx , orcImage , ssa .ApplyConfigPatch (applyConfig ), client .ForceOwnership , client . FieldOwner ( orcv1alpha1 . ImageControllerFieldOwner ))
293
+ return ctrl.Result {}, r .client .Patch (ctx , orcImage , ssa .ApplyConfigPatch (applyConfig ), client .ForceOwnership , ssaFieldOwner ( SSAFinalizerTxn ))
246
294
}
247
295
248
296
// getGlanceImage returns the glance image associated with an ORC Image, or nil if none was found.
@@ -251,12 +299,6 @@ func (r *orcImageReconciler) reconcileDelete(ctx context.Context, orcImage *orcv
251
299
func getGlanceImage (ctx context.Context , orcImage * orcv1alpha1.Image , imageClient clients.ImageClient ) (* images.Image , error ) {
252
300
log := ctrl .LoggerFrom (ctx )
253
301
254
- if orcImage .Status .ImageID != nil {
255
- log .V (4 ).Info ("Fetching existing glance image" , "imageID" , * orcImage .Status .ImageID )
256
-
257
- return imageClient .GetImage (* orcImage .Status .ImageID )
258
- }
259
-
260
302
log .V (4 ).Info ("Looking for existing glance image to adopt" )
261
303
262
304
// Check for existing image by name in case we're adopting or failed to write to status
@@ -279,10 +321,38 @@ func getGlanceImage(ctx context.Context, orcImage *orcv1alpha1.Image, imageClien
279
321
280
322
// getImageName returns the name of the glance image we should use.
281
323
func getImageName (orcImage * orcv1alpha1.Image ) string {
282
- if orcImage .Spec .ImageName != nil {
283
- return * orcImage .Spec .ImageName
324
+ return ptr .Deref (orcImage .Spec .Resource .Name , orcImage .Name )
325
+ }
326
+
327
+ func listOptsFromImportFilter (filter * orcv1alpha1.ImageFilter ) images.ListOptsBuilder {
328
+ return images.ListOpts {Name : ptr .Deref (filter .Name , "" )}
329
+ }
330
+
331
+ // listOptsFromCreation returns a listOpts which will return the image which
332
+ // would have been created from the current spec and hopefully no other image.
333
+ // Its purpose is to automatically adopt an image that we created but failed to
334
+ // write to status.id.
335
+ func listOptsFromCreation (orcImage * orcv1alpha1.Image ) images.ListOptsBuilder {
336
+ return images.ListOpts {Name : getImageName (orcImage )}
337
+ }
338
+
339
+ func getGlanceImageFromList (_ context.Context , listOpts images.ListOptsBuilder , imageClient clients.ImageClient ) (* images.Image , error ) {
340
+ glanceImages , err := imageClient .ListImages (listOpts )
341
+ if err != nil {
342
+ return nil , err
284
343
}
285
- return orcImage .Name
344
+
345
+ if len (glanceImages ) == 1 {
346
+ return & glanceImages [0 ], nil
347
+ }
348
+
349
+ // No image found
350
+ if len (glanceImages ) == 0 {
351
+ return nil , nil
352
+ }
353
+
354
+ // Multiple images found
355
+ return nil , capoerrors .Terminal (orcv1alpha1 .OpenStackConditionReasonInvalidConfiguration , fmt .Sprintf ("Expected to find exactly one image to import. Found %d" , len (glanceImages )))
286
356
}
287
357
288
358
// glancePropertiesFromStruct populates a properties struct using field values and glance tags defined on the given struct
@@ -306,7 +376,7 @@ func glancePropertiesFromStruct(propStruct interface{}, properties map[string]st
306
376
field := st .Field (i )
307
377
glanceTag , ok := field .Tag .Lookup (orcv1alpha1 .GlanceTag )
308
378
if ! ok {
309
- return fmt .Errorf ("glance tag not defined for field %s on struct %T" , field .Name , st .Name )
379
+ panic ( fmt .Errorf ("glance tag not defined for field %s on struct %T" , field .Name , st .Name ) )
310
380
}
311
381
312
382
value := s .Field (i )
@@ -328,23 +398,35 @@ func glancePropertiesFromStruct(propStruct interface{}, properties map[string]st
328
398
329
399
// createImage creates a glance image for an ORC Image.
330
400
func createImage (ctx context.Context , orcImage * orcv1alpha1.Image , imageClient clients.ImageClient ) (* images.Image , error ) {
401
+ if orcImage .Spec .ManagementPolicy == orcv1alpha1 .ManagementPolicyUnmanaged {
402
+ // Should have been caught by API validation
403
+ return nil , capoerrors .Terminal (orcv1alpha1 .OpenStackConditionReasonInvalidConfiguration , "Not creating unmanaged resource" )
404
+ }
405
+
331
406
log := ctrl .LoggerFrom (ctx )
332
407
log .V (3 ).Info ("Creating image" )
333
408
334
- if orcImage .Spec .Content == nil {
409
+ resource := orcImage .Spec .Resource
410
+
411
+ if resource == nil {
412
+ // Should have been caught by API validation
413
+ return nil , capoerrors .Terminal (orcv1alpha1 .OpenStackConditionReasonInvalidConfiguration , "Creation requested, but spec.resource is not set" )
414
+ }
415
+
416
+ if resource .Content == nil {
335
417
// Should have been caught by API validation
336
- return nil , capoerrors .Terminal (orcv1alpha1 .OpenStackConditionReasonInvalidConfiguration , "Creation requested, but spec.content is not set" )
418
+ return nil , capoerrors .Terminal (orcv1alpha1 .OpenStackConditionReasonInvalidConfiguration , "Creation requested, but spec.resource. content is not set" )
337
419
}
338
420
339
- tags := make ([]string , len (orcImage . Spec .Tags ))
340
- for i := range orcImage . Spec .Tags {
341
- tags [i ] = string (orcImage . Spec .Tags [i ])
421
+ tags := make ([]string , len (resource .Tags ))
422
+ for i := range resource .Tags {
423
+ tags [i ] = string (resource .Tags [i ])
342
424
}
343
425
// Sort tags before creation to simplify comparisons
344
426
slices .Sort (tags )
345
427
346
428
var minDisk , minMemory int
347
- properties := orcImage . Spec .Properties
429
+ properties := resource .Properties
348
430
additionalProperties := map [string ]string {}
349
431
if properties != nil {
350
432
if properties .MinDiskGB != nil {
@@ -360,19 +442,19 @@ func createImage(ctx context.Context, orcImage *orcv1alpha1.Image, imageClient c
360
442
}
361
443
362
444
var visibility * images.ImageVisibility
363
- if orcImage . Spec .Visibility != nil {
364
- visibility = ptr .To (images .ImageVisibility (* orcImage . Spec .Visibility ))
445
+ if resource .Visibility != nil {
446
+ visibility = ptr .To (images .ImageVisibility (* resource .Visibility ))
365
447
}
366
448
367
449
image , err := imageClient .CreateImage (ctx , & images.CreateOpts {
368
450
Name : getImageName (orcImage ),
369
451
Visibility : visibility ,
370
452
Tags : tags ,
371
- ContainerFormat : string (orcImage . Spec .Content .ContainerFormat ),
372
- DiskFormat : (string )(orcImage . Spec .Content .DiskFormat ),
453
+ ContainerFormat : string (resource .Content .ContainerFormat ),
454
+ DiskFormat : (string )(resource .Content .DiskFormat ),
373
455
MinDisk : minDisk ,
374
456
MinRAM : minMemory ,
375
- Protected : orcImage . Spec .Protected ,
457
+ Protected : resource .Protected ,
376
458
Properties : additionalProperties ,
377
459
})
378
460
0 commit comments