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

View File

@ -1,4 +1,4 @@
package xenserver
package common
import (
"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 (
"fmt"

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package xenserver
package common
import (
"fmt"
@ -6,11 +6,11 @@ import (
"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)
config := state.Get("config").(config)
config := state.Get("commonconfig").(CommonConfig)
ui := state.Get("ui").(packer.Ui)
instance, _ := client.GetVMByUuid(state.Get("instance_uuid").(string))
@ -28,4 +28,4 @@ func (self *stepBootWait) Run(state multistep.StateBag) multistep.StepAction {
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 (
"fmt"
@ -7,11 +7,11 @@ import (
"log"
)
type stepDetachVdi struct {
type StepDetachVdi struct {
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)
client := state.Get("client").(XenAPIClient)
@ -47,4 +47,4 @@ func (self *stepDetachVdi) Run(state multistep.StateBag) multistep.StepAction {
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 (
"fmt"
@ -6,13 +6,13 @@ import (
"github.com/mitchellh/packer/packer"
)
type stepFindVdi struct {
type StepFindVdi struct {
VdiName string
ImagePathFunc func() 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)
client := state.Get("client").(XenAPIClient)
@ -39,4 +39,4 @@ func (self *stepFindVdi) Run(state multistep.StateBag) multistep.StepAction {
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 (
"fmt"
@ -6,7 +6,7 @@ import (
"github.com/mitchellh/packer/packer"
)
type stepForwardPortOverSSH struct {
type StepForwardPortOverSSH struct {
RemotePort func(state multistep.StateBag) (uint, error)
RemoteDest func(state multistep.StateBag) (string, error)
@ -16,9 +16,9 @@ type stepForwardPortOverSSH struct {
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)
// Find a free local port:
@ -46,4 +46,4 @@ func (self *stepForwardPortOverSSH) Run(state multistep.StateBag) multistep.Step
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 (
"fmt"
@ -7,11 +7,11 @@ import (
"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.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
}
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)
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
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
@ -20,12 +20,12 @@ import (
//
// Produces:
// http_port int - The port the HTTP server started on.
type stepHTTPServer struct {
type StepHTTPServer struct {
l net.Listener
}
func (s *stepHTTPServer) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(config)
func (s *StepHTTPServer) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("commonconfig").(CommonConfig)
ui := state.Get("ui").(packer.Ui)
var httpPort uint = 0
@ -54,7 +54,7 @@ func (s *stepHTTPServer) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (s *stepHTTPServer) Cleanup(multistep.StateBag) {
func (s *StepHTTPServer) Cleanup(multistep.StateBag) {
if s.l != nil {
// Close the listener so that the HTTP server stops
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 */
@ -10,18 +10,20 @@ import (
"time"
)
type stepPrepareOutputDir struct{}
type StepPrepareOutputDir struct {
Force bool
Path string
}
func (stepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(config)
func (self *StepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
if _, err := os.Stat(config.OutputDir); err == nil && config.PackerForce {
if _, err := os.Stat(self.Path); err == nil && self.Force {
ui.Say("Deleting previous output directory...")
os.RemoveAll(config.OutputDir)
os.RemoveAll(self.Path)
}
if err := os.MkdirAll(config.OutputDir, 0755); err != nil {
if err := os.MkdirAll(self.Path, 0755); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
@ -29,17 +31,16 @@ func (stepPrepareOutputDir) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
func (stepPrepareOutputDir) Cleanup(state multistep.StateBag) {
func (self *StepPrepareOutputDir) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if cancelled || halted {
config := state.Get("config").(config)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting output directory...")
for i := 0; i < 5; i++ {
err := os.RemoveAll(config.OutputDir)
err := os.RemoveAll(self.Path)
if err == nil {
break
}

View File

@ -1,4 +1,4 @@
package xenserver
package common
import (
"fmt"
@ -6,9 +6,9 @@ import (
"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)
client := state.Get("client").(XenAPIClient)
@ -37,4 +37,4 @@ func (self *stepRemoveDevices) Run(state multistep.StateBag) multistep.StepActio
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 */
@ -13,7 +13,7 @@ import (
"time"
)
type stepShutdownAndExport struct{}
type StepShutdownAndExport struct{}
func downloadFile(url, filename string) (err error) {
@ -44,8 +44,8 @@ func downloadFile(url, filename string) (err error) {
return nil
}
func (stepShutdownAndExport) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(config)
func (StepShutdownAndExport) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("commonconfig").(CommonConfig)
ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(XenAPIClient)
instance_uuid := state.Get("instance_uuid").(string)
@ -56,7 +56,7 @@ func (stepShutdownAndExport) Run(state multistep.StateBag) multistep.StepAction
return multistep.ActionHalt
}
ui.Say("Step: Shutdown and export VPX")
ui.Say("Step: Shutdown and export")
// Shutdown the VM
success := func() bool {
@ -109,7 +109,7 @@ func (stepShutdownAndExport) Run(state multistep.StateBag) multistep.StepAction
ui.Say("Successfully shut down VM")
switch config.ExportFormat {
switch config.Format {
case "xva":
// export the VM
@ -164,7 +164,7 @@ func (stepShutdownAndExport) Run(state multistep.StateBag) multistep.StepAction
}
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)
@ -172,5 +172,4 @@ func (stepShutdownAndExport) Run(state multistep.StateBag) multistep.StepAction
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 (
gossh "code.google.com/p/go.crypto/ssh"
@ -9,7 +9,7 @@ import (
"time"
)
type stepStartOnHIMN struct{}
type StepStartOnHIMN struct{}
/*
* 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)
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")
@ -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)
return ip, nil
}
func himnSSHPort(state multistep.StateBag) (uint, error) {
func HimnSSHPort(state multistep.StateBag) (uint, error) {
return 22, nil
}

View File

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

View File

@ -1,4 +1,4 @@
package xenserver
package common
/* Heavily borrowed from builder/quemu/step_type_boot_command.go */
@ -23,10 +23,12 @@ type bootCommandTemplateData struct {
HTTPPort uint
}
type stepTypeBootCommand struct{}
type StepTypeBootCommand struct {
Tpl *packer.ConfigTemplate
}
func (self *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(config)
func (self *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("commonconfig").(CommonConfig)
ui := state.Get("ui").(packer.Ui)
vnc_port := state.Get("local_vnc_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...")
for _, command := range config.BootCommand {
command, err := config.tpl.Process(command, tplData)
command, err := self.Tpl.Process(command, tplData)
if err != nil {
err := fmt.Errorf("Error preparing boot command: %s", err)
state.Put("error", err)
@ -89,7 +91,7 @@ func (self *stepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAct
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.
func vncSendString(c *vnc.ClientConn, original string) {

View File

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

View File

@ -1,19 +1,21 @@
package xenserver
package iso
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 *VM
vdi *VDI
instance *xscommon.VM
vdi *xscommon.VDI
}
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)
ui := state.Get("ui").(packer.Ui)
@ -75,7 +77,7 @@ func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepActi
}
self.vdi = vdi
err = instance.ConnectVdi(vdi, Disk)
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
@ -83,11 +85,11 @@ func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepActi
// Connect Network
var network *Network
var network *xscommon.Network
if config.NetworkName == "" {
// No network has be specified. Use the management interface
network = new(Network)
network = new(xscommon.Network)
network.Ref = ""
network.Client = &client
@ -162,7 +164,7 @@ func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepActi
func (self *stepCreateInstance) Cleanup(state multistep.StateBag) {
config := state.Get("config").(config)
if config.ShouldKeepInstance(state) {
if config.ShouldKeepVM(state) {
return
}

View File

@ -1,11 +1,13 @@
package xenserver
package iso
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"time"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
xscommon "github.com/rdobson/packer-builder-xenserver/builder/xenserver/common"
)
type stepWait struct{}
@ -13,7 +15,7 @@ type stepWait struct{}
func (self *stepWait) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(config)
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.")
@ -26,7 +28,7 @@ func (self *stepWait) Run(state multistep.StateBag) multistep.StepAction {
}
//Expect install to be configured to shutdown on completion
err = InterruptibleWait{
err = xscommon.InterruptibleWait{
Predicate: func() (bool, error) {
log.Printf("Waiting for install to complete.")
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 (
"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() {
@ -10,6 +10,6 @@ func main() {
if err != nil {
panic(err)
}
server.RegisterBuilder(new(xenserver.Builder))
server.RegisterBuilder(new(iso.Builder))
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()
}