diff --git a/builder/virtualbox/builder.go b/builder/virtualbox/builder.go index 8f894b9b2..6862c3d04 100644 --- a/builder/virtualbox/builder.go +++ b/builder/virtualbox/builder.go @@ -295,7 +295,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe steps := []multistep.Step{ new(stepDownloadGuestAdditions), - new(stepDownloadISO), + &common.StepDownload{ + Checksum: b.config.ISOChecksum, + ChecksumType: b.config.ISOChecksumType, + Description: "ISO", + ResultKey: "iso_path", + Url: []string{b.config.ISOUrl}, + }, new(stepPrepareOutputDir), &common.StepCreateFloppy{ Files: b.config.FloppyFiles, diff --git a/builder/virtualbox/step_download_iso.go b/builder/virtualbox/step_download_iso.go deleted file mode 100644 index 39bcea3de..000000000 --- a/builder/virtualbox/step_download_iso.go +++ /dev/null @@ -1,90 +0,0 @@ -package virtualbox - -import ( - "encoding/hex" - "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/common" - "github.com/mitchellh/packer/packer" - "log" - "time" -) - -// This step downloads the ISO specified. -// -// Uses: -// cache packer.Cache -// config *config -// ui packer.Ui -// -// Produces: -// iso_path string -type stepDownloadISO struct{} - -func (s stepDownloadISO) Run(state map[string]interface{}) multistep.StepAction { - cache := state["cache"].(packer.Cache) - config := state["config"].(*config) - ui := state["ui"].(packer.Ui) - - checksum, err := hex.DecodeString(config.ISOChecksum) - if err != nil { - state["error"] = fmt.Errorf("Error parsing checksum: %s", err) - return multistep.ActionHalt - } - - log.Printf("Acquiring lock to download the ISO.") - cachePath := cache.Lock(config.ISOUrl) - defer cache.Unlock(config.ISOUrl) - - downloadConfig := &common.DownloadConfig{ - Url: config.ISOUrl, - TargetPath: cachePath, - CopyFile: false, - Hash: common.HashForType(config.ISOChecksumType), - Checksum: checksum, - } - - download := common.NewDownloadClient(downloadConfig) - - downloadCompleteCh := make(chan error, 1) - go func() { - ui.Say("Copying or downloading ISO. Progress will be reported periodically.") - cachePath, err = download.Get() - downloadCompleteCh <- err - }() - - progressTicker := time.NewTicker(5 * time.Second) - defer progressTicker.Stop() - -DownloadWaitLoop: - for { - select { - case err := <-downloadCompleteCh: - if err != nil { - err := fmt.Errorf("Error downloading ISO: %s", err) - state["error"] = err - ui.Error(err.Error()) - return multistep.ActionHalt - } - - break DownloadWaitLoop - case <-progressTicker.C: - progress := download.PercentProgress() - if progress >= 0 { - ui.Message(fmt.Sprintf("Download progress: %d%%", progress)) - } - case <-time.After(1 * time.Second): - if _, ok := state[multistep.StateCancelled]; ok { - ui.Say("Interrupt received. Cancelling download...") - return multistep.ActionHalt - } - } - } - - log.Printf("Path to ISO on disk: %s", cachePath) - state["iso_path"] = cachePath - - return multistep.ActionContinue -} - -func (stepDownloadISO) Cleanup(map[string]interface{}) {} diff --git a/common/step_download.go b/common/step_download.go new file mode 100644 index 000000000..9f97a8eac --- /dev/null +++ b/common/step_download.go @@ -0,0 +1,126 @@ +package common + +import ( + "encoding/hex" + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" + "time" +) + +// StepDownload downloads a remote file using the download client within +// this package. This step handles setting up the download configuration, +// progress reporting, interrupt handling, etc. +// +// Uses: +// cache packer.Cache +// ui packer.Ui +type StepDownload struct { + // The checksum and the type of the checksum for the download + Checksum string + ChecksumType string + + // A short description of the type of download being done. Example: + // "ISO" or "Guest Additions" + Description string + + // The name of the key where the final path of the ISO will be put + // into the state. + ResultKey string + + // A list of URLs to attempt to download this thing. + Url []string +} + +func (s *StepDownload) Run(state map[string]interface{}) multistep.StepAction { + cache := state["cache"].(packer.Cache) + ui := state["ui"].(packer.Ui) + + checksum, err := hex.DecodeString(s.Checksum) + if err != nil { + state["error"] = fmt.Errorf("Error parsing checksum: %s", err) + return multistep.ActionHalt + } + + ui.Say(fmt.Sprintf("Downloading or copying %s", s.Description)) + + var finalPath string + for _, url := range s.Url { + ui.Message(fmt.Sprintf("Downloading or copying: %s", url)) + log.Printf("Acquiring lock to download: %s", url) + cachePath := cache.Lock(url) + defer cache.Unlock(url) + + config := &DownloadConfig{ + Url: url, + TargetPath: cachePath, + CopyFile: false, + Hash: HashForType(s.ChecksumType), + Checksum: checksum, + } + + path, err, retry := s.download(config, state) + if err != nil { + ui.Message(fmt.Sprintf("Error downloading: %s", err)) + } + + if !retry { + return multistep.ActionHalt + } + + if err == nil { + finalPath = path + break + } + } + + if finalPath == "" { + err := fmt.Errorf("%s download failed.", s.Description) + state["error"] = err + ui.Error(err.Error()) + return multistep.ActionHalt + } + + state[s.ResultKey] = finalPath + return multistep.ActionContinue +} + +func (s *StepDownload) Cleanup(map[string]interface{}) {} + +func (s *StepDownload) download(config *DownloadConfig, state map[string]interface{}) (string, error, bool) { + var path string + ui := state["ui"].(packer.Ui) + download := NewDownloadClient(config) + + downloadCompleteCh := make(chan error, 1) + go func() { + var err error + path, err = download.Get() + downloadCompleteCh <- err + }() + + progressTicker := time.NewTicker(5 * time.Second) + defer progressTicker.Stop() + + for { + select { + case err := <-downloadCompleteCh: + if err != nil { + return "", err, true + } + + return path, nil, true + case <-progressTicker.C: + progress := download.PercentProgress() + if progress >= 0 { + ui.Message(fmt.Sprintf("Download progress: %d%%", progress)) + } + case <-time.After(1 * time.Second): + if _, ok := state[multistep.StateCancelled]; ok { + ui.Say("Interrupt received. Cancelling download...") + return "", nil, false + } + } + } +} diff --git a/common/step_download_test.go b/common/step_download_test.go new file mode 100644 index 000000000..258beda09 --- /dev/null +++ b/common/step_download_test.go @@ -0,0 +1,14 @@ +package common + +import ( + "github.com/mitchellh/multistep" + "testing" +) + +func TestStepDownload_Impl(t *testing.T) { + var raw interface{} + raw = new(StepDownload) + if _, ok := raw.(multistep.Step); !ok { + t.Fatalf("download should be a step") + } +}