github.com/openshift/installer@v1.4.17/pkg/destroy/powervs/powervs.go (about) 1 package powervs 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "math" 8 gohttp "net/http" 9 "strings" 10 "sync" 11 "time" 12 13 "github.com/IBM-Cloud/bluemix-go/crn" 14 "github.com/IBM-Cloud/power-go-client/clients/instance" 15 "github.com/IBM-Cloud/power-go-client/ibmpisession" 16 "github.com/IBM/go-sdk-core/v5/core" 17 "github.com/IBM/networking-go-sdk/dnsrecordsv1" 18 "github.com/IBM/networking-go-sdk/dnszonesv1" 19 "github.com/IBM/networking-go-sdk/resourcerecordsv1" 20 "github.com/IBM/networking-go-sdk/transitgatewayapisv1" 21 "github.com/IBM/networking-go-sdk/zonesv1" 22 "github.com/IBM/platform-services-go-sdk/resourcecontrollerv2" 23 "github.com/IBM/platform-services-go-sdk/resourcemanagerv2" 24 "github.com/IBM/vpc-go-sdk/vpcv1" 25 "github.com/sirupsen/logrus" 26 utilerrors "k8s.io/apimachinery/pkg/util/errors" 27 "k8s.io/apimachinery/pkg/util/wait" 28 29 "github.com/openshift/installer/pkg/asset/installconfig/powervs" 30 "github.com/openshift/installer/pkg/destroy/providers" 31 "github.com/openshift/installer/pkg/types" 32 powervstypes "github.com/openshift/installer/pkg/types/powervs" 33 "github.com/openshift/installer/pkg/version" 34 ) 35 36 var ( 37 defaultTimeout = 30 * time.Minute 38 stageTimeout = 15 * time.Minute 39 ) 40 41 func leftInContext(ctx context.Context) time.Duration { 42 deadline, ok := ctx.Deadline() 43 if !ok { 44 return math.MaxInt64 45 } 46 47 duration := time.Until(deadline) 48 49 return duration 50 } 51 52 const ( 53 // resource Id for Power Systems Virtual Server in the Global catalog. 54 powerIAASResourceID = "abd259f0-9990-11e8-acc8-b9f54a8f1661" 55 56 // cisServiceID is the Cloud Internet Services' catalog service ID. 57 cisServiceID = "75874a60-cb12-11e7-948e-37ac098eb1b9" 58 ) 59 60 // User information. 61 type User struct { 62 ID string 63 Email string 64 Account string 65 cloudName string `default:"bluemix"` 66 cloudType string `default:"public"` 67 generation int `default:"2"` 68 } 69 70 // ClusterUninstaller holds the various options for the cluster we want to delete. 71 type ClusterUninstaller struct { 72 APIKey string 73 BaseDomain string 74 CISInstanceCRN string 75 ClusterName string 76 Context context.Context 77 DNSInstanceCRN string 78 DNSZone string 79 InfraID string 80 Logger logrus.FieldLogger 81 Region string 82 ServiceGUID string 83 VPCRegion string 84 Zone string 85 86 managementSvc *resourcemanagerv2.ResourceManagerV2 87 controllerSvc *resourcecontrollerv2.ResourceControllerV2 88 vpcSvc *vpcv1.VpcV1 89 zonesSvc *zonesv1.ZonesV1 90 dnsRecordsSvc *dnsrecordsv1.DnsRecordsV1 91 dnsZonesSvc *dnszonesv1.DnsZonesV1 92 resourceRecordsSvc *resourcerecordsv1.ResourceRecordsV1 93 piSession *ibmpisession.IBMPISession 94 instanceClient *instance.IBMPIInstanceClient 95 imageClient *instance.IBMPIImageClient 96 jobClient *instance.IBMPIJobClient 97 keyClient *instance.IBMPIKeyClient 98 dhcpClient *instance.IBMPIDhcpClient 99 networkClient *instance.IBMPINetworkClient 100 tgClient *transitgatewayapisv1.TransitGatewayApisV1 101 102 resourceGroupID string 103 cosInstanceID string 104 dnsZoneID string 105 106 errorTracker 107 pendingItemTracker 108 } 109 110 // New returns an IBMCloud destroyer from ClusterMetadata. 111 func New(logger logrus.FieldLogger, metadata *types.ClusterMetadata) (providers.Destroyer, error) { 112 var ( 113 bxClient *powervs.BxClient 114 APIKey string 115 err error 116 ) 117 118 // We need to prompt for missing variables because NewPISession requires them! 119 bxClient, err = powervs.NewBxClient(true) 120 if err != nil { 121 return nil, err 122 } 123 APIKey = bxClient.GetBxClientAPIKey() 124 if APIKey == "" { 125 return nil, errors.New("powervs.GetSession did not return an API key") 126 } 127 128 logger.Debugf("powervs.New: metadata.InfraID = %v", metadata.InfraID) 129 logger.Debugf("powervs.New: metadata.ClusterPlatformMetadata.PowerVS.BaseDomain = %v", metadata.ClusterPlatformMetadata.PowerVS.BaseDomain) 130 logger.Debugf("powervs.New: metadata.ClusterPlatformMetadata.PowerVS.CISInstanceCRN = %v", metadata.ClusterPlatformMetadata.PowerVS.CISInstanceCRN) 131 logger.Debugf("powervs.New: metadata.ClusterPlatformMetadata.PowerVS.DNSInstanceCRN = %v", metadata.ClusterPlatformMetadata.PowerVS.DNSInstanceCRN) 132 logger.Debugf("powervs.New: metadata.ClusterPlatformMetadata.PowerVS.PowerVSResourceGroup = %v", metadata.ClusterPlatformMetadata.PowerVS.PowerVSResourceGroup) 133 logger.Debugf("powervs.New: metadata.ClusterPlatformMetadata.PowerVS.Region = %v", metadata.ClusterPlatformMetadata.PowerVS.Region) 134 logger.Debugf("powervs.New: metadata.ClusterPlatformMetadata.PowerVS.VPCRegion = %v", metadata.ClusterPlatformMetadata.PowerVS.VPCRegion) 135 logger.Debugf("powervs.New: metadata.ClusterPlatformMetadata.PowerVS.Zone = %v", metadata.ClusterPlatformMetadata.PowerVS.Zone) 136 logger.Debugf("powervs.New: metadata.ClusterPlatformMetadata.PowerVS.ServiceInstanceGUID = %v", metadata.ClusterPlatformMetadata.PowerVS.ServiceInstanceGUID) 137 138 // Handle an optional setting in install-config.yaml 139 if metadata.ClusterPlatformMetadata.PowerVS.VPCRegion == "" { 140 var derivedVPCRegion string 141 if derivedVPCRegion, err = powervstypes.VPCRegionForPowerVSRegion(metadata.ClusterPlatformMetadata.PowerVS.Region); err != nil { 142 return nil, fmt.Errorf("powervs.New failed to derive VPCRegion: %w", err) 143 } 144 logger.Debugf("powervs.New: PowerVS.VPCRegion is missing, derived VPCRegion = %v", derivedVPCRegion) 145 metadata.ClusterPlatformMetadata.PowerVS.VPCRegion = derivedVPCRegion 146 } 147 148 return &ClusterUninstaller{ 149 APIKey: APIKey, 150 BaseDomain: metadata.ClusterPlatformMetadata.PowerVS.BaseDomain, 151 ClusterName: metadata.ClusterName, 152 Context: context.Background(), 153 Logger: logger, 154 InfraID: metadata.InfraID, 155 CISInstanceCRN: metadata.ClusterPlatformMetadata.PowerVS.CISInstanceCRN, 156 DNSInstanceCRN: metadata.ClusterPlatformMetadata.PowerVS.DNSInstanceCRN, 157 Region: metadata.ClusterPlatformMetadata.PowerVS.Region, 158 VPCRegion: metadata.ClusterPlatformMetadata.PowerVS.VPCRegion, 159 Zone: metadata.ClusterPlatformMetadata.PowerVS.Zone, 160 pendingItemTracker: newPendingItemTracker(), 161 resourceGroupID: metadata.ClusterPlatformMetadata.PowerVS.PowerVSResourceGroup, 162 ServiceGUID: metadata.ClusterPlatformMetadata.PowerVS.ServiceInstanceGUID, 163 }, nil 164 } 165 166 // Run is the entrypoint to start the uninstall process. 167 func (o *ClusterUninstaller) Run() (*types.ClusterQuota, error) { 168 o.Logger.Debugf("powervs.Run") 169 170 var ctx context.Context 171 var deadline time.Time 172 var ok bool 173 var err error 174 175 ctx, cancel := o.contextWithTimeout() 176 defer cancel() 177 178 if ctx == nil { 179 return nil, fmt.Errorf("powervs.Run: contextWithTimeout returns nil: %w", err) 180 } 181 182 deadline, ok = ctx.Deadline() 183 if !ok { 184 return nil, fmt.Errorf("powervs.Run: failed to call ctx.Deadline: %w", err) 185 } 186 187 var duration time.Duration = deadline.Sub(time.Now()) 188 189 o.Logger.Debugf("powervs.Run: duration = %v", duration) 190 191 if duration <= 0 { 192 return nil, fmt.Errorf("powervs.Run: duration is <= 0 (%v)", duration) 193 } 194 195 err = wait.PollImmediateInfinite( 196 duration, 197 o.PolledRun, 198 ) 199 200 o.Logger.Debugf("powervs.Run: after wait.PollImmediateInfinite, err = %v", err) 201 202 return nil, err 203 } 204 205 // PolledRun is the Run function which will be called with Polling. 206 func (o *ClusterUninstaller) PolledRun() (bool, error) { 207 o.Logger.Debugf("powervs.PolledRun") 208 209 var err error 210 211 err = o.loadSDKServices() 212 if err != nil { 213 o.Logger.Debugf("powervs.PolledRun: Failed loadSDKServices") 214 return false, err 215 } 216 217 err = o.destroyCluster() 218 if err != nil { 219 o.Logger.Debugf("powervs.PolledRun: Failed destroyCluster") 220 return false, fmt.Errorf("failed to destroy cluster: %w", err) 221 } 222 223 return true, nil 224 } 225 226 func (o *ClusterUninstaller) destroyCluster() error { 227 stagedFuncs := [][]struct { 228 name string 229 execute func() error 230 }{{ 231 {name: "Transit Gateways", execute: o.destroyTransitGateways}, 232 }, { 233 {name: "Cloud Instances", execute: o.destroyCloudInstances}, 234 }, { 235 {name: "Power Instances", execute: o.destroyPowerInstances}, 236 }, { 237 {name: "Load Balancers", execute: o.destroyLoadBalancers}, 238 }, { 239 {name: "Cloud Subnets", execute: o.destroyCloudSubnets}, 240 }, { 241 {name: "Public Gateways", execute: o.destroyPublicGateways}, 242 }, { 243 {name: "DHCPs", execute: o.destroyDHCPNetworks}, 244 }, { 245 {name: "Power Subnets", execute: o.destroyPowerSubnets}, 246 {name: "Images", execute: o.destroyImages}, 247 {name: "VPCs", execute: o.destroyVPCs}, 248 }, { 249 {name: "Security Groups", execute: o.destroySecurityGroups}, 250 }, { 251 {name: "Cloud Object Storage Instances", execute: o.destroyCOSInstances}, 252 {name: "Cloud SSH Keys", execute: o.destroyCloudSSHKeys}, 253 {name: "Power SSH Keys", execute: o.destroyPowerSSHKeys}, 254 }, { 255 {name: "DNS Records", execute: o.destroyDNSRecords}, 256 {name: "DNS Resource Records", execute: o.destroyResourceRecords}, 257 }, { 258 {name: "Service Instances", execute: o.destroyServiceInstances}, 259 }} 260 261 for _, stage := range stagedFuncs { 262 var wg sync.WaitGroup 263 errCh := make(chan error) 264 wgDone := make(chan bool) 265 266 for _, f := range stage { 267 wg.Add(1) 268 // Start a parallel goroutine 269 go o.executeStageFunction(f, errCh, &wg) 270 } 271 272 // Start a parallel goroutine 273 go func() { 274 wg.Wait() 275 close(wgDone) 276 }() 277 278 select { 279 // Did the wait group goroutine finish? 280 case <-wgDone: 281 // On to the next stage 282 o.Logger.Debugf("destroyCluster: <-wgDone") 283 continue 284 // Have we taken too long? 285 case <-time.After(stageTimeout): 286 return fmt.Errorf("destroyCluster: timed out") 287 // Has an error been sent via the channel? 288 case err := <-errCh: 289 return err 290 } 291 } 292 293 return nil 294 } 295 296 func (o *ClusterUninstaller) executeStageFunction(f struct { 297 name string 298 execute func() error 299 }, errCh chan error, wg *sync.WaitGroup) error { 300 o.Logger.Debugf("executeStageFunction: Adding: %s", f.name) 301 302 defer wg.Done() 303 304 var ctx context.Context 305 var deadline time.Time 306 var ok bool 307 var err error 308 309 ctx, cancel := o.contextWithTimeout() 310 defer cancel() 311 312 if ctx == nil { 313 return fmt.Errorf("executeStageFunction contextWithTimeout returns nil: %w", err) 314 } 315 316 deadline, ok = ctx.Deadline() 317 if !ok { 318 return fmt.Errorf("executeStageFunction failed to call ctx.Deadline: %w", err) 319 } 320 321 var duration time.Duration = deadline.Sub(time.Now()) 322 323 o.Logger.Debugf("executeStageFunction: duration = %v", duration) 324 if duration <= 0 { 325 return fmt.Errorf("executeStageFunction: duration is <= 0 (%v)", duration) 326 } 327 328 err = wait.PollImmediateInfinite( 329 duration, 330 func() (bool, error) { 331 var err error 332 333 o.Logger.Debugf("executeStageFunction: Executing: %s", f.name) 334 335 err = f.execute() 336 if err != nil { 337 o.Logger.Debugf("ERROR: executeStageFunction: %s: %v", f.name, err) 338 339 return false, err 340 } 341 342 return true, nil 343 }, 344 ) 345 346 if err != nil { 347 errCh <- err 348 } 349 return nil 350 } 351 352 func (o *ClusterUninstaller) newAuthenticator(apikey string) (core.Authenticator, error) { 353 var ( 354 authenticator core.Authenticator 355 err error 356 ) 357 358 if apikey == "" { 359 return nil, errors.New("newAuthenticator: apikey is empty") 360 } 361 362 authenticator = &core.IamAuthenticator{ 363 ApiKey: apikey, 364 } 365 366 err = authenticator.Validate() 367 if err != nil { 368 return nil, fmt.Errorf("newAuthenticator: authenticator.Validate: %w", err) 369 } 370 371 return authenticator, nil 372 } 373 374 func (o *ClusterUninstaller) loadSDKServices() error { 375 var ( 376 err error 377 authenticator core.Authenticator 378 versionDate = "2023-07-04" 379 tgOptions *transitgatewayapisv1.TransitGatewayApisV1Options 380 serviceName string 381 ) 382 383 defer func() { 384 o.Logger.Debugf("loadSDKServices: o.ServiceGUID = %v", o.ServiceGUID) 385 o.Logger.Debugf("loadSDKServices: o.piSession = %v", o.piSession) 386 o.Logger.Debugf("loadSDKServices: o.instanceClient = %v", o.instanceClient) 387 o.Logger.Debugf("loadSDKServices: o.imageClient = %v", o.imageClient) 388 o.Logger.Debugf("loadSDKServices: o.jobClient = %v", o.jobClient) 389 o.Logger.Debugf("loadSDKServices: o.keyClient = %v", o.keyClient) 390 o.Logger.Debugf("loadSDKServices: o.vpcSvc = %v", o.vpcSvc) 391 o.Logger.Debugf("loadSDKServices: o.managementSvc = %v", o.managementSvc) 392 o.Logger.Debugf("loadSDKServices: o.controllerSvc = %v", o.controllerSvc) 393 }() 394 395 if o.APIKey == "" { 396 return fmt.Errorf("loadSDKServices: missing APIKey in metadata.json") 397 } 398 399 user, err := powervs.FetchUserDetails(o.APIKey) 400 if err != nil { 401 return fmt.Errorf("loadSDKServices: fetchUserDetails: %w", err) 402 } 403 404 authenticator, err = o.newAuthenticator(o.APIKey) 405 if err != nil { 406 return err 407 } 408 409 var options *ibmpisession.IBMPIOptions = &ibmpisession.IBMPIOptions{ 410 Authenticator: authenticator, 411 Debug: false, 412 UserAccount: user.Account, 413 Zone: o.Zone, 414 } 415 416 o.piSession, err = ibmpisession.NewIBMPISession(options) 417 if (err != nil) || (o.piSession == nil) { 418 if err != nil { 419 return fmt.Errorf("loadSDKServices: ibmpisession.New: %w", err) 420 } 421 return fmt.Errorf("loadSDKServices: o.piSession is nil") 422 } 423 424 authenticator, err = o.newAuthenticator(o.APIKey) 425 if err != nil { 426 return err 427 } 428 429 // https://raw.githubusercontent.com/IBM/vpc-go-sdk/master/vpcv1/vpc_v1.go 430 o.vpcSvc, err = vpcv1.NewVpcV1(&vpcv1.VpcV1Options{ 431 Authenticator: authenticator, 432 URL: "https://" + o.VPCRegion + ".iaas.cloud.ibm.com/v1", 433 }) 434 if err != nil { 435 return fmt.Errorf("loadSDKServices: vpcv1.NewVpcV1: %w", err) 436 } 437 438 userAgentString := fmt.Sprintf("OpenShift/4.x Destroyer/%s", version.Raw) 439 o.vpcSvc.Service.SetUserAgent(userAgentString) 440 441 authenticator, err = o.newAuthenticator(o.APIKey) 442 if err != nil { 443 return err 444 } 445 446 // Instantiate the service with an API key based IAM authenticator 447 o.managementSvc, err = resourcemanagerv2.NewResourceManagerV2(&resourcemanagerv2.ResourceManagerV2Options{ 448 Authenticator: authenticator, 449 }) 450 if err != nil { 451 return fmt.Errorf("loadSDKServices: creating ResourceManagerV2 Service: %w", err) 452 } 453 454 authenticator, err = o.newAuthenticator(o.APIKey) 455 if err != nil { 456 return err 457 } 458 459 // Instantiate the service with an API key based IAM authenticator 460 o.controllerSvc, err = resourcecontrollerv2.NewResourceControllerV2(&resourcecontrollerv2.ResourceControllerV2Options{ 461 Authenticator: authenticator, 462 ServiceName: "cloud-object-storage", 463 URL: "https://resource-controller.cloud.ibm.com", 464 }) 465 if err != nil { 466 return fmt.Errorf("loadSDKServices: creating ControllerV2 Service: %w", err) 467 } 468 469 authenticator, err = o.newAuthenticator(o.APIKey) 470 if err != nil { 471 return err 472 } 473 474 tgOptions = &transitgatewayapisv1.TransitGatewayApisV1Options{ 475 Authenticator: authenticator, 476 Version: &versionDate, 477 } 478 479 o.tgClient, err = transitgatewayapisv1.NewTransitGatewayApisV1(tgOptions) 480 if err != nil { 481 return fmt.Errorf("loadSDKServices: NewTransitGatewayApisV1: %w", err) 482 } 483 484 // Either CISInstanceCRN is set or DNSInstanceCRN is set. Both should not be set at the same time, 485 // but check both just to be safe. 486 if len(o.CISInstanceCRN) > 0 { 487 authenticator, err = o.newAuthenticator(o.APIKey) 488 if err != nil { 489 return err 490 } 491 492 o.zonesSvc, err = zonesv1.NewZonesV1(&zonesv1.ZonesV1Options{ 493 Authenticator: authenticator, 494 Crn: &o.CISInstanceCRN, 495 }) 496 if err != nil { 497 return fmt.Errorf("loadSDKServices: creating zonesSvc: %w", err) 498 } 499 500 ctx, cancel := o.contextWithTimeout() 501 defer cancel() 502 503 // Get the Zone ID 504 zoneOptions := o.zonesSvc.NewListZonesOptions() 505 zoneResources, detailedResponse, err := o.zonesSvc.ListZonesWithContext(ctx, zoneOptions) 506 if err != nil { 507 return fmt.Errorf("loadSDKServices: Failed to list Zones: %w and the response is: %s", err, detailedResponse) 508 } 509 510 for _, zone := range zoneResources.Result { 511 o.Logger.Debugf("loadSDKServices: Zone: %v", *zone.Name) 512 if strings.Contains(o.BaseDomain, *zone.Name) { 513 o.dnsZoneID = *zone.ID 514 } 515 } 516 517 authenticator, err = o.newAuthenticator(o.APIKey) 518 if err != nil { 519 return err 520 } 521 522 o.dnsRecordsSvc, err = dnsrecordsv1.NewDnsRecordsV1(&dnsrecordsv1.DnsRecordsV1Options{ 523 Authenticator: authenticator, 524 Crn: &o.CISInstanceCRN, 525 ZoneIdentifier: &o.dnsZoneID, 526 }) 527 if err != nil { 528 return fmt.Errorf("loadSDKServices: Failed to instantiate dnsRecordsSvc: %w", err) 529 } 530 } 531 532 if len(o.DNSInstanceCRN) > 0 { 533 authenticator, err = o.newAuthenticator(o.APIKey) 534 if err != nil { 535 return err 536 } 537 538 o.dnsZonesSvc, err = dnszonesv1.NewDnsZonesV1(&dnszonesv1.DnsZonesV1Options{ 539 Authenticator: authenticator, 540 }) 541 if err != nil { 542 return fmt.Errorf("loadSDKServices: creating zonesSvc: %w", err) 543 } 544 545 // Get the Zone ID 546 dnsCRN, err := crn.Parse(o.DNSInstanceCRN) 547 if err != nil { 548 return fmt.Errorf("failed to parse DNSInstanceCRN: %w", err) 549 } 550 options := o.dnsZonesSvc.NewListDnszonesOptions(dnsCRN.ServiceInstance) 551 listZonesResponse, detailedResponse, err := o.dnsZonesSvc.ListDnszones(options) 552 if err != nil { 553 return fmt.Errorf("loadSDKServices: Failed to list Zones: %w and the response is: %s", err, detailedResponse) 554 } 555 556 for _, zone := range listZonesResponse.Dnszones { 557 o.Logger.Debugf("loadSDKServices: Zone: %v", *zone.Name) 558 if strings.Contains(o.BaseDomain, *zone.Name) { 559 o.dnsZoneID = *zone.ID 560 } 561 } 562 563 authenticator, err = o.newAuthenticator(o.APIKey) 564 if err != nil { 565 return err 566 } 567 568 o.resourceRecordsSvc, err = resourcerecordsv1.NewResourceRecordsV1(&resourcerecordsv1.ResourceRecordsV1Options{ 569 Authenticator: authenticator, 570 }) 571 if err != nil { 572 return fmt.Errorf("loadSDKServices: Failed to instantiate resourceRecordsSvc: %w", err) 573 } 574 } 575 576 // If we should have created a service instance dynamically 577 if o.ServiceGUID == "" { 578 serviceName = fmt.Sprintf("%s-power-iaas", o.InfraID) 579 o.Logger.Debugf("loadSDKServices: serviceName = %v", serviceName) 580 581 o.ServiceGUID, err = o.ServiceInstanceNameToGUID(context.Background(), serviceName) 582 if err != nil { 583 return fmt.Errorf("loadSDKServices: ServiceInstanceNameToGUID: %w", err) 584 } 585 } 586 if o.ServiceGUID == "" { 587 // The rest of this function relies on o.ServiceGUID, so finish now! 588 return nil 589 } 590 591 o.instanceClient = instance.NewIBMPIInstanceClient(context.Background(), o.piSession, o.ServiceGUID) 592 if o.instanceClient == nil { 593 return fmt.Errorf("loadSDKServices: o.instanceClient is nil") 594 } 595 596 o.imageClient = instance.NewIBMPIImageClient(context.Background(), o.piSession, o.ServiceGUID) 597 if o.imageClient == nil { 598 return fmt.Errorf("loadSDKServices: o.imageClient is nil") 599 } 600 601 o.jobClient = instance.NewIBMPIJobClient(context.Background(), o.piSession, o.ServiceGUID) 602 if o.jobClient == nil { 603 return fmt.Errorf("loadSDKServices: o.jobClient is nil") 604 } 605 606 o.keyClient = instance.NewIBMPIKeyClient(context.Background(), o.piSession, o.ServiceGUID) 607 if o.keyClient == nil { 608 return fmt.Errorf("loadSDKServices: o.keyClient is nil") 609 } 610 611 o.dhcpClient = instance.NewIBMPIDhcpClient(context.Background(), o.piSession, o.ServiceGUID) 612 if o.dhcpClient == nil { 613 return fmt.Errorf("loadSDKServices: o.dhcpClient is nil") 614 } 615 616 o.networkClient = instance.NewIBMPINetworkClient(context.Background(), o.piSession, o.ServiceGUID) 617 if o.networkClient == nil { 618 return fmt.Errorf("loadSDKServices: o.networkClient is nil") 619 } 620 621 return nil 622 } 623 624 // ServiceInstanceNameToGUID returns the GUID of the matching service instance name which was passed in. 625 func (o *ClusterUninstaller) ServiceInstanceNameToGUID(ctx context.Context, name string) (string, error) { 626 var ( 627 options *resourcecontrollerv2.ListResourceInstancesOptions 628 resources *resourcecontrollerv2.ResourceInstancesList 629 err error 630 perPage int64 = 10 631 moreData = true 632 nextURL *string 633 groupID = o.resourceGroupID 634 ) 635 636 o.Logger.Debugf("ServiceInstanceNameToGUID: groupID = %s", groupID) 637 // If the user passes in a human readable group id, then we need to convert it to a UUID 638 listGroupOptions := o.managementSvc.NewListResourceGroupsOptions() 639 groups, _, err := o.managementSvc.ListResourceGroupsWithContext(ctx, listGroupOptions) 640 if err != nil { 641 return "", fmt.Errorf("failed to list resource groups: %w", err) 642 } 643 for _, group := range groups.Resources { 644 o.Logger.Debugf("ServiceInstanceNameToGUID: group.Name = %s", *group.Name) 645 if *group.Name == groupID { 646 groupID = *group.ID 647 } 648 } 649 o.Logger.Debugf("ServiceInstanceNameToGUID: groupID = %s", groupID) 650 651 options = o.controllerSvc.NewListResourceInstancesOptions() 652 options.SetResourceGroupID(groupID) 653 // resource ID for Power Systems Virtual Server in the Global catalog 654 options.SetResourceID(powerIAASResourceID) 655 options.SetLimit(perPage) 656 657 for moreData { 658 resources, _, err = o.controllerSvc.ListResourceInstancesWithContext(ctx, options) 659 if err != nil { 660 return "", fmt.Errorf("failed to list resource instances: %w", err) 661 } 662 663 for _, resource := range resources.Resources { 664 var ( 665 getResourceOptions *resourcecontrollerv2.GetResourceInstanceOptions 666 resourceInstance *resourcecontrollerv2.ResourceInstance 667 response *core.DetailedResponse 668 ) 669 670 o.Logger.Debugf("ServiceInstanceNameToGUID: resource.Name = %s", *resource.Name) 671 672 getResourceOptions = o.controllerSvc.NewGetResourceInstanceOptions(*resource.ID) 673 674 resourceInstance, response, err = o.controllerSvc.GetResourceInstance(getResourceOptions) 675 if err != nil { 676 return "", fmt.Errorf("failed to get instance: %w", err) 677 } 678 if response != nil && response.StatusCode == gohttp.StatusNotFound || response.StatusCode == gohttp.StatusInternalServerError { 679 return "", fmt.Errorf("failed to get instance, response is: %v", response) 680 } 681 682 if resourceInstance.Type == nil { 683 o.Logger.Debugf("ServiceInstanceNameToGUID: type: nil") 684 continue 685 } 686 o.Logger.Debugf("ServiceInstanceNameToGUID: type: %v", *resourceInstance.Type) 687 if resourceInstance.GUID == nil { 688 o.Logger.Debugf("ServiceInstanceNameToGUID: GUID: nil") 689 continue 690 } 691 if *resourceInstance.Type != "service_instance" && *resourceInstance.Type != "composite_instance" { 692 continue 693 } 694 if *resourceInstance.Name != name { 695 continue 696 } 697 698 o.Logger.Debugf("ServiceInstanceNameToGUID: Found match!") 699 700 return *resourceInstance.GUID, nil 701 } 702 703 // Based on: https://cloud.ibm.com/apidocs/resource-controller/resource-controller?code=go#list-resource-instances 704 nextURL, err = core.GetQueryParam(resources.NextURL, "start") 705 if err != nil { 706 return "", fmt.Errorf("failed to GetQueryParam on start: %w", err) 707 } 708 if nextURL == nil { 709 options.SetStart("") 710 } else { 711 options.SetStart(*nextURL) 712 } 713 714 moreData = *resources.RowsCount == perPage 715 } 716 717 return "", nil 718 } 719 720 func (o *ClusterUninstaller) contextWithTimeout() (context.Context, context.CancelFunc) { 721 return context.WithTimeout(o.Context, defaultTimeout) 722 } 723 724 func (o *ClusterUninstaller) timeout(ctx context.Context) bool { 725 var deadline time.Time 726 var ok bool 727 728 deadline, ok = ctx.Deadline() 729 if !ok { 730 o.Logger.Debugf("timeout: deadline, ok = %v, %v", deadline, ok) 731 return true 732 } 733 734 var after bool = time.Now().After(deadline) 735 736 if after { 737 // 01/02 03:04:05PM ‘06 -0700 738 o.Logger.Debugf("timeout: after deadline! (%v)", deadline.Format("2006-01-02 03:04:05PM")) 739 } 740 741 return after 742 } 743 744 type ibmError struct { 745 Status int 746 Message string 747 } 748 749 func isNoOp(err *ibmError) bool { 750 if err == nil { 751 return false 752 } 753 754 return err.Status == gohttp.StatusNotFound 755 } 756 757 // aggregateError is a utility function that takes a slice of errors and an 758 // optional pending argument, and returns an error or nil. 759 func aggregateError(errs []error, pending ...int) error { 760 err := utilerrors.NewAggregate(errs) 761 if err != nil { 762 return err 763 } 764 if len(pending) > 0 && pending[0] > 0 { 765 return fmt.Errorf("%d items pending", pending[0]) 766 } 767 return nil 768 } 769 770 // pendingItemTracker tracks a set of pending item names for a given type of resource. 771 type pendingItemTracker struct { 772 pendingItems map[string]cloudResources 773 } 774 775 func newPendingItemTracker() pendingItemTracker { 776 return pendingItemTracker{ 777 pendingItems: map[string]cloudResources{}, 778 } 779 } 780 781 // GetAllPendintItems returns a slice of all of the pending items across all types. 782 func (t pendingItemTracker) GetAllPendingItems() []cloudResource { 783 var items []cloudResource 784 for _, is := range t.pendingItems { 785 for _, i := range is { 786 items = append(items, i) 787 } 788 } 789 return items 790 } 791 792 // getPendingItems returns the list of resources to be deleted. 793 func (t pendingItemTracker) getPendingItems(itemType string) []cloudResource { 794 lastFound, exists := t.pendingItems[itemType] 795 if !exists { 796 lastFound = cloudResources{} 797 } 798 return lastFound.list() 799 } 800 801 // insertPendingItems adds to the list of resources to be deleted. 802 func (t pendingItemTracker) insertPendingItems(itemType string, items []cloudResource) []cloudResource { 803 lastFound, exists := t.pendingItems[itemType] 804 if !exists { 805 lastFound = cloudResources{} 806 } 807 lastFound = lastFound.insert(items...) 808 t.pendingItems[itemType] = lastFound 809 return lastFound.list() 810 } 811 812 // deletePendingItems removes from the list of resources to be deleted. 813 func (t pendingItemTracker) deletePendingItems(itemType string, items []cloudResource) []cloudResource { 814 lastFound, exists := t.pendingItems[itemType] 815 if !exists { 816 lastFound = cloudResources{} 817 } 818 lastFound = lastFound.delete(items...) 819 t.pendingItems[itemType] = lastFound 820 return lastFound.list() 821 } 822 823 func isErrorStatus(code int64) bool { 824 return code != 0 && (code < 200 || code >= 300) 825 }