diff --git a/installer/pkg/config-generator/fixtures/test-aws.yaml b/installer/pkg/config-generator/fixtures/test-aws.yaml index 0cd5ffdd6a3..21fd9ed9130 100644 --- a/installer/pkg/config-generator/fixtures/test-aws.yaml +++ b/installer/pkg/config-generator/fixtures/test-aws.yaml @@ -17,6 +17,7 @@ admin: email: test@coreos.com password: asd123 aws: + ec2AMIOverride: ami-0af8953af3ec06b7c region: us-east-1 sshKey: tectonic vpcCIDRBlock: 10.0.0.0/16 diff --git a/installer/pkg/config-generator/fixtures/test.yaml b/installer/pkg/config-generator/fixtures/test.yaml index 0ec4099a156..e8784ac3f2c 100644 --- a/installer/pkg/config-generator/fixtures/test.yaml +++ b/installer/pkg/config-generator/fixtures/test.yaml @@ -7,3 +7,5 @@ master: nodePools: - name: master count: 3 +aws: + ec2AMIOverride: ami-0af8953af3ec06b7c diff --git a/installer/pkg/config-generator/generator.go b/installer/pkg/config-generator/generator.go index aba44ae7532..68e9278437b 100644 --- a/installer/pkg/config-generator/generator.go +++ b/installer/pkg/config-generator/generator.go @@ -1,6 +1,7 @@ package configgenerator import ( + "context" "crypto/rand" "encoding/base64" "encoding/hex" @@ -119,7 +120,7 @@ func (c *ConfigGenerator) maoConfig(clusterDir string) (*maoOperatorConfig, erro if c.AWS.EC2AMIOverride != "" { ami = c.AWS.EC2AMIOverride } else { - ami, err = rhcos.AMI(rhcos.DefaultChannel, c.Region) + ami, err = rhcos.AMI(context.TODO(), rhcos.DefaultChannel, c.Region) if err != nil { return nil, fmt.Errorf("failed to lookup RHCOS AMI: %v", err) } diff --git a/installer/pkg/workflow/fixtures/aws.basic.yaml b/installer/pkg/workflow/fixtures/aws.basic.yaml index 9cfb628ad02..a2f2ca7f46d 100644 --- a/installer/pkg/workflow/fixtures/aws.basic.yaml +++ b/installer/pkg/workflow/fixtures/aws.basic.yaml @@ -2,6 +2,7 @@ admin: email: fake-email@example.com password: fake-password aws: + ec2AMIOverride: ami-0af8953af3ec06b7c master: ec2Type: m4.large rootVolume: diff --git a/pkg/asset/manifests/machine-api-operator.go b/pkg/asset/manifests/machine-api-operator.go index a1f61ee14f9..7ec5ca1f009 100644 --- a/pkg/asset/manifests/machine-api-operator.go +++ b/pkg/asset/manifests/machine-api-operator.go @@ -1,6 +1,7 @@ package manifests import ( + "context" "fmt" "path/filepath" @@ -111,7 +112,7 @@ func (mao *machineAPIOperator) maoConfig(dependencies map[asset.Asset]*asset.Sta if mao.installConfig.Platform.AWS != nil { var ami string - ami, err := rhcos.AMI(DefaultChannel, mao.installConfig.Platform.AWS.Region) + ami, err := rhcos.AMI(context.TODO(), DefaultChannel, mao.installConfig.Platform.AWS.Region) if err != nil { return "", fmt.Errorf("failed to lookup RHCOS AMI: %v", err) } diff --git a/pkg/rhcos/ami.go b/pkg/rhcos/ami.go index c5612fbf249..ba382769d60 100644 --- a/pkg/rhcos/ami.go +++ b/pkg/rhcos/ami.go @@ -1,7 +1,13 @@ package rhcos import ( + "context" "fmt" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ec2" ) const ( @@ -10,14 +16,72 @@ const ( ) // AMI calculates a Red Hat CoreOS AMI. -func AMI(channel, region string) (ami string, err error) { +func AMI(ctx context.Context, channel, region string) (ami string, err error) { if channel != DefaultChannel { return "", fmt.Errorf("channel %q is not yet supported", channel) } - if region != "us-east-1" { - return "", fmt.Errorf("region %q is not yet supported", region) + ssn := session.Must(session.NewSessionWithOptions(session.Options{ + SharedConfigState: session.SharedConfigEnable, + Config: aws.Config{ + Region: aws.String(region), + }, + })) + + svc := ec2.New(ssn) + + result, err := svc.DescribeImagesWithContext(ctx, &ec2.DescribeImagesInput{ + Filters: []*ec2.Filter{ + { + Name: aws.String("name"), + Values: aws.StringSlice([]string{"rhcos*"}), + }, + { + Name: aws.String("architecture"), + Values: aws.StringSlice([]string{"x86_64"}), + }, + { + Name: aws.String("virtualization-type"), + Values: aws.StringSlice([]string{"hvm"}), + }, + { + Name: aws.String("image-type"), + Values: aws.StringSlice([]string{"machine"}), + }, + { + Name: aws.String("owner-id"), + Values: aws.StringSlice([]string{"531415883065"}), + }, + { + Name: aws.String("state"), + Values: aws.StringSlice([]string{"available"}), + }, + }, + }) + if err != nil { + return "", err + } + + var image *ec2.Image + var created time.Time + for _, nextImage := range result.Images { + if nextImage.ImageId == nil || nextImage.CreationDate == nil { + continue + } + nextCreated, err := time.Parse(time.RFC3339, *nextImage.CreationDate) + if err != nil { + return "", err + } + + if image == nil || nextCreated.After(created) { + image = nextImage + created = nextCreated + } + } + + if image == nil { + return "", fmt.Errorf("no RHCOS AMIs found in %s", region) } - return "ami-0af8953af3ec06b7c", nil + return *image.ImageId, nil } diff --git a/pkg/types/config/parser.go b/pkg/types/config/parser.go index 09f286a7f3f..d2b92acfcfd 100644 --- a/pkg/types/config/parser.go +++ b/pkg/types/config/parser.go @@ -1,9 +1,11 @@ package config import ( + "context" "errors" "fmt" "io/ioutil" + "time" "gopkg.in/yaml.v2" @@ -32,7 +34,10 @@ func ParseConfig(data []byte) (*Cluster, error) { } if cluster.EC2AMIOverride == "" { - ami, err := rhcos.AMI(rhcos.DefaultChannel, cluster.AWS.Region) + ctx, cancel := context.WithTimeout(context.TODO(), 30*time.Second) + defer cancel() + + ami, err := rhcos.AMI(ctx, rhcos.DefaultChannel, cluster.AWS.Region) if err != nil { return nil, fmt.Errorf("failed to determine default AMI: %v", err) }