diff --git a/builder/virtualbox/builder.go b/builder/virtualbox/builder.go index 7e2a732de..ba53192c9 100644 --- a/builder/virtualbox/builder.go +++ b/builder/virtualbox/builder.go @@ -43,6 +43,7 @@ type config struct { SSHPort uint `mapstructure:"ssh_port"` SSHUser string `mapstructure:"ssh_username"` SSHWaitTimeout time.Duration `` + VBoxManage [][]string `mapstructure:"vboxmanage"` VMName string `mapstructure:"vm_name"` PackerDebug bool `mapstructure:"packer_debug"` @@ -94,6 +95,10 @@ func (b *Builder) Prepare(raws ...interface{}) error { b.config.SSHPort = 22 } + if b.config.VBoxManage == nil { + b.config.VBoxManage = make([][]string, 0) + } + if b.config.VMName == "" { b.config.VMName = "packer" } @@ -204,6 +209,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe new(stepCreateDisk), new(stepAttachISO), new(stepForwardSSH), + new(stepVBoxManage), new(stepRun), new(stepTypeBootCommand), new(stepWaitForSSH), diff --git a/builder/virtualbox/builder_test.go b/builder/virtualbox/builder_test.go index 92b6cae21..e32eb1064 100644 --- a/builder/virtualbox/builder_test.go +++ b/builder/virtualbox/builder_test.go @@ -4,6 +4,7 @@ import ( "github.com/mitchellh/packer/packer" "io/ioutil" "os" + "reflect" "testing" ) @@ -279,3 +280,38 @@ func TestBuilderPrepare_SSHWaitTimeout(t *testing.T) { t.Fatalf("should not have error: %s", err) } } + +func TestBuilderPrepare_VBoxManage(t *testing.T) { + var b Builder + config := testConfig() + + // Test with empty + delete(config, "vboxmanage") + err := b.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(b.config.VBoxManage, [][]string{}) { + t.Fatalf("bad: %#v", b.config.VBoxManage) + } + + // Test with a good one + config["vboxmanage"] = [][]interface{}{ + []interface{}{"foo", "bar", "baz"}, + } + + b = Builder{} + err = b.Prepare(config) + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + expected := [][]string{ + []string{"foo", "bar", "baz"}, + } + + if !reflect.DeepEqual(b.config.VBoxManage, expected) { + t.Fatalf("bad: %#v", b.config.VBoxManage) + } +} diff --git a/builder/virtualbox/step_vboxmanage.go b/builder/virtualbox/step_vboxmanage.go new file mode 100644 index 000000000..dc3f98ebc --- /dev/null +++ b/builder/virtualbox/step_vboxmanage.go @@ -0,0 +1,61 @@ +package virtualbox + +import ( + "bytes" + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "strings" + "text/template" +) + +type commandTemplate struct { + Name string +} + +// This step executes additional VBoxManage commands as specified by the +// template. +// +// Uses: +// +// Produces: +type stepVBoxManage struct{} + +func (s *stepVBoxManage) Run(state map[string]interface{}) multistep.StepAction { + config := state["config"].(*config) + driver := state["driver"].(Driver) + ui := state["ui"].(packer.Ui) + vmName := state["vmName"].(string) + + if len(config.VBoxManage) > 0 { + ui.Say("Executing custom VBoxManage commands...") + } + + tplData := &commandTemplate{ + Name: vmName, + } + + for _, originalCommand := range config.VBoxManage { + command := make([]string, len(originalCommand)) + copy(command, originalCommand) + + for i, arg := range command { + var buf bytes.Buffer + t := template.Must(template.New("arg").Parse(arg)) + t.Execute(&buf, tplData) + command[i] = buf.String() + } + + ui.Say(fmt.Sprintf("Executing: %s", strings.Join(command, " "))) + if err := driver.VBoxManage(command...); err != nil { + err := fmt.Errorf("Error executing command: %s", err) + state["error"] = err + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + return multistep.ActionContinue +} + +func (s *stepVBoxManage) Cleanup(state map[string]interface{}) {}