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

     1  // Package azure generates Machine objects for azure.
     2  package azure
     3  
     4  import (
     5  	"fmt"
     6  	"strings"
     7  
     8  	v1 "k8s.io/api/core/v1"
     9  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    10  	"k8s.io/apimachinery/pkg/util/sets"
    11  	"k8s.io/utils/ptr"
    12  	capz "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
    13  	capi "sigs.k8s.io/cluster-api/api/v1beta1"
    14  
    15  	"github.com/openshift/api/machine/v1beta1"
    16  	"github.com/openshift/installer/pkg/asset"
    17  	"github.com/openshift/installer/pkg/asset/manifests/capiutils"
    18  	"github.com/openshift/installer/pkg/types"
    19  	"github.com/openshift/installer/pkg/types/azure"
    20  )
    21  
    22  const (
    23  	genV2Suffix string = "-gen2"
    24  )
    25  
    26  // MachineInput defines the inputs needed to generate a machine asset.
    27  type MachineInput struct {
    28  	Subnet          string
    29  	Role            string
    30  	UserDataSecret  string
    31  	HyperVGen       string
    32  	UseImageGallery bool
    33  	Private         bool
    34  	UserTags        map[string]string
    35  	Platform        *azure.Platform
    36  	Pool            *types.MachinePool
    37  }
    38  
    39  // GenerateMachines returns manifests and runtime objects to provision the control plane (including bootstrap, if applicable) nodes using CAPI.
    40  func GenerateMachines(clusterID, resourceGroup, subscriptionID string, in *MachineInput) ([]*asset.RuntimeFile, error) {
    41  	if poolPlatform := in.Pool.Platform.Name(); poolPlatform != azure.Name {
    42  		return nil, fmt.Errorf("non-Azure machine-pool: %q", poolPlatform)
    43  	}
    44  	mpool := in.Pool.Platform.Azure
    45  
    46  	total := int64(1)
    47  	if in.Pool.Replicas != nil {
    48  		total = *in.Pool.Replicas
    49  	}
    50  
    51  	if len(mpool.Zones) == 0 {
    52  		// if no azs are given we set to []string{""} for convenience over later operations.
    53  		// It means no-zoned for the machine API
    54  		mpool.Zones = []string{""}
    55  	}
    56  	tags, err := CapzTagsFromUserTags(clusterID, in.UserTags)
    57  	if err != nil {
    58  		return nil, fmt.Errorf("failed to create machineapi.TagSpecifications from UserTags: %w", err)
    59  	}
    60  
    61  	userAssignedIdentityID := fmt.Sprintf("/subscriptions/%s/resourcegroups/%s/providers/Microsoft.ManagedIdentity/userAssignedIdentities/%s-identity", subscriptionID, resourceGroup, clusterID)
    62  
    63  	var image *capz.Image
    64  	osImage := mpool.OSImage
    65  	galleryName := strings.ReplaceAll(clusterID, "-", "_")
    66  
    67  	switch {
    68  	case osImage.Publisher != "":
    69  		image = &capz.Image{
    70  			Marketplace: &capz.AzureMarketplaceImage{
    71  				ImagePlan: capz.ImagePlan{
    72  					Publisher: osImage.Publisher,
    73  					Offer:     osImage.Offer,
    74  					SKU:       osImage.SKU,
    75  				},
    76  				Version:         osImage.Version,
    77  				ThirdPartyImage: osImage.Plan != azure.ImageNoPurchasePlan,
    78  			},
    79  		}
    80  	case in.UseImageGallery:
    81  		// image gallery names cannot have dashes
    82  		id := clusterID
    83  		if in.HyperVGen == "V2" {
    84  			id += genV2Suffix
    85  		}
    86  		imageID := fmt.Sprintf("/resourceGroups/%s/providers/Microsoft.Compute/galleries/gallery_%s/images/%s/versions/latest", resourceGroup, galleryName, id)
    87  		image = &capz.Image{ID: &imageID}
    88  	default:
    89  		imageID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/galleries/gallery_%s/images/%s", subscriptionID, resourceGroup, galleryName, clusterID)
    90  		if in.HyperVGen == "V2" && in.Platform.CloudName != azure.StackCloud {
    91  			imageID += genV2Suffix
    92  		}
    93  		image = &capz.Image{ID: &imageID}
    94  	}
    95  
    96  	osDisk := capz.OSDisk{
    97  		OSType:     "Linux",
    98  		DiskSizeGB: &mpool.DiskSizeGB,
    99  		ManagedDisk: &capz.ManagedDiskParameters{
   100  			StorageAccountType: mpool.DiskType,
   101  		},
   102  		CachingType: "ReadWrite",
   103  	}
   104  	ultrassd := mpool.UltraSSDCapability == "Enabled"
   105  	additionalCapabilities := &capz.AdditionalCapabilities{
   106  		UltraSSDEnabled: &ultrassd,
   107  	}
   108  	if in.Pool.Platform.Azure.DiskEncryptionSet != nil {
   109  		osDisk.ManagedDisk.DiskEncryptionSet = &capz.DiskEncryptionSetParameters{
   110  			ID: mpool.OSDisk.DiskEncryptionSet.ToID(),
   111  		}
   112  	}
   113  
   114  	machineProfile := generateSecurityProfile(mpool)
   115  	securityProfile := &capz.SecurityProfile{
   116  		EncryptionAtHost: machineProfile.EncryptionAtHost,
   117  		SecurityType:     capz.SecurityTypes(machineProfile.Settings.SecurityType),
   118  	}
   119  	if machineProfile.Settings.ConfidentialVM != nil {
   120  		securityProfile.UefiSettings = &capz.UefiSettings{
   121  			VTpmEnabled:       ptr.To[bool](machineProfile.Settings.ConfidentialVM.UEFISettings.VirtualizedTrustedPlatformModule == v1beta1.VirtualizedTrustedPlatformModulePolicyEnabled),
   122  			SecureBootEnabled: ptr.To[bool](machineProfile.Settings.ConfidentialVM.UEFISettings.SecureBoot == v1beta1.SecureBootPolicyEnabled),
   123  		}
   124  	} else if machineProfile.Settings.TrustedLaunch != nil {
   125  		securityProfile.UefiSettings = &capz.UefiSettings{
   126  			VTpmEnabled:       ptr.To(machineProfile.Settings.TrustedLaunch.UEFISettings.VirtualizedTrustedPlatformModule == v1beta1.VirtualizedTrustedPlatformModulePolicyEnabled),
   127  			SecureBootEnabled: ptr.To(machineProfile.Settings.TrustedLaunch.UEFISettings.SecureBoot == v1beta1.SecureBootPolicyEnabled),
   128  		}
   129  	}
   130  
   131  	var result []*asset.RuntimeFile
   132  	for idx := int64(0); idx < total; idx++ {
   133  		zone := mpool.Zones[int(idx)%len(mpool.Zones)]
   134  		azureMachine := &capz.AzureMachine{
   135  			ObjectMeta: metav1.ObjectMeta{
   136  				Name: fmt.Sprintf("%s-%s-%d", clusterID, in.Pool.Name, idx),
   137  				Labels: map[string]string{
   138  					"cluster.x-k8s.io/control-plane": "",
   139  					"cluster.x-k8s.io/cluster-name":  clusterID,
   140  				},
   141  			},
   142  			Spec: capz.AzureMachineSpec{
   143  				VMSize:                     mpool.InstanceType,
   144  				FailureDomain:              ptr.To(zone),
   145  				Image:                      image,
   146  				OSDisk:                     osDisk, // required
   147  				AdditionalTags:             tags,
   148  				AdditionalCapabilities:     additionalCapabilities,
   149  				DisableExtensionOperations: ptr.To(true),
   150  				AllocatePublicIP:           false,
   151  				EnableIPForwarding:         false,
   152  				SecurityProfile:            securityProfile,
   153  				NetworkInterfaces: []capz.NetworkInterface{
   154  					{
   155  						SubnetName:            in.Subnet,
   156  						AcceleratedNetworking: ptr.To(mpool.VMNetworkingType == string(azure.VMnetworkingTypeAccelerated) || mpool.VMNetworkingType == string(azure.AcceleratedNetworkingEnabled)),
   157  					},
   158  				},
   159  				Identity: capz.VMIdentityUserAssigned,
   160  				UserAssignedIdentities: []capz.UserAssignedIdentity{
   161  					{
   162  						ProviderID: userAssignedIdentityID,
   163  					},
   164  				},
   165  			},
   166  		}
   167  		azureMachine.SetGroupVersionKind(capz.GroupVersion.WithKind("AzureMachine"))
   168  		result = append(result, &asset.RuntimeFile{
   169  			File:   asset.File{Filename: fmt.Sprintf("10_inframachine_%s.yaml", azureMachine.Name)},
   170  			Object: azureMachine,
   171  		})
   172  
   173  		controlPlaneMachine := &capi.Machine{
   174  			ObjectMeta: metav1.ObjectMeta{
   175  				Name: azureMachine.Name,
   176  				Labels: map[string]string{
   177  					"cluster.x-k8s.io/control-plane": "",
   178  				},
   179  			},
   180  			Spec: capi.MachineSpec{
   181  				ClusterName: clusterID,
   182  				Bootstrap: capi.Bootstrap{
   183  					DataSecretName: ptr.To(fmt.Sprintf("%s-%s", clusterID, in.Role)),
   184  				},
   185  				InfrastructureRef: v1.ObjectReference{
   186  					APIVersion: capz.GroupVersion.String(),
   187  					Kind:       "AzureMachine",
   188  					Name:       azureMachine.Name,
   189  				},
   190  			},
   191  		}
   192  		controlPlaneMachine.SetGroupVersionKind(capi.GroupVersion.WithKind("Machine"))
   193  
   194  		result = append(result, &asset.RuntimeFile{
   195  			File:   asset.File{Filename: fmt.Sprintf("10_machine_%s.yaml", azureMachine.Name)},
   196  			Object: controlPlaneMachine,
   197  		})
   198  	}
   199  
   200  	bootstrapAzureMachine := &capz.AzureMachine{
   201  		ObjectMeta: metav1.ObjectMeta{
   202  			Name: capiutils.GenerateBoostrapMachineName(clusterID),
   203  			Labels: map[string]string{
   204  				"cluster.x-k8s.io/control-plane": "",
   205  				"install.openshift.io/bootstrap": "",
   206  				"cluster.x-k8s.io/cluster-name":  clusterID,
   207  			},
   208  		},
   209  		Spec: capz.AzureMachineSpec{
   210  			VMSize:                     mpool.InstanceType,
   211  			Image:                      image,
   212  			FailureDomain:              ptr.To(mpool.Zones[0]),
   213  			OSDisk:                     osDisk,
   214  			AdditionalTags:             tags,
   215  			DisableExtensionOperations: ptr.To(true),
   216  			AllocatePublicIP:           !in.Private,
   217  			AdditionalCapabilities:     additionalCapabilities,
   218  			SecurityProfile:            securityProfile,
   219  			Identity:                   capz.VMIdentityUserAssigned,
   220  			UserAssignedIdentities: []capz.UserAssignedIdentity{
   221  				{
   222  					ProviderID: userAssignedIdentityID,
   223  				},
   224  			},
   225  		},
   226  	}
   227  	bootstrapAzureMachine.SetGroupVersionKind(capz.GroupVersion.WithKind("AzureMachine"))
   228  
   229  	result = append(result, &asset.RuntimeFile{
   230  		File:   asset.File{Filename: fmt.Sprintf("10_inframachine_%s.yaml", bootstrapAzureMachine.Name)},
   231  		Object: bootstrapAzureMachine,
   232  	})
   233  
   234  	bootstrapMachine := &capi.Machine{
   235  		ObjectMeta: metav1.ObjectMeta{
   236  			Name: bootstrapAzureMachine.Name,
   237  			Labels: map[string]string{
   238  				"cluster.x-k8s.io/control-plane": "",
   239  			},
   240  		},
   241  		Spec: capi.MachineSpec{
   242  			ClusterName: clusterID,
   243  			Bootstrap: capi.Bootstrap{
   244  				DataSecretName: ptr.To(fmt.Sprintf("%s-%s", clusterID, "bootstrap")),
   245  			},
   246  			InfrastructureRef: v1.ObjectReference{
   247  				APIVersion: capz.GroupVersion.String(),
   248  				Kind:       "AzureMachine",
   249  				Name:       bootstrapAzureMachine.Name,
   250  			},
   251  		},
   252  	}
   253  	bootstrapMachine.SetGroupVersionKind(capi.GroupVersion.WithKind("Machine"))
   254  
   255  	result = append(result, &asset.RuntimeFile{
   256  		File:   asset.File{Filename: fmt.Sprintf("10_machine_%s.yaml", bootstrapMachine.Name)},
   257  		Object: bootstrapMachine,
   258  	})
   259  
   260  	return result, nil
   261  }
   262  
   263  // CapzTagsFromUserTags converts a map of user tags to a map of capz.Tags.
   264  func CapzTagsFromUserTags(clusterID string, usertags map[string]string) (capz.Tags, error) {
   265  	tags := capz.Tags{}
   266  	tags[fmt.Sprintf("kubernetes.io_cluster.%s", clusterID)] = "owned"
   267  
   268  	forbiddenTags := sets.New[string]()
   269  	for key := range tags {
   270  		forbiddenTags.Insert(key)
   271  	}
   272  
   273  	userTagKeys := sets.New[string]()
   274  	for key := range usertags {
   275  		userTagKeys.Insert(key)
   276  	}
   277  	if clobberedTags := userTagKeys.Intersection(forbiddenTags); clobberedTags.Len() > 0 {
   278  		return nil, fmt.Errorf("user tag keys %v are not allowed", sets.List(clobberedTags))
   279  	}
   280  	for _, k := range sets.List(userTagKeys) {
   281  		tags[k] = usertags[k]
   282  	}
   283  	return tags, nil
   284  }