@@ -74,8 +74,8 @@ func (r *orcImageReconciler) reconcileNormal(ctx context.Context, orcImage *orcv
7474 log := ctrl .LoggerFrom (ctx )
7575 log .V (3 ).Info ("Reconciling image" )
7676
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 )
7979 }
8080
8181 var statusOpts []updateStatusOpt
@@ -90,41 +90,81 @@ func (r *orcImageReconciler) reconcileNormal(ctx context.Context, orcImage *orcv
9090 }
9191
9292 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+ }
9399 }()
94100
95101 imageClient , err := r .getImageClient (ctx , orcImage )
96102 if err != nil {
97103 return ctrl.Result {}, err
98104 }
99105
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.
106135 }
107- return ctrl.Result {}, err
108- }
109136
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" )
113138
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
117144 }
118145
119146 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+
120154 glanceImage , err = createImage (ctx , orcImage , imageClient )
121155 if err != nil {
122156 return ctrl.Result {}, err
123157 }
124158 }
125159 addStatus (withGlanceImage (glanceImage ))
126160
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 )
128168 ctx = ctrl .LoggerInto (ctx , log )
129169
130170 log .V (4 ).Info ("Got glance image" , "status" , glanceImage .Status )
@@ -143,8 +183,8 @@ func (r *orcImageReconciler) reconcileNormal(ctx context.Context, orcImage *orcv
143183
144184 // Newly created image, waiting for upload, or... previous upload was interrupted and has now reset
145185 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 {
148188 addStatus (withProgressMessage ("Waiting for glance image content to be uploaded externally" ))
149189
150190 return ctrl.Result {
@@ -213,7 +253,14 @@ func (r *orcImageReconciler) reconcileDelete(ctx context.Context, orcImage *orcv
213253 }
214254 }()
215255
216- if orcImage .GetControllerOptions ().GetOnDelete () == orcv1alpha1 .ControllerOptionsOnDeleteDelete {
256+ // We won't delete the resource for an unmanaged object, or if onDelete is detach
257+ if orcImage .Spec .ManagementPolicy == orcv1alpha1 .ManagementPolicyUnmanaged || orcImage .Spec .ManagedOptions .GetOnDelete () == orcv1alpha1 .OnDeleteDetach {
258+ logPolicy := []any {"managementPolicy" , orcImage .Spec .ManagementPolicy }
259+ if orcImage .Spec .ManagementPolicy == orcv1alpha1 .ManagementPolicyManaged {
260+ logPolicy = append (logPolicy , "onDelete" , orcImage .Spec .ManagedOptions .GetOnDelete ())
261+ }
262+ log .V (4 ).Info ("Not deleting Glance image due to policy" , logPolicy ... )
263+ } else {
217264 imageClient , err := r .getImageClient (ctx , orcImage )
218265 if err != nil {
219266 return ctrl.Result {}, err
@@ -235,14 +282,15 @@ func (r *orcImageReconciler) reconcileDelete(ctx context.Context, orcImage *orcv
235282 }
236283 return ctrl.Result {RequeueAfter : 5 * time .Second }, nil
237284 }
285+
286+ log .V (4 ).Info ("Image is deleted" )
238287 }
239288
240289 deleted = true
241- log .V (4 ).Info ("Image is deleted" )
242290
243- // Clear owned fields on the base resource, including the finalizer
291+ // Clear the finalizer
244292 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 ))
246294}
247295
248296// 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
251299func getGlanceImage (ctx context.Context , orcImage * orcv1alpha1.Image , imageClient clients.ImageClient ) (* images.Image , error ) {
252300 log := ctrl .LoggerFrom (ctx )
253301
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-
260302 log .V (4 ).Info ("Looking for existing glance image to adopt" )
261303
262304 // 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
279321
280322// getImageName returns the name of the glance image we should use.
281323func 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
284343 }
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 )))
286356}
287357
288358// 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
306376 field := st .Field (i )
307377 glanceTag , ok := field .Tag .Lookup (orcv1alpha1 .GlanceTag )
308378 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 ) )
310380 }
311381
312382 value := s .Field (i )
@@ -328,23 +398,35 @@ func glancePropertiesFromStruct(propStruct interface{}, properties map[string]st
328398
329399// createImage creates a glance image for an ORC Image.
330400func 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+
331406 log := ctrl .LoggerFrom (ctx )
332407 log .V (3 ).Info ("Creating image" )
333408
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 {
335417 // 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" )
337419 }
338420
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 ])
342424 }
343425 // Sort tags before creation to simplify comparisons
344426 slices .Sort (tags )
345427
346428 var minDisk , minMemory int
347- properties := orcImage . Spec .Properties
429+ properties := resource .Properties
348430 additionalProperties := map [string ]string {}
349431 if properties != nil {
350432 if properties .MinDiskGB != nil {
@@ -360,19 +442,19 @@ func createImage(ctx context.Context, orcImage *orcv1alpha1.Image, imageClient c
360442 }
361443
362444 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 ))
365447 }
366448
367449 image , err := imageClient .CreateImage (ctx , & images.CreateOpts {
368450 Name : getImageName (orcImage ),
369451 Visibility : visibility ,
370452 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 ),
373455 MinDisk : minDisk ,
374456 MinRAM : minMemory ,
375- Protected : orcImage . Spec .Protected ,
457+ Protected : resource .Protected ,
376458 Properties : additionalProperties ,
377459 })
378460
0 commit comments