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 }