From 515072d124da42902ef0baeb37604cef4061a168 Mon Sep 17 00:00:00 2001 From: Abdul Charif Date: Wed, 11 Sep 2019 18:53:36 -0400 Subject: [PATCH 01/13] use socat to support XS 8.0 --- builder/xenserver/common/step_get_vnc_port.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/builder/xenserver/common/step_get_vnc_port.go b/builder/xenserver/common/step_get_vnc_port.go index 88afba2f..ea07e378 100644 --- a/builder/xenserver/common/step_get_vnc_port.go +++ b/builder/xenserver/common/step_get_vnc_port.go @@ -2,9 +2,10 @@ package common import ( "fmt" + "strconv" + "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "strconv" ) type StepGetVNCPort struct{} @@ -15,22 +16,28 @@ func (self *StepGetVNCPort) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Step: forward the instances VNC port over SSH") domid := state.Get("domid").(string) - cmd := fmt.Sprintf("xenstore-read /local/domain/%s/console/vnc-port", domid) + // cmd := fmt.Sprintf("xenstore-read /local/domain/%s/console/vnc-port", domid) - remote_vncport, err := ExecuteHostSSHCmd(state, cmd) + expectedport := fmt.Sprintf("590%s", domid) + cmd1 := fmt.Sprintf("nohup socat -d -d -lf /tmp/socat-%s TCP4-LISTEN:%s,reuseaddr,fork,tcpwrap=socat,allow-table=all UNIX-CONNECT:/var/run/xen/vnc-%s &>/dev/null &", expectedport, expectedport, domid) + ui.Say(fmt.Sprintf("nohup socat -d -d -lf /tmp/socat-%s TCP4-LISTEN:%s,reuseaddr,fork,tcpwrap=socat,allow-table=all UNIX-CONNECT:/var/run/xen/vnc-%s &>/dev/null &", expectedport, expectedport, domid)) + remote_vncport, err := ExecuteHostSSHCmd(state, cmd1) if err != nil { ui.Error(fmt.Sprintf("Unable to get VNC port (is the VM running?): %s", err.Error())) return multistep.ActionHalt } - + remote_vncport = expectedport remote_port, err := strconv.ParseUint(remote_vncport, 10, 16) + ui.Say(fmt.Sprint("Exposed VNC port on: %s and using rmote port %d", expectedport, remote_port)) + if err != nil { ui.Error(fmt.Sprintf("Unable to convert '%s' to an int", remote_vncport)) ui.Error(err.Error()) return multistep.ActionHalt } + ui.Say(fmt.Sprint("instance_vnc_port %d", remote_port)) state.Put("instance_vnc_port", uint(remote_port)) return multistep.ActionContinue @@ -47,4 +54,4 @@ func InstanceVNCPort(state multistep.StateBag) (uint, 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 -} +} \ No newline at end of file From e0d7f246c9c4654cfd0f4638966f2b96b6466730 Mon Sep 17 00:00:00 2001 From: Abdul Charif Date: Thu, 31 Oct 2019 15:02:50 -0400 Subject: [PATCH 02/13] fixes out of range port number --- builder/xenserver/common/step_get_vnc_port.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/xenserver/common/step_get_vnc_port.go b/builder/xenserver/common/step_get_vnc_port.go index ea07e378..02643bdf 100644 --- a/builder/xenserver/common/step_get_vnc_port.go +++ b/builder/xenserver/common/step_get_vnc_port.go @@ -18,7 +18,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) - expectedport := fmt.Sprintf("590%s", domid) + expectedport := fmt.Sprintf("59%s", domid) cmd1 := fmt.Sprintf("nohup socat -d -d -lf /tmp/socat-%s TCP4-LISTEN:%s,reuseaddr,fork,tcpwrap=socat,allow-table=all UNIX-CONNECT:/var/run/xen/vnc-%s &>/dev/null &", expectedport, expectedport, domid) ui.Say(fmt.Sprintf("nohup socat -d -d -lf /tmp/socat-%s TCP4-LISTEN:%s,reuseaddr,fork,tcpwrap=socat,allow-table=all UNIX-CONNECT:/var/run/xen/vnc-%s &>/dev/null &", expectedport, expectedport, domid)) remote_vncport, err := ExecuteHostSSHCmd(state, cmd1) From c6c9135aba65451962e727b5b245b72553542426 Mon Sep 17 00:00:00 2001 From: GodofGurus Date: Mon, 4 Nov 2019 11:32:32 -0500 Subject: [PATCH 03/13] get updates from ctlam fork (#1) * Added fix for build failing due to missing go-vnc * Add set name label to XVA import. Add forgotten err = ... for set description * Add ui.Say xva export location * Added step to configure number of disk drives * Change ISO VDI name from Packer-disk to VMName 0 * Ran gofmt -s -w . * Added option to convert VM to template * Added iso_name uuid:// and documentation * Ran gofmt -s -w . * Allow XVA builder to use templates already on XenCenter * Convert uploaded XVAs from template to VM * Remove destroy template VIFs * Added destroy vifs step * Ran gofmt -s -w . * Fixed merge error * Fix WinRM wrong host and port. Fix require ssh_username when using WinRM * Added documentation for format=xva_compressed * Add vdi_vhd to docs * Moved configure networking code from ISO to common * Ran gofmt -s -w . * Default to use source_path if source_template fails * Use iso_url as fallback if iso_name fails * Add missing period * Moved getUuid code into Run * Allow clone_template to use uuid:// * Changed disk to disc, fixed infinite loop bugs * Added XVA docs, updated ISO docs * Added cleanup for step_import_instance * Added cleanup to step_instantiate_template * Added functionality to execute scripts on the XenServer host prior to booting VM * Add preboot_host_scripts to XVA docs * Update godeps, add to vendor * gofmt -s -w . * move go get go-vnc to godeps and vendor * Added go get gox * Modified to allow for pre boot and pre export scripts. * update docs, fix typo * Added hacky fix for XenServer reboot hangs * Fix diskspace issue where uploaded VDIs are not cleaned up upon a successful run * Comment out config * Remove commented out code * Merging the winrmcp fix from the packer main branch to make the packer-ansible-windows integration work on xenserver xva target. Otherwise, powershell bails out on apparently corrupted path during the winrmcp. * Enable dynamic root passwords in boot commands * Add support to source user variables in boot_command * Add support to source user variables in boot_command in packer config file * Attempt to backport the file download for winrm * Accessing the config directly from struct as the old preferred way * Directly copy the default variables because our old version of winrm doesn't have the new params * Fix syntax error * Hacking a workaround in packer to use a default VNC value of 5900 (which seems to be all the cases) in case we cannot read that from xenstore-read command. XS7.5 and XS7.6 has stopped supporting it --- Godeps/Godeps.json | 16 +- build.sh | 3 + builder/xenserver/common/common_config.go | 31 +- .../common/step_configure_disc_drives.go | 116 +++++ .../common/step_configure_networking.go | 111 ++++ .../common/step_convert_to_template.go | 39 ++ builder/xenserver/common/step_destroy_vifs.go | 47 ++ .../common/step_execute_host_scripts.go | 98 ++++ builder/xenserver/common/step_export.go | 2 + builder/xenserver/common/step_find_vdi.go | 50 +- builder/xenserver/common/step_get_vnc_port.go | 4 +- .../common/step_type_boot_command.go | 2 + builder/xenserver/common/step_upload_vdi.go | 5 - builder/xenserver/iso/builder.go | 49 +- builder/xenserver/iso/step_create_instance.go | 111 +--- builder/xenserver/xva/builder.go | 24 +- builder/xenserver/xva/step_import_instance.go | 95 +++- .../xva/step_instantiate_template.go | 144 ++++++ docs/builders/xenserver-iso.html.markdown | 90 +++- docs/builders/xenserver-xva.html.markdown | 279 ++++++++++ .../github.com/dongyuzheng/easyssh/.gitignore | 29 ++ .../github.com/dongyuzheng/easyssh/README.md | 37 ++ .../github.com/dongyuzheng/easyssh/easyssh.go | 244 +++++++++ vendor/github.com/mitchellh/go-vnc/LICENSE | 21 + vendor/github.com/mitchellh/go-vnc/README.md | 16 + vendor/github.com/mitchellh/go-vnc/client.go | 482 ++++++++++++++++++ .../mitchellh/go-vnc/client_auth.go | 124 +++++ vendor/github.com/mitchellh/go-vnc/color.go | 6 + .../github.com/mitchellh/go-vnc/encoding.go | 69 +++ .../mitchellh/go-vnc/pixel_format.go | 151 ++++++ vendor/github.com/mitchellh/go-vnc/pointer.go | 16 + .../mitchellh/go-vnc/server_messages.go | 192 +++++++ .../packer/communicator/ssh/communicator.go | 12 +- .../packer/communicator/winrm/communicator.go | 83 ++- .../helper/communicator/step_connect_winrm.go | 26 +- .../packer-community/winrmcp/winrmcp/cp.go | 2 +- vendor/github.com/pkg/errors/.travis.yml | 3 +- vendor/github.com/pkg/errors/README.md | 2 + vendor/github.com/pkg/errors/errors.go | 161 ++++-- vendor/github.com/pkg/errors/errors_test.go | 159 ------ vendor/github.com/pkg/errors/example_test.go | 152 ------ vendor/github.com/pkg/errors/format_test.go | 153 ------ vendor/github.com/pkg/errors/stack.go | 13 + vendor/github.com/pkg/errors/stack_test.go | 295 ----------- 44 files changed, 2772 insertions(+), 992 deletions(-) create mode 100644 builder/xenserver/common/step_configure_disc_drives.go create mode 100644 builder/xenserver/common/step_configure_networking.go create mode 100644 builder/xenserver/common/step_convert_to_template.go create mode 100644 builder/xenserver/common/step_destroy_vifs.go create mode 100644 builder/xenserver/common/step_execute_host_scripts.go create mode 100644 builder/xenserver/xva/step_instantiate_template.go create mode 100644 docs/builders/xenserver-xva.html.markdown create mode 100644 vendor/github.com/dongyuzheng/easyssh/.gitignore create mode 100644 vendor/github.com/dongyuzheng/easyssh/README.md create mode 100644 vendor/github.com/dongyuzheng/easyssh/easyssh.go create mode 100644 vendor/github.com/mitchellh/go-vnc/LICENSE create mode 100644 vendor/github.com/mitchellh/go-vnc/README.md create mode 100644 vendor/github.com/mitchellh/go-vnc/client.go create mode 100644 vendor/github.com/mitchellh/go-vnc/client_auth.go create mode 100644 vendor/github.com/mitchellh/go-vnc/color.go create mode 100644 vendor/github.com/mitchellh/go-vnc/encoding.go create mode 100644 vendor/github.com/mitchellh/go-vnc/pixel_format.go create mode 100644 vendor/github.com/mitchellh/go-vnc/pointer.go create mode 100644 vendor/github.com/mitchellh/go-vnc/server_messages.go delete mode 100644 vendor/github.com/pkg/errors/errors_test.go delete mode 100644 vendor/github.com/pkg/errors/example_test.go delete mode 100644 vendor/github.com/pkg/errors/format_test.go delete mode 100644 vendor/github.com/pkg/errors/stack_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 7d833ee1..ca92d14e 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,7 +1,7 @@ { "ImportPath": "github.com/xenserver/packer-builder-xenserver", "GoVersion": "go1.6", - "GodepVersion": "v74", + "GodepVersion": "v75", "Packages": [ "./..." ], @@ -11,6 +11,11 @@ "Comment": "v0.10.0-19-gf3cfb45", "Rev": "f3cfb454f4c209e6668c95216c4744b8fddb2356" }, + { + "ImportPath": "github.com/dongyuzheng/easyssh", + "Comment": "0.1-47-g9302b2c", + "Rev": "9302b2c6dc62b655f41767906ec79638db75ba40" + }, { "ImportPath": "github.com/dylanmei/iso8601", "Comment": "v0.1.0", @@ -64,6 +69,10 @@ "ImportPath": "github.com/mitchellh/go-fs/fat", "Rev": "a34c1b9334e86165685a9449b782f20465eb8c69" }, + { + "ImportPath": "github.com/mitchellh/go-vnc", + "Rev": "723ed9867aed0f3209a81151e52ddc61681f0b01" + }, { "ImportPath": "github.com/mitchellh/iochan", "Rev": "87b45ffd0e9581375c491fef3d32130bb15c5bd7" @@ -161,6 +170,11 @@ "ImportPath": "github.com/packer-community/winrmcp/winrmcp", "Rev": "f1bcf36a69fa2945e65dd099eee11b560fbd3346" }, + { + "ImportPath": "github.com/pkg/errors", + "Comment": "v0.8.0-2-g248dadf", + "Rev": "248dadf4e9068a0b3e79f02ed0a610d935de5302" + }, { "ImportPath": "github.com/pkg/sftp", "Rev": "e84cc8c755ca39b7b64f510fe1fffc1b51f210a5" diff --git a/build.sh b/build.sh index efc343e7..83a77bd5 100755 --- a/build.sh +++ b/build.sh @@ -15,6 +15,9 @@ rm -rf pkg/* rm -rf $GOPATH/pkg/* mkdir -p bin/ +# Fix for ./build.sh: gox: command not found +go get github.com/mitchellh/gox + gox \ -os="${XC_OS}" \ -arch="${XC_ARCH}" \ diff --git a/builder/xenserver/common/common_config.go b/builder/xenserver/common/common_config.go index 26ee51ff..fc26bccc 100644 --- a/builder/xenserver/common/common_config.go +++ b/builder/xenserver/common/common_config.go @@ -14,9 +14,10 @@ import ( ) type CommonConfig struct { - Username string `mapstructure:"remote_username"` - Password string `mapstructure:"remote_password"` - HostIp string `mapstructure:"remote_host"` + Username string `mapstructure:"remote_username"` + Password string `mapstructure:"remote_password"` + HostIp string `mapstructure:"remote_host"` + XenSSHPort uint `mapstructure:"remote_ssh_port"` VMName string `mapstructure:"vm_name"` VMDescription string `mapstructure:"vm_description"` @@ -28,8 +29,10 @@ type CommonConfig struct { HostPortMin uint `mapstructure:"host_port_min"` HostPortMax uint `mapstructure:"host_port_max"` - BootCommand []string `mapstructure:"boot_command"` - ShutdownCommand string `mapstructure:"shutdown_command"` + PreBootHostScripts []string `mapstructure:"pre_boot_host_scripts"` + PreExportHostScripts []string `mapstructure:"pre_export_host_scripts"` + BootCommand []string `mapstructure:"boot_command"` + ShutdownCommand string `mapstructure:"shutdown_command"` RawBootWait string `mapstructure:"boot_wait"` BootWait time.Duration @@ -40,6 +43,8 @@ type CommonConfig struct { HTTPPortMin uint `mapstructure:"http_port_min"` HTTPPortMax uint `mapstructure:"http_port_max"` + Communicator string `mapstructure:"communicator"` + // SSHHostPortMin uint `mapstructure:"ssh_host_port_min"` // SSHHostPortMax uint `mapstructure:"ssh_host_port_max"` SSHKeyPath string `mapstructure:"ssh_key_path"` @@ -51,6 +56,10 @@ type CommonConfig struct { RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"` SSHWaitTimeout time.Duration + ConvertToTemplate bool `mapstructure:"convert_to_template"` + DestroyVIFs bool `mapstructure:"destroy_vifs"` + DiscDrives int `mapstructure:"disc_drives"` + OutputDir string `mapstructure:"output_directory"` Format string `mapstructure:"format"` KeepVM string `mapstructure:"keep_vm"` @@ -63,6 +72,10 @@ func (c *CommonConfig) Prepare(ctx *interpolate.Context, pc *common.PackerConfig // Set default values + if c.XenSSHPort == 0 { + c.XenSSHPort = 22 + } + if c.HostPortMin == 0 { c.HostPortMin = 5900 } @@ -175,7 +188,7 @@ func (c *CommonConfig) Prepare(ctx *interpolate.Context, pc *common.PackerConfig } */ - if c.SSHUser == "" { + if c.SSHUser == "" && c.Communicator != "winrm" { errs = append(errs, errors.New("An ssh_username must be specified.")) } @@ -184,10 +197,14 @@ func (c *CommonConfig) Prepare(ctx *interpolate.Context, pc *common.PackerConfig errs = append(errs, fmt.Errorf("Failed to parse ssh_wait_timeout: %s", err)) } + if c.DiscDrives < 0 { + errs = append(errs, errors.New("disc_drives greater than or equal to 0.")) + } + switch c.Format { case "xva", "xva_compressed", "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_compressed', 'vdi_raw', 'vdi_vhd', 'none'")) } switch c.KeepVM { diff --git a/builder/xenserver/common/step_configure_disc_drives.go b/builder/xenserver/common/step_configure_disc_drives.go new file mode 100644 index 00000000..1a3350dc --- /dev/null +++ b/builder/xenserver/common/step_configure_disc_drives.go @@ -0,0 +1,116 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/nilshell/xmlrpc" + xsclient "github.com/xenserver/go-xenserver-client" + "strings" +) + +type StepConfigureDiscDrives struct{} + +func (self *StepConfigureDiscDrives) 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 disc 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_disc_drives int = 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_disc_drives < config.DiscDrives { + ui.Say("Ejecting disc 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_disc_drives++ + } else { + ui.Say("Destroying excess disc 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_disc_drives < config.DiscDrives { + 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" + var failures int = 0 + for current_number_of_disc_drives < config.DiscDrives { + ui.Say("Creating disc drive") + + result := xsclient.APIResult{} + err := client.APICall(&result, "VBD.create", vbd_rec) + + if err != nil { + failures++ + if failures < 3 { + ui.Error("Error creating disc drive. Retrying...") + continue + } else { + ui.Error("Failed to create disc drive after 3 tries.") + return multistep.ActionHalt + } + } + + vbd_ref := result.Value.(string) + + result = xsclient.APIResult{} + err = client.APICall(&result, "VBD.get_uuid", vbd_ref) + + if err != nil { + failures++ + if failures < 3 { + ui.Error("Error verifying disc drive. Retrying...") + continue + } else { + ui.Error("Failed to create disc drive after 3 tries.") + return multistep.ActionHalt + } + } + + current_number_of_disc_drives++ + } + } + + return multistep.ActionContinue +} + +func (self *StepConfigureDiscDrives) Cleanup(state multistep.StateBag) {} diff --git a/builder/xenserver/common/step_configure_networking.go b/builder/xenserver/common/step_configure_networking.go new file mode 100644 index 00000000..a4555355 --- /dev/null +++ b/builder/xenserver/common/step_configure_networking.go @@ -0,0 +1,111 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + xsclient "github.com/xenserver/go-xenserver-client" +) + +type StepConfigureNetworking struct{} + +func (self *StepConfigureNetworking) Run(state multistep.StateBag) multistep.StepAction { + config := state.Get("commonconfig").(CommonConfig) + ui := state.Get("ui").(packer.Ui) + client := state.Get("client").(xsclient.XenAPIClient) + + ui.Say("Step: Configure Networking") + + 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 + } + + vifs, err := instance.GetVIFs() + if err != nil { + ui.Error(fmt.Sprintf("Error getting VIFs: %s", err.Error())) + return multistep.ActionHalt + } + + if len(vifs) != 0 { + ui.Say("VM already has VIFs. Skipping step.") + return multistep.ActionContinue + } + + ui.Say("VM has no VIFs. Automatically configuring networking.") + + var network *xsclient.Network + + if len(config.NetworkNames) == 0 { + // No network has be specified. Use the management interface + network = new(xsclient.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 + } + + _, err = instance.ConnectNetwork(network, "0") + + if err != nil { + ui.Say(err.Error()) + } + + } else { + // Look up each network by it's name label + for i, networkNameLabel := range config.NetworkNames { + networks, err := client.GetNetworkByNameLabel(networkNameLabel) + + 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.", networkNameLabel)) + 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.", networkNameLabel)) + return multistep.ActionHalt + } + + //we need the VIF index string + vifIndexString := fmt.Sprintf("%d", i) + _, err = instance.ConnectNetwork(networks[0], vifIndexString) + + if err != nil { + ui.Say(err.Error()) + } + } + } + + return multistep.ActionContinue +} + +func (self *StepConfigureNetworking) Cleanup(state multistep.StateBag) {} diff --git a/builder/xenserver/common/step_convert_to_template.go b/builder/xenserver/common/step_convert_to_template.go new file mode 100644 index 00000000..dc565a4c --- /dev/null +++ b/builder/xenserver/common/step_convert_to_template.go @@ -0,0 +1,39 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + xsclient "github.com/xenserver/go-xenserver-client" +) + +type StepConvertToTemplate struct{} + +func (self *StepConvertToTemplate) Run(state multistep.StateBag) multistep.StepAction { + config := state.Get("commonconfig").(CommonConfig) + if !config.ConvertToTemplate { + return multistep.ActionContinue + } + + ui := state.Get("ui").(packer.Ui) + client := state.Get("client").(xsclient.XenAPIClient) + + ui.Say("Step: Convert to template") + + 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 + } + + err = instance.SetIsATemplate(true) + if err != nil { + ui.Error(fmt.Sprintf("Error converting VM to a template: %s", err.Error())) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (self *StepConvertToTemplate) Cleanup(state multistep.StateBag) {} diff --git a/builder/xenserver/common/step_destroy_vifs.go b/builder/xenserver/common/step_destroy_vifs.go new file mode 100644 index 00000000..0ebf109f --- /dev/null +++ b/builder/xenserver/common/step_destroy_vifs.go @@ -0,0 +1,47 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + xsclient "github.com/xenserver/go-xenserver-client" +) + +type StepDestroyVIFs struct{} + +func (self *StepDestroyVIFs) Run(state multistep.StateBag) multistep.StepAction { + config := state.Get("commonconfig").(CommonConfig) + if !config.DestroyVIFs { + return multistep.ActionContinue + } + + ui := state.Get("ui").(packer.Ui) + client := state.Get("client").(xsclient.XenAPIClient) + + ui.Say("Step: Destroy VIFs") + + 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 + } + + 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 + } + } + + return multistep.ActionContinue +} + +func (self *StepDestroyVIFs) Cleanup(state multistep.StateBag) {} diff --git a/builder/xenserver/common/step_execute_host_scripts.go b/builder/xenserver/common/step_execute_host_scripts.go new file mode 100644 index 00000000..d166f4de --- /dev/null +++ b/builder/xenserver/common/step_execute_host_scripts.go @@ -0,0 +1,98 @@ +package common + +import ( + "fmt" + "github.com/dongyuzheng/easyssh" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "path/filepath" +) + +type StepExecuteHostScripts struct { + ScriptType string + LocalScripts []string + HostScriptFolderPath string +} + +func (self *StepExecuteHostScripts) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + config := state.Get("commonconfig").(CommonConfig) + + ui.Say(fmt.Sprintf("Step: Execute %s XenServer host scripts", self.ScriptType)) + + if len(self.LocalScripts) == 0 { + ui.Say("No scripts to execute") + return multistep.ActionContinue + } + + ssh := &easyssh.MakeConfig{ + Server: config.HostIp, + Port: fmt.Sprintf("%d", config.XenSSHPort), + User: config.Username, + Password: config.Password, + OutputHandler: func(s string) { ui.Say(s) }, + } + + uuid := state.Get("instance_uuid").(string) + self.HostScriptFolderPath = fmt.Sprintf("/tmp/packer_scripts_%s_%s", uuid, self.ScriptType) + + ui.Say(fmt.Sprintf("Creating script folder on XenServer host: %s", self.HostScriptFolderPath)) + + cmd := fmt.Sprintf("mkdir -p '%s'", self.HostScriptFolderPath) + _, sessionErr, err := ssh.Exec(cmd) + if err != nil { + ui.Error(fmt.Sprintf("Error executing SSH command: %s", err.Error())) + return multistep.ActionHalt + } else if sessionErr != nil { + ui.Error(fmt.Sprintf("Failed to create script folder: %s\n%s", cmd, sessionErr.Error())) + return multistep.ActionHalt + } + + for _, script := range self.LocalScripts { + scriptBasename := filepath.Base(script) + scriptPath := fmt.Sprintf("%s/%s", self.HostScriptFolderPath, scriptBasename) + + err = ssh.Upload(script, scriptPath) + if err != nil { + ui.Error(fmt.Sprintf("Error uploading script: %s\n%s", script, err.Error())) + return multistep.ActionHalt + } + + ui.Say(fmt.Sprintf("Executing %s ...", scriptBasename)) + cmd = fmt.Sprintf("chmod +x '%s' && '%s' '%s'", scriptPath, scriptPath, uuid) + _, sessionErr, err := ssh.Exec(cmd) + if err != nil { + ui.Error(fmt.Sprintf("Error executing SSH command: %s", err.Error())) + return multistep.ActionHalt + } else if sessionErr != nil { + ui.Error(fmt.Sprintf("Failed to execute script: %s\n%s", cmd, sessionErr.Error())) + return multistep.ActionHalt + } + } + + return multistep.ActionContinue +} + +func (self *StepExecuteHostScripts) Cleanup(state multistep.StateBag) { + ui := state.Get("ui").(packer.Ui) + ui.Say(fmt.Sprintf("Cleaning up %s host scripts", self.ScriptType)) + if self.HostScriptFolderPath == "" { + ui.Say("No scripts to cleanup") + return + } + config := state.Get("commonconfig").(CommonConfig) + ssh := &easyssh.MakeConfig{ + Server: config.HostIp, + Port: fmt.Sprintf("%d", config.XenSSHPort), + User: config.Username, + Password: config.Password, + } + ui.Say(fmt.Sprintf("Deleting script folder: %s", self.HostScriptFolderPath)) + cmd := fmt.Sprintf("rm -rf '%s'", self.HostScriptFolderPath) + _, sessionErr, err := ssh.Exec(cmd) + if err != nil { + ui.Error(fmt.Sprintf("Error executing SSH command: %s", err.Error())) + } else if sessionErr != nil { + ui.Error(fmt.Sprintf("Failed to delete scripts folder: %s\n%s", cmd, sessionErr.Error())) + } +} diff --git a/builder/xenserver/common/step_export.go b/builder/xenserver/common/step_export.go index d0083e04..8b7f5b95 100644 --- a/builder/xenserver/common/step_export.go +++ b/builder/xenserver/common/step_export.go @@ -152,6 +152,7 @@ func (StepExport) Run(state multistep.StateBag) multistep.StepAction { // 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 { @@ -251,6 +252,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/common/step_find_vdi.go b/builder/xenserver/common/step_find_vdi.go index 34912317..1f48b9d4 100644 --- a/builder/xenserver/common/step_find_vdi.go +++ b/builder/xenserver/common/step_find_vdi.go @@ -8,9 +8,17 @@ import ( ) type StepFindVdi struct { - VdiName string - ImagePathFunc func() string - VdiUuidKey string + VdiName string + VdiUuidKey string + PreviousResult string + PreviousStepAction multistep.StepAction + ErrorFunc func(errString string) multistep.StepAction +} + +func (self *StepFindVdi) ErrorHandler(msg string) multistep.StepAction { + self.PreviousResult = "FAILURE" + self.PreviousStepAction = self.ErrorFunc(msg) + return self.PreviousStepAction } func (self *StepFindVdi) Run(state multistep.StateBag) multistep.StepAction { @@ -22,27 +30,47 @@ func (self *StepFindVdi) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } + if self.PreviousResult != "" { + return self.PreviousStepAction + } + + self.PreviousResult = "SUCCESS" + self.PreviousStepAction = multistep.ActionContinue + + if self.ErrorFunc == nil { + self.ErrorFunc = func(errString string) multistep.StepAction { + ui.Error(errString) + return multistep.ActionHalt + } + } + + if len(self.VdiName) >= 7 && self.VdiName[:7] == "uuid://" { + vdiUuid := self.VdiName[7:] + _, err := client.GetVdiByUuid(vdiUuid) + if err != nil { + return self.ErrorHandler(fmt.Sprintf("Unable to get VDI from UUID '%s': %s", vdiUuid, err.Error())) + } + state.Put(self.VdiUuidKey, vdiUuid) + return self.PreviousStepAction + } + vdis, err := client.GetVdiByNameLabel(self.VdiName) switch { case len(vdis) == 0: - ui.Error(fmt.Sprintf("Couldn't find a VDI named '%s'", self.VdiName)) - return multistep.ActionHalt + return self.ErrorHandler(fmt.Sprintf("Couldn't find a VDI named '%s'", self.VdiName)) case len(vdis) > 1: - ui.Error(fmt.Sprintf("Found more than one VDI with name '%s'. Name must be unique", self.VdiName)) - return multistep.ActionHalt + return self.ErrorHandler(fmt.Sprintf("Found more than one VDI with name '%s'. Name must be unique", self.VdiName)) } vdi := vdis[0] vdiUuid, err := vdi.GetUuid() if err != nil { - ui.Error(fmt.Sprintf("Unable to get UUID of VDI '%s': %s", self.VdiName, err.Error())) - return multistep.ActionHalt + return self.ErrorHandler(fmt.Sprintf("Unable to get UUID of VDI '%s': %s", self.VdiName, err.Error())) } state.Put(self.VdiUuidKey, vdiUuid) - - return multistep.ActionContinue + return self.PreviousStepAction } func (self *StepFindVdi) Cleanup(state multistep.StateBag) {} diff --git a/builder/xenserver/common/step_get_vnc_port.go b/builder/xenserver/common/step_get_vnc_port.go index 02643bdf..dc695a33 100644 --- a/builder/xenserver/common/step_get_vnc_port.go +++ b/builder/xenserver/common/step_get_vnc_port.go @@ -24,7 +24,9 @@ func (self *StepGetVNCPort) Run(state multistep.StateBag) multistep.StepAction { remote_vncport, err := ExecuteHostSSHCmd(state, cmd1) if err != nil { ui.Error(fmt.Sprintf("Unable to get VNC port (is the VM running?): %s", err.Error())) - return multistep.ActionHalt + ui.Error(fmt.Sprintf("XS7.5/7.6 no longer support xenstore-read: Try to use 5900. See https://bugs.xenserver.org/browse/XSO-906")) + remote_vncport = "5900" + //return multistep.ActionHalt } remote_vncport = expectedport remote_port, err := strconv.ParseUint(remote_vncport, 10, 16) diff --git a/builder/xenserver/common/step_type_boot_command.go b/builder/xenserver/common/step_type_boot_command.go index a9207eca..442b2ee6 100644 --- a/builder/xenserver/common/step_type_boot_command.go +++ b/builder/xenserver/common/step_type_boot_command.go @@ -22,6 +22,7 @@ type bootCommandTemplateData struct { Name string HTTPIP string HTTPPort uint + SSHPassword string } type StepTypeBootCommand struct { @@ -83,6 +84,7 @@ func (self *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAct config.VMName, localIp, http_port, + config.SSHPassword, } ui.Say("Typing boot commands over VNC...") diff --git a/builder/xenserver/common/step_upload_vdi.go b/builder/xenserver/common/step_upload_vdi.go index 300a91ad..d0b4ea75 100644 --- a/builder/xenserver/common/step_upload_vdi.go +++ b/builder/xenserver/common/step_upload_vdi.go @@ -80,16 +80,11 @@ func (self *StepUploadVdi) Run(state multistep.StateBag) multistep.StepAction { } func (self *StepUploadVdi) Cleanup(state multistep.StateBag) { - config := state.Get("commonconfig").(CommonConfig) ui := state.Get("ui").(packer.Ui) client := state.Get("client").(xsclient.XenAPIClient) vdiName := self.VdiNameFunc() - if config.ShouldKeepVM(state) { - return - } - vdiUuidRaw, ok := state.GetOk(self.VdiUuidKey) if !ok { // VDI doesn't exist diff --git a/builder/xenserver/iso/builder.go b/builder/xenserver/iso/builder.go index 5ddd91a4..41e15a60 100644 --- a/builder/xenserver/iso/builder.go +++ b/builder/xenserver/iso/builder.go @@ -54,6 +54,7 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error err := hconfig.Decode(&self.config, &hconfig.DecodeOpts{ Interpolate: true, + InterpolateContext: &self.config.ctx, InterpolateFilter: &interpolate.RenderFilter{ Exclude: []string{ "boot_command", @@ -132,7 +133,11 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error errs, fmt.Errorf("Failed to parse install_timeout: %s", err)) } - if self.config.ISOName == "" { + if self.config.ISOUrl == "" && self.config.ISOName == "" { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("Either iso_url or iso_name must be specified")) + } + + if self.config.ISOUrl != "" { // If ISO name is not specified, assume a URL and checksum has been provided. @@ -176,10 +181,6 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error errs, fmt.Errorf("Failed to parse iso_urls[%d]: %s", i, err)) } } - } else { - - // An ISO name has been provided. It should be attached from an available SR. - } if len(errs.Errors) > 0 { @@ -224,6 +225,25 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa }, } + step_find_vdi_iso_name := &xscommon.StepFindVdi{ + VdiName: self.config.ISOName, + VdiUuidKey: "isoname_vdi_uuid", + ErrorFunc: func(errString string) multistep.StepAction { + ui.Error(errString) + ui.Error("Defaulting to use \"iso_url\".") + return multistep.ActionContinue + }, + } + + step_find_vdi_iso_name_runner := &multistep.BasicRunner{Steps: []multistep.Step{ + step_find_vdi_iso_name, + }} + step_find_vdi_iso_name_runner.Run(state) + + if step_find_vdi_iso_name.PreviousResult == "FAILURE" && self.config.ISOUrl == "" { + return nil, errors.New(fmt.Sprintf("Failed to find \"iso_name\": \"%s\" and \"iso_url\" is empty. Aborting.", self.config.ISOName)) + } + steps := []multistep.Step{ &xscommon.StepPrepareOutputDir{ Force: self.config.PackerForce, @@ -266,11 +286,9 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa VdiName: self.config.ToolsIsoName, VdiUuidKey: "tools_vdi_uuid", }, - &xscommon.StepFindVdi{ - VdiName: self.config.ISOName, - VdiUuidKey: "isoname_vdi_uuid", - }, + step_find_vdi_iso_name, new(stepCreateInstance), + new(xscommon.StepConfigureNetworking), &xscommon.StepAttachVdi{ VdiUuidKey: "floppy_vdi_uuid", VdiType: xsclient.Floppy, @@ -287,6 +305,10 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa VdiUuidKey: "tools_vdi_uuid", VdiType: xsclient.CD, }, + &xscommon.StepExecuteHostScripts{ + ScriptType: "pre-boot", + LocalScripts: self.config.PreBootHostScripts, + }, new(xscommon.StepStartVmPaused), new(xscommon.StepSetVmHostSshAddress), new(xscommon.StepGetVNCPort), @@ -332,10 +354,17 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa &xscommon.StepDetachVdi{ VdiUuidKey: "floppy_vdi_uuid", }, + new(xscommon.StepConfigureDiscDrives), + new(xscommon.StepConvertToTemplate), + new(xscommon.StepDestroyVIFs), + &xscommon.StepExecuteHostScripts{ + ScriptType: "pre-export", + LocalScripts: self.config.PreExportHostScripts, + }, new(xscommon.StepExport), } - if self.config.ISOName == "" { + if self.config.ISOName == "" || (self.config.ISOUrl != "" && step_find_vdi_iso_name.PreviousResult == "FAILURE") { steps = append(download_steps, steps...) } diff --git a/builder/xenserver/iso/step_create_instance.go b/builder/xenserver/iso/step_create_instance.go index 8369c527..fb96feba 100644 --- a/builder/xenserver/iso/step_create_instance.go +++ b/builder/xenserver/iso/step_create_instance.go @@ -22,24 +22,40 @@ func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepActi ui.Say("Step: Create Instance") // Get the template to clone from + var template *xsclient.VM + var err error - vms, err := client.GetVMByNameLabel(config.CloneTemplate) + if len(config.CloneTemplate) >= 7 && config.CloneTemplate[:7] == "uuid://" { + templateUuid := config.CloneTemplate[7:] - 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, err = client.GetVMByUuid(templateUuid) + if err != nil { + ui.Error(fmt.Sprintf("Could not get template with UUID '%s': %s", templateUuid, err.Error())) + return multistep.ActionHalt + } + } else { + templates, err := client.GetVMByNameLabel(config.CloneTemplate) + if err != nil { + ui.Error(fmt.Sprintf("Error getting template: %s", err.Error())) + return multistep.ActionHalt + } - template := vms[0] + switch { + case len(templates) == 0: + ui.Error(fmt.Sprintf("Couldn't find a template with the name-label '%s'.", config.CloneTemplate)) + return multistep.ActionHalt + case len(templates) > 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 = templates[0] + } // Clone that VM template instance, err := template.Clone(config.VMName) if err != nil { - ui.Error(fmt.Sprintf("Error cloning VM: %s", err.Error())) + ui.Error(fmt.Sprintf("Error cloning template: %s", err.Error())) return multistep.ActionHalt } self.instance = instance @@ -104,7 +120,7 @@ func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepActi return multistep.ActionHalt } - vdi, err := sr.CreateVdi("Packer-disk", int64(config.DiskSize*1024*1024)) + vdi, err := sr.CreateVdi(config.VMName+" 0", int64(config.DiskSize*1024*1024)) if err != nil { ui.Error(fmt.Sprintf("Unable to create packer disk VDI: %s", err.Error())) return multistep.ActionHalt @@ -117,77 +133,6 @@ func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepActi return multistep.ActionHalt } - // Connect Network - - var network *xsclient.Network - - if len(config.NetworkNames) == 0 { - // No network has be specified. Use the management interface - network = new(xsclient.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 - } - - _, err = instance.ConnectNetwork(network, "0") - - if err != nil { - ui.Say(err.Error()) - } - - } else { - // Look up each network by it's name label - for i, networkNameLabel := range config.NetworkNames { - networks, err := client.GetNetworkByNameLabel(networkNameLabel) - - 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.", networkNameLabel)) - 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.", networkNameLabel)) - return multistep.ActionHalt - } - - //we need the VIF index string - vifIndexString := fmt.Sprintf("%d", i) - _, err = instance.ConnectNetwork(networks[0], vifIndexString) - - 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())) diff --git a/builder/xenserver/xva/builder.go b/builder/xenserver/xva/builder.go index 278bef15..1661808b 100644 --- a/builder/xenserver/xva/builder.go +++ b/builder/xenserver/xva/builder.go @@ -20,8 +20,9 @@ type config struct { common.PackerConfig `mapstructure:",squash"` xscommon.CommonConfig `mapstructure:",squash"` - SourcePath string `mapstructure:"source_path"` - VMMemory uint `mapstructure:"vm_memory"` + SourcePath string `mapstructure:"source_path"` + SourceTemplate string `mapstructure:"source_template"` + VMMemory uint `mapstructure:"vm_memory"` PlatformArgs map[string]string `mapstructure:"platform_args"` @@ -39,6 +40,7 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error err := hconfig.Decode(&self.config, &hconfig.DecodeOpts{ Interpolate: true, + InterpolateContext: &self.config.ctx, InterpolateFilter: &interpolate.RenderFilter{ Exclude: []string{ "boot_command", @@ -52,6 +54,7 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error errs = packer.MultiErrorAppend( errs, self.config.CommonConfig.Prepare(&self.config.ctx, &self.config.PackerConfig)...) + errs = packer.MultiErrorAppend(errs, self.config.SSHConfig.Prepare(&self.config.ctx)...) // Set default values @@ -72,8 +75,8 @@ 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 self.config.SourcePath == "" && self.config.SourceTemplate == "" { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("Either source_path or source_template must be specified")) } if len(errs.Errors) > 0 { @@ -133,7 +136,9 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa VdiName: self.config.ToolsIsoName, VdiUuidKey: "tools_vdi_uuid", }, + new(stepInstantiateTemplate), new(stepImportInstance), + new(xscommon.StepConfigureNetworking), &xscommon.StepAttachVdi{ VdiUuidKey: "floppy_vdi_uuid", VdiType: xsclient.Floppy, @@ -142,6 +147,10 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa VdiUuidKey: "tools_vdi_uuid", VdiType: xsclient.CD, }, + &xscommon.StepExecuteHostScripts{ + ScriptType: "pre-boot", + LocalScripts: self.config.PreBootHostScripts, + }, new(xscommon.StepStartVmPaused), new(xscommon.StepSetVmHostSshAddress), new(xscommon.StepGetVNCPort), @@ -181,6 +190,13 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa &xscommon.StepDetachVdi{ VdiUuidKey: "tools_vdi_uuid", }, + new(xscommon.StepConfigureDiscDrives), + new(xscommon.StepConvertToTemplate), + new(xscommon.StepDestroyVIFs), + &xscommon.StepExecuteHostScripts{ + ScriptType: "pre-export", + LocalScripts: self.config.PreExportHostScripts, + }, new(xscommon.StepExport), } diff --git a/builder/xenserver/xva/step_import_instance.go b/builder/xenserver/xva/step_import_instance.go index 0a734b73..7f306031 100644 --- a/builder/xenserver/xva/step_import_instance.go +++ b/builder/xenserver/xva/step_import_instance.go @@ -12,17 +12,25 @@ import ( type stepImportInstance struct { instance *xsclient.VM - vdi *xsclient.VDI } func (self *stepImportInstance) Run(state multistep.StateBag) multistep.StepAction { - - client := state.Get("client").(xsclient.XenAPIClient) config := state.Get("config").(config) + + if state.Get("instance_uuid") != nil { + return multistep.ActionContinue + } + ui := state.Get("ui").(packer.Ui) + client := state.Get("client").(xsclient.XenAPIClient) ui.Say("Step: Import Instance") + if config.SourcePath == "" { + ui.Error(fmt.Sprintf("Failed to instantiate \"source_template\": \"%s\" and \"source_path\" is empty. Aborting.", config.SourceTemplate)) + return multistep.ActionHalt + } + // find the SR sr, err := config.GetSR(client) if err != nil { @@ -60,7 +68,25 @@ func (self *stepImportInstance) Run(state multistep.StateBag) multistep.StepActi } state.Put("instance_uuid", instanceId) - instance.SetDescription(config.VMDescription) + self.instance, err = client.GetVMByUuid(instanceId) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get VM from UUID '%s': %s", instanceId, err.Error())) + return multistep.ActionHalt + } + + err = self.instance.SetIsATemplate(false) + if err != nil { + ui.Error(fmt.Sprintf("Error converting template to a VM: %s", err.Error())) + return multistep.ActionHalt + } + + err = self.instance.SetNameLabel(config.VMName) + if err != nil { + ui.Error(fmt.Sprintf("Error setting VM name: %s", err.Error())) + return multistep.ActionHalt + } + + err = self.instance.SetDescription(config.VMDescription) if err != nil { ui.Error(fmt.Sprintf("Error setting VM description: %s", err.Error())) return multistep.ActionHalt @@ -72,29 +98,56 @@ func (self *stepImportInstance) Run(state multistep.StateBag) multistep.StepActi } func (self *stepImportInstance) Cleanup(state multistep.StateBag) { - /* - config := state.Get("config").(config) - if config.ShouldKeepVM(state) { - return - } + config := state.Get("config").(config) + if config.ShouldKeepVM(state) { + return + } - ui := state.Get("ui").(packer.Ui) + 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()) - } + // We want to perform a 'xe vm-uninstall' + // Uninstall a VM. This operation will destroy those VDIs that are marked RW and connected to this VM only. + if self.instance != nil { + vbds, err := self.instance.GetVBDs() + if err != nil { + ui.Error(err.Error()) } - if self.vdi != nil { - ui.Say("Destroying VDI") - err := self.vdi.Destroy() + // Destroy VDIs before destroying VM, since vm.Destroy() also destroys VBDs, + // in which case we will be unable to find the VDIs + for _, vbd := range vbds { + vbd_rec, err := vbd.GetRecord() if err != nil { ui.Error(err.Error()) + continue } + if vbd_rec["mode"].(string) == "RW" { + vdi, err := vbd.GetVDI() + if err != nil { + ui.Error(err.Error()) + continue + } + vdi_vbds, err := vdi.GetVBDs() + if err != nil { + ui.Error(err.Error()) + continue + } + // If connected to this VM only + if len(vdi_vbds) <= 1 { + ui.Say("Destroying VDI") + err = vdi.Destroy() + if err != nil { + ui.Error(err.Error()) + } + } + } + } + + ui.Say("Destroying VM") + _ = self.instance.HardShutdown() + err = self.instance.Destroy() + if err != nil { + ui.Error(err.Error()) } - */ + } } diff --git a/builder/xenserver/xva/step_instantiate_template.go b/builder/xenserver/xva/step_instantiate_template.go new file mode 100644 index 00000000..edcc436b --- /dev/null +++ b/builder/xenserver/xva/step_instantiate_template.go @@ -0,0 +1,144 @@ +package xva + +import ( + "fmt" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + xsclient "github.com/xenserver/go-xenserver-client" +) + +type stepInstantiateTemplate struct { + instance *xsclient.VM +} + +func (self *stepInstantiateTemplate) Run(state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(config) + + if config.SourceTemplate == "" { + return multistep.ActionContinue + } + + client := state.Get("client").(xsclient.XenAPIClient) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Step: Instantiate Template") + + var template *xsclient.VM + var err error + + if len(config.SourceTemplate) >= 7 && config.SourceTemplate[:7] == "uuid://" { + templateUuid := config.SourceTemplate[7:] + + template, err = client.GetVMByUuid(templateUuid) + if err != nil { + ui.Error(fmt.Sprintf("Could not get template with UUID '%s': %s", templateUuid, err.Error())) + ui.Error("Defaulting to use \"source_path\".") + return multistep.ActionContinue + } + } else { + templates, err := client.GetVMByNameLabel(config.SourceTemplate) + if err != nil { + ui.Error(fmt.Sprintf("Error getting template: %s", err.Error())) + ui.Error("Defaulting to use \"source_path\".") + return multistep.ActionContinue + } + + switch { + case len(templates) == 0: + ui.Error(fmt.Sprintf("Couldn't find a template with the name-label '%s'.", config.SourceTemplate)) + ui.Error("Defaulting to use \"source_path\".") + return multistep.ActionContinue + case len(templates) > 1: + ui.Error(fmt.Sprintf("Found more than one template with the name '%s'. The name must be unique.", config.SourceTemplate)) + ui.Error("Defaulting to use \"source_path\".") + return multistep.ActionContinue + } + + template = templates[0] + } + + self.instance, err = template.Clone(config.VMName) + if err != nil { + ui.Error(fmt.Sprintf("Error cloning template: %s", err.Error())) + return multistep.ActionHalt + } + + instanceId, err := self.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) + + err = self.instance.SetIsATemplate(false) + if err != nil { + ui.Error(fmt.Sprintf("Error converting template to a VM: %s", err.Error())) + return multistep.ActionHalt + } + + err = self.instance.SetDescription(config.VMDescription) + if err != nil { + ui.Error(fmt.Sprintf("Error setting VM description: %s", err.Error())) + return multistep.ActionHalt + } + + ui.Say(fmt.Sprintf("Instantiated template '%s'", instanceId)) + + return multistep.ActionContinue +} + +func (self *stepInstantiateTemplate) Cleanup(state multistep.StateBag) { + config := state.Get("config").(config) + if config.ShouldKeepVM(state) { + return + } + + ui := state.Get("ui").(packer.Ui) + + // We want to perform a 'xe vm-uninstall' + // Uninstall a VM. This operation will destroy those VDIs that are marked RW and connected to this VM only. + if self.instance != nil { + vbds, err := self.instance.GetVBDs() + if err != nil { + ui.Error(err.Error()) + } + + // Destroy VDIs before destroying VM, since vm.Destroy() also destroys VBDs, + // in which case we will be unable to find the VDIs + for _, vbd := range vbds { + vbd_rec, err := vbd.GetRecord() + if err != nil { + ui.Error(err.Error()) + continue + } + if vbd_rec["mode"].(string) == "RW" { + vdi, err := vbd.GetVDI() + if err != nil { + ui.Error(err.Error()) + continue + } + vdi_vbds, err := vdi.GetVBDs() + if err != nil { + ui.Error(err.Error()) + continue + } + // If connected to this VM only + if len(vdi_vbds) <= 1 { + ui.Say("Destroying VDI") + err = vdi.Destroy() + if err != nil { + ui.Error(err.Error()) + } + } + } + } + + ui.Say("Destroying VM") + _ = self.instance.HardShutdown() + err = self.instance.Destroy() + if err != nil { + ui.Error(err.Error()) + } + } +} diff --git a/docs/builders/xenserver-iso.html.markdown b/docs/builders/xenserver-iso.html.markdown index 8afe06be..f516efc5 100644 --- a/docs/builders/xenserver-iso.html.markdown +++ b/docs/builders/xenserver-iso.html.markdown @@ -54,21 +54,29 @@ 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. +* Either: + + * `iso_name` (string) - Unique name of an ISO available in XenCenter's storage repositories. + Or it can be the UUID of the ISO's VDI prefixed by "uuid://". For example: "uuid://aa2f2c86-b79a-4345-a35f-ce244f43c97a". + If this fails and `iso_url` is given, `iso_url` will be tried. + +* OR: + + * `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. @@ -76,7 +84,7 @@ each category, the available options are alphabetized and described. * `remote_password` (string) - The XenServer password for access to the remote machine. -* `ssh_username` (string) - The username to use to SSH into the machine +* `ssh_username` or `winrm_username` (string) - The username to use to SSH/WinRM into the machine once the OS is installed. ### Optional: @@ -98,7 +106,21 @@ each category, the available options are alphabetized and described. 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. + optimize the virtual hardware to work best with that operating system. Setting + this to a template's UUID is also acceptable: "uuid://c6a9dee0-0069-24a5-d45d-4a4101a9d7f4". + +* `communicator` (string) - Communication protocol with the VM. Can "ssh" + and "winrm" are officially supported. Default is "ssh". + +* `convert_to_template` (boolean) - Whether to convert the VM to a template + prior to exporting. Default is `false`. + +* `destroy_vifs` (boolean) - Whether to destroy VIFs on the VM prior to + exporting. Removing them may make the image more generic and reusable. + Default is `false`. + +* `disc_drives` (integer) - How many DVD drives to keep on the exported VM. + 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,10 +135,10 @@ 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 - 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". +* `format` (string) - Either "xva", "xva_compressed", "vdi_raw", "vdi_vhd" 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". * `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 @@ -172,6 +194,19 @@ each category, the available options are alphabetized and described. } ``` +* `pre_boot_host_scripts` (array of strings) - List of scripts to execute on the + XenServer host prior to booting the VM. The VM's UUID will be passed into each + script as the first parameter. The script can be of any type: bash, python, ruby, etc. + Each script must start with a shebang, ie. #!/usr/bin/env bash. + +* `pre_export_host_scripts` (array of strings) - List of scripts to execute on the + XenServer host prior to exporting the VM. The VM's UUID will be passed into each + script as the first parameter. The script can be of any type: bash, python, ruby, etc. + Each script must start with a shebang, ie. #!/usr/bin/env bash. + +* `remote_ssh_port` (integer) - SSH port of XenServer host. Only useful when using + `pre_boot_host_scripts` or `pre_export_host_scripts`. Default is 22. + * `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. @@ -221,6 +256,19 @@ each category, the available options are alphabetized and described. * `vm_memory` (integer) - The size, in megabytes, of the amount of memory to allocate for the VM. By default, this is 1024 (1 GB). +* `winrm_insecure` (boolean) - Unencrypted WinRM. Default is `false`. + +* `winrm_password` (string) - The password for `winrm_username` to use to + authenticate with WinRM. By default this is the empty string. + +* `winrm_port` (string) - Port for WinRM. Default automatically + set to 5985 or 5986 depending on `winrm_use_ssl`. + +* `winrm_timeout` (string) - The duration to wait for WinRM to become + available. ie. "1800s" + +* `winrm_use_ssl` (boolean) - Use SSL for WinRM. Default is `false`. + ## Differences with other Packer builders Currently the XenServer builder has some quirks when compared with other Packer builders. diff --git a/docs/builders/xenserver-xva.html.markdown b/docs/builders/xenserver-xva.html.markdown new file mode 100644 index 00000000..00997723 --- /dev/null +++ b/docs/builders/xenserver-xva.html.markdown @@ -0,0 +1,279 @@ +--- +layout: "docs" +page_title: "XenServer Builder (from an XVA)" +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 XVA or XVA template. +--- + +# XenServer Builder (from an XVA or XVA template) + +Type: `xenserver-xva` + +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 +XVA or XVA template. + +The builder builds a virtual machine by launching an XVA or XVA template, 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. + +```javascript +{ + "type": "xenserver-xva", + "vm_name": "some-vm", + "vm_description": "Build time: {{isotime}}", + "remote_host": "your-server.example.com", + "remote_username": "root", + "remote_password": "password", + "source_template": "windows-template", + "source_path": "xva/windows.xva", + "communicator": "winrm", + "winrm_username": "packer", + "winrm_password": "packer", + "winrm_timeout": "1800s", + "winrm_use_ssl": true, + "winrm_insecure": true, + "format": "xva_compressed", + "disc_drives": 1 +} +``` + +## 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: + +* One of: + * `source_template` (string) - Unique name of an XVA template available in XenCenter. + Or it can be the UUID of the template prefixed by "uuid://". For example: "uuid://aa2f2c86-b79a-4345-a35f-ce244f43c97a". + If this fails and `source_path` is given, `source_path` will be tried. + + * `source_path` (string) - Path to a XVA or XVA template on your workstation + +* `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` or `winrm_username` (string) - The username to use to SSH/WinRM into the machine. + +### 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. + +* `communicator` (string) - Communication protocol with the VM. Can "ssh" + and "winrm" are officially supported. Default is "ssh". + +* `convert_to_template` (boolean) - Whether to convert the VM to a template + prior to exporting. Default is `false`. + +* `destroy_vifs` (boolean) - Whether to destroy VIFs on the VM prior to + exporting. Removing them may make the image more generic and reusable. + Default is `false`. + +* `disc_drives` (integer) - How many DVD drives to keep on the exported VM. + Default is 0. + +* `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", "xva_compressed", "vdi_raw", "vdi_vhd" 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". + +* `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. + +* `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 + was produced. "always" asks Packer to leave the VM at the end of the process + regardless of success. "on_success" requests that the VM only be cleaned up if an + artifact was produced. The latter is useful for debugging templates that fail. + +* `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" +} +``` + +* `pre_boot_host_scripts` (array of strings) - List of scripts to execute on the + XenServer host prior to booting the VM. The VM's UUID will be passed into each + script as the first parameter. The script can be of any type: bash, python, ruby, etc. + Each script must start with a shebang, ie. #!/usr/bin/env bash. + +* `pre_export_host_scripts` (array of strings) - List of scripts to execute on the + XenServer host prior to exporting the VM. The VM's UUID will be passed into each + script as the first parameter. The script can be of any type: bash, python, ruby, etc. + Each script must start with a shebang, ie. #!/usr/bin/env bash. + +* `remote_ssh_port` (integer) - SSH port of XenServer host. Only useful when using + `pre_boot_host_scripts` or `pre_export_host_scripts`. Default is 22. + +* `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_port` (integer) - The port that SSH will be listening on in the guest + virtual machine. By default this is 22. + +* `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_description` (string) - The description of the new virtual + machine. By default this is the empty string. + +* `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). + +* `winrm_insecure` (boolean) - Unencrypted WinRM. Default is `false`. + +* `winrm_password` (string) - The password for `winrm_username` to use to + authenticate with WinRM. By default this is the empty string. + +* `winrm_port` (string) - Port for WinRM. Default automatically + set to 5985 or 5986 depending on `winrm_use_ssl`. + +* `winrm_timeout` (string) - The duration to wait for WinRM to become + available. ie. "1800s" + +* `winrm_use_ssl` (boolean) - Use SSL for WinRM. Default is `false`. + +## 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! diff --git a/vendor/github.com/dongyuzheng/easyssh/.gitignore b/vendor/github.com/dongyuzheng/easyssh/.gitignore new file mode 100644 index 00000000..8ddcc7ca --- /dev/null +++ b/vendor/github.com/dongyuzheng/easyssh/.gitignore @@ -0,0 +1,29 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof +debug + +*.iml +.idea +.vscode diff --git a/vendor/github.com/dongyuzheng/easyssh/README.md b/vendor/github.com/dongyuzheng/easyssh/README.md new file mode 100644 index 00000000..fab633d6 --- /dev/null +++ b/vendor/github.com/dongyuzheng/easyssh/README.md @@ -0,0 +1,37 @@ +# easyssh + +This fork of easyssh includes useful changes from other forks. + +# Description + +Package easyssh provides a simple implementation of some SSH protocol features in Go. +You can simply run command on remote server or upload a file even simple than native console SSH client. +Do not need to think about Dials, sessions, defers and public keys... Let easyssh will be think about it! + +# Install + +```go +go get github.com/dongyuzheng/easyssh +``` + +# Parameters + +```go +type MakeConfig struct { + // Required + Server string // Remote machine address (required) + User string // Remote user name (required) + Password string // Password to user + Key string // Path to private key on local machine + + // Optional + Port string // SSH port, default 22 + EnablePTY bool // PTY session, default false + OutputHandler func(string) // Line-by-line handler for output, default does nothing +} +``` + +# Examples + +### 1. [Execute a command with live output, and get output](examples/exec.go) +### 2. [Upload a file](examples/upload.go) diff --git a/vendor/github.com/dongyuzheng/easyssh/easyssh.go b/vendor/github.com/dongyuzheng/easyssh/easyssh.go new file mode 100644 index 00000000..a7b3d922 --- /dev/null +++ b/vendor/github.com/dongyuzheng/easyssh/easyssh.go @@ -0,0 +1,244 @@ +// Package easyssh provides a simple implementation of some SSH protocol +// features in Go. You can simply run a command on a remote server or get a file +// even simpler than native console SSH client. You don't need to think about +// Dials, sessions, defers, or public keys... Let easyssh think about it! +package easyssh + +import ( + "bufio" + "fmt" + "golang.org/x/crypto/ssh" + "io" + "io/ioutil" + "os" + "path/filepath" +) + +var keyMap map[string][]byte = make(map[string][]byte) + +type MakeConfig struct { + // Required + Server string // Remote machine address (required) + User string // Remote user name (required) + Password string // Password to user + Key string // Path to private key on local machine + + // Optional + Port string // SSH port, default 22 + EnablePTY bool // PTY session, default false + OutputHandler func(string) // Line-by-line handler for output, default does nothing + + // Don't touch + Update bool + client *ssh.Client +} + +// returns ssh.Signer from user you running app home path + cutted key path. +// (ex. pubkey,err := getKeyFile("/.ssh/id_rsa") ) +func getKeyFile(keypath string) (pubkey ssh.Signer, err error) { + var buf []byte + var ok bool + if buf, ok = keyMap[keypath]; !ok { + file := keypath + buf, err = ioutil.ReadFile(file) + if err != nil { + return nil, err + } + keyMap[keypath] = buf + } + pubkey, err = ssh.ParsePrivateKey(buf) + if err != nil { + return nil, err + } + return pubkey, nil + +} + +// connects to remote server using MakeConfig struct and returns *ssh.Session +func (ssh_conf *MakeConfig) connect() (*ssh.Session, error) { + // auths holds the detected ssh auth methods + auths := []ssh.AuthMethod{} + + // figure out what auths are requested, what is supported + if ssh_conf.Password != "" { + auths = append(auths, ssh.Password(ssh_conf.Password)) + + auths = append(auths, ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) ([]string, error) { + // Just send the password back for all questions + answers := make([]string, len(questions)) + for i := range answers { + answers[i] = ssh_conf.Password // replace this + } + return answers, nil + })) + } + + // Default port 22 + if ssh_conf.Port == "" { + ssh_conf.Port = "22" + } + + // Default current user + if ssh_conf.User == "" { + ssh_conf.User = os.Getenv("USER") + } + + // if sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil { + // auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers)) + // defer sshAgent.Close() + // } + + if pubkey, err := getKeyFile(ssh_conf.Key); err == nil { + auths = append(auths, ssh.PublicKeys(pubkey)) + } + + config := &ssh.ClientConfig{ + User: ssh_conf.User, + Auth: auths, + } + + if ssh_conf.client == nil || ssh_conf.Update { + if ssh_conf.client != nil { + ssh_conf.client.Close() + } + var err error + ssh_conf.client, err = ssh.Dial("tcp", ssh_conf.Server+":"+ssh_conf.Port, config) + if err != nil { + return nil, err + } + ssh_conf.Update = false + } + + session, err := ssh_conf.client.NewSession() + if err != nil { + return nil, err + } + + if ssh_conf.EnablePTY { + modes := ssh.TerminalModes{ + ssh.ECHO: 0, + ssh.TTY_OP_ISPEED: 144000, + ssh.TTY_OP_OSPEED: 144000, + } + + if err := session.RequestPty("xterm", 1024, 1024, modes); err != nil { + session.Close() + return nil, err + } + } + + return session, nil +} + +// Stream returns one channel that combines the stdout and stderr of the command +// as it is run on the remote machine, and another that sends true when the +// command is done. The sessions and channels will then be closed. +func (ssh_conf *MakeConfig) Stream(command string) (output chan string, done chan bool, err error, sessionErr error) { + // connect to remote host + session, err := ssh_conf.connect() + if err != nil { + return output, done, err, sessionErr + } + outReader, err := session.StdoutPipe() + if err != nil { + return output, done, err, sessionErr + } + errReader, err := session.StderrPipe() + if err != nil { + return output, done, err, sessionErr + } + // combine outputs, create a line-by-line scanner + outputReader := io.MultiReader(outReader, errReader) + sessionErr = session.Run(command) + scanner := bufio.NewScanner(outputReader) + // continuously send the command's output over the channel + outputChan := make(chan string) + done = make(chan bool) + go func(scanner *bufio.Scanner, out chan string, done chan bool) { + defer close(outputChan) + defer close(done) + for scanner.Scan() { + outputChan <- scanner.Text() + } + // close all of our open resources + done <- true + session.Close() + }(scanner, outputChan, done) + return outputChan, done, err, sessionErr +} + +// Execute command on remote machine +func (ssh_conf *MakeConfig) Exec(command string) (outStr string, sessionErr error, err error) { + outChan, doneChan, err, sessionErr := ssh_conf.Stream(command) + if err != nil { + ssh_conf.Update = true + outChan, doneChan, err, sessionErr = ssh_conf.Stream(command) + if err != nil { + return "", nil, err + } + } + + if ssh_conf.OutputHandler == nil { + ssh_conf.OutputHandler = func(string) {} + } + + // read from the output channel until the done signal is passed + stillGoing := true + for stillGoing { + select { + case <-doneChan: + stillGoing = false + case line := <-outChan: + ssh_conf.OutputHandler(line) + outStr += line + "\n" + } + } + + // outstr is a merge of stdout and stderr streams + return outStr, sessionErr, nil +} + +// Upload a file +func (ssh_conf *MakeConfig) Upload(localFile string, remoteFile string) error { + session, err := ssh_conf.connect() + + if err != nil { + return err + } + defer session.Close() + + targetFile := filepath.Base(localFile) + + src, srcErr := os.Open(localFile) + + if srcErr != nil { + return srcErr + } + + srcStat, statErr := src.Stat() + + if statErr != nil { + return statErr + } + + go func() { + w, _ := session.StdinPipe() + + fmt.Fprintln(w, "C0644", srcStat.Size(), targetFile) + + if srcStat.Size() > 0 { + io.Copy(w, src) + fmt.Fprint(w, "\x00") + w.Close() + } else { + fmt.Fprint(w, "\x00") + w.Close() + } + }() + + if err := session.Run(fmt.Sprintf("scp -t '%s'", remoteFile)); err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/mitchellh/go-vnc/LICENSE b/vendor/github.com/mitchellh/go-vnc/LICENSE new file mode 100644 index 00000000..f9c841a5 --- /dev/null +++ b/vendor/github.com/mitchellh/go-vnc/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Mitchell Hashimoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/mitchellh/go-vnc/README.md b/vendor/github.com/mitchellh/go-vnc/README.md new file mode 100644 index 00000000..cb0575b9 --- /dev/null +++ b/vendor/github.com/mitchellh/go-vnc/README.md @@ -0,0 +1,16 @@ +# VNC Library for Go + +go-vnc is a VNC library for Go, initially supporting VNC clients but +with the goal of eventually implementing a VNC server. + +This library implements [RFC 6143](http://tools.ietf.org/html/rfc6143). + +## Usage & Installation + +The library is installable via standard `go get`. The package name is `vnc`. + +``` +$ go get github.com/mitchellh/go-vnc +``` + +Documentation is available on GoDoc: http://godoc.org/github.com/mitchellh/go-vnc diff --git a/vendor/github.com/mitchellh/go-vnc/client.go b/vendor/github.com/mitchellh/go-vnc/client.go new file mode 100644 index 00000000..91402c94 --- /dev/null +++ b/vendor/github.com/mitchellh/go-vnc/client.go @@ -0,0 +1,482 @@ +// Package vnc implements a VNC client. +// +// References: +// [PROTOCOL]: http://tools.ietf.org/html/rfc6143 +package vnc + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "net" + "unicode" +) + +type ClientConn struct { + c net.Conn + config *ClientConfig + + // If the pixel format uses a color map, then this is the color + // map that is used. This should not be modified directly, since + // the data comes from the server. + ColorMap [256]Color + + // Encodings supported by the client. This should not be modified + // directly. Instead, SetEncodings should be used. + Encs []Encoding + + // Width of the frame buffer in pixels, sent from the server. + FrameBufferWidth uint16 + + // Height of the frame buffer in pixels, sent from the server. + FrameBufferHeight uint16 + + // Name associated with the desktop, sent from the server. + DesktopName string + + // The pixel format associated with the connection. This shouldn't + // be modified. If you wish to set a new pixel format, use the + // SetPixelFormat method. + PixelFormat PixelFormat +} + +// A ClientConfig structure is used to configure a ClientConn. After +// one has been passed to initialize a connection, it must not be modified. +type ClientConfig struct { + // A slice of ClientAuth methods. Only the first instance that is + // suitable by the server will be used to authenticate. + Auth []ClientAuth + + // Exclusive determines whether the connection is shared with other + // clients. If true, then all other clients connected will be + // disconnected when a connection is established to the VNC server. + Exclusive bool + + // The channel that all messages received from the server will be + // sent on. If the channel blocks, then the goroutine reading data + // from the VNC server may block indefinitely. It is up to the user + // of the library to ensure that this channel is properly read. + // If this is not set, then all messages will be discarded. + ServerMessageCh chan<- ServerMessage + + // A slice of supported messages that can be read from the server. + // This only needs to contain NEW server messages, and doesn't + // need to explicitly contain the RFC-required messages. + ServerMessages []ServerMessage +} + +func Client(c net.Conn, cfg *ClientConfig) (*ClientConn, error) { + conn := &ClientConn{ + c: c, + config: cfg, + } + + if err := conn.handshake(); err != nil { + conn.Close() + return nil, err + } + + go conn.mainLoop() + + return conn, nil +} + +func (c *ClientConn) Close() error { + return c.c.Close() +} + +// CutText tells the server that the client has new text in its cut buffer. +// The text string MUST only contain Latin-1 characters. This encoding +// is compatible with Go's native string format, but can only use up to +// unicode.MaxLatin values. +// +// See RFC 6143 Section 7.5.6 +func (c *ClientConn) CutText(text string) error { + var buf bytes.Buffer + + // This is the fixed size data we'll send + fixedData := []interface{}{ + uint8(6), + uint8(0), + uint8(0), + uint8(0), + uint32(len(text)), + } + + for _, val := range fixedData { + if err := binary.Write(&buf, binary.BigEndian, val); err != nil { + return err + } + } + + for _, char := range text { + if char > unicode.MaxLatin1 { + return fmt.Errorf("Character '%s' is not valid Latin-1", char) + } + + if err := binary.Write(&buf, binary.BigEndian, uint8(char)); err != nil { + return err + } + } + + dataLength := 8 + len(text) + if _, err := c.c.Write(buf.Bytes()[0:dataLength]); err != nil { + return err + } + + return nil +} + +// Requests a framebuffer update from the server. There may be an indefinite +// time between the request and the actual framebuffer update being +// received. +// +// See RFC 6143 Section 7.5.3 +func (c *ClientConn) FramebufferUpdateRequest(incremental bool, x, y, width, height uint16) error { + var buf bytes.Buffer + var incrementalByte uint8 = 0 + + if incremental { + incrementalByte = 1 + } + + data := []interface{}{ + uint8(3), + incrementalByte, + x, y, width, height, + } + + for _, val := range data { + if err := binary.Write(&buf, binary.BigEndian, val); err != nil { + return err + } + } + + if _, err := c.c.Write(buf.Bytes()[0:10]); err != nil { + return err + } + + return nil +} + +// KeyEvent indiciates a key press or release and sends it to the server. +// The key is indicated using the X Window System "keysym" value. Use +// Google to find a reference of these values. To simulate a key press, +// you must send a key with both a down event, and a non-down event. +// +// See 7.5.4. +func (c *ClientConn) KeyEvent(keysym uint32, down bool) error { + var downFlag uint8 = 0 + if down { + downFlag = 1 + } + + data := []interface{}{ + uint8(4), + downFlag, + uint8(0), + uint8(0), + keysym, + } + + for _, val := range data { + if err := binary.Write(c.c, binary.BigEndian, val); err != nil { + return err + } + } + + return nil +} + +// PointerEvent indicates that pointer movement or a pointer button +// press or release. +// +// The mask is a bitwise mask of various ButtonMask values. When a button +// is set, it is pressed, when it is unset, it is released. +// +// See RFC 6143 Section 7.5.5 +func (c *ClientConn) PointerEvent(mask ButtonMask, x, y uint16) error { + var buf bytes.Buffer + + data := []interface{}{ + uint8(5), + uint8(mask), + x, + y, + } + + for _, val := range data { + if err := binary.Write(&buf, binary.BigEndian, val); err != nil { + return err + } + } + + if _, err := c.c.Write(buf.Bytes()[0:6]); err != nil { + return err + } + + return nil +} + +// SetEncodings sets the encoding types in which the pixel data can +// be sent from the server. After calling this method, the encs slice +// given should not be modified. +// +// See RFC 6143 Section 7.5.2 +func (c *ClientConn) SetEncodings(encs []Encoding) error { + data := make([]interface{}, 3+len(encs)) + data[0] = uint8(2) + data[1] = uint8(0) + data[2] = uint16(len(encs)) + + for i, enc := range encs { + data[3+i] = int32(enc.Type()) + } + + var buf bytes.Buffer + for _, val := range data { + if err := binary.Write(&buf, binary.BigEndian, val); err != nil { + return err + } + } + + dataLength := 4 + (4 * len(encs)) + if _, err := c.c.Write(buf.Bytes()[0:dataLength]); err != nil { + return err + } + + c.Encs = encs + + return nil +} + +// SetPixelFormat sets the format in which pixel values should be sent +// in FramebufferUpdate messages from the server. +// +// See RFC 6143 Section 7.5.1 +func (c *ClientConn) SetPixelFormat(format *PixelFormat) error { + var keyEvent [20]byte + keyEvent[0] = 0 + + pfBytes, err := writePixelFormat(format) + if err != nil { + return err + } + + // Copy the pixel format bytes into the proper slice location + copy(keyEvent[4:], pfBytes) + + // Send the data down the connection + if _, err := c.c.Write(keyEvent[:]); err != nil { + return err + } + + // Reset the color map as according to RFC. + var newColorMap [256]Color + c.ColorMap = newColorMap + + return nil +} + +const pvLen = 12 // ProtocolVersion message length. + +func parseProtocolVersion(pv []byte) (uint, uint, error) { + var major, minor uint + + if len(pv) < pvLen { + return 0, 0, fmt.Errorf("ProtocolVersion message too short (%v < %v)", len(pv), pvLen) + } + + l, err := fmt.Sscanf(string(pv), "RFB %d.%d\n", &major, &minor) + if l != 2 { + return 0, 0, fmt.Errorf("error parsing ProtocolVersion.") + } + if err != nil { + return 0, 0, err + } + + return major, minor, nil +} + +func (c *ClientConn) handshake() error { + var protocolVersion [pvLen]byte + + // 7.1.1, read the ProtocolVersion message sent by the server. + if _, err := io.ReadFull(c.c, protocolVersion[:]); err != nil { + return err + } + + maxMajor, maxMinor, err := parseProtocolVersion(protocolVersion[:]) + if err != nil { + return err + } + if maxMajor < 3 { + return fmt.Errorf("unsupported major version, less than 3: %d", maxMajor) + } + if maxMinor < 8 { + return fmt.Errorf("unsupported minor version, less than 8: %d", maxMinor) + } + + // Respond with the version we will support + if _, err = c.c.Write([]byte("RFB 003.008\n")); err != nil { + return err + } + + // 7.1.2 Security Handshake from server + var numSecurityTypes uint8 + if err = binary.Read(c.c, binary.BigEndian, &numSecurityTypes); err != nil { + return err + } + + if numSecurityTypes == 0 { + return fmt.Errorf("no security types: %s", c.readErrorReason()) + } + + securityTypes := make([]uint8, numSecurityTypes) + if err = binary.Read(c.c, binary.BigEndian, &securityTypes); err != nil { + return err + } + + clientSecurityTypes := c.config.Auth + if clientSecurityTypes == nil { + clientSecurityTypes = []ClientAuth{new(ClientAuthNone)} + } + + var auth ClientAuth +FindAuth: + for _, curAuth := range clientSecurityTypes { + for _, securityType := range securityTypes { + if curAuth.SecurityType() == securityType { + // We use the first matching supported authentication + auth = curAuth + break FindAuth + } + } + } + + if auth == nil { + return fmt.Errorf("no suitable auth schemes found. server supported: %#v", securityTypes) + } + + // Respond back with the security type we'll use + if err = binary.Write(c.c, binary.BigEndian, auth.SecurityType()); err != nil { + return err + } + + if err = auth.Handshake(c.c); err != nil { + return err + } + + // 7.1.3 SecurityResult Handshake + var securityResult uint32 + if err = binary.Read(c.c, binary.BigEndian, &securityResult); err != nil { + return err + } + + if securityResult == 1 { + return fmt.Errorf("security handshake failed: %s", c.readErrorReason()) + } + + // 7.3.1 ClientInit + var sharedFlag uint8 = 1 + if c.config.Exclusive { + sharedFlag = 0 + } + + if err = binary.Write(c.c, binary.BigEndian, sharedFlag); err != nil { + return err + } + + // 7.3.2 ServerInit + if err = binary.Read(c.c, binary.BigEndian, &c.FrameBufferWidth); err != nil { + return err + } + + if err = binary.Read(c.c, binary.BigEndian, &c.FrameBufferHeight); err != nil { + return err + } + + // Read the pixel format + if err = readPixelFormat(c.c, &c.PixelFormat); err != nil { + return err + } + + var nameLength uint32 + if err = binary.Read(c.c, binary.BigEndian, &nameLength); err != nil { + return err + } + + nameBytes := make([]uint8, nameLength) + if err = binary.Read(c.c, binary.BigEndian, &nameBytes); err != nil { + return err + } + + c.DesktopName = string(nameBytes) + + return nil +} + +// mainLoop reads messages sent from the server and routes them to the +// proper channels for users of the client to read. +func (c *ClientConn) mainLoop() { + defer c.Close() + + // Build the map of available server messages + typeMap := make(map[uint8]ServerMessage) + + defaultMessages := []ServerMessage{ + new(FramebufferUpdateMessage), + new(SetColorMapEntriesMessage), + new(BellMessage), + new(ServerCutTextMessage), + } + + for _, msg := range defaultMessages { + typeMap[msg.Type()] = msg + } + + if c.config.ServerMessages != nil { + for _, msg := range c.config.ServerMessages { + typeMap[msg.Type()] = msg + } + } + + for { + var messageType uint8 + if err := binary.Read(c.c, binary.BigEndian, &messageType); err != nil { + break + } + + msg, ok := typeMap[messageType] + if !ok { + // Unsupported message type! Bad! + break + } + + parsedMsg, err := msg.Read(c, c.c) + if err != nil { + break + } + + if c.config.ServerMessageCh == nil { + continue + } + + c.config.ServerMessageCh <- parsedMsg + } +} + +func (c *ClientConn) readErrorReason() string { + var reasonLen uint32 + if err := binary.Read(c.c, binary.BigEndian, &reasonLen); err != nil { + return "" + } + + reason := make([]uint8, reasonLen) + if err := binary.Read(c.c, binary.BigEndian, &reason); err != nil { + return "" + } + + return string(reason) +} diff --git a/vendor/github.com/mitchellh/go-vnc/client_auth.go b/vendor/github.com/mitchellh/go-vnc/client_auth.go new file mode 100644 index 00000000..601e470e --- /dev/null +++ b/vendor/github.com/mitchellh/go-vnc/client_auth.go @@ -0,0 +1,124 @@ +package vnc + +import ( + "net" + + "crypto/des" + "encoding/binary" +) + +// A ClientAuth implements a method of authenticating with a remote server. +type ClientAuth interface { + // SecurityType returns the byte identifier sent by the server to + // identify this authentication scheme. + SecurityType() uint8 + + // Handshake is called when the authentication handshake should be + // performed, as part of the general RFB handshake. (see 7.2.1) + Handshake(net.Conn) error +} + +// ClientAuthNone is the "none" authentication. See 7.2.1 +type ClientAuthNone byte + +func (*ClientAuthNone) SecurityType() uint8 { + return 1 +} + +func (*ClientAuthNone) Handshake(net.Conn) error { + return nil +} + +// PasswordAuth is VNC authentication, 7.2.2 +type PasswordAuth struct { + Password string +} + +func (p *PasswordAuth) SecurityType() uint8 { + return 2 +} + +func (p *PasswordAuth) Handshake(c net.Conn) error { + randomValue := make([]uint8, 16) + if err := binary.Read(c, binary.BigEndian, &randomValue); err != nil { + return err + } + + crypted, err := p.encrypt(p.Password, randomValue) + + if err != nil { + return err + } + + if err := binary.Write(c, binary.BigEndian, &crypted); err != nil { + return err + } + + return nil +} + +func (p *PasswordAuth) reverseBits(b byte) byte { + var reverse = [256]int{ + 0, 128, 64, 192, 32, 160, 96, 224, + 16, 144, 80, 208, 48, 176, 112, 240, + 8, 136, 72, 200, 40, 168, 104, 232, + 24, 152, 88, 216, 56, 184, 120, 248, + 4, 132, 68, 196, 36, 164, 100, 228, + 20, 148, 84, 212, 52, 180, 116, 244, + 12, 140, 76, 204, 44, 172, 108, 236, + 28, 156, 92, 220, 60, 188, 124, 252, + 2, 130, 66, 194, 34, 162, 98, 226, + 18, 146, 82, 210, 50, 178, 114, 242, + 10, 138, 74, 202, 42, 170, 106, 234, + 26, 154, 90, 218, 58, 186, 122, 250, + 6, 134, 70, 198, 38, 166, 102, 230, + 22, 150, 86, 214, 54, 182, 118, 246, + 14, 142, 78, 206, 46, 174, 110, 238, + 30, 158, 94, 222, 62, 190, 126, 254, + 1, 129, 65, 193, 33, 161, 97, 225, + 17, 145, 81, 209, 49, 177, 113, 241, + 9, 137, 73, 201, 41, 169, 105, 233, + 25, 153, 89, 217, 57, 185, 121, 249, + 5, 133, 69, 197, 37, 165, 101, 229, + 21, 149, 85, 213, 53, 181, 117, 245, + 13, 141, 77, 205, 45, 173, 109, 237, + 29, 157, 93, 221, 61, 189, 125, 253, + 3, 131, 67, 195, 35, 163, 99, 227, + 19, 147, 83, 211, 51, 179, 115, 243, + 11, 139, 75, 203, 43, 171, 107, 235, + 27, 155, 91, 219, 59, 187, 123, 251, + 7, 135, 71, 199, 39, 167, 103, 231, + 23, 151, 87, 215, 55, 183, 119, 247, + 15, 143, 79, 207, 47, 175, 111, 239, + 31, 159, 95, 223, 63, 191, 127, 255, + } + + return byte(reverse[int(b)]) +} + +func (p *PasswordAuth) encrypt(key string, bytes []byte) ([]byte, error) { + keyBytes := []byte{0, 0, 0, 0, 0, 0, 0, 0} + + if len(key) > 8 { + key = key[:8] + } + + for i := 0; i < len(key); i++ { + keyBytes[i] = p.reverseBits(key[i]) + } + + block, err := des.NewCipher(keyBytes) + + if err != nil { + return nil, err + } + + result1 := make([]byte, 8) + block.Encrypt(result1, bytes) + result2 := make([]byte, 8) + block.Encrypt(result2, bytes[8:]) + + crypted := append(result1, result2...) + + return crypted, nil +} diff --git a/vendor/github.com/mitchellh/go-vnc/color.go b/vendor/github.com/mitchellh/go-vnc/color.go new file mode 100644 index 00000000..30df4673 --- /dev/null +++ b/vendor/github.com/mitchellh/go-vnc/color.go @@ -0,0 +1,6 @@ +package vnc + +// Color represents a single color in a color map. +type Color struct { + R, G, B uint16 +} diff --git a/vendor/github.com/mitchellh/go-vnc/encoding.go b/vendor/github.com/mitchellh/go-vnc/encoding.go new file mode 100644 index 00000000..71985957 --- /dev/null +++ b/vendor/github.com/mitchellh/go-vnc/encoding.go @@ -0,0 +1,69 @@ +package vnc + +import ( + "encoding/binary" + "io" +) + +// An Encoding implements a method for encoding pixel data that is +// sent by the server to the client. +type Encoding interface { + // The number that uniquely identifies this encoding type. + Type() int32 + + // Read reads the contents of the encoded pixel data from the reader. + // This should return a new Encoding implementation that contains + // the proper data. + Read(*ClientConn, *Rectangle, io.Reader) (Encoding, error) +} + +// RawEncoding is raw pixel data sent by the server. +// +// See RFC 6143 Section 7.7.1 +type RawEncoding struct { + Colors []Color +} + +func (*RawEncoding) Type() int32 { + return 0 +} + +func (*RawEncoding) Read(c *ClientConn, rect *Rectangle, r io.Reader) (Encoding, error) { + bytesPerPixel := c.PixelFormat.BPP / 8 + pixelBytes := make([]uint8, bytesPerPixel) + + var byteOrder binary.ByteOrder = binary.LittleEndian + if c.PixelFormat.BigEndian { + byteOrder = binary.BigEndian + } + + colors := make([]Color, int(rect.Height)*int(rect.Width)) + + for y := uint16(0); y < rect.Height; y++ { + for x := uint16(0); x < rect.Width; x++ { + if _, err := io.ReadFull(r, pixelBytes); err != nil { + return nil, err + } + + var rawPixel uint32 + if c.PixelFormat.BPP == 8 { + rawPixel = uint32(pixelBytes[0]) + } else if c.PixelFormat.BPP == 16 { + rawPixel = uint32(byteOrder.Uint16(pixelBytes)) + } else if c.PixelFormat.BPP == 32 { + rawPixel = byteOrder.Uint32(pixelBytes) + } + + color := &colors[int(y)*int(rect.Width)+int(x)] + if c.PixelFormat.TrueColor { + color.R = uint16((rawPixel >> c.PixelFormat.RedShift) & uint32(c.PixelFormat.RedMax)) + color.G = uint16((rawPixel >> c.PixelFormat.GreenShift) & uint32(c.PixelFormat.GreenMax)) + color.B = uint16((rawPixel >> c.PixelFormat.BlueShift) & uint32(c.PixelFormat.BlueMax)) + } else { + *color = c.ColorMap[rawPixel] + } + } + } + + return &RawEncoding{colors}, nil +} diff --git a/vendor/github.com/mitchellh/go-vnc/pixel_format.go b/vendor/github.com/mitchellh/go-vnc/pixel_format.go new file mode 100644 index 00000000..ff8edbff --- /dev/null +++ b/vendor/github.com/mitchellh/go-vnc/pixel_format.go @@ -0,0 +1,151 @@ +package vnc + +import ( + "bytes" + "encoding/binary" + "io" +) + +// PixelFormat describes the way a pixel is formatted for a VNC connection. +// +// See RFC 6143 Section 7.4 for information on each of the fields. +type PixelFormat struct { + BPP uint8 + Depth uint8 + BigEndian bool + TrueColor bool + RedMax uint16 + GreenMax uint16 + BlueMax uint16 + RedShift uint8 + GreenShift uint8 + BlueShift uint8 +} + +func readPixelFormat(r io.Reader, result *PixelFormat) error { + var rawPixelFormat [16]byte + if _, err := io.ReadFull(r, rawPixelFormat[:]); err != nil { + return err + } + + var pfBoolByte uint8 + brPF := bytes.NewReader(rawPixelFormat[:]) + if err := binary.Read(brPF, binary.BigEndian, &result.BPP); err != nil { + return err + } + + if err := binary.Read(brPF, binary.BigEndian, &result.Depth); err != nil { + return err + } + + if err := binary.Read(brPF, binary.BigEndian, &pfBoolByte); err != nil { + return err + } + + if pfBoolByte != 0 { + // Big endian is true + result.BigEndian = true + } + + if err := binary.Read(brPF, binary.BigEndian, &pfBoolByte); err != nil { + return err + } + + if pfBoolByte != 0 { + // True Color is true. So we also have to read all the color max & shifts. + result.TrueColor = true + + if err := binary.Read(brPF, binary.BigEndian, &result.RedMax); err != nil { + return err + } + + if err := binary.Read(brPF, binary.BigEndian, &result.GreenMax); err != nil { + return err + } + + if err := binary.Read(brPF, binary.BigEndian, &result.BlueMax); err != nil { + return err + } + + if err := binary.Read(brPF, binary.BigEndian, &result.RedShift); err != nil { + return err + } + + if err := binary.Read(brPF, binary.BigEndian, &result.GreenShift); err != nil { + return err + } + + if err := binary.Read(brPF, binary.BigEndian, &result.BlueShift); err != nil { + return err + } + } + + return nil +} + +func writePixelFormat(format *PixelFormat) ([]byte, error) { + var buf bytes.Buffer + + // Byte 1 + if err := binary.Write(&buf, binary.BigEndian, format.BPP); err != nil { + return nil, err + } + + // Byte 2 + if err := binary.Write(&buf, binary.BigEndian, format.Depth); err != nil { + return nil, err + } + + var boolByte byte + if format.BigEndian { + boolByte = 1 + } else { + boolByte = 0 + } + + // Byte 3 (BigEndian) + if err := binary.Write(&buf, binary.BigEndian, boolByte); err != nil { + return nil, err + } + + if format.TrueColor { + boolByte = 1 + } else { + boolByte = 0 + } + + // Byte 4 (TrueColor) + if err := binary.Write(&buf, binary.BigEndian, boolByte); err != nil { + return nil, err + } + + // If we have true color enabled then we have to fill in the rest of the + // structure with the color values. + if format.TrueColor { + if err := binary.Write(&buf, binary.BigEndian, format.RedMax); err != nil { + return nil, err + } + + if err := binary.Write(&buf, binary.BigEndian, format.GreenMax); err != nil { + return nil, err + } + + if err := binary.Write(&buf, binary.BigEndian, format.BlueMax); err != nil { + return nil, err + } + + if err := binary.Write(&buf, binary.BigEndian, format.RedShift); err != nil { + return nil, err + } + + if err := binary.Write(&buf, binary.BigEndian, format.GreenShift); err != nil { + return nil, err + } + + if err := binary.Write(&buf, binary.BigEndian, format.BlueShift); err != nil { + return nil, err + } + } + + return buf.Bytes()[0:16], nil +} diff --git a/vendor/github.com/mitchellh/go-vnc/pointer.go b/vendor/github.com/mitchellh/go-vnc/pointer.go new file mode 100644 index 00000000..86d84e39 --- /dev/null +++ b/vendor/github.com/mitchellh/go-vnc/pointer.go @@ -0,0 +1,16 @@ +package vnc + +// ButtonMask represents a mask of pointer presses/releases. +type ButtonMask uint8 + +// All available button mask components. +const ( + ButtonLeft ButtonMask = 1 << iota + ButtonMiddle + ButtonRight + Button4 + Button5 + Button6 + Button7 + Button8 +) diff --git a/vendor/github.com/mitchellh/go-vnc/server_messages.go b/vendor/github.com/mitchellh/go-vnc/server_messages.go new file mode 100644 index 00000000..c0aae566 --- /dev/null +++ b/vendor/github.com/mitchellh/go-vnc/server_messages.go @@ -0,0 +1,192 @@ +package vnc + +import ( + "encoding/binary" + "fmt" + "io" +) + +// A ServerMessage implements a message sent from the server to the client. +type ServerMessage interface { + // The type of the message that is sent down on the wire. + Type() uint8 + + // Read reads the contents of the message from the reader. At the point + // this is called, the message type has already been read from the reader. + // This should return a new ServerMessage that is the appropriate type. + Read(*ClientConn, io.Reader) (ServerMessage, error) +} + +// FramebufferUpdateMessage consists of a sequence of rectangles of +// pixel data that the client should put into its framebuffer. +type FramebufferUpdateMessage struct { + Rectangles []Rectangle +} + +// Rectangle represents a rectangle of pixel data. +type Rectangle struct { + X uint16 + Y uint16 + Width uint16 + Height uint16 + Enc Encoding +} + +func (*FramebufferUpdateMessage) Type() uint8 { + return 0 +} + +func (*FramebufferUpdateMessage) Read(c *ClientConn, r io.Reader) (ServerMessage, error) { + // Read off the padding + var padding [1]byte + if _, err := io.ReadFull(r, padding[:]); err != nil { + return nil, err + } + + var numRects uint16 + if err := binary.Read(r, binary.BigEndian, &numRects); err != nil { + return nil, err + } + + // Build the map of encodings supported + encMap := make(map[int32]Encoding) + for _, enc := range c.Encs { + encMap[enc.Type()] = enc + } + + // We must always support the raw encoding + rawEnc := new(RawEncoding) + encMap[rawEnc.Type()] = rawEnc + + rects := make([]Rectangle, numRects) + for i := uint16(0); i < numRects; i++ { + var encodingType int32 + + rect := &rects[i] + data := []interface{}{ + &rect.X, + &rect.Y, + &rect.Width, + &rect.Height, + &encodingType, + } + + for _, val := range data { + if err := binary.Read(r, binary.BigEndian, val); err != nil { + return nil, err + } + } + + enc, ok := encMap[encodingType] + if !ok { + return nil, fmt.Errorf("unsupported encoding type: %d", encodingType) + } + + var err error + rect.Enc, err = enc.Read(c, rect, r) + if err != nil { + return nil, err + } + } + + return &FramebufferUpdateMessage{rects}, nil +} + +// SetColorMapEntriesMessage is sent by the server to set values into +// the color map. This message will automatically update the color map +// for the associated connection, but contains the color change data +// if the consumer wants to read it. +// +// See RFC 6143 Section 7.6.2 +type SetColorMapEntriesMessage struct { + FirstColor uint16 + Colors []Color +} + +func (*SetColorMapEntriesMessage) Type() uint8 { + return 1 +} + +func (*SetColorMapEntriesMessage) Read(c *ClientConn, r io.Reader) (ServerMessage, error) { + // Read off the padding + var padding [1]byte + if _, err := io.ReadFull(r, padding[:]); err != nil { + return nil, err + } + + var result SetColorMapEntriesMessage + if err := binary.Read(r, binary.BigEndian, &result.FirstColor); err != nil { + return nil, err + } + + var numColors uint16 + if err := binary.Read(r, binary.BigEndian, &numColors); err != nil { + return nil, err + } + + result.Colors = make([]Color, numColors) + for i := uint16(0); i < numColors; i++ { + + color := &result.Colors[i] + data := []interface{}{ + &color.R, + &color.G, + &color.B, + } + + for _, val := range data { + if err := binary.Read(r, binary.BigEndian, val); err != nil { + return nil, err + } + } + + // Update the connection's color map + c.ColorMap[result.FirstColor+i] = *color + } + + return &result, nil +} + +// Bell signals that an audible bell should be made on the client. +// +// See RFC 6143 Section 7.6.3 +type BellMessage byte + +func (*BellMessage) Type() uint8 { + return 2 +} + +func (*BellMessage) Read(*ClientConn, io.Reader) (ServerMessage, error) { + return new(BellMessage), nil +} + +// ServerCutTextMessage indicates the server has new text in the cut buffer. +// +// See RFC 6143 Section 7.6.4 +type ServerCutTextMessage struct { + Text string +} + +func (*ServerCutTextMessage) Type() uint8 { + return 3 +} + +func (*ServerCutTextMessage) Read(c *ClientConn, r io.Reader) (ServerMessage, error) { + // Read off the padding + var padding [1]byte + if _, err := io.ReadFull(r, padding[:]); err != nil { + return nil, err + } + + var textLength uint32 + if err := binary.Read(r, binary.BigEndian, &textLength); err != nil { + return nil, err + } + + textBytes := make([]uint8, textLength) + if err := binary.Read(r, binary.BigEndian, &textBytes); err != nil { + return nil, err + } + + return &ServerCutTextMessage{string(textBytes)}, nil +} diff --git a/vendor/github.com/mitchellh/packer/communicator/ssh/communicator.go b/vendor/github.com/mitchellh/packer/communicator/ssh/communicator.go index 8bcf4ce7..7b76d81c 100644 --- a/vendor/github.com/mitchellh/packer/communicator/ssh/communicator.go +++ b/vendor/github.com/mitchellh/packer/communicator/ssh/communicator.go @@ -226,8 +226,16 @@ func (c *comm) Download(path string, output io.Writer) error { return c.scpDownloadSession(path, output) } -func (c *comm) newSession() (session *ssh.Session, err error) { +func (c *comm) newSession(args ...int) (session *ssh.Session, err error) { log.Println("opening new ssh session") + + if len(args) == 0 { + log.Println("reconnecting to ssh") + if err := c.reconnect(); err != nil { + return nil, err + } + } + if c.client == nil { err = errors.New("client not available") } else { @@ -356,7 +364,7 @@ func (c *comm) connectToAgent() { agent.ForwardToAgent(c.client, forwardingAgent) // Setup a session to request agent forwarding - session, err := c.newSession() + session, err := c.newSession(1) if err != nil { return } diff --git a/vendor/github.com/mitchellh/packer/communicator/winrm/communicator.go b/vendor/github.com/mitchellh/packer/communicator/winrm/communicator.go index 902211bd..55e069fd 100644 --- a/vendor/github.com/mitchellh/packer/communicator/winrm/communicator.go +++ b/vendor/github.com/mitchellh/packer/communicator/winrm/communicator.go @@ -1,8 +1,10 @@ package winrm import ( + "encoding/base64" "fmt" "io" + "io/ioutil" "log" "os" "sync" @@ -142,7 +144,22 @@ func (c *Communicator) UploadDir(dst string, src string, exclude []string) error } func (c *Communicator) Download(src string, dst io.Writer) error { - return fmt.Errorf("WinRM doesn't support download.") + //return fmt.Errorf("WinRM doesn't support download.") + + client, err := c.newWinRMClient() + if err != nil { + return err + } + + encodeScript := `$file=[System.IO.File]::ReadAllBytes("%s"); Write-Output $([System.Convert]::ToBase64String($file))` + + base64DecodePipe := &Base64Pipe{w: dst} + + cmd := winrm.Powershell(fmt.Sprintf(encodeScript, src)) + _, err = client.Run(cmd, base64DecodePipe, ioutil.Discard) + + return err + } func (c *Communicator) DownloadDir(src string, dst string, exclude []string) error { @@ -163,3 +180,67 @@ func (c *Communicator) newCopyClient() (*winrmcp.Winrmcp, error) { TransportDecorator: c.config.TransportDecorator, }) } + +func (c *Communicator) newWinRMClient() (*winrm.Client, error) { + + // Shamelessly borrowed from the winrmcp client to ensure + // that the client is configured using the same defaulting behaviors that + // winrmcp uses even we we aren't using winrmcp. This ensures similar + // behavior between upload, download, and copy functions. We can't use the + // one generated by winrmcp because it isn't exported. + var endpoint *winrm.Endpoint + endpoint = &winrm.Endpoint{ + Host: c.config.Host, + Port: c.config.Port, + HTTPS: c.config.Https, + Insecure: c.config.Insecure, + /* + TLSServerName: conf.TLSServerName, + CACert: conf.CACertBytes, + Timeout: conf.ConnectTimeout, + */ + } + params := winrm.NewParameters( + "PT60S", + "en-US", + 153600, + ) + + params.TransportDecorator = c.config.TransportDecorator + params.Timeout = "PT3M" + + client, err := winrm.NewClientWithParameters( + endpoint, c.config.Username, c.config.Password, params) + return client, err +} + +type Base64Pipe struct { + w io.Writer // underlying writer (file, buffer) +} + +func (d *Base64Pipe) ReadFrom(r io.Reader) (int64, error) { + b, err := ioutil.ReadAll(r) + if err != nil { + return 0, err + } + + var i int + i, err = d.Write(b) + + if err != nil { + return 0, err + } + + return int64(i), err +} + +func (d *Base64Pipe) Write(p []byte) (int, error) { + dst := make([]byte, base64.StdEncoding.DecodedLen(len(p))) + + decodedBytes, err := base64.StdEncoding.Decode(dst, p) + if err != nil { + return 0, err + } + + return d.w.Write(dst[0:decodedBytes]) +} 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..953128b0 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,19 +91,22 @@ 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") } + 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 s.WinRMPort != nil { + // port, err = s.WinRMPort(state) + // if err != nil { + // log.Printf("[DEBUG] Error getting WinRM port: %s", err) + // continue + // } + // } user := s.Config.WinRMUser password := s.Config.WinRMPassword @@ -122,6 +125,7 @@ func (s *StepConnectWinRM) waitForWinRM(state multistep.StateBag, cancel <-chan } } + var err error log.Println("[INFO] Attempting WinRM connection...") comm, err = winrm.New(&winrm.Config{ Host: host, diff --git a/vendor/github.com/packer-community/winrmcp/winrmcp/cp.go b/vendor/github.com/packer-community/winrmcp/winrmcp/cp.go index 1065e452..abe17613 100644 --- a/vendor/github.com/packer-community/winrmcp/winrmcp/cp.go +++ b/vendor/github.com/packer-community/winrmcp/winrmcp/cp.go @@ -113,7 +113,7 @@ func restoreContent(client *winrm.Client, fromPath, toPath string) error { defer shell.Close() script := fmt.Sprintf(` $tmp_file_path = [System.IO.Path]::GetFullPath("%s") - $dest_file_path = [System.IO.Path]::GetFullPath("%s") + $dest_file_path = [System.IO.Path]::GetFullPath("%s".Trim("'")) if (Test-Path $dest_file_path) { rm $dest_file_path } diff --git a/vendor/github.com/pkg/errors/.travis.yml b/vendor/github.com/pkg/errors/.travis.yml index 024e2846..567ccdbf 100644 --- a/vendor/github.com/pkg/errors/.travis.yml +++ b/vendor/github.com/pkg/errors/.travis.yml @@ -3,7 +3,8 @@ go_import_path: github.com/pkg/errors go: - 1.4.3 - 1.5.4 - - 1.6.2 + - 1.6.3 + - 1.7.3 - tip script: diff --git a/vendor/github.com/pkg/errors/README.md b/vendor/github.com/pkg/errors/README.md index 6ea6422e..273db3c9 100644 --- a/vendor/github.com/pkg/errors/README.md +++ b/vendor/github.com/pkg/errors/README.md @@ -2,6 +2,8 @@ Package errors provides simple error handling primitives. +`go get github.com/pkg/errors` + The traditional error handling idiom in Go is roughly akin to ```go if err != nil { diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go index 6c45c8dc..842ee804 100644 --- a/vendor/github.com/pkg/errors/errors.go +++ b/vendor/github.com/pkg/errors/errors.go @@ -14,13 +14,18 @@ // Adding context to an error // // The errors.Wrap function returns a new error that adds context to the -// original error. For example +// original error by recording a stack trace at the point Wrap is called, +// and the supplied message. For example // // _, err := ioutil.ReadAll(r) // if err != nil { // return errors.Wrap(err, "read failed") // } // +// If additional control is required the errors.WithStack and errors.WithMessage +// functions destructure errors.Wrap into its component operations of annotating +// an error with a stack trace and an a message, respectively. +// // Retrieving the cause of an error // // Using errors.Wrap constructs a stack of errors, adding context to the @@ -28,7 +33,7 @@ // to reverse the operation of errors.Wrap to retrieve the original error // for inspection. Any error value which implements this interface // -// type Causer interface { +// type causer interface { // Cause() error // } // @@ -43,6 +48,9 @@ // // unknown error // } // +// causer interface is not exported by this package, but is considered a part +// of stable public API. +// // Formatted printing of errors // // All error values returned from this package implement fmt.Formatter and can @@ -77,6 +85,9 @@ // } // } // +// stackTracer interface is not exported by this package, but is considered a part +// of stable public API. +// // See the documentation for Frame.Format for more details. package errors @@ -85,68 +96,74 @@ import ( "io" ) -// _error is an error implementation returned by New and Errorf -// that implements its own fmt.Formatter. -type _error struct { +// New returns an error with the supplied message. +// New also records the stack trace at the point it was called. +func New(message string) error { + return &fundamental{ + msg: message, + stack: callers(), + } +} + +// Errorf formats according to a format specifier and returns the string +// as a value that satisfies error. +// Errorf also records the stack trace at the point it was called. +func Errorf(format string, args ...interface{}) error { + return &fundamental{ + msg: fmt.Sprintf(format, args...), + stack: callers(), + } +} + +// fundamental is an error that has a message and a stack, but no caller. +type fundamental struct { msg string *stack } -func (e _error) Error() string { return e.msg } +func (f *fundamental) Error() string { return f.msg } -func (e _error) Format(s fmt.State, verb rune) { +func (f *fundamental) Format(s fmt.State, verb rune) { switch verb { case 'v': if s.Flag('+') { - io.WriteString(s, e.msg) - fmt.Fprintf(s, "%+v", e.StackTrace()) + io.WriteString(s, f.msg) + f.stack.Format(s, verb) return } fallthrough case 's': - io.WriteString(s, e.msg) + io.WriteString(s, f.msg) + case 'q': + fmt.Fprintf(s, "%q", f.msg) } } -// New returns an error with the supplied message. -func New(message string) error { - return _error{ - message, - callers(), +// WithStack annotates err with a stack trace at the point WithStack was called. +// If err is nil, WithStack returns nil. +func WithStack(err error) error { + if err == nil { + return nil } -} - -// Errorf formats according to a format specifier and returns the string -// as a value that satisfies error. -func Errorf(format string, args ...interface{}) error { - return _error{ - fmt.Sprintf(format, args...), + return &withStack{ + err, callers(), } } -type cause struct { - cause error - msg string -} - -func (c cause) Error() string { return fmt.Sprintf("%s: %v", c.msg, c.Cause()) } -func (c cause) Cause() error { return c.cause } - -// wrapper is an error implementation returned by Wrap and Wrapf -// that implements its own fmt.Formatter. -type wrapper struct { - cause +type withStack struct { + error *stack } -func (w wrapper) Format(s fmt.State, verb rune) { +func (w *withStack) Cause() error { return w.error } + +func (w *withStack) Format(s fmt.State, verb rune) { switch verb { case 'v': if s.Flag('+') { - fmt.Fprintf(s, "%+v\n", w.Cause()) - io.WriteString(s, w.msg) - fmt.Fprintf(s, "%+v", w.StackTrace()) + fmt.Fprintf(s, "%+v", w.Cause()) + w.stack.Format(s, verb) return } fallthrough @@ -157,33 +174,71 @@ func (w wrapper) Format(s fmt.State, verb rune) { } } -// Wrap returns an error annotating err with message. +// Wrap returns an error annotating err with a stack trace +// at the point Wrap is called, and the supplied message. // If err is nil, Wrap returns nil. func Wrap(err error, message string) error { if err == nil { return nil } - return wrapper{ - cause: cause{ - cause: err, - msg: message, - }, - stack: callers(), + err = &withMessage{ + cause: err, + msg: message, + } + return &withStack{ + err, + callers(), } } -// Wrapf returns an error annotating err with the format specifier. +// Wrapf returns an error annotating err with a stack trace +// at the point Wrapf is call, and the format specifier. // If err is nil, Wrapf returns nil. func Wrapf(err error, format string, args ...interface{}) error { if err == nil { return nil } - return wrapper{ - cause: cause{ - cause: err, - msg: fmt.Sprintf(format, args...), - }, - stack: callers(), + err = &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + } + return &withStack{ + err, + callers(), + } +} + +// WithMessage annotates err with a new message. +// If err is nil, WithMessage returns nil. +func WithMessage(err error, message string) error { + if err == nil { + return nil + } + return &withMessage{ + cause: err, + msg: message, + } +} + +type withMessage struct { + cause error + msg string +} + +func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } +func (w *withMessage) Cause() error { return w.cause } + +func (w *withMessage) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", w.Cause()) + io.WriteString(s, w.msg) + return + } + fallthrough + case 's', 'q': + io.WriteString(s, w.Error()) } } @@ -191,7 +246,7 @@ func Wrapf(err error, format string, args ...interface{}) error { // An error value has a cause if it implements the following // interface: // -// type Causer interface { +// type causer interface { // Cause() error // } // diff --git a/vendor/github.com/pkg/errors/errors_test.go b/vendor/github.com/pkg/errors/errors_test.go deleted file mode 100644 index 11d45554..00000000 --- a/vendor/github.com/pkg/errors/errors_test.go +++ /dev/null @@ -1,159 +0,0 @@ -package errors - -import ( - "errors" - "fmt" - "io" - "reflect" - "testing" -) - -func TestNew(t *testing.T) { - tests := []struct { - err string - want error - }{ - {"", fmt.Errorf("")}, - {"foo", fmt.Errorf("foo")}, - {"foo", New("foo")}, - {"string with format specifiers: %v", errors.New("string with format specifiers: %v")}, - } - - for _, tt := range tests { - got := New(tt.err) - if got.Error() != tt.want.Error() { - t.Errorf("New.Error(): got: %q, want %q", got, tt.want) - } - } -} - -func TestWrapNil(t *testing.T) { - got := Wrap(nil, "no error") - if got != nil { - t.Errorf("Wrap(nil, \"no error\"): got %#v, expected nil", got) - } -} - -func TestWrap(t *testing.T) { - tests := []struct { - err error - message string - want string - }{ - {io.EOF, "read error", "read error: EOF"}, - {Wrap(io.EOF, "read error"), "client error", "client error: read error: EOF"}, - } - - for _, tt := range tests { - got := Wrap(tt.err, tt.message).Error() - if got != tt.want { - t.Errorf("Wrap(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want) - } - } -} - -type nilError struct{} - -func (nilError) Error() string { return "nil error" } - -func TestCause(t *testing.T) { - x := New("error") - tests := []struct { - err error - want error - }{{ - // nil error is nil - err: nil, - want: nil, - }, { - // explicit nil error is nil - err: (error)(nil), - want: nil, - }, { - // typed nil is nil - err: (*nilError)(nil), - want: (*nilError)(nil), - }, { - // uncaused error is unaffected - err: io.EOF, - want: io.EOF, - }, { - // caused error returns cause - err: Wrap(io.EOF, "ignored"), - want: io.EOF, - }, { - err: x, // return from errors.New - want: x, - }} - - for i, tt := range tests { - got := Cause(tt.err) - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("test %d: got %#v, want %#v", i+1, got, tt.want) - } - } -} - -func TestWrapfNil(t *testing.T) { - got := Wrapf(nil, "no error") - if got != nil { - t.Errorf("Wrapf(nil, \"no error\"): got %#v, expected nil", got) - } -} - -func TestWrapf(t *testing.T) { - tests := []struct { - err error - message string - want string - }{ - {io.EOF, "read error", "read error: EOF"}, - {Wrapf(io.EOF, "read error without format specifiers"), "client error", "client error: read error without format specifiers: EOF"}, - {Wrapf(io.EOF, "read error with %d format specifier", 1), "client error", "client error: read error with 1 format specifier: EOF"}, - } - - for _, tt := range tests { - got := Wrapf(tt.err, tt.message).Error() - if got != tt.want { - t.Errorf("Wrapf(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want) - } - } -} - -func TestErrorf(t *testing.T) { - tests := []struct { - err error - want string - }{ - {Errorf("read error without format specifiers"), "read error without format specifiers"}, - {Errorf("read error with %d format specifier", 1), "read error with 1 format specifier"}, - } - - for _, tt := range tests { - got := tt.err.Error() - if got != tt.want { - t.Errorf("Errorf(%v): got: %q, want %q", tt.err, got, tt.want) - } - } -} - -// errors.New, etc values are not expected to be compared by value -// but the change in errors#27 made them incomparable. Assert that -// various kinds of errors have a functional equality operator, even -// if the result of that equality is always false. -func TestErrorEquality(t *testing.T) { - tests := []struct { - err1, err2 error - }{ - {io.EOF, io.EOF}, - {io.EOF, nil}, - {io.EOF, errors.New("EOF")}, - {io.EOF, New("EOF")}, - {New("EOF"), New("EOF")}, - {New("EOF"), Errorf("EOF")}, - {New("EOF"), Wrap(io.EOF, "EOF")}, - } - for _, tt := range tests { - _ = tt.err1 == tt.err2 // mustn't panic - } -} diff --git a/vendor/github.com/pkg/errors/example_test.go b/vendor/github.com/pkg/errors/example_test.go deleted file mode 100644 index 5bec3ad4..00000000 --- a/vendor/github.com/pkg/errors/example_test.go +++ /dev/null @@ -1,152 +0,0 @@ -package errors_test - -import ( - "fmt" - - "github.com/pkg/errors" -) - -func ExampleNew() { - err := errors.New("whoops") - fmt.Println(err) - - // Output: whoops -} - -func ExampleNew_printf() { - err := errors.New("whoops") - fmt.Printf("%+v", err) - - // Example output: - // whoops - // github.com/pkg/errors_test.ExampleNew_printf - // /home/dfc/src/github.com/pkg/errors/example_test.go:17 - // testing.runExample - // /home/dfc/go/src/testing/example.go:114 - // testing.RunExamples - // /home/dfc/go/src/testing/example.go:38 - // testing.(*M).Run - // /home/dfc/go/src/testing/testing.go:744 - // main.main - // /github.com/pkg/errors/_test/_testmain.go:106 - // runtime.main - // /home/dfc/go/src/runtime/proc.go:183 - // runtime.goexit - // /home/dfc/go/src/runtime/asm_amd64.s:2059 -} - -func ExampleWrap() { - cause := errors.New("whoops") - err := errors.Wrap(cause, "oh noes") - fmt.Println(err) - - // Output: oh noes: whoops -} - -func fn() error { - e1 := errors.New("error") - e2 := errors.Wrap(e1, "inner") - e3 := errors.Wrap(e2, "middle") - return errors.Wrap(e3, "outer") -} - -func ExampleCause() { - err := fn() - fmt.Println(err) - fmt.Println(errors.Cause(err)) - - // Output: outer: middle: inner: error - // error -} - -func ExampleWrap_extended() { - err := fn() - fmt.Printf("%+v\n", err) - - // Example output: - // error - // github.com/pkg/errors_test.fn - // /home/dfc/src/github.com/pkg/errors/example_test.go:47 - // github.com/pkg/errors_test.ExampleCause_printf - // /home/dfc/src/github.com/pkg/errors/example_test.go:63 - // testing.runExample - // /home/dfc/go/src/testing/example.go:114 - // testing.RunExamples - // /home/dfc/go/src/testing/example.go:38 - // testing.(*M).Run - // /home/dfc/go/src/testing/testing.go:744 - // main.main - // /github.com/pkg/errors/_test/_testmain.go:104 - // runtime.main - // /home/dfc/go/src/runtime/proc.go:183 - // runtime.goexit - // /home/dfc/go/src/runtime/asm_amd64.s:2059 - // github.com/pkg/errors_test.fn - // /home/dfc/src/github.com/pkg/errors/example_test.go:48: inner - // github.com/pkg/errors_test.fn - // /home/dfc/src/github.com/pkg/errors/example_test.go:49: middle - // github.com/pkg/errors_test.fn - // /home/dfc/src/github.com/pkg/errors/example_test.go:50: outer -} - -func ExampleWrapf() { - cause := errors.New("whoops") - err := errors.Wrapf(cause, "oh noes #%d", 2) - fmt.Println(err) - - // Output: oh noes #2: whoops -} - -func ExampleErrorf_extended() { - err := errors.Errorf("whoops: %s", "foo") - fmt.Printf("%+v", err) - - // Example output: - // whoops: foo - // github.com/pkg/errors_test.ExampleErrorf - // /home/dfc/src/github.com/pkg/errors/example_test.go:101 - // testing.runExample - // /home/dfc/go/src/testing/example.go:114 - // testing.RunExamples - // /home/dfc/go/src/testing/example.go:38 - // testing.(*M).Run - // /home/dfc/go/src/testing/testing.go:744 - // main.main - // /github.com/pkg/errors/_test/_testmain.go:102 - // runtime.main - // /home/dfc/go/src/runtime/proc.go:183 - // runtime.goexit - // /home/dfc/go/src/runtime/asm_amd64.s:2059 -} - -func Example_stackTrace() { - type stackTracer interface { - StackTrace() errors.StackTrace - } - - err, ok := errors.Cause(fn()).(stackTracer) - if !ok { - panic("oops, err does not implement stackTracer") - } - - st := err.StackTrace() - fmt.Printf("%+v", st[0:2]) // top two frames - - // Example output: - // github.com/pkg/errors_test.fn - // /home/dfc/src/github.com/pkg/errors/example_test.go:47 - // github.com/pkg/errors_test.Example_stackTrace - // /home/dfc/src/github.com/pkg/errors/example_test.go:127 -} - -func ExampleCause_printf() { - err := errors.Wrap(func() error { - return func() error { - return errors.Errorf("hello %s", fmt.Sprintf("world")) - }() - }(), "failed") - - fmt.Printf("%v", err) - - // Output: failed: hello world -} diff --git a/vendor/github.com/pkg/errors/format_test.go b/vendor/github.com/pkg/errors/format_test.go deleted file mode 100644 index d1b2f1b3..00000000 --- a/vendor/github.com/pkg/errors/format_test.go +++ /dev/null @@ -1,153 +0,0 @@ -package errors - -import ( - "fmt" - "io" - "regexp" - "strings" - "testing" -) - -func TestFormatNew(t *testing.T) { - tests := []struct { - error - format string - want string - }{{ - New("error"), - "%s", - "error", - }, { - New("error"), - "%v", - "error", - }, { - New("error"), - "%+v", - "error\n" + - "github.com/pkg/errors.TestFormatNew\n" + - "\t.+/github.com/pkg/errors/format_test.go:25", - }} - - for _, tt := range tests { - testFormatRegexp(t, tt.error, tt.format, tt.want) - } -} - -func TestFormatErrorf(t *testing.T) { - tests := []struct { - error - format string - want string - }{{ - Errorf("%s", "error"), - "%s", - "error", - }, { - Errorf("%s", "error"), - "%v", - "error", - }, { - Errorf("%s", "error"), - "%+v", - "error\n" + - "github.com/pkg/errors.TestFormatErrorf\n" + - "\t.+/github.com/pkg/errors/format_test.go:51", - }} - - for _, tt := range tests { - testFormatRegexp(t, tt.error, tt.format, tt.want) - } -} - -func TestFormatWrap(t *testing.T) { - tests := []struct { - error - format string - want string - }{{ - Wrap(New("error"), "error2"), - "%s", - "error2: error", - }, { - Wrap(New("error"), "error2"), - "%v", - "error2: error", - }, { - Wrap(New("error"), "error2"), - "%+v", - "error\n" + - "github.com/pkg/errors.TestFormatWrap\n" + - "\t.+/github.com/pkg/errors/format_test.go:77", - }, { - Wrap(io.EOF, "error"), - "%s", - "error: EOF", - }, { - Wrap(New("error with space"), "context"), - "%q", - `"context: error with space"`, - }} - - for _, tt := range tests { - testFormatRegexp(t, tt.error, tt.format, tt.want) - } -} - -func TestFormatWrapf(t *testing.T) { - tests := []struct { - error - format string - want string - }{{ - Wrapf(New("error"), "error%d", 2), - "%s", - "error2: error", - }, { - Wrap(io.EOF, "error"), - "%v", - "error: EOF", - }, { - Wrap(io.EOF, "error"), - "%+v", - "EOF\n" + - "error\n" + - "github.com/pkg/errors.TestFormatWrapf\n" + - "\t.+/github.com/pkg/errors/format_test.go:111", - }, { - Wrapf(New("error"), "error%d", 2), - "%v", - "error2: error", - }, { - Wrapf(New("error"), "error%d", 2), - "%+v", - "error\n" + - "github.com/pkg/errors.TestFormatWrapf\n" + - "\t.+/github.com/pkg/errors/format_test.go:122", - }, { - Wrap(Wrap(io.EOF, "error1"), "error2"), - "%+v", - "EOF\n" + - "error1\n" + - "github.com/pkg/errors.TestFormatWrapf\n" + - "\t.+/github.com/pkg/errors/format_test.go:128\n", - }} - - for _, tt := range tests { - testFormatRegexp(t, tt.error, tt.format, tt.want) - } -} - -func testFormatRegexp(t *testing.T, arg interface{}, format, want string) { - got := fmt.Sprintf(format, arg) - lines := strings.SplitN(got, "\n", -1) - for i, w := range strings.SplitN(want, "\n", -1) { - match, err := regexp.MatchString(w, lines[i]) - if err != nil { - t.Fatal(err) - } - if !match { - t.Errorf("fmt.Sprintf(%q, err): got: %q, want: %q", format, got, want) - } - } -} diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go index 243a64a2..6b1f2891 100644 --- a/vendor/github.com/pkg/errors/stack.go +++ b/vendor/github.com/pkg/errors/stack.go @@ -100,6 +100,19 @@ func (st StackTrace) Format(s fmt.State, verb rune) { // stack represents a stack of program counters. type stack []uintptr +func (s *stack) Format(st fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case st.Flag('+'): + for _, pc := range *s { + f := Frame(pc) + fmt.Fprintf(st, "\n%+v", f) + } + } + } +} + func (s *stack) StackTrace() StackTrace { f := make([]Frame, len(*s)) for i := 0; i < len(f); i++ { diff --git a/vendor/github.com/pkg/errors/stack_test.go b/vendor/github.com/pkg/errors/stack_test.go deleted file mode 100644 index cd7827bf..00000000 --- a/vendor/github.com/pkg/errors/stack_test.go +++ /dev/null @@ -1,295 +0,0 @@ -package errors - -import ( - "fmt" - "runtime" - "testing" -) - -var initpc, _, _, _ = runtime.Caller(0) - -func TestFrameLine(t *testing.T) { - var tests = []struct { - Frame - want int - }{{ - Frame(initpc), - 9, - }, { - func() Frame { - var pc, _, _, _ = runtime.Caller(0) - return Frame(pc) - }(), - 20, - }, { - func() Frame { - var pc, _, _, _ = runtime.Caller(1) - return Frame(pc) - }(), - 28, - }, { - Frame(0), // invalid PC - 0, - }} - - for _, tt := range tests { - got := tt.Frame.line() - want := tt.want - if want != got { - t.Errorf("Frame(%v): want: %v, got: %v", uintptr(tt.Frame), want, got) - } - } -} - -type X struct{} - -func (x X) val() Frame { - var pc, _, _, _ = runtime.Caller(0) - return Frame(pc) -} - -func (x *X) ptr() Frame { - var pc, _, _, _ = runtime.Caller(0) - return Frame(pc) -} - -func TestFrameFormat(t *testing.T) { - var tests = []struct { - Frame - format string - want string - }{{ - Frame(initpc), - "%s", - "stack_test.go", - }, { - Frame(initpc), - "%+s", - "github.com/pkg/errors.init\n" + - "\t.+/github.com/pkg/errors/stack_test.go", - }, { - Frame(0), - "%s", - "unknown", - }, { - Frame(0), - "%+s", - "unknown", - }, { - Frame(initpc), - "%d", - "9", - }, { - Frame(0), - "%d", - "0", - }, { - Frame(initpc), - "%n", - "init", - }, { - func() Frame { - var x X - return x.ptr() - }(), - "%n", - `\(\*X\).ptr`, - }, { - func() Frame { - var x X - return x.val() - }(), - "%n", - "X.val", - }, { - Frame(0), - "%n", - "", - }, { - Frame(initpc), - "%v", - "stack_test.go:9", - }, { - Frame(initpc), - "%+v", - "github.com/pkg/errors.init\n" + - "\t.+/github.com/pkg/errors/stack_test.go:9", - }, { - Frame(0), - "%v", - "unknown:0", - }} - - for _, tt := range tests { - testFormatRegexp(t, tt.Frame, tt.format, tt.want) - } -} - -func TestFuncname(t *testing.T) { - tests := []struct { - name, want string - }{ - {"", ""}, - {"runtime.main", "main"}, - {"github.com/pkg/errors.funcname", "funcname"}, - {"funcname", "funcname"}, - {"io.copyBuffer", "copyBuffer"}, - {"main.(*R).Write", "(*R).Write"}, - } - - for _, tt := range tests { - got := funcname(tt.name) - want := tt.want - if got != want { - t.Errorf("funcname(%q): want: %q, got %q", tt.name, want, got) - } - } -} - -func TestTrimGOPATH(t *testing.T) { - var tests = []struct { - Frame - want string - }{{ - Frame(initpc), - "github.com/pkg/errors/stack_test.go", - }} - - for _, tt := range tests { - pc := tt.Frame.pc() - fn := runtime.FuncForPC(pc) - file, _ := fn.FileLine(pc) - got := trimGOPATH(fn.Name(), file) - want := tt.want - if want != got { - t.Errorf("%v: want %q, got %q", tt.Frame, want, got) - } - } -} - -func TestStackTrace(t *testing.T) { - tests := []struct { - err error - want []string - }{{ - New("ooh"), []string{ - "github.com/pkg/errors.TestStackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:175", - }, - }, { - Wrap(New("ooh"), "ahh"), []string{ - "github.com/pkg/errors.TestStackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:180", // this is the stack of Wrap, not New - }, - }, { - Cause(Wrap(New("ooh"), "ahh")), []string{ - "github.com/pkg/errors.TestStackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:185", // this is the stack of New - }, - }, { - func() error { return New("ooh") }(), []string{ - `github.com/pkg/errors.(func·005|TestStackTrace.func1)` + - "\n\t.+/github.com/pkg/errors/stack_test.go:190", // this is the stack of New - "github.com/pkg/errors.TestStackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:190", // this is the stack of New's caller - }, - }, { - Cause(func() error { - return func() error { - return Errorf("hello %s", fmt.Sprintf("world")) - }() - }()), []string{ - `github.com/pkg/errors.(func·006|TestStackTrace.func2.1)` + - "\n\t.+/github.com/pkg/errors/stack_test.go:199", // this is the stack of Errorf - `github.com/pkg/errors.(func·007|TestStackTrace.func2)` + - "\n\t.+/github.com/pkg/errors/stack_test.go:200", // this is the stack of Errorf's caller - "github.com/pkg/errors.TestStackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:201", // this is the stack of Errorf's caller's caller - }, - }} - for _, tt := range tests { - x, ok := tt.err.(interface { - StackTrace() StackTrace - }) - if !ok { - t.Errorf("expected %#v to implement StackTrace() StackTrace", tt.err) - continue - } - st := x.StackTrace() - for j, want := range tt.want { - testFormatRegexp(t, st[j], "%+v", want) - } - } -} - -func stackTrace() StackTrace { - const depth = 8 - var pcs [depth]uintptr - n := runtime.Callers(1, pcs[:]) - var st stack = pcs[0:n] - return st.StackTrace() -} - -func TestStackTraceFormat(t *testing.T) { - tests := []struct { - StackTrace - format string - want string - }{{ - nil, - "%s", - `\[\]`, - }, { - nil, - "%v", - `\[\]`, - }, { - nil, - "%+v", - "", - }, { - nil, - "%#v", - `\[\]errors.Frame\(nil\)`, - }, { - make(StackTrace, 0), - "%s", - `\[\]`, - }, { - make(StackTrace, 0), - "%v", - `\[\]`, - }, { - make(StackTrace, 0), - "%+v", - "", - }, { - make(StackTrace, 0), - "%#v", - `\[\]errors.Frame{}`, - }, { - stackTrace()[:2], - "%s", - `\[stack_test.go stack_test.go\]`, - }, { - stackTrace()[:2], - "%v", - `\[stack_test.go:228 stack_test.go:275\]`, - }, { - stackTrace()[:2], - "%+v", - "\n" + - "github.com/pkg/errors.stackTrace\n" + - "\t.+/github.com/pkg/errors/stack_test.go:228\n" + - "github.com/pkg/errors.TestStackTraceFormat\n" + - "\t.+/github.com/pkg/errors/stack_test.go:279", - }, { - stackTrace()[:2], - "%#v", - `\[\]errors.Frame{stack_test.go:228, stack_test.go:287}`, - }} - - for _, tt := range tests { - testFormatRegexp(t, tt.StackTrace, tt.format, tt.want) - } -} From 17277bc6f1f0d9640a44f937808a8ef472753397 Mon Sep 17 00:00:00 2001 From: Abdul Charif Date: Mon, 4 Nov 2019 11:38:14 -0500 Subject: [PATCH 04/13] remove dead repo gocs cloudstack API --- vendor/github.com/svanharmelen/gocs/doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/github.com/svanharmelen/gocs/doc.go b/vendor/github.com/svanharmelen/gocs/doc.go index f84ab394..9818680a 100644 --- a/vendor/github.com/svanharmelen/gocs/doc.go +++ b/vendor/github.com/svanharmelen/gocs/doc.go @@ -105,4 +105,4 @@ // } // fmt.Println(string(response)) // } -package gocs +// package gocs From ab0383cd0eaa7e95d026f31091da405ed6dc3ec3 Mon Sep 17 00:00:00 2001 From: Abdul Charif Date: Mon, 4 Nov 2019 12:20:28 -0500 Subject: [PATCH 05/13] remove dead repo gocs cloudstack API --- .../github.com/svanharmelen/gocs/.gitignore | 22 -- vendor/github.com/svanharmelen/gocs/LICENSE | 202 -------------- vendor/github.com/svanharmelen/gocs/README.md | 30 --- vendor/github.com/svanharmelen/gocs/cache.go | 103 ------- vendor/github.com/svanharmelen/gocs/doc.go | 108 -------- vendor/github.com/svanharmelen/gocs/gocs.go | 252 ----------------- vendor/github.com/svanharmelen/gocs/params.go | 134 ---------- .../svanharmelen/gocs/unmarshaler.go | 253 ------------------ 8 files changed, 1104 deletions(-) delete mode 100644 vendor/github.com/svanharmelen/gocs/.gitignore delete mode 100644 vendor/github.com/svanharmelen/gocs/LICENSE delete mode 100644 vendor/github.com/svanharmelen/gocs/README.md delete mode 100644 vendor/github.com/svanharmelen/gocs/cache.go delete mode 100644 vendor/github.com/svanharmelen/gocs/doc.go delete mode 100644 vendor/github.com/svanharmelen/gocs/gocs.go delete mode 100644 vendor/github.com/svanharmelen/gocs/params.go delete mode 100644 vendor/github.com/svanharmelen/gocs/unmarshaler.go diff --git a/vendor/github.com/svanharmelen/gocs/.gitignore b/vendor/github.com/svanharmelen/gocs/.gitignore deleted file mode 100644 index 00268614..00000000 --- a/vendor/github.com/svanharmelen/gocs/.gitignore +++ /dev/null @@ -1,22 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe diff --git a/vendor/github.com/svanharmelen/gocs/LICENSE b/vendor/github.com/svanharmelen/gocs/LICENSE deleted file mode 100644 index e06d2081..00000000 --- a/vendor/github.com/svanharmelen/gocs/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - diff --git a/vendor/github.com/svanharmelen/gocs/README.md b/vendor/github.com/svanharmelen/gocs/README.md deleted file mode 100644 index 33e46f7e..00000000 --- a/vendor/github.com/svanharmelen/gocs/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# gocs - -gocs is a CloudStack client that enables Go programs to interact with the CloudStack API in a simple and uniform way. -The package itself is smart enough to learn the available CloudStack API commands and the required and optional -parameters of all available commands (using the listApis command), so it can be used with any given version of -CloudStack. - -Based on the processed info about all available commands and their details, the request functions will check -if all required parameters have a value and if all other used parameters are valid parameters for the requested -command before actually passing the request to the API. In case of any errors during this validation or when -executing the actual request, detailed error info will be returned to the requesting function making it very -easy to understand and fix the problem. - - -## Usage - -Install/Update the Go package: -``` -go get -u github.com/svanharmelen/gocs -``` - -Add this to your Go program: -``` -import "github.com/svanharmelen/gocs" -``` - - -## Documentation - -Package documentation is dynamically generated from the source code at [GoDoc](http://godoc.org/github.com/svanharmelen/gocs) diff --git a/vendor/github.com/svanharmelen/gocs/cache.go b/vendor/github.com/svanharmelen/gocs/cache.go deleted file mode 100644 index 8f99293a..00000000 --- a/vendor/github.com/svanharmelen/gocs/cache.go +++ /dev/null @@ -1,103 +0,0 @@ -// -// Copyright 2014, Sander van Harmelen -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package gocs - -import ( - "encoding/gob" - "net/url" - "os" - "path" - "strings" - "time" - - "github.com/mitchellh/osext" -) - -var cacheFile string - -func setCacheFile() error { - if cacheFile == "" { - exe, err := osext.Executable() - if err != nil { - return err - } - strings.TrimSuffix(exe, path.Ext(exe)) - cacheFile = exe + ".cache" - } - return nil -} - -// Updates (or creates) a cache containing the API's structure -func cacheAPICommands(cs *CloudStackClient, cacheExpirationInDays int) (err error) { - if cacheExpirationInDays > 0 { - if err := setCacheFile(); err != nil { - return err - } - beforeDate := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day()-cacheExpirationInDays, time.Now().Hour(), time.Now().Minute(), time.Now().Second(), time.Now().Nanosecond(), time.Local) - if fileInfo, err := os.Stat(cacheFile); err != nil || fileInfo.ModTime().Before(beforeDate) { - if err := updateAPICommandFile(cs); err != nil { - return err - } - } - cs.apiCommands, err = readAPICommandFile(cs) - } else { - cs.apiCommands, err = requestAPICommands(cs) - } - return -} - -func readAPICommandFile(cs *CloudStackClient) (commands, error) { - reader, err := os.Open(cacheFile) - if err != nil { - return nil, err - } - defer reader.Close() - - decoder := gob.NewDecoder(reader) - var apiCommands commands - if err := decoder.Decode(&apiCommands); err != nil { - return nil, err - } - return apiCommands, nil -} - -// Calls newRequest() directly as there is no cache yet, so it's not -// possible to check and verify this call which is done by the exported -// request functions -func requestAPICommands(cs *CloudStackClient) (commands, error) { - rawJSON, err := newRequest(cs, &command{Name: "listApis"}, url.Values{}) - if err != nil { - return nil, err - } - return unmarshalApiCommands(rawJSON) -} - -func updateAPICommandFile(cs *CloudStackClient) error { - result, err := requestAPICommands(cs) - if err != nil { - return err - } - - writer, err := os.Create(cacheFile) - if err != nil { - return err - } - defer writer.Close() - - encoder := gob.NewEncoder(writer) - return encoder.Encode(result) -} diff --git a/vendor/github.com/svanharmelen/gocs/doc.go b/vendor/github.com/svanharmelen/gocs/doc.go deleted file mode 100644 index 9818680a..00000000 --- a/vendor/github.com/svanharmelen/gocs/doc.go +++ /dev/null @@ -1,108 +0,0 @@ -//gocs is a CloudStack client that enables Go programs to interact with the CloudStack API in a simple and uniform way. -//The package itself is smart enough to learn the available CloudStack API commands and the required and optional -//parameters of all available commands (using the listApis command), so it can be used with any given version of -//CloudStack. -// -//Based on the processed info about all available commands and their details, the request functions will check -//if all required parameters have a value and if all other used parameters are valid parameters for the requested -//command before actually passing the request to the API. In case of any errors during this validation or when -//executing the actual request, detailed error info will be returned to the requesting function making it very -//easy to understand and fix the problem. -// -//A usefull (BETA!) feature when making API requests is that the request functions are able to retieve ID's for parameters -//that need an ID of some kind (e.g. virtualmachine, zone) instead of a name. Every parameter that ends with 'id' can also -//be used as a parameter without the 'id' and a value that is a name rather than an ID. In those cases the request -//functions will take care of retrieving all the needed ID's for you. -// -//NOTE: This only works when the list command for the needed ID (e.g. listVirtualMachines, listZones) doesn't require -//additional parameters like the listTemplates does, and (again) it's still a BETA functionality. When the list command -//contains required parameters you would still need to make a call yourself supplying the correct values. And again, this -//is still, and it's still BETA functionality -// -//See these two small snippets that show two ways to make the same call. Starting with the more elaborate one: -// -// // Original call using ID's, so need to retrieve all ID's first -// serviceofferingid, err := cs.Request("listServiceOfferings", "name:small") -// if err =! nil { -// return err -// } -// templateid, err := cs.Request("listTemplates", "templatefilter:featured, name:Centos 6 x64") -// if err =! nil { -// return err -// } -// zoneid, err := cs.Request("listZones", "name:myzone") -// if err =! nil { -// return err -// } -// if id, err := cs.Request("deployVirtualMachine", fmt.Sprintf("serviceofferingid:%s, templateid:%s, zoneid:%s", -// serviceofferingid, templateid, zoneid); err != nil { -// return err -// } -// return id -// -//Followed by the easier one: -// -// // Easier call using only one additional call to retrieve the templateid -// zoneid, err := cs.Request("listTemplates", "templatefilter:featured, name:Centos 6 x64") -// if err =! nil { -// return err -// } -// if id, err := cs.Request("deployVirtualMachine", fmt.Sprintf("serviceoffering:small, templateid:%s, -// zone:my zone", id)); err != nil { -// return err -// } -// return id -// -// -//The response you get from a request is determined by the type of request you are using. When using a normal -//request the function returns only an ID (if the command itself returns an ID of course) or an empty string. -//Additionally you can use a 'raw' request to get the complete raw JSON received from the API. In the latter -//case unmarshalling can be done using a custom struct that matches the response of the called command that -//fits the needs of the program using this package. -// -//The example below shows how to use the package in a very simple commandline utility. The demo utility -//uses a caching client and a raw synced request which waits for async calls to finish and returns the raw -//JSON response as output: -// -// package main -// -// import ( -// "github.com/svanharmelen/gocs" -// "fmt" -// "log" -// "os" -// "path" -// "strings" -// ) -// -// // Off course you better not do this in an actual program :) -// const ( -// apiurl = "https://cloudstack.url/client/api" -// apikey = "xxx" -// secret = "xxx" -// ) -// -// func main() { -// if len(os.Args) == 1 || os.Args[1] == "-h" || os.Args[1] == "--help" { -// fmt.Printf("Usage: %s command \n", path.Base(os.Args[0])) -// os.Exit(1) -// } -// -// // Create a new caching client -// cs, err := gocs.NewCachingClient(apiurl, apikey, secret, 1, false) -// if err != nil { -// log.Fatal(err) -// } -// -// // Get the command and join all 'parameter:value' pairs into one comma separated string -// command := os.Args[1] -// params := strings.Join(os.Args[2:], ",") -// -// // Make a raw, but synced request -// response, err := cs.RawSyncedRequest(command, params) -// if err != nil { -// log.Fatal(err) -// } -// fmt.Println(string(response)) -// } -// package gocs diff --git a/vendor/github.com/svanharmelen/gocs/gocs.go b/vendor/github.com/svanharmelen/gocs/gocs.go deleted file mode 100644 index aae994b2..00000000 --- a/vendor/github.com/svanharmelen/gocs/gocs.go +++ /dev/null @@ -1,252 +0,0 @@ -// -// Copyright 2014, Sander van Harmelen -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package gocs - -import ( - "crypto/hmac" - "crypto/sha1" - "crypto/tls" - "encoding/base64" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "strings" - "time" -) - -type commands map[string]*command - -type CloudStackClient struct { - client *http.Client // The http client for communicating - apiCommands commands // All available API commands - baseURL string // The base URL of the API - apiKey string // Api key - secret string // Secret key - timeout int64 // Max waiting timeout in seconds for async jobs to finish; defaults to 60 seconds -} - -// Creates a new client for communicating with CloudStack -func newClient(apiurl string, apikey string, secret string, verifyssl bool) *CloudStackClient { - cs := CloudStackClient{ - client: &http.Client{ - Transport: &http.Transport{ - Proxy: http.ProxyFromEnvironment, - TLSClientConfig: &tls.Config{InsecureSkipVerify: !verifyssl}, // If verifyssl is true, skipping the verify should be false and vice versa - }, - }, - baseURL: apiurl, - apiKey: apikey, - secret: secret, - timeout: 60, - } - return &cs -} - -// Creates a new non-caching CloudStack client that doesn't use any local cache for the API command info. This means that every request -// made using this client will result in (at least) 2 calls to the API. The first one will retrieve and process all available commands -// (including their details like required and optional parameters) and the second one will be the actual request. In between the -// retrieved API command info will be used to validate the request before actually executing it. -// -// When using HTTPS with a self-signed certificate to connect to your CloudStack API, you would probably want to set 'verifyssl' to -// false so the call ignores the SSL errors/warnings. -func NewClient(apiurl string, apikey string, secret string, verifyssl bool) (*CloudStackClient, error) { - cs := newClient(apiurl, apikey, secret, verifyssl) - err := cacheAPICommands(cs, 0) - return cs, err -} - -// Creates a new caching CloudStack client which uses a local cache file for the API command info which in turn is used to validate a -// request before executing it. The 'cacheExpirationInDays' parameter is used to determine when the cache needs to be updated. It's -// recommended to always use this client when working with a (more or less) stable CloudStack environment as using this client will speed -// up requests considerably and will prevent unnecessary load on the API server. When you are developing agains multiple versions of -// CloudStack or if you are using this package as part of a tool/program to migrate or update CloudStack, it's probably a good idea to -// use the non-caching client instead. -// -// When using HTTPS with a self-signed certificate to connect to your CloudStack API, you would probably want to set 'verifyssl' to -// false so the call ignores the SSL errors/warnings. -func NewCachingClient(apiurl string, apikey string, secret string, cacheExpirationInDays int, verifyssl bool) (*CloudStackClient, error) { - cs := newClient(apiurl, apikey, secret, verifyssl) - err := cacheAPICommands(cs, cacheExpirationInDays) - return cs, err -} - -// When using a synced request the function will wait for the async call to finish before returning. The default is to poll for 60 -// seconds, to check if the async job is finished. If the job is not finished within this time, the async job details will be returned -// instead of the actual result of the async command requested. Using this method you can tweak the timeout to fit your needs. -func (cs *CloudStackClient) AsyncTimeout(timeoutInSeconds int64) { - cs.timeout = timeoutInSeconds -} - -// Executes a raw request after checking and verifing the command and parameters first. The parameters can be passed as a string or -// as a map[string]string. In the latter case the keys of the map are treated as the parameter names and the values as, well, the values :) -// When sending the parameters as a single string, it needs to have the following format: "param1:value1, param2:value with space2, -// param3:value3". The return value of a successful raw request is always the raw JSON that is received as response from the API and nil. -// The return value of a failed raw request is nil and a detailed error describing the problem. -func (cs *CloudStackClient) RawRequest(command string, params interface{}) (json.RawMessage, error) { - cmd, found := cs.apiCommands[strings.ToLower(command)] - if !found { - return nil, fmt.Errorf("'%s' is not a valid CloudStack command", command) - } - parms, err := processParams(cs, cmd, params) - if err != nil { - return nil, err - } - return newRequest(cs, cmd, parms) -} - -// Executes a request after checking and verifing the command and parameters first. The parameters can be passed as a string or -// as a map[string]string. In the latter case the keys of the map are treated as the parameter names and the values as, well, -// the values :) When sending the parameters as a single string, it needs to have the following format: "param1:value1, -// param2:value with space2, param3:value3". The return value of a successful request is either a single string representing the -// returned ID or an empty string when the requested command doesn't return an ID and nil. The return value of a failed request -// is an empty string and a detailed error describing the problem. So you would definitely want to check the returned error -// value here to determine if the call was successful or not. -func (cs *CloudStackClient) Request(command string, params interface{}) (string, error) { - rawJSON, err := cs.RawRequest(command, params) - if err != nil { - return "", err - } - cmd := cs.apiCommands[strings.ToLower(command)] - if strings.HasPrefix(strings.ToLower(cmd.Name), "list") { - return unmarshalListId(strings.TrimSuffix(strings.TrimPrefix(strings.ToLower(cmd.Name), "list"), "s"), rawJSON) - } - if cmd.ReturnsAnId { - return unmarshalId(strings.ToLower(cmd.Name), rawJSON) - } - return "", nil -} - -// For sync API commands this function behaves exactly the same as a standard RawRequest() call, but for async API commands -// this function will wait until the async job is finished or until the configured AsyncTimeout is reached. When the async -// job finishes successfully it will return the raw JSON response received from the API and nil, but when the timout is -// reached it will return nil and an error containing the async job ID for the running job. -func (cs *CloudStackClient) RawSyncedRequest(command string, params interface{}) (json.RawMessage, error) { - rawJSON, err := cs.RawRequest(command, params) - if err != nil { - return nil, err - } - cmd := cs.apiCommands[strings.ToLower(command)] - if cmd.Isasync { - asyncJobResult, err := getAsyncJobResult(cs, rawJSON) - if err != nil { - return nil, err - } - if asyncJobResult.Jobstatus == 2 { - if asyncJobResult.Jobresulttype == "text" { - return nil, fmt.Errorf(string(asyncJobResult.Jobresult)) - } else { - return nil, fmt.Errorf("Undefined error: %s", string(asyncJobResult.Jobresult)) - } - } - return asyncJobResult.Jobresult, nil - } - return rawJSON, nil -} - -// For sync API commands this function behaves exactly the same as a standard Request() call, but for async API commands -// this function will wait until the async job is finished or until the configured AsyncTimeout is reached. When the async -// job finishes successfully it will return the ID or an empty string when the requested command doesn't return an ID and -// nil. When the timout is reached it will return an empty string and an error containing the async job ID for the running job. -func (cs *CloudStackClient) SyncedRequest(command string, params interface{}) (string, error) { - rawJSON, err := cs.RawSyncedRequest(command, params) - if err != nil { - return "", err - } - cmd := cs.apiCommands[strings.ToLower(command)] - if strings.HasPrefix(strings.ToLower(cmd.Name), "list") { - return unmarshalListId(strings.TrimSuffix(strings.TrimPrefix(strings.ToLower(cmd.Name), "list"), "s"), rawJSON) - } - if cmd.ReturnsAnId { - return unmarshalId(strings.ToLower(cmd.Name), rawJSON) - } - return "", nil -} - -func getAsyncJobResult(cs *CloudStackClient, rawJSON json.RawMessage) (*asyncJobResult, error) { - result, err := unmarshalAsyncResponse(rawJSON) - if err != nil { - return nil, err - } - currentTime := time.Now().Unix() - for { - rawJSON, err := cs.RawRequest("queryAsyncJobResult", fmt.Sprintf("jobid:%s", result.Jobid)) - if err != nil { - return nil, err - } - jobResult, err := unmarshalAsyncJobResponse(rawJSON) - if err != nil { - return nil, err - } - if jobResult.Jobstatus > 0 { - return jobResult, nil - } - if time.Now().Unix()-currentTime > cs.timeout { - return nil, fmt.Errorf("Timeout while waiting for async job to finish (jobid:%s)", result.Jobid) - } - time.Sleep(3 * time.Second) - } -} - -// Execute the request against a CS API. Will return the raw JSON data returned by the API and nil if -// no error occured. If the API returns an error the result will be nil and the HTTP error code and CS -// error details. If a processing (code) error occurs the result will be nil and the generated error -func newRequest(cs *CloudStackClient, cmd *command, params url.Values) (json.RawMessage, error) { - client := cs.client - - params.Set("apikey", cs.apiKey) - params.Set("command", cmd.Name) - params.Set("response", "json") - - // Generate signature for API call - // * Serialize parameters and sort them by key, done by Encode - // * Convert the entire argument string to lowercase - // * Replace all instances of '+' to '%20' - // * Calculate HMAC SHA1 of argument string with CloudStack secret - // * URL encode the string and convert to base64 - s := params.Encode() - s2 := strings.ToLower(s) - s3 := strings.Replace(s2, "+", "%20", -1) - mac := hmac.New(sha1.New, []byte(cs.secret)) - mac.Write([]byte(s3)) - signature := base64.StdEncoding.EncodeToString(mac.Sum(nil)) - signature = url.QueryEscape(signature) - - // Create the final URL before we issue the request - url := cs.baseURL + "?" + s + "&signature=" + signature - - resp, err := client.Get(url) - if err != nil { - return nil, err - } - - rawJSON, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - return nil, err - } - - if resp.StatusCode != 200 { - errorInfo, err := unmarshalCsError(rawJSON) - if err != nil { - return nil, err - } - return nil, fmt.Errorf("CloudStack API error %d (CSExceptionErrorCode: %d): %s", errorInfo.Errorcode, errorInfo.CsErrorcode, errorInfo.Errortext) - } - return rawJSON, nil -} diff --git a/vendor/github.com/svanharmelen/gocs/params.go b/vendor/github.com/svanharmelen/gocs/params.go deleted file mode 100644 index 36f9ee3b..00000000 --- a/vendor/github.com/svanharmelen/gocs/params.go +++ /dev/null @@ -1,134 +0,0 @@ -// -// Copyright 2014, Sander van Harmelen -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package gocs - -import ( - "fmt" - "net/url" - "strings" -) - -const ( - key = iota - value -) - -type csparams map[string]string - -// Return a checked url.Values value -func processParams(cs *CloudStackClient, cmd *command, rawParams interface{}) (url.Values, error) { - params, err := convertType(rawParams) - if err != nil { - return nil, err - } - if err := verifyRequiredParams(cs, cmd, ¶ms); err != nil { - return nil, err - } - if err := verifyAllUsedParams(cs, cmd, ¶ms); err != nil { - return nil, err - } - urlValues := url.Values{} - for key, value := range params { - urlValues.Set(key, value) - } - return urlValues, nil -} - -func convertType(rawParams interface{}) (params csparams, err error) { - switch rawParams.(type) { - case string: - params, err = parseParams(rawParams.(string)) - case map[string]string: - params = csparams(rawParams.(map[string]string)) - default: - return nil, fmt.Errorf("Wrong type: '%T'; only 'string' and 'map[string]string' are valid rawParams types", rawParams) - } - return -} - -func parseParams(rawParams string) (csparams, error) { - params := csparams{} - if len(rawParams) == 0 { - return params, nil - } - paramSlice := strings.Split(rawParams, ",") - for _, paramSet := range paramSlice { - keyValue := strings.SplitN(paramSet, ":", 2) - if len(keyValue) != 2 || len(keyValue[value]) == 0 { - return nil, fmt.Errorf("Error processing parameters around: ... %s ...", paramSet) - } - params[strings.ToLower(strings.Trim(strings.TrimSpace(keyValue[key]), `"`))] = strings.Trim(strings.TrimSpace(keyValue[value]), `"`) - } - return params, nil -} - -// Test to see of all the required parameters are set -func verifyRequiredParams(cs *CloudStackClient, cmd *command, rawParams *csparams) error { - params := *rawParams - for key := range cmd.RequiredParams { - if _, found := params[key]; !found { - if _, found := params[strings.TrimSuffix(key, "id")]; found { - if err := convertToId(cs, strings.TrimSuffix(key, "id"), ¶ms); err != nil { - return err - } - } else { - return fmt.Errorf("Missing required parameter: %s", key) - } - } - } - return nil -} - -// Test to see is all used parameters are valid parameters for the requested command -func verifyAllUsedParams(cs *CloudStackClient, cmd *command, rawParams *csparams) error { - params := *rawParams - // Make a slice of keys to walk over, as we could be changing the params value - // during the loop, if we find a key that needs to be resolved to an id - keys := []string{} - for key := range params { - keys = append(keys, key) - } - for _, key := range keys { - if _, found := cmd.RequiredParams[key]; !found { - if _, found := cmd.OptionalParams[key]; !found { - if _, found := cmd.OptionalParams[fmt.Sprintf("%sid", key)]; found { - if err := convertToId(cs, key, ¶ms); err != nil { - return err - } - } else { - return fmt.Errorf("Parameter '%s' is not a valid parameter", key) - } - } - } - } - return nil -} - -func convertToId(cs *CloudStackClient, key string, rawParams *csparams) error { - params := *rawParams - rawJSON, err := cs.RawRequest(fmt.Sprintf("list%ss", key), fmt.Sprintf("name:%s", params[key])) - if err != nil { - return fmt.Errorf("Error trying to resolve the id for %s %s: %s", key, params[key], err) - } - id, err := unmarshalListId(key, rawJSON) - if err != nil { - return fmt.Errorf("Error trying to resolve the id for %s %s: %s", key, params[key], err) - } - delete(params, key) - params[fmt.Sprintf("%sid", key)] = id - return nil -} diff --git a/vendor/github.com/svanharmelen/gocs/unmarshaler.go b/vendor/github.com/svanharmelen/gocs/unmarshaler.go deleted file mode 100644 index a261909b..00000000 --- a/vendor/github.com/svanharmelen/gocs/unmarshaler.go +++ /dev/null @@ -1,253 +0,0 @@ -// -// Copyright 2014, Sander van Harmelen -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package gocs - -import ( - "encoding/json" - "fmt" - "strings" -) - -type command struct { - Name string - Isasync bool - ReturnsAnId bool - RequiredParams map[string]*apiParam - OptionalParams map[string]*apiParam -} - -type apiCommand struct { - Name string - Isasync bool - Params []*apiParam - Response []*apiResponse -} - -type apiParam struct { - Name string - Type string - Required bool -} - -type apiResponse struct { - Name string - Type string - Value string -} - -type csError struct { - Errorcode int - CsErrorcode int - Errortext string -} - -type asyncResult struct { - Id string - Jobid string -} - -type asyncJobResult struct { - Jobstatus float64 - Jobresulttype string - Jobresult json.RawMessage -} - -// Generic function to get the first value of a response as json.RawMessage -func getRawValue(rawJSON json.RawMessage) (json.RawMessage, error) { - var rawData map[string]json.RawMessage - if err := json.Unmarshal(rawJSON, &rawData); err != nil { - return nil, err - } - for _, rawValue := range rawData { - return rawValue, nil - } - return nil, fmt.Errorf("Unable to extract the raw value from:\n\n%s\n\n", string(rawJSON)) -} - -func unmarshalCsError(rawJSON json.RawMessage) (*csError, error) { - rawError, err := getRawValue(rawJSON) - if err != nil { - return nil, err - } - var errorInfo csError - if err := json.Unmarshal(rawError, &errorInfo); err != nil { - return nil, err - } - return &errorInfo, nil -} - -func unmarshalListId(key string, rawJSON json.RawMessage) (string, error) { - rawResponse, err := getRawValue(rawJSON) - if err != nil { - return "", err - } - - var result map[string]json.RawMessage - if err := json.Unmarshal(rawResponse, &result); err != nil { - return "", err - } - rawResponse, found := result[key] - if !found { - return "", fmt.Errorf("Unable to find id in result: %v", result) - } - - var id []struct{ Id string } - if err := json.Unmarshal(rawResponse, &id); err != nil { - return "", err - } - if len(id) > 1 { - return "", fmt.Errorf("Multiple id's found in result: %s", string(rawResponse)) - } - return id[0].Id, nil -} - -func unmarshalId(key string, rawJSON json.RawMessage) (string, error) { - - // strip out the message header since we know what type of response we have - rawResponse, err := getRawValue(rawJSON) - if err != nil { - return "", err - } - - // we should now have something which contains a "count" and an object based on the key - var result map[string]json.RawMessage - if err := json.Unmarshal(rawResponse, &result); err != nil { - return "", err - } - - // if we're lucky, the input key (found during Api enumeration) will be present - rawResult, found := result[key] - if !found { - // ok so this isn't good, but it doens't mean the end of the world - // go find that response key and see if it pattern matches the input key - for responseKey, value := range result { - if strings.Contains(strings.ToLower(key), strings.ToLower(responseKey)) { - found = true - key = responseKey - rawResult = value - break - } - } - - if !found { - return "", fmt.Errorf("Unable to find key '%s' in result: %v", key, result) - } - } - - var id []struct{ Id string } - if err := json.Unmarshal(rawResult, &id); err != nil { - return "", err - } - if len(id) > 1 { - return "", fmt.Errorf("Multiple id's found in result: %s", string(rawResponse)) - } - return id[0].Id, nil -} - -// this one is exported to allow for callers to readily obtain the real response data -func UnmarshalResponse(key string, rawJSON json.RawMessage) (json.RawMessage, error) { - - // strip out the message header since we know what type of response we have - rawResponse, err := getRawValue(rawJSON) - if err != nil { - return nil, err - } - - // we should now have something which contains a "count" and an object based on the key - var result map[string]json.RawMessage - if err := json.Unmarshal(rawResponse, &result); err != nil { - return nil, err - } - - // if we're lucky, the input key (found during Api enumeration) will be present - rawResult, found := result[key] - if !found { - // ok so this isn't good, but it doens't mean the end of the world - // go find that response key and see if it pattern matches the input key - for responseKey, value := range result { - if strings.Contains(strings.ToLower(key), strings.ToLower(responseKey)) { - found = true - key = responseKey - rawResult = value - break - } - } - - if !found { - return nil, fmt.Errorf("Unable to find key '%s' in result: %v", key, result) - } - } - - return rawResult, nil -} - -func unmarshalAsyncResponse(rawJSON json.RawMessage) (*asyncResult, error) { - rawResponse, err := getRawValue(rawJSON) - if err != nil { - return nil, err - } - var result asyncResult - if err := json.Unmarshal(rawResponse, &result); err != nil { - return nil, err - } - return &result, nil -} - -func unmarshalAsyncJobResponse(rawJSON json.RawMessage) (*asyncJobResult, error) { - rawResponse, err := getRawValue(rawJSON) - if err != nil { - return nil, err - } - var result asyncJobResult - if err := json.Unmarshal(rawResponse, &result); err != nil { - return nil, err - } - return &result, nil -} - -func unmarshalApiCommands(rawJSON json.RawMessage) (commands, error) { - var apisResult struct { - Listapis struct { - Count int - Api []*apiCommand - } `json:"listapisresponse"` - } - if err := json.Unmarshal(rawJSON, &apisResult); err != nil { - return nil, err - } - apiCommands := make(commands, apisResult.Listapis.Count) - for _, apiCmd := range apisResult.Listapis.Api { - cmd := command{apiCmd.Name, apiCmd.Isasync, false, map[string]*apiParam{}, map[string]*apiParam{}} - for _, apiParam := range apiCmd.Params { - if apiParam.Required { - cmd.RequiredParams[strings.ToLower(apiParam.Name)] = apiParam - } else { - cmd.OptionalParams[strings.ToLower(apiParam.Name)] = apiParam - } - } - if !strings.HasPrefix(apiCmd.Name, "list") { - for _, apiResp := range apiCmd.Response { - if strings.ToLower(apiResp.Name) == "id" { - cmd.ReturnsAnId = true - break - } - } - } - apiCommands[strings.ToLower(apiCmd.Name)] = &cmd - } - return apiCommands, nil -} From 0d205fad29a369b7920139294ba26d38bee8bfda Mon Sep 17 00:00:00 2001 From: Abdul Charif Date: Mon, 4 Nov 2019 12:27:52 -0500 Subject: [PATCH 06/13] remove gocs --- Godeps/Godeps.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index ca92d14e..54d6d6f2 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -183,11 +183,6 @@ "ImportPath": "github.com/satori/go.uuid", "Rev": "d41af8bb6a7704f00bc3b7cba9355ae6a5a80048" }, - { - "ImportPath": "github.com/svanharmelen/gocs", - "Comment": "v1.0.0-4-ga53656b", - "Rev": "a53656b67c7b6db48362e3b4edb5e335a7162bf0" - }, { "ImportPath": "github.com/ugorji/go/codec", "Rev": "646ae4a518c1c3be0739df898118d9bccf993858" From 42d966f8a6bcdabb1e28ee48b9cb3adf0e366038 Mon Sep 17 00:00:00 2001 From: Abdul Charif Date: Mon, 4 Nov 2019 12:34:18 -0500 Subject: [PATCH 07/13] test --- builder/xenserver/common/step_type_boot_command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/xenserver/common/step_type_boot_command.go b/builder/xenserver/common/step_type_boot_command.go index 442b2ee6..b252ec83 100644 --- a/builder/xenserver/common/step_type_boot_command.go +++ b/builder/xenserver/common/step_type_boot_command.go @@ -4,7 +4,7 @@ package common import ( "fmt" - "github.com/mitchellh/go-vnc" + "github.com/GodofGurus/packer-builder-xenserver/vendor/github.com/mitchellh/go-vnc" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/template/interpolate" From 6c7a1cc7b60d0af8e1899e4700317145c674e762 Mon Sep 17 00:00:00 2001 From: Abdul Charif Date: Mon, 4 Nov 2019 12:36:38 -0500 Subject: [PATCH 08/13] test --- builder/xenserver/common/step_type_boot_command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/xenserver/common/step_type_boot_command.go b/builder/xenserver/common/step_type_boot_command.go index b252ec83..442b2ee6 100644 --- a/builder/xenserver/common/step_type_boot_command.go +++ b/builder/xenserver/common/step_type_boot_command.go @@ -4,7 +4,7 @@ package common import ( "fmt" - "github.com/GodofGurus/packer-builder-xenserver/vendor/github.com/mitchellh/go-vnc" + "github.com/mitchellh/go-vnc" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/template/interpolate" From 5269556a14ce65c83e48fa3d6719331862849df0 Mon Sep 17 00:00:00 2001 From: Abdul Charif Date: Mon, 4 Nov 2019 13:00:25 -0500 Subject: [PATCH 09/13] remove gocs --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 86d95d72..2b04459d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,4 +4,4 @@ go: - 1.6 - tip script: - - gofmtresult=$(gofmt -s -l .); if [[ -n $gofmtresult ]]; then echo -e "Please run \"gofmt -s -w .\" before committing for the below:\n$gofmtresult"; false; fi + - ./build.sh From 095a54e3563793916e3ad28f70a7a7bbcdc97e3e Mon Sep 17 00:00:00 2001 From: Abdul Charif Date: Mon, 4 Nov 2019 13:08:47 -0500 Subject: [PATCH 10/13] remove gocs --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 2b04459d..6ab08eeb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: go +go_import_path: https://github.com/GodofGurus/packer-builder-xenserver/tree/master/vendor go: # Test with the first and the latest go release - to ensure compatibility - 1.6 From 1b01d146b974ef9eed5ad0d20e80e4e2e73934d3 Mon Sep 17 00:00:00 2001 From: Abdul Charif Date: Mon, 4 Nov 2019 16:59:38 -0500 Subject: [PATCH 11/13] fix ci --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6ab08eeb..2b04459d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,4 @@ language: go -go_import_path: https://github.com/GodofGurus/packer-builder-xenserver/tree/master/vendor go: # Test with the first and the latest go release - to ensure compatibility - 1.6 From edbd07f978077ef850aba732b8ae179597164f83 Mon Sep 17 00:00:00 2001 From: GodofGurus Date: Mon, 4 Nov 2019 17:24:27 -0500 Subject: [PATCH 12/13] Set up CI with Azure Pipelines (#2) * Set up CI with Azure Pipelines --- azure-pipelines.yml | 51 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 00000000..5f257b2a --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,51 @@ +# Go +# Build your Go project. +# Add steps that test, save build artifacts, deploy, and more: +# https://docs.microsoft.com/azure/devops/pipelines/languages/go + +trigger: +- master + +pool: + vmImage: 'ubuntu-latest' + +variables: + GOBIN: '$(GOPATH)/bin' # Go binaries path + GOROOT: '/usr/local/go1.11' # Go installation path + GOPATH: '$(system.defaultWorkingDirectory)/gopath' # Go workspace path + modulePath: '$(GOPATH)/src/github.com/$(build.repository.name)' # Path to the module's code + +steps: +- script: | + mkdir -p '$(GOBIN)' + mkdir -p '$(GOPATH)/pkg' + mkdir -p '$(modulePath)' + shopt -s extglob + shopt -s dotglob + mv !(gopath) '$(modulePath)' + echo '##vso[task.prependpath]$(GOBIN)' + echo '##vso[task.prependpath]$(GOROOT)/bin' + displayName: 'Set up the Go workspace' + +- script: | + go version + if [ -f Gopkg.toml ]; then + curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh + dep ensure + fi + cd $(GOPATH) + mkdir -p src/github.com/xenserver/ + cd src/github.com/xenserver + git clone https://github.com/xenserver/packer-builder-xenserver.git + cd packer-builder-xenserver + go get github.com/mitchellh/go-vnc + go get github.com/mitchellh/gox + ./build.sh + workingDirectory: '$(modulePath)' + displayName: 'Get dependencies, then build' + +- task: PublishPipelineArtifact@1 + inputs: + targetPath: '$(Pipeline.Workspace)' + artifact: 'xs' + publishLocation: 'pipeline' From 93ad91a523d24b2127b1d9862d5169b04e72210e Mon Sep 17 00:00:00 2001 From: Abdul Charif Date: Mon, 4 Nov 2019 18:24:45 -0500 Subject: [PATCH 13/13] Version checks and compatibility fixes for xs 7.6 and below --- builder/xenserver/common/step_get_vnc_port.go | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/builder/xenserver/common/step_get_vnc_port.go b/builder/xenserver/common/step_get_vnc_port.go index dc695a33..34b20a81 100644 --- a/builder/xenserver/common/step_get_vnc_port.go +++ b/builder/xenserver/common/step_get_vnc_port.go @@ -6,6 +6,7 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" + xsclient "github.com/xenserver/go-xenserver-client" ) type StepGetVNCPort struct{} @@ -16,22 +17,42 @@ func (self *StepGetVNCPort) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Step: forward the instances VNC port over SSH") domid := state.Get("domid").(string) - // cmd := fmt.Sprintf("xenstore-read /local/domain/%s/console/vnc-port", domid) + cmd := fmt.Sprintf("xenstore-read /local/domain/%s/console/vnc-port", domid) - expectedport := fmt.Sprintf("59%s", domid) - cmd1 := fmt.Sprintf("nohup socat -d -d -lf /tmp/socat-%s TCP4-LISTEN:%s,reuseaddr,fork,tcpwrap=socat,allow-table=all UNIX-CONNECT:/var/run/xen/vnc-%s &>/dev/null &", expectedport, expectedport, domid) - ui.Say(fmt.Sprintf("nohup socat -d -d -lf /tmp/socat-%s TCP4-LISTEN:%s,reuseaddr,fork,tcpwrap=socat,allow-table=all UNIX-CONNECT:/var/run/xen/vnc-%s &>/dev/null &", expectedport, expectedport, domid)) - remote_vncport, err := ExecuteHostSSHCmd(state, cmd1) + 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())) ui.Error(fmt.Sprintf("XS7.5/7.6 no longer support xenstore-read: Try to use 5900. See https://bugs.xenserver.org/browse/XSO-906")) remote_vncport = "5900" - //return multistep.ActionHalt + client := state.Get("client").(xsclient.XenAPIClient) + hosts, err := client.GetHosts() + if err != nil { + ui.Error(fmt.Sprintf("Could not retrieve hosts in the pool: %s", err.Error())) + return multistep.ActionHalt + } + host := hosts[0] + host_software_versions, err := host.GetSoftwareVersion() + xs_version := host_software_versions["product_version"].(string) + + if err != nil { + ui.Error(fmt.Sprintf("Could not get the software version: %s", err.Error())) + return multistep.ActionHalt + } + if xs_version > "7.6.0" { + ui.Say(fmt.Sprintf("XS8.0+ no longer support xenstore-read: Make sure to install socat for XS8.0+, attempting to use socat")) + expectedport := fmt.Sprintf("59%s", domid) + cmd1 := fmt.Sprintf("nohup socat -d -d -lf /tmp/socat-%s TCP4-LISTEN:%s,reuseaddr,fork,tcpwrap=socat,allow-table=all UNIX-CONNECT:/var/run/xen/vnc-%s &>/dev/null &", expectedport, expectedport, domid) + remote_vncport, err := ExecuteHostSSHCmd(state, cmd1) + if err != nil { + ui.Say(fmt.Sprintf("socat not available on XenServer, halting packer ...")) + return multistep.ActionHalt + } + ui.Say(fmt.Sprintf("nohup socat -d -d -lf /tmp/socat-%s TCP4-LISTEN:%s,reuseaddr,fork,tcpwrap=socat,allow-table=all UNIX-CONNECT:/var/run/xen/vnc-%s &>/dev/null &", remote_vncport, expectedport, domid)) + remote_vncport = expectedport + } } - remote_vncport = expectedport - remote_port, err := strconv.ParseUint(remote_vncport, 10, 16) - ui.Say(fmt.Sprint("Exposed VNC port on: %s and using rmote port %d", expectedport, remote_port)) + remote_port, err := strconv.ParseUint(remote_vncport, 10, 16) if err != nil { ui.Error(fmt.Sprintf("Unable to convert '%s' to an int", remote_vncport)) @@ -39,7 +60,6 @@ func (self *StepGetVNCPort) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - ui.Say(fmt.Sprint("instance_vnc_port %d", remote_port)) state.Put("instance_vnc_port", uint(remote_port)) return multistep.ActionContinue @@ -56,4 +76,4 @@ func InstanceVNCPort(state multistep.StateBag) (uint, 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 -} \ No newline at end of file +}