Commit 08f51317 authored by Thomas D. Hite's avatar Thomas D. Hite

Added template processing for QemuArgs, floppy_files for auto-building...

Added template processing for QemuArgs, floppy_files for auto-building floppies which attach to the VM at startup, and run_once flag to trigger just one powerup/powerdown.
parent 32b480a2
...@@ -58,6 +58,7 @@ type config struct { ...@@ -58,6 +58,7 @@ type config struct {
BootCommand []string `mapstructure:"boot_command"` BootCommand []string `mapstructure:"boot_command"`
DiskInterface string `mapstructure:"disk_interface"` DiskInterface string `mapstructure:"disk_interface"`
DiskSize uint `mapstructure:"disk_size"` DiskSize uint `mapstructure:"disk_size"`
FloppyFiles []string `mapstructure:"floppy_files"`
Format string `mapstructure:"format"` Format string `mapstructure:"format"`
Headless bool `mapstructure:"headless"` Headless bool `mapstructure:"headless"`
HTTPDir string `mapstructure:"http_directory"` HTTPDir string `mapstructure:"http_directory"`
...@@ -79,6 +80,7 @@ type config struct { ...@@ -79,6 +80,7 @@ type config struct {
VNCPortMin uint `mapstructure:"vnc_port_min"` VNCPortMin uint `mapstructure:"vnc_port_min"`
VNCPortMax uint `mapstructure:"vnc_port_max"` VNCPortMax uint `mapstructure:"vnc_port_max"`
VMName string `mapstructure:"vm_name"` VMName string `mapstructure:"vm_name"`
RunOnce bool `mapstructure:"run_once"`
RawBootWait string `mapstructure:"boot_wait"` RawBootWait string `mapstructure:"boot_wait"`
RawSingleISOUrl string `mapstructure:"iso_url"` RawSingleISOUrl string `mapstructure:"iso_url"`
...@@ -150,8 +152,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { ...@@ -150,8 +152,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.VNCPortMax = 6000 b.config.VNCPortMax = 6000
} }
if b.config.QemuArgs == nil { for i, args := range b.config.QemuArgs {
b.config.QemuArgs = make([][]string, 0) for j, arg := range args {
if err := b.config.tpl.Validate(arg); err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing qemu-system_x86-64[%d][%d]: %s", i, j, err))
}
}
} }
if b.config.VMName == "" { if b.config.VMName == "" {
...@@ -162,6 +169,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { ...@@ -162,6 +169,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.Format = "qcow2" b.config.Format = "qcow2"
} }
if b.config.FloppyFiles == nil {
b.config.FloppyFiles = make([]string, 0)
}
if b.config.NetDevice == "" { if b.config.NetDevice == "" {
b.config.NetDevice = "virtio-net" b.config.NetDevice = "virtio-net"
} }
...@@ -215,6 +226,16 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { ...@@ -215,6 +226,16 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
} }
} }
for i, file := range b.config.FloppyFiles {
var err error
b.config.FloppyFiles[i], err = b.config.tpl.Process(file, nil)
if err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing floppy_files[%d]: %s",
i, err))
}
}
if !(b.config.Format == "qcow2" || b.config.Format == "raw") { if !(b.config.Format == "qcow2" || b.config.Format == "raw") {
errs = packer.MultiErrorAppend( errs = packer.MultiErrorAppend(
errs, errors.New("invalid format, only 'qcow2' or 'img' are allowed")) errs, errors.New("invalid format, only 'qcow2' or 'img' are allowed"))
...@@ -336,13 +357,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { ...@@ -336,13 +357,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max")) errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max"))
} }
for i, args := range b.config.QemuArgs { if b.config.QemuArgs == nil {
for j, arg := range args { b.config.QemuArgs = make([][]string, 0)
if err := b.config.tpl.Validate(arg); err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing qemu-system_x86-64[%d][%d]: %s", i, j, err))
}
}
} }
if errs != nil && len(errs.Errors) > 0 { if errs != nil && len(errs.Errors) > 0 {
...@@ -368,6 +384,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -368,6 +384,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Url: b.config.ISOUrls, Url: b.config.ISOUrls,
}, },
new(stepPrepareOutputDir), new(stepPrepareOutputDir),
&common.StepCreateFloppy{
Files: b.config.FloppyFiles,
},
new(stepCreateDisk), new(stepCreateDisk),
new(stepHTTPServer), new(stepHTTPServer),
new(stepForwardSSH), new(stepForwardSSH),
...@@ -376,6 +395,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -376,6 +395,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
BootDrive: "d", BootDrive: "d",
Message: "Starting VM, booting from CD-ROM", Message: "Starting VM, booting from CD-ROM",
}, },
}
if !b.config.RunOnce {
steps = append(steps,
&stepBootWait{}, &stepBootWait{},
&stepTypeBootCommand{}, &stepTypeBootCommand{},
&stepWaitForShutdown{ &stepWaitForShutdown{
...@@ -385,6 +408,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -385,6 +408,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
BootDrive: "c", BootDrive: "c",
Message: "Starting VM, booting from hard disk", Message: "Starting VM, booting from hard disk",
}, },
)
}
steps = append(steps,
&common.StepConnectSSH{ &common.StepConnectSSH{
SSHAddress: sshAddress, SSHAddress: sshAddress,
SSHConfig: sshConfig, SSHConfig: sshConfig,
...@@ -392,7 +419,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -392,7 +419,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}, },
new(common.StepProvision), new(common.StepProvision),
new(stepShutdown), new(stepShutdown),
} )
// Setup the state bag // Setup the state bag
state := new(multistep.BasicStateBag) state := new(multistep.BasicStateBag)
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"log"
"path/filepath" "path/filepath"
"strings" "strings"
) )
...@@ -14,13 +15,27 @@ type stepRun struct { ...@@ -14,13 +15,27 @@ type stepRun struct {
Message string Message string
} }
type qemuArgsTemplateData struct {
HTTPIP string
HTTPPort uint
HTTPDir string
OutputDir string
Name string
}
func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction { func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver) driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
ui.Say(s.Message) ui.Say(s.Message)
command := getCommandArgs(s.BootDrive, state) command, err := getCommandArgs(s.BootDrive, state)
if err != nil {
err := fmt.Errorf("Error processing QemuArggs: %s", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if err := driver.Qemu(command...); err != nil { if err := driver.Qemu(command...); err != nil {
err := fmt.Errorf("Error launching VM: %s", err) err := fmt.Errorf("Error launching VM: %s", err)
ui.Error(err.Error()) ui.Error(err.Error())
...@@ -39,7 +54,7 @@ func (s *stepRun) Cleanup(state multistep.StateBag) { ...@@ -39,7 +54,7 @@ func (s *stepRun) Cleanup(state multistep.StateBag) {
} }
} }
func getCommandArgs(bootDrive string, state multistep.StateBag) []string { func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error) {
config := state.Get("config").(*config) config := state.Get("config").(*config)
isoPath := state.Get("iso_path").(string) isoPath := state.Get("iso_path").(string)
vncPort := state.Get("vnc_port").(uint) vncPort := state.Get("vnc_port").(uint)
...@@ -72,14 +87,34 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) []string { ...@@ -72,14 +87,34 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) []string {
defaultArgs["-redir"] = fmt.Sprintf("tcp:%v::22", sshHostPort) defaultArgs["-redir"] = fmt.Sprintf("tcp:%v::22", sshHostPort)
defaultArgs["-vnc"] = vnc defaultArgs["-vnc"] = vnc
// Determine if we have a floppy disk to attach
if floppyPathRaw, ok := state.GetOk("floppy_path"); ok {
defaultArgs["-fda"] = floppyPathRaw.(string)
} else {
log.Println("Qemu Builder has no floppy files, not attaching a floppy.")
}
inArgs := make(map[string][]string) inArgs := make(map[string][]string)
if len(config.QemuArgs) > 0 { if len(config.QemuArgs) > 0 {
ui.Say("Overriding defaults Qemu arguments with QemuArgs...") ui.Say("Overriding defaults Qemu arguments with QemuArgs...")
// becuase qemu supports multiple appearances of the same httpPort := state.Get("http_port").(uint)
tplData := qemuArgsTemplateData{
"10.0.2.2",
httpPort,
config.HTTPDir,
config.OutputDir,
config.VMName,
}
newQemuArgs, err := processArgs(config.QemuArgs, config.tpl, &tplData)
if err != nil {
return nil, err
}
// because qemu supports multiple appearances of the same
// switch, just different values, each key in the args hash // switch, just different values, each key in the args hash
// will have an array of string values // will have an array of string values
for _, qemuArgs := range config.QemuArgs { for _, qemuArgs := range newQemuArgs {
key := qemuArgs[0] key := qemuArgs[0]
val := strings.Join(qemuArgs[1:], "") val := strings.Join(qemuArgs[1:], "")
if _, ok := inArgs[key]; !ok { if _, ok := inArgs[key]; !ok {
...@@ -112,5 +147,27 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) []string { ...@@ -112,5 +147,27 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) []string {
} }
} }
return outArgs return outArgs, nil
}
func processArgs(args [][]string, tpl *packer.ConfigTemplate, tplData *qemuArgsTemplateData) ([][]string, error) {
var err error
if args == nil {
return make([][]string, 0), err
}
newArgs := make([][]string, len(args))
for argsIdx, rowArgs := range args {
parms := make([]string, len(rowArgs))
newArgs[argsIdx] = parms
for i, parm := range rowArgs {
parms[i], err = tpl.Process(parm, &tplData)
if err != nil {
return nil, err
}
}
}
return newArgs, err
} }
...@@ -118,6 +118,14 @@ Optional: ...@@ -118,6 +118,14 @@ Optional:
* `format` (string) - Either "qcow2" or "img", this specifies the output * `format` (string) - Either "qcow2" or "img", this specifies the output
format of the virtual machine image. This defaults to "qcow2". format of the virtual machine image. This defaults to "qcow2".
* `floppy_files` (array of strings) - A list of files to place onto a floppy
disk that gets attached when Packer powers up the VM. This is most useful
for unattended Windows installs, which look for an `Autounattend.xml` file
on removable media. By default no floppy will be attached. All files
listed in this setting get placed into the root directory of the floppy
and teh floppy is attached as the first floppy device. Currently, no
support exists for sub-directories.
* `headless` (bool) - Packer defaults to building virtual machines by * `headless` (bool) - Packer defaults to building virtual machines by
launching a GUI that shows the console of the machine being built. launching a GUI that shows the console of the machine being built.
When this value is set to true, the machine will start without a console. When this value is set to true, the machine will start without a console.
...@@ -191,6 +199,16 @@ Optional: ...@@ -191,6 +199,16 @@ Optional:
By default this is "output-BUILDNAME" where "BUILDNAME" is the name By default this is "output-BUILDNAME" where "BUILDNAME" is the name
of the build. of the build.
* `run_once` (boolean) - When set to true, run_once causes the builder to run
Qemu only once, rather than twice. Normally (default false) the builder
will run Qemu once for an initial OS install, then switch the CDROM device
and boot device for a second run. In that case, Packer does not wait for
SSH connections until the second power up of the VM. This approach is often
necessary in Linux distribution installs. However, in many Windows unattended
installs, the setup handles reboots and dealing with the CDROM as the boot
device. With care, a power-down/power-up setting (run_once is set to false)
could work if the unattended install is set to restart into audit mode.
* `shutdown_command` (string) - The command to use to gracefully shut down * `shutdown_command` (string) - The command to use to gracefully shut down
the machine once all the provisioning is done. By default this is an empty the machine once all the provisioning is done. By default this is an empty
string, which tells Packer to just forcefully shut down the machine. string, which tells Packer to just forcefully shut down the machine.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment