github.com/openshift/installer@v1.4.17/pkg/asset/installconfig/powervs/client.go (about) 1 package powervs 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "net/http" 8 "strings" 9 "time" 10 11 "github.com/IBM-Cloud/bluemix-go/crn" 12 "github.com/IBM-Cloud/power-go-client/clients/instance" 13 "github.com/IBM-Cloud/power-go-client/ibmpisession" 14 "github.com/IBM-Cloud/power-go-client/power/client/datacenters" 15 "github.com/IBM-Cloud/power-go-client/power/models" 16 "github.com/IBM/go-sdk-core/v5/core" 17 "github.com/IBM/networking-go-sdk/dnsrecordsv1" 18 "github.com/IBM/networking-go-sdk/dnssvcsv1" 19 "github.com/IBM/networking-go-sdk/dnszonesv1" 20 "github.com/IBM/networking-go-sdk/resourcerecordsv1" 21 "github.com/IBM/networking-go-sdk/transitgatewayapisv1" 22 "github.com/IBM/networking-go-sdk/zonesv1" 23 "github.com/IBM/platform-services-go-sdk/iamidentityv1" 24 "github.com/IBM/platform-services-go-sdk/resourcecontrollerv2" 25 "github.com/IBM/platform-services-go-sdk/resourcemanagerv2" 26 "github.com/IBM/vpc-go-sdk/vpcv1" 27 "github.com/sirupsen/logrus" 28 "k8s.io/apimachinery/pkg/util/wait" 29 "k8s.io/utils/ptr" 30 31 "github.com/openshift/installer/pkg/types" 32 ) 33 34 //go:generate mockgen -source=./client.go -destination=./mock/powervsclient_generated.go -package=mock 35 36 // API represents the calls made to the API. 37 type API interface { 38 // DNS 39 GetDNSRecordsByName(ctx context.Context, crnstr string, zoneID string, recordName string, publish types.PublishingStrategy) ([]DNSRecordResponse, error) 40 GetDNSZoneIDByName(ctx context.Context, name string, publish types.PublishingStrategy) (string, error) 41 GetDNSZones(ctx context.Context, publish types.PublishingStrategy) ([]DNSZoneResponse, error) 42 GetDNSInstancePermittedNetworks(ctx context.Context, dnsID string, dnsZone string) ([]string, error) 43 GetDNSCustomResolverIP(ctx context.Context, dnsID string, vpcID string) (string, error) 44 CreateDNSCustomResolver(ctx context.Context, name string, dnsID string, vpcID string) (*dnssvcsv1.CustomResolver, error) 45 EnableDNSCustomResolver(ctx context.Context, dnsID string, resolverID string) (*dnssvcsv1.CustomResolver, error) 46 CreateDNSRecord(ctx context.Context, publish types.PublishingStrategy, crnstr string, baseDomain string, hostname string, cname string) error 47 AddVPCToPermittedNetworks(ctx context.Context, vpcCRN string, dnsID string, dnsZone string) error 48 49 // VPC 50 GetVPCByName(ctx context.Context, vpcName string) (*vpcv1.VPC, error) 51 GetPublicGatewayByVPC(ctx context.Context, vpcName string) (*vpcv1.PublicGateway, error) 52 SetVPCServiceURLForRegion(ctx context.Context, region string) error 53 GetVPCs(ctx context.Context, region string) ([]vpcv1.VPC, error) 54 GetVPCSubnets(ctx context.Context, vpcID string) ([]vpcv1.Subnet, error) 55 56 // TG 57 GetTGConnectionVPC(ctx context.Context, gatewayID string, vpcSubnetID string) (string, error) 58 GetAttachedTransitGateway(ctx context.Context, svcInsID string) (string, error) 59 60 // Data Center 61 GetDatacenterCapabilities(ctx context.Context, region string) (map[string]bool, error) 62 63 // API 64 GetAuthenticatorAPIKeyDetails(ctx context.Context) (*iamidentityv1.APIKey, error) 65 GetAPIKey() string 66 67 // Subnet 68 GetSubnetByName(ctx context.Context, subnetName string, region string) (*vpcv1.Subnet, error) 69 70 // Resource Groups 71 ListResourceGroups(ctx context.Context) (*resourcemanagerv2.ResourceGroupList, error) 72 73 // Service Instance 74 ListServiceInstances(ctx context.Context) ([]string, error) 75 ServiceInstanceGUIDToName(ctx context.Context, id string) (string, error) 76 ServiceInstanceNameToGUID(ctx context.Context, name string) (string, error) 77 78 // Security Group 79 ListSecurityGroupRules(ctx context.Context, securityGroupID string) (*vpcv1.SecurityGroupRuleCollection, error) 80 AddSecurityGroupRule(ctx context.Context, securityGroupID string, rule *vpcv1.SecurityGroupRulePrototype) error 81 82 // SSH 83 CreateSSHKey(ctx context.Context, serviceInstance string, zone string, sshKeyName string, sshKey string) error 84 85 // Load Balancer 86 AddIPToLoadBalancerPool(ctx context.Context, lbID string, poolName string, port int64, ip string) error 87 } 88 89 // Client makes calls to the PowerVS API. 90 type Client struct { 91 APIKey string 92 BXCli *BxClient 93 managementAPI *resourcemanagerv2.ResourceManagerV2 94 controllerAPI *resourcecontrollerv2.ResourceControllerV2 95 vpcAPI *vpcv1.VpcV1 96 dnsServicesAPI *dnssvcsv1.DnsSvcsV1 97 transitGatewayAPI *transitgatewayapisv1.TransitGatewayApisV1 98 } 99 100 // cisServiceID is the Cloud Internet Services' catalog service ID. 101 const ( 102 cisServiceID = "75874a60-cb12-11e7-948e-37ac098eb1b9" 103 dnsServiceID = "b4ed8a30-936f-11e9-b289-1d079699cbe5" 104 serviceInstanceType = "service_instance" 105 compositeInstanceType = "composite_instance" 106 ) 107 108 // DNSZoneResponse represents a DNS zone response. 109 type DNSZoneResponse struct { 110 // Name is the domain name of the zone. 111 Name string 112 113 // ID is the zone's ID. 114 ID string 115 116 // CISInstanceCRN is the IBM Cloud Resource Name for the CIS instance where 117 // the DNS zone is managed. 118 InstanceCRN string 119 120 // CISInstanceName is the display name of the CIS instance where the DNS zone 121 // is managed. 122 InstanceName string 123 124 // ResourceGroupID is the resource group ID of the CIS instance. 125 ResourceGroupID string 126 } 127 128 // DNSRecordResponse represents a DNS record response. 129 type DNSRecordResponse struct { 130 Name string 131 Type string 132 } 133 134 // NewClient initializes a client with a session. 135 func NewClient() (*Client, error) { 136 bxCli, err := NewBxClient(false) 137 if err != nil { 138 return nil, err 139 } 140 141 client := &Client{ 142 APIKey: bxCli.APIKey, 143 BXCli: bxCli, 144 } 145 146 if err := client.loadSDKServices(); err != nil { 147 return nil, fmt.Errorf("failed to load IBM SDK services: %w", err) 148 } 149 150 if bxCli.PowerVSResourceGroup == "Default" { 151 // Here we are initialized enough to handle a default resource group 152 ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Minute) 153 defer cancel() 154 155 resourceGroups, err := client.ListResourceGroups(ctx) 156 if err != nil { 157 return nil, fmt.Errorf("client.ListResourceGroups failed: %w", err) 158 } 159 if resourceGroups == nil { 160 return nil, errors.New("client.ListResourceGroups returns nil") 161 } 162 163 found := false 164 for _, resourceGroup := range resourceGroups.Resources { 165 if resourceGroup.Default != nil && *resourceGroup.Default { 166 bxCli.PowerVSResourceGroup = *resourceGroup.Name 167 found = true 168 break 169 } 170 } 171 172 if !found { 173 return nil, errors.New("no default resource group found") 174 } 175 } 176 177 return client, nil 178 } 179 180 func (c *Client) loadSDKServices() error { 181 servicesToLoad := []func() error{ 182 c.loadResourceManagementAPI, 183 c.loadResourceControllerAPI, 184 c.loadVPCV1API, 185 c.loadDNSServicesAPI, 186 c.loadTransitGatewayAPI, 187 } 188 189 // Call all the load functions. 190 for _, fn := range servicesToLoad { 191 if err := fn(); err != nil { 192 return err 193 } 194 } 195 196 return nil 197 } 198 199 // GetDNSRecordsByName gets DNS records in specific Cloud Internet Services instance 200 // by its CRN, zone ID, and DNS record name. 201 func (c *Client) GetDNSRecordsByName(ctx context.Context, crnstr string, zoneID string, recordName string, publish types.PublishingStrategy) ([]DNSRecordResponse, error) { 202 authenticator := &core.IamAuthenticator{ 203 ApiKey: c.APIKey, 204 } 205 dnsRecords := []DNSRecordResponse{} 206 switch publish { 207 case types.ExternalPublishingStrategy: 208 // Set CIS DNS record service 209 dnsService, err := dnsrecordsv1.NewDnsRecordsV1(&dnsrecordsv1.DnsRecordsV1Options{ 210 Authenticator: authenticator, 211 Crn: core.StringPtr(crnstr), 212 ZoneIdentifier: core.StringPtr(zoneID), 213 }) 214 if err != nil { 215 return nil, err 216 } 217 218 // Get CIS DNS records by name 219 records, _, err := dnsService.ListAllDnsRecordsWithContext(ctx, &dnsrecordsv1.ListAllDnsRecordsOptions{ 220 Name: core.StringPtr(recordName), 221 }) 222 if err != nil { 223 return nil, fmt.Errorf("could not retrieve DNS records: %w", err) 224 } 225 for _, record := range records.Result { 226 dnsRecords = append(dnsRecords, DNSRecordResponse{Name: *record.Name, Type: *record.Type}) 227 } 228 case types.InternalPublishingStrategy: 229 // Set DNS record service 230 dnsService, err := resourcerecordsv1.NewResourceRecordsV1(&resourcerecordsv1.ResourceRecordsV1Options{ 231 Authenticator: authenticator, 232 }) 233 if err != nil { 234 return nil, err 235 } 236 237 dnsCRN, err := crn.Parse(crnstr) 238 if err != nil { 239 return nil, fmt.Errorf("failed to parse DNSInstanceCRN: %w", err) 240 } 241 242 // Get DNS records by name 243 records, _, err := dnsService.ListResourceRecords(&resourcerecordsv1.ListResourceRecordsOptions{ 244 InstanceID: &dnsCRN.ServiceInstance, 245 DnszoneID: &zoneID, 246 }) 247 for _, record := range records.ResourceRecords { 248 if *record.Name == recordName { 249 dnsRecords = append(dnsRecords, DNSRecordResponse{Name: *record.Name, Type: *record.Type}) 250 } 251 } 252 if err != nil { 253 return nil, fmt.Errorf("could not retrieve DNS records: %w", err) 254 } 255 } 256 257 return dnsRecords, nil 258 } 259 260 // GetInstanceCRNByName finds the CRN of the instance with the specified name. 261 func (c *Client) GetInstanceCRNByName(ctx context.Context, name string, publish types.PublishingStrategy) (string, error) { 262 zones, err := c.GetDNSZones(ctx, publish) 263 if err != nil { 264 return "", err 265 } 266 267 for _, z := range zones { 268 if z.Name == name { 269 return z.InstanceCRN, nil 270 } 271 } 272 273 return "", fmt.Errorf("DNS zone %q not found", name) 274 } 275 276 // GetDNSCustomResolverIP gets the DNS Server IP of a custom resolver associated with the specified VPC subnet in the specified DNS zone. 277 func (c *Client) GetDNSCustomResolverIP(ctx context.Context, dnsID string, vpcID string) (string, error) { 278 listCustomResolversOptions := c.dnsServicesAPI.NewListCustomResolversOptions(dnsID) 279 customResolvers, _, err := c.dnsServicesAPI.ListCustomResolversWithContext(ctx, listCustomResolversOptions) 280 if err != nil { 281 return "", err 282 } 283 284 subnets, err := c.GetVPCSubnets(ctx, vpcID) 285 if err != nil { 286 return "", err 287 } 288 289 for _, customResolver := range customResolvers.CustomResolvers { 290 for _, location := range customResolver.Locations { 291 for _, subnet := range subnets { 292 if *subnet.CRN == *location.SubnetCrn { 293 return *location.DnsServerIp, nil 294 } 295 } 296 } 297 } 298 return "", fmt.Errorf("DNS server IP of custom resolver for %q not found", dnsID) 299 } 300 301 // CreateDNSCustomResolver creates a custom resolver associated with the specified VPC in the specified DNS zone. 302 func (c *Client) CreateDNSCustomResolver(ctx context.Context, name string, dnsID string, vpcID string) (*dnssvcsv1.CustomResolver, error) { 303 createCustomResolverOptions := c.dnsServicesAPI.NewCreateCustomResolverOptions(dnsID) 304 createCustomResolverOptions.SetName(name) 305 306 subnets, err := c.GetVPCSubnets(ctx, vpcID) 307 if err != nil { 308 return nil, err 309 } 310 311 locations := []dnssvcsv1.LocationInput{} 312 for _, subnet := range subnets { 313 location, err := c.dnsServicesAPI.NewLocationInput(*subnet.CRN) 314 if err != nil { 315 return nil, err 316 } 317 location.Enabled = core.BoolPtr(true) 318 locations = append(locations, *location) 319 } 320 createCustomResolverOptions.SetLocations(locations) 321 322 customResolver, _, err := c.dnsServicesAPI.CreateCustomResolverWithContext(ctx, createCustomResolverOptions) 323 if err != nil { 324 return nil, err 325 } 326 return customResolver, nil 327 } 328 329 // EnableDNSCustomResolver enables a specified custom resolver. 330 func (c *Client) EnableDNSCustomResolver(ctx context.Context, dnsID string, resolverID string) (*dnssvcsv1.CustomResolver, error) { 331 updateCustomResolverOptions := c.dnsServicesAPI.NewUpdateCustomResolverOptions(dnsID, resolverID) 332 updateCustomResolverOptions.SetEnabled(true) 333 334 customResolver, _, err := c.dnsServicesAPI.UpdateCustomResolverWithContext(ctx, updateCustomResolverOptions) 335 if err != nil { 336 return nil, err 337 } 338 return customResolver, nil 339 } 340 341 // GetDNSZoneIDByName gets the CIS zone ID from its domain name. 342 func (c *Client) GetDNSZoneIDByName(ctx context.Context, name string, publish types.PublishingStrategy) (string, error) { 343 zones, err := c.GetDNSZones(ctx, publish) 344 if err != nil { 345 return "", err 346 } 347 348 for _, z := range zones { 349 if z.Name == name { 350 return z.ID, nil 351 } 352 } 353 354 return "", fmt.Errorf("DNS zone %q not found", name) 355 } 356 357 // GetDNSZones returns all of the active DNS zones managed by CIS. 358 func (c *Client) GetDNSZones(ctx context.Context, publish types.PublishingStrategy) ([]DNSZoneResponse, error) { 359 _, cancel := context.WithTimeout(ctx, 1*time.Minute) 360 defer cancel() 361 362 options := c.controllerAPI.NewListResourceInstancesOptions() 363 switch publish { 364 case types.ExternalPublishingStrategy: 365 options.SetResourceID(cisServiceID) 366 case types.InternalPublishingStrategy: 367 options.SetResourceID(dnsServiceID) 368 default: 369 return nil, errors.New("unknown publishing strategy") 370 } 371 372 listResourceInstancesResponse, _, err := c.controllerAPI.ListResourceInstances(options) 373 if err != nil { 374 return nil, fmt.Errorf("failed to get cis instance: %w", err) 375 } 376 377 var allZones []DNSZoneResponse 378 for _, instance := range listResourceInstancesResponse.Resources { 379 authenticator := &core.IamAuthenticator{ 380 ApiKey: c.APIKey, 381 } 382 383 switch publish { 384 case types.ExternalPublishingStrategy: 385 zonesService, err := zonesv1.NewZonesV1(&zonesv1.ZonesV1Options{ 386 Authenticator: authenticator, 387 Crn: instance.CRN, 388 }) 389 if err != nil { 390 return nil, fmt.Errorf("failed to list DNS zones: %w", err) 391 } 392 393 options := zonesService.NewListZonesOptions() 394 listZonesResponse, _, err := zonesService.ListZones(options) 395 396 if listZonesResponse == nil { 397 return nil, err 398 } 399 400 for _, zone := range listZonesResponse.Result { 401 if *zone.Status == "active" { 402 zoneStruct := DNSZoneResponse{ 403 Name: *zone.Name, 404 ID: *zone.ID, 405 InstanceCRN: *instance.CRN, 406 InstanceName: *instance.Name, 407 ResourceGroupID: *instance.ResourceGroupID, 408 } 409 allZones = append(allZones, zoneStruct) 410 } 411 } 412 case types.InternalPublishingStrategy: 413 dnsZonesService, err := dnszonesv1.NewDnsZonesV1(&dnszonesv1.DnsZonesV1Options{ 414 Authenticator: authenticator, 415 }) 416 if err != nil { 417 return nil, fmt.Errorf("failed to list DNS zones: %w", err) 418 } 419 420 options := dnsZonesService.NewListDnszonesOptions(*instance.GUID) 421 listZonesResponse, _, err := dnsZonesService.ListDnszones(options) 422 423 if listZonesResponse == nil { 424 return nil, err 425 } 426 427 for _, zone := range listZonesResponse.Dnszones { 428 if *zone.State == "ACTIVE" || *zone.State == "PENDING_NETWORK_ADD" { 429 zoneStruct := DNSZoneResponse{ 430 Name: *zone.Name, 431 ID: *zone.ID, 432 InstanceCRN: *instance.CRN, 433 InstanceName: *instance.Name, 434 ResourceGroupID: *instance.ResourceGroupID, 435 } 436 allZones = append(allZones, zoneStruct) 437 } 438 } 439 } 440 } 441 return allZones, nil 442 } 443 444 // GetDNSInstancePermittedNetworks gets the permitted VPC networks for a DNS Services instance. 445 func (c *Client) GetDNSInstancePermittedNetworks(ctx context.Context, dnsID string, dnsZone string) ([]string, error) { 446 _, cancel := context.WithTimeout(ctx, 1*time.Minute) 447 defer cancel() 448 449 listPermittedNetworksOptions := c.dnsServicesAPI.NewListPermittedNetworksOptions(dnsID, dnsZone) 450 permittedNetworks, _, err := c.dnsServicesAPI.ListPermittedNetworksWithContext(ctx, listPermittedNetworksOptions) 451 if err != nil { 452 return nil, err 453 } 454 455 networks := []string{} 456 for _, network := range permittedNetworks.PermittedNetworks { 457 networks = append(networks, *network.PermittedNetwork.VpcCrn) 458 } 459 return networks, nil 460 } 461 462 // AddVPCToPermittedNetworks adds the specified VPC to the specified DNS zone. 463 func (c *Client) AddVPCToPermittedNetworks(ctx context.Context, vpcCRN string, dnsID string, dnsZone string) error { 464 createPermittedNetworkOptions := c.dnsServicesAPI.NewCreatePermittedNetworkOptions(dnsID, dnsZone) 465 permittedNetwork, err := c.dnsServicesAPI.NewPermittedNetworkVpc(vpcCRN) 466 if err != nil { 467 return err 468 } 469 470 createPermittedNetworkOptions.SetPermittedNetwork(permittedNetwork) 471 createPermittedNetworkOptions.SetType("vpc") 472 473 _, _, err = c.dnsServicesAPI.CreatePermittedNetworkWithContext(ctx, createPermittedNetworkOptions) 474 if err != nil { 475 return err 476 } 477 return nil 478 } 479 480 // CreateDNSRecord Creates a DNS CNAME record in the given base domain and CRN. 481 func (c *Client) CreateDNSRecord(ctx context.Context, publish types.PublishingStrategy, crnstr string, baseDomain string, hostname string, cname string) error { 482 switch publish { 483 case types.InternalPublishingStrategy: 484 return c.createPrivateDNSRecord(ctx, crnstr, baseDomain, hostname, cname) 485 case types.ExternalPublishingStrategy: 486 return c.createPublicDNSRecord(ctx, crnstr, baseDomain, hostname, cname) 487 default: 488 return fmt.Errorf("publish strategy %q not supported", publish) 489 } 490 } 491 492 func (c *Client) createPublicDNSRecord(ctx context.Context, crnstr string, baseDomain string, hostname string, cname string) error { 493 logrus.Debugf("createDNSRecord: crnstr = %s, hostname = %s, cname = %s", crnstr, hostname, cname) 494 495 var ( 496 zoneID string 497 err error 498 authenticator *core.IamAuthenticator 499 globalOptions *dnsrecordsv1.DnsRecordsV1Options 500 dnsRecordService *dnsrecordsv1.DnsRecordsV1 501 ) 502 503 // Get CIS zone ID by name 504 zoneID, err = c.GetDNSZoneIDByName(ctx, baseDomain, types.ExternalPublishingStrategy) 505 if err != nil { 506 logrus.Errorf("c.GetDNSZoneIDByName returns %v", err) 507 return err 508 } 509 logrus.Debugf("CreatePublicDNSRecord: zoneID = %s", zoneID) 510 511 authenticator = &core.IamAuthenticator{ 512 ApiKey: c.APIKey, 513 } 514 globalOptions = &dnsrecordsv1.DnsRecordsV1Options{ 515 Authenticator: authenticator, 516 Crn: ptr.To(crnstr), 517 ZoneIdentifier: ptr.To(zoneID), 518 } 519 dnsRecordService, err = dnsrecordsv1.NewDnsRecordsV1(globalOptions) 520 if err != nil { 521 logrus.Errorf("dnsrecordsv1.NewDnsRecordsV1 returns %v", err) 522 return err 523 } 524 logrus.Debugf("CreatePublicDNSRecord: dnsRecordService = %+v", dnsRecordService) 525 526 createOptions := dnsRecordService.NewCreateDnsRecordOptions() 527 createOptions.SetName(hostname) 528 createOptions.SetType(dnsrecordsv1.CreateDnsRecordOptions_Type_Cname) 529 createOptions.SetContent(cname) 530 531 result, response, err := dnsRecordService.CreateDnsRecord(createOptions) 532 if err != nil { 533 logrus.Errorf("dnsRecordService.CreateDnsRecord returns %v", err) 534 return err 535 } 536 logrus.Debugf("createPublicDNSRecord: Result.ID = %v, RawResult = %v", *result.Result.ID, response.RawResult) 537 538 return nil 539 } 540 541 func (c *Client) createPrivateDNSRecord(ctx context.Context, crnstr string, baseDomain string, hostname string, cname string) error { 542 logrus.Debugf("createPrivateDNSRecord: crnstr = %s, hostname = %s, cname = %s", crnstr, hostname, cname) 543 544 zoneID, err := c.GetDNSZoneIDByName(ctx, baseDomain, types.InternalPublishingStrategy) 545 if err != nil { 546 logrus.Errorf("c.GetDNSZoneIDByName returns %v", err) 547 return err 548 } 549 logrus.Debugf("createPrivateDNSRecord: zoneID = %s", zoneID) 550 551 dnsCRN, err := crn.Parse(crnstr) 552 if err != nil { 553 return fmt.Errorf("failed to parse DNSInstanceCRN: %w", err) 554 } 555 556 rdataCnameRecord, err := c.dnsServicesAPI.NewResourceRecordInputRdataRdataCnameRecord(cname) 557 if err != nil { 558 return fmt.Errorf("NewResourceRecordInputRdataRdataCnameRecord failed: %w", err) 559 } 560 createOptions := c.dnsServicesAPI.NewCreateResourceRecordOptions(dnsCRN.ServiceInstance, zoneID) 561 createOptions.SetRdata(rdataCnameRecord) 562 createOptions.SetTTL(120) 563 createOptions.SetName(hostname) 564 createOptions.SetType("CNAME") 565 result, resp, err := c.dnsServicesAPI.CreateResourceRecord(createOptions) 566 if err != nil { 567 logrus.Errorf("dnsRecordService.CreateResourceRecord returns %v", err) 568 return err 569 } 570 logrus.Debugf("createPrivateDNSRecord: result.ID = %v, resp.RawResult = %v", *result.ID, resp.RawResult) 571 572 return nil 573 } 574 575 // GetVPCByName gets a VPC by its name. 576 func (c *Client) GetVPCByName(ctx context.Context, vpcName string) (*vpcv1.VPC, error) { 577 _, cancel := context.WithTimeout(ctx, 1*time.Minute) 578 defer cancel() 579 580 listRegionsOptions := c.vpcAPI.NewListRegionsOptions() 581 listRegionsResponse, _, err := c.vpcAPI.ListRegionsWithContext(ctx, listRegionsOptions) 582 if err != nil { 583 return nil, fmt.Errorf("failed to list vpc regions: %w", err) 584 } 585 586 for _, region := range listRegionsResponse.Regions { 587 err := c.vpcAPI.SetServiceURL(fmt.Sprintf("%s/v1", *region.Endpoint)) 588 if err != nil { 589 return nil, fmt.Errorf("failed to set vpc api service url: %w", err) 590 } 591 592 vpcs, detailedResponse, err := c.vpcAPI.ListVpcsWithContext(ctx, c.vpcAPI.NewListVpcsOptions()) 593 if err != nil { 594 if detailedResponse.GetStatusCode() != http.StatusNotFound { 595 return nil, err 596 } 597 } else { 598 for _, vpc := range vpcs.Vpcs { 599 if *vpc.Name == vpcName { 600 return &vpc, nil 601 } 602 } 603 } 604 } 605 606 return nil, errors.New("failed to find VPC") 607 } 608 609 // GetPublicGatewayByVPC gets all PublicGateways in a region 610 func (c *Client) GetPublicGatewayByVPC(ctx context.Context, vpcName string) (*vpcv1.PublicGateway, error) { 611 _, cancel := context.WithTimeout(ctx, 1*time.Minute) 612 defer cancel() 613 614 vpc, err := c.GetVPCByName(ctx, vpcName) 615 if err != nil { 616 return nil, fmt.Errorf("failed to get VPC: %w", err) 617 } 618 619 vpcCRN, err := crn.Parse(*vpc.CRN) 620 if err != nil { 621 return nil, fmt.Errorf("failed to parse VPC CRN: %w", err) 622 } 623 624 err = c.SetVPCServiceURLForRegion(ctx, vpcCRN.Region) 625 if err != nil { 626 return nil, err 627 } 628 629 listPublicGatewaysOptions := c.vpcAPI.NewListPublicGatewaysOptions() 630 publicGatewayCollection, detailedResponse, err := c.vpcAPI.ListPublicGatewaysWithContext(ctx, listPublicGatewaysOptions) 631 if err != nil { 632 return nil, err 633 } else if detailedResponse.GetStatusCode() == http.StatusNotFound { 634 return nil, errors.New("failed to find publicGateways") 635 } 636 for _, gw := range publicGatewayCollection.PublicGateways { 637 if *vpc.ID == *gw.VPC.ID { 638 return &gw, nil 639 } 640 } 641 642 return nil, nil 643 } 644 645 // GetVPCSubnets retrieves all subnets in the given VPC. 646 func (c *Client) GetVPCSubnets(ctx context.Context, vpcID string) ([]vpcv1.Subnet, error) { 647 listSubnetsOptions := c.vpcAPI.NewListSubnetsOptions() 648 listSubnetsOptions.VPCID = &vpcID 649 subnets, _, err := c.vpcAPI.ListSubnetsWithContext(ctx, listSubnetsOptions) 650 if err != nil { 651 return nil, err 652 } 653 654 return subnets.Subnets, nil 655 } 656 657 // GetSubnetByName gets a VPC Subnet by its name and region. 658 func (c *Client) GetSubnetByName(ctx context.Context, subnetName string, region string) (*vpcv1.Subnet, error) { 659 _, cancel := context.WithTimeout(ctx, 1*time.Minute) 660 defer cancel() 661 662 err := c.SetVPCServiceURLForRegion(ctx, region) 663 if err != nil { 664 return nil, err 665 } 666 667 listSubnetsOptions := c.vpcAPI.NewListSubnetsOptions() 668 subnetCollection, detailedResponse, err := c.vpcAPI.ListSubnetsWithContext(ctx, listSubnetsOptions) 669 if err != nil { 670 return nil, err 671 } else if detailedResponse.GetStatusCode() == http.StatusNotFound { 672 return nil, errors.New("failed to find VPC Subnet") 673 } 674 for _, subnet := range subnetCollection.Subnets { 675 if subnetName == *subnet.Name { 676 return &subnet, nil 677 } 678 } 679 680 return nil, errors.New("failed to find VPC Subnet") 681 } 682 683 func (c *Client) loadResourceManagementAPI() error { 684 authenticator := &core.IamAuthenticator{ 685 ApiKey: c.APIKey, 686 } 687 options := &resourcemanagerv2.ResourceManagerV2Options{ 688 Authenticator: authenticator, 689 } 690 resourceManagerV2Service, err := resourcemanagerv2.NewResourceManagerV2(options) 691 if err != nil { 692 return err 693 } 694 c.managementAPI = resourceManagerV2Service 695 return nil 696 } 697 698 func (c *Client) loadResourceControllerAPI() error { 699 authenticator := &core.IamAuthenticator{ 700 ApiKey: c.APIKey, 701 } 702 options := &resourcecontrollerv2.ResourceControllerV2Options{ 703 Authenticator: authenticator, 704 } 705 resourceControllerV2Service, err := resourcecontrollerv2.NewResourceControllerV2(options) 706 if err != nil { 707 return err 708 } 709 c.controllerAPI = resourceControllerV2Service 710 return nil 711 } 712 713 func (c *Client) loadVPCV1API() error { 714 authenticator := &core.IamAuthenticator{ 715 ApiKey: c.APIKey, 716 } 717 vpcService, err := vpcv1.NewVpcV1(&vpcv1.VpcV1Options{ 718 Authenticator: authenticator, 719 }) 720 if err != nil { 721 return err 722 } 723 c.vpcAPI = vpcService 724 return nil 725 } 726 727 func (c *Client) loadDNSServicesAPI() error { 728 authenticator := &core.IamAuthenticator{ 729 ApiKey: c.APIKey, 730 } 731 dnsService, err := dnssvcsv1.NewDnsSvcsV1(&dnssvcsv1.DnsSvcsV1Options{ 732 Authenticator: authenticator, 733 }) 734 if err != nil { 735 return err 736 } 737 c.dnsServicesAPI = dnsService 738 return nil 739 } 740 741 func (c *Client) loadTransitGatewayAPI() error { 742 authenticator := &core.IamAuthenticator{ 743 ApiKey: c.APIKey, 744 } 745 versionDate := "2023-07-04" 746 tgSvc, err := transitgatewayapisv1.NewTransitGatewayApisV1(&transitgatewayapisv1.TransitGatewayApisV1Options{ 747 Authenticator: authenticator, 748 Version: &versionDate, 749 }) 750 if err != nil { 751 return err 752 } 753 c.transitGatewayAPI = tgSvc 754 return nil 755 } 756 757 // SetVPCServiceURLForRegion will set the VPC Service URL to a specific IBM Cloud Region, in order to access Region scoped resources 758 func (c *Client) SetVPCServiceURLForRegion(ctx context.Context, region string) error { 759 regionOptions := c.vpcAPI.NewGetRegionOptions(region) 760 vpcRegion, _, err := c.vpcAPI.GetRegionWithContext(ctx, regionOptions) 761 if err != nil { 762 return err 763 } 764 err = c.vpcAPI.SetServiceURL(fmt.Sprintf("%s/v1", *vpcRegion.Endpoint)) 765 if err != nil { 766 return err 767 } 768 return nil 769 } 770 771 // GetAuthenticatorAPIKeyDetails gets detailed information on the API key used 772 // for authentication to the IBM Cloud APIs. 773 func (c *Client) GetAuthenticatorAPIKeyDetails(ctx context.Context) (*iamidentityv1.APIKey, error) { 774 authenticator := &core.IamAuthenticator{ 775 ApiKey: c.APIKey, 776 } 777 iamIdentityService, err := iamidentityv1.NewIamIdentityV1(&iamidentityv1.IamIdentityV1Options{ 778 Authenticator: authenticator, 779 }) 780 if err != nil { 781 return nil, err 782 } 783 784 options := iamIdentityService.NewGetAPIKeysDetailsOptions() 785 options.SetIamAPIKey(c.APIKey) 786 details, _, err := iamIdentityService.GetAPIKeysDetailsWithContext(ctx, options) 787 if err != nil { 788 return nil, err 789 } 790 // NOTE: details.Apikey 791 // https://cloud.ibm.com/apidocs/iam-identity-token-api?code=go#get-api-keys-details 792 // This property only contains the API key value for the following cases: create an API key, 793 // update a service ID API key that stores the API key value as retrievable, or get a service 794 // ID API key that stores the API key value as retrievable. All other operations don't return 795 // the API key value, for example all user API key related operations, except for create, 796 // don't contain the API key value. 797 return details, nil 798 } 799 800 // GetAPIKey returns the PowerVS API key 801 func (c *Client) GetAPIKey() string { 802 return c.APIKey 803 } 804 805 // GetVPCs gets all VPCs in a region. 806 func (c *Client) GetVPCs(ctx context.Context, region string) ([]vpcv1.VPC, error) { 807 ctx, cancel := context.WithTimeout(ctx, 1*time.Minute) 808 defer cancel() 809 810 err := c.SetVPCServiceURLForRegion(ctx, region) 811 if err != nil { 812 return nil, fmt.Errorf("failed to set vpc api service url: %w", err) 813 } 814 815 vpcs, _, err := c.vpcAPI.ListVpcs(c.vpcAPI.NewListVpcsOptions()) 816 if err != nil { 817 return nil, err 818 } 819 820 return vpcs.Vpcs, nil 821 } 822 823 // ListResourceGroups returns a list of resource groups. 824 func (c *Client) ListResourceGroups(ctx context.Context) (*resourcemanagerv2.ResourceGroupList, error) { 825 listResourceGroupsOptions := c.managementAPI.NewListResourceGroupsOptions() 826 listResourceGroupsOptions.AccountID = &c.BXCli.User.Account 827 828 resourceGroups, _, err := c.managementAPI.ListResourceGroups(listResourceGroupsOptions) 829 if err != nil { 830 return nil, err 831 } 832 833 return resourceGroups, err 834 } 835 836 const ( 837 // resource Id for Power Systems Virtual Server in the Global catalog. 838 powerIAASResourceID = "abd259f0-9990-11e8-acc8-b9f54a8f1661" 839 ) 840 841 // ListServiceInstances lists all service instances in the cloud. 842 func (c *Client) ListServiceInstances(ctx context.Context) ([]string, error) { 843 var ( 844 serviceInstances []string 845 options *resourcecontrollerv2.ListResourceInstancesOptions 846 resources *resourcecontrollerv2.ResourceInstancesList 847 err error 848 perPage int64 = 10 849 moreData = true 850 nextURL *string 851 groupID = c.BXCli.PowerVSResourceGroup 852 ) 853 854 // If the user passes in a human readable group id, then we need to convert it to a UUID 855 listGroupOptions := c.managementAPI.NewListResourceGroupsOptions() 856 listGroupOptions.AccountID = &c.BXCli.User.Account 857 groups, _, err := c.managementAPI.ListResourceGroupsWithContext(ctx, listGroupOptions) 858 if err != nil { 859 return nil, fmt.Errorf("failed to list resource groups: %w", err) 860 } 861 for _, group := range groups.Resources { 862 if *group.Name == groupID { 863 groupID = *group.ID 864 } 865 } 866 867 options = c.controllerAPI.NewListResourceInstancesOptions() 868 options.SetResourceGroupID(groupID) 869 // resource ID for Power Systems Virtual Server in the Global catalog 870 options.SetResourceID(powerIAASResourceID) 871 options.SetLimit(perPage) 872 873 for moreData { 874 resources, _, err = c.controllerAPI.ListResourceInstancesWithContext(ctx, options) 875 if err != nil { 876 return nil, fmt.Errorf("failed to list resource instances: %w", err) 877 } 878 879 for _, resource := range resources.Resources { 880 var ( 881 getResourceOptions *resourcecontrollerv2.GetResourceInstanceOptions 882 resourceInstance *resourcecontrollerv2.ResourceInstance 883 response *core.DetailedResponse 884 ) 885 886 getResourceOptions = c.controllerAPI.NewGetResourceInstanceOptions(*resource.ID) 887 888 resourceInstance, response, err = c.controllerAPI.GetResourceInstance(getResourceOptions) 889 if err != nil { 890 return nil, fmt.Errorf("failed to get instance: %w", err) 891 } 892 if response != nil && response.StatusCode == http.StatusNotFound || response.StatusCode == http.StatusInternalServerError { 893 continue 894 } 895 896 if resourceInstance.Type != nil && (*resourceInstance.Type == serviceInstanceType || *resourceInstance.Type == compositeInstanceType) { 897 serviceInstances = append(serviceInstances, fmt.Sprintf("%s %s", *resource.Name, *resource.GUID)) 898 } 899 } 900 901 // Based on: https://cloud.ibm.com/apidocs/resource-controller/resource-controller?code=go#list-resource-instances 902 nextURL, err = core.GetQueryParam(resources.NextURL, "start") 903 if err != nil { 904 return nil, fmt.Errorf("failed to GetQueryParam on start: %w", err) 905 } 906 if nextURL == nil { 907 options.SetStart("") 908 } else { 909 options.SetStart(*nextURL) 910 } 911 912 moreData = *resources.RowsCount == perPage 913 } 914 915 return serviceInstances, nil 916 } 917 918 // ServiceInstanceGUIDToName returns the name of the matching service instance GUID which was passed in. 919 func (c *Client) ServiceInstanceGUIDToName(ctx context.Context, id string) (string, error) { 920 var ( 921 options *resourcecontrollerv2.ListResourceInstancesOptions 922 resources *resourcecontrollerv2.ResourceInstancesList 923 err error 924 perPage int64 = 10 925 moreData = true 926 nextURL *string 927 groupID = c.BXCli.PowerVSResourceGroup 928 ) 929 930 // If the user passes in a human readable group id, then we need to convert it to a UUID 931 listGroupOptions := c.managementAPI.NewListResourceGroupsOptions() 932 listGroupOptions.AccountID = &c.BXCli.User.Account 933 groups, _, err := c.managementAPI.ListResourceGroupsWithContext(ctx, listGroupOptions) 934 if err != nil { 935 return "", fmt.Errorf("failed to list resource groups: %w", err) 936 } 937 for _, group := range groups.Resources { 938 if *group.Name == groupID { 939 groupID = *group.ID 940 } 941 } 942 943 options = c.controllerAPI.NewListResourceInstancesOptions() 944 options.SetResourceGroupID(groupID) 945 // resource ID for Power Systems Virtual Server in the Global catalog 946 options.SetResourceID(powerIAASResourceID) 947 options.SetLimit(perPage) 948 949 for moreData { 950 resources, _, err = c.controllerAPI.ListResourceInstancesWithContext(ctx, options) 951 if err != nil { 952 return "", fmt.Errorf("failed to list resource instances: %w", err) 953 } 954 955 for _, resource := range resources.Resources { 956 var ( 957 getResourceOptions *resourcecontrollerv2.GetResourceInstanceOptions 958 resourceInstance *resourcecontrollerv2.ResourceInstance 959 response *core.DetailedResponse 960 ) 961 962 getResourceOptions = c.controllerAPI.NewGetResourceInstanceOptions(*resource.ID) 963 964 resourceInstance, response, err = c.controllerAPI.GetResourceInstance(getResourceOptions) 965 if err != nil { 966 return "", fmt.Errorf("failed to get instance: %w", err) 967 } 968 if response != nil && response.StatusCode == http.StatusNotFound || response.StatusCode == http.StatusInternalServerError { 969 continue 970 } 971 972 if resourceInstance.Type != nil && (*resourceInstance.Type == serviceInstanceType || *resourceInstance.Type == compositeInstanceType) { 973 if resourceInstance.GUID != nil && *resourceInstance.GUID == id { 974 if resourceInstance.Name == nil { 975 return "", nil 976 } 977 return *resourceInstance.Name, nil 978 } 979 } 980 } 981 982 // Based on: https://cloud.ibm.com/apidocs/resource-controller/resource-controller?code=go#list-resource-instances 983 nextURL, err = core.GetQueryParam(resources.NextURL, "start") 984 if err != nil { 985 return "", fmt.Errorf("failed to GetQueryParam on start: %w", err) 986 } 987 if nextURL == nil { 988 options.SetStart("") 989 } else { 990 options.SetStart(*nextURL) 991 } 992 993 moreData = *resources.RowsCount == perPage 994 } 995 996 return "", nil 997 } 998 999 // ServiceInstanceNameToGUID returns the name of the matching service instance GUID which was passed in. 1000 func (c *Client) ServiceInstanceNameToGUID(ctx context.Context, name string) (string, error) { 1001 var ( 1002 options *resourcecontrollerv2.ListResourceInstancesOptions 1003 resources *resourcecontrollerv2.ResourceInstancesList 1004 err error 1005 perPage int64 = 10 1006 moreData = true 1007 nextURL *string 1008 groupID = c.BXCli.PowerVSResourceGroup 1009 ) 1010 1011 // If the user passes in a human readable group id, then we need to convert it to a UUID 1012 listGroupOptions := c.managementAPI.NewListResourceGroupsOptions() 1013 listGroupOptions.AccountID = &c.BXCli.User.Account 1014 groups, _, err := c.managementAPI.ListResourceGroupsWithContext(ctx, listGroupOptions) 1015 if err != nil { 1016 return "", fmt.Errorf("failed to list resource groups: %w", err) 1017 } 1018 for _, group := range groups.Resources { 1019 if *group.Name == groupID { 1020 groupID = *group.ID 1021 } 1022 } 1023 1024 options = c.controllerAPI.NewListResourceInstancesOptions() 1025 options.SetResourceGroupID(groupID) 1026 // resource ID for Power Systems Virtual Server in the Global catalog 1027 options.SetResourceID(powerIAASResourceID) 1028 options.SetLimit(perPage) 1029 1030 for moreData { 1031 resources, _, err = c.controllerAPI.ListResourceInstancesWithContext(ctx, options) 1032 if err != nil { 1033 return "", fmt.Errorf("failed to list resource instances: %w", err) 1034 } 1035 1036 for _, resource := range resources.Resources { 1037 var ( 1038 getResourceOptions *resourcecontrollerv2.GetResourceInstanceOptions 1039 resourceInstance *resourcecontrollerv2.ResourceInstance 1040 response *core.DetailedResponse 1041 ) 1042 1043 getResourceOptions = c.controllerAPI.NewGetResourceInstanceOptions(*resource.ID) 1044 1045 resourceInstance, response, err = c.controllerAPI.GetResourceInstance(getResourceOptions) 1046 if err != nil { 1047 return "", fmt.Errorf("failed to get instance: %w", err) 1048 } 1049 if response != nil && response.StatusCode == http.StatusNotFound || response.StatusCode == http.StatusInternalServerError { 1050 continue 1051 } 1052 1053 if resourceInstance.Type != nil && (*resourceInstance.Type == serviceInstanceType || *resourceInstance.Type == compositeInstanceType) { 1054 if resourceInstance.Name != nil && *resourceInstance.Name == name { 1055 if resourceInstance.GUID == nil { 1056 return "", nil 1057 } 1058 return *resourceInstance.GUID, nil 1059 } 1060 } 1061 } 1062 1063 // Based on: https://cloud.ibm.com/apidocs/resource-controller/resource-controller?code=go#list-resource-instances 1064 nextURL, err = core.GetQueryParam(resources.NextURL, "start") 1065 if err != nil { 1066 return "", fmt.Errorf("failed to GetQueryParam on start: %w", err) 1067 } 1068 if nextURL == nil { 1069 options.SetStart("") 1070 } else { 1071 options.SetStart(*nextURL) 1072 } 1073 1074 moreData = *resources.RowsCount == perPage 1075 } 1076 1077 return "", nil 1078 } 1079 1080 // GetDatacenterCapabilities retrieves the capabilities of the specified datacenter. 1081 func (c *Client) GetDatacenterCapabilities(ctx context.Context, region string) (map[string]bool, error) { 1082 var err error 1083 if c.BXCli.PISession == nil { 1084 err = c.BXCli.NewPISession() 1085 if err != nil { 1086 return nil, fmt.Errorf("failed to initialize PISession in GetDatacenterCapabilities: %w", err) 1087 } 1088 } 1089 params := datacenters.NewV1DatacentersGetParamsWithContext(ctx).WithDatacenterRegion(region) 1090 getOk, err := c.BXCli.PISession.Power.Datacenters.V1DatacentersGet(params) 1091 if err != nil { 1092 return nil, fmt.Errorf("failed to get datacenter capabilities: %w", err) 1093 } 1094 return getOk.Payload.Capabilities, nil 1095 } 1096 1097 // GetAttachedTransitGateway finds an existing Transit Gateway attached to the provided PowerVS cloud instance. 1098 func (c *Client) GetAttachedTransitGateway(ctx context.Context, svcInsID string) (string, error) { 1099 var ( 1100 gateways []transitgatewayapisv1.TransitGateway 1101 gateway transitgatewayapisv1.TransitGateway 1102 err error 1103 conns []transitgatewayapisv1.TransitConnection 1104 conn transitgatewayapisv1.TransitConnection 1105 ) 1106 gateways, err = c.getTransitGateways(ctx) 1107 if err != nil { 1108 return "", err 1109 } 1110 for _, gateway = range gateways { 1111 conns, err = c.getTransitConnections(ctx, *gateway.ID) 1112 if err != nil { 1113 return "", err 1114 } 1115 for _, conn = range conns { 1116 if *conn.NetworkType == "power_virtual_server" && strings.Contains(*conn.NetworkID, svcInsID) { 1117 return *conn.TransitGateway.ID, nil 1118 } 1119 } 1120 } 1121 return "", nil 1122 } 1123 1124 // GetTGConnectionVPC checks if the VPC subnet is attached to the provided Transit Gateway. 1125 func (c *Client) GetTGConnectionVPC(ctx context.Context, gatewayID string, vpcSubnetID string) (string, error) { 1126 conns, err := c.getTransitConnections(ctx, gatewayID) 1127 if err != nil { 1128 return "", err 1129 } 1130 for _, conn := range conns { 1131 if *conn.NetworkType == "vpc" && strings.Contains(*conn.NetworkID, vpcSubnetID) { 1132 return *conn.ID, nil 1133 } 1134 } 1135 return "", nil 1136 } 1137 1138 func (c *Client) getTransitGateways(ctx context.Context) ([]transitgatewayapisv1.TransitGateway, error) { 1139 var ( 1140 listTransitGatewaysOptions *transitgatewayapisv1.ListTransitGatewaysOptions 1141 gatewayCollection *transitgatewayapisv1.TransitGatewayCollection 1142 response *core.DetailedResponse 1143 err error 1144 perPage int64 = 32 1145 moreData = true 1146 ) 1147 1148 listTransitGatewaysOptions = c.transitGatewayAPI.NewListTransitGatewaysOptions() 1149 listTransitGatewaysOptions.Limit = &perPage 1150 1151 result := []transitgatewayapisv1.TransitGateway{} 1152 1153 for moreData { 1154 // https://github.com/IBM/networking-go-sdk/blob/master/transitgatewayapisv1/transit_gateway_apis_v1.go#L184 1155 gatewayCollection, response, err = c.transitGatewayAPI.ListTransitGatewaysWithContext(ctx, listTransitGatewaysOptions) 1156 if err != nil { 1157 return nil, fmt.Errorf("failed to list transit gateways: %w and the respose is: %s", err, response) 1158 } 1159 1160 result = append(result, gatewayCollection.TransitGateways...) 1161 1162 if gatewayCollection.Next != nil { 1163 listTransitGatewaysOptions.SetStart(*gatewayCollection.Next.Start) 1164 } 1165 1166 moreData = gatewayCollection.Next != nil 1167 } 1168 1169 return result, nil 1170 } 1171 1172 func (c *Client) getTransitConnections(ctx context.Context, tgID string) ([]transitgatewayapisv1.TransitConnection, error) { 1173 var ( 1174 listConnectionsOptions *transitgatewayapisv1.ListConnectionsOptions 1175 connectionCollection *transitgatewayapisv1.TransitConnectionCollection 1176 response *core.DetailedResponse 1177 err error 1178 perPage int64 = 32 1179 moreData = true 1180 ) 1181 1182 listConnectionsOptions = c.transitGatewayAPI.NewListConnectionsOptions() 1183 listConnectionsOptions.Limit = &perPage 1184 1185 result := []transitgatewayapisv1.TransitConnection{} 1186 1187 for moreData { 1188 connectionCollection, response, err = c.transitGatewayAPI.ListConnectionsWithContext(ctx, listConnectionsOptions) 1189 if err != nil { 1190 return nil, fmt.Errorf("failed to list transit gateways: %w and the respose is: %s", err, response) 1191 } 1192 1193 result = append(result, connectionCollection.Connections...) 1194 1195 if connectionCollection.Next != nil { 1196 listConnectionsOptions.SetStart(*connectionCollection.Next.Start) 1197 } 1198 1199 moreData = connectionCollection.Next != nil 1200 } 1201 1202 return result, nil 1203 } 1204 1205 // ListSecurityGroupRules returns a list of the security group rules. 1206 func (c *Client) ListSecurityGroupRules(ctx context.Context, securityGroupID string) (*vpcv1.SecurityGroupRuleCollection, error) { 1207 logrus.Debugf("ListSecurityGroupRules: securityGroupID = %s", securityGroupID) 1208 1209 var ( 1210 vpcOptions *vpcv1.GetVPCOptions 1211 vpc *vpcv1.VPC 1212 optionsLSGR *vpcv1.ListSecurityGroupRulesOptions 1213 result *vpcv1.SecurityGroupRuleCollection 1214 response *core.DetailedResponse 1215 err error 1216 ) 1217 1218 vpcOptions = c.vpcAPI.NewGetVPCOptions(securityGroupID) 1219 1220 vpc, response, err = c.vpcAPI.GetVPC(vpcOptions) 1221 if err != nil { 1222 return nil, fmt.Errorf("failure ListSecurityGroupRules GetVPC returns %w, response is %+v", err, response) 1223 } 1224 logrus.Debugf("ListSecurityGroupRules: vpc = %+v", vpc) 1225 1226 optionsLSGR = c.vpcAPI.NewListSecurityGroupRulesOptions(*vpc.DefaultSecurityGroup.ID) 1227 1228 result, response, err = c.vpcAPI.ListSecurityGroupRulesWithContext(ctx, optionsLSGR) 1229 if err != nil { 1230 logrus.Debugf("ListSecurityGroupRules: result = %+v, response = %+v, err = %v", result, response, err) 1231 } 1232 1233 return result, err 1234 } 1235 1236 // AddSecurityGroupRule adds a security group rule to an existing security group. 1237 func (c *Client) AddSecurityGroupRule(ctx context.Context, securityGroupID string, rule *vpcv1.SecurityGroupRulePrototype) error { 1238 logrus.Debugf("AddSecurityGroupRule: securityGroupID = %s, rule = %+v", securityGroupID, *rule) 1239 1240 var ( 1241 vpcOptions *vpcv1.GetVPCOptions 1242 vpc *vpcv1.VPC 1243 optionsCSGR *vpcv1.CreateSecurityGroupRuleOptions 1244 result vpcv1.SecurityGroupRuleIntf 1245 response *core.DetailedResponse 1246 err error 1247 ) 1248 1249 vpcOptions = c.vpcAPI.NewGetVPCOptions(securityGroupID) 1250 1251 vpc, response, err = c.vpcAPI.GetVPC(vpcOptions) 1252 if err != nil { 1253 return fmt.Errorf("failure AddSecurityGroupRule GetVPC returns %w, response is %+v", err, response) 1254 } 1255 logrus.Debugf("AddSecurityGroupRule: vpc = %+v", vpc) 1256 1257 optionsCSGR = &vpcv1.CreateSecurityGroupRuleOptions{} 1258 optionsCSGR.SetSecurityGroupID(*vpc.DefaultSecurityGroup.ID) 1259 optionsCSGR.SetSecurityGroupRulePrototype(rule) 1260 1261 result, response, err = c.vpcAPI.CreateSecurityGroupRuleWithContext(ctx, optionsCSGR) 1262 if err != nil { 1263 logrus.Debugf("AddSecurityGroupRule: result = %+v, response = %+v, err = %v", result, response, err) 1264 } 1265 1266 return err 1267 } 1268 1269 // CreateSSHKey creates a SSH key in the PowerVS Workspace for the workers to use. 1270 func (c *Client) CreateSSHKey(ctx context.Context, serviceInstance string, zone string, sshKeyName string, sshKey string) error { 1271 logrus.Debugf("CreateSSHKey: serviceInstance = %s, sshKeyName = %s sshKey = %s", serviceInstance, sshKeyName, sshKey) 1272 1273 var ( 1274 user *User 1275 authenticator core.Authenticator 1276 options *ibmpisession.IBMPIOptions 1277 piSession *ibmpisession.IBMPISession 1278 keyClient *instance.IBMPIKeyClient 1279 sshKeyInput *models.SSHKey 1280 err error 1281 ) 1282 1283 user, err = FetchUserDetails(c.APIKey) 1284 if err != nil { 1285 return fmt.Errorf("createSSHKey: failed to fetch user details %w", err) 1286 } 1287 1288 authenticator = &core.IamAuthenticator{ 1289 ApiKey: c.APIKey, 1290 } 1291 err = authenticator.Validate() 1292 if err != nil { 1293 return fmt.Errorf("createSSHKey: authenticator failed validate %w", err) 1294 } 1295 1296 options = &ibmpisession.IBMPIOptions{ 1297 Authenticator: authenticator, 1298 Debug: false, 1299 UserAccount: user.Account, 1300 Zone: zone, 1301 } 1302 1303 piSession, err = ibmpisession.NewIBMPISession(options) 1304 if err != nil { 1305 return fmt.Errorf("createSSHKey: ibmpisession.New: %w", err) 1306 } 1307 if piSession == nil { 1308 return fmt.Errorf("createSSHKey: piSession is nil") 1309 } 1310 logrus.Debugf("CreateSSHKey: piSession = %+v", piSession) 1311 1312 keyClient = instance.NewIBMPIKeyClient(ctx, piSession, serviceInstance) 1313 logrus.Debugf("CreateSSHKey: keyClient = %+v", keyClient) 1314 1315 sshKeyInput = &models.SSHKey{ 1316 Name: ptr.To(sshKeyName), 1317 SSHKey: ptr.To(sshKey), 1318 } 1319 logrus.Debugf("CreateSSHKey: sshKeyInput = %+v", sshKeyInput) 1320 1321 _, err = keyClient.Create(sshKeyInput) 1322 if err != nil { 1323 return fmt.Errorf("createSSHKey: failed to create the ssh key %w", err) 1324 } 1325 1326 return nil 1327 } 1328 1329 // AddIPToLoadBalancerPool adds a server to a load balancer pool for the specified port. 1330 // @TODO Remove once https://github.com/kubernetes-sigs/cluster-api-provider-ibmcloud/issues/1679 is fixed. 1331 func (c *Client) AddIPToLoadBalancerPool(ctx context.Context, lbID string, poolName string, port int64, ip string) error { 1332 var ( 1333 glbOptions *vpcv1.GetLoadBalancerOptions 1334 llbpOptions *vpcv1.ListLoadBalancerPoolsOptions 1335 llbpmOptions *vpcv1.ListLoadBalancerPoolMembersOptions 1336 clbpmOptions *vpcv1.CreateLoadBalancerPoolMemberOptions 1337 lb *vpcv1.LoadBalancer 1338 lbPools *vpcv1.LoadBalancerPoolCollection 1339 lbPool vpcv1.LoadBalancerPool 1340 lbPoolMembers *vpcv1.LoadBalancerPoolMemberCollection 1341 lbpmtp *vpcv1.LoadBalancerPoolMemberTargetPrototypeIP 1342 lbpm *vpcv1.LoadBalancerPoolMember 1343 response *core.DetailedResponse 1344 err error 1345 ) 1346 1347 // Make sure the load balancer exists 1348 glbOptions = c.vpcAPI.NewGetLoadBalancerOptions(lbID) 1349 1350 lb, response, err = c.vpcAPI.GetLoadBalancerWithContext(ctx, glbOptions) 1351 if err != nil { 1352 return fmt.Errorf("failed to get load balancer and the response = %+v, err = %w", response, err) 1353 } 1354 logrus.Debugf("AddIPToLoadBalancerPool: GLBWC lb = %+v", lb) 1355 1356 // Query the existing load balancer pools 1357 llbpOptions = c.vpcAPI.NewListLoadBalancerPoolsOptions(lbID) 1358 1359 lbPools, response, err = c.vpcAPI.ListLoadBalancerPoolsWithContext(ctx, llbpOptions) 1360 if err != nil { 1361 return fmt.Errorf("failed to list the load balancer pools and the response = %+v, err = %w", 1362 response, 1363 err) 1364 } 1365 1366 // Find the pool with the specified name 1367 for _, pool := range lbPools.Pools { 1368 logrus.Debugf("AddIPToLoadBalancerPool: pool.ID = %v", *pool.ID) 1369 logrus.Debugf("AddIPToLoadBalancerPool: pool.Name = %v", *pool.Name) 1370 1371 if *pool.Name == poolName { 1372 lbPool = pool 1373 break 1374 } 1375 } 1376 if lbPool.ID == nil { 1377 return fmt.Errorf("could not find loadbalancer pool with name %s", poolName) 1378 } 1379 1380 // Query the load balancer pool members 1381 llbpmOptions = c.vpcAPI.NewListLoadBalancerPoolMembersOptions(lbID, *lbPool.ID) 1382 1383 lbPoolMembers, response, err = c.vpcAPI.ListLoadBalancerPoolMembersWithContext(ctx, llbpmOptions) 1384 if err != nil { 1385 return fmt.Errorf("failed to list load balancer pool members and the response = %+v, err = %w", 1386 response, 1387 err) 1388 } 1389 1390 // See if a member already exists with that IP 1391 for _, poolMember := range lbPoolMembers.Members { 1392 logrus.Debugf("AddIPToLoadBalancerPool: poolMember.ID = %s", *poolMember.ID) 1393 1394 switch pmt := poolMember.Target.(type) { 1395 case *vpcv1.LoadBalancerPoolMemberTarget: 1396 logrus.Debugf("AddIPToLoadBalancerPool: pmt.Address = %+v", *pmt.Address) 1397 if ip == *pmt.Address { 1398 logrus.Debugf("AddIPToLoadBalancerPool: found %s", ip) 1399 return nil 1400 } 1401 case *vpcv1.LoadBalancerPoolMemberTargetIP: 1402 logrus.Debugf("AddIPToLoadBalancerPool: pmt.Address = %+v", *pmt.Address) 1403 if ip == *pmt.Address { 1404 logrus.Debugf("AddIPToLoadBalancerPool: found %s", ip) 1405 return nil 1406 } 1407 case *vpcv1.LoadBalancerPoolMemberTargetInstanceReference: 1408 // No IP address, ignore 1409 default: 1410 logrus.Debugf("AddIPToLoadBalancerPool: unhandled type %T", poolMember.Target) 1411 } 1412 } 1413 1414 // Create a new member 1415 lbpmtp, err = c.vpcAPI.NewLoadBalancerPoolMemberTargetPrototypeIP(ip) 1416 if err != nil { 1417 return fmt.Errorf("could not create a new load balancer pool member, err = %w", err) 1418 } 1419 logrus.Debugf("AddIPToLoadBalancerPool: lbpmtp = %+v", *lbpmtp) 1420 1421 // Add that member to the pool 1422 clbpmOptions = c.vpcAPI.NewCreateLoadBalancerPoolMemberOptions(lbID, *lbPool.ID, port, lbpmtp) 1423 logrus.Debugf("AddIPToLoadBalancerPool: clbpmOptions = %+v", clbpmOptions) 1424 1425 return wait.PollUntilContextCancel(ctx, 1426 time.Second*10, 1427 false, 1428 func(ctx context.Context) (bool, error) { 1429 lbpm, response, err = c.vpcAPI.CreateLoadBalancerPoolMemberWithContext(ctx, clbpmOptions) 1430 if err != nil { 1431 logrus.Debugf("AddIPToLoadBalancerPool: could not add the load balancer pool member yet, err = %v", err) 1432 return false, nil 1433 } 1434 1435 logrus.Debugf("AddIPToLoadBalancerPool: CLBPMWC lbpm = %+v", lbpm) 1436 1437 return true, nil 1438 }) 1439 }