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  }