@@ -250,6 +250,39 @@ func (r *OpenStackReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
250250 return ctrl.Result {}, err
251251 }
252252
253+ // Check if OPENSTACK_RELEASE_VERSION has changed - if so, delete all owned resources
254+ // This is a one-time fix to handle incompatible upgrades
255+ if instance .Status .ReleaseVersion != nil && * instance .Status .ReleaseVersion != openstackReleaseVersion {
256+ Log .Info ("OpenStack release version changed, deleting all owned resources" ,
257+ "old" , * instance .Status .ReleaseVersion ,
258+ "new" , openstackReleaseVersion )
259+
260+ if err := r .deleteAllOwnedResources (ctx , instance ); err != nil {
261+ instance .Status .Conditions .Set (condition .FalseCondition (
262+ operatorv1beta1 .OpenStackOperatorReadyCondition ,
263+ condition .ErrorReason ,
264+ condition .SeverityWarning ,
265+ operatorv1beta1 .OpenStackOperatorErrorMessage ,
266+ err ))
267+ return ctrl.Result {}, err
268+ }
269+
270+ // Reset the container image status to force re-application of CRDs and RBAC
271+ instance .Status .ContainerImage = nil
272+
273+ // Update the release version in status
274+ instance .Status .ReleaseVersion = & openstackReleaseVersion
275+
276+ // Requeue to allow resources to be deleted before recreating
277+ Log .Info ("Resources deleted, requeuing to recreate with new version" )
278+ return ctrl.Result {RequeueAfter : time .Duration (5 ) * time .Second }, nil
279+ }
280+
281+ // Set the release version if not set
282+ if instance .Status .ReleaseVersion == nil {
283+ instance .Status .ReleaseVersion = & openstackReleaseVersion
284+ }
285+
253286 if err := r .applyManifests (ctx , instance ); err != nil {
254287 instance .Status .Conditions .Set (condition .FalseCondition (
255288 operatorv1beta1 .OpenStackOperatorReadyCondition ,
@@ -316,6 +349,91 @@ func (r *OpenStackReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
316349
317350}
318351
352+ func (r * OpenStackReconciler ) deleteAllOwnedResources (ctx context.Context , instance * operatorv1beta1.OpenStack ) error {
353+ Log := r .GetLogger (ctx )
354+ Log .Info ("Deleting all owned resources for release version upgrade" )
355+
356+ // Delete all owned deployments
357+ deployments := & appsv1.DeploymentList {}
358+ err := r .List (ctx , deployments , & client.ListOptions {Namespace : instance .Namespace })
359+ if err != nil {
360+ return errors .Wrap (err , "failed to list deployments" )
361+ }
362+ for _ , deployment := range deployments .Items {
363+ if metav1 .IsControlledBy (& deployment , instance ) {
364+ Log .Info ("Deleting deployment" , "name" , deployment .Name )
365+ err := r .Delete (ctx , & deployment )
366+ if err != nil && ! apierrors .IsNotFound (err ) {
367+ return errors .Wrapf (err , "failed to delete deployment %s" , deployment .Name )
368+ }
369+ }
370+ }
371+
372+ // Delete all owned service accounts
373+ serviceAccounts := & corev1.ServiceAccountList {}
374+ err = r .List (ctx , serviceAccounts , & client.ListOptions {Namespace : instance .Namespace })
375+ if err != nil {
376+ return errors .Wrap (err , "failed to list service accounts" )
377+ }
378+ for _ , sa := range serviceAccounts .Items {
379+ if metav1 .IsControlledBy (& sa , instance ) {
380+ Log .Info ("Deleting service account" , "name" , sa .Name )
381+ err := r .Delete (ctx , & sa )
382+ if err != nil && ! apierrors .IsNotFound (err ) {
383+ return errors .Wrapf (err , "failed to delete service account %s" , sa .Name )
384+ }
385+ }
386+ }
387+
388+ // Delete all owned services
389+ services := & corev1.ServiceList {}
390+ err = r .List (ctx , services , & client.ListOptions {Namespace : instance .Namespace })
391+ if err != nil {
392+ return errors .Wrap (err , "failed to list services" )
393+ }
394+ for _ , svc := range services .Items {
395+ if metav1 .IsControlledBy (& svc , instance ) {
396+ Log .Info ("Deleting service" , "name" , svc .Name )
397+ err := r .Delete (ctx , & svc )
398+ if err != nil && ! apierrors .IsNotFound (err ) {
399+ return errors .Wrapf (err , "failed to delete service %s" , svc .Name )
400+ }
401+ }
402+ }
403+
404+ // Delete webhooks (these are cluster-scoped and not owned, but managed by label)
405+ valWebhooks , err := r .Kclient .AdmissionregistrationV1 ().ValidatingWebhookConfigurations ().List (ctx , metav1.ListOptions {
406+ LabelSelector : "openstack.openstack.org/managed=true" ,
407+ })
408+ if err != nil {
409+ return errors .Wrap (err , "failed listing validating webhook configurations" )
410+ }
411+ for _ , webhook := range valWebhooks .Items {
412+ Log .Info ("Deleting validating webhook" , "name" , webhook .Name )
413+ err := r .Kclient .AdmissionregistrationV1 ().ValidatingWebhookConfigurations ().Delete (ctx , webhook .Name , metav1.DeleteOptions {})
414+ if err != nil && ! apierrors .IsNotFound (err ) {
415+ return errors .Wrapf (err , "failed to delete validating webhook %s" , webhook .Name )
416+ }
417+ }
418+
419+ mutWebhooks , err := r .Kclient .AdmissionregistrationV1 ().MutatingWebhookConfigurations ().List (ctx , metav1.ListOptions {
420+ LabelSelector : "openstack.openstack.org/managed=true" ,
421+ })
422+ if err != nil {
423+ return errors .Wrap (err , "failed listing mutating webhook configurations" )
424+ }
425+ for _ , webhook := range mutWebhooks .Items {
426+ Log .Info ("Deleting mutating webhook" , "name" , webhook .Name )
427+ err := r .Kclient .AdmissionregistrationV1 ().MutatingWebhookConfigurations ().Delete (ctx , webhook .Name , metav1.DeleteOptions {})
428+ if err != nil && ! apierrors .IsNotFound (err ) {
429+ return errors .Wrapf (err , "failed to delete mutating webhook %s" , webhook .Name )
430+ }
431+ }
432+
433+ Log .Info ("All owned resources deleted successfully" )
434+ return nil
435+ }
436+
319437func (r * OpenStackReconciler ) reconcileDelete (ctx context.Context , instance * operatorv1beta1.OpenStack , helper * helper.Helper ) (ctrl.Result , error ) {
320438 Log := r .GetLogger (ctx )
321439 Log .Info ("Reconciling OpenStack initialization resource delete" )
0 commit comments