Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions pkg/provider/aws/aws_ginkgo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,12 @@ status:
optFns ...func(*ec2.Options)) (*ec2.DescribeInstanceTypesOutput, error) {
return &ec2.DescribeInstanceTypesOutput{
InstanceTypes: []types.InstanceTypeInfo{
{InstanceType: types.InstanceTypeT3Medium},
{InstanceType: types.InstanceTypeT3Medium,
ProcessorInfo: &types.ProcessorInfo{
SupportedArchitectures: []types.ArchitectureType{
types.ArchitectureTypeX8664,
},
}},
},
}, nil
}
Expand All @@ -355,8 +360,18 @@ status:
optFns ...func(*ec2.Options)) (*ec2.DescribeInstanceTypesOutput, error) {
return &ec2.DescribeInstanceTypesOutput{
InstanceTypes: []types.InstanceTypeInfo{
{InstanceType: types.InstanceTypeT3Large},
{InstanceType: types.InstanceTypeT3Xlarge},
{InstanceType: types.InstanceTypeT3Large,
ProcessorInfo: &types.ProcessorInfo{
SupportedArchitectures: []types.ArchitectureType{
types.ArchitectureTypeX8664,
},
}},
{InstanceType: types.InstanceTypeT3Xlarge,
ProcessorInfo: &types.ProcessorInfo{
SupportedArchitectures: []types.ArchitectureType{
types.ArchitectureTypeX8664,
},
}},
},
}, nil
}
Expand Down Expand Up @@ -518,7 +533,12 @@ status:
optFns ...func(*ec2.Options)) (*ec2.DescribeInstanceTypesOutput, error) {
return &ec2.DescribeInstanceTypesOutput{
InstanceTypes: []types.InstanceTypeInfo{
{InstanceType: types.InstanceTypeT3Medium},
{InstanceType: types.InstanceTypeT3Medium,
ProcessorInfo: &types.ProcessorInfo{
SupportedArchitectures: []types.ArchitectureType{
types.ArchitectureTypeX8664,
},
}},
},
}, nil
}
Expand Down Expand Up @@ -563,7 +583,12 @@ status:
optFns ...func(*ec2.Options)) (*ec2.DescribeInstanceTypesOutput, error) {
return &ec2.DescribeInstanceTypesOutput{
InstanceTypes: []types.InstanceTypeInfo{
{InstanceType: types.InstanceTypeT3Medium},
{InstanceType: types.InstanceTypeT3Medium,
ProcessorInfo: &types.ProcessorInfo{
SupportedArchitectures: []types.ArchitectureType{
types.ArchitectureTypeX8664,
},
}},
},
}, nil
}
Expand Down
35 changes: 30 additions & 5 deletions pkg/provider/aws/aws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1226,7 +1226,12 @@ spec:
optFns ...func(*ec2.Options)) (*ec2.DescribeInstanceTypesOutput, error) {
return &ec2.DescribeInstanceTypesOutput{
InstanceTypes: []types.InstanceTypeInfo{
{InstanceType: types.InstanceTypeT3Medium},
{InstanceType: types.InstanceTypeT3Medium,
ProcessorInfo: &types.ProcessorInfo{
SupportedArchitectures: []types.ArchitectureType{
types.ArchitectureTypeX8664,
},
}},
},
}, nil
}
Expand Down Expand Up @@ -1471,7 +1476,12 @@ status:
optFns ...func(*ec2.Options)) (*ec2.DescribeInstanceTypesOutput, error) {
return &ec2.DescribeInstanceTypesOutput{
InstanceTypes: []types.InstanceTypeInfo{
{InstanceType: types.InstanceTypeT3Medium},
{InstanceType: types.InstanceTypeT3Medium,
ProcessorInfo: &types.ProcessorInfo{
SupportedArchitectures: []types.ArchitectureType{
types.ArchitectureTypeX8664,
},
}},
},
}, nil
}
Expand Down Expand Up @@ -1519,7 +1529,12 @@ status:
optFns ...func(*ec2.Options)) (*ec2.DescribeInstanceTypesOutput, error) {
return &ec2.DescribeInstanceTypesOutput{
InstanceTypes: []types.InstanceTypeInfo{
{InstanceType: types.InstanceTypeT4gMedium},
{InstanceType: types.InstanceTypeT4gMedium,
ProcessorInfo: &types.ProcessorInfo{
SupportedArchitectures: []types.ArchitectureType{
types.ArchitectureTypeArm64,
},
}},
},
}, nil
}
Expand Down Expand Up @@ -1598,7 +1613,12 @@ status:
optFns ...func(*ec2.Options)) (*ec2.DescribeInstanceTypesOutput, error) {
return &ec2.DescribeInstanceTypesOutput{
InstanceTypes: []types.InstanceTypeInfo{
{InstanceType: types.InstanceTypeT3Medium},
{InstanceType: types.InstanceTypeT3Medium,
ProcessorInfo: &types.ProcessorInfo{
SupportedArchitectures: []types.ArchitectureType{
types.ArchitectureTypeX8664,
},
}},
},
}, nil
}
Expand Down Expand Up @@ -1699,7 +1719,12 @@ status:
optFns ...func(*ec2.Options)) (*ec2.DescribeInstanceTypesOutput, error) {
return &ec2.DescribeInstanceTypesOutput{
InstanceTypes: []types.InstanceTypeInfo{
{InstanceType: types.InstanceTypeT3Medium},
{InstanceType: types.InstanceTypeT3Medium,
ProcessorInfo: &types.ProcessorInfo{
SupportedArchitectures: []types.ArchitectureType{
types.ArchitectureTypeX8664,
},
}},
},
}, nil
}
Expand Down
25 changes: 25 additions & 0 deletions pkg/provider/aws/dryrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,30 @@ func (p *Provider) DryRun() error {
}
cancel(nil)

// Cross-validate architecture compatibility
if p.Spec.Image.Architecture != "" {
cancel = p.log.Loading("Validating architecture compatibility")
archs, err := p.getInstanceTypeArch(p.Spec.Type)
if err != nil {
cancel(logger.ErrLoadingFailed)
return fmt.Errorf("failed to check instance type architecture: %w", err)
}
archMatch := false
for _, a := range archs {
if a == p.Spec.Image.Architecture {
archMatch = true
break
}
}
if !archMatch {
cancel(logger.ErrLoadingFailed)
return fmt.Errorf(
"architecture mismatch: AMI architecture is %s but instance type %s supports %v",
p.Spec.Image.Architecture, p.Spec.Type, archs,
)
}
cancel(nil)
}

return nil
}
89 changes: 79 additions & 10 deletions pkg/provider/aws/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func (p *Provider) resolveOSToAMI() error {
}

p.Spec.Image.ImageId = &resolved.ImageID
p.Spec.Image.Architecture = normalizeArchToEC2(arch)

// Auto-set username if not provided
//nolint:staticcheck // Auth is embedded but explicit access is clearer
Expand All @@ -102,8 +103,9 @@ func (p *Provider) resolveOSToAMI() error {

// ResolvedImage contains the resolved AMI information for instance creation.
type ResolvedImage struct {
ImageID string
SSHUsername string
ImageID string
SSHUsername string
Architecture string // EC2 architecture: "x86_64" or "arm64"
}

// resolveImageForNode resolves the AMI for a node based on OS or explicit Image.
Expand All @@ -112,9 +114,14 @@ type ResolvedImage struct {
func (p *Provider) resolveImageForNode(os string, image *v1alpha1.Image, arch string) (*ResolvedImage, error) {
// If explicit ImageId is provided, use it
if image != nil && image.ImageId != nil && *image.ImageId != "" {
arch, err := p.describeImageArch(*image.ImageId)
if err != nil {
return nil, fmt.Errorf("failed to determine architecture for image %s: %w", *image.ImageId, err)
}
return &ResolvedImage{
ImageID: *image.ImageId,
SSHUsername: "", // Username must be provided in auth config
ImageID: *image.ImageId,
SSHUsername: "", // Username must be provided in auth config
Architecture: arch,
}, nil
}

Expand All @@ -126,6 +133,7 @@ func (p *Provider) resolveImageForNode(os string, image *v1alpha1.Image, arch st
arch = "x86_64" // Default
}
}
arch = normalizeArchToEC2(arch)

// If OS is specified, resolve via AMI resolver
if os != "" {
Expand All @@ -134,8 +142,9 @@ func (p *Provider) resolveImageForNode(os string, image *v1alpha1.Image, arch st
return nil, fmt.Errorf("failed to resolve AMI for OS %s: %w", os, err)
}
return &ResolvedImage{
ImageID: resolved.ImageID,
SSHUsername: resolved.SSHUsername,
ImageID: resolved.ImageID,
SSHUsername: resolved.SSHUsername,
Architecture: arch,
}, nil
}

Expand All @@ -147,8 +156,9 @@ func (p *Provider) resolveImageForNode(os string, image *v1alpha1.Image, arch st
return nil, fmt.Errorf("failed to resolve AMI for OS %s: %w", p.Spec.Instance.OS, err)
}
return &ResolvedImage{
ImageID: resolved.ImageID,
SSHUsername: resolved.SSHUsername,
ImageID: resolved.ImageID,
SSHUsername: resolved.SSHUsername,
Architecture: arch,
}, nil
}

Expand All @@ -159,8 +169,9 @@ func (p *Provider) resolveImageForNode(os string, image *v1alpha1.Image, arch st
return nil, err
}
return &ResolvedImage{
ImageID: imageID,
SSHUsername: "ubuntu",
ImageID: imageID,
SSHUsername: "ubuntu",
Architecture: arch,
}, nil
}

Expand Down Expand Up @@ -239,6 +250,13 @@ func (p *Provider) setLegacyAMI() error {
}
p.Spec.Image.ImageId = &imageID

// Store the resolved architecture (normalized to EC2 form) for cross-validation in DryRun
if p.Spec.Image.Architecture == "" {
p.Spec.Image.Architecture = "x86_64" // Legacy default
} else {
p.Spec.Image.Architecture = normalizeArchToEC2(p.Spec.Image.Architecture)
}

// Set default username for Ubuntu if not provided
//nolint:staticcheck // Auth is embedded but explicit access is clearer
if p.Spec.Auth.Username == "" {
Expand Down Expand Up @@ -320,3 +338,54 @@ func (p *Provider) checkInstanceTypes() error {

return fmt.Errorf("instance type %s is not supported in the current region %s", p.Spec.Type, p.Spec.Region)
}

// normalizeArchToEC2 converts architecture aliases to EC2 canonical form.
// EC2 APIs use "x86_64" and "arm64", but users and other systems may use
// "amd64" (Debian convention) or "aarch64" (kernel convention).
func normalizeArchToEC2(arch string) string {
switch strings.ToLower(arch) {
case "amd64", "x86_64":
return "x86_64"
case "arm64", "aarch64":
return "arm64"
default:
return arch
}
}

// describeImageArch queries EC2 DescribeImages for a specific AMI ID and
// returns its architecture string (e.g., "x86_64" or "arm64").
func (p *Provider) describeImageArch(imageID string) (string, error) {
resp, err := p.ec2.DescribeImages(context.TODO(), &ec2.DescribeImagesInput{
ImageIds: []string{imageID},
})
if err != nil {
return "", fmt.Errorf("failed to describe image %s: %w", imageID, err)
}
if len(resp.Images) == 0 {
return "", fmt.Errorf("image %s not found", imageID)
}
return string(resp.Images[0].Architecture), nil
}

// getInstanceTypeArch queries EC2 DescribeInstanceTypes for a specific instance
// type and returns its list of supported architecture strings.
func (p *Provider) getInstanceTypeArch(instanceType string) ([]string, error) {
resp, err := p.ec2.DescribeInstanceTypes(context.TODO(), &ec2.DescribeInstanceTypesInput{
InstanceTypes: []types.InstanceType{types.InstanceType(instanceType)},
})
if err != nil {
return nil, fmt.Errorf("failed to describe instance type %s: %w", instanceType, err)
}
if len(resp.InstanceTypes) == 0 {
return nil, fmt.Errorf("instance type %s not found", instanceType)
}
if resp.InstanceTypes[0].ProcessorInfo == nil {
return nil, fmt.Errorf("no processor info for instance type %s", instanceType)
}
var archs []string
for _, a := range resp.InstanceTypes[0].ProcessorInfo.SupportedArchitectures {
archs = append(archs, string(a))
}
return archs, nil
}
Loading
Loading