diff --git a/builder/virtualbox/builder.go b/builder/virtualbox/builder.go index 36e08f187..6bba76fb1 100644 --- a/builder/virtualbox/builder.go +++ b/builder/virtualbox/builder.go @@ -26,6 +26,9 @@ type config struct { BootCommand []string `mapstructure:"boot_command"` BootWait time.Duration `` 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"` @@ -47,6 +50,14 @@ func (b *Builder) Prepare(raw interface{}) error { b.config.GuestOSType = "Other" } + if b.config.HTTPPortMin == 0 { + b.config.HTTPPortMin = 8000 + } + + if b.config.HTTPPortMax == 0 { + b.config.HTTPPortMax = 9000 + } + if b.config.OutputDir == "" { b.config.OutputDir = "virtualbox" } @@ -69,6 +80,10 @@ func (b *Builder) Prepare(raw interface{}) error { errs := make([]error, 0) + if b.config.HTTPPortMin > b.config.HTTPPortMax { + errs = append(errs, errors.New("http_port_min must be less than http_port_max")) + } + if b.config.ISOMD5 == "" { errs = append(errs, errors.New("Due to large file sizes, an iso_md5 is required")) } else { @@ -141,6 +156,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) packer steps := []multistep.Step{ new(stepDownloadISO), new(stepPrepareOutputDir), + new(stepHTTPServer), new(stepSuppressMessages), new(stepCreateVM), new(stepCreateDisk), diff --git a/builder/virtualbox/builder_test.go b/builder/virtualbox/builder_test.go index 5de684be9..99b140cfd 100644 --- a/builder/virtualbox/builder_test.go +++ b/builder/virtualbox/builder_test.go @@ -74,6 +74,34 @@ func TestBuilderPrepare_BootWait(t *testing.T) { } } +func TestBuilderPrepare_HTTPPort(t *testing.T) { + var b Builder + config := testConfig() + + // Bad + config["http_port_min"] = 1000 + config["http_port_max"] = 500 + err := b.Prepare(config) + if err == nil { + t.Fatal("should have error") + } + + // Bad + config["http_port_min"] = -500 + err = b.Prepare(config) + if err == nil { + t.Fatal("should have error") + } + + // Good + config["http_port_min"] = 500 + config["http_port_max"] = 1000 + err = b.Prepare(config) + if err != nil { + t.Fatalf("should not have error: %s", err) + } +} + func TestBuilderPrepare_ISOMD5(t *testing.T) { var b Builder config := testConfig() diff --git a/builder/virtualbox/step_http_server.go b/builder/virtualbox/step_http_server.go new file mode 100644 index 000000000..40d8b49bc --- /dev/null +++ b/builder/virtualbox/step_http_server.go @@ -0,0 +1,68 @@ +package virtualbox + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" + "math/rand" + "net" + "net/http" +) + +// This step creates and runs the HTTP server that is serving the files +// specified by the 'http_files` configuration parameter in the template. +// +// Uses: +// config *config +// ui packer.Ui +// +// Produces: +// http_port int - The port the HTTP server started on. +type stepHTTPServer struct { + l net.Listener +} + +func (s *stepHTTPServer) Run(state map[string]interface{}) multistep.StepAction { + config := state["config"].(*config) + ui := state["ui"].(packer.Ui) + + var httpPort uint = 0 + if config.HTTPDir == "" { + state["http_port"] = httpPort + return multistep.ActionContinue + } + + // Find an available TCP port for our HTTP server + var httpAddr string + portRange := int(config.HTTPPortMax - config.HTTPPortMin) + for { + var err error + httpPort = uint(rand.Intn(portRange)) + config.HTTPPortMin + httpAddr = fmt.Sprintf(":%d", httpPort) + log.Printf("Trying port: %d", httpPort) + s.l, err = net.Listen("tcp", httpAddr) + if err == nil { + break + } + } + + ui.Say(fmt.Sprintf("Starting HTTP server on port %d", httpPort)) + + // Start the HTTP server and run it in the background + fileServer := http.FileServer(http.Dir(config.HTTPDir)) + server := &http.Server{Addr: httpAddr, Handler: fileServer} + go server.Serve(s.l) + + // Save the address into the state so it can be accessed in the future + state["http_port"] = httpPort + + return multistep.ActionContinue +} + +func (s *stepHTTPServer) Cleanup(map[string]interface{}) { + if s.l != nil { + // Close the listener so that the HTTP server stops + s.l.Close() + } +}