diff --git a/README.md b/README.md index 8277c1ea..e84f97f1 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,7 @@ You can check out packer [here](https://packer.io). ## Dependencies * Packer >= 0.7.2 (https://packer.io) * XenServer > 6.2 (http://xenserver.org) -* Golang (tested with 1.2.1) - +* Golang >= 1.6; < 2.0 ## Install Go @@ -20,13 +19,7 @@ Follow these instructions and install golang on your system: ## Install Packer -Clone the Packer repository: - -```shell -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). +Follow the [instructions to build and install a development version of Packer](https://github.com/mitchellh/packer/blob/master/CONTRIBUTING.md#setting-up-go-to-work-on-packer). ## Compile the plugin @@ -34,7 +27,7 @@ Once you have installed Packer, you must compile this plugin and install the resulting binary. ```shell -cd $GOROOT +cd $GOPATH mkdir -p src/github.com/xenserver/ cd src/github.com/xenserver git clone https://github.com/xenserver/packer-builder-xenserver.git @@ -102,4 +95,4 @@ 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). +xenserver-iso docs](https://github.com/xenserver/packer-builder-xenserver/blob/master/docs/builders/xenserver-iso.html.markdown) or [the xenserver-xva docs](https://github.com/xenserver/packer-builder-xenserver/blob/master/docs/builders/xenserver-xva.html.markdown). diff --git a/build.sh b/build.sh index efc343e7..7c657a2c 100755 --- a/build.sh +++ b/build.sh @@ -15,6 +15,9 @@ rm -rf pkg/* rm -rf $GOPATH/pkg/* mkdir -p bin/ +# Fix for build failing due to missing go-vnc +go get github.com/mitchellh/go-vnc + gox \ -os="${XC_OS}" \ -arch="${XC_ARCH}" \ diff --git a/builder/xenserver/common/common_config.go b/builder/xenserver/common/common_config.go index 71ea9b67..24689af1 100644 --- a/builder/xenserver/common/common_config.go +++ b/builder/xenserver/common/common_config.go @@ -50,10 +50,12 @@ type CommonConfig struct { RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"` SSHWaitTimeout time.Duration - OutputDir string `mapstructure:"output_directory"` - Format string `mapstructure:"format"` - KeepVM string `mapstructure:"keep_vm"` - IPGetter string `mapstructure:"ip_getter"` + OutputDir string `mapstructure:"output_directory"` + Format string `mapstructure:"format"` + DiskDrives uint `mapstructure:"disk_drives"` + KeepTemplateVIFs bool `mapstructure:"keep_template_vifs"` + KeepVM string `mapstructure:"keep_vm"` + IPGetter string `mapstructure:"ip_getter"` } func (c *CommonConfig) Prepare(ctx *interpolate.Context, pc *common.PackerConfig) []error { @@ -184,9 +186,9 @@ func (c *CommonConfig) Prepare(ctx *interpolate.Context, pc *common.PackerConfig } switch c.Format { - case "xva", "xva_compressed", "vdi_raw", "vdi_vhd", "none": + case "xva", "xva_template", "vdi_raw", "vdi_vhd", "none": default: - errs = append(errs, errors.New("format must be one of 'xva', 'vdi_raw', 'vdi_vhd', 'none'")) + errs = append(errs, errors.New("format must be one of 'xva', 'xva_template', 'vdi_raw', 'vdi_vhd', 'none'")) } switch c.KeepVM { diff --git a/builder/xenserver/common/step_configure_disk_drives.go b/builder/xenserver/common/step_configure_disk_drives.go new file mode 100644 index 00000000..7e14329d --- /dev/null +++ b/builder/xenserver/common/step_configure_disk_drives.go @@ -0,0 +1,103 @@ +package common + +import ( + "fmt" + "strings" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/nilshell/xmlrpc" + xsclient "github.com/xenserver/go-xenserver-client" +) + +type StepConfigureDiskDrives struct {} + +func (self *StepConfigureDiskDrives) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + config := state.Get("commonconfig").(CommonConfig) + client := state.Get("client").(xsclient.XenAPIClient) + ui.Say("Step: Configure disk drives") + + uuid := state.Get("instance_uuid").(string) + instance, err := client.GetVMByUuid(uuid) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get VM from UUID '%s': %s", uuid, err.Error())) + return multistep.ActionHalt + } + + vbds, err := instance.GetVBDs() + if err != nil { + ui.Error(fmt.Sprintf("Error getting VBDs: %s", err.Error())) + return multistep.ActionHalt + } + + var current_number_of_disk_drives uint = 0 + for _, vbd := range vbds { + vbd_rec, err := vbd.GetRecord() + if err != nil { + ui.Error(fmt.Sprintf("Error getting VBD record: %s", err.Error())) + return multistep.ActionHalt + } + if vbd_rec["type"].(string) == "CD" { + if current_number_of_disk_drives < config.DiskDrives { + ui.Say("Ejecting disk drive") + err = vbd.Eject() + if err != nil && !strings.Contains(err.Error(), "VBD_IS_EMPTY") { + ui.Error(fmt.Sprintf("Error ejecting VBD: %s", err.Error())) + return multistep.ActionHalt + } + current_number_of_disk_drives++ + } else { + ui.Say("Destroying excess disk drive") + _ = vbd.Eject() + _ = vbd.Unplug() + err = vbd.Destroy() + if err != nil { + ui.Error(fmt.Sprintf("Error destroying VBD: %s", err.Error())) + return multistep.ActionHalt + } + } + } + } + + if current_number_of_disk_drives < config.DiskDrives { + vbd_rec := make(xmlrpc.Struct) + vbd_rec["VM"] = instance.Ref + vbd_rec["VDI"] = "OpaqueRef:NULL" + vbd_rec["userdevice"] = "autodetect" + vbd_rec["empty"] = true + vbd_rec["other_config"] = make(xmlrpc.Struct) + vbd_rec["qos_algorithm_type"] = "" + vbd_rec["qos_algorithm_params"] = make(xmlrpc.Struct) + vbd_rec["mode"] = "RO" + vbd_rec["bootable"] = true + vbd_rec["unpluggable"] = false + vbd_rec["type"] = "CD" + for current_number_of_disk_drives < config.DiskDrives { + ui.Say("Creating disk drive") + + result := xsclient.APIResult{} + err := client.APICall(&result, "VBD.create", vbd_rec) + + if err != nil { + ui.Error("Error creating disk drive. Retrying...") + continue + } + + vbd_ref := result.Value.(string) + + result = xsclient.APIResult{} + err = client.APICall(&result, "VBD.get_uuid", vbd_ref) + + if err != nil { + ui.Error("Error verifying disk drive. Retrying...") + continue + } + + current_number_of_disk_drives++ + } + } + + return multistep.ActionContinue +} + +func (self *StepConfigureDiskDrives) Cleanup(state multistep.StateBag) {} diff --git a/builder/xenserver/common/step_detach_vdi.go b/builder/xenserver/common/step_detach_vdi.go index 96147e3b..b6a36925 100644 --- a/builder/xenserver/common/step_detach_vdi.go +++ b/builder/xenserver/common/step_detach_vdi.go @@ -14,6 +14,7 @@ type StepDetachVdi struct { func (self *StepDetachVdi) Run(state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) + client := state.Get("client").(xsclient.XenAPIClient) var vdiUuid string diff --git a/builder/xenserver/common/step_export.go b/builder/xenserver/common/step_export.go index e4101f7d..b5864b9b 100644 --- a/builder/xenserver/common/step_export.go +++ b/builder/xenserver/common/step_export.go @@ -95,22 +95,38 @@ func (StepExport) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Step: export artifact") - compress_option_xe := "compress=false" - compress_option_url := "" - switch config.Format { case "none": ui.Say("Skipping export") return multistep.ActionContinue - case "xva_compressed": - compress_option_xe = "compress=true" - compress_option_url = "use_compression=true&" - fallthrough + case "xva_template": + ui.Say("Converting VM to template before export") + err = instance.SetIsATemplate(true) + if err != nil { + ui.Error(fmt.Sprintf("Error converting VM to a template prior to export: %s", err.Error())) + return multistep.ActionHalt + } + if !config.KeepTemplateVIFs { + ui.Say("Destroying VM network interfaces for template export") + vifs, err := instance.GetVIFs() + if err != nil { + ui.Error(fmt.Sprintf("Error getting VIFs: %s", err.Error())) + return multistep.ActionHalt + } + for _, vif := range vifs { + err = vif.Destroy() + if err != nil { + ui.Error(fmt.Sprintf("Error destroying VIF: %s", err.Error())) + return multistep.ActionHalt + } + } + } case "xva": // export the VM export_filename := fmt.Sprintf("%s/%s.xva", config.OutputDir, config.VMName) + ui.Say("Exporting to: " + export_filename) use_xe := os.Getenv("USE_XE") == "1" if xe, e := exec.LookPath("xe"); e == nil && use_xe { @@ -122,7 +138,7 @@ func (StepExport) Run(state multistep.StateBag) multistep.StepAction { "-pw", client.Password, "vm-export", "vm="+instance_uuid, - compress_option_xe, + "compress=true", "filename="+export_filename, ) @@ -130,9 +146,8 @@ func (StepExport) Run(state multistep.StateBag) multistep.StepAction { err = cmd.Run() } else { - export_url := fmt.Sprintf("https://%s/export?%suuid=%s&session_id=%s", + export_url := fmt.Sprintf("https://%s/export?use_compression=true&uuid=%s&session_id=%s", client.Host, - compress_option_url, instance_uuid, client.Session.(string), ) @@ -210,6 +225,7 @@ func (StepExport) Run(state multistep.StateBag) multistep.StepAction { } disk_export_filename := fmt.Sprintf("%s/%s%s", config.OutputDir, disk_uuid, suffix) + ui.Say("Exporting to: " + disk_export_filename) ui.Say("Getting VDI " + disk_export_url) err = downloadFile(disk_export_url, disk_export_filename, ui) diff --git a/builder/xenserver/iso/builder.go b/builder/xenserver/iso/builder.go index 03f26cf7..121b0fc1 100644 --- a/builder/xenserver/iso/builder.go +++ b/builder/xenserver/iso/builder.go @@ -317,6 +317,7 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa &xscommon.StepDetachVdi{ VdiUuidKey: "floppy_vdi_uuid", }, + new(xscommon.StepConfigureDiskDrives), new(xscommon.StepExport), } diff --git a/builder/xenserver/xva/builder.go b/builder/xenserver/xva/builder.go index 22be43c0..2e09eae8 100644 --- a/builder/xenserver/xva/builder.go +++ b/builder/xenserver/xva/builder.go @@ -180,6 +180,7 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa &xscommon.StepDetachVdi{ VdiUuidKey: "tools_vdi_uuid", }, + new(xscommon.StepConfigureDiskDrives), new(xscommon.StepExport), } diff --git a/builder/xenserver/xva/step_import_instance.go b/builder/xenserver/xva/step_import_instance.go index 0a734b73..5510d620 100644 --- a/builder/xenserver/xva/step_import_instance.go +++ b/builder/xenserver/xva/step_import_instance.go @@ -60,7 +60,13 @@ func (self *stepImportInstance) Run(state multistep.StateBag) multistep.StepActi } state.Put("instance_uuid", instanceId) - instance.SetDescription(config.VMDescription) + err = instance.SetNameLabel(config.VMName) + if err != nil { + ui.Error(fmt.Sprintf("Error setting VM name: %s", err.Error())) + return multistep.ActionHalt + } + + err = instance.SetDescription(config.VMDescription) if err != nil { ui.Error(fmt.Sprintf("Error setting VM description: %s", err.Error())) return multistep.ActionHalt diff --git a/docs/builders/xenserver-iso.html.markdown b/docs/builders/xenserver-iso.html.markdown index bc7c00e3..853834aa 100644 --- a/docs/builders/xenserver-iso.html.markdown +++ b/docs/builders/xenserver-iso.html.markdown @@ -100,6 +100,9 @@ each category, the available options are alphabetized and described. 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_drives` (integer) - How many DVD drives to keep on the VM when exporting. + Default is 0. + * `disk_size` (integer) - The size, in megabytes, of the hard disk to create for the VM. By default, this is 40000 (about 40 GB). @@ -113,7 +116,7 @@ each category, the available options are alphabetized and described. 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", "vdi_raw" or "none", this specifies the +* `format` (string) - Either "xva", "xva_template", "vdi_raw" or "none", 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. Set to "none" to export nothing; this is only useful with "keep_vm" set to "always" or "on_success". @@ -144,6 +147,10 @@ each category, the available options are alphabetized and described. 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. +* `keep_template_vifs` (boolean) - Whether you want to keep VIFs on the VM prior to + exporting the XVA template. Removing them may make the template more generic and + reusable. Default is "false". + * `keep_vm` (string) - Determine when to keep the VM and when to clean it up. This can be "always", "never" or "on_success". By default this is "never", and Packer always deletes the VM regardless of whether the process succeeded and an artifact diff --git a/vendor/github.com/mitchellh/packer/helper/communicator/step_connect_winrm.go b/vendor/github.com/mitchellh/packer/helper/communicator/step_connect_winrm.go index 49936ad7..2a4cdd2e 100644 --- a/vendor/github.com/mitchellh/packer/helper/communicator/step_connect_winrm.go +++ b/vendor/github.com/mitchellh/packer/helper/communicator/step_connect_winrm.go @@ -91,18 +91,20 @@ func (s *StepConnectWinRM) waitForWinRM(state multistep.StateBag, cancel <-chan case <-time.After(5 * time.Second): } - host, err := s.Host(state) - if err != nil { - log.Printf("[DEBUG] Error getting WinRM host: %s", err) - continue + var host string + if hostRaw, ok := state.GetOk("instance_ssh_address"); ok { + host = hostRaw.(string) + } else { + log.Println("[INFO] Error getting WinRM host. Exiting loop.") + return nil, errors.New("Error getting WinRM host") } + + var err error port := s.Config.WinRMPort - if s.WinRMPort != nil { - port, err = s.WinRMPort(state) - if err != nil { - log.Printf("[DEBUG] Error getting WinRM port: %s", err) - continue - } + if port == 0 && s.Config.WinRMUseSSL { + port = 5986 + } else if port == 0 { + port = 5985 } user := s.Config.WinRMUser