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  }