github.com/openshift/installer@v1.4.17/pkg/asset/installconfig/ibmcloud/client.go (about) 1 package ibmcloud 2 3 import ( 4 "context" 5 "fmt" 6 "net/http" 7 "os" 8 "strings" 9 "time" 10 11 ibmcrn "github.com/IBM-Cloud/bluemix-go/crn" 12 "github.com/IBM/go-sdk-core/v5/core" 13 kpclient "github.com/IBM/keyprotect-go-client" 14 "github.com/IBM/networking-go-sdk/dnsrecordsv1" 15 "github.com/IBM/networking-go-sdk/dnssvcsv1" 16 "github.com/IBM/networking-go-sdk/dnszonesv1" 17 "github.com/IBM/networking-go-sdk/zonesv1" 18 "github.com/IBM/platform-services-go-sdk/iamidentityv1" 19 "github.com/IBM/platform-services-go-sdk/resourcecontrollerv2" 20 "github.com/IBM/platform-services-go-sdk/resourcemanagerv2" 21 "github.com/IBM/vpc-go-sdk/vpcv1" 22 "github.com/pkg/errors" 23 "github.com/sirupsen/logrus" 24 25 configv1 "github.com/openshift/api/config/v1" 26 "github.com/openshift/installer/pkg/asset/installconfig/ibmcloud/responses" 27 "github.com/openshift/installer/pkg/types" 28 ibmcloudtypes "github.com/openshift/installer/pkg/types/ibmcloud" 29 ) 30 31 //go:generate mockgen -source=./client.go -destination=./mock/ibmcloudclient_generated.go -package=mock 32 33 // API represents the calls made to the API. 34 type API interface { 35 GetAPIKey() string 36 GetAuthenticatorAPIKeyDetails(ctx context.Context) (*iamidentityv1.APIKey, error) 37 GetCISInstance(ctx context.Context, crnstr string) (*resourcecontrollerv2.ResourceInstance, error) 38 GetDNSInstance(ctx context.Context, crnstr string) (*resourcecontrollerv2.ResourceInstance, error) 39 GetDNSInstancePermittedNetworks(ctx context.Context, dnsID string, dnsZone string) ([]string, error) 40 GetDedicatedHostByName(ctx context.Context, name string, region string) (*vpcv1.DedicatedHost, error) 41 GetDedicatedHostProfiles(ctx context.Context, region string) ([]vpcv1.DedicatedHostProfile, error) 42 GetDNSRecordsByName(ctx context.Context, crnstr string, zoneID string, recordName string) ([]dnsrecordsv1.DnsrecordDetails, error) 43 GetDNSZoneIDByName(ctx context.Context, name string, publish types.PublishingStrategy) (string, error) 44 GetDNSZones(ctx context.Context, publish types.PublishingStrategy) ([]responses.DNSZoneResponse, error) 45 GetEncryptionKey(ctx context.Context, keyCRN string) (*responses.EncryptionKeyResponse, error) 46 GetResourceGroups(ctx context.Context) ([]resourcemanagerv2.ResourceGroup, error) 47 GetResourceGroup(ctx context.Context, nameOrID string) (*resourcemanagerv2.ResourceGroup, error) 48 GetSubnet(ctx context.Context, subnetID string) (*vpcv1.Subnet, error) 49 GetSubnetByName(ctx context.Context, subnetName string, region string) (*vpcv1.Subnet, error) 50 GetVSIProfiles(ctx context.Context) ([]vpcv1.InstanceProfile, error) 51 GetVPC(ctx context.Context, vpcID string) (*vpcv1.VPC, error) 52 GetVPCs(ctx context.Context, region string) ([]vpcv1.VPC, error) 53 GetVPCByName(ctx context.Context, vpcName string) (*vpcv1.VPC, error) 54 GetVPCZonesForRegion(ctx context.Context, region string) ([]string, error) 55 SetVPCServiceURLForRegion(ctx context.Context, region string) error 56 } 57 58 // Client makes calls to the IBM Cloud API. 59 type Client struct { 60 apiKey string 61 managementAPI *resourcemanagerv2.ResourceManagerV2 62 controllerAPI *resourcecontrollerv2.ResourceControllerV2 63 vpcAPI *vpcv1.VpcV1 64 65 // A set of overriding endpoints for IBM Cloud Services 66 serviceEndpoints []configv1.IBMCloudServiceEndpoint 67 // Cache endpoint override for IBM Cloud IAM, if one was provided in serviceEndpoints 68 iamServiceEndpointOverride string 69 } 70 71 // InstanceType is the IBM Cloud network services type being used 72 type InstanceType string 73 74 const ( 75 // CISInstanceType is a Cloud Internet Services InstanceType 76 CISInstanceType InstanceType = "CIS" 77 // DNSInstanceType is a DNS Services InstanceType 78 DNSInstanceType InstanceType = "DNS" 79 80 // cisServiceID is the Cloud Internet Services' catalog service ID. 81 cisServiceID = "75874a60-cb12-11e7-948e-37ac098eb1b9" 82 // dnsServiceID is the DNS Services' catalog service ID. 83 dnsServiceID = "b4ed8a30-936f-11e9-b289-1d079699cbe5" 84 85 // hyperProtectCRNServiceName is the service name within the IBM Cloud CRN for the Hyper Protect service. 86 hyperProtectCRNServiceName = "hs-crypto" 87 // keyProtectCRNServiceName is the service name within the IBM Cloud CRN for the Key Protect service. 88 keyProtectCRNServiceName = "kms" 89 90 // hyperProtectDefaultURLTemplate is the default URL endpoint template, with region substitution, for IBM Cloud Hyper Protect service. 91 hyperProtectDefaultURLTemplate = "https://api.%s.hs-crypto.cloud.ibm.com" 92 // iamTokenDefaultURL is the default URL endpoint for IBM Cloud IAM token service. 93 iamTokenDefaultURL = "https://iam.cloud.ibm.com/identity/token" // #nosec G101 // this is the URL for IBM Cloud IAM tokens, not a credential 94 // iamTokenPath is the URL path, to add to override IAM endpoints, for the IBM Cloud IAM token service. 95 iamTokenPath = "identity/token" // #nosec G101 // this is the URI path for IBM Cloud IAM tokens, not a credential 96 // keyProtectDefaultURLTemplate is the default URL endpoint template, with region substitution, for IBM Cloud Key Protect service. 97 keyProtectDefaultURLTemplate = "https://%s.kms.cloud.ibm.com" 98 ) 99 100 // VPCResourceNotFoundError represents an error for a VPC resoruce that is not found. 101 type VPCResourceNotFoundError struct{} 102 103 // Error returns the error message for the VPCResourceNotFoundError error type. 104 func (e *VPCResourceNotFoundError) Error() string { 105 return "Not Found" 106 } 107 108 // NewClient initializes a client with any provided endpoint overrides. 109 func NewClient(endpoints []configv1.IBMCloudServiceEndpoint) (*Client, error) { 110 apiKey := os.Getenv("IC_API_KEY") 111 112 client := &Client{ 113 apiKey: apiKey, 114 serviceEndpoints: endpoints, 115 } 116 117 // Look for an override to IBM Cloud IAM service, preventing searching each time its necessary 118 for _, endpoint := range endpoints { 119 if endpoint.Name == configv1.IBMCloudServiceIAM { 120 client.iamServiceEndpointOverride = endpoint.URL 121 break 122 } 123 } 124 125 if err := client.loadSDKServices(); err != nil { 126 return nil, errors.Wrap(err, "failed to load IBM SDK services") 127 } 128 129 return client, nil 130 } 131 132 func (c *Client) loadSDKServices() error { 133 servicesToLoad := []func() error{ 134 c.loadResourceManagementAPI, 135 c.loadResourceControllerAPI, 136 c.loadVPCV1API, 137 } 138 139 // Call all the load functions. 140 for _, fn := range servicesToLoad { 141 if err := fn(); err != nil { 142 return err 143 } 144 } 145 146 return nil 147 } 148 149 // GetAPIKey gets the API Key. 150 func (c *Client) GetAPIKey() string { 151 return c.apiKey 152 } 153 154 // GetAuthenticatorAPIKeyDetails gets detailed information on the API key used 155 // for authentication to the IBM Cloud APIs 156 func (c *Client) GetAuthenticatorAPIKeyDetails(ctx context.Context) (*iamidentityv1.APIKey, error) { 157 authenticator, err := NewIamAuthenticator(c.GetAPIKey(), c.iamServiceEndpointOverride) 158 if err != nil { 159 return nil, err 160 } 161 newIamIdentityV1Options := iamidentityv1.IamIdentityV1Options{ 162 Authenticator: authenticator, 163 } 164 // If an IAM service endpoint override was provided, pass it along to override the default IAM service endpoint 165 if c.iamServiceEndpointOverride != "" { 166 newIamIdentityV1Options.URL = c.iamServiceEndpointOverride 167 } 168 iamIdentityService, err := iamidentityv1.NewIamIdentityV1(&newIamIdentityV1Options) 169 if err != nil { 170 return nil, err 171 } 172 173 options := iamIdentityService.NewGetAPIKeysDetailsOptions() 174 options.SetIamAPIKey(c.GetAPIKey()) 175 details, _, err := iamIdentityService.GetAPIKeysDetailsWithContext(ctx, options) 176 if err != nil { 177 return nil, err 178 } 179 return details, nil 180 } 181 182 // getInstance gets a specific DNS or CIS instance by its CRN. 183 func (c *Client) getInstance(ctx context.Context, crnstr string, iType InstanceType) (*resourcecontrollerv2.ResourceInstance, error) { 184 _, cancel := context.WithTimeout(ctx, 1*time.Minute) 185 defer cancel() 186 187 options := c.controllerAPI.NewGetResourceInstanceOptions(crnstr) 188 resourceInstance, _, err := c.controllerAPI.GetResourceInstance(options) 189 if err != nil { 190 return nil, errors.Wrapf(err, "failed to get %s instances", iType) 191 } 192 193 return resourceInstance, nil 194 } 195 196 // GetCISInstance gets a specific Cloud Internet Services by its CRN. 197 func (c *Client) GetCISInstance(ctx context.Context, crnstr string) (*resourcecontrollerv2.ResourceInstance, error) { 198 return c.getInstance(ctx, crnstr, CISInstanceType) 199 } 200 201 // GetDNSInstance gets a specific DNS Services instance by its CRN. 202 func (c *Client) GetDNSInstance(ctx context.Context, crnstr string) (*resourcecontrollerv2.ResourceInstance, error) { 203 return c.getInstance(ctx, crnstr, DNSInstanceType) 204 } 205 206 // GetDNSInstancePermittedNetworks gets the permitted VPC networks for a DNS Services instance 207 func (c *Client) GetDNSInstancePermittedNetworks(ctx context.Context, dnsID string, dnsZone string) ([]string, error) { 208 _, cancel := context.WithTimeout(ctx, 1*time.Minute) 209 defer cancel() 210 211 authenticator, err := NewIamAuthenticator(c.GetAPIKey(), c.iamServiceEndpointOverride) 212 if err != nil { 213 return nil, err 214 } 215 options := &dnssvcsv1.DnsSvcsV1Options{ 216 Authenticator: authenticator, 217 } 218 // If a DNS Services service endpoint override was provided, pass it along to override the default DNS Services service endpoint 219 if overrideURL := ibmcloudtypes.CheckServiceEndpointOverride(configv1.IBMCloudServiceDNSServices, c.serviceEndpoints); overrideURL != "" { 220 options.URL = overrideURL 221 } 222 // Isolate DNS Services service usage for Internal (Private) clusters; within this function 223 dnsService, err := dnssvcsv1.NewDnsSvcsV1(options) 224 if err != nil { 225 return nil, err 226 } 227 228 listPermittedNetworksOptions := dnsService.NewListPermittedNetworksOptions(dnsID, dnsZone) 229 permittedNetworks, _, err := dnsService.ListPermittedNetworksWithContext(ctx, listPermittedNetworksOptions) 230 if err != nil { 231 return nil, err 232 } 233 234 networks := []string{} 235 for _, network := range permittedNetworks.PermittedNetworks { 236 networks = append(networks, *network.PermittedNetwork.VpcCrn) 237 } 238 return networks, nil 239 } 240 241 // GetDedicatedHostByName gets dedicated host by name. 242 func (c *Client) GetDedicatedHostByName(ctx context.Context, name string, region string) (*vpcv1.DedicatedHost, error) { 243 err := c.SetVPCServiceURLForRegion(ctx, region) 244 if err != nil { 245 return nil, err 246 } 247 248 options := c.vpcAPI.NewListDedicatedHostsOptions() 249 dhosts, _, err := c.vpcAPI.ListDedicatedHostsWithContext(ctx, options) 250 if err != nil { 251 return nil, errors.Wrap(err, "failed to list dedicated hosts") 252 } 253 254 for _, dhost := range dhosts.DedicatedHosts { 255 if *dhost.Name == name { 256 return &dhost, nil 257 } 258 } 259 260 return nil, fmt.Errorf("dedicated host %q not found", name) 261 } 262 263 // GetDedicatedHostProfiles gets a list of profiles supported in a region. 264 func (c *Client) GetDedicatedHostProfiles(ctx context.Context, region string) ([]vpcv1.DedicatedHostProfile, error) { 265 err := c.SetVPCServiceURLForRegion(ctx, region) 266 if err != nil { 267 return nil, err 268 } 269 270 profilesOptions := c.vpcAPI.NewListDedicatedHostProfilesOptions() 271 profiles, _, err := c.vpcAPI.ListDedicatedHostProfilesWithContext(ctx, profilesOptions) 272 if err != nil { 273 return nil, err 274 } 275 276 return profiles.Profiles, nil 277 } 278 279 // GetDNSRecordsByName gets DNS records in specific Cloud Internet Services instance 280 // by its CRN, zone ID, and DNS record name. 281 func (c *Client) GetDNSRecordsByName(ctx context.Context, crnstr string, zoneID string, recordName string) ([]dnsrecordsv1.DnsrecordDetails, error) { 282 authenticator, err := NewIamAuthenticator(c.GetAPIKey(), c.iamServiceEndpointOverride) 283 if err != nil { 284 return nil, err 285 } 286 // Set CIS DNS record service options 287 options := &dnsrecordsv1.DnsRecordsV1Options{ 288 Authenticator: authenticator, 289 Crn: core.StringPtr(crnstr), 290 ZoneIdentifier: core.StringPtr(zoneID), 291 } 292 // If a CIS service endpoint override was provided, pass it along to override the default DNS Records service 293 // dnsrecordsv1 is provided via IBM CIS endpoint 294 if overrideURL := ibmcloudtypes.CheckServiceEndpointOverride(configv1.IBMCloudServiceCIS, c.serviceEndpoints); overrideURL != "" { 295 options.URL = overrideURL 296 } 297 // Isolate DNS Records service (for IBM Cloud CIS) usage for External (Public) clusters; within this function 298 dnsRecordsService, err := dnsrecordsv1.NewDnsRecordsV1(options) 299 if err != nil { 300 return nil, err 301 } 302 303 // Get CIS DNS records by name 304 records, _, err := dnsRecordsService.ListAllDnsRecordsWithContext(ctx, &dnsrecordsv1.ListAllDnsRecordsOptions{ 305 Name: core.StringPtr(recordName), 306 }) 307 if err != nil { 308 return nil, errors.Wrap(err, "could not retrieve DNS records") 309 } 310 311 return records.Result, nil 312 } 313 314 // GetDNSZoneIDByName gets the DNS (Internal) or CIS zone ID from its domain name. 315 func (c *Client) GetDNSZoneIDByName(ctx context.Context, name string, publish types.PublishingStrategy) (string, error) { 316 zones, err := c.GetDNSZones(ctx, publish) 317 if err != nil { 318 return "", err 319 } 320 321 for _, z := range zones { 322 if z.Name == name { 323 return z.ID, nil 324 } 325 } 326 327 return "", fmt.Errorf("DNS zone %q not found", name) 328 } 329 330 // GetDNSZones returns all of the active DNS zones managed by DNS or CIS. 331 func (c *Client) GetDNSZones(ctx context.Context, publish types.PublishingStrategy) ([]responses.DNSZoneResponse, error) { 332 _, cancel := context.WithTimeout(ctx, 1*time.Minute) 333 defer cancel() 334 335 if publish == types.InternalPublishingStrategy { 336 return c.getDNSDNSZones(ctx) 337 } 338 return c.getCISDNSZones(ctx) 339 } 340 341 func (c *Client) getDNSDNSZones(ctx context.Context) ([]responses.DNSZoneResponse, error) { 342 options := c.controllerAPI.NewListResourceInstancesOptions() 343 options.SetResourceID(dnsServiceID) 344 345 listResourceInstancesResponse, _, err := c.controllerAPI.ListResourceInstances(options) 346 if err != nil { 347 return nil, errors.Wrap(err, "failed to get dns instance") 348 } 349 350 var allZones []responses.DNSZoneResponse 351 for _, instance := range listResourceInstancesResponse.Resources { 352 authenticator, err := NewIamAuthenticator(c.GetAPIKey(), c.iamServiceEndpointOverride) 353 if err != nil { 354 return nil, err 355 } 356 options := &dnszonesv1.DnsZonesV1Options{ 357 Authenticator: authenticator, 358 } 359 // If a DNS Services service endpoint override was provided, pass it along to override the default DNS Zones service 360 // dnszonesv1 is provided via IBM Cloud DNS Services endpoint 361 if overrideURL := ibmcloudtypes.CheckServiceEndpointOverride(configv1.IBMCloudServiceDNSServices, c.serviceEndpoints); overrideURL != "" { 362 options.URL = overrideURL 363 } 364 // Isolate DNS Zones service (for IBM Cloud DNS Services) usage for Internal (Private) clusters; within this function 365 dnsZoneService, err := dnszonesv1.NewDnsZonesV1(options) 366 if err != nil { 367 return nil, fmt.Errorf("failed to list DNS zones: %w", err) 368 } 369 370 listZonesOptions := dnsZoneService.NewListDnszonesOptions(*instance.GUID) 371 result, _, err := dnsZoneService.ListDnszones(listZonesOptions) 372 if result == nil { 373 return nil, err 374 } 375 376 for _, zone := range result.Dnszones { 377 stateLower := strings.ToLower(*zone.State) 378 // DNS Zones can be 'pending_network_add' (without a permitted network, added during TF) 379 if stateLower == dnszonesv1.Dnszone_State_Active || stateLower == dnszonesv1.Dnszone_State_PendingNetworkAdd { 380 zoneStruct := responses.DNSZoneResponse{ 381 Name: *zone.Name, 382 ID: *zone.ID, 383 InstanceID: *instance.GUID, 384 InstanceCRN: *instance.CRN, 385 InstanceName: *instance.Name, 386 ResourceGroupID: *instance.ResourceGroupID, 387 } 388 allZones = append(allZones, zoneStruct) 389 } 390 } 391 } 392 393 return allZones, nil 394 } 395 396 func (c *Client) getCISDNSZones(ctx context.Context) ([]responses.DNSZoneResponse, error) { 397 options := c.controllerAPI.NewListResourceInstancesOptions() 398 options.SetResourceID(cisServiceID) 399 400 listResourceInstancesResponse, _, err := c.controllerAPI.ListResourceInstances(options) 401 if err != nil { 402 return nil, errors.Wrap(err, "failed to get cis instance") 403 } 404 405 var allZones []responses.DNSZoneResponse 406 for _, instance := range listResourceInstancesResponse.Resources { 407 authenticator, err := NewIamAuthenticator(c.GetAPIKey(), c.iamServiceEndpointOverride) 408 if err != nil { 409 return nil, err 410 } 411 options := &zonesv1.ZonesV1Options{ 412 Authenticator: authenticator, 413 Crn: instance.CRN, 414 } 415 // If a CIS service endpoint override was provided, pass it along to override the default Zones service 416 // zonesv1 is provided via IBM Cloud CIS endpoint 417 if overrideURL := ibmcloudtypes.CheckServiceEndpointOverride(configv1.IBMCloudServiceCIS, c.serviceEndpoints); overrideURL != "" { 418 options.URL = overrideURL 419 } 420 // Isolate Zones service (for IBM Cloud CIS) usage for External (Public) clusters; within this function 421 zonesService, err := zonesv1.NewZonesV1(options) 422 if err != nil { 423 return nil, fmt.Errorf("failed to list DNS zones: %w", err) 424 } 425 426 listZonesOptions := zonesService.NewListZonesOptions() 427 listZonesResponse, _, err := zonesService.ListZones(listZonesOptions) 428 429 if listZonesResponse == nil { 430 return nil, err 431 } 432 433 for _, zone := range listZonesResponse.Result { 434 if *zone.Status == "active" { 435 zoneStruct := responses.DNSZoneResponse{ 436 Name: *zone.Name, 437 ID: *zone.ID, 438 InstanceID: *instance.GUID, 439 InstanceCRN: *instance.CRN, 440 InstanceName: *instance.Name, 441 ResourceGroupID: *instance.ResourceGroupID, 442 } 443 allZones = append(allZones, zoneStruct) 444 } 445 } 446 } 447 448 return allZones, nil 449 } 450 451 // GetEncryptionKey gets data for an encryption key 452 func (c *Client) GetEncryptionKey(ctx context.Context, keyCRN string) (*responses.EncryptionKeyResponse, error) { 453 // Parse out the IBM Cloud details from CRN 454 crn, err := ibmcrn.Parse(keyCRN) 455 if err != nil { 456 return nil, err 457 } 458 459 var keyResponse *responses.EncryptionKeyResponse 460 461 keyAPI, err := c.getKeyServiceAPI(crn) 462 if err != nil { 463 return nil, err 464 } 465 466 key, err := keyAPI.GetKey(ctx, crn.Resource) 467 if err != nil { 468 return nil, err 469 } 470 471 if key != nil { 472 keyResponse = &responses.EncryptionKeyResponse{ 473 ID: key.ID, 474 Type: key.Type, 475 CRN: key.CRN, 476 State: key.State, 477 Deleted: key.Deleted, 478 } 479 } 480 481 return keyResponse, nil 482 } 483 484 // GetResourceGroup gets a resource group by its name or ID. 485 func (c *Client) GetResourceGroup(ctx context.Context, nameOrID string) (*resourcemanagerv2.ResourceGroup, error) { 486 _, cancel := context.WithTimeout(ctx, 1*time.Minute) 487 defer cancel() 488 489 groups, err := c.GetResourceGroups(ctx) 490 if err != nil { 491 return nil, err 492 } 493 494 for idx, rg := range groups { 495 if *rg.ID == nameOrID || *rg.Name == nameOrID { 496 return &groups[idx], nil 497 } 498 } 499 return nil, fmt.Errorf("resource group %q not found", nameOrID) 500 } 501 502 // GetResourceGroups gets the list of resource groups. 503 func (c *Client) GetResourceGroups(ctx context.Context) ([]resourcemanagerv2.ResourceGroup, error) { 504 _, cancel := context.WithTimeout(ctx, 1*time.Minute) 505 defer cancel() 506 507 apikey, err := c.GetAuthenticatorAPIKeyDetails(ctx) 508 if err != nil { 509 return nil, err 510 } 511 512 options := c.managementAPI.NewListResourceGroupsOptions() 513 options.SetAccountID(*apikey.AccountID) 514 listResourceGroupsResponse, _, err := c.managementAPI.ListResourceGroupsWithContext(ctx, options) 515 if err != nil { 516 return nil, err 517 } 518 return listResourceGroupsResponse.Resources, nil 519 } 520 521 // GetSubnet gets a subnet by its ID. 522 func (c *Client) GetSubnet(ctx context.Context, subnetID string) (*vpcv1.Subnet, error) { 523 _, cancel := context.WithTimeout(ctx, 1*time.Minute) 524 defer cancel() 525 526 subnet, detailedResponse, err := c.vpcAPI.GetSubnet(&vpcv1.GetSubnetOptions{ID: &subnetID}) 527 if detailedResponse.GetStatusCode() == http.StatusNotFound { 528 return nil, &VPCResourceNotFoundError{} 529 } 530 return subnet, err 531 } 532 533 // GetSubnetByName gets a subnet by its Name. 534 func (c *Client) GetSubnetByName(ctx context.Context, subnetName string, region string) (*vpcv1.Subnet, error) { 535 _, cancel := context.WithTimeout(ctx, 1*time.Minute) 536 defer cancel() 537 538 err := c.SetVPCServiceURLForRegion(ctx, region) 539 if err != nil { 540 return nil, err 541 } 542 543 listSubnetsOptions := c.vpcAPI.NewListSubnetsOptions() 544 subnetCollection, detailedResponse, err := c.vpcAPI.ListSubnetsWithContext(ctx, listSubnetsOptions) 545 if err != nil { 546 return nil, err 547 } else if detailedResponse.GetStatusCode() == http.StatusNotFound { 548 return nil, &VPCResourceNotFoundError{} 549 } 550 for _, subnet := range subnetCollection.Subnets { 551 if subnetName == *subnet.Name { 552 return &subnet, nil 553 } 554 } 555 return nil, &VPCResourceNotFoundError{} 556 } 557 558 // GetVSIProfiles gets a list of all VSI profiles. 559 func (c *Client) GetVSIProfiles(ctx context.Context) ([]vpcv1.InstanceProfile, error) { 560 listInstanceProfilesOptions := c.vpcAPI.NewListInstanceProfilesOptions() 561 profiles, _, err := c.vpcAPI.ListInstanceProfilesWithContext(ctx, listInstanceProfilesOptions) 562 if err != nil { 563 return nil, errors.Wrapf(err, "failed to list vpc vsi profiles using: %s", c.vpcAPI.Service.Options.URL) 564 } 565 return profiles.Profiles, nil 566 } 567 568 // GetVPC gets a VPC by its ID. 569 func (c *Client) GetVPC(ctx context.Context, vpcID string) (*vpcv1.VPC, error) { 570 _, cancel := context.WithTimeout(ctx, 1*time.Minute) 571 defer cancel() 572 573 regions, err := c.getVPCRegions(ctx) 574 if err != nil { 575 return nil, err 576 } 577 578 for _, region := range regions { 579 err := c.vpcAPI.SetServiceURL(fmt.Sprintf("%s/v1", *region.Endpoint)) 580 if err != nil { 581 return nil, errors.Wrap(err, "failed to set vpc api service url") 582 } 583 584 if vpc, detailedResponse, err := c.vpcAPI.GetVPC(c.vpcAPI.NewGetVPCOptions(vpcID)); err != nil { 585 if detailedResponse != nil { 586 // If the response code signifies the VPC was not found, simply move on to the next region; otherwise we log the response 587 if detailedResponse.GetStatusCode() != http.StatusNotFound { 588 logrus.Warnf("Unexpected response while checking VPC %s in %s region: %s", vpcID, *region.Name, detailedResponse) 589 } 590 } else { 591 logrus.Warnf("Failure collecting VPC %s in %s: %q", vpcID, *region.Name, err) 592 } 593 } else if vpc != nil { 594 return vpc, nil 595 } 596 } 597 598 return nil, &VPCResourceNotFoundError{} 599 } 600 601 // GetVPCs gets all VPCs in a region 602 func (c *Client) GetVPCs(ctx context.Context, region string) ([]vpcv1.VPC, error) { 603 _, cancel := context.WithTimeout(ctx, 1*time.Minute) 604 defer cancel() 605 606 err := c.SetVPCServiceURLForRegion(ctx, region) 607 if err != nil { 608 return nil, errors.Wrap(err, "failed to set vpc api service url") 609 } 610 611 allVPCs := []vpcv1.VPC{} 612 if vpcs, detailedResponse, err := c.vpcAPI.ListVpcs(c.vpcAPI.NewListVpcsOptions()); err != nil { 613 if detailedResponse.GetStatusCode() != http.StatusNotFound { 614 return nil, err 615 } 616 } else if vpcs != nil { 617 allVPCs = append(allVPCs, vpcs.Vpcs...) 618 } 619 return allVPCs, nil 620 } 621 622 // GetVPCByName gets a VPC by its name. 623 func (c *Client) GetVPCByName(ctx context.Context, vpcName string) (*vpcv1.VPC, error) { 624 _, cancel := context.WithTimeout(ctx, 1*time.Minute) 625 defer cancel() 626 627 regions, err := c.getVPCRegions(ctx) 628 if err != nil { 629 return nil, err 630 } 631 632 for _, region := range regions { 633 err := c.vpcAPI.SetServiceURL(fmt.Sprintf("%s/v1", *region.Endpoint)) 634 if err != nil { 635 return nil, fmt.Errorf("failed to set vpc api service url: %w", err) 636 } 637 638 if vpcs, detailedResponse, err := c.vpcAPI.ListVpcsWithContext(ctx, c.vpcAPI.NewListVpcsOptions()); err != nil { 639 if detailedResponse != nil { 640 // If the response code signifies no VPCs were not found, we simply move on to the next region; otherwise log the response 641 if detailedResponse.GetStatusCode() != http.StatusNotFound { 642 logrus.Warnf("Unexpected response while checking %s region: %s", *region.Name, detailedResponse) 643 } 644 } else { 645 logrus.Warnf("Failure collecting VPCs in %s: %q", *region.Name, err) 646 } 647 } else { 648 for _, vpc := range vpcs.Vpcs { 649 if *vpc.Name == vpcName { 650 return &vpc, nil 651 } 652 } 653 } 654 } 655 656 return nil, &VPCResourceNotFoundError{} 657 } 658 659 // GetVPCZonesForRegion gets the supported zones for a VPC region. 660 func (c *Client) GetVPCZonesForRegion(ctx context.Context, region string) ([]string, error) { 661 _, cancel := context.WithTimeout(ctx, 1*time.Minute) 662 defer cancel() 663 664 regionZonesOptions := c.vpcAPI.NewListRegionZonesOptions(region) 665 zones, _, err := c.vpcAPI.ListRegionZonesWithContext(ctx, regionZonesOptions) 666 if err != nil { 667 return nil, err 668 } 669 670 response := make([]string, len(zones.Zones)) 671 for idx, zone := range zones.Zones { 672 response[idx] = *zone.Name 673 } 674 return response, err 675 } 676 677 func (c *Client) getVPCRegions(ctx context.Context) ([]vpcv1.Region, error) { 678 listRegionsOptions := c.vpcAPI.NewListRegionsOptions() 679 listRegionsResponse, _, err := c.vpcAPI.ListRegionsWithContext(ctx, listRegionsOptions) 680 if err != nil { 681 return nil, errors.Wrap(err, "failed to list vpc regions") 682 } 683 684 return listRegionsResponse.Regions, nil 685 } 686 687 func (c *Client) loadResourceManagementAPI() error { 688 authenticator, err := NewIamAuthenticator(c.GetAPIKey(), c.iamServiceEndpointOverride) 689 if err != nil { 690 return err 691 } 692 options := &resourcemanagerv2.ResourceManagerV2Options{ 693 Authenticator: authenticator, 694 } 695 if overrideURL := ibmcloudtypes.CheckServiceEndpointOverride(configv1.IBMCloudServiceResourceManager, c.serviceEndpoints); overrideURL != "" { 696 options.URL = overrideURL 697 } 698 resourceManagerV2Service, err := resourcemanagerv2.NewResourceManagerV2(options) 699 if err != nil { 700 return err 701 } 702 c.managementAPI = resourceManagerV2Service 703 return nil 704 } 705 706 func (c *Client) loadResourceControllerAPI() error { 707 authenticator, err := NewIamAuthenticator(c.GetAPIKey(), c.iamServiceEndpointOverride) 708 if err != nil { 709 return err 710 } 711 options := &resourcecontrollerv2.ResourceControllerV2Options{ 712 Authenticator: authenticator, 713 } 714 if overrideURL := ibmcloudtypes.CheckServiceEndpointOverride(configv1.IBMCloudServiceResourceController, c.serviceEndpoints); overrideURL != "" { 715 options.URL = overrideURL 716 } 717 resourceControllerV2Service, err := resourcecontrollerv2.NewResourceControllerV2(options) 718 if err != nil { 719 return err 720 } 721 c.controllerAPI = resourceControllerV2Service 722 return nil 723 } 724 725 func (c *Client) loadVPCV1API() error { 726 authenticator, err := NewIamAuthenticator(c.GetAPIKey(), c.iamServiceEndpointOverride) 727 if err != nil { 728 return err 729 } 730 options := &vpcv1.VpcV1Options{ 731 Authenticator: authenticator, 732 } 733 if overrideURL := ibmcloudtypes.CheckServiceEndpointOverride(configv1.IBMCloudServiceVPC, c.serviceEndpoints); overrideURL != "" { 734 options.URL = overrideURL 735 } 736 vpcService, err := vpcv1.NewVpcV1(options) 737 if err != nil { 738 return err 739 } 740 c.vpcAPI = vpcService 741 return nil 742 } 743 744 func (c *Client) getKeyServiceAPI(crn ibmcrn.CRN) (*kpclient.Client, error) { 745 var clientConfig kpclient.ClientConfig 746 747 switch crn.ServiceName { 748 case hyperProtectCRNServiceName: 749 clientConfig = kpclient.ClientConfig{ 750 BaseURL: fmt.Sprintf(hyperProtectDefaultURLTemplate, crn.Region), 751 APIKey: c.apiKey, 752 TokenURL: iamTokenDefaultURL, 753 InstanceID: crn.ServiceInstance, 754 } 755 756 // Override HyperProtect service URL, if one was provided 757 if overrideURL := ibmcloudtypes.CheckServiceEndpointOverride(configv1.IBMCloudServiceHyperProtect, c.serviceEndpoints); overrideURL != "" { 758 clientConfig.BaseURL = overrideURL 759 } 760 case keyProtectCRNServiceName: 761 clientConfig = kpclient.ClientConfig{ 762 BaseURL: fmt.Sprintf(keyProtectDefaultURLTemplate, crn.Region), 763 APIKey: c.apiKey, 764 TokenURL: iamTokenDefaultURL, 765 InstanceID: crn.ServiceInstance, 766 } 767 768 // Override KeyProtect service URL, if one was provided 769 if overrideURL := ibmcloudtypes.CheckServiceEndpointOverride(configv1.IBMCloudServiceKeyProtect, c.serviceEndpoints); overrideURL != "" { 770 clientConfig.BaseURL = overrideURL 771 } 772 default: 773 return nil, fmt.Errorf("unknown key service for provided encryption key: %s", crn) 774 } 775 776 // Override IAM token URL, if an IAM service override URL was provided 777 if overrideURL := ibmcloudtypes.CheckServiceEndpointOverride(configv1.IBMCloudServiceIAM, c.serviceEndpoints); overrideURL != "" { 778 // Construct the token URL using the overridden IAM URL and the token path 779 clientConfig.TokenURL = fmt.Sprintf("%s/%s", overrideURL, iamTokenPath) 780 } 781 782 return kpclient.New(clientConfig, kpclient.DefaultTransport()) 783 } 784 785 // SetVPCServiceURLForRegion will set the VPC Service URL to a specific IBM Cloud Region, in order to access Region scoped resources 786 func (c *Client) SetVPCServiceURLForRegion(ctx context.Context, region string) error { 787 regionOptions := c.vpcAPI.NewGetRegionOptions(region) 788 vpcRegion, _, err := c.vpcAPI.GetRegionWithContext(ctx, regionOptions) 789 if err != nil { 790 return err 791 } 792 err = c.vpcAPI.SetServiceURL(fmt.Sprintf("%s/v1", *vpcRegion.Endpoint)) 793 if err != nil { 794 return err 795 } 796 return nil 797 }