diff --git a/builder/virtualbox/builder.go b/builder/virtualbox/builder.go index c575feb6a..2a90d41da 100644 --- a/builder/virtualbox/builder.go +++ b/builder/virtualbox/builder.go @@ -22,11 +22,14 @@ type Builder struct { } type config struct { - GuestOSType string `mapstructure:"guest_os_type"` - ISOMD5 string `mapstructure:"iso_md5"` - ISOUrl string `mapstructure:"iso_url"` - OutputDir string `mapstructure:"output_directory"` - VMName string `mapstructure:"vm_name"` + GuestOSType string `mapstructure:"guest_os_type"` + ISOMD5 string `mapstructure:"iso_md5"` + ISOUrl string `mapstructure:"iso_url"` + OutputDir string `mapstructure:"output_directory"` + SSHHostPortMin uint `mapstructure:"ssh_host_port_min"` + SSHHostPortMax uint `mapstructure:"ssh_host_port_max"` + SSHPort uint `mapstructure:"ssh_port"` + VMName string `mapstructure:"vm_name"` } func (b *Builder) Prepare(raw interface{}) error { @@ -43,6 +46,18 @@ func (b *Builder) Prepare(raw interface{}) error { b.config.OutputDir = "virtualbox" } + if b.config.SSHHostPortMin == 0 { + b.config.SSHHostPortMin = 2222 + } + + if b.config.SSHHostPortMax == 0 { + b.config.SSHHostPortMax = 4444 + } + + if b.config.SSHPort == 0 { + b.config.SSHPort = 22 + } + if b.config.VMName == "" { b.config.VMName = "packer" } @@ -114,6 +129,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) packer new(stepCreateVM), new(stepCreateDisk), new(stepAttachISO), + new(stepForwardSSH), } // Setup the state bag diff --git a/builder/virtualbox/builder_test.go b/builder/virtualbox/builder_test.go index 4049cfe7f..7fbba664f 100644 --- a/builder/virtualbox/builder_test.go +++ b/builder/virtualbox/builder_test.go @@ -38,6 +38,18 @@ func TestBuilderPrepare_Defaults(t *testing.T) { t.Errorf("bad output dir: %s", b.config.OutputDir) } + if b.config.SSHHostPortMin != 2222 { + t.Errorf("bad min ssh host port: %d", b.config.SSHHostPortMin) + } + + if b.config.SSHHostPortMax != 4444 { + t.Errorf("bad max ssh host port: %d", b.config.SSHHostPortMax) + } + + if b.config.SSHPort != 22 { + t.Errorf("bad ssh port: %d", b.config.SSHPort) + } + if b.config.VMName != "packer" { t.Errorf("bad vm name: %s", b.config.VMName) } diff --git a/builder/virtualbox/step_attach_iso.go b/builder/virtualbox/step_attach_iso.go index 6464fc1fd..fe1c4df0c 100644 --- a/builder/virtualbox/step_attach_iso.go +++ b/builder/virtualbox/step_attach_iso.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "time" ) // This step attaches the ISO to the virtual machine. @@ -12,7 +11,7 @@ import ( // Uses: // // Produces: -type stepAttachISO struct{ +type stepAttachISO struct { diskPath string } @@ -39,7 +38,6 @@ func (s *stepAttachISO) Run(state map[string]interface{}) multistep.StepAction { // Track the path so that we can unregister it from VirtualBox later s.diskPath = isoPath - time.Sleep(15 * time.Second) return multistep.ActionContinue } @@ -50,8 +48,17 @@ func (s *stepAttachISO) Cleanup(state map[string]interface{}) { driver := state["driver"].(Driver) ui := state["ui"].(packer.Ui) + vmName := state["vmName"].(string) - if err := driver.VBoxManage("closemedium", "disk", s.diskPath); err != nil { + command := []string{ + "storageattach", vmName, + "--storagectl", "IDE Controller", + "--port", "0", + "--device", "1", + "--medium", "none", + } + + if err := driver.VBoxManage(command...); err != nil { ui.Error(fmt.Sprintf("Error unregistering ISO: %s", err)) } } diff --git a/builder/virtualbox/step_create_disk.go b/builder/virtualbox/step_create_disk.go index 4f08e7085..00c395a12 100644 --- a/builder/virtualbox/step_create_disk.go +++ b/builder/virtualbox/step_create_disk.go @@ -10,9 +10,7 @@ import ( // This step creates the virtual disk that will be used as the // hard drive for the virtual machine. -type stepCreateDisk struct { - diskPath string -} +type stepCreateDisk struct {} func (s *stepCreateDisk) Run(state map[string]interface{}) multistep.StepAction { config := state["config"].(*config) @@ -38,9 +36,6 @@ func (s *stepCreateDisk) Run(state map[string]interface{}) multistep.StepAction return multistep.ActionHalt } - // Set the path so that we can delete it later - s.diskPath = path - // Add the IDE controller so we can later attach the disk controllerName := "IDE Controller" err = driver.VBoxManage("storagectl", vmName, "--name", controllerName, "--add", "ide") @@ -66,16 +61,4 @@ func (s *stepCreateDisk) Run(state map[string]interface{}) multistep.StepAction return multistep.ActionContinue } -func (s *stepCreateDisk) Cleanup(state map[string]interface{}) { - if s.diskPath == "" { - return - } - - driver := state["driver"].(Driver) - ui := state["ui"].(packer.Ui) - - ui.Say("Unregistering and deleting hard disk...") - if err := driver.VBoxManage("closemedium", "disk", s.diskPath, "--delete"); err != nil { - ui.Error(fmt.Sprintf("Error deleting hard drive: %s", err)) - } -} +func (s *stepCreateDisk) Cleanup(state map[string]interface{}) {} diff --git a/builder/virtualbox/step_forward_ssh.go b/builder/virtualbox/step_forward_ssh.go new file mode 100644 index 000000000..2caed9f36 --- /dev/null +++ b/builder/virtualbox/step_forward_ssh.go @@ -0,0 +1,56 @@ +package virtualbox + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" + "math/rand" + "net" + "time" +) + +// This step adds a NAT port forwarding definition so that SSH is available +// on the guest machine. +// +// Uses: +// +// Produces: +type stepForwardSSH struct{} + +func (s *stepForwardSSH) Run(state map[string]interface{}) multistep.StepAction { + config := state["config"].(*config) + driver := state["driver"].(Driver) + ui := state["ui"].(packer.Ui) + vmName := state["vmName"].(string) + + log.Printf("Looking for available SSH port between %d and %d", config.SSHHostPortMin, config.SSHHostPortMax) + var sshHostPort uint + portRange := int(config.SSHHostPortMax - config.SSHHostPortMin) + for { + sshHostPort = uint(rand.Intn(portRange)) + config.SSHHostPortMin + log.Printf("Trying port: %d", sshHostPort) + l, err := net.Listen("tcp", fmt.Sprintf(":%d", sshHostPort)) + if err == nil { + defer l.Close() + break + } + } + + // Attach the disk to the controller + ui.Say(fmt.Sprintf("Creating forwarded port mapping for SSH (host port %d)", sshHostPort)) + command := []string{ + "modifyvm", vmName, + "--natpf1", + fmt.Sprintf("packerssh,tcp,127.0.0.1,%d,,%d", sshHostPort, config.SSHPort), + } + if err := driver.VBoxManage(command...); err != nil { + ui.Error(fmt.Sprintf("Error creating port forwarding rule: %s", err)) + return multistep.ActionHalt + } + + time.Sleep(15 * time.Second) + return multistep.ActionContinue +} + +func (s *stepForwardSSH) Cleanup(state map[string]interface{}) {}