diff --git a/builder/virtualbox/builder.go b/builder/virtualbox/builder.go index de6a0ce68..2d61fe2d9 100644 --- a/builder/virtualbox/builder.go +++ b/builder/virtualbox/builder.go @@ -25,27 +25,28 @@ type Builder struct { } type config struct { - BootCommand []string `mapstructure:"boot_command"` - BootWait time.Duration `` - DiskSize uint `mapstructure:"disk_size"` - GuestOSType string `mapstructure:"guest_os_type"` - 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"` + GuestOSType string `mapstructure:"guest_os_type"` + 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"` PackerDebug bool `mapstructure:"packer_debug"` @@ -68,6 +69,10 @@ func (b *Builder) Prepare(raws ...interface{}) error { b.config.DiskSize = 40000 } + if b.config.GuestAdditionsPath == "" { + b.config.GuestAdditionsPath = "VBoxGuestAdditions.iso" + } + if b.config.GuestOSType == "" { b.config.GuestOSType = "Other" } @@ -206,6 +211,7 @@ func (b *Builder) Prepare(raws ...interface{}) error { func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { steps := []multistep.Step{ + new(stepDownloadGuestAdditions), new(stepDownloadISO), new(stepPrepareOutputDir), new(stepHTTPServer), diff --git a/builder/virtualbox/builder_test.go b/builder/virtualbox/builder_test.go index 4b6633aed..46163c44f 100644 --- a/builder/virtualbox/builder_test.go +++ b/builder/virtualbox/builder_test.go @@ -102,6 +102,32 @@ func TestBuilderPrepare_DiskSize(t *testing.T) { } } +func TestBuilderPrepare_GuestAdditionsPath(t *testing.T) { + var b Builder + config := testConfig() + + delete(config, "disk_size") + err := b.Prepare(config) + if err != nil { + t.Fatalf("bad err: %s", err) + } + + if b.config.GuestAdditionsPath != "VBoxGuestAdditions.iso" { + t.Fatalf("bad: %s", b.config.GuestAdditionsPath) + } + + config["guest_additions_path"] = "foo" + b = Builder{} + err = b.Prepare(config) + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.GuestAdditionsPath != "foo" { + t.Fatalf("bad size: %s", b.config.GuestAdditionsPath) + } +} + func TestBuilderPrepare_HTTPPort(t *testing.T) { var b Builder config := testConfig() diff --git a/builder/virtualbox/step_download_guest_additions.go b/builder/virtualbox/step_download_guest_additions.go new file mode 100644 index 000000000..5ea766399 --- /dev/null +++ b/builder/virtualbox/step_download_guest_additions.go @@ -0,0 +1,82 @@ +package virtualbox + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/builder/common" + "github.com/mitchellh/packer/packer" + "log" + "time" +) + +// This step uploads a file containing the VirtualBox version, which +// can be useful for various provisioning reasons. +// +// Produces: +// guest_additions_path string - Path to the guest additions. +type stepDownloadGuestAdditions struct{} + +func (s *stepDownloadGuestAdditions) Run(state map[string]interface{}) multistep.StepAction { + cache := state["cache"].(packer.Cache) + driver := state["driver"].(Driver) + ui := state["ui"].(packer.Ui) + + version, err := driver.Version() + if err != nil { + state["error"] = fmt.Errorf("Error reading version for guest additions download: %s", err) + return multistep.ActionHalt + } + + url := fmt.Sprintf( + "http://download.virtualbox.org/virtualbox/%s/VBoxGuestAdditions_%s.iso", + version, version) + 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{ + Url: url, + TargetPath: cachePath, + Hash: nil, + } + + download := common.NewDownloadClient(downloadConfig) + + downloadCompleteCh := make(chan error, 1) + go func() { + ui.Say("Downloading VirtualBox guest additions. Progress will be shown 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 { + state["error"] = fmt.Errorf("Error downloading guest additions: %s", err) + return multistep.ActionHalt + } + + break DownloadWaitLoop + case <-progressTicker.C: + ui.Message(fmt.Sprintf("Download progress: %d%%", download.PercentProgress())) + case <-time.After(1 * time.Second): + if _, ok := state[multistep.StateCancelled]; ok { + ui.Say("Interrupt received. Cancelling download...") + return multistep.ActionHalt + } + } + } + + state["guest_additions_path"] = cachePath + + return multistep.ActionContinue +} + +func (s *stepDownloadGuestAdditions) Cleanup(state map[string]interface{}) {} diff --git a/builder/virtualbox/step_download_iso.go b/builder/virtualbox/step_download_iso.go index 442c25b83..ed579d298 100644 --- a/builder/virtualbox/step_download_iso.go +++ b/builder/virtualbox/step_download_iso.go @@ -78,7 +78,7 @@ DownloadWaitLoop: break DownloadWaitLoop case <-progressTicker.C: - ui.Say(fmt.Sprintf("Download progress: %d%%", download.PercentProgress())) + ui.Message(fmt.Sprintf("Download progress: %d%%", download.PercentProgress())) case <-time.After(1 * time.Second): if _, ok := state[multistep.StateCancelled]; ok { ui.Say("Interrupt received. Cancelling download...")