From 1fe4c501e45993af0f0af1c4daab554f7d12c517 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 21 Jun 2015 12:36:07 +0100 Subject: [PATCH 001/108] Port of https://github.com/pbolduc/packer-hyperv/ Remove steps that are windows specific --- builder/hyperv/common/artifact.go | 65 +++ builder/hyperv/common/artifact_test.go | 43 ++ builder/hyperv/common/config_test.go | 11 + builder/hyperv/common/driver.go | 34 ++ builder/hyperv/common/driver_ps_4.go | 142 ++++++ builder/hyperv/common/output_config.go | 28 ++ builder/hyperv/common/output_config_test.go | 45 ++ builder/hyperv/common/ssh.go | 51 +++ builder/hyperv/common/ssh_config.go | 29 ++ builder/hyperv/common/step_configure_ip.go | 83 ++++ builder/hyperv/common/step_configure_vlan.go | 54 +++ .../common/step_create_external_switch.go | 107 +++++ builder/hyperv/common/step_create_switch.go | 83 ++++ builder/hyperv/common/step_create_tempdir.go | 55 +++ builder/hyperv/common/step_create_vm.go | 67 +++ builder/hyperv/common/step_disable_vlan.go | 41 ++ .../common/step_enable_integration_service.go | 39 ++ .../common/step_execute_online_activation.go | 62 +++ .../step_execute_online_activation_full.go | 90 ++++ builder/hyperv/common/step_export_vm.go | 74 +++ builder/hyperv/common/step_mount_dvddrive.go | 59 +++ .../hyperv/common/step_mount_floppydrive.go | 189 ++++++++ .../common/step_mount_integration_services.go | 142 ++++++ builder/hyperv/common/step_output_dir.go | 71 +++ .../common/step_polling_installation.go | 114 +++++ builder/hyperv/common/step_reboot_vm.go | 45 ++ builder/hyperv/common/step_shutdown.go | 141 ++++++ builder/hyperv/common/step_sleep.go | 32 ++ builder/hyperv/common/step_start_vm.go | 48 ++ .../hyperv/common/step_unmount_dvddrive.go | 38 ++ .../hyperv/common/step_unmount_floppydrive.go | 39 ++ .../step_unmount_integration_services.go | 52 +++ .../step_upgrade_integration_services.go | 110 +++++ .../step_wait_for_install_to_complete.go | 130 ++++++ builder/hyperv/iso/builder.go | 391 ++++++++++++++++ .../build-and-deploy.sh | 3 + plugin/packer-builder-hyperv-iso/main.go | 19 + plugin/packer-builder-hyperv-iso/main_test.go | 5 + powershell/hyperv/hyperv.go | 433 ++++++++++++++++++ powershell/powershell.go | 255 +++++++++++ powershell/powershell_test.go | 72 +++ powershell/scriptbuilder.go | 30 ++ 42 files changed, 3621 insertions(+) create mode 100644 builder/hyperv/common/artifact.go create mode 100644 builder/hyperv/common/artifact_test.go create mode 100644 builder/hyperv/common/config_test.go create mode 100644 builder/hyperv/common/driver.go create mode 100644 builder/hyperv/common/driver_ps_4.go create mode 100644 builder/hyperv/common/output_config.go create mode 100644 builder/hyperv/common/output_config_test.go create mode 100644 builder/hyperv/common/ssh.go create mode 100644 builder/hyperv/common/ssh_config.go create mode 100644 builder/hyperv/common/step_configure_ip.go create mode 100644 builder/hyperv/common/step_configure_vlan.go create mode 100644 builder/hyperv/common/step_create_external_switch.go create mode 100644 builder/hyperv/common/step_create_switch.go create mode 100644 builder/hyperv/common/step_create_tempdir.go create mode 100644 builder/hyperv/common/step_create_vm.go create mode 100644 builder/hyperv/common/step_disable_vlan.go create mode 100644 builder/hyperv/common/step_enable_integration_service.go create mode 100644 builder/hyperv/common/step_execute_online_activation.go create mode 100644 builder/hyperv/common/step_execute_online_activation_full.go create mode 100644 builder/hyperv/common/step_export_vm.go create mode 100644 builder/hyperv/common/step_mount_dvddrive.go create mode 100644 builder/hyperv/common/step_mount_floppydrive.go create mode 100644 builder/hyperv/common/step_mount_integration_services.go create mode 100644 builder/hyperv/common/step_output_dir.go create mode 100644 builder/hyperv/common/step_polling_installation.go create mode 100644 builder/hyperv/common/step_reboot_vm.go create mode 100644 builder/hyperv/common/step_shutdown.go create mode 100644 builder/hyperv/common/step_sleep.go create mode 100644 builder/hyperv/common/step_start_vm.go create mode 100644 builder/hyperv/common/step_unmount_dvddrive.go create mode 100644 builder/hyperv/common/step_unmount_floppydrive.go create mode 100644 builder/hyperv/common/step_unmount_integration_services.go create mode 100644 builder/hyperv/common/step_upgrade_integration_services.go create mode 100644 builder/hyperv/common/step_wait_for_install_to_complete.go create mode 100644 builder/hyperv/iso/builder.go create mode 100644 plugin/packer-builder-hyperv-iso/build-and-deploy.sh create mode 100644 plugin/packer-builder-hyperv-iso/main.go create mode 100644 plugin/packer-builder-hyperv-iso/main_test.go create mode 100644 powershell/hyperv/hyperv.go create mode 100644 powershell/powershell.go create mode 100644 powershell/powershell_test.go create mode 100644 powershell/scriptbuilder.go diff --git a/builder/hyperv/common/artifact.go b/builder/hyperv/common/artifact.go new file mode 100644 index 000000000..de7f94ca7 --- /dev/null +++ b/builder/hyperv/common/artifact.go @@ -0,0 +1,65 @@ +package common + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/mitchellh/packer/packer" +) + +// This is the common builder ID to all of these artifacts. +const BuilderId = "mitchellh.hyperv" + +// Artifact is the result of running the hyperv builder, namely a set +// of files associated with the resulting machine. +type artifact struct { + dir string + f []string +} + +// NewArtifact returns a hyperv artifact containing the files +// in the given directory. +func NewArtifact(dir string) (packer.Artifact, error) { + files := make([]string, 0, 5) + visit := func(path string, info os.FileInfo, err error) error { + if !info.IsDir() { + files = append(files, path) + } + + return err + } + + if err := filepath.Walk(dir, visit); err != nil { + return nil, err + } + + return &artifact{ + dir: dir, + f: files, + }, nil +} + +func (*artifact) BuilderId() string { + return BuilderId +} + +func (a *artifact) Files() []string { + return a.f +} + +func (*artifact) Id() string { + return "VM" +} + +func (a *artifact) String() string { + return fmt.Sprintf("VM files in directory: %s", a.dir) +} + +func (a *artifact) State(name string) interface{} { + return nil +} + +func (a *artifact) Destroy() error { + return os.RemoveAll(a.dir) +} diff --git a/builder/hyperv/common/artifact_test.go b/builder/hyperv/common/artifact_test.go new file mode 100644 index 000000000..f9ddc5dbf --- /dev/null +++ b/builder/hyperv/common/artifact_test.go @@ -0,0 +1,43 @@ +package common + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/mitchellh/packer/packer" +) + +func TestArtifact_impl(t *testing.T) { + var _ packer.Artifact = new(artifact) +} + +func TestNewArtifact(t *testing.T) { + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + + err = ioutil.WriteFile(filepath.Join(td, "a"), []byte("foo"), 0644) + if err != nil { + t.Fatalf("err: %s", err) + } + + if err := os.Mkdir(filepath.Join(td, "b"), 0755); err != nil { + t.Fatalf("err: %s", err) + } + + a, err := NewArtifact(td) + if err != nil { + t.Fatalf("err: %s", err) + } + + if a.BuilderId() != BuilderId { + t.Fatalf("bad: %#v", a.BuilderId()) + } + if len(a.Files()) != 1 { + t.Fatalf("should length 1: %d", len(a.Files())) + } +} diff --git a/builder/hyperv/common/config_test.go b/builder/hyperv/common/config_test.go new file mode 100644 index 000000000..eeeda864a --- /dev/null +++ b/builder/hyperv/common/config_test.go @@ -0,0 +1,11 @@ +package common + +import ( + "testing" + + "github.com/mitchellh/packer/template/interpolate" +) + +func testConfigTemplate(t *testing.T) *interpolate.Context { + return &interpolate.Context{} +} diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go new file mode 100644 index 000000000..2c9f570b4 --- /dev/null +++ b/builder/hyperv/common/driver.go @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +// A driver is able to talk to HyperV and perform certain +// operations with it. Some of the operations on here may seem overly +// specific, but they were built specifically in mind to handle features +// of the HyperV builder for Packer, and to abstract differences in +// versions out of the builder steps, so sometimes the methods are +// extremely specific. +type Driver interface { + + // Checks if the VM named is running. + IsRunning(string) (bool, error) + + // Start starts a VM specified by the name given. + Start(string) error + + // Stop stops a VM specified by the name given. + Stop(string) error + + // Verify checks to make sure that this driver should function + // properly. If there is any indication the driver can't function, + // this will return an error. + Verify() error + + // Finds the MAC address of the NIC nic0 + Mac(string) (string, error) + + // Finds the IP address of a VM connected that uses DHCP by its MAC address + IpAddress(string) (string, error) +} diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go new file mode 100644 index 000000000..a5c2abd6f --- /dev/null +++ b/builder/hyperv/common/driver_ps_4.go @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/packer/powershell" + "github.com/mitchellh/packer/powershell/hyperv" + "log" + "runtime" + "strconv" + "strings" +) + +type HypervPS4Driver struct { +} + +func NewHypervPS4Driver() (Driver, error) { + appliesTo := "Applies to Windows 8.1, Windows PowerShell 4.0, Windows Server 2012 R2 only" + + // Check this is Windows + if runtime.GOOS != "windows" { + err := fmt.Errorf("%s", appliesTo) + return nil, err + } + + ps4Driver := &HypervPS4Driver{} + + if err := ps4Driver.Verify(); err != nil { + return nil, err + } + + return ps4Driver, nil +} + +func (d *HypervPS4Driver) IsRunning(vmName string) (bool, error) { + return hyperv.IsRunning(vmName) +} + +// Start starts a VM specified by the name given. +func (d *HypervPS4Driver) Start(vmName string) error { + return hyperv.Start(vmName) +} + +// Stop stops a VM specified by the name given. +func (d *HypervPS4Driver) Stop(vmName string) error { + return hyperv.TurnOff(vmName) +} + +func (d *HypervPS4Driver) Verify() error { + + if err := d.verifyPSVersion(); err != nil { + return err + } + + if err := d.verifyPSHypervModule(); err != nil { + return err + } + + if err := d.verifyElevatedMode(); err != nil { + return err + } + + return nil +} + +// Get mac address for VM. +func (d *HypervPS4Driver) Mac(vmName string) (string, error) { + return hyperv.Mac(vmName) +} + +// Get ip address for mac address. +func (d *HypervPS4Driver) IpAddress(mac string) (string, error) { + return hyperv.IpAddress(mac) +} + +func (d *HypervPS4Driver) verifyPSVersion() error { + + log.Printf("Enter method: %s", "verifyPSVersion") + // check PS is available and is of proper version + versionCmd := "$host.version.Major" + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(versionCmd) + if err != nil { + return err + } + + versionOutput := strings.TrimSpace(string(cmdOut)) + log.Printf("%s output: %s", versionCmd, versionOutput) + + ver, err := strconv.ParseInt(versionOutput, 10, 32) + + if err != nil { + return err + } + + if ver < 4 { + err := fmt.Errorf("%s", "Windows PowerShell version 4.0 or higher is expected") + return err + } + + return nil +} + +func (d *HypervPS4Driver) verifyPSHypervModule() error { + + log.Printf("Enter method: %s", "verifyPSHypervModule") + + versionCmd := "function foo(){try{ $commands = Get-Command -Module Hyper-V;if($commands.Length -eq 0){return $false} }catch{return $false}; return $true} foo" + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(versionCmd) + if err != nil { + return err + } + + res := strings.TrimSpace(string(cmdOut)) + + if res == "False" { + err := fmt.Errorf("%s", "PS Hyper-V module is not loaded. Make sure Hyper-V feature is on.") + return err + } + + return nil +} + +func (d *HypervPS4Driver) verifyElevatedMode() error { + + log.Printf("Enter method: %s", "verifyElevatedMode") + + isAdmin, _ := powershell.IsCurrentUserAnAdministrator() + + if !isAdmin { + err := fmt.Errorf("%s", "Please restart your shell in elevated mode") + return err + } + + return nil +} diff --git a/builder/hyperv/common/output_config.go b/builder/hyperv/common/output_config.go new file mode 100644 index 000000000..40b10e6e5 --- /dev/null +++ b/builder/hyperv/common/output_config.go @@ -0,0 +1,28 @@ +package common + +import ( + "fmt" + "os" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/template/interpolate" +) + +type OutputConfig struct { + OutputDir string `mapstructure:"output_directory"` +} + +func (c *OutputConfig) Prepare(ctx *interpolate.Context, pc *common.PackerConfig) []error { + if c.OutputDir == "" { + c.OutputDir = fmt.Sprintf("output-%s", pc.PackerBuildName) + } + + var errs []error + if !pc.PackerForce { + if _, err := os.Stat(c.OutputDir); err == nil { + errs = append(errs, fmt.Errorf( + "Output directory '%s' already exists. It must not exist.", c.OutputDir)) + } + } + + return errs +} diff --git a/builder/hyperv/common/output_config_test.go b/builder/hyperv/common/output_config_test.go new file mode 100644 index 000000000..a4d8e7999 --- /dev/null +++ b/builder/hyperv/common/output_config_test.go @@ -0,0 +1,45 @@ +package common + +import ( + "github.com/mitchellh/packer/common" + "io/ioutil" + "os" + "testing" +) + +func TestOutputConfigPrepare(t *testing.T) { + c := new(OutputConfig) + if c.OutputDir != "" { + t.Fatalf("what: %s", c.OutputDir) + } + + pc := &common.PackerConfig{PackerBuildName: "foo"} + errs := c.Prepare(testConfigTemplate(t), pc) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + + if c.OutputDir == "" { + t.Fatal("should have output dir") + } +} + +func TestOutputConfigPrepare_exists(t *testing.T) { + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + + c := new(OutputConfig) + c.OutputDir = td + + pc := &common.PackerConfig{ + PackerBuildName: "foo", + PackerForce: false, + } + errs := c.Prepare(testConfigTemplate(t), pc) + if len(errs) != 0 { + t.Fatal("should not have errors") + } +} diff --git a/builder/hyperv/common/ssh.go b/builder/hyperv/common/ssh.go new file mode 100644 index 000000000..f61047dd4 --- /dev/null +++ b/builder/hyperv/common/ssh.go @@ -0,0 +1,51 @@ +package common + +import ( + "github.com/mitchellh/multistep" + commonssh "github.com/mitchellh/packer/common/ssh" + packerssh "github.com/mitchellh/packer/communicator/ssh" + "golang.org/x/crypto/ssh" +) + +func CommHost(state multistep.StateBag) (string, error) { + vmName := state.Get("vmName").(string) + driver := state.Get("driver").(Driver) + + mac, err := driver.Mac(vmName) + if err != nil { + return "", err + } + + ip, err := driver.IpAddress(mac) + if err != nil { + return "", err + } + + return ip, nil +} + +func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*ssh.ClientConfig, error) { + return func(state multistep.StateBag) (*ssh.ClientConfig, error) { + auth := []ssh.AuthMethod{ + ssh.Password(config.Comm.SSHPassword), + ssh.KeyboardInteractive( + packerssh.PasswordKeyboardInteractive(config.Comm.SSHPassword)), + } + + if config.SSHKeyPath != "" { + signer, err := commonssh.FileSigner(config.Comm.SSHPrivateKey) + if err != nil { + return nil, err + } + + auth = append(auth, ssh.PublicKeys(signer)) + } + + return &ssh.ClientConfig{ + User: config.Comm.SSHUsername, + Auth: auth, + }, nil + } +} + + diff --git a/builder/hyperv/common/ssh_config.go b/builder/hyperv/common/ssh_config.go new file mode 100644 index 000000000..bea164b06 --- /dev/null +++ b/builder/hyperv/common/ssh_config.go @@ -0,0 +1,29 @@ +package common + +import ( + "time" + + "github.com/mitchellh/packer/helper/communicator" + "github.com/mitchellh/packer/template/interpolate" +) + +type SSHConfig struct { + Comm communicator.Config `mapstructure:",squash"` + + // These are deprecated, but we keep them around for BC + // TODO(@mitchellh): remove + SSHKeyPath string `mapstructure:"ssh_key_path"` + SSHWaitTimeout time.Duration `mapstructure:"ssh_wait_timeout"` +} + +func (c *SSHConfig) Prepare(ctx *interpolate.Context) []error { + // TODO: backwards compatibility, write fixer instead + if c.SSHKeyPath != "" { + c.Comm.SSHPrivateKey = c.SSHKeyPath + } + if c.SSHWaitTimeout != 0 { + c.Comm.SSHTimeout = c.SSHWaitTimeout + } + + return c.Comm.Prepare(ctx) +} diff --git a/builder/hyperv/common/step_configure_ip.go b/builder/hyperv/common/step_configure_ip.go new file mode 100644 index 000000000..ea35818e9 --- /dev/null +++ b/builder/hyperv/common/step_configure_ip.go @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "strings" + "time" + "log" + powershell "github.com/mitchellh/packer/powershell" + "github.com/mitchellh/packer/powershell/hyperv" +) + + +type StepConfigureIp struct { +} + +func (s *StepConfigureIp) Run(state multistep.StateBag) multistep.StepAction { +// driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + errorMsg := "Error configuring ip address: %s" + vmName := state.Get("vmName").(string) + + ui.Say("Configuring ip address...") + + count := 60 + var duration time.Duration = 1 + sleepTime := time.Minute * duration + var ip string + + for count != 0 { + cmdOut, err := hyperv.GetVirtualMachineNetworkAdapterAddress(vmName) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ip = strings.TrimSpace(string(cmdOut)) + + if ip != "False" { + break; + } + + log.Println(fmt.Sprintf("Waiting for another %v minutes...", uint(duration))) + time.Sleep(sleepTime) + count-- + } + + if(count == 0){ + err := fmt.Errorf(errorMsg, "IP address assigned to the adapter is empty") + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say("ip address is " + ip) + + hostName, err := powershell.GetHostName(ip); + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say("hostname is " + hostName) + + state.Put("ip", ip) + state.Put("hostname", hostName) + + return multistep.ActionContinue +} + +func (s *StepConfigureIp) Cleanup(state multistep.StateBag) { + // do nothing +} + diff --git a/builder/hyperv/common/step_configure_vlan.go b/builder/hyperv/common/step_configure_vlan.go new file mode 100644 index 000000000..508b43e98 --- /dev/null +++ b/builder/hyperv/common/step_configure_vlan.go @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/powershell/hyperv" +) + + +const( + vlanId = "1724" +) + +type StepConfigureVlan struct { +} + +func (s *StepConfigureVlan) Run(state multistep.StateBag) multistep.StepAction { + //config := state.Get("config").(*config) + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + errorMsg := "Error configuring vlan: %s" + vmName := state.Get("vmName").(string) + switchName := state.Get("SwitchName").(string) + + ui.Say("Configuring vlan...") + + err := hyperv.SetNetworkAdapterVlanId(switchName, vlanId) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + err = hyperv.SetVirtualMachineVlanId(vmName, vlanId) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *StepConfigureVlan) Cleanup(state multistep.StateBag) { + //do nothing +} diff --git a/builder/hyperv/common/step_create_external_switch.go b/builder/hyperv/common/step_create_external_switch.go new file mode 100644 index 000000000..1685d069e --- /dev/null +++ b/builder/hyperv/common/step_create_external_switch.go @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "code.google.com/p/go-uuid/uuid" + "github.com/mitchellh/packer/powershell/hyperv" +) + +// This step creates switch for VM. +// +// Produces: +// SwitchName string - The name of the Switch +type StepCreateExternalSwitch struct { + SwitchName string + oldSwitchName string +} + +func (s *StepCreateExternalSwitch) Run(state multistep.StateBag) multistep.StepAction { + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + vmName := state.Get("vmName").(string) + errorMsg := "Error createing external switch: %s" + var err error + + ui.Say("Creating external switch...") + + packerExternalSwitchName := "paes_" + uuid.New() + + err = hyperv.CreateExternalVirtualSwitch(vmName, packerExternalSwitchName) + if err != nil { + err := fmt.Errorf("Error creating switch: %s", err) + state.Put(errorMsg, err) + ui.Error(err.Error()) + s.SwitchName = ""; + return multistep.ActionHalt + } + + switchName, err := hyperv.GetVirtualMachineSwitchName(vmName) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + if len(switchName) == 0 { + err := fmt.Errorf(errorMsg, err) + state.Put("error", "Can't get the VM switch name") + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say("External switch name is: '" + switchName + "'") + + if(switchName != packerExternalSwitchName){ + s.SwitchName = "" + } else { + s.SwitchName = packerExternalSwitchName + s.oldSwitchName = state.Get("SwitchName").(string) + } + + // Set the final name in the state bag so others can use it + state.Put("SwitchName", switchName) + + return multistep.ActionContinue +} + +func (s *StepCreateExternalSwitch) Cleanup(state multistep.StateBag) { + if s.SwitchName == "" { + return + } + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + ui.Say("Unregistering and deleting external switch...") + + var err error = nil + + errMsg := "Error deleting external switch: %s" + + // connect the vm to the old switch + if s.oldSwitchName == "" { + ui.Error(fmt.Sprintf(errMsg, "the old switch name is empty")) + return + } + + err = hyperv.ConnectVirtualMachineNetworkAdapterToSwitch(vmName, s.oldSwitchName) + if err != nil { + ui.Error(fmt.Sprintf(errMsg, err)) + return + } + + state.Put("SwitchName", s.oldSwitchName) + + err = hyperv.DeleteVirtualSwitch(s.SwitchName) + if err != nil { + ui.Error(fmt.Sprintf(errMsg, err)) + } +} diff --git a/builder/hyperv/common/step_create_switch.go b/builder/hyperv/common/step_create_switch.go new file mode 100644 index 000000000..5620e3f93 --- /dev/null +++ b/builder/hyperv/common/step_create_switch.go @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/powershell/hyperv" +) + +const ( + SwitchTypeInternal = "Internal" + SwitchTypePrivate = "Private" + DefaultSwitchType = SwitchTypeInternal +) + +// This step creates switch for VM. +// +// Produces: +// SwitchName string - The name of the Switch +type StepCreateSwitch struct { + // Specifies the name of the switch to be created. + SwitchName string + // Specifies the type of the switch to be created. Allowed values are Internal and Private. To create an External + // virtual switch, specify either the NetAdapterInterfaceDescription or the NetAdapterName parameter, which + // implicitly set the type of the virtual switch to External. + SwitchType string + // Specifies the name of the network adapter to be bound to the switch to be created. + NetAdapterName string + // Specifies the interface description of the network adapter to be bound to the switch to be created. + NetAdapterInterfaceDescription string + + createdSwitch bool +} + +func (s *StepCreateSwitch) Run(state multistep.StateBag) multistep.StepAction { + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + if len(s.SwitchType) == 0 { + s.SwitchType = DefaultSwitchType + } + + ui.Say(fmt.Sprintf("Creating switch '%v' if required...", s.SwitchName)) + + createdSwitch, err := hyperv.CreateVirtualSwitch(s.SwitchName, s.SwitchType) + if err != nil { + err := fmt.Errorf("Error creating switch: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + s.SwitchName = ""; + return multistep.ActionHalt + } + + s.createdSwitch = createdSwitch + + if !s.createdSwitch { + ui.Say(fmt.Sprintf(" switch '%v' already exists. Will not delete on cleanup...", s.SwitchName)) + } + + // Set the final name in the state bag so others can use it + state.Put("SwitchName", s.SwitchName) + + return multistep.ActionContinue +} + +func (s *StepCreateSwitch) Cleanup(state multistep.StateBag) { + if len(s.SwitchName) == 0 || !s.createdSwitch { + return + } + + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + ui.Say("Unregistering and deleting switch...") + + err := hyperv.DeleteVirtualSwitch(s.SwitchName) + if err != nil { + ui.Error(fmt.Sprintf("Error deleting switch: %s", err)) + } +} diff --git a/builder/hyperv/common/step_create_tempdir.go b/builder/hyperv/common/step_create_tempdir.go new file mode 100644 index 000000000..c4682a852 --- /dev/null +++ b/builder/hyperv/common/step_create_tempdir.go @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "io/ioutil" + "os" +) + +type StepCreateTempDir struct { + dirPath string +} + +func (s *StepCreateTempDir) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + ui.Say("Creating temporary directory...") + + tempDir := os.TempDir() + packerTempDir, err := ioutil.TempDir(tempDir, "packerhv") + if err != nil { + err := fmt.Errorf("Error creating temporary directory: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + s.dirPath = packerTempDir; + state.Put("packerTempDir", packerTempDir) + +// ui.Say("packerTempDir = '" + packerTempDir + "'") + + return multistep.ActionContinue +} + +func (s *StepCreateTempDir) Cleanup(state multistep.StateBag) { + if s.dirPath == "" { + return + } + + ui := state.Get("ui").(packer.Ui) + + ui.Say("Deleting temporary directory...") + + err := os.RemoveAll(s.dirPath) + + if err != nil { + ui.Error(fmt.Sprintf("Error deleting temporary directory: %s", err)) + } +} diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go new file mode 100644 index 000000000..6a172ada8 --- /dev/null +++ b/builder/hyperv/common/step_create_vm.go @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "strconv" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/powershell/hyperv" +) + +// This step creates the actual virtual machine. +// +// Produces: +// VMName string - The name of the VM +type StepCreateVM struct { + VMName string + SwitchName string + RamSizeMB uint + DiskSize uint +} + +func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + ui.Say("Creating virtual machine...") + + path := state.Get("packerTempDir").(string) + + // convert the MB to bytes + ramBytes := int64(s.RamSizeMB * 1024 * 1024) + diskSizeBytes := int64(s.DiskSize * 1024 * 1024) + + ram := strconv.FormatInt(ramBytes, 10) + diskSize := strconv.FormatInt(diskSizeBytes, 10) + switchName := s.SwitchName + + err := hyperv.CreateVirtualMachine(s.VMName, path, ram, diskSize, switchName) + if err != nil { + err := fmt.Errorf("Error creating virtual machine: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Set the final name in the state bag so others can use it + state.Put("vmName", s.VMName) + + return multistep.ActionContinue +} + +func (s *StepCreateVM) Cleanup(state multistep.StateBag) { + if s.VMName == "" { + return + } + + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + ui.Say("Unregistering and deleting virtual machine...") + + err := hyperv.DeleteVirtualMachine(s.VMName) + if err != nil { + ui.Error(fmt.Sprintf("Error deleting virtual machine: %s", err)) + } +} diff --git a/builder/hyperv/common/step_disable_vlan.go b/builder/hyperv/common/step_disable_vlan.go new file mode 100644 index 000000000..264affbdd --- /dev/null +++ b/builder/hyperv/common/step_disable_vlan.go @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/powershell/hyperv" +) + +type StepDisableVlan struct { +} + +func (s *StepDisableVlan) Run(state multistep.StateBag) multistep.StepAction { + //config := state.Get("config").(*config) + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + errorMsg := "Error disabling vlan: %s" + vmName := state.Get("vmName").(string) + switchName := state.Get("SwitchName").(string) + + ui.Say("Disabling vlan...") + + err := hyperv.UntagVirtualMachineNetworkAdapterVlan(vmName, switchName) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *StepDisableVlan) Cleanup(state multistep.StateBag) { + //do nothing +} diff --git a/builder/hyperv/common/step_enable_integration_service.go b/builder/hyperv/common/step_enable_integration_service.go new file mode 100644 index 000000000..89259d4d8 --- /dev/null +++ b/builder/hyperv/common/step_enable_integration_service.go @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/powershell/hyperv" +) + +type StepEnableIntegrationService struct { + name string +} + +func (s *StepEnableIntegrationService) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + ui.Say("Enabling Integration Service...") + + vmName := state.Get("vmName").(string) + s.name = "Guest Service Interface" + + err := hyperv.EnableVirtualMachineIntegrationService(vmName, s.name) + + if err != nil { + err := fmt.Errorf("Error enabling Integration Service: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *StepEnableIntegrationService) Cleanup(state multistep.StateBag) { + // do nothing +} diff --git a/builder/hyperv/common/step_execute_online_activation.go b/builder/hyperv/common/step_execute_online_activation.go new file mode 100644 index 000000000..b369934e1 --- /dev/null +++ b/builder/hyperv/common/step_execute_online_activation.go @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "bytes" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "strings" + "log" +) + +type StepExecuteOnlineActivation struct { +} + +func (s *StepExecuteOnlineActivation) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + comm := state.Get("communicator").(packer.Communicator) + + errorMsg := "Error Executing Online Activation: %s" + + var remoteCmd packer.RemoteCmd + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + var err error + + ui.Say("Executing Online Activation...") + + var blockBuffer bytes.Buffer + blockBuffer.WriteString("{ cscript \"$env:SystemRoot/system32/slmgr.vbs\" -ato //nologo }") + + remoteCmd.Command = "-ScriptBlock " + blockBuffer.String() + + remoteCmd.Stdout = stdout + remoteCmd.Stderr = stderr + + err = comm.Start(&remoteCmd) + + stderrString := strings.TrimSpace(stderr.String()) + stdoutString := strings.TrimSpace(stdout.String()) + + log.Printf("stdout: %s", stdoutString) + log.Printf("stderr: %s", stderrString) + + if len(stderrString) > 0 { + err = fmt.Errorf(errorMsg, stderrString) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say(stdoutString) + + return multistep.ActionContinue +} + +func (s *StepExecuteOnlineActivation) Cleanup(state multistep.StateBag) { + // do nothing +} diff --git a/builder/hyperv/common/step_execute_online_activation_full.go b/builder/hyperv/common/step_execute_online_activation_full.go new file mode 100644 index 000000000..6eb91936c --- /dev/null +++ b/builder/hyperv/common/step_execute_online_activation_full.go @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "bytes" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "strings" + "log" +) + +type StepExecuteOnlineActivationFull struct { + Pk string +} + +func (s *StepExecuteOnlineActivationFull) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + comm := state.Get("communicator").(packer.Communicator) + + errorMsg := "Error Executing Online Activation: %s" + + var remoteCmd packer.RemoteCmd + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + var err error + var stderrString string + var stdoutString string + + ui.Say("Executing Online Activation Full version...") + + var blockBuffer bytes.Buffer + blockBuffer.WriteString("{ cscript \"$env:SystemRoot/system32/slmgr.vbs\" /ipk "+ s.Pk +" //nologo }") + + log.Printf("cmd: %s", blockBuffer.String()) + remoteCmd.Command = "-ScriptBlock " + blockBuffer.String() + + remoteCmd.Stdout = stdout + remoteCmd.Stderr = stderr + + err = comm.Start(&remoteCmd) + + stderrString = strings.TrimSpace(stderr.String()) + stdoutString = strings.TrimSpace(stdout.String()) + + log.Printf("stdout: %s", stdoutString) + log.Printf("stderr: %s", stderrString) + + if len(stderrString) > 0 { + err = fmt.Errorf(errorMsg, stderrString) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + +// ui.Say(stdoutString) + +/* + blockBuffer.Reset() + blockBuffer.WriteString("{ cscript \"$env:SystemRoot/system32/slmgr.vbs\" -ato //nologo }") + + log.Printf("cmd: %s", blockBuffer.String()) + remoteCmd.Command = "-ScriptBlock " + blockBuffer.String() + + err = comm.Start(&remoteCmd) + + stderrString = strings.TrimSpace(stderr.String()) + stdoutString = strings.TrimSpace(stdout.String()) + + log.Printf("stdout: %s", stdoutString) + log.Printf("stderr: %s", stderrString) + + if len(stderrString) > 0 { + err = fmt.Errorf(errorMsg, stderrString) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say(stdoutString) +*/ + return multistep.ActionContinue +} + +func (s *StepExecuteOnlineActivationFull) Cleanup(state multistep.StateBag) { + // do nothing +} diff --git a/builder/hyperv/common/step_export_vm.go b/builder/hyperv/common/step_export_vm.go new file mode 100644 index 000000000..95076fd14 --- /dev/null +++ b/builder/hyperv/common/step_export_vm.go @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "path/filepath" + "io/ioutil" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/powershell/hyperv" +) + +const( + vhdDir string = "Virtual Hard Disks" + vmDir string = "Virtual Machines" +) + +type StepExportVm struct { + OutputDir string +} + +func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + var err error + var errorMsg string + + vmName := state.Get("vmName").(string) + tmpPath := state.Get("packerTempDir").(string) + outputPath := s.OutputDir + + // create temp path to export vm + errorMsg = "Error creating temp export path: %s" + vmExportPath , err := ioutil.TempDir(tmpPath, "export") + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say("Exporting vm...") + + err = hyperv.ExportVirtualMachine(vmName, vmExportPath) + if err != nil { + errorMsg = "Error exporting vm: %s" + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // copy to output dir + expPath := filepath.Join(vmExportPath,vmName) + + ui.Say("Coping to output dir...") + err = hyperv.CopyExportedVirtualMachine(expPath, outputPath, vhdDir, vmDir) + if err != nil { + errorMsg = "Error exporting vm: %s" + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *StepExportVm) Cleanup(state multistep.StateBag) { + // do nothing +} diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go new file mode 100644 index 000000000..d5e9b4ef3 --- /dev/null +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/powershell/hyperv" +) + + +type StepMountDvdDrive struct { + RawSingleISOUrl string + path string +} + +func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + errorMsg := "Error mounting dvd drive: %s" + vmName := state.Get("vmName").(string) + isoPath := s.RawSingleISOUrl + + ui.Say("Mounting dvd drive...") + + err := hyperv.MountDvdDrive(vmName, isoPath) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + s.path = isoPath + + return multistep.ActionContinue +} + +func (s *StepMountDvdDrive) Cleanup(state multistep.StateBag) { + if s.path == "" { + return + } + + errorMsg := "Error unmounting dvd drive: %s" + + vmName := state.Get("vmName").(string) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Unmounting dvd drive...") + + err := hyperv.UnmountDvdDrive(vmName) + if err != nil { + ui.Error(fmt.Sprintf(errorMsg, err)) + } +} diff --git a/builder/hyperv/common/step_mount_floppydrive.go b/builder/hyperv/common/step_mount_floppydrive.go new file mode 100644 index 000000000..c30054141 --- /dev/null +++ b/builder/hyperv/common/step_mount_floppydrive.go @@ -0,0 +1,189 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "os" + "strings" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/powershell" + "github.com/mitchellh/packer/powershell/hyperv" + "log" + "io" + "io/ioutil" + "path/filepath" +) + + +const( + FloppyFileName = "assets.vfd" +) + + + + +type StepSetUnattendedProductKey struct { + Files []string + ProductKey string +} + +func (s *StepSetUnattendedProductKey) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + if s.ProductKey == "" { + ui.Say("No product key specified...") + return multistep.ActionContinue + } + + index := -1 + for i, value := range s.Files { + if s.caseInsensitiveContains(value, "Autounattend.xml") { + index = i + break + } + } + + ui.Say("Setting product key in Autounattend.xml...") + copyOfAutounattend, err := s.copyAutounattend(s.Files[index]) + if err != nil { + state.Put("error", fmt.Errorf("Error copying Autounattend.xml: %s", err)) + return multistep.ActionHalt + } + + powershell.SetUnattendedProductKey(copyOfAutounattend, s.ProductKey) + s.Files[index] = copyOfAutounattend + return multistep.ActionContinue +} + + +func (s *StepSetUnattendedProductKey) caseInsensitiveContains(str, substr string) bool { + str, substr = strings.ToUpper(str), strings.ToUpper(substr) + return strings.Contains(str, substr) +} + +func (s *StepSetUnattendedProductKey) copyAutounattend(path string) (string, error) { + tempdir, err := ioutil.TempDir("", "packer") + if err != nil { + return "", err + } + + autounattend := filepath.Join(tempdir, "Autounattend.xml") + f, err := os.Create(autounattend) + if err != nil { + return "", err + } + defer f.Close() + + sourceF, err := os.Open(path) + if err != nil { + return "", err + } + defer sourceF.Close() + + log.Printf("Copying %s to temp location: %s", path, autounattend) + if _, err := io.Copy(f, sourceF); err != nil { + return "", err + } + + return autounattend, nil +} + + +func (s *StepSetUnattendedProductKey) Cleanup(state multistep.StateBag) { +} + + + +type StepMountFloppydrive struct { + floppyPath string +} + +func (s *StepMountFloppydrive) Run(state multistep.StateBag) multistep.StepAction { + // Determine if we even have a floppy disk to attach + var floppyPath string + if floppyPathRaw, ok := state.GetOk("floppy_path"); ok { + floppyPath = floppyPathRaw.(string) + } else { + log.Println("No floppy disk, not attaching.") + return multistep.ActionContinue + } + + // Hyper-V is really dumb and can't figure out the format of the file + // without an extension, so we need to add the "vfd" extension to the + // floppy. + floppyPath, err := s.copyFloppy(floppyPath) + if err != nil { + state.Put("error", fmt.Errorf("Error preparing floppy: %s", err)) + return multistep.ActionHalt + } + + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + ui.Say("Mounting floppy drive...") + + err = hyperv.MountFloppyDrive(vmName, floppyPath) + if err != nil { + state.Put("error", fmt.Errorf("Error mounting floppy drive: %s", err)) + return multistep.ActionHalt + } + + // Track the path so that we can unregister it from Hyper-V later + s.floppyPath = floppyPath + + return multistep.ActionContinue} + +func (s *StepMountFloppydrive) Cleanup(state multistep.StateBag) { + if s.floppyPath == "" { + return + } + + errorMsg := "Error unmounting floppy drive: %s" + + vmName := state.Get("vmName").(string) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Unmounting floppy drive (cleanup)...") + + err := hyperv.UnmountFloppyDrive(vmName) + if err != nil { + ui.Error(fmt.Sprintf(errorMsg, err)) + } + + err = os.Remove(s.floppyPath) + + if err != nil { + ui.Error(fmt.Sprintf(errorMsg, err)) + } +} + +func (s *StepMountFloppydrive) copyFloppy(path string) (string, error) { + tempdir, err := ioutil.TempDir("", "packer") + if err != nil { + return "", err + } + + floppyPath := filepath.Join(tempdir, "floppy.vfd") + f, err := os.Create(floppyPath) + if err != nil { + return "", err + } + defer f.Close() + + sourceF, err := os.Open(path) + if err != nil { + return "", err + } + defer sourceF.Close() + + log.Printf("Copying floppy to temp location: %s", floppyPath) + if _, err := io.Copy(f, sourceF); err != nil { + return "", err + } + + return floppyPath, nil +} diff --git a/builder/hyperv/common/step_mount_integration_services.go b/builder/hyperv/common/step_mount_integration_services.go new file mode 100644 index 000000000..05eaf6dc8 --- /dev/null +++ b/builder/hyperv/common/step_mount_integration_services.go @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "log" + "os" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + powershell "github.com/mitchellh/packer/powershell" +) + +type StepMountSecondaryDvdImages struct { + Files [] string + dvdProperties []DvdControllerProperties +} + +type DvdControllerProperties struct { + ControllerNumber string + ControllerLocation string +} + +func (s *StepMountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + ui.Say("Mounting secondary DVD images...") + + vmName := state.Get("vmName").(string) + + // should be able to mount up to 60 additional iso images using SCSI + // but Windows would only allow a max of 22 due to available drive letters + // Will Windows assign DVD drives to A: and B: ? + + // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) + dvdProperties, err := s.mountFiles(vmName); + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + log.Println(fmt.Sprintf("Saving DVD properties %s DVDs", len(dvdProperties))) + + state.Put("secondary.dvd.properties", dvdProperties) + + return multistep.ActionContinue +} + +func (s *StepMountSecondaryDvdImages) Cleanup(state multistep.StateBag) { + +} + + +func (s *StepMountSecondaryDvdImages) mountFiles(vmName string) ([]DvdControllerProperties, error) { + + var dvdProperties []DvdControllerProperties + + properties, err := s.addAndMountIntegrationServicesSetupDisk(vmName) + if err != nil { + return dvdProperties, err + } + + dvdProperties = append(dvdProperties, properties) + + for _, value := range s.Files { + properties, err := s.addAndMountDvdDisk(vmName, value) + if err != nil { + return dvdProperties, err + } + + dvdProperties = append(dvdProperties, properties) + } + + return dvdProperties, nil +} + + +func (s *StepMountSecondaryDvdImages) addAndMountIntegrationServicesSetupDisk(vmName string) (DvdControllerProperties, error) { + + isoPath := os.Getenv("WINDIR") + "\\system32\\vmguest.iso" + properties, err := s.addAndMountDvdDisk(vmName, isoPath) + if err != nil { + return properties, err + } + + return properties, nil +} + + + + +func (s *StepMountSecondaryDvdImages) addAndMountDvdDisk(vmName string, isoPath string) (DvdControllerProperties, error) { + + var properties DvdControllerProperties + var script powershell.ScriptBuilder + powershell := new(powershell.PowerShellCmd) + + // get the controller number that the OS install disk is mounted on + script.Reset() + script.WriteLine("param([string]$vmName)") + script.WriteLine("(Get-VMDvdDrive -VMName $vmName).ControllerNumber") + controllerNumber, err := powershell.Output(script.String(), vmName) + if err != nil { + return properties, err + } + + script.Reset() + script.WriteLine("param([string]$vmName,[int]$controllerNumber)") + script.WriteLine("Add-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber") + err = powershell.Run(script.String(), vmName, controllerNumber) + if err != nil { + return properties, err + } + + // we could try to get the controller location and number in one call, but this way we do not + // need to parse the output + script.Reset() + script.WriteLine("param([string]$vmName)") + script.WriteLine("(Get-VMDvdDrive -VMName $vmName | Where-Object {$_.Path -eq $null}).ControllerLocation") + controllerLocation, err := powershell.Output(script.String(), vmName) + if err != nil { + return properties, err + } + + script.Reset() + script.WriteLine("param([string]$vmName,[string]$path,[string]$controllerNumber,[string]$controllerLocation)") + script.WriteLine("Set-VMDvdDrive -VMName $vmName -Path $path -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation") + + err = powershell.Run(script.String(), vmName, isoPath, controllerNumber, controllerLocation) + if err != nil { + return properties, err + } + + log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v",isoPath, controllerNumber, controllerLocation)) + + properties.ControllerNumber = controllerNumber + properties.ControllerLocation = controllerLocation + + return properties, nil +} diff --git a/builder/hyperv/common/step_output_dir.go b/builder/hyperv/common/step_output_dir.go new file mode 100644 index 000000000..602ce2654 --- /dev/null +++ b/builder/hyperv/common/step_output_dir.go @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "log" + "os" + "path/filepath" + "time" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +// StepOutputDir sets up the output directory by creating it if it does +// not exist, deleting it if it does exist and we're forcing, and cleaning +// it up when we're done with it. +type StepOutputDir struct { + Force bool + Path string +} + +func (s *StepOutputDir) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + if _, err := os.Stat(s.Path); err == nil && s.Force { + ui.Say("Deleting previous output directory...") + os.RemoveAll(s.Path) + } + + // Create the directory + if err := os.MkdirAll(s.Path, 0755); err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + // Make sure we can write in the directory + f, err := os.Create(filepath.Join(s.Path, "_packer_perm_check")) + if err != nil { + err = fmt.Errorf("Couldn't write to output directory: %s", err) + state.Put("error", err) + return multistep.ActionHalt + } + f.Close() + os.Remove(f.Name()) + + return multistep.ActionContinue +} + +func (s *StepOutputDir) Cleanup(state multistep.StateBag) { + _, cancelled := state.GetOk(multistep.StateCancelled) + _, halted := state.GetOk(multistep.StateHalted) + + if cancelled || halted { + ui := state.Get("ui").(packer.Ui) + + ui.Say("Deleting output directory...") + for i := 0; i < 5; i++ { + err := os.RemoveAll(s.Path) + if err == nil { + break + } + + log.Printf("Error removing output dir: %s", err) + time.Sleep(2 * time.Second) + } + } +} diff --git a/builder/hyperv/common/step_polling_installation.go b/builder/hyperv/common/step_polling_installation.go new file mode 100644 index 000000000..c462f3da9 --- /dev/null +++ b/builder/hyperv/common/step_polling_installation.go @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "time" +// "net" + "log" + "os/exec" + "strings" + "bytes" +) + +const port string = "13000" + +type StepPollingInstalation struct { + step int +} + +func (s *StepPollingInstalation) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + errorMsg := "Error polling VM: %s" + vmIp := state.Get("ip").(string) + + ui.Say("Start polling VM to check the installation is complete...") +/* + count := 30 + var minutes time.Duration = 1 + sleepMin := time.Minute * minutes + host := vmIp + ":" + port + + timeoutSec := time.Second * 15 + + for count > 0 { + ui.Say(fmt.Sprintf("Connecting vm (%s)...", host )) + conn, err := net.DialTimeout("tcp", host, timeoutSec) + if err == nil { + ui.Say("Done!") + conn.Close() + break; + } + + log.Println(err) + ui.Say(fmt.Sprintf("Waiting more %v minutes...", uint(minutes))) + time.Sleep(sleepMin) + count-- + } + + if count == 0 { + err := fmt.Errorf(errorMsg, "a signal from vm was not received in a given time period ") + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } +*/ + host := "'" + vmIp + "'," + port + + var blockBuffer bytes.Buffer + blockBuffer.WriteString("Invoke-Command -scriptblock {function foo(){try{$client=New-Object System.Net.Sockets.TcpClient(") + blockBuffer.WriteString(host) + blockBuffer.WriteString(") -ErrorAction SilentlyContinue;if($client -eq $null){return $false}}catch{return $false}return $true} foo}") + + count := 60 + var duration time.Duration = 20 + sleepTime := time.Second * duration + + var res string + + for count > 0 { + log.Println(fmt.Sprintf("Connecting vm (%s)...", host )) + cmd := exec.Command("powershell", blockBuffer.String()) + cmdOut, err := cmd.Output() + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + res = strings.TrimSpace(string(cmdOut)) + + if res != "False" { + ui.Say("Signal was received from the VM") + // Sleep before starting provision + time.Sleep(time.Second*30) + break; + } + + log.Println(fmt.Sprintf("Slipping for more %v seconds...", uint(duration))) + time.Sleep(sleepTime) + count-- + } + + if count == 0 { + err := fmt.Errorf(errorMsg, "a signal from vm was not received in a given time period ") + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say("The installation complete") + + return multistep.ActionContinue +} + +func (s *StepPollingInstalation) Cleanup(state multistep.StateBag) { + +} diff --git a/builder/hyperv/common/step_reboot_vm.go b/builder/hyperv/common/step_reboot_vm.go new file mode 100644 index 000000000..3053a0855 --- /dev/null +++ b/builder/hyperv/common/step_reboot_vm.go @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "time" + "github.com/mitchellh/packer/powershell/hyperv" +) + +type StepRebootVm struct { +} + +func (s *StepRebootVm) Run(state multistep.StateBag) multistep.StepAction { + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + errorMsg := "Error rebooting vm: %s" + vmName := state.Get("vmName").(string) + + ui.Say("Rebooting vm...") + + err := hyperv.RestartVirtualMachine(vmName) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say("Waiting the VM to complete rebooting (2 minutes)...") + + sleepTime := time.Minute * 2 + time.Sleep(sleepTime) + + return multistep.ActionContinue +} + +func (s *StepRebootVm) Cleanup(state multistep.StateBag) { + // do nothing +} diff --git a/builder/hyperv/common/step_shutdown.go b/builder/hyperv/common/step_shutdown.go new file mode 100644 index 000000000..db80e7c56 --- /dev/null +++ b/builder/hyperv/common/step_shutdown.go @@ -0,0 +1,141 @@ +package common + +import ( + "bytes" + "errors" + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" + "time" +) + +type ShutdownConfig struct { + ShutdownCommand string `mapstructure:"shutdown_command"` + RawShutdownTimeout string `mapstructure:"shutdown_timeout"` + + ShutdownTimeout time.Duration `` +} + +func (c *ShutdownConfig) Prepare(t *packer.ConfigTemplate) []error { + if c.RawShutdownTimeout == "" { + c.RawShutdownTimeout = "5m" + } + + templates := map[string]*string{ + "shutdown_command": &c.ShutdownCommand, + "shutdown_timeout": &c.RawShutdownTimeout, + } + + errs := make([]error, 0) + for n, ptr := range templates { + var err error + *ptr, err = t.Process(*ptr, nil) + if err != nil { + errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + var err error + c.ShutdownTimeout, err = time.ParseDuration(c.RawShutdownTimeout) + if err != nil { + errs = append(errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err)) + } + + return errs +} + +// This step shuts down the machine. It first attempts to do so gracefully, +// but ultimately forcefully shuts it down if that fails. +// +// Uses: +// communicator packer.Communicator +// dir OutputDir +// driver Driver +// ui packer.Ui +// vmx_path string +// +// Produces: +// +type StepShutdown struct { + Command string + Timeout time.Duration +} + +func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction { + + comm := state.Get("communicator").(packer.Communicator) + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + if s.Command != "" { + ui.Say("Gracefully halting virtual machine...") + log.Printf("Executing shutdown command: %s", s.Command) + + var stdout, stderr bytes.Buffer + cmd := &packer.RemoteCmd{ + Command: s.Command, + Stdout: &stdout, + Stderr: &stderr, + } + if err := comm.Start(cmd); err != nil { + err := fmt.Errorf("Failed to send shutdown command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Wait for the command to run + cmd.Wait() + + // If the command failed to run, notify the user in some way. + if cmd.ExitStatus != 0 { + state.Put("error", fmt.Errorf( + "Shutdown command has non-zero exit status.\n\nStdout: %s\n\nStderr: %s", + stdout.String(), stderr.String())) + return multistep.ActionHalt + } + + if stdout.Len() > 0 { + log.Printf("Shutdown stdout: %s", stdout.String()) + } + + if stderr.Len() > 0 { + log.Printf("Shutdown stderr: %s", stderr.String()) + } + + // Wait for the machine to actually shut down + log.Printf("Waiting max %s for shutdown to complete", s.Timeout) + shutdownTimer := time.After(s.Timeout) + for { + running, _ := driver.IsRunning(vmName) + if !running { + break + } + + select { + case <-shutdownTimer: + err := errors.New("Timeout while waiting for machine to shut down.") + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + default: + time.Sleep(150 * time.Millisecond) + } + } + } else { + ui.Say("Forcibly halting virtual machine...") + if err := driver.Stop(vmName); err != nil { + err := fmt.Errorf("Error stopping VM: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + log.Println("VM shut down.") + return multistep.ActionContinue +} + +func (s *StepShutdown) Cleanup(state multistep.StateBag) {} diff --git a/builder/hyperv/common/step_sleep.go b/builder/hyperv/common/step_sleep.go new file mode 100644 index 000000000..2d0f0053a --- /dev/null +++ b/builder/hyperv/common/step_sleep.go @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "time" +) + +type StepSleep struct { + Minutes time.Duration + ActionName string +} + +func (s *StepSleep) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + if(len(s.ActionName)>0){ + ui.Say(s.ActionName + "! Waiting for "+ fmt.Sprintf("%v",uint(s.Minutes)) + " minutes to let the action to complete...") + } + time.Sleep(time.Minute*s.Minutes); + + return multistep.ActionContinue +} + +func (s *StepSleep) Cleanup(state multistep.StateBag) { + +} diff --git a/builder/hyperv/common/step_start_vm.go b/builder/hyperv/common/step_start_vm.go new file mode 100644 index 000000000..af1fcda5d --- /dev/null +++ b/builder/hyperv/common/step_start_vm.go @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "time" + "github.com/mitchellh/packer/powershell/hyperv" +) + +type StepStartVm struct { + Reason string + StartUpDelay int +} + +func (s *StepStartVm) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + errorMsg := "Error starting vm: %s" + vmName := state.Get("vmName").(string) + + ui.Say("Starting vm for " + s.Reason + "...") + + err := hyperv.StartVirtualMachine(vmName) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + if s.StartUpDelay != 0 { + //sleepTime := s.StartUpDelay * time.Second + sleepTime := 60 * time.Second + + ui.Say(fmt.Sprintf(" Waiting %v for vm to start...", sleepTime)) + time.Sleep(sleepTime); + } + + return multistep.ActionContinue +} + +func (s *StepStartVm) Cleanup(state multistep.StateBag) { +} diff --git a/builder/hyperv/common/step_unmount_dvddrive.go b/builder/hyperv/common/step_unmount_dvddrive.go new file mode 100644 index 000000000..57ffb422d --- /dev/null +++ b/builder/hyperv/common/step_unmount_dvddrive.go @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/powershell/hyperv" +) + + +type StepUnmountDvdDrive struct { +} + +func (s *StepUnmountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + vmName := state.Get("vmName").(string) + + ui.Say("Unmounting dvd drive...") + + err := hyperv.UnmountDvdDrive(vmName) + if err != nil { + err := fmt.Errorf("Error unmounting dvd drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *StepUnmountDvdDrive) Cleanup(state multistep.StateBag) { +} diff --git a/builder/hyperv/common/step_unmount_floppydrive.go b/builder/hyperv/common/step_unmount_floppydrive.go new file mode 100644 index 000000000..ae7813400 --- /dev/null +++ b/builder/hyperv/common/step_unmount_floppydrive.go @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/powershell/hyperv" +) + + +type StepUnmountFloppyDrive struct { +} + +func (s *StepUnmountFloppyDrive) Run(state multistep.StateBag) multistep.StepAction { + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + errorMsg := "Error Unmounting floppy drive: %s" + vmName := state.Get("vmName").(string) + + ui.Say("Unmounting floppy drive (Run)...") + + err := hyperv.UnmountFloppyDrive(vmName) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + } + + return multistep.ActionContinue +} + +func (s *StepUnmountFloppyDrive) Cleanup(state multistep.StateBag) { + // do nothing +} diff --git a/builder/hyperv/common/step_unmount_integration_services.go b/builder/hyperv/common/step_unmount_integration_services.go new file mode 100644 index 000000000..7c40389fe --- /dev/null +++ b/builder/hyperv/common/step_unmount_integration_services.go @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "log" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + powershell "github.com/mitchellh/packer/powershell" +) + +type StepUnmountSecondaryDvdImages struct { +} + +func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + ui.Say("Unmounting Integration Services Setup Disk...") + + vmName := state.Get("vmName").(string) + + // todo: should this message say removing the dvd? + + dvdProperties := state.Get("secondary.dvd.properties").([]DvdControllerProperties) + + log.Println(fmt.Sprintf("Found DVD properties %s", len(dvdProperties))) + + for _, dvdProperty := range dvdProperties { + controllerNumber := dvdProperty.ControllerNumber + controllerLocation := dvdProperty.ControllerLocation + + var script powershell.ScriptBuilder + powershell := new(powershell.PowerShellCmd) + + script.WriteLine("param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation)") + script.WriteLine("Remove-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation") + err := powershell.Run(script.String(), vmName, controllerNumber, controllerLocation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + + return multistep.ActionContinue +} + +func (s *StepUnmountSecondaryDvdImages) Cleanup(state multistep.StateBag) { +} diff --git a/builder/hyperv/common/step_upgrade_integration_services.go b/builder/hyperv/common/step_upgrade_integration_services.go new file mode 100644 index 000000000..ab076e7ed --- /dev/null +++ b/builder/hyperv/common/step_upgrade_integration_services.go @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + //"fmt" + "os" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + powershell "github.com/mitchellh/packer/powershell" +) + +type StepUpdateIntegrationServices struct { + Username string + Password string + + newDvdDriveProperties dvdDriveProperties +} + +type dvdDriveProperties struct { + ControllerNumber string + ControllerLocation string +} + +func (s *StepUpdateIntegrationServices) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + ui.Say("Mounting Integration Services Setup Disk...") + + _, err := s.mountIntegrationServicesSetupDisk(vmName); + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // dvdDriveLetter, err := s.getDvdDriveLetter(vmName) + // if err != nil { + // state.Put("error", err) + // ui.Error(err.Error()) + // return multistep.ActionHalt + // } + + // setup := dvdDriveLetter + ":\\support\\"+osArchitecture+"\\setup.exe /quiet /norestart" + + // ui.Say("Run: " + setup) + + return multistep.ActionContinue +} + +func (s *StepUpdateIntegrationServices) Cleanup(state multistep.StateBag) { + vmName := state.Get("vmName").(string) + + var script powershell.ScriptBuilder + script.WriteLine("param([string]$vmName)") + script.WriteLine("Set-VMDvdDrive -VMName $vmName -Path $null") + + powershell := new(powershell.PowerShellCmd) + _ = powershell.Run(script.String(), vmName) +} + +func (s *StepUpdateIntegrationServices) mountIntegrationServicesSetupDisk(vmName string) (dvdDriveProperties, error) { + + var dvdProperties dvdDriveProperties + + var script powershell.ScriptBuilder + script.WriteLine("param([string]$vmName)") + script.WriteLine("Add-VMDvdDrive -VMName $vmName") + + powershell := new(powershell.PowerShellCmd) + err := powershell.Run(script.String(), vmName) + if err != nil { + return dvdProperties, err + } + + script.Reset() + script.WriteLine("param([string]$vmName)") + script.WriteLine("(Get-VMDvdDrive -VMName $vmName | Where-Object {$_.Path -eq $null}).ControllerLocation") + controllerLocation, err := powershell.Output(script.String(), vmName) + if err != nil { + return dvdProperties, err + } + + script.Reset() + script.WriteLine("param([string]$vmName)") + script.WriteLine("(Get-VMDvdDrive -VMName $vmName | Where-Object {$_.Path -eq $null}).ControllerNumber") + controllerNumber, err := powershell.Output(script.String(), vmName) + if err != nil { + return dvdProperties, err + } + + isoPath := os.Getenv("WINDIR") + "\\system32\\vmguest.iso" + + script.Reset() + script.WriteLine("param([string]$vmName,[string]$path,[string]$controllerNumber,[string]$controllerLocation)") + script.WriteLine("Set-VMDvdDrive -VMName $vmName -Path $path -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation") + + err = powershell.Run(script.String(), vmName, isoPath, controllerNumber, controllerLocation) + if err != nil { + return dvdProperties, err + } + + dvdProperties.ControllerNumber = controllerNumber + dvdProperties.ControllerLocation = controllerLocation + + return dvdProperties, err +} diff --git a/builder/hyperv/common/step_wait_for_install_to_complete.go b/builder/hyperv/common/step_wait_for_install_to_complete.go new file mode 100644 index 000000000..22da371c8 --- /dev/null +++ b/builder/hyperv/common/step_wait_for_install_to_complete.go @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "strings" + "strconv" + "time" + powershell "github.com/mitchellh/packer/powershell" +) + +const ( + SleepSeconds = 10 +) + +type StepWaitForPowerOff struct { +} + +func (s *StepWaitForPowerOff) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + ui.Say("Waiting for vm to be powered down...") + + // unless the person has a super fast disk, it should take at least 5 minutes + // for the install and post-install operations to take. Wait 5 minutes to + // avoid hammering on getting VM status via PowerShell + time.Sleep(time.Second * 300); + + var script powershell.ScriptBuilder + script.WriteLine("param([string]$vmName)") + script.WriteLine("(Get-VM -Name $vmName).State -eq [Microsoft.HyperV.PowerShell.VMState]::Off") + isOffScript := script.String() + + for { + powershell := new(powershell.PowerShellCmd) + cmdOut, err := powershell.Output(isOffScript, vmName); + if err != nil { + err := fmt.Errorf("Error checking VM's state: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + if cmdOut == "True" { + break + } else { + time.Sleep(time.Second * SleepSeconds); + } + } + + return multistep.ActionContinue +} + +func (s *StepWaitForPowerOff) Cleanup(state multistep.StateBag) { +} + +type StepWaitForInstallToComplete struct { + ExpectedRebootCount uint + ActionName string +} + +func (s *StepWaitForInstallToComplete) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + if(len(s.ActionName)>0){ + ui.Say(fmt.Sprintf("%v ! Waiting for VM to reboot %v times...",s.ActionName, s.ExpectedRebootCount)) + } + + var rebootCount uint + var lastUptime uint64 + + var script powershell.ScriptBuilder + script.WriteLine("param([string]$vmName)") + script.WriteLine("(Get-VM -Name $vmName).Uptime.TotalSeconds") + + uptimeScript := script.String() + + for rebootCount < s.ExpectedRebootCount { + powershell := new(powershell.PowerShellCmd) + cmdOut, err := powershell.Output(uptimeScript, vmName); + if err != nil { + err := fmt.Errorf("Error checking uptime: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + uptime, _ := strconv.ParseUint(strings.TrimSpace(string(cmdOut)), 10, 64) + if uint64(uptime) < lastUptime { + rebootCount++ + ui.Say(fmt.Sprintf("%v -> Detected reboot %v after %v seconds...", s.ActionName, rebootCount, lastUptime)) + } + + lastUptime = uptime + + if (rebootCount < s.ExpectedRebootCount) { + time.Sleep(time.Second * SleepSeconds); + } + } + + + return multistep.ActionContinue +} + +func (s *StepWaitForInstallToComplete) Cleanup(state multistep.StateBag) { + +} + + +type StepWaitForWinRm struct { +} + +func (s *StepWaitForWinRm) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + //vmName := state.Get("vmName").(string) + + ui.Say("Waiting for WinRM to be ready...") + + return multistep.ActionContinue +} + +func (s *StepWaitForWinRm) Cleanup(state multistep.StateBag) { + +} diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go new file mode 100644 index 000000000..60bd5f77f --- /dev/null +++ b/builder/hyperv/iso/builder.go @@ -0,0 +1,391 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package iso + +import ( + "code.google.com/p/go-uuid/uuid" + "errors" + "fmt" + "github.com/mitchellh/multistep" + hypervcommon "github.com/mitchellh/packer/builder/hyperv/common" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/helper/communicator" + powershell "github.com/mitchellh/packer/powershell" + "github.com/mitchellh/packer/powershell/hyperv" + "log" + "os" + "regexp" + "strings" + "time" +) + +const ( + DefaultDiskSize = 127 * 1024 // 127GB + MinDiskSize = 10 * 1024 // 10GB + MaxDiskSize = 65536 * 1024 // 64TB + + DefaultRamSize = 1024 // 1GB + MinRamSize = 512 // 512MB + MaxRamSize = 32768 // 32GB + + LowRam = 512 // 512MB + + DefaultUsername = "vagrant" + DefaultPassword = "vagrant" +) + +// Builder implements packer.Builder and builds the actual Hyperv +// images. +type Builder struct { + config config + runner multistep.Runner +} + +type config struct { + // The size, in megabytes, of the hard disk to create for the VM. + // By default, this is 130048 (about 127 GB). + DiskSize uint `mapstructure:"disk_size"` + // The size, in megabytes, of the computer memory in the VM. + // By default, this is 1024 (about 1 GB). + RamSizeMB uint `mapstructure:"ram_size_mb"` + // 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. + FloppyFiles []string `mapstructure:"floppy_files"` + // + SecondaryDvdImages []string `mapstructure:"secondary_iso_images"` + // 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. + ISOChecksum string `mapstructure:"iso_checksum"` + // 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. + ISOChecksumType string `mapstructure:"iso_checksum_type"` + // 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. + RawSingleISOUrl string `mapstructure:"iso_url"` + // Multiple URLs for the ISO to download. Packer will try these in order. + // If anything goes wrong attempting to download or while downloading a + // single URL, it will move on to the next. All URLs must point to the + // same file (same checksum). By default this is empty and iso_url is + // used. Only one of iso_url or iso_urls can be specified. + ISOUrls []string `mapstructure:"iso_urls"` + // This is the name of the new virtual machine. + // By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. + VMName string `mapstructure:"vm_name"` + + common.PackerConfig `mapstructure:",squash"` + hypervcommon.OutputConfig `mapstructure:",squash"` + hypervcommon.SSHConfig `mapstructure:",squash"` + hypervcommon.ShutdownConfig `mapstructure:",squash"` + + SwitchName string `mapstructure:"switch_name"` + + Communicator string `mapstructure:"communicator"` + + // The time in seconds to wait for the virtual machine to report an IP address. + // This defaults to 120 seconds. This may have to be increased if your VM takes longer to boot. + IPAddressTimeout time.Duration `mapstructure:"ip_address_timeout"` + + SSHWaitTimeout time.Duration + + tpl *packer.ConfigTemplate +} + +// Prepare processes the build configuration parameters. +func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { + + md, err := common.DecodeConfig(&b.config, raws...) + if err != nil { + return nil, err + } + + b.config.tpl, err = packer.NewConfigTemplate() + if err != nil { + return nil, err + } + + log.Println(fmt.Sprintf("%s: %v", "PackerUserVars", b.config.PackerUserVars)) + + b.config.tpl.UserVars = b.config.PackerUserVars + + // Accumulate any errors and warnings + errs := common.CheckUnusedConfig(md) + errs = packer.MultiErrorAppend(errs, b.config.OutputConfig.Prepare(b.config.tpl, &b.config.PackerConfig)...) + errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...) + errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(b.config.tpl)...) + + warnings := make([]string, 0) + + err = b.checkDiskSize() + if err != nil { + errs = packer.MultiErrorAppend(errs, err) + } + + err = b.checkRamSize() + if err != nil { + errs = packer.MultiErrorAppend(errs, err) + } + + if b.config.VMName == "" { + b.config.VMName = fmt.Sprintf("pvm_%s", uuid.New()) + } + + if b.config.SwitchName == "" { + // no switch name, try to get one attached to a online network adapter + onlineSwitchName, err := hyperv.GetExternalOnlineVirtualSwitch() + if onlineSwitchName == "" || err != nil { + b.config.SwitchName = fmt.Sprintf("pis_%s", uuid.New()) + } else { + b.config.SwitchName = onlineSwitchName + } + } + + log.Println(fmt.Sprintf("Using switch %s", b.config.SwitchName)) + + if b.config.Communicator == "" { + b.config.Communicator = "ssh" + } else if b.config.Communicator == "ssh" || b.config.Communicator == "winrm" { + // good + } else { + err = errors.New("communicator must be either ssh or winrm") + errs = packer.MultiErrorAppend(errs, err) + } + + // Errors + templates := map[string]*string{ + "iso_url": &b.config.RawSingleISOUrl + } + + for n, ptr := range templates { + var err error + *ptr, err = b.config.tpl.Process(*ptr, nil) + if err != nil { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName)) + log.Println(fmt.Sprintf("%s: %v", "SwitchName", b.config.SwitchName)) + log.Println(fmt.Sprintf("%s: %v", "Communicator", b.config.Communicator)) + + if b.config.RawSingleISOUrl == "" { + errs = packer.MultiErrorAppend(errs, errors.New("iso_url: The option can't be missed and a path must be specified.")) + } else if _, err := os.Stat(b.config.RawSingleISOUrl); err != nil { + errs = packer.MultiErrorAppend(errs, errors.New("iso_url: Check the path is correct")) + } + + log.Println(fmt.Sprintf("%s: %v", "RawSingleISOUrl", b.config.RawSingleISOUrl)) + + b.config.SSHWaitTimeout, err = time.ParseDuration(b.config.RawSSHWaitTimeout) + + // Warnings + warning := b.checkHostAvailableMemory() + if warning != "" { + warnings = appendWarnings(warnings, warning) + } + + if b.config.ShutdownCommand == "" { + warnings = append(warnings, + "A shutdown_command was not specified. Without a shutdown command, Packer\n"+ + "will forcibly halt the virtual machine, which may result in data loss.") + } + + if errs != nil && len(errs.Errors) > 0 { + return warnings, errs + } + + return warnings, nil +} + +// Run executes a Packer build and returns a packer.Artifact representing +// a Hyperv appliance. +func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { + // Create the driver that we'll use to communicate with Hyperv + driver, err := hypervcommon.NewHypervPS4Driver() + if err != nil { + return nil, fmt.Errorf("Failed creating Hyper-V driver: %s", err) + } + + // Set up the state. + state := new(multistep.BasicStateBag) + state.Put("config", &b.config) + state.Put("driver", driver) + state.Put("hook", hook) + state.Put("ui", ui) + + steps := []multistep.Step{ + &hypervcommon.StepCreateTempDir{}, + &hypervcommon.StepOutputDir{ + Force: b.config.PackerForce, + Path: b.config.OutputDir, + }, + &common.StepCreateFloppy{ + Files: b.config.FloppyFiles, + }, + &hypervcommon.StepCreateSwitch{ + SwitchName: b.config.SwitchName, + }, + &hypervcommon.StepCreateVM{ + VMName: b.config.VMName, + SwitchName: b.config.SwitchName, + RamSizeMB: b.config.RamSizeMB, + DiskSize: b.config.DiskSize, + }, + &hypervcommon.StepEnableIntegrationService{}, + + &hypervcommon.StepMountDvdDrive{ + RawSingleISOUrl: b.config.RawSingleISOUrl, + }, + &hypervcommon.StepMountFloppydrive{}, + + &hypervcommon.StepMountSecondaryDvdImages{}, + + + &hypervcommon.StepStartVm{ + Reason: "OS installation", + }, + + // wait for the vm to be powered off + &hypervcommon.StepWaitForPowerOff{}, + + // remove the integration services dvd drive + // after we power down + &hypervcommon.StepUnmountSecondaryDvdImages{}, + + // + &hypervcommon.StepStartVm{ + Reason: "provisioning", + StartUpDelay: 60, + }, + + // configure the communicator ssh, winrm + &communicator.StepConnect{ + Config: &b.config.SSHConfig.Comm, + Host: hypervcommon.CommHost, + SSHConfig: hypervcommon.SSHConfigFunc(b.config.SSHConfig), + SSHPort: hypervcommon.SSHPort, + }, + + // provision requires communicator to be setup + &common.StepProvision{}, + + &hypervcommon.StepUnmountFloppyDrive{}, + &hypervcommon.StepUnmountDvdDrive{}, + + &hypervcommon.StepShutdown{ + Command: b.config.ShutdownCommand, + Timeout: b.config.ShutdownTimeout, + }, + + &hypervcommon.StepExportVm{ + OutputDir: b.config.OutputDir, + }, + + // the clean up actions for each step will be executed reverse order + } + + // Run the steps. + if b.config.PackerDebug { + b.runner = &multistep.DebugRunner{ + Steps: steps, + PauseFn: common.MultistepDebugFn(ui), + } + } else { + b.runner = &multistep.BasicRunner{Steps: steps} + } + b.runner.Run(state) + + // Report any errors. + if rawErr, ok := state.GetOk("error"); ok { + return nil, rawErr.(error) + } + + // If we were interrupted or cancelled, then just exit. + if _, ok := state.GetOk(multistep.StateCancelled); ok { + return nil, errors.New("Build was cancelled.") + } + + if _, ok := state.GetOk(multistep.StateHalted); ok { + return nil, errors.New("Build was halted.") + } + + return hypervcommon.NewArtifact(b.config.OutputDir) +} + +// Cancel. +func (b *Builder) Cancel() { + if b.runner != nil { + log.Println("Cancelling the step runner...") + b.runner.Cancel() + } +} + +func appendWarnings(slice []string, data ...string) []string { + m := len(slice) + n := m + len(data) + if n > cap(slice) { // if necessary, reallocate + // allocate double what's needed, for future growth. + newSlice := make([]string, (n+1)*2) + copy(newSlice, slice) + slice = newSlice + } + slice = slice[0:n] + copy(slice[m:n], data) + return slice +} + +func (b *Builder) checkDiskSize() error { + if b.config.DiskSize == 0 { + b.config.DiskSize = DefaultDiskSize + } + + log.Println(fmt.Sprintf("%s: %v", "DiskSize", b.config.DiskSize)) + + if b.config.DiskSize < MinDiskSize { + return fmt.Errorf("disk_size_gb: Windows server requires disk space >= %v GB, but defined: %v", MinDiskSize, b.config.DiskSize/1024) + } else if b.config.DiskSize > MaxDiskSize { + return fmt.Errorf("disk_size_gb: Windows server requires disk space <= %v GB, but defined: %v", MaxDiskSize, b.config.DiskSize/1024) + } + + return nil +} + +func (b *Builder) checkRamSize() error { + if b.config.RamSizeMB == 0 { + b.config.RamSizeMB = DefaultRamSize + } + + log.Println(fmt.Sprintf("%s: %v", "RamSize", b.config.RamSizeMB)) + + if b.config.RamSizeMB < MinRamSize { + return fmt.Errorf("ram_size_mb: Windows server requires memory size >= %v MB, but defined: %v", MinRamSize, b.config.RamSizeMB) + } else if b.config.RamSizeMB > MaxRamSize { + return fmt.Errorf("ram_size_mb: Windows server requires memory size <= %v MB, but defined: %v", MaxRamSize, b.config.RamSizeMB) + } + + return nil +} + +func (b *Builder) checkHostAvailableMemory() string { + freeMB := powershell.GetHostAvailableMemory() + + if (freeMB - float64(b.config.RamSizeMB)) < LowRam { + return fmt.Sprintf("Hyper-V might fail to create a VM if there is not enough free memory in the system.") + } + + return "" +} \ No newline at end of file diff --git a/plugin/packer-builder-hyperv-iso/build-and-deploy.sh b/plugin/packer-builder-hyperv-iso/build-and-deploy.sh new file mode 100644 index 000000000..8e0185a4e --- /dev/null +++ b/plugin/packer-builder-hyperv-iso/build-and-deploy.sh @@ -0,0 +1,3 @@ +go build +cp packer-builder-hyperv-iso.exe ../../../bin/ + diff --git a/plugin/packer-builder-hyperv-iso/main.go b/plugin/packer-builder-hyperv-iso/main.go new file mode 100644 index 000000000..c7be7a1ed --- /dev/null +++ b/plugin/packer-builder-hyperv-iso/main.go @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package main + +import ( + "github.com/mitchellh/packer/builder/hyperv/iso" + "github.com/mitchellh/packer/plugin" +) + +func main() { + server, err := plugin.Server() + if err != nil { + panic(err) + } + server.RegisterBuilder(new(iso.Builder)) + server.Serve() +} diff --git a/plugin/packer-builder-hyperv-iso/main_test.go b/plugin/packer-builder-hyperv-iso/main_test.go new file mode 100644 index 000000000..b07e18080 --- /dev/null +++ b/plugin/packer-builder-hyperv-iso/main_test.go @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package main diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go new file mode 100644 index 000000000..afddc928b --- /dev/null +++ b/powershell/hyperv/hyperv.go @@ -0,0 +1,433 @@ +package hyperv + +import ( + "github.com/mitchellh/packer/powershell" + "strings" +) + +func GetVirtualMachineNetworkAdapterAddress(vmName string) (string, error) { + + var script = ` +param([string]$vmName, [int]$addressIndex) +try { + $adapter = Get-VMNetworkAdapter -VMName $vmName -ErrorAction SilentlyContinue + $ip = $adapter.IPAddresses[$addressIndex] + if($ip -eq $null) { + return $false + } +} catch { + return $false +} +$ip +` + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script, vmName, "0") + + return cmdOut, err +} + +func MountDvdDrive(vmName string, path string) error { + + var script = ` +param([string]$vmName,[string]$path) +Set-VMDvdDrive -VMName $vmName -Path $path +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, path) + return err +} + +func UnmountDvdDrive(vmName string) error { + + var script = ` +param([string]$vmName) +Set-VMDvdDrive -VMName $vmName -Path $null +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} + +func MountFloppyDrive(vmName string, path string) error { + var script = ` +param([string]$vmName, [string]$path) +Set-VMFloppyDiskDrive -VMName $vmName -Path $path +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, path) + return err +} + +func UnmountFloppyDrive(vmName string) error { + + var script = ` +param([string]$vmName) +Set-VMFloppyDiskDrive -VMName $vmName -Path $null +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} + +func CreateVirtualMachine(vmName string, path string, ram string, diskSize string, switchName string) error { + + var script = ` +param([string]$vmName, [string]$path, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName) +$vhdx = $vmName + '.vhdx' +$vhdPath = Join-Path -Path $path -ChildPath $vhdx +New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, path, ram, diskSize, switchName) + return err +} + +func DeleteVirtualMachine(vmName string) error { + + var script = ` +param([string]$vmName) +Remove-VM -Name $vmName -Force +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} + +func ExportVirtualMachine(vmName string, path string) error { + + var script = ` +param([string]$vmName, [string]$path) +Export-VM -Name $vmName -Path $path +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, path) + return err +} + +func CopyExportedVirtualMachine(expPath string, outputPath string, vhdDir string, vmDir string) error { + + var script = ` +param([string]$srcPath, [string]$dstPath, [string]$vhdDirName, [string]$vmDir) +Copy-Item -Path $srcPath/$vhdDirName -Destination $dstPath -recurse +Copy-Item -Path $srcPath/$vmDir -Destination $dstPath +Copy-Item -Path $srcPath/$vmDir/*.xml -Destination $dstPath/$vmDir +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, expPath, outputPath, vhdDir, vmDir) + return err +} + +func CreateVirtualSwitch(switchName string, switchType string) (bool, error) { + + var script = ` +param([string]$switchName,[string]$switchType) +$switches = Get-VMSwitch -Name $switchName -ErrorAction SilentlyContinue +if ($switches.Count -eq 0) { + New-VMSwitch -Name $switchName -SwitchType $switchType + return $true +} +return $false +` + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script, switchName, switchType) + var created = strings.TrimSpace(cmdOut) == "True" + return created, err +} + +func DeleteVirtualSwitch(switchName string) error { + + var script = ` +param([string]$switchName) +$switch = Get-VMSwitch -Name $switchName -ErrorAction SilentlyContinue +if ($switch -ne $null) { + $switch | Remove-VMSwitch -Force +} +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, switchName) + return err +} + +func StartVirtualMachine(vmName string) error { + + var script = ` +param([string]$vmName) +Start-VM -Name $vmName +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} + +func RestartVirtualMachine(vmName string) error { + + var script = ` +param([string]$vmName) +Restart-VM $vmName -Force +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} + +func StopVirtualMachine(vmName string) error { + + var script = ` +param([string]$vmName) +$vm = Get-VM -Name $vmName +if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { + Stop-VM -VM $vm +} +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} + +func EnableVirtualMachineIntegrationService(vmName string, integrationServiceName string) error { + + var script = ` +param([string]$vmName,[string]$integrationServiceName) +Enable-VMIntegrationService -VMName $vmName -Name $integrationServiceName +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, integrationServiceName) + return err +} + +func SetNetworkAdapterVlanId(switchName string, vlanId string) error { + + var script = ` +param([string]$networkAdapterName,[string]$vlanId) +Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName $networkAdapterName -Access -VlanId $vlanId +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, switchName, vlanId) + return err +} + +func SetVirtualMachineVlanId(vmName string, vlanId string) error { + + var script = ` +param([string]$vmName,[string]$vlanId) +Set-VMNetworkAdapterVlan -VMName $vmName -Access -VlanId $vlanId +` + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, vlanId) + return err +} + +func GetExternalOnlineVirtualSwitch() (string, error) { + + var script = ` +$adapters = Get-NetAdapter -Physical -ErrorAction SilentlyContinue | Where-Object { $_.Status -eq 'Up' } | Sort-Object -Descending -Property Speed +foreach ($adapter in $adapters) { + $switch = Get-VMSwitch -SwitchType External | Where-Object { $_.NetAdapterInterfaceDescription -eq $adapter.InterfaceDescription } + + if ($switch -ne $null) { + $switch.Name + break + } +} +` + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script) + if err != nil { + return "", err + } + + var switchName = strings.TrimSpace(cmdOut) + return switchName, nil +} + +func CreateExternalVirtualSwitch(vmName string, switchName string) error { + + var script = ` +param([string]$vmName,[string]$switchName) +$switch = $null +$names = @('ethernet','wi-fi','lan') +$adapters = foreach ($name in $names) { + Get-NetAdapter -Physical -Name $name -ErrorAction SilentlyContinue | where status -eq 'up' +} + +foreach ($adapter in $adapters) { + $switch = Get-VMSwitch -SwitchType External | where { $_.NetAdapterInterfaceDescription -eq $adapter.InterfaceDescription } + + if ($switch -eq $null) { + $switch = New-VMSwitch -Name $switchName -NetAdapterName $adapter.Name -AllowManagementOS $true -Notes 'Parent OS, VMs, WiFi' + } + + if ($switch -ne $null) { + break + } +} + +if($switch -ne $null) { + Get-VMNetworkAdapter –VMName $vmName | Connect-VMNetworkAdapter -VMSwitch $switch +} else { + Write-Error 'No internet adapters found' +} +` + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, switchName) + return err +} + +func GetVirtualMachineSwitchName(vmName string) (string, error) { + + var script = ` +param([string]$vmName) +(Get-VMNetworkAdapter -VMName $vmName).SwitchName +` + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script, vmName) + if err != nil { + return "", err + } + + return strings.TrimSpace(cmdOut), nil +} + +func ConnectVirtualMachineNetworkAdapterToSwitch(vmName string, switchName string) error { + + var script = ` +param([string]$vmName,[string]$switchName) +Get-VMNetworkAdapter –VMName $vmName | Connect-VMNetworkAdapter –SwitchName $switchName +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, switchName) + return err +} + +func UntagVirtualMachineNetworkAdapterVlan(vmName string, switchName string) error { + + var script = ` +param([string]$vmName,[string]$switchName) +Set-VMNetworkAdapterVlan -VMName $vmName -Untagged +Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName $switchName -Untagged +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, switchName) + return err +} + +func IsRunning(vmName string) (bool, error) { + + var script = ` +param([string]$vmName) +$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue +$vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running +` + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script, vmName) + var isRunning = strings.TrimSpace(cmdOut) == "True" + return isRunning, err +} + +func Mac(vmName string) (string, error) { + var script = ` +param([string]$vmName, [int]$addressIndex) +try { + $adapter = Get-VMNetworkAdapter -VMName $vmName -ErrorAction SilentlyContinue + $mac = $adapter.MacAddress[$addressIndex] + if($mac -eq $null) { + return $false + } +} catch { + return $false +} +$mac +` + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script, vmName, "0") + + return cmdOut, err +} + +func IpAddress(mac string) (string, error) { + var script = ` +param([string]$mac, [int]$addressIndex) +try { + $ip = Get-Vm | %{$_.NetworkAdapters} | ?{$_.MacAddress -eq $mac} | %{$_.IpAddresses[$addressIndex]} + + if($ip -eq $null) { + return $false + } +} catch { + return $false +} +$ip +` + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script, mac, "0") + + return cmdOut, err +} + +func Start(vmName string) error { + + var script = ` +param([string]$vmName) +$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue +if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Off) { + Start-VM –Name $vmName +} +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} + +func TurnOff(vmName string) error { + + var script = ` +param([string]$vmName) +$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue +if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { + Stop-VM -Name $vmName -TurnOff +} +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} + +func ShutDown(vmName string) error { + + var script = ` +param([string]$vmName) +$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue +if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { + Stop-VM –Name $vmName +} +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} diff --git a/powershell/powershell.go b/powershell/powershell.go new file mode 100644 index 000000000..28c3d908b --- /dev/null +++ b/powershell/powershell.go @@ -0,0 +1,255 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package powershell + +import ( + "fmt" + "log" + "io" + "os" + "os/exec" + "strings" + "bytes" + "io/ioutil" + "strconv" +) + +const ( + powerShellFalse = "False" + powerShellTrue = "True" +) + +type PowerShellCmd struct { + Stdout io.Writer + Stderr io.Writer +} + +func (ps *PowerShellCmd) Run(fileContents string, params ...string) error { + _, err := ps.Output(fileContents, params...) + return err +} + +// Output runs the PowerShell command and returns its standard output. +func (ps *PowerShellCmd) Output(fileContents string, params ...string) (string, error) { + path, err := ps.getPowerShellPath(); + if err != nil { + return "", nil + } + + filename, err := saveScript(fileContents); + if err != nil { + return "", err + } + + debug := os.Getenv("PACKER_POWERSHELL_DEBUG") != "" + verbose := debug || os.Getenv("PACKER_POWERSHELL_VERBOSE") != "" + + if !debug { + defer os.Remove(filename) + } + + args := createArgs(filename, params...) + + if verbose { + log.Printf("Run: %s %s", path, args) + } + + var stdout, stderr bytes.Buffer + command := exec.Command(path, args...) + command.Stdout = &stdout + command.Stderr = &stderr + + err = command.Run() + + if ps.Stdout != nil { + stdout.WriteTo(ps.Stdout) + } + + if ps.Stderr != nil { + stderr.WriteTo(ps.Stderr) + } + + stderrString := strings.TrimSpace(stderr.String()) + + if _, ok := err.(*exec.ExitError); ok { + err = fmt.Errorf("PowerShell error: %s", stderrString) + } + + if len(stderrString) > 0 { + err = fmt.Errorf("PowerShell error: %s", stderrString) + } + + stdoutString := strings.TrimSpace(stdout.String()) + + if verbose && stdoutString != "" { + log.Printf("stdout: %s", stdoutString) + } + + // only write the stderr string if verbose because + // the error string will already be in the err return value. + if verbose && stderrString != "" { + log.Printf("stderr: %s", stderrString) + } + + return stdoutString, err; +} + +func (ps *PowerShellCmd) getPowerShellPath() (string, error) { + path, err := exec.LookPath("powershell") + if err != nil { + log.Fatal("Cannot find PowerShell in the path", err) + return "", err + } + + return path, nil +} + +func saveScript(fileContents string) (string, error) { + file, err := ioutil.TempFile(os.TempDir(), "ps") + if err != nil { + return "", err + } + + _, err = file.Write([]byte(fileContents)) + if err != nil { + return "", err + } + + err = file.Close() + if err != nil { + return "", err + } + + newFilename := file.Name() + ".ps1" + err = os.Rename(file.Name(), newFilename) + if err != nil { + return "", err + } + + return newFilename, nil +} + +func createArgs(filename string, params ...string) []string { + args := make([]string,len(params)+4) + args[0] = "-ExecutionPolicy" + args[1] = "Bypass" + + args[2] = "-File" + args[3] = filename + + for key, value := range params { + args[key+4] = value + } + + return args; +} + +func GetHostAvailableMemory() float64 { + + var script = "(Get-WmiObject Win32_OperatingSystem).FreePhysicalMemory / 1024" + + var ps PowerShellCmd + output, _ := ps.Output(script) + + freeMB, _ := strconv.ParseFloat(output, 64) + + return freeMB +} + + +func GetHostName(ip string) (string, error) { + + var script = ` +param([string]$ip) +try { + $HostName = [System.Net.Dns]::GetHostEntry($ip).HostName + if ($HostName -ne $null) { + $HostName = $HostName.Split('.')[0] + } + $HostName +} catch { } +` + + // + var ps PowerShellCmd + cmdOut, err := ps.Output(script, ip); + if err != nil { + return "", err + } + + return cmdOut, nil +} + +func IsCurrentUserAnAdministrator() (bool, error) { + var script = ` +$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() +$principal = new-object System.Security.Principal.WindowsPrincipal($identity) +$administratorRole = [System.Security.Principal.WindowsBuiltInRole]::Administrator +return $principal.IsInRole($administratorRole) +` + + var ps PowerShellCmd + cmdOut, err := ps.Output(script); + if err != nil { + return false, err + } + + res := strings.TrimSpace(cmdOut) + return res == powerShellTrue, nil +} + + +func ModuleExists(moduleName string) (bool, error) { + + var script = ` +param([string]$moduleName) +(Get-Module -Name $moduleName) -ne $null +` + var ps PowerShellCmd + cmdOut, err := ps.Output(script) + if err != nil { + return false, err + } + + res := strings.TrimSpace(string(cmdOut)) + + if(res == powerShellFalse){ + err := fmt.Errorf("PowerShell %s module is not loaded. Make sure %s feature is on.", moduleName, moduleName) + return false, err + } + + return true, nil +} + +func SetUnattendedProductKey(path string, productKey string) error { + + var script = ` +param([string]$path,[string]$productKey) + +$unattend = [xml](Get-Content -Path $path) +$ns = @{ un = 'urn:schemas-microsoft-com:unattend' } + +$setupNode = $unattend | + Select-Xml -XPath '//un:settings[@pass = "specialize"]/un:component[@name = "Microsoft-Windows-Shell-Setup"]' -Namespace $ns | + Select-Object -ExpandProperty Node + +$productKeyNode = $setupNode | + Select-Xml -XPath '//un:ProductKey' -Namespace $ns | + Select-Object -ExpandProperty Node + +if ($productKeyNode -eq $null) { + $productKeyNode = $unattend.CreateElement('ProductKey', $ns.un) + [Void]$setupNode.AppendChild($productKeyNode) +} + +$productKeyNode.InnerText = $productKey + +$unattend.Save($path) +` + + var ps PowerShellCmd + err := ps.Run(script, path, productKey) + return err +} diff --git a/powershell/powershell_test.go b/powershell/powershell_test.go new file mode 100644 index 000000000..9d60e8f60 --- /dev/null +++ b/powershell/powershell_test.go @@ -0,0 +1,72 @@ + + +package powershell + +import ( + "bytes" + "testing" +) + +func TestOutputScriptBlock(t *testing.T) { + + ps, err := powershell.Command() + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + trueOutput, err := powershell.OutputScriptBlock("$True") + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if trueOutput != "True" { + t.Fatalf("output '%v' is not 'True'", trueOutput) + } + + falseOutput, err := powershell.OutputScriptBlock("$False") + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if falseOutput != "False" { + t.Fatalf("output '%v' is not 'False'", falseOutput) + } +} + +func TestRunScriptBlock(t *testing.T) { + powershell, err := powershell.Command() + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + err = powershell.RunScriptBlock("$True") +} + +func TestVersion(t *testing.T) { + powershell, err := powershell.Command() + version, err := powershell.Version(); + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if (version != 4) { + t.Fatalf("expected version 4") + } +} + +func TestRunFile(t *testing.T) { + powershell, err := powershell.Command() + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + var blockBuffer bytes.Buffer + blockBuffer.WriteString("param([string]$a, [string]$b, [int]$x, [int]$y) $n = $x + $y; Write-Host $a, $b, $n") + + err = powershell.Run(blockBuffer.String(), "a", "b", "5", "10") + + if err != nil { + t.Fatalf("should not have error: %s", err) + } + +} diff --git a/powershell/scriptbuilder.go b/powershell/scriptbuilder.go new file mode 100644 index 000000000..d51156aa2 --- /dev/null +++ b/powershell/scriptbuilder.go @@ -0,0 +1,30 @@ +package powershell + +import ( + "bytes" +) + +type ScriptBuilder struct { + buffer bytes.Buffer +} + +func (b *ScriptBuilder) WriteLine(s string) (n int, err error) { + n, err = b.buffer.WriteString(s); + b.buffer.WriteString("\n") + + return n+1, err +} + +func (b *ScriptBuilder) WriteString(s string) (n int, err error) { + n, err = b.buffer.WriteString(s); + return n, err +} + +func (b *ScriptBuilder) String() string { + return b.buffer.String() +} + +func (b *ScriptBuilder) Reset() { + b.buffer.Reset() +} + From ca16f33fa39a828b7dd3c6a32d8dcfa39d5ad181 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 21 Jun 2015 14:06:27 +0100 Subject: [PATCH 002/108] Use the same configuration style as existing builders Move builder into correctly named folder --- builder/hyperv/common/floppy_config.go | 19 +++ builder/hyperv/common/floppy_config_test.go | 18 +++ builder/hyperv/common/run_config.go | 29 +++++ builder/hyperv/common/run_config_test.go | 37 ++++++ builder/hyperv/common/shutdown_config.go | 30 +++++ builder/hyperv/common/shutdown_config_test.go | 45 +++++++ builder/hyperv/common/step_shutdown.go | 37 +----- builder/hyperv/iso/builder.go | 115 +++++++++++------- .../build-and-deploy.sh | 0 plugin/builder-hyperv-iso/main.go | 15 +++ .../main_test.go | 0 plugin/packer-builder-hyperv-iso/main.go | 19 --- 12 files changed, 266 insertions(+), 98 deletions(-) create mode 100644 builder/hyperv/common/floppy_config.go create mode 100644 builder/hyperv/common/floppy_config_test.go create mode 100644 builder/hyperv/common/run_config.go create mode 100644 builder/hyperv/common/run_config_test.go create mode 100644 builder/hyperv/common/shutdown_config.go create mode 100644 builder/hyperv/common/shutdown_config_test.go rename plugin/{packer-builder-hyperv-iso => builder-hyperv-iso}/build-and-deploy.sh (100%) create mode 100644 plugin/builder-hyperv-iso/main.go rename plugin/{packer-builder-hyperv-iso => builder-hyperv-iso}/main_test.go (100%) delete mode 100644 plugin/packer-builder-hyperv-iso/main.go diff --git a/builder/hyperv/common/floppy_config.go b/builder/hyperv/common/floppy_config.go new file mode 100644 index 000000000..d656e103a --- /dev/null +++ b/builder/hyperv/common/floppy_config.go @@ -0,0 +1,19 @@ +package common + +import ( + "github.com/mitchellh/packer/template/interpolate" +) + +// FloppyConfig is configuration related to created floppy disks and attaching +// them to a Parallels virtual machine. +type FloppyConfig struct { + FloppyFiles []string `mapstructure:"floppy_files"` +} + +func (c *FloppyConfig) Prepare(ctx *interpolate.Context) []error { + if c.FloppyFiles == nil { + c.FloppyFiles = make([]string, 0) + } + + return nil +} diff --git a/builder/hyperv/common/floppy_config_test.go b/builder/hyperv/common/floppy_config_test.go new file mode 100644 index 000000000..3e4fc55db --- /dev/null +++ b/builder/hyperv/common/floppy_config_test.go @@ -0,0 +1,18 @@ +package common + +import ( + "testing" +) + +func TestFloppyConfigPrepare(t *testing.T) { + c := new(FloppyConfig) + + errs := c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + + if len(c.FloppyFiles) > 0 { + t.Fatal("should not have floppy files") + } +} diff --git a/builder/hyperv/common/run_config.go b/builder/hyperv/common/run_config.go new file mode 100644 index 000000000..c755cdafb --- /dev/null +++ b/builder/hyperv/common/run_config.go @@ -0,0 +1,29 @@ +package common + +import ( + "fmt" + "time" + + "github.com/mitchellh/packer/template/interpolate" +) + +type RunConfig struct { + Headless bool `mapstructure:"headless"` + RawBootWait string `mapstructure:"boot_wait"` + + BootWait time.Duration `` +} + +func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { + if c.RawBootWait == "" { + c.RawBootWait = "10s" + } + + var err error + c.BootWait, err = time.ParseDuration(c.RawBootWait) + if err != nil { + return []error{fmt.Errorf("Failed parsing boot_wait: %s", err)} + } + + return nil +} diff --git a/builder/hyperv/common/run_config_test.go b/builder/hyperv/common/run_config_test.go new file mode 100644 index 000000000..8068fe625 --- /dev/null +++ b/builder/hyperv/common/run_config_test.go @@ -0,0 +1,37 @@ +package common + +import ( + "testing" +) + +func TestRunConfigPrepare_BootWait(t *testing.T) { + var c *RunConfig + var errs []error + + // Test a default boot_wait + c = new(RunConfig) + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %s", errs) + } + + if c.RawBootWait != "10s" { + t.Fatalf("bad value: %s", c.RawBootWait) + } + + // Test with a bad boot_wait + c = new(RunConfig) + c.RawBootWait = "this is not good" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatalf("bad: %#v", errs) + } + + // Test with a good one + c = new(RunConfig) + c.RawBootWait = "5s" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %s", errs) + } +} diff --git a/builder/hyperv/common/shutdown_config.go b/builder/hyperv/common/shutdown_config.go new file mode 100644 index 000000000..83d2224c3 --- /dev/null +++ b/builder/hyperv/common/shutdown_config.go @@ -0,0 +1,30 @@ +package common + +import ( + "fmt" + "time" + + "github.com/mitchellh/packer/template/interpolate" +) + +type ShutdownConfig struct { + ShutdownCommand string `mapstructure:"shutdown_command"` + RawShutdownTimeout string `mapstructure:"shutdown_timeout"` + + ShutdownTimeout time.Duration `` +} + +func (c *ShutdownConfig) Prepare(ctx *interpolate.Context) []error { + if c.RawShutdownTimeout == "" { + c.RawShutdownTimeout = "5m" + } + + var errs []error + var err error + c.ShutdownTimeout, err = time.ParseDuration(c.RawShutdownTimeout) + if err != nil { + errs = append(errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err)) + } + + return errs +} diff --git a/builder/hyperv/common/shutdown_config_test.go b/builder/hyperv/common/shutdown_config_test.go new file mode 100644 index 000000000..5da613a19 --- /dev/null +++ b/builder/hyperv/common/shutdown_config_test.go @@ -0,0 +1,45 @@ +package common + +import ( + "testing" + "time" +) + +func testShutdownConfig() *ShutdownConfig { + return &ShutdownConfig{} +} + +func TestShutdownConfigPrepare_ShutdownCommand(t *testing.T) { + var c *ShutdownConfig + var errs []error + + c = testShutdownConfig() + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } +} + +func TestShutdownConfigPrepare_ShutdownTimeout(t *testing.T) { + var c *ShutdownConfig + var errs []error + + // Test with a bad value + c = testShutdownConfig() + c.RawShutdownTimeout = "this is not good" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatalf("should have error") + } + + // Test with a good one + c = testShutdownConfig() + c.RawShutdownTimeout = "5s" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + if c.ShutdownTimeout != 5*time.Second { + t.Fatalf("bad: %s", c.ShutdownTimeout) + } +} diff --git a/builder/hyperv/common/step_shutdown.go b/builder/hyperv/common/step_shutdown.go index db80e7c56..62bbab21f 100644 --- a/builder/hyperv/common/step_shutdown.go +++ b/builder/hyperv/common/step_shutdown.go @@ -10,41 +10,6 @@ import ( "time" ) -type ShutdownConfig struct { - ShutdownCommand string `mapstructure:"shutdown_command"` - RawShutdownTimeout string `mapstructure:"shutdown_timeout"` - - ShutdownTimeout time.Duration `` -} - -func (c *ShutdownConfig) Prepare(t *packer.ConfigTemplate) []error { - if c.RawShutdownTimeout == "" { - c.RawShutdownTimeout = "5m" - } - - templates := map[string]*string{ - "shutdown_command": &c.ShutdownCommand, - "shutdown_timeout": &c.RawShutdownTimeout, - } - - errs := make([]error, 0) - for n, ptr := range templates { - var err error - *ptr, err = t.Process(*ptr, nil) - if err != nil { - errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err)) - } - } - - var err error - c.ShutdownTimeout, err = time.ParseDuration(c.RawShutdownTimeout) - if err != nil { - errs = append(errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err)) - } - - return errs -} - // This step shuts down the machine. It first attempts to do so gracefully, // but ultimately forcefully shuts it down if that fails. // @@ -104,7 +69,7 @@ func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction { if stderr.Len() > 0 { log.Printf("Shutdown stderr: %s", stderr.String()) } - + // Wait for the machine to actually shut down log.Printf("Waiting max %s for shutdown to complete", s.Timeout) shutdownTimer := time.After(s.Timeout) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 60bd5f77f..2e4723a91 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -11,13 +11,14 @@ import ( "github.com/mitchellh/multistep" hypervcommon "github.com/mitchellh/packer/builder/hyperv/common" "github.com/mitchellh/packer/common" - "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/helper/communicator" + "github.com/mitchellh/packer/helper/config" + "github.com/mitchellh/packer/packer" powershell "github.com/mitchellh/packer/powershell" "github.com/mitchellh/packer/powershell/hyperv" + "github.com/mitchellh/packer/template/interpolate" "log" "os" - "regexp" "strings" "time" ) @@ -40,11 +41,18 @@ const ( // Builder implements packer.Builder and builds the actual Hyperv // images. type Builder struct { - config config + config Config runner multistep.Runner } -type config struct { +type Config struct { + common.PackerConfig `mapstructure:",squash"` + hypervcommon.FloppyConfig `mapstructure:",squash"` + hypervcommon.OutputConfig `mapstructure:",squash"` + hypervcommon.SSHConfig `mapstructure:",squash"` + hypervcommon.ShutdownConfig `mapstructure:",squash"` + hypervcommon.RunConfig `mapstructure:",squash"` + // The size, in megabytes, of the hard disk to create for the VM. // By default, this is 130048 (about 127 GB). DiskSize uint `mapstructure:"disk_size"` @@ -87,11 +95,6 @@ type config struct { // By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. VMName string `mapstructure:"vm_name"` - common.PackerConfig `mapstructure:",squash"` - hypervcommon.OutputConfig `mapstructure:",squash"` - hypervcommon.SSHConfig `mapstructure:",squash"` - hypervcommon.ShutdownConfig `mapstructure:",squash"` - SwitchName string `mapstructure:"switch_name"` Communicator string `mapstructure:"communicator"` @@ -102,32 +105,28 @@ type config struct { SSHWaitTimeout time.Duration - tpl *packer.ConfigTemplate + ctx interpolate.Context } // Prepare processes the build configuration parameters. func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - - md, err := common.DecodeConfig(&b.config, raws...) + err := config.Decode(&b.config, &config.DecodeOpts{ + Interpolate: true, + InterpolateFilter: &interpolate.RenderFilter{ + Exclude: []string{}, + }, + }, raws...) if err != nil { return nil, err } - b.config.tpl, err = packer.NewConfigTemplate() - if err != nil { - return nil, err - } - - log.Println(fmt.Sprintf("%s: %v", "PackerUserVars", b.config.PackerUserVars)) - - b.config.tpl.UserVars = b.config.PackerUserVars - // Accumulate any errors and warnings - errs := common.CheckUnusedConfig(md) - errs = packer.MultiErrorAppend(errs, b.config.OutputConfig.Prepare(b.config.tpl, &b.config.PackerConfig)...) - errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...) - errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(b.config.tpl)...) - + var errs *packer.MultiError + errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...) + errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...) + errs = packer.MultiErrorAppend(errs, b.config.OutputConfig.Prepare(&b.config.ctx, &b.config.PackerConfig)...) + errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(&b.config.ctx)...) + errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(&b.config.ctx)...) warnings := make([]string, 0) err = b.checkDiskSize() @@ -144,6 +143,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.VMName = fmt.Sprintf("pvm_%s", uuid.New()) } + log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName)) + if b.config.SwitchName == "" { // no switch name, try to get one attached to a online network adapter onlineSwitchName, err := hyperv.GetExternalOnlineVirtualSwitch() @@ -155,6 +156,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } log.Println(fmt.Sprintf("Using switch %s", b.config.SwitchName)) + log.Println(fmt.Sprintf("%s: %v", "SwitchName", b.config.SwitchName)) if b.config.Communicator == "" { b.config.Communicator = "ssh" @@ -165,22 +167,47 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs = packer.MultiErrorAppend(errs, err) } - // Errors - templates := map[string]*string{ - "iso_url": &b.config.RawSingleISOUrl - } + log.Println(fmt.Sprintf("%s: %v", "Communicator", b.config.Communicator)) - for n, ptr := range templates { - var err error - *ptr, err = b.config.tpl.Process(*ptr, nil) - if err != nil { - errs = packer.MultiErrorAppend(errs, fmt.Errorf("Error processing %s: %s", n, err)) + // Errors + if b.config.ISOChecksumType == "" { + errs = packer.MultiErrorAppend( + errs, errors.New("The iso_checksum_type must be specified.")) + } else { + b.config.ISOChecksumType = strings.ToLower(b.config.ISOChecksumType) + if b.config.ISOChecksumType != "none" { + if b.config.ISOChecksum == "" { + errs = packer.MultiErrorAppend( + errs, errors.New("Due to large file sizes, an iso_checksum is required")) + } else { + b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum) + } + + if h := common.HashForType(b.config.ISOChecksumType); h == nil { + errs = packer.MultiErrorAppend( + errs, + fmt.Errorf("Unsupported checksum type: %s", b.config.ISOChecksumType)) + } } } - log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName)) - log.Println(fmt.Sprintf("%s: %v", "SwitchName", b.config.SwitchName)) - log.Println(fmt.Sprintf("%s: %v", "Communicator", b.config.Communicator)) + if b.config.RawSingleISOUrl == "" && len(b.config.ISOUrls) == 0 { + errs = packer.MultiErrorAppend( + errs, errors.New("One of iso_url or iso_urls must be specified.")) + } else if b.config.RawSingleISOUrl != "" && len(b.config.ISOUrls) > 0 { + errs = packer.MultiErrorAppend( + errs, errors.New("Only one of iso_url or iso_urls may be specified.")) + } else if b.config.RawSingleISOUrl != "" { + b.config.ISOUrls = []string{b.config.RawSingleISOUrl} + } + + for i, url := range b.config.ISOUrls { + b.config.ISOUrls[i], err = common.DownloadableURL(url) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Failed to parse iso_url %d: %s", i+1, err)) + } + } if b.config.RawSingleISOUrl == "" { errs = packer.MultiErrorAppend(errs, errors.New("iso_url: The option can't be missed and a path must be specified.")) @@ -190,14 +217,18 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { log.Println(fmt.Sprintf("%s: %v", "RawSingleISOUrl", b.config.RawSingleISOUrl)) - b.config.SSHWaitTimeout, err = time.ParseDuration(b.config.RawSSHWaitTimeout) - // Warnings warning := b.checkHostAvailableMemory() if warning != "" { warnings = appendWarnings(warnings, warning) } + if b.config.ISOChecksumType == "none" { + warnings = append(warnings, + "A checksum type of 'none' was specified. Since ISO files are so big,\n"+ + "a checksum is highly recommended.") + } + if b.config.ShutdownCommand == "" { warnings = append(warnings, "A shutdown_command was not specified. Without a shutdown command, Packer\n"+ @@ -254,7 +285,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &hypervcommon.StepMountSecondaryDvdImages{}, - &hypervcommon.StepStartVm{ Reason: "OS installation", }, @@ -277,7 +307,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Config: &b.config.SSHConfig.Comm, Host: hypervcommon.CommHost, SSHConfig: hypervcommon.SSHConfigFunc(b.config.SSHConfig), - SSHPort: hypervcommon.SSHPort, }, // provision requires communicator to be setup @@ -388,4 +417,4 @@ func (b *Builder) checkHostAvailableMemory() string { } return "" -} \ No newline at end of file +} diff --git a/plugin/packer-builder-hyperv-iso/build-and-deploy.sh b/plugin/builder-hyperv-iso/build-and-deploy.sh similarity index 100% rename from plugin/packer-builder-hyperv-iso/build-and-deploy.sh rename to plugin/builder-hyperv-iso/build-and-deploy.sh diff --git a/plugin/builder-hyperv-iso/main.go b/plugin/builder-hyperv-iso/main.go new file mode 100644 index 000000000..664615049 --- /dev/null +++ b/plugin/builder-hyperv-iso/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/mitchellh/packer/builder/hyperv/iso" + "github.com/mitchellh/packer/packer/plugin" +) + +func main() { + server, err := plugin.Server() + if err != nil { + panic(err) + } + server.RegisterBuilder(new(iso.Builder)) + server.Serve() +} diff --git a/plugin/packer-builder-hyperv-iso/main_test.go b/plugin/builder-hyperv-iso/main_test.go similarity index 100% rename from plugin/packer-builder-hyperv-iso/main_test.go rename to plugin/builder-hyperv-iso/main_test.go diff --git a/plugin/packer-builder-hyperv-iso/main.go b/plugin/packer-builder-hyperv-iso/main.go deleted file mode 100644 index c7be7a1ed..000000000 --- a/plugin/packer-builder-hyperv-iso/main.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package main - -import ( - "github.com/mitchellh/packer/builder/hyperv/iso" - "github.com/mitchellh/packer/plugin" -) - -func main() { - server, err := plugin.Server() - if err != nil { - panic(err) - } - server.RegisterBuilder(new(iso.Builder)) - server.Serve() -} From a42215981ed0c61e82009db84288747aa91f4aa7 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 21 Jun 2015 14:32:48 +0100 Subject: [PATCH 003/108] The test is meant to fail if the folder already exists --- builder/hyperv/common/output_config_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/hyperv/common/output_config_test.go b/builder/hyperv/common/output_config_test.go index a4d8e7999..ebd91eab1 100644 --- a/builder/hyperv/common/output_config_test.go +++ b/builder/hyperv/common/output_config_test.go @@ -39,7 +39,7 @@ func TestOutputConfigPrepare_exists(t *testing.T) { PackerForce: false, } errs := c.Prepare(testConfigTemplate(t), pc) - if len(errs) != 0 { - t.Fatal("should not have errors") + if len(errs) == 0 { + t.Fatal("should have errors") } } From 469415fdfb09189f54848c961614bae5f0df619c Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 21 Jun 2015 14:59:37 +0100 Subject: [PATCH 004/108] Tests where for an old version of powershell class. Changed for the new one --- powershell/powershell_test.go | 48 ++++++++++------------------------- 1 file changed, 14 insertions(+), 34 deletions(-) diff --git a/powershell/powershell_test.go b/powershell/powershell_test.go index 9d60e8f60..4aff20491 100644 --- a/powershell/powershell_test.go +++ b/powershell/powershell_test.go @@ -1,5 +1,3 @@ - - package powershell import ( @@ -7,14 +5,18 @@ import ( "testing" ) -func TestOutputScriptBlock(t *testing.T) { - - ps, err := powershell.Command() +func TestOutput(t *testing.T) { + var ps PowerShellCmd + cmdOut, err := ps.Output("") if err != nil { t.Fatalf("should not have error: %s", err) } - trueOutput, err := powershell.OutputScriptBlock("$True") + if cmdOut != "" { + t.Fatalf("output '%v' is not ''", cmdOut) + } + + trueOutput, err := ps.Output("$True") if err != nil { t.Fatalf("should not have error: %s", err) } @@ -23,7 +25,7 @@ func TestOutputScriptBlock(t *testing.T) { t.Fatalf("output '%v' is not 'True'", trueOutput) } - falseOutput, err := powershell.OutputScriptBlock("$False") + falseOutput, err := ps.Output("$False") if err != nil { t.Fatalf("should not have error: %s", err) } @@ -33,40 +35,18 @@ func TestOutputScriptBlock(t *testing.T) { } } -func TestRunScriptBlock(t *testing.T) { - powershell, err := powershell.Command() - if err != nil { - t.Fatalf("should not have error: %s", err) - } - - err = powershell.RunScriptBlock("$True") -} - -func TestVersion(t *testing.T) { - powershell, err := powershell.Command() - version, err := powershell.Version(); - if err != nil { - t.Fatalf("should not have error: %s", err) - } - - if (version != 4) { - t.Fatalf("expected version 4") - } -} - func TestRunFile(t *testing.T) { - powershell, err := powershell.Command() - if err != nil { - t.Fatalf("should not have error: %s", err) - } - var blockBuffer bytes.Buffer blockBuffer.WriteString("param([string]$a, [string]$b, [int]$x, [int]$y) $n = $x + $y; Write-Host $a, $b, $n") - err = powershell.Run(blockBuffer.String(), "a", "b", "5", "10") + var ps PowerShellCmd + cmdOut, err := ps.Output(blockBuffer.String(), "a", "b", "5", "10") if err != nil { t.Fatalf("should not have error: %s", err) } + if cmdOut != "a b 15" { + t.Fatalf("output '%v' is not 'a b 15'", cmdOut) + } } From 79a5f903795a03f5f8409187e4c20148a78bdd88 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 21 Jun 2015 15:53:08 +0100 Subject: [PATCH 005/108] Use the convention for default vmname Use the convention for default hdd size Tests added for builder --- builder/hyperv/iso/builder.go | 26 ++- builder/hyperv/iso/builder_test.go | 246 +++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+), 16 deletions(-) create mode 100644 builder/hyperv/iso/builder_test.go diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 2e4723a91..f9501511e 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -5,7 +5,6 @@ package iso import ( - "code.google.com/p/go-uuid/uuid" "errors" "fmt" "github.com/mitchellh/multistep" @@ -18,13 +17,12 @@ import ( "github.com/mitchellh/packer/powershell/hyperv" "github.com/mitchellh/packer/template/interpolate" "log" - "os" "strings" "time" ) const ( - DefaultDiskSize = 127 * 1024 // 127GB + DefaultDiskSize = 40000 // ~40GB MinDiskSize = 10 * 1024 // 10GB MaxDiskSize = 65536 * 1024 // 64TB @@ -71,6 +69,7 @@ type Config struct { FloppyFiles []string `mapstructure:"floppy_files"` // SecondaryDvdImages []string `mapstructure:"secondary_iso_images"` + // 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 @@ -91,6 +90,7 @@ type Config struct { // same file (same checksum). By default this is empty and iso_url is // used. Only one of iso_url or iso_urls can be specified. ISOUrls []string `mapstructure:"iso_urls"` + // This is the name of the new virtual machine. // By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. VMName string `mapstructure:"vm_name"` @@ -140,7 +140,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if b.config.VMName == "" { - b.config.VMName = fmt.Sprintf("pvm_%s", uuid.New()) + b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) } log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName)) @@ -149,7 +149,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { // no switch name, try to get one attached to a online network adapter onlineSwitchName, err := hyperv.GetExternalOnlineVirtualSwitch() if onlineSwitchName == "" || err != nil { - b.config.SwitchName = fmt.Sprintf("pis_%s", uuid.New()) + b.config.SwitchName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) } else { b.config.SwitchName = onlineSwitchName } @@ -209,20 +209,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } - if b.config.RawSingleISOUrl == "" { - errs = packer.MultiErrorAppend(errs, errors.New("iso_url: The option can't be missed and a path must be specified.")) - } else if _, err := os.Stat(b.config.RawSingleISOUrl); err != nil { - errs = packer.MultiErrorAppend(errs, errors.New("iso_url: Check the path is correct")) - } - log.Println(fmt.Sprintf("%s: %v", "RawSingleISOUrl", b.config.RawSingleISOUrl)) // Warnings - warning := b.checkHostAvailableMemory() - if warning != "" { - warnings = appendWarnings(warnings, warning) - } - if b.config.ISOChecksumType == "none" { warnings = append(warnings, "A checksum type of 'none' was specified. Since ISO files are so big,\n"+ @@ -235,6 +224,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { "will forcibly halt the virtual machine, which may result in data loss.") } + warning := b.checkHostAvailableMemory() + if warning != "" { + warnings = appendWarnings(warnings, warning) + } + if errs != nil && len(errs.Errors) > 0 { return warnings, errs } diff --git a/builder/hyperv/iso/builder_test.go b/builder/hyperv/iso/builder_test.go new file mode 100644 index 000000000..a17851a3e --- /dev/null +++ b/builder/hyperv/iso/builder_test.go @@ -0,0 +1,246 @@ +package iso + +import ( + "github.com/mitchellh/packer/packer" + "reflect" + "testing" +) + +func testConfig() map[string]interface{} { + return map[string]interface{}{ + "iso_checksum": "foo", + "iso_checksum_type": "md5", + "iso_url": "http://www.packer.io", + "shutdown_command": "yes", + "ssh_username": "foo", + + packer.BuildNameConfigKey: "foo", + } +} + +func TestBuilder_ImplementsBuilder(t *testing.T) { + var raw interface{} + raw = &Builder{} + if _, ok := raw.(packer.Builder); !ok { + t.Error("Builder must implement builder.") + } +} + +func TestBuilderPrepare_Defaults(t *testing.T) { + var b Builder + config := testConfig() + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.VMName != "packer-foo" { + t.Errorf("bad vm name: %s", b.config.VMName) + } +} + +func TestBuilderPrepare_DiskSize(t *testing.T) { + var b Builder + config := testConfig() + + delete(config, "disk_size") + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("bad err: %s", err) + } + + if b.config.DiskSize != 40000 { + t.Fatalf("bad size: %d", b.config.DiskSize) + } + + config["disk_size"] = 60000 + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.DiskSize != 60000 { + t.Fatalf("bad size: %d", b.config.DiskSize) + } +} + +func TestBuilderPrepare_InvalidKey(t *testing.T) { + var b Builder + config := testConfig() + + // Add a random key + config["i_should_not_be_valid"] = true + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } +} + +func TestBuilderPrepare_ISOChecksum(t *testing.T) { + var b Builder + config := testConfig() + + // Test bad + config["iso_checksum"] = "" + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test good + config["iso_checksum"] = "FOo" + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.ISOChecksum != "foo" { + t.Fatalf("should've lowercased: %s", b.config.ISOChecksum) + } +} + +func TestBuilderPrepare_ISOChecksumType(t *testing.T) { + var b Builder + config := testConfig() + + // Test bad + config["iso_checksum_type"] = "" + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test good + config["iso_checksum_type"] = "mD5" + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.ISOChecksumType != "md5" { + t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType) + } + + // Test unknown + config["iso_checksum_type"] = "fake" + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test none + config["iso_checksum_type"] = "none" + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) == 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.ISOChecksumType != "none" { + t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType) + } +} + +func TestBuilderPrepare_ISOUrl(t *testing.T) { + var b Builder + config := testConfig() + delete(config, "iso_url") + delete(config, "iso_urls") + + // Test both epty + config["iso_url"] = "" + b = Builder{} + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test iso_url set + config["iso_url"] = "http://www.packer.io" + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Errorf("should not have error: %s", err) + } + + expected := []string{"http://www.packer.io"} + if !reflect.DeepEqual(b.config.ISOUrls, expected) { + t.Fatalf("bad: %#v", b.config.ISOUrls) + } + + // Test both set + config["iso_url"] = "http://www.packer.io" + config["iso_urls"] = []string{"http://www.packer.io"} + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test just iso_urls set + delete(config, "iso_url") + config["iso_urls"] = []string{ + "http://www.packer.io", + "http://www.hashicorp.com", + } + + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Errorf("should not have error: %s", err) + } + + expected = []string{ + "http://www.packer.io", + "http://www.hashicorp.com", + } + if !reflect.DeepEqual(b.config.ISOUrls, expected) { + t.Fatalf("bad: %#v", b.config.ISOUrls) + } +} From 87b3dec3d20e1d659788356a918708fbbd385b10 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 21 Jun 2015 19:35:32 +0100 Subject: [PATCH 006/108] Wait until WinRM is available, before continuing with install. This is how other system like VeeWee do it. Add support for number of cpus to use for vm Add support for vm generation --- builder/hyperv/common/step_create_vm.go | 24 ++++++++++---- .../step_wait_for_install_to_complete.go | 17 ---------- builder/hyperv/iso/builder.go | 31 +++++++++++-------- powershell/hyperv/hyperv.go | 20 +++++++++--- 4 files changed, 52 insertions(+), 40 deletions(-) diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index 6a172ada8..1ea4530d8 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -6,10 +6,10 @@ package common import ( "fmt" - "strconv" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/powershell/hyperv" + "strconv" ) // This step creates the actual virtual machine. @@ -17,17 +17,19 @@ import ( // Produces: // VMName string - The name of the VM type StepCreateVM struct { - VMName string + VMName string SwitchName string - RamSizeMB uint - DiskSize uint + RamSizeMB uint + DiskSize uint + Generation uint + Cpu uint } func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) ui.Say("Creating virtual machine...") - path := state.Get("packerTempDir").(string) + path := state.Get("packerTempDir").(string) // convert the MB to bytes ramBytes := int64(s.RamSizeMB * 1024 * 1024) @@ -36,8 +38,10 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { ram := strconv.FormatInt(ramBytes, 10) diskSize := strconv.FormatInt(diskSizeBytes, 10) switchName := s.SwitchName + generation := strconv.FormatInt(int64(s.Generation), 10) + cpu := strconv.FormatInt(int64(s.Cpu), 10) - err := hyperv.CreateVirtualMachine(s.VMName, path, ram, diskSize, switchName) + err := hyperv.CreateVirtualMachine(s.VMName, path, ram, diskSize, switchName, generation) if err != nil { err := fmt.Errorf("Error creating virtual machine: %s", err) state.Put("error", err) @@ -45,6 +49,14 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } + err = hyperv.SetVirtualMachineCpu(s.VMName, cpu) + if err != nil { + err := fmt.Errorf("Error creating setting virtual machine cpu: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + // Set the final name in the state bag so others can use it state.Put("vmName", s.VMName) diff --git a/builder/hyperv/common/step_wait_for_install_to_complete.go b/builder/hyperv/common/step_wait_for_install_to_complete.go index 22da371c8..efd8bd8b7 100644 --- a/builder/hyperv/common/step_wait_for_install_to_complete.go +++ b/builder/hyperv/common/step_wait_for_install_to_complete.go @@ -111,20 +111,3 @@ func (s *StepWaitForInstallToComplete) Run(state multistep.StateBag) multistep.S func (s *StepWaitForInstallToComplete) Cleanup(state multistep.StateBag) { } - - -type StepWaitForWinRm struct { -} - -func (s *StepWaitForWinRm) Run(state multistep.StateBag) multistep.StepAction { - ui := state.Get("ui").(packer.Ui) - //vmName := state.Get("vmName").(string) - - ui.Say("Waiting for WinRM to be ready...") - - return multistep.ActionContinue -} - -func (s *StepWaitForWinRm) Cleanup(state multistep.StateBag) { - -} diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index f9501511e..cf7e776a0 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -96,6 +96,8 @@ type Config struct { VMName string `mapstructure:"vm_name"` SwitchName string `mapstructure:"switch_name"` + Cpu uint `mapstructure:"cpu"` + Generation uint `mapstructure:"generation"` Communicator string `mapstructure:"communicator"` @@ -155,6 +157,14 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } + if b.config.Cpu < 1 { + b.config.Cpu = 1 + } + + if b.config.Generation != 2 { + b.config.Generation = 1 + } + log.Println(fmt.Sprintf("Using switch %s", b.config.SwitchName)) log.Println(fmt.Sprintf("%s: %v", "SwitchName", b.config.SwitchName)) @@ -269,6 +279,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SwitchName: b.config.SwitchName, RamSizeMB: b.config.RamSizeMB, DiskSize: b.config.DiskSize, + Generation: b.config.Generation, + Cpu: b.config.Cpu, }, &hypervcommon.StepEnableIntegrationService{}, @@ -283,19 +295,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Reason: "OS installation", }, - // wait for the vm to be powered off - &hypervcommon.StepWaitForPowerOff{}, - - // remove the integration services dvd drive - // after we power down - &hypervcommon.StepUnmountSecondaryDvdImages{}, - - // - &hypervcommon.StepStartVm{ - Reason: "provisioning", - StartUpDelay: 60, - }, - // configure the communicator ssh, winrm &communicator.StepConnect{ Config: &b.config.SSHConfig.Comm, @@ -306,6 +305,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // provision requires communicator to be setup &common.StepProvision{}, + // remove the integration services dvd drive + // after we power down + &hypervcommon.StepUnmountSecondaryDvdImages{}, &hypervcommon.StepUnmountFloppyDrive{}, &hypervcommon.StepUnmountDvdDrive{}, @@ -314,6 +316,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Timeout: b.config.ShutdownTimeout, }, + // wait for the vm to be powered off + &hypervcommon.StepWaitForPowerOff{}, + &hypervcommon.StepExportVm{ OutputDir: b.config.OutputDir, }, diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index afddc928b..250722eff 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -74,17 +74,29 @@ Set-VMFloppyDiskDrive -VMName $vmName -Path $null return err } -func CreateVirtualMachine(vmName string, path string, ram string, diskSize string, switchName string) error { +func CreateVirtualMachine(vmName string, path string, ram string, diskSize string, switchName string, generation string) error { var script = ` -param([string]$vmName, [string]$path, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName) +param([string]$vmName, [string]$path, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName, [int]generation) $vhdx = $vmName + '.vhdx' $vhdPath = Join-Path -Path $path -ChildPath $vhdx -New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName +New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName -Generation $generation ` var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, path, ram, diskSize, switchName) + err := ps.Run(script, vmName, path, ram, diskSize, switchName, generation) + return err +} + +func SetVirtualMachineCpu(vmName string, cpu string) error { + + var script = ` +param([string]$vmName, [int]cpu) +Set-VMProcessor -VMName $vmName –Count $cpu +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, cpu) return err } From 2d7cfcd65db2af08bf414814a062fe043f93ee93 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Mon, 22 Jun 2015 22:26:06 +0100 Subject: [PATCH 007/108] Throw error if we can't get mac or ip address during steps --- builder/hyperv/common/driver_ps_4.go | 25 +++++++++++++++++++++++-- builder/hyperv/common/ssh.go | 24 +++++++++++------------- builder/hyperv/iso/builder.go | 2 +- powershell/hyperv/hyperv.go | 18 +++++++++--------- 4 files changed, 44 insertions(+), 25 deletions(-) diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index a5c2abd6f..4b0146146 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -68,12 +68,33 @@ func (d *HypervPS4Driver) Verify() error { // Get mac address for VM. func (d *HypervPS4Driver) Mac(vmName string) (string, error) { - return hyperv.Mac(vmName) + res, err := hyperv.Mac(vmName) + + if err != nil { + return res, err + } + + if res == "" { + err := fmt.Errorf("%s", "No mac address.") + return res, err + } + + return res, err } // Get ip address for mac address. func (d *HypervPS4Driver) IpAddress(mac string) (string, error) { - return hyperv.IpAddress(mac) + res, err := hyperv.IpAddress(mac) + + if err != nil { + return res, err + } + + if res == "" { + err := fmt.Errorf("%s", "No ip address.") + return res, err + } + return res, err } func (d *HypervPS4Driver) verifyPSVersion() error { diff --git a/builder/hyperv/common/ssh.go b/builder/hyperv/common/ssh.go index f61047dd4..1bb06bfd2 100644 --- a/builder/hyperv/common/ssh.go +++ b/builder/hyperv/common/ssh.go @@ -3,8 +3,8 @@ package common import ( "github.com/mitchellh/multistep" commonssh "github.com/mitchellh/packer/common/ssh" - packerssh "github.com/mitchellh/packer/communicator/ssh" - "golang.org/x/crypto/ssh" + "github.com/mitchellh/packer/communicator/ssh" + gossh "golang.org/x/crypto/ssh" ) func CommHost(state multistep.StateBag) (string, error) { @@ -24,28 +24,26 @@ func CommHost(state multistep.StateBag) (string, error) { return ip, nil } -func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*ssh.ClientConfig, error) { - return func(state multistep.StateBag) (*ssh.ClientConfig, error) { - auth := []ssh.AuthMethod{ - ssh.Password(config.Comm.SSHPassword), - ssh.KeyboardInteractive( - packerssh.PasswordKeyboardInteractive(config.Comm.SSHPassword)), +func SSHConfigFunc(config *SSHConfig) func(multistep.StateBag) (*gossh.ClientConfig, error) { + return func(state multistep.StateBag) (*gossh.ClientConfig, error) { + auth := []gossh.AuthMethod{ + gossh.Password(config.Comm.SSHPassword), + gossh.KeyboardInteractive( + ssh.PasswordKeyboardInteractive(config.Comm.SSHPassword)), } - if config.SSHKeyPath != "" { + if config.Comm.SSHPrivateKey != "" { signer, err := commonssh.FileSigner(config.Comm.SSHPrivateKey) if err != nil { return nil, err } - auth = append(auth, ssh.PublicKeys(signer)) + auth = append(auth, gossh.PublicKeys(signer)) } - return &ssh.ClientConfig{ + return &gossh.ClientConfig{ User: config.Comm.SSHUsername, Auth: auth, }, nil } } - - diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index cf7e776a0..c7d4e6405 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -299,7 +299,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &communicator.StepConnect{ Config: &b.config.SSHConfig.Comm, Host: hypervcommon.CommHost, - SSHConfig: hypervcommon.SSHConfigFunc(b.config.SSHConfig), + SSHConfig: hypervcommon.SSHConfigFunc(&b.config.SSHConfig), }, // provision requires communicator to be setup diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 250722eff..b5e3fc137 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -77,7 +77,7 @@ Set-VMFloppyDiskDrive -VMName $vmName -Path $null func CreateVirtualMachine(vmName string, path string, ram string, diskSize string, switchName string, generation string) error { var script = ` -param([string]$vmName, [string]$path, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName, [int]generation) +param([string]$vmName, [string]$path, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName, [int]$generation) $vhdx = $vmName + '.vhdx' $vhdPath = Join-Path -Path $path -ChildPath $vhdx New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName -Generation $generation @@ -91,8 +91,8 @@ New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHD func SetVirtualMachineCpu(vmName string, cpu string) error { var script = ` -param([string]$vmName, [int]cpu) -Set-VMProcessor -VMName $vmName –Count $cpu +param([string]$vmName, [int]$cpu) +Set-VMProcessor -VMName $vmName -Count $cpu ` var ps powershell.PowerShellCmd @@ -359,15 +359,15 @@ $vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running func Mac(vmName string) (string, error) { var script = ` -param([string]$vmName, [int]$addressIndex) +param([string]$vmName, [int]$adapterIndex) try { $adapter = Get-VMNetworkAdapter -VMName $vmName -ErrorAction SilentlyContinue - $mac = $adapter.MacAddress[$addressIndex] + $mac = $adapter[$adapterIndex].MacAddress if($mac -eq $null) { - return $false + return "" } } catch { - return $false + return "" } $mac ` @@ -385,10 +385,10 @@ try { $ip = Get-Vm | %{$_.NetworkAdapters} | ?{$_.MacAddress -eq $mac} | %{$_.IpAddresses[$addressIndex]} if($ip -eq $null) { - return $false + return "" } } catch { - return $false + return "" } $ip ` From 03b0698eddc427a3c7ace8d3c721014e0dbb13b9 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Wed, 24 Jun 2015 19:52:42 +0100 Subject: [PATCH 008/108] Remove devices after it has been shut down Attempt to stop vm, before deleting it --- builder/hyperv/common/artifact.go | 2 +- .../common/step_unmount_integration_services.go | 5 +++-- .../common/step_wait_for_install_to_complete.go | 5 ----- builder/hyperv/iso/builder.go | 12 ++++++------ powershell/hyperv/hyperv.go | 9 +++++++-- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/builder/hyperv/common/artifact.go b/builder/hyperv/common/artifact.go index de7f94ca7..2baeb2d62 100644 --- a/builder/hyperv/common/artifact.go +++ b/builder/hyperv/common/artifact.go @@ -9,7 +9,7 @@ import ( ) // This is the common builder ID to all of these artifacts. -const BuilderId = "mitchellh.hyperv" +const BuilderId = "MSOpenTech.hyperv" // Artifact is the result of running the hyperv builder, namely a set // of files associated with the resulting machine. diff --git a/builder/hyperv/common/step_unmount_integration_services.go b/builder/hyperv/common/step_unmount_integration_services.go index 7c40389fe..a905f2a8b 100644 --- a/builder/hyperv/common/step_unmount_integration_services.go +++ b/builder/hyperv/common/step_unmount_integration_services.go @@ -6,10 +6,10 @@ package common import ( "fmt" - "log" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" powershell "github.com/mitchellh/packer/powershell" + "log" ) type StepUnmountSecondaryDvdImages struct { @@ -35,6 +35,8 @@ func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep. powershell := new(powershell.PowerShellCmd) script.WriteLine("param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation)") + script.WriteLine("$vmDvdDrive = Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation") + script.WriteLine("if (!$vmDvdDrive) {throw 'unable to find dvd drive'}") script.WriteLine("Remove-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation") err := powershell.Run(script.String(), vmName, controllerNumber, controllerLocation) if err != nil { @@ -44,7 +46,6 @@ func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep. } } - return multistep.ActionContinue } diff --git a/builder/hyperv/common/step_wait_for_install_to_complete.go b/builder/hyperv/common/step_wait_for_install_to_complete.go index efd8bd8b7..c4fafbef0 100644 --- a/builder/hyperv/common/step_wait_for_install_to_complete.go +++ b/builder/hyperv/common/step_wait_for_install_to_complete.go @@ -26,11 +26,6 @@ func (s *StepWaitForPowerOff) Run(state multistep.StateBag) multistep.StepAction vmName := state.Get("vmName").(string) ui.Say("Waiting for vm to be powered down...") - // unless the person has a super fast disk, it should take at least 5 minutes - // for the install and post-install operations to take. Wait 5 minutes to - // avoid hammering on getting VM status via PowerShell - time.Sleep(time.Second * 300); - var script powershell.ScriptBuilder script.WriteLine("param([string]$vmName)") script.WriteLine("(Get-VM -Name $vmName).State -eq [Microsoft.HyperV.PowerShell.VMState]::Off") diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index c7d4e6405..b330cfc50 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -305,12 +305,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // provision requires communicator to be setup &common.StepProvision{}, - // remove the integration services dvd drive - // after we power down - &hypervcommon.StepUnmountSecondaryDvdImages{}, - &hypervcommon.StepUnmountFloppyDrive{}, - &hypervcommon.StepUnmountDvdDrive{}, - &hypervcommon.StepShutdown{ Command: b.config.ShutdownCommand, Timeout: b.config.ShutdownTimeout, @@ -319,6 +313,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // wait for the vm to be powered off &hypervcommon.StepWaitForPowerOff{}, + // remove the integration services dvd drive + // after we power down + &hypervcommon.StepUnmountSecondaryDvdImages{}, + &hypervcommon.StepUnmountFloppyDrive{}, + &hypervcommon.StepUnmountDvdDrive{}, + &hypervcommon.StepExportVm{ OutputDir: b.config.OutputDir, }, diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index b5e3fc137..63aa8cff6 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -40,10 +40,9 @@ Set-VMDvdDrive -VMName $vmName -Path $path } func UnmountDvdDrive(vmName string) error { - var script = ` param([string]$vmName) -Set-VMDvdDrive -VMName $vmName -Path $null +Get-VMDvdDrive -VMName $vmName | Set-VMDvdDrive -Path $null ` var ps powershell.PowerShellCmd @@ -104,6 +103,12 @@ func DeleteVirtualMachine(vmName string) error { var script = ` param([string]$vmName) + +$vm = Get-VM -Name $vmName +if (($vm.State -ne [Microsoft.HyperV.PowerShell.VMState]::Off) -and ($vm.State -ne [Microsoft.HyperV.PowerShell.VMState]::OffCritical)) { + Stop-VM -VM $vm -TurnOff -Force +} + Remove-VM -Name $vmName -Force ` From aa1f1da1ff24e391a48f07ae7acca456d83c0f61 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sat, 27 Jun 2015 22:36:39 +0100 Subject: [PATCH 009/108] Support for boot_command Setup local http server Add compaction of hard drive GetHostAdapterIpAddress function added for hyperv renamed step step_start_vm to step_run to fall in ine with naming conventions of other builders --- builder/hyperv/common/driver.go | 10 +- builder/hyperv/common/driver_ps_4.go | 24 +- builder/hyperv/common/run_config.go | 36 ++- builder/hyperv/common/step_export_vm.go | 31 ++- builder/hyperv/common/step_http_server.go | 78 ++++++ builder/hyperv/common/step_run.go | 70 +++++ builder/hyperv/common/step_start_vm.go | 48 ---- .../hyperv/common/step_type_boot_command.go | 211 ++++++++++++++++ builder/hyperv/iso/builder.go | 35 ++- powershell/hyperv/hyperv.go | 239 ++++++++++++++++-- 10 files changed, 685 insertions(+), 97 deletions(-) create mode 100644 builder/hyperv/common/step_http_server.go create mode 100644 builder/hyperv/common/step_run.go delete mode 100644 builder/hyperv/common/step_start_vm.go create mode 100644 builder/hyperv/common/step_type_boot_command.go diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index 2c9f570b4..2d5044576 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -24,11 +24,17 @@ type Driver interface { // Verify checks to make sure that this driver should function // properly. If there is any indication the driver can't function, // this will return an error. - Verify() error - + Verify() error + // Finds the MAC address of the NIC nic0 Mac(string) (string, error) // Finds the IP address of a VM connected that uses DHCP by its MAC address IpAddress(string) (string, error) + + // Finds the IP address of a host adapter connected to switch + GetHostAdapterIpAddressForSwitch(string) (string, error) + + // Type scan codes to virtual keyboard of vm + TypeScanCodes(string, string) error } diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index 4b0146146..b9371a8c0 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -41,12 +41,12 @@ func (d *HypervPS4Driver) IsRunning(vmName string) (bool, error) { // Start starts a VM specified by the name given. func (d *HypervPS4Driver) Start(vmName string) error { - return hyperv.Start(vmName) + return hyperv.StartVirtualMachine(vmName) } // Stop stops a VM specified by the name given. func (d *HypervPS4Driver) Stop(vmName string) error { - return hyperv.TurnOff(vmName) + return hyperv.StopVirtualMachine(vmName) } func (d *HypervPS4Driver) Verify() error { @@ -97,6 +97,26 @@ func (d *HypervPS4Driver) IpAddress(mac string) (string, error) { return res, err } +// Finds the IP address of a host adapter connected to switch +func (d *HypervPS4Driver) GetHostAdapterIpAddressForSwitch(switchName string) (string, error) { + res, err := hyperv.GetHostAdapterIpAddressForSwitch(switchName) + + if err != nil { + return res, err + } + + if res == "" { + err := fmt.Errorf("%s", "No ip address.") + return res, err + } + return res, err +} + +// Type scan codes to virtual keyboard of vm +func (d *HypervPS4Driver) TypeScanCodes(vmName string, scanCodes string) error { + return hyperv.TypeScanCodes(vmName, scanCodes) +} + func (d *HypervPS4Driver) verifyPSVersion() error { log.Printf("Enter method: %s", "verifyPSVersion") diff --git a/builder/hyperv/common/run_config.go b/builder/hyperv/common/run_config.go index c755cdafb..4b73ae532 100644 --- a/builder/hyperv/common/run_config.go +++ b/builder/hyperv/common/run_config.go @@ -1,16 +1,20 @@ package common import ( + "errors" "fmt" - "time" - "github.com/mitchellh/packer/template/interpolate" + "time" ) type RunConfig struct { Headless bool `mapstructure:"headless"` RawBootWait string `mapstructure:"boot_wait"` + HTTPDir string `mapstructure:"http_directory"` + HTTPPortMin uint `mapstructure:"http_port_min"` + HTTPPortMax uint `mapstructure:"http_port_max"` + BootWait time.Duration `` } @@ -19,11 +23,29 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { c.RawBootWait = "10s" } - var err error - c.BootWait, err = time.ParseDuration(c.RawBootWait) - if err != nil { - return []error{fmt.Errorf("Failed parsing boot_wait: %s", err)} + if c.HTTPPortMin == 0 { + c.HTTPPortMin = 8000 } - return nil + if c.HTTPPortMax == 0 { + c.HTTPPortMax = 9000 + } + + var errs []error + var err error + + if c.RawBootWait != "" { + c.BootWait, err = time.ParseDuration(c.RawBootWait) + if err != nil { + errs = append( + errs, fmt.Errorf("Failed parsing boot_wait: %s", err)) + } + } + + if c.HTTPPortMin > c.HTTPPortMax { + errs = append(errs, + errors.New("http_port_min must be less than http_port_max")) + } + + return errs } diff --git a/builder/hyperv/common/step_export_vm.go b/builder/hyperv/common/step_export_vm.go index 95076fd14..d226c25d1 100644 --- a/builder/hyperv/common/step_export_vm.go +++ b/builder/hyperv/common/step_export_vm.go @@ -6,20 +6,21 @@ package common import ( "fmt" - "path/filepath" - "io/ioutil" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/powershell/hyperv" + "io/ioutil" + "path/filepath" ) -const( +const ( vhdDir string = "Virtual Hard Disks" - vmDir string = "Virtual Machines" + vmDir string = "Virtual Machines" ) type StepExportVm struct { - OutputDir string + OutputDir string + SkipCompaction bool } func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction { @@ -29,12 +30,12 @@ func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction { var errorMsg string vmName := state.Get("vmName").(string) - tmpPath := state.Get("packerTempDir").(string) + tmpPath := state.Get("packerTempDir").(string) outputPath := s.OutputDir // create temp path to export vm errorMsg = "Error creating temp export path: %s" - vmExportPath , err := ioutil.TempDir(tmpPath, "export") + vmExportPath, err := ioutil.TempDir(tmpPath, "export") if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) @@ -54,7 +55,21 @@ func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction { } // copy to output dir - expPath := filepath.Join(vmExportPath,vmName) + expPath := filepath.Join(vmExportPath, vmName) + + if s.SkipCompaction { + ui.Say("Skipping disk compaction...") + } else { + ui.Say("Compacting disks...") + err = hyperv.CompactDisks(expPath, vhdDir) + if err != nil { + errorMsg = "Error compacting disks: %s" + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } ui.Say("Coping to output dir...") err = hyperv.CopyExportedVirtualMachine(expPath, outputPath, vhdDir, vmDir) diff --git a/builder/hyperv/common/step_http_server.go b/builder/hyperv/common/step_http_server.go new file mode 100644 index 000000000..55874992e --- /dev/null +++ b/builder/hyperv/common/step_http_server.go @@ -0,0 +1,78 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" + "math/rand" + "net" + "net/http" +) + +// This step creates and runs the HTTP server that is serving files from the +// directory specified by the 'http_directory` configuration parameter in the +// template. +// +// Uses: +// ui packer.Ui +// +// Produces: +// http_port int - The port the HTTP server started on. +type StepHTTPServer struct { + HTTPDir string + HTTPPortMin uint + HTTPPortMax uint + + l net.Listener +} + +func (s *StepHTTPServer) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + var httpPort uint = 0 + if s.HTTPDir == "" { + state.Put("http_port", httpPort) + return multistep.ActionContinue + } + + // Find an available TCP port for our HTTP server + var httpAddr string + portRange := int(s.HTTPPortMax - s.HTTPPortMin) + for { + var err error + var offset uint = 0 + + if portRange > 0 { + // Intn will panic if portRange == 0, so we do a check. + offset = uint(rand.Intn(portRange)) + } + + httpPort = offset + s.HTTPPortMin + httpAddr = fmt.Sprintf("0.0.0.0:%d", httpPort) + log.Printf("Trying port: %d", httpPort) + s.l, err = net.Listen("tcp", httpAddr) + if err == nil { + break + } + } + + ui.Say(fmt.Sprintf("Starting HTTP server on port %d", httpPort)) + + // Start the HTTP server and run it in the background + fileServer := http.FileServer(http.Dir(s.HTTPDir)) + server := &http.Server{Addr: httpAddr, Handler: fileServer} + go server.Serve(s.l) + + // Save the address into the state so it can be accessed in the future + state.Put("http_port", httpPort) + + return multistep.ActionContinue +} + +func (s *StepHTTPServer) Cleanup(multistep.StateBag) { + if s.l != nil { + // Close the listener so that the HTTP server stops + s.l.Close() + } +} diff --git a/builder/hyperv/common/step_run.go b/builder/hyperv/common/step_run.go new file mode 100644 index 000000000..c02714692 --- /dev/null +++ b/builder/hyperv/common/step_run.go @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "time" +) + +type StepRun struct { + BootWait time.Duration + Headless bool + + vmName string +} + +func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + ui.Say("Starting the virtual machine...") + + err := driver.Start(vmName) + if err != nil { + err := fmt.Errorf("Error starting vm: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + s.vmName = vmName + + if int64(s.BootWait) > 0 { + ui.Say(fmt.Sprintf("Waiting %s for boot...", s.BootWait)) + wait := time.After(s.BootWait) + WAITLOOP: + for { + select { + case <-wait: + break WAITLOOP + case <-time.After(1 * time.Second): + if _, ok := state.GetOk(multistep.StateCancelled); ok { + return multistep.ActionHalt + } + } + } + } + + return multistep.ActionContinue +} + +func (s *StepRun) Cleanup(state multistep.StateBag) { + if s.vmName == "" { + return + } + + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + if running, _ := driver.IsRunning(s.vmName); running { + if err := driver.Stop(s.vmName); err != nil { + ui.Error(fmt.Sprintf("Error shutting down VM: %s", err)) + } + } +} diff --git a/builder/hyperv/common/step_start_vm.go b/builder/hyperv/common/step_start_vm.go deleted file mode 100644 index af1fcda5d..000000000 --- a/builder/hyperv/common/step_start_vm.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package common - -import ( - "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "time" - "github.com/mitchellh/packer/powershell/hyperv" -) - -type StepStartVm struct { - Reason string - StartUpDelay int -} - -func (s *StepStartVm) Run(state multistep.StateBag) multistep.StepAction { - ui := state.Get("ui").(packer.Ui) - - errorMsg := "Error starting vm: %s" - vmName := state.Get("vmName").(string) - - ui.Say("Starting vm for " + s.Reason + "...") - - err := hyperv.StartVirtualMachine(vmName) - if err != nil { - err := fmt.Errorf(errorMsg, err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - if s.StartUpDelay != 0 { - //sleepTime := s.StartUpDelay * time.Second - sleepTime := 60 * time.Second - - ui.Say(fmt.Sprintf(" Waiting %v for vm to start...", sleepTime)) - time.Sleep(sleepTime); - } - - return multistep.ActionContinue -} - -func (s *StepStartVm) Cleanup(state multistep.StateBag) { -} diff --git a/builder/hyperv/common/step_type_boot_command.go b/builder/hyperv/common/step_type_boot_command.go new file mode 100644 index 000000000..8c06982bc --- /dev/null +++ b/builder/hyperv/common/step_type_boot_command.go @@ -0,0 +1,211 @@ +package common + +import ( + "fmt" + "log" + "strings" + "unicode" + "unicode/utf8" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/template/interpolate" +) + +type bootCommandTemplateData struct { + HTTPIP string + HTTPPort uint + Name string +} + +// This step "types" the boot command into the VM via the prltype script, built on the +// Parallels Virtualization SDK - Python API. +// +// Uses: +// driver Driver +// http_port int +// ui packer.Ui +// vmName string +// +// Produces: +// +type StepTypeBootCommand struct { + BootCommand []string + SwitchName string + VMName string + Ctx interpolate.Context +} + +func (s *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction { + httpPort := state.Get("http_port").(uint) + ui := state.Get("ui").(packer.Ui) + + driver := state.Get("driver").(Driver) + + hostIp, err := driver.GetHostAdapterIpAddressForSwitch(s.SwitchName) + + if err != nil { + err := fmt.Errorf("Error getting host adapter ip address: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say(fmt.Sprintf("Host IP for the HyperV machine: %s", hostIp)) + + s.Ctx.Data = &bootCommandTemplateData{ + hostIp, + httpPort, + s.VMName, + } + + ui.Say("Typing the boot command...") + scanCodesToSend := []string{} + + for _, command := range s.BootCommand { + command, err := interpolate.Render(command, &s.Ctx) + + if err != nil { + err := fmt.Errorf("Error preparing boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + scanCodesToSend = append(scanCodesToSend, scancodes(command)...) + } + + scanCodesToSendString := strings.Join(scanCodesToSend, " ") + + if err := driver.TypeScanCodes(s.VMName, scanCodesToSendString); err != nil { + err := fmt.Errorf("Error sending boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (*StepTypeBootCommand) Cleanup(multistep.StateBag) {} + +func scancodes(message string) []string { + // Scancodes reference: http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html + // + // Scancodes represent raw keyboard output and are fed to the VM by the + // VBoxManage controlvm keyboardputscancode program. + // + // Scancodes are recorded here in pairs. The first entry represents + // the key press and the second entry represents the key release and is + // derived from the first by the addition of 0x80. + special := make(map[string][]string) + special[""] = []string{"0e", "8e"} + special[""] = []string{"53", "d3"} + special[""] = []string{"1c", "9c"} + special[""] = []string{"01", "81"} + special[""] = []string{"3b", "bb"} + special[""] = []string{"3c", "bc"} + special[""] = []string{"3d", "bd"} + special[""] = []string{"3e", "be"} + special[""] = []string{"3f", "bf"} + special[""] = []string{"40", "c0"} + special[""] = []string{"41", "c1"} + special[""] = []string{"42", "c2"} + special[""] = []string{"43", "c3"} + special[""] = []string{"44", "c4"} + special[""] = []string{"1c", "9c"} + special[""] = []string{"0f", "8f"} + special[""] = []string{"48", "c8"} + special[""] = []string{"50", "d0"} + special[""] = []string{"4b", "cb"} + special[""] = []string{"4d", "cd"} + special[""] = []string{"39", "b9"} + special[""] = []string{"52", "d2"} + special[""] = []string{"47", "c7"} + special[""] = []string{"4f", "cf"} + special[""] = []string{"49", "c9"} + special[""] = []string{"51", "d1"} + + shiftedChars := "~!@#$%^&*()_+{}|:\"<>?" + + scancodeIndex := make(map[string]uint) + scancodeIndex["1234567890-="] = 0x02 + scancodeIndex["!@#$%^&*()_+"] = 0x02 + scancodeIndex["qwertyuiop[]"] = 0x10 + scancodeIndex["QWERTYUIOP{}"] = 0x10 + scancodeIndex["asdfghjkl;'`"] = 0x1e + scancodeIndex[`ASDFGHJKL:"~`] = 0x1e + scancodeIndex[`\zxcvbnm,./`] = 0x2b + scancodeIndex["|ZXCVBNM<>?"] = 0x2b + scancodeIndex[" "] = 0x39 + + scancodeMap := make(map[rune]uint) + for chars, start := range scancodeIndex { + var i uint = 0 + for len(chars) > 0 { + r, size := utf8.DecodeRuneInString(chars) + chars = chars[size:] + scancodeMap[r] = start + i + i += 1 + } + } + + result := make([]string, 0, len(message)*2) + for len(message) > 0 { + var scancode []string + + if strings.HasPrefix(message, "") { + log.Printf("Special code found, will sleep 1 second at this point.") + scancode = []string{"wait"} + message = message[len(""):] + } + + if strings.HasPrefix(message, "") { + log.Printf("Special code found, will sleep 5 seconds at this point.") + scancode = []string{"wait5"} + message = message[len(""):] + } + + if strings.HasPrefix(message, "") { + log.Printf("Special code found, will sleep 10 seconds at this point.") + scancode = []string{"wait10"} + message = message[len(""):] + } + + if scancode == nil { + for specialCode, specialValue := range special { + if strings.HasPrefix(message, specialCode) { + log.Printf("Special code '%s' found, replacing with: %s", specialCode, specialValue) + scancode = specialValue + message = message[len(specialCode):] + break + } + } + } + + if scancode == nil { + r, size := utf8.DecodeRuneInString(message) + message = message[size:] + scancodeInt := scancodeMap[r] + keyShift := unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r) + + scancode = make([]string, 0, 4) + if keyShift { + scancode = append(scancode, "2a") + } + + scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt)) + + if keyShift { + scancode = append(scancode, "aa") + } + + scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt+0x80)) + log.Printf("Sending char '%c', code '%v', shift %v", r, scancode, keyShift) + } + + result = append(result, scancode...) + } + + return result +} diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index b330cfc50..89f98ef83 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -48,8 +48,8 @@ type Config struct { hypervcommon.FloppyConfig `mapstructure:",squash"` hypervcommon.OutputConfig `mapstructure:",squash"` hypervcommon.SSHConfig `mapstructure:",squash"` - hypervcommon.ShutdownConfig `mapstructure:",squash"` hypervcommon.RunConfig `mapstructure:",squash"` + hypervcommon.ShutdownConfig `mapstructure:",squash"` // The size, in megabytes, of the hard disk to create for the VM. // By default, this is 130048 (about 127 GB). @@ -95,9 +95,10 @@ type Config struct { // By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. VMName string `mapstructure:"vm_name"` - SwitchName string `mapstructure:"switch_name"` - Cpu uint `mapstructure:"cpu"` - Generation uint `mapstructure:"generation"` + BootCommand []string `mapstructure:"boot_command"` + SwitchName string `mapstructure:"switch_name"` + Cpu uint `mapstructure:"cpu"` + Generation uint `mapstructure:"generation"` Communicator string `mapstructure:"communicator"` @@ -107,6 +108,8 @@ type Config struct { SSHWaitTimeout time.Duration + SkipCompaction bool `mapstructure:"skip_compaction"` + ctx interpolate.Context } @@ -115,7 +118,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { err := config.Decode(&b.config, &config.DecodeOpts{ Interpolate: true, InterpolateFilter: &interpolate.RenderFilter{ - Exclude: []string{}, + Exclude: []string{ + "boot_command", + }, }, }, raws...) if err != nil { @@ -271,6 +276,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &common.StepCreateFloppy{ Files: b.config.FloppyFiles, }, + &hypervcommon.StepHTTPServer{ + HTTPDir: b.config.HTTPDir, + HTTPPortMin: b.config.HTTPPortMin, + HTTPPortMax: b.config.HTTPPortMax, + }, &hypervcommon.StepCreateSwitch{ SwitchName: b.config.SwitchName, }, @@ -291,8 +301,16 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &hypervcommon.StepMountSecondaryDvdImages{}, - &hypervcommon.StepStartVm{ - Reason: "OS installation", + &hypervcommon.StepRun{ + BootWait: b.config.BootWait, + Headless: b.config.Headless, + }, + + &hypervcommon.StepTypeBootCommand{ + BootCommand: b.config.BootCommand, + SwitchName: b.config.SwitchName, + VMName: b.config.VMName, + Ctx: b.config.ctx, }, // configure the communicator ssh, winrm @@ -320,7 +338,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &hypervcommon.StepUnmountDvdDrive{}, &hypervcommon.StepExportVm{ - OutputDir: b.config.OutputDir, + OutputDir: b.config.OutputDir, + SkipCompaction: b.config.SkipCompaction, }, // the clean up actions for each step will be executed reverse order diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 63aa8cff6..1a2c5956a 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -5,6 +5,29 @@ import ( "strings" ) +func GetHostAdapterIpAddressForSwitch(switchName string) (string, error) { + var script = ` +param([string]$switchName, [int]$addressIndex) + +$HostVMAdapter = Get-VMNetworkAdapter -ManagementOS -SwitchName $switchName +if ($HostVMAdapter){ + $HostNetAdapter = Get-NetAdapter | ?{ $_.DeviceID -eq $HostVMAdapter.DeviceId } + if ($HostNetAdapter){ + $HostNetAdapterConfiguration = @(get-wmiobject win32_networkadapterconfiguration -filter "IPEnabled = 'TRUE' AND InterfaceIndex=$($HostNetAdapter.ifIndex)") + if ($HostNetAdapterConfiguration){ + return $HostNetAdapterConfiguration.IpAddress[$addressIndex] + } + } +} +return $false +` + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script, switchName, "0") + + return cmdOut, err +} + func GetVirtualMachineNetworkAdapterAddress(vmName string) (string, error) { var script = ` @@ -106,7 +129,7 @@ param([string]$vmName) $vm = Get-VM -Name $vmName if (($vm.State -ne [Microsoft.HyperV.PowerShell.VMState]::Off) -and ($vm.State -ne [Microsoft.HyperV.PowerShell.VMState]::OffCritical)) { - Stop-VM -VM $vm -TurnOff -Force + Stop-VM -VM $vm -TurnOff -Force -Confirm:$false } Remove-VM -Name $vmName -Force @@ -129,6 +152,19 @@ Export-VM -Name $vmName -Path $path return err } +func CompactDisks(expPath string, vhdDir string) error { + var script = ` +param([string]$srcPath, [string]$vhdDirName) +Get-ChildItem "$srcPath/$vhdDirName" -Filter *.vhd* | %{ + Optimize-VHD -Path $_.FullName -Mode Full +} +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, expPath, vhdDir) + return err +} + func CopyExportedVirtualMachine(expPath string, outputPath string, vhdDir string, vmDir string) error { var script = ` @@ -180,7 +216,10 @@ func StartVirtualMachine(vmName string) error { var script = ` param([string]$vmName) -Start-VM -Name $vmName +$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue +if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Off) { + Start-VM -Name $vmName +} ` var ps powershell.PowerShellCmd @@ -206,7 +245,7 @@ func StopVirtualMachine(vmName string) error { param([string]$vmName) $vm = Get-VM -Name $vmName if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { - Stop-VM -VM $vm + Stop-VM -VM $vm -Confirm:$false } ` @@ -297,7 +336,7 @@ foreach ($adapter in $adapters) { } if($switch -ne $null) { - Get-VMNetworkAdapter –VMName $vmName | Connect-VMNetworkAdapter -VMSwitch $switch + Get-VMNetworkAdapter -VMName $vmName | Connect-VMNetworkAdapter -VMSwitch $switch } else { Write-Error 'No internet adapters found' } @@ -327,7 +366,7 @@ func ConnectVirtualMachineNetworkAdapterToSwitch(vmName string, switchName strin var script = ` param([string]$vmName,[string]$switchName) -Get-VMNetworkAdapter –VMName $vmName | Connect-VMNetworkAdapter –SwitchName $switchName +Get-VMNetworkAdapter -VMName $vmName | Connect-VMNetworkAdapter -SwitchName $switchName ` var ps powershell.PowerShellCmd @@ -404,28 +443,13 @@ $ip return cmdOut, err } -func Start(vmName string) error { - - var script = ` -param([string]$vmName) -$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue -if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Off) { - Start-VM –Name $vmName -} -` - - var ps powershell.PowerShellCmd - err := ps.Run(script, vmName) - return err -} - func TurnOff(vmName string) error { var script = ` param([string]$vmName) $vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { - Stop-VM -Name $vmName -TurnOff + Stop-VM -Name $vmName -TurnOff -Confirm:$false } ` @@ -440,7 +464,7 @@ func ShutDown(vmName string) error { param([string]$vmName) $vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { - Stop-VM –Name $vmName + Stop-VM -Name $vmName -Confirm:$false } ` @@ -448,3 +472,174 @@ if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { err := ps.Run(script, vmName) return err } + +func TypeScanCodes(vmName string, scanCodes string) error { + var script = ` +param([string]$vmName, [string]$scanCodes) + #Requires -Version 3 + #Requires -RunAsAdministrator + + function Get-VMConsole + { + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [string] $VMName + ) + + $ErrorActionPreference = "Stop" + + $vm = Get-CimInstance -ComputerName localhost -Namespace "root\virtualization\v2" -ClassName Msvm_ComputerSystem -ErrorAction Ignore -Verbose:$false | where ElementName -eq $VMName | select -first 1 + if ($vm -eq $null){ + Write-Error ("VirtualMachine({0}) is not found!" -f $VMName) + } + + $vmKeyboard = $vm | Get-CimAssociatedInstance -ResultClassName "Msvm_Keyboard" -ErrorAction Ignore -Verbose:$false + if ($vmKeyboard -eq $null){ + Write-Error ("VirtualMachine({0}) keyboard class is not found!" -f $VMName) + } + + #TODO: It may be better using New-Module -AsCustomObject to return console object? + + #Console object to return + $console = [pscustomobject] @{ + Msvm_ComputerSystem = $vm + Msvm_Keyboard = $vmKeyboard + } + + #Need to import assembly to use System.Windows.Input.Key + Add-Type -AssemblyName WindowsBase + + #region Add Console Members + $console | Add-Member -MemberType ScriptMethod -Name TypeText -Value { + [OutputType([bool])] + param ( + [ValidateNotNullOrEmpty()] + [Parameter(Mandatory)] + [string] $AsciiText + ) + $result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeText" -Arguments @{ asciiText = $AsciiText } + return (0 -eq $result.ReturnValue) + } + + #Define method:TypeCtrlAltDel + $console | Add-Member -MemberType ScriptMethod -Name TypeCtrlAltDel -Value { + $result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeCtrlAltDel" + return (0 -eq $result.ReturnValue) + } + + #Define method:TypeKey + $console | Add-Member -MemberType ScriptMethod -Name TypeKey -Value { + [OutputType([bool])] + param ( + [Parameter(Mandatory)] + [Windows.Input.Key] $Key, + [Windows.Input.ModifierKeys] $ModifierKey = [Windows.Input.ModifierKeys]::None + ) + + $keyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey($Key) + + switch ($ModifierKey) + { + ([Windows.Input.ModifierKeys]::Control){ $modifierKeyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey([Windows.Input.Key]::LeftCtrl)} + ([Windows.Input.ModifierKeys]::Alt){ $modifierKeyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey([Windows.Input.Key]::LeftAlt)} + ([Windows.Input.ModifierKeys]::Shift){ $modifierKeyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey([Windows.Input.Key]::LeftShift)} + ([Windows.Input.ModifierKeys]::Windows){ $modifierKeyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey([Windows.Input.Key]::LWin)} + } + + if ($ModifierKey -eq [Windows.Input.ModifierKeys]::None) + { + $result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeKey" -Arguments @{ keyCode = $keyCode } + } + else + { + $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "PressKey" -Arguments @{ keyCode = $modifierKeyCode } + $result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeKey" -Arguments @{ keyCode = $keyCode } + $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "ReleaseKey" -Arguments @{ keyCode = $modifierKeyCode } + } + $result = return (0 -eq $result.ReturnValue) + } + + #Define method:Scancodes + $console | Add-Member -MemberType ScriptMethod -Name TypeScancodes -Value { + [OutputType([bool])] + param ( + [Parameter(Mandatory)] + [byte[]] $ScanCodes + ) + $result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeScancodes" -Arguments @{ ScanCodes = $ScanCodes } + return (0 -eq $result.ReturnValue) + } + + #Define method:ExecCommand + $console | Add-Member -MemberType ScriptMethod -Name ExecCommand -Value { + param ( + [Parameter(Mandatory)] + [string] $Command + ) + if ([String]::IsNullOrEmpty($Command)){ + return + } + + $console.TypeText($Command) > $null + $console.TypeKey([Windows.Input.Key]::Enter) > $null + #sleep -Milliseconds 100 + } + + #Define method:Dispose + $console | Add-Member -MemberType ScriptMethod -Name Dispose -Value { + $this.Msvm_ComputerSystem.Dispose() + $this.Msvm_Keyboard.Dispose() + } + + + #endregion + + return $console + } + + $vmConsole = Get-VMConsole -VMName $vmName + $scanCodesToSend = '' + $scanCodes.Split(' ') | %{ + $scanCode = $_ + + if ($scanCode.StartsWith('wait')){ + $timeToWait = $scanCode.Substring(4) + if (!$timeToWait){ + $timeToWait = "10" + } + + Start-Sleep -s $timeToWait + + if ($scanCodesToSend){ + $scanCodesToSendByteArray = [byte[]]@($scanCodesToSend.Split(' ') | %{"0x$_"}) + + $scanCodesToSendByteArray | %{ + $vmConsole.TypeScancodes($_) + } + } + + $scanCodesToSend = '' + } else { + if ($scanCodesToSend){ + $scanCodesToSend = "$scanCodesToSend $scanCode" + } else { + $scanCodesToSend = "$scanCode" + } + } + } + if ($scanCodesToSend){ + $scanCodesToSendByteArray = [byte[]]@($scanCodesToSend.Split(' ') | %{"0x$_"}) + + $scanCodesToSendByteArray | %{ + $vmConsole.TypeScancodes($_) + } + } + + +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, scanCodes) + return err +} From 02db76801861a74d1367828da5dc9a1f56fe08b3 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Mon, 29 Jun 2015 21:18:25 +0100 Subject: [PATCH 010/108] With generation 2 machine by default a dvd drive is not created. So create a dvd drive for os if it does not exist. Allow secure boot mode to be configured from config. --- builder/hyperv/common/step_create_vm.go | 14 ++++++- builder/hyperv/common/step_mount_dvddrive.go | 33 ++++++++++++++-- builder/hyperv/iso/builder.go | 22 ++++++----- powershell/hyperv/hyperv.go | 40 ++++++++++++++++---- 4 files changed, 88 insertions(+), 21 deletions(-) diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index 1ea4530d8..6418dbea0 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -23,6 +23,7 @@ type StepCreateVM struct { DiskSize uint Generation uint Cpu uint + EnabeSecureBoot bool } func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { @@ -40,6 +41,7 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { switchName := s.SwitchName generation := strconv.FormatInt(int64(s.Generation), 10) cpu := strconv.FormatInt(int64(s.Cpu), 10) + enabeSecureBoot := s.EnabeSecureBoot err := hyperv.CreateVirtualMachine(s.VMName, path, ram, diskSize, switchName, generation) if err != nil { @@ -48,7 +50,7 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { ui.Error(err.Error()) return multistep.ActionHalt } - + err = hyperv.SetVirtualMachineCpu(s.VMName, cpu) if err != nil { err := fmt.Errorf("Error creating setting virtual machine cpu: %s", err) @@ -56,6 +58,16 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { ui.Error(err.Error()) return multistep.ActionHalt } + + if generation == "2" { + err = hyperv.SetSecureBoot(s.VMName, enabeSecureBoot) + if err != nil { + err := fmt.Errorf("Error setting secure boot: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } // Set the final name in the state bag so others can use it state.Put("vmName", s.VMName) diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go index d5e9b4ef3..8077dd7d1 100644 --- a/builder/hyperv/common/step_mount_dvddrive.go +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -8,13 +8,13 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" + powershell "github.com/mitchellh/packer/powershell" "github.com/mitchellh/packer/powershell/hyperv" ) - type StepMountDvdDrive struct { RawSingleISOUrl string - path string + path string } func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { @@ -25,9 +25,36 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { vmName := state.Get("vmName").(string) isoPath := s.RawSingleISOUrl + // Check that there is a virtual dvd drive + var script powershell.ScriptBuilder + powershell := new(powershell.PowerShellCmd) + + script.Reset() + script.WriteLine("param([string]$vmName)") + script.WriteLine("(Get-VMDvdDrive -VMName $vmName).ControllerNumber") + controllerNumber, err := powershell.Output(script.String(), vmName) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + if controllerNumber == "" { + // Add a virtual dvd drive as there is none + script.Reset() + script.WriteLine("param([string]$vmName)") + script.WriteLine("Add-VMDvdDrive -VMName $vmName") + err = powershell.Run(script.String(), vmName) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + ui.Say("Mounting dvd drive...") - err := hyperv.MountDvdDrive(vmName, isoPath) + err = hyperv.MountDvdDrive(vmName, isoPath) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 89f98ef83..010b06cc7 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -95,10 +95,11 @@ type Config struct { // By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. VMName string `mapstructure:"vm_name"` - BootCommand []string `mapstructure:"boot_command"` - SwitchName string `mapstructure:"switch_name"` - Cpu uint `mapstructure:"cpu"` - Generation uint `mapstructure:"generation"` + BootCommand []string `mapstructure:"boot_command"` + SwitchName string `mapstructure:"switch_name"` + Cpu uint `mapstructure:"cpu"` + Generation uint `mapstructure:"generation"` + EnableSecureBoot bool `mapstructure:"enable_secure_boot"` Communicator string `mapstructure:"communicator"` @@ -285,12 +286,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SwitchName: b.config.SwitchName, }, &hypervcommon.StepCreateVM{ - VMName: b.config.VMName, - SwitchName: b.config.SwitchName, - RamSizeMB: b.config.RamSizeMB, - DiskSize: b.config.DiskSize, - Generation: b.config.Generation, - Cpu: b.config.Cpu, + VMName: b.config.VMName, + SwitchName: b.config.SwitchName, + RamSizeMB: b.config.RamSizeMB, + DiskSize: b.config.DiskSize, + Generation: b.config.Generation, + Cpu: b.config.Cpu, + EnabeSecureBoot: b.config.EnableSecureBoot, }, &hypervcommon.StepEnableIntegrationService{}, diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 1a2c5956a..f1581b4d9 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -98,16 +98,27 @@ Set-VMFloppyDiskDrive -VMName $vmName -Path $null func CreateVirtualMachine(vmName string, path string, ram string, diskSize string, switchName string, generation string) error { - var script = ` + if generation == "2" { + var script = ` param([string]$vmName, [string]$path, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName, [int]$generation) $vhdx = $vmName + '.vhdx' $vhdPath = Join-Path -Path $path -ChildPath $vhdx New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName -Generation $generation ` - - var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, path, ram, diskSize, switchName, generation) - return err + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, path, ram, diskSize, switchName, generation) + return err + } else { + var script = ` +param([string]$vmName, [string]$path, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName) +$vhdx = $vmName + '.vhdx' +$vhdPath = Join-Path -Path $path -ChildPath $vhdx +New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName +` + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, path, ram, diskSize, switchName) + return err + } } func SetVirtualMachineCpu(vmName string, cpu string) error { @@ -122,6 +133,23 @@ Set-VMProcessor -VMName $vmName -Count $cpu return err } +func SetSecureBoot(vmName string, enable bool) error { + var script = ` +param([string]$vmName, $enableSecureBoot) +Set-VMFirmware -VMName $vmName -EnableSecureBoot $enableSecureBoot +` + + var ps powershell.PowerShellCmd + + enableSecureBoot := "Off" + if enable { + enableSecureBoot = "On" + } + + err := ps.Run(script, vmName, enableSecureBoot) + return err +} + func DeleteVirtualMachine(vmName string) error { var script = ` @@ -635,8 +663,6 @@ param([string]$vmName, [string]$scanCodes) $vmConsole.TypeScancodes($_) } } - - ` var ps powershell.PowerShellCmd From f3cc413bbb574804ce50553e769f115dc2baf671 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Mon, 29 Jun 2015 22:06:28 +0100 Subject: [PATCH 011/108] Set the dvd to the first boot device --- builder/hyperv/common/step_mount_dvddrive.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go index 8077dd7d1..56856566f 100644 --- a/builder/hyperv/common/step_mount_dvddrive.go +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -44,6 +44,8 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { script.Reset() script.WriteLine("param([string]$vmName)") script.WriteLine("Add-VMDvdDrive -VMName $vmName") + script.WriteLine("$dvdDrive = Get-VMDvdDrive -VMName $vmName | Select-Object -first 1") + script.WriteLine("Set-VMFirmware -VMName $vmName -FirstBootDevice $dvdDrive") err = powershell.Run(script.String(), vmName) if err != nil { state.Put("error", err) From bd0b1190f615153bf47fc15da4da2855551e0391 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Tue, 30 Jun 2015 16:39:03 +0100 Subject: [PATCH 012/108] When redirecting local ports to hyper visor ports we need to configure WinRM ports as well as SSH ports. --- helper/communicator/step_connect.go | 3 ++- helper/communicator/step_connect_winrm.go | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/helper/communicator/step_connect.go b/helper/communicator/step_connect.go index f4d1499b7..b42753452 100644 --- a/helper/communicator/step_connect.go +++ b/helper/communicator/step_connect.go @@ -33,6 +33,7 @@ type StepConnect struct { // WinRMConfig should return the default configuration for // connecting via WinRM. WinRMConfig func(multistep.StateBag) (*WinRMConfig, error) + WinRMPort func(multistep.StateBag) (int, error) // CustomConnect can be set to have custom connectors for specific // types. These take highest precedence so you can also override @@ -55,7 +56,7 @@ func (s *StepConnect) Run(state multistep.StateBag) multistep.StepAction { Config: s.Config, Host: s.Host, WinRMConfig: s.WinRMConfig, - WinRMPort: s.SSHPort, + WinRMPort: s.WinRMPort, }, } for k, v := range s.CustomConnect { diff --git a/helper/communicator/step_connect_winrm.go b/helper/communicator/step_connect_winrm.go index 49936ad7b..15f07a872 100644 --- a/helper/communicator/step_connect_winrm.go +++ b/helper/communicator/step_connect_winrm.go @@ -96,6 +96,7 @@ func (s *StepConnectWinRM) waitForWinRM(state multistep.StateBag, cancel <-chan log.Printf("[DEBUG] Error getting WinRM host: %s", err) continue } + port := s.Config.WinRMPort if s.WinRMPort != nil { port, err = s.WinRMPort(state) From 6c3030c73ab1cd4c2ebcbb74f514927bcd049cb7 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Wed, 1 Jul 2015 13:46:29 +0100 Subject: [PATCH 013/108] Add a little safety around variables that are passed into provisioner. --- provisioner/powershell/provisioner.go | 5 +++-- provisioner/windows-shell/provisioner.go | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index a862ef9b3..477aaf03a 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -347,8 +347,9 @@ func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string, e // Split vars into key/value components for _, envVar := range p.config.Vars { keyValue := strings.Split(envVar, "=") - if len(keyValue) != 2 { - err = errors.New("Shell provisioner environment variables must be in key=value format") + + if len(keyValue) != 2 || keyValue[0] == "" { + err = errors.New(fmt.Sprintf("Shell provisioner environment variables must be in key=value format. Currently it is '%s'", envVar)) return } envVars[keyValue[0]] = keyValue[1] diff --git a/provisioner/windows-shell/provisioner.go b/provisioner/windows-shell/provisioner.go index 6e65c8c20..17abcd2d5 100644 --- a/provisioner/windows-shell/provisioner.go +++ b/provisioner/windows-shell/provisioner.go @@ -305,10 +305,10 @@ func (p *Provisioner) createFlattenedEnvVars() (flattened string, err error) { // Split vars into key/value components for _, envVar := range p.config.Vars { keyValue := strings.Split(envVar, "=") - if len(keyValue) != 2 { - err = errors.New("Shell provisioner environment variables must be in key=value format") + if len(keyValue) != 2 || keyValue[0] == "" { + err = errors.New(fmt.Sprintf("Shell provisioner environment variables must be in key=value format. Currently it is '%s'", envVar)) return - } + } envVars[keyValue[0]] = keyValue[1] } // Create a list of env var keys in sorted order From 3051ea663341242c9e655c94781dcf78ec508ae6 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Wed, 1 Jul 2015 17:02:52 +0100 Subject: [PATCH 014/108] Quote powershell so that it does not try to interpret command to be run Get VName out of state. This allows template replacement to be run on vmname --- builder/hyperv/common/step_type_boot_command.go | 7 +++---- builder/hyperv/iso/builder.go | 5 ++--- provisioner/powershell/provisioner.go | 4 ++-- provisioner/powershell/provisioner_test.go | 4 ++-- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/builder/hyperv/common/step_type_boot_command.go b/builder/hyperv/common/step_type_boot_command.go index 8c06982bc..c801858b7 100644 --- a/builder/hyperv/common/step_type_boot_command.go +++ b/builder/hyperv/common/step_type_boot_command.go @@ -32,15 +32,14 @@ type bootCommandTemplateData struct { type StepTypeBootCommand struct { BootCommand []string SwitchName string - VMName string Ctx interpolate.Context } func (s *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction { httpPort := state.Get("http_port").(uint) ui := state.Get("ui").(packer.Ui) - driver := state.Get("driver").(Driver) + vmName := state.Get("vmName").(string) hostIp, err := driver.GetHostAdapterIpAddressForSwitch(s.SwitchName) @@ -56,7 +55,7 @@ func (s *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction s.Ctx.Data = &bootCommandTemplateData{ hostIp, httpPort, - s.VMName, + vmName, } ui.Say("Typing the boot command...") @@ -77,7 +76,7 @@ func (s *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction scanCodesToSendString := strings.Join(scanCodesToSend, " ") - if err := driver.TypeScanCodes(s.VMName, scanCodesToSendString); err != nil { + if err := driver.TypeScanCodes(vmName, scanCodesToSendString); err != nil { err := fmt.Errorf("Error sending boot command: %s", err) state.Put("error", err) ui.Error(err.Error()) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 010b06cc7..e918d3b5e 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -148,8 +148,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if b.config.VMName == "" { - b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) - } + b.config.VMName = fmt.Sprintf("packer-%s-{{timestamp}}", b.config.PackerBuildName) + } log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName)) @@ -311,7 +311,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &hypervcommon.StepTypeBootCommand{ BootCommand: b.config.BootCommand, SwitchName: b.config.SwitchName, - VMName: b.config.VMName, Ctx: b.config.ctx, }, diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index 477aaf03a..d59a6bf46 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -116,11 +116,11 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if p.config.ElevatedEnvVarFormat == "" { - p.config.ElevatedEnvVarFormat = `$env:%s="%s"; ` + p.config.ElevatedEnvVarFormat = `$env:%s=\"%s\"; ` } if p.config.ExecuteCommand == "" { - p.config.ExecuteCommand = `powershell "& { {{.Vars}}{{.Path}}; exit $LastExitCode}"` + p.config.ExecuteCommand = `powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}'` } if p.config.ElevatedExecuteCommand == "" { diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index 78484c184..7acf97d8a 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -75,8 +75,8 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { t.Error("expected elevated_password to be empty") } - if p.config.ExecuteCommand != "powershell \"& { {{.Vars}}{{.Path}}; exit $LastExitCode}\"" { - t.Fatalf("Default command should be powershell \"& { {{.Vars}}{{.Path}}; exit $LastExitCode}\", but got %s", p.config.ExecuteCommand) + if p.config.ExecuteCommand != "powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}'" { + t.Fatalf("Default command should be powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ExecuteCommand) } if p.config.ElevatedExecuteCommand != "{{.Vars}}{{.Path}}" { From ef507c7bd7944636287e09e346039fa742ead841 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 12 Jul 2015 17:19:29 +0100 Subject: [PATCH 015/108] Pass in any iso images to add as dvd drives during boot --- builder/hyperv/iso/builder.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index e918d3b5e..e1b2282f8 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -149,7 +149,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { if b.config.VMName == "" { b.config.VMName = fmt.Sprintf("packer-%s-{{timestamp}}", b.config.PackerBuildName) - } + } log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName)) @@ -171,6 +171,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.Generation = 1 } + if b.config.Generation == 2 { + if len(b.config.SecondaryDvdImages) > 0 { + err = errors.New("Generation 2 vms don't support floppy drives. Use ISO image instead.") + errs = packer.MultiErrorAppend(errs, err) + } + } + log.Println(fmt.Sprintf("Using switch %s", b.config.SwitchName)) log.Println(fmt.Sprintf("%s: %v", "SwitchName", b.config.SwitchName)) @@ -301,7 +308,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe }, &hypervcommon.StepMountFloppydrive{}, - &hypervcommon.StepMountSecondaryDvdImages{}, + &hypervcommon.StepMountSecondaryDvdImages{ + Files: b.config.SecondaryDvdImages, + }, &hypervcommon.StepRun{ BootWait: b.config.BootWait, From e18594f40495ef9d9bef973dabf7dbbdb8b01dad Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 12 Jul 2015 18:44:10 +0100 Subject: [PATCH 016/108] Hyperv generation 2 machines use scsi for dvd drives. Allow gen 1 machines to have at least 1 iso image attached --- .../common/step_mount_integration_services.go | 52 ++++++++++++------- builder/hyperv/iso/builder.go | 1 + 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/builder/hyperv/common/step_mount_integration_services.go b/builder/hyperv/common/step_mount_integration_services.go index 05eaf6dc8..66684ea63 100644 --- a/builder/hyperv/common/step_mount_integration_services.go +++ b/builder/hyperv/common/step_mount_integration_services.go @@ -6,20 +6,21 @@ package common import ( "fmt" - "log" - "os" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" powershell "github.com/mitchellh/packer/powershell" + "log" + "os" ) type StepMountSecondaryDvdImages struct { - Files [] string + Files []string dvdProperties []DvdControllerProperties + generation uint } type DvdControllerProperties struct { - ControllerNumber string + ControllerNumber string ControllerLocation string } @@ -34,13 +35,13 @@ func (s *StepMountSecondaryDvdImages) Run(state multistep.StateBag) multistep.St // Will Windows assign DVD drives to A: and B: ? // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) - dvdProperties, err := s.mountFiles(vmName); + dvdProperties, err := s.mountFiles(vmName) if err != nil { state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } - + log.Println(fmt.Sprintf("Saving DVD properties %s DVDs", len(dvdProperties))) state.Put("secondary.dvd.properties", dvdProperties) @@ -52,7 +53,6 @@ func (s *StepMountSecondaryDvdImages) Cleanup(state multistep.StateBag) { } - func (s *StepMountSecondaryDvdImages) mountFiles(vmName string) ([]DvdControllerProperties, error) { var dvdProperties []DvdControllerProperties @@ -76,7 +76,6 @@ func (s *StepMountSecondaryDvdImages) mountFiles(vmName string) ([]DvdController return dvdProperties, nil } - func (s *StepMountSecondaryDvdImages) addAndMountIntegrationServicesSetupDisk(vmName string) (DvdControllerProperties, error) { isoPath := os.Getenv("WINDIR") + "\\system32\\vmguest.iso" @@ -88,28 +87,41 @@ func (s *StepMountSecondaryDvdImages) addAndMountIntegrationServicesSetupDisk(vm return properties, nil } - - - func (s *StepMountSecondaryDvdImages) addAndMountDvdDisk(vmName string, isoPath string) (DvdControllerProperties, error) { var properties DvdControllerProperties var script powershell.ScriptBuilder powershell := new(powershell.PowerShellCmd) - // get the controller number that the OS install disk is mounted on - script.Reset() - script.WriteLine("param([string]$vmName)") - script.WriteLine("(Get-VMDvdDrive -VMName $vmName).ControllerNumber") - controllerNumber, err := powershell.Output(script.String(), vmName) - if err != nil { - return properties, err + controllerNumber := "0" + if s.generation < 2 { + // get the controller number that the OS install disk is mounted on + // generation 1 requires dvd to be added to ide controller, generation 2 uses scsi for dvd drives + script.Reset() + script.WriteLine("param([string]$vmName)") + script.WriteLine("$dvdDrives = (Get-VMDvdDrive -VMName $vmName)") + script.WriteLine("$lastControllerNumber = $dvdDrives | Sort-Object ControllerNumber | Select-Object -Last 1 | %{$_.ControllerNumber}") + script.WriteLine("if (!$lastControllerNumber) {") + script.WriteLine(" $lastControllerNumber = 0") + script.WriteLine("} elseif (!$lastControllerNumber -or ($dvdDrives | ?{ $_.ControllerNumber -eq $lastControllerNumber} | measure).count -gt 1) {") + script.WriteLine(" $lastControllerNumber += 1") + script.WriteLine("}") + script.WriteLine("$lastControllerNumber") + controllerNumber, err := powershell.Output(script.String(), vmName) + if err != nil { + return properties, err + } + + if controllerNumber != "0" || controllerNumber != "1" { + //There are only 2 ide controllers, try to use the one the hdd is attached too + controllerNumber = "0" + } } script.Reset() script.WriteLine("param([string]$vmName,[int]$controllerNumber)") script.WriteLine("Add-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber") - err = powershell.Run(script.String(), vmName, controllerNumber) + err := powershell.Run(script.String(), vmName, controllerNumber) if err != nil { return properties, err } @@ -133,7 +145,7 @@ func (s *StepMountSecondaryDvdImages) addAndMountDvdDisk(vmName string, isoPath return properties, err } - log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v",isoPath, controllerNumber, controllerLocation)) + log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", isoPath, controllerNumber, controllerLocation)) properties.ControllerNumber = controllerNumber properties.ControllerLocation = controllerLocation diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index e1b2282f8..4955b733b 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -310,6 +310,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &hypervcommon.StepMountSecondaryDvdImages{ Files: b.config.SecondaryDvdImages, + generation: b.config.Generation, }, &hypervcommon.StepRun{ From 4e8074d7e51344f2fd51c6cd13e8496ba3a5233f Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 12 Jul 2015 18:57:42 +0100 Subject: [PATCH 017/108] Need to expose generation as a public property --- builder/hyperv/common/step_mount_integration_services.go | 4 ++-- builder/hyperv/iso/builder.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/builder/hyperv/common/step_mount_integration_services.go b/builder/hyperv/common/step_mount_integration_services.go index 66684ea63..5460d52b8 100644 --- a/builder/hyperv/common/step_mount_integration_services.go +++ b/builder/hyperv/common/step_mount_integration_services.go @@ -15,8 +15,8 @@ import ( type StepMountSecondaryDvdImages struct { Files []string + Generation uint dvdProperties []DvdControllerProperties - generation uint } type DvdControllerProperties struct { @@ -94,7 +94,7 @@ func (s *StepMountSecondaryDvdImages) addAndMountDvdDisk(vmName string, isoPath powershell := new(powershell.PowerShellCmd) controllerNumber := "0" - if s.generation < 2 { + if s.Generation < 2 { // get the controller number that the OS install disk is mounted on // generation 1 requires dvd to be added to ide controller, generation 2 uses scsi for dvd drives script.Reset() diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 4955b733b..e44182fd9 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -309,8 +309,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &hypervcommon.StepMountFloppydrive{}, &hypervcommon.StepMountSecondaryDvdImages{ - Files: b.config.SecondaryDvdImages, - generation: b.config.Generation, + Files: b.config.SecondaryDvdImages, + Generation: b.config.Generation, }, &hypervcommon.StepRun{ From 837f9addd000717259d0f9603ac79a8dbd385d36 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 12 Jul 2015 19:27:16 +0100 Subject: [PATCH 018/108] Check the floppy files and not the dvd files attached --- builder/hyperv/iso/builder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index e44182fd9..edf39385c 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -172,7 +172,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if b.config.Generation == 2 { - if len(b.config.SecondaryDvdImages) > 0 { + if len(b.config.FloppyFiles) > 0 { err = errors.New("Generation 2 vms don't support floppy drives. Use ISO image instead.") errs = packer.MultiErrorAppend(errs, err) } From 5f2c12324c7df5b6aeef9cdad70d82852fd0355d Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Tue, 14 Jul 2015 07:51:03 +0100 Subject: [PATCH 019/108] No need to remove floppy controller for 2nd generation vms Don't want to be prompted for confirmation from powershell commandlets --- builder/hyperv/common/step_unmount_floppydrive.go | 6 +++++- builder/hyperv/iso/builder.go | 4 +++- powershell/hyperv/hyperv.go | 8 ++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/builder/hyperv/common/step_unmount_floppydrive.go b/builder/hyperv/common/step_unmount_floppydrive.go index ae7813400..87d1fd4a3 100644 --- a/builder/hyperv/common/step_unmount_floppydrive.go +++ b/builder/hyperv/common/step_unmount_floppydrive.go @@ -11,14 +11,18 @@ import ( "github.com/mitchellh/packer/powershell/hyperv" ) - type StepUnmountFloppyDrive struct { + Generation uint } func (s *StepUnmountFloppyDrive) Run(state multistep.StateBag) multistep.StepAction { //driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) + if s.Generation > 1 { + return multistep.ActionContinue + } + errorMsg := "Error Unmounting floppy drive: %s" vmName := state.Get("vmName").(string) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index edf39385c..e3556eb65 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -345,7 +345,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // remove the integration services dvd drive // after we power down &hypervcommon.StepUnmountSecondaryDvdImages{}, - &hypervcommon.StepUnmountFloppyDrive{}, + &hypervcommon.StepUnmountFloppyDrive{ + Generation: b.config.Generation, + }, &hypervcommon.StepUnmountDvdDrive{}, &hypervcommon.StepExportVm{ diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index f1581b4d9..efd83c684 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -160,7 +160,7 @@ if (($vm.State -ne [Microsoft.HyperV.PowerShell.VMState]::Off) -and ($vm.State - Stop-VM -VM $vm -TurnOff -Force -Confirm:$false } -Remove-VM -Name $vmName -Force +Remove-VM -Name $vmName -Force -Confirm:$false ` var ps powershell.PowerShellCmd @@ -231,7 +231,7 @@ func DeleteVirtualSwitch(switchName string) error { param([string]$switchName) $switch = Get-VMSwitch -Name $switchName -ErrorAction SilentlyContinue if ($switch -ne $null) { - $switch | Remove-VMSwitch -Force + $switch | Remove-VMSwitch -Force -Confirm:$false } ` @@ -246,7 +246,7 @@ func StartVirtualMachine(vmName string) error { param([string]$vmName) $vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Off) { - Start-VM -Name $vmName + Start-VM -Name $vmName -Confirm:$false } ` @@ -259,7 +259,7 @@ func RestartVirtualMachine(vmName string) error { var script = ` param([string]$vmName) -Restart-VM $vmName -Force +Restart-VM $vmName -Force -Confirm:$false ` var ps powershell.PowerShellCmd From efafed4f378b8398fd5a6c79df3ab62508d0778e Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Thu, 16 Jul 2015 20:48:08 +0100 Subject: [PATCH 020/108] Copy the folder structure correctly --- powershell/hyperv/hyperv.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index efd83c684..6a512d1f4 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -197,9 +197,9 @@ func CopyExportedVirtualMachine(expPath string, outputPath string, vhdDir string var script = ` param([string]$srcPath, [string]$dstPath, [string]$vhdDirName, [string]$vmDir) -Copy-Item -Path $srcPath/$vhdDirName -Destination $dstPath -recurse -Copy-Item -Path $srcPath/$vmDir -Destination $dstPath -Copy-Item -Path $srcPath/$vmDir/*.xml -Destination $dstPath/$vmDir +Move-Item -Path $srcPath/*.* -Destination $dstPath +Move-Item -Path $srcPath/$vhdDirName -Destination $dstPath +Move-Item -Path $srcPath/$vmDir -Destination $dstPath ` var ps powershell.PowerShellCmd From c24c882b842c0e98a0eb1b10ae15ce3b0021a589 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Fri, 24 Jul 2015 08:21:23 +0100 Subject: [PATCH 021/108] Use simple naming convention for now --- builder/hyperv/iso/builder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index e3556eb65..ef174a584 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -148,7 +148,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if b.config.VMName == "" { - b.config.VMName = fmt.Sprintf("packer-%s-{{timestamp}}", b.config.PackerBuildName) + b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) } log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName)) From 8e18baf493ff3baf76419646a7fdb3aa8cf62f40 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Fri, 24 Jul 2015 08:22:27 +0100 Subject: [PATCH 022/108] Use plain text password --- .../docs/builders/hyperv-iso.html.markdown | 955 ++++++++++++++++++ .../source/docs/builders/hyperv.html.markdown | 18 + 2 files changed, 973 insertions(+) create mode 100644 website/source/docs/builders/hyperv-iso.html.markdown create mode 100644 website/source/docs/builders/hyperv.html.markdown diff --git a/website/source/docs/builders/hyperv-iso.html.markdown b/website/source/docs/builders/hyperv-iso.html.markdown new file mode 100644 index 000000000..ceb946136 --- /dev/null +++ b/website/source/docs/builders/hyperv-iso.html.markdown @@ -0,0 +1,955 @@ +--- +layout: "docs" +page_title: "HyperV Builder (from an ISO)" +description: |- + The HyperV Packer builder is able to create HyperV virtual machines and export them. +--- + +# HyperV Builder (from an ISO) + +Type: `hyperv-iso` + +The HyperV Packer builder is able to create [HyperV](https://www.microsoft.com/en-us/server-cloud/solutions/virtualization.aspx) +virtual machines and export them, starting from an ISO image. + +The builder builds a virtual machine by creating a new virtual machine +from scratch, booting it, installing an OS, provisioning software within +the OS, then shutting it down. The result of the HyperV builder is a directory +containing all the files necessary to run the virtual machine portably. + +## Basic Example + +Here is a basic example. This example is not functional. It will start the +OS installer but then fail because we don't provide the preseed file for +Ubuntu to self-install. Still, the example serves to show the basic configuration: + +```javascript +{ + "type": "hyperv-iso", + "guest_os_type": "Ubuntu_64", + "iso_url": "http://releases.ubuntu.com/12.04/ubuntu-12.04.5-server-amd64.iso", + "iso_checksum": "769474248a3897f4865817446f9a4a53", + "iso_checksum_type": "md5", + "ssh_username": "packer", + "ssh_password": "packer", + "shutdown_command": "echo 'packer' | sudo -S shutdown -P now" +} +``` + +It is important to add a `shutdown_command`. By default Packer halts the +virtual machine and the file system may not be sync'd. Thus, changes made in a +provisioner might not be saved. + +## Configuration Reference + +There are many configuration options available for the HyperV builder. +They are organized below into two categories: required and optional. Within +each category, the available options are alphabetized and described. + +In addition to the options listed here, a +[communicator](/docs/templates/communicator.html) +can be configured for this builder. + + + + + + + + +### 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. + +### 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. + +* `cpu` (int) - The number of cpus the virtual machine should use. If this isn't specified, + the default is 1 cpu. + +* `disk_size` (integer) - The size, in megabytes, of the hard disk to create + for the VM. By default, this is 40000 (about 40 GB). + +* `enable_secure_boot` (bool) - If true enable secure boot for virtual machine. + This defaults to false. + +* `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. + +* `generation` (int) - The HyperV generation for the virtual machine. By + default, this is 1. Generation 2 HyperV virtual machines do not support + floppy drives. In this scenario use secondary_iso_images instead. Hard + drives and dvd drives will also be scsi and not ide. + +* `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. + +* `ip_address_timeout` (string) - The time to wait after creating the initial virtual + machine and waiting for an ip address before assuming there is an error in the process. + 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. + +* `iso_urls` (array of strings) - Multiple URLs for the ISO to download. + Packer will try these in order. If anything goes wrong attempting to download + or while downloading a single URL, it will move on to the next. All URLs + must point to the same file (same checksum). By default this is empty + and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified. + +* `output_directory` (string) - This is the path to the directory where the + resulting virtual machine will be created. This may be relative or absolute. + If relative, the path is relative to the working directory when `packer` + is executed. This directory must not exist or be empty prior to running the builder. + By default this is "output-BUILDNAME" where "BUILDNAME" is the name + of the build. + +* `secondary_iso_images` (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. + +* `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 unless a shutdown command takes place inside script so this may + safely be omitted. If one or more scripts require a reboot it is suggested to leave this blank + since reboots may fail and specify the final shutdown command in your last script. + +* `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. + +* `skip_compaction` (bool) - If true skip compacting the hard disk for virtual machine when + exporting. This defaults to false. + +* `switch_name` (string) - The name of the switch to connect the virtual machine to. Be defaulting + this to an empty string, Packer will try to determine the switch to use by looking for + external switch that is up and running. + +* `vm_name` (string) - This is the name of the virtua machine for the new virtual + machine, without the file extension. By default this is "packer-BUILDNAME", + where "BUILDNAME" is the name of the build. + +## 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 the virtual keyboard +to the machine, simulating a human actually typing the keyboard. There are +a set of special keys available. If these are in your boot command, they +will be replaced by the proper key: + +* `` - Backspace + +* `` - Delete + +* `` and `` - Simulates an actual "enter" or "return" keypress. + +* `` - Simulates pressing the escape key. + +* `` - Simulates pressing the tab key. + +* `` - `` - Simulates pressing a function key. + +* `` `` `` `` - Simulates pressing an arrow key. + +* `` - Simulates pressing the spacebar. + +* `` - Simulates pressing the insert key. + +* `` `` - Simulates pressing the home and end keys. + +* `` `` - Simulates pressing the page up and page down keys. + +* `` `` `` - Adds a 1, 5 or 10 second pause before sending any additional keys. This + is useful if you have to generally wait for the UI to update before typing more. + +In addition to the special keys, each command to type is treated as a +[configuration template](/docs/templates/configuration-templates.html). +The available variables are: + +* `HTTPIP` and `HTTPPort` - The IP and port, respectively of an HTTP server + that is started serving the directory specified by the `http_directory` + configuration parameter. If `http_directory` isn't specified, these will + be blank! + +Example boot command. This is actually a working boot command used to start +an Ubuntu 12.04 installer: + +```text +[ + "", + "/install/vmlinuz noapic ", + "preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg ", + "debian-installer=en_US auto locale=en_US kbd-chooser/method=us ", + "hostname={{ .Name }} ", + "fb=false debconf/frontend=noninteractive ", + "keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ", + "keyboard-configuration/variant=USA console-setup/ask_detect=false ", + "initrd=/install/initrd.gz -- " +] +``` + +## Integration Services + +Packer will automatically attach the integration services iso as a dvd drive +for the version of HyperV that is running. + +## Generation 1 vs Generation 2 + +Floppy drives are no longer supported by generation 2 machines. This requires you to +take another approach when dealing with preseed or answer files. Two possible options +are using virtua dvd drives or using the built in web server. + +When dealing with Windows you need to enable UEFI drives for generation 2 virtual machines. + +## Creating iso from directory + +Programs like mkisofs can be used to create an iso from a directory. +There is a [windows version of mkisofs](http://opensourcepack.blogspot.co.uk/p/cdrtools.html). + +Example powershell script. This is an actually working powershell script used to create a Windows answer iso: + +```text +$isoFolder = "answer-iso" +if (test-path $isoFolder){ + remove-item $isoFolder -Force -Recurse +} + +if (test-path windows\windows-2012R2-serverdatacenter-amd64\answer.iso){ + remove-item windows\windows-2012R2-serverdatacenter-amd64\answer.iso -Force +} + +mkdir $isoFolder + +copy windows\windows-2012R2-serverdatacenter-amd64\Autounattend.xml $isoFolder\ +copy windows\windows-2012R2-serverdatacenter-amd64\sysprep-unattend.xml $isoFolder\ +copy windows\common\set-power-config.ps1 $isoFolder\ +copy windows\common\microsoft-updates.ps1 $isoFolder\ +copy windows\common\win-updates.ps1 $isoFolder\ +copy windows\common\run-sysprep.ps1 $isoFolder\ +copy windows\common\run-sysprep.cmd $isoFolder\ + +$textFile = "$isoFolder\Autounattend.xml" + +$c = Get-Content -Encoding UTF8 $textFile + +# Enable UEFI and disable Non EUFI +$c | % { $_ -replace '','','Finish Non UEFI -->' } | % { $_ -replace '' } | % { $_ -replace 'Finish UEFI compatible -->','' } | sc -Path $textFile + +& .\mkisofs.exe -r -iso-level 4 -UDF -o windows\windows-2012R2-serverdatacenter-amd64\answer.iso $isoFolder + +if (test-path $isoFolder){ + remove-item $isoFolder -Force -Recurse +} +``` + + +## Example For Windows Server 2012 R2 Generation 2 + +Packer config: + +```text +{ + "builders": [ + { + "vm_name":"windows2012r2", + "type": "hyperv-iso", + "disk_size": 61440, + "floppy_files": [], + "secondary_iso_images": [ + "./windows/windows-2012R2-serverdatacenter-amd64/answer.iso" + ], + "http_directory": "./windows/common/http/", + "boot_wait": "0s", + "boot_command": [ + "aaa" + ], + "headless": false, + "iso_url": "http://download.microsoft.com/download/6/2/A/62A76ABB-9990-4EFC-A4FE-C7D698DAEB96/9600.16384.WINBLUE_RTM.130821-1623_X64FRE_SERVER_EVAL_EN-US-IRM_SSS_X64FREE_EN-US_DV5.ISO", + "iso_checksum_type": "md5", + "iso_checksum": "458ff91f8abc21b75cb544744bf92e6a", + "communicator":"winrm", + "winrm_username": "vagrant", + "winrm_password": "vagrant", + "winrm_timeout" : "4h", + "shutdown_command": "f:\\run-sysprep.cmd", + "ram_size_mb": 4096, + "cpu": 4, + "generation": 2, + "switch_name":"LAN", + "enable_secure_boot":true + }], + "provisioners": [{ + "type": "powershell", + "elevated_user":"vagrant", + "elevated_password":"vagrant", + "scripts": [ + "./windows/common/install-7zip.ps1", + "./windows/common/install-chef.ps1", + "./windows/common/compile-dotnet-assemblies.ps1", + "./windows/common/cleanup.ps1", + "./windows/common/ultradefrag.ps1", + "./windows/common/sdelete.ps1" + ] + }], + "post-processors": [ + { + "type": "vagrant", + "keep_input_artifact": false, + "output": "{{.Provider}}_windows-2012r2_chef.box" + } + ] +} +``` + +autounattend.xml: + +```text + + + + + + en-US + + en-US + en-US + en-US + en-US + en-US + + + + + + + + Primary + 1 + 350 + + + 2 + Primary + true + + + + + true + NTFS + + 1 + 1 + + + NTFS + + C + 2 + 2 + + + 0 + true + + + + + + + /IMAGE/NAME + Windows Server 2012 R2 SERVERSTANDARD + + + + 0 + 2 + + + + + + + + + + + + OnError + + true + Vagrant + Vagrant + + + + + + + false + + vagrant-2012r2 + Coordinated Universal Time + + + + true + + + false + false + + + true + + + true + + + + + + + + vagrant + true</PlainText> + </Password> + <Enabled>true</Enabled> + <Username>vagrant</Username> + </AutoLogon> + <FirstLogonCommands> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine> + <Description>Set Execution Policy 64 Bit</Description> + <Order>1</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>C:\Windows\SysWOW64\cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine> + <Description>Set Execution Policy 32 Bit</Description> + <Order>2</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm quickconfig -q</CommandLine> + <Description>winrm quickconfig -q</Description> + <Order>3</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm quickconfig -transport:http</CommandLine> + <Description>winrm quickconfig -transport:http</Description> + <Order>4</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config @{MaxTimeoutms="1800000"}</CommandLine> + <Description>Win RM MaxTimoutms</Description> + <Order>5</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config/winrs @{MaxMemoryPerShellMB="300"}</CommandLine> + <Description>Win RM MaxMemoryPerShellMB</Description> + <Order>6</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config/service @{AllowUnencrypted="true"}</CommandLine> + <Description>Win RM AllowUnencrypted</Description> + <Order>7</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config/service/auth @{Basic="true"}</CommandLine> + <Description>Win RM auth Basic</Description> + <Order>8</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config/client/auth @{Basic="true"}</CommandLine> + <Description>Win RM client auth Basic</Description> + <Order>9</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config/listener?Address=*+Transport=HTTP @{Port="5985"} </CommandLine> + <Description>Win RM listener Address/Port</Description> + <Order>10</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c netsh advfirewall firewall set rule group="remote administration" new enable=yes </CommandLine> + <Description>Win RM adv firewall enable</Description> + <Order>11</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c netsh advfirewall firewall add rule name="WinRM 5985" protocol=TCP dir=in localport=5985 action=allow</CommandLine> + <Description>Win RM port open</Description> + <Order>12</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c netsh advfirewall firewall add rule name="WinRM 5986" protocol=TCP dir=in localport=5986 action=allow</CommandLine> + <Description>Win RM port open</Description> + <Order>13</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c net stop winrm </CommandLine> + <Description>Stop Win RM Service </Description> + <Order>14</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c sc config winrm start= disabled</CommandLine> + <Description>Win RM Autostart</Description> + <Order>15</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v HideFileExt /t REG_DWORD /d 0 /f</CommandLine> + <Order>16</Order> + <Description>Show file extensions in Explorer</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\Console /v QuickEdit /t REG_DWORD /d 1 /f</CommandLine> + <Order>17</Order> + <Description>Enable QuickEdit mode</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v Start_ShowRun /t REG_DWORD /d 1 /f</CommandLine> + <Order>18</Order> + <Description>Show Run command in Start Menu</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v StartMenuAdminTools /t REG_DWORD /d 1 /f</CommandLine> + <Order>19</Order> + <Description>Show Administrative Tools in Start Menu</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKLM\SYSTEM\CurrentControlSet\Control\Power\ /v HibernateFileSizePercent /t REG_DWORD /d 0 /f</CommandLine> + <Order>20</Order> + <Description>Zero Hibernation File</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKLM\SYSTEM\CurrentControlSet\Control\Power\ /v HibernateEnabled /t REG_DWORD /d 0 /f</CommandLine> + <Order>21</Order> + <Description>Disable Hibernation Mode</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c wmic useraccount where "name='vagrant'" set PasswordExpires=FALSE</CommandLine> + <Order>22</Order> + <Description>Disable password expiration for vagrant user</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config/winrs @{MaxShellsPerUser="30"}</CommandLine> + <Description>Win RM MaxShellsPerUser</Description> + <Order>23</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config/winrs @{MaxProcessesPerShell="25"}</CommandLine> + <Description>Win RM MaxProcessesPerShell</Description> + <Order>24</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD "HKLM\System\CurrentControlSet\Services\Netlogon\Parameters" /v DisablePasswordChange /t REG_DWORD /d 1 /f</CommandLine> + <Description>Turn off computer password</Description> + <Order>25</Order> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c netsh advfirewall firewall add rule name="ICMP Allow incoming V4 echo request" protocol=icmpv4:8,any dir=in action=allow</CommandLine> + <Description>ICMP open for ping</Description> + <Order>26</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <!-- WITH WINDOWS UPDATES --> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c IF EXIST a:\set-power-config.ps1 (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\set-power-config.ps1) ELSE (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File f:\set-power-config.ps1)</CommandLine> + <Order>97</Order> + <Description>Turn off all power saving and timeouts</Description> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c IF EXIST a:\microsoft-updates.ps1 (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\microsoft-updates.ps1) ELSE (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File f:\microsoft-updates.ps1)</CommandLine> + <Order>98</Order> + <Description>Enable Microsoft Updates</Description> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c IF EXIST a:\win-updates.ps1 (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\win-updates.ps1) ELSE (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File f:\win-updates.ps1)</CommandLine> + <Description>Install Windows Updates</Description> + <Order>100</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <!-- END WITH WINDOWS UPDATES --> + </FirstLogonCommands> + <OOBE> + <HideEULAPage>true</HideEULAPage> + <HideLocalAccountScreen>true</HideLocalAccountScreen> + <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> + <HideOnlineAccountScreens>true</HideOnlineAccountScreens> + <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> + <NetworkLocation>Work</NetworkLocation> + <ProtectYourPC>1</ProtectYourPC> + </OOBE> + <UserAccounts> + <AdministratorPassword> + <Value>vagrant</Value> + <PlainText>true</PlainText> + </AdministratorPassword> + <LocalAccounts> + <LocalAccount wcm:action="add"> + <Password> + <Value>vagrant</Value> + <PlainText>true</PlainText> + </Password> + <Group>administrators</Group> + <DisplayName>Vagrant</DisplayName> + <Name>vagrant</Name> + <Description>Vagrant User</Description> + </LocalAccount> + </LocalAccounts> + </UserAccounts> + <RegisteredOwner /> + <TimeZone>Coordinated Universal Time</TimeZone> + </component> + </settings> + <settings pass="offlineServicing"> + <component name="Microsoft-Windows-LUA-Settings" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <EnableLUA>false</EnableLUA> + </component> + </settings> + <cpi:offlineImage cpi:source="wim:c:/projects/baseboxes/9600.16384.winblue_rtm.130821-1623_x64fre_server_eval_en-us-irm_sss_x64free_en-us_dv5_slipstream/sources/install.wim#Windows Server 2012 R2 SERVERDATACENTER" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> +</unattend> + +``` + +sysprep-unattend.xml: + +```text +<?xml version="1.0" encoding="utf-8"?> +<unattend xmlns="urn:schemas-microsoft-com:unattend"> + <settings pass="generalize"> + <component language="neutral" name="Microsoft-Windows-Security-SPP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SkipRearm>1</SkipRearm> + </component> + </settings> + <settings pass="oobeSystem"> +<!-- Setup proxy after sysprep + <component name="Microsoft-Windows-IE-ClientNetworkProtocolImplementation" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <POLICYProxySettingsPerUser>1</POLICYProxySettingsPerUser> + <HKLMProxyEnable>false</HKLMProxyEnable> + <HKLMProxyServer>cache-proxy:3142</HKLMProxyServer> + </component> +Finish proxy after sysprep --> + <component language="neutral" name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <InputLocale>0809:00000809</InputLocale> + <SystemLocale>en-GB</SystemLocale> + <UILanguage>en-US</UILanguage> + <UILanguageFallback>en-US</UILanguageFallback> + <UserLocale>en-GB</UserLocale> + </component> + <component language="neutral" name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <OOBE> + <HideEULAPage>true</HideEULAPage> + <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> + <HideOnlineAccountScreens>true</HideOnlineAccountScreens> + <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> + <NetworkLocation>Work</NetworkLocation> + <ProtectYourPC>1</ProtectYourPC> + <SkipUserOOBE>true</SkipUserOOBE> + <SkipMachineOOBE>true</SkipMachineOOBE> + </OOBE> + <UserAccounts> + <AdministratorPassword> + <Value>vagrant</Value> + <PlainText>true</PlainText> + </AdministratorPassword> + <LocalAccounts> + <LocalAccount wcm:action="add"> + <Password> + <Value>vagrant</Value> + <PlainText>true</PlainText> + </Password> + <Group>administrators</Group> + <DisplayName>Vagrant</DisplayName> + <Name>vagrant</Name> + <Description>Vagrant User</Description> + </LocalAccount> + </LocalAccounts> + </UserAccounts> + <DisableAutoDaylightTimeSet>true</DisableAutoDaylightTimeSet> + <TimeZone>Coordinated Universal Time</TimeZone> + <VisualEffects> + <SystemDefaultBackgroundColor>2</SystemDefaultBackgroundColor> + </VisualEffects> + </component> + </settings> +</unattend> +``` + +## Example For Ubuntu Vivid Generation 2 + +Packer config: + +```text +{ + "builders": [ + { + "vm_name":"ubuntu-vivid", + "type": "hyperv-iso", + "disk_size": 61440, + "headless": false, + "iso_url": "http://releases.ubuntu.com/15.04/ubuntu-15.04-server-amd64.iso", + "iso_checksum_type": "sha1", + "iso_checksum": "D10248965C2C749DF6BCCE9F2F90F16A2E75E843", + "communicator":"ssh", + "ssh_username": "vagrant", + "ssh_password": "vagrant", + "ssh_timeout" : "4h", + "http_directory": "./linux/ubuntu/http/", + "boot_wait": "5s", + "boot_command": [ + "<esc><esc><enter><wait>", + "/install/vmlinuz ", + "preseed/url=http://{{.HTTPIP}}:{{.HTTPPort}}/preseed.cfg ", + "debian-installer=en_US auto locale=en_US kbd-chooser/method=us ", + "hostname={{.Name}} ", + "fb=false debconf/frontend=noninteractive ", + "keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ", + "keyboard-configuration/variant=USA console-setup/ask_detect=false ", + "initrd=/install/initrd.gz -- <enter>" + ], + "shutdown_command": "echo 'vagrant' | sudo -S -E shutdown -P now", + "ram_size_mb": 4096, + "cpu": 4, + "generation": 1, + "switch_name":"LAN" + }], + "provisioners": [{ + "type": "shell", + "execute_command": "echo 'vagrant' | sudo -S -E sh {{.Path}}", + "scripts": [ + "./linux/ubuntu/update.sh", + "./linux/ubuntu/network.sh", + "./linux/common/vagrant.sh", + "./linux/common/chef.sh", + "./linux/common/motd.sh", + "./linux/ubuntu/cleanup.sh" + ] + }], + "post-processors": [ + { + "type": "vagrant", + "keep_input_artifact": true, + "output": "{{.Provider}}_ubuntu-15.04_chef.box" + } + ] +} +``` + +preseed.cfg: + +```text +## Options to set on the command line +d-i debian-installer/locale string en_US.utf8 +d-i console-setup/ask_detect boolean false +d-i console-setup/layout string us + +d-i netcfg/get_hostname string unassigned-hostname +d-i netcfg/get_domain string unassigned-domain + +d-i time/zone string UTC +d-i clock-setup/utc-auto boolean true +d-i clock-setup/utc boolean true + +d-i kbd-chooser/method select American English + +d-i netcfg/wireless_wep string + +d-i base-installer/kernel/override-image string linux-server + +d-i debconf debconf/frontend select Noninteractive + +d-i pkgsel/install-language-support boolean false +tasksel tasksel/first multiselect standard, ubuntu-server + +d-i partman-auto/method string lvm + +d-i partman-lvm/confirm boolean true +d-i partman-lvm/device_remove_lvm boolean true +d-i partman-auto/choose_recipe select atomic + +d-i partman/confirm_write_new_label boolean true +d-i partman/confirm_nooverwrite boolean true +d-i partman/choose_partition select finish +d-i partman/confirm boolean true + +# Write the changes to disks and configure LVM? +d-i partman-lvm/confirm boolean true +d-i partman-lvm/confirm_nooverwrite boolean true +d-i partman-auto-lvm/guided_size string max + +# Default user +d-i passwd/user-fullname string vagrant +d-i passwd/username string vagrant +d-i passwd/user-password password vagrant +d-i passwd/user-password-again password vagrant +d-i user-setup/encrypt-home boolean false +d-i user-setup/allow-password-weak boolean true + +# Minimum packages (see postinstall.sh) +d-i pkgsel/include string openssh-server ntp + +# Upgrade packages after debootstrap? (none, safe-upgrade, full-upgrade) +# (note: set to none for speed) +d-i pkgsel/upgrade select none + +d-i grub-installer/only_debian boolean true +d-i grub-installer/with_other_os boolean true +d-i finish-install/reboot_in_progress note + +d-i pkgsel/update-policy select none + +choose-mirror-bin mirror/http/proxy string + +#d-i mirror/http/proxy string http://apt-cacher:3142/ +``` \ No newline at end of file diff --git a/website/source/docs/builders/hyperv.html.markdown b/website/source/docs/builders/hyperv.html.markdown new file mode 100644 index 000000000..79c6fbff1 --- /dev/null +++ b/website/source/docs/builders/hyperv.html.markdown @@ -0,0 +1,18 @@ +--- +layout: "docs" +page_title: "HyperV Builder" +description: |- + The HyperV Packer builder is able to create HyperV virtual machines and export them. +--- + +# HyperV Builder + +The HyperV Packer builder is able to create [HyperV](https://www.microsoft.com/en-us/server-cloud/solutions/virtualization.aspx) +virtual machines and export them. + +Packer currently only support building HyperV machines with an iso: + +* [hyperv-iso](/docs/builders/hyperv-iso.html) - Starts from + an ISO file, creates a brand new HyperV VM, installs an OS, + provisions software within the OS, then exports that machine to create + an image. This is best for people who want to start from scratch. \ No newline at end of file From d57517d4a4d4a7deb5fcb1c75104086b7e7b84d8 Mon Sep 17 00:00:00 2001 From: Volodymyr Babchynskyy <vvchik@gmail.com> Date: Fri, 9 Oct 2015 16:54:55 +0300 Subject: [PATCH 023/108] fix vagrant box structure --- post-processor/vagrant/hyperv.go | 40 ++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/post-processor/vagrant/hyperv.go b/post-processor/vagrant/hyperv.go index d5fa0ce52..2f7dc1440 100644 --- a/post-processor/vagrant/hyperv.go +++ b/post-processor/vagrant/hyperv.go @@ -3,7 +3,9 @@ package vagrant import ( "fmt" "github.com/mitchellh/packer/packer" + "os" "path/filepath" + "strings" ) type HypervProvider struct{} @@ -16,14 +18,48 @@ func (p *HypervProvider) Process(ui packer.Ui, artifact packer.Artifact, dir str // Create the metadata metadata = map[string]interface{}{"provider": "hyperv"} + // ui.Message(fmt.Sprintf("artifacts all: %+v", artifact)) + var outputDir string + + // Vargant requires specific dir structure for hyperv + // hyperv builder creates the structure in the output dir + // we have to keep the structure in a temp dir + // hack little bit but string in artifact usually have output dir + artifactString := artifact.String() + d := strings.Split(artifactString, ": ") + outputDir = d[1] + // ui.Message(fmt.Sprintf("artifact dir from string: %s", outputDir)) + // Copy all of the original contents into the temporary directory for _, path := range artifact.Files() { ui.Message(fmt.Sprintf("Copying: %s", path)) - dstPath := filepath.Join(dir, filepath.Base(path)) - if err = CopyContents(dstPath, path); err != nil { + var rel string + + rel, err = filepath.Rel(outputDir, filepath.Dir(path)) + // ui.Message(fmt.Sprintf("rel is: %s", rel)) + + if err != nil { + ui.Message(fmt.Sprintf("err in: %s", rel)) return } + + dstDir := filepath.Join(dir, rel) + // ui.Message(fmt.Sprintf("dstdir is: %s", dstDir)) + if _, err = os.Stat(dstDir); err != nil { + if err = os.MkdirAll(dstDir, 0755); err != nil { + ui.Message(fmt.Sprintf("err in creating: %s", dstDir)) + return + } + } + + dstPath := filepath.Join(dstDir, filepath.Base(path)) + + if err = CopyContents(dstPath, path); err != nil { + ui.Message(fmt.Sprintf("err in copying: %s to %s", path, dstPath)) + return + } + ui.Message(fmt.Sprintf("Copyed %s to %s", path, dstPath)) } return From 54733323a85967a5f99d80ec6d48dcd10c1053bc Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 18 Oct 2015 12:26:44 +0100 Subject: [PATCH 024/108] Should only default to first controller if its not 1 of the 2 controllers available --- builder/hyperv/common/step_mount_integration_services.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/hyperv/common/step_mount_integration_services.go b/builder/hyperv/common/step_mount_integration_services.go index 5460d52b8..5aad1f4ab 100644 --- a/builder/hyperv/common/step_mount_integration_services.go +++ b/builder/hyperv/common/step_mount_integration_services.go @@ -112,7 +112,7 @@ func (s *StepMountSecondaryDvdImages) addAndMountDvdDisk(vmName string, isoPath return properties, err } - if controllerNumber != "0" || controllerNumber != "1" { + if controllerNumber != "0" && controllerNumber != "1" { //There are only 2 ide controllers, try to use the one the hdd is attached too controllerNumber = "0" } From 9f948348263891ae5c9f71ca6b0eb69830374349 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 18 Oct 2015 14:01:18 +0100 Subject: [PATCH 025/108] Use correct formatting for printf --- builder/hyperv/common/step_mount_integration_services.go | 2 +- builder/hyperv/common/step_unmount_integration_services.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/hyperv/common/step_mount_integration_services.go b/builder/hyperv/common/step_mount_integration_services.go index 5aad1f4ab..63e775a0d 100644 --- a/builder/hyperv/common/step_mount_integration_services.go +++ b/builder/hyperv/common/step_mount_integration_services.go @@ -42,7 +42,7 @@ func (s *StepMountSecondaryDvdImages) Run(state multistep.StateBag) multistep.St return multistep.ActionHalt } - log.Println(fmt.Sprintf("Saving DVD properties %s DVDs", len(dvdProperties))) + log.Println(fmt.Sprintf("Saving DVD properties %d DVDs", len(dvdProperties))) state.Put("secondary.dvd.properties", dvdProperties) diff --git a/builder/hyperv/common/step_unmount_integration_services.go b/builder/hyperv/common/step_unmount_integration_services.go index a905f2a8b..d79245782 100644 --- a/builder/hyperv/common/step_unmount_integration_services.go +++ b/builder/hyperv/common/step_unmount_integration_services.go @@ -25,7 +25,7 @@ func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep. dvdProperties := state.Get("secondary.dvd.properties").([]DvdControllerProperties) - log.Println(fmt.Sprintf("Found DVD properties %s", len(dvdProperties))) + log.Println(fmt.Sprintf("Found DVD properties %d", len(dvdProperties))) for _, dvdProperty := range dvdProperties { controllerNumber := dvdProperty.ControllerNumber From 5285a819ddb9d7d7be15e3a7509bb1cb2b39b26e Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 18 Oct 2015 16:10:06 +0100 Subject: [PATCH 026/108] Quoting of powershell commands and associated tests --- provisioner/powershell/provisioner.go | 4 ++-- provisioner/powershell/provisioner_test.go | 24 +++++++++++----------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index d59a6bf46..43c9a6898 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -116,7 +116,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if p.config.ElevatedEnvVarFormat == "" { - p.config.ElevatedEnvVarFormat = `$env:%s=\"%s\"; ` + p.config.ElevatedEnvVarFormat = `$env:%s="%s"; ` } if p.config.ExecuteCommand == "" { @@ -124,7 +124,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if p.config.ElevatedExecuteCommand == "" { - p.config.ElevatedExecuteCommand = `{{.Vars}}{{.Path}}` + p.config.ElevatedExecuteCommand = `powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}'` } if p.config.Inline != nil && len(p.config.Inline) == 0 { diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index 7acf97d8a..e0bee6bcc 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -58,7 +58,7 @@ func TestProvisioner_Impl(t *testing.T) { func TestProvisionerPrepare_Defaults(t *testing.T) { var p Provisioner config := testConfig() - + err := p.Prepare(config) if err != nil { t.Fatalf("err: %s", err) @@ -79,8 +79,8 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { t.Fatalf("Default command should be powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ExecuteCommand) } - if p.config.ElevatedExecuteCommand != "{{.Vars}}{{.Path}}" { - t.Fatalf("Default command should be powershell {{.Vars}}{{.Path}}, but got %s", p.config.ElevatedExecuteCommand) + if p.config.ElevatedExecuteCommand != "powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}'" { + t.Fatalf("Default command should be powershell powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ElevatedExecuteCommand) } if p.config.ValidExitCodes == nil { @@ -96,7 +96,7 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { } if p.config.ElevatedEnvVarFormat != `$env:%s="%s"; ` { - t.Fatalf("Default command should be powershell \"{{.Vars}}{{.Path}}\", but got %s", p.config.ElevatedEnvVarFormat) + t.Fatalf("Default command should be powershell '$env:%s=\"%s\"; ', but got %s", p.config.ElevatedEnvVarFormat) } } @@ -389,7 +389,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell "& { $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}"` + expectedCommand := `powershell '& { $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -408,7 +408,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand = `powershell "& { $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}"` + expectedCommand = `powershell '& { $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -435,7 +435,7 @@ func TestProvisionerProvision_Scripts(t *testing.T) { } //powershell -Command "$env:PACKER_BUILDER_TYPE=''"; powershell -Command "$env:PACKER_BUILD_NAME='foobuild'"; powershell -Command c:/Windows/Temp/script.ps1 - expectedCommand := `powershell "& { $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}"` + expectedCommand := `powershell '& { $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -468,7 +468,7 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell "& { $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}"` + expectedCommand := `powershell '& { $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -545,7 +545,7 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != "$env:PACKER_BUILDER_TYPE=\\\"iso\\\"; $env:PACKER_BUILD_NAME=\\\"vmware\\\"; " { + if flattenedEnvVars != `$env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -556,7 +556,7 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != "$env:FOO=\\\"bar\\\"; $env:PACKER_BUILDER_TYPE=\\\"iso\\\"; $env:PACKER_BUILD_NAME=\\\"vmware\\\"; " { + if flattenedEnvVars != `$env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -567,7 +567,7 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != "$env:BAZ=\\\"qux\\\"; $env:FOO=\\\"bar\\\"; $env:PACKER_BUILDER_TYPE=\\\"iso\\\"; $env:PACKER_BUILD_NAME=\\\"vmware\\\"; " { + if flattenedEnvVars != `$env:BAZ="qux"; $env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } } @@ -582,7 +582,7 @@ func TestProvision_createCommandText(t *testing.T) { // Non-elevated cmd, _ := p.createCommandText() - if cmd != "powershell \"& { $env:PACKER_BUILDER_TYPE=\\\"\\\"; $env:PACKER_BUILD_NAME=\\\"\\\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}\"" { + if cmd != `powershell '& { $env:PACKER_BUILDER_TYPE=""; $env:PACKER_BUILD_NAME=""; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` { t.Fatalf("Got unexpected non-elevated command: %s", cmd) } From 15137310a78d52e0ed509ea3e291a75969d61c56 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 18 Oct 2015 16:29:23 +0100 Subject: [PATCH 027/108] Must escape string formatting for string fmt --- provisioner/powershell/provisioner_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index e0bee6bcc..9ef9ec301 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -96,7 +96,7 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { } if p.config.ElevatedEnvVarFormat != `$env:%s="%s"; ` { - t.Fatalf("Default command should be powershell '$env:%s=\"%s\"; ', but got %s", p.config.ElevatedEnvVarFormat) + t.Fatalf(`Default command should be powershell '$env:%%s="%%s"; ', but got %s`, p.config.ElevatedEnvVarFormat) } } From 730c6217ad9cf4e53445de5a8c6f9a237ab1fc8a Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 18 Oct 2015 17:11:38 +0100 Subject: [PATCH 028/108] Fix disk size test Default minimum and maximum sizes were geared towards spinning up windows server instances. Linux instances are far more efficient and make do with lower requirements. Set the minimum sizes to match this. --- builder/hyperv/iso/builder.go | 22 +++++++++++----------- builder/hyperv/iso/builder_test.go | 9 +++++---- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index ef174a584..9a18c133f 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -22,15 +22,15 @@ import ( ) const ( - DefaultDiskSize = 40000 // ~40GB - MinDiskSize = 10 * 1024 // 10GB - MaxDiskSize = 65536 * 1024 // 64TB + DefaultDiskSize = 40 * 1024 // ~40GB + MinDiskSize = 256 // 256MB + MaxDiskSize = 64 * 1024 * 1024 // 64TB - DefaultRamSize = 1024 // 1GB - MinRamSize = 512 // 512MB - MaxRamSize = 32768 // 32GB + DefaultRamSize = 1 * 1024 // 1GB + MinRamSize = 32 // 32MB + MaxRamSize = 32 * 1024 // 32GB - LowRam = 512 // 512MB + LowRam = 384 // 384MB DefaultUsername = "vagrant" DefaultPassword = "vagrant" @@ -416,9 +416,9 @@ func (b *Builder) checkDiskSize() error { log.Println(fmt.Sprintf("%s: %v", "DiskSize", b.config.DiskSize)) if b.config.DiskSize < MinDiskSize { - return fmt.Errorf("disk_size_gb: Windows server requires disk space >= %v GB, but defined: %v", MinDiskSize, b.config.DiskSize/1024) + return fmt.Errorf("disk_size_gb: Virtual machine requires disk space >= %v GB, but defined: %v", MinDiskSize, b.config.DiskSize/1024) } else if b.config.DiskSize > MaxDiskSize { - return fmt.Errorf("disk_size_gb: Windows server requires disk space <= %v GB, but defined: %v", MaxDiskSize, b.config.DiskSize/1024) + return fmt.Errorf("disk_size_gb: Virtual machine requires disk space <= %v GB, but defined: %v", MaxDiskSize, b.config.DiskSize/1024) } return nil @@ -432,9 +432,9 @@ func (b *Builder) checkRamSize() error { log.Println(fmt.Sprintf("%s: %v", "RamSize", b.config.RamSizeMB)) if b.config.RamSizeMB < MinRamSize { - return fmt.Errorf("ram_size_mb: Windows server requires memory size >= %v MB, but defined: %v", MinRamSize, b.config.RamSizeMB) + return fmt.Errorf("ram_size_mb: Virtual machine requires memory size >= %v MB, but defined: %v", MinRamSize, b.config.RamSizeMB) } else if b.config.RamSizeMB > MaxRamSize { - return fmt.Errorf("ram_size_mb: Windows server requires memory size <= %v MB, but defined: %v", MaxRamSize, b.config.RamSizeMB) + return fmt.Errorf("ram_size_mb: Virtual machine requires memory size <= %v MB, but defined: %v", MaxRamSize, b.config.RamSizeMB) } return nil diff --git a/builder/hyperv/iso/builder_test.go b/builder/hyperv/iso/builder_test.go index a17851a3e..5311075d0 100644 --- a/builder/hyperv/iso/builder_test.go +++ b/builder/hyperv/iso/builder_test.go @@ -13,7 +13,8 @@ func testConfig() map[string]interface{} { "iso_url": "http://www.packer.io", "shutdown_command": "yes", "ssh_username": "foo", - + "ram_size_mb": 64, + "disk_size": 256, packer.BuildNameConfigKey: "foo", } } @@ -55,11 +56,11 @@ func TestBuilderPrepare_DiskSize(t *testing.T) { t.Fatalf("bad err: %s", err) } - if b.config.DiskSize != 40000 { + if b.config.DiskSize != 40 * 1024 { t.Fatalf("bad size: %d", b.config.DiskSize) } - config["disk_size"] = 60000 + config["disk_size"] = 256 b = Builder{} warns, err = b.Prepare(config) if len(warns) > 0 { @@ -69,7 +70,7 @@ func TestBuilderPrepare_DiskSize(t *testing.T) { t.Fatalf("should not have error: %s", err) } - if b.config.DiskSize != 60000 { + if b.config.DiskSize != 256 { t.Fatalf("bad size: %d", b.config.DiskSize) } } From 9cf8b18e096a9ba2cb69a50356539b62a5d054e5 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 25 Oct 2015 22:28:01 +0000 Subject: [PATCH 029/108] If there is only one ip address on a card we need to force it to be an array. Otherwise powershell may treat it as a property. Stop-Vm with force parameter to ensure that powershell prompts will not appear --- powershell/hyperv/hyperv.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 6a512d1f4..0b5d14869 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -15,7 +15,7 @@ if ($HostVMAdapter){ if ($HostNetAdapter){ $HostNetAdapterConfiguration = @(get-wmiobject win32_networkadapterconfiguration -filter "IPEnabled = 'TRUE' AND InterfaceIndex=$($HostNetAdapter.ifIndex)") if ($HostNetAdapterConfiguration){ - return $HostNetAdapterConfiguration.IpAddress[$addressIndex] + return @($HostNetAdapterConfiguration.IpAddress)[$addressIndex] } } } @@ -477,7 +477,7 @@ func TurnOff(vmName string) error { param([string]$vmName) $vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { - Stop-VM -Name $vmName -TurnOff -Confirm:$false + Stop-VM -Name $vmName -TurnOff -Confirm:$false -Force } ` @@ -492,7 +492,7 @@ func ShutDown(vmName string) error { param([string]$vmName) $vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { - Stop-VM -Name $vmName -Confirm:$false + Stop-VM -Name $vmName -Confirm:$false -Force } ` From 25baa26a7adf512901759eca35347d980ae4351e Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 25 Oct 2015 23:11:01 +0000 Subject: [PATCH 030/108] Incude hyperv as part of the plugins --- command/plugin.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/command/plugin.go b/command/plugin.go index dce7d9311..c0c89c9e7 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -23,6 +23,7 @@ import ( dockerbuilder "github.com/mitchellh/packer/builder/docker" filebuilder "github.com/mitchellh/packer/builder/file" googlecomputebuilder "github.com/mitchellh/packer/builder/googlecompute" + hypervbuilder "github.com/mitchellh/packer/builder/hyperv" nullbuilder "github.com/mitchellh/packer/builder/null" oneandonebuilder "github.com/mitchellh/packer/builder/oneandone" openstackbuilder "github.com/mitchellh/packer/builder/openstack" @@ -79,6 +80,7 @@ var Builders = map[string]packer.Builder{ "docker": new(dockerbuilder.Builder), "file": new(filebuilder.Builder), "googlecompute": new(googlecomputebuilder.Builder), + "hyperv": new(hypervbuilder.Builder), "null": new(nullbuilder.Builder), "oneandone": new(oneandonebuilder.Builder), "openstack": new(openstackbuilder.Builder), From ccf506ee2295ce18f73abe5f0563d75df3fd1a84 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 25 Oct 2015 23:15:16 +0000 Subject: [PATCH 031/108] Must point to iso hyperv plugin --- command/plugin.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/command/plugin.go b/command/plugin.go index c0c89c9e7..2086f5890 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -23,7 +23,7 @@ import ( dockerbuilder "github.com/mitchellh/packer/builder/docker" filebuilder "github.com/mitchellh/packer/builder/file" googlecomputebuilder "github.com/mitchellh/packer/builder/googlecompute" - hypervbuilder "github.com/mitchellh/packer/builder/hyperv" + hypervbuilder "github.com/mitchellh/packer/builder/hyperv/iso" nullbuilder "github.com/mitchellh/packer/builder/null" oneandonebuilder "github.com/mitchellh/packer/builder/oneandone" openstackbuilder "github.com/mitchellh/packer/builder/openstack" @@ -80,7 +80,7 @@ var Builders = map[string]packer.Builder{ "docker": new(dockerbuilder.Builder), "file": new(filebuilder.Builder), "googlecompute": new(googlecomputebuilder.Builder), - "hyperv": new(hypervbuilder.Builder), + "hyperv-iso": new(hypervbuilder.Builder), "null": new(nullbuilder.Builder), "oneandone": new(oneandonebuilder.Builder), "openstack": new(openstackbuilder.Builder), From 9fd635742c998df7e453ea4d14651b0aa6466b9f Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Tue, 27 Oct 2015 00:12:41 +0000 Subject: [PATCH 032/108] Always force turn off vm --- powershell/hyperv/hyperv.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 0b5d14869..3cf5d8a38 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -273,7 +273,7 @@ func StopVirtualMachine(vmName string) error { param([string]$vmName) $vm = Get-VM -Name $vmName if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { - Stop-VM -VM $vm -Confirm:$false + Stop-VM -VM $vm -Force -Confirm:$false } ` @@ -477,7 +477,7 @@ func TurnOff(vmName string) error { param([string]$vmName) $vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { - Stop-VM -Name $vmName -TurnOff -Confirm:$false -Force + Stop-VM -Name $vmName -TurnOff -Force -Confirm:$false } ` @@ -492,7 +492,7 @@ func ShutDown(vmName string) error { param([string]$vmName) $vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { - Stop-VM -Name $vmName -Confirm:$false -Force + Stop-VM -Name $vmName -Force -Confirm:$false } ` From cd6213eb553e0135c74058c48bcefbe62028c923 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Wed, 28 Oct 2015 23:09:31 +0000 Subject: [PATCH 033/108] Seems like we do need to escape the double quote --- provisioner/powershell/provisioner.go | 4 ++-- provisioner/powershell/provisioner_test.go | 28 +++++++++++----------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index 43c9a6898..ad90e56a1 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -116,7 +116,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if p.config.ElevatedEnvVarFormat == "" { - p.config.ElevatedEnvVarFormat = `$env:%s="%s"; ` + p.config.ElevatedEnvVarFormat = `$env:%s=\"%s\"; ` } if p.config.ExecuteCommand == "" { @@ -347,7 +347,7 @@ func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string, e // Split vars into key/value components for _, envVar := range p.config.Vars { keyValue := strings.Split(envVar, "=") - + if len(keyValue) != 2 || keyValue[0] == "" { err = errors.New(fmt.Sprintf("Shell provisioner environment variables must be in key=value format. Currently it is '%s'", envVar)) return diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index 9ef9ec301..e109eb6d5 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -58,7 +58,7 @@ func TestProvisioner_Impl(t *testing.T) { func TestProvisionerPrepare_Defaults(t *testing.T) { var p Provisioner config := testConfig() - + err := p.Prepare(config) if err != nil { t.Fatalf("err: %s", err) @@ -95,8 +95,8 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { } } - if p.config.ElevatedEnvVarFormat != `$env:%s="%s"; ` { - t.Fatalf(`Default command should be powershell '$env:%%s="%%s"; ', but got %s`, p.config.ElevatedEnvVarFormat) + if p.config.ElevatedEnvVarFormat != `$env:%s=\"%s\"; ` { + t.Fatalf(`Default command should be powershell '$env:%%s=\"%%s\"; ', but got %s`, p.config.ElevatedEnvVarFormat) } } @@ -389,7 +389,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& { $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand := `powershell '& { $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -408,7 +408,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand = `powershell '& { $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand = `powershell '& { $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -435,7 +435,7 @@ func TestProvisionerProvision_Scripts(t *testing.T) { } //powershell -Command "$env:PACKER_BUILDER_TYPE=''"; powershell -Command "$env:PACKER_BUILD_NAME='foobuild'"; powershell -Command c:/Windows/Temp/script.ps1 - expectedCommand := `powershell '& { $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `powershell '& { $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -468,7 +468,7 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& { $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `powershell '& { $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -500,7 +500,7 @@ func TestProvisioner_createFlattenedElevatedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != "$env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; " { + if flattenedEnvVars != `$env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -511,7 +511,7 @@ func TestProvisioner_createFlattenedElevatedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != "$env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; " { + if flattenedEnvVars != `$env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -522,7 +522,7 @@ func TestProvisioner_createFlattenedElevatedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != "$env:BAZ=\"qux\"; $env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; " { + if flattenedEnvVars != `$env:BAZ=\"qux\"; $env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } } @@ -545,7 +545,7 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { + if flattenedEnvVars != `$env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -556,7 +556,7 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { + if flattenedEnvVars != `$env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -567,7 +567,7 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:BAZ="qux"; $env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { + if flattenedEnvVars != `$env:BAZ=\"qux\"; $env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } } @@ -582,7 +582,7 @@ func TestProvision_createCommandText(t *testing.T) { // Non-elevated cmd, _ := p.createCommandText() - if cmd != `powershell '& { $env:PACKER_BUILDER_TYPE=""; $env:PACKER_BUILD_NAME=""; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` { + if cmd != `powershell '& { $env:PACKER_BUILDER_TYPE=\"\"; $env:PACKER_BUILD_NAME=\"\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` { t.Fatalf("Got unexpected non-elevated command: %s", cmd) } From 422efeeaf6719a731b450952529f744b5221c1fa Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Fri, 30 Oct 2015 08:23:30 +0000 Subject: [PATCH 034/108] Make use of driver instead of directly referencing hyper Move inline powershell to hyperv --- builder/hyperv/common/driver.go | 60 +++++++ builder/hyperv/common/driver_ps_4.go | 112 +++++++++++++ builder/hyperv/common/step_configure_ip.go | 16 +- builder/hyperv/common/step_configure_vlan.go | 20 +-- .../common/step_create_external_switch.go | 25 ++- builder/hyperv/common/step_create_switch.go | 21 ++- builder/hyperv/common/step_create_vm.go | 39 ++--- builder/hyperv/common/step_disable_vlan.go | 6 +- .../common/step_enable_integration_service.go | 4 +- builder/hyperv/common/step_export_vm.go | 8 +- builder/hyperv/common/step_mount_dvddrive.go | 9 +- .../hyperv/common/step_mount_floppydrive.go | 37 ++--- .../common/step_mount_integration_services.go | 56 +------ .../common/step_polling_installation.go | 41 +---- builder/hyperv/common/step_reboot_vm.go | 5 +- .../hyperv/common/step_unmount_dvddrive.go | 8 +- .../hyperv/common/step_unmount_floppydrive.go | 5 +- .../step_unmount_integration_services.go | 14 +- .../step_wait_for_install_to_complete.go | 44 ++---- powershell/hyperv/hyperv.go | 148 +++++++++++++++++- 20 files changed, 432 insertions(+), 246 deletions(-) diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index 2d5044576..5afa15d77 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -15,6 +15,12 @@ type Driver interface { // Checks if the VM named is running. IsRunning(string) (bool, error) + // Checks if the VM named is off. + IsOff(string) (bool, error) + + //How long has VM been on + Uptime(vmName string) (uint64, error) + // Start starts a VM specified by the name given. Start(string) error @@ -32,9 +38,63 @@ type Driver interface { // Finds the IP address of a VM connected that uses DHCP by its MAC address IpAddress(string) (string, error) + // Finds the hostname for the ip address + GetHostName(string) (string, error) + // Finds the IP address of a host adapter connected to switch GetHostAdapterIpAddressForSwitch(string) (string, error) // Type scan codes to virtual keyboard of vm TypeScanCodes(string, string) error + + //Get the ip address for network adaptor + GetVirtualMachineNetworkAdapterAddress(string) (string, error) + + //Set the vlan to use for switch + SetNetworkAdapterVlanId(string, string) error + + //Set the vlan to use for machine + SetVirtualMachineVlanId(string, string) error + + UntagVirtualMachineNetworkAdapterVlan(string, string) error + + CreateExternalVirtualSwitch(string, string) error + + GetVirtualMachineSwitchName(string) (string, error) + + ConnectVirtualMachineNetworkAdapterToSwitch(string, string) error + + CreateVirtualSwitch(string, string) (bool, error) + + DeleteVirtualSwitch(string) error + + CreateVirtualMachine(string, string, int64, int64, string, uint) error + + DeleteVirtualMachine(string) error + + SetVirtualMachineCpu(string, uint) error + + SetSecureBoot(string, bool) error + + EnableVirtualMachineIntegrationService(string, string) error + + ExportVirtualMachine(string, string) error + + CompactDisks(string, string) error + + CopyExportedVirtualMachine(string, string, string, string) error + + RestartVirtualMachine(string) error + + CreateDvdDrive(string, uint) (uint, uint, error) + + MountDvdDrive(string, string) error + + MountDvdDriveByLocation(string, string, uint, uint) error + + UnmountDvdDrive(string) error + + DeleteDvdDrive(string, string, string) error + + UnmountFloppyDrive(vmName string) error } diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index b9371a8c0..5662ce0d8 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -39,6 +39,14 @@ func (d *HypervPS4Driver) IsRunning(vmName string) (bool, error) { return hyperv.IsRunning(vmName) } +func (d *HypervPS4Driver) IsOff(vmName string) (bool, error) { + return hyperv.IsOff(vmName) +} + +func (d *HypervPS4Driver) Uptime(vmName string) (uint64, error) { + return hyperv.Uptime(vmName) +} + // Start starts a VM specified by the name given. func (d *HypervPS4Driver) Start(vmName string) error { return hyperv.StartVirtualMachine(vmName) @@ -97,6 +105,11 @@ func (d *HypervPS4Driver) IpAddress(mac string) (string, error) { return res, err } +// Get host name from ip address +func (d *HypervPS4Driver) GetHostName(ip string) (string, error) { + return powershell.GetHostName(ip) +} + // Finds the IP address of a host adapter connected to switch func (d *HypervPS4Driver) GetHostAdapterIpAddressForSwitch(switchName string) (string, error) { res, err := hyperv.GetHostAdapterIpAddressForSwitch(switchName) @@ -117,6 +130,105 @@ func (d *HypervPS4Driver) TypeScanCodes(vmName string, scanCodes string) error { return hyperv.TypeScanCodes(vmName, scanCodes) } +// Get network adapter address +func (d *HypervPS4Driver) GetVirtualMachineNetworkAdapterAddress(vmName string) (string, error) { + return hyperv.GetVirtualMachineNetworkAdapterAddress(vmName) +} + +//Set the vlan to use for switch +func (d *HypervPS4Driver) SetNetworkAdapterVlanId(switchName string, vlanId string) error { + return hyperv.SetNetworkAdapterVlanId(switchName, vlanId) +} + +//Set the vlan to use for machine +func (d *HypervPS4Driver) SetVirtualMachineVlanId(vmName string, vlanId string) error { + return hyperv.SetVirtualMachineVlanId(vmName, vlanId) +} + +func (d *HypervPS4Driver) UntagVirtualMachineNetworkAdapterVlan(vmName string, switchName string) error { + return hyperv.UntagVirtualMachineNetworkAdapterVlan(vmName, switchName) +} + +func (d *HypervPS4Driver) CreateExternalVirtualSwitch(vmName string, switchName string) error { + return hyperv.CreateExternalVirtualSwitch(vmName, switchName) +} + +func (d *HypervPS4Driver) GetVirtualMachineSwitchName(vmName string) (string, error) { + return hyperv.GetVirtualMachineSwitchName(vmName) +} + +func (d *HypervPS4Driver) ConnectVirtualMachineNetworkAdapterToSwitch(vmName string, switchName string) error { + return hyperv.ConnectVirtualMachineNetworkAdapterToSwitch(vmName, switchName) +} + +func (d *HypervPS4Driver) DeleteVirtualSwitch(switchName string) error { + return hyperv.DeleteVirtualSwitch(switchName) +} + +func (d *HypervPS4Driver) CreateVirtualSwitch(switchName string, switchType string) (bool, error) { + return hyperv.CreateVirtualSwitch(switchName, switchType) +} + +func (d *HypervPS4Driver) CreateVirtualMachine(vmName string, path string, ram int64, diskSize int64, switchName string, generation uint) error { + return hyperv.CreateVirtualMachine(vmName, path, ram, diskSize, switchName, generation) +} + +func (d *HypervPS4Driver) DeleteVirtualMachine(vmName string) error { + return hyperv.DeleteVirtualMachine(vmName) +} + +func (d *HypervPS4Driver) SetVirtualMachineCpu(vmName string, cpu uint) error { + return hyperv.SetVirtualMachineCpu(vmName, cpu) +} + +func (d *HypervPS4Driver) SetSecureBoot(vmName string, enable bool) error { + return hyperv.SetSecureBoot(vmName, enable) +} + +func (d *HypervPS4Driver) EnableVirtualMachineIntegrationService(vmName string, integrationServiceName string) error { + return hyperv.EnableVirtualMachineIntegrationService(vmName, integrationServiceName) +} + +func (d *HypervPS4Driver) ExportVirtualMachine(vmName string, path string) error { + return hyperv.ExportVirtualMachine(vmName, path) +} + +func (d *HypervPS4Driver) CompactDisks(expPath string, vhdDir string) error { + return hyperv.CompactDisks(expPath, vhdDir) +} + +func (d *HypervPS4Driver) CopyExportedVirtualMachine(expPath string, outputPath string, vhdDir string, vmDir string) error { + return hyperv.CopyExportedVirtualMachine(expPath, outputPath, vhdDir, vmDir) +} + +func (d *HypervPS4Driver) RestartVirtualMachine(vmName string) error { + return hyperv.RestartVirtualMachine(vmName) +} + +func (d *HypervPS4Driver) CreateDvdDrive(vmName string, generation uint) (uint, uint, error) { + return hyperv.CreateDvdDrive(vmName, generation) +} + +func (d *HypervPS4Driver) MountDvdDrive(vmName string, path string) error { + return hyperv.MountDvdDrive(vmName, path) +} + +func (d *HypervPS4Driver) MountDvdDriveByLocation(vmName string, path string, controllerNumber uint, controllerLocation uint) error { + return hyperv.MountDvdDriveByLocation(vmName, path, controllerNumber, controllerLocation) +} + +func (d *HypervPS4Driver) UnmountDvdDrive(vmName string) error { + return hyperv.UnmountDvdDrive(vmName) +} + +func (d *HypervPS4Driver) DeleteDvdDrive(vmName string, controllerNumber string, controllerLocation string) error { + return hyperv.DeleteDvdDrive(vmName, controllerNumber, controllerLocation) +} + +func (d *HypervPS4Driver) UnmountFloppyDrive(vmName string) error { + return hyperv.UnmountFloppyDrive(vmName) +} + func (d *HypervPS4Driver) verifyPSVersion() error { log.Printf("Enter method: %s", "verifyPSVersion") diff --git a/builder/hyperv/common/step_configure_ip.go b/builder/hyperv/common/step_configure_ip.go index ea35818e9..4396a1b9a 100644 --- a/builder/hyperv/common/step_configure_ip.go +++ b/builder/hyperv/common/step_configure_ip.go @@ -8,19 +8,16 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" + "log" "strings" "time" - "log" - powershell "github.com/mitchellh/packer/powershell" - "github.com/mitchellh/packer/powershell/hyperv" ) - type StepConfigureIp struct { } func (s *StepConfigureIp) Run(state multistep.StateBag) multistep.StepAction { -// driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) errorMsg := "Error configuring ip address: %s" @@ -34,7 +31,7 @@ func (s *StepConfigureIp) Run(state multistep.StateBag) multistep.StepAction { var ip string for count != 0 { - cmdOut, err := hyperv.GetVirtualMachineNetworkAdapterAddress(vmName) + cmdOut, err := driver.GetVirtualMachineNetworkAdapterAddress(vmName) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) @@ -45,7 +42,7 @@ func (s *StepConfigureIp) Run(state multistep.StateBag) multistep.StepAction { ip = strings.TrimSpace(string(cmdOut)) if ip != "False" { - break; + break } log.Println(fmt.Sprintf("Waiting for another %v minutes...", uint(duration))) @@ -53,7 +50,7 @@ func (s *StepConfigureIp) Run(state multistep.StateBag) multistep.StepAction { count-- } - if(count == 0){ + if count == 0 { err := fmt.Errorf(errorMsg, "IP address assigned to the adapter is empty") state.Put("error", err) ui.Error(err.Error()) @@ -62,7 +59,7 @@ func (s *StepConfigureIp) Run(state multistep.StateBag) multistep.StepAction { ui.Say("ip address is " + ip) - hostName, err := powershell.GetHostName(ip); + hostName, err := driver.GetHostName(ip) if err != nil { state.Put("error", err) ui.Error(err.Error()) @@ -80,4 +77,3 @@ func (s *StepConfigureIp) Run(state multistep.StateBag) multistep.StepAction { func (s *StepConfigureIp) Cleanup(state multistep.StateBag) { // do nothing } - diff --git a/builder/hyperv/common/step_configure_vlan.go b/builder/hyperv/common/step_configure_vlan.go index 508b43e98..94f445ddd 100644 --- a/builder/hyperv/common/step_configure_vlan.go +++ b/builder/hyperv/common/step_configure_vlan.go @@ -8,20 +8,14 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/powershell/hyperv" -) - - -const( - vlanId = "1724" ) type StepConfigureVlan struct { + vlanId string } func (s *StepConfigureVlan) Run(state multistep.StateBag) multistep.StepAction { - //config := state.Get("config").(*config) - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) errorMsg := "Error configuring vlan: %s" @@ -30,7 +24,13 @@ func (s *StepConfigureVlan) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Configuring vlan...") - err := hyperv.SetNetworkAdapterVlanId(switchName, vlanId) + vlanId := s.vlanId + + if vlanId == "" { + vlanId = "1724" + } + + err := driver.SetNetworkAdapterVlanId(switchName, vlanId) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) @@ -38,7 +38,7 @@ func (s *StepConfigureVlan) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - err = hyperv.SetVirtualMachineVlanId(vmName, vlanId) + err = driver.SetVirtualMachineVlanId(vmName, vlanId) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) diff --git a/builder/hyperv/common/step_create_external_switch.go b/builder/hyperv/common/step_create_external_switch.go index 1685d069e..4ab1c335b 100644 --- a/builder/hyperv/common/step_create_external_switch.go +++ b/builder/hyperv/common/step_create_external_switch.go @@ -5,11 +5,10 @@ package common import ( + "code.google.com/p/go-uuid/uuid" "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "code.google.com/p/go-uuid/uuid" - "github.com/mitchellh/packer/powershell/hyperv" ) // This step creates switch for VM. @@ -17,12 +16,12 @@ import ( // Produces: // SwitchName string - The name of the Switch type StepCreateExternalSwitch struct { - SwitchName string + SwitchName string oldSwitchName string } func (s *StepCreateExternalSwitch) Run(state multistep.StateBag) multistep.StepAction { - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) @@ -33,16 +32,16 @@ func (s *StepCreateExternalSwitch) Run(state multistep.StateBag) multistep.StepA packerExternalSwitchName := "paes_" + uuid.New() - err = hyperv.CreateExternalVirtualSwitch(vmName, packerExternalSwitchName) + err = driver.CreateExternalVirtualSwitch(vmName, packerExternalSwitchName) if err != nil { err := fmt.Errorf("Error creating switch: %s", err) state.Put(errorMsg, err) ui.Error(err.Error()) - s.SwitchName = ""; + s.SwitchName = "" return multistep.ActionHalt } - - switchName, err := hyperv.GetVirtualMachineSwitchName(vmName) + + switchName, err := driver.GetVirtualMachineSwitchName(vmName) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) @@ -59,10 +58,10 @@ func (s *StepCreateExternalSwitch) Run(state multistep.StateBag) multistep.StepA ui.Say("External switch name is: '" + switchName + "'") - if(switchName != packerExternalSwitchName){ + if switchName != packerExternalSwitchName { s.SwitchName = "" } else { - s.SwitchName = packerExternalSwitchName + s.SwitchName = packerExternalSwitchName s.oldSwitchName = state.Get("SwitchName").(string) } @@ -76,7 +75,7 @@ func (s *StepCreateExternalSwitch) Cleanup(state multistep.StateBag) { if s.SwitchName == "" { return } - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) @@ -92,7 +91,7 @@ func (s *StepCreateExternalSwitch) Cleanup(state multistep.StateBag) { return } - err = hyperv.ConnectVirtualMachineNetworkAdapterToSwitch(vmName, s.oldSwitchName) + err = driver.ConnectVirtualMachineNetworkAdapterToSwitch(vmName, s.oldSwitchName) if err != nil { ui.Error(fmt.Sprintf(errMsg, err)) return @@ -100,7 +99,7 @@ func (s *StepCreateExternalSwitch) Cleanup(state multistep.StateBag) { state.Put("SwitchName", s.oldSwitchName) - err = hyperv.DeleteVirtualSwitch(s.SwitchName) + err = driver.DeleteVirtualSwitch(s.SwitchName) if err != nil { ui.Error(fmt.Sprintf(errMsg, err)) } diff --git a/builder/hyperv/common/step_create_switch.go b/builder/hyperv/common/step_create_switch.go index 5620e3f93..b8eb02962 100644 --- a/builder/hyperv/common/step_create_switch.go +++ b/builder/hyperv/common/step_create_switch.go @@ -8,13 +8,12 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/powershell/hyperv" ) const ( SwitchTypeInternal = "Internal" - SwitchTypePrivate = "Private" - DefaultSwitchType = SwitchTypeInternal + SwitchTypePrivate = "Private" + DefaultSwitchType = SwitchTypeInternal ) // This step creates switch for VM. @@ -23,21 +22,21 @@ const ( // SwitchName string - The name of the Switch type StepCreateSwitch struct { // Specifies the name of the switch to be created. - SwitchName string + SwitchName string // Specifies the type of the switch to be created. Allowed values are Internal and Private. To create an External // virtual switch, specify either the NetAdapterInterfaceDescription or the NetAdapterName parameter, which // implicitly set the type of the virtual switch to External. - SwitchType string + SwitchType string // Specifies the name of the network adapter to be bound to the switch to be created. NetAdapterName string // Specifies the interface description of the network adapter to be bound to the switch to be created. NetAdapterInterfaceDescription string - createdSwitch bool + createdSwitch bool } func (s *StepCreateSwitch) Run(state multistep.StateBag) multistep.StepAction { - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) if len(s.SwitchType) == 0 { @@ -46,12 +45,12 @@ func (s *StepCreateSwitch) Run(state multistep.StateBag) multistep.StepAction { ui.Say(fmt.Sprintf("Creating switch '%v' if required...", s.SwitchName)) - createdSwitch, err := hyperv.CreateVirtualSwitch(s.SwitchName, s.SwitchType) + createdSwitch, err := driver.CreateVirtualSwitch(s.SwitchName, s.SwitchType) if err != nil { err := fmt.Errorf("Error creating switch: %s", err) state.Put("error", err) ui.Error(err.Error()) - s.SwitchName = ""; + s.SwitchName = "" return multistep.ActionHalt } @@ -72,11 +71,11 @@ func (s *StepCreateSwitch) Cleanup(state multistep.StateBag) { return } - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) ui.Say("Unregistering and deleting switch...") - err := hyperv.DeleteVirtualSwitch(s.SwitchName) + err := driver.DeleteVirtualSwitch(s.SwitchName) if err != nil { ui.Error(fmt.Sprintf("Error deleting switch: %s", err)) } diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index 6418dbea0..6d142cb0b 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -8,8 +8,6 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/powershell/hyperv" - "strconv" ) // This step creates the actual virtual machine. @@ -17,50 +15,47 @@ import ( // Produces: // VMName string - The name of the VM type StepCreateVM struct { - VMName string - SwitchName string - RamSizeMB uint - DiskSize uint - Generation uint - Cpu uint + VMName string + SwitchName string + RamSizeMB uint + DiskSize uint + Generation uint + Cpu uint EnabeSecureBoot bool } func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) ui.Say("Creating virtual machine...") path := state.Get("packerTempDir").(string) // convert the MB to bytes - ramBytes := int64(s.RamSizeMB * 1024 * 1024) - diskSizeBytes := int64(s.DiskSize * 1024 * 1024) + ram := int64(s.RamSizeMB * 1024 * 1024) + diskSize := int64(s.DiskSize * 1024 * 1024) - ram := strconv.FormatInt(ramBytes, 10) - diskSize := strconv.FormatInt(diskSizeBytes, 10) switchName := s.SwitchName - generation := strconv.FormatInt(int64(s.Generation), 10) - cpu := strconv.FormatInt(int64(s.Cpu), 10) enabeSecureBoot := s.EnabeSecureBoot - err := hyperv.CreateVirtualMachine(s.VMName, path, ram, diskSize, switchName, generation) + err := driver.CreateVirtualMachine(s.VMName, path, ram, diskSize, switchName, s.Generation) if err != nil { err := fmt.Errorf("Error creating virtual machine: %s", err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } - - err = hyperv.SetVirtualMachineCpu(s.VMName, cpu) + + err = driver.SetVirtualMachineCpu(s.VMName, s.Cpu) if err != nil { err := fmt.Errorf("Error creating setting virtual machine cpu: %s", err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } - - if generation == "2" { - err = hyperv.SetSecureBoot(s.VMName, enabeSecureBoot) + + if s.Generation == 2 { + err = driver.SetSecureBoot(s.VMName, enabeSecureBoot) if err != nil { err := fmt.Errorf("Error setting secure boot: %s", err) state.Put("error", err) @@ -80,11 +75,11 @@ func (s *StepCreateVM) Cleanup(state multistep.StateBag) { return } - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) ui.Say("Unregistering and deleting virtual machine...") - err := hyperv.DeleteVirtualMachine(s.VMName) + err := driver.DeleteVirtualMachine(s.VMName) if err != nil { ui.Error(fmt.Sprintf("Error deleting virtual machine: %s", err)) } diff --git a/builder/hyperv/common/step_disable_vlan.go b/builder/hyperv/common/step_disable_vlan.go index 264affbdd..631c941d2 100644 --- a/builder/hyperv/common/step_disable_vlan.go +++ b/builder/hyperv/common/step_disable_vlan.go @@ -8,15 +8,13 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/powershell/hyperv" ) type StepDisableVlan struct { } func (s *StepDisableVlan) Run(state multistep.StateBag) multistep.StepAction { - //config := state.Get("config").(*config) - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) errorMsg := "Error disabling vlan: %s" @@ -25,7 +23,7 @@ func (s *StepDisableVlan) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Disabling vlan...") - err := hyperv.UntagVirtualMachineNetworkAdapterVlan(vmName, switchName) + err := driver.UntagVirtualMachineNetworkAdapterVlan(vmName, switchName) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) diff --git a/builder/hyperv/common/step_enable_integration_service.go b/builder/hyperv/common/step_enable_integration_service.go index 89259d4d8..05b148232 100644 --- a/builder/hyperv/common/step_enable_integration_service.go +++ b/builder/hyperv/common/step_enable_integration_service.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/powershell/hyperv" ) type StepEnableIntegrationService struct { @@ -16,13 +15,14 @@ type StepEnableIntegrationService struct { } func (s *StepEnableIntegrationService) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) ui.Say("Enabling Integration Service...") vmName := state.Get("vmName").(string) s.name = "Guest Service Interface" - err := hyperv.EnableVirtualMachineIntegrationService(vmName, s.name) + err := driver.EnableVirtualMachineIntegrationService(vmName, s.name) if err != nil { err := fmt.Errorf("Error enabling Integration Service: %s", err) diff --git a/builder/hyperv/common/step_export_vm.go b/builder/hyperv/common/step_export_vm.go index d226c25d1..2ee10e16c 100644 --- a/builder/hyperv/common/step_export_vm.go +++ b/builder/hyperv/common/step_export_vm.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/powershell/hyperv" "io/ioutil" "path/filepath" ) @@ -24,6 +23,7 @@ type StepExportVm struct { } func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) var err error @@ -45,7 +45,7 @@ func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Exporting vm...") - err = hyperv.ExportVirtualMachine(vmName, vmExportPath) + err = driver.ExportVirtualMachine(vmName, vmExportPath) if err != nil { errorMsg = "Error exporting vm: %s" err := fmt.Errorf(errorMsg, err) @@ -61,7 +61,7 @@ func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Skipping disk compaction...") } else { ui.Say("Compacting disks...") - err = hyperv.CompactDisks(expPath, vhdDir) + err = driver.CompactDisks(expPath, vhdDir) if err != nil { errorMsg = "Error compacting disks: %s" err := fmt.Errorf(errorMsg, err) @@ -72,7 +72,7 @@ func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction { } ui.Say("Coping to output dir...") - err = hyperv.CopyExportedVirtualMachine(expPath, outputPath, vhdDir, vmDir) + err = driver.CopyExportedVirtualMachine(expPath, outputPath, vhdDir, vmDir) if err != nil { errorMsg = "Error exporting vm: %s" err := fmt.Errorf(errorMsg, err) diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go index 56856566f..a9129a378 100644 --- a/builder/hyperv/common/step_mount_dvddrive.go +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -9,7 +9,6 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" powershell "github.com/mitchellh/packer/powershell" - "github.com/mitchellh/packer/powershell/hyperv" ) type StepMountDvdDrive struct { @@ -18,7 +17,7 @@ type StepMountDvdDrive struct { } func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) errorMsg := "Error mounting dvd drive: %s" @@ -56,7 +55,7 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Mounting dvd drive...") - err = hyperv.MountDvdDrive(vmName, isoPath) + err = driver.MountDvdDrive(vmName, isoPath) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) @@ -74,6 +73,8 @@ func (s *StepMountDvdDrive) Cleanup(state multistep.StateBag) { return } + driver := state.Get("driver").(Driver) + errorMsg := "Error unmounting dvd drive: %s" vmName := state.Get("vmName").(string) @@ -81,7 +82,7 @@ func (s *StepMountDvdDrive) Cleanup(state multistep.StateBag) { ui.Say("Unmounting dvd drive...") - err := hyperv.UnmountDvdDrive(vmName) + err := driver.UnmountDvdDrive(vmName) if err != nil { ui.Error(fmt.Sprintf(errorMsg, err)) } diff --git a/builder/hyperv/common/step_mount_floppydrive.go b/builder/hyperv/common/step_mount_floppydrive.go index c30054141..78f9f0914 100644 --- a/builder/hyperv/common/step_mount_floppydrive.go +++ b/builder/hyperv/common/step_mount_floppydrive.go @@ -6,28 +6,24 @@ package common import ( "fmt" - "os" - "strings" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/powershell" "github.com/mitchellh/packer/powershell/hyperv" - "log" "io" "io/ioutil" + "log" + "os" "path/filepath" + "strings" ) - -const( +const ( FloppyFileName = "assets.vfd" ) - - - type StepSetUnattendedProductKey struct { - Files []string + Files []string ProductKey string } @@ -36,15 +32,15 @@ func (s *StepSetUnattendedProductKey) Run(state multistep.StateBag) multistep.St if s.ProductKey == "" { ui.Say("No product key specified...") - return multistep.ActionContinue + return multistep.ActionContinue } index := -1 for i, value := range s.Files { - if s.caseInsensitiveContains(value, "Autounattend.xml") { - index = i - break - } + if s.caseInsensitiveContains(value, "Autounattend.xml") { + index = i + break + } } ui.Say("Setting product key in Autounattend.xml...") @@ -59,10 +55,9 @@ func (s *StepSetUnattendedProductKey) Run(state multistep.StateBag) multistep.St return multistep.ActionContinue } - func (s *StepSetUnattendedProductKey) caseInsensitiveContains(str, substr string) bool { - str, substr = strings.ToUpper(str), strings.ToUpper(substr) - return strings.Contains(str, substr) + str, substr = strings.ToUpper(str), strings.ToUpper(substr) + return strings.Contains(str, substr) } func (s *StepSetUnattendedProductKey) copyAutounattend(path string) (string, error) { @@ -92,12 +87,9 @@ func (s *StepSetUnattendedProductKey) copyAutounattend(path string) (string, err return autounattend, nil } - func (s *StepSetUnattendedProductKey) Cleanup(state multistep.StateBag) { } - - type StepMountFloppydrive struct { floppyPath string } @@ -119,7 +111,7 @@ func (s *StepMountFloppydrive) Run(state multistep.StateBag) multistep.StepActio if err != nil { state.Put("error", fmt.Errorf("Error preparing floppy: %s", err)) return multistep.ActionHalt - } + } ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) @@ -135,7 +127,8 @@ func (s *StepMountFloppydrive) Run(state multistep.StateBag) multistep.StepActio // Track the path so that we can unregister it from Hyper-V later s.floppyPath = floppyPath - return multistep.ActionContinue} + return multistep.ActionContinue +} func (s *StepMountFloppydrive) Cleanup(state multistep.StateBag) { if s.floppyPath == "" { diff --git a/builder/hyperv/common/step_mount_integration_services.go b/builder/hyperv/common/step_mount_integration_services.go index 63e775a0d..317296916 100644 --- a/builder/hyperv/common/step_mount_integration_services.go +++ b/builder/hyperv/common/step_mount_integration_services.go @@ -8,9 +8,10 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - powershell "github.com/mitchellh/packer/powershell" + hyperv "github.com/mitchellh/packer/powershell/hyperv" "log" "os" + "strconv" ) type StepMountSecondaryDvdImages struct { @@ -88,67 +89,22 @@ func (s *StepMountSecondaryDvdImages) addAndMountIntegrationServicesSetupDisk(vm } func (s *StepMountSecondaryDvdImages) addAndMountDvdDisk(vmName string, isoPath string) (DvdControllerProperties, error) { - var properties DvdControllerProperties - var script powershell.ScriptBuilder - powershell := new(powershell.PowerShellCmd) - controllerNumber := "0" - if s.Generation < 2 { - // get the controller number that the OS install disk is mounted on - // generation 1 requires dvd to be added to ide controller, generation 2 uses scsi for dvd drives - script.Reset() - script.WriteLine("param([string]$vmName)") - script.WriteLine("$dvdDrives = (Get-VMDvdDrive -VMName $vmName)") - script.WriteLine("$lastControllerNumber = $dvdDrives | Sort-Object ControllerNumber | Select-Object -Last 1 | %{$_.ControllerNumber}") - script.WriteLine("if (!$lastControllerNumber) {") - script.WriteLine(" $lastControllerNumber = 0") - script.WriteLine("} elseif (!$lastControllerNumber -or ($dvdDrives | ?{ $_.ControllerNumber -eq $lastControllerNumber} | measure).count -gt 1) {") - script.WriteLine(" $lastControllerNumber += 1") - script.WriteLine("}") - script.WriteLine("$lastControllerNumber") - controllerNumber, err := powershell.Output(script.String(), vmName) - if err != nil { - return properties, err - } - - if controllerNumber != "0" && controllerNumber != "1" { - //There are only 2 ide controllers, try to use the one the hdd is attached too - controllerNumber = "0" - } - } - - script.Reset() - script.WriteLine("param([string]$vmName,[int]$controllerNumber)") - script.WriteLine("Add-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber") - err := powershell.Run(script.String(), vmName, controllerNumber) + controllerNumber, controllerLocation, err := hyperv.CreateDvdDrive(vmName, s.Generation) if err != nil { return properties, err } - // we could try to get the controller location and number in one call, but this way we do not - // need to parse the output - script.Reset() - script.WriteLine("param([string]$vmName)") - script.WriteLine("(Get-VMDvdDrive -VMName $vmName | Where-Object {$_.Path -eq $null}).ControllerLocation") - controllerLocation, err := powershell.Output(script.String(), vmName) - if err != nil { - return properties, err - } + properties.ControllerNumber = strconv.FormatInt(int64(controllerNumber), 10) + properties.ControllerLocation = strconv.FormatInt(int64(controllerLocation), 10) - script.Reset() - script.WriteLine("param([string]$vmName,[string]$path,[string]$controllerNumber,[string]$controllerLocation)") - script.WriteLine("Set-VMDvdDrive -VMName $vmName -Path $path -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation") - - err = powershell.Run(script.String(), vmName, isoPath, controllerNumber, controllerLocation) + err = hyperv.MountDvdDriveByLocation(vmName, isoPath, controllerNumber, controllerLocation) if err != nil { return properties, err } log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", isoPath, controllerNumber, controllerLocation)) - properties.ControllerNumber = controllerNumber - properties.ControllerLocation = controllerLocation - return properties, nil } diff --git a/builder/hyperv/common/step_polling_installation.go b/builder/hyperv/common/step_polling_installation.go index c462f3da9..34577de54 100644 --- a/builder/hyperv/common/step_polling_installation.go +++ b/builder/hyperv/common/step_polling_installation.go @@ -5,15 +5,14 @@ package common import ( + "bytes" "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "time" -// "net" "log" "os/exec" "strings" - "bytes" + "time" ) const port string = "13000" @@ -29,36 +28,6 @@ func (s *StepPollingInstalation) Run(state multistep.StateBag) multistep.StepAct vmIp := state.Get("ip").(string) ui.Say("Start polling VM to check the installation is complete...") -/* - count := 30 - var minutes time.Duration = 1 - sleepMin := time.Minute * minutes - host := vmIp + ":" + port - - timeoutSec := time.Second * 15 - - for count > 0 { - ui.Say(fmt.Sprintf("Connecting vm (%s)...", host )) - conn, err := net.DialTimeout("tcp", host, timeoutSec) - if err == nil { - ui.Say("Done!") - conn.Close() - break; - } - - log.Println(err) - ui.Say(fmt.Sprintf("Waiting more %v minutes...", uint(minutes))) - time.Sleep(sleepMin) - count-- - } - - if count == 0 { - err := fmt.Errorf(errorMsg, "a signal from vm was not received in a given time period ") - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } -*/ host := "'" + vmIp + "'," + port var blockBuffer bytes.Buffer @@ -73,7 +42,7 @@ func (s *StepPollingInstalation) Run(state multistep.StateBag) multistep.StepAct var res string for count > 0 { - log.Println(fmt.Sprintf("Connecting vm (%s)...", host )) + log.Println(fmt.Sprintf("Connecting vm (%s)...", host)) cmd := exec.Command("powershell", blockBuffer.String()) cmdOut, err := cmd.Output() if err != nil { @@ -88,8 +57,8 @@ func (s *StepPollingInstalation) Run(state multistep.StateBag) multistep.StepAct if res != "False" { ui.Say("Signal was received from the VM") // Sleep before starting provision - time.Sleep(time.Second*30) - break; + time.Sleep(time.Second * 30) + break } log.Println(fmt.Sprintf("Slipping for more %v seconds...", uint(duration))) diff --git a/builder/hyperv/common/step_reboot_vm.go b/builder/hyperv/common/step_reboot_vm.go index 3053a0855..7be0f3517 100644 --- a/builder/hyperv/common/step_reboot_vm.go +++ b/builder/hyperv/common/step_reboot_vm.go @@ -9,14 +9,13 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "time" - "github.com/mitchellh/packer/powershell/hyperv" ) type StepRebootVm struct { } func (s *StepRebootVm) Run(state multistep.StateBag) multistep.StepAction { - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) errorMsg := "Error rebooting vm: %s" @@ -24,7 +23,7 @@ func (s *StepRebootVm) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Rebooting vm...") - err := hyperv.RestartVirtualMachine(vmName) + err := driver.RestartVirtualMachine(vmName) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) diff --git a/builder/hyperv/common/step_unmount_dvddrive.go b/builder/hyperv/common/step_unmount_dvddrive.go index 57ffb422d..c5a4f2f84 100644 --- a/builder/hyperv/common/step_unmount_dvddrive.go +++ b/builder/hyperv/common/step_unmount_dvddrive.go @@ -8,22 +8,20 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/powershell/hyperv" ) - type StepUnmountDvdDrive struct { } func (s *StepUnmountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) - + ui.Say("Unmounting dvd drive...") - err := hyperv.UnmountDvdDrive(vmName) + err := driver.UnmountDvdDrive(vmName) if err != nil { err := fmt.Errorf("Error unmounting dvd drive: %s", err) state.Put("error", err) diff --git a/builder/hyperv/common/step_unmount_floppydrive.go b/builder/hyperv/common/step_unmount_floppydrive.go index 87d1fd4a3..21ae1dcc1 100644 --- a/builder/hyperv/common/step_unmount_floppydrive.go +++ b/builder/hyperv/common/step_unmount_floppydrive.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/powershell/hyperv" ) type StepUnmountFloppyDrive struct { @@ -16,7 +15,7 @@ type StepUnmountFloppyDrive struct { } func (s *StepUnmountFloppyDrive) Run(state multistep.StateBag) multistep.StepAction { - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) if s.Generation > 1 { @@ -28,7 +27,7 @@ func (s *StepUnmountFloppyDrive) Run(state multistep.StateBag) multistep.StepAct ui.Say("Unmounting floppy drive (Run)...") - err := hyperv.UnmountFloppyDrive(vmName) + err := driver.UnmountFloppyDrive(vmName) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) diff --git a/builder/hyperv/common/step_unmount_integration_services.go b/builder/hyperv/common/step_unmount_integration_services.go index d79245782..d2719744b 100644 --- a/builder/hyperv/common/step_unmount_integration_services.go +++ b/builder/hyperv/common/step_unmount_integration_services.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - powershell "github.com/mitchellh/packer/powershell" "log" ) @@ -16,6 +15,7 @@ type StepUnmountSecondaryDvdImages struct { } func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) ui.Say("Unmounting Integration Services Setup Disk...") @@ -28,17 +28,7 @@ func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep. log.Println(fmt.Sprintf("Found DVD properties %d", len(dvdProperties))) for _, dvdProperty := range dvdProperties { - controllerNumber := dvdProperty.ControllerNumber - controllerLocation := dvdProperty.ControllerLocation - - var script powershell.ScriptBuilder - powershell := new(powershell.PowerShellCmd) - - script.WriteLine("param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation)") - script.WriteLine("$vmDvdDrive = Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation") - script.WriteLine("if (!$vmDvdDrive) {throw 'unable to find dvd drive'}") - script.WriteLine("Remove-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation") - err := powershell.Run(script.String(), vmName, controllerNumber, controllerLocation) + err := driver.DeleteDvdDrive(vmName, dvdProperty.ControllerNumber, dvdProperty.ControllerLocation) if err != nil { state.Put("error", err) ui.Error(err.Error()) diff --git a/builder/hyperv/common/step_wait_for_install_to_complete.go b/builder/hyperv/common/step_wait_for_install_to_complete.go index c4fafbef0..46fc7e5cd 100644 --- a/builder/hyperv/common/step_wait_for_install_to_complete.go +++ b/builder/hyperv/common/step_wait_for_install_to_complete.go @@ -8,10 +8,7 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "strings" - "strconv" "time" - powershell "github.com/mitchellh/packer/powershell" ) const ( @@ -22,29 +19,25 @@ type StepWaitForPowerOff struct { } func (s *StepWaitForPowerOff) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) ui.Say("Waiting for vm to be powered down...") - var script powershell.ScriptBuilder - script.WriteLine("param([string]$vmName)") - script.WriteLine("(Get-VM -Name $vmName).State -eq [Microsoft.HyperV.PowerShell.VMState]::Off") - isOffScript := script.String() - for { - powershell := new(powershell.PowerShellCmd) - cmdOut, err := powershell.Output(isOffScript, vmName); + isOff, err := driver.IsOff(vmName) + if err != nil { - err := fmt.Errorf("Error checking VM's state: %s", err) + err := fmt.Errorf("Error checking if vm is off: %s", err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } - if cmdOut == "True" { + if isOff { break } else { - time.Sleep(time.Second * SleepSeconds); + time.Sleep(time.Second * SleepSeconds) } } @@ -56,29 +49,24 @@ func (s *StepWaitForPowerOff) Cleanup(state multistep.StateBag) { type StepWaitForInstallToComplete struct { ExpectedRebootCount uint - ActionName string + ActionName string } func (s *StepWaitForInstallToComplete) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) - if(len(s.ActionName)>0){ - ui.Say(fmt.Sprintf("%v ! Waiting for VM to reboot %v times...",s.ActionName, s.ExpectedRebootCount)) + if len(s.ActionName) > 0 { + ui.Say(fmt.Sprintf("%v ! Waiting for VM to reboot %v times...", s.ActionName, s.ExpectedRebootCount)) } var rebootCount uint var lastUptime uint64 - var script powershell.ScriptBuilder - script.WriteLine("param([string]$vmName)") - script.WriteLine("(Get-VM -Name $vmName).Uptime.TotalSeconds") - - uptimeScript := script.String() - for rebootCount < s.ExpectedRebootCount { - powershell := new(powershell.PowerShellCmd) - cmdOut, err := powershell.Output(uptimeScript, vmName); + uptime, err := driver.Uptime(vmName) + if err != nil { err := fmt.Errorf("Error checking uptime: %s", err) state.Put("error", err) @@ -86,20 +74,18 @@ func (s *StepWaitForInstallToComplete) Run(state multistep.StateBag) multistep.S return multistep.ActionHalt } - uptime, _ := strconv.ParseUint(strings.TrimSpace(string(cmdOut)), 10, 64) - if uint64(uptime) < lastUptime { + if uptime < lastUptime { rebootCount++ ui.Say(fmt.Sprintf("%v -> Detected reboot %v after %v seconds...", s.ActionName, rebootCount, lastUptime)) } lastUptime = uptime - if (rebootCount < s.ExpectedRebootCount) { - time.Sleep(time.Second * SleepSeconds); + if rebootCount < s.ExpectedRebootCount { + time.Sleep(time.Second * SleepSeconds) } } - return multistep.ActionContinue } diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 3cf5d8a38..162b048fa 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -2,6 +2,7 @@ package hyperv import ( "github.com/mitchellh/packer/powershell" + "strconv" "strings" ) @@ -50,6 +51,73 @@ $ip return cmdOut, err } +func CreateDvdDrive(vmName string, generation uint) (uint, uint, error) { + var ps powershell.PowerShellCmd + var script string + var controllerNumber uint + controllerNumber = 0 + if generation < 2 { + // get the controller number that the OS install disk is mounted on + // generation 1 requires dvd to be added to ide controller, generation 2 uses scsi for dvd drives + script = ` +param([string]$vmName) +$dvdDrives = (Get-VMDvdDrive -VMName $vmName) +$lastControllerNumber = $dvdDrives | Sort-Object ControllerNumber | Select-Object -Last 1 | %{$_.ControllerNumber} +if (!$lastControllerNumber) { + $lastControllerNumber = 0 +} elseif (!$lastControllerNumber -or ($dvdDrives | ?{ $_.ControllerNumber -eq $lastControllerNumber} | measure).count -gt 1) { + $lastControllerNumber += 1 +} +$lastControllerNumber +` + cmdOut, err := ps.Output(script, vmName) + if err != nil { + return 0, 0, err + } + + controllerNumberTemp, err := strconv.ParseUint(strings.TrimSpace(cmdOut), 10, 64) + if err != nil { + return 0, 0, err + } + + controllerNumber = uint(controllerNumberTemp) + + if controllerNumber != 0 && controllerNumber != 1 { + //There are only 2 ide controllers, try to use the one the hdd is attached too + controllerNumber = 0 + } + } + + script = ` +param([string]$vmName,[int]$controllerNumber) +Add-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber +` + cmdOut, err := ps.Output(script, vmName) + if err != nil { + return controllerNumber, 0, err + } + + // we could try to get the controller location and number in one call, but this way we do not + // need to parse the output + script = ` +param([string]$vmName) +(Get-VMDvdDrive -VMName $vmName | Where-Object {$_.Path -eq $null}).ControllerLocation +` + + cmdOut, err = ps.Output(script, vmName) + if err != nil { + return controllerNumber, 0, err + } + + controllerLocationTemp, err := strconv.ParseUint(strings.TrimSpace(cmdOut), 10, 64) + if err != nil { + return controllerNumber, 0, err + } + + controllerLocation := uint(controllerLocationTemp) + return controllerNumber, controllerLocation, err +} + func MountDvdDrive(vmName string, path string) error { var script = ` @@ -62,6 +130,18 @@ Set-VMDvdDrive -VMName $vmName -Path $path return err } +func MountDvdDriveByLocation(vmName string, path string, controllerNumber uint, controllerLocation uint) error { + + var script = ` +param([string]$vmName,[string]$path,[string]$controllerNumber,[string]$controllerLocation) +Set-VMDvdDrive -VMName $vmName -Path $path -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, path, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10)) + return err +} + func UnmountDvdDrive(vmName string) error { var script = ` param([string]$vmName) @@ -73,6 +153,19 @@ Get-VMDvdDrive -VMName $vmName | Set-VMDvdDrive -Path $null return err } +func DeleteDvdDrive(vmName string, controllerNumber string, controllerLocation string) error { + var script = ` +param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation) +$vmDvdDrive = Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation +if (!$vmDvdDrive) {throw 'unable to find dvd drive'} +Remove-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, controllerNumber, controllerLocation) + return err +} + func MountFloppyDrive(vmName string, path string) error { var script = ` param([string]$vmName, [string]$path) @@ -96,9 +189,9 @@ Set-VMFloppyDiskDrive -VMName $vmName -Path $null return err } -func CreateVirtualMachine(vmName string, path string, ram string, diskSize string, switchName string, generation string) error { +func CreateVirtualMachine(vmName string, path string, ram int64, diskSize int64, switchName string, generation uint) error { - if generation == "2" { + if generation == 2 { var script = ` param([string]$vmName, [string]$path, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName, [int]$generation) $vhdx = $vmName + '.vhdx' @@ -106,7 +199,7 @@ $vhdPath = Join-Path -Path $path -ChildPath $vhdx New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName -Generation $generation ` var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, path, ram, diskSize, switchName, generation) + err := ps.Run(script, vmName, path, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), switchName, strconv.FormatInt(int64(generation), 10)) return err } else { var script = ` @@ -116,12 +209,12 @@ $vhdPath = Join-Path -Path $path -ChildPath $vhdx New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName ` var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, path, ram, diskSize, switchName) + err := ps.Run(script, vmName, path, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), switchName) return err } } -func SetVirtualMachineCpu(vmName string, cpu string) error { +func SetVirtualMachineCpu(vmName string, cpu uint) error { var script = ` param([string]$vmName, [int]$cpu) @@ -129,7 +222,7 @@ Set-VMProcessor -VMName $vmName -Count $cpu ` var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, cpu) + err := ps.Run(script, vmName, strconv.FormatInt(int64(cpu), 10)) return err } @@ -425,10 +518,53 @@ $vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running var ps powershell.PowerShellCmd cmdOut, err := ps.Output(script, vmName) + + if err != nil { + return false, err + } + var isRunning = strings.TrimSpace(cmdOut) == "True" return isRunning, err } +func IsOff(vmName string) (bool, error) { + + var script = ` +param([string]$vmName) +$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue +$vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Off +` + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script, vmName) + + if err != nil { + return false, err + } + + var isRunning = strings.TrimSpace(cmdOut) == "True" + return isRunning, err +} + +func Uptime(vmName string) (uint64, error) { + + var script = ` +param([string]$vmName) +$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue +$vm.Uptime.TotalSeconds +` + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script, vmName) + + if err != nil { + return 0, err + } + + uptime, err := strconv.ParseUint(strings.TrimSpace(string(cmdOut)), 10, 64) + + return uptime, err +} + func Mac(vmName string) (string, error) { var script = ` param([string]$vmName, [int]$adapterIndex) From 010d171bec8e1c926406a89457f208be392bb6a3 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@interxion.com> Date: Fri, 30 Oct 2015 17:19:25 +0000 Subject: [PATCH 035/108] GuestAdditionsMode and GuestAdditionsPath can be set in config. If GuestAdditionsMode == "attach" it will mount the HyperV Integration Services ISO. If GuestAdditionsPath is set, then it will be used as an alternative to where the HyperV Integration Service ISO is. Included the build step to download ISO, so iso_urls works properly now. Online activation should be done via provisioner Installation of integration services should be done via provisioner Cleaned up the way dvd drives are mounted and unmounted (still need to implement feature to find unused drives before adding a new one) Cleaned up the way floppies are mounted and unmounted --- builder/hyperv/common/driver.go | 8 +- builder/hyperv/common/driver_ps_4.go | 10 +- .../common/step_execute_online_activation.go | 62 ---------- .../step_execute_online_activation_full.go | 90 -------------- builder/hyperv/common/step_mount_dvddrive.go | 84 +++++++------ .../hyperv/common/step_mount_floppydrive.go | 92 +++------------ .../common/step_mount_integration_services.go | 110 ------------------ .../hyperv/common/step_unmount_dvddrive.go | 33 ++++-- .../hyperv/common/step_unmount_floppydrive.go | 4 +- .../step_unmount_integration_services.go | 43 ------- .../step_upgrade_integration_services.go | 110 ------------------ builder/hyperv/iso/builder.go | 87 ++++++++++++-- powershell/hyperv/hyperv.go | 17 ++- 13 files changed, 196 insertions(+), 554 deletions(-) delete mode 100644 builder/hyperv/common/step_execute_online_activation.go delete mode 100644 builder/hyperv/common/step_execute_online_activation_full.go delete mode 100644 builder/hyperv/common/step_mount_integration_services.go delete mode 100644 builder/hyperv/common/step_unmount_integration_services.go delete mode 100644 builder/hyperv/common/step_upgrade_integration_services.go diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index 5afa15d77..6ceb37c0f 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -91,10 +91,14 @@ type Driver interface { MountDvdDrive(string, string) error MountDvdDriveByLocation(string, string, uint, uint) error + + SetBootDvdDrive(string, uint, uint) error UnmountDvdDrive(string) error - DeleteDvdDrive(string, string, string) error + DeleteDvdDrive(string, uint, uint) error - UnmountFloppyDrive(vmName string) error + MountFloppyDrive(string, string) error + + UnmountFloppyDrive(string) error } diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index 5662ce0d8..c6d6d4e65 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -217,14 +217,22 @@ func (d *HypervPS4Driver) MountDvdDriveByLocation(vmName string, path string, co return hyperv.MountDvdDriveByLocation(vmName, path, controllerNumber, controllerLocation) } +func (d *HypervPS4Driver) SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { + return hyperv.SetBootDvdDrive(vmName, controllerNumber, controllerLocation) +} + func (d *HypervPS4Driver) UnmountDvdDrive(vmName string) error { return hyperv.UnmountDvdDrive(vmName) } -func (d *HypervPS4Driver) DeleteDvdDrive(vmName string, controllerNumber string, controllerLocation string) error { +func (d *HypervPS4Driver) DeleteDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { return hyperv.DeleteDvdDrive(vmName, controllerNumber, controllerLocation) } +func (d *HypervPS4Driver) MountFloppyDrive(vmName string, path string) error { + return hyperv.MountFloppyDrive(vmName, path) +} + func (d *HypervPS4Driver) UnmountFloppyDrive(vmName string) error { return hyperv.UnmountFloppyDrive(vmName) } diff --git a/builder/hyperv/common/step_execute_online_activation.go b/builder/hyperv/common/step_execute_online_activation.go deleted file mode 100644 index b369934e1..000000000 --- a/builder/hyperv/common/step_execute_online_activation.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package common - -import ( - "fmt" - "bytes" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "strings" - "log" -) - -type StepExecuteOnlineActivation struct { -} - -func (s *StepExecuteOnlineActivation) Run(state multistep.StateBag) multistep.StepAction { - ui := state.Get("ui").(packer.Ui) - comm := state.Get("communicator").(packer.Communicator) - - errorMsg := "Error Executing Online Activation: %s" - - var remoteCmd packer.RemoteCmd - stdout := new(bytes.Buffer) - stderr := new(bytes.Buffer) - var err error - - ui.Say("Executing Online Activation...") - - var blockBuffer bytes.Buffer - blockBuffer.WriteString("{ cscript \"$env:SystemRoot/system32/slmgr.vbs\" -ato //nologo }") - - remoteCmd.Command = "-ScriptBlock " + blockBuffer.String() - - remoteCmd.Stdout = stdout - remoteCmd.Stderr = stderr - - err = comm.Start(&remoteCmd) - - stderrString := strings.TrimSpace(stderr.String()) - stdoutString := strings.TrimSpace(stdout.String()) - - log.Printf("stdout: %s", stdoutString) - log.Printf("stderr: %s", stderrString) - - if len(stderrString) > 0 { - err = fmt.Errorf(errorMsg, stderrString) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - ui.Say(stdoutString) - - return multistep.ActionContinue -} - -func (s *StepExecuteOnlineActivation) Cleanup(state multistep.StateBag) { - // do nothing -} diff --git a/builder/hyperv/common/step_execute_online_activation_full.go b/builder/hyperv/common/step_execute_online_activation_full.go deleted file mode 100644 index 6eb91936c..000000000 --- a/builder/hyperv/common/step_execute_online_activation_full.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package common - -import ( - "fmt" - "bytes" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "strings" - "log" -) - -type StepExecuteOnlineActivationFull struct { - Pk string -} - -func (s *StepExecuteOnlineActivationFull) Run(state multistep.StateBag) multistep.StepAction { - ui := state.Get("ui").(packer.Ui) - comm := state.Get("communicator").(packer.Communicator) - - errorMsg := "Error Executing Online Activation: %s" - - var remoteCmd packer.RemoteCmd - stdout := new(bytes.Buffer) - stderr := new(bytes.Buffer) - var err error - var stderrString string - var stdoutString string - - ui.Say("Executing Online Activation Full version...") - - var blockBuffer bytes.Buffer - blockBuffer.WriteString("{ cscript \"$env:SystemRoot/system32/slmgr.vbs\" /ipk "+ s.Pk +" //nologo }") - - log.Printf("cmd: %s", blockBuffer.String()) - remoteCmd.Command = "-ScriptBlock " + blockBuffer.String() - - remoteCmd.Stdout = stdout - remoteCmd.Stderr = stderr - - err = comm.Start(&remoteCmd) - - stderrString = strings.TrimSpace(stderr.String()) - stdoutString = strings.TrimSpace(stdout.String()) - - log.Printf("stdout: %s", stdoutString) - log.Printf("stderr: %s", stderrString) - - if len(stderrString) > 0 { - err = fmt.Errorf(errorMsg, stderrString) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - -// ui.Say(stdoutString) - -/* - blockBuffer.Reset() - blockBuffer.WriteString("{ cscript \"$env:SystemRoot/system32/slmgr.vbs\" -ato //nologo }") - - log.Printf("cmd: %s", blockBuffer.String()) - remoteCmd.Command = "-ScriptBlock " + blockBuffer.String() - - err = comm.Start(&remoteCmd) - - stderrString = strings.TrimSpace(stderr.String()) - stdoutString = strings.TrimSpace(stdout.String()) - - log.Printf("stdout: %s", stdoutString) - log.Printf("stderr: %s", stderrString) - - if len(stderrString) > 0 { - err = fmt.Errorf(errorMsg, stderrString) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - ui.Say(stdoutString) -*/ - return multistep.ActionContinue -} - -func (s *StepExecuteOnlineActivationFull) Cleanup(state multistep.StateBag) { - // do nothing -} diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go index a9129a378..f31ef4c2c 100644 --- a/builder/hyperv/common/step_mount_dvddrive.go +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -8,12 +8,13 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - powershell "github.com/mitchellh/packer/powershell" + "log" ) type StepMountDvdDrive struct { - RawSingleISOUrl string - path string + Generation uint + cleanup bool + dvdProperties DvdControllerProperties } func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { @@ -22,39 +23,37 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { errorMsg := "Error mounting dvd drive: %s" vmName := state.Get("vmName").(string) - isoPath := s.RawSingleISOUrl + isoPath := state.Get("iso_path").(string) + + // should be able to mount up to 60 additional iso images using SCSI + // but Windows would only allow a max of 22 due to available drive letters + // Will Windows assign DVD drives to A: and B: ? - // Check that there is a virtual dvd drive - var script powershell.ScriptBuilder - powershell := new(powershell.PowerShellCmd) + // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) - script.Reset() - script.WriteLine("param([string]$vmName)") - script.WriteLine("(Get-VMDvdDrive -VMName $vmName).ControllerNumber") - controllerNumber, err := powershell.Output(script.String(), vmName) + var dvdControllerProperties DvdControllerProperties + controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, s.Generation) if err != nil { state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } - if controllerNumber == "" { - // Add a virtual dvd drive as there is none - script.Reset() - script.WriteLine("param([string]$vmName)") - script.WriteLine("Add-VMDvdDrive -VMName $vmName") - script.WriteLine("$dvdDrive = Get-VMDvdDrive -VMName $vmName | Select-Object -first 1") - script.WriteLine("Set-VMFirmware -VMName $vmName -FirstBootDevice $dvdDrive") - err = powershell.Run(script.String(), vmName) - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - } - - ui.Say("Mounting dvd drive...") - + dvdControllerProperties.ControllerNumber = controllerNumber + dvdControllerProperties.ControllerLocation = controllerLocation + s.cleanup = true + s.dvdProperties = dvdControllerProperties + + ui.Say("Setting boot drive to os dvd drive %s ...") + err = driver.SetBootDvdDrive(vmName, controllerNumber, controllerLocation) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say(fmt.Sprintf("Mounting os dvd drive %s ...", isoPath)) err = driver.MountDvdDrive(vmName, isoPath) if err != nil { err := fmt.Errorf(errorMsg, err) @@ -62,28 +61,39 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { ui.Error(err.Error()) return multistep.ActionHalt } - - s.path = isoPath + + state.Put("os.dvd.properties", dvdControllerProperties) return multistep.ActionContinue } func (s *StepMountDvdDrive) Cleanup(state multistep.StateBag) { - if s.path == "" { + if !s.cleanup { return } driver := state.Get("driver").(Driver) - errorMsg := "Error unmounting dvd drive: %s" + errorMsg := "Error unmounting os dvd drive: %s" vmName := state.Get("vmName").(string) ui := state.Get("ui").(packer.Ui) - ui.Say("Unmounting dvd drive...") - - err := driver.UnmountDvdDrive(vmName) - if err != nil { - ui.Error(fmt.Sprintf(errorMsg, err)) + ui.Say("Clean up os dvd drive...") + + dvdControllerProperties := s.dvdProperties + + if dvdControllerProperties.Existing { + err := driver.UnmountDvdDrive(vmName) + if err != nil { + err := fmt.Errorf("Error unmounting dvd drive: %s", err) + log.Print(fmt.Sprintf(errorMsg, err)) + } + } else { + err := driver.DeleteDvdDrive(vmName, dvdControllerProperties.ControllerNumber, dvdControllerProperties.ControllerLocation) + if err != nil { + err := fmt.Errorf("Error deleting dvd drive: %s", err) + log.Print(fmt.Sprintf(errorMsg, err)) + } } } diff --git a/builder/hyperv/common/step_mount_floppydrive.go b/builder/hyperv/common/step_mount_floppydrive.go index 78f9f0914..8e8254083 100644 --- a/builder/hyperv/common/step_mount_floppydrive.go +++ b/builder/hyperv/common/step_mount_floppydrive.go @@ -8,93 +8,29 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/powershell" - "github.com/mitchellh/packer/powershell/hyperv" "io" "io/ioutil" "log" "os" "path/filepath" - "strings" ) const ( FloppyFileName = "assets.vfd" ) -type StepSetUnattendedProductKey struct { - Files []string - ProductKey string -} - -func (s *StepSetUnattendedProductKey) Run(state multistep.StateBag) multistep.StepAction { - ui := state.Get("ui").(packer.Ui) - - if s.ProductKey == "" { - ui.Say("No product key specified...") - return multistep.ActionContinue - } - - index := -1 - for i, value := range s.Files { - if s.caseInsensitiveContains(value, "Autounattend.xml") { - index = i - break - } - } - - ui.Say("Setting product key in Autounattend.xml...") - copyOfAutounattend, err := s.copyAutounattend(s.Files[index]) - if err != nil { - state.Put("error", fmt.Errorf("Error copying Autounattend.xml: %s", err)) - return multistep.ActionHalt - } - - powershell.SetUnattendedProductKey(copyOfAutounattend, s.ProductKey) - s.Files[index] = copyOfAutounattend - return multistep.ActionContinue -} - -func (s *StepSetUnattendedProductKey) caseInsensitiveContains(str, substr string) bool { - str, substr = strings.ToUpper(str), strings.ToUpper(substr) - return strings.Contains(str, substr) -} - -func (s *StepSetUnattendedProductKey) copyAutounattend(path string) (string, error) { - tempdir, err := ioutil.TempDir("", "packer") - if err != nil { - return "", err - } - - autounattend := filepath.Join(tempdir, "Autounattend.xml") - f, err := os.Create(autounattend) - if err != nil { - return "", err - } - defer f.Close() - - sourceF, err := os.Open(path) - if err != nil { - return "", err - } - defer sourceF.Close() - - log.Printf("Copying %s to temp location: %s", path, autounattend) - if _, err := io.Copy(f, sourceF); err != nil { - return "", err - } - - return autounattend, nil -} - -func (s *StepSetUnattendedProductKey) Cleanup(state multistep.StateBag) { -} - type StepMountFloppydrive struct { + Generation uint floppyPath string } func (s *StepMountFloppydrive) Run(state multistep.StateBag) multistep.StepAction { + if s.Generation > 1 { + return multistep.ActionContinue + } + + driver := state.Get("driver").(Driver) + // Determine if we even have a floppy disk to attach var floppyPath string if floppyPathRaw, ok := state.GetOk("floppy_path"); ok { @@ -118,7 +54,7 @@ func (s *StepMountFloppydrive) Run(state multistep.StateBag) multistep.StepActio ui.Say("Mounting floppy drive...") - err = hyperv.MountFloppyDrive(vmName, floppyPath) + err = driver.MountFloppyDrive(vmName, floppyPath) if err != nil { state.Put("error", fmt.Errorf("Error mounting floppy drive: %s", err)) return multistep.ActionHalt @@ -131,6 +67,10 @@ func (s *StepMountFloppydrive) Run(state multistep.StateBag) multistep.StepActio } func (s *StepMountFloppydrive) Cleanup(state multistep.StateBag) { + if s.Generation > 1 { + return + } + driver := state.Get("driver").(Driver) if s.floppyPath == "" { return } @@ -140,17 +80,17 @@ func (s *StepMountFloppydrive) Cleanup(state multistep.StateBag) { vmName := state.Get("vmName").(string) ui := state.Get("ui").(packer.Ui) - ui.Say("Unmounting floppy drive (cleanup)...") + ui.Say("Cleanup floppy drive...") - err := hyperv.UnmountFloppyDrive(vmName) + err := driver.UnmountFloppyDrive(vmName) if err != nil { - ui.Error(fmt.Sprintf(errorMsg, err)) + log.Print(fmt.Sprintf(errorMsg, err)) } err = os.Remove(s.floppyPath) if err != nil { - ui.Error(fmt.Sprintf(errorMsg, err)) + log.Print(fmt.Sprintf(errorMsg, err)) } } diff --git a/builder/hyperv/common/step_mount_integration_services.go b/builder/hyperv/common/step_mount_integration_services.go deleted file mode 100644 index 317296916..000000000 --- a/builder/hyperv/common/step_mount_integration_services.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package common - -import ( - "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - hyperv "github.com/mitchellh/packer/powershell/hyperv" - "log" - "os" - "strconv" -) - -type StepMountSecondaryDvdImages struct { - Files []string - Generation uint - dvdProperties []DvdControllerProperties -} - -type DvdControllerProperties struct { - ControllerNumber string - ControllerLocation string -} - -func (s *StepMountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction { - ui := state.Get("ui").(packer.Ui) - ui.Say("Mounting secondary DVD images...") - - vmName := state.Get("vmName").(string) - - // should be able to mount up to 60 additional iso images using SCSI - // but Windows would only allow a max of 22 due to available drive letters - // Will Windows assign DVD drives to A: and B: ? - - // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) - dvdProperties, err := s.mountFiles(vmName) - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - log.Println(fmt.Sprintf("Saving DVD properties %d DVDs", len(dvdProperties))) - - state.Put("secondary.dvd.properties", dvdProperties) - - return multistep.ActionContinue -} - -func (s *StepMountSecondaryDvdImages) Cleanup(state multistep.StateBag) { - -} - -func (s *StepMountSecondaryDvdImages) mountFiles(vmName string) ([]DvdControllerProperties, error) { - - var dvdProperties []DvdControllerProperties - - properties, err := s.addAndMountIntegrationServicesSetupDisk(vmName) - if err != nil { - return dvdProperties, err - } - - dvdProperties = append(dvdProperties, properties) - - for _, value := range s.Files { - properties, err := s.addAndMountDvdDisk(vmName, value) - if err != nil { - return dvdProperties, err - } - - dvdProperties = append(dvdProperties, properties) - } - - return dvdProperties, nil -} - -func (s *StepMountSecondaryDvdImages) addAndMountIntegrationServicesSetupDisk(vmName string) (DvdControllerProperties, error) { - - isoPath := os.Getenv("WINDIR") + "\\system32\\vmguest.iso" - properties, err := s.addAndMountDvdDisk(vmName, isoPath) - if err != nil { - return properties, err - } - - return properties, nil -} - -func (s *StepMountSecondaryDvdImages) addAndMountDvdDisk(vmName string, isoPath string) (DvdControllerProperties, error) { - var properties DvdControllerProperties - - controllerNumber, controllerLocation, err := hyperv.CreateDvdDrive(vmName, s.Generation) - if err != nil { - return properties, err - } - - properties.ControllerNumber = strconv.FormatInt(int64(controllerNumber), 10) - properties.ControllerLocation = strconv.FormatInt(int64(controllerLocation), 10) - - err = hyperv.MountDvdDriveByLocation(vmName, isoPath, controllerNumber, controllerLocation) - if err != nil { - return properties, err - } - - log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", isoPath, controllerNumber, controllerLocation)) - - return properties, nil -} diff --git a/builder/hyperv/common/step_unmount_dvddrive.go b/builder/hyperv/common/step_unmount_dvddrive.go index c5a4f2f84..c0f4d3e1c 100644 --- a/builder/hyperv/common/step_unmount_dvddrive.go +++ b/builder/hyperv/common/step_unmount_dvddrive.go @@ -15,20 +15,31 @@ type StepUnmountDvdDrive struct { func (s *StepUnmountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { driver := state.Get("driver").(Driver) + vmName := state.Get("vmName").(string) ui := state.Get("ui").(packer.Ui) - vmName := state.Get("vmName").(string) - - ui.Say("Unmounting dvd drive...") - - err := driver.UnmountDvdDrive(vmName) - if err != nil { - err := fmt.Errorf("Error unmounting dvd drive: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt + ui.Say("Unmounting os dvd drive...") + + dvdController := state.Get("os.dvd.properties").(DvdControllerProperties) + + if dvdController.Existing { + err := driver.UnmountDvdDrive(vmName) + if err != nil { + err := fmt.Errorf("Error unmounting os dvd drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } else { + err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) + if err != nil { + err := fmt.Errorf("Error deleting os dvd drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } } - + return multistep.ActionContinue } diff --git a/builder/hyperv/common/step_unmount_floppydrive.go b/builder/hyperv/common/step_unmount_floppydrive.go index 21ae1dcc1..45546e1d4 100644 --- a/builder/hyperv/common/step_unmount_floppydrive.go +++ b/builder/hyperv/common/step_unmount_floppydrive.go @@ -22,10 +22,10 @@ func (s *StepUnmountFloppyDrive) Run(state multistep.StateBag) multistep.StepAct return multistep.ActionContinue } - errorMsg := "Error Unmounting floppy drive: %s" vmName := state.Get("vmName").(string) - ui.Say("Unmounting floppy drive (Run)...") + + errorMsg := "Error Unmounting floppy drive: %s" err := driver.UnmountFloppyDrive(vmName) if err != nil { diff --git a/builder/hyperv/common/step_unmount_integration_services.go b/builder/hyperv/common/step_unmount_integration_services.go deleted file mode 100644 index d2719744b..000000000 --- a/builder/hyperv/common/step_unmount_integration_services.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package common - -import ( - "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "log" -) - -type StepUnmountSecondaryDvdImages struct { -} - -func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction { - driver := state.Get("driver").(Driver) - ui := state.Get("ui").(packer.Ui) - ui.Say("Unmounting Integration Services Setup Disk...") - - vmName := state.Get("vmName").(string) - - // todo: should this message say removing the dvd? - - dvdProperties := state.Get("secondary.dvd.properties").([]DvdControllerProperties) - - log.Println(fmt.Sprintf("Found DVD properties %d", len(dvdProperties))) - - for _, dvdProperty := range dvdProperties { - err := driver.DeleteDvdDrive(vmName, dvdProperty.ControllerNumber, dvdProperty.ControllerLocation) - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - } - - return multistep.ActionContinue -} - -func (s *StepUnmountSecondaryDvdImages) Cleanup(state multistep.StateBag) { -} diff --git a/builder/hyperv/common/step_upgrade_integration_services.go b/builder/hyperv/common/step_upgrade_integration_services.go deleted file mode 100644 index ab076e7ed..000000000 --- a/builder/hyperv/common/step_upgrade_integration_services.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package common - -import ( - //"fmt" - "os" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - powershell "github.com/mitchellh/packer/powershell" -) - -type StepUpdateIntegrationServices struct { - Username string - Password string - - newDvdDriveProperties dvdDriveProperties -} - -type dvdDriveProperties struct { - ControllerNumber string - ControllerLocation string -} - -func (s *StepUpdateIntegrationServices) Run(state multistep.StateBag) multistep.StepAction { - ui := state.Get("ui").(packer.Ui) - vmName := state.Get("vmName").(string) - - ui.Say("Mounting Integration Services Setup Disk...") - - _, err := s.mountIntegrationServicesSetupDisk(vmName); - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - // dvdDriveLetter, err := s.getDvdDriveLetter(vmName) - // if err != nil { - // state.Put("error", err) - // ui.Error(err.Error()) - // return multistep.ActionHalt - // } - - // setup := dvdDriveLetter + ":\\support\\"+osArchitecture+"\\setup.exe /quiet /norestart" - - // ui.Say("Run: " + setup) - - return multistep.ActionContinue -} - -func (s *StepUpdateIntegrationServices) Cleanup(state multistep.StateBag) { - vmName := state.Get("vmName").(string) - - var script powershell.ScriptBuilder - script.WriteLine("param([string]$vmName)") - script.WriteLine("Set-VMDvdDrive -VMName $vmName -Path $null") - - powershell := new(powershell.PowerShellCmd) - _ = powershell.Run(script.String(), vmName) -} - -func (s *StepUpdateIntegrationServices) mountIntegrationServicesSetupDisk(vmName string) (dvdDriveProperties, error) { - - var dvdProperties dvdDriveProperties - - var script powershell.ScriptBuilder - script.WriteLine("param([string]$vmName)") - script.WriteLine("Add-VMDvdDrive -VMName $vmName") - - powershell := new(powershell.PowerShellCmd) - err := powershell.Run(script.String(), vmName) - if err != nil { - return dvdProperties, err - } - - script.Reset() - script.WriteLine("param([string]$vmName)") - script.WriteLine("(Get-VMDvdDrive -VMName $vmName | Where-Object {$_.Path -eq $null}).ControllerLocation") - controllerLocation, err := powershell.Output(script.String(), vmName) - if err != nil { - return dvdProperties, err - } - - script.Reset() - script.WriteLine("param([string]$vmName)") - script.WriteLine("(Get-VMDvdDrive -VMName $vmName | Where-Object {$_.Path -eq $null}).ControllerNumber") - controllerNumber, err := powershell.Output(script.String(), vmName) - if err != nil { - return dvdProperties, err - } - - isoPath := os.Getenv("WINDIR") + "\\system32\\vmguest.iso" - - script.Reset() - script.WriteLine("param([string]$vmName,[string]$path,[string]$controllerNumber,[string]$controllerLocation)") - script.WriteLine("Set-VMDvdDrive -VMName $vmName -Path $path -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation") - - err = powershell.Run(script.String(), vmName, isoPath, controllerNumber, controllerLocation) - if err != nil { - return dvdProperties, err - } - - dvdProperties.ControllerNumber = controllerNumber - dvdProperties.ControllerLocation = controllerLocation - - return dvdProperties, err -} diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 9a18c133f..40b1a36a7 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -19,6 +19,7 @@ import ( "log" "strings" "time" + "os" ) const ( @@ -84,12 +85,21 @@ type Config struct { // 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. RawSingleISOUrl string `mapstructure:"iso_url"` + // Multiple URLs for the ISO to download. Packer will try these in order. // If anything goes wrong attempting to download or while downloading a // single URL, it will move on to the next. All URLs must point to the // same file (same checksum). By default this is empty and iso_url is // used. Only one of iso_url or iso_urls can be specified. ISOUrls []string `mapstructure:"iso_urls"` + + TargetPath string `mapstructure:"iso_target_path"` + + // Should integration services iso be mounted + GuestAdditionsMode string `mapstructure:"guest_additions_mode"` + + // The path to the integration services iso + GuestAdditionsPath string `mapstructure:"guest_additions_path"` // This is the name of the new virtual machine. // By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. @@ -234,6 +244,50 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { log.Println(fmt.Sprintf("%s: %v", "RawSingleISOUrl", b.config.RawSingleISOUrl)) + if b.config.GuestAdditionsMode == "" { + b.config.GuestAdditionsMode = "attach" + } + + if b.config.GuestAdditionsPath == "" { + b.config.GuestAdditionsPath = os.Getenv("WINDIR") + "\\system32\\vmguest.iso" + } + + for _, isoPath := range b.config.SecondaryDvdImages { + if _, err := os.Stat(isoPath); os.IsNotExist(err) { + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Secondary Dvd image does not exist: %s", err)) + } + } + } + + numberOfIsos := len(b.config.SecondaryDvdImages) + + if b.config.GuestAdditionsMode == "attach" { + if _, err := os.Stat(b.config.GuestAdditionsPath); os.IsNotExist(err) { + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Guest additions iso does not exist: %s", err)) + } + } + + numberOfIsos = numberOfIsos + 1 + } + + if b.config.Generation < 2 && numberOfIsos > 2 { + if b.config.GuestAdditionsMode == "attach" { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are only 2 ide controllers available, so we can't support guest additions and these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", "))) + } else { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are only 2 ide controllers available, so we can't support these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", "))) + } + } else if b.config.Generation > 1 && len(b.config.SecondaryDvdImages) > 16 { + if b.config.GuestAdditionsMode == "attach" { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are not enough drive letters available for scsi (limited to 16), so we can't support guest additions and these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", "))) + } else { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are not enough drive letters available for scsi (limited to 16), so we can't support these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", "))) + } + } + // Warnings if b.config.ISOChecksumType == "none" { warnings = append(warnings, @@ -275,12 +329,21 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe state.Put("hook", hook) state.Put("ui", ui) - steps := []multistep.Step{ + steps := []multistep.Step{ &hypervcommon.StepCreateTempDir{}, &hypervcommon.StepOutputDir{ Force: b.config.PackerForce, Path: b.config.OutputDir, }, + &common.StepDownload{ + Checksum: b.config.ISOChecksum, + ChecksumType: b.config.ISOChecksumType, + Description: "ISO", + ResultKey: "iso_path", + Url: b.config.ISOUrls, + Extension: "iso", + TargetPath: b.config.TargetPath, + }, &common.StepCreateFloppy{ Files: b.config.FloppyFiles, }, @@ -304,12 +367,20 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &hypervcommon.StepEnableIntegrationService{}, &hypervcommon.StepMountDvdDrive{ - RawSingleISOUrl: b.config.RawSingleISOUrl, + Generation: b.config.Generation, + }, + &hypervcommon.StepMountFloppydrive{ + Generation: b.config.Generation, + }, + + &hypervcommon.StepMountGuestAdditions{ + GuestAdditionsMode: b.config.GuestAdditionsMode, + GuestAdditionsPath: b.config.GuestAdditionsPath, + Generation: b.config.Generation, }, - &hypervcommon.StepMountFloppydrive{}, &hypervcommon.StepMountSecondaryDvdImages{ - Files: b.config.SecondaryDvdImages, + IsoPaths: b.config.SecondaryDvdImages, Generation: b.config.Generation, }, @@ -341,15 +412,15 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // wait for the vm to be powered off &hypervcommon.StepWaitForPowerOff{}, - - // remove the integration services dvd drive + + // remove the secondary dvd images // after we power down &hypervcommon.StepUnmountSecondaryDvdImages{}, + &hypervcommon.StepUnmountGuestAdditions{}, + &hypervcommon.StepUnmountDvdDrive{}, &hypervcommon.StepUnmountFloppyDrive{ Generation: b.config.Generation, }, - &hypervcommon.StepUnmountDvdDrive{}, - &hypervcommon.StepExportVm{ OutputDir: b.config.OutputDir, SkipCompaction: b.config.SkipCompaction, diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 162b048fa..bcba3edf5 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -153,7 +153,20 @@ Get-VMDvdDrive -VMName $vmName | Set-VMDvdDrive -Path $null return err } -func DeleteDvdDrive(vmName string, controllerNumber string, controllerLocation string) error { +func SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { + var script = ` +param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation) +$vmDvdDrive = Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation +if (!$vmDvdDrive) {throw 'unable to find dvd drive'} +Set-VMFirmware -VMName $vmName -FirstBootDevice $vmDvdDrive +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10)) + return err +} + +func DeleteDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { var script = ` param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation) $vmDvdDrive = Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation @@ -162,7 +175,7 @@ Remove-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -Controlle ` var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, controllerNumber, controllerLocation) + err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10)) return err } From 50a8d1b6b4c7b54618b6d8b0d8e641a99d4873ac Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@interxion.com> Date: Fri, 30 Oct 2015 19:47:14 +0000 Subject: [PATCH 036/108] Forgot to check in these files. --- .../common/step_mount_guest_additions.go | 96 +++++++++++++++++ .../common/step_mount_secondary_dvd_images.go | 101 ++++++++++++++++++ .../common/step_unmount_guest_additions.go | 47 ++++++++ .../step_unmount_secondary_dvd_images.go | 49 +++++++++ 4 files changed, 293 insertions(+) create mode 100644 builder/hyperv/common/step_mount_guest_additions.go create mode 100644 builder/hyperv/common/step_mount_secondary_dvd_images.go create mode 100644 builder/hyperv/common/step_unmount_guest_additions.go create mode 100644 builder/hyperv/common/step_unmount_secondary_dvd_images.go diff --git a/builder/hyperv/common/step_mount_guest_additions.go b/builder/hyperv/common/step_mount_guest_additions.go new file mode 100644 index 000000000..33c252082 --- /dev/null +++ b/builder/hyperv/common/step_mount_guest_additions.go @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" +) + +type StepMountGuestAdditions struct { + GuestAdditionsMode string + GuestAdditionsPath string + Generation uint + cleanup bool + dvdProperties DvdControllerProperties +} + +func (s *StepMountGuestAdditions) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + if s.GuestAdditionsMode != "attach" { + ui.Say("Skipping mounting Integration Services Setup Disk...") + return multistep.ActionContinue + } + + driver := state.Get("driver").(Driver) + ui.Say("Mounting Integration Services Setup Disk...") + + vmName := state.Get("vmName").(string) + + // should be able to mount up to 60 additional iso images using SCSI + // but Windows would only allow a max of 22 due to available drive letters + // Will Windows assign DVD drives to A: and B: ? + + // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) + + var dvdControllerProperties DvdControllerProperties + + controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, s.Generation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + dvdControllerProperties.ControllerNumber = controllerNumber + dvdControllerProperties.ControllerLocation = controllerLocation + s.cleanup = true + s.dvdProperties = dvdControllerProperties + + ui.Say(fmt.Sprintf("Mounting Integration Services dvd drive %s ...", s.GuestAdditionsPath)) + err = driver.MountDvdDriveByLocation(vmName, s.GuestAdditionsPath, controllerNumber, controllerLocation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", s.GuestAdditionsPath, controllerNumber, controllerLocation)) + + state.Put("guest.dvd.properties", dvdControllerProperties) + + return multistep.ActionContinue +} + +func (s *StepMountGuestAdditions) Cleanup(state multistep.StateBag) { + if !s.cleanup || s.GuestAdditionsMode != "attach" { + return + } + ui := state.Get("ui").(packer.Ui) + + driver := state.Get("driver").(Driver) + ui.Say("Cleanup Integration Services dvd drive...") + + vmName := state.Get("vmName").(string) + + dvdControllerProperties := s.dvdProperties + + errorMsg := "Error unmounting Integration Services dvd drive: %s" + + if dvdControllerProperties.Existing { + err := driver.UnmountDvdDrive(vmName) + if err != nil { + log.Print(fmt.Sprintf(errorMsg, err)) + } + } else { + err := driver.DeleteDvdDrive(vmName, dvdControllerProperties.ControllerNumber, dvdControllerProperties.ControllerLocation) + if err != nil { + log.Print(fmt.Sprintf(errorMsg, err)) + } + } +} diff --git a/builder/hyperv/common/step_mount_secondary_dvd_images.go b/builder/hyperv/common/step_mount_secondary_dvd_images.go new file mode 100644 index 000000000..5d220b55b --- /dev/null +++ b/builder/hyperv/common/step_mount_secondary_dvd_images.go @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" +) + +type StepMountSecondaryDvdImages struct { + IsoPaths []string + Generation uint + cleanup bool + dvdProperties []DvdControllerProperties +} + +type DvdControllerProperties struct { + ControllerNumber uint + ControllerLocation uint + Existing bool +} + +func (s *StepMountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + ui.Say("Mounting secondary DVD images...") + + vmName := state.Get("vmName").(string) + + // should be able to mount up to 60 additional iso images using SCSI + // but Windows would only allow a max of 22 due to available drive letters + // Will Windows assign DVD drives to A: and B: ? + + // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) + var dvdProperties []DvdControllerProperties + + for _, isoPath := range s.IsoPaths { + var properties DvdControllerProperties + + controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, s.Generation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + properties.ControllerNumber = controllerNumber + properties.ControllerLocation = controllerLocation + + s.cleanup = true + dvdProperties = append(dvdProperties, properties) + s.dvdProperties = dvdProperties + + ui.Say(fmt.Sprintf("Mounting secondary dvd drive %s ...", isoPath)) + err = driver.MountDvdDriveByLocation(vmName, isoPath, controllerNumber, controllerLocation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", isoPath, controllerNumber, controllerLocation)) + } + + state.Put("secondary.dvd.properties", dvdProperties) + + return multistep.ActionContinue +} + +func (s *StepMountSecondaryDvdImages) Cleanup(state multistep.StateBag) { + if (!s.cleanup){ + return + } + + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + ui.Say("Clean up secondary dvd drives...") + + vmName := state.Get("vmName").(string) + + errorMsg := "Error unmounting secondary dvd drive: %s" + + for _, dvdControllerProperties := range s.dvdProperties { + + if dvdControllerProperties.Existing { + err := driver.UnmountDvdDrive(vmName) + if err != nil { + log.Print(fmt.Sprintf(errorMsg, err)) + } + } else { + err := driver.DeleteDvdDrive(vmName, dvdControllerProperties.ControllerNumber, dvdControllerProperties.ControllerLocation) + if err != nil { + log.Print(fmt.Sprintf(errorMsg, err)) + } + } + } +} diff --git a/builder/hyperv/common/step_unmount_guest_additions.go b/builder/hyperv/common/step_unmount_guest_additions.go new file mode 100644 index 000000000..d4b505001 --- /dev/null +++ b/builder/hyperv/common/step_unmount_guest_additions.go @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +type StepUnmountGuestAdditions struct { +} + +func (s *StepUnmountGuestAdditions) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + vmName := state.Get("vmName").(string) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Unmounting Integration Services dvd drive...") + + dvdController := state.Get("guest.dvd.properties").(DvdControllerProperties) + + if dvdController.Existing { + err := driver.UnmountDvdDrive(vmName) + if err != nil { + err := fmt.Errorf("Error unmounting Integration Services dvd drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } else { + err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) + if err != nil { + err := fmt.Errorf("Error deleting Integration Services dvd drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + return multistep.ActionContinue +} + +func (s *StepUnmountGuestAdditions) Cleanup(state multistep.StateBag) { +} diff --git a/builder/hyperv/common/step_unmount_secondary_dvd_images.go b/builder/hyperv/common/step_unmount_secondary_dvd_images.go new file mode 100644 index 000000000..ceb6663b7 --- /dev/null +++ b/builder/hyperv/common/step_unmount_secondary_dvd_images.go @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +type StepUnmountSecondaryDvdImages struct { +} + +func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + ui.Say("Unmounting secondary dvd drives...") + + dvdControllers := state.Get("secondary.dvd.properties").([]DvdControllerProperties) + + for _, dvdController := range dvdControllers { + if dvdController.Existing { + err := driver.UnmountDvdDrive(vmName) + if err != nil { + err := fmt.Errorf("Error unmounting secondary dvd drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } else { + err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) + if err != nil { + err := fmt.Errorf("Error deleting secondary dvd drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + } + + return multistep.ActionContinue +} + +func (s *StepUnmountSecondaryDvdImages) Cleanup(state multistep.StateBag) { +} From aaf90072497f000a92972fcd75db6f9272596805 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Fri, 30 Oct 2015 19:57:27 +0000 Subject: [PATCH 037/108] Include cache in the state bag. --- builder/hyperv/iso/builder.go | 53 ++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 40b1a36a7..430499e48 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -17,18 +17,18 @@ import ( "github.com/mitchellh/packer/powershell/hyperv" "github.com/mitchellh/packer/template/interpolate" "log" + "os" "strings" "time" - "os" ) const ( - DefaultDiskSize = 40 * 1024 // ~40GB - MinDiskSize = 256 // 256MB + DefaultDiskSize = 40 * 1024 // ~40GB + MinDiskSize = 256 // 256MB MaxDiskSize = 64 * 1024 * 1024 // 64TB DefaultRamSize = 1 * 1024 // 1GB - MinRamSize = 32 // 32MB + MinRamSize = 32 // 32MB MaxRamSize = 32 * 1024 // 32GB LowRam = 384 // 384MB @@ -85,21 +85,21 @@ type Config struct { // 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. RawSingleISOUrl string `mapstructure:"iso_url"` - + // Multiple URLs for the ISO to download. Packer will try these in order. // If anything goes wrong attempting to download or while downloading a // single URL, it will move on to the next. All URLs must point to the // same file (same checksum). By default this is empty and iso_url is // used. Only one of iso_url or iso_urls can be specified. ISOUrls []string `mapstructure:"iso_urls"` - - TargetPath string `mapstructure:"iso_target_path"` - + + TargetPath string `mapstructure:"iso_target_path"` + // Should integration services iso be mounted - GuestAdditionsMode string `mapstructure:"guest_additions_mode"` - + GuestAdditionsMode string `mapstructure:"guest_additions_mode"` + // The path to the integration services iso - GuestAdditionsPath string `mapstructure:"guest_additions_path"` + GuestAdditionsPath string `mapstructure:"guest_additions_path"` // This is the name of the new virtual machine. // By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. @@ -251,29 +251,29 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { if b.config.GuestAdditionsPath == "" { b.config.GuestAdditionsPath = os.Getenv("WINDIR") + "\\system32\\vmguest.iso" } - + for _, isoPath := range b.config.SecondaryDvdImages { if _, err := os.Stat(isoPath); os.IsNotExist(err) { - if err != nil { + if err != nil { errs = packer.MultiErrorAppend( errs, fmt.Errorf("Secondary Dvd image does not exist: %s", err)) } } } - + numberOfIsos := len(b.config.SecondaryDvdImages) - + if b.config.GuestAdditionsMode == "attach" { if _, err := os.Stat(b.config.GuestAdditionsPath); os.IsNotExist(err) { - if err != nil { + if err != nil { errs = packer.MultiErrorAppend( errs, fmt.Errorf("Guest additions iso does not exist: %s", err)) } - } - + } + numberOfIsos = numberOfIsos + 1 } - + if b.config.Generation < 2 && numberOfIsos > 2 { if b.config.GuestAdditionsMode == "attach" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are only 2 ide controllers available, so we can't support guest additions and these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", "))) @@ -282,9 +282,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } else if b.config.Generation > 1 && len(b.config.SecondaryDvdImages) > 16 { if b.config.GuestAdditionsMode == "attach" { - errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are not enough drive letters available for scsi (limited to 16), so we can't support guest additions and these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", "))) + errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are not enough drive letters available for scsi (limited to 16), so we can't support guest additions and these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", "))) } else { - errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are not enough drive letters available for scsi (limited to 16), so we can't support these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", "))) + errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are not enough drive letters available for scsi (limited to 16), so we can't support these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", "))) } } @@ -324,12 +324,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // Set up the state. state := new(multistep.BasicStateBag) + state.Put("cache", cache) state.Put("config", &b.config) state.Put("driver", driver) state.Put("hook", hook) state.Put("ui", ui) - steps := []multistep.Step{ + steps := []multistep.Step{ &hypervcommon.StepCreateTempDir{}, &hypervcommon.StepOutputDir{ Force: b.config.PackerForce, @@ -343,7 +344,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Url: b.config.ISOUrls, Extension: "iso", TargetPath: b.config.TargetPath, - }, + }, &common.StepCreateFloppy{ Files: b.config.FloppyFiles, }, @@ -376,11 +377,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &hypervcommon.StepMountGuestAdditions{ GuestAdditionsMode: b.config.GuestAdditionsMode, GuestAdditionsPath: b.config.GuestAdditionsPath, - Generation: b.config.Generation, + Generation: b.config.Generation, }, &hypervcommon.StepMountSecondaryDvdImages{ - IsoPaths: b.config.SecondaryDvdImages, + IsoPaths: b.config.SecondaryDvdImages, Generation: b.config.Generation, }, @@ -412,7 +413,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // wait for the vm to be powered off &hypervcommon.StepWaitForPowerOff{}, - + // remove the secondary dvd images // after we power down &hypervcommon.StepUnmountSecondaryDvdImages{}, From 4b6ce3da6070d4cf4e74719eb3b66b1d4b295aea Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Fri, 30 Oct 2015 20:13:48 +0000 Subject: [PATCH 038/108] Build agents might not have guest additions --- builder/hyperv/iso/builder_test.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/builder/hyperv/iso/builder_test.go b/builder/hyperv/iso/builder_test.go index 5311075d0..5cc04163e 100644 --- a/builder/hyperv/iso/builder_test.go +++ b/builder/hyperv/iso/builder_test.go @@ -8,13 +8,14 @@ import ( func testConfig() map[string]interface{} { return map[string]interface{}{ - "iso_checksum": "foo", - "iso_checksum_type": "md5", - "iso_url": "http://www.packer.io", - "shutdown_command": "yes", - "ssh_username": "foo", - "ram_size_mb": 64, - "disk_size": 256, + "iso_checksum": "foo", + "iso_checksum_type": "md5", + "iso_url": "http://www.packer.io", + "shutdown_command": "yes", + "ssh_username": "foo", + "ram_size_mb": 64, + "disk_size": 256, + "guest_additions_mode": "none", packer.BuildNameConfigKey: "foo", } } @@ -56,7 +57,7 @@ func TestBuilderPrepare_DiskSize(t *testing.T) { t.Fatalf("bad err: %s", err) } - if b.config.DiskSize != 40 * 1024 { + if b.config.DiskSize != 40*1024 { t.Fatalf("bad size: %d", b.config.DiskSize) } From 31ac1da28d0ac249d10973748bc5c752afa20cd1 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 31 Oct 2015 16:53:58 +0000 Subject: [PATCH 039/108] Setting boot drive message --- builder/hyperv/common/step_mount_dvddrive.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go index f31ef4c2c..f325ada94 100644 --- a/builder/hyperv/common/step_mount_dvddrive.go +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -13,7 +13,7 @@ import ( type StepMountDvdDrive struct { Generation uint - cleanup bool + cleanup bool dvdProperties DvdControllerProperties } @@ -24,7 +24,7 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { errorMsg := "Error mounting dvd drive: %s" vmName := state.Get("vmName").(string) isoPath := state.Get("iso_path").(string) - + // should be able to mount up to 60 additional iso images using SCSI // but Windows would only allow a max of 22 due to available drive letters // Will Windows assign DVD drives to A: and B: ? @@ -43,16 +43,16 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { dvdControllerProperties.ControllerLocation = controllerLocation s.cleanup = true s.dvdProperties = dvdControllerProperties - - ui.Say("Setting boot drive to os dvd drive %s ...") + + ui.Say(fmt.Sprintf("Setting boot drive to os dvd drive %s ..."), isoPath) err = driver.SetBootDvdDrive(vmName, controllerNumber, controllerLocation) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt - } - + } + ui.Say(fmt.Sprintf("Mounting os dvd drive %s ...", isoPath)) err = driver.MountDvdDrive(vmName, isoPath) if err != nil { @@ -61,7 +61,7 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { ui.Error(err.Error()) return multistep.ActionHalt } - + state.Put("os.dvd.properties", dvdControllerProperties) return multistep.ActionContinue @@ -80,9 +80,9 @@ func (s *StepMountDvdDrive) Cleanup(state multistep.StateBag) { ui := state.Get("ui").(packer.Ui) ui.Say("Clean up os dvd drive...") - + dvdControllerProperties := s.dvdProperties - + if dvdControllerProperties.Existing { err := driver.UnmountDvdDrive(vmName) if err != nil { From 3cb621f89069ade893a96d10d95dbb3131e1ad6c Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 1 Nov 2015 16:00:56 +0000 Subject: [PATCH 040/108] Get rid of mount and unmount that does not specify controller location exactly Handle cleanup if unmount step has not already done so --- builder/hyperv/common/driver.go | 8 ++-- builder/hyperv/common/driver_ps_4.go | 12 ++---- builder/hyperv/common/step_mount_dvddrive.go | 34 ++++++++--------- .../common/step_mount_guest_additions.go | 38 +++++++++---------- .../common/step_mount_secondary_dvd_images.go | 31 +++++++-------- .../hyperv/common/step_unmount_dvddrive.go | 16 ++++++-- .../hyperv/common/step_unmount_floppydrive.go | 2 +- .../common/step_unmount_guest_additions.go | 18 +++++++-- .../step_unmount_secondary_dvd_images.go | 18 +++++++-- powershell/hyperv/hyperv.go | 28 +++++--------- 10 files changed, 107 insertions(+), 98 deletions(-) diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index 6ceb37c0f..f0a6d9d86 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -88,13 +88,11 @@ type Driver interface { CreateDvdDrive(string, uint) (uint, uint, error) - MountDvdDrive(string, string) error - - MountDvdDriveByLocation(string, string, uint, uint) error + MountDvdDrive(string, string, uint, uint) error SetBootDvdDrive(string, uint, uint) error - - UnmountDvdDrive(string) error + + UnmountDvdDrive(string, uint, uint) error DeleteDvdDrive(string, uint, uint) error diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index c6d6d4e65..bc2040811 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -209,20 +209,16 @@ func (d *HypervPS4Driver) CreateDvdDrive(vmName string, generation uint) (uint, return hyperv.CreateDvdDrive(vmName, generation) } -func (d *HypervPS4Driver) MountDvdDrive(vmName string, path string) error { - return hyperv.MountDvdDrive(vmName, path) -} - -func (d *HypervPS4Driver) MountDvdDriveByLocation(vmName string, path string, controllerNumber uint, controllerLocation uint) error { - return hyperv.MountDvdDriveByLocation(vmName, path, controllerNumber, controllerLocation) +func (d *HypervPS4Driver) MountDvdDrive(vmName string, path string, controllerNumber uint, controllerLocation uint) error { + return hyperv.MountDvdDrive(vmName, path, controllerNumber, controllerLocation) } func (d *HypervPS4Driver) SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { return hyperv.SetBootDvdDrive(vmName, controllerNumber, controllerLocation) } -func (d *HypervPS4Driver) UnmountDvdDrive(vmName string) error { - return hyperv.UnmountDvdDrive(vmName) +func (d *HypervPS4Driver) UnmountDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { + return hyperv.UnmountDvdDrive(vmName, controllerNumber, controllerLocation) } func (d *HypervPS4Driver) DeleteDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go index f325ada94..75af552ad 100644 --- a/builder/hyperv/common/step_mount_dvddrive.go +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -13,8 +13,6 @@ import ( type StepMountDvdDrive struct { Generation uint - cleanup bool - dvdProperties DvdControllerProperties } func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { @@ -41,10 +39,11 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { dvdControllerProperties.ControllerNumber = controllerNumber dvdControllerProperties.ControllerLocation = controllerLocation - s.cleanup = true - s.dvdProperties = dvdControllerProperties + dvdControllerProperties.Existing = false + + state.Put("os.dvd.properties", dvdControllerProperties) - ui.Say(fmt.Sprintf("Setting boot drive to os dvd drive %s ..."), isoPath) + ui.Say(fmt.Sprintf("Setting boot drive to os dvd drive %s ...", isoPath)) err = driver.SetBootDvdDrive(vmName, controllerNumber, controllerLocation) if err != nil { err := fmt.Errorf(errorMsg, err) @@ -54,7 +53,7 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { } ui.Say(fmt.Sprintf("Mounting os dvd drive %s ...", isoPath)) - err = driver.MountDvdDrive(vmName, isoPath) + err = driver.MountDvdDrive(vmName, isoPath, controllerNumber, controllerLocation) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) @@ -62,35 +61,32 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - state.Put("os.dvd.properties", dvdControllerProperties) - return multistep.ActionContinue } -func (s *StepMountDvdDrive) Cleanup(state multistep.StateBag) { - if !s.cleanup { +func (s *StepMountDvdDrive) Cleanup(state multistep.StateBag) { + dvdControllerState := state.Get("os.dvd.properties") + + if dvdControllerState == nil { return } - + + dvdController := dvdControllerState.(DvdControllerProperties) driver := state.Get("driver").(Driver) - - errorMsg := "Error unmounting os dvd drive: %s" - vmName := state.Get("vmName").(string) ui := state.Get("ui").(packer.Ui) + errorMsg := "Error unmounting os dvd drive: %s" ui.Say("Clean up os dvd drive...") - dvdControllerProperties := s.dvdProperties - - if dvdControllerProperties.Existing { - err := driver.UnmountDvdDrive(vmName) + if dvdController.Existing { + err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { err := fmt.Errorf("Error unmounting dvd drive: %s", err) log.Print(fmt.Sprintf(errorMsg, err)) } } else { - err := driver.DeleteDvdDrive(vmName, dvdControllerProperties.ControllerNumber, dvdControllerProperties.ControllerLocation) + err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { err := fmt.Errorf("Error deleting dvd drive: %s", err) log.Print(fmt.Sprintf(errorMsg, err)) diff --git a/builder/hyperv/common/step_mount_guest_additions.go b/builder/hyperv/common/step_mount_guest_additions.go index 33c252082..ee6b69afc 100644 --- a/builder/hyperv/common/step_mount_guest_additions.go +++ b/builder/hyperv/common/step_mount_guest_additions.go @@ -15,8 +15,6 @@ type StepMountGuestAdditions struct { GuestAdditionsMode string GuestAdditionsPath string Generation uint - cleanup bool - dvdProperties DvdControllerProperties } func (s *StepMountGuestAdditions) Run(state multistep.StateBag) multistep.StepAction { @@ -49,11 +47,11 @@ func (s *StepMountGuestAdditions) Run(state multistep.StateBag) multistep.StepAc dvdControllerProperties.ControllerNumber = controllerNumber dvdControllerProperties.ControllerLocation = controllerLocation - s.cleanup = true - s.dvdProperties = dvdControllerProperties + dvdControllerProperties.Existing = false + state.Put("guest.dvd.properties", dvdControllerProperties) ui.Say(fmt.Sprintf("Mounting Integration Services dvd drive %s ...", s.GuestAdditionsPath)) - err = driver.MountDvdDriveByLocation(vmName, s.GuestAdditionsPath, controllerNumber, controllerLocation) + err = driver.MountDvdDrive(vmName, s.GuestAdditionsPath, controllerNumber, controllerLocation) if err != nil { state.Put("error", err) ui.Error(err.Error()) @@ -62,35 +60,37 @@ func (s *StepMountGuestAdditions) Run(state multistep.StateBag) multistep.StepAc log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", s.GuestAdditionsPath, controllerNumber, controllerLocation)) - state.Put("guest.dvd.properties", dvdControllerProperties) - return multistep.ActionContinue } func (s *StepMountGuestAdditions) Cleanup(state multistep.StateBag) { - if !s.cleanup || s.GuestAdditionsMode != "attach" { + if s.GuestAdditionsMode != "attach" { return } + + dvdControllerState := state.Get("guest.dvd.properties") + + if dvdControllerState == nil { + return + } + + dvdController := dvdControllerState.(DvdControllerProperties) ui := state.Get("ui").(packer.Ui) - - driver := state.Get("driver").(Driver) - ui.Say("Cleanup Integration Services dvd drive...") - + driver := state.Get("driver").(Driver) vmName := state.Get("vmName").(string) - - dvdControllerProperties := s.dvdProperties - errorMsg := "Error unmounting Integration Services dvd drive: %s" - if dvdControllerProperties.Existing { - err := driver.UnmountDvdDrive(vmName) + ui.Say("Cleanup Integration Services dvd drive...") + + if dvdController.Existing { + err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { log.Print(fmt.Sprintf(errorMsg, err)) } } else { - err := driver.DeleteDvdDrive(vmName, dvdControllerProperties.ControllerNumber, dvdControllerProperties.ControllerLocation) + err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { log.Print(fmt.Sprintf(errorMsg, err)) } } -} +} \ No newline at end of file diff --git a/builder/hyperv/common/step_mount_secondary_dvd_images.go b/builder/hyperv/common/step_mount_secondary_dvd_images.go index 5d220b55b..548fd4a8a 100644 --- a/builder/hyperv/common/step_mount_secondary_dvd_images.go +++ b/builder/hyperv/common/step_mount_secondary_dvd_images.go @@ -14,8 +14,6 @@ import ( type StepMountSecondaryDvdImages struct { IsoPaths []string Generation uint - cleanup bool - dvdProperties []DvdControllerProperties } type DvdControllerProperties struct { @@ -50,13 +48,12 @@ func (s *StepMountSecondaryDvdImages) Run(state multistep.StateBag) multistep.St properties.ControllerNumber = controllerNumber properties.ControllerLocation = controllerLocation - - s.cleanup = true + properties.Existing = false dvdProperties = append(dvdProperties, properties) - s.dvdProperties = dvdProperties + state.Put("secondary.dvd.properties", dvdProperties) ui.Say(fmt.Sprintf("Mounting secondary dvd drive %s ...", isoPath)) - err = driver.MountDvdDriveByLocation(vmName, isoPath, controllerNumber, controllerLocation) + err = driver.MountDvdDrive(vmName, isoPath, controllerNumber, controllerLocation) if err != nil { state.Put("error", err) ui.Error(err.Error()) @@ -66,33 +63,33 @@ func (s *StepMountSecondaryDvdImages) Run(state multistep.StateBag) multistep.St log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", isoPath, controllerNumber, controllerLocation)) } - state.Put("secondary.dvd.properties", dvdProperties) - return multistep.ActionContinue } func (s *StepMountSecondaryDvdImages) Cleanup(state multistep.StateBag) { - if (!s.cleanup){ + dvdControllersState := state.Get("secondary.dvd.properties") + + if dvdControllersState == nil { return } + dvdControllers := dvdControllersState.([]DvdControllerProperties) driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + errorMsg := "Error unmounting secondary dvd drive: %s" + ui.Say("Clean up secondary dvd drives...") - vmName := state.Get("vmName").(string) - - errorMsg := "Error unmounting secondary dvd drive: %s" - - for _, dvdControllerProperties := range s.dvdProperties { + for _, dvdController := range dvdControllers { - if dvdControllerProperties.Existing { - err := driver.UnmountDvdDrive(vmName) + if dvdController.Existing { + err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { log.Print(fmt.Sprintf(errorMsg, err)) } } else { - err := driver.DeleteDvdDrive(vmName, dvdControllerProperties.ControllerNumber, dvdControllerProperties.ControllerLocation) + err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { log.Print(fmt.Sprintf(errorMsg, err)) } diff --git a/builder/hyperv/common/step_unmount_dvddrive.go b/builder/hyperv/common/step_unmount_dvddrive.go index c0f4d3e1c..26f327cc8 100644 --- a/builder/hyperv/common/step_unmount_dvddrive.go +++ b/builder/hyperv/common/step_unmount_dvddrive.go @@ -18,12 +18,19 @@ func (s *StepUnmountDvdDrive) Run(state multistep.StateBag) multistep.StepAction vmName := state.Get("vmName").(string) ui := state.Get("ui").(packer.Ui) - ui.Say("Unmounting os dvd drive...") + ui.Say("Unmount/delete os dvd drive...") - dvdController := state.Get("os.dvd.properties").(DvdControllerProperties) + dvdControllerState := state.Get("os.dvd.properties") + + if dvdControllerState == nil { + return multistep.ActionContinue + } + + dvdController := dvdControllerState.(DvdControllerProperties) if dvdController.Existing { - err := driver.UnmountDvdDrive(vmName) + ui.Say(fmt.Sprintf("Unmounting os dvd drives controller %d location %d ...", dvdController.ControllerNumber, dvdController.ControllerLocation)) + err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { err := fmt.Errorf("Error unmounting os dvd drive: %s", err) state.Put("error", err) @@ -31,6 +38,7 @@ func (s *StepUnmountDvdDrive) Run(state multistep.StateBag) multistep.StepAction return multistep.ActionHalt } } else { + ui.Say(fmt.Sprintf("Delete os dvd drives controller %d location %d ...", dvdController.ControllerNumber, dvdController.ControllerLocation)) err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { err := fmt.Errorf("Error deleting os dvd drive: %s", err) @@ -40,6 +48,8 @@ func (s *StepUnmountDvdDrive) Run(state multistep.StateBag) multistep.StepAction } } + state.Put("os.dvd.properties", nil) + return multistep.ActionContinue } diff --git a/builder/hyperv/common/step_unmount_floppydrive.go b/builder/hyperv/common/step_unmount_floppydrive.go index 45546e1d4..5ae920730 100644 --- a/builder/hyperv/common/step_unmount_floppydrive.go +++ b/builder/hyperv/common/step_unmount_floppydrive.go @@ -23,7 +23,7 @@ func (s *StepUnmountFloppyDrive) Run(state multistep.StateBag) multistep.StepAct } vmName := state.Get("vmName").(string) - ui.Say("Unmounting floppy drive (Run)...") + ui.Say("Unmount/delete floppy drive (Run)...") errorMsg := "Error Unmounting floppy drive: %s" diff --git a/builder/hyperv/common/step_unmount_guest_additions.go b/builder/hyperv/common/step_unmount_guest_additions.go index d4b505001..56c98791a 100644 --- a/builder/hyperv/common/step_unmount_guest_additions.go +++ b/builder/hyperv/common/step_unmount_guest_additions.go @@ -18,12 +18,19 @@ func (s *StepUnmountGuestAdditions) Run(state multistep.StateBag) multistep.Step vmName := state.Get("vmName").(string) ui := state.Get("ui").(packer.Ui) - ui.Say("Unmounting Integration Services dvd drive...") - - dvdController := state.Get("guest.dvd.properties").(DvdControllerProperties) + ui.Say("Unmount/delete Integration Services dvd drive...") + + dvdControllerState := state.Get("guest.dvd.properties") + + if dvdControllerState == nil { + return multistep.ActionContinue + } + + dvdController := dvdControllerState.(DvdControllerProperties) if dvdController.Existing { - err := driver.UnmountDvdDrive(vmName) + ui.Say(fmt.Sprintf("Unmounting Integration Services dvd drives controller %d location %d ...", dvdController.ControllerNumber, dvdController.ControllerLocation)) + err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { err := fmt.Errorf("Error unmounting Integration Services dvd drive: %s", err) state.Put("error", err) @@ -31,6 +38,7 @@ func (s *StepUnmountGuestAdditions) Run(state multistep.StateBag) multistep.Step return multistep.ActionHalt } } else { + ui.Say(fmt.Sprintf("Delete Integration Services dvd drives controller %d location %d ...", dvdController.ControllerNumber, dvdController.ControllerLocation)) err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { err := fmt.Errorf("Error deleting Integration Services dvd drive: %s", err) @@ -40,6 +48,8 @@ func (s *StepUnmountGuestAdditions) Run(state multistep.StateBag) multistep.Step } } + state.Put("guest.dvd.properties", nil) + return multistep.ActionContinue } diff --git a/builder/hyperv/common/step_unmount_secondary_dvd_images.go b/builder/hyperv/common/step_unmount_secondary_dvd_images.go index ceb6663b7..7c5b27781 100644 --- a/builder/hyperv/common/step_unmount_secondary_dvd_images.go +++ b/builder/hyperv/common/step_unmount_secondary_dvd_images.go @@ -18,13 +18,20 @@ func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep. ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) - ui.Say("Unmounting secondary dvd drives...") - - dvdControllers := state.Get("secondary.dvd.properties").([]DvdControllerProperties) + ui.Say("Unmount/delete secondary dvd drives...") + + dvdControllersState := state.Get("secondary.dvd.properties") + + if dvdControllersState == nil { + return multistep.ActionContinue + } + + dvdControllers := dvdControllersState.([]DvdControllerProperties) for _, dvdController := range dvdControllers { if dvdController.Existing { - err := driver.UnmountDvdDrive(vmName) + ui.Say(fmt.Sprintf("Unmounting secondary dvd drives controller %d location %d ...", dvdController.ControllerNumber, dvdController.ControllerLocation)) + err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { err := fmt.Errorf("Error unmounting secondary dvd drive: %s", err) state.Put("error", err) @@ -32,6 +39,7 @@ func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep. return multistep.ActionHalt } } else { + ui.Say(fmt.Sprintf("Delete secondary dvd drives controller %d location %d ...", dvdController.ControllerNumber, dvdController.ControllerLocation)) err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { err := fmt.Errorf("Error deleting secondary dvd drive: %s", err) @@ -41,6 +49,8 @@ func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep. } } } + + state.Put("secondary.dvd.properties", nil) return multistep.ActionContinue } diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index bcba3edf5..e25ae9313 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -118,23 +118,13 @@ param([string]$vmName) return controllerNumber, controllerLocation, err } -func MountDvdDrive(vmName string, path string) error { - - var script = ` -param([string]$vmName,[string]$path) -Set-VMDvdDrive -VMName $vmName -Path $path -` - - var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, path) - return err -} - -func MountDvdDriveByLocation(vmName string, path string, controllerNumber uint, controllerLocation uint) error { +func MountDvdDrive(vmName string, path string, controllerNumber uint, controllerLocation uint) error { var script = ` param([string]$vmName,[string]$path,[string]$controllerNumber,[string]$controllerLocation) -Set-VMDvdDrive -VMName $vmName -Path $path -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation +$vmDvdDrive = Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation +if (!$vmDvdDrive) {throw 'unable to find dvd drive'} +Set-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation -Path $path ` var ps powershell.PowerShellCmd @@ -142,14 +132,16 @@ Set-VMDvdDrive -VMName $vmName -Path $path -ControllerNumber $controllerNumber - return err } -func UnmountDvdDrive(vmName string) error { +func UnmountDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { var script = ` -param([string]$vmName) -Get-VMDvdDrive -VMName $vmName | Set-VMDvdDrive -Path $null +param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation) +$vmDvdDrive = Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation +if (!$vmDvdDrive) {throw 'unable to find dvd drive'} +Set-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation -Path $null ` var ps powershell.PowerShellCmd - err := ps.Run(script, vmName) + err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10)) return err } From fe8d99fd8ad28b76c2aeafcd2bda4bdfa4c713b7 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 1 Nov 2015 21:18:48 +0000 Subject: [PATCH 041/108] If we are not getting the exit code assigned then default to it being successful Useful to know what the exit code is if there is an error --- builder/hyperv/common/step_shutdown.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/builder/hyperv/common/step_shutdown.go b/builder/hyperv/common/step_shutdown.go index 62bbab21f..21a1e963d 100644 --- a/builder/hyperv/common/step_shutdown.go +++ b/builder/hyperv/common/step_shutdown.go @@ -40,6 +40,7 @@ func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction { var stdout, stderr bytes.Buffer cmd := &packer.RemoteCmd{ + ExitStatus: 0, Command: s.Command, Stdout: &stdout, Stderr: &stderr, @@ -57,18 +58,13 @@ func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction { // If the command failed to run, notify the user in some way. if cmd.ExitStatus != 0 { state.Put("error", fmt.Errorf( - "Shutdown command has non-zero exit status.\n\nStdout: %s\n\nStderr: %s", - stdout.String(), stderr.String())) + "Shutdown command has non-zero exit status.\n\nExitStatus: %d\n\nStdout: %s\n\nStderr: %s", + cmd.ExitStatus, stdout.String(), stderr.String())) return multistep.ActionHalt } - if stdout.Len() > 0 { - log.Printf("Shutdown stdout: %s", stdout.String()) - } - - if stderr.Len() > 0 { - log.Printf("Shutdown stderr: %s", stderr.String()) - } + log.Printf("Shutdown stdout: %s", stdout.String()) + log.Printf("Shutdown stderr: %s", stderr.String()) // Wait for the machine to actually shut down log.Printf("Waiting max %s for shutdown to complete", s.Timeout) From c42cb88ddd4abc3b38c6d9d3e628f6739364bf68 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@interxion.com> Date: Mon, 2 Nov 2015 17:23:51 +0000 Subject: [PATCH 042/108] Using Write-Output instead of Write-Host since PS v5 now leaks the host stream to stderr --- provisioner/powershell/elevated.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provisioner/powershell/elevated.go b/provisioner/powershell/elevated.go index 00bc72e4a..ec79afa36 100644 --- a/provisioner/powershell/elevated.go +++ b/provisioner/powershell/elevated.go @@ -72,7 +72,7 @@ function SlurpOutput($l) { if (Test-Path $log) { Get-Content $log | select -skip $l | ForEach { $l += 1 - Write-Host "$_" + Write-Output "$_" } } return $l From 4b394c8563dc66db875d12416d0575ee56ae8dd7 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@interxion.com> Date: Mon, 2 Nov 2015 17:35:38 +0000 Subject: [PATCH 043/108] Write output will put ouput from function, so we don't want to be getting line from output of function Fix unit tests for not showing progress stream when using powershell Ensure that progress stream does not get leaked into stdout Using Write-Output instead of Write-Host since PS v5 now leaks the host stream to stderr --- powershell/powershell_test.go | 2 +- provisioner/powershell/elevated.go | 18 ++++++++---------- provisioner/powershell/provisioner.go | 6 +++--- provisioner/powershell/provisioner_test.go | 14 +++++++------- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/powershell/powershell_test.go b/powershell/powershell_test.go index 4aff20491..0925e3c74 100644 --- a/powershell/powershell_test.go +++ b/powershell/powershell_test.go @@ -37,7 +37,7 @@ func TestOutput(t *testing.T) { func TestRunFile(t *testing.T) { var blockBuffer bytes.Buffer - blockBuffer.WriteString("param([string]$a, [string]$b, [int]$x, [int]$y) $n = $x + $y; Write-Host $a, $b, $n") + blockBuffer.WriteString(`param([string]$a, [string]$b, [int]$x, [int]$y) $ProgressPreference='SilentlyContinue'; $n = $x + $y; Write-Output "$a $b $n";`) var ps PowerShellCmd cmdOut, err := ps.Output(blockBuffer.String(), "a", "b", "5", "10") diff --git a/provisioner/powershell/elevated.go b/provisioner/powershell/elevated.go index ec79afa36..6678b4b65 100644 --- a/provisioner/powershell/elevated.go +++ b/provisioner/powershell/elevated.go @@ -58,6 +58,7 @@ $t.XmlText = @' </Actions> </Task> '@ +$ProgressPreference='SilentlyContinue'; $f = $s.GetFolder("\") $f.RegisterTaskDefinition($name, $t, 6, "{{.User}}", "{{.Password}}", 1, $null) | Out-Null $t = $f.GetTask("\$name") @@ -68,19 +69,16 @@ while ((!($t.state -eq 4)) -and ($sec -lt $timeout)) { Start-Sleep -s 1 $sec++ } -function SlurpOutput($l) { - if (Test-Path $log) { - Get-Content $log | select -skip $l | ForEach { - $l += 1 - Write-Output "$_" - } - } - return $l -} + $line = 0 do { Start-Sleep -m 100 - $line = SlurpOutput $line + if (Test-Path $log) { + Get-Content $log | select -skip $line | ForEach { + $line += 1 + Write-Output "$_" + } + } } while (!($t.state -eq 3)) $result = $t.LastTaskResult [System.Runtime.Interopservices.Marshal]::ReleaseComObject($s) | Out-Null diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index ad90e56a1..9d23dde9a 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -120,11 +120,11 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if p.config.ExecuteCommand == "" { - p.config.ExecuteCommand = `powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}'` + p.config.ExecuteCommand = `powershell '& {$ProgressPreference='SilentlyContinue'; {{.Vars}}{{.Path}}; exit $LastExitCode}'` } if p.config.ElevatedExecuteCommand == "" { - p.config.ElevatedExecuteCommand = `powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}'` + p.config.ElevatedExecuteCommand = `powershell '& {$ProgressPreference='SilentlyContinue'; {{.Vars}}{{.Path}}; exit $LastExitCode}'` } if p.config.Inline != nil && len(p.config.Inline) == 0 { @@ -425,7 +425,7 @@ func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath strin Password: p.config.ElevatedPassword, TaskDescription: "Packer elevated task", TaskName: fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()), - EncodedCommand: powershellEncode([]byte(command + "; exit $LASTEXITCODE")), + EncodedCommand: powershellEncode([]byte("$ProgressPreference='SilentlyContinue'; " + command + "; exit $LASTEXITCODE")), }) if err != nil { diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index e109eb6d5..e8b0728eb 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -75,11 +75,11 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { t.Error("expected elevated_password to be empty") } - if p.config.ExecuteCommand != "powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}'" { + if p.config.ExecuteCommand != "powershell '& {$ProgressPreference='SilentlyContinue'; {{.Vars}}{{.Path}}; exit $LastExitCode}'" { t.Fatalf("Default command should be powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ExecuteCommand) } - if p.config.ElevatedExecuteCommand != "powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}'" { + if p.config.ElevatedExecuteCommand != "powershell '& {$ProgressPreference='SilentlyContinue'; {{.Vars}}{{.Path}}; exit $LastExitCode}'" { t.Fatalf("Default command should be powershell powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ElevatedExecuteCommand) } @@ -389,7 +389,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& { $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand := `powershell '& {$ProgressPreference='SilentlyContinue'; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -408,7 +408,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand = `powershell '& { $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand = `powershell '& {$ProgressPreference='SilentlyContinue'; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -435,7 +435,7 @@ func TestProvisionerProvision_Scripts(t *testing.T) { } //powershell -Command "$env:PACKER_BUILDER_TYPE=''"; powershell -Command "$env:PACKER_BUILD_NAME='foobuild'"; powershell -Command c:/Windows/Temp/script.ps1 - expectedCommand := `powershell '& { $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `powershell '& {$ProgressPreference='SilentlyContinue'; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -468,7 +468,7 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& { $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `powershell '& {$ProgressPreference='SilentlyContinue'; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -582,7 +582,7 @@ func TestProvision_createCommandText(t *testing.T) { // Non-elevated cmd, _ := p.createCommandText() - if cmd != `powershell '& { $env:PACKER_BUILDER_TYPE=\"\"; $env:PACKER_BUILD_NAME=\"\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` { + if cmd != `powershell '& {$ProgressPreference='SilentlyContinue'; $env:PACKER_BUILDER_TYPE=\"\"; $env:PACKER_BUILD_NAME=\"\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` { t.Fatalf("Got unexpected non-elevated command: %s", cmd) } From e8c2b49be89f630a0b92b9d8984475fa8627ef90 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Mon, 2 Nov 2015 20:05:53 +0000 Subject: [PATCH 044/108] Use correct quotation of variables --- provisioner/powershell/elevated.go | 2 +- provisioner/powershell/provisioner.go | 6 +++--- provisioner/powershell/provisioner_test.go | 18 +++++++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/provisioner/powershell/elevated.go b/provisioner/powershell/elevated.go index 6678b4b65..cb3336ae2 100644 --- a/provisioner/powershell/elevated.go +++ b/provisioner/powershell/elevated.go @@ -58,7 +58,7 @@ $t.XmlText = @' </Actions> </Task> '@ -$ProgressPreference='SilentlyContinue'; +$ProgressPreference="SilentlyContinue"; $f = $s.GetFolder("\") $f.RegisterTaskDefinition($name, $t, 6, "{{.User}}", "{{.Password}}", 1, $null) | Out-Null $t = $f.GetTask("\$name") diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index 9d23dde9a..90cde164f 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -120,11 +120,11 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if p.config.ExecuteCommand == "" { - p.config.ExecuteCommand = `powershell '& {$ProgressPreference='SilentlyContinue'; {{.Vars}}{{.Path}}; exit $LastExitCode}'` + p.config.ExecuteCommand = `powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}'` } if p.config.ElevatedExecuteCommand == "" { - p.config.ElevatedExecuteCommand = `powershell '& {$ProgressPreference='SilentlyContinue'; {{.Vars}}{{.Path}}; exit $LastExitCode}'` + p.config.ElevatedExecuteCommand = `powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}'` } if p.config.Inline != nil && len(p.config.Inline) == 0 { @@ -425,7 +425,7 @@ func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath strin Password: p.config.ElevatedPassword, TaskDescription: "Packer elevated task", TaskName: fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()), - EncodedCommand: powershellEncode([]byte("$ProgressPreference='SilentlyContinue'; " + command + "; exit $LASTEXITCODE")), + EncodedCommand: powershellEncode([]byte("$ProgressPreference=\"SilentlyContinue\"; " + command + "; exit $LASTEXITCODE")), }) if err != nil { diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index e8b0728eb..87f6680ad 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -75,12 +75,12 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { t.Error("expected elevated_password to be empty") } - if p.config.ExecuteCommand != "powershell '& {$ProgressPreference='SilentlyContinue'; {{.Vars}}{{.Path}}; exit $LastExitCode}'" { - t.Fatalf("Default command should be powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ExecuteCommand) + if p.config.ExecuteCommand != `powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}'` { + t.Fatalf("Default command should be powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ExecuteCommand) } - if p.config.ElevatedExecuteCommand != "powershell '& {$ProgressPreference='SilentlyContinue'; {{.Vars}}{{.Path}}; exit $LastExitCode}'" { - t.Fatalf("Default command should be powershell powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ElevatedExecuteCommand) + if p.config.ElevatedExecuteCommand != `powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}'` { + t.Fatalf("Default command should be powershell powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ElevatedExecuteCommand) } if p.config.ValidExitCodes == nil { @@ -389,7 +389,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& {$ProgressPreference='SilentlyContinue'; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand := `powershell '& {$ProgressPreference=\"SilentlyContinue\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -408,7 +408,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand = `powershell '& {$ProgressPreference='SilentlyContinue'; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand = `powershell '& {$ProgressPreference=\"SilentlyContinue\"; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -435,7 +435,7 @@ func TestProvisionerProvision_Scripts(t *testing.T) { } //powershell -Command "$env:PACKER_BUILDER_TYPE=''"; powershell -Command "$env:PACKER_BUILD_NAME='foobuild'"; powershell -Command c:/Windows/Temp/script.ps1 - expectedCommand := `powershell '& {$ProgressPreference='SilentlyContinue'; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `powershell '& {$ProgressPreference=\"SilentlyContinue\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -468,7 +468,7 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& {$ProgressPreference='SilentlyContinue'; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `powershell '& {$ProgressPreference=\"SilentlyContinue\"; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -582,7 +582,7 @@ func TestProvision_createCommandText(t *testing.T) { // Non-elevated cmd, _ := p.createCommandText() - if cmd != `powershell '& {$ProgressPreference='SilentlyContinue'; $env:PACKER_BUILDER_TYPE=\"\"; $env:PACKER_BUILD_NAME=\"\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` { + if cmd != `powershell '& {$ProgressPreference=\"SilentlyContinue\"; $env:PACKER_BUILDER_TYPE=\"\"; $env:PACKER_BUILD_NAME=\"\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` { t.Fatalf("Got unexpected non-elevated command: %s", cmd) } From e0c10dec12682330f6662fd8667b25d79d012306 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Tue, 3 Nov 2015 09:41:55 +0000 Subject: [PATCH 045/108] If exit code = 1 and the length of the stderr is 0, then we can assume that the exit code was not set, so assume success --- builder/hyperv/common/step_shutdown.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/builder/hyperv/common/step_shutdown.go b/builder/hyperv/common/step_shutdown.go index 21a1e963d..06ddb6f39 100644 --- a/builder/hyperv/common/step_shutdown.go +++ b/builder/hyperv/common/step_shutdown.go @@ -40,7 +40,6 @@ func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction { var stdout, stderr bytes.Buffer cmd := &packer.RemoteCmd{ - ExitStatus: 0, Command: s.Command, Stdout: &stdout, Stderr: &stderr, @@ -55,16 +54,19 @@ func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction { // Wait for the command to run cmd.Wait() + stderrString := stderr.String() + stdoutString := stdout.String() + // If the command failed to run, notify the user in some way. - if cmd.ExitStatus != 0 { + if !(cmd.ExitStatus == 0 || (cmd.ExitStatus == 1 && len(stderrString) == 0)) { state.Put("error", fmt.Errorf( - "Shutdown command has non-zero exit status.\n\nExitStatus: %d\n\nStdout: %s\n\nStderr: %s", - cmd.ExitStatus, stdout.String(), stderr.String())) + "Shutdown command has not successful.\n\nExitStatus: %d\n\nStdout: %s\n\nStderr: %s", + cmd.ExitStatus, stdoutString), stderrString)) return multistep.ActionHalt } - log.Printf("Shutdown stdout: %s", stdout.String()) - log.Printf("Shutdown stderr: %s", stderr.String()) + log.Printf("Shutdown stdout: %s", stdoutString) + log.Printf("Shutdown stderr: %s", stderrString) // Wait for the machine to actually shut down log.Printf("Waiting max %s for shutdown to complete", s.Timeout) From 8477a0b748f8f74a7a440af48553938d7a8e32af Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Tue, 3 Nov 2015 09:58:45 +0000 Subject: [PATCH 046/108] Accidentally included bracket --- builder/hyperv/common/step_shutdown.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/hyperv/common/step_shutdown.go b/builder/hyperv/common/step_shutdown.go index 06ddb6f39..db60a1517 100644 --- a/builder/hyperv/common/step_shutdown.go +++ b/builder/hyperv/common/step_shutdown.go @@ -61,7 +61,7 @@ func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction { if !(cmd.ExitStatus == 0 || (cmd.ExitStatus == 1 && len(stderrString) == 0)) { state.Put("error", fmt.Errorf( "Shutdown command has not successful.\n\nExitStatus: %d\n\nStdout: %s\n\nStderr: %s", - cmd.ExitStatus, stdoutString), stderrString)) + cmd.ExitStatus, stdoutString, stderrString)) return multistep.ActionHalt } From 07764c631e7f34f3221708134f8d930bd50f11c3 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Wed, 4 Nov 2015 21:45:37 +0000 Subject: [PATCH 047/108] The default for wait is 1 second --- powershell/hyperv/hyperv.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index e25ae9313..506f1a2c1 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -775,7 +775,7 @@ param([string]$vmName, [string]$scanCodes) if ($scanCode.StartsWith('wait')){ $timeToWait = $scanCode.Substring(4) if (!$timeToWait){ - $timeToWait = "10" + $timeToWait = "1" } Start-Sleep -s $timeToWait From 93a2615f7a1ceb208e1a497af64773d085e2d262 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 7 Nov 2015 13:19:37 +0000 Subject: [PATCH 048/108] Fix gen 1 creation of dvd --- powershell/hyperv/hyperv.go | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 506f1a2c1..3e5e49308 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -61,7 +61,7 @@ func CreateDvdDrive(vmName string, generation uint) (uint, uint, error) { // generation 1 requires dvd to be added to ide controller, generation 2 uses scsi for dvd drives script = ` param([string]$vmName) -$dvdDrives = (Get-VMDvdDrive -VMName $vmName) +$dvdDrives = @(Get-VMDvdDrive -VMName $vmName) $lastControllerNumber = $dvdDrives | Sort-Object ControllerNumber | Select-Object -Last 1 | %{$_.ControllerNumber} if (!$lastControllerNumber) { $lastControllerNumber = 0 @@ -90,21 +90,11 @@ $lastControllerNumber script = ` param([string]$vmName,[int]$controllerNumber) -Add-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -` - cmdOut, err := ps.Output(script, vmName) - if err != nil { - return controllerNumber, 0, err - } - - // we could try to get the controller location and number in one call, but this way we do not - // need to parse the output - script = ` -param([string]$vmName) -(Get-VMDvdDrive -VMName $vmName | Where-Object {$_.Path -eq $null}).ControllerLocation +$dvdController = Add-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -Passthru +$dvdController.ControllerLocation ` - cmdOut, err = ps.Output(script, vmName) + cmdOut, err := ps.Output(script, vmName, strconv.FormatInt(int64(controllerNumber), 10)) if err != nil { return controllerNumber, 0, err } From 18241e5215556e19ddc930b724836fd0d4caf160 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 7 Nov 2015 13:42:26 +0000 Subject: [PATCH 049/108] Setting boot drive is generation specific --- builder/hyperv/common/driver.go | 8 +++---- builder/hyperv/common/driver_ps_4.go | 4 ++-- builder/hyperv/common/step_mount_dvddrive.go | 14 ++++++------- powershell/hyperv/hyperv.go | 22 ++++++++++++++------ 4 files changed, 29 insertions(+), 19 deletions(-) diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index f0a6d9d86..44fa37c90 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -89,14 +89,14 @@ type Driver interface { CreateDvdDrive(string, uint) (uint, uint, error) MountDvdDrive(string, string, uint, uint) error - - SetBootDvdDrive(string, uint, uint) error - + + SetBootDvdDrive(string, uint, uint, uint) error + UnmountDvdDrive(string, uint, uint) error DeleteDvdDrive(string, uint, uint) error MountFloppyDrive(string, string) error - + UnmountFloppyDrive(string) error } diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index bc2040811..0e6507ede 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -213,8 +213,8 @@ func (d *HypervPS4Driver) MountDvdDrive(vmName string, path string, controllerNu return hyperv.MountDvdDrive(vmName, path, controllerNumber, controllerLocation) } -func (d *HypervPS4Driver) SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { - return hyperv.SetBootDvdDrive(vmName, controllerNumber, controllerLocation) +func (d *HypervPS4Driver) SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint, generation uint) error { + return hyperv.SetBootDvdDrive(vmName, controllerNumber, controllerLocation, generation) } func (d *HypervPS4Driver) UnmountDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go index 75af552ad..346af2c9e 100644 --- a/builder/hyperv/common/step_mount_dvddrive.go +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -12,7 +12,7 @@ import ( ) type StepMountDvdDrive struct { - Generation uint + Generation uint } func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { @@ -40,11 +40,11 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { dvdControllerProperties.ControllerNumber = controllerNumber dvdControllerProperties.ControllerLocation = controllerLocation dvdControllerProperties.Existing = false - + state.Put("os.dvd.properties", dvdControllerProperties) ui.Say(fmt.Sprintf("Setting boot drive to os dvd drive %s ...", isoPath)) - err = driver.SetBootDvdDrive(vmName, controllerNumber, controllerLocation) + err = driver.SetBootDvdDrive(vmName, controllerNumber, controllerLocation, s.Generation) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) @@ -64,14 +64,14 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } -func (s *StepMountDvdDrive) Cleanup(state multistep.StateBag) { +func (s *StepMountDvdDrive) Cleanup(state multistep.StateBag) { dvdControllerState := state.Get("os.dvd.properties") - + if dvdControllerState == nil { return } - - dvdController := dvdControllerState.(DvdControllerProperties) + + dvdController := dvdControllerState.(DvdControllerProperties) driver := state.Get("driver").(Driver) vmName := state.Get("vmName").(string) ui := state.Get("ui").(packer.Ui) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 3e5e49308..e26e432cb 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -135,17 +135,27 @@ Set-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLo return err } -func SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { - var script = ` +func SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint, generation uint) error { + + if generation < 2 { + script := ` +param([string]$vmName) +Set-VMBios -VMName $vmName -StartupOrder @("CD", "IDE","LegacyNetworkAdapter","Floppy") +` + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err + } else { + script := ` param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation) $vmDvdDrive = Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation if (!$vmDvdDrive) {throw 'unable to find dvd drive'} Set-VMFirmware -VMName $vmName -FirstBootDevice $vmDvdDrive ` - - var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10)) - return err + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10)) + return err + } } func DeleteDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { From f8e3970d87f61987f8090acd2447d621a1f22799 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 7 Nov 2015 16:20:55 +0000 Subject: [PATCH 050/108] By default a gen 1 vm get a dvd drive. Remove it on creation so that it behaves the same as gen 2. --- powershell/hyperv/hyperv.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index e26e432cb..0d86d441c 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -215,7 +215,12 @@ New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHD ` var ps powershell.PowerShellCmd err := ps.Run(script, vmName, path, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), switchName) - return err + + if err != nil { + return err + } + + return DeleteDvdDrive(vmName, 1, 0) } } From d1961b7c8e38869a8927830a5dcbeb776f2d6869 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 7 Nov 2015 17:07:40 +0000 Subject: [PATCH 051/108] Echo output when its used --- builder/hyperv/common/step_type_boot_command.go | 10 +++++----- powershell/hyperv/hyperv.go | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/builder/hyperv/common/step_type_boot_command.go b/builder/hyperv/common/step_type_boot_command.go index c801858b7..e121ebad2 100644 --- a/builder/hyperv/common/step_type_boot_command.go +++ b/builder/hyperv/common/step_type_boot_command.go @@ -154,19 +154,19 @@ func scancodes(message string) []string { var scancode []string if strings.HasPrefix(message, "<wait>") { - log.Printf("Special code <wait> found, will sleep 1 second at this point.") + //log.Printf("Special code <wait> found, will sleep 1 second at this point.") scancode = []string{"wait"} message = message[len("<wait>"):] } if strings.HasPrefix(message, "<wait5>") { - log.Printf("Special code <wait5> found, will sleep 5 seconds at this point.") + //log.Printf("Special code <wait5> found, will sleep 5 seconds at this point.") scancode = []string{"wait5"} message = message[len("<wait5>"):] } if strings.HasPrefix(message, "<wait10>") { - log.Printf("Special code <wait10> found, will sleep 10 seconds at this point.") + //log.Printf("Special code <wait10> found, will sleep 10 seconds at this point.") scancode = []string{"wait10"} message = message[len("<wait10>"):] } @@ -174,7 +174,7 @@ func scancodes(message string) []string { if scancode == nil { for specialCode, specialValue := range special { if strings.HasPrefix(message, specialCode) { - log.Printf("Special code '%s' found, replacing with: %s", specialCode, specialValue) + //log.Printf("Special code '%s' found, replacing with: %s", specialCode, specialValue) scancode = specialValue message = message[len(specialCode):] break @@ -200,7 +200,7 @@ func scancodes(message string) []string { } scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt+0x80)) - log.Printf("Sending char '%c', code '%v', shift %v", r, scancode, keyShift) + //log.Printf("Sending char '%c', code '%v', shift %v", r, scancode, keyShift) } result = append(result, scancode...) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 0d86d441c..28213605a 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -783,6 +783,7 @@ param([string]$vmName, [string]$scanCodes) $timeToWait = "1" } + write-host "Special code <wait> found, will sleep $timeToWait second(s) at this point." Start-Sleep -s $timeToWait if ($scanCodesToSend){ @@ -796,8 +797,10 @@ param([string]$vmName, [string]$scanCodes) $scanCodesToSend = '' } else { if ($scanCodesToSend){ + write-host "Sending special code '$scanCodesToSend' '$scanCode'" $scanCodesToSend = "$scanCodesToSend $scanCode" } else { + write-host "Sending char '$scanCode'" $scanCodesToSend = "$scanCode" } } From bda2468404b8f2dd136e47253d52ee11e7334e59 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 7 Nov 2015 17:12:19 +0000 Subject: [PATCH 052/108] removed unused reference --- builder/hyperv/common/step_type_boot_command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/hyperv/common/step_type_boot_command.go b/builder/hyperv/common/step_type_boot_command.go index e121ebad2..f5cf1d909 100644 --- a/builder/hyperv/common/step_type_boot_command.go +++ b/builder/hyperv/common/step_type_boot_command.go @@ -2,7 +2,7 @@ package common import ( "fmt" - "log" + //"log" "strings" "unicode" "unicode/utf8" From 199bee688565323a65ccc9e1b495a018d81e7ecb Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 7 Nov 2015 23:33:47 +0000 Subject: [PATCH 053/108] Need to sleep after sending previous characters, not before --- powershell/hyperv/hyperv.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 28213605a..67ad79d82 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -782,10 +782,7 @@ param([string]$vmName, [string]$scanCodes) if (!$timeToWait){ $timeToWait = "1" } - - write-host "Special code <wait> found, will sleep $timeToWait second(s) at this point." - Start-Sleep -s $timeToWait - + if ($scanCodesToSend){ $scanCodesToSendByteArray = [byte[]]@($scanCodesToSend.Split(' ') | %{"0x$_"}) @@ -794,6 +791,9 @@ param([string]$vmName, [string]$scanCodes) } } + write-host "Special code <wait> found, will sleep $timeToWait second(s) at this point." + Start-Sleep -s $timeToWait + $scanCodesToSend = '' } else { if ($scanCodesToSend){ From 8734c5b25f05d2af42f700a92038b0a80db3aeee Mon Sep 17 00:00:00 2001 From: Gildas Cherruel <gildas.cherruel@inin.com> Date: Mon, 30 Nov 2015 22:34:35 +0900 Subject: [PATCH 054/108] Remove all Dvd Drives on Generation 1 --- powershell/hyperv/hyperv.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 67ad79d82..410678ee4 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -171,6 +171,17 @@ Remove-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -Controlle return err } +func DeleteAllDvdDrives(vmName string) error { + var script = ` +param([string]$vmName) +Get-VMDvdDrive -VMName $vmName | Remove-VMDvdDrive +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} + func MountFloppyDrive(vmName string, path string) error { var script = ` param([string]$vmName, [string]$path) @@ -220,7 +231,7 @@ New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHD return err } - return DeleteDvdDrive(vmName, 1, 0) + return DeleteAllDvdDrives(vmName) } } From 056fc5916608092ff594658fd6b39f370065f51a Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Tue, 1 Dec 2015 07:26:15 +0000 Subject: [PATCH 055/108] Do not try to type scancodes if there are none. We haven't figured out how to do this on Windows 10 or Windows 7 and below. This will at least allow other types of VMs to be built. --- powershell/hyperv/hyperv.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 410678ee4..2c040a0bf 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -659,6 +659,10 @@ if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { } func TypeScanCodes(vmName string, scanCodes string) error { + if len(scanCodes) == 0 { + return nil + } + var script = ` param([string]$vmName, [string]$scanCodes) #Requires -Version 3 @@ -674,7 +678,7 @@ param([string]$vmName, [string]$scanCodes) $ErrorActionPreference = "Stop" - $vm = Get-CimInstance -ComputerName localhost -Namespace "root\virtualization\v2" -ClassName Msvm_ComputerSystem -ErrorAction Ignore -Verbose:$false | where ElementName -eq $VMName | select -first 1 + $vm = Get-CimInstance -Namespace "root\virtualization\v2" -ClassName Msvm_ComputerSystem -ErrorAction Ignore -Verbose:$false | where ElementName -eq $VMName | select -first 1 if ($vm -eq $null){ Write-Error ("VirtualMachine({0}) is not found!" -f $VMName) } From 4ab2e8b2524b8db0198b7e5513557bd341a3270d Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 5 Dec 2015 02:31:28 -0800 Subject: [PATCH 056/108] try to get the virtual keyboard a number of different ways. This should hopefully work for Windows 7, Window 10 and Windows Server 2016 --- powershell/hyperv/hyperv.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 2c040a0bf..41556ba64 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -682,8 +682,17 @@ param([string]$vmName, [string]$scanCodes) if ($vm -eq $null){ Write-Error ("VirtualMachine({0}) is not found!" -f $VMName) } - + $vmKeyboard = $vm | Get-CimAssociatedInstance -ResultClassName "Msvm_Keyboard" -ErrorAction Ignore -Verbose:$false + + if ($vmKeyboard -eq $null) { + $vmKeyboard = Get-CimInstance -Namespace "root\virtualization\v2" -ClassName Msvm_Keyboard -ErrorAction Ignore -Verbose:$false | where SystemName -eq $vm.Name | select -first 1 + } + + if ($vmKeyboard -eq $null) { + $vmKeyboard = Get-CimInstance -Namespace "root\virtualization" -ClassName Msvm_Keyboard -ErrorAction Ignore -Verbose:$false | where SystemName -eq $vm.Name | select -first 1 + } + if ($vmKeyboard -eq $null){ Write-Error ("VirtualMachine({0}) keyboard class is not found!" -f $VMName) } From d878f4dd8e5fb9d65513c35f4475e14ed9ac02ba Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 5 Dec 2015 03:03:33 -0800 Subject: [PATCH 057/108] A workaround for bug in Windows 10 and Windows 2016 where you have to specify a path when creating a dvd drive --- builder/hyperv/common/driver.go | 2 +- builder/hyperv/common/driver_ps_4.go | 4 +- builder/hyperv/common/step_mount_dvddrive.go | 2 +- .../common/step_mount_guest_additions.go | 190 ++++++++--------- .../common/step_mount_secondary_dvd_images.go | 196 +++++++++--------- powershell/hyperv/hyperv.go | 9 +- 6 files changed, 202 insertions(+), 201 deletions(-) diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index 44fa37c90..a62e3112b 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -86,7 +86,7 @@ type Driver interface { RestartVirtualMachine(string) error - CreateDvdDrive(string, uint) (uint, uint, error) + CreateDvdDrive(string, string, uint) (uint, uint, error) MountDvdDrive(string, string, uint, uint) error diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index 0e6507ede..15d6c6817 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -205,8 +205,8 @@ func (d *HypervPS4Driver) RestartVirtualMachine(vmName string) error { return hyperv.RestartVirtualMachine(vmName) } -func (d *HypervPS4Driver) CreateDvdDrive(vmName string, generation uint) (uint, uint, error) { - return hyperv.CreateDvdDrive(vmName, generation) +func (d *HypervPS4Driver) CreateDvdDrive(vmName string, isoPath string, generation uint) (uint, uint, error) { + return hyperv.CreateDvdDrive(vmName, isoPath, generation) } func (d *HypervPS4Driver) MountDvdDrive(vmName string, path string, controllerNumber uint, controllerLocation uint) error { diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go index 346af2c9e..cff69627e 100644 --- a/builder/hyperv/common/step_mount_dvddrive.go +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -30,7 +30,7 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) var dvdControllerProperties DvdControllerProperties - controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, s.Generation) + controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, isoPath, s.Generation) if err != nil { state.Put("error", err) ui.Error(err.Error()) diff --git a/builder/hyperv/common/step_mount_guest_additions.go b/builder/hyperv/common/step_mount_guest_additions.go index ee6b69afc..dff8b90c3 100644 --- a/builder/hyperv/common/step_mount_guest_additions.go +++ b/builder/hyperv/common/step_mount_guest_additions.go @@ -1,96 +1,96 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package common - -import ( - "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "log" -) - -type StepMountGuestAdditions struct { - GuestAdditionsMode string - GuestAdditionsPath string - Generation uint -} - -func (s *StepMountGuestAdditions) Run(state multistep.StateBag) multistep.StepAction { - ui := state.Get("ui").(packer.Ui) - - if s.GuestAdditionsMode != "attach" { - ui.Say("Skipping mounting Integration Services Setup Disk...") - return multistep.ActionContinue - } - - driver := state.Get("driver").(Driver) - ui.Say("Mounting Integration Services Setup Disk...") - - vmName := state.Get("vmName").(string) - - // should be able to mount up to 60 additional iso images using SCSI - // but Windows would only allow a max of 22 due to available drive letters - // Will Windows assign DVD drives to A: and B: ? - - // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) - - var dvdControllerProperties DvdControllerProperties - - controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, s.Generation) - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - dvdControllerProperties.ControllerNumber = controllerNumber - dvdControllerProperties.ControllerLocation = controllerLocation - dvdControllerProperties.Existing = false - state.Put("guest.dvd.properties", dvdControllerProperties) - - ui.Say(fmt.Sprintf("Mounting Integration Services dvd drive %s ...", s.GuestAdditionsPath)) - err = driver.MountDvdDrive(vmName, s.GuestAdditionsPath, controllerNumber, controllerLocation) - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", s.GuestAdditionsPath, controllerNumber, controllerLocation)) - - return multistep.ActionContinue -} - -func (s *StepMountGuestAdditions) Cleanup(state multistep.StateBag) { - if s.GuestAdditionsMode != "attach" { - return - } - - dvdControllerState := state.Get("guest.dvd.properties") - - if dvdControllerState == nil { - return - } - - dvdController := dvdControllerState.(DvdControllerProperties) - ui := state.Get("ui").(packer.Ui) - driver := state.Get("driver").(Driver) - vmName := state.Get("vmName").(string) - errorMsg := "Error unmounting Integration Services dvd drive: %s" - - ui.Say("Cleanup Integration Services dvd drive...") - - if dvdController.Existing { - err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) - if err != nil { - log.Print(fmt.Sprintf(errorMsg, err)) - } - } else { - err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) - if err != nil { - log.Print(fmt.Sprintf(errorMsg, err)) - } - } +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" +) + +type StepMountGuestAdditions struct { + GuestAdditionsMode string + GuestAdditionsPath string + Generation uint +} + +func (s *StepMountGuestAdditions) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + if s.GuestAdditionsMode != "attach" { + ui.Say("Skipping mounting Integration Services Setup Disk...") + return multistep.ActionContinue + } + + driver := state.Get("driver").(Driver) + ui.Say("Mounting Integration Services Setup Disk...") + + vmName := state.Get("vmName").(string) + + // should be able to mount up to 60 additional iso images using SCSI + // but Windows would only allow a max of 22 due to available drive letters + // Will Windows assign DVD drives to A: and B: ? + + // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) + + var dvdControllerProperties DvdControllerProperties + + controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, s.GuestAdditionsPath, s.Generation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + dvdControllerProperties.ControllerNumber = controllerNumber + dvdControllerProperties.ControllerLocation = controllerLocation + dvdControllerProperties.Existing = false + state.Put("guest.dvd.properties", dvdControllerProperties) + + ui.Say(fmt.Sprintf("Mounting Integration Services dvd drive %s ...", s.GuestAdditionsPath)) + err = driver.MountDvdDrive(vmName, s.GuestAdditionsPath, controllerNumber, controllerLocation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", s.GuestAdditionsPath, controllerNumber, controllerLocation)) + + return multistep.ActionContinue +} + +func (s *StepMountGuestAdditions) Cleanup(state multistep.StateBag) { + if s.GuestAdditionsMode != "attach" { + return + } + + dvdControllerState := state.Get("guest.dvd.properties") + + if dvdControllerState == nil { + return + } + + dvdController := dvdControllerState.(DvdControllerProperties) + ui := state.Get("ui").(packer.Ui) + driver := state.Get("driver").(Driver) + vmName := state.Get("vmName").(string) + errorMsg := "Error unmounting Integration Services dvd drive: %s" + + ui.Say("Cleanup Integration Services dvd drive...") + + if dvdController.Existing { + err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) + if err != nil { + log.Print(fmt.Sprintf(errorMsg, err)) + } + } else { + err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) + if err != nil { + log.Print(fmt.Sprintf(errorMsg, err)) + } + } } \ No newline at end of file diff --git a/builder/hyperv/common/step_mount_secondary_dvd_images.go b/builder/hyperv/common/step_mount_secondary_dvd_images.go index 548fd4a8a..eb5ce25bc 100644 --- a/builder/hyperv/common/step_mount_secondary_dvd_images.go +++ b/builder/hyperv/common/step_mount_secondary_dvd_images.go @@ -1,98 +1,98 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package common - -import ( - "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "log" -) - -type StepMountSecondaryDvdImages struct { - IsoPaths []string - Generation uint -} - -type DvdControllerProperties struct { - ControllerNumber uint - ControllerLocation uint - Existing bool -} - -func (s *StepMountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction { - driver := state.Get("driver").(Driver) - ui := state.Get("ui").(packer.Ui) - ui.Say("Mounting secondary DVD images...") - - vmName := state.Get("vmName").(string) - - // should be able to mount up to 60 additional iso images using SCSI - // but Windows would only allow a max of 22 due to available drive letters - // Will Windows assign DVD drives to A: and B: ? - - // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) - var dvdProperties []DvdControllerProperties - - for _, isoPath := range s.IsoPaths { - var properties DvdControllerProperties - - controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, s.Generation) - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - properties.ControllerNumber = controllerNumber - properties.ControllerLocation = controllerLocation - properties.Existing = false - dvdProperties = append(dvdProperties, properties) - state.Put("secondary.dvd.properties", dvdProperties) - - ui.Say(fmt.Sprintf("Mounting secondary dvd drive %s ...", isoPath)) - err = driver.MountDvdDrive(vmName, isoPath, controllerNumber, controllerLocation) - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", isoPath, controllerNumber, controllerLocation)) - } - - return multistep.ActionContinue -} - -func (s *StepMountSecondaryDvdImages) Cleanup(state multistep.StateBag) { - dvdControllersState := state.Get("secondary.dvd.properties") - - if dvdControllersState == nil { - return - } - - dvdControllers := dvdControllersState.([]DvdControllerProperties) - driver := state.Get("driver").(Driver) - ui := state.Get("ui").(packer.Ui) - vmName := state.Get("vmName").(string) - errorMsg := "Error unmounting secondary dvd drive: %s" - - ui.Say("Clean up secondary dvd drives...") - - for _, dvdController := range dvdControllers { - - if dvdController.Existing { - err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) - if err != nil { - log.Print(fmt.Sprintf(errorMsg, err)) - } - } else { - err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) - if err != nil { - log.Print(fmt.Sprintf(errorMsg, err)) - } - } - } -} +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" +) + +type StepMountSecondaryDvdImages struct { + IsoPaths []string + Generation uint +} + +type DvdControllerProperties struct { + ControllerNumber uint + ControllerLocation uint + Existing bool +} + +func (s *StepMountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + ui.Say("Mounting secondary DVD images...") + + vmName := state.Get("vmName").(string) + + // should be able to mount up to 60 additional iso images using SCSI + // but Windows would only allow a max of 22 due to available drive letters + // Will Windows assign DVD drives to A: and B: ? + + // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) + var dvdProperties []DvdControllerProperties + + for _, isoPath := range s.IsoPaths { + var properties DvdControllerProperties + + controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, isoPath, s.Generation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + properties.ControllerNumber = controllerNumber + properties.ControllerLocation = controllerLocation + properties.Existing = false + dvdProperties = append(dvdProperties, properties) + state.Put("secondary.dvd.properties", dvdProperties) + + ui.Say(fmt.Sprintf("Mounting secondary dvd drive %s ...", isoPath)) + err = driver.MountDvdDrive(vmName, isoPath, controllerNumber, controllerLocation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", isoPath, controllerNumber, controllerLocation)) + } + + return multistep.ActionContinue +} + +func (s *StepMountSecondaryDvdImages) Cleanup(state multistep.StateBag) { + dvdControllersState := state.Get("secondary.dvd.properties") + + if dvdControllersState == nil { + return + } + + dvdControllers := dvdControllersState.([]DvdControllerProperties) + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + errorMsg := "Error unmounting secondary dvd drive: %s" + + ui.Say("Clean up secondary dvd drives...") + + for _, dvdController := range dvdControllers { + + if dvdController.Existing { + err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) + if err != nil { + log.Print(fmt.Sprintf(errorMsg, err)) + } + } else { + err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) + if err != nil { + log.Print(fmt.Sprintf(errorMsg, err)) + } + } + } +} diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 41556ba64..7a981cb1e 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -51,7 +51,7 @@ $ip return cmdOut, err } -func CreateDvdDrive(vmName string, generation uint) (uint, uint, error) { +func CreateDvdDrive(vmName string, isoPath string, generation uint) (uint, uint, error) { var ps powershell.PowerShellCmd var script string var controllerNumber uint @@ -89,12 +89,13 @@ $lastControllerNumber } script = ` -param([string]$vmName,[int]$controllerNumber) -$dvdController = Add-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -Passthru +param([string]$vmName, [string]$isoPath, [int]$controllerNumber) +$dvdController = Add-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -path $isoPath -Passthru +Set-VMDvdDrive -path $null $dvdController.ControllerLocation ` - cmdOut, err := ps.Output(script, vmName, strconv.FormatInt(int64(controllerNumber), 10)) + cmdOut, err := ps.Output(script, vmName, isoPath, strconv.FormatInt(int64(controllerNumber), 10)) if err != nil { return controllerNumber, 0, err } From 872fc559f54ac9e343a2c4d9df668d0eaabf57f6 Mon Sep 17 00:00:00 2001 From: Gildas Cherruel <gildas.cherruel@inin.com> Date: Tue, 1 Dec 2015 13:44:34 +0900 Subject: [PATCH 058/108] Export of Hyper-V/VMCX machines --- powershell/hyperv/hyperv.go | 92 +++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 7a981cb1e..e34907521 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -288,6 +288,98 @@ func ExportVirtualMachine(vmName string, path string) error { var script = ` param([string]$vmName, [string]$path) Export-VM -Name $vmName -Path $path + +if (Test-Path -Path ([IO.Path]::Combine($path, $vmName, 'Virtual Machines', '*.VMCX'))) +{ + $vm = Get-VM -Name $vmName + $vm_adapter = Get-VMNetworkAdapter -VM $vm | Select -First 1 + + $config = [xml]@" +<?xml version="1.0" ?> +<configuration> + <properties> + <subtype type="integer">$($vm.Generation - 1)</subtype> + <name type="string">$($vm.Name)</name> + </properties> + <settings> + <processors> + <count type="integer">$($vm.ProcessorCount)</count> + </processors> + <memory> + <bank> + <dynamic_memory_enabled type="bool">$($vm.DynamicMemoryEnabled)</dynamic_memory_enabled> + <limit type="integer">$($vm.MemoryMaximum / 1MB)</limit> + <reservation type="integer">$($vm.MemoryMinimum / 1MB)</reservation> + <size type="integer">$($vm.MemoryStartup / 1MB)</size> + </bank> + </memory> + </settings> + <AltSwitchName type="string">$($vm_adapter.SwitchName)</AltSwitchName> + <boot> + <device0 type="string">Optical</device0> + </boot> + <secure_boot_enabled type="bool">False</secure_boot_enabled> + <notes type="string">$($vm.Notes)</notes> + <vm-controllers/> +</configuration> +"@ + + if ($vm.Generation -eq 1) + { + $vm_controllers = Get-VMIdeController -VM $vm + $controller_type = $config.SelectSingleNode('/configuration/vm-controllers') + # IDE controllers are not stored in a special XML container + } + else + { + $vm_controllers = Get-VMScsiController -VM $vm + $controller_type = $config.CreateElement('scsi') + $controller_type.SetAttribute('ChannelInstanceGuid', 'x') + # SCSI controllers are stored in the scsi XML container + if ((Get-VMFirmware -VM $vm).SecureBoot -eq [Microsoft.HyperV.PowerShell.OnOffState]::On) + { + $config.configuration.secure_boot_enabled.'#text' = 'True' + } + else + { + $config.configuration.secure_boot_enabled.'#text' = 'False' + } + } + + $vm_controllers | ForEach { + $controller = $config.CreateElement('controller' + $_.ControllerNumber) + $_.Drives | ForEach { + $drive = $config.CreateElement('drive' + ($_.DiskNumber + 0)) + $drive_path = $config.CreateElement('pathname') + $drive_path.SetAttribute('type', 'string') + $drive_path.AppendChild($config.CreateTextNode($_.Path)) + $drive_type = $config.CreateElement('type') + $drive_type.SetAttribute('type', 'string') + if ($_ -is [Microsoft.HyperV.PowerShell.HardDiskDrive]) + { + $drive_type.AppendChild($config.CreateTextNode('VHD')) + } + elseif ($_ -is [Microsoft.HyperV.PowerShell.DvdDrive]) + { + $drive_type.AppendChild($config.CreateTextNode('ISO')) + } + else + { + $drive_type.AppendChild($config.CreateTextNode('NONE')) + } + $drive.AppendChild($drive_path) + $drive.AppendChild($drive_type) + $controller.AppendChild($drive) + } + $controller_type.AppendChild($controller) + } + if ($controller_type.Name -ne 'vm-controllers') + { + $config.SelectSingleNode('/configuration/vm-controllers').AppendChild($controller_type) + } + + $config.Save([IO.Path]::Combine($path, $vm.Name, 'Virtual Machines', 'box.xml')) +} ` var ps powershell.PowerShellCmd From 181132f32bfbd5cb5c8e0593240dc5794e18514c Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 5 Dec 2015 03:13:38 -0800 Subject: [PATCH 059/108] Forget to pass in the dvd drive to eject the iso from --- powershell/hyperv/hyperv.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index e34907521..8c330f426 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -91,7 +91,7 @@ $lastControllerNumber script = ` param([string]$vmName, [string]$isoPath, [int]$controllerNumber) $dvdController = Add-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -path $isoPath -Passthru -Set-VMDvdDrive -path $null +$dvdController | Set-VMDvdDrive -path $null $dvdController.ControllerLocation ` From de447779f8b4c96b5a018233384227b837629905 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 5 Dec 2015 03:41:30 -0800 Subject: [PATCH 060/108] Windows Server 2016 won't let you set the first boot device --- powershell/hyperv/hyperv.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 8c330f426..92d9cfd5c 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -151,7 +151,7 @@ Set-VMBios -VMName $vmName -StartupOrder @("CD", "IDE","LegacyNetworkAdapter","F param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation) $vmDvdDrive = Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation if (!$vmDvdDrive) {throw 'unable to find dvd drive'} -Set-VMFirmware -VMName $vmName -FirstBootDevice $vmDvdDrive +Set-VMFirmware -VMName $vmName -FirstBootDevice $vmDvdDrive -ErrorAction SilentlyContinue ` var ps powershell.PowerShellCmd err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10)) From fb098fcbf09451007b4a7a10e80d2931e69f9999 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Mon, 25 Jan 2016 22:43:40 +0000 Subject: [PATCH 061/108] http server has moved to common step --- builder/hyperv/common/run_config.go | 18 ------ builder/hyperv/common/step_http_server.go | 78 ----------------------- builder/hyperv/iso/builder.go | 4 +- 3 files changed, 3 insertions(+), 97 deletions(-) delete mode 100644 builder/hyperv/common/step_http_server.go diff --git a/builder/hyperv/common/run_config.go b/builder/hyperv/common/run_config.go index 4b73ae532..3ac72471c 100644 --- a/builder/hyperv/common/run_config.go +++ b/builder/hyperv/common/run_config.go @@ -1,7 +1,6 @@ package common import ( - "errors" "fmt" "github.com/mitchellh/packer/template/interpolate" "time" @@ -11,10 +10,6 @@ type RunConfig struct { Headless bool `mapstructure:"headless"` RawBootWait string `mapstructure:"boot_wait"` - HTTPDir string `mapstructure:"http_directory"` - HTTPPortMin uint `mapstructure:"http_port_min"` - HTTPPortMax uint `mapstructure:"http_port_max"` - BootWait time.Duration `` } @@ -23,14 +18,6 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { c.RawBootWait = "10s" } - if c.HTTPPortMin == 0 { - c.HTTPPortMin = 8000 - } - - if c.HTTPPortMax == 0 { - c.HTTPPortMax = 9000 - } - var errs []error var err error @@ -42,10 +29,5 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { } } - if c.HTTPPortMin > c.HTTPPortMax { - errs = append(errs, - errors.New("http_port_min must be less than http_port_max")) - } - return errs } diff --git a/builder/hyperv/common/step_http_server.go b/builder/hyperv/common/step_http_server.go deleted file mode 100644 index 55874992e..000000000 --- a/builder/hyperv/common/step_http_server.go +++ /dev/null @@ -1,78 +0,0 @@ -package common - -import ( - "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "log" - "math/rand" - "net" - "net/http" -) - -// This step creates and runs the HTTP server that is serving files from the -// directory specified by the 'http_directory` configuration parameter in the -// template. -// -// Uses: -// ui packer.Ui -// -// Produces: -// http_port int - The port the HTTP server started on. -type StepHTTPServer struct { - HTTPDir string - HTTPPortMin uint - HTTPPortMax uint - - l net.Listener -} - -func (s *StepHTTPServer) Run(state multistep.StateBag) multistep.StepAction { - ui := state.Get("ui").(packer.Ui) - - var httpPort uint = 0 - if s.HTTPDir == "" { - state.Put("http_port", httpPort) - return multistep.ActionContinue - } - - // Find an available TCP port for our HTTP server - var httpAddr string - portRange := int(s.HTTPPortMax - s.HTTPPortMin) - for { - var err error - var offset uint = 0 - - if portRange > 0 { - // Intn will panic if portRange == 0, so we do a check. - offset = uint(rand.Intn(portRange)) - } - - httpPort = offset + s.HTTPPortMin - httpAddr = fmt.Sprintf("0.0.0.0:%d", httpPort) - log.Printf("Trying port: %d", httpPort) - s.l, err = net.Listen("tcp", httpAddr) - if err == nil { - break - } - } - - ui.Say(fmt.Sprintf("Starting HTTP server on port %d", httpPort)) - - // Start the HTTP server and run it in the background - fileServer := http.FileServer(http.Dir(s.HTTPDir)) - server := &http.Server{Addr: httpAddr, Handler: fileServer} - go server.Serve(s.l) - - // Save the address into the state so it can be accessed in the future - state.Put("http_port", httpPort) - - return multistep.ActionContinue -} - -func (s *StepHTTPServer) Cleanup(multistep.StateBag) { - if s.l != nil { - // Close the listener so that the HTTP server stops - s.l.Close() - } -} diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 430499e48..c98e584ca 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -46,6 +46,7 @@ type Builder struct { type Config struct { common.PackerConfig `mapstructure:",squash"` + common.HTTPConfig `mapstructure:",squash"` hypervcommon.FloppyConfig `mapstructure:",squash"` hypervcommon.OutputConfig `mapstructure:",squash"` hypervcommon.SSHConfig `mapstructure:",squash"` @@ -141,6 +142,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { // Accumulate any errors and warnings var errs *packer.MultiError errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...) + errs = packer.MultiErrorAppend(errs, b.config.HTTPConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.OutputConfig.Prepare(&b.config.ctx, &b.config.PackerConfig)...) errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(&b.config.ctx)...) @@ -348,7 +350,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &common.StepCreateFloppy{ Files: b.config.FloppyFiles, }, - &hypervcommon.StepHTTPServer{ + &common.StepHTTPServer{ HTTPDir: b.config.HTTPDir, HTTPPortMin: b.config.HTTPPortMin, HTTPPortMax: b.config.HTTPPortMax, From 7c754bf7912a185173a07d85f3f3b0f0bfc3a40a Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Wed, 29 Jun 2016 22:53:29 +0100 Subject: [PATCH 062/108] Try to match style of other builders --- builder/hyperv/iso/builder.go | 84 +++---------------- plugin/builder-hyperv-iso/build-and-deploy.sh | 3 - plugin/builder-hyperv-iso/main.go | 15 ---- plugin/builder-hyperv-iso/main_test.go | 5 -- 4 files changed, 12 insertions(+), 95 deletions(-) delete mode 100644 plugin/builder-hyperv-iso/build-and-deploy.sh delete mode 100644 plugin/builder-hyperv-iso/main.go delete mode 100644 plugin/builder-hyperv-iso/main_test.go diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index c98e584ca..849e7d90a 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -47,6 +47,7 @@ type Builder struct { type Config struct { common.PackerConfig `mapstructure:",squash"` common.HTTPConfig `mapstructure:",squash"` + common.ISOConfig `mapstructure:",squash"` hypervcommon.FloppyConfig `mapstructure:",squash"` hypervcommon.OutputConfig `mapstructure:",squash"` hypervcommon.SSHConfig `mapstructure:",squash"` @@ -72,30 +73,6 @@ type Config struct { // SecondaryDvdImages []string `mapstructure:"secondary_iso_images"` - // 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. - ISOChecksum string `mapstructure:"iso_checksum"` - // 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. - ISOChecksumType string `mapstructure:"iso_checksum_type"` - // 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. - RawSingleISOUrl string `mapstructure:"iso_url"` - - // Multiple URLs for the ISO to download. Packer will try these in order. - // If anything goes wrong attempting to download or while downloading a - // single URL, it will move on to the next. All URLs must point to the - // same file (same checksum). By default this is empty and iso_url is - // used. Only one of iso_url or iso_urls can be specified. - ISOUrls []string `mapstructure:"iso_urls"` - - TargetPath string `mapstructure:"iso_target_path"` - // Should integration services iso be mounted GuestAdditionsMode string `mapstructure:"guest_additions_mode"` @@ -141,13 +118,18 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { // Accumulate any errors and warnings var errs *packer.MultiError + warnings := make([]string, 0) + + isoWarnings, isoErrs := b.config.ISOConfig.Prepare(&b.config.ctx) + warnings = append(warnings, isoWarnings...) + errs = packer.MultiErrorAppend(errs, isoErrs...) + errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.HTTPConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.OutputConfig.Prepare(&b.config.ctx, &b.config.PackerConfig)...) errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(&b.config.ctx)...) - warnings := make([]string, 0) err = b.checkDiskSize() if err != nil { @@ -205,47 +187,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { log.Println(fmt.Sprintf("%s: %v", "Communicator", b.config.Communicator)) // Errors - if b.config.ISOChecksumType == "" { - errs = packer.MultiErrorAppend( - errs, errors.New("The iso_checksum_type must be specified.")) - } else { - b.config.ISOChecksumType = strings.ToLower(b.config.ISOChecksumType) - if b.config.ISOChecksumType != "none" { - if b.config.ISOChecksum == "" { - errs = packer.MultiErrorAppend( - errs, errors.New("Due to large file sizes, an iso_checksum is required")) - } else { - b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum) - } - - if h := common.HashForType(b.config.ISOChecksumType); h == nil { - errs = packer.MultiErrorAppend( - errs, - fmt.Errorf("Unsupported checksum type: %s", b.config.ISOChecksumType)) - } - } - } - - if b.config.RawSingleISOUrl == "" && len(b.config.ISOUrls) == 0 { - errs = packer.MultiErrorAppend( - errs, errors.New("One of iso_url or iso_urls must be specified.")) - } else if b.config.RawSingleISOUrl != "" && len(b.config.ISOUrls) > 0 { - errs = packer.MultiErrorAppend( - errs, errors.New("Only one of iso_url or iso_urls may be specified.")) - } else if b.config.RawSingleISOUrl != "" { - b.config.ISOUrls = []string{b.config.RawSingleISOUrl} - } - - for i, url := range b.config.ISOUrls { - b.config.ISOUrls[i], err = common.DownloadableURL(url) - if err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("Failed to parse iso_url %d: %s", i+1, err)) - } - } - - log.Println(fmt.Sprintf("%s: %v", "RawSingleISOUrl", b.config.RawSingleISOUrl)) - if b.config.GuestAdditionsMode == "" { b.config.GuestAdditionsMode = "attach" } @@ -291,11 +232,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } // Warnings - if b.config.ISOChecksumType == "none" { - warnings = append(warnings, - "A checksum type of 'none' was specified. Since ISO files are so big,\n"+ - "a checksum is highly recommended.") - } if b.config.ShutdownCommand == "" { warnings = append(warnings, @@ -328,6 +264,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe state := new(multistep.BasicStateBag) state.Put("cache", cache) state.Put("config", &b.config) + state.Put("debug", b.config.PackerDebug) state.Put("driver", driver) state.Put("hook", hook) state.Put("ui", ui) @@ -434,13 +371,16 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // Run the steps. if b.config.PackerDebug { + pauseFn := common.MultistepDebugFn(ui) + state.Put("pauseFn", pauseFn) b.runner = &multistep.DebugRunner{ Steps: steps, - PauseFn: common.MultistepDebugFn(ui), + PauseFn: pauseFn, } } else { b.runner = &multistep.BasicRunner{Steps: steps} } + b.runner.Run(state) // Report any errors. diff --git a/plugin/builder-hyperv-iso/build-and-deploy.sh b/plugin/builder-hyperv-iso/build-and-deploy.sh deleted file mode 100644 index 8e0185a4e..000000000 --- a/plugin/builder-hyperv-iso/build-and-deploy.sh +++ /dev/null @@ -1,3 +0,0 @@ -go build -cp packer-builder-hyperv-iso.exe ../../../bin/ - diff --git a/plugin/builder-hyperv-iso/main.go b/plugin/builder-hyperv-iso/main.go deleted file mode 100644 index 664615049..000000000 --- a/plugin/builder-hyperv-iso/main.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "github.com/mitchellh/packer/builder/hyperv/iso" - "github.com/mitchellh/packer/packer/plugin" -) - -func main() { - server, err := plugin.Server() - if err != nil { - panic(err) - } - server.RegisterBuilder(new(iso.Builder)) - server.Serve() -} diff --git a/plugin/builder-hyperv-iso/main_test.go b/plugin/builder-hyperv-iso/main_test.go deleted file mode 100644 index b07e18080..000000000 --- a/plugin/builder-hyperv-iso/main_test.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package main From 5d5809b27e1ef229c5288a072dce540620d37974 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Wed, 29 Jun 2016 23:07:27 +0100 Subject: [PATCH 063/108] Make hiding of progress backward compatible with old versions of powershell --- powershell/powershell_test.go | 2 +- provisioner/powershell/elevated.go | 2 +- provisioner/powershell/provisioner.go | 6 +++--- provisioner/powershell/provisioner_test.go | 18 +++++++++--------- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/powershell/powershell_test.go b/powershell/powershell_test.go index 0925e3c74..2dcc36085 100644 --- a/powershell/powershell_test.go +++ b/powershell/powershell_test.go @@ -37,7 +37,7 @@ func TestOutput(t *testing.T) { func TestRunFile(t *testing.T) { var blockBuffer bytes.Buffer - blockBuffer.WriteString(`param([string]$a, [string]$b, [int]$x, [int]$y) $ProgressPreference='SilentlyContinue'; $n = $x + $y; Write-Output "$a $b $n";`) + blockBuffer.WriteString(`param([string]$a, [string]$b, [int]$x, [int]$y) if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $n = $x + $y; Write-Output "$a $b $n";`) var ps PowerShellCmd cmdOut, err := ps.Output(blockBuffer.String(), "a", "b", "5", "10") diff --git a/provisioner/powershell/elevated.go b/provisioner/powershell/elevated.go index cb3336ae2..80bdc005a 100644 --- a/provisioner/powershell/elevated.go +++ b/provisioner/powershell/elevated.go @@ -58,7 +58,7 @@ $t.XmlText = @' </Actions> </Task> '@ -$ProgressPreference="SilentlyContinue"; +if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"} $f = $s.GetFolder("\") $f.RegisterTaskDefinition($name, $t, 6, "{{.User}}", "{{.Password}}", 1, $null) | Out-Null $t = $f.GetTask("\$name") diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index 90cde164f..4d8a98e5a 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -120,11 +120,11 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if p.config.ExecuteCommand == "" { - p.config.ExecuteCommand = `powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}'` + p.config.ExecuteCommand = `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}'` } if p.config.ElevatedExecuteCommand == "" { - p.config.ElevatedExecuteCommand = `powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}'` + p.config.ElevatedExecuteCommand = `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}'` } if p.config.Inline != nil && len(p.config.Inline) == 0 { @@ -425,7 +425,7 @@ func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath strin Password: p.config.ElevatedPassword, TaskDescription: "Packer elevated task", TaskName: fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()), - EncodedCommand: powershellEncode([]byte("$ProgressPreference=\"SilentlyContinue\"; " + command + "; exit $LASTEXITCODE")), + EncodedCommand: powershellEncode([]byte("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; " + command + "; exit $LASTEXITCODE")), }) if err != nil { diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index 87f6680ad..2719a9621 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -75,12 +75,12 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { t.Error("expected elevated_password to be empty") } - if p.config.ExecuteCommand != `powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}'` { - t.Fatalf("Default command should be powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ExecuteCommand) + if p.config.ExecuteCommand != `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}'` { + t.Fatalf("Default command should be powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ExecuteCommand) } - if p.config.ElevatedExecuteCommand != `powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}'` { - t.Fatalf("Default command should be powershell powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ElevatedExecuteCommand) + if p.config.ElevatedExecuteCommand != `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}'` { + t.Fatalf("Default command should be powershell powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ElevatedExecuteCommand) } if p.config.ValidExitCodes == nil { @@ -389,7 +389,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& {$ProgressPreference=\"SilentlyContinue\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand := `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -408,7 +408,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand = `powershell '& {$ProgressPreference=\"SilentlyContinue\"; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand = `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -435,7 +435,7 @@ func TestProvisionerProvision_Scripts(t *testing.T) { } //powershell -Command "$env:PACKER_BUILDER_TYPE=''"; powershell -Command "$env:PACKER_BUILD_NAME='foobuild'"; powershell -Command c:/Windows/Temp/script.ps1 - expectedCommand := `powershell '& {$ProgressPreference=\"SilentlyContinue\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -468,7 +468,7 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& {$ProgressPreference=\"SilentlyContinue\"; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -582,7 +582,7 @@ func TestProvision_createCommandText(t *testing.T) { // Non-elevated cmd, _ := p.createCommandText() - if cmd != `powershell '& {$ProgressPreference=\"SilentlyContinue\"; $env:PACKER_BUILDER_TYPE=\"\"; $env:PACKER_BUILD_NAME=\"\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` { + if cmd != `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:PACKER_BUILDER_TYPE=\"\"; $env:PACKER_BUILD_NAME=\"\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` { t.Fatalf("Got unexpected non-elevated command: %s", cmd) } From 4d22bd1d4add5f2f34347c759e180fe3d5eba2a4 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Tue, 29 Mar 2016 22:56:26 +0100 Subject: [PATCH 064/108] Generate switch name using uuid v1 --- builder/hyperv/common/step_create_external_switch.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/builder/hyperv/common/step_create_external_switch.go b/builder/hyperv/common/step_create_external_switch.go index 4ab1c335b..75249b8b4 100644 --- a/builder/hyperv/common/step_create_external_switch.go +++ b/builder/hyperv/common/step_create_external_switch.go @@ -5,9 +5,10 @@ package common import ( - "code.google.com/p/go-uuid/uuid" "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/common/uuid" "github.com/mitchellh/packer/packer" ) @@ -30,7 +31,7 @@ func (s *StepCreateExternalSwitch) Run(state multistep.StateBag) multistep.StepA ui.Say("Creating external switch...") - packerExternalSwitchName := "paes_" + uuid.New() + packerExternalSwitchName := "paes_" + uuid.TimeOrderedUUID() err = driver.CreateExternalVirtualSwitch(vmName, packerExternalSwitchName) if err != nil { From 807d7eaced99188b8a389ed9bd16d87c5dac5a64 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Fri, 1 Apr 2016 10:51:05 +0100 Subject: [PATCH 065/108] On windows a lot of git clients will convert LF to CRLF. This would be a problem where file contents are compared exactly --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..7230f36f4 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ + +common/test-fixtures/root/* eol=lf \ No newline at end of file From e5510873bb8a10ba3742c6248fd1438c41105cb6 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Fri, 1 Apr 2016 10:55:02 +0100 Subject: [PATCH 066/108] Added file with correct line endings When dealing with windows the file url format is file:///c:/ On windows a lot of git clients will convert LF to CRLF. This would be a problem where file contents are compared exactly --- common/config_test.go | 2 +- common/download_test.go | 10 +++++++++- common/iso_config.go | 9 ++++++++- common/iso_config_test.go | 9 +++++++-- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/common/config_test.go b/common/config_test.go index 92a7316a3..8bd684739 100644 --- a/common/config_test.go +++ b/common/config_test.go @@ -120,7 +120,7 @@ func TestDownloadableURL_FilePaths(t *testing.T) { }() // Test some cases with and without a schema prefix - for _, prefix := range []string{"", "file://"} { + for _, prefix := range []string{"", filePrefix} { // Nonexistent file _, err = DownloadableURL(prefix + "i/dont/exist") if err != nil { diff --git a/common/download_test.go b/common/download_test.go index 51f6f270c..497a05649 100644 --- a/common/download_test.go +++ b/common/download_test.go @@ -8,6 +8,7 @@ import ( "net/http" "net/http/httptest" "os" + "runtime" "testing" ) @@ -56,6 +57,7 @@ func TestDownloadClient_basic(t *testing.T) { Url: ts.URL + "/basic.txt", TargetPath: tf.Name(), }) + path, err := client.Get() if err != nil { t.Fatalf("err: %s", err) @@ -352,7 +354,13 @@ func TestDownloadFileUrl(t *testing.T) { // source_path is a file path and source is a network path sourcePath := fmt.Sprintf("%s/test-fixtures/fileurl/%s", cwd, "cake") - source := fmt.Sprintf("file://" + sourcePath) + + filePrefix := "file://" + if runtime.GOOS == "windows" { + filePrefix += "/" + } + + source := fmt.Sprintf(filePrefix + sourcePath) t.Logf("Trying to download %s", source) config := &DownloadConfig{ diff --git a/common/iso_config.go b/common/iso_config.go index 655e92d98..49ee681ab 100644 --- a/common/iso_config.go +++ b/common/iso_config.go @@ -8,6 +8,7 @@ import ( "net/url" "os" "path/filepath" + "runtime" "strings" "github.com/mitchellh/packer/template/interpolate" @@ -77,7 +78,13 @@ func (c *ISOConfig) Prepare(ctx *interpolate.Context) (warnings []string, errs [ return warnings, errs } case "file": - file, err := os.Open(u.Path) + path := u.Path + + if runtime.GOOS == "windows" && len(path) > 2 && path[0] == '/' && path[2] == ':' { + path = strings.TrimLeft(path, "/") + } + + file, err := os.Open(path) if err != nil { errs = append(errs, err) return warnings, errs diff --git a/common/iso_config_test.go b/common/iso_config_test.go index 845e4e9dc..b64a00bcf 100644 --- a/common/iso_config_test.go +++ b/common/iso_config_test.go @@ -6,6 +6,7 @@ import ( "net/http" "net/http/httptest" "reflect" + "runtime" "testing" ) @@ -80,7 +81,11 @@ func TestISOConfigPrepare_ISOChecksumURL(t *testing.T) { i.ISOChecksum = "" cs_file, _ := ioutil.TempFile("", "packer-test-") ioutil.WriteFile(cs_file.Name(), []byte(cs_bsd_style), 0666) - i.ISOChecksumURL = fmt.Sprintf("file://%s", cs_file.Name()) + filePrefix := "file://" + if runtime.GOOS == "windows" { + filePrefix += "/" + } + i.ISOChecksumURL = fmt.Sprintf("%s%s", filePrefix, cs_file.Name()) warns, err = i.Prepare(nil) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) @@ -98,7 +103,7 @@ func TestISOConfigPrepare_ISOChecksumURL(t *testing.T) { i.ISOChecksum = "" cs_file, _ = ioutil.TempFile("", "packer-test-") ioutil.WriteFile(cs_file.Name(), []byte(cs_gnu_style), 0666) - i.ISOChecksumURL = fmt.Sprintf("file://%s", cs_file.Name()) + i.ISOChecksumURL = fmt.Sprintf("%s%s", filePrefix, cs_file.Name()) warns, err = i.Prepare(nil) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) From 4721327bf69ae0c81fd1ddf56ef24c32bc0b5723 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Thu, 30 Jun 2016 06:45:19 +0100 Subject: [PATCH 067/108] Fix duplicate imports --- command/plugin.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/command/plugin.go b/command/plugin.go index 2086f5890..e48cc709e 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -35,6 +35,7 @@ import ( virtualboxovfbuilder "github.com/mitchellh/packer/builder/virtualbox/ovf" vmwareisobuilder "github.com/mitchellh/packer/builder/vmware/iso" vmwarevmxbuilder "github.com/mitchellh/packer/builder/vmware/vmx" + amazonimportpostprocessor "github.com/mitchellh/packer/post-processor/amazon-import" artificepostprocessor "github.com/mitchellh/packer/post-processor/artifice" atlaspostprocessor "github.com/mitchellh/packer/post-processor/atlas" @@ -50,6 +51,7 @@ import ( vagrantpostprocessor "github.com/mitchellh/packer/post-processor/vagrant" vagrantcloudpostprocessor "github.com/mitchellh/packer/post-processor/vagrant-cloud" vspherepostprocessor "github.com/mitchellh/packer/post-processor/vsphere" + ansibleprovisioner "github.com/mitchellh/packer/provisioner/ansible" ansiblelocalprovisioner "github.com/mitchellh/packer/provisioner/ansible-local" chefclientprovisioner "github.com/mitchellh/packer/provisioner/chef-client" From 8c3239770d2360b709d6387523c3b4f1d6240619 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Thu, 30 Jun 2016 07:14:42 +0100 Subject: [PATCH 068/108] Skip these tests on platforms that don't have powershell installed --- powershell/powershell_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/powershell/powershell_test.go b/powershell/powershell_test.go index 2dcc36085..43943cb69 100644 --- a/powershell/powershell_test.go +++ b/powershell/powershell_test.go @@ -7,6 +7,13 @@ import ( func TestOutput(t *testing.T) { var ps PowerShellCmd + + powerShellPath, err := ps.getPowerShellPath() + if err != nil { + t.Skipf("powershell not installed: %s", err) + return + } + cmdOut, err := ps.Output("") if err != nil { t.Fatalf("should not have error: %s", err) @@ -40,6 +47,13 @@ func TestRunFile(t *testing.T) { blockBuffer.WriteString(`param([string]$a, [string]$b, [int]$x, [int]$y) if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $n = $x + $y; Write-Output "$a $b $n";`) var ps PowerShellCmd + + powerShellPath, err := ps.getPowerShellPath() + if err != nil { + t.Skipf("powershell not installed: %s", err) + return + } + cmdOut, err := ps.Output(blockBuffer.String(), "a", "b", "5", "10") if err != nil { From 52c6cd5fc1992021699d3be947492e31b872573e Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Thu, 30 Jun 2016 20:21:57 +0100 Subject: [PATCH 069/108] Test should run and pass even when run on an environment that does not have Powershel. --- builder/hyperv/iso/builder.go | 41 ++++++++++++------- builder/hyperv/iso/builder_test.go | 4 +- powershell/powershell.go | 64 +++++++++++++++++------------- powershell/powershell_test.go | 25 +++++++----- 4 files changed, 80 insertions(+), 54 deletions(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 849e7d90a..03c1e3f3f 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -7,6 +7,11 @@ package iso import ( "errors" "fmt" + "log" + "os" + "strings" + "time" + "github.com/mitchellh/multistep" hypervcommon "github.com/mitchellh/packer/builder/hyperv/common" "github.com/mitchellh/packer/common" @@ -16,10 +21,6 @@ import ( powershell "github.com/mitchellh/packer/powershell" "github.com/mitchellh/packer/powershell/hyperv" "github.com/mitchellh/packer/template/interpolate" - "log" - "os" - "strings" - "time" ) const ( @@ -148,13 +149,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName)) if b.config.SwitchName == "" { - // no switch name, try to get one attached to a online network adapter - onlineSwitchName, err := hyperv.GetExternalOnlineVirtualSwitch() - if onlineSwitchName == "" || err != nil { - b.config.SwitchName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) - } else { - b.config.SwitchName = onlineSwitchName - } + b.config.SwitchName = b.detectSwitchName() } if b.config.Cpu < 1 { @@ -455,11 +450,29 @@ func (b *Builder) checkRamSize() error { } func (b *Builder) checkHostAvailableMemory() string { - freeMB := powershell.GetHostAvailableMemory() + powershellAvailable, _, _ := powershell.IsPowershellAvailable() - if (freeMB - float64(b.config.RamSizeMB)) < LowRam { - return fmt.Sprintf("Hyper-V might fail to create a VM if there is not enough free memory in the system.") + if powershellAvailable { + freeMB := powershell.GetHostAvailableMemory() + + if (freeMB - float64(b.config.RamSizeMB)) < LowRam { + return fmt.Sprintf("Hyper-V might fail to create a VM if there is not enough free memory in the system.") + } } return "" } + +func (b *Builder) detectSwitchName() string { + powershellAvailable, _, _ := powershell.IsPowershellAvailable() + + if powershellAvailable { + // no switch name, try to get one attached to a online network adapter + onlineSwitchName, err := hyperv.GetExternalOnlineVirtualSwitch() + if onlineSwitchName != "" && err == nil { + return onlineSwitchName + } + } + + return fmt.Sprintf("packer-%s", b.config.PackerBuildName) +} diff --git a/builder/hyperv/iso/builder_test.go b/builder/hyperv/iso/builder_test.go index 5cc04163e..4d8f193a0 100644 --- a/builder/hyperv/iso/builder_test.go +++ b/builder/hyperv/iso/builder_test.go @@ -1,9 +1,10 @@ package iso import ( - "github.com/mitchellh/packer/packer" "reflect" "testing" + + "github.com/mitchellh/packer/packer" ) func testConfig() map[string]interface{} { @@ -31,6 +32,7 @@ func TestBuilder_ImplementsBuilder(t *testing.T) { func TestBuilderPrepare_Defaults(t *testing.T) { var b Builder config := testConfig() + warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) diff --git a/powershell/powershell.go b/powershell/powershell.go index 28c3d908b..051f03a67 100644 --- a/powershell/powershell.go +++ b/powershell/powershell.go @@ -5,20 +5,20 @@ package powershell import ( + "bytes" "fmt" - "log" "io" + "io/ioutil" + "log" "os" "os/exec" - "strings" - "bytes" - "io/ioutil" "strconv" + "strings" ) const ( powerShellFalse = "False" - powerShellTrue = "True" + powerShellTrue = "True" ) type PowerShellCmd struct { @@ -31,14 +31,14 @@ func (ps *PowerShellCmd) Run(fileContents string, params ...string) error { return err } -// Output runs the PowerShell command and returns its standard output. +// Output runs the PowerShell command and returns its standard output. func (ps *PowerShellCmd) Output(fileContents string, params ...string) (string, error) { - path, err := ps.getPowerShellPath(); + path, err := ps.getPowerShellPath() if err != nil { - return "", nil + return "", err } - filename, err := saveScript(fileContents); + filename, err := saveScript(fileContents) if err != nil { return "", err } @@ -49,7 +49,7 @@ func (ps *PowerShellCmd) Output(fileContents string, params ...string) (string, if !debug { defer os.Remove(filename) } - + args := createArgs(filename, params...) if verbose { @@ -93,13 +93,23 @@ func (ps *PowerShellCmd) Output(fileContents string, params ...string) (string, log.Printf("stderr: %s", stderrString) } - return stdoutString, err; + return stdoutString, err +} + +func IsPowershellAvailable() (bool, string, error) { + path, err := exec.LookPath("powershell") + if err != nil { + return false, "", err + } else { + return false, path, err + } } func (ps *PowerShellCmd) getPowerShellPath() (string, error) { - path, err := exec.LookPath("powershell") - if err != nil { - log.Fatal("Cannot find PowerShell in the path", err) + powershellAvailable, path, err := IsPowershellAvailable() + + if !powershellAvailable { + log.Fatal("Cannot find PowerShell in the path") return "", err } @@ -111,7 +121,7 @@ func saveScript(fileContents string) (string, error) { if err != nil { return "", err } - + _, err = file.Write([]byte(fileContents)) if err != nil { return "", err @@ -132,7 +142,7 @@ func saveScript(fileContents string) (string, error) { } func createArgs(filename string, params ...string) []string { - args := make([]string,len(params)+4) + args := make([]string, len(params)+4) args[0] = "-ExecutionPolicy" args[1] = "Bypass" @@ -141,9 +151,9 @@ func createArgs(filename string, params ...string) []string { for key, value := range params { args[key+4] = value - } + } - return args; + return args } func GetHostAvailableMemory() float64 { @@ -158,7 +168,6 @@ func GetHostAvailableMemory() float64 { return freeMB } - func GetHostName(ip string) (string, error) { var script = ` @@ -174,7 +183,7 @@ try { // var ps PowerShellCmd - cmdOut, err := ps.Output(script, ip); + cmdOut, err := ps.Output(script, ip) if err != nil { return "", err } @@ -190,8 +199,8 @@ $administratorRole = [System.Security.Principal.WindowsBuiltInRole]::Administrat return $principal.IsInRole($administratorRole) ` - var ps PowerShellCmd - cmdOut, err := ps.Output(script); + var ps PowerShellCmd + cmdOut, err := ps.Output(script) if err != nil { return false, err } @@ -200,14 +209,13 @@ return $principal.IsInRole($administratorRole) return res == powerShellTrue, nil } - func ModuleExists(moduleName string) (bool, error) { var script = ` param([string]$moduleName) (Get-Module -Name $moduleName) -ne $null ` - var ps PowerShellCmd + var ps PowerShellCmd cmdOut, err := ps.Output(script) if err != nil { return false, err @@ -215,7 +223,7 @@ param([string]$moduleName) res := strings.TrimSpace(string(cmdOut)) - if(res == powerShellFalse){ + if res == powerShellFalse { err := fmt.Errorf("PowerShell %s module is not loaded. Make sure %s feature is on.", moduleName, moduleName) return false, err } @@ -249,7 +257,7 @@ $productKeyNode.InnerText = $productKey $unattend.Save($path) ` - var ps PowerShellCmd - err := ps.Run(script, path, productKey) - return err + var ps PowerShellCmd + err := ps.Run(script, path, productKey) + return err } diff --git a/powershell/powershell_test.go b/powershell/powershell_test.go index 43943cb69..91c9a83f8 100644 --- a/powershell/powershell_test.go +++ b/powershell/powershell_test.go @@ -6,11 +6,13 @@ import ( ) func TestOutput(t *testing.T) { + var ps PowerShellCmd - powerShellPath, err := ps.getPowerShellPath() - if err != nil { - t.Skipf("powershell not installed: %s", err) + powershellAvailable, _, _ := IsPowershellAvailable() + + if !powershellAvailable { + t.Skipf("powershell not installed") return } @@ -43,17 +45,18 @@ func TestOutput(t *testing.T) { } func TestRunFile(t *testing.T) { + var ps PowerShellCmd + + powershellAvailable, _, _ := IsPowershellAvailable() + + if !powershellAvailable { + t.Skipf("powershell not installed") + return + } + var blockBuffer bytes.Buffer blockBuffer.WriteString(`param([string]$a, [string]$b, [int]$x, [int]$y) if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $n = $x + $y; Write-Output "$a $b $n";`) - var ps PowerShellCmd - - powerShellPath, err := ps.getPowerShellPath() - if err != nil { - t.Skipf("powershell not installed: %s", err) - return - } - cmdOut, err := ps.Output(blockBuffer.String(), "a", "b", "5", "10") if err != nil { From 17597b48e1002717faac9b61fdc653612fe23e00 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Mon, 4 Jul 2016 23:44:33 +0100 Subject: [PATCH 070/108] Base64 encode powershell to avoid any necessary escaping --- powershell/powershell.go | 2 +- provisioner/powershell/powershell.go | 6 ++ provisioner/powershell/provisioner.go | 38 +++++++---- provisioner/powershell/provisioner_test.go | 77 ++++++++++++++-------- 4 files changed, 83 insertions(+), 40 deletions(-) diff --git a/powershell/powershell.go b/powershell/powershell.go index 051f03a67..044ec6384 100644 --- a/powershell/powershell.go +++ b/powershell/powershell.go @@ -101,7 +101,7 @@ func IsPowershellAvailable() (bool, string, error) { if err != nil { return false, "", err } else { - return false, path, err + return true, path, err } } diff --git a/provisioner/powershell/powershell.go b/provisioner/powershell/powershell.go index 1f5a7ffad..5f339d116 100644 --- a/provisioner/powershell/powershell.go +++ b/provisioner/powershell/powershell.go @@ -15,3 +15,9 @@ func powershellEncode(buffer []byte) string { input := []uint8(wideCmd) return base64.StdEncoding.EncodeToString(input) } + +func powershellDecode(message string) (retour string) { + base64Text := make([]byte, base64.StdEncoding.DecodedLen(len(message))) + base64.StdEncoding.Decode(base64Text, []byte(message)) + return string(base64Text) +} diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index 4d8a98e5a..ad7f71b0f 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -107,24 +107,25 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { }, }, }, raws...) + if err != nil { return err } if p.config.EnvVarFormat == "" { - p.config.EnvVarFormat = `$env:%s=\"%s\"; ` + p.config.EnvVarFormat = `$env:%s="%s"; ` } if p.config.ElevatedEnvVarFormat == "" { - p.config.ElevatedEnvVarFormat = `$env:%s=\"%s\"; ` + p.config.ElevatedEnvVarFormat = `$env:%s="%s"; ` } if p.config.ExecuteCommand == "" { - p.config.ExecuteCommand = `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}'` + p.config.ExecuteCommand = `{{.Vars}}{{.Path}}` } if p.config.ElevatedExecuteCommand == "" { - p.config.ElevatedExecuteCommand = `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}'` + p.config.ElevatedExecuteCommand = `{{.Vars}}{{.Path}}'` } if p.config.Inline != nil && len(p.config.Inline) == 0 { @@ -374,28 +375,41 @@ func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string, e } func (p *Provisioner) createCommandText() (command string, err error) { + // Return the interpolated command + if p.config.ElevatedUser == "" { + return p.createCommandTextNonPrivileged() + } else { + return p.createCommandTextPrivileged() + } +} + +func (p *Provisioner) createCommandTextNonPrivileged() (command string, err error) { // Create environment variables to set before executing the command flattenedEnvVars, err := p.createFlattenedEnvVars(false) if err != nil { return "", err } - p.config.ctx.Data = &ExecuteCommandTemplate{ Vars: flattenedEnvVars, Path: p.config.RemotePath, } command, err = interpolate.Render(p.config.ExecuteCommand, &p.config.ctx) + if err != nil { return "", fmt.Errorf("Error processing command: %s", err) } - // Return the interpolated command - if p.config.ElevatedUser == "" { - return command, nil - } + encodedCommand := "powershell -executionpolicy bypass -encodedCommand " + powershellEncode([]byte("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; "+command+"; exit $LastExitCode")) + return encodedCommand, err +} + +func (p *Provisioner) createCommandTextPrivileged() (command string, err error) { // Can't double escape the env vars, lets create shiny new ones - flattenedEnvVars, err = p.createFlattenedEnvVars(true) + flattenedEnvVars, err := p.createFlattenedEnvVars(true) + if err != nil { + return "", err + } p.config.ctx.Data = &ExecuteCommandTemplate{ Vars: flattenedEnvVars, Path: p.config.RemotePath, @@ -412,7 +426,7 @@ func (p *Provisioner) createCommandText() (command string, err error) { // Return the path to the elevated shell wrapper command = fmt.Sprintf("powershell -executionpolicy bypass -file \"%s\"", path) - return + return command, err } func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath string, err error) { @@ -425,7 +439,7 @@ func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath strin Password: p.config.ElevatedPassword, TaskDescription: "Packer elevated task", TaskName: fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()), - EncodedCommand: powershellEncode([]byte("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; " + command + "; exit $LASTEXITCODE")), + EncodedCommand: powershellEncode([]byte("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; " + command + "; exit $LastExitCode")), }) if err != nil { diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index 2719a9621..cb4c26eb3 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -75,12 +75,12 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { t.Error("expected elevated_password to be empty") } - if p.config.ExecuteCommand != `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}'` { - t.Fatalf("Default command should be powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ExecuteCommand) + if p.config.ExecuteCommand != `{{.Vars}}{{.Path}}` { + t.Fatalf("Default command should be '{{.Vars}}{{.Path}}', but got %s", p.config.ExecuteCommand) } - if p.config.ElevatedExecuteCommand != `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}'` { - t.Fatalf("Default command should be powershell powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ElevatedExecuteCommand) + if p.config.ElevatedExecuteCommand != `{{.Vars}}{{.Path}}'` { + t.Fatalf("Default command should be '{{.Vars}}{{.Path}}', but got %s", p.config.ElevatedExecuteCommand) } if p.config.ValidExitCodes == nil { @@ -95,8 +95,8 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { } } - if p.config.ElevatedEnvVarFormat != `$env:%s=\"%s\"; ` { - t.Fatalf(`Default command should be powershell '$env:%%s=\"%%s\"; ', but got %s`, p.config.ElevatedEnvVarFormat) + if p.config.ElevatedEnvVarFormat != `$env:%s="%s"; ` { + t.Fatalf(`Default command should be powershell '$env:%%s="%%s"; ', but got %s`, p.config.ElevatedEnvVarFormat) } } @@ -389,11 +389,15 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode` + expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` + expectedCommandEncoded := expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) // Should run the command without alteration - if comm.StartCmd.Command != expectedCommand { - t.Fatalf("Expect command to be: %s, got %s", expectedCommand, comm.StartCmd.Command) + if comm.StartCmd.Command != expectedCommandEncoded { + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) + t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) } envVars := make([]string, 2) @@ -408,11 +412,15 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand = `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand = `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode` + expectedCommandPrefix = `powershell -executionpolicy bypass -encodedCommand ` + expectedCommandEncoded = expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) // Should run the command without alteration - if comm.StartCmd.Command != expectedCommand { - t.Fatalf("Expect command to be: %s, got: %s", expectedCommand, comm.StartCmd.Command) + if comm.StartCmd.Command != expectedCommandEncoded { + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) + t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) } } @@ -434,12 +442,15 @@ func TestProvisionerProvision_Scripts(t *testing.T) { t.Fatal("should not have error") } - //powershell -Command "$env:PACKER_BUILDER_TYPE=''"; powershell -Command "$env:PACKER_BUILD_NAME='foobuild'"; powershell -Command c:/Windows/Temp/script.ps1 - expectedCommand := `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode` + expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` + expectedCommandEncoded := expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) // Should run the command without alteration - if comm.StartCmd.Command != expectedCommand { - t.Fatalf("Expect command to be %s NOT %s", expectedCommand, comm.StartCmd.Command) + if comm.StartCmd.Command != expectedCommandEncoded { + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) + t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) } } @@ -468,11 +479,15 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode` + expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` + expectedCommandEncoded := expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) // Should run the command without alteration - if comm.StartCmd.Command != expectedCommand { - t.Fatalf("Expect command to be %s NOT %s", expectedCommand, comm.StartCmd.Command) + if comm.StartCmd.Command != expectedCommandEncoded { + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) + t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) } } @@ -500,7 +515,7 @@ func TestProvisioner_createFlattenedElevatedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { + if flattenedEnvVars != `$env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -511,7 +526,7 @@ func TestProvisioner_createFlattenedElevatedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { + if flattenedEnvVars != `$env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -522,7 +537,7 @@ func TestProvisioner_createFlattenedElevatedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:BAZ=\"qux\"; $env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { + if flattenedEnvVars != `$env:BAZ="qux"; $env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } } @@ -545,7 +560,7 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { + if flattenedEnvVars != `$env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -556,7 +571,7 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { + if flattenedEnvVars != `$env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -567,7 +582,7 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:BAZ=\"qux\"; $env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { + if flattenedEnvVars != `$env:BAZ="qux"; $env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } } @@ -582,8 +597,16 @@ func TestProvision_createCommandText(t *testing.T) { // Non-elevated cmd, _ := p.createCommandText() - if cmd != `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:PACKER_BUILDER_TYPE=\"\"; $env:PACKER_BUILD_NAME=\"\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` { - t.Fatalf("Got unexpected non-elevated command: %s", cmd) + + expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:PACKER_BUILDER_TYPE=""; $env:PACKER_BUILD_NAME=""; c:/Windows/Temp/script.ps1; exit $LastExitCode` + expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` + expectedCommandEncoded := expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) + + // Should run the command without alteration + if cmd != expectedCommandEncoded { + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) + t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) } // Elevated From d61513bf77350605cfc408f713286433c96278b1 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Wed, 13 Jul 2016 00:28:14 +0100 Subject: [PATCH 071/108] Encode powershell using utf8 Fix a bug in the size of string that was returned when decoding a base64 string Added tests around encoding and decoding powershell scripts. Used [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes('powershell commands')) | clip to generate what base 64 strings should look like --- provisioner/powershell/powershell.go | 33 ++++--- provisioner/powershell/provisioner.go | 31 ++++++- provisioner/powershell/provisioner_test.go | 102 +++++++++++++++++---- 3 files changed, 132 insertions(+), 34 deletions(-) diff --git a/provisioner/powershell/powershell.go b/provisioner/powershell/powershell.go index 5f339d116..c24e7df5c 100644 --- a/provisioner/powershell/powershell.go +++ b/provisioner/powershell/powershell.go @@ -2,22 +2,33 @@ package powershell import ( "encoding/base64" + + "golang.org/x/text/encoding/unicode" ) -func powershellEncode(buffer []byte) string { - // 2 byte chars to make PowerShell happy - wideCmd := "" - for _, b := range buffer { - wideCmd += string(b) + "\x00" +func powershellUtf8(message string) (string, error) { + utf8 := unicode.UTF8 + utfEncoder := utf8.NewEncoder() + utf8EncodedMessage, err := utfEncoder.String(message) + + return utf8EncodedMessage, err +} + +func powershellEncode(message string) (string, error) { + utf8EncodedMessage, err := powershellUtf8(message) + if err != nil { + return "", err } // Base64 encode the command - input := []uint8(wideCmd) - return base64.StdEncoding.EncodeToString(input) + input := []uint8(utf8EncodedMessage) + return base64.StdEncoding.EncodeToString(input), nil } -func powershellDecode(message string) (retour string) { - base64Text := make([]byte, base64.StdEncoding.DecodedLen(len(message))) - base64.StdEncoding.Decode(base64Text, []byte(message)) - return string(base64Text) +func powershellDecode(message string) (retour string, err error) { + data, err := base64.StdEncoding.DecodeString(message) + if err != nil { + return "", err + } + return string(data), nil } diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index ad7f71b0f..be887e8bc 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -399,9 +399,25 @@ func (p *Provisioner) createCommandTextNonPrivileged() (command string, err erro return "", fmt.Errorf("Error processing command: %s", err) } - encodedCommand := "powershell -executionpolicy bypass -encodedCommand " + powershellEncode([]byte("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; "+command+"; exit $LastExitCode")) + commandText, err := p.generateCommandLineRunner(command) + if err != nil { + return "", fmt.Errorf("Error generating command line runner: %s", err) + } - return encodedCommand, err + return commandText, err +} + +func (p *Provisioner) generateCommandLineRunner(command string) (commandText string, err error) { + log.Printf("Building command line for: %s", command) + + base64EncodedCommand, err := powershellEncode("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; " + command + "; exit $LastExitCode") + if err != nil { + return "", fmt.Errorf("Error encoding command: %s", err) + } + + commandText = "powershell -executionpolicy bypass -encodedCommand " + base64EncodedCommand + + return commandText, nil } func (p *Provisioner) createCommandTextPrivileged() (command string, err error) { @@ -422,6 +438,9 @@ func (p *Provisioner) createCommandTextPrivileged() (command string, err error) // OK so we need an elevated shell runner to wrap our command, this is going to have its own path // generate the script and update the command runner in the process path, err := p.generateElevatedRunner(command) + if err != nil { + return "", fmt.Errorf("Error generating elevated runner: %s", err) + } // Return the path to the elevated shell wrapper command = fmt.Sprintf("powershell -executionpolicy bypass -file \"%s\"", path) @@ -434,12 +453,18 @@ func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath strin // generate command var buffer bytes.Buffer + + base64EncodedCommand, err := powershellEncode("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; " + command + "; exit $LastExitCode") + if err != nil { + return "", fmt.Errorf("Error encoding command: %s", err) + } + err = elevatedTemplate.Execute(&buffer, elevatedOptions{ User: p.config.ElevatedUser, Password: p.config.ElevatedPassword, TaskDescription: "Packer elevated task", TaskName: fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()), - EncodedCommand: powershellEncode([]byte("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; " + command + "; exit $LastExitCode")), + EncodedCommand: base64EncodedCommand, }) if err != nil { diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index cb4c26eb3..f1a51e049 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -390,14 +390,27 @@ func TestProvisionerProvision_Inline(t *testing.T) { } expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode` + expectedCommandUtf8, err := powershellUtf8(expectedCommand) + if err != nil { + t.Fatal("should not have error when Utf 8 encoding") + } + expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSJTaWxlbnRseUNvbnRpbnVlIn07ICRlbnY6UEFDS0VSX0JVSUxERVJfVFlQRT0iaXNvIjsgJGVudjpQQUNLRVJfQlVJTERfTkFNRT0idm13YXJlIjsgYzovV2luZG93cy9UZW1wL2lubGluZVNjcmlwdC5iYXQ7IGV4aXQgJExhc3RFeGl0Q29kZQ==` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` - expectedCommandEncoded := expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) + expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded + + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded, err := powershellDecode(actualCommandWithoutPrefix) + if err != nil { + t.Fatal("should not have error when base64 decoding") + } // Should run the command without alteration if comm.StartCmd.Command != expectedCommandEncoded { - actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) - actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) - t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) + t.Fatalf("Expect command to be: %s, got %s.", expectedCommandEncoded, comm.StartCmd.Command) + } + + if actualCommandDecoded != expectedCommandUtf8 { + t.Fatalf("Expected decoded:%s, %s, got %s", expectedCommandEncoded, len(expectedCommandUtf8), len(actualCommandDecoded)) } envVars := make([]string, 2) @@ -413,14 +426,27 @@ func TestProvisionerProvision_Inline(t *testing.T) { } expectedCommand = `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode` + expectedCommandUtf8, err = powershellUtf8(expectedCommand) + if err != nil { + t.Fatal("should not have error when Utf 8 encoding") + } + expectedCommandBase64Encoded = `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSJTaWxlbnRseUNvbnRpbnVlIn07ICRlbnY6QkFSPSJCQVoiOyAkZW52OkZPTz0iQkFSIjsgJGVudjpQQUNLRVJfQlVJTERFUl9UWVBFPSJpc28iOyAkZW52OlBBQ0tFUl9CVUlMRF9OQU1FPSJ2bXdhcmUiOyBjOi9XaW5kb3dzL1RlbXAvaW5saW5lU2NyaXB0LmJhdDsgZXhpdCAkTGFzdEV4aXRDb2Rl` expectedCommandPrefix = `powershell -executionpolicy bypass -encodedCommand ` - expectedCommandEncoded = expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) + expectedCommandEncoded = expectedCommandPrefix + expectedCommandBase64Encoded + + actualCommandWithoutPrefix = strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded, err = powershellDecode(actualCommandWithoutPrefix) + if err != nil { + t.Fatal("should not have error when base64 decoding") + } // Should run the command without alteration if comm.StartCmd.Command != expectedCommandEncoded { - actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) - actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) - t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) + t.Fatalf("Expect command to be: %s, got %s.", expectedCommandEncoded, comm.StartCmd.Command) + } + + if actualCommandDecoded != expectedCommandUtf8 { + t.Fatalf("Expected decoded: %s, got %s", expectedCommandUtf8, actualCommandDecoded) } } @@ -443,14 +469,27 @@ func TestProvisionerProvision_Scripts(t *testing.T) { } expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode` + expectedCommandUtf8, err := powershellUtf8(expectedCommand) + if err != nil { + t.Fatal("should not have error when Utf 8 encoding") + } + expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSJTaWxlbnRseUNvbnRpbnVlIn07ICRlbnY6UEFDS0VSX0JVSUxERVJfVFlQRT0iZm9vdHlwZSI7ICRlbnY6UEFDS0VSX0JVSUxEX05BTUU9ImZvb2J1aWxkIjsgYzovV2luZG93cy9UZW1wL3NjcmlwdC5wczE7IGV4aXQgJExhc3RFeGl0Q29kZQ==` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` - expectedCommandEncoded := expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) + expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded + + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded, err := powershellDecode(actualCommandWithoutPrefix) + if err != nil { + t.Fatal("should not have error when base64 decoding") + } // Should run the command without alteration if comm.StartCmd.Command != expectedCommandEncoded { - actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) - actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) - t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) + t.Fatalf("Expect command to be: %s, got %s.", expectedCommandEncoded, comm.StartCmd.Command) + } + + if actualCommandDecoded != expectedCommandUtf8 { + t.Fatalf("Expected decoded: %n, got %n", len(expectedCommandUtf8), len(actualCommandDecoded)) } } @@ -480,14 +519,27 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { } expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode` + expectedCommandUtf8, err := powershellUtf8(expectedCommand) + if err != nil { + t.Fatal("should not have error when Utf 8 encoding") + } + expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSJTaWxlbnRseUNvbnRpbnVlIn07ICRlbnY6QkFSPSJCQVoiOyAkZW52OkZPTz0iQkFSIjsgJGVudjpQQUNLRVJfQlVJTERFUl9UWVBFPSJmb290eXBlIjsgJGVudjpQQUNLRVJfQlVJTERfTkFNRT0iZm9vYnVpbGQiOyBjOi9XaW5kb3dzL1RlbXAvc2NyaXB0LnBzMTsgZXhpdCAkTGFzdEV4aXRDb2Rl` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` - expectedCommandEncoded := expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) + expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded + + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded, err := powershellDecode(actualCommandWithoutPrefix) + if err != nil { + t.Fatal("should not have error when base64 decoding") + } // Should run the command without alteration if comm.StartCmd.Command != expectedCommandEncoded { - actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) - actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) - t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) + t.Fatalf("Expect command to be: %s, got %s.", expectedCommandEncoded, comm.StartCmd.Command) + } + + if actualCommandDecoded != expectedCommandUtf8 { + t.Fatalf("Expected decoded: %s, got %s", expectedCommandUtf8, actualCommandDecoded) } } @@ -599,14 +651,24 @@ func TestProvision_createCommandText(t *testing.T) { cmd, _ := p.createCommandText() expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:PACKER_BUILDER_TYPE=""; $env:PACKER_BUILD_NAME=""; c:/Windows/Temp/script.ps1; exit $LastExitCode` + expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSJTaWxlbnRseUNvbnRpbnVlIn07ICRlbnY6UEFDS0VSX0JVSUxERVJfVFlQRT0iIjsgJGVudjpQQUNLRVJfQlVJTERfTkFNRT0iIjsgYzovV2luZG93cy9UZW1wL3NjcmlwdC5wczE7IGV4aXQgJExhc3RFeGl0Q29kZQ==` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` - expectedCommandEncoded := expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) + expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded + + actualCommandWithoutPrefix := strings.Replace(cmd, expectedCommandPrefix, "", -1) + + actualCommandDecoded, err := powershellDecode(actualCommandWithoutPrefix) + if err != nil { + t.Fatal("should not have error when base64 decoding") + } // Should run the command without alteration if cmd != expectedCommandEncoded { - actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) - actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) - t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) + t.Fatalf("Expect command to be: %s, got %s.", expectedCommandEncoded, cmd) + } + + if actualCommandDecoded != expectedCommand { + t.Fatalf("Expected decoded: %s, got %s", expectedCommand, actualCommandDecoded) } // Elevated From e36051d394a3fa1e8ff3a3ce6b5ab9edfad1d89f Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Thu, 14 Jul 2016 00:03:07 +0100 Subject: [PATCH 072/108] Pattern for infrastructure changed to quote filename and execute file with ampersand as everything is run in powershell now Handle powershell commands by specifying any extra infrastructure around running scripts inside of ExecuteCommand and ElevatedExecuteCommand --- provisioner/powershell/provisioner.go | 8 +-- provisioner/powershell/provisioner_test.go | 81 ++++++++++------------ 2 files changed, 42 insertions(+), 47 deletions(-) diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index be887e8bc..ba0e2def3 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -121,11 +121,11 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if p.config.ExecuteCommand == "" { - p.config.ExecuteCommand = `{{.Vars}}{{.Path}}` + p.config.ExecuteCommand = `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};{{.Vars}}&'{{.Path}}';exit $LastExitCode` } if p.config.ElevatedExecuteCommand == "" { - p.config.ElevatedExecuteCommand = `{{.Vars}}{{.Path}}'` + p.config.ElevatedExecuteCommand = `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};{{.Vars}}&'{{.Path}}';exit $LastExitCode` } if p.config.Inline != nil && len(p.config.Inline) == 0 { @@ -410,7 +410,7 @@ func (p *Provisioner) createCommandTextNonPrivileged() (command string, err erro func (p *Provisioner) generateCommandLineRunner(command string) (commandText string, err error) { log.Printf("Building command line for: %s", command) - base64EncodedCommand, err := powershellEncode("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; " + command + "; exit $LastExitCode") + base64EncodedCommand, err := powershellEncode(command) if err != nil { return "", fmt.Errorf("Error encoding command: %s", err) } @@ -454,7 +454,7 @@ func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath strin // generate command var buffer bytes.Buffer - base64EncodedCommand, err := powershellEncode("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; " + command + "; exit $LastExitCode") + base64EncodedCommand, err := powershellEncode(command) if err != nil { return "", fmt.Errorf("Error encoding command: %s", err) } diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index f1a51e049..f6e20ecc2 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -75,12 +75,12 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { t.Error("expected elevated_password to be empty") } - if p.config.ExecuteCommand != `{{.Vars}}{{.Path}}` { - t.Fatalf("Default command should be '{{.Vars}}{{.Path}}', but got %s", p.config.ExecuteCommand) + if p.config.ExecuteCommand != `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};{{.Vars}}&'{{.Path}}';exit $LastExitCode` { + t.Fatalf(`Default command should be "if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};{{.Vars}}&'{{.Path}}';exit $LastExitCode", but got %s`, p.config.ExecuteCommand) } - if p.config.ElevatedExecuteCommand != `{{.Vars}}{{.Path}}'` { - t.Fatalf("Default command should be '{{.Vars}}{{.Path}}', but got %s", p.config.ElevatedExecuteCommand) + if p.config.ElevatedExecuteCommand != `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};{{.Vars}}&'{{.Path}}';exit $LastExitCode` { + t.Fatalf(`Default command should be "if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};{{.Vars}}&'{{.Path}}';exit $LastExitCode", but got %s`, p.config.ElevatedExecuteCommand) } if p.config.ValidExitCodes == nil { @@ -328,7 +328,7 @@ func TestProvisionerProvision_ValidExitCodes(t *testing.T) { delete(config, "inline") // Defaults provided by Packer - config["remote_path"] = "c:/Windows/Temp/inlineScript.bat" + config["remote_path"] = "c:/Windows/Temp/inlineScript.ps1" config["inline"] = []string{"whoami"} ui := testUi() p := new(Provisioner) @@ -351,7 +351,7 @@ func TestProvisionerProvision_InvalidExitCodes(t *testing.T) { delete(config, "inline") // Defaults provided by Packer - config["remote_path"] = "c:/Windows/Temp/inlineScript.bat" + config["remote_path"] = "c:/Windows/Temp/inlineScript.ps1" config["inline"] = []string{"whoami"} ui := testUi() p := new(Provisioner) @@ -374,7 +374,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { delete(config, "inline") // Defaults provided by Packer - config["remote_path"] = "c:/Windows/Temp/inlineScript.bat" + config["remote_path"] = "c:/Windows/Temp/inlineScript.ps1" config["inline"] = []string{"whoami"} ui := testUi() p := new(Provisioner) @@ -389,12 +389,12 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode` + expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};$env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; &'c:/Windows/Temp/inlineScript.ps1';exit $LastExitCode` expectedCommandUtf8, err := powershellUtf8(expectedCommand) if err != nil { t.Fatal("should not have error when Utf 8 encoding") } - expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSJTaWxlbnRseUNvbnRpbnVlIn07ICRlbnY6UEFDS0VSX0JVSUxERVJfVFlQRT0iaXNvIjsgJGVudjpQQUNLRVJfQlVJTERfTkFNRT0idm13YXJlIjsgYzovV2luZG93cy9UZW1wL2lubGluZVNjcmlwdC5iYXQ7IGV4aXQgJExhc3RFeGl0Q29kZQ==` + expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSdTaWxlbnRseUNvbnRpbnVlJ307JGVudjpQQUNLRVJfQlVJTERFUl9UWVBFPSJpc28iOyAkZW52OlBBQ0tFUl9CVUlMRF9OQU1FPSJ2bXdhcmUiOyAmJ2M6L1dpbmRvd3MvVGVtcC9pbmxpbmVTY3JpcHQucHMxJztleGl0ICRMYXN0RXhpdENvZGU=` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded @@ -404,20 +404,19 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error when base64 decoding") } - // Should run the command without alteration - if comm.StartCmd.Command != expectedCommandEncoded { - t.Fatalf("Expect command to be: %s, got %s.", expectedCommandEncoded, comm.StartCmd.Command) + if actualCommandDecoded != expectedCommandUtf8 { + t.Fatalf("Expected decoded:%s, %s, got %s", expectedCommandEncoded, expectedCommandUtf8, actualCommandDecoded) } - if actualCommandDecoded != expectedCommandUtf8 { - t.Fatalf("Expected decoded:%s, %s, got %s", expectedCommandEncoded, len(expectedCommandUtf8), len(actualCommandDecoded)) + if comm.StartCmd.Command != expectedCommandEncoded { + t.Fatalf("Expect command to be: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command) } envVars := make([]string, 2) envVars[0] = "FOO=BAR" envVars[1] = "BAR=BAZ" config["environment_vars"] = envVars - config["remote_path"] = "c:/Windows/Temp/inlineScript.bat" + config["remote_path"] = "c:/Windows/Temp/inlineScript.ps1" p.Prepare(config) err = p.Provision(ui, comm) @@ -425,12 +424,12 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand = `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode` + expectedCommand = `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};$env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; &'c:/Windows/Temp/inlineScript.ps1';exit $LastExitCode` expectedCommandUtf8, err = powershellUtf8(expectedCommand) if err != nil { t.Fatal("should not have error when Utf 8 encoding") } - expectedCommandBase64Encoded = `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSJTaWxlbnRseUNvbnRpbnVlIn07ICRlbnY6QkFSPSJCQVoiOyAkZW52OkZPTz0iQkFSIjsgJGVudjpQQUNLRVJfQlVJTERFUl9UWVBFPSJpc28iOyAkZW52OlBBQ0tFUl9CVUlMRF9OQU1FPSJ2bXdhcmUiOyBjOi9XaW5kb3dzL1RlbXAvaW5saW5lU2NyaXB0LmJhdDsgZXhpdCAkTGFzdEV4aXRDb2Rl` + expectedCommandBase64Encoded = `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSdTaWxlbnRseUNvbnRpbnVlJ307JGVudjpCQVI9IkJBWiI7ICRlbnY6Rk9PPSJCQVIiOyAkZW52OlBBQ0tFUl9CVUlMREVSX1RZUEU9ImlzbyI7ICRlbnY6UEFDS0VSX0JVSUxEX05BTUU9InZtd2FyZSI7ICYnYzovV2luZG93cy9UZW1wL2lubGluZVNjcmlwdC5wczEnO2V4aXQgJExhc3RFeGl0Q29kZQ==` expectedCommandPrefix = `powershell -executionpolicy bypass -encodedCommand ` expectedCommandEncoded = expectedCommandPrefix + expectedCommandBase64Encoded @@ -440,14 +439,13 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error when base64 decoding") } - // Should run the command without alteration - if comm.StartCmd.Command != expectedCommandEncoded { - t.Fatalf("Expect command to be: %s, got %s.", expectedCommandEncoded, comm.StartCmd.Command) - } - if actualCommandDecoded != expectedCommandUtf8 { t.Fatalf("Expected decoded: %s, got %s", expectedCommandUtf8, actualCommandDecoded) } + + if comm.StartCmd.Command != expectedCommandEncoded { + t.Fatalf("Expect command to be: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command) + } } func TestProvisionerProvision_Scripts(t *testing.T) { @@ -468,12 +466,12 @@ func TestProvisionerProvision_Scripts(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode` + expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};$env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; &'c:/Windows/Temp/script.ps1';exit $LastExitCode` expectedCommandUtf8, err := powershellUtf8(expectedCommand) if err != nil { t.Fatal("should not have error when Utf 8 encoding") } - expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSJTaWxlbnRseUNvbnRpbnVlIn07ICRlbnY6UEFDS0VSX0JVSUxERVJfVFlQRT0iZm9vdHlwZSI7ICRlbnY6UEFDS0VSX0JVSUxEX05BTUU9ImZvb2J1aWxkIjsgYzovV2luZG93cy9UZW1wL3NjcmlwdC5wczE7IGV4aXQgJExhc3RFeGl0Q29kZQ==` + expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSdTaWxlbnRseUNvbnRpbnVlJ307JGVudjpQQUNLRVJfQlVJTERFUl9UWVBFPSJmb290eXBlIjsgJGVudjpQQUNLRVJfQlVJTERfTkFNRT0iZm9vYnVpbGQiOyAmJ2M6L1dpbmRvd3MvVGVtcC9zY3JpcHQucHMxJztleGl0ICRMYXN0RXhpdENvZGU=` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded @@ -483,13 +481,12 @@ func TestProvisionerProvision_Scripts(t *testing.T) { t.Fatal("should not have error when base64 decoding") } - // Should run the command without alteration - if comm.StartCmd.Command != expectedCommandEncoded { - t.Fatalf("Expect command to be: %s, got %s.", expectedCommandEncoded, comm.StartCmd.Command) + if actualCommandDecoded != expectedCommandUtf8 { + t.Fatalf("Expected decoded: %s, got %s", expectedCommandUtf8, actualCommandDecoded) } - if actualCommandDecoded != expectedCommandUtf8 { - t.Fatalf("Expected decoded: %n, got %n", len(expectedCommandUtf8), len(actualCommandDecoded)) + if comm.StartCmd.Command != expectedCommandEncoded { + t.Fatalf("Expect command to be: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command) } } @@ -518,12 +515,12 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode` + expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};$env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; &'c:/Windows/Temp/script.ps1';exit $LastExitCode` expectedCommandUtf8, err := powershellUtf8(expectedCommand) if err != nil { t.Fatal("should not have error when Utf 8 encoding") } - expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSJTaWxlbnRseUNvbnRpbnVlIn07ICRlbnY6QkFSPSJCQVoiOyAkZW52OkZPTz0iQkFSIjsgJGVudjpQQUNLRVJfQlVJTERFUl9UWVBFPSJmb290eXBlIjsgJGVudjpQQUNLRVJfQlVJTERfTkFNRT0iZm9vYnVpbGQiOyBjOi9XaW5kb3dzL1RlbXAvc2NyaXB0LnBzMTsgZXhpdCAkTGFzdEV4aXRDb2Rl` + expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSdTaWxlbnRseUNvbnRpbnVlJ307JGVudjpCQVI9IkJBWiI7ICRlbnY6Rk9PPSJCQVIiOyAkZW52OlBBQ0tFUl9CVUlMREVSX1RZUEU9ImZvb3R5cGUiOyAkZW52OlBBQ0tFUl9CVUlMRF9OQU1FPSJmb29idWlsZCI7ICYnYzovV2luZG93cy9UZW1wL3NjcmlwdC5wczEnO2V4aXQgJExhc3RFeGl0Q29kZQ==` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded @@ -533,14 +530,13 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { t.Fatal("should not have error when base64 decoding") } - // Should run the command without alteration - if comm.StartCmd.Command != expectedCommandEncoded { - t.Fatalf("Expect command to be: %s, got %s.", expectedCommandEncoded, comm.StartCmd.Command) - } - if actualCommandDecoded != expectedCommandUtf8 { t.Fatalf("Expected decoded: %s, got %s", expectedCommandUtf8, actualCommandDecoded) } + + if comm.StartCmd.Command != expectedCommandEncoded { + t.Fatalf("Expect command to be: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command) + } } func TestProvisionerProvision_UISlurp(t *testing.T) { @@ -650,8 +646,8 @@ func TestProvision_createCommandText(t *testing.T) { // Non-elevated cmd, _ := p.createCommandText() - expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:PACKER_BUILDER_TYPE=""; $env:PACKER_BUILD_NAME=""; c:/Windows/Temp/script.ps1; exit $LastExitCode` - expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSJTaWxlbnRseUNvbnRpbnVlIn07ICRlbnY6UEFDS0VSX0JVSUxERVJfVFlQRT0iIjsgJGVudjpQQUNLRVJfQlVJTERfTkFNRT0iIjsgYzovV2luZG93cy9UZW1wL3NjcmlwdC5wczE7IGV4aXQgJExhc3RFeGl0Q29kZQ==` + expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};$env:PACKER_BUILDER_TYPE=""; $env:PACKER_BUILD_NAME=""; &'c:/Windows/Temp/script.ps1';exit $LastExitCode` + expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSdTaWxlbnRseUNvbnRpbnVlJ307JGVudjpQQUNLRVJfQlVJTERFUl9UWVBFPSIiOyAkZW52OlBBQ0tFUl9CVUlMRF9OQU1FPSIiOyAmJ2M6L1dpbmRvd3MvVGVtcC9zY3JpcHQucHMxJztleGl0ICRMYXN0RXhpdENvZGU=` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded @@ -662,15 +658,14 @@ func TestProvision_createCommandText(t *testing.T) { t.Fatal("should not have error when base64 decoding") } - // Should run the command without alteration - if cmd != expectedCommandEncoded { - t.Fatalf("Expect command to be: %s, got %s.", expectedCommandEncoded, cmd) - } - if actualCommandDecoded != expectedCommand { t.Fatalf("Expected decoded: %s, got %s", expectedCommand, actualCommandDecoded) } + if cmd != expectedCommandEncoded { + t.Fatalf("Expect command to be: %s, got %s", expectedCommandEncoded, cmd) + } + // Elevated p.config.ElevatedUser = "vagrant" p.config.ElevatedPassword = "vagrant" From f38d787b0ea33185cb0144450829ebe52278d86f Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 24 Jul 2016 14:44:07 +0100 Subject: [PATCH 073/108] Powershell uses UTF16Le for encodedCommand --- provisioner/powershell/powershell.go | 40 +++++++++++++++------ provisioner/powershell/provisioner_test.go | 42 +++++++--------------- 2 files changed, 43 insertions(+), 39 deletions(-) diff --git a/provisioner/powershell/powershell.go b/provisioner/powershell/powershell.go index c24e7df5c..086e3e554 100644 --- a/provisioner/powershell/powershell.go +++ b/provisioner/powershell/powershell.go @@ -2,33 +2,53 @@ package powershell import ( "encoding/base64" + "encoding/binary" + "unicode/utf16" + "unicode/utf8" "golang.org/x/text/encoding/unicode" ) -func powershellUtf8(message string) (string, error) { - utf8 := unicode.UTF8 - utfEncoder := utf8.NewEncoder() - utf8EncodedMessage, err := utfEncoder.String(message) +func convertUtf8ToUtf16LE(message string) (string, error) { + utf16le := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM) + utfEncoder := utf16le.NewEncoder() + ut16LeEncodedMessage, err := utfEncoder.String(message) - return utf8EncodedMessage, err + return ut16LeEncodedMessage, err +} + +// UTF16BytesToString converts UTF-16 encoded bytes, in big or little endian byte order, +// to a UTF-8 encoded string. +func UTF16BytesToString(b []byte, o binary.ByteOrder) string { + utf := make([]uint16, (len(b)+(2-1))/2) + for i := 0; i+(2-1) < len(b); i += 2 { + utf[i/2] = o.Uint16(b[i:]) + } + if len(b)/2 < len(utf) { + utf[len(utf)-1] = utf8.RuneError + } + return string(utf16.Decode(utf)) } func powershellEncode(message string) (string, error) { - utf8EncodedMessage, err := powershellUtf8(message) + utf16LEEncodedMessage, err := convertUtf8ToUtf16LE(message) if err != nil { return "", err } // Base64 encode the command - input := []uint8(utf8EncodedMessage) + input := []uint8(utf16LEEncodedMessage) return base64.StdEncoding.EncodeToString(input), nil } -func powershellDecode(message string) (retour string, err error) { - data, err := base64.StdEncoding.DecodeString(message) +func powershellDecode(messageBase64 string) (retour string, err error) { + messageUtf16LeByteArray, err := base64.StdEncoding.DecodeString(messageBase64) + if err != nil { return "", err } - return string(data), nil + + message := UTF16BytesToString(messageUtf16LeByteArray, binary.LittleEndian) + + return message, nil } diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index f6e20ecc2..473fa1d8e 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -390,11 +390,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { } expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};$env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; &'c:/Windows/Temp/inlineScript.ps1';exit $LastExitCode` - expectedCommandUtf8, err := powershellUtf8(expectedCommand) - if err != nil { - t.Fatal("should not have error when Utf 8 encoding") - } - expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSdTaWxlbnRseUNvbnRpbnVlJ307JGVudjpQQUNLRVJfQlVJTERFUl9UWVBFPSJpc28iOyAkZW52OlBBQ0tFUl9CVUlMRF9OQU1FPSJ2bXdhcmUiOyAmJ2M6L1dpbmRvd3MvVGVtcC9pbmxpbmVTY3JpcHQucHMxJztleGl0ICRMYXN0RXhpdENvZGU=` + expectedCommandBase64Encoded := `aQBmACAAKABUAGUAcwB0AC0AUABhAHQAaAAgAHYAYQByAGkAYQBiAGwAZQA6AGcAbABvAGIAYQBsADoAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAKQB7ACQAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAPQAnAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAJwB9ADsAJABlAG4AdgA6AFAAQQBDAEsARQBSAF8AQgBVAEkATABEAEUAUgBfAFQAWQBQAEUAPQAiAGkAcwBvACIAOwAgACQAZQBuAHYAOgBQAEEAQwBLAEUAUgBfAEIAVQBJAEwARABfAE4AQQBNAEUAPQAiAHYAbQB3AGEAcgBlACIAOwAgACYAJwBjADoALwBXAGkAbgBkAG8AdwBzAC8AVABlAG0AcAAvAGkAbgBsAGkAbgBlAFMAYwByAGkAcAB0AC4AcABzADEAJwA7AGUAeABpAHQAIAAkAEwAYQBzAHQARQB4AGkAdABDAG8AZABlAA==` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded @@ -404,8 +400,8 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error when base64 decoding") } - if actualCommandDecoded != expectedCommandUtf8 { - t.Fatalf("Expected decoded:%s, %s, got %s", expectedCommandEncoded, expectedCommandUtf8, actualCommandDecoded) + if actualCommandDecoded != expectedCommand { + t.Fatalf("Expected decoded: %s, got %s", expectedCommand, actualCommandDecoded) } if comm.StartCmd.Command != expectedCommandEncoded { @@ -425,11 +421,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { } expectedCommand = `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};$env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; &'c:/Windows/Temp/inlineScript.ps1';exit $LastExitCode` - expectedCommandUtf8, err = powershellUtf8(expectedCommand) - if err != nil { - t.Fatal("should not have error when Utf 8 encoding") - } - expectedCommandBase64Encoded = `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSdTaWxlbnRseUNvbnRpbnVlJ307JGVudjpCQVI9IkJBWiI7ICRlbnY6Rk9PPSJCQVIiOyAkZW52OlBBQ0tFUl9CVUlMREVSX1RZUEU9ImlzbyI7ICRlbnY6UEFDS0VSX0JVSUxEX05BTUU9InZtd2FyZSI7ICYnYzovV2luZG93cy9UZW1wL2lubGluZVNjcmlwdC5wczEnO2V4aXQgJExhc3RFeGl0Q29kZQ==` + expectedCommandBase64Encoded = `aQBmACAAKABUAGUAcwB0AC0AUABhAHQAaAAgAHYAYQByAGkAYQBiAGwAZQA6AGcAbABvAGIAYQBsADoAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAKQB7ACQAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAPQAnAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAJwB9ADsAJABlAG4AdgA6AEIAQQBSAD0AIgBCAEEAWgAiADsAIAAkAGUAbgB2ADoARgBPAE8APQAiAEIAQQBSACIAOwAgACQAZQBuAHYAOgBQAEEAQwBLAEUAUgBfAEIAVQBJAEwARABFAFIAXwBUAFkAUABFAD0AIgBpAHMAbwAiADsAIAAkAGUAbgB2ADoAUABBAEMASwBFAFIAXwBCAFUASQBMAEQAXwBOAEEATQBFAD0AIgB2AG0AdwBhAHIAZQAiADsAIAAmACcAYwA6AC8AVwBpAG4AZABvAHcAcwAvAFQAZQBtAHAALwBpAG4AbABpAG4AZQBTAGMAcgBpAHAAdAAuAHAAcwAxACcAOwBlAHgAaQB0ACAAJABMAGEAcwB0AEUAeABpAHQAQwBvAGQAZQA=` expectedCommandPrefix = `powershell -executionpolicy bypass -encodedCommand ` expectedCommandEncoded = expectedCommandPrefix + expectedCommandBase64Encoded @@ -439,8 +431,8 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error when base64 decoding") } - if actualCommandDecoded != expectedCommandUtf8 { - t.Fatalf("Expected decoded: %s, got %s", expectedCommandUtf8, actualCommandDecoded) + if actualCommandDecoded != expectedCommand { + t.Fatalf("Expected decoded: %s, got %s", expectedCommand, actualCommandDecoded) } if comm.StartCmd.Command != expectedCommandEncoded { @@ -467,11 +459,7 @@ func TestProvisionerProvision_Scripts(t *testing.T) { } expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};$env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; &'c:/Windows/Temp/script.ps1';exit $LastExitCode` - expectedCommandUtf8, err := powershellUtf8(expectedCommand) - if err != nil { - t.Fatal("should not have error when Utf 8 encoding") - } - expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSdTaWxlbnRseUNvbnRpbnVlJ307JGVudjpQQUNLRVJfQlVJTERFUl9UWVBFPSJmb290eXBlIjsgJGVudjpQQUNLRVJfQlVJTERfTkFNRT0iZm9vYnVpbGQiOyAmJ2M6L1dpbmRvd3MvVGVtcC9zY3JpcHQucHMxJztleGl0ICRMYXN0RXhpdENvZGU=` + expectedCommandBase64Encoded := `aQBmACAAKABUAGUAcwB0AC0AUABhAHQAaAAgAHYAYQByAGkAYQBiAGwAZQA6AGcAbABvAGIAYQBsADoAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAKQB7ACQAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAPQAnAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAJwB9ADsAJABlAG4AdgA6AFAAQQBDAEsARQBSAF8AQgBVAEkATABEAEUAUgBfAFQAWQBQAEUAPQAiAGYAbwBvAHQAeQBwAGUAIgA7ACAAJABlAG4AdgA6AFAAQQBDAEsARQBSAF8AQgBVAEkATABEAF8ATgBBAE0ARQA9ACIAZgBvAG8AYgB1AGkAbABkACIAOwAgACYAJwBjADoALwBXAGkAbgBkAG8AdwBzAC8AVABlAG0AcAAvAHMAYwByAGkAcAB0AC4AcABzADEAJwA7AGUAeABpAHQAIAAkAEwAYQBzAHQARQB4AGkAdABDAG8AZABlAA==` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded @@ -481,8 +469,8 @@ func TestProvisionerProvision_Scripts(t *testing.T) { t.Fatal("should not have error when base64 decoding") } - if actualCommandDecoded != expectedCommandUtf8 { - t.Fatalf("Expected decoded: %s, got %s", expectedCommandUtf8, actualCommandDecoded) + if actualCommandDecoded != expectedCommand { + t.Fatalf("Expected decoded: %s, got %s", expectedCommand, actualCommandDecoded) } if comm.StartCmd.Command != expectedCommandEncoded { @@ -516,11 +504,7 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { } expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};$env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; &'c:/Windows/Temp/script.ps1';exit $LastExitCode` - expectedCommandUtf8, err := powershellUtf8(expectedCommand) - if err != nil { - t.Fatal("should not have error when Utf 8 encoding") - } - expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSdTaWxlbnRseUNvbnRpbnVlJ307JGVudjpCQVI9IkJBWiI7ICRlbnY6Rk9PPSJCQVIiOyAkZW52OlBBQ0tFUl9CVUlMREVSX1RZUEU9ImZvb3R5cGUiOyAkZW52OlBBQ0tFUl9CVUlMRF9OQU1FPSJmb29idWlsZCI7ICYnYzovV2luZG93cy9UZW1wL3NjcmlwdC5wczEnO2V4aXQgJExhc3RFeGl0Q29kZQ==` + expectedCommandBase64Encoded := `aQBmACAAKABUAGUAcwB0AC0AUABhAHQAaAAgAHYAYQByAGkAYQBiAGwAZQA6AGcAbABvAGIAYQBsADoAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAKQB7ACQAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAPQAnAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAJwB9ADsAJABlAG4AdgA6AEIAQQBSAD0AIgBCAEEAWgAiADsAIAAkAGUAbgB2ADoARgBPAE8APQAiAEIAQQBSACIAOwAgACQAZQBuAHYAOgBQAEEAQwBLAEUAUgBfAEIAVQBJAEwARABFAFIAXwBUAFkAUABFAD0AIgBmAG8AbwB0AHkAcABlACIAOwAgACQAZQBuAHYAOgBQAEEAQwBLAEUAUgBfAEIAVQBJAEwARABfAE4AQQBNAEUAPQAiAGYAbwBvAGIAdQBpAGwAZAAiADsAIAAmACcAYwA6AC8AVwBpAG4AZABvAHcAcwAvAFQAZQBtAHAALwBzAGMAcgBpAHAAdAAuAHAAcwAxACcAOwBlAHgAaQB0ACAAJABMAGEAcwB0AEUAeABpAHQAQwBvAGQAZQA=` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded @@ -530,8 +514,8 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { t.Fatal("should not have error when base64 decoding") } - if actualCommandDecoded != expectedCommandUtf8 { - t.Fatalf("Expected decoded: %s, got %s", expectedCommandUtf8, actualCommandDecoded) + if actualCommandDecoded != expectedCommand { + t.Fatalf("Expected decoded: %s, got %s", expectedCommand, actualCommandDecoded) } if comm.StartCmd.Command != expectedCommandEncoded { @@ -647,7 +631,7 @@ func TestProvision_createCommandText(t *testing.T) { cmd, _ := p.createCommandText() expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};$env:PACKER_BUILDER_TYPE=""; $env:PACKER_BUILD_NAME=""; &'c:/Windows/Temp/script.ps1';exit $LastExitCode` - expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSdTaWxlbnRseUNvbnRpbnVlJ307JGVudjpQQUNLRVJfQlVJTERFUl9UWVBFPSIiOyAkZW52OlBBQ0tFUl9CVUlMRF9OQU1FPSIiOyAmJ2M6L1dpbmRvd3MvVGVtcC9zY3JpcHQucHMxJztleGl0ICRMYXN0RXhpdENvZGU=` + expectedCommandBase64Encoded := `aQBmACAAKABUAGUAcwB0AC0AUABhAHQAaAAgAHYAYQByAGkAYQBiAGwAZQA6AGcAbABvAGIAYQBsADoAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAKQB7ACQAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAPQAnAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAJwB9ADsAJABlAG4AdgA6AFAAQQBDAEsARQBSAF8AQgBVAEkATABEAEUAUgBfAFQAWQBQAEUAPQAiACIAOwAgACQAZQBuAHYAOgBQAEEAQwBLAEUAUgBfAEIAVQBJAEwARABfAE4AQQBNAEUAPQAiACIAOwAgACYAJwBjADoALwBXAGkAbgBkAG8AdwBzAC8AVABlAG0AcAAvAHMAYwByAGkAcAB0AC4AcABzADEAJwA7AGUAeABpAHQAIAAkAEwAYQBzAHQARQB4AGkAdABDAG8AZABlAA==` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded From c1538c59c9a21e83f16e310988965c0fdda25649 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 24 Jul 2016 18:07:43 +0100 Subject: [PATCH 074/108] Builds in AppVeyor are failing as they do not have enough free memory. Drop the thresh hold a little more. --- builder/hyperv/iso/builder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 03c1e3f3f..d294c8cf5 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -32,7 +32,7 @@ const ( MinRamSize = 32 // 32MB MaxRamSize = 32 * 1024 // 32GB - LowRam = 384 // 384MB + LowRam = 256 // 256MB DefaultUsername = "vagrant" DefaultPassword = "vagrant" From 5f7290c6f89c216f5b8294169dbae49f4ed22899 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 30 Jul 2016 18:42:10 +0100 Subject: [PATCH 075/108] Add type scan support for ctrl, shift, alt. <leftAlt> <leftCtrl> <leftShift> <rightAlt> <rightCtrl> <rightShift> Add type support for combinations of ctr, alt, shift with (only use lower case characters with this): <leftAltOn> <leftCtrlOn> <leftShiftOn> <leftAltOff> <leftCtrlOff> <leftShiftOff> <rightAltOn> <rightCtrlOn> <rightShiftOn> <rightAltOff> <rightCtrlOff> <rightShiftOff> --- .../hyperv/common/step_type_boot_command.go | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/builder/hyperv/common/step_type_boot_command.go b/builder/hyperv/common/step_type_boot_command.go index f5cf1d909..8468d1115 100644 --- a/builder/hyperv/common/step_type_boot_command.go +++ b/builder/hyperv/common/step_type_boot_command.go @@ -124,6 +124,12 @@ func scancodes(message string) []string { special["<end>"] = []string{"4f", "cf"} special["<pageUp>"] = []string{"49", "c9"} special["<pageDown>"] = []string{"51", "d1"} + special["<leftAlt>"] = []string{"38", "b8"} + special["<leftCtrl>"] = []string{"1d", "9d"} + special["<leftShift>"] = []string{"2a", "aa"} + special["<rightAlt>"] = []string{"e038", "e0b8"} + special["<rightCtrl>"] = []string{"e01d", "e09d"} + special["<rightShift>"] = []string{"36", "b6"} shiftedChars := "~!@#$%^&*()_+{}|:\"<>?" @@ -153,6 +159,66 @@ func scancodes(message string) []string { for len(message) > 0 { var scancode []string + if strings.HasPrefix(message, "<leftAltOn>") { + scancode = []string{"38"} + message = message[len("<leftAltOn>"):] + } + + if strings.HasPrefix(message, "<leftCtrlOn>") { + scancode = []string{"1d"} + message = message[len("<leftCtrlOn>"):] + } + + if strings.HasPrefix(message, "<leftShiftOn>") { + scancode = []string{"2a"} + message = message[len("<leftShiftOn>"):] + } + + if strings.HasPrefix(message, "<leftAltOff>") { + scancode = []string{"b8"} + message = message[len("<leftAltOff>"):] + } + + if strings.HasPrefix(message, "<leftCtrlOff>") { + scancode = []string{"9d"} + message = message[len("<leftCtrlOff>"):] + } + + if strings.HasPrefix(message, "<leftShiftOff>") { + scancode = []string{"aa"} + message = message[len("<leftShiftOff>"):] + } + + if strings.HasPrefix(message, "<rightAltOn>") { + scancode = []string{"e038"} + message = message[len("<rightAltOn>"):] + } + + if strings.HasPrefix(message, "<rightCtrlOn>") { + scancode = []string{"e01d"} + message = message[len("<rightCtrlOn>"):] + } + + if strings.HasPrefix(message, "<rightShiftOn>") { + scancode = []string{"36"} + message = message[len("<rightShiftOn>"):] + } + + if strings.HasPrefix(message, "<rightAltOff>") { + scancode = []string{"e0b8"} + message = message[len("<rightAltOff>"):] + } + + if strings.HasPrefix(message, "<rightCtrlOff>") { + scancode = []string{"e09d"} + message = message[len("<rightCtrlOff>"):] + } + + if strings.HasPrefix(message, "<rightShiftOff>") { + scancode = []string{"b6"} + message = message[len("<rightShiftOff>"):] + } + if strings.HasPrefix(message, "<wait>") { //log.Printf("Special code <wait> found, will sleep 1 second at this point.") scancode = []string{"wait"} From e636c3de3c1d38bfe72b0e121d71b8bbdb5ad109 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 31 Jul 2016 16:23:40 +0100 Subject: [PATCH 076/108] Dvd drive creation will auto select the controller number and controller location. This fixes the bug of first generation machines not being able to add more then 1 dvd drive. --- powershell/hyperv/hyperv.go | 69 ++++++++++++++----------------------- 1 file changed, 25 insertions(+), 44 deletions(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 92d9cfd5c..59d35e5c7 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -1,9 +1,11 @@ package hyperv import ( - "github.com/mitchellh/packer/powershell" + "errors" "strconv" "strings" + + "github.com/mitchellh/packer/powershell" ) func GetHostAdapterIpAddressForSwitch(switchName string) (string, error) { @@ -54,58 +56,37 @@ $ip func CreateDvdDrive(vmName string, isoPath string, generation uint) (uint, uint, error) { var ps powershell.PowerShellCmd var script string - var controllerNumber uint - controllerNumber = 0 - if generation < 2 { - // get the controller number that the OS install disk is mounted on - // generation 1 requires dvd to be added to ide controller, generation 2 uses scsi for dvd drives - script = ` -param([string]$vmName) -$dvdDrives = @(Get-VMDvdDrive -VMName $vmName) -$lastControllerNumber = $dvdDrives | Sort-Object ControllerNumber | Select-Object -Last 1 | %{$_.ControllerNumber} -if (!$lastControllerNumber) { - $lastControllerNumber = 0 -} elseif (!$lastControllerNumber -or ($dvdDrives | ?{ $_.ControllerNumber -eq $lastControllerNumber} | measure).count -gt 1) { - $lastControllerNumber += 1 -} -$lastControllerNumber -` - cmdOut, err := ps.Output(script, vmName) - if err != nil { - return 0, 0, err - } - - controllerNumberTemp, err := strconv.ParseUint(strings.TrimSpace(cmdOut), 10, 64) - if err != nil { - return 0, 0, err - } - - controllerNumber = uint(controllerNumberTemp) - - if controllerNumber != 0 && controllerNumber != 1 { - //There are only 2 ide controllers, try to use the one the hdd is attached too - controllerNumber = 0 - } - } script = ` -param([string]$vmName, [string]$isoPath, [int]$controllerNumber) -$dvdController = Add-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -path $isoPath -Passthru +param([string]$vmName, [string]$isoPath) +$dvdController = Add-VMDvdDrive -VMName $vmName -path $isoPath -Passthru $dvdController | Set-VMDvdDrive -path $null -$dvdController.ControllerLocation +$result = "$($dvdController.ControllerNumber),$($dvdController.ControllerLocation)" +$result ` - cmdOut, err := ps.Output(script, vmName, isoPath, strconv.FormatInt(int64(controllerNumber), 10)) + cmdOut, err := ps.Output(script, vmName, isoPath) + if err != nil { + return 0, 0, err + } + + cmdOutArray := strings.Split(cmdOut, ",") + if len(cmdOutArray) != 2 { + return 0, 0, errors.New("Did not return controller number and controller location") + } + + controllerNumberTemp, err := strconv.ParseUint(strings.TrimSpace(cmdOutArray[0]), 10, 64) + if err != nil { + return 0, 0, err + } + controllerNumber := uint(controllerNumberTemp) + + controllerLocationTemp, err := strconv.ParseUint(strings.TrimSpace(cmdOutArray[1]), 10, 64) if err != nil { return controllerNumber, 0, err } - - controllerLocationTemp, err := strconv.ParseUint(strings.TrimSpace(cmdOut), 10, 64) - if err != nil { - return controllerNumber, 0, err - } - controllerLocation := uint(controllerLocationTemp) + return controllerNumber, controllerLocation, err } From 9c53df1e0e01a2c46cc29f7ddee41a573c594d4c Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 31 Jul 2016 19:18:11 +0100 Subject: [PATCH 077/108] Add special key modifiers to log output when used. Update documentation to include new key modifiers. --- .../hyperv/common/step_type_boot_command.go | 24 +- .../docs/builders/hyperv-iso.html.markdown | 245 ++++++++++-------- 2 files changed, 151 insertions(+), 118 deletions(-) diff --git a/builder/hyperv/common/step_type_boot_command.go b/builder/hyperv/common/step_type_boot_command.go index 8468d1115..e9a1bc4a4 100644 --- a/builder/hyperv/common/step_type_boot_command.go +++ b/builder/hyperv/common/step_type_boot_command.go @@ -2,7 +2,7 @@ package common import ( "fmt" - //"log" + "log" "strings" "unicode" "unicode/utf8" @@ -162,77 +162,89 @@ func scancodes(message string) []string { if strings.HasPrefix(message, "<leftAltOn>") { scancode = []string{"38"} message = message[len("<leftAltOn>"):] + log.Printf("Special code '<leftAltOn>' found, replacing with: 38") } if strings.HasPrefix(message, "<leftCtrlOn>") { scancode = []string{"1d"} message = message[len("<leftCtrlOn>"):] + log.Printf("Special code '<leftCtrlOn>' found, replacing with: 1d") } if strings.HasPrefix(message, "<leftShiftOn>") { scancode = []string{"2a"} message = message[len("<leftShiftOn>"):] + log.Printf("Special code '<leftShiftOn>' found, replacing with: 2a") } if strings.HasPrefix(message, "<leftAltOff>") { scancode = []string{"b8"} message = message[len("<leftAltOff>"):] + log.Printf("Special code '<leftAltOff>' found, replacing with: b8") } if strings.HasPrefix(message, "<leftCtrlOff>") { scancode = []string{"9d"} message = message[len("<leftCtrlOff>"):] + log.Printf("Special code '<leftCtrlOff>' found, replacing with: 9d") } if strings.HasPrefix(message, "<leftShiftOff>") { scancode = []string{"aa"} message = message[len("<leftShiftOff>"):] + log.Printf("Special code '<leftShiftOff>' found, replacing with: aa") } if strings.HasPrefix(message, "<rightAltOn>") { scancode = []string{"e038"} message = message[len("<rightAltOn>"):] + log.Printf("Special code '<rightAltOn>' found, replacing with: e038") } if strings.HasPrefix(message, "<rightCtrlOn>") { scancode = []string{"e01d"} message = message[len("<rightCtrlOn>"):] + log.Printf("Special code '<rightCtrlOn>' found, replacing with: e01d") } if strings.HasPrefix(message, "<rightShiftOn>") { scancode = []string{"36"} message = message[len("<rightShiftOn>"):] + log.Printf("Special code '<rightShiftOn>' found, replacing with: 36") } if strings.HasPrefix(message, "<rightAltOff>") { scancode = []string{"e0b8"} message = message[len("<rightAltOff>"):] + log.Printf("Special code '<rightAltOff>' found, replacing with: e0b8") } if strings.HasPrefix(message, "<rightCtrlOff>") { scancode = []string{"e09d"} message = message[len("<rightCtrlOff>"):] + log.Printf("Special code '<rightCtrlOff>' found, replacing with: e09d") } if strings.HasPrefix(message, "<rightShiftOff>") { scancode = []string{"b6"} message = message[len("<rightShiftOff>"):] + log.Printf("Special code '<rightShiftOff>' found, replacing with: b6") } if strings.HasPrefix(message, "<wait>") { - //log.Printf("Special code <wait> found, will sleep 1 second at this point.") + log.Printf("Special code <wait> found, will sleep 1 second at this point.") scancode = []string{"wait"} message = message[len("<wait>"):] } if strings.HasPrefix(message, "<wait5>") { - //log.Printf("Special code <wait5> found, will sleep 5 seconds at this point.") + log.Printf("Special code <wait5> found, will sleep 5 seconds at this point.") scancode = []string{"wait5"} message = message[len("<wait5>"):] } if strings.HasPrefix(message, "<wait10>") { - //log.Printf("Special code <wait10> found, will sleep 10 seconds at this point.") + log.Printf("Special code <wait10> found, will sleep 10 seconds at this point.") scancode = []string{"wait10"} message = message[len("<wait10>"):] } @@ -240,7 +252,7 @@ func scancodes(message string) []string { if scancode == nil { for specialCode, specialValue := range special { if strings.HasPrefix(message, specialCode) { - //log.Printf("Special code '%s' found, replacing with: %s", specialCode, specialValue) + log.Printf("Special code '%s' found, replacing with: %s", specialCode, specialValue) scancode = specialValue message = message[len(specialCode):] break @@ -266,7 +278,7 @@ func scancodes(message string) []string { } scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt+0x80)) - //log.Printf("Sending char '%c', code '%v', shift %v", r, scancode, keyShift) + log.Printf("Sending char '%c', code '%v', shift %v", r, scancode, keyShift) } result = append(result, scancode...) diff --git a/website/source/docs/builders/hyperv-iso.html.markdown b/website/source/docs/builders/hyperv-iso.html.markdown index ceb946136..2d030d7c3 100644 --- a/website/source/docs/builders/hyperv-iso.html.markdown +++ b/website/source/docs/builders/hyperv-iso.html.markdown @@ -1,8 +1,8 @@ --- -layout: "docs" -page_title: "HyperV Builder (from an ISO)" description: |- The HyperV Packer builder is able to create HyperV virtual machines and export them. +layout: "docs" +page_title: "HyperV Builder (from an ISO)" --- # HyperV Builder (from an ISO) @@ -59,126 +59,126 @@ can be configured for this builder. ### 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` (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_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. +- `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. ### 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_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. +- `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. -* `cpu` (int) - The number of cpus the virtual machine should use. If this isn't specified, - the default is 1 cpu. +- `cpu` (int) - The number of cpus the virtual machine should use. If this isn't specified, + the default is 1 cpu. -* `disk_size` (integer) - The size, in megabytes, of the hard disk to create - for the VM. By default, this is 40000 (about 40 GB). +- `disk_size` (integer) - The size, in megabytes, of the hard disk to create + for the VM. By default, this is 40000 (about 40 GB). -* `enable_secure_boot` (bool) - If true enable secure boot for virtual machine. - This defaults to false. +- `enable_secure_boot` (bool) - If true enable secure boot for virtual machine. + This defaults to false. -* `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. +- `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. -* `generation` (int) - The HyperV generation for the virtual machine. By - default, this is 1. Generation 2 HyperV virtual machines do not support - floppy drives. In this scenario use secondary_iso_images instead. Hard - drives and dvd drives will also be scsi and not ide. +- `generation` (int) - The HyperV generation for the virtual machine. By + default, this is 1. Generation 2 HyperV virtual machines do not support + floppy drives. In this scenario use secondary_iso_images instead. Hard + drives and dvd drives will also be scsi and not ide. -* `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_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. +- `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. -* `ip_address_timeout` (string) - The time to wait after creating the initial virtual - machine and waiting for an ip address before assuming there is an error in the process. - 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. +- `ip_address_timeout` (string) - The time to wait after creating the initial virtual + machine and waiting for an ip address before assuming there is an error in the process. + 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. -* `iso_urls` (array of strings) - Multiple URLs for the ISO to download. - Packer will try these in order. If anything goes wrong attempting to download - or while downloading a single URL, it will move on to the next. All URLs - must point to the same file (same checksum). By default this is empty - and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified. +- `iso_urls` (array of strings) - Multiple URLs for the ISO to download. + Packer will try these in order. If anything goes wrong attempting to download + or while downloading a single URL, it will move on to the next. All URLs + must point to the same file (same checksum). By default this is empty + and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified. -* `output_directory` (string) - This is the path to the directory where the - resulting virtual machine will be created. This may be relative or absolute. - If relative, the path is relative to the working directory when `packer` - is executed. This directory must not exist or be empty prior to running the builder. - By default this is "output-BUILDNAME" where "BUILDNAME" is the name - of the build. +- `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. * `secondary_iso_images` (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. + 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. -* `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 unless a shutdown command takes place inside script so this may - safely be omitted. If one or more scripts require a reboot it is suggested to leave this blank - since reboots may fail and specify the final shutdown command in your last script. +- `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 unless a shutdown command takes place inside script so this may + safely be omitted. If one or more scripts require a reboot it is suggested to leave this blank + since reboots may fail and specify the final shutdown command in your last script. -* `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. +- `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. -* `skip_compaction` (bool) - If true skip compacting the hard disk for virtual machine when - exporting. This defaults to false. +- `skip_compaction` (bool) - If true skip compacting the hard disk for virtual machine when + exporting. This defaults to false. -* `switch_name` (string) - The name of the switch to connect the virtual machine to. Be defaulting - this to an empty string, Packer will try to determine the switch to use by looking for - external switch that is up and running. +- `switch_name` (string) - The name of the switch to connect the virtual machine to. Be defaulting + this to an empty string, Packer will try to determine the switch to use by looking for + external switch that is up and running. -* `vm_name` (string) - This is the name of the virtua machine for the new virtual - machine, without the file extension. By default this is "packer-BUILDNAME", - where "BUILDNAME" is the name of the build. +- `vm_name` (string) - This is the name of the virtua machine for the new virtual + machine, without the file extension. By default this is "packer-BUILDNAME", + where "BUILDNAME" is the name of the build. ## Boot Command @@ -196,30 +196,51 @@ 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: -* `<bs>` - Backspace +- `<bs>` - Backspace -* `<del>` - Delete +- `<del>` - Delete -* `<enter>` and `<return>` - Simulates an actual "enter" or "return" keypress. +- `<enter>` and `<return>` - Simulates an actual "enter" or "return" keypress. -* `<esc>` - Simulates pressing the escape key. +- `<esc>` - Simulates pressing the escape key. -* `<tab>` - Simulates pressing the tab key. +- `<tab>` - Simulates pressing the tab key. -* `<f1>` - `<f12>` - Simulates pressing a function key. +- `<f1>` - `<f12>` - Simulates pressing a function key. -* `<up>` `<down>` `<left>` `<right>` - Simulates pressing an arrow key. +- `<up>` `<down>` `<left>` `<right>` - Simulates pressing an arrow key. -* `<spacebar>` - Simulates pressing the spacebar. +- `<spacebar>` - Simulates pressing the spacebar. -* `<insert>` - Simulates pressing the insert key. +- `<insert>` - Simulates pressing the insert key. -* `<home>` `<end>` - Simulates pressing the home and end keys. +- `<home>` `<end>` - Simulates pressing the home and end keys. -* `<pageUp>` `<pageDown>` - Simulates pressing the page up and page down keys. +- `<pageUp>` `<pageDown>` - Simulates pressing the page up and page down keys. -* `<wait>` `<wait5>` `<wait10>` - 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. +- `<leftAlt>` `<rightAlt>` - Simulates pressing the alt key. + +- `<leftCtrl>` `<rightCtrl>` - Simulates pressing the ctrl key. + +- `<leftShift>` `<rightShift>` - Simulates pressing the shift key. + +- `<leftAltOn>` `<rightAltOn>` - Simulates pressing and holding the alt key. + +- `<leftCtrlOn>` `<rightCtrlOn>` - Simulates pressing and holding the ctrl key. + +- `<leftShiftOn>` `<rightShiftOn>` - Simulates pressing and holding the shift key. + +- `<leftAltOff>` `<rightAltOff>` - Simulates releasing a held alt key. + +- `<leftCtrlOff>` `<rightCtrlOff>` - Simulates releasing a held ctrl key. + +- `<leftShiftOff>` `<rightShiftOff>` - Simulates releasing a held shift key. + +- `<wait>` `<wait5>` `<wait10>` - 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. + +When using modifier keys `ctrl`, `alt`, `shift` ensure that you release them, otherwise they will be held down until the machine reboots. Use lowercase characters as well inside modifiers. For example: to simulate ctrl+c use `<leftCtrlOn>c<leftCtrlOff>`. In addition to the special keys, each command to type is treated as a [configuration template](/docs/templates/configuration-templates.html). From 7ce9203530f38316852f8271408289517481bb77 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Tue, 2 Aug 2016 21:41:32 +0100 Subject: [PATCH 078/108] Enable-VMIntegrationService cmdlet has a language dependent value for -Name option. So avoid using it, as it will break on machines with different languages. --- powershell/hyperv/hyperv.go | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 59d35e5c7..32952520f 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -472,13 +472,31 @@ if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { func EnableVirtualMachineIntegrationService(vmName string, integrationServiceName string) error { + integrationServiceId := "" + switch integrationServiceName { + case "Time Synchronization": + integrationServiceId = "2497F4DE-E9FA-4204-80E4-4B75C46419C0" + case "Heartbeat": + integrationServiceId = "84EAAE65-2F2E-45F5-9BB5-0E857DC8EB47" + case "Key-Value Pair Exchange": + integrationServiceId = "2A34B1C2-FD73-4043-8A5B-DD2159BC743F" + case "Shutdown": + integrationServiceId = "9F8233AC-BE49-4C79-8EE3-E7E1985B2077" + case "VSS": + integrationServiceId = "5CED1297-4598-4915-A5FC-AD21BB4D02A4" + case "Guest Service Interface": + integrationServiceId = "6C09BB55-D683-4DA0-8931-C9BF705F6480" + default: + panic("unrecognized Integration Service Name") + } + var script = ` -param([string]$vmName,[string]$integrationServiceName) -Enable-VMIntegrationService -VMName $vmName -Name $integrationServiceName +param([string]$vmName,[string]$integrationServiceId) +Get-VMIntegrationService -VmName $vmName | ?{$_.Id -match $integrationServiceId} | Enable-VMIntegrationService ` var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, integrationServiceName) + err := ps.Run(script, vmName, integrationServiceId) return err } From 6342e1525de01fd5b627234867f2df3d410a7b24 Mon Sep 17 00:00:00 2001 From: Patrick Lang <patrick.lang@hotmail.com> Date: Fri, 5 Aug 2016 18:03:30 -0700 Subject: [PATCH 079/108] s/EnabeSecureBoot/EnableSecureBoot/g --- builder/hyperv/common/step_create_vm.go | 6 +++--- builder/hyperv/iso/builder.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index 6d142cb0b..a23c393c9 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -21,7 +21,7 @@ type StepCreateVM struct { DiskSize uint Generation uint Cpu uint - EnabeSecureBoot bool + EnableSecureBoot bool } func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { @@ -36,7 +36,7 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { diskSize := int64(s.DiskSize * 1024 * 1024) switchName := s.SwitchName - enabeSecureBoot := s.EnabeSecureBoot + enableSecureBoot := s.EnableSecureBoot err := driver.CreateVirtualMachine(s.VMName, path, ram, diskSize, switchName, s.Generation) if err != nil { @@ -55,7 +55,7 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { } if s.Generation == 2 { - err = driver.SetSecureBoot(s.VMName, enabeSecureBoot) + err = driver.SetSecureBoot(s.VMName, enableSecureBoot) if err != nil { err := fmt.Errorf("Error setting secure boot: %s", err) state.Put("error", err) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index d294c8cf5..ac3fbae85 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -297,7 +297,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe DiskSize: b.config.DiskSize, Generation: b.config.Generation, Cpu: b.config.Cpu, - EnabeSecureBoot: b.config.EnableSecureBoot, + EnableSecureBoot: b.config.EnableSecureBoot, }, &hypervcommon.StepEnableIntegrationService{}, From f94c2cd2f74061bfa83650a26fad8d74e25e006f Mon Sep 17 00:00:00 2001 From: Patrick Lang <patrick.lang@hotmail.com> Date: Fri, 5 Aug 2016 20:14:36 -0700 Subject: [PATCH 080/108] Adding nested virtualization support --- builder/hyperv/common/driver.go | 2 +- builder/hyperv/common/driver_ps_4.go | 4 ++-- builder/hyperv/common/step_create_vm.go | 3 ++- builder/hyperv/iso/builder.go | 2 ++ powershell/hyperv/hyperv.go | 6 +++--- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index a62e3112b..8cfe67582 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -72,7 +72,7 @@ type Driver interface { DeleteVirtualMachine(string) error - SetVirtualMachineCpu(string, uint) error + SetVirtualMachineCpu(string, uint, bool) error SetSecureBoot(string, bool) error diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index 15d6c6817..df1355c59 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -177,8 +177,8 @@ func (d *HypervPS4Driver) DeleteVirtualMachine(vmName string) error { return hyperv.DeleteVirtualMachine(vmName) } -func (d *HypervPS4Driver) SetVirtualMachineCpu(vmName string, cpu uint) error { - return hyperv.SetVirtualMachineCpu(vmName, cpu) +func (d *HypervPS4Driver) SetVirtualMachineCpu(vmName string, cpu uint, exposeVirtualizationExtensions bool) error { + return hyperv.SetVirtualMachineCpu(vmName, cpu, exposeVirtualizationExtensions) } func (d *HypervPS4Driver) SetSecureBoot(vmName string, enable bool) error { diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index a23c393c9..e67942920 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -22,6 +22,7 @@ type StepCreateVM struct { Generation uint Cpu uint EnableSecureBoot bool + ExposeVirtualizationExtensions bool } func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { @@ -46,7 +47,7 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - err = driver.SetVirtualMachineCpu(s.VMName, s.Cpu) + err = driver.SetVirtualMachineCpu(s.VMName, s.Cpu, s.ExposeVirtualizationExtensions) if err != nil { err := fmt.Errorf("Error creating setting virtual machine cpu: %s", err) state.Put("error", err) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index ac3fbae85..92d192424 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -89,6 +89,7 @@ type Config struct { Cpu uint `mapstructure:"cpu"` Generation uint `mapstructure:"generation"` EnableSecureBoot bool `mapstructure:"enable_secure_boot"` + EnableVirtualizationExtensions `mapstructure:"enable_virtualization_extensions` Communicator string `mapstructure:"communicator"` @@ -298,6 +299,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Generation: b.config.Generation, Cpu: b.config.Cpu, EnableSecureBoot: b.config.EnableSecureBoot, + EnableVirtualizationExtensions: b.config.EnableVirtualizationExtensions }, &hypervcommon.StepEnableIntegrationService{}, diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 32952520f..b75a5e0b7 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -217,11 +217,11 @@ New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHD } } -func SetVirtualMachineCpu(vmName string, cpu uint) error { +func SetVirtualMachineCpu(vmName string, cpu uint, exposeVirtualizationExtensions bool) error { var script = ` -param([string]$vmName, [int]$cpu) -Set-VMProcessor -VMName $vmName -Count $cpu +param([string]$vmName, [int]$cpu, [bool]$exposeVirtualizationExtensions) +Set-VMProcessor -VMName $vmName -Count $cpu -exposeVirtualizationExtensions $exposeVirtualizationExtensions ` var ps powershell.PowerShellCmd From 6d391fc34a677d5b3435a83d1fc06bdd4d9872b2 Mon Sep 17 00:00:00 2001 From: Patrick Lang <patrick.lang@hotmail.com> Date: Fri, 5 Aug 2016 23:09:33 -0700 Subject: [PATCH 081/108] Adding support for nested Hyper-V --- builder/hyperv/common/step_create_vm.go | 4 ++-- builder/hyperv/iso/builder.go | 4 ++-- powershell/hyperv/hyperv.go | 9 ++++++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index e67942920..c03636f30 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -22,7 +22,7 @@ type StepCreateVM struct { Generation uint Cpu uint EnableSecureBoot bool - ExposeVirtualizationExtensions bool + EnableVirtualizationExtensions bool } func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { @@ -47,7 +47,7 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - err = driver.SetVirtualMachineCpu(s.VMName, s.Cpu, s.ExposeVirtualizationExtensions) + err = driver.SetVirtualMachineCpu(s.VMName, s.Cpu, s.EnableVirtualizationExtensions) if err != nil { err := fmt.Errorf("Error creating setting virtual machine cpu: %s", err) state.Put("error", err) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 92d192424..274dca2ea 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -89,7 +89,7 @@ type Config struct { Cpu uint `mapstructure:"cpu"` Generation uint `mapstructure:"generation"` EnableSecureBoot bool `mapstructure:"enable_secure_boot"` - EnableVirtualizationExtensions `mapstructure:"enable_virtualization_extensions` + EnableVirtualizationExtensions bool `mapstructure:"enable_virtualization_extensions"` Communicator string `mapstructure:"communicator"` @@ -299,7 +299,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Generation: b.config.Generation, Cpu: b.config.Cpu, EnableSecureBoot: b.config.EnableSecureBoot, - EnableVirtualizationExtensions: b.config.EnableVirtualizationExtensions + EnableVirtualizationExtensions: b.config.EnableVirtualizationExtensions, }, &hypervcommon.StepEnableIntegrationService{}, diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index b75a5e0b7..2b75a7c5e 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -217,15 +217,18 @@ New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHD } } -func SetVirtualMachineCpu(vmName string, cpu uint, exposeVirtualizationExtensions bool) error { +func SetVirtualMachineCpu(vmName string, cpu uint, enableVirtualizationExtensions bool) error { var script = ` param([string]$vmName, [int]$cpu, [bool]$exposeVirtualizationExtensions) Set-VMProcessor -VMName $vmName -Count $cpu -exposeVirtualizationExtensions $exposeVirtualizationExtensions ` - + exposeVirtualizationExtensionsString := "$False" + if enableVirtualizationExtensions { + exposeVirtualizationExtensionsString = "$True" + } var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, strconv.FormatInt(int64(cpu), 10)) + err := ps.Run(script, vmName, strconv.FormatInt(int64(cpu), 10), exposeVirtualizationExtensionsString) return err } From 1277a25d0b70aaf6e07c564a3852c04cbbd90127 Mon Sep 17 00:00:00 2001 From: Patrick Lang <patrick.lang@hotmail.com> Date: Fri, 5 Aug 2016 23:59:16 -0700 Subject: [PATCH 082/108] Fix type casting --- powershell/hyperv/hyperv.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 2b75a7c5e..f242dfa3c 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -220,12 +220,13 @@ New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHD func SetVirtualMachineCpu(vmName string, cpu uint, enableVirtualizationExtensions bool) error { var script = ` -param([string]$vmName, [int]$cpu, [bool]$exposeVirtualizationExtensions) -Set-VMProcessor -VMName $vmName -Count $cpu -exposeVirtualizationExtensions $exposeVirtualizationExtensions +param([string]$vmName, [int]$cpu, [string]$exposeVirtualizationExtensions) +$nested = [System.Boolean]::Parse($exposeVirtualizationExtensions) +Set-VMProcessor -VMName $vmName -Count $cpu -exposeVirtualizationExtensions $nested ` - exposeVirtualizationExtensionsString := "$False" + exposeVirtualizationExtensionsString := "False" if enableVirtualizationExtensions { - exposeVirtualizationExtensionsString = "$True" + exposeVirtualizationExtensionsString = "True" } var ps powershell.PowerShellCmd err := ps.Run(script, vmName, strconv.FormatInt(int64(cpu), 10), exposeVirtualizationExtensionsString) From 9fbd1e472adc1be00a038b59fa56dd7a775224db Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 7 Aug 2016 12:26:27 +0100 Subject: [PATCH 083/108] Add support for mac spoofing and dynamic memory. To enable nested virtualization, mac spoofing, no dynamic memory and at least 4gb of ram should be set for the vm. Set warning if this has not been done. Detected Virtualization Extensions are supported by the machine your are running on, as it only works for Windows 10 and Windows Server 2016 onwards. --- builder/hyperv/common/driver.go | 10 +++- builder/hyperv/common/driver_ps_4.go | 25 +++++++--- builder/hyperv/common/step_create_vm.go | 57 ++++++++++++++++----- builder/hyperv/iso/builder.go | 65 ++++++++++++++++++------ powershell/hyperv/hyperv.go | 66 ++++++++++++++++++++----- powershell/powershell.go | 17 +++++++ 6 files changed, 192 insertions(+), 48 deletions(-) diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index 8cfe67582..35df38cb3 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -72,9 +72,15 @@ type Driver interface { DeleteVirtualMachine(string) error - SetVirtualMachineCpu(string, uint, bool) error + SetVirtualMachineCpuCount(string, uint) error - SetSecureBoot(string, bool) error + SetVirtualMachineMacSpoofing(string, bool) error + + SetVirtualMachineDynamicMemory(string, bool) error + + SetVirtualMachineSecureBoot(string, bool) error + + SetVirtualMachineVirtualizationExtensions(string, bool) error EnableVirtualMachineIntegrationService(string, string) error diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index df1355c59..1fa522034 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -6,12 +6,13 @@ package common import ( "fmt" - "github.com/mitchellh/packer/powershell" - "github.com/mitchellh/packer/powershell/hyperv" "log" "runtime" "strconv" "strings" + + "github.com/mitchellh/packer/powershell" + "github.com/mitchellh/packer/powershell/hyperv" ) type HypervPS4Driver struct { @@ -177,12 +178,24 @@ func (d *HypervPS4Driver) DeleteVirtualMachine(vmName string) error { return hyperv.DeleteVirtualMachine(vmName) } -func (d *HypervPS4Driver) SetVirtualMachineCpu(vmName string, cpu uint, exposeVirtualizationExtensions bool) error { - return hyperv.SetVirtualMachineCpu(vmName, cpu, exposeVirtualizationExtensions) +func (d *HypervPS4Driver) SetVirtualMachineCpuCount(vmName string, cpu uint) error { + return hyperv.SetVirtualMachineCpuCount(vmName, cpu) } -func (d *HypervPS4Driver) SetSecureBoot(vmName string, enable bool) error { - return hyperv.SetSecureBoot(vmName, enable) +func (d *HypervPS4Driver) SetVirtualMachineMacSpoofing(vmName string, enable bool) error { + return hyperv.SetVirtualMachineMacSpoofing(vmName, enable) +} + +func (d *HypervPS4Driver) SetVirtualMachineDynamicMemory(vmName string, enable bool) error { + return hyperv.SetVirtualMachineDynamicMemory(vmName, enable) +} + +func (d *HypervPS4Driver) SetVirtualMachineSecureBoot(vmName string, enable bool) error { + return hyperv.SetVirtualMachineSecureBoot(vmName, enable) +} + +func (d *HypervPS4Driver) SetVirtualMachineVirtualizationExtensions(vmName string, enable bool) error { + return hyperv.SetVirtualMachineVirtualizationExtensions(vmName, enable) } func (d *HypervPS4Driver) EnableVirtualMachineIntegrationService(vmName string, integrationServiceName string) error { diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index c03636f30..54a5a7103 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -6,6 +6,7 @@ package common import ( "fmt" + "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" ) @@ -15,13 +16,15 @@ import ( // Produces: // VMName string - The name of the VM type StepCreateVM struct { - VMName string - SwitchName string - RamSizeMB uint - DiskSize uint - Generation uint - Cpu uint - EnableSecureBoot bool + VMName string + SwitchName string + RamSizeMB uint + DiskSize uint + Generation uint + Cpu uint + EnableMacSpoofing bool + EnableDynamicMemory bool + EnableSecureBoot bool EnableVirtualizationExtensions bool } @@ -36,10 +39,7 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { ram := int64(s.RamSizeMB * 1024 * 1024) diskSize := int64(s.DiskSize * 1024 * 1024) - switchName := s.SwitchName - enableSecureBoot := s.EnableSecureBoot - - err := driver.CreateVirtualMachine(s.VMName, path, ram, diskSize, switchName, s.Generation) + err := driver.CreateVirtualMachine(s.VMName, path, ram, diskSize, s.SwitchName, s.Generation) if err != nil { err := fmt.Errorf("Error creating virtual machine: %s", err) state.Put("error", err) @@ -47,7 +47,7 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - err = driver.SetVirtualMachineCpu(s.VMName, s.Cpu, s.EnableVirtualizationExtensions) + err = driver.SetVirtualMachineCpuCount(s.VMName, s.Cpu) if err != nil { err := fmt.Errorf("Error creating setting virtual machine cpu: %s", err) state.Put("error", err) @@ -55,8 +55,28 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } + if s.EnableDynamicMemory { + err = driver.SetVirtualMachineDynamicMemory(s.VMName, s.EnableDynamicMemory) + if err != nil { + err := fmt.Errorf("Error creating setting virtual machine dynamic memory: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + if s.EnableMacSpoofing { + err = driver.SetVirtualMachineMacSpoofing(s.VMName, s.EnableMacSpoofing) + if err != nil { + err := fmt.Errorf("Error creating setting virtual machine mac spoofing: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + if s.Generation == 2 { - err = driver.SetSecureBoot(s.VMName, enableSecureBoot) + err = driver.SetVirtualMachineSecureBoot(s.VMName, s.EnableSecureBoot) if err != nil { err := fmt.Errorf("Error setting secure boot: %s", err) state.Put("error", err) @@ -65,6 +85,17 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { } } + if s.EnableVirtualizationExtensions { + //This is only supported on Windows 10 and Windows Server 2016 onwards + err = driver.SetVirtualMachineVirtualizationExtensions(s.VMName, s.EnableVirtualizationExtensions) + if err != nil { + err := fmt.Errorf("Error creating setting virtual machine virtualization extensions: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + // Set the final name in the state bag so others can use it state.Put("vmName", s.VMName) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 274dca2ea..0772e24ba 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -28,9 +28,10 @@ const ( MinDiskSize = 256 // 256MB MaxDiskSize = 64 * 1024 * 1024 // 64TB - DefaultRamSize = 1 * 1024 // 1GB - MinRamSize = 32 // 32MB - MaxRamSize = 32 * 1024 // 32GB + DefaultRamSize = 1 * 1024 // 1GB + MinRamSize = 32 // 32MB + MaxRamSize = 32 * 1024 // 32GB + MinNestedVirtualizationRamSize = 4 * 1024 // 4GB LowRam = 256 // 256MB @@ -84,12 +85,14 @@ type Config struct { // By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. VMName string `mapstructure:"vm_name"` - BootCommand []string `mapstructure:"boot_command"` - SwitchName string `mapstructure:"switch_name"` - Cpu uint `mapstructure:"cpu"` - Generation uint `mapstructure:"generation"` - EnableSecureBoot bool `mapstructure:"enable_secure_boot"` - EnableVirtualizationExtensions bool `mapstructure:"enable_virtualization_extensions"` + BootCommand []string `mapstructure:"boot_command"` + SwitchName string `mapstructure:"switch_name"` + Cpu uint `mapstructure:"cpu"` + Generation uint `mapstructure:"generation"` + EnableMacSpoofing bool `mapstructure:"enable_mac_spoofing"` + EnableDynamicMemory bool `mapstructure:"enable_dynamic_memory"` + EnableSecureBoot bool `mapstructure:"enable_secure_boot"` + EnableVirtualizationExtensions bool `mapstructure:"enable_virtualization_extensions"` Communicator string `mapstructure:"communicator"` @@ -227,6 +230,17 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } + if b.config.EnableVirtualizationExtensions { + hasVirtualMachineVirtualizationExtensions, err := powershell.HasVirtualMachineVirtualizationExtensions() + if err != nil { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed detecting virtual machine virtualization extensions support: %s", err)) + } else { + if !hasVirtualMachineVirtualizationExtensions { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("This version of Hyper-V does not support virtual machine virtualization extension. Please use Windows 10 or Windows Server 2016 or newer.")) + } + } + } + // Warnings if b.config.ShutdownCommand == "" { @@ -240,6 +254,23 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { warnings = appendWarnings(warnings, warning) } + if b.config.EnableVirtualizationExtensions { + if b.config.EnableDynamicMemory { + warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, dynamic memory should not be allowed.") + warnings = appendWarnings(warnings, warning) + } + + if !b.config.EnableMacSpoofing { + warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, mac spoofing should be allowed.") + warnings = appendWarnings(warnings, warning) + } + + if b.config.RamSizeMB < MinNestedVirtualizationRamSize { + warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, there should be 4GB or more memory set for the vm, otherwise Hyper-V may fail to start any nested VMs.") + warnings = appendWarnings(warnings, warning) + } + } + if errs != nil && len(errs.Errors) > 0 { return warnings, errs } @@ -292,13 +323,15 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SwitchName: b.config.SwitchName, }, &hypervcommon.StepCreateVM{ - VMName: b.config.VMName, - SwitchName: b.config.SwitchName, - RamSizeMB: b.config.RamSizeMB, - DiskSize: b.config.DiskSize, - Generation: b.config.Generation, - Cpu: b.config.Cpu, - EnableSecureBoot: b.config.EnableSecureBoot, + VMName: b.config.VMName, + SwitchName: b.config.SwitchName, + RamSizeMB: b.config.RamSizeMB, + DiskSize: b.config.DiskSize, + Generation: b.config.Generation, + Cpu: b.config.Cpu, + EnableMacSpoofing: b.config.EnableMacSpoofing, + EnableDynamicMemory: b.config.EnableDynamicMemory, + EnableSecureBoot: b.config.EnableSecureBoot, EnableVirtualizationExtensions: b.config.EnableVirtualizationExtensions, }, &hypervcommon.StepEnableIntegrationService{}, diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index f242dfa3c..67e26216a 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -217,23 +217,67 @@ New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHD } } -func SetVirtualMachineCpu(vmName string, cpu uint, enableVirtualizationExtensions bool) error { +func SetVirtualMachineCpuCount(vmName string, cpu uint) error { var script = ` -param([string]$vmName, [int]$cpu, [string]$exposeVirtualizationExtensions) -$nested = [System.Boolean]::Parse($exposeVirtualizationExtensions) -Set-VMProcessor -VMName $vmName -Count $cpu -exposeVirtualizationExtensions $nested +param([string]$vmName, [int]$cpu) +Set-VMProcessor -VMName $vmName -Count $cpu +` + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, strconv.FormatInt(int64(cpu), 10)) + return err +} + +func SetVirtualMachineVirtualizationExtensions(vmName string, enableVirtualizationExtensions bool) error { + + var script = ` +param([string]$vmName, [string]$exposeVirtualizationExtensionsString) +$exposeVirtualizationExtensions = [System.Boolean]::Parse($exposeVirtualizationExtensionsString) +Set-VMProcessor -VMName $vmName -ExposeVirtualizationExtensions $exposeVirtualizationExtensions ` exposeVirtualizationExtensionsString := "False" if enableVirtualizationExtensions { exposeVirtualizationExtensionsString = "True" - } + } var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, strconv.FormatInt(int64(cpu), 10), exposeVirtualizationExtensionsString) + err := ps.Run(script, vmName, exposeVirtualizationExtensionsString) return err } -func SetSecureBoot(vmName string, enable bool) error { +func SetVirtualMachineDynamicMemory(vmName string, enableDynamicMemory bool) error { + + var script = ` +param([string]$vmName, [string]$enableDynamicMemoryString) +$enableDynamicMemory = [System.Boolean]::Parse($enableDynamicMemoryString) +Set-VMMemory -VMName $vmName -DynamicMemoryEnabled $enableDynamicMemory +` + enableDynamicMemoryString := "False" + if enableDynamicMemory { + enableDynamicMemoryString = "True" + } + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, enableDynamicMemoryString) + return err +} + +func SetVirtualMachineMacSpoofing(vmName string, enableMacSpoofing bool) error { + var script = ` +param([string]$vmName, $enableMacSpoofing) +Set-VMNetworkAdapter -VMName $vmName -MacAddressSpoofing $enableMacSpoofing +` + + var ps powershell.PowerShellCmd + + enableMacSpoofingString := "Off" + if enableMacSpoofing { + enableMacSpoofingString = "On" + } + + err := ps.Run(script, vmName, enableMacSpoofingString) + return err +} + +func SetVirtualMachineSecureBoot(vmName string, enableSecureBoot bool) error { var script = ` param([string]$vmName, $enableSecureBoot) Set-VMFirmware -VMName $vmName -EnableSecureBoot $enableSecureBoot @@ -241,12 +285,12 @@ Set-VMFirmware -VMName $vmName -EnableSecureBoot $enableSecureBoot var ps powershell.PowerShellCmd - enableSecureBoot := "Off" - if enable { - enableSecureBoot = "On" + enableSecureBootString := "Off" + if enableSecureBoot { + enableSecureBootString = "On" } - err := ps.Run(script, vmName, enableSecureBoot) + err := ps.Run(script, vmName, enableSecureBootString) return err } diff --git a/powershell/powershell.go b/powershell/powershell.go index 044ec6384..93ba91150 100644 --- a/powershell/powershell.go +++ b/powershell/powershell.go @@ -231,6 +231,23 @@ param([string]$moduleName) return true, nil } +func HasVirtualMachineVirtualizationExtensions() (bool, error) { + + var script = ` +(GET-Command Set-VMProcessor).parameters.keys -contains "ExposeVirtualizationExtensions" +` + + var ps PowerShellCmd + cmdOut, err := ps.Output(script) + + if err != nil { + return false, err + } + + var hasVirtualMachineVirtualizationExtensions = strings.TrimSpace(cmdOut) == "True" + return hasVirtualMachineVirtualizationExtensions, err +} + func SetUnattendedProductKey(path string, productKey string) error { var script = ` From b8554dad7d80b334474f3d222505b74a28988587 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 7 Aug 2016 12:32:11 +0100 Subject: [PATCH 084/108] Add documentation for: enable_mac_spoofing, enable_dynamic_memory and enable_virtualization_extensions --- website/source/docs/builders/hyperv-iso.html.markdown | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/website/source/docs/builders/hyperv-iso.html.markdown b/website/source/docs/builders/hyperv-iso.html.markdown index 2d030d7c3..0148c518d 100644 --- a/website/source/docs/builders/hyperv-iso.html.markdown +++ b/website/source/docs/builders/hyperv-iso.html.markdown @@ -96,9 +96,19 @@ can be configured for this builder. - `disk_size` (integer) - The size, in megabytes, of the hard disk to create for the VM. By default, this is 40000 (about 40 GB). +- `enable_mac_spoofing` (bool) - If true enable mac spoofing for virtual machine. + This defaults to false. + +- `enable_dynamic_memory` (bool) - If true enable dynamic memory for virtual machine. + This defaults to false. + - `enable_secure_boot` (bool) - If true enable secure boot for virtual machine. This defaults to false. +- `enable_virtualization_extensions` (bool) - If true enable virtualization extensions for virtual machine. + This defaults to false. For nested virtualization you need to enable mac spoofing, disable dynamic memory + and have at least 4GB of RAM for virtual machine. + - `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 From 48b6cc065053ce5261fe41ad8881f4e66e019180 Mon Sep 17 00:00:00 2001 From: James Johnson <james.johnson@equifax.com> Date: Wed, 21 Sep 2016 18:31:06 +0000 Subject: [PATCH 085/108] Enable VlanID --- builder/hyperv/common/step_configure_vlan.go | 7 ++++--- builder/hyperv/iso/builder.go | 5 +++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/builder/hyperv/common/step_configure_vlan.go b/builder/hyperv/common/step_configure_vlan.go index 94f445ddd..cd0b34169 100644 --- a/builder/hyperv/common/step_configure_vlan.go +++ b/builder/hyperv/common/step_configure_vlan.go @@ -11,7 +11,7 @@ import ( ) type StepConfigureVlan struct { - vlanId string + VlanID string } func (s *StepConfigureVlan) Run(state multistep.StateBag) multistep.StepAction { @@ -24,10 +24,11 @@ func (s *StepConfigureVlan) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Configuring vlan...") - vlanId := s.vlanId + vlanId := s.VlanID if vlanId == "" { - vlanId = "1724" + // If no vlan ID is specified, do not enable Virtual LAN Identification + return multistep.ActionContinue } err := driver.SetNetworkAdapterVlanId(switchName, vlanId) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 0772e24ba..162e3c915 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -87,6 +87,7 @@ type Config struct { BootCommand []string `mapstructure:"boot_command"` SwitchName string `mapstructure:"switch_name"` + VlandID string `mapstructure:"vlan_id"` Cpu uint `mapstructure:"cpu"` Generation uint `mapstructure:"generation"` EnableMacSpoofing bool `mapstructure:"enable_mac_spoofing"` @@ -353,6 +354,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe IsoPaths: b.config.SecondaryDvdImages, Generation: b.config.Generation, }, + + &hypervcommon.StepConfigureVlan{ + VlanID: b.config.VlandID, + }, &hypervcommon.StepRun{ BootWait: b.config.BootWait, From c2d1f6b617d6e11890d706bda94a38fe67e1753d Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Thu, 29 Sep 2016 19:37:07 +0100 Subject: [PATCH 086/108] Added the ability to independently configure switch vlan. This will people to leave the switch in trunk mode and set a vlan for the vm. --- builder/hyperv/common/step_configure_vlan.go | 41 ++++++++++--------- builder/hyperv/iso/builder.go | 15 +++++-- .../docs/builders/hyperv-iso.html.markdown | 10 ++++- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/builder/hyperv/common/step_configure_vlan.go b/builder/hyperv/common/step_configure_vlan.go index cd0b34169..bfd24b490 100644 --- a/builder/hyperv/common/step_configure_vlan.go +++ b/builder/hyperv/common/step_configure_vlan.go @@ -6,12 +6,14 @@ package common import ( "fmt" + "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" ) type StepConfigureVlan struct { - VlanID string + VlanId string + SwitchVlanId string } func (s *StepConfigureVlan) Run(state multistep.StateBag) multistep.StepAction { @@ -21,30 +23,29 @@ func (s *StepConfigureVlan) Run(state multistep.StateBag) multistep.StepAction { errorMsg := "Error configuring vlan: %s" vmName := state.Get("vmName").(string) switchName := state.Get("SwitchName").(string) + vlanId := s.VlanId + switchVlanId := s.SwitchVlanId ui.Say("Configuring vlan...") - vlanId := s.VlanID - - if vlanId == "" { - // If no vlan ID is specified, do not enable Virtual LAN Identification - return multistep.ActionContinue + if switchVlanId != "" { + err := driver.SetNetworkAdapterVlanId(switchName, vlanId) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } } - err := driver.SetNetworkAdapterVlanId(switchName, vlanId) - if err != nil { - err := fmt.Errorf(errorMsg, err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - err = driver.SetVirtualMachineVlanId(vmName, vlanId) - if err != nil { - err := fmt.Errorf(errorMsg, err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt + if vlanId != "" { + err := driver.SetVirtualMachineVlanId(vmName, vlanId) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } } return multistep.ActionContinue diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 162e3c915..ac390eba1 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -87,7 +87,8 @@ type Config struct { BootCommand []string `mapstructure:"boot_command"` SwitchName string `mapstructure:"switch_name"` - VlandID string `mapstructure:"vlan_id"` + SwitchVlanId string `mapstructure:"switch_vlan_id"` + VlanId string `mapstructure:"vlan_id"` Cpu uint `mapstructure:"cpu"` Generation uint `mapstructure:"generation"` EnableMacSpoofing bool `mapstructure:"enable_mac_spoofing"` @@ -272,6 +273,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } + if b.config.SwitchVlanId != "" { + if b.config.SwitchVlanId != b.config.VlanId { + warning = fmt.Sprintf("Switch network adaptor vlan should match virtual machine network adaptor vlan. The switch will not be able to see traffic from the VM.") + warnings = appendWarnings(warnings, warning) + } + } + if errs != nil && len(errs.Errors) > 0 { return warnings, errs } @@ -354,9 +362,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe IsoPaths: b.config.SecondaryDvdImages, Generation: b.config.Generation, }, - + &hypervcommon.StepConfigureVlan{ - VlanID: b.config.VlandID, + VlanId: b.config.VlanId, + SwitchVlanId: b.config.SwitchVlanId, }, &hypervcommon.StepRun{ diff --git a/website/source/docs/builders/hyperv-iso.html.markdown b/website/source/docs/builders/hyperv-iso.html.markdown index 0148c518d..062f6bd79 100644 --- a/website/source/docs/builders/hyperv-iso.html.markdown +++ b/website/source/docs/builders/hyperv-iso.html.markdown @@ -186,7 +186,15 @@ can be configured for this builder. this to an empty string, Packer will try to determine the switch to use by looking for external switch that is up and running. -- `vm_name` (string) - This is the name of the virtua machine for the new virtual +- `switch_vlan_id` (string) - This is the vlan of the virtual switch's network card. + By default none is set. If none is set then a vlan is not set on the switch's network card. + If this value is set it should match the vlan specified in by `vlan_id`. + +- `vlan_id` (string) - This is the vlan of the virtual machine's network card for the new virtual + machine. By default none is set. If none is set then vlans are not set on the virtual machine's + network card. + +- `vm_name` (string) - This is the name of the virtual machine for the new virtual machine, without the file extension. By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. From d56d683bd101c764e4f31d875ac6d3aaaa6b5234 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Wed, 26 Oct 2016 10:12:29 +0100 Subject: [PATCH 087/108] Add vendoring for unicode: govendor fetch golang.org/x/text/encoding/unicode --- vendor/github.com/stretchr/testify/LICENSE | 22 + vendor/golang.org/x/text/LICENSE | 27 + vendor/golang.org/x/text/PATENTS | 22 + vendor/golang.org/x/text/encoding/encoding.go | 335 ++++ .../text/encoding/internal/identifier/gen.go | 137 ++ .../internal/identifier/identifier.go | 81 + .../text/encoding/internal/identifier/mib.go | 1621 +++++++++++++++++ .../x/text/encoding/internal/internal.go | 75 + .../x/text/encoding/unicode/override.go | 82 + .../x/text/encoding/unicode/unicode.go | 434 +++++ vendor/golang.org/x/text/internal/gen/code.go | 339 ++++ vendor/golang.org/x/text/internal/gen/gen.go | 281 +++ .../internal/utf8internal/utf8internal.go | 87 + vendor/golang.org/x/text/runes/cond.go | 187 ++ vendor/golang.org/x/text/runes/runes.go | 355 ++++ .../golang.org/x/text/transform/transform.go | 705 +++++++ vendor/golang.org/x/text/unicode/cldr/base.go | 100 + vendor/golang.org/x/text/unicode/cldr/cldr.go | 130 ++ .../golang.org/x/text/unicode/cldr/collate.go | 359 ++++ .../golang.org/x/text/unicode/cldr/decode.go | 171 ++ .../golang.org/x/text/unicode/cldr/makexml.go | 400 ++++ .../golang.org/x/text/unicode/cldr/resolve.go | 602 ++++++ .../golang.org/x/text/unicode/cldr/slice.go | 144 ++ vendor/golang.org/x/text/unicode/cldr/xml.go | 1456 +++++++++++++++ vendor/google.golang.org/cloud/LICENSE | 202 ++ vendor/vendor.json | 6 + 26 files changed, 8360 insertions(+) create mode 100644 vendor/github.com/stretchr/testify/LICENSE create mode 100644 vendor/golang.org/x/text/LICENSE create mode 100644 vendor/golang.org/x/text/PATENTS create mode 100644 vendor/golang.org/x/text/encoding/encoding.go create mode 100644 vendor/golang.org/x/text/encoding/internal/identifier/gen.go create mode 100644 vendor/golang.org/x/text/encoding/internal/identifier/identifier.go create mode 100644 vendor/golang.org/x/text/encoding/internal/identifier/mib.go create mode 100644 vendor/golang.org/x/text/encoding/internal/internal.go create mode 100644 vendor/golang.org/x/text/encoding/unicode/override.go create mode 100644 vendor/golang.org/x/text/encoding/unicode/unicode.go create mode 100644 vendor/golang.org/x/text/internal/gen/code.go create mode 100644 vendor/golang.org/x/text/internal/gen/gen.go create mode 100644 vendor/golang.org/x/text/internal/utf8internal/utf8internal.go create mode 100644 vendor/golang.org/x/text/runes/cond.go create mode 100644 vendor/golang.org/x/text/runes/runes.go create mode 100644 vendor/golang.org/x/text/transform/transform.go create mode 100644 vendor/golang.org/x/text/unicode/cldr/base.go create mode 100644 vendor/golang.org/x/text/unicode/cldr/cldr.go create mode 100644 vendor/golang.org/x/text/unicode/cldr/collate.go create mode 100644 vendor/golang.org/x/text/unicode/cldr/decode.go create mode 100644 vendor/golang.org/x/text/unicode/cldr/makexml.go create mode 100644 vendor/golang.org/x/text/unicode/cldr/resolve.go create mode 100644 vendor/golang.org/x/text/unicode/cldr/slice.go create mode 100644 vendor/golang.org/x/text/unicode/cldr/xml.go create mode 100644 vendor/google.golang.org/cloud/LICENSE diff --git a/vendor/github.com/stretchr/testify/LICENSE b/vendor/github.com/stretchr/testify/LICENSE new file mode 100644 index 000000000..473b670a7 --- /dev/null +++ b/vendor/github.com/stretchr/testify/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell + +Please consider promoting this project if you find it useful. + +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/golang.org/x/text/LICENSE b/vendor/golang.org/x/text/LICENSE new file mode 100644 index 000000000..6a66aea5e --- /dev/null +++ b/vendor/golang.org/x/text/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/text/PATENTS b/vendor/golang.org/x/text/PATENTS new file mode 100644 index 000000000..733099041 --- /dev/null +++ b/vendor/golang.org/x/text/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google 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, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/text/encoding/encoding.go b/vendor/golang.org/x/text/encoding/encoding.go new file mode 100644 index 000000000..221f175c0 --- /dev/null +++ b/vendor/golang.org/x/text/encoding/encoding.go @@ -0,0 +1,335 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package encoding defines an interface for character encodings, such as Shift +// JIS and Windows 1252, that can convert to and from UTF-8. +// +// Encoding implementations are provided in other packages, such as +// golang.org/x/text/encoding/charmap and +// golang.org/x/text/encoding/japanese. +package encoding // import "golang.org/x/text/encoding" + +import ( + "errors" + "io" + "strconv" + "unicode/utf8" + + "golang.org/x/text/encoding/internal/identifier" + "golang.org/x/text/transform" +) + +// TODO: +// - There seems to be some inconsistency in when decoders return errors +// and when not. Also documentation seems to suggest they shouldn't return +// errors at all (except for UTF-16). +// - Encoders seem to rely on or at least benefit from the input being in NFC +// normal form. Perhaps add an example how users could prepare their output. + +// Encoding is a character set encoding that can be transformed to and from +// UTF-8. +type Encoding interface { + // NewDecoder returns a Decoder. + NewDecoder() *Decoder + + // NewEncoder returns an Encoder. + NewEncoder() *Encoder +} + +// A Decoder converts bytes to UTF-8. It implements transform.Transformer. +// +// Transforming source bytes that are not of that encoding will not result in an +// error per se. Each byte that cannot be transcoded will be represented in the +// output by the UTF-8 encoding of '\uFFFD', the replacement rune. +type Decoder struct { + transform.Transformer + + // This forces external creators of Decoders to use names in struct + // initializers, allowing for future extendibility without having to break + // code. + _ struct{} +} + +// Bytes converts the given encoded bytes to UTF-8. It returns the converted +// bytes or nil, err if any error occurred. +func (d *Decoder) Bytes(b []byte) ([]byte, error) { + b, _, err := transform.Bytes(d, b) + if err != nil { + return nil, err + } + return b, nil +} + +// String converts the given encoded string to UTF-8. It returns the converted +// string or "", err if any error occurred. +func (d *Decoder) String(s string) (string, error) { + s, _, err := transform.String(d, s) + if err != nil { + return "", err + } + return s, nil +} + +// Reader wraps another Reader to decode its bytes. +// +// The Decoder may not be used for any other operation as long as the returned +// Reader is in use. +func (d *Decoder) Reader(r io.Reader) io.Reader { + return transform.NewReader(r, d) +} + +// An Encoder converts bytes from UTF-8. It implements transform.Transformer. +// +// Each rune that cannot be transcoded will result in an error. In this case, +// the transform will consume all source byte up to, not including the offending +// rune. Transforming source bytes that are not valid UTF-8 will be replaced by +// `\uFFFD`. To return early with an error instead, use transform.Chain to +// preprocess the data with a UTF8Validator. +type Encoder struct { + transform.Transformer + + // This forces external creators of Encoders to use names in struct + // initializers, allowing for future extendibility without having to break + // code. + _ struct{} +} + +// Bytes converts bytes from UTF-8. It returns the converted bytes or nil, err if +// any error occurred. +func (e *Encoder) Bytes(b []byte) ([]byte, error) { + b, _, err := transform.Bytes(e, b) + if err != nil { + return nil, err + } + return b, nil +} + +// String converts a string from UTF-8. It returns the converted string or +// "", err if any error occurred. +func (e *Encoder) String(s string) (string, error) { + s, _, err := transform.String(e, s) + if err != nil { + return "", err + } + return s, nil +} + +// Writer wraps another Writer to encode its UTF-8 output. +// +// The Encoder may not be used for any other operation as long as the returned +// Writer is in use. +func (e *Encoder) Writer(w io.Writer) io.Writer { + return transform.NewWriter(w, e) +} + +// ASCIISub is the ASCII substitute character, as recommended by +// http://unicode.org/reports/tr36/#Text_Comparison +const ASCIISub = '\x1a' + +// Nop is the nop encoding. Its transformed bytes are the same as the source +// bytes; it does not replace invalid UTF-8 sequences. +var Nop Encoding = nop{} + +type nop struct{} + +func (nop) NewDecoder() *Decoder { + return &Decoder{Transformer: transform.Nop} +} +func (nop) NewEncoder() *Encoder { + return &Encoder{Transformer: transform.Nop} +} + +// Replacement is the replacement encoding. Decoding from the replacement +// encoding yields a single '\uFFFD' replacement rune. Encoding from UTF-8 to +// the replacement encoding yields the same as the source bytes except that +// invalid UTF-8 is converted to '\uFFFD'. +// +// It is defined at http://encoding.spec.whatwg.org/#replacement +var Replacement Encoding = replacement{} + +type replacement struct{} + +func (replacement) NewDecoder() *Decoder { + return &Decoder{Transformer: replacementDecoder{}} +} + +func (replacement) NewEncoder() *Encoder { + return &Encoder{Transformer: replacementEncoder{}} +} + +func (replacement) ID() (mib identifier.MIB, other string) { + return identifier.Replacement, "" +} + +type replacementDecoder struct{ transform.NopResetter } + +func (replacementDecoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + if len(dst) < 3 { + return 0, 0, transform.ErrShortDst + } + if atEOF { + const fffd = "\ufffd" + dst[0] = fffd[0] + dst[1] = fffd[1] + dst[2] = fffd[2] + nDst = 3 + } + return nDst, len(src), nil +} + +type replacementEncoder struct{ transform.NopResetter } + +func (replacementEncoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + r, size := rune(0), 0 + + for ; nSrc < len(src); nSrc += size { + r = rune(src[nSrc]) + + // Decode a 1-byte rune. + if r < utf8.RuneSelf { + size = 1 + + } else { + // Decode a multi-byte rune. + r, size = utf8.DecodeRune(src[nSrc:]) + if size == 1 { + // All valid runes of size 1 (those below utf8.RuneSelf) were + // handled above. We have invalid UTF-8 or we haven't seen the + // full character yet. + if !atEOF && !utf8.FullRune(src[nSrc:]) { + err = transform.ErrShortSrc + break + } + r = '\ufffd' + } + } + + if nDst+utf8.RuneLen(r) > len(dst) { + err = transform.ErrShortDst + break + } + nDst += utf8.EncodeRune(dst[nDst:], r) + } + return nDst, nSrc, err +} + +// HTMLEscapeUnsupported wraps encoders to replace source runes outside the +// repertoire of the destination encoding with HTML escape sequences. +// +// This wrapper exists to comply to URL and HTML forms requiring a +// non-terminating legacy encoder. The produced sequences may lead to data +// loss as they are indistinguishable from legitimate input. To avoid this +// issue, use UTF-8 encodings whenever possible. +func HTMLEscapeUnsupported(e *Encoder) *Encoder { + return &Encoder{Transformer: &errorHandler{e, errorToHTML}} +} + +// ReplaceUnsupported wraps encoders to replace source runes outside the +// repertoire of the destination encoding with an encoding-specific +// replacement. +// +// This wrapper is only provided for backwards compatibility and legacy +// handling. Its use is strongly discouraged. Use UTF-8 whenever possible. +func ReplaceUnsupported(e *Encoder) *Encoder { + return &Encoder{Transformer: &errorHandler{e, errorToReplacement}} +} + +type errorHandler struct { + *Encoder + handler func(dst []byte, r rune, err repertoireError) (n int, ok bool) +} + +// TODO: consider making this error public in some form. +type repertoireError interface { + Replacement() byte +} + +func (h errorHandler) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + nDst, nSrc, err = h.Transformer.Transform(dst, src, atEOF) + for err != nil { + rerr, ok := err.(repertoireError) + if !ok { + return nDst, nSrc, err + } + r, sz := utf8.DecodeRune(src[nSrc:]) + n, ok := h.handler(dst[nDst:], r, rerr) + if !ok { + return nDst, nSrc, transform.ErrShortDst + } + err = nil + nDst += n + if nSrc += sz; nSrc < len(src) { + var dn, sn int + dn, sn, err = h.Transformer.Transform(dst[nDst:], src[nSrc:], atEOF) + nDst += dn + nSrc += sn + } + } + return nDst, nSrc, err +} + +func errorToHTML(dst []byte, r rune, err repertoireError) (n int, ok bool) { + buf := [8]byte{} + b := strconv.AppendUint(buf[:0], uint64(r), 10) + if n = len(b) + len("&#;"); n >= len(dst) { + return 0, false + } + dst[0] = '&' + dst[1] = '#' + dst[copy(dst[2:], b)+2] = ';' + return n, true +} + +func errorToReplacement(dst []byte, r rune, err repertoireError) (n int, ok bool) { + if len(dst) == 0 { + return 0, false + } + dst[0] = err.Replacement() + return 1, true +} + +// ErrInvalidUTF8 means that a transformer encountered invalid UTF-8. +var ErrInvalidUTF8 = errors.New("encoding: invalid UTF-8") + +// UTF8Validator is a transformer that returns ErrInvalidUTF8 on the first +// input byte that is not valid UTF-8. +var UTF8Validator transform.Transformer = utf8Validator{} + +type utf8Validator struct{ transform.NopResetter } + +func (utf8Validator) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + n := len(src) + if n > len(dst) { + n = len(dst) + } + for i := 0; i < n; { + if c := src[i]; c < utf8.RuneSelf { + dst[i] = c + i++ + continue + } + _, size := utf8.DecodeRune(src[i:]) + if size == 1 { + // All valid runes of size 1 (those below utf8.RuneSelf) were + // handled above. We have invalid UTF-8 or we haven't seen the + // full character yet. + err = ErrInvalidUTF8 + if !atEOF && !utf8.FullRune(src[i:]) { + err = transform.ErrShortSrc + } + return i, i, err + } + if i+size > len(dst) { + return i, i, transform.ErrShortDst + } + for ; size > 0; size-- { + dst[i] = src[i] + i++ + } + } + if len(src) > len(dst) { + err = transform.ErrShortDst + } + return n, n, err +} diff --git a/vendor/golang.org/x/text/encoding/internal/identifier/gen.go b/vendor/golang.org/x/text/encoding/internal/identifier/gen.go new file mode 100644 index 000000000..0c8eba7e5 --- /dev/null +++ b/vendor/golang.org/x/text/encoding/internal/identifier/gen.go @@ -0,0 +1,137 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build ignore + +package main + +import ( + "bytes" + "encoding/xml" + "fmt" + "io" + "log" + "strings" + + "golang.org/x/text/internal/gen" +) + +type registry struct { + XMLName xml.Name `xml:"registry"` + Updated string `xml:"updated"` + Registry []struct { + ID string `xml:"id,attr"` + Record []struct { + Name string `xml:"name"` + Xref []struct { + Type string `xml:"type,attr"` + Data string `xml:"data,attr"` + } `xml:"xref"` + Desc struct { + Data string `xml:",innerxml"` + // Any []struct { + // Data string `xml:",chardata"` + // } `xml:",any"` + // Data string `xml:",chardata"` + } `xml:"description,"` + MIB string `xml:"value"` + Alias []string `xml:"alias"` + MIME string `xml:"preferred_alias"` + } `xml:"record"` + } `xml:"registry"` +} + +func main() { + r := gen.OpenIANAFile("assignments/character-sets/character-sets.xml") + reg := &registry{} + if err := xml.NewDecoder(r).Decode(&reg); err != nil && err != io.EOF { + log.Fatalf("Error decoding charset registry: %v", err) + } + if len(reg.Registry) == 0 || reg.Registry[0].ID != "character-sets-1" { + log.Fatalf("Unexpected ID %s", reg.Registry[0].ID) + } + + w := &bytes.Buffer{} + fmt.Fprintf(w, "const (\n") + for _, rec := range reg.Registry[0].Record { + constName := "" + for _, a := range rec.Alias { + if strings.HasPrefix(a, "cs") && strings.IndexByte(a, '-') == -1 { + // Some of the constant definitions have comments in them. Strip those. + constName = strings.Title(strings.SplitN(a[2:], "\n", 2)[0]) + } + } + if constName == "" { + switch rec.MIB { + case "2085": + constName = "HZGB2312" // Not listed as alias for some reason. + default: + log.Fatalf("No cs alias defined for %s.", rec.MIB) + } + } + if rec.MIME != "" { + rec.MIME = fmt.Sprintf(" (MIME: %s)", rec.MIME) + } + fmt.Fprintf(w, "// %s is the MIB identifier with IANA name %s%s.\n//\n", constName, rec.Name, rec.MIME) + if len(rec.Desc.Data) > 0 { + fmt.Fprint(w, "// ") + d := xml.NewDecoder(strings.NewReader(rec.Desc.Data)) + inElem := true + attr := "" + for { + t, err := d.Token() + if err != nil { + if err != io.EOF { + log.Fatal(err) + } + break + } + switch x := t.(type) { + case xml.CharData: + attr = "" // Don't need attribute info. + a := bytes.Split([]byte(x), []byte("\n")) + for i, b := range a { + if b = bytes.TrimSpace(b); len(b) != 0 { + if !inElem && i > 0 { + fmt.Fprint(w, "\n// ") + } + inElem = false + fmt.Fprintf(w, "%s ", string(b)) + } + } + case xml.StartElement: + if x.Name.Local == "xref" { + inElem = true + use := false + for _, a := range x.Attr { + if a.Name.Local == "type" { + use = use || a.Value != "person" + } + if a.Name.Local == "data" && use { + attr = a.Value + " " + } + } + } + case xml.EndElement: + inElem = false + fmt.Fprint(w, attr) + } + } + fmt.Fprint(w, "\n") + } + for _, x := range rec.Xref { + switch x.Type { + case "rfc": + fmt.Fprintf(w, "// Reference: %s\n", strings.ToUpper(x.Data)) + case "uri": + fmt.Fprintf(w, "// Reference: %s\n", x.Data) + } + } + fmt.Fprintf(w, "%s MIB = %s\n", constName, rec.MIB) + fmt.Fprintln(w) + } + fmt.Fprintln(w, ")") + + gen.WriteGoFile("mib.go", "identifier", w.Bytes()) +} diff --git a/vendor/golang.org/x/text/encoding/internal/identifier/identifier.go b/vendor/golang.org/x/text/encoding/internal/identifier/identifier.go new file mode 100644 index 000000000..2a2da0ef2 --- /dev/null +++ b/vendor/golang.org/x/text/encoding/internal/identifier/identifier.go @@ -0,0 +1,81 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:generate go run gen.go + +// Package identifier defines the contract between implementations of Encoding +// and Index by defining identifiers that uniquely identify standardized coded +// character sets (CCS) and character encoding schemes (CES), which we will +// together refer to as encodings, for which Encoding implementations provide +// converters to and from UTF-8. This package is typically only of concern to +// implementers of Indexes and Encodings. +// +// One part of the identifier is the MIB code, which is defined by IANA and +// uniquely identifies a CCS or CES. Each code is associated with data that +// references authorities, official documentation as well as aliases and MIME +// names. +// +// Not all CESs are covered by the IANA registry. The "other" string that is +// returned by ID can be used to identify other character sets or versions of +// existing ones. +// +// It is recommended that each package that provides a set of Encodings provide +// the All and Common variables to reference all supported encodings and +// commonly used subset. This allows Index implementations to include all +// available encodings without explicitly referencing or knowing about them. +package identifier + +// Note: this package is internal, but could be made public if there is a need +// for writing third-party Indexes and Encodings. + +// References: +// - http://source.icu-project.org/repos/icu/icu/trunk/source/data/mappings/convrtrs.txt +// - http://www.iana.org/assignments/character-sets/character-sets.xhtml +// - http://www.iana.org/assignments/ianacharset-mib/ianacharset-mib +// - http://www.ietf.org/rfc/rfc2978.txt +// - http://www.unicode.org/reports/tr22/ +// - http://www.w3.org/TR/encoding/ +// - http://www.w3.org/TR/encoding/indexes/encodings.json +// - https://encoding.spec.whatwg.org/ +// - https://tools.ietf.org/html/rfc6657#section-5 + +// Interface can be implemented by Encodings to define the CCS or CES for which +// it implements conversions. +type Interface interface { + // ID returns an encoding identifier. Exactly one of the mib and other + // values should be non-zero. + // + // In the usual case it is only necessary to indicate the MIB code. The + // other string can be used to specify encodings for which there is no MIB, + // such as "x-mac-dingbat". + // + // The other string may only contain the characters a-z, A-Z, 0-9, - and _. + ID() (mib MIB, other string) + + // NOTE: the restrictions on the encoding are to allow extending the syntax + // with additional information such as versions, vendors and other variants. +} + +// A MIB identifies an encoding. It is derived from the IANA MIB codes and adds +// some identifiers for some encodings that are not covered by the IANA +// standard. +// +// See http://www.iana.org/assignments/ianacharset-mib. +type MIB uint16 + +// These additional MIB types are not defined in IANA. They are added because +// they are common and defined within the text repo. +const ( + // Unofficial marks the start of encodings not registered by IANA. + Unofficial MIB = 10000 + iota + + // Replacement is the WhatWG replacement encoding. + Replacement + + // XUserDefined is the code for x-user-defined. + XUserDefined + + // MacintoshCyrillic is the code for x-mac-cyrillic. + MacintoshCyrillic +) diff --git a/vendor/golang.org/x/text/encoding/internal/identifier/mib.go b/vendor/golang.org/x/text/encoding/internal/identifier/mib.go new file mode 100644 index 000000000..915abfa29 --- /dev/null +++ b/vendor/golang.org/x/text/encoding/internal/identifier/mib.go @@ -0,0 +1,1621 @@ +// This file was generated by go generate; DO NOT EDIT + +package identifier + +const ( + // ASCII is the MIB identifier with IANA name US-ASCII (MIME: US-ASCII). + // + // ANSI X3.4-1986 + // Reference: RFC2046 + ASCII MIB = 3 + + // ISOLatin1 is the MIB identifier with IANA name ISO_8859-1:1987 (MIME: ISO-8859-1). + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISOLatin1 MIB = 4 + + // ISOLatin2 is the MIB identifier with IANA name ISO_8859-2:1987 (MIME: ISO-8859-2). + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISOLatin2 MIB = 5 + + // ISOLatin3 is the MIB identifier with IANA name ISO_8859-3:1988 (MIME: ISO-8859-3). + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISOLatin3 MIB = 6 + + // ISOLatin4 is the MIB identifier with IANA name ISO_8859-4:1988 (MIME: ISO-8859-4). + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISOLatin4 MIB = 7 + + // ISOLatinCyrillic is the MIB identifier with IANA name ISO_8859-5:1988 (MIME: ISO-8859-5). + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISOLatinCyrillic MIB = 8 + + // ISOLatinArabic is the MIB identifier with IANA name ISO_8859-6:1987 (MIME: ISO-8859-6). + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISOLatinArabic MIB = 9 + + // ISOLatinGreek is the MIB identifier with IANA name ISO_8859-7:1987 (MIME: ISO-8859-7). + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1947 + // Reference: RFC1345 + ISOLatinGreek MIB = 10 + + // ISOLatinHebrew is the MIB identifier with IANA name ISO_8859-8:1988 (MIME: ISO-8859-8). + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISOLatinHebrew MIB = 11 + + // ISOLatin5 is the MIB identifier with IANA name ISO_8859-9:1989 (MIME: ISO-8859-9). + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISOLatin5 MIB = 12 + + // ISOLatin6 is the MIB identifier with IANA name ISO-8859-10 (MIME: ISO-8859-10). + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISOLatin6 MIB = 13 + + // ISOTextComm is the MIB identifier with IANA name ISO_6937-2-add. + // + // ISO-IR: International Register of Escape Sequences and ISO 6937-2:1983 + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISOTextComm MIB = 14 + + // HalfWidthKatakana is the MIB identifier with IANA name JIS_X0201. + // + // JIS X 0201-1976. One byte only, this is equivalent to + // JIS/Roman (similar to ASCII) plus eight-bit half-width + // Katakana + // Reference: RFC1345 + HalfWidthKatakana MIB = 15 + + // JISEncoding is the MIB identifier with IANA name JIS_Encoding. + // + // JIS X 0202-1991. Uses ISO 2022 escape sequences to + // shift code sets as documented in JIS X 0202-1991. + JISEncoding MIB = 16 + + // ShiftJIS is the MIB identifier with IANA name Shift_JIS (MIME: Shift_JIS). + // + // This charset is an extension of csHalfWidthKatakana by + // adding graphic characters in JIS X 0208. The CCS's are + // JIS X0201:1997 and JIS X0208:1997. The + // complete definition is shown in Appendix 1 of JIS + // X0208:1997. + // This charset can be used for the top-level media type "text". + ShiftJIS MIB = 17 + + // EUCPkdFmtJapanese is the MIB identifier with IANA name Extended_UNIX_Code_Packed_Format_for_Japanese (MIME: EUC-JP). + // + // Standardized by OSF, UNIX International, and UNIX Systems + // Laboratories Pacific. Uses ISO 2022 rules to select + // code set 0: US-ASCII (a single 7-bit byte set) + // code set 1: JIS X0208-1990 (a double 8-bit byte set) + // restricted to A0-FF in both bytes + // code set 2: Half Width Katakana (a single 7-bit byte set) + // requiring SS2 as the character prefix + // code set 3: JIS X0212-1990 (a double 7-bit byte set) + // restricted to A0-FF in both bytes + // requiring SS3 as the character prefix + EUCPkdFmtJapanese MIB = 18 + + // EUCFixWidJapanese is the MIB identifier with IANA name Extended_UNIX_Code_Fixed_Width_for_Japanese. + // + // Used in Japan. Each character is 2 octets. + // code set 0: US-ASCII (a single 7-bit byte set) + // 1st byte = 00 + // 2nd byte = 20-7E + // code set 1: JIS X0208-1990 (a double 7-bit byte set) + // restricted to A0-FF in both bytes + // code set 2: Half Width Katakana (a single 7-bit byte set) + // 1st byte = 00 + // 2nd byte = A0-FF + // code set 3: JIS X0212-1990 (a double 7-bit byte set) + // restricted to A0-FF in + // the first byte + // and 21-7E in the second byte + EUCFixWidJapanese MIB = 19 + + // ISO4UnitedKingdom is the MIB identifier with IANA name BS_4730. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO4UnitedKingdom MIB = 20 + + // ISO11SwedishForNames is the MIB identifier with IANA name SEN_850200_C. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO11SwedishForNames MIB = 21 + + // ISO15Italian is the MIB identifier with IANA name IT. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO15Italian MIB = 22 + + // ISO17Spanish is the MIB identifier with IANA name ES. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO17Spanish MIB = 23 + + // ISO21German is the MIB identifier with IANA name DIN_66003. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO21German MIB = 24 + + // ISO60Norwegian1 is the MIB identifier with IANA name NS_4551-1. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO60Norwegian1 MIB = 25 + + // ISO69French is the MIB identifier with IANA name NF_Z_62-010. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO69French MIB = 26 + + // ISO10646UTF1 is the MIB identifier with IANA name ISO-10646-UTF-1. + // + // Universal Transfer Format (1), this is the multibyte + // encoding, that subsets ASCII-7. It does not have byte + // ordering issues. + ISO10646UTF1 MIB = 27 + + // ISO646basic1983 is the MIB identifier with IANA name ISO_646.basic:1983. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO646basic1983 MIB = 28 + + // INVARIANT is the MIB identifier with IANA name INVARIANT. + // + // Reference: RFC1345 + INVARIANT MIB = 29 + + // ISO2IntlRefVersion is the MIB identifier with IANA name ISO_646.irv:1983. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO2IntlRefVersion MIB = 30 + + // NATSSEFI is the MIB identifier with IANA name NATS-SEFI. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + NATSSEFI MIB = 31 + + // NATSSEFIADD is the MIB identifier with IANA name NATS-SEFI-ADD. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + NATSSEFIADD MIB = 32 + + // NATSDANO is the MIB identifier with IANA name NATS-DANO. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + NATSDANO MIB = 33 + + // NATSDANOADD is the MIB identifier with IANA name NATS-DANO-ADD. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + NATSDANOADD MIB = 34 + + // ISO10Swedish is the MIB identifier with IANA name SEN_850200_B. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO10Swedish MIB = 35 + + // KSC56011987 is the MIB identifier with IANA name KS_C_5601-1987. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + KSC56011987 MIB = 36 + + // ISO2022KR is the MIB identifier with IANA name ISO-2022-KR (MIME: ISO-2022-KR). + // + // rfc1557 (see also KS_C_5601-1987) + // Reference: RFC1557 + ISO2022KR MIB = 37 + + // EUCKR is the MIB identifier with IANA name EUC-KR (MIME: EUC-KR). + // + // rfc1557 (see also KS_C_5861-1992) + // Reference: RFC1557 + EUCKR MIB = 38 + + // ISO2022JP is the MIB identifier with IANA name ISO-2022-JP (MIME: ISO-2022-JP). + // + // rfc1468 (see also rfc2237 ) + // Reference: RFC1468 + ISO2022JP MIB = 39 + + // ISO2022JP2 is the MIB identifier with IANA name ISO-2022-JP-2 (MIME: ISO-2022-JP-2). + // + // rfc1554 + // Reference: RFC1554 + ISO2022JP2 MIB = 40 + + // ISO13JISC6220jp is the MIB identifier with IANA name JIS_C6220-1969-jp. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO13JISC6220jp MIB = 41 + + // ISO14JISC6220ro is the MIB identifier with IANA name JIS_C6220-1969-ro. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO14JISC6220ro MIB = 42 + + // ISO16Portuguese is the MIB identifier with IANA name PT. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO16Portuguese MIB = 43 + + // ISO18Greek7Old is the MIB identifier with IANA name greek7-old. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO18Greek7Old MIB = 44 + + // ISO19LatinGreek is the MIB identifier with IANA name latin-greek. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO19LatinGreek MIB = 45 + + // ISO25French is the MIB identifier with IANA name NF_Z_62-010_(1973). + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO25French MIB = 46 + + // ISO27LatinGreek1 is the MIB identifier with IANA name Latin-greek-1. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO27LatinGreek1 MIB = 47 + + // ISO5427Cyrillic is the MIB identifier with IANA name ISO_5427. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO5427Cyrillic MIB = 48 + + // ISO42JISC62261978 is the MIB identifier with IANA name JIS_C6226-1978. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO42JISC62261978 MIB = 49 + + // ISO47BSViewdata is the MIB identifier with IANA name BS_viewdata. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO47BSViewdata MIB = 50 + + // ISO49INIS is the MIB identifier with IANA name INIS. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO49INIS MIB = 51 + + // ISO50INIS8 is the MIB identifier with IANA name INIS-8. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO50INIS8 MIB = 52 + + // ISO51INISCyrillic is the MIB identifier with IANA name INIS-cyrillic. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO51INISCyrillic MIB = 53 + + // ISO54271981 is the MIB identifier with IANA name ISO_5427:1981. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO54271981 MIB = 54 + + // ISO5428Greek is the MIB identifier with IANA name ISO_5428:1980. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO5428Greek MIB = 55 + + // ISO57GB1988 is the MIB identifier with IANA name GB_1988-80. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO57GB1988 MIB = 56 + + // ISO58GB231280 is the MIB identifier with IANA name GB_2312-80. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO58GB231280 MIB = 57 + + // ISO61Norwegian2 is the MIB identifier with IANA name NS_4551-2. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO61Norwegian2 MIB = 58 + + // ISO70VideotexSupp1 is the MIB identifier with IANA name videotex-suppl. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO70VideotexSupp1 MIB = 59 + + // ISO84Portuguese2 is the MIB identifier with IANA name PT2. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO84Portuguese2 MIB = 60 + + // ISO85Spanish2 is the MIB identifier with IANA name ES2. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO85Spanish2 MIB = 61 + + // ISO86Hungarian is the MIB identifier with IANA name MSZ_7795.3. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO86Hungarian MIB = 62 + + // ISO87JISX0208 is the MIB identifier with IANA name JIS_C6226-1983. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO87JISX0208 MIB = 63 + + // ISO88Greek7 is the MIB identifier with IANA name greek7. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO88Greek7 MIB = 64 + + // ISO89ASMO449 is the MIB identifier with IANA name ASMO_449. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO89ASMO449 MIB = 65 + + // ISO90 is the MIB identifier with IANA name iso-ir-90. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO90 MIB = 66 + + // ISO91JISC62291984a is the MIB identifier with IANA name JIS_C6229-1984-a. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO91JISC62291984a MIB = 67 + + // ISO92JISC62991984b is the MIB identifier with IANA name JIS_C6229-1984-b. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO92JISC62991984b MIB = 68 + + // ISO93JIS62291984badd is the MIB identifier with IANA name JIS_C6229-1984-b-add. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO93JIS62291984badd MIB = 69 + + // ISO94JIS62291984hand is the MIB identifier with IANA name JIS_C6229-1984-hand. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO94JIS62291984hand MIB = 70 + + // ISO95JIS62291984handadd is the MIB identifier with IANA name JIS_C6229-1984-hand-add. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO95JIS62291984handadd MIB = 71 + + // ISO96JISC62291984kana is the MIB identifier with IANA name JIS_C6229-1984-kana. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO96JISC62291984kana MIB = 72 + + // ISO2033 is the MIB identifier with IANA name ISO_2033-1983. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO2033 MIB = 73 + + // ISO99NAPLPS is the MIB identifier with IANA name ANSI_X3.110-1983. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO99NAPLPS MIB = 74 + + // ISO102T617bit is the MIB identifier with IANA name T.61-7bit. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO102T617bit MIB = 75 + + // ISO103T618bit is the MIB identifier with IANA name T.61-8bit. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO103T618bit MIB = 76 + + // ISO111ECMACyrillic is the MIB identifier with IANA name ECMA-cyrillic. + // + // ISO registry + // (formerly ECMA + // registry ) + ISO111ECMACyrillic MIB = 77 + + // ISO121Canadian1 is the MIB identifier with IANA name CSA_Z243.4-1985-1. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO121Canadian1 MIB = 78 + + // ISO122Canadian2 is the MIB identifier with IANA name CSA_Z243.4-1985-2. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO122Canadian2 MIB = 79 + + // ISO123CSAZ24341985gr is the MIB identifier with IANA name CSA_Z243.4-1985-gr. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO123CSAZ24341985gr MIB = 80 + + // ISO88596E is the MIB identifier with IANA name ISO_8859-6-E (MIME: ISO-8859-6-E). + // + // rfc1556 + // Reference: RFC1556 + ISO88596E MIB = 81 + + // ISO88596I is the MIB identifier with IANA name ISO_8859-6-I (MIME: ISO-8859-6-I). + // + // rfc1556 + // Reference: RFC1556 + ISO88596I MIB = 82 + + // ISO128T101G2 is the MIB identifier with IANA name T.101-G2. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO128T101G2 MIB = 83 + + // ISO88598E is the MIB identifier with IANA name ISO_8859-8-E (MIME: ISO-8859-8-E). + // + // rfc1556 + // Reference: RFC1556 + ISO88598E MIB = 84 + + // ISO88598I is the MIB identifier with IANA name ISO_8859-8-I (MIME: ISO-8859-8-I). + // + // rfc1556 + // Reference: RFC1556 + ISO88598I MIB = 85 + + // ISO139CSN369103 is the MIB identifier with IANA name CSN_369103. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO139CSN369103 MIB = 86 + + // ISO141JUSIB1002 is the MIB identifier with IANA name JUS_I.B1.002. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO141JUSIB1002 MIB = 87 + + // ISO143IECP271 is the MIB identifier with IANA name IEC_P27-1. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO143IECP271 MIB = 88 + + // ISO146Serbian is the MIB identifier with IANA name JUS_I.B1.003-serb. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO146Serbian MIB = 89 + + // ISO147Macedonian is the MIB identifier with IANA name JUS_I.B1.003-mac. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO147Macedonian MIB = 90 + + // ISO150GreekCCITT is the MIB identifier with IANA name greek-ccitt. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO150GreekCCITT MIB = 91 + + // ISO151Cuba is the MIB identifier with IANA name NC_NC00-10:81. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO151Cuba MIB = 92 + + // ISO6937Add is the MIB identifier with IANA name ISO_6937-2-25. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO6937Add MIB = 93 + + // ISO153GOST1976874 is the MIB identifier with IANA name GOST_19768-74. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO153GOST1976874 MIB = 94 + + // ISO8859Supp is the MIB identifier with IANA name ISO_8859-supp. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO8859Supp MIB = 95 + + // ISO10367Box is the MIB identifier with IANA name ISO_10367-box. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO10367Box MIB = 96 + + // ISO158Lap is the MIB identifier with IANA name latin-lap. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO158Lap MIB = 97 + + // ISO159JISX02121990 is the MIB identifier with IANA name JIS_X0212-1990. + // + // ISO-IR: International Register of Escape Sequences + // Note: The current registration authority is IPSJ/ITSCJ, Japan. + // Reference: RFC1345 + ISO159JISX02121990 MIB = 98 + + // ISO646Danish is the MIB identifier with IANA name DS_2089. + // + // Danish Standard, DS 2089, February 1974 + // Reference: RFC1345 + ISO646Danish MIB = 99 + + // USDK is the MIB identifier with IANA name us-dk. + // + // Reference: RFC1345 + USDK MIB = 100 + + // DKUS is the MIB identifier with IANA name dk-us. + // + // Reference: RFC1345 + DKUS MIB = 101 + + // KSC5636 is the MIB identifier with IANA name KSC5636. + // + // Reference: RFC1345 + KSC5636 MIB = 102 + + // Unicode11UTF7 is the MIB identifier with IANA name UNICODE-1-1-UTF-7. + // + // rfc1642 + // Reference: RFC1642 + Unicode11UTF7 MIB = 103 + + // ISO2022CN is the MIB identifier with IANA name ISO-2022-CN. + // + // rfc1922 + // Reference: RFC1922 + ISO2022CN MIB = 104 + + // ISO2022CNEXT is the MIB identifier with IANA name ISO-2022-CN-EXT. + // + // rfc1922 + // Reference: RFC1922 + ISO2022CNEXT MIB = 105 + + // UTF8 is the MIB identifier with IANA name UTF-8. + // + // rfc3629 + // Reference: RFC3629 + UTF8 MIB = 106 + + // ISO885913 is the MIB identifier with IANA name ISO-8859-13. + // + // ISO See http://www.iana.org/assignments/charset-reg/ISO-8859-13 http://www.iana.org/assignments/charset-reg/ISO-8859-13 + ISO885913 MIB = 109 + + // ISO885914 is the MIB identifier with IANA name ISO-8859-14. + // + // ISO See http://www.iana.org/assignments/charset-reg/ISO-8859-14 + ISO885914 MIB = 110 + + // ISO885915 is the MIB identifier with IANA name ISO-8859-15. + // + // ISO + // Please see: http://www.iana.org/assignments/charset-reg/ISO-8859-15 + ISO885915 MIB = 111 + + // ISO885916 is the MIB identifier with IANA name ISO-8859-16. + // + // ISO + ISO885916 MIB = 112 + + // GBK is the MIB identifier with IANA name GBK. + // + // Chinese IT Standardization Technical Committee + // Please see: http://www.iana.org/assignments/charset-reg/GBK + GBK MIB = 113 + + // GB18030 is the MIB identifier with IANA name GB18030. + // + // Chinese IT Standardization Technical Committee + // Please see: http://www.iana.org/assignments/charset-reg/GB18030 + GB18030 MIB = 114 + + // OSDEBCDICDF0415 is the MIB identifier with IANA name OSD_EBCDIC_DF04_15. + // + // Fujitsu-Siemens standard mainframe EBCDIC encoding + // Please see: http://www.iana.org/assignments/charset-reg/OSD-EBCDIC-DF04-15 + OSDEBCDICDF0415 MIB = 115 + + // OSDEBCDICDF03IRV is the MIB identifier with IANA name OSD_EBCDIC_DF03_IRV. + // + // Fujitsu-Siemens standard mainframe EBCDIC encoding + // Please see: http://www.iana.org/assignments/charset-reg/OSD-EBCDIC-DF03-IRV + OSDEBCDICDF03IRV MIB = 116 + + // OSDEBCDICDF041 is the MIB identifier with IANA name OSD_EBCDIC_DF04_1. + // + // Fujitsu-Siemens standard mainframe EBCDIC encoding + // Please see: http://www.iana.org/assignments/charset-reg/OSD-EBCDIC-DF04-1 + OSDEBCDICDF041 MIB = 117 + + // ISO115481 is the MIB identifier with IANA name ISO-11548-1. + // + // See http://www.iana.org/assignments/charset-reg/ISO-11548-1 + ISO115481 MIB = 118 + + // KZ1048 is the MIB identifier with IANA name KZ-1048. + // + // See http://www.iana.org/assignments/charset-reg/KZ-1048 + KZ1048 MIB = 119 + + // Unicode is the MIB identifier with IANA name ISO-10646-UCS-2. + // + // the 2-octet Basic Multilingual Plane, aka Unicode + // this needs to specify network byte order: the standard + // does not specify (it is a 16-bit integer space) + Unicode MIB = 1000 + + // UCS4 is the MIB identifier with IANA name ISO-10646-UCS-4. + // + // the full code space. (same comment about byte order, + // these are 31-bit numbers. + UCS4 MIB = 1001 + + // UnicodeASCII is the MIB identifier with IANA name ISO-10646-UCS-Basic. + // + // ASCII subset of Unicode. Basic Latin = collection 1 + // See ISO 10646, Appendix A + UnicodeASCII MIB = 1002 + + // UnicodeLatin1 is the MIB identifier with IANA name ISO-10646-Unicode-Latin1. + // + // ISO Latin-1 subset of Unicode. Basic Latin and Latin-1 + // Supplement = collections 1 and 2. See ISO 10646, + // Appendix A. See rfc1815 . + UnicodeLatin1 MIB = 1003 + + // UnicodeJapanese is the MIB identifier with IANA name ISO-10646-J-1. + // + // ISO 10646 Japanese, see rfc1815 . + UnicodeJapanese MIB = 1004 + + // UnicodeIBM1261 is the MIB identifier with IANA name ISO-Unicode-IBM-1261. + // + // IBM Latin-2, -3, -5, Extended Presentation Set, GCSGID: 1261 + UnicodeIBM1261 MIB = 1005 + + // UnicodeIBM1268 is the MIB identifier with IANA name ISO-Unicode-IBM-1268. + // + // IBM Latin-4 Extended Presentation Set, GCSGID: 1268 + UnicodeIBM1268 MIB = 1006 + + // UnicodeIBM1276 is the MIB identifier with IANA name ISO-Unicode-IBM-1276. + // + // IBM Cyrillic Greek Extended Presentation Set, GCSGID: 1276 + UnicodeIBM1276 MIB = 1007 + + // UnicodeIBM1264 is the MIB identifier with IANA name ISO-Unicode-IBM-1264. + // + // IBM Arabic Presentation Set, GCSGID: 1264 + UnicodeIBM1264 MIB = 1008 + + // UnicodeIBM1265 is the MIB identifier with IANA name ISO-Unicode-IBM-1265. + // + // IBM Hebrew Presentation Set, GCSGID: 1265 + UnicodeIBM1265 MIB = 1009 + + // Unicode11 is the MIB identifier with IANA name UNICODE-1-1. + // + // rfc1641 + // Reference: RFC1641 + Unicode11 MIB = 1010 + + // SCSU is the MIB identifier with IANA name SCSU. + // + // SCSU See http://www.iana.org/assignments/charset-reg/SCSU + SCSU MIB = 1011 + + // UTF7 is the MIB identifier with IANA name UTF-7. + // + // rfc2152 + // Reference: RFC2152 + UTF7 MIB = 1012 + + // UTF16BE is the MIB identifier with IANA name UTF-16BE. + // + // rfc2781 + // Reference: RFC2781 + UTF16BE MIB = 1013 + + // UTF16LE is the MIB identifier with IANA name UTF-16LE. + // + // rfc2781 + // Reference: RFC2781 + UTF16LE MIB = 1014 + + // UTF16 is the MIB identifier with IANA name UTF-16. + // + // rfc2781 + // Reference: RFC2781 + UTF16 MIB = 1015 + + // CESU8 is the MIB identifier with IANA name CESU-8. + // + // http://www.unicode.org/unicode/reports/tr26 + CESU8 MIB = 1016 + + // UTF32 is the MIB identifier with IANA name UTF-32. + // + // http://www.unicode.org/unicode/reports/tr19/ + UTF32 MIB = 1017 + + // UTF32BE is the MIB identifier with IANA name UTF-32BE. + // + // http://www.unicode.org/unicode/reports/tr19/ + UTF32BE MIB = 1018 + + // UTF32LE is the MIB identifier with IANA name UTF-32LE. + // + // http://www.unicode.org/unicode/reports/tr19/ + UTF32LE MIB = 1019 + + // BOCU1 is the MIB identifier with IANA name BOCU-1. + // + // http://www.unicode.org/notes/tn6/ + BOCU1 MIB = 1020 + + // Windows30Latin1 is the MIB identifier with IANA name ISO-8859-1-Windows-3.0-Latin-1. + // + // Extended ISO 8859-1 Latin-1 for Windows 3.0. + // PCL Symbol Set id: 9U + Windows30Latin1 MIB = 2000 + + // Windows31Latin1 is the MIB identifier with IANA name ISO-8859-1-Windows-3.1-Latin-1. + // + // Extended ISO 8859-1 Latin-1 for Windows 3.1. + // PCL Symbol Set id: 19U + Windows31Latin1 MIB = 2001 + + // Windows31Latin2 is the MIB identifier with IANA name ISO-8859-2-Windows-Latin-2. + // + // Extended ISO 8859-2. Latin-2 for Windows 3.1. + // PCL Symbol Set id: 9E + Windows31Latin2 MIB = 2002 + + // Windows31Latin5 is the MIB identifier with IANA name ISO-8859-9-Windows-Latin-5. + // + // Extended ISO 8859-9. Latin-5 for Windows 3.1 + // PCL Symbol Set id: 5T + Windows31Latin5 MIB = 2003 + + // HPRoman8 is the MIB identifier with IANA name hp-roman8. + // + // LaserJet IIP Printer User's Manual, + // HP part no 33471-90901, Hewlet-Packard, June 1989. + // Reference: RFC1345 + HPRoman8 MIB = 2004 + + // AdobeStandardEncoding is the MIB identifier with IANA name Adobe-Standard-Encoding. + // + // PostScript Language Reference Manual + // PCL Symbol Set id: 10J + AdobeStandardEncoding MIB = 2005 + + // VenturaUS is the MIB identifier with IANA name Ventura-US. + // + // Ventura US. ASCII plus characters typically used in + // publishing, like pilcrow, copyright, registered, trade mark, + // section, dagger, and double dagger in the range A0 (hex) + // to FF (hex). + // PCL Symbol Set id: 14J + VenturaUS MIB = 2006 + + // VenturaInternational is the MIB identifier with IANA name Ventura-International. + // + // Ventura International. ASCII plus coded characters similar + // to Roman8. + // PCL Symbol Set id: 13J + VenturaInternational MIB = 2007 + + // DECMCS is the MIB identifier with IANA name DEC-MCS. + // + // VAX/VMS User's Manual, + // Order Number: AI-Y517A-TE, April 1986. + // Reference: RFC1345 + DECMCS MIB = 2008 + + // PC850Multilingual is the MIB identifier with IANA name IBM850. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + PC850Multilingual MIB = 2009 + + // PC8DanishNorwegian is the MIB identifier with IANA name PC8-Danish-Norwegian. + // + // PC Danish Norwegian + // 8-bit PC set for Danish Norwegian + // PCL Symbol Set id: 11U + PC8DanishNorwegian MIB = 2012 + + // PC862LatinHebrew is the MIB identifier with IANA name IBM862. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + PC862LatinHebrew MIB = 2013 + + // PC8Turkish is the MIB identifier with IANA name PC8-Turkish. + // + // PC Latin Turkish. PCL Symbol Set id: 9T + PC8Turkish MIB = 2014 + + // IBMSymbols is the MIB identifier with IANA name IBM-Symbols. + // + // Presentation Set, CPGID: 259 + IBMSymbols MIB = 2015 + + // IBMThai is the MIB identifier with IANA name IBM-Thai. + // + // Presentation Set, CPGID: 838 + IBMThai MIB = 2016 + + // HPLegal is the MIB identifier with IANA name HP-Legal. + // + // PCL 5 Comparison Guide, Hewlett-Packard, + // HP part number 5961-0510, October 1992 + // PCL Symbol Set id: 1U + HPLegal MIB = 2017 + + // HPPiFont is the MIB identifier with IANA name HP-Pi-font. + // + // PCL 5 Comparison Guide, Hewlett-Packard, + // HP part number 5961-0510, October 1992 + // PCL Symbol Set id: 15U + HPPiFont MIB = 2018 + + // HPMath8 is the MIB identifier with IANA name HP-Math8. + // + // PCL 5 Comparison Guide, Hewlett-Packard, + // HP part number 5961-0510, October 1992 + // PCL Symbol Set id: 8M + HPMath8 MIB = 2019 + + // HPPSMath is the MIB identifier with IANA name Adobe-Symbol-Encoding. + // + // PostScript Language Reference Manual + // PCL Symbol Set id: 5M + HPPSMath MIB = 2020 + + // HPDesktop is the MIB identifier with IANA name HP-DeskTop. + // + // PCL 5 Comparison Guide, Hewlett-Packard, + // HP part number 5961-0510, October 1992 + // PCL Symbol Set id: 7J + HPDesktop MIB = 2021 + + // VenturaMath is the MIB identifier with IANA name Ventura-Math. + // + // PCL 5 Comparison Guide, Hewlett-Packard, + // HP part number 5961-0510, October 1992 + // PCL Symbol Set id: 6M + VenturaMath MIB = 2022 + + // MicrosoftPublishing is the MIB identifier with IANA name Microsoft-Publishing. + // + // PCL 5 Comparison Guide, Hewlett-Packard, + // HP part number 5961-0510, October 1992 + // PCL Symbol Set id: 6J + MicrosoftPublishing MIB = 2023 + + // Windows31J is the MIB identifier with IANA name Windows-31J. + // + // Windows Japanese. A further extension of Shift_JIS + // to include NEC special characters (Row 13), NEC + // selection of IBM extensions (Rows 89 to 92), and IBM + // extensions (Rows 115 to 119). The CCS's are + // JIS X0201:1997, JIS X0208:1997, and these extensions. + // This charset can be used for the top-level media type "text", + // but it is of limited or specialized use (see rfc2278 ). + // PCL Symbol Set id: 19K + Windows31J MIB = 2024 + + // GB2312 is the MIB identifier with IANA name GB2312 (MIME: GB2312). + // + // Chinese for People's Republic of China (PRC) mixed one byte, + // two byte set: + // 20-7E = one byte ASCII + // A1-FE = two byte PRC Kanji + // See GB 2312-80 + // PCL Symbol Set Id: 18C + GB2312 MIB = 2025 + + // Big5 is the MIB identifier with IANA name Big5 (MIME: Big5). + // + // Chinese for Taiwan Multi-byte set. + // PCL Symbol Set Id: 18T + Big5 MIB = 2026 + + // Macintosh is the MIB identifier with IANA name macintosh. + // + // The Unicode Standard ver1.0, ISBN 0-201-56788-1, Oct 1991 + // Reference: RFC1345 + Macintosh MIB = 2027 + + // IBM037 is the MIB identifier with IANA name IBM037. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM037 MIB = 2028 + + // IBM038 is the MIB identifier with IANA name IBM038. + // + // IBM 3174 Character Set Ref, GA27-3831-02, March 1990 + // Reference: RFC1345 + IBM038 MIB = 2029 + + // IBM273 is the MIB identifier with IANA name IBM273. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM273 MIB = 2030 + + // IBM274 is the MIB identifier with IANA name IBM274. + // + // IBM 3174 Character Set Ref, GA27-3831-02, March 1990 + // Reference: RFC1345 + IBM274 MIB = 2031 + + // IBM275 is the MIB identifier with IANA name IBM275. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM275 MIB = 2032 + + // IBM277 is the MIB identifier with IANA name IBM277. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM277 MIB = 2033 + + // IBM278 is the MIB identifier with IANA name IBM278. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM278 MIB = 2034 + + // IBM280 is the MIB identifier with IANA name IBM280. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM280 MIB = 2035 + + // IBM281 is the MIB identifier with IANA name IBM281. + // + // IBM 3174 Character Set Ref, GA27-3831-02, March 1990 + // Reference: RFC1345 + IBM281 MIB = 2036 + + // IBM284 is the MIB identifier with IANA name IBM284. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM284 MIB = 2037 + + // IBM285 is the MIB identifier with IANA name IBM285. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM285 MIB = 2038 + + // IBM290 is the MIB identifier with IANA name IBM290. + // + // IBM 3174 Character Set Ref, GA27-3831-02, March 1990 + // Reference: RFC1345 + IBM290 MIB = 2039 + + // IBM297 is the MIB identifier with IANA name IBM297. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM297 MIB = 2040 + + // IBM420 is the MIB identifier with IANA name IBM420. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990, + // IBM NLS RM p 11-11 + // Reference: RFC1345 + IBM420 MIB = 2041 + + // IBM423 is the MIB identifier with IANA name IBM423. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM423 MIB = 2042 + + // IBM424 is the MIB identifier with IANA name IBM424. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM424 MIB = 2043 + + // PC8CodePage437 is the MIB identifier with IANA name IBM437. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + PC8CodePage437 MIB = 2011 + + // IBM500 is the MIB identifier with IANA name IBM500. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM500 MIB = 2044 + + // IBM851 is the MIB identifier with IANA name IBM851. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM851 MIB = 2045 + + // PCp852 is the MIB identifier with IANA name IBM852. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + PCp852 MIB = 2010 + + // IBM855 is the MIB identifier with IANA name IBM855. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM855 MIB = 2046 + + // IBM857 is the MIB identifier with IANA name IBM857. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM857 MIB = 2047 + + // IBM860 is the MIB identifier with IANA name IBM860. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM860 MIB = 2048 + + // IBM861 is the MIB identifier with IANA name IBM861. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM861 MIB = 2049 + + // IBM863 is the MIB identifier with IANA name IBM863. + // + // IBM Keyboard layouts and code pages, PN 07G4586 June 1991 + // Reference: RFC1345 + IBM863 MIB = 2050 + + // IBM864 is the MIB identifier with IANA name IBM864. + // + // IBM Keyboard layouts and code pages, PN 07G4586 June 1991 + // Reference: RFC1345 + IBM864 MIB = 2051 + + // IBM865 is the MIB identifier with IANA name IBM865. + // + // IBM DOS 3.3 Ref (Abridged), 94X9575 (Feb 1987) + // Reference: RFC1345 + IBM865 MIB = 2052 + + // IBM868 is the MIB identifier with IANA name IBM868. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM868 MIB = 2053 + + // IBM869 is the MIB identifier with IANA name IBM869. + // + // IBM Keyboard layouts and code pages, PN 07G4586 June 1991 + // Reference: RFC1345 + IBM869 MIB = 2054 + + // IBM870 is the MIB identifier with IANA name IBM870. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM870 MIB = 2055 + + // IBM871 is the MIB identifier with IANA name IBM871. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM871 MIB = 2056 + + // IBM880 is the MIB identifier with IANA name IBM880. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM880 MIB = 2057 + + // IBM891 is the MIB identifier with IANA name IBM891. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM891 MIB = 2058 + + // IBM903 is the MIB identifier with IANA name IBM903. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM903 MIB = 2059 + + // IBBM904 is the MIB identifier with IANA name IBM904. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBBM904 MIB = 2060 + + // IBM905 is the MIB identifier with IANA name IBM905. + // + // IBM 3174 Character Set Ref, GA27-3831-02, March 1990 + // Reference: RFC1345 + IBM905 MIB = 2061 + + // IBM918 is the MIB identifier with IANA name IBM918. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM918 MIB = 2062 + + // IBM1026 is the MIB identifier with IANA name IBM1026. + // + // IBM NLS RM Vol2 SE09-8002-01, March 1990 + // Reference: RFC1345 + IBM1026 MIB = 2063 + + // IBMEBCDICATDE is the MIB identifier with IANA name EBCDIC-AT-DE. + // + // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987 + // Reference: RFC1345 + IBMEBCDICATDE MIB = 2064 + + // EBCDICATDEA is the MIB identifier with IANA name EBCDIC-AT-DE-A. + // + // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987 + // Reference: RFC1345 + EBCDICATDEA MIB = 2065 + + // EBCDICCAFR is the MIB identifier with IANA name EBCDIC-CA-FR. + // + // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987 + // Reference: RFC1345 + EBCDICCAFR MIB = 2066 + + // EBCDICDKNO is the MIB identifier with IANA name EBCDIC-DK-NO. + // + // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987 + // Reference: RFC1345 + EBCDICDKNO MIB = 2067 + + // EBCDICDKNOA is the MIB identifier with IANA name EBCDIC-DK-NO-A. + // + // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987 + // Reference: RFC1345 + EBCDICDKNOA MIB = 2068 + + // EBCDICFISE is the MIB identifier with IANA name EBCDIC-FI-SE. + // + // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987 + // Reference: RFC1345 + EBCDICFISE MIB = 2069 + + // EBCDICFISEA is the MIB identifier with IANA name EBCDIC-FI-SE-A. + // + // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987 + // Reference: RFC1345 + EBCDICFISEA MIB = 2070 + + // EBCDICFR is the MIB identifier with IANA name EBCDIC-FR. + // + // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987 + // Reference: RFC1345 + EBCDICFR MIB = 2071 + + // EBCDICIT is the MIB identifier with IANA name EBCDIC-IT. + // + // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987 + // Reference: RFC1345 + EBCDICIT MIB = 2072 + + // EBCDICPT is the MIB identifier with IANA name EBCDIC-PT. + // + // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987 + // Reference: RFC1345 + EBCDICPT MIB = 2073 + + // EBCDICES is the MIB identifier with IANA name EBCDIC-ES. + // + // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987 + // Reference: RFC1345 + EBCDICES MIB = 2074 + + // EBCDICESA is the MIB identifier with IANA name EBCDIC-ES-A. + // + // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987 + // Reference: RFC1345 + EBCDICESA MIB = 2075 + + // EBCDICESS is the MIB identifier with IANA name EBCDIC-ES-S. + // + // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987 + // Reference: RFC1345 + EBCDICESS MIB = 2076 + + // EBCDICUK is the MIB identifier with IANA name EBCDIC-UK. + // + // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987 + // Reference: RFC1345 + EBCDICUK MIB = 2077 + + // EBCDICUS is the MIB identifier with IANA name EBCDIC-US. + // + // IBM 3270 Char Set Ref Ch 10, GA27-2837-9, April 1987 + // Reference: RFC1345 + EBCDICUS MIB = 2078 + + // Unknown8BiT is the MIB identifier with IANA name UNKNOWN-8BIT. + // + // Reference: RFC1428 + Unknown8BiT MIB = 2079 + + // Mnemonic is the MIB identifier with IANA name MNEMONIC. + // + // rfc1345 , also known as "mnemonic+ascii+38" + // Reference: RFC1345 + Mnemonic MIB = 2080 + + // Mnem is the MIB identifier with IANA name MNEM. + // + // rfc1345 , also known as "mnemonic+ascii+8200" + // Reference: RFC1345 + Mnem MIB = 2081 + + // VISCII is the MIB identifier with IANA name VISCII. + // + // rfc1456 + // Reference: RFC1456 + VISCII MIB = 2082 + + // VIQR is the MIB identifier with IANA name VIQR. + // + // rfc1456 + // Reference: RFC1456 + VIQR MIB = 2083 + + // KOI8R is the MIB identifier with IANA name KOI8-R (MIME: KOI8-R). + // + // rfc1489 , based on GOST-19768-74, ISO-6937/8, + // INIS-Cyrillic, ISO-5427. + // Reference: RFC1489 + KOI8R MIB = 2084 + + // HZGB2312 is the MIB identifier with IANA name HZ-GB-2312. + // + // rfc1842 , rfc1843 rfc1843 rfc1842 + HZGB2312 MIB = 2085 + + // IBM866 is the MIB identifier with IANA name IBM866. + // + // IBM NLDG Volume 2 (SE09-8002-03) August 1994 + IBM866 MIB = 2086 + + // PC775Baltic is the MIB identifier with IANA name IBM775. + // + // HP PCL 5 Comparison Guide (P/N 5021-0329) pp B-13, 1996 + PC775Baltic MIB = 2087 + + // KOI8U is the MIB identifier with IANA name KOI8-U. + // + // rfc2319 + // Reference: RFC2319 + KOI8U MIB = 2088 + + // IBM00858 is the MIB identifier with IANA name IBM00858. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM00858 + IBM00858 MIB = 2089 + + // IBM00924 is the MIB identifier with IANA name IBM00924. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM00924 + IBM00924 MIB = 2090 + + // IBM01140 is the MIB identifier with IANA name IBM01140. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM01140 + IBM01140 MIB = 2091 + + // IBM01141 is the MIB identifier with IANA name IBM01141. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM01141 + IBM01141 MIB = 2092 + + // IBM01142 is the MIB identifier with IANA name IBM01142. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM01142 + IBM01142 MIB = 2093 + + // IBM01143 is the MIB identifier with IANA name IBM01143. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM01143 + IBM01143 MIB = 2094 + + // IBM01144 is the MIB identifier with IANA name IBM01144. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM01144 + IBM01144 MIB = 2095 + + // IBM01145 is the MIB identifier with IANA name IBM01145. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM01145 + IBM01145 MIB = 2096 + + // IBM01146 is the MIB identifier with IANA name IBM01146. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM01146 + IBM01146 MIB = 2097 + + // IBM01147 is the MIB identifier with IANA name IBM01147. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM01147 + IBM01147 MIB = 2098 + + // IBM01148 is the MIB identifier with IANA name IBM01148. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM01148 + IBM01148 MIB = 2099 + + // IBM01149 is the MIB identifier with IANA name IBM01149. + // + // IBM See http://www.iana.org/assignments/charset-reg/IBM01149 + IBM01149 MIB = 2100 + + // Big5HKSCS is the MIB identifier with IANA name Big5-HKSCS. + // + // See http://www.iana.org/assignments/charset-reg/Big5-HKSCS + Big5HKSCS MIB = 2101 + + // IBM1047 is the MIB identifier with IANA name IBM1047. + // + // IBM1047 (EBCDIC Latin 1/Open Systems) http://www-1.ibm.com/servers/eserver/iseries/software/globalization/pdf/cp01047z.pdf + IBM1047 MIB = 2102 + + // PTCP154 is the MIB identifier with IANA name PTCP154. + // + // See http://www.iana.org/assignments/charset-reg/PTCP154 + PTCP154 MIB = 2103 + + // Amiga1251 is the MIB identifier with IANA name Amiga-1251. + // + // See http://www.amiga.ultranet.ru/Amiga-1251.html + Amiga1251 MIB = 2104 + + // KOI7switched is the MIB identifier with IANA name KOI7-switched. + // + // See http://www.iana.org/assignments/charset-reg/KOI7-switched + KOI7switched MIB = 2105 + + // BRF is the MIB identifier with IANA name BRF. + // + // See http://www.iana.org/assignments/charset-reg/BRF + BRF MIB = 2106 + + // TSCII is the MIB identifier with IANA name TSCII. + // + // See http://www.iana.org/assignments/charset-reg/TSCII + TSCII MIB = 2107 + + // CP51932 is the MIB identifier with IANA name CP51932. + // + // See http://www.iana.org/assignments/charset-reg/CP51932 + CP51932 MIB = 2108 + + // Windows874 is the MIB identifier with IANA name windows-874. + // + // See http://www.iana.org/assignments/charset-reg/windows-874 + Windows874 MIB = 2109 + + // Windows1250 is the MIB identifier with IANA name windows-1250. + // + // Microsoft http://www.iana.org/assignments/charset-reg/windows-1250 + Windows1250 MIB = 2250 + + // Windows1251 is the MIB identifier with IANA name windows-1251. + // + // Microsoft http://www.iana.org/assignments/charset-reg/windows-1251 + Windows1251 MIB = 2251 + + // Windows1252 is the MIB identifier with IANA name windows-1252. + // + // Microsoft http://www.iana.org/assignments/charset-reg/windows-1252 + Windows1252 MIB = 2252 + + // Windows1253 is the MIB identifier with IANA name windows-1253. + // + // Microsoft http://www.iana.org/assignments/charset-reg/windows-1253 + Windows1253 MIB = 2253 + + // Windows1254 is the MIB identifier with IANA name windows-1254. + // + // Microsoft http://www.iana.org/assignments/charset-reg/windows-1254 + Windows1254 MIB = 2254 + + // Windows1255 is the MIB identifier with IANA name windows-1255. + // + // Microsoft http://www.iana.org/assignments/charset-reg/windows-1255 + Windows1255 MIB = 2255 + + // Windows1256 is the MIB identifier with IANA name windows-1256. + // + // Microsoft http://www.iana.org/assignments/charset-reg/windows-1256 + Windows1256 MIB = 2256 + + // Windows1257 is the MIB identifier with IANA name windows-1257. + // + // Microsoft http://www.iana.org/assignments/charset-reg/windows-1257 + Windows1257 MIB = 2257 + + // Windows1258 is the MIB identifier with IANA name windows-1258. + // + // Microsoft http://www.iana.org/assignments/charset-reg/windows-1258 + Windows1258 MIB = 2258 + + // TIS620 is the MIB identifier with IANA name TIS-620. + // + // Thai Industrial Standards Institute (TISI) + TIS620 MIB = 2259 + + // CP50220 is the MIB identifier with IANA name CP50220. + // + // See http://www.iana.org/assignments/charset-reg/CP50220 + CP50220 MIB = 2260 +) diff --git a/vendor/golang.org/x/text/encoding/internal/internal.go b/vendor/golang.org/x/text/encoding/internal/internal.go new file mode 100644 index 000000000..75a5fd165 --- /dev/null +++ b/vendor/golang.org/x/text/encoding/internal/internal.go @@ -0,0 +1,75 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package internal contains code that is shared among encoding implementations. +package internal + +import ( + "golang.org/x/text/encoding" + "golang.org/x/text/encoding/internal/identifier" + "golang.org/x/text/transform" +) + +// Encoding is an implementation of the Encoding interface that adds the String +// and ID methods to an existing encoding. +type Encoding struct { + encoding.Encoding + Name string + MIB identifier.MIB +} + +// _ verifies that Encoding implements identifier.Interface. +var _ identifier.Interface = (*Encoding)(nil) + +func (e *Encoding) String() string { + return e.Name +} + +func (e *Encoding) ID() (mib identifier.MIB, other string) { + return e.MIB, "" +} + +// SimpleEncoding is an Encoding that combines two Transformers. +type SimpleEncoding struct { + Decoder transform.Transformer + Encoder transform.Transformer +} + +func (e *SimpleEncoding) NewDecoder() *encoding.Decoder { + return &encoding.Decoder{Transformer: e.Decoder} +} + +func (e *SimpleEncoding) NewEncoder() *encoding.Encoder { + return &encoding.Encoder{Transformer: e.Encoder} +} + +// FuncEncoding is an Encoding that combines two functions returning a new +// Transformer. +type FuncEncoding struct { + Decoder func() transform.Transformer + Encoder func() transform.Transformer +} + +func (e FuncEncoding) NewDecoder() *encoding.Decoder { + return &encoding.Decoder{Transformer: e.Decoder()} +} + +func (e FuncEncoding) NewEncoder() *encoding.Encoder { + return &encoding.Encoder{Transformer: e.Encoder()} +} + +// A RepertoireError indicates a rune is not in the repertoire of a destination +// encoding. It is associated with an encoding-specific suggested replacement +// byte. +type RepertoireError byte + +// Error implements the error interrface. +func (r RepertoireError) Error() string { + return "encoding: rune not supported by encoding." +} + +// Replacement returns the replacement string associated with this error. +func (r RepertoireError) Replacement() byte { return byte(r) } + +var ErrASCIIReplacement = RepertoireError(encoding.ASCIISub) diff --git a/vendor/golang.org/x/text/encoding/unicode/override.go b/vendor/golang.org/x/text/encoding/unicode/override.go new file mode 100644 index 000000000..35d62fcc9 --- /dev/null +++ b/vendor/golang.org/x/text/encoding/unicode/override.go @@ -0,0 +1,82 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package unicode + +import ( + "golang.org/x/text/transform" +) + +// BOMOverride returns a new decoder transformer that is identical to fallback, +// except that the presence of a Byte Order Mark at the start of the input +// causes it to switch to the corresponding Unicode decoding. It will only +// consider BOMs for UTF-8, UTF-16BE, and UTF-16LE. +// +// This differs from using ExpectBOM by allowing a BOM to switch to UTF-8, not +// just UTF-16 variants, and allowing falling back to any encoding scheme. +// +// This technique is recommended by the W3C for use in HTML 5: "For +// compatibility with deployed content, the byte order mark (also known as BOM) +// is considered more authoritative than anything else." +// http://www.w3.org/TR/encoding/#specification-hooks +// +// Using BOMOverride is mostly intended for use cases where the first characters +// of a fallback encoding are known to not be a BOM, for example, for valid HTML +// and most encodings. +func BOMOverride(fallback transform.Transformer) transform.Transformer { + // TODO: possibly allow a variadic argument of unicode encodings to allow + // specifying details of which fallbacks are supported as well as + // specifying the details of the implementations. This would also allow for + // support for UTF-32, which should not be supported by default. + return &bomOverride{fallback: fallback} +} + +type bomOverride struct { + fallback transform.Transformer + current transform.Transformer +} + +func (d *bomOverride) Reset() { + d.current = nil + d.fallback.Reset() +} + +var ( + // TODO: we could use decode functions here, instead of allocating a new + // decoder on every NewDecoder as IgnoreBOM decoders can be stateless. + utf16le = UTF16(LittleEndian, IgnoreBOM) + utf16be = UTF16(BigEndian, IgnoreBOM) +) + +const utf8BOM = "\ufeff" + +func (d *bomOverride) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + if d.current != nil { + return d.current.Transform(dst, src, atEOF) + } + if len(src) < 3 && !atEOF { + return 0, 0, transform.ErrShortSrc + } + d.current = d.fallback + bomSize := 0 + if len(src) >= 2 { + if src[0] == 0xFF && src[1] == 0xFE { + d.current = utf16le.NewDecoder() + bomSize = 2 + } else if src[0] == 0xFE && src[1] == 0xFF { + d.current = utf16be.NewDecoder() + bomSize = 2 + } else if len(src) >= 3 && + src[0] == utf8BOM[0] && + src[1] == utf8BOM[1] && + src[2] == utf8BOM[2] { + d.current = transform.Nop + bomSize = 3 + } + } + if bomSize < len(src) { + nDst, nSrc, err = d.current.Transform(dst, src[bomSize:], atEOF) + } + return nDst, nSrc + bomSize, err +} diff --git a/vendor/golang.org/x/text/encoding/unicode/unicode.go b/vendor/golang.org/x/text/encoding/unicode/unicode.go new file mode 100644 index 000000000..579cadfb1 --- /dev/null +++ b/vendor/golang.org/x/text/encoding/unicode/unicode.go @@ -0,0 +1,434 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package unicode provides Unicode encodings such as UTF-16. +package unicode // import "golang.org/x/text/encoding/unicode" + +import ( + "errors" + "unicode/utf16" + "unicode/utf8" + + "golang.org/x/text/encoding" + "golang.org/x/text/encoding/internal" + "golang.org/x/text/encoding/internal/identifier" + "golang.org/x/text/internal/utf8internal" + "golang.org/x/text/runes" + "golang.org/x/text/transform" +) + +// TODO: I think the Transformers really should return errors on unmatched +// surrogate pairs and odd numbers of bytes. This is not required by RFC 2781, +// which leaves it open, but is suggested by WhatWG. It will allow for all error +// modes as defined by WhatWG: fatal, HTML and Replacement. This would require +// the introduction of some kind of error type for conveying the erroneous code +// point. + +// UTF8 is the UTF-8 encoding. +var UTF8 encoding.Encoding = utf8enc + +var utf8enc = &internal.Encoding{ + &internal.SimpleEncoding{utf8Decoder{}, runes.ReplaceIllFormed()}, + "UTF-8", + identifier.UTF8, +} + +type utf8Decoder struct{ transform.NopResetter } + +func (utf8Decoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + var pSrc int // point from which to start copy in src + var accept utf8internal.AcceptRange + + // The decoder can only make the input larger, not smaller. + n := len(src) + if len(dst) < n { + err = transform.ErrShortDst + n = len(dst) + atEOF = false + } + for nSrc < n { + c := src[nSrc] + if c < utf8.RuneSelf { + nSrc++ + continue + } + first := utf8internal.First[c] + size := int(first & utf8internal.SizeMask) + if first == utf8internal.FirstInvalid { + goto handleInvalid // invalid starter byte + } + accept = utf8internal.AcceptRanges[first>>utf8internal.AcceptShift] + if nSrc+size > n { + if !atEOF { + // We may stop earlier than necessary here if the short sequence + // has invalid bytes. Not checking for this simplifies the code + // and may avoid duplicate computations in certain conditions. + if err == nil { + err = transform.ErrShortSrc + } + break + } + // Determine the maximal subpart of an ill-formed subsequence. + switch { + case nSrc+1 >= n || src[nSrc+1] < accept.Lo || accept.Hi < src[nSrc+1]: + size = 1 + case nSrc+2 >= n || src[nSrc+2] < utf8internal.LoCB || utf8internal.HiCB < src[nSrc+2]: + size = 2 + default: + size = 3 // As we are short, the maximum is 3. + } + goto handleInvalid + } + if c = src[nSrc+1]; c < accept.Lo || accept.Hi < c { + size = 1 + goto handleInvalid // invalid continuation byte + } else if size == 2 { + } else if c = src[nSrc+2]; c < utf8internal.LoCB || utf8internal.HiCB < c { + size = 2 + goto handleInvalid // invalid continuation byte + } else if size == 3 { + } else if c = src[nSrc+3]; c < utf8internal.LoCB || utf8internal.HiCB < c { + size = 3 + goto handleInvalid // invalid continuation byte + } + nSrc += size + continue + + handleInvalid: + // Copy the scanned input so far. + nDst += copy(dst[nDst:], src[pSrc:nSrc]) + + // Append RuneError to the destination. + const runeError = "\ufffd" + if nDst+len(runeError) > len(dst) { + return nDst, nSrc, transform.ErrShortDst + } + nDst += copy(dst[nDst:], runeError) + + // Skip the maximal subpart of an ill-formed subsequence according to + // the W3C standard way instead of the Go way. This Transform is + // probably the only place in the text repo where it is warranted. + nSrc += size + pSrc = nSrc + + // Recompute the maximum source length. + if sz := len(dst) - nDst; sz < len(src)-nSrc { + err = transform.ErrShortDst + n = nSrc + sz + atEOF = false + } + } + return nDst + copy(dst[nDst:], src[pSrc:nSrc]), nSrc, err +} + +// UTF16 returns a UTF-16 Encoding for the given default endianness and byte +// order mark (BOM) policy. +// +// When decoding from UTF-16 to UTF-8, if the BOMPolicy is IgnoreBOM then +// neither BOMs U+FEFF nor noncharacters U+FFFE in the input stream will affect +// the endianness used for decoding, and will instead be output as their +// standard UTF-8 encodings: "\xef\xbb\xbf" and "\xef\xbf\xbe". If the BOMPolicy +// is UseBOM or ExpectBOM a staring BOM is not written to the UTF-8 output. +// Instead, it overrides the default endianness e for the remainder of the +// transformation. Any subsequent BOMs U+FEFF or noncharacters U+FFFE will not +// affect the endianness used, and will instead be output as their standard +// UTF-8 encodings. For UseBOM, if there is no starting BOM, it will proceed +// with the default Endianness. For ExpectBOM, in that case, the transformation +// will return early with an ErrMissingBOM error. +// +// When encoding from UTF-8 to UTF-16, a BOM will be inserted at the start of +// the output if the BOMPolicy is UseBOM or ExpectBOM. Otherwise, a BOM will not +// be inserted. The UTF-8 input does not need to contain a BOM. +// +// There is no concept of a 'native' endianness. If the UTF-16 data is produced +// and consumed in a greater context that implies a certain endianness, use +// IgnoreBOM. Otherwise, use ExpectBOM and always produce and consume a BOM. +// +// In the language of http://www.unicode.org/faq/utf_bom.html#bom10, IgnoreBOM +// corresponds to "Where the precise type of the data stream is known... the +// BOM should not be used" and ExpectBOM corresponds to "A particular +// protocol... may require use of the BOM". +func UTF16(e Endianness, b BOMPolicy) encoding.Encoding { + return utf16Encoding{config{e, b}, mibValue[e][b&bomMask]} +} + +// mibValue maps Endianness and BOMPolicy settings to MIB constants. Note that +// some configurations map to the same MIB identifier. RFC 2781 has requirements +// and recommendations. Some of the "configurations" are merely recommendations, +// so multiple configurations could match. +var mibValue = map[Endianness][numBOMValues]identifier.MIB{ + BigEndian: [numBOMValues]identifier.MIB{ + IgnoreBOM: identifier.UTF16BE, + UseBOM: identifier.UTF16, // BigEnding default is preferred by RFC 2781. + // TODO: acceptBOM | strictBOM would map to UTF16BE as well. + }, + LittleEndian: [numBOMValues]identifier.MIB{ + IgnoreBOM: identifier.UTF16LE, + UseBOM: identifier.UTF16, // LittleEndian default is allowed and preferred on Windows. + // TODO: acceptBOM | strictBOM would map to UTF16LE as well. + }, + // ExpectBOM is not widely used and has no valid MIB identifier. +} + +// All lists a configuration for each IANA-defined UTF-16 variant. +var All = []encoding.Encoding{ + UTF8, + UTF16(BigEndian, UseBOM), + UTF16(BigEndian, IgnoreBOM), + UTF16(LittleEndian, IgnoreBOM), +} + +// BOMPolicy is a UTF-16 encoding's byte order mark policy. +type BOMPolicy uint8 + +const ( + writeBOM BOMPolicy = 0x01 + acceptBOM BOMPolicy = 0x02 + requireBOM BOMPolicy = 0x04 + bomMask BOMPolicy = 0x07 + + // HACK: numBOMValues == 8 triggers a bug in the 1.4 compiler (cannot have a + // map of an array of length 8 of a type that is also used as a key or value + // in another map). See golang.org/issue/11354. + // TODO: consider changing this value back to 8 if the use of 1.4.* has + // been minimized. + numBOMValues = 8 + 1 + + // IgnoreBOM means to ignore any byte order marks. + IgnoreBOM BOMPolicy = 0 + // Common and RFC 2781-compliant interpretation for UTF-16BE/LE. + + // UseBOM means that the UTF-16 form may start with a byte order mark, which + // will be used to override the default encoding. + UseBOM BOMPolicy = writeBOM | acceptBOM + // Common and RFC 2781-compliant interpretation for UTF-16. + + // ExpectBOM means that the UTF-16 form must start with a byte order mark, + // which will be used to override the default encoding. + ExpectBOM BOMPolicy = writeBOM | acceptBOM | requireBOM + // Used in Java as Unicode (not to be confused with Java's UTF-16) and + // ICU's UTF-16,version=1. Not compliant with RFC 2781. + + // TODO (maybe): strictBOM: BOM must match Endianness. This would allow: + // - UTF-16(B|L)E,version=1: writeBOM | acceptBOM | requireBOM | strictBOM + // (UnicodeBig and UnicodeLittle in Java) + // - RFC 2781-compliant, but less common interpretation for UTF-16(B|L)E: + // acceptBOM | strictBOM (e.g. assigned to CheckBOM). + // This addition would be consistent with supporting ExpectBOM. +) + +// Endianness is a UTF-16 encoding's default endianness. +type Endianness bool + +const ( + // BigEndian is UTF-16BE. + BigEndian Endianness = false + // LittleEndian is UTF-16LE. + LittleEndian Endianness = true +) + +// ErrMissingBOM means that decoding UTF-16 input with ExpectBOM did not find a +// starting byte order mark. +var ErrMissingBOM = errors.New("encoding: missing byte order mark") + +type utf16Encoding struct { + config + mib identifier.MIB +} + +type config struct { + endianness Endianness + bomPolicy BOMPolicy +} + +func (u utf16Encoding) NewDecoder() *encoding.Decoder { + return &encoding.Decoder{Transformer: &utf16Decoder{ + initial: u.config, + current: u.config, + }} +} + +func (u utf16Encoding) NewEncoder() *encoding.Encoder { + return &encoding.Encoder{Transformer: &utf16Encoder{ + endianness: u.endianness, + initialBOMPolicy: u.bomPolicy, + currentBOMPolicy: u.bomPolicy, + }} +} + +func (u utf16Encoding) ID() (mib identifier.MIB, other string) { + return u.mib, "" +} + +func (u utf16Encoding) String() string { + e, b := "B", "" + if u.endianness == LittleEndian { + e = "L" + } + switch u.bomPolicy { + case ExpectBOM: + b = "Expect" + case UseBOM: + b = "Use" + case IgnoreBOM: + b = "Ignore" + } + return "UTF-16" + e + "E (" + b + " BOM)" +} + +type utf16Decoder struct { + initial config + current config +} + +func (u *utf16Decoder) Reset() { + u.current = u.initial +} + +func (u *utf16Decoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + if len(src) == 0 { + if atEOF && u.current.bomPolicy&requireBOM != 0 { + return 0, 0, ErrMissingBOM + } + return 0, 0, nil + } + if u.current.bomPolicy&acceptBOM != 0 { + if len(src) < 2 { + return 0, 0, transform.ErrShortSrc + } + switch { + case src[0] == 0xfe && src[1] == 0xff: + u.current.endianness = BigEndian + nSrc = 2 + case src[0] == 0xff && src[1] == 0xfe: + u.current.endianness = LittleEndian + nSrc = 2 + default: + if u.current.bomPolicy&requireBOM != 0 { + return 0, 0, ErrMissingBOM + } + } + u.current.bomPolicy = IgnoreBOM + } + + var r rune + var dSize, sSize int + for nSrc < len(src) { + if nSrc+1 < len(src) { + x := uint16(src[nSrc+0])<<8 | uint16(src[nSrc+1]) + if u.current.endianness == LittleEndian { + x = x>>8 | x<<8 + } + r, sSize = rune(x), 2 + if utf16.IsSurrogate(r) { + if nSrc+3 < len(src) { + x = uint16(src[nSrc+2])<<8 | uint16(src[nSrc+3]) + if u.current.endianness == LittleEndian { + x = x>>8 | x<<8 + } + // Save for next iteration if it is not a high surrogate. + if isHighSurrogate(rune(x)) { + r, sSize = utf16.DecodeRune(r, rune(x)), 4 + } + } else if !atEOF { + err = transform.ErrShortSrc + break + } + } + if dSize = utf8.RuneLen(r); dSize < 0 { + r, dSize = utf8.RuneError, 3 + } + } else if atEOF { + // Single trailing byte. + r, dSize, sSize = utf8.RuneError, 3, 1 + } else { + err = transform.ErrShortSrc + break + } + if nDst+dSize > len(dst) { + err = transform.ErrShortDst + break + } + nDst += utf8.EncodeRune(dst[nDst:], r) + nSrc += sSize + } + return nDst, nSrc, err +} + +func isHighSurrogate(r rune) bool { + return 0xDC00 <= r && r <= 0xDFFF +} + +type utf16Encoder struct { + endianness Endianness + initialBOMPolicy BOMPolicy + currentBOMPolicy BOMPolicy +} + +func (u *utf16Encoder) Reset() { + u.currentBOMPolicy = u.initialBOMPolicy +} + +func (u *utf16Encoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + if u.currentBOMPolicy&writeBOM != 0 { + if len(dst) < 2 { + return 0, 0, transform.ErrShortDst + } + dst[0], dst[1] = 0xfe, 0xff + u.currentBOMPolicy = IgnoreBOM + nDst = 2 + } + + r, size := rune(0), 0 + for nSrc < len(src) { + r = rune(src[nSrc]) + + // Decode a 1-byte rune. + if r < utf8.RuneSelf { + size = 1 + + } else { + // Decode a multi-byte rune. + r, size = utf8.DecodeRune(src[nSrc:]) + if size == 1 { + // All valid runes of size 1 (those below utf8.RuneSelf) were + // handled above. We have invalid UTF-8 or we haven't seen the + // full character yet. + if !atEOF && !utf8.FullRune(src[nSrc:]) { + err = transform.ErrShortSrc + break + } + } + } + + if r <= 0xffff { + if nDst+2 > len(dst) { + err = transform.ErrShortDst + break + } + dst[nDst+0] = uint8(r >> 8) + dst[nDst+1] = uint8(r) + nDst += 2 + } else { + if nDst+4 > len(dst) { + err = transform.ErrShortDst + break + } + r1, r2 := utf16.EncodeRune(r) + dst[nDst+0] = uint8(r1 >> 8) + dst[nDst+1] = uint8(r1) + dst[nDst+2] = uint8(r2 >> 8) + dst[nDst+3] = uint8(r2) + nDst += 4 + } + nSrc += size + } + + if u.endianness == LittleEndian { + for i := 0; i < nDst; i += 2 { + dst[i], dst[i+1] = dst[i+1], dst[i] + } + } + return nDst, nSrc, err +} diff --git a/vendor/golang.org/x/text/internal/gen/code.go b/vendor/golang.org/x/text/internal/gen/code.go new file mode 100644 index 000000000..2202c9f64 --- /dev/null +++ b/vendor/golang.org/x/text/internal/gen/code.go @@ -0,0 +1,339 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gen + +import ( + "bytes" + "encoding/gob" + "fmt" + "hash" + "hash/fnv" + "io" + "log" + "os" + "reflect" + "strings" + "unicode" + "unicode/utf8" +) + +// This file contains utilities for generating code. + +// TODO: other write methods like: +// - slices, maps, types, etc. + +// CodeWriter is a utility for writing structured code. It computes the content +// hash and size of written content. It ensures there are newlines between +// written code blocks. +type CodeWriter struct { + buf bytes.Buffer + Size int + Hash hash.Hash32 // content hash + gob *gob.Encoder + // For comments we skip the usual one-line separator if they are followed by + // a code block. + skipSep bool +} + +func (w *CodeWriter) Write(p []byte) (n int, err error) { + return w.buf.Write(p) +} + +// NewCodeWriter returns a new CodeWriter. +func NewCodeWriter() *CodeWriter { + h := fnv.New32() + return &CodeWriter{Hash: h, gob: gob.NewEncoder(h)} +} + +// WriteGoFile appends the buffer with the total size of all created structures +// and writes it as a Go file to the the given file with the given package name. +func (w *CodeWriter) WriteGoFile(filename, pkg string) { + f, err := os.Create(filename) + if err != nil { + log.Fatalf("Could not create file %s: %v", filename, err) + } + defer f.Close() + if _, err = w.WriteGo(f, pkg); err != nil { + log.Fatalf("Error writing file %s: %v", filename, err) + } +} + +// WriteGo appends the buffer with the total size of all created structures and +// writes it as a Go file to the the given writer with the given package name. +func (w *CodeWriter) WriteGo(out io.Writer, pkg string) (n int, err error) { + sz := w.Size + w.WriteComment("Total table size %d bytes (%dKiB); checksum: %X\n", sz, sz/1024, w.Hash.Sum32()) + defer w.buf.Reset() + return WriteGo(out, pkg, w.buf.Bytes()) +} + +func (w *CodeWriter) printf(f string, x ...interface{}) { + fmt.Fprintf(w, f, x...) +} + +func (w *CodeWriter) insertSep() { + if w.skipSep { + w.skipSep = false + return + } + // Use at least two newlines to ensure a blank space between the previous + // block. WriteGoFile will remove extraneous newlines. + w.printf("\n\n") +} + +// WriteComment writes a comment block. All line starts are prefixed with "//". +// Initial empty lines are gobbled. The indentation for the first line is +// stripped from consecutive lines. +func (w *CodeWriter) WriteComment(comment string, args ...interface{}) { + s := fmt.Sprintf(comment, args...) + s = strings.Trim(s, "\n") + + // Use at least two newlines to ensure a blank space between the previous + // block. WriteGoFile will remove extraneous newlines. + w.printf("\n\n// ") + w.skipSep = true + + // strip first indent level. + sep := "\n" + for ; len(s) > 0 && (s[0] == '\t' || s[0] == ' '); s = s[1:] { + sep += s[:1] + } + + strings.NewReplacer(sep, "\n// ", "\n", "\n// ").WriteString(w, s) + + w.printf("\n") +} + +func (w *CodeWriter) writeSizeInfo(size int) { + w.printf("// Size: %d bytes\n", size) +} + +// WriteConst writes a constant of the given name and value. +func (w *CodeWriter) WriteConst(name string, x interface{}) { + w.insertSep() + v := reflect.ValueOf(x) + + switch v.Type().Kind() { + case reflect.String: + // See golang.org/issue/13145. + const arbitraryCutoff = 16 + if v.Len() > arbitraryCutoff { + w.printf("var %s %s = ", name, typeName(x)) + } else { + w.printf("const %s %s = ", name, typeName(x)) + } + w.WriteString(v.String()) + w.printf("\n") + default: + w.printf("const %s = %#v\n", name, x) + } +} + +// WriteVar writes a variable of the given name and value. +func (w *CodeWriter) WriteVar(name string, x interface{}) { + w.insertSep() + v := reflect.ValueOf(x) + oldSize := w.Size + sz := int(v.Type().Size()) + w.Size += sz + + switch v.Type().Kind() { + case reflect.String: + w.printf("var %s %s = ", name, typeName(x)) + w.WriteString(v.String()) + case reflect.Struct: + w.gob.Encode(x) + fallthrough + case reflect.Slice, reflect.Array: + w.printf("var %s = ", name) + w.writeValue(v) + w.writeSizeInfo(w.Size - oldSize) + default: + w.printf("var %s %s = ", name, typeName(x)) + w.gob.Encode(x) + w.writeValue(v) + w.writeSizeInfo(w.Size - oldSize) + } + w.printf("\n") +} + +func (w *CodeWriter) writeValue(v reflect.Value) { + x := v.Interface() + switch v.Kind() { + case reflect.String: + w.WriteString(v.String()) + case reflect.Array: + // Don't double count: callers of WriteArray count on the size being + // added, so we need to discount it here. + w.Size -= int(v.Type().Size()) + w.writeSlice(x, true) + case reflect.Slice: + w.writeSlice(x, false) + case reflect.Struct: + w.printf("%s{\n", typeName(v.Interface())) + t := v.Type() + for i := 0; i < v.NumField(); i++ { + w.printf("%s: ", t.Field(i).Name) + w.writeValue(v.Field(i)) + w.printf(",\n") + } + w.printf("}") + default: + w.printf("%#v", x) + } +} + +// WriteString writes a string literal. +func (w *CodeWriter) WriteString(s string) { + s = strings.Replace(s, `\`, `\\`, -1) + io.WriteString(w.Hash, s) // content hash + w.Size += len(s) + + const maxInline = 40 + if len(s) <= maxInline { + w.printf("%q", s) + return + } + + // We will render the string as a multi-line string. + const maxWidth = 80 - 4 - len(`"`) - len(`" +`) + + // When starting on its own line, go fmt indents line 2+ an extra level. + n, max := maxWidth, maxWidth-4 + + // Print "" +\n, if a string does not start on its own line. + b := w.buf.Bytes() + if p := len(bytes.TrimRight(b, " \t")); p > 0 && b[p-1] != '\n' { + w.printf("\"\" + // Size: %d bytes\n", len(s)) + n, max = maxWidth, maxWidth + } + + w.printf(`"`) + + for sz, p := 0, 0; p < len(s); { + var r rune + r, sz = utf8.DecodeRuneInString(s[p:]) + out := s[p : p+sz] + chars := 1 + if !unicode.IsPrint(r) || r == utf8.RuneError || r == '"' { + switch sz { + case 1: + out = fmt.Sprintf("\\x%02x", s[p]) + case 2, 3: + out = fmt.Sprintf("\\u%04x", r) + case 4: + out = fmt.Sprintf("\\U%08x", r) + } + chars = len(out) + } + if n -= chars; n < 0 { + w.printf("\" +\n\"") + n = max - len(out) + } + w.printf("%s", out) + p += sz + } + w.printf(`"`) +} + +// WriteSlice writes a slice value. +func (w *CodeWriter) WriteSlice(x interface{}) { + w.writeSlice(x, false) +} + +// WriteArray writes an array value. +func (w *CodeWriter) WriteArray(x interface{}) { + w.writeSlice(x, true) +} + +func (w *CodeWriter) writeSlice(x interface{}, isArray bool) { + v := reflect.ValueOf(x) + w.gob.Encode(v.Len()) + w.Size += v.Len() * int(v.Type().Elem().Size()) + name := typeName(x) + if isArray { + name = fmt.Sprintf("[%d]%s", v.Len(), name[strings.Index(name, "]")+1:]) + } + if isArray { + w.printf("%s{\n", name) + } else { + w.printf("%s{ // %d elements\n", name, v.Len()) + } + + switch kind := v.Type().Elem().Kind(); kind { + case reflect.String: + for _, s := range x.([]string) { + w.WriteString(s) + w.printf(",\n") + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + // nLine and nBlock are the number of elements per line and block. + nLine, nBlock, format := 8, 64, "%d," + switch kind { + case reflect.Uint8: + format = "%#02x," + case reflect.Uint16: + format = "%#04x," + case reflect.Uint32: + nLine, nBlock, format = 4, 32, "%#08x," + case reflect.Uint, reflect.Uint64: + nLine, nBlock, format = 4, 32, "%#016x," + case reflect.Int8: + nLine = 16 + } + n := nLine + for i := 0; i < v.Len(); i++ { + if i%nBlock == 0 && v.Len() > nBlock { + w.printf("// Entry %X - %X\n", i, i+nBlock-1) + } + x := v.Index(i).Interface() + w.gob.Encode(x) + w.printf(format, x) + if n--; n == 0 { + n = nLine + w.printf("\n") + } + } + w.printf("\n") + case reflect.Struct: + zero := reflect.Zero(v.Type().Elem()).Interface() + for i := 0; i < v.Len(); i++ { + x := v.Index(i).Interface() + w.gob.EncodeValue(v) + if !reflect.DeepEqual(zero, x) { + line := fmt.Sprintf("%#v,\n", x) + line = line[strings.IndexByte(line, '{'):] + w.printf("%d: ", i) + w.printf(line) + } + } + case reflect.Array: + for i := 0; i < v.Len(); i++ { + w.printf("%d: %#v,\n", i, v.Index(i).Interface()) + } + default: + panic("gen: slice elem type not supported") + } + w.printf("}") +} + +// WriteType writes a definition of the type of the given value and returns the +// type name. +func (w *CodeWriter) WriteType(x interface{}) string { + t := reflect.TypeOf(x) + w.printf("type %s struct {\n", t.Name()) + for i := 0; i < t.NumField(); i++ { + w.printf("\t%s %s\n", t.Field(i).Name, t.Field(i).Type) + } + w.printf("}\n") + return t.Name() +} + +// typeName returns the name of the go type of x. +func typeName(x interface{}) string { + t := reflect.ValueOf(x).Type() + return strings.Replace(fmt.Sprint(t), "main.", "", 1) +} diff --git a/vendor/golang.org/x/text/internal/gen/gen.go b/vendor/golang.org/x/text/internal/gen/gen.go new file mode 100644 index 000000000..84c699faa --- /dev/null +++ b/vendor/golang.org/x/text/internal/gen/gen.go @@ -0,0 +1,281 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package gen contains common code for the various code generation tools in the +// text repository. Its usage ensures consistency between tools. +// +// This package defines command line flags that are common to most generation +// tools. The flags allow for specifying specific Unicode and CLDR versions +// in the public Unicode data repository (http://www.unicode.org/Public). +// +// A local Unicode data mirror can be set through the flag -local or the +// environment variable UNICODE_DIR. The former takes precedence. The local +// directory should follow the same structure as the public repository. +// +// IANA data can also optionally be mirrored by putting it in the iana directory +// rooted at the top of the local mirror. Beware, though, that IANA data is not +// versioned. So it is up to the developer to use the right version. +package gen // import "golang.org/x/text/internal/gen" + +import ( + "bytes" + "flag" + "fmt" + "go/build" + "go/format" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "path" + "path/filepath" + "sync" + "unicode" + + "golang.org/x/text/unicode/cldr" +) + +var ( + url = flag.String("url", + "http://www.unicode.org/Public", + "URL of Unicode database directory") + iana = flag.String("iana", + "http://www.iana.org", + "URL of the IANA repository") + unicodeVersion = flag.String("unicode", + getEnv("UNICODE_VERSION", unicode.Version), + "unicode version to use") + cldrVersion = flag.String("cldr", + getEnv("CLDR_VERSION", cldr.Version), + "cldr version to use") +) + +func getEnv(name, def string) string { + if v := os.Getenv(name); v != "" { + return v + } + return def +} + +// Init performs common initialization for a gen command. It parses the flags +// and sets up the standard logging parameters. +func Init() { + log.SetPrefix("") + log.SetFlags(log.Lshortfile) + flag.Parse() +} + +const header = `// This file was generated by go generate; DO NOT EDIT + +package %s + +` + +// UnicodeVersion reports the requested Unicode version. +func UnicodeVersion() string { + return *unicodeVersion +} + +// UnicodeVersion reports the requested CLDR version. +func CLDRVersion() string { + return *cldrVersion +} + +// IsLocal reports whether data files are available locally. +func IsLocal() bool { + dir, err := localReadmeFile() + if err != nil { + return false + } + if _, err = os.Stat(dir); err != nil { + return false + } + return true +} + +// OpenUCDFile opens the requested UCD file. The file is specified relative to +// the public Unicode root directory. It will call log.Fatal if there are any +// errors. +func OpenUCDFile(file string) io.ReadCloser { + return openUnicode(path.Join(*unicodeVersion, "ucd", file)) +} + +// OpenCLDRCoreZip opens the CLDR core zip file. It will call log.Fatal if there +// are any errors. +func OpenCLDRCoreZip() io.ReadCloser { + return OpenUnicodeFile("cldr", *cldrVersion, "core.zip") +} + +// OpenUnicodeFile opens the requested file of the requested category from the +// root of the Unicode data archive. The file is specified relative to the +// public Unicode root directory. If version is "", it will use the default +// Unicode version. It will call log.Fatal if there are any errors. +func OpenUnicodeFile(category, version, file string) io.ReadCloser { + if version == "" { + version = UnicodeVersion() + } + return openUnicode(path.Join(category, version, file)) +} + +// OpenIANAFile opens the requested IANA file. The file is specified relative +// to the IANA root, which is typically either http://www.iana.org or the +// iana directory in the local mirror. It will call log.Fatal if there are any +// errors. +func OpenIANAFile(path string) io.ReadCloser { + return Open(*iana, "iana", path) +} + +var ( + dirMutex sync.Mutex + localDir string +) + +const permissions = 0755 + +func localReadmeFile() (string, error) { + p, err := build.Import("golang.org/x/text", "", build.FindOnly) + if err != nil { + return "", fmt.Errorf("Could not locate package: %v", err) + } + return filepath.Join(p.Dir, "DATA", "README"), nil +} + +func getLocalDir() string { + dirMutex.Lock() + defer dirMutex.Unlock() + + readme, err := localReadmeFile() + if err != nil { + log.Fatal(err) + } + dir := filepath.Dir(readme) + if _, err := os.Stat(readme); err != nil { + if err := os.MkdirAll(dir, permissions); err != nil { + log.Fatalf("Could not create directory: %v", err) + } + ioutil.WriteFile(readme, []byte(readmeTxt), permissions) + } + return dir +} + +const readmeTxt = `Generated by golang.org/x/text/internal/gen. DO NOT EDIT. + +This directory contains downloaded files used to generate the various tables +in the golang.org/x/text subrepo. + +Note that the language subtag repo (iana/assignments/language-subtag-registry) +and all other times in the iana subdirectory are not versioned and will need +to be periodically manually updated. The easiest way to do this is to remove +the entire iana directory. This is mostly of concern when updating the language +package. +` + +// Open opens subdir/path if a local directory is specified and the file exists, +// where subdir is a directory relative to the local root, or fetches it from +// urlRoot/path otherwise. It will call log.Fatal if there are any errors. +func Open(urlRoot, subdir, path string) io.ReadCloser { + file := filepath.Join(getLocalDir(), subdir, filepath.FromSlash(path)) + return open(file, urlRoot, path) +} + +func openUnicode(path string) io.ReadCloser { + file := filepath.Join(getLocalDir(), filepath.FromSlash(path)) + return open(file, *url, path) +} + +// TODO: automatically periodically update non-versioned files. + +func open(file, urlRoot, path string) io.ReadCloser { + if f, err := os.Open(file); err == nil { + return f + } + r := get(urlRoot, path) + defer r.Close() + b, err := ioutil.ReadAll(r) + if err != nil { + log.Fatalf("Could not download file: %v", err) + } + os.MkdirAll(filepath.Dir(file), permissions) + if err := ioutil.WriteFile(file, b, permissions); err != nil { + log.Fatalf("Could not create file: %v", err) + } + return ioutil.NopCloser(bytes.NewReader(b)) +} + +func get(root, path string) io.ReadCloser { + url := root + "/" + path + fmt.Printf("Fetching %s...", url) + defer fmt.Println(" done.") + resp, err := http.Get(url) + if err != nil { + log.Fatalf("HTTP GET: %v", err) + } + if resp.StatusCode != 200 { + log.Fatalf("Bad GET status for %q: %q", url, resp.Status) + } + return resp.Body +} + +// TODO: use Write*Version in all applicable packages. + +// WriteUnicodeVersion writes a constant for the Unicode version from which the +// tables are generated. +func WriteUnicodeVersion(w io.Writer) { + fmt.Fprintf(w, "// UnicodeVersion is the Unicode version from which the tables in this package are derived.\n") + fmt.Fprintf(w, "const UnicodeVersion = %q\n\n", UnicodeVersion()) +} + +// WriteCLDRVersion writes a constant for the CLDR version from which the +// tables are generated. +func WriteCLDRVersion(w io.Writer) { + fmt.Fprintf(w, "// CLDRVersion is the CLDR version from which the tables in this package are derived.\n") + fmt.Fprintf(w, "const CLDRVersion = %q\n\n", CLDRVersion()) +} + +// WriteGoFile prepends a standard file comment and package statement to the +// given bytes, applies gofmt, and writes them to a file with the given name. +// It will call log.Fatal if there are any errors. +func WriteGoFile(filename, pkg string, b []byte) { + w, err := os.Create(filename) + if err != nil { + log.Fatalf("Could not create file %s: %v", filename, err) + } + defer w.Close() + if _, err = WriteGo(w, pkg, b); err != nil { + log.Fatalf("Error writing file %s: %v", filename, err) + } +} + +// WriteGo prepends a standard file comment and package statement to the given +// bytes, applies gofmt, and writes them to w. +func WriteGo(w io.Writer, pkg string, b []byte) (n int, err error) { + src := []byte(fmt.Sprintf(header, pkg)) + src = append(src, b...) + formatted, err := format.Source(src) + if err != nil { + // Print the generated code even in case of an error so that the + // returned error can be meaningfully interpreted. + n, _ = w.Write(src) + return n, err + } + return w.Write(formatted) +} + +// Repackage rewrites a Go file from belonging to package main to belonging to +// the given package. +func Repackage(inFile, outFile, pkg string) { + src, err := ioutil.ReadFile(inFile) + if err != nil { + log.Fatalf("reading %s: %v", inFile, err) + } + const toDelete = "package main\n\n" + i := bytes.Index(src, []byte(toDelete)) + if i < 0 { + log.Fatalf("Could not find %q in %s.", toDelete, inFile) + } + w := &bytes.Buffer{} + w.Write(src[i+len(toDelete):]) + WriteGoFile(outFile, pkg, w.Bytes()) +} diff --git a/vendor/golang.org/x/text/internal/utf8internal/utf8internal.go b/vendor/golang.org/x/text/internal/utf8internal/utf8internal.go new file mode 100644 index 000000000..575cea870 --- /dev/null +++ b/vendor/golang.org/x/text/internal/utf8internal/utf8internal.go @@ -0,0 +1,87 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package utf8internal contains low-level utf8-related constants, tables, etc. +// that are used internally by the text package. +package utf8internal + +// The default lowest and highest continuation byte. +const ( + LoCB = 0x80 // 1000 0000 + HiCB = 0xBF // 1011 1111 +) + +// Constants related to getting information of first bytes of UTF-8 sequences. +const ( + // ASCII identifies a UTF-8 byte as ASCII. + ASCII = as + + // FirstInvalid indicates a byte is invalid as a first byte of a UTF-8 + // sequence. + FirstInvalid = xx + + // SizeMask is a mask for the size bits. Use use x&SizeMask to get the size. + SizeMask = 7 + + // AcceptShift is the right-shift count for the first byte info byte to get + // the index into the AcceptRanges table. See AcceptRanges. + AcceptShift = 4 + + // The names of these constants are chosen to give nice alignment in the + // table below. The first nibble is an index into acceptRanges or F for + // special one-byte cases. The second nibble is the Rune length or the + // Status for the special one-byte case. + xx = 0xF1 // invalid: size 1 + as = 0xF0 // ASCII: size 1 + s1 = 0x02 // accept 0, size 2 + s2 = 0x13 // accept 1, size 3 + s3 = 0x03 // accept 0, size 3 + s4 = 0x23 // accept 2, size 3 + s5 = 0x34 // accept 3, size 4 + s6 = 0x04 // accept 0, size 4 + s7 = 0x44 // accept 4, size 4 +) + +// First is information about the first byte in a UTF-8 sequence. +var First = [256]uint8{ + // 1 2 3 4 5 6 7 8 9 A B C D E F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x00-0x0F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x10-0x1F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x20-0x2F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x30-0x3F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x40-0x4F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x50-0x5F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x60-0x6F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x70-0x7F + // 1 2 3 4 5 6 7 8 9 A B C D E F + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0x80-0x8F + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0x90-0x9F + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0xA0-0xAF + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0xB0-0xBF + xx, xx, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, // 0xC0-0xCF + s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, // 0xD0-0xDF + s2, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s4, s3, s3, // 0xE0-0xEF + s5, s6, s6, s6, s7, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0xF0-0xFF +} + +// AcceptRange gives the range of valid values for the second byte in a UTF-8 +// sequence for any value for First that is not ASCII or FirstInvalid. +type AcceptRange struct { + Lo uint8 // lowest value for second byte. + Hi uint8 // highest value for second byte. +} + +// AcceptRanges is a slice of AcceptRange values. For a given byte sequence b +// +// AcceptRanges[First[b[0]]>>AcceptShift] +// +// will give the value of AcceptRange for the multi-byte UTF-8 sequence starting +// at b[0]. +var AcceptRanges = [...]AcceptRange{ + 0: {LoCB, HiCB}, + 1: {0xA0, HiCB}, + 2: {LoCB, 0x9F}, + 3: {0x90, HiCB}, + 4: {LoCB, 0x8F}, +} diff --git a/vendor/golang.org/x/text/runes/cond.go b/vendor/golang.org/x/text/runes/cond.go new file mode 100644 index 000000000..df7aa02db --- /dev/null +++ b/vendor/golang.org/x/text/runes/cond.go @@ -0,0 +1,187 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package runes + +import ( + "unicode/utf8" + + "golang.org/x/text/transform" +) + +// Note: below we pass invalid UTF-8 to the tIn and tNotIn transformers as is. +// This is done for various reasons: +// - To retain the semantics of the Nop transformer: if input is passed to a Nop +// one would expect it to be unchanged. +// - It would be very expensive to pass a converted RuneError to a transformer: +// a transformer might need more source bytes after RuneError, meaning that +// the only way to pass it safely is to create a new buffer and manage the +// intermingling of RuneErrors and normal input. +// - Many transformers leave ill-formed UTF-8 as is, so this is not +// inconsistent. Generally ill-formed UTF-8 is only replaced if it is a +// logical consequence of the operation (as for Map) or if it otherwise would +// pose security concerns (as for Remove). +// - An alternative would be to return an error on ill-formed UTF-8, but this +// would be inconsistent with other operations. + +// If returns a transformer that applies tIn to consecutive runes for which +// s.Contains(r) and tNotIn to consecutive runes for which !s.Contains(r). Reset +// is called on tIn and tNotIn at the start of each run. A Nop transformer will +// substitute a nil value passed to tIn or tNotIn. Invalid UTF-8 is translated +// to RuneError to determine which transformer to apply, but is passed as is to +// the respective transformer. +func If(s Set, tIn, tNotIn transform.Transformer) Transformer { + if tIn == nil && tNotIn == nil { + return Transformer{transform.Nop} + } + if tIn == nil { + tIn = transform.Nop + } + if tNotIn == nil { + tNotIn = transform.Nop + } + sIn, ok := tIn.(transform.SpanningTransformer) + if !ok { + sIn = dummySpan{tIn} + } + sNotIn, ok := tNotIn.(transform.SpanningTransformer) + if !ok { + sNotIn = dummySpan{tNotIn} + } + + a := &cond{ + tIn: sIn, + tNotIn: sNotIn, + f: s.Contains, + } + a.Reset() + return Transformer{a} +} + +type dummySpan struct{ transform.Transformer } + +func (d dummySpan) Span(src []byte, atEOF bool) (n int, err error) { + return 0, transform.ErrEndOfSpan +} + +type cond struct { + tIn, tNotIn transform.SpanningTransformer + f func(rune) bool + check func(rune) bool // current check to perform + t transform.SpanningTransformer // current transformer to use +} + +// Reset implements transform.Transformer. +func (t *cond) Reset() { + t.check = t.is + t.t = t.tIn + t.t.Reset() // notIn will be reset on first usage. +} + +func (t *cond) is(r rune) bool { + if t.f(r) { + return true + } + t.check = t.isNot + t.t = t.tNotIn + t.tNotIn.Reset() + return false +} + +func (t *cond) isNot(r rune) bool { + if !t.f(r) { + return true + } + t.check = t.is + t.t = t.tIn + t.tIn.Reset() + return false +} + +// This implementation of Span doesn't help all too much, but it needs to be +// there to satisfy this package's Transformer interface. +// TODO: there are certainly room for improvements, though. For example, if +// t.t == transform.Nop (which will a common occurrence) it will save a bundle +// to special-case that loop. +func (t *cond) Span(src []byte, atEOF bool) (n int, err error) { + p := 0 + for n < len(src) && err == nil { + // Don't process too much at a time as the Spanner that will be + // called on this block may terminate early. + const maxChunk = 4096 + max := len(src) + if v := n + maxChunk; v < max { + max = v + } + atEnd := false + size := 0 + current := t.t + for ; p < max; p += size { + r := rune(src[p]) + if r < utf8.RuneSelf { + size = 1 + } else if r, size = utf8.DecodeRune(src[p:]); size == 1 { + if !atEOF && !utf8.FullRune(src[p:]) { + err = transform.ErrShortSrc + break + } + } + if !t.check(r) { + // The next rune will be the start of a new run. + atEnd = true + break + } + } + n2, err2 := current.Span(src[n:p], atEnd || (atEOF && p == len(src))) + n += n2 + if err2 != nil { + return n, err2 + } + // At this point either err != nil or t.check will pass for the rune at p. + p = n + size + } + return n, err +} + +func (t *cond) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + p := 0 + for nSrc < len(src) && err == nil { + // Don't process too much at a time, as the work might be wasted if the + // destination buffer isn't large enough to hold the result or a + // transform returns an error early. + const maxChunk = 4096 + max := len(src) + if n := nSrc + maxChunk; n < len(src) { + max = n + } + atEnd := false + size := 0 + current := t.t + for ; p < max; p += size { + r := rune(src[p]) + if r < utf8.RuneSelf { + size = 1 + } else if r, size = utf8.DecodeRune(src[p:]); size == 1 { + if !atEOF && !utf8.FullRune(src[p:]) { + err = transform.ErrShortSrc + break + } + } + if !t.check(r) { + // The next rune will be the start of a new run. + atEnd = true + break + } + } + nDst2, nSrc2, err2 := current.Transform(dst[nDst:], src[nSrc:p], atEnd || (atEOF && p == len(src))) + nDst += nDst2 + nSrc += nSrc2 + if err2 != nil { + return nDst, nSrc, err2 + } + // At this point either err != nil or t.check will pass for the rune at p. + p = nSrc + size + } + return nDst, nSrc, err +} diff --git a/vendor/golang.org/x/text/runes/runes.go b/vendor/golang.org/x/text/runes/runes.go new file mode 100644 index 000000000..71933696f --- /dev/null +++ b/vendor/golang.org/x/text/runes/runes.go @@ -0,0 +1,355 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package runes provide transforms for UTF-8 encoded text. +package runes // import "golang.org/x/text/runes" + +import ( + "unicode" + "unicode/utf8" + + "golang.org/x/text/transform" +) + +// A Set is a collection of runes. +type Set interface { + // Contains returns true if r is contained in the set. + Contains(r rune) bool +} + +type setFunc func(rune) bool + +func (s setFunc) Contains(r rune) bool { + return s(r) +} + +// Note: using funcs here instead of wrapping types result in cleaner +// documentation and a smaller API. + +// In creates a Set with a Contains method that returns true for all runes in +// the given RangeTable. +func In(rt *unicode.RangeTable) Set { + return setFunc(func(r rune) bool { return unicode.Is(rt, r) }) +} + +// In creates a Set with a Contains method that returns true for all runes not +// in the given RangeTable. +func NotIn(rt *unicode.RangeTable) Set { + return setFunc(func(r rune) bool { return !unicode.Is(rt, r) }) +} + +// Predicate creates a Set with a Contains method that returns f(r). +func Predicate(f func(rune) bool) Set { + return setFunc(f) +} + +// Transformer implements the transform.Transformer interface. +type Transformer struct { + t transform.SpanningTransformer +} + +func (t Transformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + return t.t.Transform(dst, src, atEOF) +} + +func (t Transformer) Span(b []byte, atEOF bool) (n int, err error) { + return t.t.Span(b, atEOF) +} + +func (t Transformer) Reset() { t.t.Reset() } + +// Bytes returns a new byte slice with the result of converting b using t. It +// calls Reset on t. It returns nil if any error was found. This can only happen +// if an error-producing Transformer is passed to If. +func (t Transformer) Bytes(b []byte) []byte { + b, _, err := transform.Bytes(t, b) + if err != nil { + return nil + } + return b +} + +// String returns a string with the result of converting s using t. It calls +// Reset on t. It returns the empty string if any error was found. This can only +// happen if an error-producing Transformer is passed to If. +func (t Transformer) String(s string) string { + s, _, err := transform.String(t, s) + if err != nil { + return "" + } + return s +} + +// TODO: +// - Copy: copying strings and bytes in whole-rune units. +// - Validation (maybe) +// - Well-formed-ness (maybe) + +const runeErrorString = string(utf8.RuneError) + +// Remove returns a Transformer that removes runes r for which s.Contains(r). +// Illegal input bytes are replaced by RuneError before being passed to f. +func Remove(s Set) Transformer { + if f, ok := s.(setFunc); ok { + // This little trick cuts the running time of BenchmarkRemove for sets + // created by Predicate roughly in half. + // TODO: special-case RangeTables as well. + return Transformer{remove(f)} + } + return Transformer{remove(s.Contains)} +} + +// TODO: remove transform.RemoveFunc. + +type remove func(r rune) bool + +func (remove) Reset() {} + +// Span implements transform.Spanner. +func (t remove) Span(src []byte, atEOF bool) (n int, err error) { + for r, size := rune(0), 0; n < len(src); { + if r = rune(src[n]); r < utf8.RuneSelf { + size = 1 + } else if r, size = utf8.DecodeRune(src[n:]); size == 1 { + // Invalid rune. + if !atEOF && !utf8.FullRune(src[n:]) { + err = transform.ErrShortSrc + } else { + err = transform.ErrEndOfSpan + } + break + } + if t(r) { + err = transform.ErrEndOfSpan + break + } + n += size + } + return +} + +// Transform implements transform.Transformer. +func (t remove) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + for r, size := rune(0), 0; nSrc < len(src); { + if r = rune(src[nSrc]); r < utf8.RuneSelf { + size = 1 + } else if r, size = utf8.DecodeRune(src[nSrc:]); size == 1 { + // Invalid rune. + if !atEOF && !utf8.FullRune(src[nSrc:]) { + err = transform.ErrShortSrc + break + } + // We replace illegal bytes with RuneError. Not doing so might + // otherwise turn a sequence of invalid UTF-8 into valid UTF-8. + // The resulting byte sequence may subsequently contain runes + // for which t(r) is true that were passed unnoticed. + if !t(utf8.RuneError) { + if nDst+3 > len(dst) { + err = transform.ErrShortDst + break + } + dst[nDst+0] = runeErrorString[0] + dst[nDst+1] = runeErrorString[1] + dst[nDst+2] = runeErrorString[2] + nDst += 3 + } + nSrc++ + continue + } + if t(r) { + nSrc += size + continue + } + if nDst+size > len(dst) { + err = transform.ErrShortDst + break + } + for i := 0; i < size; i++ { + dst[nDst] = src[nSrc] + nDst++ + nSrc++ + } + } + return +} + +// Map returns a Transformer that maps the runes in the input using the given +// mapping. Illegal bytes in the input are converted to utf8.RuneError before +// being passed to the mapping func. +func Map(mapping func(rune) rune) Transformer { + return Transformer{mapper(mapping)} +} + +type mapper func(rune) rune + +func (mapper) Reset() {} + +// Span implements transform.Spanner. +func (t mapper) Span(src []byte, atEOF bool) (n int, err error) { + for r, size := rune(0), 0; n < len(src); n += size { + if r = rune(src[n]); r < utf8.RuneSelf { + size = 1 + } else if r, size = utf8.DecodeRune(src[n:]); size == 1 { + // Invalid rune. + if !atEOF && !utf8.FullRune(src[n:]) { + err = transform.ErrShortSrc + } else { + err = transform.ErrEndOfSpan + } + break + } + if t(r) != r { + err = transform.ErrEndOfSpan + break + } + } + return n, err +} + +// Transform implements transform.Transformer. +func (t mapper) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + var replacement rune + var b [utf8.UTFMax]byte + + for r, size := rune(0), 0; nSrc < len(src); { + if r = rune(src[nSrc]); r < utf8.RuneSelf { + if replacement = t(r); replacement < utf8.RuneSelf { + if nDst == len(dst) { + err = transform.ErrShortDst + break + } + dst[nDst] = byte(replacement) + nDst++ + nSrc++ + continue + } + size = 1 + } else if r, size = utf8.DecodeRune(src[nSrc:]); size == 1 { + // Invalid rune. + if !atEOF && !utf8.FullRune(src[nSrc:]) { + err = transform.ErrShortSrc + break + } + + if replacement = t(utf8.RuneError); replacement == utf8.RuneError { + if nDst+3 > len(dst) { + err = transform.ErrShortDst + break + } + dst[nDst+0] = runeErrorString[0] + dst[nDst+1] = runeErrorString[1] + dst[nDst+2] = runeErrorString[2] + nDst += 3 + nSrc++ + continue + } + } else if replacement = t(r); replacement == r { + if nDst+size > len(dst) { + err = transform.ErrShortDst + break + } + for i := 0; i < size; i++ { + dst[nDst] = src[nSrc] + nDst++ + nSrc++ + } + continue + } + + n := utf8.EncodeRune(b[:], replacement) + + if nDst+n > len(dst) { + err = transform.ErrShortDst + break + } + for i := 0; i < n; i++ { + dst[nDst] = b[i] + nDst++ + } + nSrc += size + } + return +} + +// ReplaceIllFormed returns a transformer that replaces all input bytes that are +// not part of a well-formed UTF-8 code sequence with utf8.RuneError. +func ReplaceIllFormed() Transformer { + return Transformer{&replaceIllFormed{}} +} + +type replaceIllFormed struct{ transform.NopResetter } + +func (t replaceIllFormed) Span(src []byte, atEOF bool) (n int, err error) { + for n < len(src) { + // ASCII fast path. + if src[n] < utf8.RuneSelf { + n++ + continue + } + + r, size := utf8.DecodeRune(src[n:]) + + // Look for a valid non-ASCII rune. + if r != utf8.RuneError || size != 1 { + n += size + continue + } + + // Look for short source data. + if !atEOF && !utf8.FullRune(src[n:]) { + err = transform.ErrShortSrc + break + } + + // We have an invalid rune. + err = transform.ErrEndOfSpan + break + } + return n, err +} + +func (t replaceIllFormed) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + for nSrc < len(src) { + // ASCII fast path. + if r := src[nSrc]; r < utf8.RuneSelf { + if nDst == len(dst) { + err = transform.ErrShortDst + break + } + dst[nDst] = r + nDst++ + nSrc++ + continue + } + + // Look for a valid non-ASCII rune. + if _, size := utf8.DecodeRune(src[nSrc:]); size != 1 { + if size != copy(dst[nDst:], src[nSrc:nSrc+size]) { + err = transform.ErrShortDst + break + } + nDst += size + nSrc += size + continue + } + + // Look for short source data. + if !atEOF && !utf8.FullRune(src[nSrc:]) { + err = transform.ErrShortSrc + break + } + + // We have an invalid rune. + if nDst+3 > len(dst) { + err = transform.ErrShortDst + break + } + dst[nDst+0] = runeErrorString[0] + dst[nDst+1] = runeErrorString[1] + dst[nDst+2] = runeErrorString[2] + nDst += 3 + nSrc++ + } + return nDst, nSrc, err +} diff --git a/vendor/golang.org/x/text/transform/transform.go b/vendor/golang.org/x/text/transform/transform.go new file mode 100644 index 000000000..fe47b9b35 --- /dev/null +++ b/vendor/golang.org/x/text/transform/transform.go @@ -0,0 +1,705 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package transform provides reader and writer wrappers that transform the +// bytes passing through as well as various transformations. Example +// transformations provided by other packages include normalization and +// conversion between character sets. +package transform // import "golang.org/x/text/transform" + +import ( + "bytes" + "errors" + "io" + "unicode/utf8" +) + +var ( + // ErrShortDst means that the destination buffer was too short to + // receive all of the transformed bytes. + ErrShortDst = errors.New("transform: short destination buffer") + + // ErrShortSrc means that the source buffer has insufficient data to + // complete the transformation. + ErrShortSrc = errors.New("transform: short source buffer") + + // ErrEndOfSpan means that the input and output (the transformed input) + // are not identical. + ErrEndOfSpan = errors.New("transform: input and output are not identical") + + // errInconsistentByteCount means that Transform returned success (nil + // error) but also returned nSrc inconsistent with the src argument. + errInconsistentByteCount = errors.New("transform: inconsistent byte count returned") + + // errShortInternal means that an internal buffer is not large enough + // to make progress and the Transform operation must be aborted. + errShortInternal = errors.New("transform: short internal buffer") +) + +// Transformer transforms bytes. +type Transformer interface { + // Transform writes to dst the transformed bytes read from src, and + // returns the number of dst bytes written and src bytes read. The + // atEOF argument tells whether src represents the last bytes of the + // input. + // + // Callers should always process the nDst bytes produced and account + // for the nSrc bytes consumed before considering the error err. + // + // A nil error means that all of the transformed bytes (whether freshly + // transformed from src or left over from previous Transform calls) + // were written to dst. A nil error can be returned regardless of + // whether atEOF is true. If err is nil then nSrc must equal len(src); + // the converse is not necessarily true. + // + // ErrShortDst means that dst was too short to receive all of the + // transformed bytes. ErrShortSrc means that src had insufficient data + // to complete the transformation. If both conditions apply, then + // either error may be returned. Other than the error conditions listed + // here, implementations are free to report other errors that arise. + Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) + + // Reset resets the state and allows a Transformer to be reused. + Reset() +} + +// SpanningTransformer extends the Transformer interface with a Span method +// that determines how much of the input already conforms to the Transformer. +type SpanningTransformer interface { + Transformer + + // Span returns a position in src such that transforming src[:n] results in + // identical output src[:n] for these bytes. It does not necessarily return + // the largest such n. The atEOF argument tells whether src represents the + // last bytes of the input. + // + // Callers should always account for the n bytes consumed before + // considering the error err. + // + // A nil error means that all input bytes are known to be identical to the + // output produced by the Transformer. A nil error can be be returned + // regardless of whether atEOF is true. If err is nil, then then n must + // equal len(src); the converse is not necessarily true. + // + // ErrEndOfSpan means that the Transformer output may differ from the + // input after n bytes. Note that n may be len(src), meaning that the output + // would contain additional bytes after otherwise identical output. + // ErrShortSrc means that src had insufficient data to determine whether the + // remaining bytes would change. Other than the error conditions listed + // here, implementations are free to report other errors that arise. + // + // Calling Span can modify the Transformer state as a side effect. In + // effect, it does the transformation just as calling Transform would, only + // without copying to a destination buffer and only up to a point it can + // determine the input and output bytes are the same. This is obviously more + // limited than calling Transform, but can be more efficient in terms of + // copying and allocating buffers. Calls to Span and Transform may be + // interleaved. + Span(src []byte, atEOF bool) (n int, err error) +} + +// NopResetter can be embedded by implementations of Transformer to add a nop +// Reset method. +type NopResetter struct{} + +// Reset implements the Reset method of the Transformer interface. +func (NopResetter) Reset() {} + +// Reader wraps another io.Reader by transforming the bytes read. +type Reader struct { + r io.Reader + t Transformer + err error + + // dst[dst0:dst1] contains bytes that have been transformed by t but + // not yet copied out via Read. + dst []byte + dst0, dst1 int + + // src[src0:src1] contains bytes that have been read from r but not + // yet transformed through t. + src []byte + src0, src1 int + + // transformComplete is whether the transformation is complete, + // regardless of whether or not it was successful. + transformComplete bool +} + +const defaultBufSize = 4096 + +// NewReader returns a new Reader that wraps r by transforming the bytes read +// via t. It calls Reset on t. +func NewReader(r io.Reader, t Transformer) *Reader { + t.Reset() + return &Reader{ + r: r, + t: t, + dst: make([]byte, defaultBufSize), + src: make([]byte, defaultBufSize), + } +} + +// Read implements the io.Reader interface. +func (r *Reader) Read(p []byte) (int, error) { + n, err := 0, error(nil) + for { + // Copy out any transformed bytes and return the final error if we are done. + if r.dst0 != r.dst1 { + n = copy(p, r.dst[r.dst0:r.dst1]) + r.dst0 += n + if r.dst0 == r.dst1 && r.transformComplete { + return n, r.err + } + return n, nil + } else if r.transformComplete { + return 0, r.err + } + + // Try to transform some source bytes, or to flush the transformer if we + // are out of source bytes. We do this even if r.r.Read returned an error. + // As the io.Reader documentation says, "process the n > 0 bytes returned + // before considering the error". + if r.src0 != r.src1 || r.err != nil { + r.dst0 = 0 + r.dst1, n, err = r.t.Transform(r.dst, r.src[r.src0:r.src1], r.err == io.EOF) + r.src0 += n + + switch { + case err == nil: + if r.src0 != r.src1 { + r.err = errInconsistentByteCount + } + // The Transform call was successful; we are complete if we + // cannot read more bytes into src. + r.transformComplete = r.err != nil + continue + case err == ErrShortDst && (r.dst1 != 0 || n != 0): + // Make room in dst by copying out, and try again. + continue + case err == ErrShortSrc && r.src1-r.src0 != len(r.src) && r.err == nil: + // Read more bytes into src via the code below, and try again. + default: + r.transformComplete = true + // The reader error (r.err) takes precedence over the + // transformer error (err) unless r.err is nil or io.EOF. + if r.err == nil || r.err == io.EOF { + r.err = err + } + continue + } + } + + // Move any untransformed source bytes to the start of the buffer + // and read more bytes. + if r.src0 != 0 { + r.src0, r.src1 = 0, copy(r.src, r.src[r.src0:r.src1]) + } + n, r.err = r.r.Read(r.src[r.src1:]) + r.src1 += n + } +} + +// TODO: implement ReadByte (and ReadRune??). + +// Writer wraps another io.Writer by transforming the bytes read. +// The user needs to call Close to flush unwritten bytes that may +// be buffered. +type Writer struct { + w io.Writer + t Transformer + dst []byte + + // src[:n] contains bytes that have not yet passed through t. + src []byte + n int +} + +// NewWriter returns a new Writer that wraps w by transforming the bytes written +// via t. It calls Reset on t. +func NewWriter(w io.Writer, t Transformer) *Writer { + t.Reset() + return &Writer{ + w: w, + t: t, + dst: make([]byte, defaultBufSize), + src: make([]byte, defaultBufSize), + } +} + +// Write implements the io.Writer interface. If there are not enough +// bytes available to complete a Transform, the bytes will be buffered +// for the next write. Call Close to convert the remaining bytes. +func (w *Writer) Write(data []byte) (n int, err error) { + src := data + if w.n > 0 { + // Append bytes from data to the last remainder. + // TODO: limit the amount copied on first try. + n = copy(w.src[w.n:], data) + w.n += n + src = w.src[:w.n] + } + for { + nDst, nSrc, err := w.t.Transform(w.dst, src, false) + if _, werr := w.w.Write(w.dst[:nDst]); werr != nil { + return n, werr + } + src = src[nSrc:] + if w.n == 0 { + n += nSrc + } else if len(src) <= n { + // Enough bytes from w.src have been consumed. We make src point + // to data instead to reduce the copying. + w.n = 0 + n -= len(src) + src = data[n:] + if n < len(data) && (err == nil || err == ErrShortSrc) { + continue + } + } + switch err { + case ErrShortDst: + // This error is okay as long as we are making progress. + if nDst > 0 || nSrc > 0 { + continue + } + case ErrShortSrc: + if len(src) < len(w.src) { + m := copy(w.src, src) + // If w.n > 0, bytes from data were already copied to w.src and n + // was already set to the number of bytes consumed. + if w.n == 0 { + n += m + } + w.n = m + err = nil + } else if nDst > 0 || nSrc > 0 { + // Not enough buffer to store the remainder. Keep processing as + // long as there is progress. Without this case, transforms that + // require a lookahead larger than the buffer may result in an + // error. This is not something one may expect to be common in + // practice, but it may occur when buffers are set to small + // sizes during testing. + continue + } + case nil: + if w.n > 0 { + err = errInconsistentByteCount + } + } + return n, err + } +} + +// Close implements the io.Closer interface. +func (w *Writer) Close() error { + src := w.src[:w.n] + for { + nDst, nSrc, err := w.t.Transform(w.dst, src, true) + if _, werr := w.w.Write(w.dst[:nDst]); werr != nil { + return werr + } + if err != ErrShortDst { + return err + } + src = src[nSrc:] + } +} + +type nop struct{ NopResetter } + +func (nop) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + n := copy(dst, src) + if n < len(src) { + err = ErrShortDst + } + return n, n, err +} + +func (nop) Span(src []byte, atEOF bool) (n int, err error) { + return len(src), nil +} + +type discard struct{ NopResetter } + +func (discard) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + return 0, len(src), nil +} + +var ( + // Discard is a Transformer for which all Transform calls succeed + // by consuming all bytes and writing nothing. + Discard Transformer = discard{} + + // Nop is a SpanningTransformer that copies src to dst. + Nop SpanningTransformer = nop{} +) + +// chain is a sequence of links. A chain with N Transformers has N+1 links and +// N+1 buffers. Of those N+1 buffers, the first and last are the src and dst +// buffers given to chain.Transform and the middle N-1 buffers are intermediate +// buffers owned by the chain. The i'th link transforms bytes from the i'th +// buffer chain.link[i].b at read offset chain.link[i].p to the i+1'th buffer +// chain.link[i+1].b at write offset chain.link[i+1].n, for i in [0, N). +type chain struct { + link []link + err error + // errStart is the index at which the error occurred plus 1. Processing + // errStart at this level at the next call to Transform. As long as + // errStart > 0, chain will not consume any more source bytes. + errStart int +} + +func (c *chain) fatalError(errIndex int, err error) { + if i := errIndex + 1; i > c.errStart { + c.errStart = i + c.err = err + } +} + +type link struct { + t Transformer + // b[p:n] holds the bytes to be transformed by t. + b []byte + p int + n int +} + +func (l *link) src() []byte { + return l.b[l.p:l.n] +} + +func (l *link) dst() []byte { + return l.b[l.n:] +} + +// Chain returns a Transformer that applies t in sequence. +func Chain(t ...Transformer) Transformer { + if len(t) == 0 { + return nop{} + } + c := &chain{link: make([]link, len(t)+1)} + for i, tt := range t { + c.link[i].t = tt + } + // Allocate intermediate buffers. + b := make([][defaultBufSize]byte, len(t)-1) + for i := range b { + c.link[i+1].b = b[i][:] + } + return c +} + +// Reset resets the state of Chain. It calls Reset on all the Transformers. +func (c *chain) Reset() { + for i, l := range c.link { + if l.t != nil { + l.t.Reset() + } + c.link[i].p, c.link[i].n = 0, 0 + } +} + +// TODO: make chain use Span (is going to be fun to implement!) + +// Transform applies the transformers of c in sequence. +func (c *chain) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + // Set up src and dst in the chain. + srcL := &c.link[0] + dstL := &c.link[len(c.link)-1] + srcL.b, srcL.p, srcL.n = src, 0, len(src) + dstL.b, dstL.n = dst, 0 + var lastFull, needProgress bool // for detecting progress + + // i is the index of the next Transformer to apply, for i in [low, high]. + // low is the lowest index for which c.link[low] may still produce bytes. + // high is the highest index for which c.link[high] has a Transformer. + // The error returned by Transform determines whether to increase or + // decrease i. We try to completely fill a buffer before converting it. + for low, i, high := c.errStart, c.errStart, len(c.link)-2; low <= i && i <= high; { + in, out := &c.link[i], &c.link[i+1] + nDst, nSrc, err0 := in.t.Transform(out.dst(), in.src(), atEOF && low == i) + out.n += nDst + in.p += nSrc + if i > 0 && in.p == in.n { + in.p, in.n = 0, 0 + } + needProgress, lastFull = lastFull, false + switch err0 { + case ErrShortDst: + // Process the destination buffer next. Return if we are already + // at the high index. + if i == high { + return dstL.n, srcL.p, ErrShortDst + } + if out.n != 0 { + i++ + // If the Transformer at the next index is not able to process any + // source bytes there is nothing that can be done to make progress + // and the bytes will remain unprocessed. lastFull is used to + // detect this and break out of the loop with a fatal error. + lastFull = true + continue + } + // The destination buffer was too small, but is completely empty. + // Return a fatal error as this transformation can never complete. + c.fatalError(i, errShortInternal) + case ErrShortSrc: + if i == 0 { + // Save ErrShortSrc in err. All other errors take precedence. + err = ErrShortSrc + break + } + // Source bytes were depleted before filling up the destination buffer. + // Verify we made some progress, move the remaining bytes to the errStart + // and try to get more source bytes. + if needProgress && nSrc == 0 || in.n-in.p == len(in.b) { + // There were not enough source bytes to proceed while the source + // buffer cannot hold any more bytes. Return a fatal error as this + // transformation can never complete. + c.fatalError(i, errShortInternal) + break + } + // in.b is an internal buffer and we can make progress. + in.p, in.n = 0, copy(in.b, in.src()) + fallthrough + case nil: + // if i == low, we have depleted the bytes at index i or any lower levels. + // In that case we increase low and i. In all other cases we decrease i to + // fetch more bytes before proceeding to the next index. + if i > low { + i-- + continue + } + default: + c.fatalError(i, err0) + } + // Exhausted level low or fatal error: increase low and continue + // to process the bytes accepted so far. + i++ + low = i + } + + // If c.errStart > 0, this means we found a fatal error. We will clear + // all upstream buffers. At this point, no more progress can be made + // downstream, as Transform would have bailed while handling ErrShortDst. + if c.errStart > 0 { + for i := 1; i < c.errStart; i++ { + c.link[i].p, c.link[i].n = 0, 0 + } + err, c.errStart, c.err = c.err, 0, nil + } + return dstL.n, srcL.p, err +} + +// Deprecated: use runes.Remove instead. +func RemoveFunc(f func(r rune) bool) Transformer { + return removeF(f) +} + +type removeF func(r rune) bool + +func (removeF) Reset() {} + +// Transform implements the Transformer interface. +func (t removeF) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + for r, sz := rune(0), 0; len(src) > 0; src = src[sz:] { + + if r = rune(src[0]); r < utf8.RuneSelf { + sz = 1 + } else { + r, sz = utf8.DecodeRune(src) + + if sz == 1 { + // Invalid rune. + if !atEOF && !utf8.FullRune(src) { + err = ErrShortSrc + break + } + // We replace illegal bytes with RuneError. Not doing so might + // otherwise turn a sequence of invalid UTF-8 into valid UTF-8. + // The resulting byte sequence may subsequently contain runes + // for which t(r) is true that were passed unnoticed. + if !t(r) { + if nDst+3 > len(dst) { + err = ErrShortDst + break + } + nDst += copy(dst[nDst:], "\uFFFD") + } + nSrc++ + continue + } + } + + if !t(r) { + if nDst+sz > len(dst) { + err = ErrShortDst + break + } + nDst += copy(dst[nDst:], src[:sz]) + } + nSrc += sz + } + return +} + +// grow returns a new []byte that is longer than b, and copies the first n bytes +// of b to the start of the new slice. +func grow(b []byte, n int) []byte { + m := len(b) + if m <= 32 { + m = 64 + } else if m <= 256 { + m *= 2 + } else { + m += m >> 1 + } + buf := make([]byte, m) + copy(buf, b[:n]) + return buf +} + +const initialBufSize = 128 + +// String returns a string with the result of converting s[:n] using t, where +// n <= len(s). If err == nil, n will be len(s). It calls Reset on t. +func String(t Transformer, s string) (result string, n int, err error) { + t.Reset() + if s == "" { + // Fast path for the common case for empty input. Results in about a + // 86% reduction of running time for BenchmarkStringLowerEmpty. + if _, _, err := t.Transform(nil, nil, true); err == nil { + return "", 0, nil + } + } + + // Allocate only once. Note that both dst and src escape when passed to + // Transform. + buf := [2 * initialBufSize]byte{} + dst := buf[:initialBufSize:initialBufSize] + src := buf[initialBufSize : 2*initialBufSize] + + // The input string s is transformed in multiple chunks (starting with a + // chunk size of initialBufSize). nDst and nSrc are per-chunk (or + // per-Transform-call) indexes, pDst and pSrc are overall indexes. + nDst, nSrc := 0, 0 + pDst, pSrc := 0, 0 + + // pPrefix is the length of a common prefix: the first pPrefix bytes of the + // result will equal the first pPrefix bytes of s. It is not guaranteed to + // be the largest such value, but if pPrefix, len(result) and len(s) are + // all equal after the final transform (i.e. calling Transform with atEOF + // being true returned nil error) then we don't need to allocate a new + // result string. + pPrefix := 0 + for { + // Invariant: pDst == pPrefix && pSrc == pPrefix. + + n := copy(src, s[pSrc:]) + nDst, nSrc, err = t.Transform(dst, src[:n], pSrc+n == len(s)) + pDst += nDst + pSrc += nSrc + + // TODO: let transformers implement an optional Spanner interface, akin + // to norm's QuickSpan. This would even allow us to avoid any allocation. + if !bytes.Equal(dst[:nDst], src[:nSrc]) { + break + } + pPrefix = pSrc + if err == ErrShortDst { + // A buffer can only be short if a transformer modifies its input. + break + } else if err == ErrShortSrc { + if nSrc == 0 { + // No progress was made. + break + } + // Equal so far and !atEOF, so continue checking. + } else if err != nil || pPrefix == len(s) { + return string(s[:pPrefix]), pPrefix, err + } + } + // Post-condition: pDst == pPrefix + nDst && pSrc == pPrefix + nSrc. + + // We have transformed the first pSrc bytes of the input s to become pDst + // transformed bytes. Those transformed bytes are discontiguous: the first + // pPrefix of them equal s[:pPrefix] and the last nDst of them equal + // dst[:nDst]. We copy them around, into a new dst buffer if necessary, so + // that they become one contiguous slice: dst[:pDst]. + if pPrefix != 0 { + newDst := dst + if pDst > len(newDst) { + newDst = make([]byte, len(s)+nDst-nSrc) + } + copy(newDst[pPrefix:pDst], dst[:nDst]) + copy(newDst[:pPrefix], s[:pPrefix]) + dst = newDst + } + + // Prevent duplicate Transform calls with atEOF being true at the end of + // the input. Also return if we have an unrecoverable error. + if (err == nil && pSrc == len(s)) || + (err != nil && err != ErrShortDst && err != ErrShortSrc) { + return string(dst[:pDst]), pSrc, err + } + + // Transform the remaining input, growing dst and src buffers as necessary. + for { + n := copy(src, s[pSrc:]) + nDst, nSrc, err := t.Transform(dst[pDst:], src[:n], pSrc+n == len(s)) + pDst += nDst + pSrc += nSrc + + // If we got ErrShortDst or ErrShortSrc, do not grow as long as we can + // make progress. This may avoid excessive allocations. + if err == ErrShortDst { + if nDst == 0 { + dst = grow(dst, pDst) + } + } else if err == ErrShortSrc { + if nSrc == 0 { + src = grow(src, 0) + } + } else if err != nil || pSrc == len(s) { + return string(dst[:pDst]), pSrc, err + } + } +} + +// Bytes returns a new byte slice with the result of converting b[:n] using t, +// where n <= len(b). If err == nil, n will be len(b). It calls Reset on t. +func Bytes(t Transformer, b []byte) (result []byte, n int, err error) { + return doAppend(t, 0, make([]byte, len(b)), b) +} + +// Append appends the result of converting src[:n] using t to dst, where +// n <= len(src), If err == nil, n will be len(src). It calls Reset on t. +func Append(t Transformer, dst, src []byte) (result []byte, n int, err error) { + if len(dst) == cap(dst) { + n := len(src) + len(dst) // It is okay for this to be 0. + b := make([]byte, n) + dst = b[:copy(b, dst)] + } + return doAppend(t, len(dst), dst[:cap(dst)], src) +} + +func doAppend(t Transformer, pDst int, dst, src []byte) (result []byte, n int, err error) { + t.Reset() + pSrc := 0 + for { + nDst, nSrc, err := t.Transform(dst[pDst:], src[pSrc:], true) + pDst += nDst + pSrc += nSrc + if err != ErrShortDst { + return dst[:pDst], pSrc, err + } + + // Grow the destination buffer, but do not grow as long as we can make + // progress. This may avoid excessive allocations. + if nDst == 0 { + dst = grow(dst, pDst) + } + } +} diff --git a/vendor/golang.org/x/text/unicode/cldr/base.go b/vendor/golang.org/x/text/unicode/cldr/base.go new file mode 100644 index 000000000..2382f4d6d --- /dev/null +++ b/vendor/golang.org/x/text/unicode/cldr/base.go @@ -0,0 +1,100 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cldr + +import ( + "encoding/xml" + "regexp" + "strconv" +) + +// Elem is implemented by every XML element. +type Elem interface { + setEnclosing(Elem) + setName(string) + enclosing() Elem + + GetCommon() *Common +} + +type hidden struct { + CharData string `xml:",chardata"` + Alias *struct { + Common + Source string `xml:"source,attr"` + Path string `xml:"path,attr"` + } `xml:"alias"` + Def *struct { + Common + Choice string `xml:"choice,attr,omitempty"` + Type string `xml:"type,attr,omitempty"` + } `xml:"default"` +} + +// Common holds several of the most common attributes and sub elements +// of an XML element. +type Common struct { + XMLName xml.Name + name string + enclElem Elem + Type string `xml:"type,attr,omitempty"` + Reference string `xml:"reference,attr,omitempty"` + Alt string `xml:"alt,attr,omitempty"` + ValidSubLocales string `xml:"validSubLocales,attr,omitempty"` + Draft string `xml:"draft,attr,omitempty"` + hidden +} + +// Default returns the default type to select from the enclosed list +// or "" if no default value is specified. +func (e *Common) Default() string { + if e.Def == nil { + return "" + } + if e.Def.Choice != "" { + return e.Def.Choice + } else if e.Def.Type != "" { + // Type is still used by the default element in collation. + return e.Def.Type + } + return "" +} + +// GetCommon returns e. It is provided such that Common implements Elem. +func (e *Common) GetCommon() *Common { + return e +} + +// Data returns the character data accumulated for this element. +func (e *Common) Data() string { + e.CharData = charRe.ReplaceAllStringFunc(e.CharData, replaceUnicode) + return e.CharData +} + +func (e *Common) setName(s string) { + e.name = s +} + +func (e *Common) enclosing() Elem { + return e.enclElem +} + +func (e *Common) setEnclosing(en Elem) { + e.enclElem = en +} + +// Escape characters that can be escaped without further escaping the string. +var charRe = regexp.MustCompile(`&#x[0-9a-fA-F]*;|\\u[0-9a-fA-F]{4}|\\U[0-9a-fA-F]{8}|\\x[0-9a-fA-F]{2}|\\[0-7]{3}|\\[abtnvfr]`) + +// replaceUnicode converts hexadecimal Unicode codepoint notations to a one-rune string. +// It assumes the input string is correctly formatted. +func replaceUnicode(s string) string { + if s[1] == '#' { + r, _ := strconv.ParseInt(s[3:len(s)-1], 16, 32) + return string(r) + } + r, _, _, _ := strconv.UnquoteChar(s, 0) + return string(r) +} diff --git a/vendor/golang.org/x/text/unicode/cldr/cldr.go b/vendor/golang.org/x/text/unicode/cldr/cldr.go new file mode 100644 index 000000000..2197f8ac2 --- /dev/null +++ b/vendor/golang.org/x/text/unicode/cldr/cldr.go @@ -0,0 +1,130 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:generate go run makexml.go -output xml.go + +// Package cldr provides a parser for LDML and related XML formats. +// This package is intended to be used by the table generation tools +// for the various internationalization-related packages. +// As the XML types are generated from the CLDR DTD, and as the CLDR standard +// is periodically amended, this package may change considerably over time. +// This mostly means that data may appear and disappear between versions. +// That is, old code should keep compiling for newer versions, but data +// may have moved or changed. +// CLDR version 22 is the first version supported by this package. +// Older versions may not work. +package cldr // import "golang.org/x/text/unicode/cldr" + +import ( + "fmt" + "sort" +) + +// CLDR provides access to parsed data of the Unicode Common Locale Data Repository. +type CLDR struct { + parent map[string][]string + locale map[string]*LDML + resolved map[string]*LDML + bcp47 *LDMLBCP47 + supp *SupplementalData +} + +func makeCLDR() *CLDR { + return &CLDR{ + parent: make(map[string][]string), + locale: make(map[string]*LDML), + resolved: make(map[string]*LDML), + bcp47: &LDMLBCP47{}, + supp: &SupplementalData{}, + } +} + +// BCP47 returns the parsed BCP47 LDML data. If no such data was parsed, nil is returned. +func (cldr *CLDR) BCP47() *LDMLBCP47 { + return nil +} + +// Draft indicates the draft level of an element. +type Draft int + +const ( + Approved Draft = iota + Contributed + Provisional + Unconfirmed +) + +var drafts = []string{"unconfirmed", "provisional", "contributed", "approved", ""} + +// ParseDraft returns the Draft value corresponding to the given string. The +// empty string corresponds to Approved. +func ParseDraft(level string) (Draft, error) { + if level == "" { + return Approved, nil + } + for i, s := range drafts { + if level == s { + return Unconfirmed - Draft(i), nil + } + } + return Approved, fmt.Errorf("cldr: unknown draft level %q", level) +} + +func (d Draft) String() string { + return drafts[len(drafts)-1-int(d)] +} + +// SetDraftLevel sets which draft levels to include in the evaluated LDML. +// Any draft element for which the draft level is higher than lev will be excluded. +// If multiple draft levels are available for a single element, the one with the +// lowest draft level will be selected, unless preferDraft is true, in which case +// the highest draft will be chosen. +// It is assumed that the underlying LDML is canonicalized. +func (cldr *CLDR) SetDraftLevel(lev Draft, preferDraft bool) { + // TODO: implement + cldr.resolved = make(map[string]*LDML) +} + +// RawLDML returns the LDML XML for id in unresolved form. +// id must be one of the strings returned by Locales. +func (cldr *CLDR) RawLDML(loc string) *LDML { + return cldr.locale[loc] +} + +// LDML returns the fully resolved LDML XML for loc, which must be one of +// the strings returned by Locales. +func (cldr *CLDR) LDML(loc string) (*LDML, error) { + return cldr.resolve(loc) +} + +// Supplemental returns the parsed supplemental data. If no such data was parsed, +// nil is returned. +func (cldr *CLDR) Supplemental() *SupplementalData { + return cldr.supp +} + +// Locales returns the locales for which there exist files. +// Valid sublocales for which there is no file are not included. +// The root locale is always sorted first. +func (cldr *CLDR) Locales() []string { + loc := []string{"root"} + hasRoot := false + for l, _ := range cldr.locale { + if l == "root" { + hasRoot = true + continue + } + loc = append(loc, l) + } + sort.Strings(loc[1:]) + if !hasRoot { + return loc[1:] + } + return loc +} + +// Get fills in the fields of x based on the XPath path. +func Get(e Elem, path string) (res Elem, err error) { + return walkXPath(e, path) +} diff --git a/vendor/golang.org/x/text/unicode/cldr/collate.go b/vendor/golang.org/x/text/unicode/cldr/collate.go new file mode 100644 index 000000000..80ee28d79 --- /dev/null +++ b/vendor/golang.org/x/text/unicode/cldr/collate.go @@ -0,0 +1,359 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cldr + +import ( + "bufio" + "encoding/xml" + "errors" + "fmt" + "strconv" + "strings" + "unicode" + "unicode/utf8" +) + +// RuleProcessor can be passed to Collator's Process method, which +// parses the rules and calls the respective method for each rule found. +type RuleProcessor interface { + Reset(anchor string, before int) error + Insert(level int, str, context, extend string) error + Index(id string) +} + +const ( + // cldrIndex is a Unicode-reserved sentinel value used to mark the start + // of a grouping within an index. + // We ignore any rule that starts with this rune. + // See http://unicode.org/reports/tr35/#Collation_Elements for details. + cldrIndex = "\uFDD0" + + // specialAnchor is the format in which to represent logical reset positions, + // such as "first tertiary ignorable". + specialAnchor = "<%s/>" +) + +// Process parses the rules for the tailorings of this collation +// and calls the respective methods of p for each rule found. +func (c Collation) Process(p RuleProcessor) (err error) { + if len(c.Cr) > 0 { + if len(c.Cr) > 1 { + return fmt.Errorf("multiple cr elements, want 0 or 1") + } + return processRules(p, c.Cr[0].Data()) + } + if c.Rules.Any != nil { + return c.processXML(p) + } + return errors.New("no tailoring data") +} + +// processRules parses rules in the Collation Rule Syntax defined in +// http://www.unicode.org/reports/tr35/tr35-collation.html#Collation_Tailorings. +func processRules(p RuleProcessor, s string) (err error) { + chk := func(s string, e error) string { + if err == nil { + err = e + } + return s + } + i := 0 // Save the line number for use after the loop. + scanner := bufio.NewScanner(strings.NewReader(s)) + for ; scanner.Scan() && err == nil; i++ { + for s := skipSpace(scanner.Text()); s != "" && s[0] != '#'; s = skipSpace(s) { + level := 5 + var ch byte + switch ch, s = s[0], s[1:]; ch { + case '&': // followed by <anchor> or '[' <key> ']' + if s = skipSpace(s); consume(&s, '[') { + s = chk(parseSpecialAnchor(p, s)) + } else { + s = chk(parseAnchor(p, 0, s)) + } + case '<': // sort relation '<'{1,4}, optionally followed by '*'. + for level = 1; consume(&s, '<'); level++ { + } + if level > 4 { + err = fmt.Errorf("level %d > 4", level) + } + fallthrough + case '=': // identity relation, optionally followed by *. + if consume(&s, '*') { + s = chk(parseSequence(p, level, s)) + } else { + s = chk(parseOrder(p, level, s)) + } + default: + chk("", fmt.Errorf("illegal operator %q", ch)) + break + } + } + } + if chk("", scanner.Err()); err != nil { + return fmt.Errorf("%d: %v", i, err) + } + return nil +} + +// parseSpecialAnchor parses the anchor syntax which is either of the form +// ['before' <level>] <anchor> +// or +// [<label>] +// The starting should already be consumed. +func parseSpecialAnchor(p RuleProcessor, s string) (tail string, err error) { + i := strings.IndexByte(s, ']') + if i == -1 { + return "", errors.New("unmatched bracket") + } + a := strings.TrimSpace(s[:i]) + s = s[i+1:] + if strings.HasPrefix(a, "before ") { + l, err := strconv.ParseUint(skipSpace(a[len("before "):]), 10, 3) + if err != nil { + return s, err + } + return parseAnchor(p, int(l), s) + } + return s, p.Reset(fmt.Sprintf(specialAnchor, a), 0) +} + +func parseAnchor(p RuleProcessor, level int, s string) (tail string, err error) { + anchor, s, err := scanString(s) + if err != nil { + return s, err + } + return s, p.Reset(anchor, level) +} + +func parseOrder(p RuleProcessor, level int, s string) (tail string, err error) { + var value, context, extend string + if value, s, err = scanString(s); err != nil { + return s, err + } + if strings.HasPrefix(value, cldrIndex) { + p.Index(value[len(cldrIndex):]) + return + } + if consume(&s, '|') { + if context, s, err = scanString(s); err != nil { + return s, errors.New("missing string after context") + } + } + if consume(&s, '/') { + if extend, s, err = scanString(s); err != nil { + return s, errors.New("missing string after extension") + } + } + return s, p.Insert(level, value, context, extend) +} + +// scanString scans a single input string. +func scanString(s string) (str, tail string, err error) { + if s = skipSpace(s); s == "" { + return s, s, errors.New("missing string") + } + buf := [16]byte{} // small but enough to hold most cases. + value := buf[:0] + for s != "" { + if consume(&s, '\'') { + i := strings.IndexByte(s, '\'') + if i == -1 { + return "", "", errors.New(`unmatched single quote`) + } + if i == 0 { + value = append(value, '\'') + } else { + value = append(value, s[:i]...) + } + s = s[i+1:] + continue + } + r, sz := utf8.DecodeRuneInString(s) + if unicode.IsSpace(r) || strings.ContainsRune("&<=#", r) { + break + } + value = append(value, s[:sz]...) + s = s[sz:] + } + return string(value), skipSpace(s), nil +} + +func parseSequence(p RuleProcessor, level int, s string) (tail string, err error) { + if s = skipSpace(s); s == "" { + return s, errors.New("empty sequence") + } + last := rune(0) + for s != "" { + r, sz := utf8.DecodeRuneInString(s) + s = s[sz:] + + if r == '-' { + // We have a range. The first element was already written. + if last == 0 { + return s, errors.New("range without starter value") + } + r, sz = utf8.DecodeRuneInString(s) + s = s[sz:] + if r == utf8.RuneError || r < last { + return s, fmt.Errorf("invalid range %q-%q", last, r) + } + for i := last + 1; i <= r; i++ { + if err := p.Insert(level, string(i), "", ""); err != nil { + return s, err + } + } + last = 0 + continue + } + + if unicode.IsSpace(r) || unicode.IsPunct(r) { + break + } + + // normal case + if err := p.Insert(level, string(r), "", ""); err != nil { + return s, err + } + last = r + } + return s, nil +} + +func skipSpace(s string) string { + return strings.TrimLeftFunc(s, unicode.IsSpace) +} + +// consumes returns whether the next byte is ch. If so, it gobbles it by +// updating s. +func consume(s *string, ch byte) (ok bool) { + if *s == "" || (*s)[0] != ch { + return false + } + *s = (*s)[1:] + return true +} + +// The following code parses Collation rules of CLDR version 24 and before. + +var lmap = map[byte]int{ + 'p': 1, + 's': 2, + 't': 3, + 'i': 5, +} + +type rulesElem struct { + Rules struct { + Common + Any []*struct { + XMLName xml.Name + rule + } `xml:",any"` + } `xml:"rules"` +} + +type rule struct { + Value string `xml:",chardata"` + Before string `xml:"before,attr"` + Any []*struct { + XMLName xml.Name + rule + } `xml:",any"` +} + +var emptyValueError = errors.New("cldr: empty rule value") + +func (r *rule) value() (string, error) { + // Convert hexadecimal Unicode codepoint notation to a string. + s := charRe.ReplaceAllStringFunc(r.Value, replaceUnicode) + r.Value = s + if s == "" { + if len(r.Any) != 1 { + return "", emptyValueError + } + r.Value = fmt.Sprintf(specialAnchor, r.Any[0].XMLName.Local) + r.Any = nil + } else if len(r.Any) != 0 { + return "", fmt.Errorf("cldr: XML elements found in collation rule: %v", r.Any) + } + return r.Value, nil +} + +func (r rule) process(p RuleProcessor, name, context, extend string) error { + v, err := r.value() + if err != nil { + return err + } + switch name { + case "p", "s", "t", "i": + if strings.HasPrefix(v, cldrIndex) { + p.Index(v[len(cldrIndex):]) + return nil + } + if err := p.Insert(lmap[name[0]], v, context, extend); err != nil { + return err + } + case "pc", "sc", "tc", "ic": + level := lmap[name[0]] + for _, s := range v { + if err := p.Insert(level, string(s), context, extend); err != nil { + return err + } + } + default: + return fmt.Errorf("cldr: unsupported tag: %q", name) + } + return nil +} + +// processXML parses the format of CLDR versions 24 and older. +func (c Collation) processXML(p RuleProcessor) (err error) { + // Collation is generated and defined in xml.go. + var v string + for _, r := range c.Rules.Any { + switch r.XMLName.Local { + case "reset": + level := 0 + switch r.Before { + case "primary", "1": + level = 1 + case "secondary", "2": + level = 2 + case "tertiary", "3": + level = 3 + case "": + default: + return fmt.Errorf("cldr: unknown level %q", r.Before) + } + v, err = r.value() + if err == nil { + err = p.Reset(v, level) + } + case "x": + var context, extend string + for _, r1 := range r.Any { + v, err = r1.value() + switch r1.XMLName.Local { + case "context": + context = v + case "extend": + extend = v + } + } + for _, r1 := range r.Any { + if t := r1.XMLName.Local; t == "context" || t == "extend" { + continue + } + r1.rule.process(p, r1.XMLName.Local, context, extend) + } + default: + err = r.rule.process(p, r.XMLName.Local, "", "") + } + if err != nil { + return err + } + } + return nil +} diff --git a/vendor/golang.org/x/text/unicode/cldr/decode.go b/vendor/golang.org/x/text/unicode/cldr/decode.go new file mode 100644 index 000000000..e5ee4aed1 --- /dev/null +++ b/vendor/golang.org/x/text/unicode/cldr/decode.go @@ -0,0 +1,171 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cldr + +import ( + "archive/zip" + "bytes" + "encoding/xml" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "path/filepath" + "regexp" +) + +// A Decoder loads an archive of CLDR data. +type Decoder struct { + dirFilter []string + sectionFilter []string + loader Loader + cldr *CLDR + curLocale string +} + +// SetSectionFilter takes a list top-level LDML element names to which +// evaluation of LDML should be limited. It automatically calls SetDirFilter. +func (d *Decoder) SetSectionFilter(filter ...string) { + d.sectionFilter = filter + // TODO: automatically set dir filter +} + +// SetDirFilter limits the loading of LDML XML files of the specied directories. +// Note that sections may be split across directories differently for different CLDR versions. +// For more robust code, use SetSectionFilter. +func (d *Decoder) SetDirFilter(dir ...string) { + d.dirFilter = dir +} + +// A Loader provides access to the files of a CLDR archive. +type Loader interface { + Len() int + Path(i int) string + Reader(i int) (io.ReadCloser, error) +} + +var fileRe = regexp.MustCompile(".*/(.*)/(.*)\\.xml") + +// Decode loads and decodes the files represented by l. +func (d *Decoder) Decode(l Loader) (cldr *CLDR, err error) { + d.cldr = makeCLDR() + for i := 0; i < l.Len(); i++ { + fname := l.Path(i) + if m := fileRe.FindStringSubmatch(fname); m != nil { + if len(d.dirFilter) > 0 && !in(d.dirFilter, m[1]) { + continue + } + var r io.Reader + if r, err = l.Reader(i); err == nil { + err = d.decode(m[1], m[2], r) + } + if err != nil { + return nil, err + } + } + } + d.cldr.finalize(d.sectionFilter) + return d.cldr, nil +} + +func (d *Decoder) decode(dir, id string, r io.Reader) error { + var v interface{} + var l *LDML + cldr := d.cldr + switch { + case dir == "supplemental": + v = cldr.supp + case dir == "transforms": + return nil + case dir == "bcp47": + v = cldr.bcp47 + case dir == "validity": + return nil + default: + ok := false + if v, ok = cldr.locale[id]; !ok { + l = &LDML{} + v, cldr.locale[id] = l, l + } + } + x := xml.NewDecoder(r) + if err := x.Decode(v); err != nil { + log.Printf("%s/%s: %v", dir, id, err) + return err + } + if l != nil { + if l.Identity == nil { + return fmt.Errorf("%s/%s: missing identity element", dir, id) + } + // TODO: verify when CLDR bug http://unicode.org/cldr/trac/ticket/8970 + // is resolved. + // path := strings.Split(id, "_") + // if lang := l.Identity.Language.Type; lang != path[0] { + // return fmt.Errorf("%s/%s: language was %s; want %s", dir, id, lang, path[0]) + // } + } + return nil +} + +type pathLoader []string + +func makePathLoader(path string) (pl pathLoader, err error) { + err = filepath.Walk(path, func(path string, _ os.FileInfo, err error) error { + pl = append(pl, path) + return err + }) + return pl, err +} + +func (pl pathLoader) Len() int { + return len(pl) +} + +func (pl pathLoader) Path(i int) string { + return pl[i] +} + +func (pl pathLoader) Reader(i int) (io.ReadCloser, error) { + return os.Open(pl[i]) +} + +// DecodePath loads CLDR data from the given path. +func (d *Decoder) DecodePath(path string) (cldr *CLDR, err error) { + loader, err := makePathLoader(path) + if err != nil { + return nil, err + } + return d.Decode(loader) +} + +type zipLoader struct { + r *zip.Reader +} + +func (zl zipLoader) Len() int { + return len(zl.r.File) +} + +func (zl zipLoader) Path(i int) string { + return zl.r.File[i].Name +} + +func (zl zipLoader) Reader(i int) (io.ReadCloser, error) { + return zl.r.File[i].Open() +} + +// DecodeZip loads CLDR data from the zip archive for which r is the source. +func (d *Decoder) DecodeZip(r io.Reader) (cldr *CLDR, err error) { + buffer, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + archive, err := zip.NewReader(bytes.NewReader(buffer), int64(len(buffer))) + if err != nil { + return nil, err + } + return d.Decode(zipLoader{archive}) +} diff --git a/vendor/golang.org/x/text/unicode/cldr/makexml.go b/vendor/golang.org/x/text/unicode/cldr/makexml.go new file mode 100644 index 000000000..6114d01cb --- /dev/null +++ b/vendor/golang.org/x/text/unicode/cldr/makexml.go @@ -0,0 +1,400 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build ignore + +// This tool generates types for the various XML formats of CLDR. +package main + +import ( + "archive/zip" + "bytes" + "encoding/xml" + "flag" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "regexp" + "strings" + + "golang.org/x/text/internal/gen" +) + +var outputFile = flag.String("output", "xml.go", "output file name") + +func main() { + flag.Parse() + + r := gen.OpenCLDRCoreZip() + buffer, err := ioutil.ReadAll(r) + if err != nil { + log.Fatal("Could not read zip file") + } + r.Close() + z, err := zip.NewReader(bytes.NewReader(buffer), int64(len(buffer))) + if err != nil { + log.Fatalf("Could not read zip archive: %v", err) + } + + var buf bytes.Buffer + + version := gen.CLDRVersion() + + for _, dtd := range files { + for _, f := range z.File { + if strings.HasSuffix(f.Name, dtd.file+".dtd") { + r, err := f.Open() + failOnError(err) + + b := makeBuilder(&buf, dtd) + b.parseDTD(r) + b.resolve(b.index[dtd.top[0]]) + b.write() + if b.version != "" && version != b.version { + println(f.Name) + log.Fatalf("main: inconsistent versions: found %s; want %s", b.version, version) + } + break + } + } + } + fmt.Fprintln(&buf, "// Version is the version of CLDR from which the XML definitions are generated.") + fmt.Fprintf(&buf, "const Version = %q\n", version) + + gen.WriteGoFile(*outputFile, "cldr", buf.Bytes()) +} + +func failOnError(err error) { + if err != nil { + log.New(os.Stderr, "", log.Lshortfile).Output(2, err.Error()) + os.Exit(1) + } +} + +// configuration data per DTD type +type dtd struct { + file string // base file name + root string // Go name of the root XML element + top []string // create a different type for this section + + skipElem []string // hard-coded or deprecated elements + skipAttr []string // attributes to exclude + predefined []string // hard-coded elements exist of the form <name>Elem + forceRepeat []string // elements to make slices despite DTD +} + +var files = []dtd{ + { + file: "ldmlBCP47", + root: "LDMLBCP47", + top: []string{"ldmlBCP47"}, + skipElem: []string{ + "cldrVersion", // deprecated, not used + }, + }, + { + file: "ldmlSupplemental", + root: "SupplementalData", + top: []string{"supplementalData"}, + skipElem: []string{ + "cldrVersion", // deprecated, not used + }, + forceRepeat: []string{ + "plurals", // data defined in plurals.xml and ordinals.xml + }, + }, + { + file: "ldml", + root: "LDML", + top: []string{ + "ldml", "collation", "calendar", "timeZoneNames", "localeDisplayNames", "numbers", + }, + skipElem: []string{ + "cp", // not used anywhere + "special", // not used anywhere + "fallback", // deprecated, not used + "alias", // in Common + "default", // in Common + }, + skipAttr: []string{ + "hiraganaQuarternary", // typo in DTD, correct version included as well + }, + predefined: []string{"rules"}, + }, +} + +var comments = map[string]string{ + "ldmlBCP47": ` +// LDMLBCP47 holds information on allowable values for various variables in LDML. +`, + "supplementalData": ` +// SupplementalData holds information relevant for internationalization +// and proper use of CLDR, but that is not contained in the locale hierarchy. +`, + "ldml": ` +// LDML is the top-level type for locale-specific data. +`, + "collation": ` +// Collation contains rules that specify a certain sort-order, +// as a tailoring of the root order. +// The parsed rules are obtained by passing a RuleProcessor to Collation's +// Process method. +`, + "calendar": ` +// Calendar specifies the fields used for formatting and parsing dates and times. +// The month and quarter names are identified numerically, starting at 1. +// The day (of the week) names are identified with short strings, since there is +// no universally-accepted numeric designation. +`, + "dates": ` +// Dates contains information regarding the format and parsing of dates and times. +`, + "localeDisplayNames": ` +// LocaleDisplayNames specifies localized display names for for scripts, languages, +// countries, currencies, and variants. +`, + "numbers": ` +// Numbers supplies information for formatting and parsing numbers and currencies. +`, +} + +type element struct { + name string // XML element name + category string // elements contained by this element + signature string // category + attrKey* + + attr []*attribute // attributes supported by this element. + sub []struct { // parsed and evaluated sub elements of this element. + e *element + repeat bool // true if the element needs to be a slice + } + + resolved bool // prevent multiple resolutions of this element. +} + +type attribute struct { + name string + key string + list []string + + tag string // Go tag +} + +var ( + reHead = regexp.MustCompile(` *(\w+) +([\w\-]+)`) + reAttr = regexp.MustCompile(` *(\w+) *(?:(\w+)|\(([\w\- \|]+)\)) *(?:#([A-Z]*) *(?:\"([\.\d+])\")?)? *("[\w\-:]*")?`) + reElem = regexp.MustCompile(`^ *(EMPTY|ANY|\(.*\)[\*\+\?]?) *$`) + reToken = regexp.MustCompile(`\w\-`) +) + +// builder is used to read in the DTD files from CLDR and generate Go code +// to be used with the encoding/xml package. +type builder struct { + w io.Writer + index map[string]*element + elem []*element + info dtd + version string +} + +func makeBuilder(w io.Writer, d dtd) builder { + return builder{ + w: w, + index: make(map[string]*element), + elem: []*element{}, + info: d, + } +} + +// parseDTD parses a DTD file. +func (b *builder) parseDTD(r io.Reader) { + for d := xml.NewDecoder(r); ; { + t, err := d.Token() + if t == nil { + break + } + failOnError(err) + dir, ok := t.(xml.Directive) + if !ok { + continue + } + m := reHead.FindSubmatch(dir) + dir = dir[len(m[0]):] + ename := string(m[2]) + el, elementFound := b.index[ename] + switch string(m[1]) { + case "ELEMENT": + if elementFound { + log.Fatal("parseDTD: duplicate entry for element %q", ename) + } + m := reElem.FindSubmatch(dir) + if m == nil { + log.Fatalf("parseDTD: invalid element %q", string(dir)) + } + if len(m[0]) != len(dir) { + log.Fatal("parseDTD: invalid element %q", string(dir), len(dir), len(m[0]), string(m[0])) + } + s := string(m[1]) + el = &element{ + name: ename, + category: s, + } + b.index[ename] = el + case "ATTLIST": + if !elementFound { + log.Fatalf("parseDTD: unknown element %q", ename) + } + s := string(dir) + m := reAttr.FindStringSubmatch(s) + if m == nil { + log.Fatal(fmt.Errorf("parseDTD: invalid attribute %q", string(dir))) + } + if m[4] == "FIXED" { + b.version = m[5] + } else { + switch m[1] { + case "draft", "references", "alt", "validSubLocales", "standard" /* in Common */ : + case "type", "choice": + default: + el.attr = append(el.attr, &attribute{ + name: m[1], + key: s, + list: reToken.FindAllString(m[3], -1), + }) + el.signature = fmt.Sprintf("%s=%s+%s", el.signature, m[1], m[2]) + } + } + } + } +} + +var reCat = regexp.MustCompile(`[ ,\|]*(?:(\(|\)|\#?[\w_-]+)([\*\+\?]?))?`) + +// resolve takes a parsed element and converts it into structured data +// that can be used to generate the XML code. +func (b *builder) resolve(e *element) { + if e.resolved { + return + } + b.elem = append(b.elem, e) + e.resolved = true + s := e.category + found := make(map[string]bool) + sequenceStart := []int{} + for len(s) > 0 { + m := reCat.FindStringSubmatch(s) + if m == nil { + log.Fatalf("%s: invalid category string %q", e.name, s) + } + repeat := m[2] == "*" || m[2] == "+" || in(b.info.forceRepeat, m[1]) + switch m[1] { + case "": + case "(": + sequenceStart = append(sequenceStart, len(e.sub)) + case ")": + if len(sequenceStart) == 0 { + log.Fatalf("%s: unmatched closing parenthesis", e.name) + } + for i := sequenceStart[len(sequenceStart)-1]; i < len(e.sub); i++ { + e.sub[i].repeat = e.sub[i].repeat || repeat + } + sequenceStart = sequenceStart[:len(sequenceStart)-1] + default: + if in(b.info.skipElem, m[1]) { + } else if sub, ok := b.index[m[1]]; ok { + if !found[sub.name] { + e.sub = append(e.sub, struct { + e *element + repeat bool + }{sub, repeat}) + found[sub.name] = true + b.resolve(sub) + } + } else if m[1] == "#PCDATA" || m[1] == "ANY" { + } else if m[1] != "EMPTY" { + log.Fatalf("resolve:%s: element %q not found", e.name, m[1]) + } + } + s = s[len(m[0]):] + } +} + +// return true if s is contained in set. +func in(set []string, s string) bool { + for _, v := range set { + if v == s { + return true + } + } + return false +} + +var repl = strings.NewReplacer("-", " ", "_", " ") + +// title puts the first character or each character following '_' in title case and +// removes all occurrences of '_'. +func title(s string) string { + return strings.Replace(strings.Title(repl.Replace(s)), " ", "", -1) +} + +// writeElem generates Go code for a single element, recursively. +func (b *builder) writeElem(tab int, e *element) { + p := func(f string, x ...interface{}) { + f = strings.Replace(f, "\n", "\n"+strings.Repeat("\t", tab), -1) + fmt.Fprintf(b.w, f, x...) + } + if len(e.sub) == 0 && len(e.attr) == 0 { + p("Common") + return + } + p("struct {") + tab++ + p("\nCommon") + for _, attr := range e.attr { + if !in(b.info.skipAttr, attr.name) { + p("\n%s string `xml:\"%s,attr\"`", title(attr.name), attr.name) + } + } + for _, sub := range e.sub { + if in(b.info.predefined, sub.e.name) { + p("\n%sElem", sub.e.name) + continue + } + if in(b.info.skipElem, sub.e.name) { + continue + } + p("\n%s ", title(sub.e.name)) + if sub.repeat { + p("[]") + } + p("*") + if in(b.info.top, sub.e.name) { + p(title(sub.e.name)) + } else { + b.writeElem(tab, sub.e) + } + p(" `xml:\"%s\"`", sub.e.name) + } + tab-- + p("\n}") +} + +// write generates the Go XML code. +func (b *builder) write() { + for i, name := range b.info.top { + e := b.index[name] + if e != nil { + fmt.Fprintf(b.w, comments[name]) + name := title(e.name) + if i == 0 { + name = b.info.root + } + fmt.Fprintf(b.w, "type %s ", name) + b.writeElem(0, e) + fmt.Fprint(b.w, "\n") + } + } +} diff --git a/vendor/golang.org/x/text/unicode/cldr/resolve.go b/vendor/golang.org/x/text/unicode/cldr/resolve.go new file mode 100644 index 000000000..691b5903f --- /dev/null +++ b/vendor/golang.org/x/text/unicode/cldr/resolve.go @@ -0,0 +1,602 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cldr + +// This file implements the various inheritance constructs defined by LDML. +// See http://www.unicode.org/reports/tr35/#Inheritance_and_Validity +// for more details. + +import ( + "fmt" + "log" + "reflect" + "regexp" + "sort" + "strings" +) + +// fieldIter iterates over fields in a struct. It includes +// fields of embedded structs. +type fieldIter struct { + v reflect.Value + index, n []int +} + +func iter(v reflect.Value) fieldIter { + if v.Kind() != reflect.Struct { + log.Panicf("value %v must be a struct", v) + } + i := fieldIter{ + v: v, + index: []int{0}, + n: []int{v.NumField()}, + } + i.descent() + return i +} + +func (i *fieldIter) descent() { + for f := i.field(); f.Anonymous && f.Type.NumField() > 0; f = i.field() { + i.index = append(i.index, 0) + i.n = append(i.n, f.Type.NumField()) + } +} + +func (i *fieldIter) done() bool { + return len(i.index) == 1 && i.index[0] >= i.n[0] +} + +func skip(f reflect.StructField) bool { + return !f.Anonymous && (f.Name[0] < 'A' || f.Name[0] > 'Z') +} + +func (i *fieldIter) next() { + for { + k := len(i.index) - 1 + i.index[k]++ + if i.index[k] < i.n[k] { + if !skip(i.field()) { + break + } + } else { + if k == 0 { + return + } + i.index = i.index[:k] + i.n = i.n[:k] + } + } + i.descent() +} + +func (i *fieldIter) value() reflect.Value { + return i.v.FieldByIndex(i.index) +} + +func (i *fieldIter) field() reflect.StructField { + return i.v.Type().FieldByIndex(i.index) +} + +type visitor func(v reflect.Value) error + +var stopDescent = fmt.Errorf("do not recurse") + +func (f visitor) visit(x interface{}) error { + return f.visitRec(reflect.ValueOf(x)) +} + +// visit recursively calls f on all nodes in v. +func (f visitor) visitRec(v reflect.Value) error { + if v.Kind() == reflect.Ptr { + if v.IsNil() { + return nil + } + return f.visitRec(v.Elem()) + } + if err := f(v); err != nil { + if err == stopDescent { + return nil + } + return err + } + switch v.Kind() { + case reflect.Struct: + for i := iter(v); !i.done(); i.next() { + if err := f.visitRec(i.value()); err != nil { + return err + } + } + case reflect.Slice: + for i := 0; i < v.Len(); i++ { + if err := f.visitRec(v.Index(i)); err != nil { + return err + } + } + } + return nil +} + +// getPath is used for error reporting purposes only. +func getPath(e Elem) string { + if e == nil { + return "<nil>" + } + if e.enclosing() == nil { + return e.GetCommon().name + } + if e.GetCommon().Type == "" { + return fmt.Sprintf("%s.%s", getPath(e.enclosing()), e.GetCommon().name) + } + return fmt.Sprintf("%s.%s[type=%s]", getPath(e.enclosing()), e.GetCommon().name, e.GetCommon().Type) +} + +// xmlName returns the xml name of the element or attribute +func xmlName(f reflect.StructField) (name string, attr bool) { + tags := strings.Split(f.Tag.Get("xml"), ",") + for _, s := range tags { + attr = attr || s == "attr" + } + return tags[0], attr +} + +func findField(v reflect.Value, key string) (reflect.Value, error) { + v = reflect.Indirect(v) + for i := iter(v); !i.done(); i.next() { + if n, _ := xmlName(i.field()); n == key { + return i.value(), nil + } + } + return reflect.Value{}, fmt.Errorf("cldr: no field %q in element %#v", key, v.Interface()) +} + +var xpathPart = regexp.MustCompile(`(\pL+)(?:\[@(\pL+)='([\w-]+)'\])?`) + +func walkXPath(e Elem, path string) (res Elem, err error) { + for _, c := range strings.Split(path, "/") { + if c == ".." { + if e = e.enclosing(); e == nil { + panic("path ..") + return nil, fmt.Errorf(`cldr: ".." moves past root in path %q`, path) + } + continue + } else if c == "" { + continue + } + m := xpathPart.FindStringSubmatch(c) + if len(m) == 0 || len(m[0]) != len(c) { + return nil, fmt.Errorf("cldr: syntax error in path component %q", c) + } + v, err := findField(reflect.ValueOf(e), m[1]) + if err != nil { + return nil, err + } + switch v.Kind() { + case reflect.Slice: + i := 0 + if m[2] != "" || v.Len() > 1 { + if m[2] == "" { + m[2] = "type" + if m[3] = e.GetCommon().Default(); m[3] == "" { + return nil, fmt.Errorf("cldr: type selector or default value needed for element %s", m[1]) + } + } + for ; i < v.Len(); i++ { + vi := v.Index(i) + key, err := findField(vi.Elem(), m[2]) + if err != nil { + return nil, err + } + key = reflect.Indirect(key) + if key.Kind() == reflect.String && key.String() == m[3] { + break + } + } + } + if i == v.Len() || v.Index(i).IsNil() { + return nil, fmt.Errorf("no %s found with %s==%s", m[1], m[2], m[3]) + } + e = v.Index(i).Interface().(Elem) + case reflect.Ptr: + if v.IsNil() { + return nil, fmt.Errorf("cldr: element %q not found within element %q", m[1], e.GetCommon().name) + } + var ok bool + if e, ok = v.Interface().(Elem); !ok { + return nil, fmt.Errorf("cldr: %q is not an XML element", m[1]) + } else if m[2] != "" || m[3] != "" { + return nil, fmt.Errorf("cldr: no type selector allowed for element %s", m[1]) + } + default: + return nil, fmt.Errorf("cldr: %q is not an XML element", m[1]) + } + } + return e, nil +} + +const absPrefix = "//ldml/" + +func (cldr *CLDR) resolveAlias(e Elem, src, path string) (res Elem, err error) { + if src != "locale" { + if !strings.HasPrefix(path, absPrefix) { + return nil, fmt.Errorf("cldr: expected absolute path, found %q", path) + } + path = path[len(absPrefix):] + if e, err = cldr.resolve(src); err != nil { + return nil, err + } + } + return walkXPath(e, path) +} + +func (cldr *CLDR) resolveAndMergeAlias(e Elem) error { + alias := e.GetCommon().Alias + if alias == nil { + return nil + } + a, err := cldr.resolveAlias(e, alias.Source, alias.Path) + if err != nil { + return fmt.Errorf("%v: error evaluating path %q: %v", getPath(e), alias.Path, err) + } + // Ensure alias node was already evaluated. TODO: avoid double evaluation. + err = cldr.resolveAndMergeAlias(a) + v := reflect.ValueOf(e).Elem() + for i := iter(reflect.ValueOf(a).Elem()); !i.done(); i.next() { + if vv := i.value(); vv.Kind() != reflect.Ptr || !vv.IsNil() { + if _, attr := xmlName(i.field()); !attr { + v.FieldByIndex(i.index).Set(vv) + } + } + } + return err +} + +func (cldr *CLDR) aliasResolver() visitor { + return func(v reflect.Value) (err error) { + if e, ok := v.Addr().Interface().(Elem); ok { + err = cldr.resolveAndMergeAlias(e) + if err == nil && blocking[e.GetCommon().name] { + return stopDescent + } + } + return err + } +} + +// elements within blocking elements do not inherit. +// Taken from CLDR's supplementalMetaData.xml. +var blocking = map[string]bool{ + "identity": true, + "supplementalData": true, + "cldrTest": true, + "collation": true, + "transform": true, +} + +// Distinguishing attributes affect inheritance; two elements with different +// distinguishing attributes are treated as different for purposes of inheritance, +// except when such attributes occur in the indicated elements. +// Taken from CLDR's supplementalMetaData.xml. +var distinguishing = map[string][]string{ + "key": nil, + "request_id": nil, + "id": nil, + "registry": nil, + "alt": nil, + "iso4217": nil, + "iso3166": nil, + "mzone": nil, + "from": nil, + "to": nil, + "type": []string{ + "abbreviationFallback", + "default", + "mapping", + "measurementSystem", + "preferenceOrdering", + }, + "numberSystem": nil, +} + +func in(set []string, s string) bool { + for _, v := range set { + if v == s { + return true + } + } + return false +} + +// attrKey computes a key based on the distinguishable attributes of +// an element and it's values. +func attrKey(v reflect.Value, exclude ...string) string { + parts := []string{} + ename := v.Interface().(Elem).GetCommon().name + v = v.Elem() + for i := iter(v); !i.done(); i.next() { + if name, attr := xmlName(i.field()); attr { + if except, ok := distinguishing[name]; ok && !in(exclude, name) && !in(except, ename) { + v := i.value() + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + if v.IsValid() { + parts = append(parts, fmt.Sprintf("%s=%s", name, v.String())) + } + } + } + } + sort.Strings(parts) + return strings.Join(parts, ";") +} + +// Key returns a key for e derived from all distinguishing attributes +// except those specified by exclude. +func Key(e Elem, exclude ...string) string { + return attrKey(reflect.ValueOf(e), exclude...) +} + +// linkEnclosing sets the enclosing element as well as the name +// for all sub-elements of child, recursively. +func linkEnclosing(parent, child Elem) { + child.setEnclosing(parent) + v := reflect.ValueOf(child).Elem() + for i := iter(v); !i.done(); i.next() { + vf := i.value() + if vf.Kind() == reflect.Slice { + for j := 0; j < vf.Len(); j++ { + linkEnclosing(child, vf.Index(j).Interface().(Elem)) + } + } else if vf.Kind() == reflect.Ptr && !vf.IsNil() && vf.Elem().Kind() == reflect.Struct { + linkEnclosing(child, vf.Interface().(Elem)) + } + } +} + +func setNames(e Elem, name string) { + e.setName(name) + v := reflect.ValueOf(e).Elem() + for i := iter(v); !i.done(); i.next() { + vf := i.value() + name, _ = xmlName(i.field()) + if vf.Kind() == reflect.Slice { + for j := 0; j < vf.Len(); j++ { + setNames(vf.Index(j).Interface().(Elem), name) + } + } else if vf.Kind() == reflect.Ptr && !vf.IsNil() && vf.Elem().Kind() == reflect.Struct { + setNames(vf.Interface().(Elem), name) + } + } +} + +// deepCopy copies elements of v recursively. All elements of v that may +// be modified by inheritance are explicitly copied. +func deepCopy(v reflect.Value) reflect.Value { + switch v.Kind() { + case reflect.Ptr: + if v.IsNil() || v.Elem().Kind() != reflect.Struct { + return v + } + nv := reflect.New(v.Elem().Type()) + nv.Elem().Set(v.Elem()) + deepCopyRec(nv.Elem(), v.Elem()) + return nv + case reflect.Slice: + nv := reflect.MakeSlice(v.Type(), v.Len(), v.Len()) + for i := 0; i < v.Len(); i++ { + deepCopyRec(nv.Index(i), v.Index(i)) + } + return nv + } + panic("deepCopy: must be called with pointer or slice") +} + +// deepCopyRec is only called by deepCopy. +func deepCopyRec(nv, v reflect.Value) { + if v.Kind() == reflect.Struct { + t := v.Type() + for i := 0; i < v.NumField(); i++ { + if name, attr := xmlName(t.Field(i)); name != "" && !attr { + deepCopyRec(nv.Field(i), v.Field(i)) + } + } + } else { + nv.Set(deepCopy(v)) + } +} + +// newNode is used to insert a missing node during inheritance. +func (cldr *CLDR) newNode(v, enc reflect.Value) reflect.Value { + n := reflect.New(v.Type()) + for i := iter(v); !i.done(); i.next() { + if name, attr := xmlName(i.field()); name == "" || attr { + n.Elem().FieldByIndex(i.index).Set(i.value()) + } + } + n.Interface().(Elem).GetCommon().setEnclosing(enc.Addr().Interface().(Elem)) + return n +} + +// v, parent must be pointers to struct +func (cldr *CLDR) inheritFields(v, parent reflect.Value) (res reflect.Value, err error) { + t := v.Type() + nv := reflect.New(t) + nv.Elem().Set(v) + for i := iter(v); !i.done(); i.next() { + vf := i.value() + f := i.field() + name, attr := xmlName(f) + if name == "" || attr { + continue + } + pf := parent.FieldByIndex(i.index) + if blocking[name] { + if vf.IsNil() { + vf = pf + } + nv.Elem().FieldByIndex(i.index).Set(deepCopy(vf)) + continue + } + switch f.Type.Kind() { + case reflect.Ptr: + if f.Type.Elem().Kind() == reflect.Struct { + if !vf.IsNil() { + if vf, err = cldr.inheritStructPtr(vf, pf); err != nil { + return reflect.Value{}, err + } + vf.Interface().(Elem).setEnclosing(nv.Interface().(Elem)) + nv.Elem().FieldByIndex(i.index).Set(vf) + } else if !pf.IsNil() { + n := cldr.newNode(pf.Elem(), v) + if vf, err = cldr.inheritStructPtr(n, pf); err != nil { + return reflect.Value{}, err + } + vf.Interface().(Elem).setEnclosing(nv.Interface().(Elem)) + nv.Elem().FieldByIndex(i.index).Set(vf) + } + } + case reflect.Slice: + vf, err := cldr.inheritSlice(nv.Elem(), vf, pf) + if err != nil { + return reflect.Zero(t), err + } + nv.Elem().FieldByIndex(i.index).Set(vf) + } + } + return nv, nil +} + +func root(e Elem) *LDML { + for ; e.enclosing() != nil; e = e.enclosing() { + } + return e.(*LDML) +} + +// inheritStructPtr first merges possible aliases in with v and then inherits +// any underspecified elements from parent. +func (cldr *CLDR) inheritStructPtr(v, parent reflect.Value) (r reflect.Value, err error) { + if !v.IsNil() { + e := v.Interface().(Elem).GetCommon() + alias := e.Alias + if alias == nil && !parent.IsNil() { + alias = parent.Interface().(Elem).GetCommon().Alias + } + if alias != nil { + a, err := cldr.resolveAlias(v.Interface().(Elem), alias.Source, alias.Path) + if a != nil { + if v, err = cldr.inheritFields(v.Elem(), reflect.ValueOf(a).Elem()); err != nil { + return reflect.Value{}, err + } + } + } + if !parent.IsNil() { + return cldr.inheritFields(v.Elem(), parent.Elem()) + } + } else if parent.IsNil() { + panic("should not reach here") + } + return v, nil +} + +// Must be slice of struct pointers. +func (cldr *CLDR) inheritSlice(enc, v, parent reflect.Value) (res reflect.Value, err error) { + t := v.Type() + index := make(map[string]reflect.Value) + if !v.IsNil() { + for i := 0; i < v.Len(); i++ { + vi := v.Index(i) + key := attrKey(vi) + index[key] = vi + } + } + if !parent.IsNil() { + for i := 0; i < parent.Len(); i++ { + vi := parent.Index(i) + key := attrKey(vi) + if w, ok := index[key]; ok { + index[key], err = cldr.inheritStructPtr(w, vi) + } else { + n := cldr.newNode(vi.Elem(), enc) + index[key], err = cldr.inheritStructPtr(n, vi) + } + index[key].Interface().(Elem).setEnclosing(enc.Addr().Interface().(Elem)) + if err != nil { + return v, err + } + } + } + keys := make([]string, 0, len(index)) + for k, _ := range index { + keys = append(keys, k) + } + sort.Strings(keys) + sl := reflect.MakeSlice(t, len(index), len(index)) + for i, k := range keys { + sl.Index(i).Set(index[k]) + } + return sl, nil +} + +func parentLocale(loc string) string { + parts := strings.Split(loc, "_") + if len(parts) == 1 { + return "root" + } + parts = parts[:len(parts)-1] + key := strings.Join(parts, "_") + return key +} + +func (cldr *CLDR) resolve(loc string) (res *LDML, err error) { + if r := cldr.resolved[loc]; r != nil { + return r, nil + } + x := cldr.RawLDML(loc) + if x == nil { + return nil, fmt.Errorf("cldr: unknown locale %q", loc) + } + var v reflect.Value + if loc == "root" { + x = deepCopy(reflect.ValueOf(x)).Interface().(*LDML) + linkEnclosing(nil, x) + err = cldr.aliasResolver().visit(x) + } else { + key := parentLocale(loc) + var parent *LDML + for ; cldr.locale[key] == nil; key = parentLocale(key) { + } + if parent, err = cldr.resolve(key); err != nil { + return nil, err + } + v, err = cldr.inheritFields(reflect.ValueOf(x).Elem(), reflect.ValueOf(parent).Elem()) + x = v.Interface().(*LDML) + linkEnclosing(nil, x) + } + if err != nil { + return nil, err + } + cldr.resolved[loc] = x + return x, err +} + +// finalize finalizes the initialization of the raw LDML structs. It also +// removed unwanted fields, as specified by filter, so that they will not +// be unnecessarily evaluated. +func (cldr *CLDR) finalize(filter []string) { + for _, x := range cldr.locale { + if filter != nil { + v := reflect.ValueOf(x).Elem() + t := v.Type() + for i := 0; i < v.NumField(); i++ { + f := t.Field(i) + name, _ := xmlName(f) + if name != "" && name != "identity" && !in(filter, name) { + v.Field(i).Set(reflect.Zero(f.Type)) + } + } + } + linkEnclosing(nil, x) // for resolving aliases and paths + setNames(x, "ldml") + } +} diff --git a/vendor/golang.org/x/text/unicode/cldr/slice.go b/vendor/golang.org/x/text/unicode/cldr/slice.go new file mode 100644 index 000000000..388c983ff --- /dev/null +++ b/vendor/golang.org/x/text/unicode/cldr/slice.go @@ -0,0 +1,144 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cldr + +import ( + "fmt" + "reflect" + "sort" +) + +// Slice provides utilities for modifying slices of elements. +// It can be wrapped around any slice of which the element type implements +// interface Elem. +type Slice struct { + ptr reflect.Value + typ reflect.Type +} + +// Value returns the reflect.Value of the underlying slice. +func (s *Slice) Value() reflect.Value { + return s.ptr.Elem() +} + +// MakeSlice wraps a pointer to a slice of Elems. +// It replaces the array pointed to by the slice so that subsequent modifications +// do not alter the data in a CLDR type. +// It panics if an incorrect type is passed. +func MakeSlice(slicePtr interface{}) Slice { + ptr := reflect.ValueOf(slicePtr) + if ptr.Kind() != reflect.Ptr { + panic(fmt.Sprintf("MakeSlice: argument must be pointer to slice, found %v", ptr.Type())) + } + sl := ptr.Elem() + if sl.Kind() != reflect.Slice { + panic(fmt.Sprintf("MakeSlice: argument must point to a slice, found %v", sl.Type())) + } + intf := reflect.TypeOf((*Elem)(nil)).Elem() + if !sl.Type().Elem().Implements(intf) { + panic(fmt.Sprintf("MakeSlice: element type of slice (%v) does not implement Elem", sl.Type().Elem())) + } + nsl := reflect.MakeSlice(sl.Type(), sl.Len(), sl.Len()) + reflect.Copy(nsl, sl) + sl.Set(nsl) + return Slice{ + ptr: ptr, + typ: sl.Type().Elem().Elem(), + } +} + +func (s Slice) indexForAttr(a string) []int { + for i := iter(reflect.Zero(s.typ)); !i.done(); i.next() { + if n, _ := xmlName(i.field()); n == a { + return i.index + } + } + panic(fmt.Sprintf("MakeSlice: no attribute %q for type %v", a, s.typ)) +} + +// Filter filters s to only include elements for which fn returns true. +func (s Slice) Filter(fn func(e Elem) bool) { + k := 0 + sl := s.Value() + for i := 0; i < sl.Len(); i++ { + vi := sl.Index(i) + if fn(vi.Interface().(Elem)) { + sl.Index(k).Set(vi) + k++ + } + } + sl.Set(sl.Slice(0, k)) +} + +// Group finds elements in s for which fn returns the same value and groups +// them in a new Slice. +func (s Slice) Group(fn func(e Elem) string) []Slice { + m := make(map[string][]reflect.Value) + sl := s.Value() + for i := 0; i < sl.Len(); i++ { + vi := sl.Index(i) + key := fn(vi.Interface().(Elem)) + m[key] = append(m[key], vi) + } + keys := []string{} + for k, _ := range m { + keys = append(keys, k) + } + sort.Strings(keys) + res := []Slice{} + for _, k := range keys { + nsl := reflect.New(sl.Type()) + nsl.Elem().Set(reflect.Append(nsl.Elem(), m[k]...)) + res = append(res, MakeSlice(nsl.Interface())) + } + return res +} + +// SelectAnyOf filters s to contain only elements for which attr matches +// any of the values. +func (s Slice) SelectAnyOf(attr string, values ...string) { + index := s.indexForAttr(attr) + s.Filter(func(e Elem) bool { + vf := reflect.ValueOf(e).Elem().FieldByIndex(index) + return in(values, vf.String()) + }) +} + +// SelectOnePerGroup filters s to include at most one element e per group of +// elements matching Key(attr), where e has an attribute a that matches any +// the values in v. +// If more than one element in a group matches a value in v preference +// is given to the element that matches the first value in v. +func (s Slice) SelectOnePerGroup(a string, v []string) { + index := s.indexForAttr(a) + grouped := s.Group(func(e Elem) string { return Key(e, a) }) + sl := s.Value() + sl.Set(sl.Slice(0, 0)) + for _, g := range grouped { + e := reflect.Value{} + found := len(v) + gsl := g.Value() + for i := 0; i < gsl.Len(); i++ { + vi := gsl.Index(i).Elem().FieldByIndex(index) + j := 0 + for ; j < len(v) && v[j] != vi.String(); j++ { + } + if j < found { + found = j + e = gsl.Index(i) + } + } + if found < len(v) { + sl.Set(reflect.Append(sl, e)) + } + } +} + +// SelectDraft drops all elements from the list with a draft level smaller than d +// and selects the highest draft level of the remaining. +// This method assumes that the input CLDR is canonicalized. +func (s Slice) SelectDraft(d Draft) { + s.SelectOnePerGroup("draft", drafts[len(drafts)-2-int(d):]) +} diff --git a/vendor/golang.org/x/text/unicode/cldr/xml.go b/vendor/golang.org/x/text/unicode/cldr/xml.go new file mode 100644 index 000000000..a1550ed95 --- /dev/null +++ b/vendor/golang.org/x/text/unicode/cldr/xml.go @@ -0,0 +1,1456 @@ +// This file was generated by go generate; DO NOT EDIT + +package cldr + +// LDMLBCP47 holds information on allowable values for various variables in LDML. +type LDMLBCP47 struct { + Common + Version *struct { + Common + Number string `xml:"number,attr"` + } `xml:"version"` + Generation *struct { + Common + Date string `xml:"date,attr"` + } `xml:"generation"` + Keyword []*struct { + Common + Key []*struct { + Common + Extension string `xml:"extension,attr"` + Name string `xml:"name,attr"` + Description string `xml:"description,attr"` + Deprecated string `xml:"deprecated,attr"` + Preferred string `xml:"preferred,attr"` + Alias string `xml:"alias,attr"` + ValueType string `xml:"valueType,attr"` + Since string `xml:"since,attr"` + Type []*struct { + Common + Name string `xml:"name,attr"` + Description string `xml:"description,attr"` + Deprecated string `xml:"deprecated,attr"` + Preferred string `xml:"preferred,attr"` + Alias string `xml:"alias,attr"` + Since string `xml:"since,attr"` + } `xml:"type"` + } `xml:"key"` + } `xml:"keyword"` + Attribute []*struct { + Common + Name string `xml:"name,attr"` + Description string `xml:"description,attr"` + Deprecated string `xml:"deprecated,attr"` + Preferred string `xml:"preferred,attr"` + Since string `xml:"since,attr"` + } `xml:"attribute"` +} + +// SupplementalData holds information relevant for internationalization +// and proper use of CLDR, but that is not contained in the locale hierarchy. +type SupplementalData struct { + Common + Version *struct { + Common + Number string `xml:"number,attr"` + } `xml:"version"` + Generation *struct { + Common + Date string `xml:"date,attr"` + } `xml:"generation"` + CurrencyData *struct { + Common + Fractions []*struct { + Common + Info []*struct { + Common + Iso4217 string `xml:"iso4217,attr"` + Digits string `xml:"digits,attr"` + Rounding string `xml:"rounding,attr"` + CashDigits string `xml:"cashDigits,attr"` + CashRounding string `xml:"cashRounding,attr"` + } `xml:"info"` + } `xml:"fractions"` + Region []*struct { + Common + Iso3166 string `xml:"iso3166,attr"` + Currency []*struct { + Common + Before string `xml:"before,attr"` + From string `xml:"from,attr"` + To string `xml:"to,attr"` + Iso4217 string `xml:"iso4217,attr"` + Digits string `xml:"digits,attr"` + Rounding string `xml:"rounding,attr"` + CashRounding string `xml:"cashRounding,attr"` + Tender string `xml:"tender,attr"` + Alternate []*struct { + Common + Iso4217 string `xml:"iso4217,attr"` + } `xml:"alternate"` + } `xml:"currency"` + } `xml:"region"` + } `xml:"currencyData"` + TerritoryContainment *struct { + Common + Group []*struct { + Common + Contains string `xml:"contains,attr"` + Grouping string `xml:"grouping,attr"` + Status string `xml:"status,attr"` + } `xml:"group"` + } `xml:"territoryContainment"` + SubdivisionContainment *struct { + Common + Subgroup []*struct { + Common + Subtype string `xml:"subtype,attr"` + Contains string `xml:"contains,attr"` + } `xml:"subgroup"` + } `xml:"subdivisionContainment"` + LanguageData *struct { + Common + Language []*struct { + Common + Scripts string `xml:"scripts,attr"` + Territories string `xml:"territories,attr"` + Variants string `xml:"variants,attr"` + } `xml:"language"` + } `xml:"languageData"` + TerritoryInfo *struct { + Common + Territory []*struct { + Common + Gdp string `xml:"gdp,attr"` + LiteracyPercent string `xml:"literacyPercent,attr"` + Population string `xml:"population,attr"` + LanguagePopulation []*struct { + Common + WritingPercent string `xml:"writingPercent,attr"` + PopulationPercent string `xml:"populationPercent,attr"` + OfficialStatus string `xml:"officialStatus,attr"` + } `xml:"languagePopulation"` + } `xml:"territory"` + } `xml:"territoryInfo"` + PostalCodeData *struct { + Common + PostCodeRegex []*struct { + Common + TerritoryId string `xml:"territoryId,attr"` + } `xml:"postCodeRegex"` + } `xml:"postalCodeData"` + CalendarData *struct { + Common + Calendar []*struct { + Common + Territories string `xml:"territories,attr"` + CalendarSystem *Common `xml:"calendarSystem"` + Eras *struct { + Common + Era []*struct { + Common + Start string `xml:"start,attr"` + End string `xml:"end,attr"` + } `xml:"era"` + } `xml:"eras"` + } `xml:"calendar"` + } `xml:"calendarData"` + CalendarPreferenceData *struct { + Common + CalendarPreference []*struct { + Common + Territories string `xml:"territories,attr"` + Ordering string `xml:"ordering,attr"` + } `xml:"calendarPreference"` + } `xml:"calendarPreferenceData"` + WeekData *struct { + Common + MinDays []*struct { + Common + Count string `xml:"count,attr"` + Territories string `xml:"territories,attr"` + } `xml:"minDays"` + FirstDay []*struct { + Common + Day string `xml:"day,attr"` + Territories string `xml:"territories,attr"` + } `xml:"firstDay"` + WeekendStart []*struct { + Common + Day string `xml:"day,attr"` + Territories string `xml:"territories,attr"` + } `xml:"weekendStart"` + WeekendEnd []*struct { + Common + Day string `xml:"day,attr"` + Territories string `xml:"territories,attr"` + } `xml:"weekendEnd"` + WeekOfPreference []*struct { + Common + Locales string `xml:"locales,attr"` + Ordering string `xml:"ordering,attr"` + } `xml:"weekOfPreference"` + } `xml:"weekData"` + TimeData *struct { + Common + Hours []*struct { + Common + Allowed string `xml:"allowed,attr"` + Preferred string `xml:"preferred,attr"` + Regions string `xml:"regions,attr"` + } `xml:"hours"` + } `xml:"timeData"` + MeasurementData *struct { + Common + MeasurementSystem []*struct { + Common + Category string `xml:"category,attr"` + Territories string `xml:"territories,attr"` + } `xml:"measurementSystem"` + PaperSize []*struct { + Common + Territories string `xml:"territories,attr"` + } `xml:"paperSize"` + } `xml:"measurementData"` + UnitPreferenceData *struct { + Common + UnitPreferences []*struct { + Common + Category string `xml:"category,attr"` + Usage string `xml:"usage,attr"` + Scope string `xml:"scope,attr"` + UnitPreference []*struct { + Common + Regions string `xml:"regions,attr"` + } `xml:"unitPreference"` + } `xml:"unitPreferences"` + } `xml:"unitPreferenceData"` + TimezoneData *struct { + Common + MapTimezones []*struct { + Common + OtherVersion string `xml:"otherVersion,attr"` + TypeVersion string `xml:"typeVersion,attr"` + MapZone []*struct { + Common + Other string `xml:"other,attr"` + Territory string `xml:"territory,attr"` + } `xml:"mapZone"` + } `xml:"mapTimezones"` + ZoneFormatting []*struct { + Common + Multizone string `xml:"multizone,attr"` + TzidVersion string `xml:"tzidVersion,attr"` + ZoneItem []*struct { + Common + Territory string `xml:"territory,attr"` + Aliases string `xml:"aliases,attr"` + } `xml:"zoneItem"` + } `xml:"zoneFormatting"` + } `xml:"timezoneData"` + Characters *struct { + Common + CharacterFallback []*struct { + Common + Character []*struct { + Common + Value string `xml:"value,attr"` + Substitute []*Common `xml:"substitute"` + } `xml:"character"` + } `xml:"character-fallback"` + } `xml:"characters"` + Transforms *struct { + Common + Transform []*struct { + Common + Source string `xml:"source,attr"` + Target string `xml:"target,attr"` + Variant string `xml:"variant,attr"` + Direction string `xml:"direction,attr"` + Alias string `xml:"alias,attr"` + BackwardAlias string `xml:"backwardAlias,attr"` + Visibility string `xml:"visibility,attr"` + Comment []*Common `xml:"comment"` + TRule []*Common `xml:"tRule"` + } `xml:"transform"` + } `xml:"transforms"` + Metadata *struct { + Common + AttributeOrder *Common `xml:"attributeOrder"` + ElementOrder *Common `xml:"elementOrder"` + SerialElements *Common `xml:"serialElements"` + Suppress *struct { + Common + Attributes []*struct { + Common + Element string `xml:"element,attr"` + Attribute string `xml:"attribute,attr"` + AttributeValue string `xml:"attributeValue,attr"` + } `xml:"attributes"` + } `xml:"suppress"` + Validity *struct { + Common + Variable []*struct { + Common + Id string `xml:"id,attr"` + } `xml:"variable"` + AttributeValues []*struct { + Common + Dtds string `xml:"dtds,attr"` + Elements string `xml:"elements,attr"` + Attributes string `xml:"attributes,attr"` + Order string `xml:"order,attr"` + } `xml:"attributeValues"` + } `xml:"validity"` + Alias *struct { + Common + LanguageAlias []*struct { + Common + Replacement string `xml:"replacement,attr"` + Reason string `xml:"reason,attr"` + } `xml:"languageAlias"` + ScriptAlias []*struct { + Common + Replacement string `xml:"replacement,attr"` + Reason string `xml:"reason,attr"` + } `xml:"scriptAlias"` + TerritoryAlias []*struct { + Common + Replacement string `xml:"replacement,attr"` + Reason string `xml:"reason,attr"` + } `xml:"territoryAlias"` + SubdivisionAlias []*struct { + Common + Replacement string `xml:"replacement,attr"` + Reason string `xml:"reason,attr"` + } `xml:"subdivisionAlias"` + VariantAlias []*struct { + Common + Replacement string `xml:"replacement,attr"` + Reason string `xml:"reason,attr"` + } `xml:"variantAlias"` + ZoneAlias []*struct { + Common + Replacement string `xml:"replacement,attr"` + Reason string `xml:"reason,attr"` + } `xml:"zoneAlias"` + } `xml:"alias"` + Deprecated *struct { + Common + DeprecatedItems []*struct { + Common + Elements string `xml:"elements,attr"` + Attributes string `xml:"attributes,attr"` + Values string `xml:"values,attr"` + } `xml:"deprecatedItems"` + } `xml:"deprecated"` + Distinguishing *struct { + Common + DistinguishingItems []*struct { + Common + Exclude string `xml:"exclude,attr"` + Elements string `xml:"elements,attr"` + Attributes string `xml:"attributes,attr"` + } `xml:"distinguishingItems"` + } `xml:"distinguishing"` + Blocking *struct { + Common + BlockingItems []*struct { + Common + Elements string `xml:"elements,attr"` + } `xml:"blockingItems"` + } `xml:"blocking"` + CoverageAdditions *struct { + Common + LanguageCoverage []*struct { + Common + Values string `xml:"values,attr"` + } `xml:"languageCoverage"` + ScriptCoverage []*struct { + Common + Values string `xml:"values,attr"` + } `xml:"scriptCoverage"` + TerritoryCoverage []*struct { + Common + Values string `xml:"values,attr"` + } `xml:"territoryCoverage"` + CurrencyCoverage []*struct { + Common + Values string `xml:"values,attr"` + } `xml:"currencyCoverage"` + TimezoneCoverage []*struct { + Common + Values string `xml:"values,attr"` + } `xml:"timezoneCoverage"` + } `xml:"coverageAdditions"` + SkipDefaultLocale *struct { + Common + Services string `xml:"services,attr"` + } `xml:"skipDefaultLocale"` + DefaultContent *struct { + Common + Locales string `xml:"locales,attr"` + } `xml:"defaultContent"` + } `xml:"metadata"` + CodeMappings *struct { + Common + LanguageCodes []*struct { + Common + Alpha3 string `xml:"alpha3,attr"` + } `xml:"languageCodes"` + TerritoryCodes []*struct { + Common + Numeric string `xml:"numeric,attr"` + Alpha3 string `xml:"alpha3,attr"` + Fips10 string `xml:"fips10,attr"` + Internet string `xml:"internet,attr"` + } `xml:"territoryCodes"` + CurrencyCodes []*struct { + Common + Numeric string `xml:"numeric,attr"` + } `xml:"currencyCodes"` + } `xml:"codeMappings"` + ParentLocales *struct { + Common + ParentLocale []*struct { + Common + Parent string `xml:"parent,attr"` + Locales string `xml:"locales,attr"` + } `xml:"parentLocale"` + } `xml:"parentLocales"` + LikelySubtags *struct { + Common + LikelySubtag []*struct { + Common + From string `xml:"from,attr"` + To string `xml:"to,attr"` + } `xml:"likelySubtag"` + } `xml:"likelySubtags"` + MetazoneInfo *struct { + Common + Timezone []*struct { + Common + UsesMetazone []*struct { + Common + From string `xml:"from,attr"` + To string `xml:"to,attr"` + Mzone string `xml:"mzone,attr"` + } `xml:"usesMetazone"` + } `xml:"timezone"` + } `xml:"metazoneInfo"` + Plurals []*struct { + Common + PluralRules []*struct { + Common + Locales string `xml:"locales,attr"` + PluralRule []*struct { + Common + Count string `xml:"count,attr"` + } `xml:"pluralRule"` + } `xml:"pluralRules"` + PluralRanges []*struct { + Common + Locales string `xml:"locales,attr"` + PluralRange []*struct { + Common + Start string `xml:"start,attr"` + End string `xml:"end,attr"` + Result string `xml:"result,attr"` + } `xml:"pluralRange"` + } `xml:"pluralRanges"` + } `xml:"plurals"` + TelephoneCodeData *struct { + Common + CodesByTerritory []*struct { + Common + Territory string `xml:"territory,attr"` + TelephoneCountryCode []*struct { + Common + Code string `xml:"code,attr"` + From string `xml:"from,attr"` + To string `xml:"to,attr"` + } `xml:"telephoneCountryCode"` + } `xml:"codesByTerritory"` + } `xml:"telephoneCodeData"` + NumberingSystems *struct { + Common + NumberingSystem []*struct { + Common + Id string `xml:"id,attr"` + Radix string `xml:"radix,attr"` + Digits string `xml:"digits,attr"` + Rules string `xml:"rules,attr"` + } `xml:"numberingSystem"` + } `xml:"numberingSystems"` + Bcp47KeywordMappings *struct { + Common + MapKeys *struct { + Common + KeyMap []*struct { + Common + Bcp47 string `xml:"bcp47,attr"` + } `xml:"keyMap"` + } `xml:"mapKeys"` + MapTypes []*struct { + Common + TypeMap []*struct { + Common + Bcp47 string `xml:"bcp47,attr"` + } `xml:"typeMap"` + } `xml:"mapTypes"` + } `xml:"bcp47KeywordMappings"` + Gender *struct { + Common + PersonList []*struct { + Common + Locales string `xml:"locales,attr"` + } `xml:"personList"` + } `xml:"gender"` + References *struct { + Common + Reference []*struct { + Common + Uri string `xml:"uri,attr"` + } `xml:"reference"` + } `xml:"references"` + LanguageMatching *struct { + Common + LanguageMatches []*struct { + Common + LanguageMatch []*struct { + Common + Desired string `xml:"desired,attr"` + Oneway string `xml:"oneway,attr"` + Percent string `xml:"percent,attr"` + Supported string `xml:"supported,attr"` + } `xml:"languageMatch"` + } `xml:"languageMatches"` + } `xml:"languageMatching"` + DayPeriodRuleSet []*struct { + Common + DayPeriodRules []*struct { + Common + Locales string `xml:"locales,attr"` + DayPeriodRule []*struct { + Common + At string `xml:"at,attr"` + After string `xml:"after,attr"` + Before string `xml:"before,attr"` + From string `xml:"from,attr"` + To string `xml:"to,attr"` + } `xml:"dayPeriodRule"` + } `xml:"dayPeriodRules"` + } `xml:"dayPeriodRuleSet"` + MetaZones *struct { + Common + MetazoneInfo *struct { + Common + Timezone []*struct { + Common + UsesMetazone []*struct { + Common + From string `xml:"from,attr"` + To string `xml:"to,attr"` + Mzone string `xml:"mzone,attr"` + } `xml:"usesMetazone"` + } `xml:"timezone"` + } `xml:"metazoneInfo"` + MapTimezones *struct { + Common + OtherVersion string `xml:"otherVersion,attr"` + TypeVersion string `xml:"typeVersion,attr"` + MapZone []*struct { + Common + Other string `xml:"other,attr"` + Territory string `xml:"territory,attr"` + } `xml:"mapZone"` + } `xml:"mapTimezones"` + } `xml:"metaZones"` + PrimaryZones *struct { + Common + PrimaryZone []*struct { + Common + Iso3166 string `xml:"iso3166,attr"` + } `xml:"primaryZone"` + } `xml:"primaryZones"` + WindowsZones *struct { + Common + MapTimezones *struct { + Common + OtherVersion string `xml:"otherVersion,attr"` + TypeVersion string `xml:"typeVersion,attr"` + MapZone []*struct { + Common + Other string `xml:"other,attr"` + Territory string `xml:"territory,attr"` + } `xml:"mapZone"` + } `xml:"mapTimezones"` + } `xml:"windowsZones"` + CoverageLevels *struct { + Common + ApprovalRequirements *struct { + Common + ApprovalRequirement []*struct { + Common + Votes string `xml:"votes,attr"` + Locales string `xml:"locales,attr"` + Paths string `xml:"paths,attr"` + } `xml:"approvalRequirement"` + } `xml:"approvalRequirements"` + CoverageVariable []*struct { + Common + Key string `xml:"key,attr"` + Value string `xml:"value,attr"` + } `xml:"coverageVariable"` + CoverageLevel []*struct { + Common + InLanguage string `xml:"inLanguage,attr"` + InScript string `xml:"inScript,attr"` + InTerritory string `xml:"inTerritory,attr"` + Value string `xml:"value,attr"` + Match string `xml:"match,attr"` + } `xml:"coverageLevel"` + } `xml:"coverageLevels"` + IdValidity *struct { + Common + Id []*struct { + Common + IdStatus string `xml:"idStatus,attr"` + } `xml:"id"` + } `xml:"idValidity"` + RgScope *struct { + Common + RgPath []*struct { + Common + Path string `xml:"path,attr"` + } `xml:"rgPath"` + } `xml:"rgScope"` +} + +// LDML is the top-level type for locale-specific data. +type LDML struct { + Common + Version string `xml:"version,attr"` + Identity *struct { + Common + Version *struct { + Common + Number string `xml:"number,attr"` + } `xml:"version"` + Generation *struct { + Common + Date string `xml:"date,attr"` + } `xml:"generation"` + Language *Common `xml:"language"` + Script *Common `xml:"script"` + Territory *Common `xml:"territory"` + Variant *Common `xml:"variant"` + } `xml:"identity"` + LocaleDisplayNames *LocaleDisplayNames `xml:"localeDisplayNames"` + Layout *struct { + Common + Orientation []*struct { + Common + Characters string `xml:"characters,attr"` + Lines string `xml:"lines,attr"` + CharacterOrder []*Common `xml:"characterOrder"` + LineOrder []*Common `xml:"lineOrder"` + } `xml:"orientation"` + InList []*struct { + Common + Casing string `xml:"casing,attr"` + } `xml:"inList"` + InText []*Common `xml:"inText"` + } `xml:"layout"` + ContextTransforms *struct { + Common + ContextTransformUsage []*struct { + Common + ContextTransform []*Common `xml:"contextTransform"` + } `xml:"contextTransformUsage"` + } `xml:"contextTransforms"` + Characters *struct { + Common + ExemplarCharacters []*Common `xml:"exemplarCharacters"` + Ellipsis []*Common `xml:"ellipsis"` + MoreInformation []*Common `xml:"moreInformation"` + Stopwords []*struct { + Common + StopwordList []*Common `xml:"stopwordList"` + } `xml:"stopwords"` + IndexLabels []*struct { + Common + IndexSeparator []*Common `xml:"indexSeparator"` + CompressedIndexSeparator []*Common `xml:"compressedIndexSeparator"` + IndexRangePattern []*Common `xml:"indexRangePattern"` + IndexLabelBefore []*Common `xml:"indexLabelBefore"` + IndexLabelAfter []*Common `xml:"indexLabelAfter"` + IndexLabel []*struct { + Common + IndexSource string `xml:"indexSource,attr"` + Priority string `xml:"priority,attr"` + } `xml:"indexLabel"` + } `xml:"indexLabels"` + Mapping []*struct { + Common + Registry string `xml:"registry,attr"` + } `xml:"mapping"` + } `xml:"characters"` + Delimiters *struct { + Common + QuotationStart []*Common `xml:"quotationStart"` + QuotationEnd []*Common `xml:"quotationEnd"` + AlternateQuotationStart []*Common `xml:"alternateQuotationStart"` + AlternateQuotationEnd []*Common `xml:"alternateQuotationEnd"` + } `xml:"delimiters"` + Measurement *struct { + Common + MeasurementSystem []*Common `xml:"measurementSystem"` + PaperSize []*struct { + Common + Height []*Common `xml:"height"` + Width []*Common `xml:"width"` + } `xml:"paperSize"` + } `xml:"measurement"` + Dates *struct { + Common + LocalizedPatternChars []*Common `xml:"localizedPatternChars"` + DateRangePattern []*Common `xml:"dateRangePattern"` + Calendars *struct { + Common + Calendar []*Calendar `xml:"calendar"` + } `xml:"calendars"` + Fields *struct { + Common + Field []*struct { + Common + DisplayName []*struct { + Common + Count string `xml:"count,attr"` + } `xml:"displayName"` + Relative []*Common `xml:"relative"` + RelativeTime []*struct { + Common + RelativeTimePattern []*struct { + Common + Count string `xml:"count,attr"` + } `xml:"relativeTimePattern"` + } `xml:"relativeTime"` + RelativePeriod []*Common `xml:"relativePeriod"` + } `xml:"field"` + } `xml:"fields"` + TimeZoneNames *TimeZoneNames `xml:"timeZoneNames"` + } `xml:"dates"` + Numbers *Numbers `xml:"numbers"` + Units *struct { + Common + Unit []*struct { + Common + DisplayName []*struct { + Common + Count string `xml:"count,attr"` + } `xml:"displayName"` + UnitPattern []*struct { + Common + Count string `xml:"count,attr"` + } `xml:"unitPattern"` + PerUnitPattern []*Common `xml:"perUnitPattern"` + } `xml:"unit"` + UnitLength []*struct { + Common + CompoundUnit []*struct { + Common + CompoundUnitPattern []*Common `xml:"compoundUnitPattern"` + } `xml:"compoundUnit"` + Unit []*struct { + Common + DisplayName []*struct { + Common + Count string `xml:"count,attr"` + } `xml:"displayName"` + UnitPattern []*struct { + Common + Count string `xml:"count,attr"` + } `xml:"unitPattern"` + PerUnitPattern []*Common `xml:"perUnitPattern"` + } `xml:"unit"` + CoordinateUnit []*struct { + Common + CoordinateUnitPattern []*Common `xml:"coordinateUnitPattern"` + } `xml:"coordinateUnit"` + } `xml:"unitLength"` + DurationUnit []*struct { + Common + DurationUnitPattern []*Common `xml:"durationUnitPattern"` + } `xml:"durationUnit"` + } `xml:"units"` + ListPatterns *struct { + Common + ListPattern []*struct { + Common + ListPatternPart []*Common `xml:"listPatternPart"` + } `xml:"listPattern"` + } `xml:"listPatterns"` + Collations *struct { + Common + Version string `xml:"version,attr"` + DefaultCollation *Common `xml:"defaultCollation"` + Collation []*Collation `xml:"collation"` + } `xml:"collations"` + Posix *struct { + Common + Messages []*struct { + Common + Yesstr []*Common `xml:"yesstr"` + Nostr []*Common `xml:"nostr"` + Yesexpr []*Common `xml:"yesexpr"` + Noexpr []*Common `xml:"noexpr"` + } `xml:"messages"` + } `xml:"posix"` + CharacterLabels *struct { + Common + CharacterLabelPattern []*struct { + Common + Count string `xml:"count,attr"` + } `xml:"characterLabelPattern"` + CharacterLabel []*Common `xml:"characterLabel"` + } `xml:"characterLabels"` + Segmentations *struct { + Common + Segmentation []*struct { + Common + Variables *struct { + Common + Variable []*struct { + Common + Id string `xml:"id,attr"` + } `xml:"variable"` + } `xml:"variables"` + SegmentRules *struct { + Common + Rule []*struct { + Common + Id string `xml:"id,attr"` + } `xml:"rule"` + } `xml:"segmentRules"` + Exceptions *struct { + Common + Exception []*Common `xml:"exception"` + } `xml:"exceptions"` + Suppressions *struct { + Common + Suppression []*Common `xml:"suppression"` + } `xml:"suppressions"` + } `xml:"segmentation"` + } `xml:"segmentations"` + Rbnf *struct { + Common + RulesetGrouping []*struct { + Common + Ruleset []*struct { + Common + Access string `xml:"access,attr"` + AllowsParsing string `xml:"allowsParsing,attr"` + Rbnfrule []*struct { + Common + Value string `xml:"value,attr"` + Radix string `xml:"radix,attr"` + Decexp string `xml:"decexp,attr"` + } `xml:"rbnfrule"` + } `xml:"ruleset"` + } `xml:"rulesetGrouping"` + } `xml:"rbnf"` + Annotations *struct { + Common + Annotation []*struct { + Common + Cp string `xml:"cp,attr"` + Tts string `xml:"tts,attr"` + } `xml:"annotation"` + } `xml:"annotations"` + Metadata *struct { + Common + CasingData *struct { + Common + CasingItem []*struct { + Common + Override string `xml:"override,attr"` + ForceError string `xml:"forceError,attr"` + } `xml:"casingItem"` + } `xml:"casingData"` + } `xml:"metadata"` + References *struct { + Common + Reference []*struct { + Common + Uri string `xml:"uri,attr"` + } `xml:"reference"` + } `xml:"references"` +} + +// Collation contains rules that specify a certain sort-order, +// as a tailoring of the root order. +// The parsed rules are obtained by passing a RuleProcessor to Collation's +// Process method. +type Collation struct { + Common + Visibility string `xml:"visibility,attr"` + Base *Common `xml:"base"` + Import []*struct { + Common + Source string `xml:"source,attr"` + } `xml:"import"` + Settings *struct { + Common + Strength string `xml:"strength,attr"` + Alternate string `xml:"alternate,attr"` + Backwards string `xml:"backwards,attr"` + Normalization string `xml:"normalization,attr"` + CaseLevel string `xml:"caseLevel,attr"` + CaseFirst string `xml:"caseFirst,attr"` + HiraganaQuaternary string `xml:"hiraganaQuaternary,attr"` + MaxVariable string `xml:"maxVariable,attr"` + Numeric string `xml:"numeric,attr"` + Private string `xml:"private,attr"` + VariableTop string `xml:"variableTop,attr"` + Reorder string `xml:"reorder,attr"` + } `xml:"settings"` + SuppressContractions *Common `xml:"suppress_contractions"` + Optimize *Common `xml:"optimize"` + Cr []*Common `xml:"cr"` + rulesElem +} + +// Calendar specifies the fields used for formatting and parsing dates and times. +// The month and quarter names are identified numerically, starting at 1. +// The day (of the week) names are identified with short strings, since there is +// no universally-accepted numeric designation. +type Calendar struct { + Common + Months *struct { + Common + MonthContext []*struct { + Common + MonthWidth []*struct { + Common + Month []*struct { + Common + Yeartype string `xml:"yeartype,attr"` + } `xml:"month"` + } `xml:"monthWidth"` + } `xml:"monthContext"` + } `xml:"months"` + MonthNames *struct { + Common + Month []*struct { + Common + Yeartype string `xml:"yeartype,attr"` + } `xml:"month"` + } `xml:"monthNames"` + MonthAbbr *struct { + Common + Month []*struct { + Common + Yeartype string `xml:"yeartype,attr"` + } `xml:"month"` + } `xml:"monthAbbr"` + MonthPatterns *struct { + Common + MonthPatternContext []*struct { + Common + MonthPatternWidth []*struct { + Common + MonthPattern []*Common `xml:"monthPattern"` + } `xml:"monthPatternWidth"` + } `xml:"monthPatternContext"` + } `xml:"monthPatterns"` + Days *struct { + Common + DayContext []*struct { + Common + DayWidth []*struct { + Common + Day []*Common `xml:"day"` + } `xml:"dayWidth"` + } `xml:"dayContext"` + } `xml:"days"` + DayNames *struct { + Common + Day []*Common `xml:"day"` + } `xml:"dayNames"` + DayAbbr *struct { + Common + Day []*Common `xml:"day"` + } `xml:"dayAbbr"` + Quarters *struct { + Common + QuarterContext []*struct { + Common + QuarterWidth []*struct { + Common + Quarter []*Common `xml:"quarter"` + } `xml:"quarterWidth"` + } `xml:"quarterContext"` + } `xml:"quarters"` + Week *struct { + Common + MinDays []*struct { + Common + Count string `xml:"count,attr"` + } `xml:"minDays"` + FirstDay []*struct { + Common + Day string `xml:"day,attr"` + } `xml:"firstDay"` + WeekendStart []*struct { + Common + Day string `xml:"day,attr"` + Time string `xml:"time,attr"` + } `xml:"weekendStart"` + WeekendEnd []*struct { + Common + Day string `xml:"day,attr"` + Time string `xml:"time,attr"` + } `xml:"weekendEnd"` + } `xml:"week"` + Am []*Common `xml:"am"` + Pm []*Common `xml:"pm"` + DayPeriods *struct { + Common + DayPeriodContext []*struct { + Common + DayPeriodWidth []*struct { + Common + DayPeriod []*Common `xml:"dayPeriod"` + } `xml:"dayPeriodWidth"` + } `xml:"dayPeriodContext"` + } `xml:"dayPeriods"` + Eras *struct { + Common + EraNames *struct { + Common + Era []*Common `xml:"era"` + } `xml:"eraNames"` + EraAbbr *struct { + Common + Era []*Common `xml:"era"` + } `xml:"eraAbbr"` + EraNarrow *struct { + Common + Era []*Common `xml:"era"` + } `xml:"eraNarrow"` + } `xml:"eras"` + CyclicNameSets *struct { + Common + CyclicNameSet []*struct { + Common + CyclicNameContext []*struct { + Common + CyclicNameWidth []*struct { + Common + CyclicName []*Common `xml:"cyclicName"` + } `xml:"cyclicNameWidth"` + } `xml:"cyclicNameContext"` + } `xml:"cyclicNameSet"` + } `xml:"cyclicNameSets"` + DateFormats *struct { + Common + DateFormatLength []*struct { + Common + DateFormat []*struct { + Common + Pattern []*struct { + Common + Numbers string `xml:"numbers,attr"` + Count string `xml:"count,attr"` + } `xml:"pattern"` + DisplayName []*struct { + Common + Count string `xml:"count,attr"` + } `xml:"displayName"` + } `xml:"dateFormat"` + } `xml:"dateFormatLength"` + } `xml:"dateFormats"` + TimeFormats *struct { + Common + TimeFormatLength []*struct { + Common + TimeFormat []*struct { + Common + Pattern []*struct { + Common + Numbers string `xml:"numbers,attr"` + Count string `xml:"count,attr"` + } `xml:"pattern"` + DisplayName []*struct { + Common + Count string `xml:"count,attr"` + } `xml:"displayName"` + } `xml:"timeFormat"` + } `xml:"timeFormatLength"` + } `xml:"timeFormats"` + DateTimeFormats *struct { + Common + DateTimeFormatLength []*struct { + Common + DateTimeFormat []*struct { + Common + Pattern []*struct { + Common + Numbers string `xml:"numbers,attr"` + Count string `xml:"count,attr"` + } `xml:"pattern"` + DisplayName []*struct { + Common + Count string `xml:"count,attr"` + } `xml:"displayName"` + } `xml:"dateTimeFormat"` + } `xml:"dateTimeFormatLength"` + AvailableFormats []*struct { + Common + DateFormatItem []*struct { + Common + Id string `xml:"id,attr"` + Count string `xml:"count,attr"` + } `xml:"dateFormatItem"` + } `xml:"availableFormats"` + AppendItems []*struct { + Common + AppendItem []*struct { + Common + Request string `xml:"request,attr"` + } `xml:"appendItem"` + } `xml:"appendItems"` + IntervalFormats []*struct { + Common + IntervalFormatFallback []*Common `xml:"intervalFormatFallback"` + IntervalFormatItem []*struct { + Common + Id string `xml:"id,attr"` + GreatestDifference []*struct { + Common + Id string `xml:"id,attr"` + } `xml:"greatestDifference"` + } `xml:"intervalFormatItem"` + } `xml:"intervalFormats"` + } `xml:"dateTimeFormats"` + Fields []*struct { + Common + Field []*struct { + Common + DisplayName []*struct { + Common + Count string `xml:"count,attr"` + } `xml:"displayName"` + Relative []*Common `xml:"relative"` + RelativeTime []*struct { + Common + RelativeTimePattern []*struct { + Common + Count string `xml:"count,attr"` + } `xml:"relativeTimePattern"` + } `xml:"relativeTime"` + RelativePeriod []*Common `xml:"relativePeriod"` + } `xml:"field"` + } `xml:"fields"` +} +type TimeZoneNames struct { + Common + HourFormat []*Common `xml:"hourFormat"` + HoursFormat []*Common `xml:"hoursFormat"` + GmtFormat []*Common `xml:"gmtFormat"` + GmtZeroFormat []*Common `xml:"gmtZeroFormat"` + RegionFormat []*Common `xml:"regionFormat"` + FallbackFormat []*Common `xml:"fallbackFormat"` + FallbackRegionFormat []*Common `xml:"fallbackRegionFormat"` + AbbreviationFallback []*Common `xml:"abbreviationFallback"` + PreferenceOrdering []*Common `xml:"preferenceOrdering"` + SingleCountries []*struct { + Common + List string `xml:"list,attr"` + } `xml:"singleCountries"` + Zone []*struct { + Common + Long []*struct { + Common + Generic []*Common `xml:"generic"` + Standard []*Common `xml:"standard"` + Daylight []*Common `xml:"daylight"` + } `xml:"long"` + Short []*struct { + Common + Generic []*Common `xml:"generic"` + Standard []*Common `xml:"standard"` + Daylight []*Common `xml:"daylight"` + } `xml:"short"` + CommonlyUsed []*struct { + Common + Used string `xml:"used,attr"` + } `xml:"commonlyUsed"` + ExemplarCity []*Common `xml:"exemplarCity"` + } `xml:"zone"` + Metazone []*struct { + Common + Long []*struct { + Common + Generic []*Common `xml:"generic"` + Standard []*Common `xml:"standard"` + Daylight []*Common `xml:"daylight"` + } `xml:"long"` + Short []*struct { + Common + Generic []*Common `xml:"generic"` + Standard []*Common `xml:"standard"` + Daylight []*Common `xml:"daylight"` + } `xml:"short"` + CommonlyUsed []*struct { + Common + Used string `xml:"used,attr"` + } `xml:"commonlyUsed"` + } `xml:"metazone"` +} + +// LocaleDisplayNames specifies localized display names for for scripts, languages, +// countries, currencies, and variants. +type LocaleDisplayNames struct { + Common + LocaleDisplayPattern *struct { + Common + LocalePattern []*Common `xml:"localePattern"` + LocaleSeparator []*Common `xml:"localeSeparator"` + LocaleKeyTypePattern []*Common `xml:"localeKeyTypePattern"` + } `xml:"localeDisplayPattern"` + Languages *struct { + Common + Language []*Common `xml:"language"` + } `xml:"languages"` + Scripts *struct { + Common + Script []*Common `xml:"script"` + } `xml:"scripts"` + Territories *struct { + Common + Territory []*Common `xml:"territory"` + } `xml:"territories"` + Subdivisions *struct { + Common + Subdivision []*Common `xml:"subdivision"` + } `xml:"subdivisions"` + Variants *struct { + Common + Variant []*Common `xml:"variant"` + } `xml:"variants"` + Keys *struct { + Common + Key []*Common `xml:"key"` + } `xml:"keys"` + Types *struct { + Common + Type []*struct { + Common + Key string `xml:"key,attr"` + } `xml:"type"` + } `xml:"types"` + TransformNames *struct { + Common + TransformName []*Common `xml:"transformName"` + } `xml:"transformNames"` + MeasurementSystemNames *struct { + Common + MeasurementSystemName []*Common `xml:"measurementSystemName"` + } `xml:"measurementSystemNames"` + CodePatterns *struct { + Common + CodePattern []*Common `xml:"codePattern"` + } `xml:"codePatterns"` +} + +// Numbers supplies information for formatting and parsing numbers and currencies. +type Numbers struct { + Common + DefaultNumberingSystem []*Common `xml:"defaultNumberingSystem"` + OtherNumberingSystems []*struct { + Common + Native []*Common `xml:"native"` + Traditional []*Common `xml:"traditional"` + Finance []*Common `xml:"finance"` + } `xml:"otherNumberingSystems"` + MinimumGroupingDigits []*Common `xml:"minimumGroupingDigits"` + Symbols []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + Decimal []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + } `xml:"decimal"` + Group []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + } `xml:"group"` + List []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + } `xml:"list"` + PercentSign []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + } `xml:"percentSign"` + NativeZeroDigit []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + } `xml:"nativeZeroDigit"` + PatternDigit []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + } `xml:"patternDigit"` + PlusSign []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + } `xml:"plusSign"` + MinusSign []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + } `xml:"minusSign"` + Exponential []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + } `xml:"exponential"` + SuperscriptingExponent []*Common `xml:"superscriptingExponent"` + PerMille []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + } `xml:"perMille"` + Infinity []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + } `xml:"infinity"` + Nan []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + } `xml:"nan"` + CurrencyDecimal []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + } `xml:"currencyDecimal"` + CurrencyGroup []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + } `xml:"currencyGroup"` + TimeSeparator []*Common `xml:"timeSeparator"` + } `xml:"symbols"` + DecimalFormats []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + DecimalFormatLength []*struct { + Common + DecimalFormat []*struct { + Common + Pattern []*struct { + Common + Numbers string `xml:"numbers,attr"` + Count string `xml:"count,attr"` + } `xml:"pattern"` + } `xml:"decimalFormat"` + } `xml:"decimalFormatLength"` + } `xml:"decimalFormats"` + ScientificFormats []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + ScientificFormatLength []*struct { + Common + ScientificFormat []*struct { + Common + Pattern []*struct { + Common + Numbers string `xml:"numbers,attr"` + Count string `xml:"count,attr"` + } `xml:"pattern"` + } `xml:"scientificFormat"` + } `xml:"scientificFormatLength"` + } `xml:"scientificFormats"` + PercentFormats []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + PercentFormatLength []*struct { + Common + PercentFormat []*struct { + Common + Pattern []*struct { + Common + Numbers string `xml:"numbers,attr"` + Count string `xml:"count,attr"` + } `xml:"pattern"` + } `xml:"percentFormat"` + } `xml:"percentFormatLength"` + } `xml:"percentFormats"` + CurrencyFormats []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + CurrencySpacing []*struct { + Common + BeforeCurrency []*struct { + Common + CurrencyMatch []*Common `xml:"currencyMatch"` + SurroundingMatch []*Common `xml:"surroundingMatch"` + InsertBetween []*Common `xml:"insertBetween"` + } `xml:"beforeCurrency"` + AfterCurrency []*struct { + Common + CurrencyMatch []*Common `xml:"currencyMatch"` + SurroundingMatch []*Common `xml:"surroundingMatch"` + InsertBetween []*Common `xml:"insertBetween"` + } `xml:"afterCurrency"` + } `xml:"currencySpacing"` + CurrencyFormatLength []*struct { + Common + CurrencyFormat []*struct { + Common + Pattern []*struct { + Common + Numbers string `xml:"numbers,attr"` + Count string `xml:"count,attr"` + } `xml:"pattern"` + } `xml:"currencyFormat"` + } `xml:"currencyFormatLength"` + UnitPattern []*struct { + Common + Count string `xml:"count,attr"` + } `xml:"unitPattern"` + } `xml:"currencyFormats"` + Currencies *struct { + Common + Currency []*struct { + Common + Pattern []*struct { + Common + Numbers string `xml:"numbers,attr"` + Count string `xml:"count,attr"` + } `xml:"pattern"` + DisplayName []*struct { + Common + Count string `xml:"count,attr"` + } `xml:"displayName"` + Symbol []*Common `xml:"symbol"` + Decimal []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + } `xml:"decimal"` + Group []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + } `xml:"group"` + } `xml:"currency"` + } `xml:"currencies"` + MiscPatterns []*struct { + Common + NumberSystem string `xml:"numberSystem,attr"` + Pattern []*struct { + Common + Numbers string `xml:"numbers,attr"` + Count string `xml:"count,attr"` + } `xml:"pattern"` + } `xml:"miscPatterns"` +} + +// Version is the version of CLDR from which the XML definitions are generated. +const Version = "30" diff --git a/vendor/google.golang.org/cloud/LICENSE b/vendor/google.golang.org/cloud/LICENSE new file mode 100644 index 000000000..a4c5efd82 --- /dev/null +++ b/vendor/google.golang.org/cloud/LICENSE @@ -0,0 +1,202 @@ + + 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 2014 Google Inc. + + 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/vendor.json b/vendor/vendor.json index 777ca297b..8e28de3e9 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -807,6 +807,12 @@ "path": "golang.org/x/sys/unix", "revision": "50c6bc5e4292a1d4e65c6e9be5f53be28bcbe28e" }, + { + "checksumSHA1": "G9LfJI9gySazd+MyyC6QbTHx4to=", + "path": "golang.org/x/text/encoding/unicode", + "revision": "16e1d1f27f7aba51c74c0aeb7a7ee31a75c5c63c", + "revisionTime": "2016-10-12T14:34:48Z" + }, { "checksumSHA1": "hrUTmck0J+LE+lBtCvHvemNDY8U=", "path": "google.golang.org/api/compute/v1", From ffc7f3f3dc91fdb2d6a3665b7bb38e6c37f320c3 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Wed, 26 Oct 2016 12:20:50 +0100 Subject: [PATCH 088/108] Address documentation layout and copy issues --- .../docs/builders/hyperv-iso.html.markdown | 180 ++++++++---------- 1 file changed, 84 insertions(+), 96 deletions(-) diff --git a/website/source/docs/builders/hyperv-iso.html.markdown b/website/source/docs/builders/hyperv-iso.html.markdown index 062f6bd79..dd6a710a8 100644 --- a/website/source/docs/builders/hyperv-iso.html.markdown +++ b/website/source/docs/builders/hyperv-iso.html.markdown @@ -50,153 +50,141 @@ In addition to the options listed here, a [communicator](/docs/templates/communicator.html) can be configured for this builder. - - - - - - - ### 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` (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_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. + 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. ### 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. + 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. + 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. -- `cpu` (int) - The number of cpus the virtual machine should use. If this isn't specified, - the default is 1 cpu. +- `cpu` (integer) - The number of cpus the virtual machine should use. If this isn't specified, + the default is 1 cpu. - `disk_size` (integer) - The size, in megabytes, of the hard disk to create - for the VM. By default, this is 40000 (about 40 GB). + for the VM. By default, this is 40000 (about 40 GB). - `enable_mac_spoofing` (bool) - If true enable mac spoofing for virtual machine. - This defaults to false. + This defaults to false. - `enable_dynamic_memory` (bool) - If true enable dynamic memory for virtual machine. - This defaults to false. + This defaults to false. - `enable_secure_boot` (bool) - If true enable secure boot for virtual machine. - This defaults to false. + This defaults to false. - `enable_virtualization_extensions` (bool) - If true enable virtualization extensions for virtual machine. - This defaults to false. For nested virtualization you need to enable mac spoofing, disable dynamic memory - and have at least 4GB of RAM for virtual machine. + This defaults to false. For nested virtualization you need to enable mac spoofing, disable dynamic memory + and have at least 4GB of RAM for virtual machine. - `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. + 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. -- `generation` (int) - The HyperV generation for the virtual machine. By - default, this is 1. Generation 2 HyperV virtual machines do not support - floppy drives. In this scenario use secondary_iso_images instead. Hard - drives and dvd drives will also be scsi and not ide. +- `generation` (integer) - The HyperV generation for the virtual machine. By + default, this is 1. Generation 2 HyperV virtual machines do not support + floppy drives. In this scenario use `secondary_iso_images` instead. Hard + drives and dvd drives will also be scsi and not ide. - `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. + 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. + 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. - `ip_address_timeout` (string) - The time to wait after creating the initial virtual - machine and waiting for an ip address before assuming there is an error in the process. - 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. + machine and waiting for an ip address before assuming there is an error in the process. + 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. - `iso_urls` (array of strings) - Multiple URLs for the ISO to download. - Packer will try these in order. If anything goes wrong attempting to download - or while downloading a single URL, it will move on to the next. All URLs - must point to the same file (same checksum). By default this is empty - and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified. + Packer will try these in order. If anything goes wrong attempting to download + or while downloading a single URL, it will move on to the next. All URLs + must point to the same file (same checksum). By default this is empty + and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified. - `output_directory` (string) - This is the path to the directory where the - resulting virtual machine will be created. This may be relative or absolute. - If relative, the path is relative to the working directory when `packer` - is executed. This directory must not exist or be empty prior to running the builder. - By default this is "output-BUILDNAME" where "BUILDNAME" is the name - of the build. + 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. -* `secondary_iso_images` (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. +* `secondary_iso_images` (array of strings) - A list of iso paths to attached to a + VM when it is booted. This is most useful for unattended Windows installs, which + look for an `Autounattend.xml` file on removable media. By default, no + secondary iso will be attached. - `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 unless a shutdown command takes place inside script so this may - safely be omitted. If one or more scripts require a reboot it is suggested to leave this blank - since reboots may fail and specify the final shutdown command in your last script. + the provisioning is done. By default this is an empty string, which tells Packer to just + forcefully shut down the machine unless a shutdown command takes place inside script so this may + safely be omitted. If one or more scripts require a reboot it is suggested to leave this blank + since reboots may fail and specify the final shutdown command in your last script. - `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. + 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. - `skip_compaction` (bool) - If true skip compacting the hard disk for virtual machine when - exporting. This defaults to false. + exporting. This defaults to false. - `switch_name` (string) - The name of the switch to connect the virtual machine to. Be defaulting - this to an empty string, Packer will try to determine the switch to use by looking for - external switch that is up and running. + this to an empty string, Packer will try to determine the switch to use by looking for + external switch that is up and running. - `switch_vlan_id` (string) - This is the vlan of the virtual switch's network card. - By default none is set. If none is set then a vlan is not set on the switch's network card. - If this value is set it should match the vlan specified in by `vlan_id`. + By default none is set. If none is set then a vlan is not set on the switch's network card. + If this value is set it should match the vlan specified in by `vlan_id`. - `vlan_id` (string) - This is the vlan of the virtual machine's network card for the new virtual - machine. By default none is set. If none is set then vlans are not set on the virtual machine's - network card. + machine. By default none is set. If none is set then vlans are not set on the virtual machine's + network card. - `vm_name` (string) - This is the name of the virtual machine for the new virtual - machine, without the file extension. By default this is "packer-BUILDNAME", - where "BUILDNAME" is the name of the build. + machine, without the file extension. By default this is "packer-BUILDNAME", + where "BUILDNAME" is the name of the build. ## Boot Command From 55353fae89c57039d739e8c7f2bf7c0e6fcc8e75 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Wed, 26 Oct 2016 13:32:55 +0100 Subject: [PATCH 089/108] Fix spelling mistake Fix code type for formatting Remove empty space --- provisioner/windows-shell/provisioner.go | 2 +- website/source/docs/builders/hyperv-iso.html.markdown | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/provisioner/windows-shell/provisioner.go b/provisioner/windows-shell/provisioner.go index 17abcd2d5..59c6edabf 100644 --- a/provisioner/windows-shell/provisioner.go +++ b/provisioner/windows-shell/provisioner.go @@ -322,4 +322,4 @@ func (p *Provisioner) createFlattenedEnvVars() (flattened string, err error) { flattened += fmt.Sprintf(p.config.EnvVarFormat, key, envVars[key]) } return -} +} \ No newline at end of file diff --git a/website/source/docs/builders/hyperv-iso.html.markdown b/website/source/docs/builders/hyperv-iso.html.markdown index dd6a710a8..e01deffb5 100644 --- a/website/source/docs/builders/hyperv-iso.html.markdown +++ b/website/source/docs/builders/hyperv-iso.html.markdown @@ -283,7 +283,7 @@ for the version of HyperV that is running. Floppy drives are no longer supported by generation 2 machines. This requires you to take another approach when dealing with preseed or answer files. Two possible options -are using virtua dvd drives or using the built in web server. +are using virtual dvd drives or using the built in web server. When dealing with Windows you need to enable UEFI drives for generation 2 virtual machines. @@ -333,7 +333,7 @@ if (test-path $isoFolder){ Packer config: -```text +```javascript { "builders": [ { @@ -389,7 +389,7 @@ Packer config: autounattend.xml: -```text +```xml <?xml version="1.0" encoding="utf-8"?> <unattend xmlns="urn:schemas-microsoft-com:unattend"> <settings pass="windowsPE"> @@ -857,7 +857,7 @@ Finish proxy after sysprep --> Packer config: -```text +```javascript { "builders": [ { From c88a2ef872653a10d6c1172c990807b53b8040c3 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Wed, 26 Oct 2016 13:39:58 +0100 Subject: [PATCH 090/108] Remove trailing tabs --- provisioner/windows-shell/provisioner.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provisioner/windows-shell/provisioner.go b/provisioner/windows-shell/provisioner.go index 59c6edabf..930bfd3f8 100644 --- a/provisioner/windows-shell/provisioner.go +++ b/provisioner/windows-shell/provisioner.go @@ -308,7 +308,7 @@ func (p *Provisioner) createFlattenedEnvVars() (flattened string, err error) { if len(keyValue) != 2 || keyValue[0] == "" { err = errors.New(fmt.Sprintf("Shell provisioner environment variables must be in key=value format. Currently it is '%s'", envVar)) return - } + } envVars[keyValue[0]] = keyValue[1] } // Create a list of env var keys in sorted order From 501845cb4f71149bcba166cb98aff09beef4826e Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 6 Nov 2016 13:06:10 +0000 Subject: [PATCH 091/108] SSHKeyPath and SSHWaitTimeout have been deprecated in favor of communicator configuration --- builder/hyperv/common/ssh_config.go | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/builder/hyperv/common/ssh_config.go b/builder/hyperv/common/ssh_config.go index bea164b06..0217287e0 100644 --- a/builder/hyperv/common/ssh_config.go +++ b/builder/hyperv/common/ssh_config.go @@ -9,21 +9,8 @@ import ( type SSHConfig struct { Comm communicator.Config `mapstructure:",squash"` - - // These are deprecated, but we keep them around for BC - // TODO(@mitchellh): remove - SSHKeyPath string `mapstructure:"ssh_key_path"` - SSHWaitTimeout time.Duration `mapstructure:"ssh_wait_timeout"` } func (c *SSHConfig) Prepare(ctx *interpolate.Context) []error { - // TODO: backwards compatibility, write fixer instead - if c.SSHKeyPath != "" { - c.Comm.SSHPrivateKey = c.SSHKeyPath - } - if c.SSHWaitTimeout != 0 { - c.Comm.SSHTimeout = c.SSHWaitTimeout - } - return c.Comm.Prepare(ctx) } From a27d75ff89c90bc433760afd9d20e80d0a630084 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 6 Nov 2016 13:07:50 +0000 Subject: [PATCH 092/108] Fix comment left over from initial import --- builder/hyperv/common/step_type_boot_command.go | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/builder/hyperv/common/step_type_boot_command.go b/builder/hyperv/common/step_type_boot_command.go index e9a1bc4a4..b8b95f43a 100644 --- a/builder/hyperv/common/step_type_boot_command.go +++ b/builder/hyperv/common/step_type_boot_command.go @@ -18,17 +18,7 @@ type bootCommandTemplateData struct { Name string } -// This step "types" the boot command into the VM via the prltype script, built on the -// Parallels Virtualization SDK - Python API. -// -// Uses: -// driver Driver -// http_port int -// ui packer.Ui -// vmName string -// -// Produces: -// <nothing> +// This step "types" the boot command into the VM via the Hyper-V virtual keyboard type StepTypeBootCommand struct { BootCommand []string SwitchName string From d7117560cfc52c3b22b7f6fd2b0efce2e2a68b2a Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 6 Nov 2016 13:12:44 +0000 Subject: [PATCH 093/108] We are using powershell and Msvm to send keys to virtual keyboard so update comment --- builder/hyperv/common/step_type_boot_command.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/hyperv/common/step_type_boot_command.go b/builder/hyperv/common/step_type_boot_command.go index b8b95f43a..4af792eff 100644 --- a/builder/hyperv/common/step_type_boot_command.go +++ b/builder/hyperv/common/step_type_boot_command.go @@ -81,8 +81,8 @@ func (*StepTypeBootCommand) Cleanup(multistep.StateBag) {} func scancodes(message string) []string { // Scancodes reference: http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html // - // Scancodes represent raw keyboard output and are fed to the VM by the - // VBoxManage controlvm keyboardputscancode program. + // Scancodes represent raw keyboard output and are fed to the VM by using + // powershell to use Msvm_Keyboard // // Scancodes are recorded here in pairs. The first entry represents // the key press and the second entry represents the key release and is From 20643e35a87308aa6324dcb76b26aaa444179e30 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 6 Nov 2016 13:14:39 +0000 Subject: [PATCH 094/108] Do not specify a default username or password --- builder/hyperv/iso/builder.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index ac390eba1..bdbf5a794 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -35,8 +35,8 @@ const ( LowRam = 256 // 256MB - DefaultUsername = "vagrant" - DefaultPassword = "vagrant" + DefaultUsername = "" + DefaultPassword = "" ) // Builder implements packer.Builder and builds the actual Hyperv From 0515487062478ef6235762ecfd63f0b90fc17a97 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 6 Nov 2016 13:59:40 +0000 Subject: [PATCH 095/108] Configuration settings are no longer used --- builder/hyperv/iso/builder.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index bdbf5a794..410238f2a 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -98,12 +98,6 @@ type Config struct { Communicator string `mapstructure:"communicator"` - // The time in seconds to wait for the virtual machine to report an IP address. - // This defaults to 120 seconds. This may have to be increased if your VM takes longer to boot. - IPAddressTimeout time.Duration `mapstructure:"ip_address_timeout"` - - SSHWaitTimeout time.Duration - SkipCompaction bool `mapstructure:"skip_compaction"` ctx interpolate.Context From 3c1df5a7919ddb4adb683daa9720b82c3100a83a Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 6 Nov 2016 14:07:46 +0000 Subject: [PATCH 096/108] Remove inconsistent "_mb" from ram size --- builder/hyperv/common/step_create_vm.go | 6 ++--- builder/hyperv/iso/builder.go | 22 +++++++++---------- .../docs/builders/hyperv-iso.html.markdown | 4 ++-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index 54a5a7103..1606a41e4 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -18,7 +18,7 @@ import ( type StepCreateVM struct { VMName string SwitchName string - RamSizeMB uint + RamSize uint DiskSize uint Generation uint Cpu uint @@ -36,10 +36,10 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { path := state.Get("packerTempDir").(string) // convert the MB to bytes - ram := int64(s.RamSizeMB * 1024 * 1024) + ramSize := int64(s.RamSize * 1024 * 1024) diskSize := int64(s.DiskSize * 1024 * 1024) - err := driver.CreateVirtualMachine(s.VMName, path, ram, diskSize, s.SwitchName, s.Generation) + err := driver.CreateVirtualMachine(s.VMName, path, ramSize, diskSize, s.SwitchName, s.Generation) if err != nil { err := fmt.Errorf("Error creating virtual machine: %s", err) state.Put("error", err) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 410238f2a..e81f498e0 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -61,7 +61,7 @@ type Config struct { DiskSize uint `mapstructure:"disk_size"` // The size, in megabytes, of the computer memory in the VM. // By default, this is 1024 (about 1 GB). - RamSizeMB uint `mapstructure:"ram_size_mb"` + RamSize uint `mapstructure:"ram_size"` // 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, @@ -261,7 +261,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { warnings = appendWarnings(warnings, warning) } - if b.config.RamSizeMB < MinNestedVirtualizationRamSize { + if b.config.RamSize < MinNestedVirtualizationRamSize { warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, there should be 4GB or more memory set for the vm, otherwise Hyper-V may fail to start any nested VMs.") warnings = appendWarnings(warnings, warning) } @@ -328,7 +328,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &hypervcommon.StepCreateVM{ VMName: b.config.VMName, SwitchName: b.config.SwitchName, - RamSizeMB: b.config.RamSizeMB, + RamSize: b.config.RamSize, DiskSize: b.config.DiskSize, Generation: b.config.Generation, Cpu: b.config.Cpu, @@ -477,16 +477,16 @@ func (b *Builder) checkDiskSize() error { } func (b *Builder) checkRamSize() error { - if b.config.RamSizeMB == 0 { - b.config.RamSizeMB = DefaultRamSize + if b.config.RamSize == 0 { + b.config.RamSize = DefaultRamSize } - log.Println(fmt.Sprintf("%s: %v", "RamSize", b.config.RamSizeMB)) + log.Println(fmt.Sprintf("%s: %v", "RamSize", b.config.RamSize)) - if b.config.RamSizeMB < MinRamSize { - return fmt.Errorf("ram_size_mb: Virtual machine requires memory size >= %v MB, but defined: %v", MinRamSize, b.config.RamSizeMB) - } else if b.config.RamSizeMB > MaxRamSize { - return fmt.Errorf("ram_size_mb: Virtual machine requires memory size <= %v MB, but defined: %v", MaxRamSize, b.config.RamSizeMB) + if b.config.RamSize < MinRamSize { + return fmt.Errorf("ram_size_mb: Virtual machine requires memory size >= %v MB, but defined: %v", MinRamSize, b.config.RamSize) + } else if b.config.RamSize > MaxRamSize { + return fmt.Errorf("ram_size_mb: Virtual machine requires memory size <= %v MB, but defined: %v", MaxRamSize, b.config.RamSize) } return nil @@ -498,7 +498,7 @@ func (b *Builder) checkHostAvailableMemory() string { if powershellAvailable { freeMB := powershell.GetHostAvailableMemory() - if (freeMB - float64(b.config.RamSizeMB)) < LowRam { + if (freeMB - float64(b.config.RamSize)) < LowRam { return fmt.Sprintf("Hyper-V might fail to create a VM if there is not enough free memory in the system.") } } diff --git a/website/source/docs/builders/hyperv-iso.html.markdown b/website/source/docs/builders/hyperv-iso.html.markdown index e01deffb5..dd161b034 100644 --- a/website/source/docs/builders/hyperv-iso.html.markdown +++ b/website/source/docs/builders/hyperv-iso.html.markdown @@ -358,7 +358,7 @@ Packer config: "winrm_password": "vagrant", "winrm_timeout" : "4h", "shutdown_command": "f:\\run-sysprep.cmd", - "ram_size_mb": 4096, + "ram_size": 4096, "cpu": 4, "generation": 2, "switch_name":"LAN", @@ -886,7 +886,7 @@ Packer config: "initrd=/install/initrd.gz -- <enter>" ], "shutdown_command": "echo 'vagrant' | sudo -S -E shutdown -P now", - "ram_size_mb": 4096, + "ram_size": 4096, "cpu": 4, "generation": 1, "switch_name":"LAN" From 7add6d3470c8d26b271302218f4adc2880b258dd Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 6 Nov 2016 14:44:13 +0000 Subject: [PATCH 097/108] Add missing configuration elements. Remove configuration elements that are no longer used. --- .../docs/builders/hyperv-iso.html.markdown | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/website/source/docs/builders/hyperv-iso.html.markdown b/website/source/docs/builders/hyperv-iso.html.markdown index dd161b034..3c25cdbe8 100644 --- a/website/source/docs/builders/hyperv-iso.html.markdown +++ b/website/source/docs/builders/hyperv-iso.html.markdown @@ -87,14 +87,14 @@ can be configured for this builder. the default is 1 cpu. - `disk_size` (integer) - The size, in megabytes, of the hard disk to create - for the VM. By default, this is 40000 (about 40 GB). - -- `enable_mac_spoofing` (bool) - If true enable mac spoofing for virtual machine. - This defaults to false. + for the VM. By default, this is 40 GB. - `enable_dynamic_memory` (bool) - If true enable dynamic memory for virtual machine. This defaults to false. +- `enable_mac_spoofing` (bool) - If true enable mac spoofing for virtual machine. + This defaults to false. + - `enable_secure_boot` (bool) - If true enable secure boot for virtual machine. This defaults to false. @@ -117,6 +117,12 @@ can be configured for this builder. floppy drives. In this scenario use `secondary_iso_images` instead. Hard drives and dvd drives will also be scsi and not ide. +- `guest_additions_mode` (string) - How should guest additions be installed. + If value `attach` then attach iso image with by specified by `guest_additions_path`. + Otherwise guest additions is not installed. + +- `guest_additions_path` (string) - The path to the iso image for guest additions. + - `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 @@ -132,12 +138,6 @@ can be configured for this builder. server to be on one port, make this minimum and maximum port the same. By default the values are 8000 and 9000, respectively. -- `ip_address_timeout` (string) - The time to wait after creating the initial virtual - machine and waiting for an ip address before assuming there is an error in the process. - 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. - - `iso_urls` (array of strings) - Multiple URLs for the ISO to download. Packer will try these in order. If anything goes wrong attempting to download or while downloading a single URL, it will move on to the next. All URLs @@ -151,6 +151,9 @@ can be configured for this builder. By default this is "output-BUILDNAME" where "BUILDNAME" is the name of the build. +- `ram_size` (integer) - The size, in megabytes, of the ram to create + for the VM. By default, this is 1 GB. + * `secondary_iso_images` (array of strings) - A list of iso paths to attached to a VM when it is booted. This is most useful for unattended Windows installs, which look for an `Autounattend.xml` file on removable media. By default, no From 3f0835aab2e467e4ffcd4893e644f40831ed2d12 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 6 Nov 2016 14:45:22 +0000 Subject: [PATCH 098/108] Remove headless option as it is not used. --- builder/hyperv/common/run_config.go | 1 - builder/hyperv/common/step_run.go | 1 - builder/hyperv/iso/builder.go | 1 - 3 files changed, 3 deletions(-) diff --git a/builder/hyperv/common/run_config.go b/builder/hyperv/common/run_config.go index 3ac72471c..35cdb3b5d 100644 --- a/builder/hyperv/common/run_config.go +++ b/builder/hyperv/common/run_config.go @@ -7,7 +7,6 @@ import ( ) type RunConfig struct { - Headless bool `mapstructure:"headless"` RawBootWait string `mapstructure:"boot_wait"` BootWait time.Duration `` diff --git a/builder/hyperv/common/step_run.go b/builder/hyperv/common/step_run.go index c02714692..d3e3b439a 100644 --- a/builder/hyperv/common/step_run.go +++ b/builder/hyperv/common/step_run.go @@ -13,7 +13,6 @@ import ( type StepRun struct { BootWait time.Duration - Headless bool vmName string } diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index e81f498e0..3357dd087 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -364,7 +364,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &hypervcommon.StepRun{ BootWait: b.config.BootWait, - Headless: b.config.Headless, }, &hypervcommon.StepTypeBootCommand{ From 04a6880361c0fd74a8f27568d50ea0bc3ed9199c Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 6 Nov 2016 15:18:39 +0000 Subject: [PATCH 099/108] Remove unused import --- builder/hyperv/common/ssh_config.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/builder/hyperv/common/ssh_config.go b/builder/hyperv/common/ssh_config.go index 0217287e0..3569a00b6 100644 --- a/builder/hyperv/common/ssh_config.go +++ b/builder/hyperv/common/ssh_config.go @@ -1,8 +1,6 @@ package common import ( - "time" - "github.com/mitchellh/packer/helper/communicator" "github.com/mitchellh/packer/template/interpolate" ) From fb192ce6d67b463a6204539b573b51fc3d37ac73 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 6 Nov 2016 15:38:10 +0000 Subject: [PATCH 100/108] Remove unused import --- builder/hyperv/iso/builder.go | 1 - 1 file changed, 1 deletion(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 3357dd087..dd53f7693 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -10,7 +10,6 @@ import ( "log" "os" "strings" - "time" "github.com/mitchellh/multistep" hypervcommon "github.com/mitchellh/packer/builder/hyperv/common" From 44f73fa75f1ccc31c81978b5b03cd8e1cbfc334d Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 6 Nov 2016 15:51:48 +0000 Subject: [PATCH 101/108] Changed ram_size_mb to ram_size --- builder/hyperv/iso/builder.go | 8 ++++---- builder/hyperv/iso/builder_test.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index dd53f7693..ac55399b3 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -466,9 +466,9 @@ func (b *Builder) checkDiskSize() error { log.Println(fmt.Sprintf("%s: %v", "DiskSize", b.config.DiskSize)) if b.config.DiskSize < MinDiskSize { - return fmt.Errorf("disk_size_gb: Virtual machine requires disk space >= %v GB, but defined: %v", MinDiskSize, b.config.DiskSize/1024) + return fmt.Errorf("disk_size: Virtual machine requires disk space >= %v GB, but defined: %v", MinDiskSize, b.config.DiskSize/1024) } else if b.config.DiskSize > MaxDiskSize { - return fmt.Errorf("disk_size_gb: Virtual machine requires disk space <= %v GB, but defined: %v", MaxDiskSize, b.config.DiskSize/1024) + return fmt.Errorf("disk_size: Virtual machine requires disk space <= %v GB, but defined: %v", MaxDiskSize, b.config.DiskSize/1024) } return nil @@ -482,9 +482,9 @@ func (b *Builder) checkRamSize() error { log.Println(fmt.Sprintf("%s: %v", "RamSize", b.config.RamSize)) if b.config.RamSize < MinRamSize { - return fmt.Errorf("ram_size_mb: Virtual machine requires memory size >= %v MB, but defined: %v", MinRamSize, b.config.RamSize) + return fmt.Errorf("ram_size: Virtual machine requires memory size >= %v MB, but defined: %v", MinRamSize, b.config.RamSize) } else if b.config.RamSize > MaxRamSize { - return fmt.Errorf("ram_size_mb: Virtual machine requires memory size <= %v MB, but defined: %v", MaxRamSize, b.config.RamSize) + return fmt.Errorf("ram_size: Virtual machine requires memory size <= %v MB, but defined: %v", MaxRamSize, b.config.RamSize) } return nil diff --git a/builder/hyperv/iso/builder_test.go b/builder/hyperv/iso/builder_test.go index 4d8f193a0..3adf05aa9 100644 --- a/builder/hyperv/iso/builder_test.go +++ b/builder/hyperv/iso/builder_test.go @@ -14,7 +14,7 @@ func testConfig() map[string]interface{} { "iso_url": "http://www.packer.io", "shutdown_command": "yes", "ssh_username": "foo", - "ram_size_mb": 64, + "ram_size": 64, "disk_size": 256, "guest_additions_mode": "none", packer.BuildNameConfigKey: "foo", From 335615408a55287107ee5b383044f67fe38ef6fa Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 6 Nov 2016 16:02:00 +0000 Subject: [PATCH 102/108] Run go fmt on files --- builder/hyperv/common/output_config.go | 2 +- builder/hyperv/common/step_create_tempdir.go | 4 +- .../hyperv/common/step_mount_floppydrive.go | 6 +- .../common/step_mount_guest_additions.go | 192 ++++++++--------- .../common/step_mount_secondary_dvd_images.go | 196 +++++++++--------- builder/hyperv/common/step_sleep.go | 8 +- .../hyperv/common/step_unmount_dvddrive.go | 14 +- .../hyperv/common/step_unmount_floppydrive.go | 2 +- .../common/step_unmount_guest_additions.go | 10 +- .../step_unmount_secondary_dvd_images.go | 10 +- command/plugin.go | 2 +- helper/communicator/step_connect.go | 2 +- helper/communicator/step_connect_winrm.go | 2 +- powershell/scriptbuilder.go | 7 +- provisioner/windows-shell/provisioner.go | 2 +- 15 files changed, 229 insertions(+), 230 deletions(-) diff --git a/builder/hyperv/common/output_config.go b/builder/hyperv/common/output_config.go index 40b10e6e5..ee589d49d 100644 --- a/builder/hyperv/common/output_config.go +++ b/builder/hyperv/common/output_config.go @@ -2,9 +2,9 @@ package common import ( "fmt" - "os" "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/template/interpolate" + "os" ) type OutputConfig struct { diff --git a/builder/hyperv/common/step_create_tempdir.go b/builder/hyperv/common/step_create_tempdir.go index c4682a852..d703ed5ac 100644 --- a/builder/hyperv/common/step_create_tempdir.go +++ b/builder/hyperv/common/step_create_tempdir.go @@ -30,10 +30,10 @@ func (s *StepCreateTempDir) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - s.dirPath = packerTempDir; + s.dirPath = packerTempDir state.Put("packerTempDir", packerTempDir) -// ui.Say("packerTempDir = '" + packerTempDir + "'") + // ui.Say("packerTempDir = '" + packerTempDir + "'") return multistep.ActionContinue } diff --git a/builder/hyperv/common/step_mount_floppydrive.go b/builder/hyperv/common/step_mount_floppydrive.go index 8e8254083..180a6049a 100644 --- a/builder/hyperv/common/step_mount_floppydrive.go +++ b/builder/hyperv/common/step_mount_floppydrive.go @@ -28,9 +28,9 @@ func (s *StepMountFloppydrive) Run(state multistep.StateBag) multistep.StepActio if s.Generation > 1 { return multistep.ActionContinue } - + driver := state.Get("driver").(Driver) - + // Determine if we even have a floppy disk to attach var floppyPath string if floppyPathRaw, ok := state.GetOk("floppy_path"); ok { @@ -69,7 +69,7 @@ func (s *StepMountFloppydrive) Run(state multistep.StateBag) multistep.StepActio func (s *StepMountFloppydrive) Cleanup(state multistep.StateBag) { if s.Generation > 1 { return - } + } driver := state.Get("driver").(Driver) if s.floppyPath == "" { return diff --git a/builder/hyperv/common/step_mount_guest_additions.go b/builder/hyperv/common/step_mount_guest_additions.go index dff8b90c3..9e171ac0f 100644 --- a/builder/hyperv/common/step_mount_guest_additions.go +++ b/builder/hyperv/common/step_mount_guest_additions.go @@ -1,96 +1,96 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package common - -import ( - "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "log" -) - -type StepMountGuestAdditions struct { - GuestAdditionsMode string - GuestAdditionsPath string - Generation uint -} - -func (s *StepMountGuestAdditions) Run(state multistep.StateBag) multistep.StepAction { - ui := state.Get("ui").(packer.Ui) - - if s.GuestAdditionsMode != "attach" { - ui.Say("Skipping mounting Integration Services Setup Disk...") - return multistep.ActionContinue - } - - driver := state.Get("driver").(Driver) - ui.Say("Mounting Integration Services Setup Disk...") - - vmName := state.Get("vmName").(string) - - // should be able to mount up to 60 additional iso images using SCSI - // but Windows would only allow a max of 22 due to available drive letters - // Will Windows assign DVD drives to A: and B: ? - - // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) - - var dvdControllerProperties DvdControllerProperties - - controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, s.GuestAdditionsPath, s.Generation) - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - dvdControllerProperties.ControllerNumber = controllerNumber - dvdControllerProperties.ControllerLocation = controllerLocation - dvdControllerProperties.Existing = false - state.Put("guest.dvd.properties", dvdControllerProperties) - - ui.Say(fmt.Sprintf("Mounting Integration Services dvd drive %s ...", s.GuestAdditionsPath)) - err = driver.MountDvdDrive(vmName, s.GuestAdditionsPath, controllerNumber, controllerLocation) - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", s.GuestAdditionsPath, controllerNumber, controllerLocation)) - - return multistep.ActionContinue -} - -func (s *StepMountGuestAdditions) Cleanup(state multistep.StateBag) { - if s.GuestAdditionsMode != "attach" { - return - } - - dvdControllerState := state.Get("guest.dvd.properties") - - if dvdControllerState == nil { - return - } - - dvdController := dvdControllerState.(DvdControllerProperties) - ui := state.Get("ui").(packer.Ui) - driver := state.Get("driver").(Driver) - vmName := state.Get("vmName").(string) - errorMsg := "Error unmounting Integration Services dvd drive: %s" - - ui.Say("Cleanup Integration Services dvd drive...") - - if dvdController.Existing { - err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) - if err != nil { - log.Print(fmt.Sprintf(errorMsg, err)) - } - } else { - err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) - if err != nil { - log.Print(fmt.Sprintf(errorMsg, err)) - } - } -} \ No newline at end of file +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" +) + +type StepMountGuestAdditions struct { + GuestAdditionsMode string + GuestAdditionsPath string + Generation uint +} + +func (s *StepMountGuestAdditions) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + if s.GuestAdditionsMode != "attach" { + ui.Say("Skipping mounting Integration Services Setup Disk...") + return multistep.ActionContinue + } + + driver := state.Get("driver").(Driver) + ui.Say("Mounting Integration Services Setup Disk...") + + vmName := state.Get("vmName").(string) + + // should be able to mount up to 60 additional iso images using SCSI + // but Windows would only allow a max of 22 due to available drive letters + // Will Windows assign DVD drives to A: and B: ? + + // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) + + var dvdControllerProperties DvdControllerProperties + + controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, s.GuestAdditionsPath, s.Generation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + dvdControllerProperties.ControllerNumber = controllerNumber + dvdControllerProperties.ControllerLocation = controllerLocation + dvdControllerProperties.Existing = false + state.Put("guest.dvd.properties", dvdControllerProperties) + + ui.Say(fmt.Sprintf("Mounting Integration Services dvd drive %s ...", s.GuestAdditionsPath)) + err = driver.MountDvdDrive(vmName, s.GuestAdditionsPath, controllerNumber, controllerLocation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", s.GuestAdditionsPath, controllerNumber, controllerLocation)) + + return multistep.ActionContinue +} + +func (s *StepMountGuestAdditions) Cleanup(state multistep.StateBag) { + if s.GuestAdditionsMode != "attach" { + return + } + + dvdControllerState := state.Get("guest.dvd.properties") + + if dvdControllerState == nil { + return + } + + dvdController := dvdControllerState.(DvdControllerProperties) + ui := state.Get("ui").(packer.Ui) + driver := state.Get("driver").(Driver) + vmName := state.Get("vmName").(string) + errorMsg := "Error unmounting Integration Services dvd drive: %s" + + ui.Say("Cleanup Integration Services dvd drive...") + + if dvdController.Existing { + err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) + if err != nil { + log.Print(fmt.Sprintf(errorMsg, err)) + } + } else { + err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) + if err != nil { + log.Print(fmt.Sprintf(errorMsg, err)) + } + } +} diff --git a/builder/hyperv/common/step_mount_secondary_dvd_images.go b/builder/hyperv/common/step_mount_secondary_dvd_images.go index eb5ce25bc..6072737b5 100644 --- a/builder/hyperv/common/step_mount_secondary_dvd_images.go +++ b/builder/hyperv/common/step_mount_secondary_dvd_images.go @@ -1,98 +1,98 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package common - -import ( - "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "log" -) - -type StepMountSecondaryDvdImages struct { - IsoPaths []string - Generation uint -} - -type DvdControllerProperties struct { - ControllerNumber uint - ControllerLocation uint - Existing bool -} - -func (s *StepMountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction { - driver := state.Get("driver").(Driver) - ui := state.Get("ui").(packer.Ui) - ui.Say("Mounting secondary DVD images...") - - vmName := state.Get("vmName").(string) - - // should be able to mount up to 60 additional iso images using SCSI - // but Windows would only allow a max of 22 due to available drive letters - // Will Windows assign DVD drives to A: and B: ? - - // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) - var dvdProperties []DvdControllerProperties - - for _, isoPath := range s.IsoPaths { - var properties DvdControllerProperties - - controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, isoPath, s.Generation) - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - properties.ControllerNumber = controllerNumber - properties.ControllerLocation = controllerLocation - properties.Existing = false - dvdProperties = append(dvdProperties, properties) - state.Put("secondary.dvd.properties", dvdProperties) - - ui.Say(fmt.Sprintf("Mounting secondary dvd drive %s ...", isoPath)) - err = driver.MountDvdDrive(vmName, isoPath, controllerNumber, controllerLocation) - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", isoPath, controllerNumber, controllerLocation)) - } - - return multistep.ActionContinue -} - -func (s *StepMountSecondaryDvdImages) Cleanup(state multistep.StateBag) { - dvdControllersState := state.Get("secondary.dvd.properties") - - if dvdControllersState == nil { - return - } - - dvdControllers := dvdControllersState.([]DvdControllerProperties) - driver := state.Get("driver").(Driver) - ui := state.Get("ui").(packer.Ui) - vmName := state.Get("vmName").(string) - errorMsg := "Error unmounting secondary dvd drive: %s" - - ui.Say("Clean up secondary dvd drives...") - - for _, dvdController := range dvdControllers { - - if dvdController.Existing { - err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) - if err != nil { - log.Print(fmt.Sprintf(errorMsg, err)) - } - } else { - err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) - if err != nil { - log.Print(fmt.Sprintf(errorMsg, err)) - } - } - } -} +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" +) + +type StepMountSecondaryDvdImages struct { + IsoPaths []string + Generation uint +} + +type DvdControllerProperties struct { + ControllerNumber uint + ControllerLocation uint + Existing bool +} + +func (s *StepMountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + ui.Say("Mounting secondary DVD images...") + + vmName := state.Get("vmName").(string) + + // should be able to mount up to 60 additional iso images using SCSI + // but Windows would only allow a max of 22 due to available drive letters + // Will Windows assign DVD drives to A: and B: ? + + // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) + var dvdProperties []DvdControllerProperties + + for _, isoPath := range s.IsoPaths { + var properties DvdControllerProperties + + controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, isoPath, s.Generation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + properties.ControllerNumber = controllerNumber + properties.ControllerLocation = controllerLocation + properties.Existing = false + dvdProperties = append(dvdProperties, properties) + state.Put("secondary.dvd.properties", dvdProperties) + + ui.Say(fmt.Sprintf("Mounting secondary dvd drive %s ...", isoPath)) + err = driver.MountDvdDrive(vmName, isoPath, controllerNumber, controllerLocation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", isoPath, controllerNumber, controllerLocation)) + } + + return multistep.ActionContinue +} + +func (s *StepMountSecondaryDvdImages) Cleanup(state multistep.StateBag) { + dvdControllersState := state.Get("secondary.dvd.properties") + + if dvdControllersState == nil { + return + } + + dvdControllers := dvdControllersState.([]DvdControllerProperties) + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + errorMsg := "Error unmounting secondary dvd drive: %s" + + ui.Say("Clean up secondary dvd drives...") + + for _, dvdController := range dvdControllers { + + if dvdController.Existing { + err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) + if err != nil { + log.Print(fmt.Sprintf(errorMsg, err)) + } + } else { + err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) + if err != nil { + log.Print(fmt.Sprintf(errorMsg, err)) + } + } + } +} diff --git a/builder/hyperv/common/step_sleep.go b/builder/hyperv/common/step_sleep.go index 2d0f0053a..658e13120 100644 --- a/builder/hyperv/common/step_sleep.go +++ b/builder/hyperv/common/step_sleep.go @@ -12,17 +12,17 @@ import ( ) type StepSleep struct { - Minutes time.Duration + Minutes time.Duration ActionName string } func (s *StepSleep) Run(state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) - if(len(s.ActionName)>0){ - ui.Say(s.ActionName + "! Waiting for "+ fmt.Sprintf("%v",uint(s.Minutes)) + " minutes to let the action to complete...") + if len(s.ActionName) > 0 { + ui.Say(s.ActionName + "! Waiting for " + fmt.Sprintf("%v", uint(s.Minutes)) + " minutes to let the action to complete...") } - time.Sleep(time.Minute*s.Minutes); + time.Sleep(time.Minute * s.Minutes) return multistep.ActionContinue } diff --git a/builder/hyperv/common/step_unmount_dvddrive.go b/builder/hyperv/common/step_unmount_dvddrive.go index 26f327cc8..78078cf08 100644 --- a/builder/hyperv/common/step_unmount_dvddrive.go +++ b/builder/hyperv/common/step_unmount_dvddrive.go @@ -19,15 +19,15 @@ func (s *StepUnmountDvdDrive) Run(state multistep.StateBag) multistep.StepAction ui := state.Get("ui").(packer.Ui) ui.Say("Unmount/delete os dvd drive...") - + dvdControllerState := state.Get("os.dvd.properties") - + if dvdControllerState == nil { return multistep.ActionContinue } - - dvdController := dvdControllerState.(DvdControllerProperties) - + + dvdController := dvdControllerState.(DvdControllerProperties) + if dvdController.Existing { ui.Say(fmt.Sprintf("Unmounting os dvd drives controller %d location %d ...", dvdController.ControllerNumber, dvdController.ControllerLocation)) err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) @@ -47,9 +47,9 @@ func (s *StepUnmountDvdDrive) Run(state multistep.StateBag) multistep.StepAction return multistep.ActionHalt } } - + state.Put("os.dvd.properties", nil) - + return multistep.ActionContinue } diff --git a/builder/hyperv/common/step_unmount_floppydrive.go b/builder/hyperv/common/step_unmount_floppydrive.go index 5ae920730..5ad7a2209 100644 --- a/builder/hyperv/common/step_unmount_floppydrive.go +++ b/builder/hyperv/common/step_unmount_floppydrive.go @@ -24,7 +24,7 @@ func (s *StepUnmountFloppyDrive) Run(state multistep.StateBag) multistep.StepAct vmName := state.Get("vmName").(string) ui.Say("Unmount/delete floppy drive (Run)...") - + errorMsg := "Error Unmounting floppy drive: %s" err := driver.UnmountFloppyDrive(vmName) diff --git a/builder/hyperv/common/step_unmount_guest_additions.go b/builder/hyperv/common/step_unmount_guest_additions.go index 56c98791a..5b367e8c3 100644 --- a/builder/hyperv/common/step_unmount_guest_additions.go +++ b/builder/hyperv/common/step_unmount_guest_additions.go @@ -21,13 +21,13 @@ func (s *StepUnmountGuestAdditions) Run(state multistep.StateBag) multistep.Step ui.Say("Unmount/delete Integration Services dvd drive...") dvdControllerState := state.Get("guest.dvd.properties") - + if dvdControllerState == nil { return multistep.ActionContinue } - + dvdController := dvdControllerState.(DvdControllerProperties) - + if dvdController.Existing { ui.Say(fmt.Sprintf("Unmounting Integration Services dvd drives controller %d location %d ...", dvdController.ControllerNumber, dvdController.ControllerLocation)) err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) @@ -47,9 +47,9 @@ func (s *StepUnmountGuestAdditions) Run(state multistep.StateBag) multistep.Step return multistep.ActionHalt } } - + state.Put("guest.dvd.properties", nil) - + return multistep.ActionContinue } diff --git a/builder/hyperv/common/step_unmount_secondary_dvd_images.go b/builder/hyperv/common/step_unmount_secondary_dvd_images.go index 7c5b27781..1d7e36058 100644 --- a/builder/hyperv/common/step_unmount_secondary_dvd_images.go +++ b/builder/hyperv/common/step_unmount_secondary_dvd_images.go @@ -19,13 +19,13 @@ func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep. vmName := state.Get("vmName").(string) ui.Say("Unmount/delete secondary dvd drives...") - + dvdControllersState := state.Get("secondary.dvd.properties") - + if dvdControllersState == nil { return multistep.ActionContinue } - + dvdControllers := dvdControllersState.([]DvdControllerProperties) for _, dvdController := range dvdControllers { @@ -47,9 +47,9 @@ func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep. ui.Error(err.Error()) return multistep.ActionHalt } - } + } } - + state.Put("secondary.dvd.properties", nil) return multistep.ActionContinue diff --git a/command/plugin.go b/command/plugin.go index e48cc709e..2a2fce88f 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -82,7 +82,7 @@ var Builders = map[string]packer.Builder{ "docker": new(dockerbuilder.Builder), "file": new(filebuilder.Builder), "googlecompute": new(googlecomputebuilder.Builder), - "hyperv-iso": new(hypervbuilder.Builder), + "hyperv-iso": new(hypervbuilder.Builder), "null": new(nullbuilder.Builder), "oneandone": new(oneandonebuilder.Builder), "openstack": new(openstackbuilder.Builder), diff --git a/helper/communicator/step_connect.go b/helper/communicator/step_connect.go index b42753452..4e3ddaf14 100644 --- a/helper/communicator/step_connect.go +++ b/helper/communicator/step_connect.go @@ -56,7 +56,7 @@ func (s *StepConnect) Run(state multistep.StateBag) multistep.StepAction { Config: s.Config, Host: s.Host, WinRMConfig: s.WinRMConfig, - WinRMPort: s.WinRMPort, + WinRMPort: s.WinRMPort, }, } for k, v := range s.CustomConnect { diff --git a/helper/communicator/step_connect_winrm.go b/helper/communicator/step_connect_winrm.go index 15f07a872..a2b53ea90 100644 --- a/helper/communicator/step_connect_winrm.go +++ b/helper/communicator/step_connect_winrm.go @@ -96,7 +96,7 @@ func (s *StepConnectWinRM) waitForWinRM(state multistep.StateBag, cancel <-chan log.Printf("[DEBUG] Error getting WinRM host: %s", err) continue } - + port := s.Config.WinRMPort if s.WinRMPort != nil { port, err = s.WinRMPort(state) diff --git a/powershell/scriptbuilder.go b/powershell/scriptbuilder.go index d51156aa2..84f454bc7 100644 --- a/powershell/scriptbuilder.go +++ b/powershell/scriptbuilder.go @@ -9,14 +9,14 @@ type ScriptBuilder struct { } func (b *ScriptBuilder) WriteLine(s string) (n int, err error) { - n, err = b.buffer.WriteString(s); + n, err = b.buffer.WriteString(s) b.buffer.WriteString("\n") - return n+1, err + return n + 1, err } func (b *ScriptBuilder) WriteString(s string) (n int, err error) { - n, err = b.buffer.WriteString(s); + n, err = b.buffer.WriteString(s) return n, err } @@ -27,4 +27,3 @@ func (b *ScriptBuilder) String() string { func (b *ScriptBuilder) Reset() { b.buffer.Reset() } - diff --git a/provisioner/windows-shell/provisioner.go b/provisioner/windows-shell/provisioner.go index 930bfd3f8..6f58bcb9e 100644 --- a/provisioner/windows-shell/provisioner.go +++ b/provisioner/windows-shell/provisioner.go @@ -322,4 +322,4 @@ func (p *Provisioner) createFlattenedEnvVars() (flattened string, err error) { flattened += fmt.Sprintf(p.config.EnvVarFormat, key, envVars[key]) } return -} \ No newline at end of file +} From 602c207f60485da998626a509767cc91fde6f0d1 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 6 Nov 2016 16:20:32 +0000 Subject: [PATCH 103/108] Make it clear iso is downloaded --- website/source/docs/builders/hyperv-iso.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/builders/hyperv-iso.html.markdown b/website/source/docs/builders/hyperv-iso.html.markdown index 3c25cdbe8..4adb21d46 100644 --- a/website/source/docs/builders/hyperv-iso.html.markdown +++ b/website/source/docs/builders/hyperv-iso.html.markdown @@ -65,7 +65,7 @@ can be configured for this builder. - `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 + If this is an HTTP URL, Packer will download iso and cache it between runs. ### Optional: From 24e9810c2320c4364c7123a4c7190c7d37cd68ff Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Mon, 14 Nov 2016 22:40:14 +0000 Subject: [PATCH 104/108] guest_os_type is no longer used --- website/source/docs/builders/hyperv-iso.html.markdown | 1 - 1 file changed, 1 deletion(-) diff --git a/website/source/docs/builders/hyperv-iso.html.markdown b/website/source/docs/builders/hyperv-iso.html.markdown index 4adb21d46..b4f09dee1 100644 --- a/website/source/docs/builders/hyperv-iso.html.markdown +++ b/website/source/docs/builders/hyperv-iso.html.markdown @@ -26,7 +26,6 @@ Ubuntu to self-install. Still, the example serves to show the basic configuratio ```javascript { "type": "hyperv-iso", - "guest_os_type": "Ubuntu_64", "iso_url": "http://releases.ubuntu.com/12.04/ubuntu-12.04.5-server-amd64.iso", "iso_checksum": "769474248a3897f4865817446f9a4a53", "iso_checksum_type": "md5", From 143ac300bf18b55562ef7126ca2f2d0cab5bdaf5 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Mon, 14 Nov 2016 22:40:49 +0000 Subject: [PATCH 105/108] headless is no longer used --- website/source/docs/builders/hyperv-iso.html.markdown | 2 -- 1 file changed, 2 deletions(-) diff --git a/website/source/docs/builders/hyperv-iso.html.markdown b/website/source/docs/builders/hyperv-iso.html.markdown index b4f09dee1..20d67b544 100644 --- a/website/source/docs/builders/hyperv-iso.html.markdown +++ b/website/source/docs/builders/hyperv-iso.html.markdown @@ -351,7 +351,6 @@ Packer config: "boot_command": [ "a<wait>a<wait>a" ], - "headless": false, "iso_url": "http://download.microsoft.com/download/6/2/A/62A76ABB-9990-4EFC-A4FE-C7D698DAEB96/9600.16384.WINBLUE_RTM.130821-1623_X64FRE_SERVER_EVAL_EN-US-IRM_SSS_X64FREE_EN-US_DV5.ISO", "iso_checksum_type": "md5", "iso_checksum": "458ff91f8abc21b75cb544744bf92e6a", @@ -866,7 +865,6 @@ Packer config: "vm_name":"ubuntu-vivid", "type": "hyperv-iso", "disk_size": 61440, - "headless": false, "iso_url": "http://releases.ubuntu.com/15.04/ubuntu-15.04-server-amd64.iso", "iso_checksum_type": "sha1", "iso_checksum": "D10248965C2C749DF6BCCE9F2F90F16A2E75E843", From 74255c553b982b1fceca2bfa9d9dfb9bbf05dcf2 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Mon, 14 Nov 2016 23:11:26 +0000 Subject: [PATCH 106/108] Be smarter about loading guest additions iso. Windows 10 and Windows Server 2016 no longer come with iso. So default to not loading guest additions when the iso is missing --- builder/hyperv/iso/builder.go | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index ac55399b3..e68c38e2d 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -182,11 +182,30 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { // Errors if b.config.GuestAdditionsMode == "" { - b.config.GuestAdditionsMode = "attach" + if b.config.GuestAdditionsPath != "" { + b.config.GuestAdditionsMode = "attach" + } else { + b.config.GuestAdditionsPath = os.Getenv("WINDIR") + "\\system32\\vmguest.iso" + + if _, err := os.Stat(b.config.GuestAdditionsPath); os.IsNotExist(err) { + if err != nil { + b.config.GuestAdditionsPath = "" + b.config.GuestAdditionsMode = "none" + } else { + b.config.GuestAdditionsMode = "attach" + } + } + } } - if b.config.GuestAdditionsPath == "" { + if b.config.GuestAdditionsPath == "" && b.config.GuestAdditionsMode == "attach" { b.config.GuestAdditionsPath = os.Getenv("WINDIR") + "\\system32\\vmguest.iso" + + if _, err := os.Stat(b.config.GuestAdditionsPath); os.IsNotExist(err) { + if err != nil { + b.config.GuestAdditionsPath = "" + } + } } for _, isoPath := range b.config.SecondaryDvdImages { From e357c56186d8da1464b0649ab98690907b80521a Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Tue, 22 Nov 2016 08:10:42 +0000 Subject: [PATCH 107/108] Builders don't need to set this up anymore --- builder/hyperv/iso/builder.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index e68c38e2d..b825cb2b7 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -169,17 +169,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { log.Println(fmt.Sprintf("Using switch %s", b.config.SwitchName)) log.Println(fmt.Sprintf("%s: %v", "SwitchName", b.config.SwitchName)) - if b.config.Communicator == "" { - b.config.Communicator = "ssh" - } else if b.config.Communicator == "ssh" || b.config.Communicator == "winrm" { - // good - } else { - err = errors.New("communicator must be either ssh or winrm") - errs = packer.MultiErrorAppend(errs, err) - } - - log.Println(fmt.Sprintf("%s: %v", "Communicator", b.config.Communicator)) - // Errors if b.config.GuestAdditionsMode == "" { if b.config.GuestAdditionsPath != "" { From e50fe9fd547ae677418a5499d22458ba365fa7a4 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Wed, 23 Nov 2016 06:44:16 +0000 Subject: [PATCH 108/108] Porting over paulmey changes to license and copyright headers. Good guy Microsoft have changed the licensing to MPL2 and removed the copyright headers so that we can include it in the official Packer release. https://github.com/Microsoft/packer-hyperv/pull/21/commits/1ce6ba91f7308e1283d5d1dcc35ed43b95303ff7?diff=unified#diff-5d60887f0940c9f8d6724e59843ac1fb --- builder/hyperv/common/driver.go | 4 ---- builder/hyperv/common/driver_ps_4.go | 4 ---- builder/hyperv/common/step_configure_ip.go | 4 ---- builder/hyperv/common/step_configure_vlan.go | 4 ---- builder/hyperv/common/step_create_external_switch.go | 4 ---- builder/hyperv/common/step_create_switch.go | 4 ---- builder/hyperv/common/step_create_tempdir.go | 4 ---- builder/hyperv/common/step_create_vm.go | 4 ---- builder/hyperv/common/step_disable_vlan.go | 4 ---- builder/hyperv/common/step_enable_integration_service.go | 4 ---- builder/hyperv/common/step_export_vm.go | 4 ---- builder/hyperv/common/step_mount_dvddrive.go | 4 ---- builder/hyperv/common/step_mount_floppydrive.go | 4 ---- builder/hyperv/common/step_mount_guest_additions.go | 4 ---- builder/hyperv/common/step_mount_secondary_dvd_images.go | 4 ---- builder/hyperv/common/step_output_dir.go | 4 ---- builder/hyperv/common/step_polling_installation.go | 4 ---- builder/hyperv/common/step_reboot_vm.go | 4 ---- builder/hyperv/common/step_run.go | 4 ---- builder/hyperv/common/step_sleep.go | 4 ---- builder/hyperv/common/step_unmount_dvddrive.go | 4 ---- builder/hyperv/common/step_unmount_floppydrive.go | 4 ---- builder/hyperv/common/step_unmount_guest_additions.go | 4 ---- builder/hyperv/common/step_unmount_secondary_dvd_images.go | 4 ---- builder/hyperv/common/step_wait_for_install_to_complete.go | 4 ---- builder/hyperv/iso/builder.go | 4 ---- powershell/powershell.go | 4 ---- 27 files changed, 108 deletions(-) diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index 35df38cb3..9446c0410 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common // A driver is able to talk to HyperV and perform certain diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index 1fa522034..22152d4cf 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_configure_ip.go b/builder/hyperv/common/step_configure_ip.go index 4396a1b9a..90e17b238 100644 --- a/builder/hyperv/common/step_configure_ip.go +++ b/builder/hyperv/common/step_configure_ip.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_configure_vlan.go b/builder/hyperv/common/step_configure_vlan.go index bfd24b490..cb81a8a6c 100644 --- a/builder/hyperv/common/step_configure_vlan.go +++ b/builder/hyperv/common/step_configure_vlan.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_create_external_switch.go b/builder/hyperv/common/step_create_external_switch.go index 75249b8b4..112d79f45 100644 --- a/builder/hyperv/common/step_create_external_switch.go +++ b/builder/hyperv/common/step_create_external_switch.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_create_switch.go b/builder/hyperv/common/step_create_switch.go index b8eb02962..4f84e6bf1 100644 --- a/builder/hyperv/common/step_create_switch.go +++ b/builder/hyperv/common/step_create_switch.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_create_tempdir.go b/builder/hyperv/common/step_create_tempdir.go index d703ed5ac..8ecb81b40 100644 --- a/builder/hyperv/common/step_create_tempdir.go +++ b/builder/hyperv/common/step_create_tempdir.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index 1606a41e4..9fa65b1a8 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_disable_vlan.go b/builder/hyperv/common/step_disable_vlan.go index 631c941d2..fd101c7a1 100644 --- a/builder/hyperv/common/step_disable_vlan.go +++ b/builder/hyperv/common/step_disable_vlan.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_enable_integration_service.go b/builder/hyperv/common/step_enable_integration_service.go index 05b148232..399e1af44 100644 --- a/builder/hyperv/common/step_enable_integration_service.go +++ b/builder/hyperv/common/step_enable_integration_service.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_export_vm.go b/builder/hyperv/common/step_export_vm.go index 2ee10e16c..904a4b62e 100644 --- a/builder/hyperv/common/step_export_vm.go +++ b/builder/hyperv/common/step_export_vm.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go index cff69627e..58426a252 100644 --- a/builder/hyperv/common/step_mount_dvddrive.go +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_mount_floppydrive.go b/builder/hyperv/common/step_mount_floppydrive.go index 180a6049a..bb9272c54 100644 --- a/builder/hyperv/common/step_mount_floppydrive.go +++ b/builder/hyperv/common/step_mount_floppydrive.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_mount_guest_additions.go b/builder/hyperv/common/step_mount_guest_additions.go index 9e171ac0f..66e3a8bc6 100644 --- a/builder/hyperv/common/step_mount_guest_additions.go +++ b/builder/hyperv/common/step_mount_guest_additions.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_mount_secondary_dvd_images.go b/builder/hyperv/common/step_mount_secondary_dvd_images.go index 6072737b5..2550cd9f0 100644 --- a/builder/hyperv/common/step_mount_secondary_dvd_images.go +++ b/builder/hyperv/common/step_mount_secondary_dvd_images.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_output_dir.go b/builder/hyperv/common/step_output_dir.go index 602ce2654..209bbabe2 100644 --- a/builder/hyperv/common/step_output_dir.go +++ b/builder/hyperv/common/step_output_dir.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_polling_installation.go b/builder/hyperv/common/step_polling_installation.go index 34577de54..7cef6a108 100644 --- a/builder/hyperv/common/step_polling_installation.go +++ b/builder/hyperv/common/step_polling_installation.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_reboot_vm.go b/builder/hyperv/common/step_reboot_vm.go index 7be0f3517..6e45dfd62 100644 --- a/builder/hyperv/common/step_reboot_vm.go +++ b/builder/hyperv/common/step_reboot_vm.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_run.go b/builder/hyperv/common/step_run.go index d3e3b439a..b052b1050 100644 --- a/builder/hyperv/common/step_run.go +++ b/builder/hyperv/common/step_run.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_sleep.go b/builder/hyperv/common/step_sleep.go index 658e13120..ce61753e4 100644 --- a/builder/hyperv/common/step_sleep.go +++ b/builder/hyperv/common/step_sleep.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_unmount_dvddrive.go b/builder/hyperv/common/step_unmount_dvddrive.go index 78078cf08..3af6566b3 100644 --- a/builder/hyperv/common/step_unmount_dvddrive.go +++ b/builder/hyperv/common/step_unmount_dvddrive.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_unmount_floppydrive.go b/builder/hyperv/common/step_unmount_floppydrive.go index 5ad7a2209..c183f463b 100644 --- a/builder/hyperv/common/step_unmount_floppydrive.go +++ b/builder/hyperv/common/step_unmount_floppydrive.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_unmount_guest_additions.go b/builder/hyperv/common/step_unmount_guest_additions.go index 5b367e8c3..16a4c14e8 100644 --- a/builder/hyperv/common/step_unmount_guest_additions.go +++ b/builder/hyperv/common/step_unmount_guest_additions.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_unmount_secondary_dvd_images.go b/builder/hyperv/common/step_unmount_secondary_dvd_images.go index 1d7e36058..175f6484b 100644 --- a/builder/hyperv/common/step_unmount_secondary_dvd_images.go +++ b/builder/hyperv/common/step_unmount_secondary_dvd_images.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/common/step_wait_for_install_to_complete.go b/builder/hyperv/common/step_wait_for_install_to_complete.go index 46fc7e5cd..b1674b2d6 100644 --- a/builder/hyperv/common/step_wait_for_install_to_complete.go +++ b/builder/hyperv/common/step_wait_for_install_to_complete.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package common import ( diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index b825cb2b7..a160d4ab0 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package iso import ( diff --git a/powershell/powershell.go b/powershell/powershell.go index 93ba91150..ed8b5a6f0 100644 --- a/powershell/powershell.go +++ b/powershell/powershell.go @@ -1,7 +1,3 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. package powershell import (