diff --git a/builder/virtualbox/step_download_guest_additions.go b/builder/virtualbox/step_download_guest_additions.go index 5ea766399..f28d9cd90 100644 --- a/builder/virtualbox/step_download_guest_additions.go +++ b/builder/virtualbox/step_download_guest_additions.go @@ -1,11 +1,18 @@ package virtualbox import ( + "bytes" + "crypto/sha256" + "encoding/hex" "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/builder/common" "github.com/mitchellh/packer/packer" + "io" + "io/ioutil" "log" + "os" + "strings" "time" ) @@ -17,6 +24,7 @@ import ( type stepDownloadGuestAdditions struct{} func (s *stepDownloadGuestAdditions) Run(state map[string]interface{}) multistep.StepAction { + var action multistep.StepAction cache := state["cache"].(packer.Cache) driver := state["driver"].(Driver) ui := state["ui"].(packer.Ui) @@ -27,56 +35,137 @@ func (s *stepDownloadGuestAdditions) Run(state map[string]interface{}) multistep return multistep.ActionHalt } + // First things first, we get the list of checksums for the files available + // for this version. + checksumsUrl := fmt.Sprintf("http://download.virtualbox.org/virtualbox/%s/SHA256SUMS", version) + checksumsFile, err := ioutil.TempFile("", "packer") + if err != nil { + state["error"] = fmt.Errorf( + "Failed creating temporary file to store guest addition checksums: %s", + err) + return multistep.ActionHalt + } + checksumsFile.Close() + defer os.Remove(checksumsFile.Name()) + + downloadConfig := &common.DownloadConfig{ + Url: checksumsUrl, + TargetPath: checksumsFile.Name(), + Hash: nil, + } + + log.Printf("Downloading guest addition checksums: %s", checksumsUrl) + download := common.NewDownloadClient(downloadConfig) + checksumsPath, action := s.progressDownload(download, state) + if action != multistep.ActionContinue { + return action + } + + additionsName := fmt.Sprintf("VBoxGuestAdditions_%s.iso", version) + + // Next, we find the checksum for the file we're looking to download. + // It is an error if the checksum cannot be found. + checksumsF, err := os.Open(checksumsPath) + if err != nil { + state["error"] = fmt.Errorf("Error opening guest addition checksums: %s", err) + return multistep.ActionHalt + } + defer checksumsF.Close() + + // We copy the contents of the file into memory. In general this file + // is quite small so that is okay. In the future, we probably want to + // use bufio and iterate line by line. + var contents bytes.Buffer + io.Copy(&contents, checksumsF) + + checksum := "" + for _, line := range strings.Split(contents.String(), "\n") { + parts := strings.Fields(line) + log.Printf("Checksum file parts: %#v", parts) + if len(parts) != 2 { + // Bogus line + continue + } + + if strings.HasSuffix(parts[1], additionsName) { + checksum = parts[0] + log.Printf("Guest additions checksum: %s", checksum) + break + } + } + + if checksum == "" { + state["error"] = fmt.Errorf("The checksum for the file '%s' could not be found.", additionsName) + return multistep.ActionHalt + } + + checksumBytes, err := hex.DecodeString(checksum) + if err != nil { + state["error"] = fmt.Errorf("Couldn't decode checksum into bytes: %s", checksum) + return multistep.ActionHalt + } + url := fmt.Sprintf( - "http://download.virtualbox.org/virtualbox/%s/VBoxGuestAdditions_%s.iso", - version, version) + "http://download.virtualbox.org/virtualbox/%s/%s", + version, additionsName) log.Printf("Guest additions URL: %s", url) log.Printf("Acquiring lock to download the guest additions ISO.") cachePath := cache.Lock(url) defer cache.Unlock(url) - downloadConfig := &common.DownloadConfig{ + downloadConfig = &common.DownloadConfig{ Url: url, TargetPath: cachePath, - Hash: nil, + Hash: sha256.New(), + Checksum: checksumBytes, } - download := common.NewDownloadClient(downloadConfig) + download = common.NewDownloadClient(downloadConfig) + ui.Say("Downloading VirtualBox guest additions. Progress will be shown periodically.") + state["guest_additions_path"], action = s.progressDownload(download, state) + return action +} + +func (s *stepDownloadGuestAdditions) Cleanup(state map[string]interface{}) {} + +func (s *stepDownloadGuestAdditions) progressDownload(c *common.DownloadClient, state map[string]interface{}) (string, multistep.StepAction) { + ui := state["ui"].(packer.Ui) + var result string downloadCompleteCh := make(chan error, 1) + + // Start a goroutine to actually do the download... go func() { - ui.Say("Downloading VirtualBox guest additions. Progress will be shown periodically.") - cachePath, err = download.Get() + var err error + result, err = c.Get() downloadCompleteCh <- err }() progressTicker := time.NewTicker(5 * time.Second) defer progressTicker.Stop() + // A loop that handles showing progress as well as timing out and handling + // interrupts and all that. DownloadWaitLoop: for { select { case err := <-downloadCompleteCh: if err != nil { - state["error"] = fmt.Errorf("Error downloading guest additions: %s", err) - return multistep.ActionHalt + state["error"] = fmt.Errorf("Error downloading: %s", err) + return "", multistep.ActionHalt } break DownloadWaitLoop case <-progressTicker.C: - ui.Message(fmt.Sprintf("Download progress: %d%%", download.PercentProgress())) + ui.Message(fmt.Sprintf("Download progress: %d%%", c.PercentProgress())) case <-time.After(1 * time.Second): if _, ok := state[multistep.StateCancelled]; ok { ui.Say("Interrupt received. Cancelling download...") - return multistep.ActionHalt + return "", multistep.ActionHalt } } } - state["guest_additions_path"] = cachePath - - return multistep.ActionContinue + return result, multistep.ActionContinue } - -func (s *stepDownloadGuestAdditions) Cleanup(state map[string]interface{}) {} diff --git a/builder/virtualbox/step_upload_guest_additions.go b/builder/virtualbox/step_upload_guest_additions.go index 7ed96c08b..81b6f49fd 100644 --- a/builder/virtualbox/step_upload_guest_additions.go +++ b/builder/virtualbox/step_upload_guest_additions.go @@ -43,7 +43,7 @@ func (s *stepUploadGuestAdditions) Run(state map[string]interface{}) multistep.S t := template.Must(template.New("path").Parse(config.GuestAdditionsPath)) t.Execute(&processedPath, tplData) - ui.Say("Upload VirtualBox guest additions ISO...") + ui.Say("Uploading VirtualBox guest additions ISO...") if err := comm.Upload(processedPath.String(), f); err != nil { state["error"] = fmt.Errorf("Error uploading guest additions: %s", err) return multistep.ActionHalt