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:
parent
32b480a263
commit
08f5131746
|
@ -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 {
|
||||||
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) {
|
||||||
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) {
|
||||||
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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
},
|
},
|
||||||
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 (
|
||||||
"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 {
|
||||||
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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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:
|
||||||
* `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:
|
||||||
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.
|
||||||
|
|
Loading…
Reference in New Issue