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  }