From 5a9a61b11508cfc833fce816d0daabdff435d9ed Mon Sep 17 00:00:00 2001 From: barbacbd Date: Mon, 22 Jun 2026 14:46:48 -0400 Subject: [PATCH 1/2] OCPBUGS-46605: Find instances by label GCP This fixes a regression where finding instances by label was not working correctly. The labels field must be requested when querying for instance information. --- pkg/destroy/gcp/instance.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/destroy/gcp/instance.go b/pkg/destroy/gcp/instance.go index 5159fd076aa..712a08538da 100644 --- a/pkg/destroy/gcp/instance.go +++ b/pkg/destroy/gcp/instance.go @@ -27,12 +27,12 @@ func (o *ClusterUninstaller) getInstanceNameAndZone(instanceURL string) (string, } func (o *ClusterUninstaller) listInstances(ctx context.Context) ([]cloudResource, error) { - byName, err := o.listInstancesWithFilter(ctx, "items/*/instances(name,zone,status,machineType),nextPageToken", o.clusterIDFilter(), nil) + byName, err := o.listInstancesWithFilter(ctx, "items/*/instances(name,zone,status,machineType,labels),nextPageToken", o.clusterIDFilter(), nil) if err != nil { return nil, err } - byLabel, err := o.listInstancesWithFilter(ctx, "items/*/instances(name,zone,status,machineType),nextPageToken", o.clusterLabelFilter(), nil) + byLabel, err := o.listInstancesWithFilter(ctx, "items/*/instances(name,zone,status,machineType,labels),nextPageToken", o.clusterLabelFilter(), nil) if err != nil { return nil, err } From 631c430ebc890fe6e3043cd0b24f5cd92b8df3f1 Mon Sep 17 00:00:00 2001 From: barbacbd Date: Mon, 22 Jun 2026 14:47:37 -0400 Subject: [PATCH 2/2] OCPBUGS-90710: Destroy all private cluster backend service resources When a private cluster was created, the destroy process would not find and destroy backend services. This was happening specifically when no backends were created/found. Enhanced backend service discovery to check firewall rules for cluster ID tags when backends are empty. This allows proper identification of orphaned backend services that should be deleted during cluster destroy. Also added global health check discovery to ensure all related resources are properly cleaned up. --- pkg/destroy/gcp/cloudcontroller.go | 75 +++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/pkg/destroy/gcp/cloudcontroller.go b/pkg/destroy/gcp/cloudcontroller.go index c1f0496723d..d8ce9587c48 100644 --- a/pkg/destroy/gcp/cloudcontroller.go +++ b/pkg/destroy/gcp/cloudcontroller.go @@ -3,9 +3,13 @@ package gcp import ( "context" "fmt" + "strings" compute "google.golang.org/api/compute/v1" + "google.golang.org/api/googleapi" "k8s.io/apimachinery/pkg/util/sets" + + "github.com/openshift/installer/pkg/types/gcp" ) // listCloudControllerInstanceGroups returns instance groups created by the cloud controller. @@ -21,12 +25,81 @@ func (o *ClusterUninstaller) listCloudControllerInstanceGroups(ctx context.Conte // It list all backend services matching the cloud controller name convention that contain // only cluster instance groups. func (o *ClusterUninstaller) listCloudControllerBackendServices(ctx context.Context, instanceGroups []cloudResource) ([]cloudResource, error) { + o.Logger.Debugf("Listing cloud controller backend services") urls := sets.Set[string]{} for _, instanceGroup := range instanceGroups { urls.Insert(instanceGroup.url) } + filter := "name eq \"a[0-9a-f]{30,50}\"" - return o.listBackendServicesWithFilter(ctx, regionBackendServiceResource, "items(name,backends),nextPageToken", filter, urls) + result := []cloudResource{} + + // Fetch firewall rules once (fully paginated); used to validate backend + // services that have no backends. + var firewalls []*compute.Firewall + if err := o.computeSvc.Firewalls.List(o.ProjectID). + Fields(googleapi.Field("items(name,targetTags),nextPageToken")). + Pages(ctx, func(list *compute.FirewallList) error { + firewalls = append(firewalls, list.Items...) + return nil + }); err != nil { + return nil, err + } + + req := o.computeSvc.RegionBackendServices.List(o.ProjectID, o.Region).Fields(googleapi.Field("items(name,backends),nextPageToken")).Filter(filter) + err := req.Pages(ctx, func(list *compute.BackendServiceList) error { + for _, item := range list.Items { + if len(item.Backends) == 0 { + o.Logger.Debugf("Backend service %s has no backends, checking firewall rules for cluster ID", item.Name) + found := false + for _, fw := range firewalls { + if strings.Contains(fw.Name, item.Name) { + for _, tag := range fw.TargetTags { + if strings.Contains(tag, o.ClusterID) { + found = true + break + } + } + } + if found { + break + } + } + if !found { + continue + } + } else { + allBackendsMatch := true + for _, backend := range item.Backends { + if !urls.Has(backend.Group) { + allBackendsMatch = false + break + } + } + if !allBackendsMatch { + continue + } + } + o.Logger.Debugf("Found backend service: %s", item.Name) + result = append(result, cloudResource{ + key: item.Name, + name: item.Name, + typeName: regionBackendServiceResource, + quota: []gcp.QuotaUsage{{ + Metric: &gcp.Metric{ + Service: gcp.ServiceComputeEngineAPI, + Limit: "backend_services", + }, + Amount: 1, + }}, + }) + } + return nil + }) + if err != nil { + return nil, err + } + return result, nil } // listCloudControllerTargetPools returns target pools created by the cloud controller or owned by the cloud controller.