github.com/openshift/installer@v1.4.17/pkg/asset/installconfig/ibmcloud/metadata.go (about)

     1  package ibmcloud
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  
     8  	"github.com/IBM/go-sdk-core/v5/core"
     9  	"github.com/pkg/errors"
    10  
    11  	configv1 "github.com/openshift/api/config/v1"
    12  	"github.com/openshift/installer/pkg/types"
    13  )
    14  
    15  // Metadata holds additional metadata for InstallConfig resources that
    16  // does not need to be user-supplied (e.g. because it can be retrieved
    17  // from external APIs).
    18  type Metadata struct {
    19  	BaseDomain              string
    20  	ComputeSubnetNames      []string
    21  	ControlPlaneSubnetNames []string
    22  	Region                  string
    23  
    24  	accountID           string
    25  	cisInstanceCRN      string
    26  	client              API
    27  	computeSubnets      map[string]Subnet
    28  	controlPlaneSubnets map[string]Subnet
    29  	dnsInstance         *DNSInstance
    30  	publishStrategy     types.PublishingStrategy
    31  	serviceEndpoints    []configv1.IBMCloudServiceEndpoint
    32  
    33  	mutex       sync.Mutex
    34  	clientMutex sync.Mutex
    35  }
    36  
    37  // DNSInstance holds information for a DNS Services instance
    38  type DNSInstance struct {
    39  	ID   string
    40  	CRN  string
    41  	Zone string
    42  }
    43  
    44  // NewMetadata initializes a new Metadata object.
    45  func NewMetadata(config *types.InstallConfig) *Metadata {
    46  	return &Metadata{
    47  		BaseDomain:              config.BaseDomain,
    48  		ComputeSubnetNames:      config.Platform.IBMCloud.ComputeSubnets,
    49  		ControlPlaneSubnetNames: config.Platform.IBMCloud.ControlPlaneSubnets,
    50  		publishStrategy:         config.Publish,
    51  		Region:                  config.Platform.IBMCloud.Region,
    52  		serviceEndpoints:        config.Platform.IBMCloud.ServiceEndpoints,
    53  	}
    54  }
    55  
    56  // AccountID returns the IBM Cloud account ID associated with the authentication
    57  // credentials.
    58  func (m *Metadata) AccountID(ctx context.Context) (string, error) {
    59  	m.mutex.Lock()
    60  	defer m.mutex.Unlock()
    61  
    62  	if m.accountID == "" {
    63  		client, err := m.Client()
    64  		if err != nil {
    65  			return "", err
    66  		}
    67  
    68  		apiKeyDetails, err := client.GetAuthenticatorAPIKeyDetails(ctx)
    69  		if err != nil {
    70  			return "", err
    71  		}
    72  
    73  		m.accountID = *apiKeyDetails.AccountID
    74  	}
    75  	return m.accountID, nil
    76  }
    77  
    78  // CISInstanceCRN returns the Cloud Internet Services instance CRN that is
    79  // managing the DNS zone for the base domain.
    80  func (m *Metadata) CISInstanceCRN(ctx context.Context) (string, error) {
    81  	m.mutex.Lock()
    82  	defer m.mutex.Unlock()
    83  
    84  	// Only attempt to find the CIS instance if using ExternalPublishingStrategy and we have not collected it already
    85  	if m.publishStrategy == types.ExternalPublishingStrategy && m.cisInstanceCRN == "" {
    86  		client, err := m.Client()
    87  		if err != nil {
    88  			return "", err
    89  		}
    90  
    91  		zones, err := client.GetDNSZones(ctx, types.ExternalPublishingStrategy)
    92  		if err != nil {
    93  			return "", err
    94  		}
    95  
    96  		for _, z := range zones {
    97  			if z.Name == m.BaseDomain {
    98  				m.cisInstanceCRN = z.InstanceCRN
    99  				return m.cisInstanceCRN, nil
   100  			}
   101  		}
   102  		return "", fmt.Errorf("cisInstanceCRN unknown due to DNS zone %q not found", m.BaseDomain)
   103  	}
   104  	return m.cisInstanceCRN, nil
   105  }
   106  
   107  // DNSInstance returns a DNSInstance holding information about the DNS Services instance
   108  // managing the DNS zone for the base domain.
   109  func (m *Metadata) DNSInstance(ctx context.Context) (*DNSInstance, error) {
   110  	if m.dnsInstance != nil {
   111  		return m.dnsInstance, nil
   112  	}
   113  
   114  	m.mutex.Lock()
   115  	defer m.mutex.Unlock()
   116  
   117  	// Only attempt to find the DNS Services instance if using InternalPublishingStrategy and also
   118  	// prevent multiple attempts to retrieve (set) the dnsInstance if it hasn't been set (multiple threads reach mutex concurrently)
   119  	if m.publishStrategy == types.InternalPublishingStrategy && m.dnsInstance == nil {
   120  		client, err := m.Client()
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  
   125  		zones, err := client.GetDNSZones(ctx, types.InternalPublishingStrategy)
   126  		if err != nil {
   127  			return nil, err
   128  		}
   129  
   130  		for _, z := range zones {
   131  			if z.Name == m.BaseDomain {
   132  				if z.InstanceID == "" || z.InstanceCRN == "" {
   133  					return nil, fmt.Errorf("dnsInstance has unknown ID/CRN: %q - %q", z.InstanceID, z.InstanceCRN)
   134  				}
   135  				m.dnsInstance = &DNSInstance{
   136  					ID:   z.InstanceID,
   137  					CRN:  z.InstanceCRN,
   138  					Zone: z.ID,
   139  				}
   140  				return m.dnsInstance, nil
   141  			}
   142  		}
   143  		return nil, fmt.Errorf("dnsInstance unknown due to DNS zone %q not found", m.BaseDomain)
   144  	}
   145  	return m.dnsInstance, nil
   146  }
   147  
   148  // IsVPCPermittedNetwork checks if the VPC is a Permitted Network for the DNS Zone
   149  func (m *Metadata) IsVPCPermittedNetwork(ctx context.Context, vpcName string) (bool, error) {
   150  	// An empty pre-existing VPC Name signifies a new VPC will be created (not pre-existing), so it won't be permitted
   151  	if vpcName == "" {
   152  		return false, nil
   153  	}
   154  	// Collect DNSInstance details if not already collected
   155  	if m.dnsInstance == nil {
   156  		_, err := m.DNSInstance(ctx)
   157  		if err != nil {
   158  			return false, errors.Wrap(err, "cannot collect DNS permitted networks without DNS Instance")
   159  		}
   160  	}
   161  
   162  	client, err := m.Client()
   163  	if err != nil {
   164  		return false, err
   165  	}
   166  
   167  	networks, err := client.GetDNSInstancePermittedNetworks(ctx, m.dnsInstance.ID, m.dnsInstance.Zone)
   168  	if err != nil {
   169  		return false, err
   170  	}
   171  	if len(networks) < 1 {
   172  		return false, nil
   173  	}
   174  
   175  	vpc, err := client.GetVPCByName(ctx, vpcName)
   176  	if err != nil {
   177  		return false, err
   178  	}
   179  	for _, network := range networks {
   180  		if network == *vpc.CRN {
   181  			return true, nil
   182  		}
   183  	}
   184  
   185  	return false, nil
   186  }
   187  
   188  // ComputeSubnets gets the Subnet details for compute subnets
   189  func (m *Metadata) ComputeSubnets(ctx context.Context) (map[string]Subnet, error) {
   190  	m.mutex.Lock()
   191  	defer m.mutex.Unlock()
   192  
   193  	if len(m.ComputeSubnetNames) > 0 && len(m.computeSubnets) == 0 {
   194  		client, err := m.Client()
   195  		if err != nil {
   196  			return nil, err
   197  		}
   198  		m.computeSubnets, err = getSubnets(ctx, client, m.Region, m.ComputeSubnetNames)
   199  		if err != nil {
   200  			return nil, err
   201  		}
   202  	}
   203  
   204  	return m.computeSubnets, nil
   205  }
   206  
   207  // ControlPlaneSubnets gets the Subnet details for control plane subnets
   208  func (m *Metadata) ControlPlaneSubnets(ctx context.Context) (map[string]Subnet, error) {
   209  	m.mutex.Lock()
   210  	defer m.mutex.Unlock()
   211  
   212  	if len(m.ControlPlaneSubnetNames) > 0 && len(m.controlPlaneSubnets) == 0 {
   213  		client, err := m.Client()
   214  		if err != nil {
   215  			return nil, err
   216  		}
   217  		m.controlPlaneSubnets, err = getSubnets(ctx, client, m.Region, m.ControlPlaneSubnetNames)
   218  		if err != nil {
   219  			return nil, err
   220  		}
   221  	}
   222  
   223  	return m.controlPlaneSubnets, nil
   224  }
   225  
   226  // Client returns a client used for making API calls to IBM Cloud services.
   227  func (m *Metadata) Client() (API, error) {
   228  	if m.client != nil {
   229  		return m.client, nil
   230  	}
   231  
   232  	m.clientMutex.Lock()
   233  	defer m.clientMutex.Unlock()
   234  
   235  	client, err := NewClient(m.serviceEndpoints)
   236  	if err != nil {
   237  		return nil, err
   238  	}
   239  	err = client.SetVPCServiceURLForRegion(context.TODO(), m.Region)
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  	m.client = client
   244  	return m.client, nil
   245  }
   246  
   247  // NewIamAuthenticator returns a new IamAuthenticator for using IBM Cloud services.
   248  func NewIamAuthenticator(apiKey string, iamServiceEndpointOverride string) (*core.IamAuthenticator, error) {
   249  	if iamServiceEndpointOverride != "" {
   250  		return core.NewIamAuthenticatorBuilder().SetApiKey(apiKey).SetURL(iamServiceEndpointOverride).Build()
   251  	}
   252  	return core.NewIamAuthenticatorBuilder().SetApiKey(apiKey).Build()
   253  }