Skip to content

Commit 65fba4f

Browse files
Merge pull request openshift#7969 from rna-afk/machine_manifests_capz
CORS-3251: Create capz machine manifests
2 parents 1a50ad3 + dcb741f commit 65fba4f

File tree

8 files changed

+344
-5
lines changed

8 files changed

+344
-5
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ require (
110110
k8s.io/utils v0.0.0-20240102154912-e7106e64919e
111111
sigs.k8s.io/cluster-api v1.6.2
112112
sigs.k8s.io/cluster-api-provider-aws/v2 v2.0.0-00010101000000-000000000000
113-
sigs.k8s.io/cluster-api-provider-azure v0.0.0-00010101000000-000000000000
113+
sigs.k8s.io/cluster-api-provider-azure v1.13.0
114114
sigs.k8s.io/cluster-api-provider-gcp v1.5.0
115115
sigs.k8s.io/cluster-api-provider-openstack v0.8.0
116116
sigs.k8s.io/cluster-api-provider-vsphere v1.9.0
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
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+
// GenerateMachines returns manifests and runtime objects to provision the control plane (including bootstrap, if applicable) nodes using CAPI.
27+
func GenerateMachines(platform *azure.Platform, pool *types.MachinePool, userDataSecret string, clusterID string, role string, capabilities map[string]string, useImageGallery bool, userTags map[string]string, hyperVGen string, subnet string, resourceGroup string) ([]*asset.RuntimeFile, error) {
28+
if poolPlatform := pool.Platform.Name(); poolPlatform != azure.Name {
29+
return nil, fmt.Errorf("non-Azure machine-pool: %q", poolPlatform)
30+
}
31+
mpool := pool.Platform.Azure
32+
33+
total := int64(1)
34+
if pool.Replicas != nil {
35+
total = *pool.Replicas
36+
}
37+
38+
if len(mpool.Zones) == 0 {
39+
// if no azs are given we set to []string{""} for convenience over later operations.
40+
// It means no-zoned for the machine API
41+
mpool.Zones = []string{""}
42+
}
43+
tags, err := CapzTagsFromUserTags(clusterID, userTags)
44+
if err != nil {
45+
return nil, fmt.Errorf("failed to create machineapi.TagSpecifications from UserTags: %w", err)
46+
}
47+
48+
var image *capz.Image
49+
osImage := mpool.OSImage
50+
switch {
51+
case osImage.Publisher != "":
52+
image = &capz.Image{
53+
Marketplace: &capz.AzureMarketplaceImage{
54+
ImagePlan: capz.ImagePlan{
55+
Publisher: osImage.Publisher,
56+
Offer: osImage.Offer,
57+
SKU: osImage.SKU,
58+
},
59+
Version: osImage.Version,
60+
},
61+
}
62+
case useImageGallery:
63+
// image gallery names cannot have dashes
64+
galleryName := strings.ReplaceAll(clusterID, "-", "_")
65+
id := clusterID
66+
if hyperVGen == "V2" {
67+
id += genV2Suffix
68+
}
69+
imageID := fmt.Sprintf("/resourceGroups/%s/providers/Microsoft.Compute/galleries/gallery_%s/images/%s/versions/latest", resourceGroup, galleryName, id)
70+
image = &capz.Image{ID: &imageID}
71+
default:
72+
imageID := fmt.Sprintf("/resourceGroups/%s/providers/Microsoft.Compute/images/%s", resourceGroup, clusterID)
73+
if hyperVGen == "V2" && platform.CloudName != azure.StackCloud {
74+
imageID += genV2Suffix
75+
}
76+
image = &capz.Image{ID: &imageID}
77+
}
78+
79+
osDisk := capz.OSDisk{
80+
OSType: "Linux",
81+
DiskSizeGB: &mpool.DiskSizeGB,
82+
ManagedDisk: &capz.ManagedDiskParameters{
83+
StorageAccountType: mpool.DiskType,
84+
},
85+
}
86+
ultrassd := mpool.UltraSSDCapability == "Enabled"
87+
additionalCapabilities := &capz.AdditionalCapabilities{
88+
UltraSSDEnabled: &ultrassd,
89+
}
90+
91+
machineProfile := generateSecurityProfile(mpool)
92+
securityProfile := &capz.SecurityProfile{
93+
EncryptionAtHost: machineProfile.EncryptionAtHost,
94+
SecurityType: capz.SecurityTypes(machineProfile.Settings.SecurityType),
95+
}
96+
if machineProfile.Settings.ConfidentialVM != nil {
97+
securityProfile.UefiSettings = &capz.UefiSettings{
98+
VTpmEnabled: ptr.To[bool](machineProfile.Settings.ConfidentialVM.UEFISettings.VirtualizedTrustedPlatformModule == v1beta1.VirtualizedTrustedPlatformModulePolicyEnabled),
99+
SecureBootEnabled: ptr.To[bool](machineProfile.Settings.ConfidentialVM.UEFISettings.SecureBoot == v1beta1.SecureBootPolicyEnabled),
100+
}
101+
} else if machineProfile.Settings.TrustedLaunch != nil {
102+
securityProfile.UefiSettings = &capz.UefiSettings{
103+
VTpmEnabled: ptr.To(machineProfile.Settings.TrustedLaunch.UEFISettings.VirtualizedTrustedPlatformModule == v1beta1.VirtualizedTrustedPlatformModulePolicyEnabled),
104+
SecureBootEnabled: ptr.To(machineProfile.Settings.TrustedLaunch.UEFISettings.SecureBoot == v1beta1.SecureBootPolicyEnabled),
105+
}
106+
}
107+
108+
var result []*asset.RuntimeFile
109+
for idx := int64(0); idx < total; idx++ {
110+
zone := mpool.Zones[int(idx)%len(mpool.Zones)]
111+
azureMachine := &capz.AzureMachine{
112+
TypeMeta: metav1.TypeMeta{
113+
APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
114+
Kind: "AzureMachine",
115+
},
116+
ObjectMeta: metav1.ObjectMeta{
117+
Name: fmt.Sprintf("%s-%s-%d", clusterID, pool.Name, idx),
118+
Labels: map[string]string{
119+
"cluster.x-k8s.io/control-plane": "",
120+
"cluster.x-k8s.io/cluster-name": clusterID,
121+
},
122+
},
123+
Spec: capz.AzureMachineSpec{
124+
VMSize: mpool.InstanceType,
125+
FailureDomain: ptr.To(zone),
126+
Image: image,
127+
OSDisk: osDisk,
128+
AdditionalTags: tags,
129+
AdditionalCapabilities: additionalCapabilities,
130+
AllocatePublicIP: false,
131+
SecurityProfile: securityProfile,
132+
},
133+
}
134+
result = append(result, &asset.RuntimeFile{
135+
File: asset.File{Filename: fmt.Sprintf("10_inframachine_%s.yaml", azureMachine.Name)},
136+
Object: azureMachine,
137+
})
138+
139+
controlPlaneMachine := &capi.Machine{
140+
ObjectMeta: metav1.ObjectMeta{
141+
Name: azureMachine.Name,
142+
Labels: map[string]string{
143+
"cluster.x-k8s.io/control-plane": "",
144+
},
145+
},
146+
Spec: capi.MachineSpec{
147+
ClusterName: clusterID,
148+
Bootstrap: capi.Bootstrap{
149+
DataSecretName: ptr.To(fmt.Sprintf("%s-%s", clusterID, role)),
150+
},
151+
InfrastructureRef: v1.ObjectReference{
152+
APIVersion: "infrastructure.cluster.x-k8s.io/v1beta2",
153+
Kind: "AzureMachine",
154+
Name: azureMachine.Name,
155+
},
156+
},
157+
}
158+
159+
result = append(result, &asset.RuntimeFile{
160+
File: asset.File{Filename: fmt.Sprintf("10_machine_%s.yaml", azureMachine.Name)},
161+
Object: controlPlaneMachine,
162+
})
163+
}
164+
165+
bootstrapAzureMachine := &capz.AzureMachine{
166+
TypeMeta: metav1.TypeMeta{
167+
APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
168+
Kind: "AzureMachine",
169+
},
170+
ObjectMeta: metav1.ObjectMeta{
171+
Name: capiutils.GenerateBoostrapMachineName(clusterID),
172+
Labels: map[string]string{
173+
"cluster.x-k8s.io/control-plane": "",
174+
"install.openshift.io/bootstrap": "",
175+
"cluster.x-k8s.io/cluster-name": clusterID,
176+
},
177+
},
178+
Spec: capz.AzureMachineSpec{
179+
VMSize: mpool.InstanceType,
180+
Image: image,
181+
FailureDomain: ptr.To(mpool.Zones[0]),
182+
OSDisk: osDisk,
183+
AdditionalTags: tags,
184+
AllocatePublicIP: true,
185+
AdditionalCapabilities: additionalCapabilities,
186+
SecurityProfile: securityProfile,
187+
},
188+
}
189+
190+
result = append(result, &asset.RuntimeFile{
191+
File: asset.File{Filename: fmt.Sprintf("10_inframachine_%s.yaml", bootstrapAzureMachine.Name)},
192+
Object: bootstrapAzureMachine,
193+
})
194+
195+
bootstrapMachine := &capi.Machine{
196+
ObjectMeta: metav1.ObjectMeta{
197+
Name: bootstrapAzureMachine.Name,
198+
Labels: map[string]string{
199+
"cluster.x-k8s.io/control-plane": "",
200+
},
201+
},
202+
Spec: capi.MachineSpec{
203+
ClusterName: clusterID,
204+
Bootstrap: capi.Bootstrap{
205+
DataSecretName: ptr.To(fmt.Sprintf("%s-%s", clusterID, "bootstrap")),
206+
},
207+
InfrastructureRef: v1.ObjectReference{
208+
APIVersion: "infrastructure.cluster.x-k8s.io/v1beta2",
209+
Kind: "AzureMachine",
210+
Name: bootstrapAzureMachine.Name,
211+
},
212+
},
213+
}
214+
215+
result = append(result, &asset.RuntimeFile{
216+
File: asset.File{Filename: fmt.Sprintf("10_machine_%s.yaml", bootstrapMachine.Name)},
217+
Object: bootstrapMachine,
218+
})
219+
220+
return result, nil
221+
}
222+
223+
// CapzTagsFromUserTags converts a map of user tags to a map of capz.Tags.
224+
func CapzTagsFromUserTags(clusterID string, usertags map[string]string) (capz.Tags, error) {
225+
tags := capz.Tags{}
226+
tags[fmt.Sprintf("kubernetes.io_cluster.%s", clusterID)] = "owned"
227+
228+
forbiddenTags := sets.New[string]()
229+
for key := range tags {
230+
forbiddenTags.Insert(key)
231+
}
232+
233+
userTagKeys := sets.New[string]()
234+
for key := range usertags {
235+
userTagKeys.Insert(key)
236+
}
237+
if clobberedTags := userTagKeys.Intersection(forbiddenTags); clobberedTags.Len() > 0 {
238+
return nil, fmt.Errorf("user tag keys %v are not allowed", sets.List(clobberedTags))
239+
}
240+
for _, k := range sets.List(userTagKeys) {
241+
tags[k] = usertags[k]
242+
}
243+
return tags, nil
244+
}

pkg/asset/machines/clusterapi.go

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import (
2121
configv1 "github.com/openshift/api/config/v1"
2222
"github.com/openshift/installer/pkg/asset"
2323
"github.com/openshift/installer/pkg/asset/installconfig"
24+
icazure "github.com/openshift/installer/pkg/asset/installconfig/azure"
2425
"github.com/openshift/installer/pkg/asset/machines/aws"
26+
"github.com/openshift/installer/pkg/asset/machines/azure"
2527
"github.com/openshift/installer/pkg/asset/machines/gcp"
2628
"github.com/openshift/installer/pkg/asset/machines/openstack"
2729
vspherecapi "github.com/openshift/installer/pkg/asset/machines/vsphere"
@@ -32,6 +34,7 @@ import (
3234
awstypes "github.com/openshift/installer/pkg/types/aws"
3335
awsdefaults "github.com/openshift/installer/pkg/types/aws/defaults"
3436
azuretypes "github.com/openshift/installer/pkg/types/azure"
37+
azuredefaults "github.com/openshift/installer/pkg/types/azure/defaults"
3538
gcptypes "github.com/openshift/installer/pkg/types/gcp"
3639
openstacktypes "github.com/openshift/installer/pkg/types/openstack"
3740
vspheretypes "github.com/openshift/installer/pkg/types/vsphere"
@@ -225,7 +228,76 @@ func (c *ClusterAPI) Generate(dependencies asset.Parents) error {
225228
Object: bootstrapMachine,
226229
})
227230
case azuretypes.Name:
228-
// TODO: implement
231+
mpool := defaultAzureMachinePoolPlatform()
232+
mpool.InstanceType = azuredefaults.ControlPlaneInstanceType(
233+
installConfig.Config.Platform.Azure.CloudName,
234+
installConfig.Config.Platform.Azure.Region,
235+
installConfig.Config.ControlPlane.Architecture,
236+
)
237+
mpool.OSDisk.DiskSizeGB = 1024
238+
if installConfig.Config.Platform.Azure.CloudName == azuretypes.StackCloud {
239+
mpool.OSDisk.DiskSizeGB = azuredefaults.AzurestackMinimumDiskSize
240+
}
241+
mpool.Set(ic.Platform.Azure.DefaultMachinePlatform)
242+
mpool.Set(pool.Platform.Azure)
243+
244+
session, err := installConfig.Azure.Session()
245+
if err != nil {
246+
return fmt.Errorf("failed to fetch session: %w", err)
247+
}
248+
client := icazure.NewClient(session)
249+
250+
if len(mpool.Zones) == 0 {
251+
// if no azs are given we set to []string{""} for convenience over later operations.
252+
// It means no-zoned for the machine API
253+
mpool.Zones = []string{""}
254+
}
255+
if len(mpool.Zones) == 0 {
256+
azs, err := client.GetAvailabilityZones(context.TODO(), ic.Platform.Azure.Region, mpool.InstanceType)
257+
if err != nil {
258+
return fmt.Errorf("failed to fetch availability zones: %w", err)
259+
}
260+
mpool.Zones = azs
261+
if len(azs) == 0 {
262+
// if no azs are given we set to []string{""} for convenience over later operations.
263+
// It means no-zoned for the machine API
264+
mpool.Zones = []string{""}
265+
}
266+
}
267+
// client.GetControlPlaneSubnet(context.TODO(), ic.Platform.Azure.ResourceGroupName, ic.Platform.Azure.VirtualNetwork, )
268+
269+
if mpool.OSImage.Publisher != "" {
270+
img, ierr := client.GetMarketplaceImage(context.TODO(), ic.Platform.Azure.Region, mpool.OSImage.Publisher, mpool.OSImage.Offer, mpool.OSImage.SKU, mpool.OSImage.Version)
271+
if ierr != nil {
272+
return fmt.Errorf("failed to fetch marketplace image: %w", ierr)
273+
}
274+
// Publisher is case-sensitive and matched against exactly. Also the
275+
// Plan's publisher might not be exactly the same as the Image's
276+
// publisher
277+
if img.Plan != nil && img.Plan.Publisher != nil {
278+
mpool.OSImage.Publisher = *img.Plan.Publisher
279+
}
280+
}
281+
pool.Platform.Azure = &mpool
282+
subnet := ic.Azure.ControlPlaneSubnet
283+
284+
capabilities, err := client.GetVMCapabilities(context.TODO(), mpool.InstanceType, installConfig.Config.Platform.Azure.Region)
285+
if err != nil {
286+
return err
287+
}
288+
hyperVGen, err := icazure.GetHyperVGenerationVersion(capabilities, "")
289+
if err != nil {
290+
return err
291+
}
292+
useImageGallery := installConfig.Azure.CloudName != azuretypes.StackCloud
293+
masterUserDataSecretName := "master-user-data"
294+
295+
azureMachines, err := azure.GenerateMachines(installConfig.Config.Platform.Azure, &pool, masterUserDataSecretName, clusterID.InfraID, "master", capabilities, useImageGallery, installConfig.Config.Platform.Azure.UserTags, hyperVGen, subnet, ic.Azure.ResourceGroupName)
296+
if err != nil {
297+
return fmt.Errorf("failed to create master machine objects: %w", err)
298+
}
299+
300+
c.FileList = append(c.FileList, azureMachines...)
229301
case gcptypes.Name:
230302
// Generate GCP master machines using ControPlane machinepool
231303
mpool := defaultGCPMachinePoolPlatform(pool.Architecture)

pkg/asset/manifests/azure/cluster.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package azure
22

33
import (
4+
"fmt"
5+
46
"github.com/pkg/errors"
57
corev1 "k8s.io/api/core/v1"
68
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -40,7 +42,7 @@ func GenerateClusterAssets(installConfig *installconfig.InstallConfig, clusterID
4042
Namespace: capiutils.Namespace,
4143
},
4244
Spec: capz.AzureClusterSpec{
43-
ResourceGroup: clusterID.InfraID,
45+
ResourceGroup: fmt.Sprintf("%s-rg", clusterID.InfraID),
4446
AzureClusterClassSpec: capz.AzureClusterClassSpec{
4547
SubscriptionID: session.Credentials.SubscriptionID,
4648
Location: installConfig.Config.Azure.Region,

pkg/infrastructure/azure/azure.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package azure
2+
3+
import (
4+
"github.com/openshift/installer/pkg/infrastructure/clusterapi"
5+
azuretypes "github.com/openshift/installer/pkg/types/azure"
6+
)
7+
8+
var _ clusterapi.Provider = (*Provider)(nil)
9+
10+
// Provider implements Azure CAPI installation.
11+
type Provider struct{}
12+
13+
// Name gives the name of the provider, Azure.
14+
func (*Provider) Name() string { return azuretypes.Name }

0 commit comments

Comments
 (0)