github.com/openshift/installer@v1.4.17/pkg/asset/manifests/gcp/cluster.go (about)

     1  package gcp
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"time"
     8  
     9  	"github.com/apparentlymart/go-cidr/cidr"
    10  	"google.golang.org/api/compute/v1"
    11  	"google.golang.org/api/option"
    12  	corev1 "k8s.io/api/core/v1"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/apimachinery/pkg/util/sets"
    15  	"k8s.io/utils/ptr"
    16  	capg "sigs.k8s.io/cluster-api-provider-gcp/api/v1beta1"
    17  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    18  
    19  	"github.com/openshift/installer/pkg/asset"
    20  	"github.com/openshift/installer/pkg/asset/installconfig"
    21  	gcpic "github.com/openshift/installer/pkg/asset/installconfig/gcp"
    22  	"github.com/openshift/installer/pkg/asset/manifests/capiutils"
    23  	gcpconsts "github.com/openshift/installer/pkg/constants/gcp"
    24  	"github.com/openshift/installer/pkg/types"
    25  	"github.com/openshift/installer/pkg/types/gcp"
    26  )
    27  
    28  // InstanceGroupRoleTag is the tag used in the instance
    29  // group name to maintain compatibility between MAPI & CAPI.
    30  const InstanceGroupRoleTag = "master"
    31  
    32  // GenerateClusterAssets generates the manifests for the cluster-api.
    33  func GenerateClusterAssets(installConfig *installconfig.InstallConfig, clusterID *installconfig.ClusterID) (*capiutils.GenerateClusterAssetsOutput, error) {
    34  	manifests := []*asset.RuntimeFile{}
    35  	const description = "Created By OpenShift Installer"
    36  
    37  	networkName := fmt.Sprintf("%s-network", clusterID.InfraID)
    38  	if installConfig.Config.GCP.Network != "" {
    39  		networkName = installConfig.Config.GCP.Network
    40  	}
    41  
    42  	networkProject := installConfig.Config.GCP.ProjectID
    43  	if installConfig.Config.GCP.NetworkProjectID != "" {
    44  		networkProject = installConfig.Config.GCP.NetworkProjectID
    45  	}
    46  
    47  	controlPlaneSubnetName := gcp.DefaultSubnetName(clusterID.InfraID, "master")
    48  	controlPlaneSubnetCidr := ""
    49  	if installConfig.Config.GCP.ControlPlaneSubnet != "" {
    50  		controlPlaneSubnetName = installConfig.Config.GCP.ControlPlaneSubnet
    51  
    52  		controlPlaneSubnet, err := getSubnet(context.TODO(), networkProject, installConfig.Config.GCP.Region, controlPlaneSubnetName)
    53  		if err != nil {
    54  			return nil, fmt.Errorf("failed to get control plane subnet: %w", err)
    55  		}
    56  		// IpCidr is the IPv4 version, the IPv6 version can be accessed as well
    57  		controlPlaneSubnetCidr = controlPlaneSubnet.IpCidrRange
    58  	}
    59  
    60  	controlPlane := capg.SubnetSpec{
    61  		Name:        controlPlaneSubnetName,
    62  		CidrBlock:   controlPlaneSubnetCidr,
    63  		Description: ptr.To(description),
    64  		Region:      installConfig.Config.GCP.Region,
    65  	}
    66  
    67  	computeSubnetName := gcp.DefaultSubnetName(clusterID.InfraID, "worker")
    68  	computeSubnetCidr := ""
    69  	if installConfig.Config.GCP.ComputeSubnet != "" {
    70  		computeSubnetName = installConfig.Config.GCP.ComputeSubnet
    71  
    72  		computeSubnet, err := getSubnet(context.TODO(), networkProject, installConfig.Config.GCP.Region, computeSubnetName)
    73  		if err != nil {
    74  			return nil, fmt.Errorf("failed to get compute subnet: %w", err)
    75  		}
    76  		// IpCidr is the IPv4 version, the IPv6 version can be accessed as well
    77  		computeSubnetCidr = computeSubnet.IpCidrRange
    78  	}
    79  
    80  	compute := capg.SubnetSpec{
    81  		Name:        computeSubnetName,
    82  		CidrBlock:   computeSubnetCidr,
    83  		Description: ptr.To(description),
    84  		Region:      installConfig.Config.GCP.Region,
    85  	}
    86  
    87  	// Add the CIDR information.
    88  	machineV4CIDRs := []string{}
    89  	for _, network := range installConfig.Config.Networking.MachineNetwork {
    90  		if network.CIDR.IPNet.IP.To4() != nil {
    91  			machineV4CIDRs = append(machineV4CIDRs, network.CIDR.IPNet.String())
    92  		}
    93  	}
    94  
    95  	if len(machineV4CIDRs) == 0 {
    96  		return nil, fmt.Errorf("failed to parse machine CIDRs")
    97  	}
    98  
    99  	_, ipv4Net, err := net.ParseCIDR(machineV4CIDRs[0])
   100  	if err != nil {
   101  		return nil, fmt.Errorf("failed to parse machine network CIDR: %w", err)
   102  	}
   103  
   104  	if installConfig.Config.GCP.ControlPlaneSubnet == "" {
   105  		masterCIDR, err := cidr.Subnet(ipv4Net, 1, 0)
   106  		if err != nil {
   107  			return nil, fmt.Errorf("failed to create the master subnet %w", err)
   108  		}
   109  		controlPlane.CidrBlock = masterCIDR.String()
   110  	}
   111  
   112  	if installConfig.Config.GCP.ComputeSubnet == "" {
   113  		computeCIDR, err := cidr.Subnet(ipv4Net, 1, 1)
   114  		if err != nil {
   115  			return nil, fmt.Errorf("failed to create the compute subnet %w", err)
   116  		}
   117  		compute.CidrBlock = computeCIDR.String()
   118  	}
   119  
   120  	subnets := []capg.SubnetSpec{controlPlane, compute}
   121  	// Subnets should never be auto created, even in shared VPC installs
   122  	autoCreateSubnets := false
   123  
   124  	labels := map[string]string{}
   125  	labels[fmt.Sprintf(gcpconsts.ClusterIDLabelFmt, clusterID.InfraID)] = "owned"
   126  	labels[fmt.Sprintf("capg-cluster-%s", clusterID.InfraID)] = "owned"
   127  	for _, label := range installConfig.Config.GCP.UserLabels {
   128  		labels[label.Key] = label.Value
   129  	}
   130  
   131  	capgLoadBalancerType := capg.InternalExternal
   132  	if installConfig.Config.Publish == types.InternalPublishingStrategy {
   133  		capgLoadBalancerType = capg.Internal
   134  	}
   135  
   136  	gcpCluster := &capg.GCPCluster{
   137  		ObjectMeta: metav1.ObjectMeta{
   138  			Name:      clusterID.InfraID,
   139  			Namespace: capiutils.Namespace,
   140  		},
   141  		Spec: capg.GCPClusterSpec{
   142  			Project:              installConfig.Config.GCP.ProjectID,
   143  			Region:               installConfig.Config.GCP.Region,
   144  			ControlPlaneEndpoint: clusterv1.APIEndpoint{Port: 6443},
   145  			Network: capg.NetworkSpec{
   146  				// TODO: Need a network project for installs where the network resources will exist in another
   147  				// project such as shared vpc installs
   148  				Name:                  ptr.To(networkName),
   149  				Subnets:               subnets,
   150  				AutoCreateSubnetworks: ptr.To(autoCreateSubnets),
   151  			},
   152  			AdditionalLabels: labels,
   153  			FailureDomains:   findFailureDomains(installConfig),
   154  			LoadBalancer: capg.LoadBalancerSpec{
   155  				APIServerInstanceGroupTagOverride: ptr.To(InstanceGroupRoleTag),
   156  				LoadBalancerType:                  ptr.To(capgLoadBalancerType),
   157  			},
   158  			ResourceManagerTags: GetTagsFromInstallConfig(installConfig),
   159  		},
   160  	}
   161  	gcpCluster.SetGroupVersionKind(capg.GroupVersion.WithKind("GCPCluster"))
   162  
   163  	// Set the network project during shared vpc installs
   164  	if installConfig.Config.GCP.NetworkProjectID != "" {
   165  		gcpCluster.Spec.Network.HostProject = ptr.To(installConfig.Config.GCP.NetworkProjectID)
   166  	}
   167  
   168  	manifests = append(manifests, &asset.RuntimeFile{
   169  		Object: gcpCluster,
   170  		File:   asset.File{Filename: "02_gcp-cluster.yaml"},
   171  	})
   172  
   173  	return &capiutils.GenerateClusterAssetsOutput{
   174  		Manifests: manifests,
   175  		InfrastructureRefs: []*corev1.ObjectReference{
   176  			{
   177  				APIVersion: capg.GroupVersion.String(),
   178  				Kind:       "GCPCluster",
   179  				Name:       gcpCluster.Name,
   180  				Namespace:  gcpCluster.Namespace,
   181  			},
   182  		},
   183  	}, nil
   184  }
   185  
   186  // findFailureDomains will find the failure domains or availability zones for the GCP platform.
   187  // When the default machine platform is defined, take any zone from the compute node(s) and
   188  // any defined in the control plane node(s). When the default machine platform is not defined,
   189  // only use zones if both the compute and control plane node availability zones exist.
   190  func findFailureDomains(installConfig *installconfig.InstallConfig) []string {
   191  	zones := sets.New[string]()
   192  
   193  	var controlPlaneZones, computeZones []string
   194  	if installConfig.Config.ControlPlane.Platform.GCP != nil {
   195  		controlPlaneZones = installConfig.Config.ControlPlane.Platform.GCP.Zones
   196  	}
   197  
   198  	if installConfig.Config.Compute[0].Platform.GCP != nil {
   199  		computeZones = installConfig.Config.Compute[0].Platform.GCP.Zones
   200  	}
   201  
   202  	def := installConfig.Config.GCP.DefaultMachinePlatform
   203  	if def != nil && len(def.Zones) > 0 {
   204  		for _, zone := range def.Zones {
   205  			zones.Insert(zone)
   206  		}
   207  
   208  		for _, zone := range controlPlaneZones {
   209  			zones.Insert(zone)
   210  		}
   211  
   212  		for _, zone := range computeZones {
   213  			zones.Insert(zone)
   214  		}
   215  	} else if len(controlPlaneZones) > 0 && len(computeZones) > 0 {
   216  		for _, zone := range controlPlaneZones {
   217  			zones.Insert(zone)
   218  		}
   219  		for _, zone := range computeZones {
   220  			zones.Insert(zone)
   221  		}
   222  	}
   223  
   224  	return zones.UnsortedList()
   225  }
   226  
   227  // getSubnet will find a subnet in a project by the name. The matching subnet structure will be returned if
   228  // one is found.
   229  func getSubnet(ctx context.Context, project, region, subnetName string) (*compute.Subnetwork, error) {
   230  	ctx, cancel := context.WithTimeout(ctx, time.Minute*1)
   231  	defer cancel()
   232  
   233  	ssn, err := gcpic.GetSession(ctx)
   234  	if err != nil {
   235  		return nil, fmt.Errorf("failed to get session: %w", err)
   236  	}
   237  
   238  	computeService, err := compute.NewService(ctx, option.WithCredentials(ssn.Credentials))
   239  	if err != nil {
   240  		return nil, fmt.Errorf("failed to create compute service: %w", err)
   241  	}
   242  
   243  	subnetService := compute.NewSubnetworksService(computeService)
   244  	subnet, err := subnetService.Get(project, region, subnetName).Context(ctx).Do()
   245  	if err != nil {
   246  		return nil, fmt.Errorf("failed to find subnet %s: %w", subnetName, err)
   247  	} else if subnet == nil {
   248  		return nil, fmt.Errorf("subnet %s is empty", subnetName)
   249  	}
   250  
   251  	return subnet, nil
   252  }
   253  
   254  // GetTagsFromInstallConfig will return a slice of ResourceManagerTags from UserTags in install-config.
   255  func GetTagsFromInstallConfig(installConfig *installconfig.InstallConfig) []capg.ResourceManagerTag {
   256  	tags := make([]capg.ResourceManagerTag, len(installConfig.Config.Platform.GCP.UserTags))
   257  	for i, tag := range installConfig.Config.Platform.GCP.UserTags {
   258  		tags[i] = capg.ResourceManagerTag{
   259  			ParentID: tag.ParentID,
   260  			Key:      tag.Key,
   261  			Value:    tag.Value,
   262  		}
   263  	}
   264  
   265  	return tags
   266  }