diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..7230f36f4b4 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ + +common/test-fixtures/root/* eol=lf \ No newline at end of file diff --git a/builder/hyperv/common/artifact.go b/builder/hyperv/common/artifact.go new file mode 100644 index 00000000000..2baeb2d62bf --- /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 = "MSOpenTech.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 00000000000..f9ddc5dbfc8 --- /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 00000000000..eeeda864a3d --- /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 00000000000..9446c0410a9 --- /dev/null +++ b/builder/hyperv/common/driver.go @@ -0,0 +1,104 @@ +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) + + // 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 + + // 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) + + // 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 + + SetVirtualMachineCpuCount(string, uint) error + + SetVirtualMachineMacSpoofing(string, bool) error + + SetVirtualMachineDynamicMemory(string, bool) error + + SetVirtualMachineSecureBoot(string, bool) error + + SetVirtualMachineVirtualizationExtensions(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, string, uint) (uint, uint, error) + + MountDvdDrive(string, 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 new file mode 100644 index 00000000000..22152d4cf86 --- /dev/null +++ b/builder/hyperv/common/driver_ps_4.go @@ -0,0 +1,308 @@ +package common + +import ( + "fmt" + "log" + "runtime" + "strconv" + "strings" + + "github.com/mitchellh/packer/powershell" + "github.com/mitchellh/packer/powershell/hyperv" +) + +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) +} + +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) +} + +// Stop stops a VM specified by the name given. +func (d *HypervPS4Driver) Stop(vmName string) error { + return hyperv.StopVirtualMachine(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) { + 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) { + 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 +} + +// 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) + + 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) +} + +// 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) SetVirtualMachineCpuCount(vmName string, cpu uint) error { + return hyperv.SetVirtualMachineCpuCount(vmName, cpu) +} + +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 { + 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, 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 { + return hyperv.MountDvdDrive(vmName, path, 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 { + return hyperv.UnmountDvdDrive(vmName, controllerNumber, controllerLocation) +} + +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) +} + +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/floppy_config.go b/builder/hyperv/common/floppy_config.go new file mode 100644 index 00000000000..d656e103a8f --- /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 00000000000..3e4fc55db3a --- /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/output_config.go b/builder/hyperv/common/output_config.go new file mode 100644 index 00000000000..ee589d49df1 --- /dev/null +++ b/builder/hyperv/common/output_config.go @@ -0,0 +1,28 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/template/interpolate" + "os" +) + +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 00000000000..ebd91eab13d --- /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 have errors") + } +} diff --git a/builder/hyperv/common/run_config.go b/builder/hyperv/common/run_config.go new file mode 100644 index 00000000000..35cdb3b5d06 --- /dev/null +++ b/builder/hyperv/common/run_config.go @@ -0,0 +1,32 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/packer/template/interpolate" + "time" +) + +type RunConfig struct { + RawBootWait string `mapstructure:"boot_wait"` + + BootWait time.Duration `` +} + +func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { + if c.RawBootWait == "" { + c.RawBootWait = "10s" + } + + 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)) + } + } + + return errs +} diff --git a/builder/hyperv/common/run_config_test.go b/builder/hyperv/common/run_config_test.go new file mode 100644 index 00000000000..8068fe625e3 --- /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 00000000000..83d2224c386 --- /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 00000000000..5da613a19a2 --- /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/ssh.go b/builder/hyperv/common/ssh.go new file mode 100644 index 00000000000..1bb06bfd250 --- /dev/null +++ b/builder/hyperv/common/ssh.go @@ -0,0 +1,49 @@ +package common + +import ( + "github.com/mitchellh/multistep" + commonssh "github.com/mitchellh/packer/common/ssh" + "github.com/mitchellh/packer/communicator/ssh" + gossh "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) (*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.Comm.SSHPrivateKey != "" { + signer, err := commonssh.FileSigner(config.Comm.SSHPrivateKey) + if err != nil { + return nil, err + } + + auth = append(auth, gossh.PublicKeys(signer)) + } + + return &gossh.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 00000000000..3569a00b621 --- /dev/null +++ b/builder/hyperv/common/ssh_config.go @@ -0,0 +1,14 @@ +package common + +import ( + "github.com/mitchellh/packer/helper/communicator" + "github.com/mitchellh/packer/template/interpolate" +) + +type SSHConfig struct { + Comm communicator.Config `mapstructure:",squash"` +} + +func (c *SSHConfig) Prepare(ctx *interpolate.Context) []error { + 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 00000000000..90e17b23867 --- /dev/null +++ b/builder/hyperv/common/step_configure_ip.go @@ -0,0 +1,75 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" + "strings" + "time" +) + +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 := driver.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 := driver.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 00000000000..cb81a8a6c70 --- /dev/null +++ b/builder/hyperv/common/step_configure_vlan.go @@ -0,0 +1,52 @@ +package common + +import ( + "fmt" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +type StepConfigureVlan struct { + VlanId string + SwitchVlanId string +} + +func (s *StepConfigureVlan) Run(state multistep.StateBag) multistep.StepAction { + 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) + vlanId := s.VlanId + switchVlanId := s.SwitchVlanId + + ui.Say("Configuring vlan...") + + 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 + } + } + + 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 +} + +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 00000000000..112d79f4576 --- /dev/null +++ b/builder/hyperv/common/step_create_external_switch.go @@ -0,0 +1,103 @@ +package common + +import ( + "fmt" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/common/uuid" + "github.com/mitchellh/packer/packer" +) + +// 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.TimeOrderedUUID() + + 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 = "" + return multistep.ActionHalt + } + + switchName, err := driver.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 = driver.ConnectVirtualMachineNetworkAdapterToSwitch(vmName, s.oldSwitchName) + if err != nil { + ui.Error(fmt.Sprintf(errMsg, err)) + return + } + + state.Put("SwitchName", s.oldSwitchName) + + 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 new file mode 100644 index 00000000000..4f84e6bf1a4 --- /dev/null +++ b/builder/hyperv/common/step_create_switch.go @@ -0,0 +1,78 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +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 := 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 = "" + 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 := driver.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 00000000000..8ecb81b40ad --- /dev/null +++ b/builder/hyperv/common/step_create_tempdir.go @@ -0,0 +1,51 @@ +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 00000000000..9fa65b1a86e --- /dev/null +++ b/builder/hyperv/common/step_create_vm.go @@ -0,0 +1,114 @@ +package common + +import ( + "fmt" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +// This step creates the actual virtual machine. +// +// Produces: +// VMName string - The name of the VM +type StepCreateVM struct { + VMName string + SwitchName string + RamSize uint + DiskSize uint + Generation uint + Cpu uint + EnableMacSpoofing bool + EnableDynamicMemory bool + EnableSecureBoot bool + EnableVirtualizationExtensions 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 + ramSize := int64(s.RamSize * 1024 * 1024) + diskSize := int64(s.DiskSize * 1024 * 1024) + + 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) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + 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) + ui.Error(err.Error()) + 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.SetVirtualMachineSecureBoot(s.VMName, s.EnableSecureBoot) + if err != nil { + err := fmt.Errorf("Error setting secure boot: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + 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) + + 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 := 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 new file mode 100644 index 00000000000..fd101c7a1fe --- /dev/null +++ b/builder/hyperv/common/step_disable_vlan.go @@ -0,0 +1,35 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +type StepDisableVlan struct { +} + +func (s *StepDisableVlan) Run(state multistep.StateBag) multistep.StepAction { + 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 := driver.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 00000000000..399e1af4463 --- /dev/null +++ b/builder/hyperv/common/step_enable_integration_service.go @@ -0,0 +1,35 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +type StepEnableIntegrationService struct { + name string +} + +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 := driver.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_export_vm.go b/builder/hyperv/common/step_export_vm.go new file mode 100644 index 00000000000..904a4b62e76 --- /dev/null +++ b/builder/hyperv/common/step_export_vm.go @@ -0,0 +1,85 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "io/ioutil" + "path/filepath" +) + +const ( + vhdDir string = "Virtual Hard Disks" + vmDir string = "Virtual Machines" +) + +type StepExportVm struct { + OutputDir string + SkipCompaction bool +} + +func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + 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 = driver.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) + + if s.SkipCompaction { + ui.Say("Skipping disk compaction...") + } else { + ui.Say("Compacting disks...") + err = driver.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 = driver.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 00000000000..58426a252f0 --- /dev/null +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -0,0 +1,91 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" +) + +type StepMountDvdDrive struct { + Generation uint +} + +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 := 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: ? + + // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) + + var dvdControllerProperties DvdControllerProperties + controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, isoPath, 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("os.dvd.properties", dvdControllerProperties) + + ui.Say(fmt.Sprintf("Setting boot drive to os dvd drive %s ...", isoPath)) + err = driver.SetBootDvdDrive(vmName, controllerNumber, controllerLocation, s.Generation) + 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, controllerNumber, controllerLocation) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +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) + 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...") + + 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, 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_floppydrive.go b/builder/hyperv/common/step_mount_floppydrive.go new file mode 100644 index 00000000000..bb9272c54f4 --- /dev/null +++ b/builder/hyperv/common/step_mount_floppydrive.go @@ -0,0 +1,118 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "io" + "io/ioutil" + "log" + "os" + "path/filepath" +) + +const ( + FloppyFileName = "assets.vfd" +) + +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 { + 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 = driver.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.Generation > 1 { + return + } + driver := state.Get("driver").(Driver) + if s.floppyPath == "" { + return + } + + errorMsg := "Error unmounting floppy drive: %s" + + vmName := state.Get("vmName").(string) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Cleanup floppy drive...") + + err := driver.UnmountFloppyDrive(vmName) + if err != nil { + log.Print(fmt.Sprintf(errorMsg, err)) + } + + err = os.Remove(s.floppyPath) + + if err != nil { + log.Print(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_guest_additions.go b/builder/hyperv/common/step_mount_guest_additions.go new file mode 100644 index 00000000000..66e3a8bc67d --- /dev/null +++ b/builder/hyperv/common/step_mount_guest_additions.go @@ -0,0 +1,92 @@ +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 new file mode 100644 index 00000000000..2550cd9f038 --- /dev/null +++ b/builder/hyperv/common/step_mount_secondary_dvd_images.go @@ -0,0 +1,94 @@ +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_output_dir.go b/builder/hyperv/common/step_output_dir.go new file mode 100644 index 00000000000..209bbabe2a4 --- /dev/null +++ b/builder/hyperv/common/step_output_dir.go @@ -0,0 +1,67 @@ +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 00000000000..7cef6a10878 --- /dev/null +++ b/builder/hyperv/common/step_polling_installation.go @@ -0,0 +1,79 @@ +package common + +import ( + "bytes" + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" + "os/exec" + "strings" + "time" +) + +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...") + 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 00000000000..6e45dfd62e8 --- /dev/null +++ b/builder/hyperv/common/step_reboot_vm.go @@ -0,0 +1,40 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "time" +) + +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 := driver.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_run.go b/builder/hyperv/common/step_run.go new file mode 100644 index 00000000000..b052b1050fd --- /dev/null +++ b/builder/hyperv/common/step_run.go @@ -0,0 +1,65 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "time" +) + +type StepRun struct { + BootWait time.Duration + + 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_shutdown.go b/builder/hyperv/common/step_shutdown.go new file mode 100644 index 00000000000..db60a15176b --- /dev/null +++ b/builder/hyperv/common/step_shutdown.go @@ -0,0 +1,104 @@ +package common + +import ( + "bytes" + "errors" + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" + "time" +) + +// 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() + + stderrString := stderr.String() + stdoutString := stdout.String() + + // If the command failed to run, notify the user in some way. + 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)) + return multistep.ActionHalt + } + + 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) + 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 00000000000..ce61753e425 --- /dev/null +++ b/builder/hyperv/common/step_sleep.go @@ -0,0 +1,28 @@ +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_type_boot_command.go b/builder/hyperv/common/step_type_boot_command.go new file mode 100644 index 00000000000..4af792effc1 --- /dev/null +++ b/builder/hyperv/common/step_type_boot_command.go @@ -0,0 +1,278 @@ +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 Hyper-V virtual keyboard +type StepTypeBootCommand struct { + BootCommand []string + SwitchName 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) + + 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, + 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(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 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 + // 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"} + special[""] = []string{"38", "b8"} + special[""] = []string{"1d", "9d"} + special[""] = []string{"2a", "aa"} + special[""] = []string{"e038", "e0b8"} + special[""] = []string{"e01d", "e09d"} + special[""] = []string{"36", "b6"} + + 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, "") { + scancode = []string{"38"} + message = message[len(""):] + log.Printf("Special code '' found, replacing with: 38") + } + + if strings.HasPrefix(message, "") { + scancode = []string{"1d"} + message = message[len(""):] + log.Printf("Special code '' found, replacing with: 1d") + } + + if strings.HasPrefix(message, "") { + scancode = []string{"2a"} + message = message[len(""):] + log.Printf("Special code '' found, replacing with: 2a") + } + + if strings.HasPrefix(message, "") { + scancode = []string{"b8"} + message = message[len(""):] + log.Printf("Special code '' found, replacing with: b8") + } + + if strings.HasPrefix(message, "") { + scancode = []string{"9d"} + message = message[len(""):] + log.Printf("Special code '' found, replacing with: 9d") + } + + if strings.HasPrefix(message, "") { + scancode = []string{"aa"} + message = message[len(""):] + log.Printf("Special code '' found, replacing with: aa") + } + + if strings.HasPrefix(message, "") { + scancode = []string{"e038"} + message = message[len(""):] + log.Printf("Special code '' found, replacing with: e038") + } + + if strings.HasPrefix(message, "") { + scancode = []string{"e01d"} + message = message[len(""):] + log.Printf("Special code '' found, replacing with: e01d") + } + + if strings.HasPrefix(message, "") { + scancode = []string{"36"} + message = message[len(""):] + log.Printf("Special code '' found, replacing with: 36") + } + + if strings.HasPrefix(message, "") { + scancode = []string{"e0b8"} + message = message[len(""):] + log.Printf("Special code '' found, replacing with: e0b8") + } + + if strings.HasPrefix(message, "") { + scancode = []string{"e09d"} + message = message[len(""):] + log.Printf("Special code '' found, replacing with: e09d") + } + + if strings.HasPrefix(message, "") { + scancode = []string{"b6"} + message = message[len(""):] + log.Printf("Special code '' found, replacing with: b6") + } + + 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/common/step_unmount_dvddrive.go b/builder/hyperv/common/step_unmount_dvddrive.go new file mode 100644 index 00000000000..3af6566b306 --- /dev/null +++ b/builder/hyperv/common/step_unmount_dvddrive.go @@ -0,0 +1,53 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +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) + + ui.Say("Unmount/delete os dvd drive...") + + dvdControllerState := state.Get("os.dvd.properties") + + if dvdControllerState == nil { + return multistep.ActionContinue + } + + 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) + 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 { + 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) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + state.Put("os.dvd.properties", nil) + + 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 00000000000..c183f463b49 --- /dev/null +++ b/builder/hyperv/common/step_unmount_floppydrive.go @@ -0,0 +1,38 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +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 + } + + vmName := state.Get("vmName").(string) + ui.Say("Unmount/delete floppy drive (Run)...") + + errorMsg := "Error Unmounting floppy drive: %s" + + err := driver.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_guest_additions.go b/builder/hyperv/common/step_unmount_guest_additions.go new file mode 100644 index 00000000000..16a4c14e893 --- /dev/null +++ b/builder/hyperv/common/step_unmount_guest_additions.go @@ -0,0 +1,53 @@ +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("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) + 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 { + 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) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + state.Put("guest.dvd.properties", nil) + + 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 00000000000..175f6484b09 --- /dev/null +++ b/builder/hyperv/common/step_unmount_secondary_dvd_images.go @@ -0,0 +1,55 @@ +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("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 { + 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) + ui.Error(err.Error()) + 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) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + } + + state.Put("secondary.dvd.properties", nil) + + return multistep.ActionContinue +} + +func (s *StepUnmountSecondaryDvdImages) Cleanup(state multistep.StateBag) { +} 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 00000000000..b1674b2d625 --- /dev/null +++ b/builder/hyperv/common/step_wait_for_install_to_complete.go @@ -0,0 +1,90 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "time" +) + +const ( + SleepSeconds = 10 +) + +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...") + + for { + isOff, err := driver.IsOff(vmName) + + if err != nil { + err := fmt.Errorf("Error checking if vm is off: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + if isOff { + 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 { + 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)) + } + + var rebootCount uint + var lastUptime uint64 + + for rebootCount < s.ExpectedRebootCount { + uptime, err := driver.Uptime(vmName) + + if err != nil { + err := fmt.Errorf("Error checking uptime: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + 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) + } + } + + return multistep.ActionContinue +} + +func (s *StepWaitForInstallToComplete) Cleanup(state multistep.StateBag) { + +} diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go new file mode 100644 index 00000000000..a160d4ab0d7 --- /dev/null +++ b/builder/hyperv/iso/builder.go @@ -0,0 +1,523 @@ +package iso + +import ( + "errors" + "fmt" + "log" + "os" + "strings" + + "github.com/mitchellh/multistep" + hypervcommon "github.com/mitchellh/packer/builder/hyperv/common" + "github.com/mitchellh/packer/common" + "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" +) + +const ( + DefaultDiskSize = 40 * 1024 // ~40GB + MinDiskSize = 256 // 256MB + MaxDiskSize = 64 * 1024 * 1024 // 64TB + + DefaultRamSize = 1 * 1024 // 1GB + MinRamSize = 32 // 32MB + MaxRamSize = 32 * 1024 // 32GB + MinNestedVirtualizationRamSize = 4 * 1024 // 4GB + + LowRam = 256 // 256MB + + DefaultUsername = "" + DefaultPassword = "" +) + +// Builder implements packer.Builder and builds the actual Hyperv +// images. +type Builder struct { + config Config + runner multistep.Runner +} + +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"` + 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). + DiskSize uint `mapstructure:"disk_size"` + // The size, in megabytes, of the computer memory in the VM. + // By default, this is 1024 (about 1 GB). + 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, + // 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"` + + // 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. + VMName string `mapstructure:"vm_name"` + + BootCommand []string `mapstructure:"boot_command"` + SwitchName string `mapstructure:"switch_name"` + 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"` + EnableDynamicMemory bool `mapstructure:"enable_dynamic_memory"` + EnableSecureBoot bool `mapstructure:"enable_secure_boot"` + EnableVirtualizationExtensions bool `mapstructure:"enable_virtualization_extensions"` + + Communicator string `mapstructure:"communicator"` + + SkipCompaction bool `mapstructure:"skip_compaction"` + + ctx interpolate.Context +} + +// Prepare processes the build configuration parameters. +func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { + err := config.Decode(&b.config, &config.DecodeOpts{ + Interpolate: true, + InterpolateFilter: &interpolate.RenderFilter{ + Exclude: []string{ + "boot_command", + }, + }, + }, raws...) + if err != nil { + return nil, err + } + + // 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)...) + + 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("packer-%s", b.config.PackerBuildName) + } + + log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName)) + + if b.config.SwitchName == "" { + b.config.SwitchName = b.detectSwitchName() + } + + if b.config.Cpu < 1 { + b.config.Cpu = 1 + } + + if b.config.Generation != 2 { + b.config.Generation = 1 + } + + if b.config.Generation == 2 { + 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) + } + } + + log.Println(fmt.Sprintf("Using switch %s", b.config.SwitchName)) + log.Println(fmt.Sprintf("%s: %v", "SwitchName", b.config.SwitchName)) + + // Errors + if b.config.GuestAdditionsMode == "" { + 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 == "" && 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 { + 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, ", "))) + } + } + + 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 == "" { + 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.") + } + + warning := b.checkHostAvailableMemory() + if warning != "" { + 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.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) + } + } + + 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 + } + + 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("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) + + 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, + }, + &common.StepHTTPServer{ + HTTPDir: b.config.HTTPDir, + HTTPPortMin: b.config.HTTPPortMin, + HTTPPortMax: b.config.HTTPPortMax, + }, + &hypervcommon.StepCreateSwitch{ + SwitchName: b.config.SwitchName, + }, + &hypervcommon.StepCreateVM{ + VMName: b.config.VMName, + SwitchName: b.config.SwitchName, + RamSize: b.config.RamSize, + 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{}, + + &hypervcommon.StepMountDvdDrive{ + 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.StepMountSecondaryDvdImages{ + IsoPaths: b.config.SecondaryDvdImages, + Generation: b.config.Generation, + }, + + &hypervcommon.StepConfigureVlan{ + VlanId: b.config.VlanId, + SwitchVlanId: b.config.SwitchVlanId, + }, + + &hypervcommon.StepRun{ + BootWait: b.config.BootWait, + }, + + &hypervcommon.StepTypeBootCommand{ + BootCommand: b.config.BootCommand, + SwitchName: b.config.SwitchName, + Ctx: b.config.ctx, + }, + + // configure the communicator ssh, winrm + &communicator.StepConnect{ + Config: &b.config.SSHConfig.Comm, + Host: hypervcommon.CommHost, + SSHConfig: hypervcommon.SSHConfigFunc(&b.config.SSHConfig), + }, + + // provision requires communicator to be setup + &common.StepProvision{}, + + &hypervcommon.StepShutdown{ + Command: b.config.ShutdownCommand, + Timeout: b.config.ShutdownTimeout, + }, + + // wait for the vm to be powered off + &hypervcommon.StepWaitForPowerOff{}, + + // remove the secondary dvd images + // after we power down + &hypervcommon.StepUnmountSecondaryDvdImages{}, + &hypervcommon.StepUnmountGuestAdditions{}, + &hypervcommon.StepUnmountDvdDrive{}, + &hypervcommon.StepUnmountFloppyDrive{ + Generation: b.config.Generation, + }, + &hypervcommon.StepExportVm{ + OutputDir: b.config.OutputDir, + SkipCompaction: b.config.SkipCompaction, + }, + + // the clean up actions for each step will be executed reverse order + } + + // Run the steps. + if b.config.PackerDebug { + pauseFn := common.MultistepDebugFn(ui) + state.Put("pauseFn", pauseFn) + b.runner = &multistep.DebugRunner{ + Steps: steps, + PauseFn: pauseFn, + } + } 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: 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: Virtual machine requires disk space <= %v GB, but defined: %v", MaxDiskSize, b.config.DiskSize/1024) + } + + return nil +} + +func (b *Builder) checkRamSize() error { + if b.config.RamSize == 0 { + b.config.RamSize = DefaultRamSize + } + + log.Println(fmt.Sprintf("%s: %v", "RamSize", b.config.RamSize)) + + if b.config.RamSize < MinRamSize { + 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: Virtual machine requires memory size <= %v MB, but defined: %v", MaxRamSize, b.config.RamSize) + } + + return nil +} + +func (b *Builder) checkHostAvailableMemory() string { + powershellAvailable, _, _ := powershell.IsPowershellAvailable() + + if powershellAvailable { + freeMB := powershell.GetHostAvailableMemory() + + 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.") + } + } + + 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 new file mode 100644 index 00000000000..3adf05aa9ad --- /dev/null +++ b/builder/hyperv/iso/builder_test.go @@ -0,0 +1,250 @@ +package iso + +import ( + "reflect" + "testing" + + "github.com/mitchellh/packer/packer" +) + +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": 64, + "disk_size": 256, + "guest_additions_mode": "none", + 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 != 40*1024 { + t.Fatalf("bad size: %d", b.config.DiskSize) + } + + config["disk_size"] = 256 + 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 != 256 { + 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) + } +} diff --git a/command/plugin.go b/command/plugin.go index dce7d93118f..2a2fce88f3a 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/iso" nullbuilder "github.com/mitchellh/packer/builder/null" oneandonebuilder "github.com/mitchellh/packer/builder/oneandone" openstackbuilder "github.com/mitchellh/packer/builder/openstack" @@ -34,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" @@ -49,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" @@ -79,6 +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), "null": new(nullbuilder.Builder), "oneandone": new(oneandonebuilder.Builder), "openstack": new(openstackbuilder.Builder), diff --git a/common/config_test.go b/common/config_test.go index 92a7316a314..8bd68473919 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 51f6f270c82..497a0564990 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 655e92d9848..49ee681ab03 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 845e4e9dc18..b64a00bcf7b 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) diff --git a/helper/communicator/step_connect.go b/helper/communicator/step_connect.go index f4d1499b7c8..4e3ddaf1475 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 49936ad7b54..a2b53ea9007 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) diff --git a/post-processor/vagrant/hyperv.go b/post-processor/vagrant/hyperv.go index d5fa0ce5234..2f7dc144059 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)) + 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 diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go new file mode 100644 index 00000000000..67e26216ada --- /dev/null +++ b/powershell/hyperv/hyperv.go @@ -0,0 +1,984 @@ +package hyperv + +import ( + "errors" + "strconv" + "strings" + + "github.com/mitchellh/packer/powershell" +) + +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 = ` +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 CreateDvdDrive(vmName string, isoPath string, generation uint) (uint, uint, error) { + var ps powershell.PowerShellCmd + var script string + + script = ` +param([string]$vmName, [string]$isoPath) +$dvdController = Add-VMDvdDrive -VMName $vmName -path $isoPath -Passthru +$dvdController | Set-VMDvdDrive -path $null +$result = "$($dvdController.ControllerNumber),$($dvdController.ControllerLocation)" +$result +` + + 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 + } + controllerLocation := uint(controllerLocationTemp) + + return controllerNumber, controllerLocation, err +} + +func MountDvdDrive(vmName string, path string, controllerNumber uint, controllerLocation uint) error { + + var script = ` +param([string]$vmName,[string]$path,[string]$controllerNumber,[string]$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 + err := ps.Run(script, vmName, path, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10)) + return err +} + +func UnmountDvdDrive(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-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation -Path $null +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10)) + return err +} + +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 -ErrorAction SilentlyContinue +` + 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 +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, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10)) + 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) +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 int64, diskSize int64, switchName string, generation uint) error { + + 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, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), switchName, strconv.FormatInt(int64(generation), 10)) + 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, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), switchName) + + if err != nil { + return err + } + + return DeleteAllDvdDrives(vmName) + } +} + +func SetVirtualMachineCpuCount(vmName string, cpu uint) error { + + var script = ` +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, exposeVirtualizationExtensionsString) + return err +} + +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 +` + + var ps powershell.PowerShellCmd + + enableSecureBootString := "Off" + if enableSecureBoot { + enableSecureBootString = "On" + } + + err := ps.Run(script, vmName, enableSecureBootString) + return err +} + +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 -Confirm:$false +} + +Remove-VM -Name $vmName -Force -Confirm:$false +` + + 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 + +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]@" + + + + $($vm.Generation - 1) + $($vm.Name) + + + + $($vm.ProcessorCount) + + + + $($vm.DynamicMemoryEnabled) + $($vm.MemoryMaximum / 1MB) + $($vm.MemoryMinimum / 1MB) + $($vm.MemoryStartup / 1MB) + + + + $($vm_adapter.SwitchName) + + Optical + + False + $($vm.Notes) + + +"@ + + 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 + err := ps.Run(script, vmName, 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 = ` +param([string]$srcPath, [string]$dstPath, [string]$vhdDirName, [string]$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 + 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 -Confirm:$false +} +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, switchName) + return err +} + +func StartVirtualMachine(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 -Confirm:$false +} +` + + 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 -Confirm:$false +` + + 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 -Force -Confirm:$false +} +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} + +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]$integrationServiceId) +Get-VMIntegrationService -VmName $vmName | ?{$_.Id -match $integrationServiceId} | Enable-VMIntegrationService +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, integrationServiceId) + 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) + + 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) +try { + $adapter = Get-VMNetworkAdapter -VMName $vmName -ErrorAction SilentlyContinue + $mac = $adapter[$adapterIndex].MacAddress + if($mac -eq $null) { + return "" + } +} catch { + return "" +} +$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 "" + } +} catch { + return "" +} +$ip +` + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script, mac, "0") + + return cmdOut, 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 -Force -Confirm:$false +} +` + + 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 -Force -Confirm:$false +} +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} + +func TypeScanCodes(vmName string, scanCodes string) error { + if len(scanCodes) == 0 { + return nil + } + + 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 -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) { + $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) + } + + #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 = "1" + } + + if ($scanCodesToSend){ + $scanCodesToSendByteArray = [byte[]]@($scanCodesToSend.Split(' ') | %{"0x$_"}) + + $scanCodesToSendByteArray | %{ + $vmConsole.TypeScancodes($_) + } + } + + write-host "Special code found, will sleep $timeToWait second(s) at this point." + Start-Sleep -s $timeToWait + + $scanCodesToSend = '' + } else { + if ($scanCodesToSend){ + write-host "Sending special code '$scanCodesToSend' '$scanCode'" + $scanCodesToSend = "$scanCodesToSend $scanCode" + } else { + write-host "Sending char '$scanCode'" + $scanCodesToSend = "$scanCode" + } + } + } + if ($scanCodesToSend){ + $scanCodesToSendByteArray = [byte[]]@($scanCodesToSend.Split(' ') | %{"0x$_"}) + + $scanCodesToSendByteArray | %{ + $vmConsole.TypeScancodes($_) + } + } +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, scanCodes) + return err +} diff --git a/powershell/powershell.go b/powershell/powershell.go new file mode 100644 index 00000000000..ed8b5a6f02d --- /dev/null +++ b/powershell/powershell.go @@ -0,0 +1,276 @@ +package powershell + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "os/exec" + "strconv" + "strings" +) + +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 "", err + } + + 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 IsPowershellAvailable() (bool, string, error) { + path, err := exec.LookPath("powershell") + if err != nil { + return false, "", err + } else { + return true, path, err + } +} + +func (ps *PowerShellCmd) getPowerShellPath() (string, error) { + powershellAvailable, path, err := IsPowershellAvailable() + + if !powershellAvailable { + log.Fatal("Cannot find PowerShell in the path") + 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 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 = ` +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 00000000000..91c9a83f83c --- /dev/null +++ b/powershell/powershell_test.go @@ -0,0 +1,69 @@ +package powershell + +import ( + "bytes" + "testing" +) + +func TestOutput(t *testing.T) { + + var ps PowerShellCmd + + powershellAvailable, _, _ := IsPowershellAvailable() + + if !powershellAvailable { + t.Skipf("powershell not installed") + return + } + + cmdOut, err := ps.Output("") + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + 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) + } + + if trueOutput != "True" { + t.Fatalf("output '%v' is not 'True'", trueOutput) + } + + falseOutput, err := ps.Output("$False") + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if falseOutput != "False" { + t.Fatalf("output '%v' is not 'False'", falseOutput) + } +} + +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";`) + + 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) + } +} diff --git a/powershell/scriptbuilder.go b/powershell/scriptbuilder.go new file mode 100644 index 00000000000..84f454bc7e8 --- /dev/null +++ b/powershell/scriptbuilder.go @@ -0,0 +1,29 @@ +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() +} diff --git a/provisioner/powershell/elevated.go b/provisioner/powershell/elevated.go index 00bc72e4adf..80bdc005a6d 100644 --- a/provisioner/powershell/elevated.go +++ b/provisioner/powershell/elevated.go @@ -58,6 +58,7 @@ $t.XmlText = @' '@ +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") @@ -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-Host "$_" - } - } - 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/powershell.go b/provisioner/powershell/powershell.go index 1f5a7ffad64..086e3e5542e 100644 --- a/provisioner/powershell/powershell.go +++ b/provisioner/powershell/powershell.go @@ -2,16 +2,53 @@ package powershell import ( "encoding/base64" + "encoding/binary" + "unicode/utf16" + "unicode/utf8" + + "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 convertUtf8ToUtf16LE(message string) (string, error) { + utf16le := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM) + utfEncoder := utf16le.NewEncoder() + ut16LeEncodedMessage, err := utfEncoder.String(message) + + 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) { + utf16LEEncodedMessage, err := convertUtf8ToUtf16LE(message) + if err != nil { + return "", err } // Base64 encode the command - input := []uint8(wideCmd) - return base64.StdEncoding.EncodeToString(input) + input := []uint8(utf16LEEncodedMessage) + return base64.StdEncoding.EncodeToString(input), nil +} + +func powershellDecode(messageBase64 string) (retour string, err error) { + messageUtf16LeByteArray, err := base64.StdEncoding.DecodeString(messageBase64) + + if err != nil { + return "", err + } + + message := UTF16BytesToString(messageUtf16LeByteArray, binary.LittleEndian) + + return message, nil } diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index a862ef9b3d5..ba0e2def3fd 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -107,12 +107,13 @@ 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 == "" { @@ -120,11 +121,11 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if p.config.ExecuteCommand == "" { - p.config.ExecuteCommand = `powershell "& { {{.Vars}}{{.Path}}; exit $LastExitCode}"` + 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 { @@ -347,8 +348,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] @@ -373,28 +375,57 @@ 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 + commandText, err := p.generateCommandLineRunner(command) + if err != nil { + return "", fmt.Errorf("Error generating command line runner: %s", 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(command) + 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) { // 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, @@ -407,11 +438,14 @@ func (p *Provisioner) createCommandText() (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) - return + return command, err } func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath string, err error) { @@ -419,12 +453,18 @@ func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath strin // generate command var buffer bytes.Buffer + + base64EncodedCommand, err := powershellEncode(command) + 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(command + "; exit $LASTEXITCODE")), + EncodedCommand: base64EncodedCommand, }) if err != nil { diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index 78484c18415..473fa1d8efa 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 \"& { {{.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 != `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 powershell {{.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 { @@ -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) } } @@ -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,18 +389,30 @@ 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 := `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` + expectedCommandBase64Encoded := `aQBmACAAKABUAGUAcwB0AC0AUABhAHQAaAAgAHYAYQByAGkAYQBiAGwAZQA6AGcAbABvAGIAYQBsADoAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAKQB7ACQAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAPQAnAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAJwB9ADsAJABlAG4AdgA6AFAAQQBDAEsARQBSAF8AQgBVAEkATABEAEUAUgBfAFQAWQBQAEUAPQAiAGkAcwBvACIAOwAgACQAZQBuAHYAOgBQAEEAQwBLAEUAUgBfAEIAVQBJAEwARABfAE4AQQBNAEUAPQAiAHYAbQB3AGEAcgBlACIAOwAgACYAJwBjADoALwBXAGkAbgBkAG8AdwBzAC8AVABlAG0AcAAvAGkAbgBsAGkAbgBlAFMAYwByAGkAcAB0AC4AcABzADEAJwA7AGUAeABpAHQAIAAkAEwAYQBzAHQARQB4AGkAdABDAG8AZABlAA==` + expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` + expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded - // Should run the command without alteration - if comm.StartCmd.Command != expectedCommand { - t.Fatalf("Expect command to be: %s, got %s", expectedCommand, comm.StartCmd.Command) + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded, err := powershellDecode(actualCommandWithoutPrefix) + if err != nil { + t.Fatal("should not have error when base64 decoding") + } + + if actualCommandDecoded != expectedCommand { + t.Fatalf("Expected decoded: %s, got %s", expectedCommand, 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) @@ -408,11 +420,23 @@ 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 = `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` + expectedCommandBase64Encoded = `aQBmACAAKABUAGUAcwB0AC0AUABhAHQAaAAgAHYAYQByAGkAYQBiAGwAZQA6AGcAbABvAGIAYQBsADoAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAKQB7ACQAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAPQAnAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAJwB9ADsAJABlAG4AdgA6AEIAQQBSAD0AIgBCAEEAWgAiADsAIAAkAGUAbgB2ADoARgBPAE8APQAiAEIAQQBSACIAOwAgACQAZQBuAHYAOgBQAEEAQwBLAEUAUgBfAEIAVQBJAEwARABFAFIAXwBUAFkAUABFAD0AIgBpAHMAbwAiADsAIAAkAGUAbgB2ADoAUABBAEMASwBFAFIAXwBCAFUASQBMAEQAXwBOAEEATQBFAD0AIgB2AG0AdwBhAHIAZQAiADsAIAAmACcAYwA6AC8AVwBpAG4AZABvAHcAcwAvAFQAZQBtAHAALwBpAG4AbABpAG4AZQBTAGMAcgBpAHAAdAAuAHAAcwAxACcAOwBlAHgAaQB0ACAAJABMAGEAcwB0AEUAeABpAHQAQwBvAGQAZQA=` + expectedCommandPrefix = `powershell -executionpolicy bypass -encodedCommand ` + 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") + } + + if actualCommandDecoded != expectedCommand { + t.Fatalf("Expected decoded: %s, got %s", expectedCommand, actualCommandDecoded) + } - // 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 { + t.Fatalf("Expect command to be: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command) } } @@ -434,12 +458,23 @@ 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 "& { $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` + expectedCommandBase64Encoded := `aQBmACAAKABUAGUAcwB0AC0AUABhAHQAaAAgAHYAYQByAGkAYQBiAGwAZQA6AGcAbABvAGIAYQBsADoAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAKQB7ACQAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAPQAnAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAJwB9ADsAJABlAG4AdgA6AFAAQQBDAEsARQBSAF8AQgBVAEkATABEAEUAUgBfAFQAWQBQAEUAPQAiAGYAbwBvAHQAeQBwAGUAIgA7ACAAJABlAG4AdgA6AFAAQQBDAEsARQBSAF8AQgBVAEkATABEAF8ATgBBAE0ARQA9ACIAZgBvAG8AYgB1AGkAbABkACIAOwAgACYAJwBjADoALwBXAGkAbgBkAG8AdwBzAC8AVABlAG0AcAAvAHMAYwByAGkAcAB0AC4AcABzADEAJwA7AGUAeABpAHQAIAAkAEwAYQBzAHQARQB4AGkAdABDAG8AZABlAA==` + expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` + 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") + } + + if actualCommandDecoded != expectedCommand { + t.Fatalf("Expected decoded: %s, got %s", expectedCommand, actualCommandDecoded) + } - // 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 { + t.Fatalf("Expect command to be: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command) } } @@ -468,11 +503,23 @@ 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 := `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` + expectedCommandBase64Encoded := `aQBmACAAKABUAGUAcwB0AC0AUABhAHQAaAAgAHYAYQByAGkAYQBiAGwAZQA6AGcAbABvAGIAYQBsADoAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAKQB7ACQAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAPQAnAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAJwB9ADsAJABlAG4AdgA6AEIAQQBSAD0AIgBCAEEAWgAiADsAIAAkAGUAbgB2ADoARgBPAE8APQAiAEIAQQBSACIAOwAgACQAZQBuAHYAOgBQAEEAQwBLAEUAUgBfAEIAVQBJAEwARABFAFIAXwBUAFkAUABFAD0AIgBmAG8AbwB0AHkAcABlACIAOwAgACQAZQBuAHYAOgBQAEEAQwBLAEUAUgBfAEIAVQBJAEwARABfAE4AQQBNAEUAPQAiAGYAbwBvAGIAdQBpAGwAZAAiADsAIAAmACcAYwA6AC8AVwBpAG4AZABvAHcAcwAvAFQAZQBtAHAALwBzAGMAcgBpAHAAdAAuAHAAcwAxACcAOwBlAHgAaQB0ACAAJABMAGEAcwB0AEUAeABpAHQAQwBvAGQAZQA=` + expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` + 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 != expectedCommand { - t.Fatalf("Expect command to be %s NOT %s", expectedCommand, comm.StartCmd.Command) + if actualCommandDecoded != expectedCommand { + t.Fatalf("Expected decoded: %s, got %s", expectedCommand, actualCommandDecoded) + } + + if comm.StartCmd.Command != expectedCommandEncoded { + t.Fatalf("Expect command to be: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command) } } @@ -500,7 +547,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 +558,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 +569,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 +592,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 +603,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 +614,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 +629,25 @@ 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}\"" { - 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` + expectedCommandBase64Encoded := `aQBmACAAKABUAGUAcwB0AC0AUABhAHQAaAAgAHYAYQByAGkAYQBiAGwAZQA6AGcAbABvAGIAYQBsADoAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAKQB7ACQAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAPQAnAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAJwB9ADsAJABlAG4AdgA6AFAAQQBDAEsARQBSAF8AQgBVAEkATABEAEUAUgBfAFQAWQBQAEUAPQAiACIAOwAgACQAZQBuAHYAOgBQAEEAQwBLAEUAUgBfAEIAVQBJAEwARABfAE4AQQBNAEUAPQAiACIAOwAgACYAJwBjADoALwBXAGkAbgBkAG8AdwBzAC8AVABlAG0AcAAvAHMAYwByAGkAcAB0AC4AcABzADEAJwA7AGUAeABpAHQAIAAkAEwAYQBzAHQARQB4AGkAdABDAG8AZABlAA==` + expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` + 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") + } + + 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 diff --git a/provisioner/windows-shell/provisioner.go b/provisioner/windows-shell/provisioner.go index 6e65c8c2046..6f58bcb9e0c 100644 --- a/provisioner/windows-shell/provisioner.go +++ b/provisioner/windows-shell/provisioner.go @@ -305,8 +305,8 @@ 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] diff --git a/vendor/github.com/stretchr/testify/LICENSE b/vendor/github.com/stretchr/testify/LICENSE new file mode 100644 index 00000000000..473b670a7c6 --- /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 00000000000..6a66aea5eaf --- /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 00000000000..733099041f8 --- /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 00000000000..221f175c01e --- /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 00000000000..0c8eba7e526 --- /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 := ®istry{} + if err := xml.NewDecoder(r).Decode(®); 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 00000000000..2a2da0ef253 --- /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 00000000000..915abfa2973 --- /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 00000000000..75a5fd16582 --- /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 00000000000..35d62fcc99b --- /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 00000000000..579cadfb12a --- /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 00000000000..2202c9f6426 --- /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 00000000000..84c699faa96 --- /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 00000000000..575cea8707b --- /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 00000000000..df7aa02db6d --- /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 00000000000..71933696f59 --- /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 00000000000..fe47b9b35f0 --- /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 00000000000..2382f4d6da1 --- /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 00000000000..2197f8ac268 --- /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 00000000000..80ee28d795e --- /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 or '[' ']' + 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' ] +// or +// [