Skip to content

Commit b398ffc

Browse files
Fix review comments 3
1 parent 4ea52c8 commit b398ffc

File tree

1 file changed

+116
-98
lines changed

1 file changed

+116
-98
lines changed

controllers/awsmachinetemplate_controller.go

Lines changed: 116 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,145 @@ 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+
// Strategy 1: Extract nodeInfo from the AMI if an ID is set.
242+
if amiID := ptr.Deref(template.Spec.Template.Spec.AMI.ID, ""); amiID != "" {
243+
result, err := ec2Client.DescribeImages(ctx, &ec2.DescribeImagesInput{
244+
ImageIds: []string{amiID},
245+
})
255246
if err != nil {
256-
return nil, errors.Wrapf(err, "failed to describe instance type %q", instanceType)
247+
return nil, errors.Wrapf(err, "failed to describe AMI %q", amiID)
257248
}
258-
259-
if len(result.InstanceTypes) == 0 {
260-
return nil, errors.Errorf("no information found for instance type %q", instanceType)
249+
if len(result.Images) == 0 {
250+
return nil, errors.Errorf("no information found for AMI %q", amiID)
261251
}
252+
// Extract nodeInfo directly from the image object (no additional API call needed)
253+
return r.extractNodeInfoFromImage(result.Images[0]), nil
254+
}
262255

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-
}
256+
// No explicit AMI ID specified, query instance type to determine architecture
257+
// This architecture will be used to lookup default AMI (Strategy 2) or as fallback (Strategy 3)
258+
result, err := ec2Client.DescribeInstanceTypes(ctx, &ec2.DescribeInstanceTypesInput{
259+
InstanceTypes: []ec2types.InstanceType{ec2types.InstanceType(instanceType)},
260+
})
261+
if err != nil {
262+
return nil, errors.Wrapf(err, "failed to describe instance type %q", instanceType)
263+
}
280264

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-
}
265+
if len(result.InstanceTypes) == 0 {
266+
return nil, errors.Errorf("no information found for instance type %q", instanceType)
308267
}
309268

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

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},
271+
// Instance type must support exactly one architecture
272+
if instanceTypeInfo.ProcessorInfo == nil || len(instanceTypeInfo.ProcessorInfo.SupportedArchitectures) != 1 {
273+
return nil, errors.Errorf("instance type must support exactly one architecture, got %d", len(instanceTypeInfo.ProcessorInfo.SupportedArchitectures))
317274
}
318275

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

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

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

330-
// Get architecture from AMI
331-
var arch infrav1.Architecture
316+
// extractNodeInfoFromImage extracts nodeInfo (architecture and OS) from an EC2 image.
317+
// This is a pure function with no AWS API calls.
318+
func (r *AWSMachineTemplateReconciler) extractNodeInfoFromImage(image ec2types.Image) *infrav1.NodeInfo {
319+
nodeInfo := &infrav1.NodeInfo{}
320+
321+
// Extract architecture from AMI
332322
switch image.Architecture {
333323
case ec2types.ArchitectureValuesX8664:
334-
arch = infrav1.ArchitectureAmd64
324+
nodeInfo.Architecture = infrav1.ArchitectureAmd64
335325
case ec2types.ArchitectureValuesArm64:
336-
arch = infrav1.ArchitectureArm64
326+
nodeInfo.Architecture = infrav1.ArchitectureArm64
337327
}
338328

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

343-
// 1. Check Platform field (most reliable for Windows detection)
333+
// Check Platform field (most reliable for Windows detection)
344334
if image.Platform == ec2types.PlatformValuesWindows {
345-
os = infrav1.OperatingSystemWindows
335+
nodeInfo.OperatingSystem = infrav1.OperatingSystemWindows
336+
return nodeInfo
346337
}
347338

348-
// 2. Check PlatformDetails field for Windows indication
349-
if os != infrav1.OperatingSystemWindows && image.PlatformDetails != nil {
339+
// Check PlatformDetails field for Windows indication
340+
if image.PlatformDetails != nil {
350341
platformDetails := strings.ToLower(*image.PlatformDetails)
351342
if strings.Contains(platformDetails, string(infrav1.OperatingSystemWindows)) {
352-
os = infrav1.OperatingSystemWindows
343+
nodeInfo.OperatingSystem = infrav1.OperatingSystemWindows
353344
}
354345
}
355346

356-
return arch, os, nil
347+
return nodeInfo
357348
}
358349

359350
// getKubernetesVersion attempts to find the Kubernetes version by querying MachineDeployments
360351
// or KubeadmControlPlanes that reference this AWSMachineTemplate.
361352
func (r *AWSMachineTemplateReconciler) getKubernetesVersion(ctx context.Context, template *infrav1.AWSMachineTemplate) (string, error) {
353+
listOpts, err := getParentListOptions(template.ObjectMeta)
354+
if err != nil {
355+
return "", errors.Wrap(err, "failed to get parent list options")
356+
}
357+
362358
// Try to find version from MachineDeployment first
363359
machineDeploymentList := &clusterv1.MachineDeploymentList{}
364-
if err := r.List(ctx, machineDeploymentList, client.InNamespace(template.Namespace)); err != nil {
360+
if err := r.List(ctx, machineDeploymentList, listOpts...); err != nil {
365361
return "", errors.Wrap(err, "failed to list MachineDeployments")
366362
}
367363

368364
// Find MachineDeployments that reference this AWSMachineTemplate
369365
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
366+
if version := ptr.Deref(md.Spec.Template.Spec.Version, ""); md.Spec.Template.Spec.InfrastructureRef.Kind == "AWSMachineTemplate" &&
367+
md.Spec.Template.Spec.InfrastructureRef.Name == template.Name && version != "" {
368+
return version, nil
374369
}
375370
}
376371

377372
// If not found in MachineDeployment, try KubeadmControlPlane
378373
kcpList := &controlplanev1.KubeadmControlPlaneList{}
379-
if err := r.List(ctx, kcpList, client.InNamespace(template.Namespace)); err != nil {
374+
if err := r.List(ctx, kcpList, listOpts...); err != nil {
380375
return "", errors.Wrap(err, "failed to list KubeadmControlPlanes")
381376
}
382377

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

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

0 commit comments

Comments
 (0)