diff --git a/builder/virtualbox/common/run_config.go b/builder/virtualbox/common/run_config.go index 755d0f1c1..b024189a6 100644 --- a/builder/virtualbox/common/run_config.go +++ b/builder/virtualbox/common/run_config.go @@ -1,15 +1,21 @@ package common import ( + "errors" "fmt" - "github.com/mitchellh/packer/packer" "time" + + "github.com/mitchellh/packer/packer" ) type RunConfig struct { Headless bool `mapstructure:"headless"` RawBootWait string `mapstructure:"boot_wait"` + HTTPDir string `mapstructure:"http_directory"` + HTTPPortMin uint `mapstructure:"http_port_min"` + HTTPPortMax uint `mapstructure:"http_port_max"` + BootWait time.Duration `` } @@ -18,8 +24,17 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { c.RawBootWait = "10s" } + if c.HTTPPortMin == 0 { + c.HTTPPortMin = 8000 + } + + if c.HTTPPortMax == 0 { + c.HTTPPortMax = 9000 + } + templates := map[string]*string{ - "boot_wait": &c.RawBootWait, + "boot_wait": &c.RawBootWait, + "http_directory": &c.HTTPDir, } errs := make([]error, 0) @@ -37,5 +52,10 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { errs = append(errs, fmt.Errorf("Failed parsing boot_wait: %s", err)) } + if c.HTTPPortMin > c.HTTPPortMax { + errs = append(errs, + errors.New("http_port_min must be less than http_port_max")) + } + return errs } diff --git a/builder/virtualbox/common/step_http_server.go b/builder/virtualbox/common/step_http_server.go new file mode 100644 index 000000000..440d9adbc --- /dev/null +++ b/builder/virtualbox/common/step_http_server.go @@ -0,0 +1,78 @@ +package common + +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 files from the +// directory specified by the 'http_directory` configuration parameter in the +// template. +// +// Uses: +// ui packer.Ui +// +// Produces: +// http_port int - The port the HTTP server started on. +type StepHTTPServer struct { + HTTPDir string + HTTPPortMin uint + HTTPPortMax uint + + l net.Listener +} + +func (s *StepHTTPServer) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + var httpPort uint = 0 + if s.HTTPDir == "" { + state.Put("http_port", httpPort) + return multistep.ActionContinue + } + + // Find an available TCP port for our HTTP server + var httpAddr string + portRange := int(s.HTTPPortMax - s.HTTPPortMin) + for { + var err error + var offset uint = 0 + + if portRange > 0 { + // Intn will panic if portRange == 0, so we do a check. + offset = uint(rand.Intn(portRange)) + } + + httpPort = offset + s.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(s.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.Put("http_port", httpPort) + + return multistep.ActionContinue +} + +func (s *StepHTTPServer) Cleanup(multistep.StateBag) { + if s.l != nil { + // Close the listener so that the HTTP server stops + s.l.Close() + } +} diff --git a/builder/virtualbox/iso/builder.go b/builder/virtualbox/iso/builder.go index cbfac90cb..c4c51c892 100644 --- a/builder/virtualbox/iso/builder.go +++ b/builder/virtualbox/iso/builder.go @@ -42,9 +42,6 @@ type config struct { GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"` GuestOSType string `mapstructure:"guest_os_type"` HardDriveInterface string `mapstructure:"hard_drive_interface"` - HTTPDir string `mapstructure:"http_directory"` - HTTPPortMin uint `mapstructure:"http_port_min"` - HTTPPortMax uint `mapstructure:"http_port_max"` ISOChecksum string `mapstructure:"iso_checksum"` ISOChecksumType string `mapstructure:"iso_checksum_type"` ISOInterface string `mapstructure:"iso_interface"` @@ -103,14 +100,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, 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.ISOInterface == "" { b.config.ISOInterface = "ide" } @@ -125,7 +114,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { "guest_additions_sha256": &b.config.GuestAdditionsSHA256, "guest_os_type": &b.config.GuestOSType, "hard_drive_interface": &b.config.HardDriveInterface, - "http_directory": &b.config.HTTPDir, "iso_checksum": &b.config.ISOChecksum, "iso_checksum_type": &b.config.ISOChecksumType, "iso_interface": &b.config.ISOInterface, @@ -175,11 +163,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs, errors.New("hard_drive_interface can only be ide or sata")) } - if b.config.HTTPPortMin > b.config.HTTPPortMax { - errs = packer.MultiErrorAppend( - errs, errors.New("http_port_min must be less than http_port_max")) - } - if b.config.ISOChecksumType == "" { errs = packer.MultiErrorAppend( errs, errors.New("The iso_checksum_type must be specified.")) @@ -298,7 +281,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &common.StepCreateFloppy{ Files: b.config.FloppyFiles, }, - new(stepHTTPServer), + &vboxcommon.StepHTTPServer{ + HTTPDir: b.config.HTTPDir, + HTTPPortMin: b.config.HTTPPortMin, + HTTPPortMax: b.config.HTTPPortMax, + }, new(vboxcommon.StepSuppressMessages), new(stepCreateVM), new(stepCreateDisk), diff --git a/builder/virtualbox/ovf/builder.go b/builder/virtualbox/ovf/builder.go index eeacdf1e8..9e022b398 100644 --- a/builder/virtualbox/ovf/builder.go +++ b/builder/virtualbox/ovf/builder.go @@ -61,6 +61,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &common.StepCreateFloppy{ Files: b.config.FloppyFiles, }, + &vboxcommon.StepHTTPServer{ + HTTPDir: b.config.HTTPDir, + HTTPPortMin: b.config.HTTPPortMin, + HTTPPortMax: b.config.HTTPPortMax, + }, &vboxcommon.StepDownloadGuestAdditions{ GuestAdditionsMode: b.config.GuestAdditionsMode, GuestAdditionsURL: b.config.GuestAdditionsURL, diff --git a/website/source/docs/builders/virtualbox-ovf.html.markdown b/website/source/docs/builders/virtualbox-ovf.html.markdown index 90acd7489..6db0a536e 100644 --- a/website/source/docs/builders/virtualbox-ovf.html.markdown +++ b/website/source/docs/builders/virtualbox-ovf.html.markdown @@ -109,6 +109,21 @@ each category, the available options are alphabetized and described. machine being built. When this value is set to true, the machine will start without a console. +* `http_directory` (string) - Path to a directory to serve using an HTTP + server. The files in this directory will be available over HTTP that will + be requestable from the virtual machine. This is useful for hosting + kickstart files and so on. By default this is "", which means no HTTP + server will be started. The address and port of the HTTP server will be + available as variables in `boot_command`. This is covered in more detail + below. + +* `http_port_min` and `http_port_max` (integer) - These are the minimum and + maximum port to use for the HTTP server started to serve the `http_directory`. + Because Packer often runs in parallel, Packer will choose a randomly available + port in this range to run the HTTP server. If you want to force the HTTP + server to be on one port, make this minimum and maximum port the same. + By default the values are 8000 and 9000, respectively. + * `import_flags` (array of strings) - Additional flags to pass to `VBoxManage import`. This can be used to add additional command-line flags such as `--eula-accept` to accept a EULA in the OVF.