@@ -43,6 +43,7 @@ import (
4343 "github.com/openstack-k8s-operators/openstack-operator/internal/operator"
4444 "github.com/openstack-k8s-operators/openstack-operator/internal/operator/bindata"
4545 "github.com/pkg/errors"
46+ admissionv1 "k8s.io/api/admissionregistration/v1"
4647 appsv1 "k8s.io/api/apps/v1"
4748 corev1 "k8s.io/api/core/v1"
4849 discoveryv1 "k8s.io/api/discovery/v1"
@@ -250,6 +251,48 @@ func (r *OpenStackReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
250251 return ctrl.Result {}, err
251252 }
252253
254+ // Check if OPENSTACK_RELEASE_VERSION has changed - if so, delete all owned resources
255+ // This is a one-time fix to handle incompatible upgrades
256+ shouldReinstall := false
257+ if instance .Status .ReleaseVersion != nil && * instance .Status .ReleaseVersion != openstackReleaseVersion {
258+ // Explicit version change detected
259+ shouldReinstall = true
260+ } else if ! isNewInstance && instance .Status .ReleaseVersion == nil {
261+ // Existing installation upgrading from version without ReleaseVersion field
262+ shouldReinstall = true
263+ }
264+
265+ if shouldReinstall {
266+ Log .Info ("OpenStack release version changed, deleting all owned resources" ,
267+ "old" , instance .Status .ReleaseVersion ,
268+ "new" , openstackReleaseVersion )
269+
270+ if err := r .deleteAllOwnedResources (ctx , instance ); err != nil {
271+ instance .Status .Conditions .Set (condition .FalseCondition (
272+ operatorv1beta1 .OpenStackOperatorReadyCondition ,
273+ condition .ErrorReason ,
274+ condition .SeverityWarning ,
275+ operatorv1beta1 .OpenStackOperatorErrorMessage ,
276+ err ))
277+ return ctrl.Result {}, err
278+ }
279+
280+ // Reset the container image status to force re-application of CRDs and RBAC
281+ instance .Status .ContainerImage = nil
282+
283+ // Update the release version in status
284+ instance .Status .ReleaseVersion = & openstackReleaseVersion
285+
286+ // Requeue to allow resources to be deleted before recreating
287+ Log .Info ("Resources deleted, requeuing to recreate with new version" )
288+ return ctrl.Result {RequeueAfter : time .Duration (5 ) * time .Second }, nil
289+ }
290+
291+ // Set the release version if not set (for new installations only)
292+ if instance .Status .ReleaseVersion == nil {
293+ instance .Status .ReleaseVersion = & openstackReleaseVersion
294+ }
295+
253296 if err := r .applyManifests (ctx , instance ); err != nil {
254297 instance .Status .Conditions .Set (condition .FalseCondition (
255298 operatorv1beta1 .OpenStackOperatorReadyCondition ,
@@ -316,6 +359,69 @@ func (r *OpenStackReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
316359
317360}
318361
362+ func deleteOwnedResources [L client.ObjectList , T any ](
363+ ctx context.Context ,
364+ r * OpenStackReconciler ,
365+ instance client.Object ,
366+ list L ,
367+ itemsGetter func (L ) []T ,
368+ ) error {
369+ log := r .GetLogger (ctx )
370+
371+ err := r .List (ctx , any (list ).(client.ObjectList ), & client.ListOptions {Namespace : instance .GetNamespace ()})
372+ if err != nil {
373+ return errors .Wrap (err , "failed to list resources" )
374+ }
375+
376+ for _ , item := range itemsGetter (list ) {
377+ obj := any (& item ).(client.Object )
378+ if metav1 .IsControlledBy (obj , instance ) {
379+ log .Info ("Deleting owned resource" , "kind" , obj .GetObjectKind ().GroupVersionKind ().Kind , "name" , obj .GetName ())
380+ err := r .Delete (ctx , obj )
381+ if err != nil && ! apierrors .IsNotFound (err ) {
382+ return errors .Wrapf (err , "failed to delete %s" , obj .GetName ())
383+ }
384+ }
385+ }
386+ return nil
387+ }
388+
389+ func (r * OpenStackReconciler ) deleteAllOwnedResources (ctx context.Context , instance * operatorv1beta1.OpenStack ) error {
390+ Log := r .GetLogger (ctx )
391+ Log .Info ("Deleting all owned resources for release version upgrade" )
392+
393+ err := deleteOwnedResources (ctx , r , instance , & appsv1.DeploymentList {}, func (l * appsv1.DeploymentList ) []appsv1.Deployment { return l .Items })
394+ if err != nil {
395+ return err
396+ }
397+
398+ err = deleteOwnedResources (ctx , r , instance , & corev1.ServiceAccountList {}, func (l * corev1.ServiceAccountList ) []corev1.ServiceAccount { return l .Items })
399+ if err != nil {
400+ return err
401+ }
402+
403+ err = deleteOwnedResources (ctx , r , instance , & corev1.ServiceList {}, func (l * corev1.ServiceList ) []corev1.Service { return l .Items })
404+ if err != nil {
405+ return err
406+ }
407+
408+ labelSelector , _ := metav1 .LabelSelectorAsSelector (& metav1.LabelSelector {
409+ MatchLabels : map [string ]string {"openstack.openstack.org/managed" : "true" },
410+ })
411+
412+ deleteOpts := client.DeleteAllOfOptions {
413+ ListOptions : client.ListOptions {LabelSelector : labelSelector },
414+ }
415+
416+ err = r .DeleteAllOf (ctx , & admissionv1.ValidatingWebhookConfiguration {}, & deleteOpts )
417+ if err != nil && ! apierrors .IsNotFound (err ) {
418+ return errors .Wrap (err , "failed to delete validating webhooks" )
419+ }
420+
421+ Log .Info ("All owned resources deleted successfully" )
422+ return nil
423+ }
424+
319425func (r * OpenStackReconciler ) reconcileDelete (ctx context.Context , instance * operatorv1beta1.OpenStack , helper * helper.Helper ) (ctrl.Result , error ) {
320426 Log := r .GetLogger (ctx )
321427 Log .Info ("Reconciling OpenStack initialization resource delete" )
@@ -987,7 +1093,7 @@ func (r *OpenStackReconciler) postCleanupObsoleteResources(ctx context.Context,
9871093 // The horizon-operator.openstack-operators has references to old roles/bindings
9881094 // the code below will delete those references before continuing
9891095 for _ , ref := range refs {
990- refData := ref .(map [string ]interface {} )
1096+ refData := ref .(map [string ]any )
9911097 Log .Info ("Deleting operator reference" , "Reference" , ref )
9921098 obj := uns.Unstructured {}
9931099 obj .SetName (refData ["name" ].(string ))
0 commit comments