github.com/openshift/installer@v1.4.17/pkg/asset/installconfig/azure/validation.go (about)

     1  package azure
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"net/url"
     8  	"os"
     9  	"sort"
    10  	"strconv"
    11  	"strings"
    12  
    13  	azdns "github.com/Azure/azure-sdk-for-go/profiles/2018-03-01/dns/mgmt/dns"
    14  	aznetwork "github.com/Azure/azure-sdk-for-go/profiles/2020-09-01/network/mgmt/network"
    15  	azenc "github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute"
    16  	"github.com/Azure/go-autorest/autorest/to"
    17  	"github.com/sirupsen/logrus"
    18  	"k8s.io/apimachinery/pkg/util/sets"
    19  	"k8s.io/apimachinery/pkg/util/validation/field"
    20  
    21  	"github.com/openshift/installer/pkg/types"
    22  	aztypes "github.com/openshift/installer/pkg/types/azure"
    23  	"github.com/openshift/installer/pkg/types/azure/defaults"
    24  )
    25  
    26  type resourceRequirements struct {
    27  	minimumVCpus  int64
    28  	minimumMemory int64
    29  }
    30  
    31  var controlPlaneReq = resourceRequirements{
    32  	minimumVCpus:  4,
    33  	minimumMemory: 16,
    34  }
    35  
    36  var computeReq = resourceRequirements{
    37  	minimumVCpus:  2,
    38  	minimumMemory: 8,
    39  }
    40  
    41  // Validate executes platform-specific validation.
    42  func Validate(client API, ic *types.InstallConfig) error {
    43  	allErrs := field.ErrorList{}
    44  
    45  	allErrs = append(allErrs, validateNetworks(client, ic.Azure, ic.Networking.MachineNetwork, field.NewPath("platform").Child("azure"))...)
    46  	allErrs = append(allErrs, validateRegion(client, field.NewPath("platform").Child("azure").Child("region"), ic.Azure)...)
    47  	if ic.Azure.CloudName == aztypes.StackCloud {
    48  		allErrs = append(allErrs, validateAzureStackDiskType(client, ic)...)
    49  	}
    50  	allErrs = append(allErrs, validateInstanceTypes(client, ic)...)
    51  	if ic.Azure.CloudName == aztypes.StackCloud && ic.Azure.ClusterOSImage != "" {
    52  		StorageEndpointSuffix, err := client.GetStorageEndpointSuffix(context.TODO())
    53  		if err != nil {
    54  			return err
    55  		}
    56  		allErrs = append(allErrs, validateAzureStackClusterOSImage(StorageEndpointSuffix, ic.Azure.ClusterOSImage, field.NewPath("platform").Child("azure"))...)
    57  	}
    58  	allErrs = append(allErrs, validateMarketplaceImages(client, ic)...)
    59  	return allErrs.ToAggregate()
    60  }
    61  
    62  // ValidateDiskEncryptionSet ensures the disk encryption set exists and is valid.
    63  func ValidateDiskEncryptionSet(client API, ic *types.InstallConfig) field.ErrorList {
    64  	allErrs := field.ErrorList{}
    65  
    66  	if ic.Platform.Azure.DefaultMachinePlatform != nil && ic.Platform.Azure.DefaultMachinePlatform.OSDisk.DiskEncryptionSet != nil {
    67  		diskEncryptionSet := ic.Platform.Azure.DefaultMachinePlatform.OSDisk.DiskEncryptionSet
    68  		_, err := client.GetDiskEncryptionSet(context.TODO(), diskEncryptionSet.SubscriptionID, diskEncryptionSet.ResourceGroup, diskEncryptionSet.Name)
    69  		if err != nil {
    70  			allErrs = append(allErrs, field.Invalid(field.NewPath("platform").Child("azure", "defaultMachinePlatform", "osDisk", "diskEncryptionSet"), diskEncryptionSet, err.Error()))
    71  		}
    72  	}
    73  
    74  	if ic.ControlPlane != nil && ic.ControlPlane.Platform.Azure != nil && ic.ControlPlane.Platform.Azure.OSDisk.DiskEncryptionSet != nil {
    75  		diskEncryptionSet := ic.ControlPlane.Platform.Azure.OSDisk.DiskEncryptionSet
    76  		_, err := client.GetDiskEncryptionSet(context.TODO(), diskEncryptionSet.SubscriptionID, diskEncryptionSet.ResourceGroup, diskEncryptionSet.Name)
    77  		if err != nil {
    78  			allErrs = append(allErrs, field.Invalid(field.NewPath("platform").Child("azure", "osDisk", "diskEncryptionSet"), diskEncryptionSet, err.Error()))
    79  		}
    80  	}
    81  
    82  	for idx, compute := range ic.Compute {
    83  		fieldPath := field.NewPath("compute").Index(idx)
    84  		if compute.Platform.Azure != nil && compute.Platform.Azure.OSDisk.DiskEncryptionSet != nil {
    85  			diskEncryptionSet := compute.Platform.Azure.OSDisk.DiskEncryptionSet
    86  			_, err := client.GetDiskEncryptionSet(context.TODO(), diskEncryptionSet.SubscriptionID, diskEncryptionSet.ResourceGroup, diskEncryptionSet.Name)
    87  			if err != nil {
    88  				allErrs = append(allErrs, field.Invalid(fieldPath.Child("platform", "azure", "osDisk", "diskEncryptionSet"), diskEncryptionSet, err.Error()))
    89  			}
    90  		}
    91  	}
    92  
    93  	return allErrs
    94  }
    95  
    96  func validateConfidentialDiskEncryptionSet(client API, diskEncryptionSet *aztypes.DiskEncryptionSet, desFieldPath *field.Path) error {
    97  	resp, requestErr := client.GetDiskEncryptionSet(context.TODO(), diskEncryptionSet.SubscriptionID, diskEncryptionSet.ResourceGroup, diskEncryptionSet.Name)
    98  	if requestErr != nil {
    99  		return requestErr
   100  	} else if resp == nil || resp.EncryptionSetProperties == nil || resp.EncryptionSetProperties.EncryptionType != azenc.ConfidentialVMEncryptedWithCustomerKey {
   101  		return fmt.Errorf("the disk encryption set should be created with type %s", azenc.ConfidentialVMEncryptedWithCustomerKey)
   102  	}
   103  	return nil
   104  }
   105  
   106  // ValidateSecurityProfileDiskEncryptionSet ensures the security profile disk encryption set exists and is valid.
   107  func ValidateSecurityProfileDiskEncryptionSet(client API, ic *types.InstallConfig) field.ErrorList {
   108  	allErrs := field.ErrorList{}
   109  
   110  	if ic.Platform.Azure.DefaultMachinePlatform != nil &&
   111  		ic.Platform.Azure.DefaultMachinePlatform.OSDisk.SecurityProfile != nil &&
   112  		ic.Platform.Azure.DefaultMachinePlatform.OSDisk.SecurityProfile.DiskEncryptionSet != nil {
   113  		desFieldPath := field.NewPath("platform").Child("azure", "defaultMachinePlatform", "osDisk", "securityProfile", "diskEncryptionSet")
   114  		diskEncryptionSet := ic.Platform.Azure.DefaultMachinePlatform.OSDisk.SecurityProfile.DiskEncryptionSet
   115  		err := validateConfidentialDiskEncryptionSet(client, diskEncryptionSet, desFieldPath)
   116  		if err != nil {
   117  			allErrs = append(allErrs, field.Invalid(desFieldPath, diskEncryptionSet, err.Error()))
   118  		}
   119  	}
   120  
   121  	if ic.ControlPlane != nil &&
   122  		ic.ControlPlane.Platform.Azure != nil &&
   123  		ic.ControlPlane.Platform.Azure.OSDisk.SecurityProfile != nil &&
   124  		ic.ControlPlane.Platform.Azure.OSDisk.SecurityProfile.DiskEncryptionSet != nil {
   125  		desFieldPath := field.NewPath("platform").Child("azure", "osDisk", "securityProfile", "diskEncryptionSet")
   126  		diskEncryptionSet := ic.ControlPlane.Platform.Azure.OSDisk.SecurityProfile.DiskEncryptionSet
   127  		err := validateConfidentialDiskEncryptionSet(client, diskEncryptionSet, desFieldPath)
   128  		if err != nil {
   129  			allErrs = append(allErrs, field.Invalid(desFieldPath, diskEncryptionSet, err.Error()))
   130  		}
   131  	}
   132  
   133  	for idx, compute := range ic.Compute {
   134  		fieldPath := field.NewPath("compute").Index(idx)
   135  		if compute.Platform.Azure != nil &&
   136  			compute.Platform.Azure.OSDisk.SecurityProfile != nil &&
   137  			compute.Platform.Azure.OSDisk.SecurityProfile.DiskEncryptionSet != nil {
   138  			desFieldPath := fieldPath.Child("platform", "azure", "osDisk", "securityProfile", "diskEncryptionSet")
   139  			diskEncryptionSet := compute.Platform.Azure.OSDisk.SecurityProfile.DiskEncryptionSet
   140  			err := validateConfidentialDiskEncryptionSet(client, diskEncryptionSet, desFieldPath)
   141  			if err != nil {
   142  				allErrs = append(allErrs, field.Invalid(desFieldPath, diskEncryptionSet, err.Error()))
   143  			}
   144  		}
   145  	}
   146  
   147  	return allErrs
   148  }
   149  
   150  func validatePremiumDisk(fieldPath *field.Path, diskType string, instanceType string, capabilities map[string]string) field.ErrorList {
   151  	fldPath := fieldPath.Child("osDisk", "diskType")
   152  	val, ok := capabilities["PremiumIO"]
   153  	if !ok {
   154  		return field.ErrorList{field.Invalid(fldPath, diskType, "capability not found: PremiumIO")}
   155  	}
   156  	if strings.EqualFold(val, "False") {
   157  		errMsg := fmt.Sprintf("PremiumIO not supported for instance type %s", instanceType)
   158  		return field.ErrorList{field.Invalid(fldPath, diskType, errMsg)}
   159  	}
   160  	return field.ErrorList{}
   161  }
   162  
   163  func validateVMArchitecture(fieldPath *field.Path, instanceType string, architecture types.Architecture, capabilities map[string]string) field.ErrorList {
   164  	allErrs := field.ErrorList{}
   165  
   166  	val, ok := capabilities["CpuArchitectureType"]
   167  	if ok {
   168  		if architecture != types.ArchitectureARM64 && architecture != types.ArchitectureAMD64 {
   169  			errMsg := fmt.Sprintf("invalid install config architecture %s", architecture)
   170  			allErrs = append(allErrs, field.Invalid(fieldPath, instanceType, errMsg))
   171  		} else if (architecture == types.ArchitectureARM64 && !strings.EqualFold(val, "Arm64")) || (architecture == types.ArchitectureAMD64 && !strings.EqualFold(val, "x64")) {
   172  			errMsg := fmt.Sprintf("instance type architecture '%s' does not match install config architecture %s", val, architecture)
   173  			allErrs = append(allErrs, field.Invalid(fieldPath, instanceType, errMsg))
   174  		}
   175  	} else {
   176  		logrus.Warnf("Could not determine VM's architecture from its capabilities. Assuming it is %v", architecture)
   177  	}
   178  
   179  	return allErrs
   180  }
   181  
   182  func validateMininumRequirements(fieldPath *field.Path, req resourceRequirements, instanceType string, capabilities map[string]string) field.ErrorList {
   183  	allErrs := field.ErrorList{}
   184  
   185  	val, ok := capabilities["vCPUsAvailable"]
   186  	if ok {
   187  		cpus, err := strconv.ParseFloat(val, 0)
   188  		if err != nil {
   189  			return append(allErrs, field.InternalError(fieldPath, err))
   190  		}
   191  		if cpus < float64(req.minimumVCpus) {
   192  			errMsg := fmt.Sprintf("instance type does not meet minimum resource requirements of %d vCPUsAvailable", req.minimumVCpus)
   193  			allErrs = append(allErrs, field.Invalid(fieldPath, instanceType, errMsg))
   194  		}
   195  	} else {
   196  		logrus.Warnf("could not find vCPUsAvailable information for instance type %s", instanceType)
   197  	}
   198  
   199  	val, ok = capabilities["MemoryGB"]
   200  	if ok {
   201  		memory, err := strconv.ParseFloat(val, 0)
   202  		if err != nil {
   203  			return append(allErrs, field.InternalError(fieldPath, err))
   204  		}
   205  		if memory < float64(req.minimumMemory) {
   206  			errMsg := fmt.Sprintf("instance type does not meet minimum resource requirements of %d GB Memory", req.minimumMemory)
   207  			allErrs = append(allErrs, field.Invalid(fieldPath, instanceType, errMsg))
   208  		}
   209  	} else {
   210  		logrus.Warnf("could not find MemoryGB information for instance type %s", instanceType)
   211  	}
   212  
   213  	return allErrs
   214  }
   215  
   216  func validateSecurityType(fieldPath *field.Path, securityType aztypes.SecurityTypes, instanceType string, capabilities map[string]string) field.ErrorList {
   217  	allErrs := field.ErrorList{}
   218  
   219  	_, hasTrustedLaunchDisabled := capabilities["TrustedLaunchDisabled"]
   220  	confidentialComputingType, hasConfidentialComputingType := capabilities["ConfidentialComputingType"]
   221  	isConfidentialComputingTypeSNP := confidentialComputingType == "SNP"
   222  
   223  	var reason string
   224  	supportedSecurityType := true
   225  	switch securityType {
   226  	case aztypes.SecurityTypesConfidentialVM:
   227  		supportedSecurityType = hasConfidentialComputingType && isConfidentialComputingTypeSNP
   228  
   229  		if !hasConfidentialComputingType {
   230  			reason = "no support for Confidential Computing"
   231  		} else if !isConfidentialComputingTypeSNP {
   232  			reason = "no support for AMD-SEV SNP"
   233  		}
   234  	case aztypes.SecurityTypesTrustedLaunch:
   235  		supportedSecurityType = !(hasTrustedLaunchDisabled || hasConfidentialComputingType)
   236  
   237  		if hasTrustedLaunchDisabled {
   238  			reason = "no support for Trusted Launch"
   239  		} else if hasConfidentialComputingType {
   240  			reason = "confidential VMs do not support Trusted Launch for VMs"
   241  		}
   242  	}
   243  
   244  	if !supportedSecurityType {
   245  		errMsg := fmt.Sprintf("this security type is not supported for instance type %s, %s", instanceType, reason)
   246  		allErrs = append(allErrs, field.Invalid(fieldPath, securityType, errMsg))
   247  	}
   248  
   249  	return allErrs
   250  }
   251  
   252  func validateFamily(fieldPath *field.Path, instanceType, family string) field.ErrorList {
   253  	windowsVMFamilies := sets.NewString(
   254  		"standardNVSv4Family",
   255  	)
   256  	diskNVMeVMFamilies := sets.NewString(
   257  		"standardEIBDSv5Family",
   258  		"standardEIBSv5Family",
   259  	)
   260  	allErrs := field.ErrorList{}
   261  	if windowsVMFamilies.Has(family) {
   262  		errMsg := fmt.Sprintf("%s is currently only supported on Windows", family)
   263  		allErrs = append(allErrs, field.Invalid(fieldPath, instanceType, errMsg))
   264  	}
   265  	// FIXME: remove when supported has been added to the provider
   266  	// https://github.com/hashicorp/terraform-provider-azurerm/issues/22058
   267  	if diskNVMeVMFamilies.Has(family) {
   268  		errMsg := fmt.Sprintf("%s is not currently supported but might be in a future release", family)
   269  		allErrs = append(allErrs, field.Invalid(fieldPath, instanceType, errMsg))
   270  	}
   271  
   272  	return allErrs
   273  }
   274  
   275  func validateAcceleratedNetworking(fieldPath *field.Path, vmNetworkingType string, instanceType string, capabilities map[string]string) field.ErrorList {
   276  	val, ok := capabilities[string(aztypes.AcceleratedNetworkingEnabled)]
   277  	if ok {
   278  		if !strings.EqualFold(val, "True") {
   279  			errMsg := fmt.Sprintf("vm networking type is not supported for instance type %s", instanceType)
   280  			return field.ErrorList{field.Invalid(fieldPath.Child("vmNetworkingType"), vmNetworkingType, errMsg)}
   281  		}
   282  	} else {
   283  		return field.ErrorList{field.Invalid(fieldPath.Child("type"), instanceType, "capability not found: AcceleratedNetworkingEnabled")}
   284  	}
   285  
   286  	return field.ErrorList{}
   287  }
   288  
   289  func validateUltraSSD(client API, fieldPath *field.Path, icZones []string, region string, instanceType string, capabilities map[string]string) field.ErrorList {
   290  	allErrs := field.ErrorList{}
   291  
   292  	locationInfo, err := client.GetLocationInfo(context.TODO(), region, instanceType)
   293  	if err != nil {
   294  		errMsg := fmt.Sprintf("could not determine Availability Zones support in the %s region: %v", region, err)
   295  		return append(allErrs, field.Invalid(fieldPath, instanceType, errMsg))
   296  	}
   297  	// If Availability Zones not supported
   298  	if locationInfo == nil || len(to.StringSlice(locationInfo.Zones)) == 0 {
   299  		errMsg := fmt.Sprintf("UltraSSD capability is not compatible with Availability Sets which are used because region %s does not support Availability Zones", region)
   300  		return append(allErrs, field.Invalid(fieldPath, instanceType, errMsg))
   301  	}
   302  	allZones := to.StringSlice(locationInfo.Zones)
   303  
   304  	// The UltraSSDAvailable capability might not be present at all, in which case it must assumed to be false
   305  	ultraSSDAvailable := false
   306  	if val, ok := capabilities["UltraSSDAvailable"]; ok {
   307  		ultraSSDAvailable = strings.EqualFold(val, "True")
   308  	}
   309  	for _, zoneDetails := range *locationInfo.ZoneDetails {
   310  		for _, capability := range *zoneDetails.Capabilities {
   311  			if !strings.EqualFold(to.String(capability.Name), "UltraSSDAvailable") {
   312  				continue
   313  			}
   314  			if strings.EqualFold(to.String(capability.Value), "True") {
   315  				zones := icZones
   316  				// If no zones are configured in the install config, then all available zones in the region are used
   317  				if len(zones) == 0 {
   318  					zones = allZones
   319  				}
   320  				capZones := to.StringSlice(zoneDetails.Name)
   321  				ultraSSDZones := sets.NewString(capZones...)
   322  				if !ultraSSDZones.HasAll(zones...) {
   323  					errMsg := fmt.Sprintf("UltraSSD capability only supported in zones %v for this instance type in the %s region", capZones, region)
   324  					return append(allErrs, field.Invalid(fieldPath, instanceType, errMsg))
   325  				}
   326  				ultraSSDAvailable = true
   327  			}
   328  		}
   329  	}
   330  	if !ultraSSDAvailable {
   331  		errMsg := fmt.Sprintf("UltraSSD capability not supported for this instance type in the %s region", region)
   332  		allErrs = append(allErrs, field.Invalid(fieldPath, instanceType, errMsg))
   333  	}
   334  
   335  	return allErrs
   336  }
   337  
   338  // ValidateInstanceType ensures the instance type has sufficient Vcpu, Memory, and a valid family type.
   339  func ValidateInstanceType(client API, fieldPath *field.Path, region, instanceType, diskType string, req resourceRequirements, ultraSSDEnabled bool, vmNetworkingType string, icZones []string, architecture types.Architecture, securityType aztypes.SecurityTypes) field.ErrorList {
   340  	allErrs := field.ErrorList{}
   341  
   342  	capabilities, err := client.GetVMCapabilities(context.TODO(), instanceType, region)
   343  	if err != nil {
   344  		return append(allErrs, field.Invalid(fieldPath.Child("type"), instanceType, err.Error()))
   345  	}
   346  
   347  	allErrs = append(allErrs, validateMininumRequirements(fieldPath.Child("type"), req, instanceType, capabilities)...)
   348  	allErrs = append(allErrs, validateVMArchitecture(fieldPath.Child("type"), instanceType, architecture, capabilities)...)
   349  	allErrs = append(allErrs, validateSecurityType(fieldPath.Child("settings", "securityType"), securityType, instanceType, capabilities)...)
   350  
   351  	family, _ := client.GetVirtualMachineFamily(context.TODO(), instanceType, region)
   352  	if family != "" {
   353  		allErrs = append(allErrs, validateFamily(fieldPath.Child("type"), instanceType, family)...)
   354  	}
   355  
   356  	if diskType == "Premium_LRS" {
   357  		allErrs = append(allErrs, validatePremiumDisk(fieldPath, diskType, instanceType, capabilities)...)
   358  	}
   359  
   360  	if vmNetworkingType == string(aztypes.VMnetworkingTypeAccelerated) {
   361  		allErrs = append(allErrs, validateAcceleratedNetworking(fieldPath, vmNetworkingType, instanceType, capabilities)...)
   362  	}
   363  
   364  	if ultraSSDEnabled {
   365  		allErrs = append(allErrs, validateUltraSSD(client, fieldPath.Child("type"), icZones, region, instanceType, capabilities)...)
   366  	}
   367  
   368  	return allErrs
   369  }
   370  
   371  // validateInstanceTypes checks that the user-provided instance types are valid.
   372  func validateInstanceTypes(client API, ic *types.InstallConfig) field.ErrorList {
   373  	allErrs := field.ErrorList{}
   374  
   375  	var securityType aztypes.SecurityTypes
   376  
   377  	defaultDiskType := aztypes.DefaultDiskType
   378  	defaultInstanceType := ""
   379  	defaultUltraSSDCapability := "Disabled"
   380  	defaultVMNetworkingType := ""
   381  	defaultZones := []string{}
   382  	useDefaultInstanceType := false
   383  
   384  	if ic.Platform.Azure.DefaultMachinePlatform != nil {
   385  		if ic.Platform.Azure.DefaultMachinePlatform.OSDisk.DiskType != "" {
   386  			defaultDiskType = ic.Platform.Azure.DefaultMachinePlatform.OSDisk.DiskType
   387  		}
   388  		if ic.Platform.Azure.DefaultMachinePlatform.InstanceType != "" {
   389  			defaultInstanceType = ic.Platform.Azure.DefaultMachinePlatform.InstanceType
   390  		}
   391  		if ic.Platform.Azure.DefaultMachinePlatform.UltraSSDCapability != "" {
   392  			defaultUltraSSDCapability = ic.Platform.Azure.DefaultMachinePlatform.UltraSSDCapability
   393  		}
   394  		if ic.Platform.Azure.DefaultMachinePlatform.VMNetworkingType != "" {
   395  			defaultVMNetworkingType = ic.Platform.Azure.DefaultMachinePlatform.VMNetworkingType
   396  		}
   397  		if ic.Platform.Azure.DefaultMachinePlatform.Zones != nil {
   398  			defaultZones = ic.Platform.Azure.DefaultMachinePlatform.Zones
   399  		}
   400  		if ic.Platform.Azure.DefaultMachinePlatform.Settings != nil {
   401  			securityType = ic.Platform.Azure.DefaultMachinePlatform.Settings.SecurityType
   402  		}
   403  	}
   404  
   405  	if ic.ControlPlane != nil && ic.ControlPlane.Platform.Azure != nil {
   406  		fieldPath := field.NewPath("controlPlane", "platform", "azure")
   407  		diskType := ic.ControlPlane.Platform.Azure.OSDisk.DiskType
   408  		instanceType := ic.ControlPlane.Platform.Azure.InstanceType
   409  		ultraSSDCapability := ic.ControlPlane.Platform.Azure.UltraSSDCapability
   410  		vmNetworkingType := ic.ControlPlane.Platform.Azure.VMNetworkingType
   411  		zones := ic.ControlPlane.Platform.Azure.Zones
   412  		architecture := ic.ControlPlane.Architecture
   413  
   414  		if ic.ControlPlane.Platform.Azure.Settings != nil {
   415  			securityType = ic.ControlPlane.Platform.Azure.Settings.SecurityType
   416  		}
   417  		if diskType == "" {
   418  			diskType = defaultDiskType
   419  		}
   420  		if instanceType == "" {
   421  			instanceType = defaultInstanceType
   422  			useDefaultInstanceType = true
   423  		}
   424  		if instanceType == "" {
   425  			instanceType = defaults.ControlPlaneInstanceType(ic.Azure.CloudName, ic.Azure.Region, architecture)
   426  		}
   427  		if ultraSSDCapability == "" {
   428  			ultraSSDCapability = defaultUltraSSDCapability
   429  		}
   430  		if vmNetworkingType == "" {
   431  			vmNetworkingType = defaultVMNetworkingType
   432  		}
   433  		if len(zones) == 0 {
   434  			zones = defaultZones
   435  		}
   436  		ultraSSDEnabled := strings.EqualFold(ultraSSDCapability, "Enabled")
   437  		allErrs = append(allErrs, ValidateInstanceType(client, fieldPath, ic.Azure.Region, instanceType, diskType, controlPlaneReq, ultraSSDEnabled, vmNetworkingType, zones, architecture, securityType)...)
   438  	}
   439  
   440  	for idx, compute := range ic.Compute {
   441  		fieldPath := field.NewPath("compute").Index(idx)
   442  		if compute.Platform.Azure != nil {
   443  			diskType := compute.Platform.Azure.OSDisk.DiskType
   444  			instanceType := compute.Platform.Azure.InstanceType
   445  			ultraSSDCapability := compute.Platform.Azure.UltraSSDCapability
   446  			vmNetworkingType := compute.Platform.Azure.VMNetworkingType
   447  			zones := compute.Platform.Azure.Zones
   448  			architecture := compute.Architecture
   449  
   450  			if compute.Platform.Azure.Settings != nil {
   451  				securityType = compute.Platform.Azure.Settings.SecurityType
   452  			}
   453  			if diskType == "" {
   454  				diskType = defaultDiskType
   455  			}
   456  			if instanceType == "" {
   457  				instanceType = defaultInstanceType
   458  				useDefaultInstanceType = true
   459  			}
   460  			if instanceType == "" {
   461  				instanceType = defaults.ComputeInstanceType(ic.Azure.CloudName, ic.Azure.Region, architecture)
   462  			}
   463  			if ultraSSDCapability == "" {
   464  				ultraSSDCapability = defaultUltraSSDCapability
   465  			}
   466  			if vmNetworkingType == "" {
   467  				vmNetworkingType = defaultVMNetworkingType
   468  			}
   469  			if len(zones) == 0 {
   470  				zones = defaultZones
   471  			}
   472  			ultraSSDEnabled := strings.EqualFold(ultraSSDCapability, "Enabled")
   473  			allErrs = append(allErrs, ValidateInstanceType(client, fieldPath.Child("platform", "azure"),
   474  				ic.Azure.Region, instanceType, diskType, computeReq, ultraSSDEnabled, vmNetworkingType, zones, architecture, securityType)...)
   475  		}
   476  	}
   477  
   478  	// checking here if the defaultInstanceType is present and not used previously. If so, check it now.
   479  	// The assumption here is that since cp and compute arches cannot differ today, it's ok to not check the
   480  	// default instance as long as it is used in any one place.
   481  	if !useDefaultInstanceType && defaultInstanceType != "" {
   482  		architecture := types.Architecture(types.ArchitectureAMD64)
   483  		if ic.ControlPlane != nil {
   484  			architecture = ic.ControlPlane.Architecture
   485  		}
   486  		minReq := computeReq
   487  		if ic.ControlPlane == nil || ic.ControlPlane.Platform.Azure == nil {
   488  			minReq = controlPlaneReq
   489  		}
   490  		fieldPath := field.NewPath("platform", "azure", "defaultMachinePlatform")
   491  		ultraSSDEnabled := strings.EqualFold(defaultUltraSSDCapability, "Enabled")
   492  		allErrs = append(allErrs, ValidateInstanceType(client, fieldPath,
   493  			ic.Azure.Region, defaultInstanceType, defaultDiskType, minReq, ultraSSDEnabled, defaultVMNetworkingType, defaultZones, architecture, securityType)...)
   494  	}
   495  	return allErrs
   496  }
   497  
   498  // validateNetworks checks that the user-provided VNet and subnets are valid.
   499  func validateNetworks(client API, p *aztypes.Platform, machineNetworks []types.MachineNetworkEntry, fieldPath *field.Path) field.ErrorList {
   500  	allErrs := field.ErrorList{}
   501  
   502  	if p.VirtualNetwork != "" {
   503  		_, err := client.GetVirtualNetwork(context.TODO(), p.NetworkResourceGroupName, p.VirtualNetwork)
   504  		if err != nil {
   505  			return append(allErrs, field.Invalid(fieldPath.Child("virtualNetwork"), p.VirtualNetwork, err.Error()))
   506  		}
   507  
   508  		computeSubnet, err := client.GetComputeSubnet(context.TODO(), p.NetworkResourceGroupName, p.VirtualNetwork, p.ComputeSubnet)
   509  		if err != nil {
   510  			return append(allErrs, field.Invalid(fieldPath.Child("computeSubnet"), p.ComputeSubnet, "failed to retrieve compute subnet"))
   511  		}
   512  
   513  		allErrs = append(allErrs, validateSubnet(client, fieldPath.Child("computeSubnet"), computeSubnet, p.ComputeSubnet, machineNetworks)...)
   514  
   515  		controlPlaneSubnet, err := client.GetControlPlaneSubnet(context.TODO(), p.NetworkResourceGroupName, p.VirtualNetwork, p.ControlPlaneSubnet)
   516  		if err != nil {
   517  			return append(allErrs, field.Invalid(fieldPath.Child("controlPlaneSubnet"), p.ControlPlaneSubnet, "failed to retrieve control plane subnet"))
   518  		}
   519  
   520  		allErrs = append(allErrs, validateSubnet(client, fieldPath.Child("controlPlaneSubnet"), controlPlaneSubnet, p.ControlPlaneSubnet, machineNetworks)...)
   521  	}
   522  
   523  	return allErrs
   524  }
   525  
   526  // validateSubnet checks that the subnet is in the same network as the machine CIDR
   527  func validateSubnet(client API, fieldPath *field.Path, subnet *aznetwork.Subnet, subnetName string, networks []types.MachineNetworkEntry) field.ErrorList {
   528  	allErrs := field.ErrorList{}
   529  
   530  	var addressPrefix string
   531  	switch {
   532  	case subnet.AddressPrefix != nil:
   533  		addressPrefix = *subnet.AddressPrefix
   534  	// NOTE: if the subscription has the `AllowMultipleAddressPrefixesOnSubnet` feature, the Azure API will return a
   535  	// `addressPrefixes` field with a slice of addresses instead of a single value via `addressPrefix`.
   536  	case subnet.AddressPrefixes != nil && len(*subnet.AddressPrefixes) > 0:
   537  		addressPrefix = (*subnet.AddressPrefixes)[0]
   538  	default:
   539  		return append(allErrs, field.Invalid(fieldPath, subnetName, "subnet does not have an address prefix"))
   540  	}
   541  
   542  	subnetIP, _, err := net.ParseCIDR(addressPrefix)
   543  	if err != nil {
   544  		return append(allErrs, field.Invalid(fieldPath, subnetName, "unable to parse subnet CIDR"))
   545  	}
   546  
   547  	allErrs = append(allErrs, validateMachineNetworksContainIP(fieldPath, networks, *subnet.Name, subnetIP)...)
   548  	return allErrs
   549  }
   550  
   551  func validateMachineNetworksContainIP(fldPath *field.Path, networks []types.MachineNetworkEntry, subnetName string, ip net.IP) field.ErrorList {
   552  	for _, network := range networks {
   553  		if network.CIDR.Contains(ip) {
   554  			return nil
   555  		}
   556  	}
   557  	return field.ErrorList{field.Invalid(fldPath, subnetName, fmt.Sprintf("subnet %s address prefix is outside of the specified machine networks", ip))}
   558  }
   559  
   560  // validateRegion checks that the desired region is valid and available to the user
   561  func validateRegion(client API, fieldPath *field.Path, p *aztypes.Platform) field.ErrorList {
   562  	locations, err := client.ListLocations(context.TODO())
   563  	if err != nil {
   564  		return field.ErrorList{field.InternalError(fieldPath, fmt.Errorf("failed to retrieve available regions: %w", err))}
   565  	}
   566  
   567  	availableRegions := map[string]string{}
   568  	for _, location := range *locations {
   569  		availableRegions[to.String(location.Name)] = to.String(location.DisplayName)
   570  	}
   571  
   572  	displayName, ok := availableRegions[p.Region]
   573  	if !ok {
   574  		errMsg := fmt.Sprintf("region %q is not valid or not available for this account", p.Region)
   575  
   576  		normalizedRegion := strings.Replace(strings.ToLower(p.Region), " ", "", -1)
   577  		if _, ok := availableRegions[normalizedRegion]; ok {
   578  			errMsg += fmt.Sprintf(", did you mean %q?", normalizedRegion)
   579  		}
   580  
   581  		return field.ErrorList{field.Invalid(fieldPath, p.Region, errMsg)}
   582  
   583  	}
   584  
   585  	provider, err := client.GetResourcesProvider(context.TODO(), "Microsoft.Resources")
   586  	if err != nil {
   587  		return field.ErrorList{field.InternalError(fieldPath, fmt.Errorf("failed to retrieve resource capable regions: %w", err))}
   588  	}
   589  
   590  	for _, resType := range *provider.ResourceTypes {
   591  		if *resType.ResourceType == "resourceGroups" {
   592  			for _, resourceCapableRegion := range *resType.Locations {
   593  				if resourceCapableRegion == displayName {
   594  					return field.ErrorList{}
   595  				}
   596  			}
   597  		}
   598  	}
   599  
   600  	return field.ErrorList{field.Invalid(fieldPath, p.Region, fmt.Sprintf("region %q does not support resource creation", p.Region))}
   601  }
   602  
   603  // ValidatePublicDNS checks DNS for CNAME, A, and AAA records for
   604  // api.zoneName. If a record exists, it's likely a cluster already exists.
   605  func ValidatePublicDNS(ic *types.InstallConfig, azureDNS *DNSConfig) error {
   606  	// If this is an internal cluster, this check is not necessary
   607  	if ic.Publish == types.InternalPublishingStrategy {
   608  		return nil
   609  	}
   610  
   611  	clusterName := ic.ObjectMeta.Name
   612  	record := fmt.Sprintf("api.%s", clusterName)
   613  	rgName := ic.Azure.BaseDomainResourceGroupName
   614  	zoneName := ic.BaseDomain
   615  	fmtStr := "api.%s %s record already exists in %s and might be in use by another cluster, please remove it to continue"
   616  
   617  	// Look for an existing CNAME first
   618  	rs, err := azureDNS.GetDNSRecordSet(rgName, zoneName, record, azdns.CNAME)
   619  	if err == nil && rs.CnameRecord != nil {
   620  		return fmt.Errorf(fmtStr, zoneName, azdns.CNAME, clusterName)
   621  	}
   622  
   623  	// Look for an A record
   624  	rs, err = azureDNS.GetDNSRecordSet(rgName, zoneName, record, azdns.A)
   625  	if err == nil && rs.ARecords != nil && len(*rs.ARecords) > 0 {
   626  		return fmt.Errorf(fmtStr, zoneName, azdns.A, clusterName)
   627  	}
   628  
   629  	// Look for an AAAA record
   630  	rs, err = azureDNS.GetDNSRecordSet(rgName, zoneName, record, azdns.AAAA)
   631  	if err == nil && rs.AaaaRecords != nil && len(*rs.AaaaRecords) > 0 {
   632  		return fmt.Errorf(fmtStr, zoneName, azdns.AAAA, clusterName)
   633  	}
   634  
   635  	return nil
   636  }
   637  
   638  // ValidateForProvisioning validates if the install config is valid for provisioning the cluster.
   639  func ValidateForProvisioning(client API, ic *types.InstallConfig) error {
   640  	allErrs := field.ErrorList{}
   641  	allErrs = append(allErrs, validateResourceGroup(client, field.NewPath("platform").Child("azure"), ic.Azure)...)
   642  	allErrs = append(allErrs, ValidateDiskEncryptionSet(client, ic)...)
   643  	allErrs = append(allErrs, ValidateSecurityProfileDiskEncryptionSet(client, ic)...)
   644  	allErrs = append(allErrs, validateSkipImageUpload(field.NewPath("image"), ic)...)
   645  	if ic.Azure.CloudName == aztypes.StackCloud {
   646  		allErrs = append(allErrs, checkAzureStackClusterOSImageSet(ic.Azure.ClusterOSImage, field.NewPath("platform").Child("azure"))...)
   647  	}
   648  	return allErrs.ToAggregate()
   649  }
   650  
   651  func validateSkipImageUpload(fieldPath *field.Path, ic *types.InstallConfig) field.ErrorList {
   652  	allErrs := field.ErrorList{}
   653  	defaultOSImage := aztypes.OSImage{}
   654  	if ic.Azure.DefaultMachinePlatform != nil {
   655  		defaultOSImage = aztypes.OSImage{
   656  			Plan:      ic.Azure.DefaultMachinePlatform.OSImage.Plan,
   657  			Publisher: ic.Azure.DefaultMachinePlatform.OSImage.Publisher,
   658  			SKU:       ic.Azure.DefaultMachinePlatform.OSImage.SKU,
   659  			Version:   ic.Azure.DefaultMachinePlatform.OSImage.Version,
   660  			Offer:     ic.Azure.DefaultMachinePlatform.OSImage.Offer,
   661  		}
   662  	}
   663  	controlPlaneOSImage := defaultOSImage
   664  	if ic.ControlPlane.Platform.Azure != nil {
   665  		controlPlaneOSImage = ic.ControlPlane.Platform.Azure.OSImage
   666  	}
   667  	allErrs = append(allErrs, validateOSImage(fieldPath.Child("controlplane"), controlPlaneOSImage)...)
   668  	computeOSImage := defaultOSImage
   669  	if len(ic.Compute) > 0 && ic.Compute[0].Platform.Azure != nil {
   670  		computeOSImage = ic.Compute[0].Platform.Azure.OSImage
   671  	}
   672  	allErrs = append(allErrs, validateOSImage(fieldPath.Child("compute"), computeOSImage)...)
   673  	return allErrs
   674  }
   675  func validateOSImage(fieldPath *field.Path, osImage aztypes.OSImage) field.ErrorList {
   676  	if _, ok := os.LookupEnv("OPENSHIFT_INSTALL_SKIP_IMAGE_UPLOAD"); ok {
   677  		if len(osImage.SKU) > 0 {
   678  			return nil
   679  		}
   680  		return field.ErrorList{field.Invalid(fieldPath, "image", "cannot skip image upload without marketplace image specified")}
   681  	}
   682  	return nil
   683  }
   684  func validateResourceGroup(client API, fieldPath *field.Path, platform *aztypes.Platform) field.ErrorList {
   685  	allErrs := field.ErrorList{}
   686  	if len(platform.ResourceGroupName) == 0 {
   687  		return allErrs
   688  	}
   689  	group, err := client.GetGroup(context.TODO(), platform.ResourceGroupName)
   690  	if err != nil {
   691  		return append(allErrs, field.InternalError(fieldPath.Child("resourceGroupName"), fmt.Errorf("failed to get resource group: %w", err)))
   692  	}
   693  
   694  	normalizedRegion := strings.Replace(strings.ToLower(to.String(group.Location)), " ", "", -1)
   695  	if !strings.EqualFold(normalizedRegion, platform.Region) {
   696  		allErrs = append(allErrs, field.Invalid(fieldPath.Child("resourceGroupName"), platform.ResourceGroupName, fmt.Sprintf("expected to in region %s, but found it to be in %s", platform.Region, normalizedRegion)))
   697  	}
   698  
   699  	tagKeys := make([]string, 0, len(group.Tags))
   700  	for key := range group.Tags {
   701  		tagKeys = append(tagKeys, key)
   702  	}
   703  	sort.Strings(tagKeys)
   704  	conflictingTagKeys := tagKeys[:0]
   705  	for _, key := range tagKeys {
   706  		if strings.HasPrefix(key, "kubernetes.io_cluster") {
   707  			conflictingTagKeys = append(conflictingTagKeys, key)
   708  		}
   709  	}
   710  	if len(conflictingTagKeys) > 0 {
   711  		allErrs = append(allErrs, field.Invalid(fieldPath.Child("resourceGroupName"), platform.ResourceGroupName, fmt.Sprintf("resource group has conflicting tags %s", strings.Join(conflictingTagKeys, ", "))))
   712  	}
   713  
   714  	// ARO provisions Azure resources before resolving the asset graph.
   715  	if !platform.IsARO() {
   716  		ids, err := client.ListResourceIDsByGroup(context.TODO(), platform.ResourceGroupName)
   717  		if err != nil {
   718  			return append(allErrs, field.InternalError(fieldPath.Child("resourceGroupName"), fmt.Errorf("failed to list resources in the resource group: %w", err)))
   719  		}
   720  		if l := len(ids); l > 0 {
   721  			if len(ids) > 2 {
   722  				ids = ids[:2]
   723  			}
   724  			allErrs = append(allErrs, field.Invalid(fieldPath.Child("resourceGroupName"), platform.ResourceGroupName, fmt.Sprintf("resource group must be empty but it has %d resources like %s ...", l, strings.Join(ids, ", "))))
   725  		}
   726  	}
   727  	return allErrs
   728  }
   729  
   730  func checkAzureStackClusterOSImageSet(ClusterOSImage string, fldPath *field.Path) field.ErrorList {
   731  	var allErrs field.ErrorList
   732  	if ClusterOSImage == "" {
   733  		allErrs = append(allErrs, field.Required(fldPath.Child("clusterOSImage"), "clusterOSImage must be set when installing on Azure Stack"))
   734  	}
   735  	return allErrs
   736  }
   737  
   738  func validateAzureStackClusterOSImage(StorageEndpointSuffix string, ClusterOSImage string, fldPath *field.Path) field.ErrorList {
   739  	var allErrs field.ErrorList
   740  	imageParsedURL, err := url.Parse(ClusterOSImage)
   741  	if err != nil {
   742  		allErrs = append(allErrs, field.Invalid(fldPath.Child("clusterOSImage"), ClusterOSImage, fmt.Errorf("clusterOSImage URL is invalid: %w", err).Error()))
   743  	}
   744  	// If the URL for the image isn't in the Azure Stack environment we can't use it.
   745  	if !strings.HasSuffix(imageParsedURL.Host, StorageEndpointSuffix) {
   746  		allErrs = append(allErrs, field.Invalid(fldPath.Child("clusterOSImage"), ClusterOSImage, "clusterOSImage must be in the Azure Stack environment"))
   747  	}
   748  	return allErrs
   749  }
   750  
   751  func validateMarketplaceImages(client API, installConfig *types.InstallConfig) field.ErrorList {
   752  	var allErrs field.ErrorList
   753  
   754  	region := installConfig.Azure.Region
   755  	cloudName := installConfig.Azure.CloudName
   756  
   757  	var defaultInstanceType string
   758  	var defaultOSImage aztypes.OSImage
   759  	if installConfig.Azure.DefaultMachinePlatform != nil {
   760  		defaultInstanceType = installConfig.Azure.DefaultMachinePlatform.InstanceType
   761  		defaultOSImage = installConfig.Azure.DefaultMachinePlatform.OSImage
   762  	}
   763  
   764  	// Validate ControlPlane marketplace images
   765  	if installConfig.ControlPlane != nil {
   766  		platform := installConfig.ControlPlane.Platform.Azure
   767  		fldPath := field.NewPath("controlPlane")
   768  
   769  		// Determine instance type
   770  		instanceType := ""
   771  		if platform != nil {
   772  			instanceType = platform.InstanceType
   773  		}
   774  		if instanceType == "" {
   775  			instanceType = defaultInstanceType
   776  		}
   777  		if instanceType == "" {
   778  			instanceType = defaults.ControlPlaneInstanceType(cloudName, region, installConfig.ControlPlane.Architecture)
   779  		}
   780  
   781  		capabilities, err := client.GetVMCapabilities(context.Background(), instanceType, region)
   782  		if err != nil {
   783  			allErrs = append(allErrs, field.Invalid(fldPath.Child("platform", "azure", "type"), instanceType, err.Error()))
   784  		}
   785  
   786  		generations, err := GetHyperVGenerationVersions(capabilities)
   787  		if err != nil {
   788  			allErrs = append(allErrs, field.Invalid(fldPath.Child("platform", "azure", "type"), instanceType, err.Error()))
   789  		}
   790  
   791  		// If not set, try to use the OS Image definition from the default machine pool
   792  		var osImage aztypes.OSImage
   793  		if platform != nil {
   794  			osImage = platform.OSImage
   795  		}
   796  		if osImage.Publisher == "" {
   797  			osImage = defaultOSImage
   798  		}
   799  
   800  		imgErr := validateMarketplaceImage(client, region, generations, &osImage, fldPath)
   801  		if imgErr != nil {
   802  			allErrs = append(allErrs, imgErr)
   803  		}
   804  	}
   805  
   806  	// Validate Compute marketplace images
   807  	for i, compute := range installConfig.Compute {
   808  		platform := compute.Platform.Azure
   809  		fldPath := field.NewPath("compute").Index(i)
   810  
   811  		// Determine instance type
   812  		instanceType := ""
   813  		if platform != nil {
   814  			instanceType = platform.InstanceType
   815  		}
   816  		if instanceType == "" {
   817  			instanceType = defaultInstanceType
   818  		}
   819  		if instanceType == "" {
   820  			instanceType = defaults.ComputeInstanceType(cloudName, region, compute.Architecture)
   821  		}
   822  
   823  		capabilities, err := client.GetVMCapabilities(context.Background(), instanceType, region)
   824  		if err != nil {
   825  			allErrs = append(allErrs, field.Invalid(fldPath.Child("platform", "azure", "type"), instanceType, err.Error()))
   826  			continue
   827  		}
   828  
   829  		generations, err := GetHyperVGenerationVersions(capabilities)
   830  		if err != nil {
   831  			allErrs = append(allErrs, field.Invalid(fldPath.Child("platform", "azure", "type"), instanceType, err.Error()))
   832  			continue
   833  		}
   834  
   835  		// If not set, try to use the OS Image definition from the default machine pool
   836  		var osImage aztypes.OSImage
   837  		if platform != nil {
   838  			osImage = platform.OSImage
   839  		}
   840  		if osImage.Publisher == "" {
   841  			osImage = defaultOSImage
   842  		}
   843  		imgErr := validateMarketplaceImage(client, region, generations, &osImage, fldPath)
   844  		if imgErr != nil {
   845  			allErrs = append(allErrs, imgErr)
   846  		}
   847  	}
   848  
   849  	return allErrs
   850  }
   851  
   852  func validateMarketplaceImage(client API, region string, instanceHyperVGenSet sets.Set[string], osImage *aztypes.OSImage, fldPath *field.Path) *field.Error {
   853  	// Marketplace image not specified
   854  	if osImage.Publisher == "" {
   855  		return nil
   856  	}
   857  
   858  	osImageFieldPath := fldPath.Child("platform", "azure", "osImage")
   859  	vmImage, err := client.GetMarketplaceImage(
   860  		context.Background(),
   861  		region,
   862  		osImage.Publisher,
   863  		osImage.Offer,
   864  		osImage.SKU,
   865  		osImage.Version,
   866  	)
   867  	if err != nil {
   868  		return field.Invalid(osImageFieldPath, osImage, err.Error())
   869  	}
   870  	imageHyperVGen := string(vmImage.HyperVGeneration)
   871  	if !instanceHyperVGenSet.Has(imageHyperVGen) {
   872  		errMsg := fmt.Sprintf("instance type supports HyperVGenerations %v but the specified image is for HyperVGeneration %s; to correct this issue either specify a compatible instance type or change the HyperVGeneration for the image by using a different SKU", instanceHyperVGenSet.UnsortedList(), imageHyperVGen)
   873  		return field.Invalid(osImageFieldPath, osImage.SKU, errMsg)
   874  	}
   875  
   876  	// Image has license terms to be accepted
   877  	osImagePlan := osImage.Plan
   878  	if len(osImagePlan) == 0 {
   879  		// Use the default if not set in the install-config
   880  		osImagePlan = aztypes.ImageWithPurchasePlan
   881  	}
   882  	if plan := vmImage.Plan; plan != nil {
   883  		if osImagePlan == aztypes.ImageNoPurchasePlan {
   884  			return field.Invalid(osImageFieldPath, osImage, "marketplace image requires license terms to be accepted")
   885  		}
   886  		termsAccepted, err := client.AreMarketplaceImageTermsAccepted(context.Background(), osImage.Publisher, osImage.Offer, osImage.SKU)
   887  		if err != nil {
   888  			return field.Invalid(osImageFieldPath, osImage, fmt.Sprintf("could not determine if the license terms for the marketplace image have been accepted: %v", err))
   889  		}
   890  		if !termsAccepted {
   891  			return field.Invalid(osImageFieldPath, osImage, "the license terms for the marketplace image have not been accepted")
   892  		}
   893  	} else if osImagePlan == aztypes.ImageWithPurchasePlan {
   894  		return field.Invalid(osImageFieldPath, osImage, "image has no license terms. Set Plan to \"NoPurchasePlan\" to continue.")
   895  	}
   896  
   897  	return nil
   898  }
   899  
   900  func validateAzureStackDiskType(_ API, installConfig *types.InstallConfig) field.ErrorList {
   901  	var allErrs field.ErrorList
   902  
   903  	supportedTypes := sets.New("Premium_LRS", "Standard_LRS")
   904  	errMsg := fmt.Sprintf("disk format not supported. Must be one of %v", sets.List(supportedTypes))
   905  
   906  	defaultDiskType := aztypes.DefaultDiskType
   907  	if installConfig.Azure.DefaultMachinePlatform != nil &&
   908  		installConfig.Azure.DefaultMachinePlatform.DiskType != "" {
   909  		defaultDiskType = installConfig.Azure.DefaultMachinePlatform.DiskType
   910  	}
   911  
   912  	diskType := defaultDiskType
   913  	if installConfig.ControlPlane != nil &&
   914  		installConfig.ControlPlane.Platform.Azure != nil &&
   915  		installConfig.ControlPlane.Platform.Azure.DiskType != "" {
   916  		diskType = installConfig.ControlPlane.Platform.Azure.DiskType
   917  	}
   918  	if !supportedTypes.Has(diskType) {
   919  		allErrs = append(allErrs, field.Invalid(field.NewPath("controlPlane", "platform", "azure", "OSDisk", "diskType"), diskType, errMsg))
   920  	}
   921  
   922  	for idx, compute := range installConfig.Compute {
   923  		fieldPath := field.NewPath("compute").Index(idx)
   924  		diskType := defaultDiskType
   925  		if compute.Platform.Azure != nil && compute.Platform.Azure.DiskType != "" {
   926  			diskType = compute.Platform.Azure.DiskType
   927  		}
   928  		if !supportedTypes.Has(diskType) {
   929  			allErrs = append(allErrs, field.Invalid(fieldPath.Child("platform", "azure", "OSDisk", "diskType"), diskType, errMsg))
   930  		}
   931  	}
   932  
   933  	return allErrs
   934  }