github.com/openshift/installer@v1.4.17/pkg/infrastructure/gcp/clusterapi/clusterapi.go (about) 1 package clusterapi 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "path" 8 "strings" 9 10 "github.com/sirupsen/logrus" 11 capg "sigs.k8s.io/cluster-api-provider-gcp/api/v1beta1" 12 "sigs.k8s.io/controller-runtime/pkg/client" 13 14 "github.com/openshift/installer/pkg/asset/cluster/tfvars" 15 "github.com/openshift/installer/pkg/asset/ignition/bootstrap/gcp" 16 icgcp "github.com/openshift/installer/pkg/asset/installconfig/gcp" 17 "github.com/openshift/installer/pkg/asset/manifests/capiutils" 18 "github.com/openshift/installer/pkg/infrastructure/clusterapi" 19 "github.com/openshift/installer/pkg/types" 20 gcptypes "github.com/openshift/installer/pkg/types/gcp" 21 ) 22 23 // Provider implements gcp infrastructure in conjunction with the 24 // GCP CAPI provider. 25 type Provider struct { 26 } 27 28 var _ clusterapi.PreProvider = (*Provider)(nil) 29 var _ clusterapi.IgnitionProvider = (*Provider)(nil) 30 var _ clusterapi.InfraReadyProvider = (*Provider)(nil) 31 var _ clusterapi.PostProvider = (*Provider)(nil) 32 var _ clusterapi.BootstrapDestroyer = (*Provider)(nil) 33 34 // Name returns the name for the platform. 35 func (p Provider) Name() string { 36 return gcptypes.Name 37 } 38 39 // PublicGatherEndpoint indicates that machine ready checks should wait for an ExternalIP 40 // in the status and use that when gathering bootstrap log bundles. 41 func (Provider) PublicGatherEndpoint() clusterapi.GatherEndpoint { return clusterapi.ExternalIP } 42 43 // PreProvision is called before provisioning using CAPI controllers has initiated. 44 // GCP resources that are not created by CAPG (and are required for other stages of the install) are 45 // created here using the gcp sdk. 46 func (p Provider) PreProvision(ctx context.Context, in clusterapi.PreProvisionInput) error { 47 // Create ServiceAccounts which will be used for machines 48 platform := in.InstallConfig.Config.Platform.GCP 49 projectID := platform.ProjectID 50 51 // Only create ServiceAccounts for machines if a pre-created Service Account is not defined 52 controlPlaneMpool := &gcptypes.MachinePool{} 53 controlPlaneMpool.Set(in.InstallConfig.Config.GCP.DefaultMachinePlatform) 54 if in.InstallConfig.Config.ControlPlane != nil { 55 controlPlaneMpool.Set(in.InstallConfig.Config.ControlPlane.Platform.GCP) 56 } 57 58 if controlPlaneMpool.ServiceAccount != "" { 59 logrus.Debugf("Using pre-created ServiceAccount for control plane nodes") 60 } else { 61 // Create ServiceAccount for control plane nodes 62 logrus.Debugf("Creating ServiceAccount for control plane nodes") 63 masterSA, err := CreateServiceAccount(ctx, in.InfraID, projectID, "master") 64 if err != nil { 65 return fmt.Errorf("failed to create master serviceAccount: %w", err) 66 } 67 if err = AddServiceAccountRoles(ctx, projectID, masterSA, GetMasterRoles()); err != nil { 68 return fmt.Errorf("failed to add master roles: %w", err) 69 } 70 } 71 72 createSA := false 73 for _, compute := range in.InstallConfig.Config.Compute { 74 computeMpool := compute.Platform.GCP 75 if gcptypes.GetConfiguredServiceAccount(platform, computeMpool) == "" { 76 // If any compute nodes aren't using defined service account then create the service account 77 createSA = true 78 } 79 } 80 if createSA { 81 // Create ServiceAccount for workers 82 logrus.Debugf("Creating ServiceAccount for compute nodes") 83 workerSA, err := CreateServiceAccount(ctx, in.InfraID, projectID, "worker") 84 if err != nil { 85 return fmt.Errorf("failed to create worker serviceAccount: %w", err) 86 } 87 if err = AddServiceAccountRoles(ctx, projectID, workerSA, GetWorkerRoles()); err != nil { 88 return fmt.Errorf("failed to add worker roles: %w", err) 89 } 90 } 91 92 return nil 93 } 94 95 // Ignition provisions the GCP bucket and url that points to the bucket. Bootstrap ignition data cannot 96 // populate the metadata field of the bootstrap instance as the data can be too large. Instead, the data is 97 // added to a bucket. A signed url is generated to point to the bucket and the ignition data will be 98 // updated to point to the url. This is also allows for bootstrap data to be edited after its initial creation. 99 func (p Provider) Ignition(ctx context.Context, in clusterapi.IgnitionInput) ([]byte, error) { 100 // Create the bucket and presigned url. The url is generated using a known/expected name so that the 101 // url can be retrieved from the api by this name. 102 bucketName := gcp.GetBootstrapStorageName(in.InfraID) 103 storageClient, err := gcp.NewStorageClient(ctx) 104 if err != nil { 105 return nil, fmt.Errorf("failed to create storage client: %w", err) 106 } 107 108 bucketHandle := storageClient.Bucket(bucketName) 109 if err := gcp.CreateStorage(ctx, in.InstallConfig, bucketHandle, in.InfraID); err != nil { 110 return nil, fmt.Errorf("failed to create bucket %s: %w", bucketName, err) 111 } 112 113 editedIgnitionBytes, err := EditIgnition(ctx, in) 114 if err != nil { 115 return nil, fmt.Errorf("failed to edit bootstrap ignition: %w", err) 116 } 117 118 ignitionBytes := in.BootstrapIgnData 119 if editedIgnitionBytes != nil { 120 ignitionBytes = editedIgnitionBytes 121 } 122 123 if err := gcp.FillBucket(ctx, bucketHandle, string(ignitionBytes)); err != nil { 124 return nil, fmt.Errorf("ignition failed to fill bucket: %w", err) 125 } 126 127 for _, file := range in.TFVarsAsset.Files() { 128 if file.Filename == tfvars.TfPlatformVarsFileName { 129 var found bool 130 tfvarsData := make(map[string]interface{}) 131 err = json.Unmarshal(file.Data, &tfvarsData) 132 if err != nil { 133 return nil, fmt.Errorf("failed to unmarshal %s to json: %w", tfvars.TfPlatformVarsFileName, err) 134 } 135 136 ignShim, found := tfvarsData["gcp_ignition_shim"].(string) 137 if !found { 138 return nil, fmt.Errorf("failed to find ignition shim") 139 } 140 141 return []byte(ignShim), nil 142 } 143 } 144 145 return nil, fmt.Errorf("failed to complete ignition process") 146 } 147 148 // InfraReady is called once cluster.Status.InfrastructureReady 149 // is true, typically after load balancers have been provisioned. It can be used 150 // to create DNS records. 151 func (p Provider) InfraReady(ctx context.Context, in clusterapi.InfraReadyInput) error { 152 gcpCluster := &capg.GCPCluster{} 153 key := client.ObjectKey{ 154 Name: in.InfraID, 155 Namespace: capiutils.Namespace, 156 } 157 if err := in.Client.Get(ctx, key, gcpCluster); err != nil { 158 return fmt.Errorf("failed to get GCP cluster: %w", err) 159 } 160 161 // public load balancer is created by CAPG. The health check for this load balancer is also created by 162 // the CAPG. 163 apiIPAddress := gcpCluster.Spec.ControlPlaneEndpoint.Host 164 if apiIPAddress == "" && in.InstallConfig.Config.Publish == types.ExternalPublishingStrategy { 165 logrus.Debugf("publish strategy is set to external but api address is empty") 166 } 167 168 if err := createBootstrapFirewallRules(ctx, in, *gcpCluster.Status.Network.SelfLink); err != nil { 169 return fmt.Errorf("failed to add bootstrap firewall rule: %w", err) 170 } 171 172 client, err := icgcp.NewClient(context.TODO()) 173 if err != nil { 174 return err 175 } 176 177 networkProjectID := in.InstallConfig.Config.GCP.NetworkProjectID 178 if networkProjectID == "" { 179 networkProjectID = in.InstallConfig.Config.GCP.ProjectID 180 } 181 182 networkSelfLink := *gcpCluster.Status.Network.SelfLink 183 networkName := path.Base(networkSelfLink) 184 masterSubnetName := gcptypes.DefaultSubnetName(in.InfraID, "master") 185 if in.InstallConfig.Config.GCP.ControlPlaneSubnet != "" { 186 masterSubnetName = in.InstallConfig.Config.GCP.ControlPlaneSubnet 187 } 188 189 subnets, err := client.GetSubnetworks(context.TODO(), networkName, networkProjectID, in.InstallConfig.Config.GCP.Region) 190 if err != nil { 191 return fmt.Errorf("failed to retrieve subnets: %w", err) 192 } 193 194 var masterSubnetSelflink string 195 for _, s := range subnets { 196 if strings.EqualFold(s.Name, masterSubnetName) { 197 masterSubnetSelflink = s.SelfLink 198 break 199 } 200 } 201 202 if masterSubnetSelflink == "" { 203 return fmt.Errorf("could not find master subnet %s in subnets %v", masterSubnetName, subnets) 204 } 205 206 // The firewall for masters, aka control-plane, is created by CAPG 207 // Create the ones needed for worker to master communication 208 if err = createFirewallRules(ctx, in, *gcpCluster.Status.Network.SelfLink); err != nil { 209 return fmt.Errorf("failed to add firewall rules: %w", err) 210 } 211 212 if in.InstallConfig.Config.GCP.UserProvisionedDNS != gcptypes.UserProvisionedDNSEnabled { 213 // Get the network from the GCP Cluster. The network is used to create the private managed zone. 214 if gcpCluster.Status.Network.SelfLink == nil { 215 return fmt.Errorf("failed to get GCP network: %w", err) 216 } 217 218 // Create the private zone if one does not exist 219 if err := createPrivateManagedZone(ctx, in.InstallConfig, in.InfraID, *gcpCluster.Status.Network.SelfLink); err != nil { 220 return fmt.Errorf("failed to create the private managed zone: %w", err) 221 } 222 223 apiIntIPAddress, err := getInternalLBAddress(ctx, in.InstallConfig.Config.GCP.ProjectID, in.InstallConfig.Config.GCP.Region, getAPIAddressName(in.InfraID)) 224 if err != nil { 225 return fmt.Errorf("failed to get the internal load balancer address: %w", err) 226 } 227 228 // Create the public (optional) and private dns records 229 if err := createDNSRecords(ctx, in.InstallConfig, in.InfraID, apiIPAddress, apiIntIPAddress); err != nil { 230 return fmt.Errorf("failed to create DNS records: %w", err) 231 } 232 } 233 234 return nil 235 } 236 237 // DestroyBootstrap destroys the temporary bootstrap resources. 238 func (p Provider) DestroyBootstrap(ctx context.Context, in clusterapi.BootstrapDestroyInput) error { 239 logrus.Warnf("Destroying GCP Bootstrap Resources") 240 if err := gcp.DestroyStorage(ctx, in.Metadata.InfraID); err != nil { 241 return fmt.Errorf("failed to destroy storage: %w", err) 242 } 243 244 projectID := in.Metadata.GCP.ProjectID 245 if in.Metadata.GCP.NetworkProjectID != "" { 246 projectID = in.Metadata.GCP.NetworkProjectID 247 } 248 if err := removeBootstrapFirewallRules(ctx, in.Metadata.InfraID, projectID); err != nil { 249 return fmt.Errorf("failed to remove bootstrap firewall rules: %w", err) 250 } 251 252 return nil 253 } 254 255 // PostProvision should be called to add or update and GCP resources after provisioning has completed. 256 func (p Provider) PostProvision(ctx context.Context, in clusterapi.PostProvisionInput) error { 257 return nil 258 }