Initial work on splitting ISO and XVA builders

Compiles -- but xva create_instance is incomplete

Several changes were rolled into this commit, including:

- "export_format" is now "format", and "keep_instance" is "keep_vm"
- gox is used for building (with the intention of allowing
  cross-compilation in the future)
This commit is contained in:
Cheng Sun 2014-12-29 12:46:54 +00:00
parent 4c4ba3026b
commit 152bdee7f2
31 changed files with 1469 additions and 640 deletions

View File

@ -1 +1,8 @@
go build -o $PACKERPATH/packer-builder-xenserver main.go XC_OS=${XC_OS:-$(go env GOOS)}
XC_ARCH=${XC_ARCH:-$(go env GOARCH)}
gox \
-os="${XC_OS}" \
-arch="${XC_ARCH}" \
-output "build/{{.OS}}_{{.Arch}}/packer-{{.Dir}}" \
./...

View File

@ -1,528 +0,0 @@
package xenserver
import (
"errors"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common"
commonssh "github.com/mitchellh/packer/common/ssh"
"github.com/mitchellh/packer/packer"
"log"
"os"
"path"
"strings"
"time"
)
// Set the unique ID for this builder
const BuilderId = "packer.xenserver"
type config struct {
common.PackerConfig `mapstructure:",squash"`
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
HostIp string `mapstructure:"host_ip"`
VMName string `mapstructure:"vm_name"`
VMMemory uint `mapstructure:"vm_memory"`
DiskSize uint `mapstructure:"disk_size"`
CloneTemplate string `mapstructure:"clone_template"`
SrName string `mapstructure:"sr_name"`
FloppyFiles []string `mapstructure:"floppy_files"`
NetworkName string `mapstructure:"network_name"`
HostPortMin uint `mapstructure:"host_port_min"`
HostPortMax uint `mapstructure:"host_port_max"`
BootCommand []string `mapstructure:"boot_command"`
ShutdownCommand string `mapstructure:"shutdown_command"`
RawBootWait string `mapstructure:"boot_wait"`
BootWait time.Duration ``
ISOChecksum string `mapstructure:"iso_checksum"`
ISOChecksumType string `mapstructure:"iso_checksum_type"`
ISOUrls []string `mapstructure:"iso_urls"`
ISOUrl string `mapstructure:"iso_url"`
ToolsIsoName string `mapstructure:"tools_iso_name"`
HTTPDir string `mapstructure:"http_directory"`
HTTPPortMin uint `mapstructure:"http_port_min"`
HTTPPortMax uint `mapstructure:"http_port_max"`
LocalIp string `mapstructure:"local_ip"`
PlatformArgs map[string]string `mapstructure:"platform_args"`
RawInstallTimeout string `mapstructure:"install_timeout"`
InstallTimeout time.Duration ``
RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"`
SSHWaitTimeout time.Duration ``
SSHPassword string `mapstructure:"ssh_password"`
SSHUser string `mapstructure:"ssh_username"`
SSHKeyPath string `mapstructure:"ssh_key_path"`
OutputDir string `mapstructure:"output_directory"`
ExportFormat string `mapstructure:"export_format"`
KeepInstance string `mapstructure:"keep_instance"`
tpl *packer.ConfigTemplate
}
type Builder struct {
config config
runner multistep.Runner
}
func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error) {
md, err := common.DecodeConfig(&self.config, raws...)
if err != nil {
return nil, err
}
errs := common.CheckUnusedConfig(md)
if errs == nil {
errs = &packer.MultiError{}
}
self.config.tpl, err = packer.NewConfigTemplate()
if err != nil {
return nil, err
}
self.config.tpl.UserVars = self.config.PackerUserVars
// Set default values
if self.config.HostPortMin == 0 {
self.config.HostPortMin = 5900
}
if self.config.HostPortMax == 0 {
self.config.HostPortMax = 6000
}
if self.config.RawBootWait == "" {
self.config.RawBootWait = "5s"
}
if self.config.RawInstallTimeout == "" {
self.config.RawInstallTimeout = "200m"
}
if self.config.ToolsIsoName == "" {
self.config.ToolsIsoName = "xs-tools.iso"
}
if self.config.HTTPPortMin == 0 {
self.config.HTTPPortMin = 8000
}
if self.config.HTTPPortMax == 0 {
self.config.HTTPPortMax = 9000
}
if self.config.RawSSHWaitTimeout == "" {
self.config.RawSSHWaitTimeout = "200m"
}
if self.config.DiskSize == 0 {
self.config.DiskSize = 40000
}
if self.config.VMMemory == 0 {
self.config.VMMemory = 1024
}
if self.config.CloneTemplate == "" {
self.config.CloneTemplate = "Other install media"
}
if self.config.FloppyFiles == nil {
self.config.FloppyFiles = make([]string, 0)
}
if self.config.OutputDir == "" {
self.config.OutputDir = fmt.Sprintf("output-%s", self.config.PackerBuildName)
}
if self.config.ExportFormat == "" {
self.config.ExportFormat = "xva"
}
if self.config.KeepInstance == "" {
self.config.KeepInstance = "never"
}
if len(self.config.PlatformArgs) == 0 {
pargs := make(map[string]string)
pargs["viridian"] = "false"
pargs["nx"] = "true"
pargs["pae"] = "true"
pargs["apic"] = "true"
pargs["timeoffset"] = "0"
pargs["acpi"] = "1"
self.config.PlatformArgs = pargs
}
// Template substitution
templates := map[string]*string{
"username": &self.config.Username,
"password": &self.config.Password,
"host_ip": &self.config.HostIp,
"vm_name": &self.config.VMName,
"clone_template": &self.config.CloneTemplate,
"sr_name": &self.config.SrName,
"network_name": &self.config.NetworkName,
"shutdown_command": &self.config.ShutdownCommand,
"boot_wait": &self.config.RawBootWait,
"iso_checksum": &self.config.ISOChecksum,
"iso_checksum_type": &self.config.ISOChecksumType,
"iso_url": &self.config.ISOUrl,
"tools_iso_name": &self.config.ToolsIsoName,
"http_directory": &self.config.HTTPDir,
"local_ip": &self.config.LocalIp,
"install_timeout": &self.config.RawInstallTimeout,
"ssh_wait_timeout": &self.config.RawSSHWaitTimeout,
"ssh_username": &self.config.SSHUser,
"ssh_password": &self.config.SSHPassword,
"ssh_key_path": &self.config.SSHKeyPath,
"output_directory": &self.config.OutputDir,
"export_format": &self.config.ExportFormat,
"keep_instance": &self.config.KeepInstance,
}
for i := range self.config.FloppyFiles {
templates[fmt.Sprintf("floppy_files[%d]", i)] = &self.config.FloppyFiles[i]
}
for i := range self.config.ISOUrls {
templates[fmt.Sprintf("iso_urls[%d]", i)] = &self.config.ISOUrls[i]
}
for n, ptr := range templates {
var err error
*ptr, err = self.config.tpl.Process(*ptr, nil)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
// Validation
self.config.BootWait, err = time.ParseDuration(self.config.RawBootWait)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed to parse boot_wait: %s", err))
}
self.config.SSHWaitTimeout, err = time.ParseDuration(self.config.RawSSHWaitTimeout)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed to parse ssh_wait_timeout: %s", err))
}
self.config.InstallTimeout, err = time.ParseDuration(self.config.RawInstallTimeout)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed to parse install_timeout: %s", err))
}
for i, command := range self.config.BootCommand {
if err := self.config.tpl.Validate(command); err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Error processing boot_command[%d]: %s", i, err))
}
}
if self.config.SSHUser == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("An ssh_username must be specified."))
}
if self.config.SSHKeyPath != "" {
if _, err := os.Stat(self.config.SSHKeyPath); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
} else if _, err := commonssh.FileSigner(self.config.SSHKeyPath); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
}
}
if self.config.Username == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("A username for the xenserver host must be specified."))
}
if self.config.Password == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("A password for the xenserver host must be specified."))
}
if self.config.HostIp == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("An ip for the xenserver host must be specified."))
}
if self.config.VMName == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("vm_name must be specified."))
}
switch self.config.ExportFormat {
case "xva", "vdi_raw":
default:
errs = packer.MultiErrorAppend(
errs, errors.New("export_format must be one of 'xva', 'vdi_raw'"))
}
switch self.config.KeepInstance {
case "always", "never", "on_success":
default:
errs = packer.MultiErrorAppend(
errs, errors.New("keep_instance must be one of 'always', 'never', 'on_success'"))
}
/*
if self.config.LocalIp == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("A local IP visible to XenServer's mangement interface is required to serve files."))
}
*/
if self.config.HTTPPortMin > self.config.HTTPPortMax {
errs = packer.MultiErrorAppend(
errs, errors.New("the HTTP min port must be less than the max"))
}
if self.config.HostPortMin > self.config.HostPortMax {
errs = packer.MultiErrorAppend(
errs, errors.New("the host min port must be less than the max"))
}
if self.config.ISOChecksumType == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("The iso_checksum_type must be specified."))
} else {
self.config.ISOChecksumType = strings.ToLower(self.config.ISOChecksumType)
if self.config.ISOChecksumType != "none" {
if self.config.ISOChecksum == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("Due to the file size being large, an iso_checksum is required."))
} else {
self.config.ISOChecksum = strings.ToLower(self.config.ISOChecksum)
}
if hash := common.HashForType(self.config.ISOChecksumType); hash == nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Unsupported checksum type: %s", self.config.ISOChecksumType))
}
}
}
if len(self.config.ISOUrls) == 0 {
if self.config.ISOUrl == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("One of iso_url or iso_urls must be specified."))
} else {
self.config.ISOUrls = []string{self.config.ISOUrl}
}
} else if self.config.ISOUrl != "" {
errs = packer.MultiErrorAppend(
errs, errors.New("Only one of iso_url or iso_urls may be specified."))
}
for i, url := range self.config.ISOUrls {
self.config.ISOUrls[i], err = common.DownloadableURL(url)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed to parse iso_urls[%d]: %s", i, err))
}
}
if len(errs.Errors) > 0 {
retErr = errors.New(errs.Error())
}
return nil, retErr
}
func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
//Setup XAPI client
client := NewXenAPIClient(self.config.HostIp, self.config.Username, self.config.Password)
err := client.Login()
if err != nil {
return nil, err.(error)
}
ui.Say("XAPI client session established")
client.GetHosts()
//Share state between the other steps using a statebag
state := new(multistep.BasicStateBag)
state.Put("cache", cache)
state.Put("client", client)
state.Put("config", self.config)
state.Put("hook", hook)
state.Put("ui", ui)
//Build the steps
steps := []multistep.Step{
&common.StepDownload{
Checksum: self.config.ISOChecksum,
ChecksumType: self.config.ISOChecksumType,
Description: "ISO",
ResultKey: "iso_path",
Url: self.config.ISOUrls,
},
new(stepPrepareOutputDir),
&common.StepCreateFloppy{
Files: self.config.FloppyFiles,
},
new(stepHTTPServer),
&stepUploadVdi{
VdiName: "Packer-floppy-disk",
ImagePathFunc: func() string {
if floppyPath, ok := state.GetOk("floppy_path"); ok {
return floppyPath.(string)
}
return ""
},
VdiUuidKey: "floppy_vdi_uuid",
},
&stepUploadVdi{
VdiName: path.Base(self.config.ISOUrls[0]),
ImagePathFunc: func() string {
return state.Get("iso_path").(string)
},
VdiUuidKey: "iso_vdi_uuid",
},
&stepFindVdi{
VdiName: self.config.ToolsIsoName,
VdiUuidKey: "tools_vdi_uuid",
},
new(stepCreateInstance),
&stepAttachVdi{
VdiUuidKey: "floppy_vdi_uuid",
VdiType: Floppy,
},
&stepAttachVdi{
VdiUuidKey: "iso_vdi_uuid",
VdiType: CD,
},
&stepAttachVdi{
VdiUuidKey: "tools_vdi_uuid",
VdiType: CD,
},
new(stepStartVmPaused),
new(stepGetVNCPort),
&stepForwardPortOverSSH{
RemotePort: instanceVNCPort,
RemoteDest: instanceVNCIP,
HostPortMin: self.config.HostPortMin,
HostPortMax: self.config.HostPortMax,
ResultKey: "local_vnc_port",
},
new(stepBootWait),
new(stepTypeBootCommand),
new(stepWait),
&stepDetachVdi{
VdiUuidKey: "floppy_vdi_uuid",
},
&stepDetachVdi{
VdiUuidKey: "iso_vdi_uuid",
},
new(stepRemoveDevices),
new(stepStartOnHIMN),
&stepForwardPortOverSSH{
RemotePort: himnSSHPort,
RemoteDest: himnSSHIP,
HostPortMin: self.config.HostPortMin,
HostPortMax: self.config.HostPortMax,
ResultKey: "local_ssh_port",
},
&common.StepConnectSSH{
SSHAddress: sshLocalAddress,
SSHConfig: sshConfig,
SSHWaitTimeout: self.config.SSHWaitTimeout,
},
new(common.StepProvision),
new(stepShutdownAndExport),
}
self.runner = &multistep.BasicRunner{Steps: steps}
self.runner.Run(state)
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If we were interrupted or cancelled, then just exit.
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("Build was cancelled.")
}
if _, ok := state.GetOk(multistep.StateHalted); ok {
return nil, errors.New("Build was halted.")
}
artifact, _ := NewArtifact(self.config.OutputDir)
return artifact, nil
}
func (self *Builder) Cancel() {
if self.runner != nil {
log.Println("Cancelling the step runner...")
self.runner.Cancel()
}
fmt.Println("Cancelling the builder")
}
// all steps should check config.ShouldKeepInstance first before cleaning up
func (cfg config) ShouldKeepInstance(state multistep.StateBag) bool {
switch cfg.KeepInstance {
case "always":
return true
case "never":
return false
case "on_success":
// only keep instance if build was successful
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
return !(cancelled || halted)
default:
panic(fmt.Sprintf("Unknown keep_instance value '%s'", cfg.KeepInstance))
}
}
func (config config) GetSR(client XenAPIClient) (*SR, error) {
if config.SrName == "" {
// Find the default SR
return client.GetDefaultSR()
} else {
// Use the provided name label to find the SR to use
srs, err := client.GetSRByNameLabel(config.SrName)
if err != nil {
return nil, err
}
switch {
case len(srs) == 0:
return nil, fmt.Errorf("Couldn't find a SR with the specified name-label '%s'", config.SrName)
case len(srs) > 1:
return nil, fmt.Errorf("Found more than one SR with the name '%s'. The name must be unique", config.SrName)
}
return srs[0], nil
}
}

View File

@ -1,4 +1,4 @@
package xenserver package common
import ( import (
"fmt" "fmt"
@ -7,6 +7,9 @@ import (
"path/filepath" "path/filepath"
) )
// This is the common builder ID to all of these artifacts.
const BuilderId = "packer.xenserver"
type LocalArtifact struct { type LocalArtifact struct {
dir string dir string
f []string f []string

View File

@ -1,4 +1,4 @@
package xenserver package common
import ( import (
"errors" "errors"

View File

@ -0,0 +1,278 @@
package common
import (
"errors"
"fmt"
"os"
"time"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common"
commonssh "github.com/mitchellh/packer/common/ssh"
"github.com/mitchellh/packer/packer"
)
type CommonConfig struct {
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
HostIp string `mapstructure:"host_ip"`
VMName string `mapstructure:"vm_name"`
SrName string `mapstructure:"sr_name"`
FloppyFiles []string `mapstructure:"floppy_files"`
NetworkName string `mapstructure:"network_name"`
HostPortMin uint `mapstructure:"host_port_min"`
HostPortMax uint `mapstructure:"host_port_max"`
BootCommand []string `mapstructure:"boot_command"`
ShutdownCommand string `mapstructure:"shutdown_command"`
RawBootWait string `mapstructure:"boot_wait"`
BootWait time.Duration
ToolsIsoName string `mapstructure:"tools_iso_name"`
HTTPDir string `mapstructure:"http_directory"`
HTTPPortMin uint `mapstructure:"http_port_min"`
HTTPPortMax uint `mapstructure:"http_port_max"`
LocalIp string `mapstructure:"local_ip"`
// SSHHostPortMin uint `mapstructure:"ssh_host_port_min"`
// SSHHostPortMax uint `mapstructure:"ssh_host_port_max"`
SSHKeyPath string `mapstructure:"ssh_key_path"`
SSHPassword string `mapstructure:"ssh_password"`
// SSHPort uint `mapstructure:"ssh_port"`
SSHUser string `mapstructure:"ssh_username"`
RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"`
SSHWaitTimeout time.Duration
OutputDir string `mapstructure:"output_directory"`
Format string `mapstructure:"format"`
KeepVM string `mapstructure:"keep_vm"`
}
func (c *CommonConfig) Prepare(t *packer.ConfigTemplate, pc *common.PackerConfig) []error {
var err error
// Set default values
if c.HostPortMin == 0 {
c.HostPortMin = 5900
}
if c.HostPortMax == 0 {
c.HostPortMax = 6000
}
if c.RawBootWait == "" {
c.RawBootWait = "5s"
}
if c.ToolsIsoName == "" {
c.ToolsIsoName = "xs-tools.iso"
}
if c.HTTPPortMin == 0 {
c.HTTPPortMin = 8000
}
if c.HTTPPortMax == 0 {
c.HTTPPortMax = 9000
}
if c.RawSSHWaitTimeout == "" {
c.RawSSHWaitTimeout = "200m"
}
if c.FloppyFiles == nil {
c.FloppyFiles = make([]string, 0)
}
/*
if c.SSHHostPortMin == 0 {
c.SSHHostPortMin = 2222
}
if c.SSHHostPortMax == 0 {
c.SSHHostPortMax = 4444
}
if c.SSHPort == 0 {
c.SSHPort = 22
}
*/
if c.RawSSHWaitTimeout == "" {
c.RawSSHWaitTimeout = "20m"
}
if c.OutputDir == "" {
c.OutputDir = fmt.Sprintf("output-%s", pc.PackerBuildName)
}
if c.Format == "" {
c.Format = "xva"
}
if c.KeepVM == "" {
c.KeepVM = "never"
}
// Template substitution
templates := map[string]*string{
"username": &c.Username,
"password": &c.Password,
"host_ip": &c.HostIp,
"vm_name": &c.VMName,
"sr_name": &c.SrName,
"shutdown_command": &c.ShutdownCommand,
"boot_wait": &c.RawBootWait,
"tools_iso_name": &c.ToolsIsoName,
"http_directory": &c.HTTPDir,
"local_ip": &c.LocalIp,
"ssh_key_path": &c.SSHKeyPath,
"ssh_password": &c.SSHPassword,
"ssh_username": &c.SSHUser,
"ssh_wait_timeout": &c.RawSSHWaitTimeout,
"output_directory": &c.OutputDir,
"format": &c.Format,
"keep_vm": &c.KeepVM,
}
for i := range c.FloppyFiles {
templates[fmt.Sprintf("floppy_files[%d]", i)] = &c.FloppyFiles[i]
}
errs := make([]error, 0)
for n, ptr := range templates {
*ptr, err = t.Process(*ptr, nil)
if err != nil {
errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
// Validation
if c.Username == "" {
errs = append(errs, errors.New("A username for the xenserver host must be specified."))
}
if c.Password == "" {
errs = append(errs, errors.New("A password for the xenserver host must be specified."))
}
if c.HostIp == "" {
errs = append(errs, errors.New("An ip for the xenserver host must be specified."))
}
if c.VMName == "" {
errs = append(errs, errors.New("vm_name must be specified."))
}
if c.HostPortMin > c.HostPortMax {
errs = append(errs, errors.New("the host min port must be less than the max"))
}
if c.HTTPPortMin > c.HTTPPortMax {
errs = append(errs, errors.New("the HTTP min port must be less than the max"))
}
c.BootWait, err = time.ParseDuration(c.RawBootWait)
if err != nil {
errs = append(errs, fmt.Errorf("Failed to parse boot_wait: %s", err))
}
for i, command := range c.BootCommand {
if err := t.Validate(command); err != nil {
errs = append(errs,
fmt.Errorf("Error processing boot_command[%d]: %s", i, err))
}
}
if c.SSHKeyPath != "" {
if _, err := os.Stat(c.SSHKeyPath); err != nil {
errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
} else if _, err := commonssh.FileSigner(c.SSHKeyPath); err != nil {
errs = append(errs, fmt.Errorf("ssh_key_path is invalid: %s", err))
}
}
/*
if c.SSHHostPortMin > c.SSHHostPortMax {
errs = append(errs,
errors.New("ssh_host_port_min must be less than ssh_host_port_max"))
}
*/
if c.SSHUser == "" {
errs = append(errs, errors.New("An ssh_username must be specified."))
}
c.SSHWaitTimeout, err = time.ParseDuration(c.RawSSHWaitTimeout)
if err != nil {
errs = append(errs, fmt.Errorf("Failed to parse ssh_wait_timeout: %s", err))
}
switch c.Format {
case "xva", "vdi_raw":
default:
errs = append(errs, errors.New("format must be one of 'xva', 'vdi_raw'"))
}
switch c.KeepVM {
case "always", "never", "on_success":
default:
errs = append(errs, errors.New("keep_vm must be one of 'always', 'never', 'on_success'"))
}
/*
if c.LocalIp == "" {
errs = append(errs, errors.New("A local IP visible to XenServer's mangement interface is required to serve files."))
}
*/
return errs
}
// steps should check config.ShouldKeepVM first before cleaning up the VM
func (c CommonConfig) ShouldKeepVM(state multistep.StateBag) bool {
switch c.KeepVM {
case "always":
return true
case "never":
return false
case "on_success":
// only keep instance if build was successful
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
return !(cancelled || halted)
default:
panic(fmt.Sprintf("Unknown keep_vm value '%s'", c.KeepVM))
}
}
func (config CommonConfig) GetSR(client XenAPIClient) (*SR, error) {
if config.SrName == "" {
// Find the default SR
return client.GetDefaultSR()
} else {
// Use the provided name label to find the SR to use
srs, err := client.GetSRByNameLabel(config.SrName)
if err != nil {
return nil, err
}
switch {
case len(srs) == 0:
return nil, fmt.Errorf("Couldn't find a SR with the specified name-label '%s'", config.SrName)
case len(srs) > 1:
return nil, fmt.Errorf("Found more than one SR with the name '%s'. The name must be unique", config.SrName)
}
return srs[0], nil
}
}

View File

@ -1,4 +1,4 @@
package xenserver package common
import ( import (
"fmt" "fmt"

View File

@ -1,4 +1,4 @@
package xenserver package common
import ( import (
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"

View File

@ -1,4 +1,4 @@
package xenserver package common
import ( import (
"bytes" "bytes"
@ -13,22 +13,21 @@ import (
"strings" "strings"
) )
func sshAddress(state multistep.StateBag) (string, error) { func SSHAddress(state multistep.StateBag) (string, error) {
sshIP := state.Get("ssh_address").(string) sshIP := state.Get("ssh_address").(string)
sshHostPort := 22 sshHostPort := 22
return fmt.Sprintf("%s:%d", sshIP, sshHostPort), nil return fmt.Sprintf("%s:%d", sshIP, sshHostPort), nil
} }
func sshLocalAddress(state multistep.StateBag) (string, error) { func SSHLocalAddress(state multistep.StateBag) (string, error) {
sshLocalPort := state.Get("local_ssh_port").(uint) sshLocalPort := state.Get("local_ssh_port").(uint)
conn_str := fmt.Sprintf("%s:%d", "127.0.0.1", sshLocalPort) conn_str := fmt.Sprintf("%s:%d", "127.0.0.1", sshLocalPort)
log.Printf("sshLocalAddress: %s", conn_str) log.Printf("sshLocalAddress: %s", conn_str)
return conn_str, nil return conn_str, nil
} }
func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) { func SSHConfig(state multistep.StateBag) (*gossh.ClientConfig, error) {
config := state.Get("config").(config) config := state.Get("commonconfig").(CommonConfig)
auth := []gossh.AuthMethod{ auth := []gossh.AuthMethod{
gossh.Password(config.SSHPassword), gossh.Password(config.SSHPassword),
gossh.KeyboardInteractive( gossh.KeyboardInteractive(

View File

@ -1,4 +1,4 @@
package xenserver package common
import ( import (
"fmt" "fmt"
@ -7,14 +7,14 @@ import (
"log" "log"
) )
type stepAttachVdi struct { type StepAttachVdi struct {
VdiUuidKey string VdiUuidKey string
VdiType VDIType VdiType VDIType
vdi *VDI vdi *VDI
} }
func (self *stepAttachVdi) Run(state multistep.StateBag) multistep.StepAction { func (self *StepAttachVdi) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(XenAPIClient) client := state.Get("client").(XenAPIClient)
@ -51,10 +51,10 @@ func (self *stepAttachVdi) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue return multistep.ActionContinue
} }
func (self *stepAttachVdi) Cleanup(state multistep.StateBag) { func (self *StepAttachVdi) Cleanup(state multistep.StateBag) {
config := state.Get("config").(config) config := state.Get("commonconfig").(CommonConfig)
client := state.Get("client").(XenAPIClient) client := state.Get("client").(XenAPIClient)
if config.ShouldKeepInstance(state) { if config.ShouldKeepVM(state) {
return return
} }

View File

@ -1,4 +1,4 @@
package xenserver package common
import ( import (
"fmt" "fmt"
@ -6,11 +6,11 @@ import (
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
) )
type stepBootWait struct{} type StepBootWait struct{}
func (self *stepBootWait) Run(state multistep.StateBag) multistep.StepAction { func (self *StepBootWait) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(XenAPIClient) client := state.Get("client").(XenAPIClient)
config := state.Get("config").(config) config := state.Get("commonconfig").(CommonConfig)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
instance, _ := client.GetVMByUuid(state.Get("instance_uuid").(string)) instance, _ := client.GetVMByUuid(state.Get("instance_uuid").(string))
@ -28,4 +28,4 @@ func (self *stepBootWait) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue return multistep.ActionContinue
} }
func (self *stepBootWait) Cleanup(state multistep.StateBag) {} func (self *StepBootWait) Cleanup(state multistep.StateBag) {}

View File

@ -1,4 +1,4 @@
package xenserver package common
import ( import (
"fmt" "fmt"
@ -7,11 +7,11 @@ import (
"log" "log"
) )
type stepDetachVdi struct { type StepDetachVdi struct {
VdiUuidKey string VdiUuidKey string
} }
func (self *stepDetachVdi) Run(state multistep.StateBag) multistep.StepAction { func (self *StepDetachVdi) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(XenAPIClient) client := state.Get("client").(XenAPIClient)
@ -47,4 +47,4 @@ func (self *stepDetachVdi) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue return multistep.ActionContinue
} }
func (self *stepDetachVdi) Cleanup(state multistep.StateBag) {} func (self *StepDetachVdi) Cleanup(state multistep.StateBag) {}

View File

@ -1,4 +1,4 @@
package xenserver package common
import ( import (
"fmt" "fmt"
@ -6,13 +6,13 @@ import (
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
) )
type stepFindVdi struct { type StepFindVdi struct {
VdiName string VdiName string
ImagePathFunc func() string ImagePathFunc func() string
VdiUuidKey string VdiUuidKey string
} }
func (self *stepFindVdi) Run(state multistep.StateBag) multistep.StepAction { func (self *StepFindVdi) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(XenAPIClient) client := state.Get("client").(XenAPIClient)
@ -39,4 +39,4 @@ func (self *stepFindVdi) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue return multistep.ActionContinue
} }
func (self *stepFindVdi) Cleanup(state multistep.StateBag) {} func (self *StepFindVdi) Cleanup(state multistep.StateBag) {}

View File

@ -1,4 +1,4 @@
package xenserver package common
import ( import (
"fmt" "fmt"
@ -6,7 +6,7 @@ import (
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
) )
type stepForwardPortOverSSH struct { type StepForwardPortOverSSH struct {
RemotePort func(state multistep.StateBag) (uint, error) RemotePort func(state multistep.StateBag) (uint, error)
RemoteDest func(state multistep.StateBag) (string, error) RemoteDest func(state multistep.StateBag) (string, error)
@ -16,9 +16,9 @@ type stepForwardPortOverSSH struct {
ResultKey string ResultKey string
} }
func (self *stepForwardPortOverSSH) Run(state multistep.StateBag) multistep.StepAction { func (self *StepForwardPortOverSSH) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(config) config := state.Get("commonconfig").(CommonConfig)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
// Find a free local port: // Find a free local port:
@ -46,4 +46,4 @@ func (self *stepForwardPortOverSSH) Run(state multistep.StateBag) multistep.Step
return multistep.ActionContinue return multistep.ActionContinue
} }
func (self *stepForwardPortOverSSH) Cleanup(state multistep.StateBag) {} func (self *StepForwardPortOverSSH) Cleanup(state multistep.StateBag) {}

View File

@ -1,4 +1,4 @@
package xenserver package common
import ( import (
"fmt" "fmt"
@ -7,11 +7,11 @@ import (
"strconv" "strconv"
) )
type stepGetVNCPort struct{} type StepGetVNCPort struct{}
func (self *stepGetVNCPort) Run(state multistep.StateBag) multistep.StepAction { func (self *StepGetVNCPort) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(config) config := state.Get("commonconfig").(CommonConfig)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
ui.Say("Step: forward the instances VNC port over SSH") ui.Say("Step: forward the instances VNC port over SSH")
@ -38,15 +38,15 @@ func (self *stepGetVNCPort) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue return multistep.ActionContinue
} }
func (self *stepGetVNCPort) Cleanup(state multistep.StateBag) { func (self *StepGetVNCPort) Cleanup(state multistep.StateBag) {
} }
func instanceVNCPort(state multistep.StateBag) (uint, error) { func InstanceVNCPort(state multistep.StateBag) (uint, error) {
vncPort := state.Get("instance_vnc_port").(uint) vncPort := state.Get("instance_vnc_port").(uint)
return vncPort, nil return vncPort, nil
} }
func instanceVNCIP(state multistep.StateBag) (string, error) { func InstanceVNCIP(state multistep.StateBag) (string, error) {
// The port is in Dom0, so we want to forward from localhost // The port is in Dom0, so we want to forward from localhost
return "127.0.0.1", nil return "127.0.0.1", nil
} }

View File

@ -1,4 +1,4 @@
package xenserver package common
// Taken from mitchellh/packer/builder/qemu/step_http_server.go // Taken from mitchellh/packer/builder/qemu/step_http_server.go
@ -20,12 +20,12 @@ import (
// //
// Produces: // Produces:
// http_port int - The port the HTTP server started on. // http_port int - The port the HTTP server started on.
type stepHTTPServer struct { type StepHTTPServer struct {
l net.Listener l net.Listener
} }
func (s *stepHTTPServer) Run(state multistep.StateBag) multistep.StepAction { func (s *StepHTTPServer) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(config) config := state.Get("commonconfig").(CommonConfig)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
var httpPort uint = 0 var httpPort uint = 0
@ -54,7 +54,7 @@ func (s *stepHTTPServer) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue return multistep.ActionContinue
} }
func (s *stepHTTPServer) Cleanup(multistep.StateBag) { func (s *StepHTTPServer) Cleanup(multistep.StateBag) {
if s.l != nil { if s.l != nil {
// Close the listener so that the HTTP server stops // Close the listener so that the HTTP server stops
s.l.Close() s.l.Close()

View File

@ -1,4 +1,4 @@
package xenserver package common
/* Taken from https://raw.githubusercontent.com/mitchellh/packer/master/builder/qemu/step_prepare_output_dir.go */ /* Taken from https://raw.githubusercontent.com/mitchellh/packer/master/builder/qemu/step_prepare_output_dir.go */
@ -10,18 +10,20 @@ import (
"time" "time"
) )
type stepPrepareOutputDir struct{} type StepPrepareOutputDir struct {
Force bool
func (stepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepAction { Path string
config := state.Get("config").(config)
ui := state.Get("ui").(packer.Ui)
if _, err := os.Stat(config.OutputDir); err == nil && config.PackerForce {
ui.Say("Deleting previous output directory...")
os.RemoveAll(config.OutputDir)
} }
if err := os.MkdirAll(config.OutputDir, 0755); err != nil { func (self *StepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
if _, err := os.Stat(self.Path); err == nil && self.Force {
ui.Say("Deleting previous output directory...")
os.RemoveAll(self.Path)
}
if err := os.MkdirAll(self.Path, 0755); err != nil {
state.Put("error", err) state.Put("error", err)
return multistep.ActionHalt return multistep.ActionHalt
} }
@ -29,17 +31,16 @@ func (stepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue return multistep.ActionContinue
} }
func (stepPrepareOutputDir) Cleanup(state multistep.StateBag) { func (self *StepPrepareOutputDir) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled) _, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted) _, halted := state.GetOk(multistep.StateHalted)
if cancelled || halted { if cancelled || halted {
config := state.Get("config").(config)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting output directory...") ui.Say("Deleting output directory...")
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
err := os.RemoveAll(config.OutputDir) err := os.RemoveAll(self.Path)
if err == nil { if err == nil {
break break
} }

View File

@ -1,4 +1,4 @@
package xenserver package common
import ( import (
"fmt" "fmt"
@ -6,9 +6,9 @@ import (
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
) )
type stepRemoveDevices struct{} type StepRemoveDevices struct{}
func (self *stepRemoveDevices) Run(state multistep.StateBag) multistep.StepAction { func (self *StepRemoveDevices) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(XenAPIClient) client := state.Get("client").(XenAPIClient)
@ -37,4 +37,4 @@ func (self *stepRemoveDevices) Run(state multistep.StateBag) multistep.StepActio
return multistep.ActionContinue return multistep.ActionContinue
} }
func (self *stepRemoveDevices) Cleanup(state multistep.StateBag) {} func (self *StepRemoveDevices) Cleanup(state multistep.StateBag) {}

View File

@ -1,4 +1,4 @@
package xenserver package common
/* Taken from https://raw.githubusercontent.com/mitchellh/packer/master/builder/qemu/step_prepare_output_dir.go */ /* Taken from https://raw.githubusercontent.com/mitchellh/packer/master/builder/qemu/step_prepare_output_dir.go */
@ -13,7 +13,7 @@ import (
"time" "time"
) )
type stepShutdownAndExport struct{} type StepShutdownAndExport struct{}
func downloadFile(url, filename string) (err error) { func downloadFile(url, filename string) (err error) {
@ -44,8 +44,8 @@ func downloadFile(url, filename string) (err error) {
return nil return nil
} }
func (stepShutdownAndExport) Run(state multistep.StateBag) multistep.StepAction { func (StepShutdownAndExport) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(config) config := state.Get("commonconfig").(CommonConfig)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(XenAPIClient) client := state.Get("client").(XenAPIClient)
instance_uuid := state.Get("instance_uuid").(string) instance_uuid := state.Get("instance_uuid").(string)
@ -56,7 +56,7 @@ func (stepShutdownAndExport) Run(state multistep.StateBag) multistep.StepAction
return multistep.ActionHalt return multistep.ActionHalt
} }
ui.Say("Step: Shutdown and export VPX") ui.Say("Step: Shutdown and export")
// Shutdown the VM // Shutdown the VM
success := func() bool { success := func() bool {
@ -109,7 +109,7 @@ func (stepShutdownAndExport) Run(state multistep.StateBag) multistep.StepAction
ui.Say("Successfully shut down VM") ui.Say("Successfully shut down VM")
switch config.ExportFormat { switch config.Format {
case "xva": case "xva":
// export the VM // export the VM
@ -164,7 +164,7 @@ func (stepShutdownAndExport) Run(state multistep.StateBag) multistep.StepAction
} }
default: default:
panic(fmt.Sprintf("Unknown export_format '%s'", config.ExportFormat)) panic(fmt.Sprintf("Unknown export format '%s'", config.Format))
} }
ui.Say("Download completed: " + config.OutputDir) ui.Say("Download completed: " + config.OutputDir)
@ -172,5 +172,4 @@ func (stepShutdownAndExport) Run(state multistep.StateBag) multistep.StepAction
return multistep.ActionContinue return multistep.ActionContinue
} }
func (stepShutdownAndExport) Cleanup(state multistep.StateBag) { func (StepShutdownAndExport) Cleanup(state multistep.StateBag) {}
}

View File

@ -1,4 +1,4 @@
package xenserver package common
import ( import (
gossh "code.google.com/p/go.crypto/ssh" gossh "code.google.com/p/go.crypto/ssh"
@ -9,7 +9,7 @@ import (
"time" "time"
) )
type stepStartOnHIMN struct{} type StepStartOnHIMN struct{}
/* /*
* This step starts the installed guest on the Host Internal Management Network * This step starts the installed guest on the Host Internal Management Network
@ -19,11 +19,11 @@ type stepStartOnHIMN struct{}
* *
*/ */
func (self *stepStartOnHIMN) Run(state multistep.StateBag) multistep.StepAction { func (self *StepStartOnHIMN) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(XenAPIClient) client := state.Get("client").(XenAPIClient)
config := state.Get("config").(config) config := state.Get("commonconfig").(CommonConfig)
ui.Say("Step: Start VM on the Host Internal Mangement Network") ui.Say("Step: Start VM on the Host Internal Mangement Network")
@ -124,13 +124,13 @@ func (self *stepStartOnHIMN) Run(state multistep.StateBag) multistep.StepAction
} }
func (self *stepStartOnHIMN) Cleanup(state multistep.StateBag) {} func (self *StepStartOnHIMN) Cleanup(state multistep.StateBag) {}
func himnSSHIP(state multistep.StateBag) (string, error) { func HimnSSHIP(state multistep.StateBag) (string, error) {
ip := state.Get("himn_ssh_address").(string) ip := state.Get("himn_ssh_address").(string)
return ip, nil return ip, nil
} }
func himnSSHPort(state multistep.StateBag) (uint, error) { func HimnSSHPort(state multistep.StateBag) (uint, error) {
return 22, nil return 22, nil
} }

View File

@ -1,4 +1,4 @@
package xenserver package common
import ( import (
"fmt" "fmt"
@ -7,9 +7,9 @@ import (
"log" "log"
) )
type stepStartVmPaused struct{} type StepStartVmPaused struct{}
func (self *stepStartVmPaused) Run(state multistep.StateBag) multistep.StepAction { func (self *StepStartVmPaused) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(XenAPIClient) client := state.Get("client").(XenAPIClient)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
@ -39,11 +39,11 @@ func (self *stepStartVmPaused) Run(state multistep.StateBag) multistep.StepActio
return multistep.ActionContinue return multistep.ActionContinue
} }
func (self *stepStartVmPaused) Cleanup(state multistep.StateBag) { func (self *StepStartVmPaused) Cleanup(state multistep.StateBag) {
config := state.Get("config").(config) config := state.Get("commonconfig").(CommonConfig)
client := state.Get("client").(XenAPIClient) client := state.Get("client").(XenAPIClient)
if config.ShouldKeepInstance(state) { if config.ShouldKeepVM(state) {
return return
} }

View File

@ -1,4 +1,4 @@
package xenserver package common
/* Heavily borrowed from builder/quemu/step_type_boot_command.go */ /* Heavily borrowed from builder/quemu/step_type_boot_command.go */
@ -23,10 +23,12 @@ type bootCommandTemplateData struct {
HTTPPort uint HTTPPort uint
} }
type stepTypeBootCommand struct{} type StepTypeBootCommand struct {
Tpl *packer.ConfigTemplate
}
func (self *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction { func (self *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(config) config := state.Get("commonconfig").(CommonConfig)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
vnc_port := state.Get("local_vnc_port").(uint) vnc_port := state.Get("local_vnc_port").(uint)
http_port := state.Get("http_port").(uint) http_port := state.Get("http_port").(uint)
@ -68,7 +70,7 @@ func (self *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAct
ui.Say("About to type boot commands over VNC...") ui.Say("About to type boot commands over VNC...")
for _, command := range config.BootCommand { for _, command := range config.BootCommand {
command, err := config.tpl.Process(command, tplData) command, err := self.Tpl.Process(command, tplData)
if err != nil { if err != nil {
err := fmt.Errorf("Error preparing boot command: %s", err) err := fmt.Errorf("Error preparing boot command: %s", err)
state.Put("error", err) state.Put("error", err)
@ -89,7 +91,7 @@ func (self *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAct
return multistep.ActionContinue return multistep.ActionContinue
} }
func (self *stepTypeBootCommand) Cleanup(multistep.StateBag) {} func (self *StepTypeBootCommand) Cleanup(multistep.StateBag) {}
// Taken from qemu's builder plugin - not an exported function. // Taken from qemu's builder plugin - not an exported function.
func vncSendString(c *vnc.ClientConn, original string) { func vncSendString(c *vnc.ClientConn, original string) {

View File

@ -1,4 +1,4 @@
package xenserver package common
import ( import (
"crypto/tls" "crypto/tls"
@ -11,14 +11,14 @@ import (
"time" "time"
) )
type stepUploadVdi struct { type StepUploadVdi struct {
VdiName string VdiName string
ImagePathFunc func() string ImagePathFunc func() string
VdiUuidKey string VdiUuidKey string
} }
func (self *stepUploadVdi) Run(state multistep.StateBag) multistep.StepAction { func (self *StepUploadVdi) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(config) config := state.Get("commonconfig").(CommonConfig)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(XenAPIClient) client := state.Get("client").(XenAPIClient)
@ -154,12 +154,12 @@ func (self *stepUploadVdi) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue return multistep.ActionContinue
} }
func (self *stepUploadVdi) Cleanup(state multistep.StateBag) { func (self *StepUploadVdi) Cleanup(state multistep.StateBag) {
config := state.Get("config").(config) config := state.Get("commonconfig").(CommonConfig)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(XenAPIClient) client := state.Get("client").(XenAPIClient)
if config.ShouldKeepInstance(state) { if config.ShouldKeepVM(state) {
return return
} }

View File

@ -0,0 +1,304 @@
package iso
import (
"errors"
"fmt"
"log"
"path"
"strings"
"time"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
xscommon "github.com/rdobson/packer-builder-xenserver/builder/xenserver/common"
)
type config struct {
common.PackerConfig `mapstructure:",squash"`
xscommon.CommonConfig `mapstructure:",squash"`
VMMemory uint `mapstructure:"vm_memory"`
DiskSize uint `mapstructure:"disk_size"`
CloneTemplate string `mapstructure:"clone_template"`
ISOChecksum string `mapstructure:"iso_checksum"`
ISOChecksumType string `mapstructure:"iso_checksum_type"`
ISOUrls []string `mapstructure:"iso_urls"`
ISOUrl string `mapstructure:"iso_url"`
PlatformArgs map[string]string `mapstructure:"platform_args"`
RawInstallTimeout string `mapstructure:"install_timeout"`
InstallTimeout time.Duration ``
tpl *packer.ConfigTemplate
}
type Builder struct {
config config
runner multistep.Runner
}
func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error) {
md, err := common.DecodeConfig(&self.config, raws...)
if err != nil {
return nil, err
}
self.config.tpl, err = packer.NewConfigTemplate()
if err != nil {
return nil, err
}
self.config.tpl.UserVars = self.config.PackerUserVars
errs := common.CheckUnusedConfig(md)
errs = packer.MultiErrorAppend(
errs, self.config.CommonConfig.Prepare(self.config.tpl, &self.config.PackerConfig)...)
// Set default values
if self.config.RawInstallTimeout == "" {
self.config.RawInstallTimeout = "200m"
}
if self.config.DiskSize == 0 {
self.config.DiskSize = 40000
}
if self.config.VMMemory == 0 {
self.config.VMMemory = 1024
}
if self.config.CloneTemplate == "" {
self.config.CloneTemplate = "Other install media"
}
if len(self.config.PlatformArgs) == 0 {
pargs := make(map[string]string)
pargs["viridian"] = "false"
pargs["nx"] = "true"
pargs["pae"] = "true"
pargs["apic"] = "true"
pargs["timeoffset"] = "0"
pargs["acpi"] = "1"
self.config.PlatformArgs = pargs
}
// Template substitution
templates := map[string]*string{
"clone_template": &self.config.CloneTemplate,
"network_name": &self.config.NetworkName,
"iso_checksum": &self.config.ISOChecksum,
"iso_checksum_type": &self.config.ISOChecksumType,
"iso_url": &self.config.ISOUrl,
"install_timeout": &self.config.RawInstallTimeout,
}
for i := range self.config.ISOUrls {
templates[fmt.Sprintf("iso_urls[%d]", i)] = &self.config.ISOUrls[i]
}
for n, ptr := range templates {
var err error
*ptr, err = self.config.tpl.Process(*ptr, nil)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
// Validation
self.config.InstallTimeout, err = time.ParseDuration(self.config.RawInstallTimeout)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed to parse install_timeout: %s", err))
}
if self.config.ISOChecksumType == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("The iso_checksum_type must be specified."))
} else {
self.config.ISOChecksumType = strings.ToLower(self.config.ISOChecksumType)
if self.config.ISOChecksumType != "none" {
if self.config.ISOChecksum == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("Due to the file size being large, an iso_checksum is required."))
} else {
self.config.ISOChecksum = strings.ToLower(self.config.ISOChecksum)
}
if hash := common.HashForType(self.config.ISOChecksumType); hash == nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Unsupported checksum type: %s", self.config.ISOChecksumType))
}
}
}
if len(self.config.ISOUrls) == 0 {
if self.config.ISOUrl == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("One of iso_url or iso_urls must be specified."))
} else {
self.config.ISOUrls = []string{self.config.ISOUrl}
}
} else if self.config.ISOUrl != "" {
errs = packer.MultiErrorAppend(
errs, errors.New("Only one of iso_url or iso_urls may be specified."))
}
for i, url := range self.config.ISOUrls {
self.config.ISOUrls[i], err = common.DownloadableURL(url)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed to parse iso_urls[%d]: %s", i, err))
}
}
if len(errs.Errors) > 0 {
retErr = errors.New(errs.Error())
}
return nil, retErr
}
func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
//Setup XAPI client
client := xscommon.NewXenAPIClient(self.config.HostIp, self.config.Username, self.config.Password)
err := client.Login()
if err != nil {
return nil, err.(error)
}
ui.Say("XAPI client session established")
client.GetHosts()
//Share state between the other steps using a statebag
state := new(multistep.BasicStateBag)
state.Put("cache", cache)
state.Put("client", client)
state.Put("config", self.config)
state.Put("commonconfig", self.config.CommonConfig)
state.Put("hook", hook)
state.Put("ui", ui)
//Build the steps
steps := []multistep.Step{
&common.StepDownload{
Checksum: self.config.ISOChecksum,
ChecksumType: self.config.ISOChecksumType,
Description: "ISO",
ResultKey: "iso_path",
Url: self.config.ISOUrls,
},
&xscommon.StepPrepareOutputDir{
Force: self.config.PackerForce,
Path: self.config.OutputDir,
},
&common.StepCreateFloppy{
Files: self.config.FloppyFiles,
},
new(xscommon.StepHTTPServer),
&xscommon.StepUploadVdi{
VdiName: "Packer-floppy-disk",
ImagePathFunc: func() string {
if floppyPath, ok := state.GetOk("floppy_path"); ok {
return floppyPath.(string)
}
return ""
},
VdiUuidKey: "floppy_vdi_uuid",
},
&xscommon.StepUploadVdi{
VdiName: path.Base(self.config.ISOUrls[0]),
ImagePathFunc: func() string {
return state.Get("iso_path").(string)
},
VdiUuidKey: "iso_vdi_uuid",
},
&xscommon.StepFindVdi{
VdiName: self.config.ToolsIsoName,
VdiUuidKey: "tools_vdi_uuid",
},
new(stepCreateInstance),
&xscommon.StepAttachVdi{
VdiUuidKey: "floppy_vdi_uuid",
VdiType: xscommon.Floppy,
},
&xscommon.StepAttachVdi{
VdiUuidKey: "iso_vdi_uuid",
VdiType: xscommon.CD,
},
&xscommon.StepAttachVdi{
VdiUuidKey: "tools_vdi_uuid",
VdiType: xscommon.CD,
},
new(xscommon.StepStartVmPaused),
new(xscommon.StepGetVNCPort),
&xscommon.StepForwardPortOverSSH{
RemotePort: xscommon.InstanceVNCPort,
RemoteDest: xscommon.InstanceVNCIP,
HostPortMin: self.config.HostPortMin,
HostPortMax: self.config.HostPortMax,
ResultKey: "local_vnc_port",
},
new(xscommon.StepBootWait),
&xscommon.StepTypeBootCommand{
Tpl: self.config.tpl,
},
new(stepWait),
&xscommon.StepDetachVdi{
VdiUuidKey: "floppy_vdi_uuid",
},
&xscommon.StepDetachVdi{
VdiUuidKey: "iso_vdi_uuid",
},
new(xscommon.StepRemoveDevices),
new(xscommon.StepStartOnHIMN),
&xscommon.StepForwardPortOverSSH{
RemotePort: xscommon.HimnSSHPort,
RemoteDest: xscommon.HimnSSHIP,
HostPortMin: self.config.HostPortMin,
HostPortMax: self.config.HostPortMax,
ResultKey: "local_ssh_port",
},
&common.StepConnectSSH{
SSHAddress: xscommon.SSHLocalAddress,
SSHConfig: xscommon.SSHConfig,
SSHWaitTimeout: self.config.SSHWaitTimeout,
},
new(common.StepProvision),
new(xscommon.StepShutdownAndExport),
}
self.runner = &multistep.BasicRunner{Steps: steps}
self.runner.Run(state)
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If we were interrupted or cancelled, then just exit.
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("Build was cancelled.")
}
if _, ok := state.GetOk(multistep.StateHalted); ok {
return nil, errors.New("Build was halted.")
}
artifact, _ := xscommon.NewArtifact(self.config.OutputDir)
return artifact, nil
}
func (self *Builder) Cancel() {
if self.runner != nil {
log.Println("Cancelling the step runner...")
self.runner.Cancel()
}
fmt.Println("Cancelling the builder")
}

View File

@ -1,4 +1,4 @@
package xenserver package iso
import ( import (
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"

View File

@ -1,19 +1,21 @@
package xenserver package iso
import ( import (
"fmt" "fmt"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
xscommon "github.com/rdobson/packer-builder-xenserver/builder/xenserver/common"
) )
type stepCreateInstance struct { type stepCreateInstance struct {
instance *VM instance *xscommon.VM
vdi *VDI vdi *xscommon.VDI
} }
func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepAction { func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(XenAPIClient) client := state.Get("client").(xscommon.XenAPIClient)
config := state.Get("config").(config) config := state.Get("config").(config)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
@ -75,7 +77,7 @@ func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepActi
} }
self.vdi = vdi self.vdi = vdi
err = instance.ConnectVdi(vdi, Disk) err = instance.ConnectVdi(vdi, xscommon.Disk)
if err != nil { if err != nil {
ui.Error(fmt.Sprintf("Unable to connect packer disk VDI: %s", err.Error())) ui.Error(fmt.Sprintf("Unable to connect packer disk VDI: %s", err.Error()))
return multistep.ActionHalt return multistep.ActionHalt
@ -83,11 +85,11 @@ func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepActi
// Connect Network // Connect Network
var network *Network var network *xscommon.Network
if config.NetworkName == "" { if config.NetworkName == "" {
// No network has be specified. Use the management interface // No network has be specified. Use the management interface
network = new(Network) network = new(xscommon.Network)
network.Ref = "" network.Ref = ""
network.Client = &client network.Client = &client
@ -162,7 +164,7 @@ func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepActi
func (self *stepCreateInstance) Cleanup(state multistep.StateBag) { func (self *stepCreateInstance) Cleanup(state multistep.StateBag) {
config := state.Get("config").(config) config := state.Get("config").(config)
if config.ShouldKeepInstance(state) { if config.ShouldKeepVM(state) {
return return
} }

View File

@ -1,11 +1,13 @@
package xenserver package iso
import ( import (
"fmt" "fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log" "log"
"time" "time"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
xscommon "github.com/rdobson/packer-builder-xenserver/builder/xenserver/common"
) )
type stepWait struct{} type stepWait struct{}
@ -13,7 +15,7 @@ type stepWait struct{}
func (self *stepWait) Run(state multistep.StateBag) multistep.StepAction { func (self *stepWait) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(config) config := state.Get("config").(config)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(XenAPIClient) client := state.Get("client").(xscommon.XenAPIClient)
ui.Say("Step: Wait for install to complete.") ui.Say("Step: Wait for install to complete.")
@ -26,7 +28,7 @@ func (self *stepWait) Run(state multistep.StateBag) multistep.StepAction {
} }
//Expect install to be configured to shutdown on completion //Expect install to be configured to shutdown on completion
err = InterruptibleWait{ err = xscommon.InterruptibleWait{
Predicate: func() (bool, error) { Predicate: func() (bool, error) {
log.Printf("Waiting for install to complete.") log.Printf("Waiting for install to complete.")
power_state, err := instance.GetPowerState() power_state, err := instance.GetPowerState()

View File

@ -0,0 +1,192 @@
package xva
import (
"errors"
"fmt"
"log"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
xscommon "github.com/rdobson/packer-builder-xenserver/builder/xenserver/common"
)
type config struct {
common.PackerConfig `mapstructure:",squash"`
xscommon.CommonConfig `mapstructure:",squash"`
VMMemory uint `mapstructure:"vm_memory"`
CloneTemplate string `mapstructure:"clone_template"`
PlatformArgs map[string]string `mapstructure:"platform_args"`
tpl *packer.ConfigTemplate
}
type Builder struct {
config config
runner multistep.Runner
}
func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error) {
md, err := common.DecodeConfig(&self.config, raws...)
if err != nil {
return nil, err
}
self.config.tpl, err = packer.NewConfigTemplate()
if err != nil {
return nil, err
}
self.config.tpl.UserVars = self.config.PackerUserVars
errs := common.CheckUnusedConfig(md)
errs = packer.MultiErrorAppend(
errs, self.config.CommonConfig.Prepare(self.config.tpl, &self.config.PackerConfig)...)
// Set default values
if self.config.VMMemory == 0 {
self.config.VMMemory = 1024
}
if len(self.config.PlatformArgs) == 0 {
pargs := make(map[string]string)
pargs["viridian"] = "false"
pargs["nx"] = "true"
pargs["pae"] = "true"
pargs["apic"] = "true"
pargs["timeoffset"] = "0"
pargs["acpi"] = "1"
self.config.PlatformArgs = pargs
}
// Template substitution
templates := map[string]*string{
"clone_template": &self.config.CloneTemplate,
"network_name": &self.config.NetworkName,
}
for n, ptr := range templates {
var err error
*ptr, err = self.config.tpl.Process(*ptr, nil)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
// Validation
if len(errs.Errors) > 0 {
retErr = errors.New(errs.Error())
}
return nil, retErr
}
func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
//Setup XAPI client
client := xscommon.NewXenAPIClient(self.config.HostIp, self.config.Username, self.config.Password)
err := client.Login()
if err != nil {
return nil, err.(error)
}
ui.Say("XAPI client session established")
client.GetHosts()
//Share state between the other steps using a statebag
state := new(multistep.BasicStateBag)
state.Put("cache", cache)
state.Put("client", client)
state.Put("config", self.config)
state.Put("hook", hook)
state.Put("ui", ui)
//Build the steps
steps := []multistep.Step{
&xscommon.StepPrepareOutputDir{
Force: self.config.PackerForce,
Path: self.config.OutputDir,
},
&common.StepCreateFloppy{
Files: self.config.FloppyFiles,
},
new(xscommon.StepHTTPServer),
&xscommon.StepUploadVdi{
VdiName: "Packer-floppy-disk",
ImagePathFunc: func() string {
if floppyPath, ok := state.GetOk("floppy_path"); ok {
return floppyPath.(string)
}
return ""
},
VdiUuidKey: "floppy_vdi_uuid",
},
&xscommon.StepFindVdi{
VdiName: self.config.ToolsIsoName,
VdiUuidKey: "tools_vdi_uuid",
},
new(stepCreateInstance),
&xscommon.StepAttachVdi{
VdiUuidKey: "floppy_vdi_uuid",
VdiType: xscommon.Floppy,
},
&xscommon.StepAttachVdi{
VdiUuidKey: "tools_vdi_uuid",
VdiType: xscommon.CD,
},
new(xscommon.StepStartOnHIMN),
&xscommon.StepForwardPortOverSSH{
RemotePort: xscommon.HimnSSHPort,
RemoteDest: xscommon.HimnSSHIP,
HostPortMin: self.config.HostPortMin,
HostPortMax: self.config.HostPortMax,
ResultKey: "local_ssh_port",
},
&common.StepConnectSSH{
SSHAddress: xscommon.SSHLocalAddress,
SSHConfig: xscommon.SSHConfig,
SSHWaitTimeout: self.config.SSHWaitTimeout,
},
new(common.StepProvision),
&xscommon.StepDetachVdi{
VdiUuidKey: "floppy_vdi_uuid",
},
&xscommon.StepDetachVdi{
VdiUuidKey: "iso_vdi_uuid",
},
new(xscommon.StepShutdownAndExport),
}
self.runner = &multistep.BasicRunner{Steps: steps}
self.runner.Run(state)
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If we were interrupted or cancelled, then just exit.
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("Build was cancelled.")
}
if _, ok := state.GetOk(multistep.StateHalted); ok {
return nil, errors.New("Build was halted.")
}
artifact, _ := xscommon.NewArtifact(self.config.OutputDir)
return artifact, nil
}
func (self *Builder) Cancel() {
if self.runner != nil {
log.Println("Cancelling the step runner...")
self.runner.Cancel()
}
fmt.Println("Cancelling the builder")
}

View File

@ -0,0 +1,360 @@
package xva
import (
"github.com/mitchellh/packer/packer"
"reflect"
"testing"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{
"host_ip": "localhost",
"username": "admin",
"password": "admin",
"vm_name": "foo",
"iso_checksum": "foo",
"iso_checksum_type": "md5",
"iso_url": "http://www.google.com/",
"shutdown_command": "yes",
"ssh_username": "foo",
packer.BuildNameConfigKey: "foo",
}
}
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packer.Builder); !ok {
t.Error("Builder must implement builder.")
}
}
func TestBuilderPrepare_Defaults(t *testing.T) {
var b Builder
config := testConfig()
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.ToolsIsoName != "xs-tools.iso" {
t.Errorf("bad tools ISO name: %s", b.config.ToolsIsoName)
}
if b.config.CloneTemplate != "Other install media" {
t.Errorf("bad clone template: %s", b.config.CloneTemplate)
}
if b.config.VMName == "" {
t.Errorf("bad vm name: %s", b.config.VMName)
}
if b.config.ExportFormat != "xva" {
t.Errorf("bad format: %s", b.config.ExportFormat)
}
if b.config.KeepInstance != "never" {
t.Errorf("bad keep instance: %s", b.config.KeepInstance)
}
}
func TestBuilderPrepare_DiskSize(t *testing.T) {
var b Builder
config := testConfig()
delete(config, "disk_size")
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("bad err: %s", err)
}
if b.config.DiskSize != 40000 {
t.Fatalf("bad size: %d", b.config.DiskSize)
}
config["disk_size"] = 60000
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.DiskSize != 60000 {
t.Fatalf("bad size: %s", b.config.DiskSize)
}
}
func TestBuilderPrepare_Format(t *testing.T) {
var b Builder
config := testConfig()
// Bad
config["export_format"] = "foo"
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Good
config["export_format"] = "vdi_raw"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_HTTPPort(t *testing.T) {
var b Builder
config := testConfig()
// Bad
config["http_port_min"] = 1000
config["http_port_max"] = 500
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Bad
config["http_port_min"] = -500
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Good
config["http_port_min"] = 500
config["http_port_max"] = 1000
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_InvalidKey(t *testing.T) {
var b Builder
config := testConfig()
// Add a random key
config["i_should_not_be_valid"] = true
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_ISOChecksum(t *testing.T) {
var b Builder
config := testConfig()
// Test bad
config["iso_checksum"] = ""
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test good
config["iso_checksum"] = "FOo"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.ISOChecksum != "foo" {
t.Fatalf("should've lowercased: %s", b.config.ISOChecksum)
}
}
func TestBuilderPrepare_ISOChecksumType(t *testing.T) {
var b Builder
config := testConfig()
// Test bad
config["iso_checksum_type"] = ""
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test good
config["iso_checksum_type"] = "mD5"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.ISOChecksumType != "md5" {
t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType)
}
// Test unknown
config["iso_checksum_type"] = "fake"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test none
config["iso_checksum_type"] = "none"
b = Builder{}
warns, err = b.Prepare(config)
// @todo: give warning in this case?
/*
if len(warns) == 0 {
t.Fatalf("bad: %#v", warns)
}
*/
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.ISOChecksumType != "none" {
t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType)
}
}
func TestBuilderPrepare_ISOUrl(t *testing.T) {
var b Builder
config := testConfig()
delete(config, "iso_url")
delete(config, "iso_urls")
// Test both epty
config["iso_url"] = ""
b = Builder{}
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test iso_url set
config["iso_url"] = "http://www.packer.io"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Errorf("should not have error: %s", err)
}
expected := []string{"http://www.packer.io"}
if !reflect.DeepEqual(b.config.ISOUrls, expected) {
t.Fatalf("bad: %#v", b.config.ISOUrls)
}
// Test both set
config["iso_url"] = "http://www.packer.io"
config["iso_urls"] = []string{"http://www.packer.io"}
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test just iso_urls set
delete(config, "iso_url")
config["iso_urls"] = []string{
"http://www.packer.io",
"http://www.hashicorp.com",
}
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Errorf("should not have error: %s", err)
}
expected = []string{
"http://www.packer.io",
"http://www.hashicorp.com",
}
if !reflect.DeepEqual(b.config.ISOUrls, expected) {
t.Fatalf("bad: %#v", b.config.ISOUrls)
}
}
func TestBuilderPrepare_KeepInstance(t *testing.T) {
var b Builder
config := testConfig()
// Bad
config["keep_instance"] = "foo"
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Good
config["keep_instance"] = "always"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}

View File

@ -0,0 +1,193 @@
package xva
import (
// "fmt"
"github.com/mitchellh/multistep"
// "github.com/mitchellh/packer/packer"
xscommon "github.com/rdobson/packer-builder-xenserver/builder/xenserver/common"
)
type stepCreateInstance struct {
instance *xscommon.VM
vdi *xscommon.VDI
}
func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepAction {
/*
client := state.Get("client").(xscommon.XenAPIClient)
config := state.Get("config").(config)
ui := state.Get("ui").(packer.Ui)
ui.Say("Step: Create Instance")
// Get the template to clone from
vms, err := client.GetVMByNameLabel(config.CloneTemplate)
switch {
case len(vms) == 0:
ui.Error(fmt.Sprintf("Couldn't find a template with the name-label '%s'. Aborting.", config.CloneTemplate))
return multistep.ActionHalt
case len(vms) > 1:
ui.Error(fmt.Sprintf("Found more than one template with the name '%s'. The name must be unique. Aborting.", config.CloneTemplate))
return multistep.ActionHalt
}
template := vms[0]
// Clone that VM template
instance, err := template.Clone(config.VMName)
if err != nil {
ui.Error(fmt.Sprintf("Error cloning VM: %s", err.Error()))
return multistep.ActionHalt
}
self.instance = instance
err = instance.SetIsATemplate(false)
if err != nil {
ui.Error(fmt.Sprintf("Error setting is_a_template=false: %s", err.Error()))
return multistep.ActionHalt
}
err = instance.SetStaticMemoryRange(config.VMMemory*1024*1024, config.VMMemory*1024*1024)
if err != nil {
ui.Error(fmt.Sprintf("Error setting VM memory=%d: %s", config.VMMemory*1024*1024, err.Error()))
return multistep.ActionHalt
}
instance.SetPlatform(config.PlatformArgs)
if err != nil {
ui.Error(fmt.Sprintf("Error setting VM platform: %s", err.Error()))
return multistep.ActionHalt
}
// Create VDI for the instance
sr, err := config.GetSR(client)
if err != nil {
ui.Error(fmt.Sprintf("Unable to get SR: %s", err.Error()))
return multistep.ActionHalt
}
vdi, err := sr.CreateVdi("Packer-disk", int64(config.DiskSize*1024*1024))
if err != nil {
ui.Error(fmt.Sprintf("Unable to create packer disk VDI: %s", err.Error()))
return multistep.ActionHalt
}
self.vdi = vdi
err = instance.ConnectVdi(vdi, xscommon.Disk)
if err != nil {
ui.Error(fmt.Sprintf("Unable to connect packer disk VDI: %s", err.Error()))
return multistep.ActionHalt
}
// Connect Network
var network *xscommon.Network
if config.NetworkName == "" {
// No network has be specified. Use the management interface
network = new(xscommon.Network)
network.Ref = ""
network.Client = &client
pifs, err := client.GetPIFs()
if err != nil {
ui.Error(fmt.Sprintf("Error getting PIFs: %s", err.Error()))
return multistep.ActionHalt
}
for _, pif := range pifs {
pif_rec, err := pif.GetRecord()
if err != nil {
ui.Error(fmt.Sprintf("Error getting PIF record: %s", err.Error()))
return multistep.ActionHalt
}
if pif_rec["management"].(bool) {
network.Ref = pif_rec["network"].(string)
}
}
if network.Ref == "" {
ui.Error("Error: couldn't find management network. Aborting.")
return multistep.ActionHalt
}
} else {
// Look up the network by it's name label
networks, err := client.GetNetworkByNameLabel(config.NetworkName)
if err != nil {
ui.Error(fmt.Sprintf("Error occured getting Network by name-label: %s", err.Error()))
return multistep.ActionHalt
}
switch {
case len(networks) == 0:
ui.Error(fmt.Sprintf("Couldn't find a network with the specified name-label '%s'. Aborting.", config.NetworkName))
return multistep.ActionHalt
case len(networks) > 1:
ui.Error(fmt.Sprintf("Found more than one network with the name '%s'. The name must be unique. Aborting.", config.NetworkName))
return multistep.ActionHalt
}
network = networks[0]
}
if err != nil {
ui.Say(err.Error())
}
_, err = instance.ConnectNetwork(network, "0")
if err != nil {
ui.Say(err.Error())
}
instanceId, err := instance.GetUuid()
if err != nil {
ui.Error(fmt.Sprintf("Unable to get VM UUID: %s", err.Error()))
return multistep.ActionHalt
}
state.Put("instance_uuid", instanceId)
ui.Say(fmt.Sprintf("Created instance '%s'", instanceId))
*/
return multistep.ActionContinue
}
func (self *stepCreateInstance) Cleanup(state multistep.StateBag) {
/*
config := state.Get("config").(config)
if config.ShouldKeepVM(state) {
return
}
ui := state.Get("ui").(packer.Ui)
if self.instance != nil {
ui.Say("Destroying VM")
_ = self.instance.HardShutdown() // redundant, just in case
err := self.instance.Destroy()
if err != nil {
ui.Error(err.Error())
}
}
if self.vdi != nil {
ui.Say("Destroying VDI")
err := self.vdi.Destroy()
if err != nil {
ui.Error(err.Error())
}
}
*/
}

View File

@ -2,7 +2,7 @@ package main
import ( import (
"github.com/mitchellh/packer/packer/plugin" "github.com/mitchellh/packer/packer/plugin"
"github.com/rdobson/packer-builder-xenserver/builder/xenserver" "github.com/rdobson/packer-builder-xenserver/builder/xenserver/iso"
) )
func main() { func main() {
@ -10,6 +10,6 @@ func main() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
server.RegisterBuilder(new(xenserver.Builder)) server.RegisterBuilder(new(iso.Builder))
server.Serve() server.Serve()
} }

View File

@ -0,0 +1,15 @@
package main
import (
"github.com/mitchellh/packer/packer/plugin"
"github.com/rdobson/packer-builder-xenserver/builder/xenserver/xva"
)
func main() {
server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterBuilder(new(xva.Builder))
server.Serve()
}