From 216eb2aab500d34b9be6736001afb33cd61221b6 Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Mon, 22 Dec 2014 14:16:21 +0000 Subject: [PATCH 01/21] Fix enum types --- builder/xenserver/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/xenserver/client.go b/builder/xenserver/client.go index 9fdffcd23b4..a4111b0a0e3 100644 --- a/builder/xenserver/client.go +++ b/builder/xenserver/client.go @@ -40,7 +40,7 @@ type VDI struct { type VDIType int const ( - _ = iota + _ VDIType = iota Disk CD Floppy @@ -79,7 +79,7 @@ type Task struct { type TaskStatusType int const ( - _ = iota + _ TaskStatusType = iota Pending Success Failure From 4c4ba3026b652eebf9fa51f3ded2b9112d8e5c9c Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Mon, 22 Dec 2014 17:11:43 +0000 Subject: [PATCH 02/21] Add builder config test Borrowed from virtualbox builder test --- builder/xenserver/builder_test.go | 360 ++++++++++++++++++++++++++++++ 1 file changed, 360 insertions(+) create mode 100644 builder/xenserver/builder_test.go diff --git a/builder/xenserver/builder_test.go b/builder/xenserver/builder_test.go new file mode 100644 index 00000000000..a8c5781083c --- /dev/null +++ b/builder/xenserver/builder_test.go @@ -0,0 +1,360 @@ +package xenserver + +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) + } +} From 152bdee7f264486262bd87f92711174283deeafc Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Mon, 29 Dec 2014 12:46:54 +0000 Subject: [PATCH 03/21] 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) --- build.sh | 9 +- builder/xenserver/builder.go | 528 ------------------ builder/xenserver/{ => common}/artifact.go | 5 +- builder/xenserver/{ => common}/client.go | 2 +- builder/xenserver/common/common_config.go | 278 +++++++++ builder/xenserver/{ => common}/find_port.go | 2 +- .../{ => common}/interruptible_wait.go | 2 +- builder/xenserver/{ => common}/ssh.go | 11 +- .../xenserver/{ => common}/step_attach_vdi.go | 12 +- .../xenserver/{ => common}/step_boot_wait.go | 10 +- .../xenserver/{ => common}/step_detach_vdi.go | 8 +- .../xenserver/{ => common}/step_find_vdi.go | 8 +- .../step_forward_port_over_ssh.go | 10 +- .../{ => common}/step_get_vnc_port.go | 14 +- .../{ => common}/step_http_server.go | 10 +- .../{ => common}/step_prepare_output_dir.go | 21 +- .../{ => common}/step_remove_devices.go | 8 +- .../{ => common}/step_shutdown_and_export.go | 17 +- .../{ => common}/step_start_on_himn.go | 14 +- .../{ => common}/step_start_vm_paused.go | 12 +- .../{ => common}/step_type_boot_command.go | 14 +- .../xenserver/{ => common}/step_upload_vdi.go | 14 +- builder/xenserver/iso/builder.go | 304 ++++++++++ builder/xenserver/{ => iso}/builder_test.go | 2 +- .../{ => iso}/step_create_instance.go | 18 +- builder/xenserver/{ => iso}/step_wait.go | 12 +- builder/xenserver/xva/builder.go | 192 +++++++ builder/xenserver/xva/builder_test.go | 360 ++++++++++++ builder/xenserver/xva/step_create_instance.go | 193 +++++++ .../builder-xenserver-iso/main.go | 4 +- plugin/builder-xenserver-xva/main.go | 15 + 31 files changed, 1469 insertions(+), 640 deletions(-) delete mode 100644 builder/xenserver/builder.go rename builder/xenserver/{ => common}/artifact.go (89%) rename builder/xenserver/{ => common}/client.go (99%) create mode 100644 builder/xenserver/common/common_config.go rename builder/xenserver/{ => common}/find_port.go (97%) rename builder/xenserver/{ => common}/interruptible_wait.go (99%) rename builder/xenserver/{ => common}/ssh.go (92%) rename builder/xenserver/{ => common}/step_attach_vdi.go (86%) rename builder/xenserver/{ => common}/step_boot_wait.go (73%) rename builder/xenserver/{ => common}/step_detach_vdi.go (86%) rename builder/xenserver/{ => common}/step_find_vdi.go (83%) rename builder/xenserver/{ => common}/step_forward_port_over_ssh.go (83%) rename builder/xenserver/{ => common}/step_get_vnc_port.go (74%) rename builder/xenserver/{ => common}/step_http_server.go (86%) rename builder/xenserver/{ => common}/step_prepare_output_dir.go (61%) rename builder/xenserver/{ => common}/step_remove_devices.go (81%) rename builder/xenserver/{ => common}/step_shutdown_and_export.go (91%) rename builder/xenserver/{ => common}/step_start_on_himn.go (90%) rename builder/xenserver/{ => common}/step_start_vm_paused.go (83%) rename builder/xenserver/{ => common}/step_type_boot_command.go (93%) rename builder/xenserver/{ => common}/step_upload_vdi.go (93%) create mode 100644 builder/xenserver/iso/builder.go rename builder/xenserver/{ => iso}/builder_test.go (99%) rename builder/xenserver/{ => iso}/step_create_instance.go (92%) rename builder/xenserver/{ => iso}/step_wait.go (86%) create mode 100644 builder/xenserver/xva/builder.go create mode 100644 builder/xenserver/xva/builder_test.go create mode 100644 builder/xenserver/xva/step_create_instance.go rename main.go => plugin/builder-xenserver-iso/main.go (59%) create mode 100644 plugin/builder-xenserver-xva/main.go diff --git a/build.sh b/build.sh index 98a3862b17e..21a27b07789 100755 --- a/build.sh +++ b/build.sh @@ -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}}" \ + ./... diff --git a/builder/xenserver/builder.go b/builder/xenserver/builder.go deleted file mode 100644 index 9574caafc8b..00000000000 --- a/builder/xenserver/builder.go +++ /dev/null @@ -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 - } -} diff --git a/builder/xenserver/artifact.go b/builder/xenserver/common/artifact.go similarity index 89% rename from builder/xenserver/artifact.go rename to builder/xenserver/common/artifact.go index 002b82f5fdb..5e6a7cd00e6 100644 --- a/builder/xenserver/artifact.go +++ b/builder/xenserver/common/artifact.go @@ -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 diff --git a/builder/xenserver/client.go b/builder/xenserver/common/client.go similarity index 99% rename from builder/xenserver/client.go rename to builder/xenserver/common/client.go index a4111b0a0e3..dee96977284 100644 --- a/builder/xenserver/client.go +++ b/builder/xenserver/common/client.go @@ -1,4 +1,4 @@ -package xenserver +package common import ( "errors" diff --git a/builder/xenserver/common/common_config.go b/builder/xenserver/common/common_config.go new file mode 100644 index 00000000000..3a0f772f3a7 --- /dev/null +++ b/builder/xenserver/common/common_config.go @@ -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 + } +} diff --git a/builder/xenserver/find_port.go b/builder/xenserver/common/find_port.go similarity index 97% rename from builder/xenserver/find_port.go rename to builder/xenserver/common/find_port.go index a6a8492f49c..ea75a733870 100644 --- a/builder/xenserver/find_port.go +++ b/builder/xenserver/common/find_port.go @@ -1,4 +1,4 @@ -package xenserver +package common import ( "fmt" diff --git a/builder/xenserver/interruptible_wait.go b/builder/xenserver/common/interruptible_wait.go similarity index 99% rename from builder/xenserver/interruptible_wait.go rename to builder/xenserver/common/interruptible_wait.go index cba8c7a8db6..3fc4103ae29 100644 --- a/builder/xenserver/interruptible_wait.go +++ b/builder/xenserver/common/interruptible_wait.go @@ -1,4 +1,4 @@ -package xenserver +package common import ( "github.com/mitchellh/multistep" diff --git a/builder/xenserver/ssh.go b/builder/xenserver/common/ssh.go similarity index 92% rename from builder/xenserver/ssh.go rename to builder/xenserver/common/ssh.go index 66bf6af2639..d7a9a6bac14 100644 --- a/builder/xenserver/ssh.go +++ b/builder/xenserver/common/ssh.go @@ -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( diff --git a/builder/xenserver/step_attach_vdi.go b/builder/xenserver/common/step_attach_vdi.go similarity index 86% rename from builder/xenserver/step_attach_vdi.go rename to builder/xenserver/common/step_attach_vdi.go index a43e8a78cc3..85bce0d7834 100644 --- a/builder/xenserver/step_attach_vdi.go +++ b/builder/xenserver/common/step_attach_vdi.go @@ -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 } diff --git a/builder/xenserver/step_boot_wait.go b/builder/xenserver/common/step_boot_wait.go similarity index 73% rename from builder/xenserver/step_boot_wait.go rename to builder/xenserver/common/step_boot_wait.go index 49786191e4d..af907e958f8 100644 --- a/builder/xenserver/step_boot_wait.go +++ b/builder/xenserver/common/step_boot_wait.go @@ -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) {} diff --git a/builder/xenserver/step_detach_vdi.go b/builder/xenserver/common/step_detach_vdi.go similarity index 86% rename from builder/xenserver/step_detach_vdi.go rename to builder/xenserver/common/step_detach_vdi.go index 63f9477266e..c7a992d8ce1 100644 --- a/builder/xenserver/step_detach_vdi.go +++ b/builder/xenserver/common/step_detach_vdi.go @@ -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) {} diff --git a/builder/xenserver/step_find_vdi.go b/builder/xenserver/common/step_find_vdi.go similarity index 83% rename from builder/xenserver/step_find_vdi.go rename to builder/xenserver/common/step_find_vdi.go index 7799887c49e..80295bacf2c 100644 --- a/builder/xenserver/step_find_vdi.go +++ b/builder/xenserver/common/step_find_vdi.go @@ -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) {} diff --git a/builder/xenserver/step_forward_port_over_ssh.go b/builder/xenserver/common/step_forward_port_over_ssh.go similarity index 83% rename from builder/xenserver/step_forward_port_over_ssh.go rename to builder/xenserver/common/step_forward_port_over_ssh.go index 4c407c8ea4b..da3078c1880 100644 --- a/builder/xenserver/step_forward_port_over_ssh.go +++ b/builder/xenserver/common/step_forward_port_over_ssh.go @@ -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) {} diff --git a/builder/xenserver/step_get_vnc_port.go b/builder/xenserver/common/step_get_vnc_port.go similarity index 74% rename from builder/xenserver/step_get_vnc_port.go rename to builder/xenserver/common/step_get_vnc_port.go index ff3ef221be9..4dd208c1d51 100644 --- a/builder/xenserver/step_get_vnc_port.go +++ b/builder/xenserver/common/step_get_vnc_port.go @@ -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 } diff --git a/builder/xenserver/step_http_server.go b/builder/xenserver/common/step_http_server.go similarity index 86% rename from builder/xenserver/step_http_server.go rename to builder/xenserver/common/step_http_server.go index f233f6ef1e9..26aa84df78d 100644 --- a/builder/xenserver/step_http_server.go +++ b/builder/xenserver/common/step_http_server.go @@ -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() diff --git a/builder/xenserver/step_prepare_output_dir.go b/builder/xenserver/common/step_prepare_output_dir.go similarity index 61% rename from builder/xenserver/step_prepare_output_dir.go rename to builder/xenserver/common/step_prepare_output_dir.go index 2faaf6dac9d..129c300d2f7 100644 --- a/builder/xenserver/step_prepare_output_dir.go +++ b/builder/xenserver/common/step_prepare_output_dir.go @@ -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 } diff --git a/builder/xenserver/step_remove_devices.go b/builder/xenserver/common/step_remove_devices.go similarity index 81% rename from builder/xenserver/step_remove_devices.go rename to builder/xenserver/common/step_remove_devices.go index cec045edf97..3213a3b5165 100644 --- a/builder/xenserver/step_remove_devices.go +++ b/builder/xenserver/common/step_remove_devices.go @@ -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) {} diff --git a/builder/xenserver/step_shutdown_and_export.go b/builder/xenserver/common/step_shutdown_and_export.go similarity index 91% rename from builder/xenserver/step_shutdown_and_export.go rename to builder/xenserver/common/step_shutdown_and_export.go index 7dcd89ffa4b..c49edfe099c 100644 --- a/builder/xenserver/step_shutdown_and_export.go +++ b/builder/xenserver/common/step_shutdown_and_export.go @@ -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) {} diff --git a/builder/xenserver/step_start_on_himn.go b/builder/xenserver/common/step_start_on_himn.go similarity index 90% rename from builder/xenserver/step_start_on_himn.go rename to builder/xenserver/common/step_start_on_himn.go index 8999cc91d9d..fbc0ae857b0 100644 --- a/builder/xenserver/step_start_on_himn.go +++ b/builder/xenserver/common/step_start_on_himn.go @@ -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 } diff --git a/builder/xenserver/step_start_vm_paused.go b/builder/xenserver/common/step_start_vm_paused.go similarity index 83% rename from builder/xenserver/step_start_vm_paused.go rename to builder/xenserver/common/step_start_vm_paused.go index 31b15d508ed..bb18938f92e 100644 --- a/builder/xenserver/step_start_vm_paused.go +++ b/builder/xenserver/common/step_start_vm_paused.go @@ -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 } diff --git a/builder/xenserver/step_type_boot_command.go b/builder/xenserver/common/step_type_boot_command.go similarity index 93% rename from builder/xenserver/step_type_boot_command.go rename to builder/xenserver/common/step_type_boot_command.go index 28b5f63251c..9edc976860e 100644 --- a/builder/xenserver/step_type_boot_command.go +++ b/builder/xenserver/common/step_type_boot_command.go @@ -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) { diff --git a/builder/xenserver/step_upload_vdi.go b/builder/xenserver/common/step_upload_vdi.go similarity index 93% rename from builder/xenserver/step_upload_vdi.go rename to builder/xenserver/common/step_upload_vdi.go index fe3e57fc1d0..8f452d23e48 100644 --- a/builder/xenserver/step_upload_vdi.go +++ b/builder/xenserver/common/step_upload_vdi.go @@ -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 } diff --git a/builder/xenserver/iso/builder.go b/builder/xenserver/iso/builder.go new file mode 100644 index 00000000000..27577c5c6d5 --- /dev/null +++ b/builder/xenserver/iso/builder.go @@ -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") +} diff --git a/builder/xenserver/builder_test.go b/builder/xenserver/iso/builder_test.go similarity index 99% rename from builder/xenserver/builder_test.go rename to builder/xenserver/iso/builder_test.go index a8c5781083c..960b6e6d546 100644 --- a/builder/xenserver/builder_test.go +++ b/builder/xenserver/iso/builder_test.go @@ -1,4 +1,4 @@ -package xenserver +package iso import ( "github.com/mitchellh/packer/packer" diff --git a/builder/xenserver/step_create_instance.go b/builder/xenserver/iso/step_create_instance.go similarity index 92% rename from builder/xenserver/step_create_instance.go rename to builder/xenserver/iso/step_create_instance.go index 927bca5088d..c35d65e0ff5 100644 --- a/builder/xenserver/step_create_instance.go +++ b/builder/xenserver/iso/step_create_instance.go @@ -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 } diff --git a/builder/xenserver/step_wait.go b/builder/xenserver/iso/step_wait.go similarity index 86% rename from builder/xenserver/step_wait.go rename to builder/xenserver/iso/step_wait.go index 18be2d97de2..831985c7d63 100644 --- a/builder/xenserver/step_wait.go +++ b/builder/xenserver/iso/step_wait.go @@ -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() diff --git a/builder/xenserver/xva/builder.go b/builder/xenserver/xva/builder.go new file mode 100644 index 00000000000..94f256c865c --- /dev/null +++ b/builder/xenserver/xva/builder.go @@ -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") +} diff --git a/builder/xenserver/xva/builder_test.go b/builder/xenserver/xva/builder_test.go new file mode 100644 index 00000000000..5fbef57c109 --- /dev/null +++ b/builder/xenserver/xva/builder_test.go @@ -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) + } +} diff --git a/builder/xenserver/xva/step_create_instance.go b/builder/xenserver/xva/step_create_instance.go new file mode 100644 index 00000000000..986516af4ea --- /dev/null +++ b/builder/xenserver/xva/step_create_instance.go @@ -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()) + } + } + */ +} diff --git a/main.go b/plugin/builder-xenserver-iso/main.go similarity index 59% rename from main.go rename to plugin/builder-xenserver-iso/main.go index 983ed22c24a..186b0bd7d4f 100644 --- a/main.go +++ b/plugin/builder-xenserver-iso/main.go @@ -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() } diff --git a/plugin/builder-xenserver-xva/main.go b/plugin/builder-xenserver-xva/main.go new file mode 100644 index 00000000000..0e4b9077c99 --- /dev/null +++ b/plugin/builder-xenserver-xva/main.go @@ -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() +} From 2ce67b0465946759bca756e17ad156ab3159e712 Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Wed, 31 Dec 2014 14:29:09 +0000 Subject: [PATCH 04/21] Rename logIteration --- builder/xenserver/common/step_upload_vdi.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/builder/xenserver/common/step_upload_vdi.go b/builder/xenserver/common/step_upload_vdi.go index 8f452d23e48..adc38076873 100644 --- a/builder/xenserver/common/step_upload_vdi.go +++ b/builder/xenserver/common/step_upload_vdi.go @@ -105,7 +105,7 @@ func (self *StepUploadVdi) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - logInterval := 0 + logIteration := 0 err = InterruptibleWait{ Predicate: func() (bool, error) { status, err := task.GetStatus() @@ -118,8 +118,8 @@ func (self *StepUploadVdi) Run(state multistep.StateBag) multistep.StepAction { if err != nil { return false, fmt.Errorf("Failed to get progress: %s", err.Error()) } - logInterval = logInterval + 1 - if logInterval%5 == 0 { + logIteration = logIteration + 1 + if logIteration%5 == 0 { log.Printf("Upload %.0f%% complete", progress*100) } return false, nil From 4efd1029a2cd8e0f20d482f6663a846139c7095b Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Wed, 31 Dec 2014 15:21:15 +0000 Subject: [PATCH 05/21] Abstract out http PUT --- builder/xenserver/common/http_upload.go | 115 ++++++++++++++++++++ builder/xenserver/common/step_upload_vdi.go | 85 +-------------- 2 files changed, 119 insertions(+), 81 deletions(-) create mode 100644 builder/xenserver/common/http_upload.go diff --git a/builder/xenserver/common/http_upload.go b/builder/xenserver/common/http_upload.go new file mode 100644 index 00000000000..57443ac377e --- /dev/null +++ b/builder/xenserver/common/http_upload.go @@ -0,0 +1,115 @@ +package common + +import ( + "crypto/tls" + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" + "net/http" + "net/url" + "os" + "time" +) + +func appendQuery(urlstring, k, v string) (string, error) { + u, err := url.Parse(urlstring) + if err != nil { + return "", fmt.Errorf("Unable to parse URL '%s': %s", urlstring, err.Error()) + } + m := u.Query() + m.Add(k, v) + u.RawQuery = m.Encode() + return u.String(), err +} + +func httpUpload(import_url string, fh *os.File, state multistep.StateBag) error { + ui := state.Get("ui").(packer.Ui) + client := state.Get("client").(XenAPIClient) + + task, err := client.CreateTask() + if err != nil { + return fmt.Errorf("Unable to create task: %s", err.Error()) + } + defer task.Destroy() + + import_task_url, err := appendQuery(import_url, "task_id", task.Ref) + if err != nil { + return err + } + + // Get file length + fstat, err := fh.Stat() + if err != nil { + return fmt.Errorf("Unable to stat '%s': %s", fh.Name(), err.Error()) + } + fileLength := fstat.Size() + + // Define a new transport which allows self-signed certs + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + + // Create a client + httpClient := &http.Client{Transport: tr} + + // Create request and download file + request, err := http.NewRequest("PUT", import_task_url, fh) + request.ContentLength = fileLength + + ui.Say(fmt.Sprintf("PUT '%s'", import_task_url)) + + resp, err := httpClient.Do(request) // Do closes fh for us, according to docs + if err != nil { + return err + } + + if resp.StatusCode != 200 { + return fmt.Errorf("PUT request got non-200 status code: %s", resp.Status) + } + + logIteration := 0 + err = InterruptibleWait{ + Predicate: func() (bool, error) { + status, err := task.GetStatus() + if err != nil { + return false, fmt.Errorf("Failed to get task status: %s", err.Error()) + } + switch status { + case Pending: + progress, err := task.GetProgress() + if err != nil { + return false, fmt.Errorf("Failed to get progress: %s", err.Error()) + } + logIteration = logIteration + 1 + if logIteration%5 == 0 { + log.Printf("Upload %.0f%% complete", progress*100) + } + return false, nil + case Success: + return true, nil + case Failure: + errorInfo, err := task.GetErrorInfo() + if err != nil { + errorInfo = []string{fmt.Sprintf("furthermore, failed to get error info: %s", err.Error())} + } + return false, fmt.Errorf("Task failed: %s", errorInfo) + case Cancelling, Cancelled: + return false, fmt.Errorf("Task cancelled") + default: + return false, fmt.Errorf("Unknown task status %v", status) + } + }, + PredicateInterval: 1 * time.Second, + Timeout: 24 * time.Hour, + }.Wait(state) + + resp.Body.Close() + + if err != nil { + return fmt.Errorf("Error uploading: %s", err.Error()) + } + + log.Printf("Upload complete") + return nil +} diff --git a/builder/xenserver/common/step_upload_vdi.go b/builder/xenserver/common/step_upload_vdi.go index adc38076873..6de6d92fb71 100644 --- a/builder/xenserver/common/step_upload_vdi.go +++ b/builder/xenserver/common/step_upload_vdi.go @@ -1,12 +1,10 @@ package common import ( - "crypto/tls" "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "log" - "net/http" "os" "time" ) @@ -37,7 +35,7 @@ func (self *StepUploadVdi) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - // Open the file for reading (NB: putFile closes the file for us) + // Open the file for reading (NB: httpUpload closes the file for us) fh, err := os.Open(imagePath) if err != nil { ui.Error(fmt.Sprintf("Unable to open disk image '%s': %s", imagePath, err.Error())) @@ -66,91 +64,16 @@ func (self *StepUploadVdi) Run(state multistep.StateBag) multistep.StepAction { } state.Put(self.VdiUuidKey, vdiUuid) - task, err := client.CreateTask() - if err != nil { - ui.Error(fmt.Sprintf("Unable to create task: %s", err.Error())) - return multistep.ActionHalt - } - defer task.Destroy() - - import_url := fmt.Sprintf("https://%s/import_raw_vdi?vdi=%s&session_id=%s&task_id=%s", + err = httpUpload(fmt.Sprintf("https://%s/import_raw_vdi?vdi=%s&session_id=%s", client.Host, vdi.Ref, client.Session.(string), - task.Ref, - ) - - // Define a new transport which allows self-signed certs - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - - // Create a client - httpClient := &http.Client{Transport: tr} - - // Create request and download file - request, err := http.NewRequest("PUT", import_url, fh) - request.ContentLength = fileLength - - ui.Say(fmt.Sprintf("PUT disk image '%s'", import_url)) - - resp, err := httpClient.Do(request) // Do closes fh for us, according to docs + ), fh, state) if err != nil { - ui.Error(fmt.Sprintf("Unable to upload disk image: %s", err.Error())) + ui.Error(fmt.Sprintf("Unable to upload VDI: %s", err.Error())) return multistep.ActionHalt } - if resp.StatusCode != 200 { - ui.Error(fmt.Sprintf("Unable to upload disk image: PUT request got non-200 status code: %s", resp.Status)) - return multistep.ActionHalt - } - - logIteration := 0 - err = InterruptibleWait{ - Predicate: func() (bool, error) { - status, err := task.GetStatus() - if err != nil { - return false, fmt.Errorf("Failed to get task status: %s", err.Error()) - } - switch status { - case Pending: - progress, err := task.GetProgress() - if err != nil { - return false, fmt.Errorf("Failed to get progress: %s", err.Error()) - } - logIteration = logIteration + 1 - if logIteration%5 == 0 { - log.Printf("Upload %.0f%% complete", progress*100) - } - return false, nil - case Success: - return true, nil - case Failure: - errorInfo, err := task.GetErrorInfo() - if err != nil { - errorInfo = []string{fmt.Sprintf("furthermore, failed to get error info: %s", err.Error())} - } - return false, fmt.Errorf("Task failed: %s", errorInfo) - case Cancelling, Cancelled: - return false, fmt.Errorf("Task cancelled") - default: - return false, fmt.Errorf("Unknown task status %v", status) - } - }, - PredicateInterval: 1 * time.Second, - Timeout: 24 * time.Hour, - }.Wait(state) - - resp.Body.Close() - - if err != nil { - ui.Error(fmt.Sprintf("Error uploading: %s", err.Error())) - - return multistep.ActionHalt - } - - log.Printf("Upload complete") - return multistep.ActionContinue } From 396a8de131682b7978349955779acbd82b38ff47 Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Wed, 31 Dec 2014 16:29:13 +0000 Subject: [PATCH 06/21] Add task GetResult method --- builder/xenserver/common/client.go | 70 +++++++++++++----------------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/builder/xenserver/common/client.go b/builder/xenserver/common/client.go index dee96977284..3be184e36c2 100644 --- a/builder/xenserver/common/client.go +++ b/builder/xenserver/common/client.go @@ -22,20 +22,20 @@ type APIResult struct { ErrorDescription string } -type VM struct { +type XenAPIObject struct { Ref string Client *XenAPIClient } -type SR struct { - Ref string - Client *XenAPIClient -} - -type VDI struct { - Ref string - Client *XenAPIClient -} +type VM XenAPIObject +type SR XenAPIObject +type VDI XenAPIObject +type Network XenAPIObject +type VBD XenAPIObject +type VIF XenAPIObject +type PIF XenAPIObject +type Pool XenAPIObject +type Task XenAPIObject type VDIType int @@ -46,36 +46,6 @@ const ( Floppy ) -type Network struct { - Ref string - Client *XenAPIClient -} - -type VBD struct { - Ref string - Client *XenAPIClient -} - -type VIF struct { - Ref string - Client *XenAPIClient -} - -type PIF struct { - Ref string - Client *XenAPIClient -} - -type Pool struct { - Ref string - Client *XenAPIClient -} - -type Task struct { - Ref string - Client *XenAPIClient -} - type TaskStatusType int const ( @@ -902,6 +872,26 @@ func (self *Task) GetProgress() (progress float64, err error) { return } +func (self *Task) GetResult() (object *XenAPIObject, err error) { + result := APIResult{} + err = self.Client.APICall(&result, "task.get_result", self.Ref) + if err != nil { + return + } + switch ref := result.Value.(type) { + case string: + object = &XenAPIObject{ + Ref: ref.(string), + Client: self.Client, + } + case nil: + object = nil + default: + err = fmt.Errorf("task.get_result: unknown value type %T (expected string or nil)", ref) + } + return +} + func (self *Task) GetErrorInfo() (errorInfo []string, err error) { result := APIResult{} err = self.Client.APICall(&result, "task.get_error_info", self.Ref) From 462082bee83be29fbf824de19b1316e8070d25c8 Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Wed, 31 Dec 2014 15:21:54 +0000 Subject: [PATCH 07/21] Initial work on XVA import --- builder/xenserver/common/http_upload.go | 26 ++-- builder/xenserver/common/step_detach_vdi.go | 3 +- builder/xenserver/common/step_upload_vdi.go | 4 +- builder/xenserver/xva/builder.go | 10 +- ...te_instance.go => step_import_instance.go} | 120 ++++++++---------- 5 files changed, 82 insertions(+), 81 deletions(-) rename builder/xenserver/xva/{step_create_instance.go => step_import_instance.go} (57%) diff --git a/builder/xenserver/common/http_upload.go b/builder/xenserver/common/http_upload.go index 57443ac377e..4932f96ac86 100644 --- a/builder/xenserver/common/http_upload.go +++ b/builder/xenserver/common/http_upload.go @@ -23,25 +23,27 @@ func appendQuery(urlstring, k, v string) (string, error) { return u.String(), err } -func httpUpload(import_url string, fh *os.File, state multistep.StateBag) error { +func HTTPUpload(import_url string, fh *os.File, state multistep.StateBag) (result *XenAPIObject, err error) { ui := state.Get("ui").(packer.Ui) client := state.Get("client").(XenAPIClient) task, err := client.CreateTask() if err != nil { - return fmt.Errorf("Unable to create task: %s", err.Error()) + err = fmt.Errorf("Unable to create task: %s", err.Error()) + return } defer task.Destroy() import_task_url, err := appendQuery(import_url, "task_id", task.Ref) if err != nil { - return err + return } // Get file length fstat, err := fh.Stat() if err != nil { - return fmt.Errorf("Unable to stat '%s': %s", fh.Name(), err.Error()) + err = fmt.Errorf("Unable to stat '%s': %s", fh.Name(), err.Error()) + return } fileLength := fstat.Size() @@ -61,11 +63,12 @@ func httpUpload(import_url string, fh *os.File, state multistep.StateBag) error resp, err := httpClient.Do(request) // Do closes fh for us, according to docs if err != nil { - return err + return } if resp.StatusCode != 200 { - return fmt.Errorf("PUT request got non-200 status code: %s", resp.Status) + err = fmt.Errorf("PUT request got non-200 status code: %s", resp.Status) + return } logIteration := 0 @@ -107,9 +110,16 @@ func httpUpload(import_url string, fh *os.File, state multistep.StateBag) error resp.Body.Close() if err != nil { - return fmt.Errorf("Error uploading: %s", err.Error()) + err = fmt.Errorf("Error uploading: %s", err.Error()) + return + } + + result, err = task.GetResult() + if err != nil { + err = fmt.Errorf("Error getting result: %s", err.Error()) + return } log.Printf("Upload complete") - return nil + return } diff --git a/builder/xenserver/common/step_detach_vdi.go b/builder/xenserver/common/step_detach_vdi.go index c7a992d8ce1..8445f9d5ade 100644 --- a/builder/xenserver/common/step_detach_vdi.go +++ b/builder/xenserver/common/step_detach_vdi.go @@ -39,7 +39,8 @@ func (self *StepDetachVdi) Run(state multistep.StateBag) multistep.StepAction { err = instance.DisconnectVdi(vdi) if err != nil { ui.Error(fmt.Sprintf("Unable to detach VDI '%s': %s", vdiUuid, err.Error())) - return multistep.ActionHalt + //return multistep.ActionHalt + return multistep.ActionContinue } log.Printf("Detached VDI '%s'", vdiUuid) diff --git a/builder/xenserver/common/step_upload_vdi.go b/builder/xenserver/common/step_upload_vdi.go index 6de6d92fb71..ce3c8d36ac6 100644 --- a/builder/xenserver/common/step_upload_vdi.go +++ b/builder/xenserver/common/step_upload_vdi.go @@ -35,7 +35,7 @@ func (self *StepUploadVdi) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - // Open the file for reading (NB: httpUpload closes the file for us) + // Open the file for reading (NB: HTTPUpload closes the file for us) fh, err := os.Open(imagePath) if err != nil { ui.Error(fmt.Sprintf("Unable to open disk image '%s': %s", imagePath, err.Error())) @@ -64,7 +64,7 @@ func (self *StepUploadVdi) Run(state multistep.StateBag) multistep.StepAction { } state.Put(self.VdiUuidKey, vdiUuid) - err = httpUpload(fmt.Sprintf("https://%s/import_raw_vdi?vdi=%s&session_id=%s", + _, err = HTTPUpload(fmt.Sprintf("https://%s/import_raw_vdi?vdi=%s&session_id=%s", client.Host, vdi.Ref, client.Session.(string), diff --git a/builder/xenserver/xva/builder.go b/builder/xenserver/xva/builder.go index 94f256c865c..1787c5dfda7 100644 --- a/builder/xenserver/xva/builder.go +++ b/builder/xenserver/xva/builder.go @@ -15,6 +15,7 @@ type config struct { common.PackerConfig `mapstructure:",squash"` xscommon.CommonConfig `mapstructure:",squash"` + SourcePath string `mapstructure:"source_path"` VMMemory uint `mapstructure:"vm_memory"` CloneTemplate string `mapstructure:"clone_template"` @@ -65,6 +66,7 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error // Template substitution templates := map[string]*string{ + "source_path": &self.config.SourcePath, "clone_template": &self.config.CloneTemplate, "network_name": &self.config.NetworkName, } @@ -79,6 +81,10 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error // Validation + if self.config.SourcePath == "" { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("A source_path must be specified")) + } + if len(errs.Errors) > 0 { retErr = errors.New(errs.Error()) } @@ -104,6 +110,7 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa 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) @@ -131,7 +138,7 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa VdiName: self.config.ToolsIsoName, VdiUuidKey: "tools_vdi_uuid", }, - new(stepCreateInstance), + new(stepImportInstance), &xscommon.StepAttachVdi{ VdiUuidKey: "floppy_vdi_uuid", VdiType: xscommon.Floppy, @@ -140,6 +147,7 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa VdiUuidKey: "tools_vdi_uuid", VdiType: xscommon.CD, }, + new(xscommon.StepRemoveDevices), new(xscommon.StepStartOnHIMN), &xscommon.StepForwardPortOverSSH{ RemotePort: xscommon.HimnSSHPort, diff --git a/builder/xenserver/xva/step_create_instance.go b/builder/xenserver/xva/step_import_instance.go similarity index 57% rename from builder/xenserver/xva/step_create_instance.go rename to builder/xenserver/xva/step_import_instance.go index 986516af4ea..47531cd9488 100644 --- a/builder/xenserver/xva/step_create_instance.go +++ b/builder/xenserver/xva/step_import_instance.go @@ -1,56 +1,58 @@ package xva import ( - // "fmt" + "fmt" + "os" "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 stepImportInstance struct { instance *xscommon.VM vdi *xscommon.VDI } -func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepAction { +func (self *stepImportInstance) 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: Import Instance") + + // find the SR + sr, err := config.GetSR(client) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get SR: %s", err.Error())) + return multistep.ActionHalt + } + + // Open the file for reading (NB: httpUpload closes the file for us) + fh, err := os.Open(config.SourcePath) + if err != nil { + ui.Error(fmt.Sprintf("Unable to open XVA '%s': %s", config.SourcePath, err.Error())) + return multistep.ActionHalt + } + + result, err := xscommon.HTTPUpload(fmt.Sprintf("https://%s/import?session_id=%s&sr_id=%s", + client.Host, + client.Session.(string), + sr.Ref, + ), fh, state) + if err != nil { + ui.Error(fmt.Sprintf("Unable to upload VDI: %s", err.Error())) + return multistep.ActionHalt + } + if result == nil { + ui.Error("XAPI did not reply with an instance reference") + return multistep.ActionHalt + } + + instance := xscommon.VM(*result) /* - 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())) @@ -63,27 +65,6 @@ func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepActi 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 @@ -151,20 +132,21 @@ func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepActi 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)) */ + 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("Imported instance '%s'", instanceId)) + return multistep.ActionContinue } -func (self *stepCreateInstance) Cleanup(state multistep.StateBag) { +func (self *stepImportInstance) Cleanup(state multistep.StateBag) { /* config := state.Get("config").(config) if config.ShouldKeepVM(state) { From cd1875019de324503dcfb48612c3a398363ad830 Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Wed, 31 Dec 2014 17:09:14 +0000 Subject: [PATCH 08/21] Prefix remote_ for all host config --- builder/xenserver/common/common_config.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/builder/xenserver/common/common_config.go b/builder/xenserver/common/common_config.go index 3a0f772f3a7..015df7a7c47 100644 --- a/builder/xenserver/common/common_config.go +++ b/builder/xenserver/common/common_config.go @@ -13,9 +13,9 @@ import ( ) type CommonConfig struct { - Username string `mapstructure:"username"` - Password string `mapstructure:"password"` - HostIp string `mapstructure:"host_ip"` + Username string `mapstructure:"remote_username"` + Password string `mapstructure:"remote_password"` + HostIp string `mapstructure:"remote_host"` VMName string `mapstructure:"vm_name"` SrName string `mapstructure:"sr_name"` @@ -123,9 +123,9 @@ func (c *CommonConfig) Prepare(t *packer.ConfigTemplate, pc *common.PackerConfig // Template substitution templates := map[string]*string{ - "username": &c.Username, - "password": &c.Password, - "host_ip": &c.HostIp, + "remote_username": &c.Username, + "remote_password": &c.Password, + "remote_host": &c.HostIp, "vm_name": &c.VMName, "sr_name": &c.SrName, "shutdown_command": &c.ShutdownCommand, @@ -156,15 +156,15 @@ func (c *CommonConfig) Prepare(t *packer.ConfigTemplate, pc *common.PackerConfig // Validation if c.Username == "" { - errs = append(errs, errors.New("A username for the xenserver host must be specified.")) + errs = append(errs, errors.New("remote_username must be specified.")) } if c.Password == "" { - errs = append(errs, errors.New("A password for the xenserver host must be specified.")) + errs = append(errs, errors.New("remote_password must be specified.")) } if c.HostIp == "" { - errs = append(errs, errors.New("An ip for the xenserver host must be specified.")) + errs = append(errs, errors.New("remote_host must be specified.")) } if c.VMName == "" { From 201a9be4a832ca544a2cdf14689cb8f496f925b3 Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Wed, 31 Dec 2014 17:26:27 +0000 Subject: [PATCH 09/21] Set default VM name to packer-BUILDNAME-TIMESTAMP --- builder/xenserver/common/common_config.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/builder/xenserver/common/common_config.go b/builder/xenserver/common/common_config.go index 015df7a7c47..378e90a8806 100644 --- a/builder/xenserver/common/common_config.go +++ b/builder/xenserver/common/common_config.go @@ -112,6 +112,10 @@ func (c *CommonConfig) Prepare(t *packer.ConfigTemplate, pc *common.PackerConfig c.OutputDir = fmt.Sprintf("output-%s", pc.PackerBuildName) } + if c.VMName == "" { + c.VMName = fmt.Sprintf("packer-%s-{{timestamp}}", pc.PackerBuildName) + } + if c.Format == "" { c.Format = "xva" } @@ -167,10 +171,6 @@ func (c *CommonConfig) Prepare(t *packer.ConfigTemplate, pc *common.PackerConfig errs = append(errs, errors.New("remote_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")) } From 0cb3aff4e8c23d51c0ee42195435d379270d3c7a Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Fri, 2 Jan 2015 12:55:13 +0000 Subject: [PATCH 10/21] Fix Task.GetErrorInfo typecast crash --- builder/xenserver/common/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/xenserver/common/client.go b/builder/xenserver/common/client.go index 3be184e36c2..46b838ab3fd 100644 --- a/builder/xenserver/common/client.go +++ b/builder/xenserver/common/client.go @@ -900,7 +900,7 @@ func (self *Task) GetErrorInfo() (errorInfo []string, err error) { } errorInfo = make([]string, 0) for _, infoRaw := range result.Value.([]interface{}) { - errorInfo = append(errorInfo, infoRaw.(string)) + errorInfo = append(errorInfo, fmt.Sprintf("%v", infoRaw)) } return } From fc03c2010f1c62946f3de200fdffe9106492442e Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Fri, 2 Jan 2015 12:55:34 +0000 Subject: [PATCH 11/21] Workaround xapi bug in Task.GetResult xapi currently sends us an xmlrpc-encoded string via xmlrpc. This seems to be a bug. Remove this workaround when it's fixed --- builder/xenserver/common/client.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/builder/xenserver/common/client.go b/builder/xenserver/common/client.go index 46b838ab3fd..ff7f3718017 100644 --- a/builder/xenserver/common/client.go +++ b/builder/xenserver/common/client.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/nilshell/xmlrpc" "log" + "regexp" ) type XenAPIClient struct { @@ -880,9 +881,17 @@ func (self *Task) GetResult() (object *XenAPIObject, err error) { } switch ref := result.Value.(type) { case string: - object = &XenAPIObject{ - Ref: ref.(string), - Client: self.Client, + // @fixme: xapi currently sends us an xmlrpc-encoded string via xmlrpc. + // This seems to be a bug in xapi. Remove this workaround when it's fixed + re := regexp.MustCompile("^([^<]*).*$") + match := re.FindStringSubmatch(ref) + if match == nil { + object = nil + } else { + object = &XenAPIObject{ + Ref: match[1], + Client: self.Client, + } } case nil: object = nil From f0149fa49521dcda32a71465d1039487e37bcaa9 Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Fri, 2 Jan 2015 13:41:54 +0000 Subject: [PATCH 12/21] Update build script Adapted from Packer's build script --- build.sh | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/build.sh b/build.sh index 21a27b07789..4fdbcc33bbb 100755 --- a/build.sh +++ b/build.sh @@ -1,8 +1,49 @@ +#!/bin/bash +# +# This script builds the application from source for multiple platforms. +# Adapted from from packer/scripts/build.sh + +# Determine the arch/os combos we're building for XC_OS=${XC_OS:-$(go env GOOS)} XC_ARCH=${XC_ARCH:-$(go env GOARCH)} +# Install dependencies +echo "==> Getting dependencies..." +go get ./... + +# Delete the old dir +echo "==> Removing old directory..." +rm -f bin/* +rm -rf pkg/* +mkdir -p bin/ + gox \ -os="${XC_OS}" \ -arch="${XC_ARCH}" \ - -output "build/{{.OS}}_{{.Arch}}/packer-{{.Dir}}" \ - ./... + -output "pkg/{{.OS}}_{{.Arch}}/packer-{{.Dir}}" \ + ./... \ + || exit 1 + +# Move all the compiled things to the $GOPATH/bin +GOPATH=${GOPATH:-$(go env GOPATH)} +case $(uname) in + CYGWIN*) + GOPATH="$(cygpath $GOPATH)" + ;; +esac +OLDIFS=$IFS +IFS=: MAIN_GOPATH=($GOPATH) +IFS=$OLDIFS + +# Copy our OS/Arch to the bin/ directory +echo "==> Copying binaries for this platform..." +DEV_PLATFORM="./pkg/$(go env GOOS)_$(go env GOARCH)" +for F in $(find ${DEV_PLATFORM} -mindepth 1 -maxdepth 1 -type f); do + cp ${F} bin/ + cp ${F} ${MAIN_GOPATH}/bin/ +done + +# Done! +echo +echo "==> Results:" +ls -hl bin/ From d619e751e360e60d7dc0398c65ebac04a66e9f8f Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Fri, 2 Jan 2015 13:47:29 +0000 Subject: [PATCH 13/21] Update README build instructions --- README.md | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 75541126f1f..cd71f16f593 100644 --- a/README.md +++ b/README.md @@ -38,14 +38,14 @@ Follow these instructions and install golang on your system: ## Install Packer -Visit https://packer.io and install the latest version of packer. Once the -install has completed, setup an environment variable 'PACKERPATH' pointing -to the installation location. E.g. +Clone the Packer repository: ```shell -export PACKERPATH=/usr/local/packer +git clone https://github.com/mitchellh/packer.git ``` +Then follow the [instructions to build and install a development version of Packer](https://github.com/mitchellh/packer#developing-packer). + ## Compile the plugin Once you have installed Packer, you must compile this plugin and install the @@ -58,11 +58,10 @@ cd src/github.com/rdobson git clone https://github.com/rdobson/packer-builder-xenserver.git cd packer-builder-xenserver ./build.sh - ``` -If the build is successful, you should now have a 'packer-builder-xenserver' binary -in your $PACKERPATH directory and you are ready to get going with packer. +If the build is successful, you should now have a `packer-builder-xenserver` binary +in your `$GOPATH/bin` directory and you are ready to get going with packer. ## Centos 6.4 Example @@ -103,10 +102,3 @@ to build this VM with the following: ``` packer build centos-6.4.conf ``` - - - - - - - From 27819cd51f66c54d4f8d63d0e608dfa3adaf1ac7 Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Fri, 2 Jan 2015 15:08:51 +0000 Subject: [PATCH 14/21] Find local IP automatically --- builder/xenserver/common/common_config.go | 9 --------- .../xenserver/common/step_type_boot_command.go | 16 ++++++++++++++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/builder/xenserver/common/common_config.go b/builder/xenserver/common/common_config.go index 378e90a8806..d188f488143 100644 --- a/builder/xenserver/common/common_config.go +++ b/builder/xenserver/common/common_config.go @@ -37,8 +37,6 @@ type CommonConfig struct { 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"` @@ -136,7 +134,6 @@ func (c *CommonConfig) Prepare(t *packer.ConfigTemplate, pc *common.PackerConfig "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, @@ -227,12 +224,6 @@ func (c *CommonConfig) Prepare(t *packer.ConfigTemplate, pc *common.PackerConfig 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 } diff --git a/builder/xenserver/common/step_type_boot_command.go b/builder/xenserver/common/step_type_boot_command.go index 9edc976860e..d1e1f69b34e 100644 --- a/builder/xenserver/common/step_type_boot_command.go +++ b/builder/xenserver/common/step_type_boot_command.go @@ -60,10 +60,22 @@ func (self *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAct log.Printf("Connected to the VNC console: %s", c.DesktopName) - // @todo - include http port/ip so kickstarter files can be grabbed + // find local ip + envVar, err := execute_ssh_cmd("echo $SSH_CLIENT", config.HostIp, "22", config.Username, config.Password) + if err != nil { + ui.Error(fmt.Sprintf("Error detecting local IP: %s", err)) + return multistep.ActionHalt + } + if envVar == "" { + ui.Error("Error detecting local IP: $SSH_CLIENT was empty") + return multistep.ActionHalt + } + localIp := strings.Split(envVar, " ")[0] + ui.Message(fmt.Sprintf("Found local IP: %s", localIp)) + tplData := &bootCommandTemplateData{ config.VMName, - config.LocalIp, + localIp, http_port, } From d2b7f3a7d84143ac5a883a097bcce3ef4e37f44c Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Fri, 2 Jan 2015 15:13:01 +0000 Subject: [PATCH 15/21] Add build dirs to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index d38c149cc61..a6e3994780a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ *.swp *~ +bin +pkg From 16f8c25d84b7b4a68b28d0daae6a18e718d009e2 Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Fri, 2 Jan 2015 16:22:36 +0000 Subject: [PATCH 16/21] Fix shutdown_command bug Fixes the somewhat embarrassing bug where the shutdown_command would be run on the host rather than the guest. This commit also rejigs things so that it is easier to do SSH commands on either the host or the guest --- builder/xenserver/common/ssh.go | 45 +++++++++++++------ builder/xenserver/common/step_get_vnc_port.go | 4 +- .../common/step_shutdown_and_export.go | 2 +- .../xenserver/common/step_start_on_himn.go | 3 +- .../common/step_type_boot_command.go | 2 +- 5 files changed, 35 insertions(+), 21 deletions(-) diff --git a/builder/xenserver/common/ssh.go b/builder/xenserver/common/ssh.go index d7a9a6bac14..8500a587966 100644 --- a/builder/xenserver/common/ssh.go +++ b/builder/xenserver/common/ssh.go @@ -20,9 +20,11 @@ func SSHAddress(state multistep.StateBag) (string, error) { } func SSHLocalAddress(state multistep.StateBag) (string, error) { - sshLocalPort := state.Get("local_ssh_port").(uint) + sshLocalPort, ok := state.Get("local_ssh_port").(uint) + if !ok { + return "", fmt.Errorf("SSH port forwarding hasn't been set up yet") + } conn_str := fmt.Sprintf("%s:%d", "127.0.0.1", sshLocalPort) - log.Printf("sshLocalAddress: %s", conn_str) return conn_str, nil } @@ -49,24 +51,14 @@ func SSHConfig(state multistep.StateBag) (*gossh.ClientConfig, error) { }, nil } -func execute_ssh_cmd(cmd, host, port, username, password string) (stdout string, err error) { - // Setup connection config - config := &gossh.ClientConfig{ - User: username, - Auth: []gossh.AuthMethod{ - gossh.Password(password), - }, - } - - client, err := gossh.Dial("tcp", host+":"+port, config) - +func doExecuteSSHCmd(cmd, target string, config *gossh.ClientConfig) (stdout string, err error) { + client, err := gossh.Dial("tcp", target, config) if err != nil { return "", err } //Create session session, err := client.NewSession() - if err != nil { return "", err } @@ -82,6 +74,31 @@ func execute_ssh_cmd(cmd, host, port, username, password string) (stdout string, return strings.Trim(b.String(), "\n"), nil } +func ExecuteHostSSHCmd(state multistep.StateBag, cmd string) (stdout string, err error) { + config := state.Get("commonconfig").(CommonConfig) + // Setup connection config + sshConfig := &gossh.ClientConfig{ + User: config.Username, + Auth: []gossh.AuthMethod{ + gossh.Password(config.Password), + }, + } + return doExecuteSSHCmd(cmd, config.HostIp+":22", sshConfig) +} + +func ExecuteGuestSSHCmd(state multistep.StateBag, cmd string) (stdout string, err error) { + localAddress, err := SSHLocalAddress(state) + if err != nil { + return + } + sshConfig, err := SSHConfig(state) + if err != nil { + return + } + + return doExecuteSSHCmd(cmd, localAddress, sshConfig) +} + func forward(local_conn net.Conn, config *gossh.ClientConfig, server, remote_dest string, remote_port uint) error { defer local_conn.Close() diff --git a/builder/xenserver/common/step_get_vnc_port.go b/builder/xenserver/common/step_get_vnc_port.go index 4dd208c1d51..88afba2f84b 100644 --- a/builder/xenserver/common/step_get_vnc_port.go +++ b/builder/xenserver/common/step_get_vnc_port.go @@ -10,8 +10,6 @@ import ( type StepGetVNCPort struct{} func (self *StepGetVNCPort) Run(state multistep.StateBag) multistep.StepAction { - - config := state.Get("commonconfig").(CommonConfig) ui := state.Get("ui").(packer.Ui) ui.Say("Step: forward the instances VNC port over SSH") @@ -19,7 +17,7 @@ func (self *StepGetVNCPort) Run(state multistep.StateBag) multistep.StepAction { domid := state.Get("domid").(string) cmd := fmt.Sprintf("xenstore-read /local/domain/%s/console/vnc-port", domid) - remote_vncport, err := execute_ssh_cmd(cmd, config.HostIp, "22", config.Username, config.Password) + remote_vncport, err := ExecuteHostSSHCmd(state, cmd) if err != nil { ui.Error(fmt.Sprintf("Unable to get VNC port (is the VM running?): %s", err.Error())) return multistep.ActionHalt diff --git a/builder/xenserver/common/step_shutdown_and_export.go b/builder/xenserver/common/step_shutdown_and_export.go index c49edfe099c..39fe0d670f2 100644 --- a/builder/xenserver/common/step_shutdown_and_export.go +++ b/builder/xenserver/common/step_shutdown_and_export.go @@ -63,7 +63,7 @@ func (StepShutdownAndExport) Run(state multistep.StateBag) multistep.StepAction if config.ShutdownCommand != "" { ui.Say("Executing shutdown command...") - _, err := execute_ssh_cmd(config.ShutdownCommand, config.HostIp, "22", config.Username, config.Password) + _, err := ExecuteGuestSSHCmd(state, config.ShutdownCommand) if err != nil { ui.Error(fmt.Sprintf("Shutdown command failed: %s", err.Error())) return false diff --git a/builder/xenserver/common/step_start_on_himn.go b/builder/xenserver/common/step_start_on_himn.go index fbc0ae857b0..816c228b561 100644 --- a/builder/xenserver/common/step_start_on_himn.go +++ b/builder/xenserver/common/step_start_on_himn.go @@ -23,7 +23,6 @@ func (self *StepStartOnHIMN) Run(state multistep.StateBag) multistep.StepAction ui := state.Get("ui").(packer.Ui) client := state.Get("client").(XenAPIClient) - config := state.Get("commonconfig").(CommonConfig) ui.Say("Step: Start VM on the Host Internal Mangement Network") @@ -96,7 +95,7 @@ func (self *StepStartOnHIMN) Run(state multistep.StateBag) multistep.StepAction err = InterruptibleWait{ Predicate: func() (success bool, err error) { ui.Message(fmt.Sprintf("Attempting to ping interface: %s", ping_cmd)) - _, err = execute_ssh_cmd(ping_cmd, config.HostIp, "22", config.Username, config.Password) + _, err = ExecuteHostSSHCmd(state, ping_cmd) switch err.(type) { case nil: diff --git a/builder/xenserver/common/step_type_boot_command.go b/builder/xenserver/common/step_type_boot_command.go index d1e1f69b34e..76afcb9ab35 100644 --- a/builder/xenserver/common/step_type_boot_command.go +++ b/builder/xenserver/common/step_type_boot_command.go @@ -61,7 +61,7 @@ func (self *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAct log.Printf("Connected to the VNC console: %s", c.DesktopName) // find local ip - envVar, err := execute_ssh_cmd("echo $SSH_CLIENT", config.HostIp, "22", config.Username, config.Password) + envVar, err := ExecuteHostSSHCmd(state, "echo $SSH_CLIENT") if err != nil { ui.Error(fmt.Sprintf("Error detecting local IP: %s", err)) return multistep.ActionHalt From 22cf62c6148e3e87baaab0dfd0788193f334afd6 Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Wed, 31 Dec 2014 17:21:21 +0000 Subject: [PATCH 17/21] Initial docs --- docs/builders/xenserver-iso.html.markdown | 281 ++++++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 docs/builders/xenserver-iso.html.markdown diff --git a/docs/builders/xenserver-iso.html.markdown b/docs/builders/xenserver-iso.html.markdown new file mode 100644 index 00000000000..8f63ef62b33 --- /dev/null +++ b/docs/builders/xenserver-iso.html.markdown @@ -0,0 +1,281 @@ +--- +layout: "docs" +page_title: "XenServer Builder (from an ISO)" +description: |- + The XenServer Packer builder is able to create XenServer virtual machines and export them either as an XVA or a VDI, starting from an ISO image. +--- + +# XenServer Builder (from an ISO) + +Type: `xenserver-iso` + +The XenServer Packer builder is able to create [XenServer](https://www.xenserver.org/) +virtual machines and export them either as an XVA or a VDI, starting from an +ISO image. + +The builder builds a virtual machine by creating a new virtual machine +from scratch, booting it, installing an OS, provisioning software within +the OS, then shutting it down. The result of the XenServer builder is a +directory containing all the files necessary to run the virtual machine +portably. + +## Basic Example + +Here is a basic example. This example is not functional. Even when the +`remote_*` fields have been completed, it will start the OS installer but then +fail because we don't provide the preseed file for Ubuntu to self-install. +Still, the example serves to show the basic configuration: + +```javascript +{ + "type": "xenserver-iso", + "remote_host": "your-server.example.com", + "remote_username": "root", + "remote_password": "password", + "iso_url": "http://releases.ubuntu.com/12.04/ubuntu-12.04.5-server-amd64.iso", + "iso_checksum": "769474248a3897f4865817446f9a4a53", + "iso_checksum_type": "md5", + "ssh_username": "packer", + "ssh_password": "packer", + "ssh_wait_timeout": "30s", + "shutdown_command": "echo 'packer' | sudo -S shutdown -P now" +} +``` + +It is important to add a `shutdown_command`. By default Packer forcibly halts the +virtual machine and the file system may not be sync'd. Thus, changes made in a +provisioner might not be saved. + +## Configuration Reference + +There are many configuration options available for the XenServer builder. +They are organized below into two categories: required and optional. Within +each category, the available options are alphabetized and described. + +### Required: + +* `iso_checksum` (string) - The checksum for the OS ISO file. Because ISO + files are so large, this is required and Packer will verify it prior + to booting a virtual machine with the ISO attached. The type of the + checksum is specified with `iso_checksum_type`, documented below. + +* `iso_checksum_type` (string) - The type of the checksum specified in + `iso_checksum`. Valid values are "none", "md5", "sha1", "sha256", or + "sha512" currently. While "none" will skip checksumming, this is not + recommended since ISO files are generally large and corruption does happen + from time to time. + +* `iso_url` (string) - A URL to the ISO containing the installation image. + This URL can be either an HTTP URL or a file URL (or path to a file). + If this is an HTTP URL, Packer will download it and cache it between + runs. + +* `remote_host` (string) - The host of the remote machine. + +* `remote_username` (string) - The XenServer username used to access the remote machine. + +* `remote_password` (string) - The XenServer password for access to the remote machine. + +* `ssh_username` (string) - The username to use to SSH into the machine + once the OS is installed. + +### Optional: + +* `boot_command` (array of strings) - This is an array of commands to type + when the virtual machine is first booted. The goal of these commands should + be to type just enough to initialize the operating system installer. Special + keys can be typed as well, and are covered in the section below on the boot + command. If this is not specified, it is assumed the installer will start + itself. + +* `boot_wait` (string) - The time to wait after booting the initial virtual + machine before typing the `boot_command`. The value of this should be + a duration. Examples are "5s" and "1m30s" which will cause Packer to wait + five seconds and one minute 30 seconds, respectively. If this isn't specified, + the default is 10 seconds. + +* `clone_template` (string) - The template to clone. Defaults to "Other install + media", this is "other", but you can get _dramatic_ performance improvements + by setting this to the proper value. To view all available values for this + run `xe template-list`. Setting the correct value hints to XenServer how to + optimize the virtual hardware to work best with that operating system. + +* `disk_size` (integer) - The size, in megabytes, of the hard disk to create + for the VM. By default, this is 40000 (about 40 GB). + +* `floppy_files` (array of strings) - A list of files to place onto a floppy + disk that is attached when the VM is booted. This is most useful + for unattended Windows installs, which look for an `Autounattend.xml` file + on removable media. By default, no floppy will be attached. All files + listed in this setting get placed into the root directory of the floppy + and the floppy is attached as the first floppy device. Currently, no + support exists for creating sub-directories on the floppy. Wildcard + characters (\*, ?, and []) are allowed. Directory names are also allowed, + which will add all the files found in the directory to the floppy. + +* `format` (string) - Either "xva" or "vdi_raw", this specifies the output + format of the exported virtual machine. This defaults to "xva". Set to + "vdi_raw" to export just the raw disk image. + +* `http_directory` (string) - Path to a directory to serve using an HTTP + server. The files in this directory will be available over HTTP that will + be requestable from the virtual machine. This is useful for hosting + kickstart files and so on. By default this is "", which means no HTTP + server will be started. The address and port of the HTTP server will be + available as variables in `boot_command`. This is covered in more detail + below. + +* `http_port_min` and `http_port_max` (integer) - These are the minimum and + maximum port to use for the HTTP server started to serve the `http_directory`. + Because Packer often runs in parallel, Packer will choose a randomly available + port in this range to run the HTTP server. If you want to force the HTTP + server to be on one port, make this minimum and maximum port the same. + By default the values are 8000 and 9000, respectively. + +* `install_timeout` (string) - The amount of time to wait after booting the VM + for the installer to shut itself down. + If it doesn't shut down in this time, it is an error. By default, the timeout + is "200m", or over three hours. + +* `iso_urls` (array of strings) - Multiple URLs for the ISO to download. + Packer will try these in order. If anything goes wrong attempting to download + or while downloading a single URL, it will move on to the next. All URLs + must point to the same file (same checksum). By default this is empty + and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified. + +* `output_directory` (string) - This is the path to the directory where the + resulting virtual machine will be created. This may be relative or absolute. + If relative, the path is relative to the working directory when `packer` + is executed. This directory must not exist or be empty prior to running the builder. + By default this is "output-BUILDNAME" where "BUILDNAME" is the name + of the build. + +* `platform_args` (object of key/value strings) - The platform args. + Defaults to +```javascript +{ + "viridian": "false", + "nx": "true", + "pae": "true", + "apic": "true", + "timeoffset": "0", + "acpi": "1" +} +``` + +* `shutdown_command` (string) - The command to use to gracefully shut down + the machine once all the provisioning is done. By default this is an empty + string, which tells Packer to just forcefully shut down the machine. + +* `shutdown_timeout` (string) - The amount of time to wait after executing + the `shutdown_command` for the virtual machine to actually shut down. + If it doesn't shut down in this time, it is an error. By default, the timeout + is "5m", or five minutes. + +* `ssh_host_port_min` and `ssh_host_port_max` (integer) - The minimum and + maximum port to use for the SSH port on the host machine which is forwarded + to the SSH port on the guest machine. Because Packer often runs in parallel, + Packer will choose a randomly available port in this range to use as the + host port. + +* `ssh_key_path` (string) - Path to a private key to use for authenticating + with SSH. By default this is not set (key-based auth won't be used). + The associated public key is expected to already be configured on the + VM being prepared by some other process (kickstart, etc.). + +* `ssh_password` (string) - The password for `ssh_username` to use to + authenticate with SSH. By default this is the empty string. + + + +* `ssh_wait_timeout` (string) - The duration to wait for SSH to become + available. By default this is "20m", or 20 minutes. Note that this should + be quite long since the timer begins as soon as the virtual machine is booted. + +* `tools_iso_name` (string) - The name of the XenServer Tools ISO. Defaults to + "xs-tools.iso". + +* `vm_name` (string) - This is the name of the new virtual + machine, without the file extension. By default this is + "packer-BUILDNAME-TIMESTAMP", where "BUILDNAME" is the name of the build. + +* `vm_memory` (integer) - The size, in megabytes, of the amount of memory to + allocate for the VM. By default, this is 1024 (1 GB). + +## Differences with other Packer builders + +Currently the XenServer builder has some quirks when compared with other Packer builders. + +The builder currently only works remotely. + +The installer is expected to shut down the VM to indicate that it has completed. This is in contrast to other builders, which instead detect completion by a successful SSH connection. The reason for this difference is that currently the builder has no way of knowing what the IP address of the VM is without starting it on the HIMN. + +## Boot Command + +The `boot_command` configuration is very important: it specifies the keys +to type when the virtual machine is first booted in order to start the +OS installer. This command is typed after `boot_wait`, which gives the +virtual machine some time to actually load the ISO. + +As documented above, the `boot_command` is an array of strings. The +strings are all typed in sequence. It is an array only to improve readability +within the template. + +The boot command is "typed" character for character over a VNC connection +to the machine, simulating a human actually typing the keyboard. There are +a set of special keys available. If these are in your boot command, they +will be replaced by the proper key: + +* `` - Backspace + +* `` - Delete + +* `` and `` - Simulates an actual "enter" or "return" keypress. + +* `` - Simulates pressing the escape key. + +* `` - Simulates pressing the tab key. + +* `` - `` - Simulates pressing a function key. + +* `` `` `` `` - Simulates pressing an arrow key. + +* `` - Simulates pressing the spacebar. + +* `` - Simulates pressing the insert key. + +* `` `` - Simulates pressing the home and end keys. + +* `` `` - Simulates pressing the page up and page down keys. + +* `` `` `` - Adds a 1, 5 or 10 second pause before sending any additional keys. This + is useful if you have to generally wait for the UI to update before typing more. + +In addition to the special keys, each command to type is treated as a +[configuration template](/docs/templates/configuration-templates.html). +The available variables are: + +* `HTTPIP` and `HTTPPort` - The IP and port, respectively of an HTTP server + that is started serving the directory specified by the `http_directory` + configuration parameter. If `http_directory` isn't specified, these will be + blank! + +Example boot command. This is actually a working boot command used to start +an Ubuntu 12.04 installer: + +```javascript +[ + "<esc><esc><enter><wait>", + "/install/vmlinuz noapic ", + "preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg ", + "debian-installer=en_US auto locale=en_US kbd-chooser/method=us ", + "hostname={{ .Name }} ", + "fb=false debconf/frontend=noninteractive ", + "keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ", + "keyboard-configuration/variant=USA console-setup/ask_detect=false ", + "initrd=/install/initrd.gz -- <enter>" +] +``` From 1cf49992fa569751fcba23d68450e0792c5771b1 Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Fri, 2 Jan 2015 14:09:05 +0000 Subject: [PATCH 18/21] Update example --- examples/centos-6.4.conf | 23 ----------------------- examples/centos-6.6.json | 30 ++++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 23 deletions(-) delete mode 100644 examples/centos-6.4.conf create mode 100644 examples/centos-6.6.json diff --git a/examples/centos-6.4.conf b/examples/centos-6.4.conf deleted file mode 100644 index 2adfc77f658..00000000000 --- a/examples/centos-6.4.conf +++ /dev/null @@ -1,23 +0,0 @@ - - "builders": [{ - "type": "xenserver", - "username": "root", - "password": "hostpassword", - "host_ip": "10.81.2.105", - "vm_name": "packer-centos-6-4", - "vm_memory": 2048, - "disk_size": 40000, - "iso_name": "CentOS-6.4-x86_64-minimal.iso", - "http_directory": "http", - "local_ip": "10.80.3.223", - "install_timeout": "600s", - "ssh_username": "root", - "ssh_password": "vmpassword", - "boot_command": - [ - "", - " ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/centos6-ks.cfg" - ], - "keep_instance": "always" - }] -} diff --git a/examples/centos-6.6.json b/examples/centos-6.6.json new file mode 100644 index 00000000000..dc60d642bef --- /dev/null +++ b/examples/centos-6.6.json @@ -0,0 +1,30 @@ +{ + "builders": [ + { + "type": "xenserver-iso", + "remote_host": "your-server.example.com", + "remote_username": "root", + "remote_password": "password", + + "boot_command": [ + " text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/centos6-ks.cfg" + ], + "boot_wait": "10s", + "disk_size": 40960, + "http_directory": "http", + "iso_checksum": "4ed6c56d365bd3ab12cd88b8a480f4a62e7c66d2", + "iso_checksum_type": "sha1", + "iso_url": "{{user `mirror`}}/6.6/isos/x86_64/CentOS-6.6-x86_64-minimal.iso", + "output_directory": "packer-centos-6.6-x86_64-xenserver", + "shutdown_command": "/sbin/halt", + "ssh_username": "root", + "ssh_password": "vmpassword", + "ssh_wait_timeout": "10000s", + "vm_name": "packer-centos-6.6-x86_64" + } + ], + + "variables": { + "mirror": "http://www.mirrorservice.org/sites/mirror.centos.org" + } +} From 72fb12ab28f3b1d9732c017989a3f374ac21eaee Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Fri, 2 Jan 2015 15:43:12 +0000 Subject: [PATCH 19/21] Update README for new example --- README.md | 57 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index cd71f16f593..b2f626680d2 100644 --- a/README.md +++ b/README.md @@ -63,42 +63,45 @@ cd packer-builder-xenserver If the build is successful, you should now have a `packer-builder-xenserver` binary in your `$GOPATH/bin` directory and you are ready to get going with packer. -## Centos 6.4 Example +## Centos 6.6 Example Once you've setup the above, you are good to go with an example. -To get you started, there is an example config file which you can use: [`examples/centos-6.4.conf`](https://github.com/rdobson/packer-builder-xenserver/blob/master/examples/centos-6.4.conf) - -Currently it is not (easily) possible to take care of the ISO download and upload, -so you will need to attach an ISO SR to the XenServer host (NFS/CIFS) with the -ISO you want to use for installation. You will then need to specify the name -in the config file (this must be unique). - - -An explanation of what these parameters are doing: - * `type` - this specifies the builder. This must be 'xenserver'. - * `username` - this is the username for the XenServer host being used. - * `password` - this is the password for the XenServer host being used. - * `host_ip` - this is the IP for the XenServer host being used. - * `instance_name` - this is the name that should be given to the created VM. - * `instance_memory` - this is the static memory configuration for the VM. - * `root_disk_size` - this is the size of the disk the VM should be created with. - * `iso_name` - this is the name of the ISO visible on a ISO SR connected to the XenServer host. +To get you started, there is an example config file which you can use: +[`examples/centos-6.6.json`](https://github.com/rdobson/packer-builder-xenserver/blob/master/examples/centos-6.6.json) + +The example is functional, once suitable `remote_host`, `remote_username` and +`remote_password` configurations have been substituted. + +A brief explanation of what the config parameters mean: + * `type` - specifies the builder type. This is 'xenserver-iso', for installing + a VM from scratch, or 'xenserver-xva' to import existing XVA as a starting + point. + * `remote_username` - the username for the XenServer host being used. + * `remote_password` - the password for the XenServer host being used. + * `remote_host` - the IP for the XenServer host being used. + * `vm_name` - the name that should be given to the created VM. + * `vm_memory` - the static memory configuration for the VM, in MB. + * `disk_size` - the size of the disk the VM should be created with, in MB. + * `iso_name` - the name of the ISO visible on a ISO SR connected to the XenServer host. * `http_directory` - the path to a local directory to serve up over http. - * `local_ip` - the IP on the machine you are running packer that your XenServer can connect too. * `ssh_username` - the username set by the installer for the instance. * `ssh_password` - the password set by the installer for the instance. - * `boot_command` - a set of commands to be sent to the instance over VNC. + * `boot_command` - a list of commands to be sent to the instance over VNC. - -Note, the `http_directory` and `local_ip` parameters are only required if you -want packer to serve up files over HTTP. In this example, the templated variables -`{{ .HTTPIP }}` and `{{ .HTTPPort }}` will be substituted for the `local_ip` and -the port that packer starts it's HTTP service on. +Note, the `http_directory` parameter is only required if you +want Packer to serve up files over HTTP. In this example, the templated variables +`{{ .HTTPIP }}` and `{{ .HTTPPort }}` will be substituted for the local IP and +the port that Packer starts its HTTP service on. Once you've updated the config file with your own parameters, you can use packer -to build this VM with the following: +to build this VM with the following command: ``` -packer build centos-6.4.conf +packer build centos-6.6.json ``` + +# Documentation + +For complete documentation on configuration commands, see either [the +xenserver-iso docs](https://github.com/rdobson/packer-builder-xenserver/blob/master/docs/builders/xenserver-iso.html.markdown) or [the xenserver-xva docs](https://github.com/rdobson/packer-builder-xenserver/blob/master/docs/builders/xenserver-xva.html.markdown). From 1539688cb8ddce794c30fbf119fd92e0fcd87aa7 Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Fri, 2 Jan 2015 16:48:12 +0000 Subject: [PATCH 20/21] Update tests --- builder/xenserver/iso/builder_test.go | 24 +-- builder/xenserver/xva/builder.go | 2 - builder/xenserver/xva/builder_test.go | 208 +++----------------------- 3 files changed, 30 insertions(+), 204 deletions(-) diff --git a/builder/xenserver/iso/builder_test.go b/builder/xenserver/iso/builder_test.go index 960b6e6d546..1a7a19b86e0 100644 --- a/builder/xenserver/iso/builder_test.go +++ b/builder/xenserver/iso/builder_test.go @@ -8,9 +8,9 @@ import ( func testConfig() map[string]interface{} { return map[string]interface{}{ - "host_ip": "localhost", - "username": "admin", - "password": "admin", + "remote_host": "localhost", + "remote_username": "admin", + "remote_password": "admin", "vm_name": "foo", "iso_checksum": "foo", "iso_checksum_type": "md5", @@ -53,12 +53,12 @@ func TestBuilderPrepare_Defaults(t *testing.T) { 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.Format != "xva" { + t.Errorf("bad format: %s", b.config.Format) } - if b.config.KeepInstance != "never" { - t.Errorf("bad keep instance: %s", b.config.KeepInstance) + if b.config.KeepVM != "never" { + t.Errorf("bad keep instance: %s", b.config.KeepVM) } } @@ -99,7 +99,7 @@ func TestBuilderPrepare_Format(t *testing.T) { config := testConfig() // Bad - config["export_format"] = "foo" + config["format"] = "foo" warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) @@ -109,7 +109,7 @@ func TestBuilderPrepare_Format(t *testing.T) { } // Good - config["export_format"] = "vdi_raw" + config["format"] = "vdi_raw" b = Builder{} warns, err = b.Prepare(config) if len(warns) > 0 { @@ -333,12 +333,12 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) { } } -func TestBuilderPrepare_KeepInstance(t *testing.T) { +func TestBuilderPrepare_KeepVM(t *testing.T) { var b Builder config := testConfig() // Bad - config["keep_instance"] = "foo" + config["keep_vm"] = "foo" warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) @@ -348,7 +348,7 @@ func TestBuilderPrepare_KeepInstance(t *testing.T) { } // Good - config["keep_instance"] = "always" + config["keep_vm"] = "always" b = Builder{} warns, err = b.Prepare(config) if len(warns) > 0 { diff --git a/builder/xenserver/xva/builder.go b/builder/xenserver/xva/builder.go index 1787c5dfda7..9c4cbf1d54c 100644 --- a/builder/xenserver/xva/builder.go +++ b/builder/xenserver/xva/builder.go @@ -17,7 +17,6 @@ type config struct { SourcePath string `mapstructure:"source_path"` VMMemory uint `mapstructure:"vm_memory"` - CloneTemplate string `mapstructure:"clone_template"` PlatformArgs map[string]string `mapstructure:"platform_args"` @@ -67,7 +66,6 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error templates := map[string]*string{ "source_path": &self.config.SourcePath, - "clone_template": &self.config.CloneTemplate, "network_name": &self.config.NetworkName, } diff --git a/builder/xenserver/xva/builder_test.go b/builder/xenserver/xva/builder_test.go index 5fbef57c109..f2237673a6a 100644 --- a/builder/xenserver/xva/builder_test.go +++ b/builder/xenserver/xva/builder_test.go @@ -2,21 +2,18 @@ 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", + "remote_host": "localhost", + "remote_username": "admin", + "remote_password": "admin", "vm_name": "foo", - "iso_checksum": "foo", - "iso_checksum_type": "md5", - "iso_url": "http://www.google.com/", "shutdown_command": "yes", "ssh_username": "foo", + "source_path": ".", packer.BuildNameConfigKey: "foo", } @@ -45,52 +42,16 @@ func TestBuilderPrepare_Defaults(t *testing.T) { 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.Format != "xva" { + t.Errorf("bad format: %s", b.config.Format) } - 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) + if b.config.KeepVM != "never" { + t.Errorf("bad keep instance: %s", b.config.KeepVM) } } @@ -99,7 +60,7 @@ func TestBuilderPrepare_Format(t *testing.T) { config := testConfig() // Bad - config["export_format"] = "foo" + config["format"] = "foo" warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) @@ -109,7 +70,7 @@ func TestBuilderPrepare_Format(t *testing.T) { } // Good - config["export_format"] = "vdi_raw" + config["format"] = "vdi_raw" b = Builder{} warns, err = b.Prepare(config) if len(warns) > 0 { @@ -174,42 +135,12 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) { } } -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) { +func TestBuilderPrepare_KeepVM(t *testing.T) { var b Builder config := testConfig() - // Test bad - config["iso_checksum_type"] = "" + // Bad + config["keep_vm"] = "foo" warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) @@ -218,127 +149,24 @@ func TestBuilderPrepare_ISOChecksumType(t *testing.T) { 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" + // Good + config["keep_vm"] = "always" 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) { +func TestBuilderPrepare_SourcePath(t *testing.T) { var b Builder config := testConfig() // Bad - config["keep_instance"] = "foo" + config["source_path"] = "" warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) @@ -348,7 +176,7 @@ func TestBuilderPrepare_KeepInstance(t *testing.T) { } // Good - config["keep_instance"] = "always" + config["source_path"] = "." b = Builder{} warns, err = b.Prepare(config) if len(warns) > 0 { From 2591ae8226927b9f92e01e9730124204992002e7 Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Fri, 2 Jan 2015 18:19:00 +0000 Subject: [PATCH 21/21] Add ssh_port config --- builder/xenserver/common/common_config.go | 14 +++++++------- builder/xenserver/common/step_start_on_himn.go | 3 ++- docs/builders/xenserver-iso.html.markdown | 2 -- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/builder/xenserver/common/common_config.go b/builder/xenserver/common/common_config.go index d188f488143..2c1ff3835e4 100644 --- a/builder/xenserver/common/common_config.go +++ b/builder/xenserver/common/common_config.go @@ -39,9 +39,9 @@ type CommonConfig struct { // 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"` + 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 @@ -96,12 +96,12 @@ func (c *CommonConfig) Prepare(t *packer.ConfigTemplate, pc *common.PackerConfig if c.SSHHostPortMax == 0 { c.SSHHostPortMax = 4444 } - - if c.SSHPort == 0 { - c.SSHPort = 22 - } */ + if c.SSHPort == 0 { + c.SSHPort = 22 + } + if c.RawSSHWaitTimeout == "" { c.RawSSHWaitTimeout = "20m" } diff --git a/builder/xenserver/common/step_start_on_himn.go b/builder/xenserver/common/step_start_on_himn.go index 816c228b561..6e9f8785943 100644 --- a/builder/xenserver/common/step_start_on_himn.go +++ b/builder/xenserver/common/step_start_on_himn.go @@ -131,5 +131,6 @@ func HimnSSHIP(state multistep.StateBag) (string, error) { } func HimnSSHPort(state multistep.StateBag) (uint, error) { - return 22, nil + config := state.Get("commonconfig").(CommonConfig) + return config.SSHPort, nil } diff --git a/docs/builders/xenserver-iso.html.markdown b/docs/builders/xenserver-iso.html.markdown index 8f63ef62b33..f1ba972632c 100644 --- a/docs/builders/xenserver-iso.html.markdown +++ b/docs/builders/xenserver-iso.html.markdown @@ -186,10 +186,8 @@ each category, the available options are alphabetized and described. * `ssh_password` (string) - The password for `ssh_username` to use to authenticate with SSH. By default this is the empty string. - * `ssh_wait_timeout` (string) - The duration to wait for SSH to become available. By default this is "20m", or 20 minutes. Note that this should