diff --git a/builder/virtualbox/builder.go b/builder/virtualbox/builder.go index 47df9e54b..f7ec0e455 100644 --- a/builder/virtualbox/builder.go +++ b/builder/virtualbox/builder.go @@ -25,29 +25,31 @@ type Builder struct { } type config struct { - BootCommand []string `mapstructure:"boot_command"` - BootWait time.Duration `` - DiskSize uint `mapstructure:"disk_size"` - GuestAdditionsPath string `mapstructure:"guest_additions_path"` - GuestOSType string `mapstructure:"guest_os_type"` - Headless bool `mapstructure:"headless"` - HTTPDir string `mapstructure:"http_directory"` - HTTPPortMin uint `mapstructure:"http_port_min"` - HTTPPortMax uint `mapstructure:"http_port_max"` - ISOMD5 string `mapstructure:"iso_md5"` - ISOUrl string `mapstructure:"iso_url"` - OutputDir string `mapstructure:"output_directory"` - ShutdownCommand string `mapstructure:"shutdown_command"` - ShutdownTimeout time.Duration `` - SSHHostPortMin uint `mapstructure:"ssh_host_port_min"` - SSHHostPortMax uint `mapstructure:"ssh_host_port_max"` - SSHPassword string `mapstructure:"ssh_password"` - SSHPort uint `mapstructure:"ssh_port"` - SSHUser string `mapstructure:"ssh_username"` - SSHWaitTimeout time.Duration `` - VBoxVersionFile string `mapstructure:"virtualbox_version_file"` - VBoxManage [][]string `mapstructure:"vboxmanage"` - VMName string `mapstructure:"vm_name"` + BootCommand []string `mapstructure:"boot_command"` + BootWait time.Duration `` + DiskSize uint `mapstructure:"disk_size"` + GuestAdditionsPath string `mapstructure:"guest_additions_path"` + GuestAdditionsURL string `mapstructure:"guest_additions_url"` + GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"` + GuestOSType string `mapstructure:"guest_os_type"` + Headless bool `mapstructure:"headless"` + HTTPDir string `mapstructure:"http_directory"` + HTTPPortMin uint `mapstructure:"http_port_min"` + HTTPPortMax uint `mapstructure:"http_port_max"` + ISOMD5 string `mapstructure:"iso_md5"` + ISOUrl string `mapstructure:"iso_url"` + OutputDir string `mapstructure:"output_directory"` + ShutdownCommand string `mapstructure:"shutdown_command"` + ShutdownTimeout time.Duration `` + SSHHostPortMin uint `mapstructure:"ssh_host_port_min"` + SSHHostPortMax uint `mapstructure:"ssh_host_port_max"` + SSHPassword string `mapstructure:"ssh_password"` + SSHPort uint `mapstructure:"ssh_port"` + SSHUser string `mapstructure:"ssh_username"` + SSHWaitTimeout time.Duration `` + VBoxVersionFile string `mapstructure:"virtualbox_version_file"` + VBoxManage [][]string `mapstructure:"vboxmanage"` + VMName string `mapstructure:"vm_name"` PackerBuildName string `mapstructure:"packer_build_name"` PackerDebug bool `mapstructure:"packer_debug"` @@ -170,6 +172,47 @@ func (b *Builder) Prepare(raws ...interface{}) error { } } + if b.config.GuestAdditionsSHA256 != "" { + b.config.GuestAdditionsSHA256 = strings.ToLower(b.config.GuestAdditionsSHA256) + } + + if b.config.GuestAdditionsURL != "" { + url, err := url.Parse(b.config.GuestAdditionsURL) + if err != nil { + errs = append(errs, fmt.Errorf("guest_additions_url is not a valid URL: %s", err)) + } else { + if url.Scheme == "" { + url.Scheme = "file" + } + + if url.Scheme == "file" { + if _, err := os.Stat(url.Path); err != nil { + errs = append(errs, fmt.Errorf("guest_additions_url points to bad file: %s", err)) + } + } else { + supportedSchemes := []string{"file", "http", "https"} + scheme := strings.ToLower(url.Scheme) + + found := false + for _, supported := range supportedSchemes { + if scheme == supported { + found = true + break + } + } + + if !found { + errs = append(errs, fmt.Errorf("Unsupported URL scheme in guest_additions_url: %s", scheme)) + } + } + } + + if len(errs) == 0 { + // Put the URL back together since we may have modified it + b.config.GuestAdditionsURL = url.String() + } + } + if _, err := os.Stat(b.config.OutputDir); err == nil { errs = append(errs, errors.New("Output directory already exists. It must not exist.")) } diff --git a/builder/virtualbox/step_download_guest_additions.go b/builder/virtualbox/step_download_guest_additions.go index 127f9de8e..3253a1b9d 100644 --- a/builder/virtualbox/step_download_guest_additions.go +++ b/builder/virtualbox/step_download_guest_additions.go @@ -33,7 +33,9 @@ func (s *stepDownloadGuestAdditions) Run(state map[string]interface{}) multistep cache := state["cache"].(packer.Cache) driver := state["driver"].(Driver) ui := state["ui"].(packer.Ui) + config := state["config"].(*config) + // Get VBox version version, err := driver.Version() if err != nil { state["error"] = fmt.Errorf("Error reading version for guest additions download: %s", err) @@ -45,69 +47,19 @@ func (s *stepDownloadGuestAdditions) Run(state map[string]interface{}) multistep version = newVersion } - // 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() + // Use provided version or get it from virtualbox.org + var checksum string - // 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 - } + if config.GuestAdditionsSHA256 != "" { + checksum = config.GuestAdditionsSHA256 + } else { + checksum, action = s.downloadAdditionsSHA256(state, version, additionsName) + if action != multistep.ActionContinue { + return action + } + } checksumBytes, err := hex.DecodeString(checksum) if err != nil { @@ -115,23 +67,29 @@ func (s *stepDownloadGuestAdditions) Run(state map[string]interface{}) multistep return multistep.ActionHalt } - url := fmt.Sprintf( - "http://download.virtualbox.org/virtualbox/%s/%s", - version, additionsName) + // Use the provided source (URL or file path) or generate it + url := config.GuestAdditionsURL + if url == "" { + url = fmt.Sprintf( + "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: 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 @@ -179,3 +137,72 @@ DownloadWaitLoop: return result, multistep.ActionContinue } + +func (s *stepDownloadGuestAdditions) downloadAdditionsSHA256 (state map[string]interface{}, additionsVersion string, additionsName string) (string, multistep.StepAction) { + // 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", additionsVersion) + 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 + } + + // 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 + } + + return checksum, multistep.ActionContinue + +}