diff --git a/builder/amazon/common/run_config.go b/builder/amazon/common/run_config.go index c44c5db05..b3fc15a94 100644 --- a/builder/amazon/common/run_config.go +++ b/builder/amazon/common/run_config.go @@ -15,13 +15,13 @@ import ( var reShutdownBehavior = regexp.MustCompile("^(stop|terminate)$") type DynamicAmiOptions struct { - Filters map[*string]*string - Owners []*string - Most_recent bool + Filters map[*string]*string + Owners []*string + MostRecent bool `mapstructure:"most_recent"` } -func (d *DynamicAmiOptions) Validate() bool { - return true +func (d *DynamicAmiOptions) Empty() bool { + return len(d.Owners) == 0 && len(d.Filters) == 0 } // RunConfig contains configuration for running an instance from a source @@ -67,7 +67,7 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { // Validation errs := c.Comm.Prepare(ctx) - if c.SourceAmi == "" { + if c.SourceAmi == "" && c.DynamicSourceAmi.Empty() { errs = append(errs, errors.New("A source_ami or dynamic_source_ami must be specified")) } diff --git a/builder/amazon/common/step_source_ami_info.go b/builder/amazon/common/step_source_ami_info.go index a349f3bd9..6946650c1 100644 --- a/builder/amazon/common/step_source_ami_info.go +++ b/builder/amazon/common/step_source_ami_info.go @@ -2,6 +2,9 @@ package common import ( "fmt" + "log" + "sort" + "time" "github.com/aws/aws-sdk-go/service/ec2" "github.com/mitchellh/multistep" @@ -23,11 +26,6 @@ type StepSourceAMIInfo struct { func buildAmiFilters(input map[*string]*string) []*ec2.Filter { var filters []*ec2.Filter for k, v := range input { - /*m := v.(map[string]interface{}) - var filterValues []*string - for _, e := range m["values"].([]interface{}) { - filterValues = append(filterValues, aws.String(e.(string))) - }*/ filters = append(filters, &ec2.Filter{ Name: k, Values: []*string{v}, @@ -36,17 +34,40 @@ func buildAmiFilters(input map[*string]*string) []*ec2.Filter { return filters } +type imageSort []*ec2.Image + +func (a imageSort) Len() int { return len(a) } +func (a imageSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a imageSort) Less(i, j int) bool { + itime, _ := time.Parse(time.RFC3339, *a[i].CreationDate) + jtime, _ := time.Parse(time.RFC3339, *a[j].CreationDate) + return itime.Unix() < jtime.Unix() +} + +// Returns the most recent AMI out of a slice of images. +func mostRecentAmi(images []*ec2.Image) *ec2.Image { + sortedImages := images + sort.Sort(imageSort(sortedImages)) + return sortedImages[len(sortedImages)-1] +} + func (s *StepSourceAMIInfo) Run(state multistep.StateBag) multistep.StepAction { ec2conn := state.Get("ec2").(*ec2.EC2) ui := state.Get("ui").(packer.Ui) params := &ec2.DescribeImagesInput{} - params.Filters = buildAmiFilters(s.AmiFilters.Filters) - params.Owners = s.AmiFilters.Owners - ui.Say(fmt.Sprintf("Using AMI Filters %v", params)) + + if s.SourceAmi != "" { + params.ImageIds = []*string{&s.SourceAmi} + } + + // We have filters to apply + if len(s.AmiFilters.Filters) > 0 { + params.Filters = buildAmiFilters(s.AmiFilters.Filters) + } + + log.Printf("Using AMI Filters %v", params) imageResp, err := ec2conn.DescribeImages(params) - //ui.Say(fmt.Sprintf("Inspecting the source AMI (%s)...", s.SourceAmi)) - //imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{&s.SourceAmi}}) if err != nil { err := fmt.Errorf("Error querying AMI: %s", err) state.Put("error", err) @@ -55,14 +76,27 @@ func (s *StepSourceAMIInfo) Run(state multistep.StateBag) multistep.StepAction { } if len(imageResp.Images) == 0 { - err := fmt.Errorf("Source AMI '%s' was not found!", s.SourceAmi) + err := fmt.Errorf("No AMI was found matching filters: %v", params) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } - image := imageResp.Images[0] - ui.Say(fmt.Sprintf("Got Image %v", image)) + if len(imageResp.Images) > 1 && s.AmiFilters.MostRecent == false { + err := fmt.Errorf("Your query returned more than one result. Please try a more specific search, or set most_recent to true.") + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + var image *ec2.Image + if s.AmiFilters.MostRecent { + image = mostRecentAmi(imageResp.Images) + } else { + image = imageResp.Images[0] + } + + log.Printf(fmt.Sprintf("Got Image %v", image)) // Enhanced Networking (SriovNetSupport) can only be enabled on HVM AMIs. // See http://goo.gl/icuXh5