Skip to content

Commit 00485e3

Browse files
Fix review comments 3
1 parent 4ea52c8 commit 00485e3

File tree

1 file changed

+115
-98
lines changed

1 file changed

+115
-98
lines changed

controllers/awsmachinetemplate_controller.go

Lines changed: 115 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ import (
2727
corev1 "k8s.io/api/core/v1"
2828
apierrors "k8s.io/apimachinery/pkg/api/errors"
2929
"k8s.io/apimachinery/pkg/api/resource"
30+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31+
"k8s.io/apimachinery/pkg/runtime/schema"
32+
"k8s.io/utils/ptr"
3033
ctrl "sigs.k8s.io/controller-runtime"
3134
"sigs.k8s.io/controller-runtime/pkg/client"
3235
"sigs.k8s.io/controller-runtime/pkg/controller"
@@ -230,153 +233,144 @@ func (r *AWSMachineTemplateReconciler) getInstanceTypeCapacity(ctx context.Conte
230233
}
231234

232235
// getNodeInfo queries node information (architecture and OS) for the AWSMachineTemplate.
233-
// It uses AMI ID if specified, otherwise attempts AMI lookup or falls back to instance type info.
236+
// It attempts to resolve nodeInfo using three strategies in order of priority:
237+
// 1. Directly from explicitly specified AMI ID
238+
// 2. From default AMI lookup (requires Kubernetes version from owner MachineDeployment/KubeadmControlPlane)
239+
// 3. From instance type architecture (OS cannot be determined, only architecture)
234240
func (r *AWSMachineTemplateReconciler) getNodeInfo(ctx context.Context, ec2Client *ec2.Client, template *infrav1.AWSMachineTemplate, instanceType string) (*infrav1.NodeInfo, error) {
235-
nodeInfo := &infrav1.NodeInfo{}
236-
amiID := template.Spec.Template.Spec.AMI.ID
237-
if amiID != nil && *amiID != "" {
238-
// AMI ID is specified, query it directly
239-
arch, os, err := r.getNodeInfoFromAMI(ctx, ec2Client, *amiID)
240-
if err == nil {
241-
if arch != "" {
242-
nodeInfo.Architecture = arch
243-
}
244-
if os != "" {
245-
nodeInfo.OperatingSystem = os
246-
}
247-
}
248-
} else {
249-
// AMI ID is not specified, query instance type to get architecture
250-
input := &ec2.DescribeInstanceTypesInput{
251-
InstanceTypes: []ec2types.InstanceType{ec2types.InstanceType(instanceType)},
252-
}
253-
254-
result, err := ec2Client.DescribeInstanceTypes(ctx, input)
241+
if amiID := ptr.Deref(template.Spec.Template.Spec.AMI.ID, ""); amiID != "" {
242+
result, err := ec2Client.DescribeImages(ctx, &ec2.DescribeImagesInput{
243+
ImageIds: []string{amiID},
244+
})
255245
if err != nil {
256-
return nil, errors.Wrapf(err, "failed to describe instance type %q", instanceType)
246+
return nil, errors.Wrapf(err, "failed to describe AMI %q", amiID)
257247
}
258-
259-
if len(result.InstanceTypes) == 0 {
260-
return nil, errors.Errorf("no information found for instance type %q", instanceType)
248+
if len(result.Images) == 0 {
249+
return nil, errors.Errorf("no information found for AMI %q", amiID)
261250
}
251+
// Extract nodeInfo directly from the image object (no additional API call needed)
252+
return r.extractNodeInfoFromImage(result.Images[0]), nil
253+
}
262254

263-
instanceTypeInfo := result.InstanceTypes[0]
264-
265-
// Infer architecture from instance type
266-
var architecture string
267-
if instanceTypeInfo.ProcessorInfo != nil && len(instanceTypeInfo.ProcessorInfo.SupportedArchitectures) == 1 {
268-
// Use the supported architecture
269-
switch instanceTypeInfo.ProcessorInfo.SupportedArchitectures[0] {
270-
case ec2types.ArchitectureTypeX8664:
271-
architecture = ec2service.Amd64ArchitectureTag
272-
nodeInfo.Architecture = infrav1.ArchitectureAmd64
273-
case ec2types.ArchitectureTypeArm64:
274-
architecture = ec2service.Arm64ArchitectureTag
275-
nodeInfo.Architecture = infrav1.ArchitectureArm64
276-
}
277-
} else {
278-
return nil, errors.Errorf("instance type must support exactly one architecture, got %d", len(instanceTypeInfo.ProcessorInfo.SupportedArchitectures))
279-
}
255+
// No explicit AMI ID specified, query instance type to determine architecture
256+
// This architecture will be used to lookup default AMI (Strategy 2) or as fallback (Strategy 3)
257+
result, err := ec2Client.DescribeInstanceTypes(ctx, &ec2.DescribeInstanceTypesInput{
258+
InstanceTypes: []ec2types.InstanceType{ec2types.InstanceType(instanceType)},
259+
})
260+
if err != nil {
261+
return nil, errors.Wrapf(err, "failed to describe instance type %q", instanceType)
262+
}
280263

281-
// Attempt to get Kubernetes version from MachineDeployment
282-
kubernetesVersion, versionErr := r.getKubernetesVersion(ctx, template)
283-
if versionErr == nil && kubernetesVersion != "" {
284-
// Try to look up AMI using the version
285-
image, err := ec2service.DefaultAMILookup(
286-
ec2Client,
287-
template.Spec.Template.Spec.ImageLookupOrg,
288-
template.Spec.Template.Spec.ImageLookupBaseOS,
289-
kubernetesVersion,
290-
architecture,
291-
template.Spec.Template.Spec.ImageLookupFormat,
292-
)
293-
if err == nil && image != nil {
294-
// Successfully found AMI, extract accurate nodeInfo from it
295-
arch, os, _ := r.getNodeInfoFromAMI(ctx, ec2Client, *image.ImageId)
296-
if arch != "" {
297-
nodeInfo.Architecture = arch
298-
}
299-
if os != "" {
300-
nodeInfo.OperatingSystem = os
301-
}
302-
return nodeInfo, nil
303-
}
304-
// AMI lookup failed, fall through to defaults
305-
} else {
306-
return nil, errors.Errorf("failed to get Kubernetes version: %v", versionErr)
307-
}
264+
if len(result.InstanceTypes) == 0 {
265+
return nil, errors.Errorf("no information found for instance type %q", instanceType)
308266
}
309267

310-
return nodeInfo, nil
311-
}
268+
instanceTypeInfo := result.InstanceTypes[0]
312269

313-
// getNodeInfoFromAMI queries the AMI to determine architecture and operating system.
314-
func (r *AWSMachineTemplateReconciler) getNodeInfoFromAMI(ctx context.Context, ec2Client *ec2.Client, amiID string) (infrav1.Architecture, infrav1.OperatingSystem, error) {
315-
input := &ec2.DescribeImagesInput{
316-
ImageIds: []string{amiID},
270+
// Instance type must support exactly one architecture
271+
if instanceTypeInfo.ProcessorInfo == nil || len(instanceTypeInfo.ProcessorInfo.SupportedArchitectures) != 1 {
272+
return nil, errors.Errorf("instance type must support exactly one architecture, got %d", len(instanceTypeInfo.ProcessorInfo.SupportedArchitectures))
317273
}
318274

319-
result, err := ec2Client.DescribeImages(ctx, input)
320-
if err != nil {
321-
return "", "", errors.Wrapf(err, "failed to describe AMI %q", amiID)
275+
// Map EC2 architecture type to architecture tag for AMI lookup
276+
var architecture string
277+
var nodeInfoArch infrav1.Architecture
278+
switch instanceTypeInfo.ProcessorInfo.SupportedArchitectures[0] {
279+
case ec2types.ArchitectureTypeX8664:
280+
architecture = ec2service.Amd64ArchitectureTag
281+
nodeInfoArch = infrav1.ArchitectureAmd64
282+
case ec2types.ArchitectureTypeArm64:
283+
architecture = ec2service.Arm64ArchitectureTag
284+
nodeInfoArch = infrav1.ArchitectureArm64
285+
default:
286+
return nil, errors.Errorf("unsupported architecture: %v", instanceTypeInfo.ProcessorInfo.SupportedArchitectures[0])
322287
}
323288

324-
if len(result.Images) == 0 {
325-
return "", "", errors.Errorf("no information found for AMI %q", amiID)
289+
// Strategy 2: Try to get Kubernetes version and lookup default AMI
290+
kubernetesVersion, err := r.getKubernetesVersion(ctx, template)
291+
if err == nil && kubernetesVersion != "" {
292+
// Attempt AMI lookup with the version
293+
image, err := ec2service.DefaultAMILookup(
294+
ec2Client,
295+
template.Spec.Template.Spec.ImageLookupOrg,
296+
template.Spec.Template.Spec.ImageLookupBaseOS,
297+
kubernetesVersion,
298+
architecture,
299+
template.Spec.Template.Spec.ImageLookupFormat,
300+
)
301+
if err == nil && image != nil {
302+
// Successfully found AMI, extract accurate nodeInfo from it
303+
return r.extractNodeInfoFromImage(*image), nil
304+
}
305+
// AMI lookup failed, fall through to Strategy 3
326306
}
327307

328-
image := result.Images[0]
308+
// Strategy 3: Fallback to instance type architecture only
309+
// Note: OS cannot be determined from instance type alone, only architecture
310+
return &infrav1.NodeInfo{
311+
Architecture: nodeInfoArch,
312+
}, nil
313+
}
329314

330-
// Get architecture from AMI
331-
var arch infrav1.Architecture
315+
// extractNodeInfoFromImage extracts nodeInfo (architecture and OS) from an EC2 image.
316+
// This is a pure function with no AWS API calls.
317+
func (r *AWSMachineTemplateReconciler) extractNodeInfoFromImage(image ec2types.Image) *infrav1.NodeInfo {
318+
nodeInfo := &infrav1.NodeInfo{}
319+
320+
// Extract architecture from AMI
332321
switch image.Architecture {
333322
case ec2types.ArchitectureValuesX8664:
334-
arch = infrav1.ArchitectureAmd64
323+
nodeInfo.Architecture = infrav1.ArchitectureAmd64
335324
case ec2types.ArchitectureValuesArm64:
336-
arch = infrav1.ArchitectureArm64
325+
nodeInfo.Architecture = infrav1.ArchitectureArm64
337326
}
338327

339328
// Determine OS - default to Linux, change to Windows if detected
340329
// Most AMIs are Linux-based, so we initialize with Linux as the default
341-
os := infrav1.OperatingSystemLinux
330+
nodeInfo.OperatingSystem = infrav1.OperatingSystemLinux
342331

343-
// 1. Check Platform field (most reliable for Windows detection)
332+
// Check Platform field (most reliable for Windows detection)
344333
if image.Platform == ec2types.PlatformValuesWindows {
345-
os = infrav1.OperatingSystemWindows
334+
nodeInfo.OperatingSystem = infrav1.OperatingSystemWindows
335+
return nodeInfo
346336
}
347337

348-
// 2. Check PlatformDetails field for Windows indication
349-
if os != infrav1.OperatingSystemWindows && image.PlatformDetails != nil {
338+
// Check PlatformDetails field for Windows indication
339+
if image.PlatformDetails != nil {
350340
platformDetails := strings.ToLower(*image.PlatformDetails)
351341
if strings.Contains(platformDetails, string(infrav1.OperatingSystemWindows)) {
352-
os = infrav1.OperatingSystemWindows
342+
nodeInfo.OperatingSystem = infrav1.OperatingSystemWindows
353343
}
354344
}
355345

356-
return arch, os, nil
346+
return nodeInfo
357347
}
358348

359349
// getKubernetesVersion attempts to find the Kubernetes version by querying MachineDeployments
360350
// or KubeadmControlPlanes that reference this AWSMachineTemplate.
361351
func (r *AWSMachineTemplateReconciler) getKubernetesVersion(ctx context.Context, template *infrav1.AWSMachineTemplate) (string, error) {
352+
listOpts, err := getParentListOptions(template.ObjectMeta)
353+
if err != nil {
354+
return "", errors.Wrap(err, "failed to get parent list options")
355+
}
356+
362357
// Try to find version from MachineDeployment first
363358
machineDeploymentList := &clusterv1.MachineDeploymentList{}
364-
if err := r.List(ctx, machineDeploymentList, client.InNamespace(template.Namespace)); err != nil {
359+
if err := r.List(ctx, machineDeploymentList, listOpts...); err != nil {
365360
return "", errors.Wrap(err, "failed to list MachineDeployments")
366361
}
367362

368363
// Find MachineDeployments that reference this AWSMachineTemplate
369364
for _, md := range machineDeploymentList.Items {
370-
if md.Spec.Template.Spec.InfrastructureRef.Kind == "AWSMachineTemplate" &&
371-
md.Spec.Template.Spec.InfrastructureRef.Name == template.Name &&
372-
md.Spec.Template.Spec.Version != nil {
373-
return *md.Spec.Template.Spec.Version, nil
365+
if version := ptr.Deref(md.Spec.Template.Spec.Version, ""); md.Spec.Template.Spec.InfrastructureRef.Kind == "AWSMachineTemplate" &&
366+
md.Spec.Template.Spec.InfrastructureRef.Name == template.Name && version != "" {
367+
return version, nil
374368
}
375369
}
376370

377371
// If not found in MachineDeployment, try KubeadmControlPlane
378372
kcpList := &controlplanev1.KubeadmControlPlaneList{}
379-
if err := r.List(ctx, kcpList, client.InNamespace(template.Namespace)); err != nil {
373+
if err := r.List(ctx, kcpList, listOpts...); err != nil {
380374
return "", errors.Wrap(err, "failed to list KubeadmControlPlanes")
381375
}
382376

@@ -391,3 +385,26 @@ func (r *AWSMachineTemplateReconciler) getKubernetesVersion(ctx context.Context,
391385

392386
return "", errors.New("no MachineDeployment or KubeadmControlPlane found referencing this AWSMachineTemplate with a version")
393387
}
388+
389+
func getParentListOptions(obj metav1.ObjectMeta) ([]client.ListOption, error) {
390+
listOpts := []client.ListOption{
391+
client.InNamespace(obj.Namespace),
392+
}
393+
394+
for _, ref := range obj.GetOwnerReferences() {
395+
if ref.Kind != "Cluster" {
396+
continue
397+
}
398+
gv, err := schema.ParseGroupVersion(ref.APIVersion)
399+
if err != nil {
400+
return nil, errors.WithStack(err)
401+
}
402+
if gv.Group == clusterv1.GroupVersion.Group {
403+
listOpts = append(listOpts, client.MatchingLabels{
404+
clusterv1.ClusterNameLabel: ref.Name,
405+
})
406+
break
407+
}
408+
}
409+
return listOpts, nil
410+
}

0 commit comments

Comments
 (0)