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.

This commit is contained in:
Thomas D. Hite 2013-12-06 18:20:25 -06:00
parent 32b480a263
commit 08f5131746
3 changed files with 126 additions and 24 deletions

View File

@ -58,6 +58,7 @@ type config struct {
BootCommand []string `mapstructure:"boot_command"`
DiskInterface string `mapstructure:"disk_interface"`
DiskSize uint `mapstructure:"disk_size"`
FloppyFiles []string `mapstructure:"floppy_files"`
Format string `mapstructure:"format"`
Headless bool `mapstructure:"headless"`
HTTPDir string `mapstructure:"http_directory"`
@ -79,6 +80,7 @@ type config struct {
VNCPortMin uint `mapstructure:"vnc_port_min"`
VNCPortMax uint `mapstructure:"vnc_port_max"`
VMName string `mapstructure:"vm_name"`
RunOnce bool `mapstructure:"run_once"`
RawBootWait string `mapstructure:"boot_wait"`
RawSingleISOUrl string `mapstructure:"iso_url"`
@ -150,8 +152,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.VNCPortMax = 6000
}
if b.config.QemuArgs == nil {
b.config.QemuArgs = make([][]string, 0)
for i, args := range b.config.QemuArgs {
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 == "" {
@ -162,6 +169,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.Format = "qcow2"
}
if b.config.FloppyFiles == nil {
b.config.FloppyFiles = make([]string, 0)
}
if b.config.NetDevice == "" {
b.config.NetDevice = "virtio-net"
}
@ -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") {
errs = packer.MultiErrorAppend(
errs, errors.New("invalid format, only 'qcow2' or 'img' are allowed"))
@ -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"))
}
for i, args := range b.config.QemuArgs {
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.QemuArgs == nil {
b.config.QemuArgs = make([][]string, 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
Url: b.config.ISOUrls,
},
new(stepPrepareOutputDir),
&common.StepCreateFloppy{
Files: b.config.FloppyFiles,
},
new(stepCreateDisk),
new(stepHTTPServer),
new(stepForwardSSH),
@ -376,6 +395,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
BootDrive: "d",
Message: "Starting VM, booting from CD-ROM",
},
}
if !b.config.RunOnce {
steps = append(steps,
&stepBootWait{},
&stepTypeBootCommand{},
&stepWaitForShutdown{
@ -385,6 +408,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
BootDrive: "c",
Message: "Starting VM, booting from hard disk",
},
)
}
steps = append(steps,
&common.StepConnectSSH{
SSHAddress: sshAddress,
SSHConfig: sshConfig,
@ -392,7 +419,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
},
new(common.StepProvision),
new(stepShutdown),
}
)
// Setup the state bag
state := new(multistep.BasicStateBag)

View File

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"path/filepath"
"strings"
)
@ -14,13 +15,27 @@ type stepRun struct {
Message string
}
type qemuArgsTemplateData struct {
HTTPIP string
HTTPPort uint
HTTPDir string
OutputDir string
Name string
}
func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
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 {
err := fmt.Errorf("Error launching VM: %s", err)
ui.Error(err.Error())
@ -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)
isoPath := state.Get("iso_path").(string)
vncPort := state.Get("vnc_port").(uint)
@ -72,14 +87,34 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) []string {
defaultArgs["-redir"] = fmt.Sprintf("tcp:%v::22", sshHostPort)
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)
if len(config.QemuArgs) > 0 {
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
// will have an array of string values
for _, qemuArgs := range config.QemuArgs {
for _, qemuArgs := range newQemuArgs {
key := qemuArgs[0]
val := strings.Join(qemuArgs[1:], "")
if _, ok := inArgs[key]; !ok {
@ -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
}

View File

@ -118,6 +118,14 @@ Optional:
* `format` (string) - Either "qcow2" or "img", this specifies the output
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
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.
@ -191,6 +199,16 @@ Optional:
By default this is "output-BUILDNAME" where "BUILDNAME" is the name
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
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.