From 27608a7b0f802a15b032c820cecfb01d0d66b6b0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 12 Sep 2013 20:33:32 -0700 Subject: [PATCH] builder/amazon/*: use WaitForState for AMIs --- CHANGELOG.md | 5 ++++ builder/amazon/chroot/step_register_ami.go | 10 ++++++- builder/amazon/common/ami.go | 30 ------------------- builder/amazon/common/instance.go | 26 ++++++++++++++++ builder/amazon/common/step_ami_region_copy.go | 13 ++++++-- builder/amazon/ebs/step_create_ami.go | 10 ++++++- builder/amazon/instance/step_register_ami.go | 10 ++++++- 7 files changed, 69 insertions(+), 35 deletions(-) delete mode 100644 builder/amazon/common/ami.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 4966cc39c..6dfb440ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,12 @@ ## 0.3.8 (unreleased) +IMPROVEMENTS: + +* builder/amazon/*: Interrupts work while waiting for AMI to be ready. + BUG FIXES: +* builder/amazon/*: While waiting for AMI, will detect "failed" state. * provisioner/puppet-masterless: Fix failure case when both facter vars are used and prevent_sudo. [GH-415] diff --git a/builder/amazon/chroot/step_register_ami.go b/builder/amazon/chroot/step_register_ami.go index 4c75d0c27..cb52b646c 100644 --- a/builder/amazon/chroot/step_register_ami.go +++ b/builder/amazon/chroot/step_register_ami.go @@ -52,8 +52,16 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction { state.Put("amis", amis) // Wait for the image to become ready + stateChange := awscommon.StateChangeConf{ + Conn: ec2conn, + Pending: []string{"pending"}, + Target: "available", + Refresh: awscommon.AMIStateRefreshFunc(ec2conn, registerResp.ImageId), + StepState: state, + } + ui.Say("Waiting for AMI to become ready...") - if err := awscommon.WaitForAMI(ec2conn, registerResp.ImageId); err != nil { + if _, err := awscommon.WaitForState(&stateChange); err != nil { err := fmt.Errorf("Error waiting for AMI: %s", err) state.Put("error", err) ui.Error(err.Error()) diff --git a/builder/amazon/common/ami.go b/builder/amazon/common/ami.go deleted file mode 100644 index 2fec62871..000000000 --- a/builder/amazon/common/ami.go +++ /dev/null @@ -1,30 +0,0 @@ -package common - -import ( - "github.com/mitchellh/goamz/ec2" - "log" - "time" -) - -// WaitForAMI waits for the given AMI ID to become ready. -func WaitForAMI(c *ec2.EC2, imageId string) error { - for { - imageResp, err := c.Images([]string{imageId}, ec2.NewFilter()) - if err != nil { - if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidAMIID.NotFound" { - log.Println("AMI not found, probably state issues on AWS side. Trying again.") - continue - } - - return err - } - - if imageResp.Images[0].State == "available" { - return nil - } - - log.Printf("Image in state %s, sleeping 2s before checking again", - imageResp.Images[0].State) - time.Sleep(2 * time.Second) - } -} diff --git a/builder/amazon/common/instance.go b/builder/amazon/common/instance.go index 462bb58e7..2dedfb3a0 100644 --- a/builder/amazon/common/instance.go +++ b/builder/amazon/common/instance.go @@ -30,6 +30,32 @@ type StateChangeConf struct { Target string } +// AMIStateRefreshFunc returns a StateRefreshFunc that is used to watch +// an AMI for state changes. +func AMIStateRefreshFunc(conn *ec2.EC2, imageId string) StateRefreshFunc { + return func() (interface{}, string, error) { + resp, err := conn.Images([]string{imageId}, ec2.NewFilter()) + if err != nil { + if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidAMIID.NotFound" { + // Set this to nil as if we didn't find anything. + resp = nil + } else { + log.Printf("Error on AMIStateRefresh: %s", err) + return nil, "", err + } + } + + if resp == nil || len(resp.Images) == 0 { + // Sometimes AWS has consistency issues and doesn't see the + // AMI. Return an empty state. + return nil, "", nil + } + + i := resp.Images[0] + return i, i.State, nil + } +} + // InstanceStateRefreshFunc returns a StateRefreshFunc that is used to watch // an EC2 instance. func InstanceStateRefreshFunc(conn *ec2.EC2, i *ec2.Instance) StateRefreshFunc { diff --git a/builder/amazon/common/step_ami_region_copy.go b/builder/amazon/common/step_ami_region_copy.go index 8d50f5dfc..5ee486522 100644 --- a/builder/amazon/common/step_ami_region_copy.go +++ b/builder/amazon/common/step_ami_region_copy.go @@ -40,8 +40,17 @@ func (s *StepAMIRegionCopy) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - ui.Say(fmt.Sprintf("Waiting for AMI (%s) in region (%s) to become ready...", resp.ImageId, region)) - if err := WaitForAMI(regionconn, resp.ImageId); err != nil { + stateChange := StateChangeConf{ + Conn: regionconn, + Pending: []string{"pending"}, + Target: "available", + Refresh: AMIStateRefreshFunc(regionconn, resp.ImageId), + StepState: state, + } + + ui.Say(fmt.Sprintf("Waiting for AMI (%s) in region (%s) to become ready...", + resp.ImageId, region)) + if _, err := WaitForState(&stateChange); err != nil { err := fmt.Errorf("Error waiting for AMI (%s) in region (%s): %s", resp.ImageId, region, err) state.Put("error", err) ui.Error(err.Error()) diff --git a/builder/amazon/ebs/step_create_ami.go b/builder/amazon/ebs/step_create_ami.go index ec89827a0..9dea11317 100644 --- a/builder/amazon/ebs/step_create_ami.go +++ b/builder/amazon/ebs/step_create_ami.go @@ -39,8 +39,16 @@ func (s *stepCreateAMI) Run(state multistep.StateBag) multistep.StepAction { state.Put("amis", amis) // Wait for the image to become ready + stateChange := awscommon.StateChangeConf{ + Conn: ec2conn, + Pending: []string{"pending"}, + Target: "available", + Refresh: awscommon.AMIStateRefreshFunc(ec2conn, createResp.ImageId), + StepState: state, + } + ui.Say("Waiting for AMI to become ready...") - if err := awscommon.WaitForAMI(ec2conn, createResp.ImageId); err != nil { + if _, err := awscommon.WaitForState(&stateChange); err != nil { err := fmt.Errorf("Error waiting for AMI: %s", err) state.Put("error", err) ui.Error(err.Error()) diff --git a/builder/amazon/instance/step_register_ami.go b/builder/amazon/instance/step_register_ami.go index 729c020d3..35f31882d 100644 --- a/builder/amazon/instance/step_register_ami.go +++ b/builder/amazon/instance/step_register_ami.go @@ -37,8 +37,16 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction { state.Put("amis", amis) // Wait for the image to become ready + stateChange := awscommon.StateChangeConf{ + Conn: ec2conn, + Pending: []string{"pending"}, + Target: "available", + Refresh: awscommon.AMIStateRefreshFunc(ec2conn, registerResp.ImageId), + StepState: state, + } + ui.Say("Waiting for AMI to become ready...") - if err := awscommon.WaitForAMI(ec2conn, registerResp.ImageId); err != nil { + if _, err := awscommon.WaitForState(&stateChange); err != nil { err := fmt.Errorf("Error waiting for AMI: %s", err) state.Put("error", err) ui.Error(err.Error())