diff --git a/builder/amazon/common/step_create_tags.go b/builder/amazon/common/step_create_tags.go index 81ede5482..29e8a8c73 100644 --- a/builder/amazon/common/step_create_tags.go +++ b/builder/amazon/common/step_create_tags.go @@ -105,7 +105,7 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction { ReportTags(ui, snapshotTags) // Retry creating tags for about 2.5 minutes - err = retry.Retry(0.2, 30, 11, func() (bool, error) { + err = retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) { // Tag images and snapshots _, err := regionconn.CreateTags(&ec2.CreateTagsInput{ Resources: resourceIds, diff --git a/builder/amazon/common/step_run_source_instance.go b/builder/amazon/common/step_run_source_instance.go index a808d49ee..1aecd75f5 100644 --- a/builder/amazon/common/step_run_source_instance.go +++ b/builder/amazon/common/step_run_source_instance.go @@ -294,7 +294,7 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi ReportTags(ui, ec2Tags) // Retry creating tags for about 2.5 minutes - err = retry.Retry(0.2, 30, 11, func() (bool, error) { + err = retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) { _, err := ec2conn.CreateTags(&ec2.CreateTagsInput{ Tags: ec2Tags, Resources: []*string{instance.InstanceId}, diff --git a/builder/googlecompute/driver_gce.go b/builder/googlecompute/driver_gce.go index b4cdd8d84..7addec698 100644 --- a/builder/googlecompute/driver_gce.go +++ b/builder/googlecompute/driver_gce.go @@ -582,7 +582,7 @@ type stateRefreshFunc func() (string, error) // waitForState will spin in a loop forever waiting for state to // reach a certain target. func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) error { - err := common.Retry(2, 2, 0, func() (bool, error) { + err := common.Retry(2, 2, 0, func(_ uint) (bool, error) { state, err := refresh() if err != nil { return false, err diff --git a/builder/googlecompute/step_wait_startup_script.go b/builder/googlecompute/step_wait_startup_script.go index 78bb4e4e3..f2e1e523f 100644 --- a/builder/googlecompute/step_wait_startup_script.go +++ b/builder/googlecompute/step_wait_startup_script.go @@ -22,7 +22,7 @@ func (s *StepWaitStartupScript) Run(state multistep.StateBag) multistep.StepActi ui.Say("Waiting for any running startup script to finish...") // Keep checking the serial port output to see if the startup script is done. - err := common.Retry(10, 60, 0, func() (bool, error) { + err := common.Retry(10, 60, 0, func(_ uint) (bool, error) { status, err := driver.GetInstanceMetadata(config.Zone, instanceName, StartupScriptStatusKey) diff --git a/builder/virtualbox/common/step_remove_devices.go b/builder/virtualbox/common/step_remove_devices.go index 52d32f1b2..7860d53e9 100644 --- a/builder/virtualbox/common/step_remove_devices.go +++ b/builder/virtualbox/common/step_remove_devices.go @@ -45,7 +45,7 @@ func (s *StepRemoveDevices) Run(state multistep.StateBag) multistep.StepAction { var vboxErr error // Retry for 10 minutes to remove the floppy controller. log.Printf("Trying for 10 minutes to remove floppy controller.") - err := common.Retry(15, 15, 40, func() (bool, error) { + err := common.Retry(15, 15, 40, func(_ uint) (bool, error) { // Don't forget to remove the floppy controller as well command = []string{ "storagectl", vmName, diff --git a/common/retry.go b/common/retry.go index 4307af8b4..4f229e892 100644 --- a/common/retry.go +++ b/common/retry.go @@ -8,7 +8,11 @@ import ( var RetryExhaustedError error = fmt.Errorf("Function never succeeded in Retry") -type RetryableFunc func() (bool, error) +// RetryableFunc performs an action and returns a bool indicating whether the +// function is done, or if it should keep retrying, and an erorr which will +// abort the retry and be returned by the Retry function. The 0-indexed attempt +// is passed with each call. +type RetryableFunc func(uint) (bool, error) // Retry retries a function up to numTries times with exponential backoff. // If numTries == 0, retry indefinitely. If interval == 0, Retry will not delay retrying and there will be @@ -27,7 +31,7 @@ func Retry(initialInterval float64, maxInterval float64, numTries uint, function done := false interval := initialInterval for i := uint(0); !done && (numTries == 0 || i < numTries); i++ { - done, err = function() + done, err = function(i) if err != nil { return err } diff --git a/common/retry_test.go b/common/retry_test.go index 141e0fae4..364f31a5c 100644 --- a/common/retry_test.go +++ b/common/retry_test.go @@ -8,7 +8,7 @@ import ( func TestRetry(t *testing.T) { numTries := uint(0) // Test that a passing function only gets called once. - err := Retry(0, 0, 0, func() (bool, error) { + err := Retry(0, 0, 0, func(i uint) (bool, error) { numTries++ return true, nil }) @@ -22,7 +22,7 @@ func TestRetry(t *testing.T) { // Test that a failing function gets retried (once in this example). numTries = 0 results := []bool{false, true} - err = Retry(0, 0, 0, func() (bool, error) { + err = Retry(0, 0, 0, func(i uint) (bool, error) { result := results[numTries] numTries++ return result, nil @@ -37,7 +37,7 @@ func TestRetry(t *testing.T) { // Test that a function error gets returned, and the function does not get called again. numTries = 0 funcErr := fmt.Errorf("This function had an error!") - err = Retry(0, 0, 0, func() (bool, error) { + err = Retry(0, 0, 0, func(i uint) (bool, error) { numTries++ return false, funcErr }) @@ -51,7 +51,7 @@ func TestRetry(t *testing.T) { // Test when a function exhausts its retries. numTries = 0 expectedTries := uint(3) - err = Retry(0, 0, expectedTries, func() (bool, error) { + err = Retry(0, 0, expectedTries, func(i uint) (bool, error) { numTries++ return false, nil }) diff --git a/post-processor/vagrant-cloud/post-processor.go b/post-processor/vagrant-cloud/post-processor.go index 419944153..01575def7 100644 --- a/post-processor/vagrant-cloud/post-processor.go +++ b/post-processor/vagrant-cloud/post-processor.go @@ -152,7 +152,6 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac new(stepCreateProvider), new(stepPrepareUpload), new(stepUpload), - new(stepVerifyUpload), new(stepReleaseVersion), } } else { diff --git a/post-processor/vagrant-cloud/step_prepare_upload.go b/post-processor/vagrant-cloud/step_prepare_upload.go index 62e270fd8..26d82471e 100644 --- a/post-processor/vagrant-cloud/step_prepare_upload.go +++ b/post-processor/vagrant-cloud/step_prepare_upload.go @@ -2,12 +2,12 @@ package vagrantcloud import ( "fmt" + "github.com/hashicorp/packer/packer" "github.com/mitchellh/multistep" ) type Upload struct { - Token string `json:"token"` UploadPath string `json:"upload_path"` } @@ -41,8 +41,6 @@ func (s *stepPrepareUpload) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - ui.Message(fmt.Sprintf("Box upload prepared with token %s", upload.Token)) - // Save the upload details to the state state.Put("upload", upload) diff --git a/post-processor/vagrant-cloud/step_upload.go b/post-processor/vagrant-cloud/step_upload.go index cca7ef83f..7004177fd 100644 --- a/post-processor/vagrant-cloud/step_upload.go +++ b/post-processor/vagrant-cloud/step_upload.go @@ -2,8 +2,9 @@ package vagrantcloud import ( "fmt" - "time" + "log" + "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/packer" "github.com/mitchellh/multistep" ) @@ -23,34 +24,27 @@ func (s *stepUpload) Run(state multistep.StateBag) multistep.StepAction { "Depending on your internet connection and the size of the box,\n" + "this may take some time") - var finalErr error - for i := 0; i < 3; i++ { - if i > 0 { - ui.Message(fmt.Sprintf("Uploading box, attempt %d", i+1)) - } + err := common.Retry(10, 10, 3, func(i uint) (bool, error) { + ui.Message(fmt.Sprintf("Uploading box, attempt %d", i+1)) resp, err := client.Upload(artifactFilePath, url) if err != nil { - finalErr = err ui.Message(fmt.Sprintf( "Error uploading box! Will retry in 10 seconds. Error: %s", err)) - time.Sleep(10 * time.Second) - continue + return false, nil } if resp.StatusCode != 200 { - finalErr = fmt.Errorf("bad HTTP status: %d", resp.StatusCode) + log.Printf("bad HTTP status: %d", resp.StatusCode) ui.Message(fmt.Sprintf( "Error uploading box! Will retry in 10 seconds. Status: %d", resp.StatusCode)) - time.Sleep(10 * time.Second) - continue + return false, nil } + return true, nil + }) - finalErr = nil - } - - if finalErr != nil { - state.Put("error", finalErr) + if err != nil { + state.Put("error", err) return multistep.ActionHalt } diff --git a/post-processor/vagrant-cloud/step_verify_upload.go b/post-processor/vagrant-cloud/step_verify_upload.go deleted file mode 100644 index 2aab64b08..000000000 --- a/post-processor/vagrant-cloud/step_verify_upload.go +++ /dev/null @@ -1,104 +0,0 @@ -package vagrantcloud - -import ( - "fmt" - "log" - "time" - - "github.com/hashicorp/packer/packer" - "github.com/mitchellh/multistep" -) - -type stepVerifyUpload struct { -} - -func (s *stepVerifyUpload) Run(state multistep.StateBag) multistep.StepAction { - client := state.Get("client").(*VagrantCloudClient) - ui := state.Get("ui").(packer.Ui) - box := state.Get("box").(*Box) - version := state.Get("version").(*Version) - upload := state.Get("upload").(*Upload) - provider := state.Get("provider").(*Provider) - - path := fmt.Sprintf("box/%s/version/%v/provider/%s", box.Tag, version.Version, provider.Name) - - providerCheck := &Provider{} - - ui.Say(fmt.Sprintf("Verifying provider upload: %s", provider.Name)) - - done := make(chan struct{}) - defer close(done) - - result := make(chan error, 1) - - go func() { - attempts := 0 - for { - attempts += 1 - - log.Printf("Checking token match for provider.. (attempt: %d)", attempts) - - resp, err := client.Get(path) - - if err != nil || (resp.StatusCode != 200) { - cloudErrors := &VagrantCloudErrors{} - err = decodeBody(resp, cloudErrors) - err = fmt.Errorf("Error retrieving provider: %s", cloudErrors.FormatErrors()) - result <- err - return - } - - if err = decodeBody(resp, providerCheck); err != nil { - err = fmt.Errorf("Error parsing provider response: %s", err) - result <- err - return - } - - if err != nil { - result <- err - return - } - - if upload.Token == providerCheck.HostedToken { - // success! - result <- nil - return - } - - // Wait 3 seconds in between - time.Sleep(3 * time.Second) - - // Verify we shouldn't exit - select { - case <-done: - // We finished, so just exit the goroutine - return - default: - // Keep going - } - } - }() - - ui.Message("Waiting for upload token match") - log.Printf("Waiting for up to 600 seconds for provider hosted token to match %s", upload.Token) - - select { - case err := <-result: - if err != nil { - state.Put("error", err) - return multistep.ActionHalt - } - - ui.Message(fmt.Sprintf("Upload successfully verified with token %s", providerCheck.HostedToken)) - log.Printf("Box successfully verified %s == %s", upload.Token, providerCheck.HostedToken) - - return multistep.ActionContinue - case <-time.After(600 * time.Second): - state.Put("error", fmt.Errorf("Timeout while waiting to for upload to verify token '%s'", upload.Token)) - return multistep.ActionHalt - } -} - -func (s *stepVerifyUpload) Cleanup(state multistep.StateBag) { - // No cleanup -}