@@ -18,6 +18,7 @@ package controllers
1818
1919import (
2020 "context"
21+ "strings"
2122
2223 "github.com/aws/aws-sdk-go-v2/service/ec2"
2324 ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
@@ -67,8 +68,8 @@ func (r *AWSMachineTemplateReconciler) Reconcile(ctx context.Context, req ctrl.R
6768 return ctrl.Result {}, err
6869 }
6970
70- // Skip if capacity is already set
71- if len (awsMachineTemplate .Status .Capacity ) > 0 {
71+ // Skip if capacity and nodeInfo are already set
72+ if len (awsMachineTemplate .Status .Capacity ) > 0 && awsMachineTemplate . Status . NodeInfo != nil {
7273 return ctrl.Result {}, nil
7374 }
7475
@@ -98,21 +99,22 @@ func (r *AWSMachineTemplateReconciler) Reconcile(ctx context.Context, req ctrl.R
9899 return ctrl.Result {}, nil
99100 }
100101
101- // Query instance type capacity
102- capacity , err := r .getInstanceTypeCapacity (ctx , globalScope , instanceType )
102+ // Query instance type capacity and node info
103+ capacity , nodeInfo , err := r .getInstanceTypeInfo (ctx , globalScope , awsMachineTemplate , instanceType )
103104 if err != nil {
104105 record .Warnf (awsMachineTemplate , "CapacityQueryFailed" , "Failed to query capacity for instance type %q: %v" , instanceType , err )
105106 return ctrl.Result {}, nil
106107 }
107108
108- // Update status with capacity
109+ // Update status with capacity and nodeInfo
109110 awsMachineTemplate .Status .Capacity = capacity
111+ awsMachineTemplate .Status .NodeInfo = nodeInfo
110112
111113 if err := r .Status ().Update (ctx , awsMachineTemplate ); err != nil {
112114 return ctrl.Result {}, errors .Wrap (err , "failed to update AWSMachineTemplate status" )
113115 }
114116
115- log .Info ("Successfully populated capacity information " , "instanceType" , instanceType , "region" , region , "capacity" , capacity )
117+ log .Info ("Successfully populated capacity and nodeInfo " , "instanceType" , instanceType , "region" , region , "capacity" , capacity , "nodeInfo" , nodeInfo )
116118 return ctrl.Result {}, nil
117119}
118120
@@ -145,8 +147,8 @@ func (r *AWSMachineTemplateReconciler) getRegion(ctx context.Context, template *
145147 return "" , nil
146148}
147149
148- // getInstanceTypeCapacity queries AWS EC2 API for instance type capacity.
149- func (r * AWSMachineTemplateReconciler ) getInstanceTypeCapacity (ctx context.Context , globalScope * scope.GlobalScope , instanceType string ) (corev1.ResourceList , error ) {
150+ // getInstanceTypeInfo queries AWS EC2 API for instance type capacity and node info .
151+ func (r * AWSMachineTemplateReconciler ) getInstanceTypeInfo (ctx context.Context , globalScope * scope.GlobalScope , template * infrav1. AWSMachineTemplate , instanceType string ) (corev1.ResourceList , * infrav1. NodeInfo , error ) {
150152 // Create EC2 client from global scope
151153 ec2Client := ec2 .NewFromConfig (globalScope .Session ())
152154
@@ -157,11 +159,11 @@ func (r *AWSMachineTemplateReconciler) getInstanceTypeCapacity(ctx context.Conte
157159
158160 result , err := ec2Client .DescribeInstanceTypes (ctx , input )
159161 if err != nil {
160- return nil , errors .Wrapf (err , "failed to describe instance type %q" , instanceType )
162+ return nil , nil , errors .Wrapf (err , "failed to describe instance type %q" , instanceType )
161163 }
162164
163165 if len (result .InstanceTypes ) == 0 {
164- return nil , errors .Errorf ("no information found for instance type %q" , instanceType )
166+ return nil , nil , errors .Errorf ("no information found for instance type %q" , instanceType )
165167 }
166168
167169 // Extract capacity information
@@ -178,7 +180,71 @@ func (r *AWSMachineTemplateReconciler) getInstanceTypeCapacity(ctx context.Conte
178180 memoryBytes := * info .MemoryInfo .SizeInMiB * 1024 * 1024
179181 resourceList [corev1 .ResourceMemory ] = * resource .NewQuantity (memoryBytes , resource .BinarySI )
180182 }
181- return resourceList , nil
183+
184+ // Extract node info from AMI if available
185+ nodeInfo := & infrav1.NodeInfo {}
186+ amiID := template .Spec .Template .Spec .AMI .ID
187+ if amiID != nil && * amiID != "" {
188+ arch , os , err := r .getNodeInfoFromAMI (ctx , ec2Client , * amiID )
189+ if err == nil {
190+ if arch != "" {
191+ nodeInfo .Architecture = arch
192+ }
193+ if os != "" {
194+ nodeInfo .OperatingSystem = os
195+ }
196+ }
197+ }
198+
199+ return resourceList , nodeInfo , nil
200+ }
201+
202+ // getNodeInfoFromAMI queries the AMI to determine architecture and operating system.
203+ func (r * AWSMachineTemplateReconciler ) getNodeInfoFromAMI (ctx context.Context , ec2Client * ec2.Client , amiID string ) (infrav1.Architecture , string , error ) {
204+ input := & ec2.DescribeImagesInput {
205+ ImageIds : []string {amiID },
206+ }
207+
208+ result , err := ec2Client .DescribeImages (ctx , input )
209+ if err != nil {
210+ return "" , "" , errors .Wrapf (err , "failed to describe AMI %q" , amiID )
211+ }
212+
213+ if len (result .Images ) == 0 {
214+ return "" , "" , errors .Errorf ("no information found for AMI %q" , amiID )
215+ }
216+
217+ image := result .Images [0 ]
218+
219+ // Get architecture from AMI
220+ var arch infrav1.Architecture
221+ switch image .Architecture {
222+ case ec2types .ArchitectureValuesX8664 :
223+ arch = infrav1 .ArchitectureAmd64
224+ case ec2types .ArchitectureValuesArm64 :
225+ arch = infrav1 .ArchitectureArm64
226+ }
227+
228+ // Determine OS - check Platform field first (specifically for Windows identification)
229+ var os string
230+
231+ // 1. Check Platform field (most reliable for Windows detection)
232+ if image .Platform == ec2types .PlatformValuesWindows {
233+ os = "windows"
234+ }
235+
236+ // 2. Check PlatformDetails field (provides more detailed information)
237+ if os == "" && image .PlatformDetails != nil {
238+ platformDetails := strings .ToLower (* image .PlatformDetails )
239+ switch {
240+ case strings .Contains (platformDetails , "windows" ):
241+ os = "windows"
242+ case strings .Contains (platformDetails , "linux" ), strings .Contains (platformDetails , "unix" ):
243+ os = "linux"
244+ }
245+ }
246+
247+ return arch , os , nil
182248}
183249
184250// SetupWithManager sets up the controller with the Manager.
0 commit comments