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

     1  package manifests
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/go-openapi/swag"
    13  	"github.com/pkg/errors"
    14  	"github.com/sirupsen/logrus"
    15  	corev1 "k8s.io/api/core/v1"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/apimachinery/pkg/util/validation/field"
    18  	"sigs.k8s.io/yaml"
    19  
    20  	operv1 "github.com/openshift/api/operator/v1"
    21  	hiveext "github.com/openshift/assisted-service/api/hiveextension/v1beta1"
    22  	aiv1beta1 "github.com/openshift/assisted-service/api/v1beta1"
    23  	hivev1 "github.com/openshift/hive/apis/hive/v1"
    24  	"github.com/openshift/installer/pkg/asset"
    25  	"github.com/openshift/installer/pkg/asset/agent"
    26  	"github.com/openshift/installer/pkg/asset/agent/agentconfig"
    27  	"github.com/openshift/installer/pkg/asset/agent/workflow"
    28  	"github.com/openshift/installer/pkg/ipnet"
    29  	"github.com/openshift/installer/pkg/types"
    30  	"github.com/openshift/installer/pkg/types/baremetal"
    31  	"github.com/openshift/installer/pkg/types/defaults"
    32  	"github.com/openshift/installer/pkg/types/external"
    33  	"github.com/openshift/installer/pkg/types/none"
    34  	"github.com/openshift/installer/pkg/types/vsphere"
    35  )
    36  
    37  const (
    38  	installConfigOverrides = aiv1beta1.Group + "/install-config-overrides"
    39  )
    40  
    41  var (
    42  	agentClusterInstallFilename = filepath.Join(clusterManifestDir, "agent-cluster-install.yaml")
    43  )
    44  
    45  // AgentClusterInstall generates the agent-cluster-install.yaml file.
    46  type AgentClusterInstall struct {
    47  	File   *asset.File
    48  	Config *hiveext.AgentClusterInstall
    49  }
    50  
    51  type agentClusterInstallOnPremPlatform struct {
    52  	// APIVIPs contains the VIP(s) to use for internal API communication. In
    53  	// dual stack clusters it contains an IPv4 and IPv6 address, otherwise only
    54  	// one VIP
    55  	APIVIPs []string `json:"apiVIPs,omitempty"`
    56  
    57  	// IngressVIPs contains the VIP(s) to use for ingress traffic. In dual stack
    58  	// clusters it contains an IPv4 and IPv6 address, otherwise only one VIP
    59  	IngressVIPs []string `json:"ingressVIPs,omitempty"`
    60  
    61  	// Host, including BMC, configuration.
    62  	Hosts []baremetal.Host `json:"hosts,omitempty"`
    63  
    64  	// ClusterProvisioningIP is the IP on the dedicated provisioning network.
    65  	ClusterProvisioningIP string `json:"clusterProvisioningIP,omitempty"`
    66  
    67  	// ProvisioningNetwork is used to indicate if we will have a provisioning network, and how it will be managed.
    68  	ProvisioningNetwork baremetal.ProvisioningNetwork `json:"provisioningNetwork,omitempty"`
    69  
    70  	// ProvisioningNetworkInterface is the name of the network interface on a control plane
    71  	// baremetal host that is connected to the provisioning network.
    72  	ProvisioningNetworkInterface string `json:"provisioningNetworkInterface,omitempty"`
    73  
    74  	// ProvisioningNetworkCIDR defines the network to use for provisioning.
    75  	ProvisioningNetworkCIDR *ipnet.IPNet `json:"provisioningNetworkCIDR,omitempty"`
    76  
    77  	// ProvisioningDHCPRange is used to provide DHCP services to hosts
    78  	// for provisioning.
    79  	ProvisioningDHCPRange string `json:"provisioningDHCPRange,omitempty"`
    80  }
    81  
    82  type agentClusterInstallOnPremExternalPlatform struct {
    83  	// PlatformName holds the arbitrary string representing the infrastructure provider name, expected to be set at the installation time.
    84  	PlatformName string `json:"platformName,omitempty"`
    85  	// CloudControllerManager when set to external, this property will enable an external cloud provider.
    86  	CloudControllerManager external.CloudControllerManager `json:"cloudControllerManager,omitempty"`
    87  }
    88  
    89  type agentClusterInstallPlatform struct {
    90  	// BareMetal is the configuration used when installing on bare metal.
    91  	// +optional
    92  	BareMetal *agentClusterInstallOnPremPlatform `json:"baremetal,omitempty"`
    93  	// VSphere is the configuration used when installing on vSphere.
    94  	// +optional
    95  	VSphere *vsphere.Platform `json:"vsphere,omitempty"`
    96  	// External is the configuration used when installing on external cloud provider.
    97  	// +optional
    98  	External *agentClusterInstallOnPremExternalPlatform `json:"external,omitempty"`
    99  }
   100  
   101  // Used to generate InstallConfig overrides for Assisted Service to apply
   102  type agentClusterInstallInstallConfigOverrides struct {
   103  	// FIPS configures https://www.nist.gov/itl/fips-general-information
   104  	//
   105  	// +kubebuilder:default=false
   106  	// +optional
   107  	FIPS bool `json:"fips,omitempty"`
   108  	// Platform is the configuration for the specific platform upon which to
   109  	// perform the installation.
   110  	Platform *agentClusterInstallPlatform `json:"platform,omitempty"`
   111  	// Capabilities selects the managed set of optional, core cluster components.
   112  	Capabilities *types.Capabilities `json:"capabilities,omitempty"`
   113  	// Allow override of network type
   114  	Networking *types.Networking `json:"networking,omitempty"`
   115  	// Allow override of CPUPartitioning
   116  	CPUPartitioning types.CPUPartitioningMode `json:"cpuPartitioningMode,omitempty"`
   117  }
   118  
   119  var _ asset.WritableAsset = (*AgentClusterInstall)(nil)
   120  
   121  // Name returns a human friendly name for the asset.
   122  func (*AgentClusterInstall) Name() string {
   123  	return "AgentClusterInstall Config"
   124  }
   125  
   126  // Dependencies returns all of the dependencies directly needed to generate
   127  // the asset.
   128  func (*AgentClusterInstall) Dependencies() []asset.Asset {
   129  	return []asset.Asset{
   130  		&workflow.AgentWorkflow{},
   131  		&agent.OptionalInstallConfig{},
   132  		&agentconfig.AgentHosts{},
   133  	}
   134  }
   135  
   136  // Generate generates the AgentClusterInstall manifest.
   137  //
   138  //nolint:gocyclo
   139  func (a *AgentClusterInstall) Generate(_ context.Context, dependencies asset.Parents) error {
   140  	agentWorkflow := &workflow.AgentWorkflow{}
   141  	installConfig := &agent.OptionalInstallConfig{}
   142  	agentHosts := &agentconfig.AgentHosts{}
   143  	dependencies.Get(agentWorkflow, agentHosts, installConfig)
   144  
   145  	// This manifest is not required for AddNodes workflow
   146  	if agentWorkflow.Workflow == workflow.AgentWorkflowTypeAddNodes {
   147  		return nil
   148  	}
   149  
   150  	if installConfig.Config != nil {
   151  		var numberOfWorkers int = 0
   152  		for _, compute := range installConfig.Config.Compute {
   153  			numberOfWorkers = numberOfWorkers + int(*compute.Replicas)
   154  		}
   155  
   156  		clusterNetwork := []hiveext.ClusterNetworkEntry{}
   157  		for _, cn := range installConfig.Config.Networking.ClusterNetwork {
   158  			entry := hiveext.ClusterNetworkEntry{
   159  				CIDR:       cn.CIDR.String(),
   160  				HostPrefix: cn.HostPrefix,
   161  			}
   162  			clusterNetwork = append(clusterNetwork, entry)
   163  		}
   164  
   165  		serviceNetwork := []string{}
   166  		for _, sn := range installConfig.Config.Networking.ServiceNetwork {
   167  			serviceNetwork = append(serviceNetwork, sn.String())
   168  		}
   169  
   170  		machineNetwork := []hiveext.MachineNetworkEntry{}
   171  		for _, mn := range installConfig.Config.Networking.MachineNetwork {
   172  			entry := hiveext.MachineNetworkEntry{
   173  				CIDR: mn.CIDR.String(),
   174  			}
   175  			machineNetwork = append(machineNetwork, entry)
   176  		}
   177  
   178  		agentClusterInstall := &hiveext.AgentClusterInstall{
   179  			TypeMeta: metav1.TypeMeta{
   180  				Kind:       "AgentClusterInstall",
   181  				APIVersion: hiveext.GroupVersion.String(),
   182  			},
   183  			ObjectMeta: metav1.ObjectMeta{
   184  				Name:      getAgentClusterInstallName(installConfig),
   185  				Namespace: installConfig.ClusterNamespace(),
   186  			},
   187  			Spec: hiveext.AgentClusterInstallSpec{
   188  				ImageSetRef: &hivev1.ClusterImageSetReference{
   189  					Name: getClusterImageSetReferenceName(),
   190  				},
   191  				ClusterDeploymentRef: corev1.LocalObjectReference{
   192  					Name: getClusterDeploymentName(installConfig),
   193  				},
   194  				Networking: hiveext.Networking{
   195  					MachineNetwork: machineNetwork,
   196  					ClusterNetwork: clusterNetwork,
   197  					ServiceNetwork: serviceNetwork,
   198  				},
   199  				SSHPublicKey: strings.Trim(installConfig.Config.SSHKey, "|\n\t"),
   200  				ProvisionRequirements: hiveext.ProvisionRequirements{
   201  					ControlPlaneAgents: int(*installConfig.Config.ControlPlane.Replicas),
   202  					WorkerAgents:       numberOfWorkers,
   203  				},
   204  				PlatformType: agent.HivePlatformType(installConfig.Config.Platform),
   205  			},
   206  		}
   207  
   208  		if agentClusterInstall.Spec.PlatformType == hiveext.ExternalPlatformType {
   209  			agentClusterInstall.Spec.ExternalPlatformSpec = &hiveext.ExternalPlatformSpec{
   210  				PlatformName: installConfig.Config.Platform.External.PlatformName,
   211  			}
   212  		}
   213  
   214  		if installConfig.Config.Platform.Name() == none.Name || installConfig.Config.Platform.Name() == external.Name {
   215  			logrus.Debugf("Setting UserManagedNetworking to true for %s platform", installConfig.Config.Platform.Name())
   216  			agentClusterInstall.Spec.Networking.UserManagedNetworking = swag.Bool(true)
   217  		}
   218  
   219  		icOverridden := false
   220  		icOverrides := agentClusterInstallInstallConfigOverrides{}
   221  		if installConfig.Config.FIPS {
   222  			icOverridden = true
   223  			icOverrides.FIPS = installConfig.Config.FIPS
   224  		}
   225  
   226  		if installConfig.Config.Proxy != nil {
   227  			agentClusterInstall.Spec.Proxy = (*hiveext.Proxy)(getProxy(installConfig.Config.Proxy))
   228  		}
   229  
   230  		if installConfig.Config.Platform.BareMetal != nil {
   231  			baremetalPlatform := agentClusterInstallOnPremPlatform{}
   232  			bmIcOverridden := false
   233  
   234  			for _, host := range agentHosts.Hosts {
   235  				// Override if BMC values are not the same as default
   236  				if host.BMC.Username != "" || host.BMC.Password != "" || host.BMC.Address != "" {
   237  					bmhost := baremetal.Host{
   238  						Name: host.Hostname,
   239  						Role: host.Role,
   240  					}
   241  					if len(host.Interfaces) > 0 {
   242  						// Boot MAC address is stored as first interface
   243  						bmhost.BootMACAddress = host.Interfaces[0].MacAddress
   244  					} else {
   245  						logrus.Infof("Could not obtain baremetal BootMacAddress for %s", installConfig.Config.Platform.Name())
   246  					}
   247  					bmIcOverridden = true
   248  					bmhost.BMC = host.BMC
   249  					baremetalPlatform.Hosts = append(baremetalPlatform.Hosts, bmhost)
   250  
   251  					// Set provisioning network configuration
   252  					baremetalPlatform.ClusterProvisioningIP = installConfig.Config.Platform.BareMetal.ClusterProvisioningIP
   253  					baremetalPlatform.ProvisioningNetwork = installConfig.Config.Platform.BareMetal.ProvisioningNetwork
   254  					baremetalPlatform.ProvisioningNetworkInterface = installConfig.Config.Platform.BareMetal.ProvisioningNetworkInterface
   255  					baremetalPlatform.ProvisioningNetworkCIDR = installConfig.Config.Platform.BareMetal.ProvisioningNetworkCIDR
   256  					baremetalPlatform.ProvisioningDHCPRange = installConfig.Config.Platform.BareMetal.ProvisioningDHCPRange
   257  				}
   258  			}
   259  			if bmIcOverridden {
   260  				icOverridden = true
   261  				icOverrides.Platform = &agentClusterInstallPlatform{}
   262  				icOverrides.Platform = &agentClusterInstallPlatform{
   263  					BareMetal: &baremetalPlatform,
   264  				}
   265  			}
   266  
   267  			agentClusterInstall.Spec.APIVIPs = installConfig.Config.Platform.BareMetal.APIVIPs
   268  			agentClusterInstall.Spec.IngressVIPs = installConfig.Config.Platform.BareMetal.IngressVIPs
   269  			agentClusterInstall.Spec.APIVIP = installConfig.Config.Platform.BareMetal.APIVIPs[0]
   270  			agentClusterInstall.Spec.IngressVIP = installConfig.Config.Platform.BareMetal.IngressVIPs[0]
   271  		} else if installConfig.Config.Platform.VSphere != nil {
   272  			vspherePlatform := vsphere.Platform{}
   273  			if len(installConfig.Config.Platform.VSphere.APIVIPs) > 1 {
   274  				icOverridden = true
   275  				vspherePlatform.APIVIPs = installConfig.Config.Platform.VSphere.APIVIPs
   276  				vspherePlatform.IngressVIPs = installConfig.Config.Platform.VSphere.IngressVIPs
   277  			}
   278  			hasCredentials := false
   279  			if len(installConfig.Config.Platform.VSphere.VCenters) > 0 {
   280  				for _, vcenter := range installConfig.Config.Platform.VSphere.VCenters {
   281  					if agent.VCenterCredentialsAreProvided(vcenter) {
   282  						icOverridden = true
   283  						hasCredentials = true
   284  						vspherePlatform.VCenters = append(vspherePlatform.VCenters, vcenter)
   285  					}
   286  				}
   287  			}
   288  			if hasCredentials && len(installConfig.Config.Platform.VSphere.FailureDomains) > 0 {
   289  				icOverridden = true
   290  				vspherePlatform.FailureDomains = append(vspherePlatform.FailureDomains, installConfig.Config.VSphere.FailureDomains...)
   291  			}
   292  			if icOverridden {
   293  				icOverrides.Platform = &agentClusterInstallPlatform{
   294  					VSphere: &vspherePlatform,
   295  				}
   296  			}
   297  			agentClusterInstall.Spec.APIVIPs = installConfig.Config.Platform.VSphere.APIVIPs
   298  			agentClusterInstall.Spec.IngressVIPs = installConfig.Config.Platform.VSphere.IngressVIPs
   299  		} else if installConfig.Config.Platform.External != nil {
   300  			icOverridden = true
   301  			icOverrides.Platform = &agentClusterInstallPlatform{
   302  				External: &agentClusterInstallOnPremExternalPlatform{
   303  					PlatformName:           installConfig.Config.External.PlatformName,
   304  					CloudControllerManager: installConfig.Config.External.CloudControllerManager,
   305  				},
   306  			}
   307  		}
   308  
   309  		networkOverridden := setNetworkType(agentClusterInstall, installConfig.Config, "NetworkType is not specified in InstallConfig.")
   310  		if networkOverridden {
   311  			icOverridden = true
   312  			icOverrides.Networking = installConfig.Config.Networking
   313  		}
   314  
   315  		if installConfig.Config.Capabilities != nil {
   316  			icOverrides.Capabilities = installConfig.Config.Capabilities
   317  			icOverridden = true
   318  		}
   319  
   320  		if installConfig.Config.CPUPartitioning != "" {
   321  			icOverridden = true
   322  			icOverrides.CPUPartitioning = installConfig.Config.CPUPartitioning
   323  		}
   324  
   325  		if icOverridden {
   326  			overrides, err := json.Marshal(icOverrides)
   327  			if err != nil {
   328  				return errors.Wrap(err, "failed to marshal AgentClusterInstall installConfigOverrides")
   329  			}
   330  			agentClusterInstall.SetAnnotations(map[string]string{
   331  				installConfigOverrides: string(overrides),
   332  			})
   333  		}
   334  
   335  		a.Config = agentClusterInstall
   336  
   337  	}
   338  	return a.finish()
   339  }
   340  
   341  // Files returns the files generated by the asset.
   342  func (a *AgentClusterInstall) Files() []*asset.File {
   343  	if a.File != nil {
   344  		return []*asset.File{a.File}
   345  	}
   346  	return []*asset.File{}
   347  }
   348  
   349  // Load returns agentclusterinstall asset from the disk.
   350  func (a *AgentClusterInstall) Load(f asset.FileFetcher) (bool, error) {
   351  
   352  	agentClusterInstallFile, err := f.FetchByName(agentClusterInstallFilename)
   353  	if err != nil {
   354  		if os.IsNotExist(err) {
   355  			return false, nil
   356  		}
   357  		return false, errors.Wrap(err, fmt.Sprintf("failed to load %s file", agentClusterInstallFilename))
   358  	}
   359  
   360  	agentClusterInstall := &hiveext.AgentClusterInstall{}
   361  	if err := yaml.UnmarshalStrict(agentClusterInstallFile.Data, agentClusterInstall); err != nil {
   362  		err = errors.Wrapf(err, "failed to unmarshal %s", agentClusterInstallFilename)
   363  		return false, err
   364  	}
   365  
   366  	setNetworkType(agentClusterInstall, &types.InstallConfig{}, "NetworkType is not specified in AgentClusterInstall.")
   367  
   368  	// Due to OCPBUGS-7495 we previously required lowercase platform names here,
   369  	// even though that is incorrect. Rewrite to the correct mixed case names
   370  	// for backward compatibility.
   371  	switch string(agentClusterInstall.Spec.PlatformType) {
   372  	case baremetal.Name:
   373  		agentClusterInstall.Spec.PlatformType = hiveext.BareMetalPlatformType
   374  	case external.Name:
   375  		agentClusterInstall.Spec.PlatformType = hiveext.ExternalPlatformType
   376  	case none.Name:
   377  		agentClusterInstall.Spec.PlatformType = hiveext.NonePlatformType
   378  	case vsphere.Name:
   379  		agentClusterInstall.Spec.PlatformType = hiveext.VSpherePlatformType
   380  	}
   381  
   382  	// Set the default value for userManagedNetworking, as would be done by the
   383  	// mutating webhook in ZTP.
   384  	if agentClusterInstall.Spec.Networking.UserManagedNetworking == nil {
   385  		switch agentClusterInstall.Spec.PlatformType {
   386  		case hiveext.NonePlatformType, hiveext.ExternalPlatformType:
   387  			logrus.Debugf("Setting UserManagedNetworking to true for %s platform", agentClusterInstall.Spec.PlatformType)
   388  			agentClusterInstall.Spec.Networking.UserManagedNetworking = swag.Bool(true)
   389  		}
   390  	}
   391  
   392  	a.Config = agentClusterInstall
   393  
   394  	if err = a.finish(); err != nil {
   395  		return false, err
   396  	}
   397  	return true, nil
   398  }
   399  
   400  func (a *AgentClusterInstall) finish() error {
   401  
   402  	if a.Config == nil {
   403  		return errors.New("missing configuration or manifest file")
   404  	}
   405  
   406  	if err := a.validateIPAddressAndNetworkType().ToAggregate(); err != nil {
   407  		return errors.Wrapf(err, "invalid NetworkType configured")
   408  	}
   409  
   410  	if err := a.validateSupportedPlatforms().ToAggregate(); err != nil {
   411  		return errors.Wrapf(err, "invalid PlatformType configured")
   412  	}
   413  
   414  	agentClusterInstallData, err := yaml.Marshal(a.Config)
   415  	if err != nil {
   416  		return errors.Wrap(err, "failed to marshal agent installer AgentClusterInstall")
   417  	}
   418  
   419  	a.File = &asset.File{
   420  		Filename: agentClusterInstallFilename,
   421  		Data:     agentClusterInstallData,
   422  	}
   423  	return nil
   424  }
   425  
   426  // Sets the default network type to OVNKubernetes if it is unspecified in the
   427  // AgentClusterInstall or InstallConfig.
   428  func setNetworkType(aci *hiveext.AgentClusterInstall, installConfig *types.InstallConfig,
   429  	warningMessage string) bool {
   430  	if aci.Spec.Networking.NetworkType != "" {
   431  		return false
   432  	}
   433  
   434  	if installConfig != nil && installConfig.Networking != nil &&
   435  		installConfig.Networking.NetworkType != "" {
   436  		if installConfig.Networking.NetworkType == string(operv1.NetworkTypeOVNKubernetes) || installConfig.Networking.NetworkType == string(operv1.NetworkTypeOpenShiftSDN) {
   437  			aci.Spec.Networking.NetworkType = installConfig.NetworkType
   438  			return false
   439  		}
   440  
   441  		// Set OVNKubernetes in AgentClusterInstall and return true to indicate InstallConfigOverride should be used
   442  		aci.Spec.Networking.NetworkType = string(operv1.NetworkTypeOVNKubernetes)
   443  		return true
   444  	}
   445  
   446  	defaults.SetInstallConfigDefaults(installConfig)
   447  	logrus.Infof("%s Defaulting NetworkType to %s.", warningMessage, installConfig.NetworkType)
   448  	aci.Spec.Networking.NetworkType = installConfig.NetworkType
   449  	return false
   450  }
   451  
   452  func isIPv6(ipAddress net.IP) bool {
   453  	// Using To16() on IPv4 addresses does not return nil so it cannot be used to determine if
   454  	// IP addresses are IPv6. Instead we are checking if the address is IPv6 by using To4().
   455  	// Same as https://github.com/openshift/installer/blob/6eca978b89fc0be17f70fc8a28fa20aab1316843/pkg/types/validation/installconfig.go#L193
   456  	ip := ipAddress.To4()
   457  	return ip == nil
   458  }
   459  
   460  func (a *AgentClusterInstall) validateIPAddressAndNetworkType() field.ErrorList {
   461  	var allErrs field.ErrorList
   462  
   463  	fieldPath := field.NewPath("spec", "networking", "networkType")
   464  	clusterNetworkPath := field.NewPath("spec", "networking", "clusterNetwork")
   465  	serviceNetworkPath := field.NewPath("spec", "networking", "serviceNetwork")
   466  
   467  	if a.Config.Spec.Networking.NetworkType == string(operv1.NetworkTypeOpenShiftSDN) {
   468  		hasIPv6 := false
   469  		for _, cn := range a.Config.Spec.Networking.ClusterNetwork {
   470  			ipNet, errCIDR := ipnet.ParseCIDR(cn.CIDR)
   471  			if errCIDR != nil {
   472  				allErrs = append(allErrs, field.Required(clusterNetworkPath, "error parsing the clusterNetwork CIDR"))
   473  				continue
   474  			}
   475  			if isIPv6(ipNet.IP) {
   476  				hasIPv6 = true
   477  			}
   478  		}
   479  		if hasIPv6 {
   480  			allErrs = append(allErrs, field.Required(fieldPath,
   481  				fmt.Sprintf("clusterNetwork CIDR is IPv6 and is not compatible with networkType %s",
   482  					operv1.NetworkTypeOpenShiftSDN)))
   483  		}
   484  
   485  		hasIPv6 = false
   486  		for _, cidr := range a.Config.Spec.Networking.ServiceNetwork {
   487  			ipNet, errCIDR := ipnet.ParseCIDR(cidr)
   488  			if errCIDR != nil {
   489  				allErrs = append(allErrs, field.Required(serviceNetworkPath, "error parsing the clusterNetwork CIDR"))
   490  				continue
   491  			}
   492  			if isIPv6(ipNet.IP) {
   493  				hasIPv6 = true
   494  			}
   495  		}
   496  		if hasIPv6 {
   497  			allErrs = append(allErrs, field.Required(fieldPath,
   498  				fmt.Sprintf("serviceNetwork CIDR is IPv6 and is not compatible with networkType %s",
   499  					operv1.NetworkTypeOpenShiftSDN)))
   500  		}
   501  	}
   502  
   503  	return allErrs
   504  }
   505  
   506  func (a *AgentClusterInstall) validateSupportedPlatforms() field.ErrorList {
   507  	var allErrs field.ErrorList
   508  
   509  	if a.Config.Spec.PlatformType != "" && !agent.IsSupportedPlatform(a.Config.Spec.PlatformType) {
   510  		fieldPath := field.NewPath("spec", "platformType")
   511  		allErrs = append(allErrs, field.NotSupported(fieldPath, a.Config.Spec.PlatformType, agent.SupportedHivePlatforms()))
   512  	}
   513  
   514  	switch a.Config.Spec.PlatformType {
   515  	case hiveext.NonePlatformType, hiveext.ExternalPlatformType:
   516  		if a.Config.Spec.Networking.UserManagedNetworking != nil && !*a.Config.Spec.Networking.UserManagedNetworking {
   517  			fieldPath := field.NewPath("spec", "networking", "userManagedNetworking")
   518  			allErrs = append(allErrs, field.Forbidden(fieldPath,
   519  				fmt.Sprintf("%s platform requires user-managed networking",
   520  					a.Config.Spec.PlatformType)))
   521  		}
   522  	}
   523  	return allErrs
   524  }
   525  
   526  // FIPSEnabled returns whether FIPS is enabled in the cluster configuration.
   527  func (a *AgentClusterInstall) FIPSEnabled() bool {
   528  	icOverrides := agentClusterInstallInstallConfigOverrides{}
   529  	if err := json.Unmarshal([]byte(a.Config.Annotations[installConfigOverrides]), &icOverrides); err == nil {
   530  		return icOverrides.FIPS
   531  	}
   532  	return false
   533  }
   534  
   535  // GetExternalPlatformName returns the platform name for the external platform.
   536  func (a *AgentClusterInstall) GetExternalPlatformName() string {
   537  	if a.Config != nil && a.Config.Spec.ExternalPlatformSpec != nil {
   538  		return a.Config.Spec.ExternalPlatformSpec.PlatformName
   539  	}
   540  	return ""
   541  }