diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e5e744e8..b93652196 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,14 @@ * post-processor/vagrant: Large VMDKs should no longer show a 0-byte size on OS X. [GH-6084] * builder/scaleway: Fix compilation issues on solaris/amd64. [GH-6069] +* common/bootcommand: Fix numerous bugs in the boot command code, and make supported features consistent across builders. [GH-6129] ### IMPROVEMENTS: * builder/amazon: Setting `force_delete` will only delete AMIs owned by the user. This should prevent failures where we try to delete an AMI with a matching name, but owned by someone else. [GH-6111] +* builder/openstack: Add configuration option for `instance_name`. [GH-6041] ## 1.2.2 (March 26, 2018) diff --git a/Makefile b/Makefile index 54852ce26..0ccd547b4 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,7 @@ package: deps: @go get golang.org/x/tools/cmd/stringer + @go get -u github.com/mna/pigeon @go get github.com/kardianos/govendor @govendor sync @@ -73,6 +74,8 @@ fmt-examples: # source files. generate: deps ## Generate dynamically generated code go generate . + gofmt -w common/bootcommand/boot_command.go + goimports -w common/bootcommand/boot_command.go gofmt -w command/plugin.go test: deps fmt-check ## Run unit tests diff --git a/builder/amazon/common/step_get_password.go b/builder/amazon/common/step_get_password.go index c9011d930..8b0d0c74c 100644 --- a/builder/amazon/common/step_get_password.go +++ b/builder/amazon/common/step_get_password.go @@ -21,9 +21,10 @@ import ( // StepGetPassword reads the password from a Windows server and sets it // on the WinRM config. type StepGetPassword struct { - Debug bool - Comm *communicator.Config - Timeout time.Duration + Debug bool + Comm *communicator.Config + Timeout time.Duration + BuildName string } func (s *StepGetPassword) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { @@ -94,13 +95,13 @@ WaitLoop: "Password (since debug is enabled): %s", s.Comm.WinRMPassword)) } // store so that we can access this later during provisioning - commonhelper.SetSharedState("winrm_password", s.Comm.WinRMPassword) + commonhelper.SetSharedState("winrm_password", s.Comm.WinRMPassword, s.BuildName) return multistep.ActionContinue } func (s *StepGetPassword) Cleanup(multistep.StateBag) { - commonhelper.RemoveSharedStateFile("winrm_password") + commonhelper.RemoveSharedStateFile("winrm_password", s.BuildName) } func (s *StepGetPassword) waitForPassword(state multistep.StateBag, cancel <-chan struct{}) (string, error) { diff --git a/builder/amazon/common/step_run_source_instance.go b/builder/amazon/common/step_run_source_instance.go index 8542e9387..114da38e1 100644 --- a/builder/amazon/common/step_run_source_instance.go +++ b/builder/amazon/common/step_run_source_instance.go @@ -39,7 +39,7 @@ type StepRunSourceInstance struct { instanceId string } -func (s *StepRunSourceInstance) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { +func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { ec2conn := state.Get("ec2").(*ec2.EC2) var keyName string if name, ok := state.GetOk("keyPair"); ok { @@ -185,7 +185,7 @@ func (s *StepRunSourceInstance) Run(_ context.Context, state multistep.StateBag) describeInstance := &ec2.DescribeInstancesInput{ InstanceIds: []*string{aws.String(instanceId)}, } - if err := ec2conn.WaitUntilInstanceRunning(describeInstance); err != nil { + if err := ec2conn.WaitUntilInstanceRunningWithContext(ctx, describeInstance); err != nil { err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", instanceId, err) state.Put("error", err) ui.Error(err.Error()) diff --git a/builder/amazon/common/step_run_spot_instance.go b/builder/amazon/common/step_run_spot_instance.go index d90dd391a..8c7cbf74a 100644 --- a/builder/amazon/common/step_run_spot_instance.go +++ b/builder/amazon/common/step_run_spot_instance.go @@ -43,7 +43,7 @@ type StepRunSpotInstance struct { spotRequest *ec2.SpotInstanceRequest } -func (s *StepRunSpotInstance) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { +func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { ec2conn := state.Get("ec2").(*ec2.EC2) var keyName string if name, ok := state.GetOk("keyPair"); ok { @@ -235,7 +235,7 @@ func (s *StepRunSpotInstance) Run(_ context.Context, state multistep.StateBag) m describeInstance := &ec2.DescribeInstancesInput{ InstanceIds: []*string{aws.String(instanceId)}, } - if err := ec2conn.WaitUntilInstanceRunning(describeInstance); err != nil { + if err := ec2conn.WaitUntilInstanceRunningWithContext(ctx, describeInstance); err != nil { err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", instanceId, err) state.Put("error", err) ui.Error(err.Error()) diff --git a/builder/amazon/common/step_stop_ebs_instance.go b/builder/amazon/common/step_stop_ebs_instance.go index 10a2f5a45..184430b91 100644 --- a/builder/amazon/common/step_stop_ebs_instance.go +++ b/builder/amazon/common/step_stop_ebs_instance.go @@ -16,7 +16,7 @@ type StepStopEBSBackedInstance struct { DisableStopInstance bool } -func (s *StepStopEBSBackedInstance) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { +func (s *StepStopEBSBackedInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { ec2conn := state.Get("ec2").(*ec2.EC2) instance := state.Get("instance").(*ec2.Instance) ui := state.Get("ui").(packer.Ui) @@ -78,7 +78,7 @@ func (s *StepStopEBSBackedInstance) Run(_ context.Context, state multistep.State // Wait for the instance to actually stop ui.Say("Waiting for the instance to stop...") - err = ec2conn.WaitUntilInstanceStopped(&ec2.DescribeInstancesInput{ + err = ec2conn.WaitUntilInstanceStoppedWithContext(ctx, &ec2.DescribeInstancesInput{ InstanceIds: []*string{instance.InstanceId}, }) diff --git a/builder/amazon/ebs/builder.go b/builder/amazon/ebs/builder.go index 87354223c..5e63b05c8 100644 --- a/builder/amazon/ebs/builder.go +++ b/builder/amazon/ebs/builder.go @@ -193,9 +193,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe }, instanceStep, &awscommon.StepGetPassword{ - Debug: b.config.PackerDebug, - Comm: &b.config.RunConfig.Comm, - Timeout: b.config.WindowsPasswordTimeout, + Debug: b.config.PackerDebug, + Comm: &b.config.RunConfig.Comm, + Timeout: b.config.WindowsPasswordTimeout, + BuildName: b.config.PackerBuildName, }, &communicator.StepConnect{ Config: &b.config.RunConfig.Comm, diff --git a/builder/amazon/ebssurrogate/builder.go b/builder/amazon/ebssurrogate/builder.go index 002560602..31f47164f 100644 --- a/builder/amazon/ebssurrogate/builder.go +++ b/builder/amazon/ebssurrogate/builder.go @@ -207,9 +207,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe }, instanceStep, &awscommon.StepGetPassword{ - Debug: b.config.PackerDebug, - Comm: &b.config.RunConfig.Comm, - Timeout: b.config.WindowsPasswordTimeout, + Debug: b.config.PackerDebug, + Comm: &b.config.RunConfig.Comm, + Timeout: b.config.WindowsPasswordTimeout, + BuildName: b.config.PackerBuildName, }, &communicator.StepConnect{ Config: &b.config.RunConfig.Comm, diff --git a/builder/amazon/ebsvolume/builder.go b/builder/amazon/ebsvolume/builder.go index cdfda8e14..a1cc1fd2a 100644 --- a/builder/amazon/ebsvolume/builder.go +++ b/builder/amazon/ebsvolume/builder.go @@ -186,9 +186,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Ctx: b.config.ctx, }, &awscommon.StepGetPassword{ - Debug: b.config.PackerDebug, - Comm: &b.config.RunConfig.Comm, - Timeout: b.config.WindowsPasswordTimeout, + Debug: b.config.PackerDebug, + Comm: &b.config.RunConfig.Comm, + Timeout: b.config.WindowsPasswordTimeout, + BuildName: b.config.PackerBuildName, }, &communicator.StepConnect{ Config: &b.config.RunConfig.Comm, diff --git a/builder/amazon/instance/builder.go b/builder/amazon/instance/builder.go index 98d9dc24d..eb3ecdbda 100644 --- a/builder/amazon/instance/builder.go +++ b/builder/amazon/instance/builder.go @@ -269,9 +269,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe }, instanceStep, &awscommon.StepGetPassword{ - Debug: b.config.PackerDebug, - Comm: &b.config.RunConfig.Comm, - Timeout: b.config.WindowsPasswordTimeout, + Debug: b.config.PackerDebug, + Comm: &b.config.RunConfig.Comm, + Timeout: b.config.WindowsPasswordTimeout, + BuildName: b.config.PackerBuildName, }, &communicator.StepConnect{ Config: &b.config.RunConfig.Comm, diff --git a/builder/azure/arm/builder.go b/builder/azure/arm/builder.go index 8fb4389ad..efaf228e7 100644 --- a/builder/azure/arm/builder.go +++ b/builder/azure/arm/builder.go @@ -177,7 +177,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe NewStepDeployTemplate(azureClient, ui, b.config, deploymentName, GetVirtualMachineDeployment), NewStepGetIPAddress(azureClient, ui, endpointConnectType), &StepSaveWinRMPassword{ - Password: b.config.tmpAdminPassword, + Password: b.config.tmpAdminPassword, + BuildName: b.config.PackerBuildName, }, &communicator.StepConnectWinRM{ Config: &b.config.Comm, diff --git a/builder/azure/arm/config.go b/builder/azure/arm/config.go index 5d789708d..6d41fc7dc 100644 --- a/builder/azure/arm/config.go +++ b/builder/azure/arm/config.go @@ -359,7 +359,7 @@ func setRuntimeValues(c *Config) { c.tmpAdminPassword = tempName.AdminPassword // store so that we can access this later during provisioning - commonhelper.SetSharedState("winrm_password", c.tmpAdminPassword) + commonhelper.SetSharedState("winrm_password", c.tmpAdminPassword, c.PackerConfig.PackerBuildName) c.tmpCertificatePassword = tempName.CertificatePassword if c.TempComputeName == "" { diff --git a/builder/azure/arm/step_save_winrm_password.go b/builder/azure/arm/step_save_winrm_password.go index e28fcd771..700a4b048 100644 --- a/builder/azure/arm/step_save_winrm_password.go +++ b/builder/azure/arm/step_save_winrm_password.go @@ -8,15 +8,16 @@ import ( ) type StepSaveWinRMPassword struct { - Password string + Password string + BuildName string } func (s *StepSaveWinRMPassword) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { // store so that we can access this later during provisioning - commonhelper.SetSharedState("winrm_password", s.Password) + commonhelper.SetSharedState("winrm_password", s.Password, s.BuildName) return multistep.ActionContinue } func (s *StepSaveWinRMPassword) Cleanup(multistep.StateBag) { - commonhelper.RemoveSharedStateFile("winrm_password") + commonhelper.RemoveSharedStateFile("winrm_password", s.BuildName) } diff --git a/builder/docker/communicator.go b/builder/docker/communicator.go index 7b0ecae07..e898ecd1c 100644 --- a/builder/docker/communicator.go +++ b/builder/docker/communicator.go @@ -105,22 +105,6 @@ func (c *Communicator) uploadReader(dst string, src io.Reader) error { // uploadFile uses docker cp to copy the file from the host to the container func (c *Communicator) uploadFile(dst string, src io.Reader, fi *os.FileInfo) error { - // find out if it's a directory - testDirectoryCommand := fmt.Sprintf(`test -d "%s"`, dst) - cmd := &packer.RemoteCmd{Command: testDirectoryCommand} - - err := c.Start(cmd) - - if err != nil { - log.Printf("Unable to check whether remote path is a dir: %s", err) - return err - } - cmd.Wait() - if cmd.ExitStatus == 0 { - log.Printf("path is a directory; copying file into directory.") - dst = filepath.Join(dst, filepath.Base((*fi).Name())) - } - // command format: docker cp /path/to/infile containerid:/path/to/outfile log.Printf("Copying to %s on container %s.", dst, c.ContainerID) diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index 571214acd..e7b58464e 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -66,9 +66,9 @@ type Driver interface { DeleteVirtualSwitch(string) error - CreateVirtualMachine(string, string, string, string, int64, int64, string, uint, bool) error + CreateVirtualMachine(string, string, string, string, int64, int64, int64, string, uint, bool) error - AddVirtualMachineHardDrive(string, string, string, int64, string) error + AddVirtualMachineHardDrive(string, string, string, int64, int64, string) error CloneVirtualMachine(string, string, string, bool, string, string, string, int64, string) error diff --git a/builder/hyperv/common/driver_mock.go b/builder/hyperv/common/driver_mock.go index 7d8e02c6a..8af637e66 100644 --- a/builder/hyperv/common/driver_mock.go +++ b/builder/hyperv/common/driver_mock.go @@ -112,6 +112,7 @@ type DriverMock struct { AddVirtualMachineHardDrive_VhdFile string AddVirtualMachineHardDrive_VhdName string AddVirtualMachineHardDrive_VhdSizeBytes int64 + AddVirtualMachineHardDrive_VhdBlockSize int64 AddVirtualMachineHardDrive_ControllerType string AddVirtualMachineHardDrive_Err error @@ -122,6 +123,7 @@ type DriverMock struct { CreateVirtualMachine_VhdPath string CreateVirtualMachine_Ram int64 CreateVirtualMachine_DiskSize int64 + CreateVirtualMachine_DiskBlockSize int64 CreateVirtualMachine_SwitchName string CreateVirtualMachine_Generation uint CreateVirtualMachine_DifferentialDisk bool @@ -377,17 +379,18 @@ func (d *DriverMock) CreateVirtualSwitch(switchName string, switchType string) ( return d.CreateVirtualSwitch_Return, d.CreateVirtualSwitch_Err } -func (d *DriverMock) AddVirtualMachineHardDrive(vmName string, vhdFile string, vhdName string, vhdSizeBytes int64, controllerType string) error { +func (d *DriverMock) AddVirtualMachineHardDrive(vmName string, vhdFile string, vhdName string, vhdSizeBytes int64, vhdDiskBlockSize int64, controllerType string) error { d.AddVirtualMachineHardDrive_Called = true d.AddVirtualMachineHardDrive_VmName = vmName d.AddVirtualMachineHardDrive_VhdFile = vhdFile d.AddVirtualMachineHardDrive_VhdName = vhdName d.AddVirtualMachineHardDrive_VhdSizeBytes = vhdSizeBytes + d.AddVirtualMachineHardDrive_VhdSizeBytes = vhdDiskBlockSize d.AddVirtualMachineHardDrive_ControllerType = controllerType return d.AddVirtualMachineHardDrive_Err } -func (d *DriverMock) CreateVirtualMachine(vmName string, path string, harddrivePath string, vhdPath string, ram int64, diskSize int64, switchName string, generation uint, diffDisks bool) error { +func (d *DriverMock) CreateVirtualMachine(vmName string, path string, harddrivePath string, vhdPath string, ram int64, diskSize int64, diskBlockSize int64, switchName string, generation uint, diffDisks bool) error { d.CreateVirtualMachine_Called = true d.CreateVirtualMachine_VmName = vmName d.CreateVirtualMachine_Path = path @@ -395,6 +398,7 @@ func (d *DriverMock) CreateVirtualMachine(vmName string, path string, harddriveP d.CreateVirtualMachine_VhdPath = vhdPath d.CreateVirtualMachine_Ram = ram d.CreateVirtualMachine_DiskSize = diskSize + d.CreateVirtualMachine_DiskBlockSize = diskBlockSize d.CreateVirtualMachine_SwitchName = switchName d.CreateVirtualMachine_Generation = generation d.CreateVirtualMachine_DifferentialDisk = diffDisks diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index a6c1b7352..34a9edbb5 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -174,12 +174,12 @@ func (d *HypervPS4Driver) CreateVirtualSwitch(switchName string, switchType stri return hyperv.CreateVirtualSwitch(switchName, switchType) } -func (d *HypervPS4Driver) AddVirtualMachineHardDrive(vmName string, vhdFile string, vhdName string, vhdSizeBytes int64, controllerType string) error { - return hyperv.AddVirtualMachineHardDiskDrive(vmName, vhdFile, vhdName, vhdSizeBytes, controllerType) +func (d *HypervPS4Driver) AddVirtualMachineHardDrive(vmName string, vhdFile string, vhdName string, vhdSizeBytes int64, diskBlockSize int64, controllerType string) error { + return hyperv.AddVirtualMachineHardDiskDrive(vmName, vhdFile, vhdName, vhdSizeBytes, diskBlockSize, controllerType) } -func (d *HypervPS4Driver) CreateVirtualMachine(vmName string, path string, harddrivePath string, vhdPath string, ram int64, diskSize int64, switchName string, generation uint, diffDisks bool) error { - return hyperv.CreateVirtualMachine(vmName, path, harddrivePath, vhdPath, ram, diskSize, switchName, generation, diffDisks) +func (d *HypervPS4Driver) CreateVirtualMachine(vmName string, path string, harddrivePath string, vhdPath string, ram int64, diskSize int64, diskBlockSize int64, switchName string, generation uint, diffDisks bool) error { + return hyperv.CreateVirtualMachine(vmName, path, harddrivePath, vhdPath, ram, diskSize, diskBlockSize, switchName, generation, diffDisks) } func (d *HypervPS4Driver) CloneVirtualMachine(cloneFromVmxcPath string, cloneFromVmName string, cloneFromSnapshotName string, cloneAllSnapshots bool, vmName string, path string, harddrivePath string, ram int64, switchName string) error { diff --git a/builder/hyperv/common/run_config.go b/builder/hyperv/common/run_config.go deleted file mode 100644 index 8e29511b7..000000000 --- a/builder/hyperv/common/run_config.go +++ /dev/null @@ -1,33 +0,0 @@ -package common - -import ( - "fmt" - "time" - - "github.com/hashicorp/packer/template/interpolate" -) - -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 deleted file mode 100644 index 8068fe625..000000000 --- a/builder/hyperv/common/run_config_test.go +++ /dev/null @@ -1,37 +0,0 @@ -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/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index f046d1a48..f8125ed91 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -21,6 +21,7 @@ type StepCreateVM struct { HarddrivePath string RamSize uint DiskSize uint + DiskBlockSize uint Generation uint Cpu uint EnableMacSpoofing bool @@ -64,8 +65,9 @@ func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multiste // convert the MB to bytes ramSize := int64(s.RamSize * 1024 * 1024) diskSize := int64(s.DiskSize * 1024 * 1024) + diskBlockSize := int64(s.DiskBlockSize * 1024 * 1024) - err := driver.CreateVirtualMachine(s.VMName, path, harddrivePath, vhdPath, ramSize, diskSize, s.SwitchName, s.Generation, s.DifferencingDisk) + err := driver.CreateVirtualMachine(s.VMName, path, harddrivePath, vhdPath, ramSize, diskSize, diskBlockSize, s.SwitchName, s.Generation, s.DifferencingDisk) if err != nil { err := fmt.Errorf("Error creating virtual machine: %s", err) state.Put("error", err) @@ -124,7 +126,7 @@ func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multiste for index, size := range s.AdditionalDiskSize { diskSize := int64(size * 1024 * 1024) diskFile := fmt.Sprintf("%s-%d.vhdx", s.VMName, index) - err = driver.AddVirtualMachineHardDrive(s.VMName, vhdPath, diskFile, diskSize, "SCSI") + err = driver.AddVirtualMachineHardDrive(s.VMName, vhdPath, diskFile, diskSize, diskBlockSize, "SCSI") if err != nil { err := fmt.Errorf("Error creating and attaching additional disk drive: %s", err) state.Put("error", err) @@ -163,4 +165,6 @@ func (s *StepCreateVM) Cleanup(state multistep.StateBag) { if err != nil { ui.Error(fmt.Sprintf("Error deleting virtual machine: %s", err)) } + + // TODO: Clean up created VHDX } diff --git a/builder/hyperv/common/step_run.go b/builder/hyperv/common/step_run.go index cd8213c5e..02996fb6f 100644 --- a/builder/hyperv/common/step_run.go +++ b/builder/hyperv/common/step_run.go @@ -3,15 +3,12 @@ package common import ( "context" "fmt" - "time" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" ) type StepRun struct { - BootWait time.Duration - vmName string } @@ -32,22 +29,6 @@ func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.Ste 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 } diff --git a/builder/hyperv/common/step_type_boot_command.go b/builder/hyperv/common/step_type_boot_command.go index 43deee1fd..de96d4293 100644 --- a/builder/hyperv/common/step_type_boot_command.go +++ b/builder/hyperv/common/step_type_boot_command.go @@ -3,12 +3,11 @@ package common import ( "context" "fmt" - "log" "strings" - "unicode" - "unicode/utf8" + "time" "github.com/hashicorp/packer/common" + "github.com/hashicorp/packer/common/bootcommand" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" @@ -22,17 +21,29 @@ type bootCommandTemplateData struct { // This step "types" the boot command into the VM via the Hyper-V virtual keyboard type StepTypeBootCommand struct { - BootCommand []string + BootCommand string + BootWait time.Duration SwitchName string Ctx interpolate.Context } -func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { +func (s *StepTypeBootCommand) Run(ctx context.Context, 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) + // Wait the for the vm to boot. + if int64(s.BootWait) > 0 { + ui.Say(fmt.Sprintf("Waiting %s for boot...", s.BootWait.String())) + select { + case <-time.After(s.BootWait): + break + case <-ctx.Done(): + return multistep.ActionHalt + } + } + hostIp, err := driver.GetHostAdapterIpAddressForSwitch(s.SwitchName) if err != nil { @@ -51,26 +62,32 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m vmName, } + sendCodes := func(codes []string) error { + scanCodesToSendString := strings.Join(codes, " ") + return driver.TypeScanCodes(vmName, scanCodesToSendString) + } + d := bootcommand.NewPCXTDriver(sendCodes, -1) + ui.Say("Typing the boot command...") - scanCodesToSend := []string{} + command, err := interpolate.Render(s.BootCommand, &s.Ctx) - 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)...) + if err != nil { + err := fmt.Errorf("Error preparing boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt } - scanCodesToSendString := strings.Join(scanCodesToSend, " ") + seq, err := bootcommand.GenerateExpressionSequence(command) + if err != nil { + err := fmt.Errorf("Error generating boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } - if err := driver.TypeScanCodes(vmName, scanCodesToSendString); err != nil { - err := fmt.Errorf("Error sending boot command: %s", err) + if err := seq.Do(ctx, d); err != nil { + err := fmt.Errorf("Error running boot command: %s", err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt @@ -80,202 +97,3 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m } 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/iso/builder.go b/builder/hyperv/iso/builder.go index b912ab316..9d32eef43 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -10,6 +10,7 @@ import ( hypervcommon "github.com/hashicorp/packer/builder/hyperv/common" "github.com/hashicorp/packer/common" + "github.com/hashicorp/packer/common/bootcommand" powershell "github.com/hashicorp/packer/common/powershell" "github.com/hashicorp/packer/common/powershell/hyperv" "github.com/hashicorp/packer/helper/communicator" @@ -24,6 +25,10 @@ const ( MinDiskSize = 256 // 256MB MaxDiskSize = 64 * 1024 * 1024 // 64TB + DefaultDiskBlockSize = 32 // 32MB + MinDiskBlockSize = 1 // 1MB + MaxDiskBlockSize = 256 // 256MB + DefaultRamSize = 1 * 1024 // 1GB MinRamSize = 32 // 32MB MaxRamSize = 32 * 1024 // 32GB @@ -47,17 +52,23 @@ type Config struct { common.HTTPConfig `mapstructure:",squash"` common.ISOConfig `mapstructure:",squash"` common.FloppyConfig `mapstructure:",squash"` + bootcommand.BootConfig `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 block size used to create the hard disk. + // By default, this is 32768 (about 32 MB) + DiskBlockSize uint `mapstructure:"disk_block_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"` + // SecondaryDvdImages []string `mapstructure:"secondary_iso_images"` @@ -71,18 +82,17 @@ type Config struct { // By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. VMName string `mapstructure:"vm_name"` - BootCommand []string `mapstructure:"boot_command"` - SwitchName string `mapstructure:"switch_name"` - SwitchVlanId string `mapstructure:"switch_vlan_id"` - MacAddress string `mapstructure:"mac_address"` - 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"` - TempPath string `mapstructure:"temp_path"` + SwitchName string `mapstructure:"switch_name"` + SwitchVlanId string `mapstructure:"switch_vlan_id"` + MacAddress string `mapstructure:"mac_address"` + 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"` + TempPath string `mapstructure:"temp_path"` // A separate path can be used for storing the VM's disk image. The purpose is to enable // reading and writing to take place on different physical disks (read from VHD temp path @@ -126,9 +136,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { warnings = append(warnings, isoWarnings...) errs = packer.MultiErrorAppend(errs, isoErrs...) + errs = packer.MultiErrorAppend(errs, b.config.BootConfig.Prepare(&b.config.ctx)...) 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)...) @@ -141,6 +151,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } + err = b.checkDiskBlockSize() + if err != nil { + errs = packer.MultiErrorAppend(errs, err) + } + err = b.checkRamSize() if err != nil { errs = packer.MultiErrorAppend(errs, err) @@ -352,6 +367,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SwitchName: b.config.SwitchName, RamSize: b.config.RamSize, DiskSize: b.config.DiskSize, + DiskBlockSize: b.config.DiskBlockSize, Generation: b.config.Generation, Cpu: b.config.Cpu, EnableMacSpoofing: b.config.EnableMacSpoofing, @@ -388,12 +404,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SwitchVlanId: b.config.SwitchVlanId, }, - &hypervcommon.StepRun{ - BootWait: b.config.BootWait, - }, + &hypervcommon.StepRun{}, &hypervcommon.StepTypeBootCommand{ - BootCommand: b.config.BootCommand, + BootCommand: b.config.FlatBootCommand(), + BootWait: b.config.BootWait, SwitchName: b.config.SwitchName, Ctx: b.config.ctx, }, @@ -492,6 +507,22 @@ func (b *Builder) checkDiskSize() error { return nil } +func (b *Builder) checkDiskBlockSize() error { + if b.config.DiskBlockSize == 0 { + b.config.DiskBlockSize = DefaultDiskBlockSize + } + + log.Println(fmt.Sprintf("%s: %v", "DiskBlockSize", b.config.DiskBlockSize)) + + if b.config.DiskBlockSize < MinDiskBlockSize { + return fmt.Errorf("disk_block_size: Virtual machine requires disk block size >= %v MB, but defined: %v", MinDiskBlockSize, b.config.DiskBlockSize) + } else if b.config.DiskBlockSize > MaxDiskBlockSize { + return fmt.Errorf("disk_block_size: Virtual machine requires disk block size <= %v MB, but defined: %v", MaxDiskBlockSize, b.config.DiskBlockSize) + } + + return nil +} + func (b *Builder) checkRamSize() error { if b.config.RamSize == 0 { b.config.RamSize = DefaultRamSize diff --git a/builder/hyperv/iso/builder_test.go b/builder/hyperv/iso/builder_test.go index b08a683f9..1663b4727 100644 --- a/builder/hyperv/iso/builder_test.go +++ b/builder/hyperv/iso/builder_test.go @@ -23,6 +23,7 @@ func testConfig() map[string]interface{} { "ssh_username": "foo", "ram_size": 64, "disk_size": 256, + "disk_block_size": 1, "guest_additions_mode": "none", "disk_additional_size": "50000,40000,30000", packer.BuildNameConfigKey: "foo", @@ -86,6 +87,58 @@ func TestBuilderPrepare_DiskSize(t *testing.T) { } } +func TestBuilderPrepare_DiskBlockSize(t *testing.T) { + var b Builder + config := testConfig() + expected_default_block_size := uint(32) + expected_min_block_size := uint(0) + expected_max_block_size := uint(256) + + // Test default with empty disk_block_size + delete(config, "disk_block_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.DiskBlockSize != expected_default_block_size { + t.Fatalf("bad default block size with empty config: %d. Expected %d", b.config.DiskBlockSize, expected_default_block_size) + } + + test_sizes := []uint{0, 1, 32, 256, 512, 1 * 1024, 32 * 1024} + for _, test_size := range test_sizes { + config["disk_block_size"] = test_size + b = Builder{} + warns, err = b.Prepare(config) + if test_size > expected_max_block_size || test_size < expected_min_block_size { + if len(warns) > 0 { + t.Fatalf("bad, should have no warns: %#v", warns) + } + if err == nil { + t.Fatalf("bad, should have error but didn't. disk_block_size=%d outside expected valid range [%d,%d]", test_size, expected_min_block_size, expected_max_block_size) + } + } else { + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("bad, should not have error: %s", err) + } + if test_size == 0 { + if b.config.DiskBlockSize != expected_default_block_size { + t.Fatalf("bad default block size with 0 value config: %d. Expected: %d", b.config.DiskBlockSize, expected_default_block_size) + } + } else { + if b.config.DiskBlockSize != test_size { + t.Fatalf("bad block size with 0 value config: %d. Expected: %d", b.config.DiskBlockSize, expected_default_block_size) + } + } + } + } +} + func TestBuilderPrepare_FloppyFiles(t *testing.T) { var b Builder config := testConfig() @@ -509,7 +562,7 @@ func TestUserVariablesInBootCommand(t *testing.T) { state.Put("vmName", "packer-foo") step := &hypervcommon.StepTypeBootCommand{ - BootCommand: b.config.BootCommand, + BootCommand: b.config.FlatBootCommand(), SwitchName: b.config.SwitchName, Ctx: b.config.ctx, } diff --git a/builder/hyperv/vmcx/builder.go b/builder/hyperv/vmcx/builder.go index 8309759fb..31212e1f5 100644 --- a/builder/hyperv/vmcx/builder.go +++ b/builder/hyperv/vmcx/builder.go @@ -9,6 +9,7 @@ import ( hypervcommon "github.com/hashicorp/packer/builder/hyperv/common" "github.com/hashicorp/packer/common" + "github.com/hashicorp/packer/common/bootcommand" powershell "github.com/hashicorp/packer/common/powershell" "github.com/hashicorp/packer/common/powershell/hyperv" "github.com/hashicorp/packer/helper/communicator" @@ -42,9 +43,9 @@ type Config struct { common.HTTPConfig `mapstructure:",squash"` common.ISOConfig `mapstructure:",squash"` common.FloppyConfig `mapstructure:",squash"` + bootcommand.BootConfig `mapstructure:",squash"` hypervcommon.OutputConfig `mapstructure:",squash"` hypervcommon.SSHConfig `mapstructure:",squash"` - hypervcommon.RunConfig `mapstructure:",squash"` hypervcommon.ShutdownConfig `mapstructure:",squash"` // The size, in megabytes, of the computer memory in the VM. @@ -79,12 +80,11 @@ type Config struct { // Use differencing disk DifferencingDisk bool `mapstructure:"differencing_disk"` - BootCommand []string `mapstructure:"boot_command"` - SwitchName string `mapstructure:"switch_name"` - SwitchVlanId string `mapstructure:"switch_vlan_id"` - MacAddress string `mapstructure:"mac_address"` - VlanId string `mapstructure:"vlan_id"` - Cpu uint `mapstructure:"cpu"` + SwitchName string `mapstructure:"switch_name"` + SwitchVlanId string `mapstructure:"switch_vlan_id"` + MacAddress string `mapstructure:"mac_address"` + VlanId string `mapstructure:"vlan_id"` + Cpu uint `mapstructure:"cpu"` Generation uint EnableMacSpoofing bool `mapstructure:"enable_mac_spoofing"` EnableDynamicMemory bool `mapstructure:"enable_dynamic_memory"` @@ -125,9 +125,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs = packer.MultiErrorAppend(errs, isoErrs...) } + errs = packer.MultiErrorAppend(errs, b.config.BootConfig.Prepare(&b.config.ctx)...) 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)...) @@ -434,12 +434,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SwitchVlanId: b.config.SwitchVlanId, }, - &hypervcommon.StepRun{ - BootWait: b.config.BootWait, - }, + &hypervcommon.StepRun{}, &hypervcommon.StepTypeBootCommand{ - BootCommand: b.config.BootCommand, + BootCommand: b.config.FlatBootCommand(), + BootWait: b.config.BootWait, SwitchName: b.config.SwitchName, Ctx: b.config.ctx, }, diff --git a/builder/hyperv/vmcx/builder_test.go b/builder/hyperv/vmcx/builder_test.go index 08444c26d..9a4a54c33 100644 --- a/builder/hyperv/vmcx/builder_test.go +++ b/builder/hyperv/vmcx/builder_test.go @@ -530,7 +530,7 @@ func TestUserVariablesInBootCommand(t *testing.T) { state.Put("vmName", "packer-foo") step := &hypervcommon.StepTypeBootCommand{ - BootCommand: b.config.BootCommand, + BootCommand: b.config.FlatBootCommand(), SwitchName: b.config.SwitchName, Ctx: b.config.ctx, } diff --git a/builder/openstack/builder.go b/builder/openstack/builder.go index 42770c625..a718c3d59 100755 --- a/builder/openstack/builder.go +++ b/builder/openstack/builder.go @@ -52,6 +52,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { return nil, errs } + // By default, instance name is same as image name + if b.config.InstanceName == "" { + b.config.InstanceName = b.config.ImageName + } + log.Println(common.ScrubConfig(b.config, b.config.Password)) return nil, nil } @@ -82,7 +87,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SSHAgentAuth: b.config.RunConfig.Comm.SSHAgentAuth, }, &StepRunSourceServer{ - Name: b.config.ImageName, + Name: b.config.InstanceName, SourceImage: b.config.SourceImage, SourceImageName: b.config.SourceImageName, SecurityGroups: b.config.SecurityGroups, diff --git a/builder/openstack/run_config.go b/builder/openstack/run_config.go index 55da56271..6b4e0932e 100644 --- a/builder/openstack/run_config.go +++ b/builder/openstack/run_config.go @@ -30,6 +30,7 @@ type RunConfig struct { Networks []string `mapstructure:"networks"` UserData string `mapstructure:"user_data"` UserDataFile string `mapstructure:"user_data_file"` + InstanceName string `mapstructure:"instance_name"` InstanceMetadata map[string]string `mapstructure:"instance_metadata"` ConfigDrive bool `mapstructure:"config_drive"` diff --git a/builder/parallels/common/driver.go b/builder/parallels/common/driver.go index ac23a7880..4fde15a6c 100644 --- a/builder/parallels/common/driver.go +++ b/builder/parallels/common/driver.go @@ -131,6 +131,7 @@ func NewDriver() (Driver, error) { latestDriver := 11 version, _ := drivers[strconv.Itoa(latestDriver)].Version() majVer, _ := strconv.Atoi(strings.SplitN(version, ".", 2)[0]) + log.Printf("Parallels version: %s", version) if majVer > latestDriver { log.Printf("Your version of Parallels Desktop for Mac is %s, Packer will use driver for version %d.", version, latestDriver) return drivers[strconv.Itoa(latestDriver)], nil diff --git a/builder/parallels/common/driver_9.go b/builder/parallels/common/driver_9.go index b8b697ba1..5634fb123 100644 --- a/builder/parallels/common/driver_9.go +++ b/builder/parallels/common/driver_9.go @@ -222,7 +222,7 @@ func (d *Parallels9Driver) IsRunning(name string) (bool, error) { // Stop forcibly stops the VM. func (d *Parallels9Driver) Stop(name string) error { - if err := d.Prlctl("stop", name); err != nil { + if err := d.Prlctl("stop", name, "--kill"); err != nil { return err } @@ -275,7 +275,6 @@ func (d *Parallels9Driver) Version() (string, error) { } version := matches[1] - log.Printf("Parallels Desktop version: %s", version) return version, nil } diff --git a/builder/parallels/common/run_config.go b/builder/parallels/common/run_config.go deleted file mode 100644 index 53fb8757b..000000000 --- a/builder/parallels/common/run_config.go +++ /dev/null @@ -1,30 +0,0 @@ -package common - -import ( - "fmt" - "time" - - "github.com/hashicorp/packer/template/interpolate" -) - -// RunConfig contains the configuration for VM run. -type RunConfig struct { - RawBootWait string `mapstructure:"boot_wait"` - - BootWait time.Duration `` -} - -// Prepare sets the configuration for VM run. -func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { - if c.RawBootWait == "" { - c.RawBootWait = "10s" - } - - var err error - c.BootWait, err = time.ParseDuration(c.RawBootWait) - if err != nil { - return []error{fmt.Errorf("Failed parsing boot_wait: %s", err)} - } - - return nil -} diff --git a/builder/parallels/common/run_config_test.go b/builder/parallels/common/run_config_test.go deleted file mode 100644 index 8068fe625..000000000 --- a/builder/parallels/common/run_config_test.go +++ /dev/null @@ -1,37 +0,0 @@ -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/parallels/common/step_run.go b/builder/parallels/common/step_run.go index 073ad0550..d59051a06 100644 --- a/builder/parallels/common/step_run.go +++ b/builder/parallels/common/step_run.go @@ -3,7 +3,6 @@ package common import ( "context" "fmt" - "time" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" @@ -18,8 +17,6 @@ import ( // // Produces: type StepRun struct { - BootWait time.Duration - vmName string } @@ -40,22 +37,6 @@ func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.Ste 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 } @@ -69,7 +50,7 @@ func (s *StepRun) Cleanup(state multistep.StateBag) { ui := state.Get("ui").(packer.Ui) if running, _ := driver.IsRunning(s.vmName); running { - if err := driver.Prlctl("stop", s.vmName); err != nil { + if err := driver.Stop(s.vmName); err != nil { ui.Error(fmt.Sprintf("Error stopping VM: %s", err)) } } diff --git a/builder/parallels/common/step_type_boot_command.go b/builder/parallels/common/step_type_boot_command.go index e39a90b6c..ed21c049d 100644 --- a/builder/parallels/common/step_type_boot_command.go +++ b/builder/parallels/common/step_type_boot_command.go @@ -3,13 +3,10 @@ package common import ( "context" "fmt" - "log" - "strings" "time" - "unicode" - "unicode/utf8" packer_common "github.com/hashicorp/packer/common" + "github.com/hashicorp/packer/common/bootcommand" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" @@ -23,29 +20,32 @@ type bootCommandTemplateData struct { // StepTypeBootCommand is a step that "types" the boot command into the VM via // the prltype script, built on the Parallels Virtualization SDK - Python API. -// -// Uses: -// driver Driver -// http_port int -// ui packer.Ui -// vmName string -// -// Produces: -// type StepTypeBootCommand struct { - BootCommand []string + BootCommand string + BootWait time.Duration HostInterfaces []string VMName string Ctx interpolate.Context } // Run types the boot command by sending key scancodes into the VM. -func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { +func (s *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { debug := state.Get("debug").(bool) httpPort := state.Get("http_port").(uint) ui := state.Get("ui").(packer.Ui) driver := state.Get("driver").(Driver) + // Wait the for the vm to boot. + if int64(s.BootWait) > 0 { + ui.Say(fmt.Sprintf("Waiting %s for boot...", s.BootWait.String())) + select { + case <-time.After(s.BootWait): + break + case <-ctx.Done(): + return multistep.ActionHalt + } + } + var pauseFn multistep.DebugPauseFn if debug { pauseFn = state.Get("pauseFn").(multistep.DebugPauseFn) @@ -76,73 +76,37 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m s.VMName, } + sendCodes := func(codes []string) error { + return driver.SendKeyScanCodes(s.VMName, codes...) + } + d := bootcommand.NewPCXTDriver(sendCodes, -1) + ui.Say("Typing the boot command...") - for i, 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 - } + command, err := interpolate.Render(s.BootCommand, &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 + } - codes := []string{} - for _, code := range scancodes(command) { - if code == "wait" { - if err := driver.SendKeyScanCodes(s.VMName, codes...); err != nil { - err = fmt.Errorf("Error sending boot command: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - codes = []string{} - time.Sleep(1 * time.Second) - continue - } + seq, err := bootcommand.GenerateExpressionSequence(command) + if err != nil { + err := fmt.Errorf("Error generating boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } - if code == "wait5" { - if err := driver.SendKeyScanCodes(s.VMName, codes...); err != nil { - err = fmt.Errorf("Error sending boot command: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - codes = []string{} - time.Sleep(5 * time.Second) - continue - } + if err := seq.Do(ctx, d); err != nil { + err := fmt.Errorf("Error running boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } - if code == "wait10" { - if err := driver.SendKeyScanCodes(s.VMName, codes...); err != nil { - err = fmt.Errorf("Error sending boot command: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - codes = []string{} - time.Sleep(10 * time.Second) - continue - } - - // Since typing is sometimes so slow, we check for an interrupt - // in between each character. - if _, ok := state.GetOk(multistep.StateCancelled); ok { - return multistep.ActionHalt - } - codes = append(codes, code) - } - - if pauseFn != nil { - pauseFn(multistep.DebugLocationAfterRun, fmt.Sprintf("boot_command[%d]: %s", i, command), state) - } - - log.Printf("Sending scancodes: %#v", codes) - if err := driver.SendKeyScanCodes(s.VMName, codes...); err != nil { - err = fmt.Errorf("Error sending boot command: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } + if pauseFn != nil { + pauseFn(multistep.DebugLocationAfterRun, fmt.Sprintf("boot_command: %s", command), state) } return multistep.ActionContinue @@ -150,202 +114,3 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m // Cleanup does nothing. func (*StepTypeBootCommand) Cleanup(multistep.StateBag) {} - -func scancodes(message string) []string { - // Scancodes reference: http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html - // - // Scancodes represent raw keyboard output and are fed to the VM by the - // Parallels Virtualization SDK - C API, PrlDevKeyboard_SendKeyEvent - // - // 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 - for len(chars) > 0 { - r, size := utf8.DecodeRuneInString(chars) - chars = chars[size:] - scancodeMap[r] = start + i - i++ - } - } - - 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)) - scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt+0x80)) - - if keyShift { - scancode = append(scancode, "aa") - } - } - - result = append(result, scancode...) - } - - return result -} diff --git a/builder/parallels/common/step_type_boot_command_test.go b/builder/parallels/common/step_type_boot_command_test.go deleted file mode 100644 index 72b5056b4..000000000 --- a/builder/parallels/common/step_type_boot_command_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package common - -import ( - "context" - "strings" - "testing" - - "github.com/hashicorp/packer/helper/multistep" - "github.com/hashicorp/packer/packer" -) - -func TestStepTypeBootCommand(t *testing.T) { - state := testState(t) - - var bootcommand = []string{ - "1234567890-=", - "!@#$%^&*()_+", - "qwertyuiop[]", - "QWERTYUIOP{}", - "asdfghjkl;'`", - `ASDFGHJKL:"~`, - "\\zxcvbnm,./", - "|ZXCVBNM<>?", - " ", - } - - step := StepTypeBootCommand{ - BootCommand: bootcommand, - HostInterfaces: []string{}, - VMName: "myVM", - Ctx: *testConfigTemplate(t), - } - - comm := new(packer.MockCommunicator) - state.Put("communicator", comm) - - driver := state.Get("driver").(*DriverMock) - driver.VersionResult = "foo" - state.Put("http_port", uint(0)) - - // Test the run - if action := step.Run(context.Background(), state); action != multistep.ActionContinue { - t.Fatalf("bad action: %#v", action) - } - if _, ok := state.GetOk("error"); ok { - t.Fatal("should NOT have error") - } - - // Verify - var expected = [][]string{ - {"02", "82", "03", "83", "04", "84", "05", "85", "06", "86", "07", "87", "08", "88", "09", "89", "0a", "8a", "0b", "8b", "0c", "8c", "0d", "8d", "1c", "9c"}, - {}, - {"2a", "02", "82", "aa", "2a", "03", "83", "aa", "2a", "04", "84", "aa", "2a", "05", "85", "aa", "2a", "06", "86", "aa", "2a", "07", "87", "aa", "2a", "08", "88", "aa", "2a", "09", "89", "aa", "2a", "0a", "8a", "aa", "2a", "0b", "8b", "aa", "2a", "0c", "8c", "aa", "2a", "0d", "8d", "aa", "1c", "9c"}, - {"10", "90", "11", "91", "12", "92", "13", "93", "14", "94", "15", "95", "16", "96", "17", "97", "18", "98", "19", "99", "1a", "9a", "1b", "9b", "1c", "9c"}, - {"2a", "10", "90", "aa", "2a", "11", "91", "aa", "2a", "12", "92", "aa", "2a", "13", "93", "aa", "2a", "14", "94", "aa", "2a", "15", "95", "aa", "2a", "16", "96", "aa", "2a", "17", "97", "aa", "2a", "18", "98", "aa", "2a", "19", "99", "aa", "2a", "1a", "9a", "aa", "2a", "1b", "9b", "aa", "1c", "9c"}, - {"1e", "9e", "1f", "9f", "20", "a0", "21", "a1", "22", "a2", "23", "a3", "24", "a4", "25", "a5", "26", "a6", "27", "a7", "28", "a8", "29", "a9", "1c", "9c"}, - {"2a", "1e", "9e", "aa", "2a", "1f", "9f", "aa", "2a", "20", "a0", "aa", "2a", "21", "a1", "aa", "2a", "22", "a2", "aa", "2a", "23", "a3", "aa", "2a", "24", "a4", "aa", "2a", "25", "a5", "aa", "2a", "26", "a6", "aa", "2a", "27", "a7", "aa", "2a", "28", "a8", "aa", "2a", "29", "a9", "aa", "1c", "9c"}, - {"2b", "ab", "2c", "ac", "2d", "ad", "2e", "ae", "2f", "af", "30", "b0", "31", "b1", "32", "b2", "33", "b3", "34", "b4", "35", "b5", "1c", "9c"}, - {"2a", "2b", "ab", "aa", "2a", "2c", "ac", "aa", "2a", "2d", "ad", "aa", "2a", "2e", "ae", "aa", "2a", "2f", "af", "aa", "2a", "30", "b0", "aa", "2a", "31", "b1", "aa", "2a", "32", "b2", "aa", "2a", "33", "b3", "aa", "2a", "34", "b4", "aa", "2a", "35", "b5", "aa", "1c", "9c"}, - {"39", "b9", "1c", "9c"}, - } - fail := false - - for i := range driver.SendKeyScanCodesCalls { - t.Logf("prltype %s\n", strings.Join(driver.SendKeyScanCodesCalls[i], " ")) - } - - for i := range expected { - for j := range expected[i] { - if driver.SendKeyScanCodesCalls[i][j] != expected[i][j] { - fail = true - } - } - } - if fail { - t.Fatalf("Sent bad scancodes: %#v\n Expected: %#v", driver.SendKeyScanCodesCalls, expected) - } -} diff --git a/builder/parallels/iso/builder.go b/builder/parallels/iso/builder.go index cf8159f6b..9ba2002f7 100644 --- a/builder/parallels/iso/builder.go +++ b/builder/parallels/iso/builder.go @@ -7,6 +7,7 @@ import ( parallelscommon "github.com/hashicorp/packer/builder/parallels/common" "github.com/hashicorp/packer/common" + "github.com/hashicorp/packer/common/bootcommand" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/helper/multistep" @@ -26,16 +27,15 @@ type Config struct { common.HTTPConfig `mapstructure:",squash"` common.ISOConfig `mapstructure:",squash"` common.FloppyConfig `mapstructure:",squash"` + bootcommand.BootConfig `mapstructure:",squash"` parallelscommon.OutputConfig `mapstructure:",squash"` parallelscommon.PrlctlConfig `mapstructure:",squash"` parallelscommon.PrlctlPostConfig `mapstructure:",squash"` parallelscommon.PrlctlVersionConfig `mapstructure:",squash"` - parallelscommon.RunConfig `mapstructure:",squash"` parallelscommon.ShutdownConfig `mapstructure:",squash"` parallelscommon.SSHConfig `mapstructure:",squash"` parallelscommon.ToolsConfig `mapstructure:",squash"` - BootCommand []string `mapstructure:"boot_command"` DiskSize uint `mapstructure:"disk_size"` DiskType string `mapstructure:"disk_type"` GuestOSType string `mapstructure:"guest_os_type"` @@ -76,13 +76,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend( errs, b.config.OutputConfig.Prepare(&b.config.ctx, &b.config.PackerConfig)...) - errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.PrlctlConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.PrlctlPostConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.PrlctlVersionConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.ToolsConfig.Prepare(&b.config.ctx)...) + errs = packer.MultiErrorAppend(errs, b.config.BootConfig.Prepare(&b.config.ctx)...) if b.config.DiskSize == 0 { b.config.DiskSize = 40000 @@ -185,11 +185,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Commands: b.config.Prlctl, Ctx: b.config.ctx, }, - ¶llelscommon.StepRun{ - BootWait: b.config.BootWait, - }, + ¶llelscommon.StepRun{}, ¶llelscommon.StepTypeBootCommand{ - BootCommand: b.config.BootCommand, + BootWait: b.config.BootWait, + BootCommand: b.config.FlatBootCommand(), HostInterfaces: b.config.HostInterfaces, VMName: b.config.VMName, Ctx: b.config.ctx, diff --git a/builder/parallels/pvm/builder.go b/builder/parallels/pvm/builder.go index 1e0d3c49a..d21388eff 100644 --- a/builder/parallels/pvm/builder.go +++ b/builder/parallels/pvm/builder.go @@ -74,11 +74,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Commands: b.config.Prlctl, Ctx: b.config.ctx, }, - ¶llelscommon.StepRun{ - BootWait: b.config.BootWait, - }, + ¶llelscommon.StepRun{}, ¶llelscommon.StepTypeBootCommand{ - BootCommand: b.config.BootCommand, + BootCommand: b.config.FlatBootCommand(), + BootWait: b.config.BootWait, HostInterfaces: []string{}, VMName: b.config.VMName, Ctx: b.config.ctx, diff --git a/builder/parallels/pvm/config.go b/builder/parallels/pvm/config.go index 7641e0c9d..edccea50e 100644 --- a/builder/parallels/pvm/config.go +++ b/builder/parallels/pvm/config.go @@ -6,6 +6,7 @@ import ( parallelscommon "github.com/hashicorp/packer/builder/parallels/common" "github.com/hashicorp/packer/common" + "github.com/hashicorp/packer/common/bootcommand" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" @@ -19,15 +20,14 @@ type Config struct { parallelscommon.PrlctlConfig `mapstructure:",squash"` parallelscommon.PrlctlPostConfig `mapstructure:",squash"` parallelscommon.PrlctlVersionConfig `mapstructure:",squash"` - parallelscommon.RunConfig `mapstructure:",squash"` parallelscommon.SSHConfig `mapstructure:",squash"` parallelscommon.ShutdownConfig `mapstructure:",squash"` + bootcommand.BootConfig `mapstructure:",squash"` parallelscommon.ToolsConfig `mapstructure:",squash"` - BootCommand []string `mapstructure:"boot_command"` - SourcePath string `mapstructure:"source_path"` - VMName string `mapstructure:"vm_name"` - ReassignMAC bool `mapstructure:"reassign_mac"` + SourcePath string `mapstructure:"source_path"` + VMName string `mapstructure:"vm_name"` + ReassignMAC bool `mapstructure:"reassign_mac"` ctx interpolate.Context } @@ -61,7 +61,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { errs = packer.MultiErrorAppend(errs, c.PrlctlConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.PrlctlPostConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.PrlctlVersionConfig.Prepare(&c.ctx)...) - errs = packer.MultiErrorAppend(errs, c.RunConfig.Prepare(&c.ctx)...) + errs = packer.MultiErrorAppend(errs, c.BootConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.SSHConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.ToolsConfig.Prepare(&c.ctx)...) diff --git a/builder/qemu/builder.go b/builder/qemu/builder.go index d1d91d79f..e16f15541 100644 --- a/builder/qemu/builder.go +++ b/builder/qemu/builder.go @@ -11,6 +11,7 @@ import ( "time" "github.com/hashicorp/packer/common" + "github.com/hashicorp/packer/common/bootcommand" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/helper/multistep" @@ -79,15 +80,15 @@ type Builder struct { } type Config struct { - common.PackerConfig `mapstructure:",squash"` - common.HTTPConfig `mapstructure:",squash"` - common.ISOConfig `mapstructure:",squash"` - Comm communicator.Config `mapstructure:",squash"` - common.FloppyConfig `mapstructure:",squash"` + common.PackerConfig `mapstructure:",squash"` + common.HTTPConfig `mapstructure:",squash"` + common.ISOConfig `mapstructure:",squash"` + bootcommand.VNCConfig `mapstructure:",squash"` + Comm communicator.Config `mapstructure:",squash"` + common.FloppyConfig `mapstructure:",squash"` ISOSkipCache bool `mapstructure:"iso_skip_cache"` Accelerator string `mapstructure:"accelerator"` - BootCommand []string `mapstructure:"boot_command"` DiskInterface string `mapstructure:"disk_interface"` DiskSize uint `mapstructure:"disk_size"` DiskCache string `mapstructure:"disk_cache"` @@ -118,10 +119,8 @@ type Config struct { // TODO(mitchellh): deprecate RunOnce bool `mapstructure:"run_once"` - RawBootWait string `mapstructure:"boot_wait"` RawShutdownTimeout string `mapstructure:"shutdown_timeout"` - bootWait time.Duration `` shutdownTimeout time.Duration `` ctx interpolate.Context } @@ -188,10 +187,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.QemuBinary = "qemu-system-x86_64" } - if b.config.RawBootWait == "" { - b.config.RawBootWait = "10s" - } - if b.config.SSHHostPortMin == 0 { b.config.SSHHostPortMin = 2222 } @@ -280,7 +275,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { if _, ok := diskDiscard[b.config.DiskDiscard]; !ok { errs = packer.MultiErrorAppend( - errs, errors.New("unrecognized disk cache type")) + errs, errors.New("unrecognized disk discard type")) } if !b.config.PackerForce { @@ -291,12 +286,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } - b.config.bootWait, err = time.ParseDuration(b.config.RawBootWait) - if err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("Failed parsing boot_wait: %s", err)) - } - if b.config.RawShutdownTimeout == "" { b.config.RawShutdownTimeout = "5m" } @@ -388,7 +377,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe steps = append(steps, new(stepConfigureVNC), steprun, - &stepBootWait{}, &stepTypeBootCommand{}, ) diff --git a/builder/qemu/builder_test.go b/builder/qemu/builder_test.go index 7ef29034e..4ddfa1560 100644 --- a/builder/qemu/builder_test.go +++ b/builder/qemu/builder_test.go @@ -94,46 +94,6 @@ func TestBuilderPrepare_Defaults(t *testing.T) { } } -func TestBuilderPrepare_BootWait(t *testing.T) { - var b Builder - config := testConfig() - - // Test a default boot_wait - delete(config, "boot_wait") - warns, err := b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("err: %s", err) - } - - if b.config.RawBootWait != "10s" { - t.Fatalf("bad value: %s", b.config.RawBootWait) - } - - // Test with a bad boot_wait - config["boot_wait"] = "this is not good" - warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatal("should have error") - } - - // Test with a good one - config["boot_wait"] = "5s" - b = Builder{} - warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("should not have error: %s", err) - } -} - func TestBuilderPrepare_VNCBindAddress(t *testing.T) { var b Builder config := testConfig() diff --git a/builder/qemu/step_boot_wait.go b/builder/qemu/step_boot_wait.go deleted file mode 100644 index 0c3935004..000000000 --- a/builder/qemu/step_boot_wait.go +++ /dev/null @@ -1,27 +0,0 @@ -package qemu - -import ( - "context" - "fmt" - "time" - - "github.com/hashicorp/packer/helper/multistep" - "github.com/hashicorp/packer/packer" -) - -// stepBootWait waits the configured time period. -type stepBootWait struct{} - -func (s *stepBootWait) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { - config := state.Get("config").(*Config) - ui := state.Get("ui").(packer.Ui) - - if int64(config.bootWait) > 0 { - ui.Say(fmt.Sprintf("Waiting %s for boot...", config.bootWait)) - time.Sleep(config.bootWait) - } - - return multistep.ActionContinue -} - -func (s *stepBootWait) Cleanup(state multistep.StateBag) {} diff --git a/builder/qemu/step_type_boot_command.go b/builder/qemu/step_type_boot_command.go index f1dc895e9..da3eb48aa 100644 --- a/builder/qemu/step_type_boot_command.go +++ b/builder/qemu/step_type_boot_command.go @@ -5,15 +5,10 @@ import ( "fmt" "log" "net" - "regexp" - "strings" "time" - "unicode" - "unicode/utf8" - - "os" "github.com/hashicorp/packer/common" + "github.com/hashicorp/packer/common/bootcommand" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" @@ -40,13 +35,29 @@ type bootCommandTemplateData struct { // type stepTypeBootCommand struct{} -func (s *stepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { +func (s *stepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*Config) debug := state.Get("debug").(bool) httpPort := state.Get("http_port").(uint) ui := state.Get("ui").(packer.Ui) vncPort := state.Get("vnc_port").(uint) + if config.VNCConfig.DisableVNC { + log.Println("Skipping boot command step...") + return multistep.ActionContinue + } + + // Wait the for the vm to boot. + if int64(config.BootConfig.BootWait) > 0 { + ui.Say(fmt.Sprintf("Waiting %s for boot...", config.BootWait.String())) + select { + case <-time.After(config.BootWait): + break + case <-ctx.Done(): + return multistep.ActionHalt + } + } + var pauseFn multistep.DebugPauseFn if debug { pauseFn = state.Get("pauseFn").(multistep.DebugPauseFn) @@ -76,276 +87,44 @@ func (s *stepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m hostIP := "10.0.2.2" common.SetHTTPIP(hostIP) - ctx := config.ctx - ctx.Data = &bootCommandTemplateData{ + configCtx := config.ctx + configCtx.Data = &bootCommandTemplateData{ hostIP, httpPort, config.VMName, } + d := bootcommand.NewVNCDriver(c) + ui.Say("Typing the boot command over VNC...") - for i, command := range config.BootCommand { - command, err := interpolate.Render(command, &ctx) - if err != nil { - err := fmt.Errorf("Error preparing boot command: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } + command, err := interpolate.Render(config.VNCConfig.FlatBootCommand(), &configCtx) + if err != nil { + err := fmt.Errorf("Error preparing boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } - // Check for interrupts between typing things so we can cancel - // since this isn't the fastest thing. - if _, ok := state.GetOk(multistep.StateCancelled); ok { - return multistep.ActionHalt - } + seq, err := bootcommand.GenerateExpressionSequence(command) + if err != nil { + err := fmt.Errorf("Error generating boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } - if pauseFn != nil { - pauseFn(multistep.DebugLocationAfterRun, fmt.Sprintf("boot_command[%d]: %s", i, command), state) - } + if err := seq.Do(ctx, d); err != nil { + err := fmt.Errorf("Error running boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } - vncSendString(c, command) + if pauseFn != nil { + pauseFn(multistep.DebugLocationAfterRun, fmt.Sprintf("boot_command: %s", command), state) } return multistep.ActionContinue } func (*stepTypeBootCommand) Cleanup(multistep.StateBag) {} - -func vncSendString(c *vnc.ClientConn, original string) { - // Scancodes reference: https://github.com/qemu/qemu/blob/master/ui/vnc_keysym.h - special := make(map[string]uint32) - special[""] = 0xFF08 - special[""] = 0xFFFF - special[""] = 0xFF0D - special[""] = 0xFF1B - special[""] = 0xFFBE - special[""] = 0xFFBF - special[""] = 0xFFC0 - special[""] = 0xFFC1 - special[""] = 0xFFC2 - special[""] = 0xFFC3 - special[""] = 0xFFC4 - special[""] = 0xFFC5 - special[""] = 0xFFC6 - special[""] = 0xFFC7 - special[""] = 0xFFC8 - special[""] = 0xFFC9 - special[""] = 0xFF0D - special[""] = 0xFF09 - special[""] = 0xFF52 - special[""] = 0xFF54 - special[""] = 0xFF51 - special[""] = 0xFF53 - special[""] = 0x020 - special[""] = 0xFF63 - special[""] = 0xFF50 - special[""] = 0xFF57 - special[""] = 0xFF55 - special[""] = 0xFF56 - special[""] = 0xFFE9 - special[""] = 0xFFE3 - special[""] = 0xFFE1 - special[""] = 0xFFEA - special[""] = 0xFFE4 - special[""] = 0xFFE2 - - shiftedChars := "~!@#$%^&*()_+{}|:\"<>?" - waitRe := regexp.MustCompile(`^`) - - // We delay (default 100ms) between each key event to allow for CPU or - // network latency. See PackerKeyEnv for tuning. - keyInterval := common.PackerKeyDefault - if delay, err := time.ParseDuration(os.Getenv(common.PackerKeyEnv)); err == nil { - keyInterval = delay - } - - // TODO(mitchellh): Ripe for optimizations of some point, perhaps. - for len(original) > 0 { - var keyCode uint32 - keyShift := false - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, true) - time.Sleep(keyInterval) - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, true) - time.Sleep(keyInterval) - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, true) - time.Sleep(keyInterval) - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, false) - time.Sleep(keyInterval) - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, false) - time.Sleep(keyInterval) - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, false) - time.Sleep(keyInterval) - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, true) - time.Sleep(keyInterval) - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, true) - time.Sleep(keyInterval) - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, true) - time.Sleep(keyInterval) - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, false) - time.Sleep(keyInterval) - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, false) - time.Sleep(keyInterval) - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, false) - time.Sleep(keyInterval) - continue - } - - if strings.HasPrefix(original, "") { - log.Printf("Special code '' found, sleeping one second") - time.Sleep(1 * time.Second) - original = original[len(""):] - continue - } - - if strings.HasPrefix(original, "") { - log.Printf("Special code '' found, sleeping 5 seconds") - time.Sleep(5 * time.Second) - original = original[len(""):] - continue - } - - if strings.HasPrefix(original, "") { - log.Printf("Special code '' found, sleeping 10 seconds") - time.Sleep(10 * time.Second) - original = original[len(""):] - continue - } - - waitMatch := waitRe.FindStringSubmatch(original) - if len(waitMatch) > 1 { - log.Printf("Special code %s found, sleeping", waitMatch[0]) - if dt, err := time.ParseDuration(waitMatch[1]); err == nil { - time.Sleep(dt) - original = original[len(waitMatch[0]):] - continue - } - } - - for specialCode, specialValue := range special { - if strings.HasPrefix(original, specialCode) { - log.Printf("Special code '%s' found, replacing with: %d", specialCode, specialValue) - keyCode = specialValue - original = original[len(specialCode):] - break - } - } - - if keyCode == 0 { - r, size := utf8.DecodeRuneInString(original) - original = original[size:] - keyCode = uint32(r) - keyShift = unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r) - - log.Printf("Sending char '%c', code %d, shift %v", r, keyCode, keyShift) - } - - if keyShift { - c.KeyEvent(KeyLeftShift, true) - } - - c.KeyEvent(keyCode, true) - time.Sleep(keyInterval) - - c.KeyEvent(keyCode, false) - time.Sleep(keyInterval) - - if keyShift { - c.KeyEvent(KeyLeftShift, false) - } - time.Sleep(keyInterval) - } -} diff --git a/builder/triton/access_config.go b/builder/triton/access_config.go index 3212c37a1..3fe380e73 100644 --- a/builder/triton/access_config.go +++ b/builder/triton/access_config.go @@ -17,11 +17,12 @@ import ( // AccessConfig is for common configuration related to Triton access type AccessConfig struct { - Endpoint string `mapstructure:"triton_url"` - Account string `mapstructure:"triton_account"` - Username string `mapstructure:"triton_user"` - KeyID string `mapstructure:"triton_key_id"` - KeyMaterial string `mapstructure:"triton_key_material"` + Endpoint string `mapstructure:"triton_url"` + Account string `mapstructure:"triton_account"` + Username string `mapstructure:"triton_user"` + KeyID string `mapstructure:"triton_key_id"` + KeyMaterial string `mapstructure:"triton_key_material"` + InsecureSkipTLSVerify bool `mapstructure:"insecure_skip_tls_verify"` signer authentication.Signer } @@ -131,12 +132,14 @@ func (c *AccessConfig) CreateTritonClient() (*Client, error) { } return &Client{ - config: config, + config: config, + insecureSkipTLSVerify: c.InsecureSkipTLSVerify, }, nil } type Client struct { - config *tgo.ClientConfig + config *tgo.ClientConfig + insecureSkipTLSVerify bool } func (c *Client) Compute() (*compute.ComputeClient, error) { @@ -145,6 +148,10 @@ func (c *Client) Compute() (*compute.ComputeClient, error) { return nil, errwrap.Wrapf("Error Creating Triton Compute Client: {{err}}", err) } + if c.insecureSkipTLSVerify { + computeClient.Client.InsecureSkipTLSVerify() + } + return computeClient, nil } @@ -154,6 +161,10 @@ func (c *Client) Network() (*network.NetworkClient, error) { return nil, errwrap.Wrapf("Error Creating Triton Network Client: {{err}}", err) } + if c.insecureSkipTLSVerify { + networkClient.Client.InsecureSkipTLSVerify() + } + return networkClient, nil } diff --git a/builder/virtualbox/common/run_config.go b/builder/virtualbox/common/run_config.go index 6d3f58686..cf94c306b 100644 --- a/builder/virtualbox/common/run_config.go +++ b/builder/virtualbox/common/run_config.go @@ -2,27 +2,19 @@ package common import ( "fmt" - "time" "github.com/hashicorp/packer/template/interpolate" ) type RunConfig struct { - Headless bool `mapstructure:"headless"` - RawBootWait string `mapstructure:"boot_wait"` + Headless bool `mapstructure:"headless"` VRDPBindAddress string `mapstructure:"vrdp_bind_address"` VRDPPortMin uint `mapstructure:"vrdp_port_min"` VRDPPortMax uint `mapstructure:"vrdp_port_max"` - - BootWait time.Duration `` } -func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { - if c.RawBootWait == "" { - c.RawBootWait = "10s" - } - +func (c *RunConfig) Prepare(ctx *interpolate.Context) (errs []error) { if c.VRDPBindAddress == "" { c.VRDPBindAddress = "127.0.0.1" } @@ -35,17 +27,10 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { c.VRDPPortMax = 6000 } - var errs []error - var err error - c.BootWait, err = time.ParseDuration(c.RawBootWait) - if err != nil { - errs = append(errs, fmt.Errorf("Failed parsing boot_wait: %s", err)) - } - if c.VRDPPortMin > c.VRDPPortMax { errs = append( errs, fmt.Errorf("vrdp_port_min must be less than vrdp_port_max")) } - return errs + return } diff --git a/builder/virtualbox/common/run_config_test.go b/builder/virtualbox/common/run_config_test.go index 87b9b1b69..1caa7e3c8 100644 --- a/builder/virtualbox/common/run_config_test.go +++ b/builder/virtualbox/common/run_config_test.go @@ -4,38 +4,6 @@ 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) - } -} - func TestRunConfigPrepare_VRDPBindAddress(t *testing.T) { var c *RunConfig var errs []error diff --git a/builder/virtualbox/common/step_run.go b/builder/virtualbox/common/step_run.go index 62d98d894..689b58602 100644 --- a/builder/virtualbox/common/step_run.go +++ b/builder/virtualbox/common/step_run.go @@ -3,7 +3,6 @@ package common import ( "context" "fmt" - "time" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" @@ -18,7 +17,6 @@ import ( // // Produces: type StepRun struct { - BootWait time.Duration Headless bool vmName string @@ -60,22 +58,6 @@ func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.Ste 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 } diff --git a/builder/virtualbox/common/step_type_boot_command.go b/builder/virtualbox/common/step_type_boot_command.go index be9397e76..0effc3628 100644 --- a/builder/virtualbox/common/step_type_boot_command.go +++ b/builder/virtualbox/common/step_type_boot_command.go @@ -3,14 +3,10 @@ package common import ( "context" "fmt" - "log" - "regexp" - "strings" "time" - "unicode" - "unicode/utf8" "github.com/hashicorp/packer/common" + "github.com/hashicorp/packer/common/bootcommand" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" @@ -24,29 +20,31 @@ type bootCommandTemplateData struct { Name string } -// This step "types" the boot command into the VM over VNC. -// -// Uses: -// driver Driver -// http_port int -// ui packer.Ui -// vmName string -// -// Produces: -// type StepTypeBootCommand struct { - BootCommand []string + BootCommand string + BootWait time.Duration VMName string Ctx interpolate.Context } -func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { +func (s *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { debug := state.Get("debug").(bool) driver := state.Get("driver").(Driver) httpPort := state.Get("http_port").(uint) ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) + // Wait the for the vm to boot. + if int64(s.BootWait) > 0 { + ui.Say(fmt.Sprintf("Waiting %s for boot...", s.BootWait.String())) + select { + case <-time.After(s.BootWait): + break + case <-ctx.Done(): + return multistep.ActionHalt + } + } + var pauseFn multistep.DebugPauseFn if debug { pauseFn = state.Get("pauseFn").(multistep.DebugPauseFn) @@ -60,336 +58,43 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m s.VMName, } + sendCodes := func(codes []string) error { + args := []string{"controlvm", vmName, "keyboardputscancode"} + args = append(args, codes...) + + return driver.VBoxManage(args...) + } + d := bootcommand.NewPCXTDriver(sendCodes, 25) + ui.Say("Typing the boot command...") - for i, 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 - } + command, err := interpolate.Render(s.BootCommand, &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 + } - for _, code := range scancodes(command) { - if code == "wait" { - time.Sleep(1 * time.Second) - continue - } + seq, err := bootcommand.GenerateExpressionSequence(command) + if err != nil { + err := fmt.Errorf("Error generating boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } - if code == "wait5" { - time.Sleep(5 * time.Second) - continue - } - - if code == "wait10" { - time.Sleep(10 * time.Second) - continue - } - - // Since typing is sometimes so slow, we check for an interrupt - // in between each character. - if _, ok := state.GetOk(multistep.StateCancelled); ok { - return multistep.ActionHalt - } - - var codes []string - - for i := 0; i < len(code)/2; i++ { - codes = append(codes, code[i*2:i*2+2]) - } - - args := []string{"controlvm", vmName, "keyboardputscancode"} - args = append(args, codes...) - - if err := driver.VBoxManage(args...); err != nil { - err := fmt.Errorf("Error sending boot command: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - } - - if pauseFn != nil { - pauseFn(multistep.DebugLocationAfterRun, fmt.Sprintf("boot_command[%d]: %s", i, command), state) - } + if err := seq.Do(ctx, d); err != nil { + err := fmt.Errorf("Error running boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + if pauseFn != nil { + pauseFn(multistep.DebugLocationAfterRun, fmt.Sprintf("boot_command: %s", command), state) } return multistep.ActionContinue } func (*StepTypeBootCommand) Cleanup(multistep.StateBag) {} - -func scancodes(message string) []string { - // Scancodes reference: https://www.win.tue.nl/~aeb/linux/kbd/scancodes-10.html - // - // Scancodes represent raw keyboard output and are fed to the VM by the - // VBoxManage controlvm keyboardputscancode program. - // - // Scancodes are recorded here in pairs. The first entry represents - // the key press and the second entry represents the key release and is - // derived from the first by the addition of 0x80. - special := make(map[string][]string) - special[""] = []string{"0e", "8e"} - special[""] = []string{"e053", "e0d3"} - 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{"57", "d7"} - special[""] = []string{"58", "d8"} - special[""] = []string{"1c", "9c"} - special[""] = []string{"0f", "8f"} - special[""] = []string{"e048", "e0c8"} - special[""] = []string{"e050", "e0d0"} - special[""] = []string{"e04b", "e0cb"} - special[""] = []string{"e04d", "e0cd"} - special[""] = []string{"39", "b9"} - special[""] = []string{"e052", "e0d2"} - special[""] = []string{"e047", "e0c7"} - special[""] = []string{"e04f", "e0cf"} - special[""] = []string{"e049", "e0c9"} - special[""] = []string{"e051", "e0d1"} - special[""] = []string{"38", "b8"} - special[""] = []string{"1d", "9d"} - special[""] = []string{"2a", "aa"} - special[""] = []string{"e038", "e0b8"} - special[""] = []string{"e01d", "e09d"} - special[""] = []string{"36", "b6"} - special[""] = []string{"e05b", "e0db"} - special[""] = []string{"e05c", "e0dc"} - - 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 - } - } - - azOnRegex := regexp.MustCompile("^<(?P[a-zA-Z])On>") - azOffRegex := regexp.MustCompile("^<(?P[a-zA-Z])Off>") - - result := make([]string, 0, len(message)*2) - for len(message) > 0 { - var scancode []string - - if azOnRegex.MatchString(message) { - m := azOnRegex.FindStringSubmatch(message) - r, _ := utf8.DecodeRuneInString(m[1]) - message = message[len(""):] - scancodeInt := scancodeMap[r] - keyShift := unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r) - - if keyShift { - scancode = append(scancode, "2a") - } - - scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt)) - - log.Printf("Sending char '%c', code '%v', shift %v", r, scancodeInt, keyShift) - } - - if azOffRegex.MatchString(message) { - m := azOffRegex.FindStringSubmatch(message) - r, _ := utf8.DecodeRuneInString(m[1]) - message = message[len(""):] - scancodeInt := scancodeMap[r] + 0x80 - keyShift := unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r) - - if keyShift { - scancode = append(scancode, "aa") - } - - scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt)) - - log.Printf("Sending char '%c', code '%v', shift %v", r, scancodeInt, keyShift) - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "58") - message = message[len(""):] - log.Printf("Special code '', replacing with: 58") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "38") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: 38") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "1d") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: 1d") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "2a") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: 2a") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "e05b") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: e05b") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "d8") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: d8") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "b8") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: b8") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "9d") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: 9d") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "aa") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: aa") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "e0db") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: e0db") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "e038") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: e038") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "e01d") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: e01d") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "36") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: 36") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "e05c") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: e05c") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "e0b8") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: e0b8") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "e09d") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: e09d") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "b6") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: b6") - } - - if strings.HasPrefix(message, "") { - scancode = append(scancode, "e0dc") - message = message[len(""):] - log.Printf("Special code '' found, replacing with: e0dc") - } - - if strings.HasPrefix(message, "") { - log.Printf("Special code found, will sleep 1 second at this point.") - scancode = append(scancode, "wait") - message = message[len(""):] - } - - if strings.HasPrefix(message, "") { - log.Printf("Special code found, will sleep 5 seconds at this point.") - scancode = append(scancode, "wait5") - message = message[len(""):] - } - - if strings.HasPrefix(message, "") { - log.Printf("Special code found, will sleep 10 seconds at this point.") - scancode = append(scancode, "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 = append(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/virtualbox/common/step_type_boot_command_test.go b/builder/virtualbox/common/step_type_boot_command_test.go deleted file mode 100644 index 23a5d9077..000000000 --- a/builder/virtualbox/common/step_type_boot_command_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package common - -import ( - "testing" -) - -func TestScancodes(t *testing.T) { - var bootcommand = []string{ - "1234567890-=", - "!@#$%^&*()_+", - "qwertyuiop[]", - "QWERTYUIOP{}", - "asdfghjkl;'`", - `ASDFGHJKL:"~`, - "\\zxcvbnm,./", - "|ZXCVBNM<>?", - "", - "", - "", - } - - var expected = [][]string{ - {"02", "82", "03", "83", "04", "84", "05", "85", "06", "86", "07", "87", "08", "88", "09", "89", "0a", "8a", "0b", "8b", "0c", "8c", "0d", "8d", "1c", "9c", "wait"}, - {"2a", "02", "aa", "82", "2a", "03", "aa", "83", "2a", "04", "aa", "84", "2a", "05", "aa", "85", "2a", "06", "aa", "86", "2a", "07", "aa", "87", "2a", "08", "aa", "88", "2a", "09", "aa", "89", "2a", "0a", "aa", "8a", "2a", "0b", "aa", "8b", "2a", "0c", "aa", "8c", "2a", "0d", "aa", "8d", "1c", "9c"}, - {"10", "90", "11", "91", "12", "92", "13", "93", "14", "94", "15", "95", "16", "96", "17", "97", "18", "98", "19", "99", "1a", "9a", "1b", "9b", "1c", "9c"}, - {"2a", "10", "aa", "90", "2a", "11", "aa", "91", "2a", "12", "aa", "92", "2a", "13", "aa", "93", "2a", "14", "aa", "94", "2a", "15", "aa", "95", "2a", "16", "aa", "96", "2a", "17", "aa", "97", "2a", "18", "aa", "98", "2a", "19", "aa", "99", "2a", "1a", "aa", "9a", "2a", "1b", "aa", "9b", "1c", "9c"}, - {"1e", "9e", "1f", "9f", "20", "a0", "21", "a1", "22", "a2", "23", "a3", "24", "a4", "25", "a5", "26", "a6", "27", "a7", "28", "a8", "29", "a9", "1c", "9c"}, - {"2a", "1e", "aa", "9e", "2a", "1f", "aa", "9f", "2a", "20", "aa", "a0", "2a", "21", "aa", "a1", "2a", "22", "aa", "a2", "2a", "23", "aa", "a3", "2a", "24", "aa", "a4", "2a", "25", "aa", "a5", "2a", "26", "aa", "a6", "2a", "27", "aa", "a7", "2a", "28", "aa", "a8", "2a", "29", "aa", "a9", "1c", "9c"}, - {"2b", "ab", "2c", "ac", "2d", "ad", "2e", "ae", "2f", "af", "30", "b0", "31", "b1", "32", "b2", "33", "b3", "34", "b4", "35", "b5", "1c", "9c"}, - {"2a", "2b", "aa", "ab", "2a", "2c", "aa", "ac", "2a", "2d", "aa", "ad", "2a", "2e", "aa", "ae", "2a", "2f", "aa", "af", "2a", "30", "aa", "b0", "2a", "31", "aa", "b1", "2a", "32", "aa", "b2", "2a", "33", "aa", "b3", "2a", "34", "aa", "b4", "2a", "35", "aa", "b5", "1c", "9c"}, - {"1c", "9c"}, - {"38", "01", "81", "b8", "wait5"}, - {"0f", "8f", "0f", "8f", "0f", "8f", "0f", "8f", "0f", "8f", "39", "b9", "wait5"}, - } - - for i, command := range bootcommand { - for j, code := range scancodes(command) { - if code != expected[i][j] { - t.Fatalf("%#v should have become %#v", scancodes(command), expected[i]) - } - } - } -} diff --git a/builder/virtualbox/iso/builder.go b/builder/virtualbox/iso/builder.go index 0d1be21dd..2ea19e70f 100644 --- a/builder/virtualbox/iso/builder.go +++ b/builder/virtualbox/iso/builder.go @@ -8,6 +8,7 @@ import ( vboxcommon "github.com/hashicorp/packer/builder/virtualbox/common" "github.com/hashicorp/packer/common" + "github.com/hashicorp/packer/common/bootcommand" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/helper/multistep" @@ -27,6 +28,7 @@ type Config struct { common.HTTPConfig `mapstructure:",squash"` common.ISOConfig `mapstructure:",squash"` common.FloppyConfig `mapstructure:",squash"` + bootcommand.BootConfig `mapstructure:",squash"` vboxcommon.ExportConfig `mapstructure:",squash"` vboxcommon.ExportOpts `mapstructure:",squash"` vboxcommon.OutputConfig `mapstructure:",squash"` @@ -37,21 +39,20 @@ type Config struct { vboxcommon.VBoxManagePostConfig `mapstructure:",squash"` vboxcommon.VBoxVersionConfig `mapstructure:",squash"` - BootCommand []string `mapstructure:"boot_command"` - DiskSize uint `mapstructure:"disk_size"` - GuestAdditionsMode string `mapstructure:"guest_additions_mode"` - GuestAdditionsPath string `mapstructure:"guest_additions_path"` - GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"` - GuestAdditionsURL string `mapstructure:"guest_additions_url"` - GuestOSType string `mapstructure:"guest_os_type"` - HardDriveDiscard bool `mapstructure:"hard_drive_discard"` - HardDriveInterface string `mapstructure:"hard_drive_interface"` - SATAPortCount int `mapstructure:"sata_port_count"` - HardDriveNonrotational bool `mapstructure:"hard_drive_nonrotational"` - ISOInterface string `mapstructure:"iso_interface"` - KeepRegistered bool `mapstructure:"keep_registered"` - SkipExport bool `mapstructure:"skip_export"` - VMName string `mapstructure:"vm_name"` + DiskSize uint `mapstructure:"disk_size"` + GuestAdditionsMode string `mapstructure:"guest_additions_mode"` + GuestAdditionsPath string `mapstructure:"guest_additions_path"` + GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"` + GuestAdditionsURL string `mapstructure:"guest_additions_url"` + GuestOSType string `mapstructure:"guest_os_type"` + HardDriveDiscard bool `mapstructure:"hard_drive_discard"` + HardDriveInterface string `mapstructure:"hard_drive_interface"` + SATAPortCount int `mapstructure:"sata_port_count"` + HardDriveNonrotational bool `mapstructure:"hard_drive_nonrotational"` + ISOInterface string `mapstructure:"iso_interface"` + KeepRegistered bool `mapstructure:"keep_registered"` + SkipExport bool `mapstructure:"skip_export"` + VMName string `mapstructure:"vm_name"` ctx interpolate.Context } @@ -94,6 +95,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs = packer.MultiErrorAppend(errs, b.config.VBoxManageConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.VBoxManagePostConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.VBoxVersionConfig.Prepare(&b.config.ctx)...) + errs = packer.MultiErrorAppend(errs, b.config.BootConfig.Prepare(&b.config.ctx)...) if b.config.DiskSize == 0 { b.config.DiskSize = 40000 @@ -240,11 +242,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Ctx: b.config.ctx, }, &vboxcommon.StepRun{ - BootWait: b.config.BootWait, Headless: b.config.Headless, }, &vboxcommon.StepTypeBootCommand{ - BootCommand: b.config.BootCommand, + BootWait: b.config.BootWait, + BootCommand: b.config.FlatBootCommand(), VMName: b.config.VMName, Ctx: b.config.ctx, }, diff --git a/builder/virtualbox/ovf/builder.go b/builder/virtualbox/ovf/builder.go index d9783244f..09fd416a3 100644 --- a/builder/virtualbox/ovf/builder.go +++ b/builder/virtualbox/ovf/builder.go @@ -103,11 +103,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Ctx: b.config.ctx, }, &vboxcommon.StepRun{ - BootWait: b.config.BootWait, Headless: b.config.Headless, }, &vboxcommon.StepTypeBootCommand{ - BootCommand: b.config.BootCommand, + BootWait: b.config.BootWait, + BootCommand: b.config.FlatBootCommand(), VMName: b.config.VMName, Ctx: b.config.ctx, }, diff --git a/builder/virtualbox/ovf/config.go b/builder/virtualbox/ovf/config.go index 9a90b1a5b..bd60a4c4c 100644 --- a/builder/virtualbox/ovf/config.go +++ b/builder/virtualbox/ovf/config.go @@ -6,6 +6,7 @@ import ( vboxcommon "github.com/hashicorp/packer/builder/virtualbox/common" "github.com/hashicorp/packer/common" + "github.com/hashicorp/packer/common/bootcommand" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" @@ -16,6 +17,7 @@ type Config struct { common.PackerConfig `mapstructure:",squash"` common.HTTPConfig `mapstructure:",squash"` common.FloppyConfig `mapstructure:",squash"` + bootcommand.BootConfig `mapstructure:",squash"` vboxcommon.ExportConfig `mapstructure:",squash"` vboxcommon.ExportOpts `mapstructure:",squash"` vboxcommon.OutputConfig `mapstructure:",squash"` @@ -26,7 +28,6 @@ type Config struct { vboxcommon.VBoxManagePostConfig `mapstructure:",squash"` vboxcommon.VBoxVersionConfig `mapstructure:",squash"` - BootCommand []string `mapstructure:"boot_command"` Checksum string `mapstructure:"checksum"` ChecksumType string `mapstructure:"checksum_type"` GuestAdditionsMode string `mapstructure:"guest_additions_mode"` @@ -90,6 +91,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { errs = packer.MultiErrorAppend(errs, c.VBoxManageConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.VBoxManagePostConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.VBoxVersionConfig.Prepare(&c.ctx)...) + errs = packer.MultiErrorAppend(errs, c.BootConfig.Prepare(&c.ctx)...) c.ChecksumType = strings.ToLower(c.ChecksumType) c.Checksum = strings.ToLower(c.Checksum) diff --git a/builder/vmware/common/run_config.go b/builder/vmware/common/run_config.go index 9463d8753..c4bfc3413 100644 --- a/builder/vmware/common/run_config.go +++ b/builder/vmware/common/run_config.go @@ -2,30 +2,20 @@ package common import ( "fmt" - "time" "github.com/hashicorp/packer/template/interpolate" ) type RunConfig struct { - Headless bool `mapstructure:"headless"` - RawBootWait string `mapstructure:"boot_wait"` - DisableVNC bool `mapstructure:"disable_vnc"` - BootCommand []string `mapstructure:"boot_command"` + Headless bool `mapstructure:"headless"` VNCBindAddress string `mapstructure:"vnc_bind_address"` VNCPortMin uint `mapstructure:"vnc_port_min"` VNCPortMax uint `mapstructure:"vnc_port_max"` VNCDisablePassword bool `mapstructure:"vnc_disable_password"` - - BootWait time.Duration `` } -func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { - if c.RawBootWait == "" { - c.RawBootWait = "10s" - } - +func (c *RunConfig) Prepare(ctx *interpolate.Context) (errs []error) { if c.VNCPortMin == 0 { c.VNCPortMin = 5900 } @@ -38,25 +28,9 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { c.VNCBindAddress = "127.0.0.1" } - var errs []error - var err error - if len(c.BootCommand) > 0 && c.DisableVNC { - errs = append(errs, - fmt.Errorf("A boot command cannot be used when vnc is disabled.")) - } - - if c.RawBootWait != "" { - c.BootWait, err = time.ParseDuration(c.RawBootWait) - if err != nil { - errs = append( - errs, fmt.Errorf("Failed parsing boot_wait: %s", err)) - } - } - if c.VNCPortMin > c.VNCPortMax { - errs = append( - errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max")) + errs = append(errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max")) } - return errs + return } diff --git a/builder/vmware/common/run_config_test.go b/builder/vmware/common/run_config_test.go deleted file mode 100644 index d94e254f7..000000000 --- a/builder/vmware/common/run_config_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package common - -import ( - "testing" -) - -func TestRunConfigPrepare(t *testing.T) { - var c *RunConfig - - // Test a default boot_wait - c = new(RunConfig) - c.RawBootWait = "" - errs := c.Prepare(testConfigTemplate(t)) - if len(errs) > 0 { - t.Fatalf("bad: %#v", 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.Fatal("should error") - } - - // Test with a good one - c = new(RunConfig) - c.RawBootWait = "5s" - errs = c.Prepare(testConfigTemplate(t)) - if len(errs) > 0 { - t.Fatalf("bad: %#v", errs) - } -} diff --git a/builder/vmware/common/step_run.go b/builder/vmware/common/step_run.go index f85d5948f..2dad6faed 100644 --- a/builder/vmware/common/step_run.go +++ b/builder/vmware/common/step_run.go @@ -19,7 +19,6 @@ import ( // Produces: // type StepRun struct { - BootWait time.Duration DurationBeforeStop time.Duration Headless bool @@ -65,24 +64,6 @@ func (s *StepRun) Run(_ context.Context, state multistep.StateBag) multistep.Ste return multistep.ActionHalt } - // Wait the wait amount - if int64(s.BootWait) > 0 { - ui.Say(fmt.Sprintf("Waiting %s for boot...", s.BootWait.String())) - 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 } diff --git a/builder/vmware/common/step_type_boot_command.go b/builder/vmware/common/step_type_boot_command.go index 02404af91..83d55ce11 100644 --- a/builder/vmware/common/step_type_boot_command.go +++ b/builder/vmware/common/step_type_boot_command.go @@ -5,28 +5,16 @@ import ( "fmt" "log" "net" - "os" - "regexp" - "strings" "time" - "unicode" - "unicode/utf8" "github.com/hashicorp/packer/common" + "github.com/hashicorp/packer/common/bootcommand" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" "github.com/mitchellh/go-vnc" ) -const KeyLeftShift uint32 = 0xFFE1 - -type bootCommandTemplateData struct { - HTTPIP string - HTTPPort uint - Name string -} - // This step "types" the boot command into the VM over VNC. // // Uses: @@ -37,13 +25,19 @@ type bootCommandTemplateData struct { // Produces: // type StepTypeBootCommand struct { + BootCommand string VNCEnabled bool - BootCommand []string + BootWait time.Duration VMName string Ctx interpolate.Context } +type bootCommandTemplateData struct { + HTTPIP string + HTTPPort uint + Name string +} -func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { +func (s *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { if !s.VNCEnabled { log.Println("Skipping boot command step...") return multistep.ActionContinue @@ -57,6 +51,17 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m vncPort := state.Get("vnc_port").(uint) vncPassword := state.Get("vnc_password") + // Wait the for the vm to boot. + if int64(s.BootWait) > 0 { + ui.Say(fmt.Sprintf("Waiting %s for boot...", s.BootWait.String())) + select { + case <-time.After(s.BootWait): + break + case <-ctx.Done(): + return multistep.ActionHalt + } + } + var pauseFn multistep.DebugPauseFn if debug { pauseFn = state.Get("pauseFn").(multistep.DebugPauseFn) @@ -110,355 +115,38 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m s.VMName, } + d := bootcommand.NewVNCDriver(c) + ui.Say("Typing the boot command over VNC...") - for i, 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 - } + command, err := interpolate.Render(s.BootCommand, &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 + } - // Check for interrupts between typing things so we can cancel - // since this isn't the fastest thing. - if _, ok := state.GetOk(multistep.StateCancelled); ok { - return multistep.ActionHalt - } + seq, err := bootcommand.GenerateExpressionSequence(command) + if err != nil { + err := fmt.Errorf("Error generating boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } - if pauseFn != nil { - pauseFn(multistep.DebugLocationAfterRun, fmt.Sprintf("boot_command[%d]: %s", i, command), state) - } + if err := seq.Do(ctx, d); err != nil { + err := fmt.Errorf("Error running boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } - vncSendString(c, command) + if pauseFn != nil { + pauseFn(multistep.DebugLocationAfterRun, + fmt.Sprintf("boot_command: %s", command), state) } return multistep.ActionContinue } func (*StepTypeBootCommand) Cleanup(multistep.StateBag) {} - -func vncSendString(c *vnc.ClientConn, original string) { - // Scancodes reference: https://github.com/qemu/qemu/blob/master/ui/vnc_keysym.h - special := make(map[string]uint32) - special[""] = 0xFF08 - special[""] = 0xFFFF - special[""] = 0xFF0D - special[""] = 0xFF1B - special[""] = 0xFFBE - special[""] = 0xFFBF - special[""] = 0xFFC0 - special[""] = 0xFFC1 - special[""] = 0xFFC2 - special[""] = 0xFFC3 - special[""] = 0xFFC4 - special[""] = 0xFFC5 - special[""] = 0xFFC6 - special[""] = 0xFFC7 - special[""] = 0xFFC8 - special[""] = 0xFFC9 - special[""] = 0xFF0D - special[""] = 0xFF09 - special[""] = 0xFF52 - special[""] = 0xFF54 - special[""] = 0xFF51 - special[""] = 0xFF53 - special[""] = 0x020 - special[""] = 0xFF63 - special[""] = 0xFF50 - special[""] = 0xFF57 - special[""] = 0xFF55 - special[""] = 0xFF56 - special[""] = 0xFFE9 - special[""] = 0xFFE3 - special[""] = 0xFFE1 - special[""] = 0xFFEA - special[""] = 0xFFE4 - special[""] = 0xFFE2 - special[""] = 0xFFEB - special[""] = 0xFFEC - - shiftedChars := "~!@#$%^&*()_+{}|:\"<>?" - - // We delay (default 100ms) between each key event to allow for CPU or - // network latency. See PackerKeyEnv for tuning. - keyInterval := common.PackerKeyDefault - if delay, err := time.ParseDuration(os.Getenv(common.PackerKeyEnv)); err == nil { - keyInterval = delay - } - - azOnRegex := regexp.MustCompile("^<(?P[a-zA-Z])On>") - azOffRegex := regexp.MustCompile("^<(?P[a-zA-Z])Off>") - - // TODO(mitchellh): Ripe for optimizations of some point, perhaps. - for len(original) > 0 { - var keyCode uint32 - keyShift := false - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, true) - time.Sleep(keyInterval) - - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, true) - time.Sleep(keyInterval) - - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, true) - time.Sleep(keyInterval) - - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, true) - time.Sleep(keyInterval) - - continue - } - - if azOnRegex.MatchString(original) { - m := azOnRegex.FindStringSubmatch(original) - r, _ := utf8.DecodeRuneInString(m[1]) - original = original[len(""):] - keyCode = uint32(r) - keyShift = unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r) - - log.Printf("Special code '%s' found, replacing with %d, shift %v", m[0], keyCode, keyShift) - - if keyShift { - c.KeyEvent(KeyLeftShift, true) - } - - c.KeyEvent(keyCode, true) - time.Sleep(keyInterval) - - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, false) - time.Sleep(keyInterval) - - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, false) - time.Sleep(keyInterval) - - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, false) - time.Sleep(keyInterval) - - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, false) - time.Sleep(keyInterval) - - continue - } - - if azOffRegex.MatchString(original) { - m := azOffRegex.FindStringSubmatch(original) - r, _ := utf8.DecodeRuneInString(m[1]) - original = original[len(""):] - keyCode = uint32(r) - keyShift = unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r) - - log.Printf("Special code '%s' found, replacing with %d, shift %v", m[0], keyCode, keyShift) - - if keyShift { - c.KeyEvent(KeyLeftShift, false) - } - - c.KeyEvent(keyCode, false) - time.Sleep(keyInterval) - - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, true) - time.Sleep(keyInterval) - - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, true) - time.Sleep(keyInterval) - - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, true) - time.Sleep(keyInterval) - - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, true) - time.Sleep(keyInterval) - - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, false) - time.Sleep(keyInterval) - - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, false) - time.Sleep(keyInterval) - - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, false) - time.Sleep(keyInterval) - - continue - } - - if strings.HasPrefix(original, "") { - keyCode = special[""] - original = original[len(""):] - log.Printf("Special code '' found, replacing with: %d", keyCode) - - c.KeyEvent(keyCode, false) - time.Sleep(keyInterval) - - continue - } - - if strings.HasPrefix(original, "") { - log.Printf("Special code '' found, sleeping one second") - time.Sleep(1 * time.Second) - original = original[len(""):] - continue - } - - if strings.HasPrefix(original, "") { - log.Printf("Special code '' found, sleeping 5 seconds") - time.Sleep(5 * time.Second) - original = original[len(""):] - continue - } - - if strings.HasPrefix(original, "") { - log.Printf("Special code '' found, sleeping 10 seconds") - time.Sleep(10 * time.Second) - original = original[len(""):] - continue - } - - for specialCode, specialValue := range special { - if strings.HasPrefix(original, specialCode) { - log.Printf("Special code '%s' found, replacing with: %d", specialCode, specialValue) - keyCode = specialValue - original = original[len(specialCode):] - break - } - } - - if keyCode == 0 { - r, size := utf8.DecodeRuneInString(original) - original = original[size:] - keyCode = uint32(r) - keyShift = unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r) - - log.Printf("Sending char '%c', code %d, shift %v", r, keyCode, keyShift) - } - - if keyShift { - c.KeyEvent(KeyLeftShift, true) - } - - c.KeyEvent(keyCode, true) - time.Sleep(keyInterval) - c.KeyEvent(keyCode, false) - time.Sleep(keyInterval) - - if keyShift { - c.KeyEvent(KeyLeftShift, false) - } - } -} diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 9e7e74452..60c0fc462 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -11,6 +11,7 @@ import ( vmwcommon "github.com/hashicorp/packer/builder/vmware/common" "github.com/hashicorp/packer/common" + "github.com/hashicorp/packer/common/bootcommand" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/helper/multistep" @@ -30,6 +31,7 @@ type Config struct { common.HTTPConfig `mapstructure:",squash"` common.ISOConfig `mapstructure:",squash"` common.FloppyConfig `mapstructure:",squash"` + bootcommand.VNCConfig `mapstructure:",squash"` vmwcommon.DriverConfig `mapstructure:",squash"` vmwcommon.OutputConfig `mapstructure:",squash"` vmwcommon.RunConfig `mapstructure:",squash"` @@ -122,6 +124,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs = packer.MultiErrorAppend(errs, b.config.ToolsConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.VMXConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...) + errs = packer.MultiErrorAppend(errs, b.config.VNCConfig.Prepare(&b.config.ctx)...) if b.config.DiskName == "" { b.config.DiskName = "disk" @@ -319,13 +322,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Format: b.config.Format, }, &vmwcommon.StepRun{ - BootWait: b.config.BootWait, DurationBeforeStop: 5 * time.Second, Headless: b.config.Headless, }, &vmwcommon.StepTypeBootCommand{ + BootWait: b.config.BootWait, VNCEnabled: !b.config.DisableVNC, - BootCommand: b.config.BootCommand, + BootCommand: b.config.FlatBootCommand(), VMName: b.config.VMName, Ctx: b.config.ctx, }, diff --git a/builder/vmware/iso/step_create_vmx_test.go b/builder/vmware/iso/step_create_vmx_test.go index 7553dff82..26605a1f5 100644 --- a/builder/vmware/iso/step_create_vmx_test.go +++ b/builder/vmware/iso/step_create_vmx_test.go @@ -12,10 +12,11 @@ import ( "strconv" "strings" + "testing" + "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/provisioner/shell" "github.com/hashicorp/packer/template" - "testing" ) var vmxTestBuilderConfig = map[string]string{ diff --git a/builder/vmware/vmx/builder.go b/builder/vmware/vmx/builder.go index 97852b534..130c533d7 100644 --- a/builder/vmware/vmx/builder.go +++ b/builder/vmware/vmx/builder.go @@ -32,7 +32,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } // Run executes a Packer build and returns a packer.Artifact representing -// a VirtualBox appliance. +// a VMware image. func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { driver, err := vmwcommon.NewDriver(&b.config.DriverConfig, &b.config.SSHConfig) if err != nil { @@ -87,13 +87,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe VNCDisablePassword: b.config.VNCDisablePassword, }, &vmwcommon.StepRun{ - BootWait: b.config.BootWait, DurationBeforeStop: 5 * time.Second, Headless: b.config.Headless, }, &vmwcommon.StepTypeBootCommand{ + BootWait: b.config.BootWait, VNCEnabled: !b.config.DisableVNC, - BootCommand: b.config.BootCommand, + BootCommand: b.config.FlatBootCommand(), VMName: b.config.VMName, Ctx: b.config.ctx, }, diff --git a/builder/vmware/vmx/config.go b/builder/vmware/vmx/config.go index b4d1c009f..0fb9993aa 100644 --- a/builder/vmware/vmx/config.go +++ b/builder/vmware/vmx/config.go @@ -6,6 +6,7 @@ import ( vmwcommon "github.com/hashicorp/packer/builder/vmware/common" "github.com/hashicorp/packer/common" + "github.com/hashicorp/packer/common/bootcommand" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" @@ -16,6 +17,7 @@ type Config struct { common.PackerConfig `mapstructure:",squash"` common.HTTPConfig `mapstructure:",squash"` common.FloppyConfig `mapstructure:",squash"` + bootcommand.VNCConfig `mapstructure:",squash"` vmwcommon.DriverConfig `mapstructure:",squash"` vmwcommon.OutputConfig `mapstructure:",squash"` vmwcommon.RunConfig `mapstructure:",squash"` @@ -65,6 +67,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { errs = packer.MultiErrorAppend(errs, c.ToolsConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.VMXConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.FloppyConfig.Prepare(&c.ctx)...) + errs = packer.MultiErrorAppend(errs, c.VNCConfig.Prepare(&c.ctx)...) if c.SourcePath == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is blank, but is required")) diff --git a/builder/vmware/vmx/step_clone_vmx.go b/builder/vmware/vmx/step_clone_vmx.go index f0bdd586d..edd81ea99 100644 --- a/builder/vmware/vmx/step_clone_vmx.go +++ b/builder/vmware/vmx/step_clone_vmx.go @@ -18,14 +18,70 @@ type StepCloneVMX struct { VMName string } +type vmxAdapter struct { + // The string portion of the address used in the vmx file + strAddr string + // Max address for adapter, controller, or controller channel + aAddrMax int + // Max address for device or channel supported by adapter + dAddrMax int +} + +const ( + // VMware Configuration Maximums - Virtual Hardware Versions 13/14 + // + // Specifying the max numbers for the adapter/controller:bus/channel + // *address* as opposed to specifying the maximums as per the VMware + // documentation allows consistent (inclusive) treatment when looping + // over each adapter/controller type + // + // SCSI - Address range: scsi0:0 to scsi3:15 + scsiAddrName = "scsi" // String part of address used in the vmx file + maxSCSIAdapterAddr = 3 // Max 4 adapters + maxSCSIDeviceAddr = 15 // Max 15 devices per adapter; ID 7 is the HBA + // SATA - Address range: sata0:0 to scsi3:29 + sataAddrName = "sata" // String part of address used in the vmx file + maxSATAAdapterAddr = 3 // Max 4 controllers + maxSATADeviceAddr = 29 // Max 30 devices per controller + // NVMe - Address range: nvme0:0 to nvme3:14 + nvmeAddrName = "nvme" // String part of address used in the vmx file + maxNVMeAdapterAddr = 3 // Max 4 adapters + maxNVMeDeviceAddr = 14 // Max 15 devices per adapter + // IDE - Address range: ide0:0 to ide1:1 + ideAddrName = "ide" // String part of address used in the vmx file + maxIDEAdapterAddr = 1 // One controller with primary/secondary channels + maxIDEDeviceAddr = 1 // Each channel supports master and slave +) + +var ( + scsiAdapter = vmxAdapter{ + strAddr: scsiAddrName, + aAddrMax: maxSCSIAdapterAddr, + dAddrMax: maxSCSIDeviceAddr, + } + sataAdapter = vmxAdapter{ + strAddr: sataAddrName, + aAddrMax: maxSATAAdapterAddr, + dAddrMax: maxSATADeviceAddr, + } + nvmeAdapter = vmxAdapter{ + strAddr: nvmeAddrName, + aAddrMax: maxNVMeAdapterAddr, + dAddrMax: maxNVMeDeviceAddr, + } + ideAdapter = vmxAdapter{ + strAddr: ideAddrName, + aAddrMax: maxIDEAdapterAddr, + dAddrMax: maxIDEDeviceAddr, + } +) + func (s *StepCloneVMX) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { driver := state.Get("driver").(vmwcommon.Driver) ui := state.Get("ui").(packer.Ui) - // initially we need to stash the path to the original .vmx file + // Set the path we want for the new .vmx file and clone vmxPath := filepath.Join(s.OutputDir, s.VMName+".vmx") - - // so first, let's clone the source path to the vmxPath ui.Say("Cloning source VM...") log.Printf("Cloning from: %s", s.Path) log.Printf("Cloning to: %s", vmxPath) @@ -33,34 +89,44 @@ func (s *StepCloneVMX) Run(_ context.Context, state multistep.StateBag) multiste state.Put("error", err) return multistep.ActionHalt } - ui.Say(fmt.Sprintf("Successfully cloned source VM to: %s", vmxPath)) - // now we read the .vmx so we can determine what else to stash + // Read in the machine configuration from the cloned VMX file + // + // * The main driver needs the path to the vmx (set above) and the + // network type so that it can work out things like IP's and MAC + // addresses + // * The disk compaction step needs the paths to all attached disks vmxData, err := vmwcommon.ReadVMX(vmxPath) if err != nil { state.Put("error", err) return multistep.ActionHalt } - // figure out the disk filename by walking through all device types - var diskName string - if _, ok := vmxData["scsi0:0.filename"]; ok { - diskName = vmxData["scsi0:0.filename"] + // Search across all adapter types to get the filenames of attached disks + allDiskAdapters := []vmxAdapter{ + scsiAdapter, + sataAdapter, + nvmeAdapter, + ideAdapter, } - if _, ok := vmxData["sata0:0.filename"]; ok { - diskName = vmxData["sata0:0.filename"] + var diskFilenames []string + for _, adapter := range allDiskAdapters { + diskFilenames = append(diskFilenames, getAttachedDisks(adapter, vmxData)...) } - if _, ok := vmxData["ide0:0.filename"]; ok { - diskName = vmxData["ide0:0.filename"] + + // Write out the relative, host filesystem paths to the disks + var diskFullPaths []string + for _, diskFilename := range diskFilenames { + log.Printf("Found attached disk with filename: %s", diskFilename) + diskFullPaths = append(diskFullPaths, filepath.Join(s.OutputDir, diskFilename)) } - if diskName == "" { - err := fmt.Errorf("Root disk filename could not be found!") - state.Put("error", err) + + if len(diskFullPaths) == 0 { + state.Put("error", fmt.Errorf("Could not enumerate disk info from the vmx file")) return multistep.ActionHalt } - log.Printf("Found root disk filename: %s", diskName) - // determine the network type by reading out of the .vmx + // Determine the network type by reading out of the .vmx var networkType string if _, ok := vmxData["ethernet0.connectiontype"]; ok { networkType = vmxData["ethernet0.connectiontype"] @@ -70,11 +136,13 @@ func (s *StepCloneVMX) Run(_ context.Context, state multistep.StateBag) multiste networkType = "nat" log.Printf("Defaulting to network type: %s", networkType) } - ui.Say(fmt.Sprintf("Using network type: %s", networkType)) - // we were able to find everything, so stash it in our state. + // Stash all required information in our state bag state.Put("vmx_path", vmxPath) - state.Put("full_disk_path", filepath.Join(s.OutputDir, diskName)) + // What disks get assigned to what key doesn't actually matter here + // since it's unimportant to the way the disk compaction step works + state.Put("full_disk_path", diskFullPaths[0]) + state.Put("additional_disk_paths", diskFullPaths[1:]) state.Put("vmnetwork", networkType) return multistep.ActionContinue @@ -82,3 +150,17 @@ func (s *StepCloneVMX) Run(_ context.Context, state multistep.StateBag) multiste func (s *StepCloneVMX) Cleanup(state multistep.StateBag) { } + +func getAttachedDisks(a vmxAdapter, data map[string]string) (attachedDisks []string) { + // Loop over possible adapter, controller or controller channel + for x := 0; x <= a.aAddrMax; x++ { + // Loop over possible addresses for attached devices + for y := 0; y <= a.dAddrMax; y++ { + address := fmt.Sprintf("%s%d:%d.filename", a.strAddr, x, y) + if device, _ := data[address]; filepath.Ext(device) == ".vmdk" { + attachedDisks = append(attachedDisks, device) + } + } + } + return +} diff --git a/builder/vmware/vmx/step_clone_vmx_test.go b/builder/vmware/vmx/step_clone_vmx_test.go index e086f0ce5..e7ef9ff95 100644 --- a/builder/vmware/vmx/step_clone_vmx_test.go +++ b/builder/vmware/vmx/step_clone_vmx_test.go @@ -2,6 +2,7 @@ package vmx import ( "context" + "fmt" "io/ioutil" "os" "path/filepath" @@ -9,6 +10,14 @@ import ( vmwcommon "github.com/hashicorp/packer/builder/vmware/common" "github.com/hashicorp/packer/helper/multistep" + "github.com/stretchr/testify/assert" +) + +const ( + scsiFilename = "scsiDisk.vmdk" + sataFilename = "sataDisk.vmdk" + nvmeFilename = "nvmeDisk.vmdk" + ideFilename = "ideDisk.vmdk" ) func TestStepCloneVMX_impl(t *testing.T) { @@ -23,6 +32,22 @@ func TestStepCloneVMX(t *testing.T) { } defer os.RemoveAll(td) + // Set up mock vmx file contents + var testCloneVMX = fmt.Sprintf("scsi0:0.filename = \"%s\"\n"+ + "sata0:0.filename = \"%s\"\n"+ + "nvme0:0.filename = \"%s\"\n"+ + "ide1:0.filename = \"%s\"\n"+ + "ide0:0.filename = \"auto detect\"\n"+ + "ethernet0.connectiontype = \"nat\"\n", scsiFilename, + sataFilename, nvmeFilename, ideFilename) + + // Set up expected mock disk file paths + diskFilenames := []string{scsiFilename, sataFilename, ideFilename, nvmeFilename} + var diskPaths []string + for _, diskFilename := range diskFilenames { + diskPaths = append(diskPaths, filepath.Join(td, diskFilename)) + } + // Create the source sourcePath := filepath.Join(td, "source.vmx") if err := ioutil.WriteFile(sourcePath, []byte(testCloneVMX), 0644); err != nil { @@ -60,16 +85,26 @@ func TestStepCloneVMX(t *testing.T) { if vmxPath, ok := state.GetOk("vmx_path"); !ok { t.Fatal("should set vmx_path") } else if vmxPath != destPath { - t.Fatalf("bad: %#v", vmxPath) + t.Fatalf("bad path to vmx: %#v", vmxPath) } if diskPath, ok := state.GetOk("full_disk_path"); !ok { t.Fatal("should set full_disk_path") - } else if diskPath != filepath.Join(td, "foo") { - t.Fatalf("bad: %#v", diskPath) + } else if diskPath != diskPaths[0] { + t.Fatalf("bad disk path: %#v", diskPath) + } + + if stateDiskPaths, ok := state.GetOk("additional_disk_paths"); !ok { + t.Fatal("should set additional_disk_paths") + } else { + assert.ElementsMatchf(t, stateDiskPaths.([]string), diskPaths[1:], + "%s\nshould contain the same elements as:\n%s", stateDiskPaths.([]string), diskPaths[1:]) + } + + // Test we got the network type + if networkType, ok := state.GetOk("vmnetwork"); !ok { + t.Fatal("should set vmnetwork") + } else if networkType != "nat" { + t.Fatalf("bad network type: %#v", networkType) } } - -const testCloneVMX = ` -scsi0:0.fileName = "foo" -` diff --git a/common/bootcommand/boot_command.go b/common/bootcommand/boot_command.go new file mode 100644 index 000000000..18578a42f --- /dev/null +++ b/common/bootcommand/boot_command.go @@ -0,0 +1,2072 @@ +package bootcommand + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "math" + "os" + "sort" + "strconv" + "strings" + "time" + "unicode" + "unicode/utf8" +) + +var g = &grammar{ + rules: []*rule{ + { + name: "Input", + pos: position{line: 6, col: 1, offset: 26}, + expr: &actionExpr{ + pos: position{line: 6, col: 10, offset: 35}, + run: (*parser).callonInput1, + expr: &seqExpr{ + pos: position{line: 6, col: 10, offset: 35}, + exprs: []interface{}{ + &labeledExpr{ + pos: position{line: 6, col: 10, offset: 35}, + label: "expr", + expr: &ruleRefExpr{ + pos: position{line: 6, col: 15, offset: 40}, + name: "Expr", + }, + }, + &ruleRefExpr{ + pos: position{line: 6, col: 20, offset: 45}, + name: "EOF", + }, + }, + }, + }, + }, + { + name: "Expr", + pos: position{line: 10, col: 1, offset: 75}, + expr: &actionExpr{ + pos: position{line: 10, col: 9, offset: 83}, + run: (*parser).callonExpr1, + expr: &labeledExpr{ + pos: position{line: 10, col: 9, offset: 83}, + label: "l", + expr: &oneOrMoreExpr{ + pos: position{line: 10, col: 11, offset: 85}, + expr: &choiceExpr{ + pos: position{line: 10, col: 13, offset: 87}, + alternatives: []interface{}{ + &ruleRefExpr{ + pos: position{line: 10, col: 13, offset: 87}, + name: "Wait", + }, + &ruleRefExpr{ + pos: position{line: 10, col: 20, offset: 94}, + name: "CharToggle", + }, + &ruleRefExpr{ + pos: position{line: 10, col: 33, offset: 107}, + name: "Special", + }, + &ruleRefExpr{ + pos: position{line: 10, col: 43, offset: 117}, + name: "Literal", + }, + }, + }, + }, + }, + }, + }, + { + name: "Wait", + pos: position{line: 14, col: 1, offset: 150}, + expr: &actionExpr{ + pos: position{line: 14, col: 8, offset: 157}, + run: (*parser).callonWait1, + expr: &seqExpr{ + pos: position{line: 14, col: 8, offset: 157}, + exprs: []interface{}{ + &ruleRefExpr{ + pos: position{line: 14, col: 8, offset: 157}, + name: "ExprStart", + }, + &litMatcher{ + pos: position{line: 14, col: 18, offset: 167}, + val: "wait", + ignoreCase: false, + }, + &labeledExpr{ + pos: position{line: 14, col: 25, offset: 174}, + label: "duration", + expr: &zeroOrOneExpr{ + pos: position{line: 14, col: 34, offset: 183}, + expr: &choiceExpr{ + pos: position{line: 14, col: 36, offset: 185}, + alternatives: []interface{}{ + &ruleRefExpr{ + pos: position{line: 14, col: 36, offset: 185}, + name: "Duration", + }, + &ruleRefExpr{ + pos: position{line: 14, col: 47, offset: 196}, + name: "Integer", + }, + }, + }, + }, + }, + &ruleRefExpr{ + pos: position{line: 14, col: 58, offset: 207}, + name: "ExprEnd", + }, + }, + }, + }, + }, + { + name: "CharToggle", + pos: position{line: 27, col: 1, offset: 453}, + expr: &actionExpr{ + pos: position{line: 27, col: 14, offset: 466}, + run: (*parser).callonCharToggle1, + expr: &seqExpr{ + pos: position{line: 27, col: 14, offset: 466}, + exprs: []interface{}{ + &ruleRefExpr{ + pos: position{line: 27, col: 14, offset: 466}, + name: "ExprStart", + }, + &labeledExpr{ + pos: position{line: 27, col: 24, offset: 476}, + label: "lit", + expr: &ruleRefExpr{ + pos: position{line: 27, col: 29, offset: 481}, + name: "Literal", + }, + }, + &labeledExpr{ + pos: position{line: 27, col: 38, offset: 490}, + label: "t", + expr: &choiceExpr{ + pos: position{line: 27, col: 41, offset: 493}, + alternatives: []interface{}{ + &ruleRefExpr{ + pos: position{line: 27, col: 41, offset: 493}, + name: "On", + }, + &ruleRefExpr{ + pos: position{line: 27, col: 46, offset: 498}, + name: "Off", + }, + }, + }, + }, + &ruleRefExpr{ + pos: position{line: 27, col: 51, offset: 503}, + name: "ExprEnd", + }, + }, + }, + }, + }, + { + name: "Special", + pos: position{line: 31, col: 1, offset: 574}, + expr: &actionExpr{ + pos: position{line: 31, col: 11, offset: 584}, + run: (*parser).callonSpecial1, + expr: &seqExpr{ + pos: position{line: 31, col: 11, offset: 584}, + exprs: []interface{}{ + &ruleRefExpr{ + pos: position{line: 31, col: 11, offset: 584}, + name: "ExprStart", + }, + &labeledExpr{ + pos: position{line: 31, col: 21, offset: 594}, + label: "s", + expr: &ruleRefExpr{ + pos: position{line: 31, col: 24, offset: 597}, + name: "SpecialKey", + }, + }, + &labeledExpr{ + pos: position{line: 31, col: 36, offset: 609}, + label: "t", + expr: &zeroOrOneExpr{ + pos: position{line: 31, col: 38, offset: 611}, + expr: &choiceExpr{ + pos: position{line: 31, col: 39, offset: 612}, + alternatives: []interface{}{ + &ruleRefExpr{ + pos: position{line: 31, col: 39, offset: 612}, + name: "On", + }, + &ruleRefExpr{ + pos: position{line: 31, col: 44, offset: 617}, + name: "Off", + }, + }, + }, + }, + }, + &ruleRefExpr{ + pos: position{line: 31, col: 50, offset: 623}, + name: "ExprEnd", + }, + }, + }, + }, + }, + { + name: "Number", + pos: position{line: 39, col: 1, offset: 810}, + expr: &actionExpr{ + pos: position{line: 39, col: 10, offset: 819}, + run: (*parser).callonNumber1, + expr: &seqExpr{ + pos: position{line: 39, col: 10, offset: 819}, + exprs: []interface{}{ + &zeroOrOneExpr{ + pos: position{line: 39, col: 10, offset: 819}, + expr: &litMatcher{ + pos: position{line: 39, col: 10, offset: 819}, + val: "-", + ignoreCase: false, + }, + }, + &ruleRefExpr{ + pos: position{line: 39, col: 15, offset: 824}, + name: "Integer", + }, + &zeroOrOneExpr{ + pos: position{line: 39, col: 23, offset: 832}, + expr: &seqExpr{ + pos: position{line: 39, col: 25, offset: 834}, + exprs: []interface{}{ + &litMatcher{ + pos: position{line: 39, col: 25, offset: 834}, + val: ".", + ignoreCase: false, + }, + &oneOrMoreExpr{ + pos: position{line: 39, col: 29, offset: 838}, + expr: &ruleRefExpr{ + pos: position{line: 39, col: 29, offset: 838}, + name: "Digit", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "Integer", + pos: position{line: 43, col: 1, offset: 884}, + expr: &choiceExpr{ + pos: position{line: 43, col: 11, offset: 894}, + alternatives: []interface{}{ + &litMatcher{ + pos: position{line: 43, col: 11, offset: 894}, + val: "0", + ignoreCase: false, + }, + &actionExpr{ + pos: position{line: 43, col: 17, offset: 900}, + run: (*parser).callonInteger3, + expr: &seqExpr{ + pos: position{line: 43, col: 17, offset: 900}, + exprs: []interface{}{ + &ruleRefExpr{ + pos: position{line: 43, col: 17, offset: 900}, + name: "NonZeroDigit", + }, + &zeroOrMoreExpr{ + pos: position{line: 43, col: 30, offset: 913}, + expr: &ruleRefExpr{ + pos: position{line: 43, col: 30, offset: 913}, + name: "Digit", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "Duration", + pos: position{line: 47, col: 1, offset: 977}, + expr: &actionExpr{ + pos: position{line: 47, col: 12, offset: 988}, + run: (*parser).callonDuration1, + expr: &oneOrMoreExpr{ + pos: position{line: 47, col: 12, offset: 988}, + expr: &seqExpr{ + pos: position{line: 47, col: 14, offset: 990}, + exprs: []interface{}{ + &ruleRefExpr{ + pos: position{line: 47, col: 14, offset: 990}, + name: "Number", + }, + &ruleRefExpr{ + pos: position{line: 47, col: 21, offset: 997}, + name: "TimeUnit", + }, + }, + }, + }, + }, + }, + { + name: "On", + pos: position{line: 51, col: 1, offset: 1060}, + expr: &actionExpr{ + pos: position{line: 51, col: 6, offset: 1065}, + run: (*parser).callonOn1, + expr: &litMatcher{ + pos: position{line: 51, col: 6, offset: 1065}, + val: "on", + ignoreCase: true, + }, + }, + }, + { + name: "Off", + pos: position{line: 55, col: 1, offset: 1098}, + expr: &actionExpr{ + pos: position{line: 55, col: 7, offset: 1104}, + run: (*parser).callonOff1, + expr: &litMatcher{ + pos: position{line: 55, col: 7, offset: 1104}, + val: "off", + ignoreCase: true, + }, + }, + }, + { + name: "Literal", + pos: position{line: 59, col: 1, offset: 1139}, + expr: &actionExpr{ + pos: position{line: 59, col: 11, offset: 1149}, + run: (*parser).callonLiteral1, + expr: &anyMatcher{ + line: 59, col: 11, offset: 1149, + }, + }, + }, + { + name: "ExprEnd", + pos: position{line: 64, col: 1, offset: 1230}, + expr: &litMatcher{ + pos: position{line: 64, col: 11, offset: 1240}, + val: ">", + ignoreCase: false, + }, + }, + { + name: "ExprStart", + pos: position{line: 65, col: 1, offset: 1244}, + expr: &litMatcher{ + pos: position{line: 65, col: 13, offset: 1256}, + val: "<", + ignoreCase: false, + }, + }, + { + name: "SpecialKey", + pos: position{line: 66, col: 1, offset: 1260}, + expr: &choiceExpr{ + pos: position{line: 66, col: 14, offset: 1273}, + alternatives: []interface{}{ + &litMatcher{ + pos: position{line: 66, col: 14, offset: 1273}, + val: "bs", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 66, col: 22, offset: 1281}, + val: "del", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 66, col: 31, offset: 1290}, + val: "enter", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 66, col: 42, offset: 1301}, + val: "esc", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 66, col: 51, offset: 1310}, + val: "f10", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 66, col: 60, offset: 1319}, + val: "f11", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 66, col: 69, offset: 1328}, + val: "f12", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 67, col: 11, offset: 1345}, + val: "f1", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 67, col: 19, offset: 1353}, + val: "f2", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 67, col: 27, offset: 1361}, + val: "f3", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 67, col: 35, offset: 1369}, + val: "f4", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 67, col: 43, offset: 1377}, + val: "f5", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 67, col: 51, offset: 1385}, + val: "f6", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 67, col: 59, offset: 1393}, + val: "f7", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 67, col: 67, offset: 1401}, + val: "f8", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 67, col: 75, offset: 1409}, + val: "f9", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 68, col: 12, offset: 1426}, + val: "return", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 68, col: 24, offset: 1438}, + val: "tab", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 68, col: 33, offset: 1447}, + val: "up", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 68, col: 41, offset: 1455}, + val: "down", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 68, col: 51, offset: 1465}, + val: "spacebar", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 68, col: 65, offset: 1479}, + val: "insert", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 68, col: 77, offset: 1491}, + val: "home", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 69, col: 11, offset: 1509}, + val: "end", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 69, col: 20, offset: 1518}, + val: "pageup", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 69, col: 32, offset: 1530}, + val: "pagedown", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 69, col: 46, offset: 1544}, + val: "leftalt", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 69, col: 59, offset: 1557}, + val: "leftctrl", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 69, col: 73, offset: 1571}, + val: "leftshift", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 70, col: 11, offset: 1594}, + val: "rightalt", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 70, col: 25, offset: 1608}, + val: "rightctrl", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 70, col: 40, offset: 1623}, + val: "rightshift", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 70, col: 56, offset: 1639}, + val: "leftsuper", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 70, col: 71, offset: 1654}, + val: "rightsuper", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 71, col: 11, offset: 1678}, + val: "left", + ignoreCase: true, + }, + &litMatcher{ + pos: position{line: 71, col: 21, offset: 1688}, + val: "right", + ignoreCase: true, + }, + }, + }, + }, + { + name: "NonZeroDigit", + pos: position{line: 73, col: 1, offset: 1698}, + expr: &charClassMatcher{ + pos: position{line: 73, col: 16, offset: 1713}, + val: "[1-9]", + ranges: []rune{'1', '9'}, + ignoreCase: false, + inverted: false, + }, + }, + { + name: "Digit", + pos: position{line: 74, col: 1, offset: 1719}, + expr: &charClassMatcher{ + pos: position{line: 74, col: 9, offset: 1727}, + val: "[0-9]", + ranges: []rune{'0', '9'}, + ignoreCase: false, + inverted: false, + }, + }, + { + name: "TimeUnit", + pos: position{line: 75, col: 1, offset: 1733}, + expr: &choiceExpr{ + pos: position{line: 75, col: 13, offset: 1745}, + alternatives: []interface{}{ + &litMatcher{ + pos: position{line: 75, col: 13, offset: 1745}, + val: "ns", + ignoreCase: false, + }, + &litMatcher{ + pos: position{line: 75, col: 20, offset: 1752}, + val: "us", + ignoreCase: false, + }, + &litMatcher{ + pos: position{line: 75, col: 27, offset: 1759}, + val: "µs", + ignoreCase: false, + }, + &litMatcher{ + pos: position{line: 75, col: 34, offset: 1767}, + val: "ms", + ignoreCase: false, + }, + &litMatcher{ + pos: position{line: 75, col: 41, offset: 1774}, + val: "s", + ignoreCase: false, + }, + &litMatcher{ + pos: position{line: 75, col: 47, offset: 1780}, + val: "m", + ignoreCase: false, + }, + &litMatcher{ + pos: position{line: 75, col: 53, offset: 1786}, + val: "h", + ignoreCase: false, + }, + }, + }, + }, + { + name: "_", + displayName: "\"whitespace\"", + pos: position{line: 77, col: 1, offset: 1792}, + expr: &zeroOrMoreExpr{ + pos: position{line: 77, col: 19, offset: 1810}, + expr: &charClassMatcher{ + pos: position{line: 77, col: 19, offset: 1810}, + val: "[ \\n\\t\\r]", + chars: []rune{' ', '\n', '\t', '\r'}, + ignoreCase: false, + inverted: false, + }, + }, + }, + { + name: "EOF", + pos: position{line: 79, col: 1, offset: 1822}, + expr: ¬Expr{ + pos: position{line: 79, col: 8, offset: 1829}, + expr: &anyMatcher{ + line: 79, col: 9, offset: 1830, + }, + }, + }, + }, +} + +func (c *current) onInput1(expr interface{}) (interface{}, error) { + return expr, nil +} + +func (p *parser) callonInput1() (interface{}, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onInput1(stack["expr"]) +} + +func (c *current) onExpr1(l interface{}) (interface{}, error) { + return l, nil +} + +func (p *parser) callonExpr1() (interface{}, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onExpr1(stack["l"]) +} + +func (c *current) onWait1(duration interface{}) (interface{}, error) { + var d time.Duration + switch t := duration.(type) { + case time.Duration: + d = t + case int64: + d = time.Duration(t) * time.Second + default: + d = time.Second + } + return &waitExpression{d}, nil +} + +func (p *parser) callonWait1() (interface{}, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onWait1(stack["duration"]) +} + +func (c *current) onCharToggle1(lit, t interface{}) (interface{}, error) { + return &literal{lit.(*literal).s, t.(KeyAction)}, nil +} + +func (p *parser) callonCharToggle1() (interface{}, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onCharToggle1(stack["lit"], stack["t"]) +} + +func (c *current) onSpecial1(s, t interface{}) (interface{}, error) { + l := strings.ToLower(string(s.([]byte))) + if t == nil { + return &specialExpression{l, KeyPress}, nil + } + return &specialExpression{l, t.(KeyAction)}, nil +} + +func (p *parser) callonSpecial1() (interface{}, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onSpecial1(stack["s"], stack["t"]) +} + +func (c *current) onNumber1() (interface{}, error) { + return string(c.text), nil +} + +func (p *parser) callonNumber1() (interface{}, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onNumber1() +} + +func (c *current) onInteger3() (interface{}, error) { + return strconv.ParseInt(string(c.text), 10, 64) +} + +func (p *parser) callonInteger3() (interface{}, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onInteger3() +} + +func (c *current) onDuration1() (interface{}, error) { + return time.ParseDuration(string(c.text)) +} + +func (p *parser) callonDuration1() (interface{}, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onDuration1() +} + +func (c *current) onOn1() (interface{}, error) { + return KeyOn, nil +} + +func (p *parser) callonOn1() (interface{}, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onOn1() +} + +func (c *current) onOff1() (interface{}, error) { + return KeyOff, nil +} + +func (p *parser) callonOff1() (interface{}, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onOff1() +} + +func (c *current) onLiteral1() (interface{}, error) { + r, _ := utf8.DecodeRune(c.text) + return &literal{r, KeyPress}, nil +} + +func (p *parser) callonLiteral1() (interface{}, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onLiteral1() +} + +var ( + // errNoRule is returned when the grammar to parse has no rule. + errNoRule = errors.New("grammar has no rule") + + // errInvalidEntrypoint is returned when the specified entrypoint rule + // does not exit. + errInvalidEntrypoint = errors.New("invalid entrypoint") + + // errInvalidEncoding is returned when the source is not properly + // utf8-encoded. + errInvalidEncoding = errors.New("invalid encoding") + + // errMaxExprCnt is used to signal that the maximum number of + // expressions have been parsed. + errMaxExprCnt = errors.New("max number of expresssions parsed") +) + +// Option is a function that can set an option on the parser. It returns +// the previous setting as an Option. +type Option func(*parser) Option + +// MaxExpressions creates an Option to stop parsing after the provided +// number of expressions have been parsed, if the value is 0 then the parser will +// parse for as many steps as needed (possibly an infinite number). +// +// The default for maxExprCnt is 0. +func MaxExpressions(maxExprCnt uint64) Option { + return func(p *parser) Option { + oldMaxExprCnt := p.maxExprCnt + p.maxExprCnt = maxExprCnt + return MaxExpressions(oldMaxExprCnt) + } +} + +// Entrypoint creates an Option to set the rule name to use as entrypoint. +// The rule name must have been specified in the -alternate-entrypoints +// if generating the parser with the -optimize-grammar flag, otherwise +// it may have been optimized out. Passing an empty string sets the +// entrypoint to the first rule in the grammar. +// +// The default is to start parsing at the first rule in the grammar. +func Entrypoint(ruleName string) Option { + return func(p *parser) Option { + oldEntrypoint := p.entrypoint + p.entrypoint = ruleName + if ruleName == "" { + p.entrypoint = g.rules[0].name + } + return Entrypoint(oldEntrypoint) + } +} + +// Statistics adds a user provided Stats struct to the parser to allow +// the user to process the results after the parsing has finished. +// Also the key for the "no match" counter is set. +// +// Example usage: +// +// input := "input" +// stats := Stats{} +// _, err := Parse("input-file", []byte(input), Statistics(&stats, "no match")) +// if err != nil { +// log.Panicln(err) +// } +// b, err := json.MarshalIndent(stats.ChoiceAltCnt, "", " ") +// if err != nil { +// log.Panicln(err) +// } +// fmt.Println(string(b)) +// +func Statistics(stats *Stats, choiceNoMatch string) Option { + return func(p *parser) Option { + oldStats := p.Stats + p.Stats = stats + oldChoiceNoMatch := p.choiceNoMatch + p.choiceNoMatch = choiceNoMatch + if p.Stats.ChoiceAltCnt == nil { + p.Stats.ChoiceAltCnt = make(map[string]map[string]int) + } + return Statistics(oldStats, oldChoiceNoMatch) + } +} + +// Debug creates an Option to set the debug flag to b. When set to true, +// debugging information is printed to stdout while parsing. +// +// The default is false. +func Debug(b bool) Option { + return func(p *parser) Option { + old := p.debug + p.debug = b + return Debug(old) + } +} + +// Memoize creates an Option to set the memoize flag to b. When set to true, +// the parser will cache all results so each expression is evaluated only +// once. This guarantees linear parsing time even for pathological cases, +// at the expense of more memory and slower times for typical cases. +// +// The default is false. +func Memoize(b bool) Option { + return func(p *parser) Option { + old := p.memoize + p.memoize = b + return Memoize(old) + } +} + +// AllowInvalidUTF8 creates an Option to allow invalid UTF-8 bytes. +// Every invalid UTF-8 byte is treated as a utf8.RuneError (U+FFFD) +// by character class matchers and is matched by the any matcher. +// The returned matched value, c.text and c.offset are NOT affected. +// +// The default is false. +func AllowInvalidUTF8(b bool) Option { + return func(p *parser) Option { + old := p.allowInvalidUTF8 + p.allowInvalidUTF8 = b + return AllowInvalidUTF8(old) + } +} + +// Recover creates an Option to set the recover flag to b. When set to +// true, this causes the parser to recover from panics and convert it +// to an error. Setting it to false can be useful while debugging to +// access the full stack trace. +// +// The default is true. +func Recover(b bool) Option { + return func(p *parser) Option { + old := p.recover + p.recover = b + return Recover(old) + } +} + +// GlobalStore creates an Option to set a key to a certain value in +// the globalStore. +func GlobalStore(key string, value interface{}) Option { + return func(p *parser) Option { + old := p.cur.globalStore[key] + p.cur.globalStore[key] = value + return GlobalStore(key, old) + } +} + +// InitState creates an Option to set a key to a certain value in +// the global "state" store. +func InitState(key string, value interface{}) Option { + return func(p *parser) Option { + old := p.cur.state[key] + p.cur.state[key] = value + return InitState(key, old) + } +} + +// ParseFile parses the file identified by filename. +func ParseFile(filename string, opts ...Option) (i interface{}, err error) { + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer func() { + if closeErr := f.Close(); closeErr != nil { + err = closeErr + } + }() + return ParseReader(filename, f, opts...) +} + +// ParseReader parses the data from r using filename as information in the +// error messages. +func ParseReader(filename string, r io.Reader, opts ...Option) (interface{}, error) { + b, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + + return Parse(filename, b, opts...) +} + +// Parse parses the data from b using filename as information in the +// error messages. +func Parse(filename string, b []byte, opts ...Option) (interface{}, error) { + return newParser(filename, b, opts...).parse(g) +} + +// position records a position in the text. +type position struct { + line, col, offset int +} + +func (p position) String() string { + return fmt.Sprintf("%d:%d [%d]", p.line, p.col, p.offset) +} + +// savepoint stores all state required to go back to this point in the +// parser. +type savepoint struct { + position + rn rune + w int +} + +type current struct { + pos position // start position of the match + text []byte // raw text of the match + + // state is a store for arbitrary key,value pairs that the user wants to be + // tied to the backtracking of the parser. + // This is always rolled back if a parsing rule fails. + state storeDict + + // globalStore is a general store for the user to store arbitrary key-value + // pairs that they need to manage and that they do not want tied to the + // backtracking of the parser. This is only modified by the user and never + // rolled back by the parser. It is always up to the user to keep this in a + // consistent state. + globalStore storeDict +} + +type storeDict map[string]interface{} + +// the AST types... + +type grammar struct { + pos position + rules []*rule +} + +type rule struct { + pos position + name string + displayName string + expr interface{} +} + +type choiceExpr struct { + pos position + alternatives []interface{} +} + +type actionExpr struct { + pos position + expr interface{} + run func(*parser) (interface{}, error) +} + +type recoveryExpr struct { + pos position + expr interface{} + recoverExpr interface{} + failureLabel []string +} + +type seqExpr struct { + pos position + exprs []interface{} +} + +type throwExpr struct { + pos position + label string +} + +type labeledExpr struct { + pos position + label string + expr interface{} +} + +type expr struct { + pos position + expr interface{} +} + +type andExpr expr +type notExpr expr +type zeroOrOneExpr expr +type zeroOrMoreExpr expr +type oneOrMoreExpr expr + +type ruleRefExpr struct { + pos position + name string +} + +type stateCodeExpr struct { + pos position + run func(*parser) error +} + +type andCodeExpr struct { + pos position + run func(*parser) (bool, error) +} + +type notCodeExpr struct { + pos position + run func(*parser) (bool, error) +} + +type litMatcher struct { + pos position + val string + ignoreCase bool +} + +type charClassMatcher struct { + pos position + val string + basicLatinChars [128]bool + chars []rune + ranges []rune + classes []*unicode.RangeTable + ignoreCase bool + inverted bool +} + +type anyMatcher position + +// errList cumulates the errors found by the parser. +type errList []error + +func (e *errList) add(err error) { + *e = append(*e, err) +} + +func (e errList) err() error { + if len(e) == 0 { + return nil + } + e.dedupe() + return e +} + +func (e *errList) dedupe() { + var cleaned []error + set := make(map[string]bool) + for _, err := range *e { + if msg := err.Error(); !set[msg] { + set[msg] = true + cleaned = append(cleaned, err) + } + } + *e = cleaned +} + +func (e errList) Error() string { + switch len(e) { + case 0: + return "" + case 1: + return e[0].Error() + default: + var buf bytes.Buffer + + for i, err := range e { + if i > 0 { + buf.WriteRune('\n') + } + buf.WriteString(err.Error()) + } + return buf.String() + } +} + +// parserError wraps an error with a prefix indicating the rule in which +// the error occurred. The original error is stored in the Inner field. +type parserError struct { + Inner error + pos position + prefix string + expected []string +} + +// Error returns the error message. +func (p *parserError) Error() string { + return p.prefix + ": " + p.Inner.Error() +} + +// newParser creates a parser with the specified input source and options. +func newParser(filename string, b []byte, opts ...Option) *parser { + stats := Stats{ + ChoiceAltCnt: make(map[string]map[string]int), + } + + p := &parser{ + filename: filename, + errs: new(errList), + data: b, + pt: savepoint{position: position{line: 1}}, + recover: true, + cur: current{ + state: make(storeDict), + globalStore: make(storeDict), + }, + maxFailPos: position{col: 1, line: 1}, + maxFailExpected: make([]string, 0, 20), + Stats: &stats, + // start rule is rule [0] unless an alternate entrypoint is specified + entrypoint: g.rules[0].name, + emptyState: make(storeDict), + } + p.setOptions(opts) + + if p.maxExprCnt == 0 { + p.maxExprCnt = math.MaxUint64 + } + + return p +} + +// setOptions applies the options to the parser. +func (p *parser) setOptions(opts []Option) { + for _, opt := range opts { + opt(p) + } +} + +type resultTuple struct { + v interface{} + b bool + end savepoint +} + +const choiceNoMatch = -1 + +// Stats stores some statistics, gathered during parsing +type Stats struct { + // ExprCnt counts the number of expressions processed during parsing + // This value is compared to the maximum number of expressions allowed + // (set by the MaxExpressions option). + ExprCnt uint64 + + // ChoiceAltCnt is used to count for each ordered choice expression, + // which alternative is used how may times. + // These numbers allow to optimize the order of the ordered choice expression + // to increase the performance of the parser + // + // The outer key of ChoiceAltCnt is composed of the name of the rule as well + // as the line and the column of the ordered choice. + // The inner key of ChoiceAltCnt is the number (one-based) of the matching alternative. + // For each alternative the number of matches are counted. If an ordered choice does not + // match, a special counter is incremented. The name of this counter is set with + // the parser option Statistics. + // For an alternative to be included in ChoiceAltCnt, it has to match at least once. + ChoiceAltCnt map[string]map[string]int +} + +type parser struct { + filename string + pt savepoint + cur current + + data []byte + errs *errList + + depth int + recover bool + debug bool + + memoize bool + // memoization table for the packrat algorithm: + // map[offset in source] map[expression or rule] {value, match} + memo map[int]map[interface{}]resultTuple + + // rules table, maps the rule identifier to the rule node + rules map[string]*rule + // variables stack, map of label to value + vstack []map[string]interface{} + // rule stack, allows identification of the current rule in errors + rstack []*rule + + // parse fail + maxFailPos position + maxFailExpected []string + maxFailInvertExpected bool + + // max number of expressions to be parsed + maxExprCnt uint64 + // entrypoint for the parser + entrypoint string + + allowInvalidUTF8 bool + + *Stats + + choiceNoMatch string + // recovery expression stack, keeps track of the currently available recovery expression, these are traversed in reverse + recoveryStack []map[string]interface{} + + // emptyState contains an empty storeDict, which is used to optimize cloneState if global "state" store is not used. + emptyState storeDict +} + +// push a variable set on the vstack. +func (p *parser) pushV() { + if cap(p.vstack) == len(p.vstack) { + // create new empty slot in the stack + p.vstack = append(p.vstack, nil) + } else { + // slice to 1 more + p.vstack = p.vstack[:len(p.vstack)+1] + } + + // get the last args set + m := p.vstack[len(p.vstack)-1] + if m != nil && len(m) == 0 { + // empty map, all good + return + } + + m = make(map[string]interface{}) + p.vstack[len(p.vstack)-1] = m +} + +// pop a variable set from the vstack. +func (p *parser) popV() { + // if the map is not empty, clear it + m := p.vstack[len(p.vstack)-1] + if len(m) > 0 { + // GC that map + p.vstack[len(p.vstack)-1] = nil + } + p.vstack = p.vstack[:len(p.vstack)-1] +} + +// push a recovery expression with its labels to the recoveryStack +func (p *parser) pushRecovery(labels []string, expr interface{}) { + if cap(p.recoveryStack) == len(p.recoveryStack) { + // create new empty slot in the stack + p.recoveryStack = append(p.recoveryStack, nil) + } else { + // slice to 1 more + p.recoveryStack = p.recoveryStack[:len(p.recoveryStack)+1] + } + + m := make(map[string]interface{}, len(labels)) + for _, fl := range labels { + m[fl] = expr + } + p.recoveryStack[len(p.recoveryStack)-1] = m +} + +// pop a recovery expression from the recoveryStack +func (p *parser) popRecovery() { + // GC that map + p.recoveryStack[len(p.recoveryStack)-1] = nil + + p.recoveryStack = p.recoveryStack[:len(p.recoveryStack)-1] +} + +func (p *parser) print(prefix, s string) string { + if !p.debug { + return s + } + + fmt.Printf("%s %d:%d:%d: %s [%#U]\n", + prefix, p.pt.line, p.pt.col, p.pt.offset, s, p.pt.rn) + return s +} + +func (p *parser) in(s string) string { + p.depth++ + return p.print(strings.Repeat(" ", p.depth)+">", s) +} + +func (p *parser) out(s string) string { + p.depth-- + return p.print(strings.Repeat(" ", p.depth)+"<", s) +} + +func (p *parser) addErr(err error) { + p.addErrAt(err, p.pt.position, []string{}) +} + +func (p *parser) addErrAt(err error, pos position, expected []string) { + var buf bytes.Buffer + if p.filename != "" { + buf.WriteString(p.filename) + } + if buf.Len() > 0 { + buf.WriteString(":") + } + buf.WriteString(fmt.Sprintf("%d:%d (%d)", pos.line, pos.col, pos.offset)) + if len(p.rstack) > 0 { + if buf.Len() > 0 { + buf.WriteString(": ") + } + rule := p.rstack[len(p.rstack)-1] + if rule.displayName != "" { + buf.WriteString("rule " + rule.displayName) + } else { + buf.WriteString("rule " + rule.name) + } + } + pe := &parserError{Inner: err, pos: pos, prefix: buf.String(), expected: expected} + p.errs.add(pe) +} + +func (p *parser) failAt(fail bool, pos position, want string) { + // process fail if parsing fails and not inverted or parsing succeeds and invert is set + if fail == p.maxFailInvertExpected { + if pos.offset < p.maxFailPos.offset { + return + } + + if pos.offset > p.maxFailPos.offset { + p.maxFailPos = pos + p.maxFailExpected = p.maxFailExpected[:0] + } + + if p.maxFailInvertExpected { + want = "!" + want + } + p.maxFailExpected = append(p.maxFailExpected, want) + } +} + +// read advances the parser to the next rune. +func (p *parser) read() { + p.pt.offset += p.pt.w + rn, n := utf8.DecodeRune(p.data[p.pt.offset:]) + p.pt.rn = rn + p.pt.w = n + p.pt.col++ + if rn == '\n' { + p.pt.line++ + p.pt.col = 0 + } + + if rn == utf8.RuneError && n == 1 { // see utf8.DecodeRune + if !p.allowInvalidUTF8 { + p.addErr(errInvalidEncoding) + } + } +} + +// restore parser position to the savepoint pt. +func (p *parser) restore(pt savepoint) { + if p.debug { + defer p.out(p.in("restore")) + } + if pt.offset == p.pt.offset { + return + } + p.pt = pt +} + +// Cloner is implemented by any value that has a Clone method, which returns a +// copy of the value. This is mainly used for types which are not passed by +// value (e.g map, slice, chan) or structs that contain such types. +// +// This is used in conjunction with the global state feature to create proper +// copies of the state to allow the parser to properly restore the state in +// the case of backtracking. +type Cloner interface { + Clone() interface{} +} + +// clone and return parser current state. +func (p *parser) cloneState() storeDict { + if p.debug { + defer p.out(p.in("cloneState")) + } + + if len(p.cur.state) == 0 { + if len(p.emptyState) > 0 { + p.emptyState = make(storeDict) + } + return p.emptyState + } + + state := make(storeDict, len(p.cur.state)) + for k, v := range p.cur.state { + if c, ok := v.(Cloner); ok { + state[k] = c.Clone() + } else { + state[k] = v + } + } + return state +} + +// restore parser current state to the state storeDict. +// every restoreState should applied only one time for every cloned state +func (p *parser) restoreState(state storeDict) { + if p.debug { + defer p.out(p.in("restoreState")) + } + p.cur.state = state +} + +// get the slice of bytes from the savepoint start to the current position. +func (p *parser) sliceFrom(start savepoint) []byte { + return p.data[start.position.offset:p.pt.position.offset] +} + +func (p *parser) getMemoized(node interface{}) (resultTuple, bool) { + if len(p.memo) == 0 { + return resultTuple{}, false + } + m := p.memo[p.pt.offset] + if len(m) == 0 { + return resultTuple{}, false + } + res, ok := m[node] + return res, ok +} + +func (p *parser) setMemoized(pt savepoint, node interface{}, tuple resultTuple) { + if p.memo == nil { + p.memo = make(map[int]map[interface{}]resultTuple) + } + m := p.memo[pt.offset] + if m == nil { + m = make(map[interface{}]resultTuple) + p.memo[pt.offset] = m + } + m[node] = tuple +} + +func (p *parser) buildRulesTable(g *grammar) { + p.rules = make(map[string]*rule, len(g.rules)) + for _, r := range g.rules { + p.rules[r.name] = r + } +} + +func (p *parser) parse(g *grammar) (val interface{}, err error) { + if len(g.rules) == 0 { + p.addErr(errNoRule) + return nil, p.errs.err() + } + + // TODO : not super critical but this could be generated + p.buildRulesTable(g) + + if p.recover { + // panic can be used in action code to stop parsing immediately + // and return the panic as an error. + defer func() { + if e := recover(); e != nil { + if p.debug { + defer p.out(p.in("panic handler")) + } + val = nil + switch e := e.(type) { + case error: + p.addErr(e) + default: + p.addErr(fmt.Errorf("%v", e)) + } + err = p.errs.err() + } + }() + } + + startRule, ok := p.rules[p.entrypoint] + if !ok { + p.addErr(errInvalidEntrypoint) + return nil, p.errs.err() + } + + p.read() // advance to first rune + val, ok = p.parseRule(startRule) + if !ok { + if len(*p.errs) == 0 { + // If parsing fails, but no errors have been recorded, the expected values + // for the farthest parser position are returned as error. + maxFailExpectedMap := make(map[string]struct{}, len(p.maxFailExpected)) + for _, v := range p.maxFailExpected { + maxFailExpectedMap[v] = struct{}{} + } + expected := make([]string, 0, len(maxFailExpectedMap)) + eof := false + if _, ok := maxFailExpectedMap["!."]; ok { + delete(maxFailExpectedMap, "!.") + eof = true + } + for k := range maxFailExpectedMap { + expected = append(expected, k) + } + sort.Strings(expected) + if eof { + expected = append(expected, "EOF") + } + p.addErrAt(errors.New("no match found, expected: "+listJoin(expected, ", ", "or")), p.maxFailPos, expected) + } + + return nil, p.errs.err() + } + return val, p.errs.err() +} + +func listJoin(list []string, sep string, lastSep string) string { + switch len(list) { + case 0: + return "" + case 1: + return list[0] + default: + return fmt.Sprintf("%s %s %s", strings.Join(list[:len(list)-1], sep), lastSep, list[len(list)-1]) + } +} + +func (p *parser) parseRule(rule *rule) (interface{}, bool) { + if p.debug { + defer p.out(p.in("parseRule " + rule.name)) + } + + if p.memoize { + res, ok := p.getMemoized(rule) + if ok { + p.restore(res.end) + return res.v, res.b + } + } + + start := p.pt + p.rstack = append(p.rstack, rule) + p.pushV() + val, ok := p.parseExpr(rule.expr) + p.popV() + p.rstack = p.rstack[:len(p.rstack)-1] + if ok && p.debug { + p.print(strings.Repeat(" ", p.depth)+"MATCH", string(p.sliceFrom(start))) + } + + if p.memoize { + p.setMemoized(start, rule, resultTuple{val, ok, p.pt}) + } + return val, ok +} + +func (p *parser) parseExpr(expr interface{}) (interface{}, bool) { + var pt savepoint + + if p.memoize { + res, ok := p.getMemoized(expr) + if ok { + p.restore(res.end) + return res.v, res.b + } + pt = p.pt + } + + p.ExprCnt++ + if p.ExprCnt > p.maxExprCnt { + panic(errMaxExprCnt) + } + + var val interface{} + var ok bool + switch expr := expr.(type) { + case *actionExpr: + val, ok = p.parseActionExpr(expr) + case *andCodeExpr: + val, ok = p.parseAndCodeExpr(expr) + case *andExpr: + val, ok = p.parseAndExpr(expr) + case *anyMatcher: + val, ok = p.parseAnyMatcher(expr) + case *charClassMatcher: + val, ok = p.parseCharClassMatcher(expr) + case *choiceExpr: + val, ok = p.parseChoiceExpr(expr) + case *labeledExpr: + val, ok = p.parseLabeledExpr(expr) + case *litMatcher: + val, ok = p.parseLitMatcher(expr) + case *notCodeExpr: + val, ok = p.parseNotCodeExpr(expr) + case *notExpr: + val, ok = p.parseNotExpr(expr) + case *oneOrMoreExpr: + val, ok = p.parseOneOrMoreExpr(expr) + case *recoveryExpr: + val, ok = p.parseRecoveryExpr(expr) + case *ruleRefExpr: + val, ok = p.parseRuleRefExpr(expr) + case *seqExpr: + val, ok = p.parseSeqExpr(expr) + case *stateCodeExpr: + val, ok = p.parseStateCodeExpr(expr) + case *throwExpr: + val, ok = p.parseThrowExpr(expr) + case *zeroOrMoreExpr: + val, ok = p.parseZeroOrMoreExpr(expr) + case *zeroOrOneExpr: + val, ok = p.parseZeroOrOneExpr(expr) + default: + panic(fmt.Sprintf("unknown expression type %T", expr)) + } + if p.memoize { + p.setMemoized(pt, expr, resultTuple{val, ok, p.pt}) + } + return val, ok +} + +func (p *parser) parseActionExpr(act *actionExpr) (interface{}, bool) { + if p.debug { + defer p.out(p.in("parseActionExpr")) + } + + start := p.pt + val, ok := p.parseExpr(act.expr) + if ok { + p.cur.pos = start.position + p.cur.text = p.sliceFrom(start) + state := p.cloneState() + actVal, err := act.run(p) + if err != nil { + p.addErrAt(err, start.position, []string{}) + } + p.restoreState(state) + + val = actVal + } + if ok && p.debug { + p.print(strings.Repeat(" ", p.depth)+"MATCH", string(p.sliceFrom(start))) + } + return val, ok +} + +func (p *parser) parseAndCodeExpr(and *andCodeExpr) (interface{}, bool) { + if p.debug { + defer p.out(p.in("parseAndCodeExpr")) + } + + state := p.cloneState() + + ok, err := and.run(p) + if err != nil { + p.addErr(err) + } + p.restoreState(state) + + return nil, ok +} + +func (p *parser) parseAndExpr(and *andExpr) (interface{}, bool) { + if p.debug { + defer p.out(p.in("parseAndExpr")) + } + + pt := p.pt + state := p.cloneState() + p.pushV() + _, ok := p.parseExpr(and.expr) + p.popV() + p.restoreState(state) + p.restore(pt) + + return nil, ok +} + +func (p *parser) parseAnyMatcher(any *anyMatcher) (interface{}, bool) { + if p.debug { + defer p.out(p.in("parseAnyMatcher")) + } + + if p.pt.rn == utf8.RuneError && p.pt.w == 0 { + // EOF - see utf8.DecodeRune + p.failAt(false, p.pt.position, ".") + return nil, false + } + start := p.pt + p.read() + p.failAt(true, start.position, ".") + return p.sliceFrom(start), true +} + +func (p *parser) parseCharClassMatcher(chr *charClassMatcher) (interface{}, bool) { + if p.debug { + defer p.out(p.in("parseCharClassMatcher")) + } + + cur := p.pt.rn + start := p.pt + + // can't match EOF + if cur == utf8.RuneError && p.pt.w == 0 { // see utf8.DecodeRune + p.failAt(false, start.position, chr.val) + return nil, false + } + + if chr.ignoreCase { + cur = unicode.ToLower(cur) + } + + // try to match in the list of available chars + for _, rn := range chr.chars { + if rn == cur { + if chr.inverted { + p.failAt(false, start.position, chr.val) + return nil, false + } + p.read() + p.failAt(true, start.position, chr.val) + return p.sliceFrom(start), true + } + } + + // try to match in the list of ranges + for i := 0; i < len(chr.ranges); i += 2 { + if cur >= chr.ranges[i] && cur <= chr.ranges[i+1] { + if chr.inverted { + p.failAt(false, start.position, chr.val) + return nil, false + } + p.read() + p.failAt(true, start.position, chr.val) + return p.sliceFrom(start), true + } + } + + // try to match in the list of Unicode classes + for _, cl := range chr.classes { + if unicode.Is(cl, cur) { + if chr.inverted { + p.failAt(false, start.position, chr.val) + return nil, false + } + p.read() + p.failAt(true, start.position, chr.val) + return p.sliceFrom(start), true + } + } + + if chr.inverted { + p.read() + p.failAt(true, start.position, chr.val) + return p.sliceFrom(start), true + } + p.failAt(false, start.position, chr.val) + return nil, false +} + +func (p *parser) incChoiceAltCnt(ch *choiceExpr, altI int) { + choiceIdent := fmt.Sprintf("%s %d:%d", p.rstack[len(p.rstack)-1].name, ch.pos.line, ch.pos.col) + m := p.ChoiceAltCnt[choiceIdent] + if m == nil { + m = make(map[string]int) + p.ChoiceAltCnt[choiceIdent] = m + } + // We increment altI by 1, so the keys do not start at 0 + alt := strconv.Itoa(altI + 1) + if altI == choiceNoMatch { + alt = p.choiceNoMatch + } + m[alt]++ +} + +func (p *parser) parseChoiceExpr(ch *choiceExpr) (interface{}, bool) { + if p.debug { + defer p.out(p.in("parseChoiceExpr")) + } + + for altI, alt := range ch.alternatives { + // dummy assignment to prevent compile error if optimized + _ = altI + + state := p.cloneState() + + p.pushV() + val, ok := p.parseExpr(alt) + p.popV() + if ok { + p.incChoiceAltCnt(ch, altI) + return val, ok + } + p.restoreState(state) + } + p.incChoiceAltCnt(ch, choiceNoMatch) + return nil, false +} + +func (p *parser) parseLabeledExpr(lab *labeledExpr) (interface{}, bool) { + if p.debug { + defer p.out(p.in("parseLabeledExpr")) + } + + p.pushV() + val, ok := p.parseExpr(lab.expr) + p.popV() + if ok && lab.label != "" { + m := p.vstack[len(p.vstack)-1] + m[lab.label] = val + } + return val, ok +} + +func (p *parser) parseLitMatcher(lit *litMatcher) (interface{}, bool) { + if p.debug { + defer p.out(p.in("parseLitMatcher")) + } + + ignoreCase := "" + if lit.ignoreCase { + ignoreCase = "i" + } + val := fmt.Sprintf("%q%s", lit.val, ignoreCase) + start := p.pt + for _, want := range lit.val { + cur := p.pt.rn + if lit.ignoreCase { + cur = unicode.ToLower(cur) + } + if cur != want { + p.failAt(false, start.position, val) + p.restore(start) + return nil, false + } + p.read() + } + p.failAt(true, start.position, val) + return p.sliceFrom(start), true +} + +func (p *parser) parseNotCodeExpr(not *notCodeExpr) (interface{}, bool) { + if p.debug { + defer p.out(p.in("parseNotCodeExpr")) + } + + state := p.cloneState() + + ok, err := not.run(p) + if err != nil { + p.addErr(err) + } + p.restoreState(state) + + return nil, !ok +} + +func (p *parser) parseNotExpr(not *notExpr) (interface{}, bool) { + if p.debug { + defer p.out(p.in("parseNotExpr")) + } + + pt := p.pt + state := p.cloneState() + p.pushV() + p.maxFailInvertExpected = !p.maxFailInvertExpected + _, ok := p.parseExpr(not.expr) + p.maxFailInvertExpected = !p.maxFailInvertExpected + p.popV() + p.restoreState(state) + p.restore(pt) + + return nil, !ok +} + +func (p *parser) parseOneOrMoreExpr(expr *oneOrMoreExpr) (interface{}, bool) { + if p.debug { + defer p.out(p.in("parseOneOrMoreExpr")) + } + + var vals []interface{} + + for { + p.pushV() + val, ok := p.parseExpr(expr.expr) + p.popV() + if !ok { + if len(vals) == 0 { + // did not match once, no match + return nil, false + } + return vals, true + } + vals = append(vals, val) + } +} + +func (p *parser) parseRecoveryExpr(recover *recoveryExpr) (interface{}, bool) { + if p.debug { + defer p.out(p.in("parseRecoveryExpr (" + strings.Join(recover.failureLabel, ",") + ")")) + } + + p.pushRecovery(recover.failureLabel, recover.recoverExpr) + val, ok := p.parseExpr(recover.expr) + p.popRecovery() + + return val, ok +} + +func (p *parser) parseRuleRefExpr(ref *ruleRefExpr) (interface{}, bool) { + if p.debug { + defer p.out(p.in("parseRuleRefExpr " + ref.name)) + } + + if ref.name == "" { + panic(fmt.Sprintf("%s: invalid rule: missing name", ref.pos)) + } + + rule := p.rules[ref.name] + if rule == nil { + p.addErr(fmt.Errorf("undefined rule: %s", ref.name)) + return nil, false + } + return p.parseRule(rule) +} + +func (p *parser) parseSeqExpr(seq *seqExpr) (interface{}, bool) { + if p.debug { + defer p.out(p.in("parseSeqExpr")) + } + + vals := make([]interface{}, 0, len(seq.exprs)) + + pt := p.pt + state := p.cloneState() + for _, expr := range seq.exprs { + val, ok := p.parseExpr(expr) + if !ok { + p.restoreState(state) + p.restore(pt) + return nil, false + } + vals = append(vals, val) + } + return vals, true +} + +func (p *parser) parseStateCodeExpr(state *stateCodeExpr) (interface{}, bool) { + if p.debug { + defer p.out(p.in("parseStateCodeExpr")) + } + + err := state.run(p) + if err != nil { + p.addErr(err) + } + return nil, true +} + +func (p *parser) parseThrowExpr(expr *throwExpr) (interface{}, bool) { + if p.debug { + defer p.out(p.in("parseThrowExpr")) + } + + for i := len(p.recoveryStack) - 1; i >= 0; i-- { + if recoverExpr, ok := p.recoveryStack[i][expr.label]; ok { + if val, ok := p.parseExpr(recoverExpr); ok { + return val, ok + } + } + } + + return nil, false +} + +func (p *parser) parseZeroOrMoreExpr(expr *zeroOrMoreExpr) (interface{}, bool) { + if p.debug { + defer p.out(p.in("parseZeroOrMoreExpr")) + } + + var vals []interface{} + + for { + p.pushV() + val, ok := p.parseExpr(expr.expr) + p.popV() + if !ok { + return vals, true + } + vals = append(vals, val) + } +} + +func (p *parser) parseZeroOrOneExpr(expr *zeroOrOneExpr) (interface{}, bool) { + if p.debug { + defer p.out(p.in("parseZeroOrOneExpr")) + } + + p.pushV() + val, _ := p.parseExpr(expr.expr) + p.popV() + // whether it matched or not, consider it a match + return val, true +} diff --git a/common/bootcommand/boot_command.pigeon b/common/bootcommand/boot_command.pigeon new file mode 100644 index 000000000..fdfbba0cf --- /dev/null +++ b/common/bootcommand/boot_command.pigeon @@ -0,0 +1,79 @@ +{ +package bootcommand + +} + +Input <- expr:Expr EOF { + return expr, nil +} + +Expr <- l:( Wait / CharToggle / Special / Literal)+ { + return l, nil +} + +Wait = ExprStart "wait" duration:( Duration / Integer )? ExprEnd { + var d time.Duration + switch t := duration.(type) { + case time.Duration: + d = t + case int64: + d = time.Duration(t) * time.Second + default: + d = time.Second + } + return &waitExpression{d}, nil +} + +CharToggle = ExprStart lit:(Literal) t:(On / Off) ExprEnd { + return &literal{lit.(*literal).s, t.(KeyAction)}, nil +} + +Special = ExprStart s:(SpecialKey) t:(On / Off)? ExprEnd { + l := strings.ToLower(string(s.([]byte))) + if t == nil { + return &specialExpression{l, KeyPress}, nil + } + return &specialExpression{l, t.(KeyAction)}, nil +} + +Number = '-'? Integer ( '.' Digit+ )? { + return string(c.text), nil +} + +Integer = '0' / NonZeroDigit Digit* { + return strconv.ParseInt(string(c.text), 10, 64) +} + +Duration = ( Number TimeUnit )+ { + return time.ParseDuration(string(c.text)) +} + +On = "on"i { + return KeyOn, nil +} + +Off = "off"i { + return KeyOff, nil +} + +Literal = . { + r, _ := utf8.DecodeRune(c.text) + return &literal{r, KeyPress}, nil +} + +ExprEnd = ">" +ExprStart = "<" +SpecialKey = "bs"i / "del"i / "enter"i / "esc"i / "f10"i / "f11"i / "f12"i + / "f1"i / "f2"i / "f3"i / "f4"i / "f5"i / "f6"i / "f7"i / "f8"i / "f9"i + / "return"i / "tab"i / "up"i / "down"i / "spacebar"i / "insert"i / "home"i + / "end"i / "pageUp"i / "pageDown"i / "leftAlt"i / "leftCtrl"i / "leftShift"i + / "rightAlt"i / "rightCtrl"i / "rightShift"i / "leftSuper"i / "rightSuper"i + / "left"i / "right"i + +NonZeroDigit = [1-9] +Digit = [0-9] +TimeUnit = ("ns" / "us" / "µs" / "ms" / "s" / "m" / "h") + +_ "whitespace" <- [ \n\t\r]* + +EOF <- !. diff --git a/common/bootcommand/boot_command_ast.go b/common/bootcommand/boot_command_ast.go new file mode 100644 index 000000000..f680345c4 --- /dev/null +++ b/common/bootcommand/boot_command_ast.go @@ -0,0 +1,157 @@ +package bootcommand + +import ( + "context" + "fmt" + "log" + "strings" + "time" +) + +// KeysAction represents what we want to do with a key press. +// It can take 3 states. We either want to: +// * press the key once +// * press and hold +// * press and release +type KeyAction int + +const ( + KeyOn KeyAction = 1 << iota + KeyOff + KeyPress +) + +func (k KeyAction) String() string { + switch k { + case KeyOn: + return "On" + case KeyOff: + return "Off" + case KeyPress: + return "Press" + } + panic(fmt.Sprintf("Unknwon KeyAction %d", k)) +} + +type expression interface { + // Do executes the expression + Do(context.Context, BCDriver) error + // Validate validates the expression without executing it + Validate() error +} + +type expressionSequence []expression + +// Do executes every expression in the sequence and then flushes remaining +// scancodes. +func (s expressionSequence) Do(ctx context.Context, b BCDriver) error { + // validate should never fail here, since it should be called before + // expressionSequence.Do. Only reason we don't panic is so we can clean up. + if errs := s.Validate(); errs != nil { + return fmt.Errorf("Found an invalid boot command. This is likely an error in Packer, so please open a ticket.") + } + + for _, exp := range s { + if err := ctx.Err(); err != nil { + return err + } + if err := exp.Do(ctx, b); err != nil { + return err + } + } + return b.Flush() +} + +// Validate tells us if every expression in the sequence is valid. +func (s expressionSequence) Validate() (errs []error) { + for _, exp := range s { + if err := exp.Validate(); err != nil { + errs = append(errs, err) + } + } + return +} + +// GenerateExpressionSequence generates a sequence of expressions from the +// given command. This is the primary entry point to the boot command parser. +func GenerateExpressionSequence(command string) (expressionSequence, error) { + seq := expressionSequence{} + if command == "" { + return seq, nil + } + got, err := ParseReader("", strings.NewReader(command)) + if err != nil { + return nil, err + } + for _, exp := range got.([]interface{}) { + seq = append(seq, exp.(expression)) + } + return seq, nil +} + +type waitExpression struct { + d time.Duration +} + +// Do waits the amount of time described by the expression. It is cancellable +// through the context. +func (w *waitExpression) Do(ctx context.Context, driver BCDriver) error { + driver.Flush() + log.Printf("[INFO] Waiting %s", w.d) + select { + case <-time.After(w.d): + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +// Validate returns an error if the time is <= 0 +func (w *waitExpression) Validate() error { + if w.d <= 0 { + return fmt.Errorf("Expecting a positive wait value. Got %s", w.d) + } + return nil +} + +func (w *waitExpression) String() string { + return fmt.Sprintf("Wait<%s>", w.d) +} + +type specialExpression struct { + s string + action KeyAction +} + +// Do sends the special command to the driver, along with the key action. +func (s *specialExpression) Do(ctx context.Context, driver BCDriver) error { + return driver.SendSpecial(s.s, s.action) +} + +// Validate always passes +func (s *specialExpression) Validate() error { + return nil +} + +func (s *specialExpression) String() string { + return fmt.Sprintf("Spec-%s(%s)", s.action, s.s) +} + +type literal struct { + s rune + action KeyAction +} + +// Do sends the key to the driver, along with the key action. +func (l *literal) Do(ctx context.Context, driver BCDriver) error { + return driver.SendKey(l.s, l.action) +} + +// Validate always passes +func (l *literal) Validate() error { + return nil +} + +func (l *literal) String() string { + return fmt.Sprintf("LIT-%s(%s)", l.action, string(l.s)) +} diff --git a/common/bootcommand/boot_command_ast_test.go b/common/bootcommand/boot_command_ast_test.go new file mode 100644 index 000000000..49bbb5cd4 --- /dev/null +++ b/common/bootcommand/boot_command_ast_test.go @@ -0,0 +1,134 @@ +package bootcommand + +import ( + "fmt" + "log" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_parse(t *testing.T) { + in := "" + in += "foo/bar > one 界" + in += " b" + in += "" + expected := []string{ + "Wait<1s>", + "Wait<20s>", + "Wait<3s>", + "Wait<4m0.000000002s>", + "LIT-Press(f)", + "LIT-Press(o)", + "LIT-Press(o)", + "LIT-Press(/)", + "LIT-Press(b)", + "LIT-Press(a)", + "LIT-Press(r)", + "LIT-Press( )", + "LIT-Press(>)", + "LIT-Press( )", + "LIT-Press(o)", + "LIT-Press(n)", + "LIT-Press(e)", + "LIT-Press( )", + "LIT-Press(界)", + "LIT-On(f)", + "LIT-Press( )", + "LIT-Press(b)", + "LIT-Off(f)", + "LIT-Press(<)", + "LIT-Press(f)", + "LIT-Press(o)", + "LIT-Press(o)", + "LIT-Press(>)", + "Spec-Press(f3)", + "Spec-Press(f12)", + "Spec-Press(spacebar)", + "Spec-Press(leftalt)", + "Spec-Press(rightshift)", + "Spec-Press(rightsuper)", + } + + seq, err := GenerateExpressionSequence(in) + if err != nil { + log.Fatal(err) + } + for i, exp := range seq { + assert.Equal(t, expected[i], fmt.Sprintf("%s", exp)) + log.Printf("%s\n", exp) + } +} + +func Test_special(t *testing.T) { + var specials = []struct { + in string + out string + }{ + { + "", + "Spec-Press(rightshift)", + }, + { + "", + "Spec-On(del)", + }, + { + "", + "Spec-Off(enter)", + }, + } + for _, tt := range specials { + seq, err := GenerateExpressionSequence(tt.in) + if err != nil { + log.Fatal(err) + } + for _, exp := range seq { + assert.Equal(t, tt.out, exp.(*specialExpression).String()) + } + } +} + +func Test_validation(t *testing.T) { + var expressions = []struct { + in string + valid bool + }{ + { + "", + true, + }, + { + "", + false, + }, + { + "", + true, + }, + { + "<", + true, + }, + } + for _, tt := range expressions { + exp, err := GenerateExpressionSequence(tt.in) + if err != nil { + log.Fatal(err) + } + + assert.Len(t, exp, 1) + err = exp[0].Validate() + if tt.valid { + assert.NoError(t, err) + } else { + assert.Error(t, err) + } + } +} + +func Test_empty(t *testing.T) { + exp, err := GenerateExpressionSequence("") + assert.NoError(t, err, "should have parsed an empty input okay.") + assert.Len(t, exp, 0) +} diff --git a/common/bootcommand/config.go b/common/bootcommand/config.go new file mode 100644 index 000000000..532c7c82c --- /dev/null +++ b/common/bootcommand/config.go @@ -0,0 +1,60 @@ +package bootcommand + +import ( + "fmt" + "strings" + "time" + + "github.com/hashicorp/packer/template/interpolate" +) + +type BootConfig struct { + RawBootWait string `mapstructure:"boot_wait"` + BootCommand []string `mapstructure:"boot_command"` + + BootWait time.Duration `` +} + +type VNCConfig struct { + BootConfig `mapstructure:",squash"` + DisableVNC bool `mapstructure:"disable_vnc"` +} + +func (c *BootConfig) Prepare(ctx *interpolate.Context) (errs []error) { + if c.RawBootWait == "" { + c.RawBootWait = "10s" + } + if c.RawBootWait != "" { + bw, err := time.ParseDuration(c.RawBootWait) + if err != nil { + errs = append( + errs, fmt.Errorf("Failed parsing boot_wait: %s", err)) + } else { + c.BootWait = bw + } + } + + if c.BootCommand != nil { + expSeq, err := GenerateExpressionSequence(c.FlatBootCommand()) + if err != nil { + errs = append(errs, err) + } else if vErrs := expSeq.Validate(); vErrs != nil { + errs = append(errs, vErrs...) + } + } + + return +} + +func (c *BootConfig) FlatBootCommand() string { + return strings.Join(c.BootCommand, "") +} + +func (c *VNCConfig) Prepare(ctx *interpolate.Context) (errs []error) { + if len(c.BootCommand) > 0 && c.DisableVNC { + errs = append(errs, + fmt.Errorf("A boot command cannot be used when vnc is disabled.")) + } + errs = append(errs, c.BootConfig.Prepare(ctx)...) + return +} diff --git a/common/bootcommand/config_test.go b/common/bootcommand/config_test.go new file mode 100644 index 000000000..551f2c785 --- /dev/null +++ b/common/bootcommand/config_test.go @@ -0,0 +1,65 @@ +package bootcommand + +import ( + "testing" + + "github.com/hashicorp/packer/template/interpolate" +) + +func TestConfigPrepare(t *testing.T) { + var c *BootConfig + + // Test a default boot_wait + c = new(BootConfig) + c.RawBootWait = "" + errs := c.Prepare(&interpolate.Context{}) + if len(errs) > 0 { + t.Fatalf("bad: %#v", errs) + } + if c.RawBootWait != "10s" { + t.Fatalf("bad value: %s", c.RawBootWait) + } + + // Test with a bad boot_wait + c = new(BootConfig) + c.RawBootWait = "this is not good" + errs = c.Prepare(&interpolate.Context{}) + if len(errs) == 0 { + t.Fatal("should error") + } + + // Test with a good one + c = new(BootConfig) + c.RawBootWait = "5s" + errs = c.Prepare(&interpolate.Context{}) + if len(errs) > 0 { + t.Fatalf("bad: %#v", errs) + } +} + +func TestVNCConfigPrepare(t *testing.T) { + var c *VNCConfig + + // Test with a boot command + c = new(VNCConfig) + c.BootCommand = []string{"a", "b"} + errs := c.Prepare(&interpolate.Context{}) + if len(errs) > 0 { + t.Fatalf("bad: %#v", errs) + } + + // Test with disabled vnc + c.DisableVNC = true + errs = c.Prepare(&interpolate.Context{}) + if len(errs) == 0 { + t.Fatal("should error") + } + + // Test no boot command with no vnc + c = new(VNCConfig) + c.DisableVNC = true + errs = c.Prepare(&interpolate.Context{}) + if len(errs) > 0 { + t.Fatalf("bad: %#v", errs) + } +} diff --git a/common/bootcommand/driver.go b/common/bootcommand/driver.go new file mode 100644 index 000000000..04b0eecd6 --- /dev/null +++ b/common/bootcommand/driver.go @@ -0,0 +1,11 @@ +package bootcommand + +const shiftedChars = "~!@#$%^&*()_+{}|:\"<>?" + +// BCDriver is our access to the VM we want to type boot commands to +type BCDriver interface { + SendKey(key rune, action KeyAction) error + SendSpecial(special string, action KeyAction) error + // Flush will be called when we want to send scancodes to the VM. + Flush() error +} diff --git a/common/bootcommand/gen.go b/common/bootcommand/gen.go new file mode 100644 index 000000000..c5796e65e --- /dev/null +++ b/common/bootcommand/gen.go @@ -0,0 +1,3 @@ +package bootcommand + +//go:generate pigeon -o boot_command.go boot_command.pigeon diff --git a/common/bootcommand/pc_xt_driver.go b/common/bootcommand/pc_xt_driver.go new file mode 100644 index 000000000..7aca80852 --- /dev/null +++ b/common/bootcommand/pc_xt_driver.go @@ -0,0 +1,211 @@ +package bootcommand + +import ( + "fmt" + "log" + "os" + "strings" + "time" + "unicode" + "unicode/utf8" + + "github.com/hashicorp/packer/common" +) + +// SendCodeFunc will be called to send codes to the VM +type SendCodeFunc func([]string) error +type scMap map[string]*scancode + +type pcXTDriver struct { + interval time.Duration + sendImpl SendCodeFunc + specialMap scMap + scancodeMap map[rune]byte + buffer [][]string + // TODO: set from env + scancodeChunkSize int +} + +type scancode struct { + make []string + break_ []string +} + +func (sc *scancode) makeBreak() []string { + return append(sc.make, sc.break_...) +} + +// NewPCXTDriver creates a new boot command driver for VMs that expect PC-XT +// keyboard codes. `send` should send its argument to the VM. `chunkSize` should +// be the maximum number of keyboard codes to send to `send` at one time. +func NewPCXTDriver(send SendCodeFunc, chunkSize int) *pcXTDriver { + // We delay (default 100ms) between each input event to allow for CPU or + // network latency. See PackerKeyEnv for tuning. + keyInterval := common.PackerKeyDefault + if delay, err := time.ParseDuration(os.Getenv(common.PackerKeyEnv)); err == nil { + keyInterval = delay + } + // Scancodes reference: https://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html + // https://www.win.tue.nl/~aeb/linux/kbd/scancodes-10.html + // + // 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. + sMap := make(scMap) + sMap["bs"] = &scancode{[]string{"0e"}, []string{"8e"}} + sMap["del"] = &scancode{[]string{"e0", "53"}, []string{"e0", "d3"}} + sMap["down"] = &scancode{[]string{"e0", "50"}, []string{"e0", "d0"}} + sMap["end"] = &scancode{[]string{"e0", "4f"}, []string{"e0", "cf"}} + sMap["enter"] = &scancode{[]string{"1c"}, []string{"9c"}} + sMap["esc"] = &scancode{[]string{"01"}, []string{"81"}} + sMap["f1"] = &scancode{[]string{"3b"}, []string{"bb"}} + sMap["f2"] = &scancode{[]string{"3c"}, []string{"bc"}} + sMap["f3"] = &scancode{[]string{"3d"}, []string{"bd"}} + sMap["f4"] = &scancode{[]string{"3e"}, []string{"be"}} + sMap["f5"] = &scancode{[]string{"3f"}, []string{"bf"}} + sMap["f6"] = &scancode{[]string{"40"}, []string{"c0"}} + sMap["f7"] = &scancode{[]string{"41"}, []string{"c1"}} + sMap["f8"] = &scancode{[]string{"42"}, []string{"c2"}} + sMap["f9"] = &scancode{[]string{"43"}, []string{"c3"}} + sMap["f10"] = &scancode{[]string{"44"}, []string{"c4"}} + sMap["f11"] = &scancode{[]string{"57"}, []string{"d7"}} + sMap["f12"] = &scancode{[]string{"58"}, []string{"d8"}} + sMap["home"] = &scancode{[]string{"e0", "47"}, []string{"e0", "c7"}} + sMap["insert"] = &scancode{[]string{"e0", "52"}, []string{"e0", "d2"}} + sMap["left"] = &scancode{[]string{"e0", "4b"}, []string{"e0", "cb"}} + sMap["leftalt"] = &scancode{[]string{"38"}, []string{"b8"}} + sMap["leftctrl"] = &scancode{[]string{"1d"}, []string{"9d"}} + sMap["leftshift"] = &scancode{[]string{"2a"}, []string{"aa"}} + sMap["leftsuper"] = &scancode{[]string{"e0", "5b"}, []string{"e0", "db"}} + sMap["menu"] = &scancode{[]string{"e0", "5d"}, []string{"e0", "dd"}} + sMap["pagedown"] = &scancode{[]string{"e0", "51"}, []string{"e0", "d1"}} + sMap["pageup"] = &scancode{[]string{"e0", "49"}, []string{"e0", "c9"}} + sMap["return"] = &scancode{[]string{"1c"}, []string{"9c"}} + sMap["right"] = &scancode{[]string{"e0", "4d"}, []string{"e0", "cd"}} + sMap["rightalt"] = &scancode{[]string{"e0", "38"}, []string{"e0", "b8"}} + sMap["rightctrl"] = &scancode{[]string{"e0", "1d"}, []string{"e0", "9d"}} + sMap["rightshift"] = &scancode{[]string{"36"}, []string{"b6"}} + sMap["rightsuper"] = &scancode{[]string{"e0", "5c"}, []string{"e0", "dc"}} + sMap["spacebar"] = &scancode{[]string{"39"}, []string{"b9"}} + sMap["tab"] = &scancode{[]string{"0f"}, []string{"8f"}} + sMap["up"] = &scancode{[]string{"e0", "48"}, []string{"e0", "c8"}} + + scancodeIndex := make(map[string]byte) + 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]byte) + for chars, start := range scancodeIndex { + var i byte = 0 + for len(chars) > 0 { + r, size := utf8.DecodeRuneInString(chars) + chars = chars[size:] + scancodeMap[r] = start + i + i += 1 + } + } + + return &pcXTDriver{ + interval: keyInterval, + sendImpl: send, + specialMap: sMap, + scancodeMap: scancodeMap, + scancodeChunkSize: chunkSize, + } +} + +// Flush send all scanecodes. +func (d *pcXTDriver) Flush() error { + defer func() { + d.buffer = nil + }() + sc, err := chunkScanCodes(d.buffer, d.scancodeChunkSize) + if err != nil { + return err + } + for _, b := range sc { + if err := d.sendImpl(b); err != nil { + return err + } + time.Sleep(d.interval) + } + return nil +} + +func (d *pcXTDriver) SendKey(key rune, action KeyAction) error { + keyShift := unicode.IsUpper(key) || strings.ContainsRune(shiftedChars, key) + + var sc []string + + if action&(KeyOn|KeyPress) != 0 { + scInt := d.scancodeMap[key] + if keyShift { + sc = append(sc, "2a") + } + sc = append(sc, fmt.Sprintf("%02x", scInt)) + } + + if action&(KeyOff|KeyPress) != 0 { + scInt := d.scancodeMap[key] + 0x80 + if keyShift { + sc = append(sc, "aa") + } + sc = append(sc, fmt.Sprintf("%02x", scInt)) + } + + log.Printf("Sending char '%c', code '%s', shift %v", + key, strings.Join(sc, ""), keyShift) + + d.send(sc) + return nil +} + +func (d *pcXTDriver) SendSpecial(special string, action KeyAction) error { + keyCode, ok := d.specialMap[special] + if !ok { + return fmt.Errorf("special %s not found.", special) + } + log.Printf("Special code '%s' '<%s>' found, replacing with: %v", action.String(), special, keyCode) + + switch action { + case KeyOn: + d.send(keyCode.make) + case KeyOff: + d.send(keyCode.break_) + case KeyPress: + d.send(keyCode.makeBreak()) + } + return nil +} + +// send stores the codes in an internal buffer. Use Flush to send them. +func (d *pcXTDriver) send(codes []string) { + d.buffer = append(d.buffer, codes) +} + +func chunkScanCodes(sc [][]string, size int) (out [][]string, err error) { + var running []string + for _, codes := range sc { + if size > 0 { + if len(codes) > size { + return nil, fmt.Errorf("chunkScanCodes: size cannot be smaller than sc width.") + } + if len(running)+len(codes) > size { + out = append(out, running) + running = nil + } + } + running = append(running, codes...) + } + if running != nil { + out = append(out, running) + } + return +} diff --git a/common/bootcommand/pc_xt_driver_test.go b/common/bootcommand/pc_xt_driver_test.go new file mode 100644 index 000000000..cd0e04ec8 --- /dev/null +++ b/common/bootcommand/pc_xt_driver_test.go @@ -0,0 +1,123 @@ +package bootcommand + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_chunkScanCodes(t *testing.T) { + + var chunktests = []struct { + size int + in [][]string + out [][]string + }{ + { + 3, + [][]string{ + {"a", "b"}, + {"c"}, + {"d"}, + {"e", "f"}, + {"g", "h"}, + {"i", "j"}, + {"k"}, + {"l", "m"}, + }, + [][]string{ + {"a", "b", "c"}, + {"d", "e", "f"}, + {"g", "h"}, + {"i", "j", "k"}, + {"l", "m"}, + }, + }, + { + -1, + [][]string{ + {"a", "b"}, + {"c"}, + {"d"}, + {"e", "f"}, + {"g", "h"}, + {"i", "j"}, + {"k"}, + {"l", "m"}, + }, + [][]string{ + {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m"}, + }, + }, + } + + for _, tt := range chunktests { + out, err := chunkScanCodes(tt.in, tt.size) + assert.NoError(t, err) + assert.Equalf(t, tt.out, out, "expecting chunks of %d.", tt.size) + } +} + +func Test_chunkScanCodeError(t *testing.T) { + // can't go from wider to thinner + in := [][]string{ + {"a", "b", "c"}, + {"d", "e", "f"}, + {"g", "h"}, + } + + _, err := chunkScanCodes(in, 2) + assert.Error(t, err) +} + +func Test_pcxtSpecialOnOff(t *testing.T) { + in := "" + expected := []string{"36", "b6", "b6", "36"} + var codes []string + sendCodes := func(c []string) error { + codes = c + return nil + } + d := NewPCXTDriver(sendCodes, -1) + seq, err := GenerateExpressionSequence(in) + assert.NoError(t, err) + err = seq.Do(context.Background(), d) + assert.NoError(t, err) + assert.Equal(t, expected, codes) +} + +func Test_pcxtSpecial(t *testing.T) { + in := "" + expected := []string{"e0", "4b", "e0", "cb"} + var codes []string + sendCodes := func(c []string) error { + codes = c + return nil + } + d := NewPCXTDriver(sendCodes, -1) + seq, err := GenerateExpressionSequence(in) + assert.NoError(t, err) + err = seq.Do(context.Background(), d) + assert.NoError(t, err) + assert.Equal(t, expected, codes) +} + +func Test_flushes(t *testing.T) { + in := "abc123098" + expected := [][]string{ + {"1e", "9e", "30", "b0", "2e", "ae", "02", "82", "03", "83", "04", "84"}, + {"0b", "8b", "0a", "8a", "09", "89"}, + } + var actual [][]string + sendCodes := func(c []string) error { + actual = append(actual, c) + return nil + } + d := NewPCXTDriver(sendCodes, -1) + seq, err := GenerateExpressionSequence(in) + assert.NoError(t, err) + err = seq.Do(context.Background(), d) + assert.NoError(t, err) + assert.Equal(t, expected, actual) +} diff --git a/common/bootcommand/vnc_driver.go b/common/bootcommand/vnc_driver.go new file mode 100644 index 000000000..dfe75b9b2 --- /dev/null +++ b/common/bootcommand/vnc_driver.go @@ -0,0 +1,147 @@ +package bootcommand + +import ( + "fmt" + "log" + "os" + "strings" + "time" + "unicode" + + "github.com/hashicorp/packer/common" +) + +const KeyLeftShift uint32 = 0xFFE1 + +type VNCKeyEvent interface { + KeyEvent(uint32, bool) error +} + +type vncDriver struct { + c VNCKeyEvent + interval time.Duration + specialMap map[string]uint32 + // keyEvent can set this error which will prevent it from continuing + err error +} + +func NewVNCDriver(c VNCKeyEvent) *vncDriver { + // We delay (default 100ms) between each key event to allow for CPU or + // network latency. See PackerKeyEnv for tuning. + keyInterval := common.PackerKeyDefault + if delay, err := time.ParseDuration(os.Getenv(common.PackerKeyEnv)); err == nil { + keyInterval = delay + } + + // Scancodes reference: https://github.com/qemu/qemu/blob/master/ui/vnc_keysym.h + sMap := make(map[string]uint32) + sMap["bs"] = 0xFF08 + sMap["del"] = 0xFFFF + sMap["down"] = 0xFF54 + sMap["end"] = 0xFF57 + sMap["enter"] = 0xFF0D + sMap["esc"] = 0xFF1B + sMap["f1"] = 0xFFBE + sMap["f2"] = 0xFFBF + sMap["f3"] = 0xFFC0 + sMap["f4"] = 0xFFC1 + sMap["f5"] = 0xFFC2 + sMap["f6"] = 0xFFC3 + sMap["f7"] = 0xFFC4 + sMap["f8"] = 0xFFC5 + sMap["f9"] = 0xFFC6 + sMap["f10"] = 0xFFC7 + sMap["f11"] = 0xFFC8 + sMap["f12"] = 0xFFC9 + sMap["home"] = 0xFF50 + sMap["insert"] = 0xFF63 + sMap["left"] = 0xFF51 + sMap["leftalt"] = 0xFFE9 + sMap["leftctrl"] = 0xFFE3 + sMap["leftshift"] = 0xFFE1 + sMap["leftsuper"] = 0xFFEB + sMap["menu"] = 0xFF67 + sMap["pagedown"] = 0xFF56 + sMap["pageup"] = 0xFF55 + sMap["return"] = 0xFF0D + sMap["right"] = 0xFF53 + sMap["rightalt"] = 0xFFEA + sMap["rightctrl"] = 0xFFE4 + sMap["rightshift"] = 0xFFE2 + sMap["rightsuper"] = 0xFFEC + sMap["spacebar"] = 0x020 + sMap["tab"] = 0xFF09 + sMap["up"] = 0xFF52 + + return &vncDriver{ + c: c, + interval: keyInterval, + specialMap: sMap, + } +} + +func (d *vncDriver) keyEvent(k uint32, down bool) error { + if d.err != nil { + return nil + } + if err := d.c.KeyEvent(k, down); err != nil { + d.err = err + return err + } + time.Sleep(d.interval) + return nil +} + +// Flush does nothing here +func (d *vncDriver) Flush() error { + return nil +} + +func (d *vncDriver) SendKey(key rune, action KeyAction) error { + keyShift := unicode.IsUpper(key) || strings.ContainsRune(shiftedChars, key) + keyCode := uint32(key) + log.Printf("Sending char '%c', code 0x%X, shift %v", key, keyCode, keyShift) + + switch action { + case KeyOn: + if keyShift { + d.keyEvent(KeyLeftShift, true) + } + d.keyEvent(keyCode, true) + case KeyOff: + if keyShift { + d.keyEvent(KeyLeftShift, false) + } + d.keyEvent(keyCode, false) + case KeyPress: + if keyShift { + d.keyEvent(KeyLeftShift, true) + } + d.keyEvent(keyCode, true) + d.keyEvent(keyCode, false) + if keyShift { + d.keyEvent(KeyLeftShift, false) + } + } + return d.err +} + +func (d *vncDriver) SendSpecial(special string, action KeyAction) error { + keyCode, ok := d.specialMap[special] + if !ok { + return fmt.Errorf("special %s not found.", special) + } + log.Printf("Special code '<%s>' found, replacing with: 0x%X", special, keyCode) + + switch action { + case KeyOn: + d.keyEvent(keyCode, true) + case KeyOff: + d.keyEvent(keyCode, false) + case KeyPress: + d.keyEvent(keyCode, true) + d.keyEvent(keyCode, false) + } + + return d.err +} diff --git a/common/bootcommand/vnc_driver_test.go b/common/bootcommand/vnc_driver_test.go new file mode 100644 index 000000000..11ca41669 --- /dev/null +++ b/common/bootcommand/vnc_driver_test.go @@ -0,0 +1,39 @@ +package bootcommand + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +type event struct { + u uint32 + down bool +} + +type sender struct { + e []event +} + +func (s *sender) KeyEvent(u uint32, down bool) error { + s.e = append(s.e, event{u, down}) + return nil +} + +func Test_vncSpecialLookup(t *testing.T) { + in := "" + expected := []event{ + {0xFFE2, true}, + {0xFFE2, false}, + {0xFFE2, false}, + {0xFFE2, true}, + } + s := &sender{} + d := NewVNCDriver(s) + seq, err := GenerateExpressionSequence(in) + assert.NoError(t, err) + err = seq.Do(context.Background(), d) + assert.NoError(t, err) + assert.Equal(t, expected, s.e) +} diff --git a/common/powershell/hyperv/hyperv.go b/common/powershell/hyperv/hyperv.go index f1dfc940c..50eb7cb1a 100644 --- a/common/powershell/hyperv/hyperv.go +++ b/common/powershell/hyperv/hyperv.go @@ -187,49 +187,51 @@ Hyper-V\Set-VMFloppyDiskDrive -VMName $vmName -Path $null return err } -func CreateVirtualMachine(vmName string, path string, harddrivePath string, vhdRoot string, ram int64, diskSize int64, switchName string, generation uint, diffDisks bool) error { +func CreateVirtualMachine(vmName string, path string, harddrivePath string, vhdRoot string, ram int64, diskSize int64, diskBlockSize int64, switchName string, generation uint, diffDisks bool) error { if generation == 2 { var script = ` -param([string]$vmName, [string]$path, [string]$harddrivePath, [string]$vhdRoot, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName, [int]$generation, [string]$diffDisks) +param([string]$vmName, [string]$path, [string]$harddrivePath, [string]$vhdRoot, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [long]$vhdBlockSizeBytes, [string]$switchName, [int]$generation, [string]$diffDisks) $vhdx = $vmName + '.vhdx' $vhdPath = Join-Path -Path $vhdRoot -ChildPath $vhdx if ($harddrivePath){ if($diffDisks -eq "true"){ - New-VHD -Path $vhdPath -ParentPath $harddrivePath -Differencing + New-VHD -Path $vhdPath -ParentPath $harddrivePath -Differencing -BlockSizeBytes $vhdBlockSizeBytes } else { Copy-Item -Path $harddrivePath -Destination $vhdPath } Hyper-V\New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -VHDPath $vhdPath -SwitchName $switchName -Generation $generation } else { - Hyper-V\New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName -Generation $generation + Hyper-V\New-VHD -Path $vhdPath -SizeBytes $newVHDSizeBytes -BlockSizeBytes $vhdBlockSizeBytes + Hyper-V\New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -VHDPath $vhdPath -SwitchName $switchName -Generation $generation } ` var ps powershell.PowerShellCmd - if err := ps.Run(script, vmName, path, harddrivePath, vhdRoot, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), switchName, strconv.FormatInt(int64(generation), 10), strconv.FormatBool(diffDisks)); err != nil { + if err := ps.Run(script, vmName, path, harddrivePath, vhdRoot, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), strconv.FormatInt(diskBlockSize, 10), switchName, strconv.FormatInt(int64(generation), 10), strconv.FormatBool(diffDisks)); err != nil { return err } return DisableAutomaticCheckpoints(vmName) } else { var script = ` -param([string]$vmName, [string]$path, [string]$harddrivePath, [string]$vhdRoot, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName, [string]$diffDisks) +param([string]$vmName, [string]$path, [string]$harddrivePath, [string]$vhdRoot, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [long]$vhdBlockSizeBytes, [string]$switchName, [string]$diffDisks) $vhdx = $vmName + '.vhdx' $vhdPath = Join-Path -Path $vhdRoot -ChildPath $vhdx if ($harddrivePath){ if($diffDisks -eq "true"){ - New-VHD -Path $vhdPath -ParentPath $harddrivePath -Differencing + New-VHD -Path $vhdPath -ParentPath $harddrivePath -Differencing -BlockSizeBytes $vhdBlockSizeBytes } else{ Copy-Item -Path $harddrivePath -Destination $vhdPath } Hyper-V\New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -VHDPath $vhdPath -SwitchName $switchName } else { - Hyper-V\New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName + Hyper-V\New-VHD -Path $vhdPath -SizeBytes $newVHDSizeBytes -BlockSizeBytes $vhdBlockSizeBytes + Hyper-V\New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -VHDPath $vhdPath -SwitchName $switchName } ` var ps powershell.PowerShellCmd - if err := ps.Run(script, vmName, path, harddrivePath, vhdRoot, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), switchName, strconv.FormatBool(diffDisks)); err != nil { + if err := ps.Run(script, vmName, path, harddrivePath, vhdRoot, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), strconv.FormatInt(diskBlockSize, 10), switchName, strconv.FormatBool(diffDisks)); err != nil { return err } @@ -872,16 +874,16 @@ Hyper-V\Get-VMNetworkAdapter -VMName $vmName | Hyper-V\Connect-VMNetworkAdapter return err } -func AddVirtualMachineHardDiskDrive(vmName string, vhdRoot string, vhdName string, vhdSizeBytes int64, controllerType string) error { +func AddVirtualMachineHardDiskDrive(vmName string, vhdRoot string, vhdName string, vhdSizeBytes int64, vhdBlockSize int64, controllerType string) error { var script = ` -param([string]$vmName,[string]$vhdRoot, [string]$vhdName, [string]$vhdSizeInBytes, [string]$controllerType) +param([string]$vmName,[string]$vhdRoot, [string]$vhdName, [string]$vhdSizeInBytes,[string]$vhdBlockSizeInByte [string]$controllerType) $vhdPath = Join-Path -Path $vhdRoot -ChildPath $vhdName -New-VHD $vhdPath -SizeBytes $vhdSizeInBytes +Hyper-V\New-VHD -path $vhdPath -SizeBytes $vhdSizeInBytes -BlockSizeBytes $vhdBlockSizeInByte Hyper-V\Add-VMHardDiskDrive -VMName $vmName -path $vhdPath -controllerType $controllerType ` var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, vhdRoot, vhdName, strconv.FormatInt(vhdSizeBytes, 10), controllerType) + err := ps.Run(script, vmName, vhdRoot, vhdName, strconv.FormatInt(vhdSizeBytes, 10), strconv.FormatInt(vhdBlockSize, 10), controllerType) return err } diff --git a/common/step_http_server.go b/common/step_http_server.go index 953ef38c1..394042961 100644 --- a/common/step_http_server.go +++ b/common/step_http_server.go @@ -76,20 +76,20 @@ func (s *StepHTTPServer) Run(_ context.Context, state multistep.StateBag) multis } func SetHTTPPort(port string) error { - return common.SetSharedState("port", port) + return common.SetSharedState("port", port, "") } func SetHTTPIP(ip string) error { - return common.SetSharedState("ip", ip) + return common.SetSharedState("ip", ip, "") } func GetHTTPAddr() string { - ip, err := common.RetrieveSharedState("ip") + ip, err := common.RetrieveSharedState("ip", "") if err != nil { return "" } - port, err := common.RetrieveSharedState("port") + port, err := common.RetrieveSharedState("port", "") if err != nil { return "" } @@ -101,6 +101,6 @@ func (s *StepHTTPServer) Cleanup(multistep.StateBag) { // Close the listener so that the HTTP server stops s.l.Close() } - common.RemoveSharedStateFile("port") - common.RemoveSharedStateFile("ip") + common.RemoveSharedStateFile("port", "") + common.RemoveSharedStateFile("ip", "") } diff --git a/communicator/ssh/communicator.go b/communicator/ssh/communicator.go index 218c11be6..6545d1d5c 100644 --- a/communicator/ssh/communicator.go +++ b/communicator/ssh/communicator.go @@ -410,27 +410,6 @@ func (c *comm) sftpUploadSession(path string, input io.Reader, fi *os.FileInfo) func (c *comm) sftpUploadFile(path string, input io.Reader, client *sftp.Client, fi *os.FileInfo) error { log.Printf("[DEBUG] sftp: uploading %s", path) - - // find out if destination is a directory (this is to replicate rsync behavior) - testDirectoryCommand := fmt.Sprintf(`test -d "%s"`, path) - - cmd := &packer.RemoteCmd{ - Command: testDirectoryCommand, - } - - err := c.Start(cmd) - - if err != nil { - log.Printf("[ERROR] Unable to check whether remote path is a dir: %s", err) - return err - } - cmd.Wait() - if cmd.ExitStatus == 0 { - return fmt.Errorf( - "Destination path (%s) is a directory that already exists. "+ - "Please ensure the destination is writable.", path) - } - f, err := client.Create(path) if err != nil { return err @@ -586,34 +565,6 @@ func (c *comm) scpUploadSession(path string, input io.Reader, fi *os.FileInfo) e target_dir := filepath.Dir(path) target_file := filepath.Base(path) - // find out if destination is a directory (this is to replicate rsync behavior) - testDirectoryCommand := fmt.Sprintf(`test -d "%s"`, path) - var stdout, stderr bytes.Buffer - cmd := &packer.RemoteCmd{ - Command: testDirectoryCommand, - Stdout: &stdout, - Stderr: &stderr, - } - - err := c.Start(cmd) - - if err != nil { - log.Printf("[ERROR] Unable to check whether remote path is a dir: %s", err) - return err - } - cmd.Wait() - if stdout.Len() > 0 { - return fmt.Errorf("%s", stdout.Bytes()) - } - if stderr.Len() > 0 { - return fmt.Errorf("%s", stderr.Bytes()) - } - if cmd.ExitStatus == 0 { - return fmt.Errorf( - "Destination path (%s) is a directory that already exists. "+ - "Please ensure the destination is writable.", path) - } - // On windows, filepath.Dir uses backslash separators (ie. "\tmp"). // This does not work when the target host is unix. Switch to forward slash // which works for unix and windows diff --git a/contrib/azure-setup.sh b/contrib/azure-setup.sh index 6149b38d0..6473c5a08 100755 --- a/contrib/azure-setup.sh +++ b/contrib/azure-setup.sh @@ -85,9 +85,9 @@ askSubscription() { if [ "$azure_subscription_id" != "" ]; then az account set --subscription $azure_subscription_id else - azure_subscription_id=$(az account list | jq -r '.[] | select(.isDefault==true) | .id') + azure_subscription_id=$(az account list --output json | jq -r '.[] | select(.isDefault==true) | .id') fi - azure_tenant_id=$(az account list | jq -r '.[] | select(.id=="'$azure_subscription_id'") | .tenantId') + azure_tenant_id=$(az account list --output json | jq -r '.[] | select(.id=="'$azure_subscription_id'") | .tenantId') echo "Using subscription_id: $azure_subscription_id" echo "Using tenant_id: $azure_tenant_id" } @@ -152,14 +152,14 @@ createStorageAccount() { createApplication() { echo "==> Creating application" echo "==> Does application exist?" - azure_client_id=$(az ad app list | jq -r '.[] | select(.displayName | contains("'$meta_name'")) ') - + azure_client_id=$(az ad app list --output json | jq -r '.[] | select(.displayName | contains("'$meta_name'")) ') + if [ "$azure_client_id" != "" ]; then echo "==> application already exist, grab appId" - azure_client_id=$(az ad app list | jq -r '.[] | select(.displayName | contains("'$meta_name'")) .appId') + azure_client_id=$(az ad app list az ad app list --output json | jq -r '.[] | select(.displayName | contains("'$meta_name'")) .appId') else echo "==> application does not exist" - azure_client_id=$(az ad app create --display-name $meta_name --identifier-uris http://$meta_name --homepage http://$meta_name --password $azure_client_secret | jq -r .appId) + azure_client_id=$(az ad app create --display-name $meta_name --identifier-uris http://$meta_name --homepage http://$meta_name --password $azure_client_secret --output json | jq -r .appId) fi if [ $? -ne 0 ]; then @@ -170,7 +170,7 @@ createApplication() { createServicePrincipal() { echo "==> Creating service principal" - azure_object_id=$(az ad sp create --id $azure_client_id | jq -r .objectId) + azure_object_id=$(az ad sp create --id $azure_client_id --output json | jq -r .objectId) echo $azure_object_id "was selected." if [ $? -ne 0 ]; then diff --git a/examples/azure/freebsd.json b/examples/azure/freebsd.json new file mode 100644 index 000000000..33f90c9a6 --- /dev/null +++ b/examples/azure/freebsd.json @@ -0,0 +1,47 @@ +{ + "variables": { + "client_id": "{{env `ARM_CLIENT_ID`}}", + "client_secret": "{{env `ARM_CLIENT_SECRET`}}", + "resource_group": "{{env `ARM_RESOURCE_GROUP`}}", + "storage_account": "{{env `ARM_STORAGE_ACCOUNT`}}", + "subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}", + "ssh_user": "packer", + "ssh_pass": null + }, + "builders": [{ + "type": "azure-arm", + + "client_id": "{{user `client_id`}}", + "client_secret": "{{user `client_secret`}}", + "resource_group_name": "{{user `resource_group`}}", + "storage_account": "{{user `storage_account`}}", + "subscription_id": "{{user `subscription_id`}}", + + "capture_container_name": "images", + "capture_name_prefix": "packer", + + "ssh_username": "{{user `ssh_user`}}", + "ssh_password": "{{user `ssh_pass`}}", + + "os_type": "Linux", + "image_publisher": "MicrosoftOSTC", + "image_offer": "FreeBSD", + "image_sku": "11.1", + "image_version": "latest", + + "location": "West US 2", + "vm_size": "Standard_DS2_v2" + }], + "provisioners": [{ + "execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'", + "inline": [ + "env ASSUME_ALWAYS_YES=YES pkg bootstrap", + "/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync" + ], + "inline_shebang": "/bin/sh -x", + "type": "shell", + "skip_clean": "true", + "expect_disconnect": "true" + }] + +} diff --git a/fix/fixer_powershell_escapes.go b/fix/fixer_powershell_escapes.go index 9da9ae91f..e9e3b0033 100644 --- a/fix/fixer_powershell_escapes.go +++ b/fix/fixer_powershell_escapes.go @@ -1,8 +1,9 @@ package fix import ( - "github.com/mitchellh/mapstructure" "strings" + + "github.com/mitchellh/mapstructure" ) // FixerPowerShellEscapes removes the PowerShell escape character from user diff --git a/helper/common/shared_state.go b/helper/common/shared_state.go index 92ffbaeaf..87e111cfb 100644 --- a/helper/common/shared_state.go +++ b/helper/common/shared_state.go @@ -9,23 +9,23 @@ import ( // Used to set variables which we need to access later in the build, where // state bag and config information won't work -func sharedStateFilename(suffix string) string { +func sharedStateFilename(suffix string, buildName string) string { uuid := os.Getenv("PACKER_RUN_UUID") - return filepath.Join(os.TempDir(), fmt.Sprintf("packer-%s-%s", uuid, suffix)) + return filepath.Join(os.TempDir(), fmt.Sprintf("packer-%s-%s-%s", uuid, suffix, buildName)) } -func SetSharedState(key string, value string) error { - return ioutil.WriteFile(sharedStateFilename(key), []byte(value), 0600) +func SetSharedState(key string, value string, buildName string) error { + return ioutil.WriteFile(sharedStateFilename(key, buildName), []byte(value), 0600) } -func RetrieveSharedState(key string) (string, error) { - value, err := ioutil.ReadFile(sharedStateFilename(key)) +func RetrieveSharedState(key string, buildName string) (string, error) { + value, err := ioutil.ReadFile(sharedStateFilename(key, buildName)) if err != nil { return "", err } return string(value), nil } -func RemoveSharedStateFile(key string) { - os.Remove(sharedStateFilename(key)) +func RemoveSharedStateFile(key string, buildName string) { + os.Remove(sharedStateFilename(key, buildName)) } diff --git a/main.go b/main.go index 26a1a541d..ff53d1744 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ // This is the main package for the `packer` application. //go:generate go run ./scripts/generate-plugins.go +//go:generate go generate ./common/bootcommand/... package main import ( diff --git a/post-processor/vagrant/google_test.go b/post-processor/vagrant/google_test.go index a66be8539..22eab0c77 100644 --- a/post-processor/vagrant/google_test.go +++ b/post-processor/vagrant/google_test.go @@ -1,9 +1,10 @@ package vagrant import ( - "github.com/hashicorp/packer/packer" "strings" "testing" + + "github.com/hashicorp/packer/packer" ) func TestGoogleProvider_impl(t *testing.T) { diff --git a/post-processor/vagrant/scaleway.go b/post-processor/vagrant/scaleway.go index 879f2b59e..ee275cffd 100644 --- a/post-processor/vagrant/scaleway.go +++ b/post-processor/vagrant/scaleway.go @@ -3,9 +3,10 @@ package vagrant import ( "bytes" "fmt" - "github.com/hashicorp/packer/packer" "strings" "text/template" + + "github.com/hashicorp/packer/packer" ) type scalewayVagrantfileTemplate struct { diff --git a/post-processor/vsphere/artifact_test.go b/post-processor/vsphere/artifact_test.go index b4f17c759..f6d0d54be 100644 --- a/post-processor/vsphere/artifact_test.go +++ b/post-processor/vsphere/artifact_test.go @@ -1,8 +1,9 @@ package vsphere import ( - "github.com/hashicorp/packer/packer" "testing" + + "github.com/hashicorp/packer/packer" ) func TestArtifact_ImplementsArtifact(t *testing.T) { diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index e28becef0..c5a4845f7 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -377,7 +377,7 @@ func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string) { // interpolate environment variables p.config.ctx.Data = &EnvVarsTemplate{ - WinRMPassword: getWinRMPassword(), + WinRMPassword: getWinRMPassword(p.config.PackerBuildName), } // Split vars into key/value components for _, envVar := range p.config.Vars { @@ -445,7 +445,7 @@ func (p *Provisioner) createCommandTextNonPrivileged() (command string, err erro p.config.ctx.Data = &ExecuteCommandTemplate{ Path: p.config.RemotePath, Vars: envVarPath, - WinRMPassword: getWinRMPassword(), + WinRMPassword: getWinRMPassword(p.config.PackerBuildName), } command, err = interpolate.Render(p.config.ExecuteCommand, &p.config.ctx) @@ -457,8 +457,8 @@ func (p *Provisioner) createCommandTextNonPrivileged() (command string, err erro return command, nil } -func getWinRMPassword() string { - winRMPass, _ := commonhelper.RetrieveSharedState("winrm_password") +func getWinRMPassword(buildName string) string { + winRMPass, _ := commonhelper.RetrieveSharedState("winrm_password", buildName) return winRMPass } @@ -472,7 +472,7 @@ func (p *Provisioner) createCommandTextPrivileged() (command string, err error) p.config.ctx.Data = &ExecuteCommandTemplate{ Path: p.config.RemotePath, Vars: envVarPath, - WinRMPassword: getWinRMPassword(), + WinRMPassword: getWinRMPassword(p.config.PackerBuildName), } command, err = interpolate.Render(p.config.ElevatedExecuteCommand, &p.config.ctx) if err != nil { @@ -530,7 +530,7 @@ func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath strin } // Replace ElevatedPassword for winrm users who used this feature p.config.ctx.Data = &EnvVarsTemplate{ - WinRMPassword: getWinRMPassword(), + WinRMPassword: getWinRMPassword(p.config.PackerBuildName), } p.config.ElevatedPassword, _ = interpolate.Render(p.config.ElevatedPassword, &p.config.ctx) diff --git a/vendor/github.com/davecgh/go-spew/LICENSE b/vendor/github.com/davecgh/go-spew/LICENSE index bb6733231..c83641619 100644 --- a/vendor/github.com/davecgh/go-spew/LICENSE +++ b/vendor/github.com/davecgh/go-spew/LICENSE @@ -1,6 +1,6 @@ ISC License -Copyright (c) 2012-2013 Dave Collins +Copyright (c) 2012-2016 Dave Collins Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/vendor/github.com/davecgh/go-spew/spew/bypass.go b/vendor/github.com/davecgh/go-spew/spew/bypass.go index d42a0bc4a..8a4a6589a 100644 --- a/vendor/github.com/davecgh/go-spew/spew/bypass.go +++ b/vendor/github.com/davecgh/go-spew/spew/bypass.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Dave Collins +// Copyright (c) 2015-2016 Dave Collins // // Permission to use, copy, modify, and distribute this software for any // purpose with or without fee is hereby granted, provided that the above diff --git a/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go b/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go deleted file mode 100644 index e47a4e795..000000000 --- a/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2015 Dave Collins -// -// Permission to use, copy, modify, and distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -// NOTE: Due to the following build constraints, this file will only be compiled -// when the code is running on Google App Engine, compiled by GopherJS, or -// "-tags safe" is added to the go build command line. The "disableunsafe" -// tag is deprecated and thus should not be used. -// +build js appengine safe disableunsafe - -package spew - -import "reflect" - -const ( - // UnsafeDisabled is a build-time constant which specifies whether or - // not access to the unsafe package is available. - UnsafeDisabled = true -) - -// unsafeReflectValue typically converts the passed reflect.Value into a one -// that bypasses the typical safety restrictions preventing access to -// unaddressable and unexported data. However, doing this relies on access to -// the unsafe package. This is a stub version which simply returns the passed -// reflect.Value when the unsafe package is not available. -func unsafeReflectValue(v reflect.Value) reflect.Value { - return v -} diff --git a/vendor/github.com/davecgh/go-spew/spew/common.go b/vendor/github.com/davecgh/go-spew/spew/common.go index 14f02dc15..7c519ff47 100644 --- a/vendor/github.com/davecgh/go-spew/spew/common.go +++ b/vendor/github.com/davecgh/go-spew/spew/common.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Dave Collins + * Copyright (c) 2013-2016 Dave Collins * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above diff --git a/vendor/github.com/davecgh/go-spew/spew/config.go b/vendor/github.com/davecgh/go-spew/spew/config.go index 555282723..2e3d22f31 100644 --- a/vendor/github.com/davecgh/go-spew/spew/config.go +++ b/vendor/github.com/davecgh/go-spew/spew/config.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Dave Collins + * Copyright (c) 2013-2016 Dave Collins * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -67,6 +67,15 @@ type ConfigState struct { // Google App Engine or with the "safe" build tag specified. DisablePointerMethods bool + // DisablePointerAddresses specifies whether to disable the printing of + // pointer addresses. This is useful when diffing data structures in tests. + DisablePointerAddresses bool + + // DisableCapacities specifies whether to disable the printing of capacities + // for arrays, slices, maps and channels. This is useful when diffing + // data structures in tests. + DisableCapacities bool + // ContinueOnMethod specifies whether or not recursion should continue once // a custom error or Stringer interface is invoked. The default, false, // means it will print the results of invoking the custom error or Stringer diff --git a/vendor/github.com/davecgh/go-spew/spew/doc.go b/vendor/github.com/davecgh/go-spew/spew/doc.go index 5be0c4060..aacaac6f1 100644 --- a/vendor/github.com/davecgh/go-spew/spew/doc.go +++ b/vendor/github.com/davecgh/go-spew/spew/doc.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Dave Collins + * Copyright (c) 2013-2016 Dave Collins * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -91,6 +91,15 @@ The following configuration options are available: which only accept pointer receivers from non-pointer variables. Pointer method invocation is enabled by default. + * DisablePointerAddresses + DisablePointerAddresses specifies whether to disable the printing of + pointer addresses. This is useful when diffing data structures in tests. + + * DisableCapacities + DisableCapacities specifies whether to disable the printing of + capacities for arrays, slices, maps and channels. This is useful when + diffing data structures in tests. + * ContinueOnMethod Enables recursion into types after invoking error and Stringer interface methods. Recursion after method invocation is disabled by default. diff --git a/vendor/github.com/davecgh/go-spew/spew/dump.go b/vendor/github.com/davecgh/go-spew/spew/dump.go index a0ff95e27..df1d582a7 100644 --- a/vendor/github.com/davecgh/go-spew/spew/dump.go +++ b/vendor/github.com/davecgh/go-spew/spew/dump.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Dave Collins + * Copyright (c) 2013-2016 Dave Collins * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -129,7 +129,7 @@ func (d *dumpState) dumpPtr(v reflect.Value) { d.w.Write(closeParenBytes) // Display pointer information. - if len(pointerChain) > 0 { + if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 { d.w.Write(openParenBytes) for i, addr := range pointerChain { if i > 0 { @@ -282,13 +282,13 @@ func (d *dumpState) dump(v reflect.Value) { case reflect.Map, reflect.String: valueLen = v.Len() } - if valueLen != 0 || valueCap != 0 { + if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 { d.w.Write(openParenBytes) if valueLen != 0 { d.w.Write(lenEqualsBytes) printInt(d.w, int64(valueLen), 10) } - if valueCap != 0 { + if !d.cs.DisableCapacities && valueCap != 0 { if valueLen != 0 { d.w.Write(spaceBytes) } diff --git a/vendor/github.com/davecgh/go-spew/spew/format.go b/vendor/github.com/davecgh/go-spew/spew/format.go index ecf3b80e2..c49875bac 100644 --- a/vendor/github.com/davecgh/go-spew/spew/format.go +++ b/vendor/github.com/davecgh/go-spew/spew/format.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Dave Collins + * Copyright (c) 2013-2016 Dave Collins * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above diff --git a/vendor/github.com/davecgh/go-spew/spew/spew.go b/vendor/github.com/davecgh/go-spew/spew/spew.go index d8233f542..32c0e3388 100644 --- a/vendor/github.com/davecgh/go-spew/spew/spew.go +++ b/vendor/github.com/davecgh/go-spew/spew/spew.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Dave Collins + * Copyright (c) 2013-2016 Dave Collins * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above diff --git a/vendor/github.com/denverdino/aliyungo/common/client.go b/vendor/github.com/denverdino/aliyungo/common/client.go old mode 100755 new mode 100644 index 10dcd9000..436b239b2 --- a/vendor/github.com/denverdino/aliyungo/common/client.go +++ b/vendor/github.com/denverdino/aliyungo/common/client.go @@ -9,10 +9,10 @@ import ( "log" "net/http" "net/url" - "strings" - "time" "os" "strconv" + "strings" + "time" "github.com/denverdino/aliyungo/util" ) @@ -43,7 +43,11 @@ type Client struct { // Initialize properties of a client instance func (client *Client) Init(endpoint, version, accessKeyId, accessKeySecret string) { client.AccessKeyId = accessKeyId - client.AccessKeySecret = accessKeySecret + "&" + ak := accessKeySecret + if !strings.HasSuffix(ak, "&") { + ak += "&" + } + client.AccessKeySecret = ak client.debug = false handshakeTimeout, err := strconv.Atoi(os.Getenv("TLSHandshakeTimeout")) if err != nil { @@ -53,8 +57,8 @@ func (client *Client) Init(endpoint, version, accessKeyId, accessKeySecret strin client.httpClient = &http.Client{} } else { t := &http.Transport{ - TLSHandshakeTimeout: time.Duration(handshakeTimeout) * time.Second,} - client.httpClient = &http.Client{Transport: t,} + TLSHandshakeTimeout: time.Duration(handshakeTimeout) * time.Second} + client.httpClient = &http.Client{Transport: t} } client.endpoint = endpoint client.version = version @@ -65,7 +69,7 @@ func (client *Client) NewInit(endpoint, version, accessKeyId, accessKeySecret, s client.Init(endpoint, version, accessKeyId, accessKeySecret) client.serviceCode = serviceCode client.regionID = regionID - client.setEndpointByLocation(regionID, serviceCode, accessKeyId, accessKeySecret) + client.setEndpointByLocation(regionID, serviceCode, accessKeyId, accessKeySecret, client.securityToken) } // Intialize client object when all properties are ready @@ -79,16 +83,21 @@ func (client *Client) InitClient() *Client { client.httpClient = &http.Client{} } else { t := &http.Transport{ - TLSHandshakeTimeout: time.Duration(handshakeTimeout) * time.Second,} - client.httpClient = &http.Client{Transport: t,} + TLSHandshakeTimeout: time.Duration(handshakeTimeout) * time.Second} + client.httpClient = &http.Client{Transport: t} } - client.setEndpointByLocation(client.regionID, client.serviceCode, client.AccessKeyId, client.AccessKeySecret) + client.setEndpointByLocation(client.regionID, client.serviceCode, client.AccessKeyId, client.AccessKeySecret, client.securityToken) return client } +func (client *Client) NewInitForAssumeRole(endpoint, version, accessKeyId, accessKeySecret, serviceCode string, regionID Region, securityToken string) { + client.NewInit(endpoint, version, accessKeyId, accessKeySecret, serviceCode, regionID) + client.securityToken = securityToken +} + //NewClient using location service -func (client *Client) setEndpointByLocation(region Region, serviceCode, accessKeyId, accessKeySecret string) { - locationClient := NewLocationClient(accessKeyId, accessKeySecret) +func (client *Client) setEndpointByLocation(region Region, serviceCode, accessKeyId, accessKeySecret, securityToken string) { + locationClient := NewLocationClient(accessKeyId, accessKeySecret, securityToken) ep := locationClient.DescribeOpenAPIEndpoint(region, serviceCode) if ep == "" { ep = loadEndpointFromFile(region, serviceCode) @@ -218,11 +227,6 @@ func (client *Client) SetAccessKeySecret(secret string) { client.AccessKeySecret = secret + "&" } -// SetAccessKeySecret sets securityToken -func (client *Client) SetSecurityToken(securityToken string) { - client.securityToken = securityToken -} - // SetDebug sets debug mode to log the request/response message func (client *Client) SetDebug(debug bool) { client.debug = debug @@ -242,6 +246,11 @@ func (client *Client) SetUserAgent(userAgent string) { client.userAgent = userAgent } +//set SecurityToken +func (client *Client) SetSecurityToken(securityToken string) { + client.securityToken = securityToken +} + // Invoke sends the raw HTTP request for ECS services func (client *Client) Invoke(action string, args interface{}, response interface{}) error { if err := client.ensureProperties(); err != nil { @@ -268,6 +277,7 @@ func (client *Client) Invoke(action string, args interface{}, response interface // TODO move to util and add build val flag httpReq.Header.Set("X-SDK-Client", `AliyunGO/`+Version+client.businessInfo) + httpReq.Header.Set("User-Agent", httpReq.UserAgent()+" "+client.userAgent) t0 := time.Now() @@ -341,6 +351,7 @@ func (client *Client) InvokeByFlattenMethod(action string, args interface{}, res // TODO move to util and add build val flag httpReq.Header.Set("X-SDK-Client", `AliyunGO/`+Version+client.businessInfo) + httpReq.Header.Set("User-Agent", httpReq.UserAgent()+" "+client.userAgent) t0 := time.Now() @@ -397,7 +408,6 @@ func (client *Client) InvokeByAnyMethod(method, action, path string, args interf request := Request{} request.init(client.version, action, client.AccessKeyId, client.securityToken, client.regionID) - data := util.ConvertToQueryValues(request) util.SetQueryValues(args, &data) diff --git a/vendor/github.com/denverdino/aliyungo/common/endpoint.go b/vendor/github.com/denverdino/aliyungo/common/endpoint.go index 16bcbf9d6..786606cf3 100644 --- a/vendor/github.com/denverdino/aliyungo/common/endpoint.go +++ b/vendor/github.com/denverdino/aliyungo/common/endpoint.go @@ -18,6 +18,51 @@ const ( var ( endpoints = make(map[Region]map[string]string) + + SpecailEnpoints = map[Region]map[string]string{ + APNorthEast1: { + "ecs": "https://ecs.ap-northeast-1.aliyuncs.com", + "slb": "https://slb.ap-northeast-1.aliyuncs.com", + "rds": "https://rds.ap-northeast-1.aliyuncs.com", + "vpc": "https://vpc.ap-northeast-1.aliyuncs.com", + }, + APSouthEast2: { + "ecs": "https://ecs.ap-southeast-2.aliyuncs.com", + "slb": "https://slb.ap-southeast-2.aliyuncs.com", + "rds": "https://rds.ap-southeast-2.aliyuncs.com", + "vpc": "https://vpc.ap-southeast-2.aliyuncs.com", + }, + APSouthEast3: { + "ecs": "https://ecs.ap-southeast-3.aliyuncs.com", + "slb": "https://slb.ap-southeast-3.aliyuncs.com", + "rds": "https://rds.ap-southeast-3.aliyuncs.com", + "vpc": "https://vpc.ap-southeast-3.aliyuncs.com", + }, + MEEast1: { + "ecs": "https://ecs.me-east-1.aliyuncs.com", + "slb": "https://slb.me-east-1.aliyuncs.com", + "rds": "https://rds.me-east-1.aliyuncs.com", + "vpc": "https://vpc.me-east-1.aliyuncs.com", + }, + EUCentral1: { + "ecs": "https://ecs.eu-central-1.aliyuncs.com", + "slb": "https://slb.eu-central-1.aliyuncs.com", + "rds": "https://rds.eu-central-1.aliyuncs.com", + "vpc": "https://vpc.eu-central-1.aliyuncs.com", + }, + Zhangjiakou: { + "ecs": "https://ecs.cn-zhangjiakou.aliyuncs.com", + "slb": "https://slb.cn-zhangjiakou.aliyuncs.com", + "rds": "https://rds.cn-zhangjiakou.aliyuncs.com", + "vpc": "https://vpc.cn-zhangjiakou.aliyuncs.com", + }, + Huhehaote: { + "ecs": "https://ecs.cn-huhehaote.aliyuncs.com", + "slb": "https://slb.cn-huhehaote.aliyuncs.com", + "rds": "https://rds.cn-huhehaote.aliyuncs.com", + "vpc": "https://vpc.cn-huhehaote.aliyuncs.com", + }, + } ) //init endpoints from file @@ -25,18 +70,39 @@ func init() { } -func NewLocationClient(accessKeyId, accessKeySecret string) *Client { +type LocationClient struct { + Client +} + +func NewLocationClient(accessKeyId, accessKeySecret, securityToken string) *LocationClient { endpoint := os.Getenv("LOCATION_ENDPOINT") if endpoint == "" { endpoint = locationDefaultEndpoint } - client := &Client{} + client := &LocationClient{} client.Init(endpoint, locationAPIVersion, accessKeyId, accessKeySecret) + client.securityToken = securityToken return client } -func (client *Client) DescribeEndpoint(args *DescribeEndpointArgs) (*DescribeEndpointResponse, error) { +func NewLocationClientWithSecurityToken(accessKeyId, accessKeySecret, securityToken string) *LocationClient { + endpoint := os.Getenv("LOCATION_ENDPOINT") + if endpoint == "" { + endpoint = locationDefaultEndpoint + } + + client := &LocationClient{} + client.WithEndpoint(endpoint). + WithVersion(locationAPIVersion). + WithAccessKeyId(accessKeyId). + WithAccessKeySecret(accessKeySecret). + WithSecurityToken(securityToken). + InitClient() + return client +} + +func (client *LocationClient) DescribeEndpoint(args *DescribeEndpointArgs) (*DescribeEndpointResponse, error) { response := &DescribeEndpointResponse{} err := client.Invoke("DescribeEndpoint", args, response) if err != nil { @@ -45,6 +111,15 @@ func (client *Client) DescribeEndpoint(args *DescribeEndpointArgs) (*DescribeEnd return response, err } +func (client *LocationClient) DescribeEndpoints(args *DescribeEndpointsArgs) (*DescribeEndpointsResponse, error) { + response := &DescribeEndpointsResponse{} + err := client.Invoke("DescribeEndpoints", args, response) + if err != nil { + return nil, err + } + return response, err +} + func getProductRegionEndpoint(region Region, serviceCode string) string { if sp, ok := endpoints[region]; ok { if endpoint, ok := sp[serviceCode]; ok { @@ -61,34 +136,34 @@ func setProductRegionEndpoint(region Region, serviceCode string, endpoint string } } -func (client *Client) DescribeOpenAPIEndpoint(region Region, serviceCode string) string { +func (client *LocationClient) DescribeOpenAPIEndpoint(region Region, serviceCode string) string { if endpoint := getProductRegionEndpoint(region, serviceCode); endpoint != "" { return endpoint } defaultProtocols := HTTP_PROTOCOL - args := &DescribeEndpointArgs{ + args := &DescribeEndpointsArgs{ Id: region, ServiceCode: serviceCode, Type: "openAPI", } - endpoint, err := client.DescribeEndpoint(args) - if err != nil || endpoint.Endpoint == "" { + endpoint, err := client.DescribeEndpoints(args) + if err != nil || len(endpoint.Endpoints.Endpoint) <= 0 { return "" } - for _, protocol := range endpoint.Protocols.Protocols { + for _, protocol := range endpoint.Endpoints.Endpoint[0].Protocols.Protocols { if strings.ToLower(protocol) == HTTPS_PROTOCOL { defaultProtocols = HTTPS_PROTOCOL break } } - ep := fmt.Sprintf("%s://%s", defaultProtocols, endpoint.Endpoint) + ep := fmt.Sprintf("%s://%s", defaultProtocols, endpoint.Endpoints.Endpoint[0].Endpoint) - setProductRegionEndpoint(region, serviceCode, ep) + //setProductRegionEndpoint(region, serviceCode, ep) return ep } @@ -97,13 +172,11 @@ func loadEndpointFromFile(region Region, serviceCode string) string { if err != nil { return "" } - var endpoints Endpoints err = xml.Unmarshal(data, &endpoints) if err != nil { return "" } - for _, endpoint := range endpoints.Endpoint { if endpoint.RegionIds.RegionId == string(region) { for _, product := range endpoint.Products.Product { diff --git a/vendor/github.com/denverdino/aliyungo/common/endpoints.xml b/vendor/github.com/denverdino/aliyungo/common/endpoints.xml index 4079bcd2b..21f3a0b2e 100644 --- a/vendor/github.com/denverdino/aliyungo/common/endpoints.xml +++ b/vendor/github.com/denverdino/aliyungo/common/endpoints.xml @@ -1346,4 +1346,14 @@ Slbslb.cn-zhangjiakou.aliyuncs.com + + cn-huhehaote + + Rdsrds.cn-huhehaote.aliyuncs.com + Ecsecs.cn-huhehaote.aliyuncs.com + Vpcvpc.cn-huhehaote.aliyuncs.com + Cmsmetrics.cn-hangzhou.aliyuncs.com + Slbslb.cn-huhehaote.aliyuncs.com + + diff --git a/vendor/github.com/denverdino/aliyungo/common/regions.go b/vendor/github.com/denverdino/aliyungo/common/regions.go index c87fe0691..38b14dd86 100644 --- a/vendor/github.com/denverdino/aliyungo/common/regions.go +++ b/vendor/github.com/denverdino/aliyungo/common/regions.go @@ -12,11 +12,15 @@ const ( Shenzhen = Region("cn-shenzhen") Shanghai = Region("cn-shanghai") Zhangjiakou = Region("cn-zhangjiakou") + Huhehaote = Region("cn-huhehaote") APSouthEast1 = Region("ap-southeast-1") APNorthEast1 = Region("ap-northeast-1") APSouthEast2 = Region("ap-southeast-2") APSouthEast3 = Region("ap-southeast-3") + APSouthEast5 = Region("ap-southeast-5") + + APSouth1 = Region("ap-south-1") USWest1 = Region("us-west-1") USEast1 = Region("us-east-1") @@ -24,12 +28,17 @@ const ( MEEast1 = Region("me-east-1") EUCentral1 = Region("eu-central-1") + + ShenZhenFinance = Region("cn-shenzhen-finance-1") + ShanghaiFinance = Region("cn-shanghai-finance-1") ) var ValidRegions = []Region{ - Hangzhou, Qingdao, Beijing, Shenzhen, Hongkong, Shanghai, Zhangjiakou, + Hangzhou, Qingdao, Beijing, Shenzhen, Hongkong, Shanghai, Zhangjiakou, Huhehaote, USWest1, USEast1, - APNorthEast1, APSouthEast1, APSouthEast2, APSouthEast3, + APNorthEast1, APSouthEast1, APSouthEast2, APSouthEast3, APSouthEast5, + APSouth1, MEEast1, EUCentral1, + ShenZhenFinance, ShanghaiFinance, } diff --git a/vendor/github.com/denverdino/aliyungo/common/types.go b/vendor/github.com/denverdino/aliyungo/common/types.go index a74e150e9..cf161f11b 100644 --- a/vendor/github.com/denverdino/aliyungo/common/types.go +++ b/vendor/github.com/denverdino/aliyungo/common/types.go @@ -36,6 +36,23 @@ type DescribeEndpointResponse struct { EndpointItem } +type DescribeEndpointsArgs struct { + Id Region + ServiceCode string + Type string +} + +type DescribeEndpointsResponse struct { + Response + Endpoints APIEndpoints + RequestId string + Success bool +} + +type APIEndpoints struct { + Endpoint []EndpointItem +} + type NetType string const ( @@ -48,6 +65,7 @@ type TimeType string const ( Hour = TimeType("Hour") Day = TimeType("Day") + Week = TimeType("Week") Month = TimeType("Month") Year = TimeType("Year") ) diff --git a/vendor/github.com/Sirupsen/logrus/CHANGELOG.md b/vendor/github.com/sirupsen/logrus/CHANGELOG.md similarity index 100% rename from vendor/github.com/Sirupsen/logrus/CHANGELOG.md rename to vendor/github.com/sirupsen/logrus/CHANGELOG.md diff --git a/vendor/github.com/Sirupsen/logrus/LICENSE b/vendor/github.com/sirupsen/logrus/LICENSE similarity index 100% rename from vendor/github.com/Sirupsen/logrus/LICENSE rename to vendor/github.com/sirupsen/logrus/LICENSE diff --git a/vendor/github.com/Sirupsen/logrus/README.md b/vendor/github.com/sirupsen/logrus/README.md similarity index 100% rename from vendor/github.com/Sirupsen/logrus/README.md rename to vendor/github.com/sirupsen/logrus/README.md diff --git a/vendor/github.com/Sirupsen/logrus/alt_exit.go b/vendor/github.com/sirupsen/logrus/alt_exit.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/alt_exit.go rename to vendor/github.com/sirupsen/logrus/alt_exit.go diff --git a/vendor/github.com/Sirupsen/logrus/doc.go b/vendor/github.com/sirupsen/logrus/doc.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/doc.go rename to vendor/github.com/sirupsen/logrus/doc.go diff --git a/vendor/github.com/Sirupsen/logrus/entry.go b/vendor/github.com/sirupsen/logrus/entry.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/entry.go rename to vendor/github.com/sirupsen/logrus/entry.go diff --git a/vendor/github.com/Sirupsen/logrus/exported.go b/vendor/github.com/sirupsen/logrus/exported.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/exported.go rename to vendor/github.com/sirupsen/logrus/exported.go diff --git a/vendor/github.com/Sirupsen/logrus/formatter.go b/vendor/github.com/sirupsen/logrus/formatter.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/formatter.go rename to vendor/github.com/sirupsen/logrus/formatter.go diff --git a/vendor/github.com/Sirupsen/logrus/hooks.go b/vendor/github.com/sirupsen/logrus/hooks.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/hooks.go rename to vendor/github.com/sirupsen/logrus/hooks.go diff --git a/vendor/github.com/Sirupsen/logrus/json_formatter.go b/vendor/github.com/sirupsen/logrus/json_formatter.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/json_formatter.go rename to vendor/github.com/sirupsen/logrus/json_formatter.go diff --git a/vendor/github.com/Sirupsen/logrus/logger.go b/vendor/github.com/sirupsen/logrus/logger.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/logger.go rename to vendor/github.com/sirupsen/logrus/logger.go diff --git a/vendor/github.com/Sirupsen/logrus/logrus.go b/vendor/github.com/sirupsen/logrus/logrus.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/logrus.go rename to vendor/github.com/sirupsen/logrus/logrus.go diff --git a/vendor/github.com/Sirupsen/logrus/terminal_bsd.go b/vendor/github.com/sirupsen/logrus/terminal_bsd.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/terminal_bsd.go rename to vendor/github.com/sirupsen/logrus/terminal_bsd.go diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_appengine.go b/vendor/github.com/sirupsen/logrus/terminal_check_appengine.go deleted file mode 100644 index 3de08e802..000000000 --- a/vendor/github.com/sirupsen/logrus/terminal_check_appengine.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build appengine gopherjs - -package logrus - -import ( - "io" -) - -func checkIfTerminal(w io.Writer) bool { - return true -} diff --git a/vendor/github.com/Sirupsen/logrus/terminal_linux.go b/vendor/github.com/sirupsen/logrus/terminal_linux.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/terminal_linux.go rename to vendor/github.com/sirupsen/logrus/terminal_linux.go diff --git a/vendor/github.com/Sirupsen/logrus/text_formatter.go b/vendor/github.com/sirupsen/logrus/text_formatter.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/text_formatter.go rename to vendor/github.com/sirupsen/logrus/text_formatter.go diff --git a/vendor/github.com/Sirupsen/logrus/writer.go b/vendor/github.com/sirupsen/logrus/writer.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/writer.go rename to vendor/github.com/sirupsen/logrus/writer.go diff --git a/vendor/github.com/stretchr/objx/Gopkg.lock b/vendor/github.com/stretchr/objx/Gopkg.lock new file mode 100644 index 000000000..1f5739c91 --- /dev/null +++ b/vendor/github.com/stretchr/objx/Gopkg.lock @@ -0,0 +1,27 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/davecgh/go-spew" + packages = ["spew"] + revision = "346938d642f2ec3594ed81d874461961cd0faa76" + version = "v1.1.0" + +[[projects]] + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + +[[projects]] + name = "github.com/stretchr/testify" + packages = ["assert"] + revision = "b91bfb9ebec76498946beb6af7c0230c7cc7ba6c" + version = "v1.2.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "50e2495ec1af6e2f7ffb2f3551e4300d30357d7c7fe38ff6056469fa9cfb3673" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/vendor/github.com/stretchr/objx/Gopkg.toml b/vendor/github.com/stretchr/objx/Gopkg.toml new file mode 100644 index 000000000..f87e18eb5 --- /dev/null +++ b/vendor/github.com/stretchr/objx/Gopkg.toml @@ -0,0 +1,3 @@ +[[constraint]] + name = "github.com/stretchr/testify" + version = "~1.2.0" diff --git a/vendor/github.com/stretchr/objx/LICENSE b/vendor/github.com/stretchr/objx/LICENSE new file mode 100644 index 000000000..44d4d9d5a --- /dev/null +++ b/vendor/github.com/stretchr/objx/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2014 Stretchr, Inc. +Copyright (c) 2017-2018 objx contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/stretchr/objx/README.md b/vendor/github.com/stretchr/objx/README.md new file mode 100644 index 000000000..4e2400eb1 --- /dev/null +++ b/vendor/github.com/stretchr/objx/README.md @@ -0,0 +1,78 @@ +# Objx +[![Build Status](https://travis-ci.org/stretchr/objx.svg?branch=master)](https://travis-ci.org/stretchr/objx) +[![Go Report Card](https://goreportcard.com/badge/github.com/stretchr/objx)](https://goreportcard.com/report/github.com/stretchr/objx) +[![Sourcegraph](https://sourcegraph.com/github.com/stretchr/objx/-/badge.svg)](https://sourcegraph.com/github.com/stretchr/objx) +[![GoDoc](https://godoc.org/github.com/stretchr/objx?status.svg)](https://godoc.org/github.com/stretchr/objx) + +Objx - Go package for dealing with maps, slices, JSON and other data. + +Get started: + +- Install Objx with [one line of code](#installation), or [update it with another](#staying-up-to-date) +- Check out the API Documentation http://godoc.org/github.com/stretchr/objx + +## Overview +Objx provides the `objx.Map` type, which is a `map[string]interface{}` that exposes a powerful `Get` method (among others) that allows you to easily and quickly get access to data within the map, without having to worry too much about type assertions, missing data, default values etc. + +### Pattern +Objx uses a preditable pattern to make access data from within `map[string]interface{}` easy. Call one of the `objx.` functions to create your `objx.Map` to get going: + + m, err := objx.FromJSON(json) + +NOTE: Any methods or functions with the `Must` prefix will panic if something goes wrong, the rest will be optimistic and try to figure things out without panicking. + +Use `Get` to access the value you're interested in. You can use dot and array +notation too: + + m.Get("places[0].latlng") + +Once you have sought the `Value` you're interested in, you can use the `Is*` methods to determine its type. + + if m.Get("code").IsStr() { // Your code... } + +Or you can just assume the type, and use one of the strong type methods to extract the real value: + + m.Get("code").Int() + +If there's no value there (or if it's the wrong type) then a default value will be returned, or you can be explicit about the default value. + + Get("code").Int(-1) + +If you're dealing with a slice of data as a value, Objx provides many useful methods for iterating, manipulating and selecting that data. You can find out more by exploring the index below. + +### Reading data +A simple example of how to use Objx: + + // Use MustFromJSON to make an objx.Map from some JSON + m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`) + + // Get the details + name := m.Get("name").Str() + age := m.Get("age").Int() + + // Get their nickname (or use their name if they don't have one) + nickname := m.Get("nickname").Str(name) + +### Ranging +Since `objx.Map` is a `map[string]interface{}` you can treat it as such. For example, to `range` the data, do what you would expect: + + m := objx.MustFromJSON(json) + for key, value := range m { + // Your code... + } + +## Installation +To install Objx, use go get: + + go get github.com/stretchr/objx + +### Staying up to date +To update Objx to the latest version, run: + + go get -u github.com/stretchr/objx + +### Supported go versions +We support the lastest two major Go versions, which are 1.8 and 1.9 at the moment. + +## Contributing +Please feel free to submit issues, fork the repository and send pull requests! diff --git a/vendor/github.com/stretchr/objx/Taskfile.yml b/vendor/github.com/stretchr/objx/Taskfile.yml new file mode 100644 index 000000000..403b5f06e --- /dev/null +++ b/vendor/github.com/stretchr/objx/Taskfile.yml @@ -0,0 +1,26 @@ +default: + deps: [test] + +dl-deps: + desc: Downloads cli dependencies + cmds: + - go get -u github.com/golang/lint/golint + - go get -u github.com/golang/dep/cmd/dep + +update-deps: + desc: Updates dependencies + cmds: + - dep ensure + - dep ensure -update + - dep prune + +lint: + desc: Runs golint + cmds: + - golint $(ls *.go | grep -v "doc.go") + silent: true + +test: + desc: Runs go tests + cmds: + - go test -race . diff --git a/vendor/github.com/stretchr/objx/accessors.go b/vendor/github.com/stretchr/objx/accessors.go new file mode 100644 index 000000000..d95be0ca9 --- /dev/null +++ b/vendor/github.com/stretchr/objx/accessors.go @@ -0,0 +1,171 @@ +package objx + +import ( + "fmt" + "regexp" + "strconv" + "strings" +) + +// arrayAccesRegexString is the regex used to extract the array number +// from the access path +const arrayAccesRegexString = `^(.+)\[([0-9]+)\]$` + +// arrayAccesRegex is the compiled arrayAccesRegexString +var arrayAccesRegex = regexp.MustCompile(arrayAccesRegexString) + +// Get gets the value using the specified selector and +// returns it inside a new Obj object. +// +// If it cannot find the value, Get will return a nil +// value inside an instance of Obj. +// +// Get can only operate directly on map[string]interface{} and []interface. +// +// Example +// +// To access the title of the third chapter of the second book, do: +// +// o.Get("books[1].chapters[2].title") +func (m Map) Get(selector string) *Value { + rawObj := access(m, selector, nil, false, false) + return &Value{data: rawObj} +} + +// Set sets the value using the specified selector and +// returns the object on which Set was called. +// +// Set can only operate directly on map[string]interface{} and []interface +// +// Example +// +// To set the title of the third chapter of the second book, do: +// +// o.Set("books[1].chapters[2].title","Time to Go") +func (m Map) Set(selector string, value interface{}) Map { + access(m, selector, value, true, false) + return m +} + +// access accesses the object using the selector and performs the +// appropriate action. +func access(current, selector, value interface{}, isSet, panics bool) interface{} { + + switch selector.(type) { + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + + if array, ok := current.([]interface{}); ok { + index := intFromInterface(selector) + + if index >= len(array) { + if panics { + panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array))) + } + return nil + } + + return array[index] + } + + return nil + + case string: + + selStr := selector.(string) + selSegs := strings.SplitN(selStr, PathSeparator, 2) + thisSel := selSegs[0] + index := -1 + var err error + + if strings.Contains(thisSel, "[") { + arrayMatches := arrayAccesRegex.FindStringSubmatch(thisSel) + + if len(arrayMatches) > 0 { + // Get the key into the map + thisSel = arrayMatches[1] + + // Get the index into the array at the key + index, err = strconv.Atoi(arrayMatches[2]) + + if err != nil { + // This should never happen. If it does, something has gone + // seriously wrong. Panic. + panic("objx: Array index is not an integer. Must use array[int].") + } + } + } + + if curMap, ok := current.(Map); ok { + current = map[string]interface{}(curMap) + } + + // get the object in question + switch current.(type) { + case map[string]interface{}: + curMSI := current.(map[string]interface{}) + if len(selSegs) <= 1 && isSet { + curMSI[thisSel] = value + return nil + } + current = curMSI[thisSel] + default: + current = nil + } + + if current == nil && panics { + panic(fmt.Sprintf("objx: '%v' invalid on object.", selector)) + } + + // do we need to access the item of an array? + if index > -1 { + if array, ok := current.([]interface{}); ok { + if index < len(array) { + current = array[index] + } else { + if panics { + panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array))) + } + current = nil + } + } + } + + if len(selSegs) > 1 { + current = access(current, selSegs[1], value, isSet, panics) + } + + } + return current +} + +// intFromInterface converts an interface object to the largest +// representation of an unsigned integer using a type switch and +// assertions +func intFromInterface(selector interface{}) int { + var value int + switch selector.(type) { + case int: + value = selector.(int) + case int8: + value = int(selector.(int8)) + case int16: + value = int(selector.(int16)) + case int32: + value = int(selector.(int32)) + case int64: + value = int(selector.(int64)) + case uint: + value = int(selector.(uint)) + case uint8: + value = int(selector.(uint8)) + case uint16: + value = int(selector.(uint16)) + case uint32: + value = int(selector.(uint32)) + case uint64: + value = int(selector.(uint64)) + default: + panic("objx: array access argument is not an integer type (this should never happen)") + } + return value +} diff --git a/vendor/github.com/stretchr/objx/constants.go b/vendor/github.com/stretchr/objx/constants.go new file mode 100644 index 000000000..f9eb42a25 --- /dev/null +++ b/vendor/github.com/stretchr/objx/constants.go @@ -0,0 +1,13 @@ +package objx + +const ( + // PathSeparator is the character used to separate the elements + // of the keypath. + // + // For example, `location.address.city` + PathSeparator string = "." + + // SignatureSeparator is the character that is used to + // separate the Base64 string from the security signature. + SignatureSeparator = "_" +) diff --git a/vendor/github.com/stretchr/objx/conversions.go b/vendor/github.com/stretchr/objx/conversions.go new file mode 100644 index 000000000..5e020f310 --- /dev/null +++ b/vendor/github.com/stretchr/objx/conversions.go @@ -0,0 +1,108 @@ +package objx + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "net/url" +) + +// JSON converts the contained object to a JSON string +// representation +func (m Map) JSON() (string, error) { + result, err := json.Marshal(m) + if err != nil { + err = errors.New("objx: JSON encode failed with: " + err.Error()) + } + return string(result), err +} + +// MustJSON converts the contained object to a JSON string +// representation and panics if there is an error +func (m Map) MustJSON() string { + result, err := m.JSON() + if err != nil { + panic(err.Error()) + } + return result +} + +// Base64 converts the contained object to a Base64 string +// representation of the JSON string representation +func (m Map) Base64() (string, error) { + var buf bytes.Buffer + + jsonData, err := m.JSON() + if err != nil { + return "", err + } + + encoder := base64.NewEncoder(base64.StdEncoding, &buf) + _, err = encoder.Write([]byte(jsonData)) + if err != nil { + return "", err + } + _ = encoder.Close() + + return buf.String(), nil +} + +// MustBase64 converts the contained object to a Base64 string +// representation of the JSON string representation and panics +// if there is an error +func (m Map) MustBase64() string { + result, err := m.Base64() + if err != nil { + panic(err.Error()) + } + return result +} + +// SignedBase64 converts the contained object to a Base64 string +// representation of the JSON string representation and signs it +// using the provided key. +func (m Map) SignedBase64(key string) (string, error) { + base64, err := m.Base64() + if err != nil { + return "", err + } + + sig := HashWithKey(base64, key) + return base64 + SignatureSeparator + sig, nil +} + +// MustSignedBase64 converts the contained object to a Base64 string +// representation of the JSON string representation and signs it +// using the provided key and panics if there is an error +func (m Map) MustSignedBase64(key string) string { + result, err := m.SignedBase64(key) + if err != nil { + panic(err.Error()) + } + return result +} + +/* + URL Query + ------------------------------------------------ +*/ + +// URLValues creates a url.Values object from an Obj. This +// function requires that the wrapped object be a map[string]interface{} +func (m Map) URLValues() url.Values { + vals := make(url.Values) + for k, v := range m { + //TODO: can this be done without sprintf? + vals.Set(k, fmt.Sprintf("%v", v)) + } + return vals +} + +// URLQuery gets an encoded URL query representing the given +// Obj. This function requires that the wrapped object be a +// map[string]interface{} +func (m Map) URLQuery() (string, error) { + return m.URLValues().Encode(), nil +} diff --git a/vendor/github.com/stretchr/objx/doc.go b/vendor/github.com/stretchr/objx/doc.go new file mode 100644 index 000000000..6d6af1a83 --- /dev/null +++ b/vendor/github.com/stretchr/objx/doc.go @@ -0,0 +1,66 @@ +/* +Objx - Go package for dealing with maps, slices, JSON and other data. + +Overview + +Objx provides the `objx.Map` type, which is a `map[string]interface{}` that exposes +a powerful `Get` method (among others) that allows you to easily and quickly get +access to data within the map, without having to worry too much about type assertions, +missing data, default values etc. + +Pattern + +Objx uses a preditable pattern to make access data from within `map[string]interface{}` easy. +Call one of the `objx.` functions to create your `objx.Map` to get going: + + m, err := objx.FromJSON(json) + +NOTE: Any methods or functions with the `Must` prefix will panic if something goes wrong, +the rest will be optimistic and try to figure things out without panicking. + +Use `Get` to access the value you're interested in. You can use dot and array +notation too: + + m.Get("places[0].latlng") + +Once you have sought the `Value` you're interested in, you can use the `Is*` methods to determine its type. + + if m.Get("code").IsStr() { // Your code... } + +Or you can just assume the type, and use one of the strong type methods to extract the real value: + + m.Get("code").Int() + +If there's no value there (or if it's the wrong type) then a default value will be returned, +or you can be explicit about the default value. + + Get("code").Int(-1) + +If you're dealing with a slice of data as a value, Objx provides many useful methods for iterating, +manipulating and selecting that data. You can find out more by exploring the index below. + +Reading data + +A simple example of how to use Objx: + + // Use MustFromJSON to make an objx.Map from some JSON + m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`) + + // Get the details + name := m.Get("name").Str() + age := m.Get("age").Int() + + // Get their nickname (or use their name if they don't have one) + nickname := m.Get("nickname").Str(name) + +Ranging + +Since `objx.Map` is a `map[string]interface{}` you can treat it as such. +For example, to `range` the data, do what you would expect: + + m := objx.MustFromJSON(json) + for key, value := range m { + // Your code... + } +*/ +package objx diff --git a/vendor/github.com/stretchr/objx/map.go b/vendor/github.com/stretchr/objx/map.go new file mode 100644 index 000000000..7e9389a20 --- /dev/null +++ b/vendor/github.com/stretchr/objx/map.go @@ -0,0 +1,193 @@ +package objx + +import ( + "encoding/base64" + "encoding/json" + "errors" + "io/ioutil" + "net/url" + "strings" +) + +// MSIConvertable is an interface that defines methods for converting your +// custom types to a map[string]interface{} representation. +type MSIConvertable interface { + // MSI gets a map[string]interface{} (msi) representing the + // object. + MSI() map[string]interface{} +} + +// Map provides extended functionality for working with +// untyped data, in particular map[string]interface (msi). +type Map map[string]interface{} + +// Value returns the internal value instance +func (m Map) Value() *Value { + return &Value{data: m} +} + +// Nil represents a nil Map. +var Nil = New(nil) + +// New creates a new Map containing the map[string]interface{} in the data argument. +// If the data argument is not a map[string]interface, New attempts to call the +// MSI() method on the MSIConvertable interface to create one. +func New(data interface{}) Map { + if _, ok := data.(map[string]interface{}); !ok { + if converter, ok := data.(MSIConvertable); ok { + data = converter.MSI() + } else { + return nil + } + } + return Map(data.(map[string]interface{})) +} + +// MSI creates a map[string]interface{} and puts it inside a new Map. +// +// The arguments follow a key, value pattern. +// +// Panics +// +// Panics if any key argument is non-string or if there are an odd number of arguments. +// +// Example +// +// To easily create Maps: +// +// m := objx.MSI("name", "Mat", "age", 29, "subobj", objx.MSI("active", true)) +// +// // creates an Map equivalent to +// m := objx.New(map[string]interface{}{"name": "Mat", "age": 29, "subobj": map[string]interface{}{"active": true}}) +func MSI(keyAndValuePairs ...interface{}) Map { + newMap := make(map[string]interface{}) + keyAndValuePairsLen := len(keyAndValuePairs) + if keyAndValuePairsLen%2 != 0 { + panic("objx: MSI must have an even number of arguments following the 'key, value' pattern.") + } + + for i := 0; i < keyAndValuePairsLen; i = i + 2 { + key := keyAndValuePairs[i] + value := keyAndValuePairs[i+1] + + // make sure the key is a string + keyString, keyStringOK := key.(string) + if !keyStringOK { + panic("objx: MSI must follow 'string, interface{}' pattern. " + keyString + " is not a valid key.") + } + newMap[keyString] = value + } + return New(newMap) +} + +// ****** Conversion Constructors + +// MustFromJSON creates a new Map containing the data specified in the +// jsonString. +// +// Panics if the JSON is invalid. +func MustFromJSON(jsonString string) Map { + o, err := FromJSON(jsonString) + if err != nil { + panic("objx: MustFromJSON failed with error: " + err.Error()) + } + return o +} + +// FromJSON creates a new Map containing the data specified in the +// jsonString. +// +// Returns an error if the JSON is invalid. +func FromJSON(jsonString string) (Map, error) { + var data interface{} + err := json.Unmarshal([]byte(jsonString), &data) + if err != nil { + return Nil, err + } + return New(data), nil +} + +// FromBase64 creates a new Obj containing the data specified +// in the Base64 string. +// +// The string is an encoded JSON string returned by Base64 +func FromBase64(base64String string) (Map, error) { + decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64String)) + decoded, err := ioutil.ReadAll(decoder) + if err != nil { + return nil, err + } + return FromJSON(string(decoded)) +} + +// MustFromBase64 creates a new Obj containing the data specified +// in the Base64 string and panics if there is an error. +// +// The string is an encoded JSON string returned by Base64 +func MustFromBase64(base64String string) Map { + result, err := FromBase64(base64String) + if err != nil { + panic("objx: MustFromBase64 failed with error: " + err.Error()) + } + return result +} + +// FromSignedBase64 creates a new Obj containing the data specified +// in the Base64 string. +// +// The string is an encoded JSON string returned by SignedBase64 +func FromSignedBase64(base64String, key string) (Map, error) { + parts := strings.Split(base64String, SignatureSeparator) + if len(parts) != 2 { + return nil, errors.New("objx: Signed base64 string is malformed") + } + + sig := HashWithKey(parts[0], key) + if parts[1] != sig { + return nil, errors.New("objx: Signature for base64 data does not match") + } + return FromBase64(parts[0]) +} + +// MustFromSignedBase64 creates a new Obj containing the data specified +// in the Base64 string and panics if there is an error. +// +// The string is an encoded JSON string returned by Base64 +func MustFromSignedBase64(base64String, key string) Map { + result, err := FromSignedBase64(base64String, key) + if err != nil { + panic("objx: MustFromSignedBase64 failed with error: " + err.Error()) + } + return result +} + +// FromURLQuery generates a new Obj by parsing the specified +// query. +// +// For queries with multiple values, the first value is selected. +func FromURLQuery(query string) (Map, error) { + vals, err := url.ParseQuery(query) + if err != nil { + return nil, err + } + + m := make(map[string]interface{}) + for k, vals := range vals { + m[k] = vals[0] + } + return New(m), nil +} + +// MustFromURLQuery generates a new Obj by parsing the specified +// query. +// +// For queries with multiple values, the first value is selected. +// +// Panics if it encounters an error +func MustFromURLQuery(query string) Map { + o, err := FromURLQuery(query) + if err != nil { + panic("objx: MustFromURLQuery failed with error: " + err.Error()) + } + return o +} diff --git a/vendor/github.com/stretchr/objx/mutations.go b/vendor/github.com/stretchr/objx/mutations.go new file mode 100644 index 000000000..e7b8eb794 --- /dev/null +++ b/vendor/github.com/stretchr/objx/mutations.go @@ -0,0 +1,74 @@ +package objx + +// Exclude returns a new Map with the keys in the specified []string +// excluded. +func (m Map) Exclude(exclude []string) Map { + excluded := make(Map) + for k, v := range m { + var shouldInclude = true + for _, toExclude := range exclude { + if k == toExclude { + shouldInclude = false + break + } + } + if shouldInclude { + excluded[k] = v + } + } + return excluded +} + +// Copy creates a shallow copy of the Obj. +func (m Map) Copy() Map { + copied := make(map[string]interface{}) + for k, v := range m { + copied[k] = v + } + return New(copied) +} + +// Merge blends the specified map with a copy of this map and returns the result. +// +// Keys that appear in both will be selected from the specified map. +// This method requires that the wrapped object be a map[string]interface{} +func (m Map) Merge(merge Map) Map { + return m.Copy().MergeHere(merge) +} + +// MergeHere blends the specified map with this map and returns the current map. +// +// Keys that appear in both will be selected from the specified map. The original map +// will be modified. This method requires that +// the wrapped object be a map[string]interface{} +func (m Map) MergeHere(merge Map) Map { + for k, v := range merge { + m[k] = v + } + return m +} + +// Transform builds a new Obj giving the transformer a chance +// to change the keys and values as it goes. This method requires that +// the wrapped object be a map[string]interface{} +func (m Map) Transform(transformer func(key string, value interface{}) (string, interface{})) Map { + newMap := make(map[string]interface{}) + for k, v := range m { + modifiedKey, modifiedVal := transformer(k, v) + newMap[modifiedKey] = modifiedVal + } + return New(newMap) +} + +// TransformKeys builds a new map using the specified key mapping. +// +// Unspecified keys will be unaltered. +// This method requires that the wrapped object be a map[string]interface{} +func (m Map) TransformKeys(mapping map[string]string) Map { + return m.Transform(func(key string, value interface{}) (string, interface{}) { + if newKey, ok := mapping[key]; ok { + return newKey, value + } + return key, value + }) +} diff --git a/vendor/github.com/stretchr/objx/security.go b/vendor/github.com/stretchr/objx/security.go new file mode 100644 index 000000000..e052ff890 --- /dev/null +++ b/vendor/github.com/stretchr/objx/security.go @@ -0,0 +1,17 @@ +package objx + +import ( + "crypto/sha1" + "encoding/hex" +) + +// HashWithKey hashes the specified string using the security +// key. +func HashWithKey(data, key string) string { + hash := sha1.New() + _, err := hash.Write([]byte(data + ":" + key)) + if err != nil { + return "" + } + return hex.EncodeToString(hash.Sum(nil)) +} diff --git a/vendor/github.com/stretchr/objx/tests.go b/vendor/github.com/stretchr/objx/tests.go new file mode 100644 index 000000000..d9e0b479a --- /dev/null +++ b/vendor/github.com/stretchr/objx/tests.go @@ -0,0 +1,17 @@ +package objx + +// Has gets whether there is something at the specified selector +// or not. +// +// If m is nil, Has will always return false. +func (m Map) Has(selector string) bool { + if m == nil { + return false + } + return !m.Get(selector).IsNil() +} + +// IsNil gets whether the data is nil or not. +func (v *Value) IsNil() bool { + return v == nil || v.data == nil +} diff --git a/vendor/github.com/stretchr/objx/type_specific_codegen.go b/vendor/github.com/stretchr/objx/type_specific_codegen.go new file mode 100644 index 000000000..202a91f8c --- /dev/null +++ b/vendor/github.com/stretchr/objx/type_specific_codegen.go @@ -0,0 +1,2501 @@ +package objx + +/* + Inter (interface{} and []interface{}) +*/ + +// Inter gets the value as a interface{}, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Inter(optionalDefault ...interface{}) interface{} { + if s, ok := v.data.(interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInter gets the value as a interface{}. +// +// Panics if the object is not a interface{}. +func (v *Value) MustInter() interface{} { + return v.data.(interface{}) +} + +// InterSlice gets the value as a []interface{}, returns the optionalDefault +// value or nil if the value is not a []interface{}. +func (v *Value) InterSlice(optionalDefault ...[]interface{}) []interface{} { + if s, ok := v.data.([]interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInterSlice gets the value as a []interface{}. +// +// Panics if the object is not a []interface{}. +func (v *Value) MustInterSlice() []interface{} { + return v.data.([]interface{}) +} + +// IsInter gets whether the object contained is a interface{} or not. +func (v *Value) IsInter() bool { + _, ok := v.data.(interface{}) + return ok +} + +// IsInterSlice gets whether the object contained is a []interface{} or not. +func (v *Value) IsInterSlice() bool { + _, ok := v.data.([]interface{}) + return ok +} + +// EachInter calls the specified callback for each object +// in the []interface{}. +// +// Panics if the object is the wrong type. +func (v *Value) EachInter(callback func(int, interface{}) bool) *Value { + for index, val := range v.MustInterSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInter uses the specified decider function to select items +// from the []interface{}. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInter(decider func(int, interface{}) bool) *Value { + var selected []interface{} + v.EachInter(func(index int, val interface{}) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInter uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]interface{}. +func (v *Value) GroupInter(grouper func(int, interface{}) string) *Value { + groups := make(map[string][]interface{}) + v.EachInter(func(index int, val interface{}) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]interface{}, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInter uses the specified function to replace each interface{}s +// by iterating each item. The data in the returned result will be a +// []interface{} containing the replaced items. +func (v *Value) ReplaceInter(replacer func(int, interface{}) interface{}) *Value { + arr := v.MustInterSlice() + replaced := make([]interface{}, len(arr)) + v.EachInter(func(index int, val interface{}) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInter uses the specified collector function to collect a value +// for each of the interface{}s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInter(collector func(int, interface{}) interface{}) *Value { + arr := v.MustInterSlice() + collected := make([]interface{}, len(arr)) + v.EachInter(func(index int, val interface{}) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + MSI (map[string]interface{} and []map[string]interface{}) +*/ + +// MSI gets the value as a map[string]interface{}, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) MSI(optionalDefault ...map[string]interface{}) map[string]interface{} { + if s, ok := v.data.(map[string]interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustMSI gets the value as a map[string]interface{}. +// +// Panics if the object is not a map[string]interface{}. +func (v *Value) MustMSI() map[string]interface{} { + return v.data.(map[string]interface{}) +} + +// MSISlice gets the value as a []map[string]interface{}, returns the optionalDefault +// value or nil if the value is not a []map[string]interface{}. +func (v *Value) MSISlice(optionalDefault ...[]map[string]interface{}) []map[string]interface{} { + if s, ok := v.data.([]map[string]interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustMSISlice gets the value as a []map[string]interface{}. +// +// Panics if the object is not a []map[string]interface{}. +func (v *Value) MustMSISlice() []map[string]interface{} { + return v.data.([]map[string]interface{}) +} + +// IsMSI gets whether the object contained is a map[string]interface{} or not. +func (v *Value) IsMSI() bool { + _, ok := v.data.(map[string]interface{}) + return ok +} + +// IsMSISlice gets whether the object contained is a []map[string]interface{} or not. +func (v *Value) IsMSISlice() bool { + _, ok := v.data.([]map[string]interface{}) + return ok +} + +// EachMSI calls the specified callback for each object +// in the []map[string]interface{}. +// +// Panics if the object is the wrong type. +func (v *Value) EachMSI(callback func(int, map[string]interface{}) bool) *Value { + for index, val := range v.MustMSISlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereMSI uses the specified decider function to select items +// from the []map[string]interface{}. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereMSI(decider func(int, map[string]interface{}) bool) *Value { + var selected []map[string]interface{} + v.EachMSI(func(index int, val map[string]interface{}) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupMSI uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]map[string]interface{}. +func (v *Value) GroupMSI(grouper func(int, map[string]interface{}) string) *Value { + groups := make(map[string][]map[string]interface{}) + v.EachMSI(func(index int, val map[string]interface{}) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]map[string]interface{}, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceMSI uses the specified function to replace each map[string]interface{}s +// by iterating each item. The data in the returned result will be a +// []map[string]interface{} containing the replaced items. +func (v *Value) ReplaceMSI(replacer func(int, map[string]interface{}) map[string]interface{}) *Value { + arr := v.MustMSISlice() + replaced := make([]map[string]interface{}, len(arr)) + v.EachMSI(func(index int, val map[string]interface{}) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectMSI uses the specified collector function to collect a value +// for each of the map[string]interface{}s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectMSI(collector func(int, map[string]interface{}) interface{}) *Value { + arr := v.MustMSISlice() + collected := make([]interface{}, len(arr)) + v.EachMSI(func(index int, val map[string]interface{}) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + ObjxMap ((Map) and [](Map)) +*/ + +// ObjxMap gets the value as a (Map), returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) ObjxMap(optionalDefault ...(Map)) Map { + if s, ok := v.data.((Map)); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return New(nil) +} + +// MustObjxMap gets the value as a (Map). +// +// Panics if the object is not a (Map). +func (v *Value) MustObjxMap() Map { + return v.data.((Map)) +} + +// ObjxMapSlice gets the value as a [](Map), returns the optionalDefault +// value or nil if the value is not a [](Map). +func (v *Value) ObjxMapSlice(optionalDefault ...[](Map)) [](Map) { + if s, ok := v.data.([](Map)); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustObjxMapSlice gets the value as a [](Map). +// +// Panics if the object is not a [](Map). +func (v *Value) MustObjxMapSlice() [](Map) { + return v.data.([](Map)) +} + +// IsObjxMap gets whether the object contained is a (Map) or not. +func (v *Value) IsObjxMap() bool { + _, ok := v.data.((Map)) + return ok +} + +// IsObjxMapSlice gets whether the object contained is a [](Map) or not. +func (v *Value) IsObjxMapSlice() bool { + _, ok := v.data.([](Map)) + return ok +} + +// EachObjxMap calls the specified callback for each object +// in the [](Map). +// +// Panics if the object is the wrong type. +func (v *Value) EachObjxMap(callback func(int, Map) bool) *Value { + for index, val := range v.MustObjxMapSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereObjxMap uses the specified decider function to select items +// from the [](Map). The object contained in the result will contain +// only the selected items. +func (v *Value) WhereObjxMap(decider func(int, Map) bool) *Value { + var selected [](Map) + v.EachObjxMap(func(index int, val Map) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupObjxMap uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][](Map). +func (v *Value) GroupObjxMap(grouper func(int, Map) string) *Value { + groups := make(map[string][](Map)) + v.EachObjxMap(func(index int, val Map) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([](Map), 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceObjxMap uses the specified function to replace each (Map)s +// by iterating each item. The data in the returned result will be a +// [](Map) containing the replaced items. +func (v *Value) ReplaceObjxMap(replacer func(int, Map) Map) *Value { + arr := v.MustObjxMapSlice() + replaced := make([](Map), len(arr)) + v.EachObjxMap(func(index int, val Map) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectObjxMap uses the specified collector function to collect a value +// for each of the (Map)s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectObjxMap(collector func(int, Map) interface{}) *Value { + arr := v.MustObjxMapSlice() + collected := make([]interface{}, len(arr)) + v.EachObjxMap(func(index int, val Map) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Bool (bool and []bool) +*/ + +// Bool gets the value as a bool, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Bool(optionalDefault ...bool) bool { + if s, ok := v.data.(bool); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return false +} + +// MustBool gets the value as a bool. +// +// Panics if the object is not a bool. +func (v *Value) MustBool() bool { + return v.data.(bool) +} + +// BoolSlice gets the value as a []bool, returns the optionalDefault +// value or nil if the value is not a []bool. +func (v *Value) BoolSlice(optionalDefault ...[]bool) []bool { + if s, ok := v.data.([]bool); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustBoolSlice gets the value as a []bool. +// +// Panics if the object is not a []bool. +func (v *Value) MustBoolSlice() []bool { + return v.data.([]bool) +} + +// IsBool gets whether the object contained is a bool or not. +func (v *Value) IsBool() bool { + _, ok := v.data.(bool) + return ok +} + +// IsBoolSlice gets whether the object contained is a []bool or not. +func (v *Value) IsBoolSlice() bool { + _, ok := v.data.([]bool) + return ok +} + +// EachBool calls the specified callback for each object +// in the []bool. +// +// Panics if the object is the wrong type. +func (v *Value) EachBool(callback func(int, bool) bool) *Value { + for index, val := range v.MustBoolSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereBool uses the specified decider function to select items +// from the []bool. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereBool(decider func(int, bool) bool) *Value { + var selected []bool + v.EachBool(func(index int, val bool) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupBool uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]bool. +func (v *Value) GroupBool(grouper func(int, bool) string) *Value { + groups := make(map[string][]bool) + v.EachBool(func(index int, val bool) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]bool, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceBool uses the specified function to replace each bools +// by iterating each item. The data in the returned result will be a +// []bool containing the replaced items. +func (v *Value) ReplaceBool(replacer func(int, bool) bool) *Value { + arr := v.MustBoolSlice() + replaced := make([]bool, len(arr)) + v.EachBool(func(index int, val bool) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectBool uses the specified collector function to collect a value +// for each of the bools in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectBool(collector func(int, bool) interface{}) *Value { + arr := v.MustBoolSlice() + collected := make([]interface{}, len(arr)) + v.EachBool(func(index int, val bool) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Str (string and []string) +*/ + +// Str gets the value as a string, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Str(optionalDefault ...string) string { + if s, ok := v.data.(string); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return "" +} + +// MustStr gets the value as a string. +// +// Panics if the object is not a string. +func (v *Value) MustStr() string { + return v.data.(string) +} + +// StrSlice gets the value as a []string, returns the optionalDefault +// value or nil if the value is not a []string. +func (v *Value) StrSlice(optionalDefault ...[]string) []string { + if s, ok := v.data.([]string); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustStrSlice gets the value as a []string. +// +// Panics if the object is not a []string. +func (v *Value) MustStrSlice() []string { + return v.data.([]string) +} + +// IsStr gets whether the object contained is a string or not. +func (v *Value) IsStr() bool { + _, ok := v.data.(string) + return ok +} + +// IsStrSlice gets whether the object contained is a []string or not. +func (v *Value) IsStrSlice() bool { + _, ok := v.data.([]string) + return ok +} + +// EachStr calls the specified callback for each object +// in the []string. +// +// Panics if the object is the wrong type. +func (v *Value) EachStr(callback func(int, string) bool) *Value { + for index, val := range v.MustStrSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereStr uses the specified decider function to select items +// from the []string. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereStr(decider func(int, string) bool) *Value { + var selected []string + v.EachStr(func(index int, val string) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupStr uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]string. +func (v *Value) GroupStr(grouper func(int, string) string) *Value { + groups := make(map[string][]string) + v.EachStr(func(index int, val string) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]string, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceStr uses the specified function to replace each strings +// by iterating each item. The data in the returned result will be a +// []string containing the replaced items. +func (v *Value) ReplaceStr(replacer func(int, string) string) *Value { + arr := v.MustStrSlice() + replaced := make([]string, len(arr)) + v.EachStr(func(index int, val string) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectStr uses the specified collector function to collect a value +// for each of the strings in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectStr(collector func(int, string) interface{}) *Value { + arr := v.MustStrSlice() + collected := make([]interface{}, len(arr)) + v.EachStr(func(index int, val string) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Int (int and []int) +*/ + +// Int gets the value as a int, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int(optionalDefault ...int) int { + if s, ok := v.data.(int); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt gets the value as a int. +// +// Panics if the object is not a int. +func (v *Value) MustInt() int { + return v.data.(int) +} + +// IntSlice gets the value as a []int, returns the optionalDefault +// value or nil if the value is not a []int. +func (v *Value) IntSlice(optionalDefault ...[]int) []int { + if s, ok := v.data.([]int); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustIntSlice gets the value as a []int. +// +// Panics if the object is not a []int. +func (v *Value) MustIntSlice() []int { + return v.data.([]int) +} + +// IsInt gets whether the object contained is a int or not. +func (v *Value) IsInt() bool { + _, ok := v.data.(int) + return ok +} + +// IsIntSlice gets whether the object contained is a []int or not. +func (v *Value) IsIntSlice() bool { + _, ok := v.data.([]int) + return ok +} + +// EachInt calls the specified callback for each object +// in the []int. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt(callback func(int, int) bool) *Value { + for index, val := range v.MustIntSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInt uses the specified decider function to select items +// from the []int. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt(decider func(int, int) bool) *Value { + var selected []int + v.EachInt(func(index int, val int) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInt uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int. +func (v *Value) GroupInt(grouper func(int, int) string) *Value { + groups := make(map[string][]int) + v.EachInt(func(index int, val int) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInt uses the specified function to replace each ints +// by iterating each item. The data in the returned result will be a +// []int containing the replaced items. +func (v *Value) ReplaceInt(replacer func(int, int) int) *Value { + arr := v.MustIntSlice() + replaced := make([]int, len(arr)) + v.EachInt(func(index int, val int) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInt uses the specified collector function to collect a value +// for each of the ints in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt(collector func(int, int) interface{}) *Value { + arr := v.MustIntSlice() + collected := make([]interface{}, len(arr)) + v.EachInt(func(index int, val int) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Int8 (int8 and []int8) +*/ + +// Int8 gets the value as a int8, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int8(optionalDefault ...int8) int8 { + if s, ok := v.data.(int8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt8 gets the value as a int8. +// +// Panics if the object is not a int8. +func (v *Value) MustInt8() int8 { + return v.data.(int8) +} + +// Int8Slice gets the value as a []int8, returns the optionalDefault +// value or nil if the value is not a []int8. +func (v *Value) Int8Slice(optionalDefault ...[]int8) []int8 { + if s, ok := v.data.([]int8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt8Slice gets the value as a []int8. +// +// Panics if the object is not a []int8. +func (v *Value) MustInt8Slice() []int8 { + return v.data.([]int8) +} + +// IsInt8 gets whether the object contained is a int8 or not. +func (v *Value) IsInt8() bool { + _, ok := v.data.(int8) + return ok +} + +// IsInt8Slice gets whether the object contained is a []int8 or not. +func (v *Value) IsInt8Slice() bool { + _, ok := v.data.([]int8) + return ok +} + +// EachInt8 calls the specified callback for each object +// in the []int8. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt8(callback func(int, int8) bool) *Value { + for index, val := range v.MustInt8Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInt8 uses the specified decider function to select items +// from the []int8. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt8(decider func(int, int8) bool) *Value { + var selected []int8 + v.EachInt8(func(index int, val int8) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInt8 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int8. +func (v *Value) GroupInt8(grouper func(int, int8) string) *Value { + groups := make(map[string][]int8) + v.EachInt8(func(index int, val int8) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int8, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInt8 uses the specified function to replace each int8s +// by iterating each item. The data in the returned result will be a +// []int8 containing the replaced items. +func (v *Value) ReplaceInt8(replacer func(int, int8) int8) *Value { + arr := v.MustInt8Slice() + replaced := make([]int8, len(arr)) + v.EachInt8(func(index int, val int8) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInt8 uses the specified collector function to collect a value +// for each of the int8s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt8(collector func(int, int8) interface{}) *Value { + arr := v.MustInt8Slice() + collected := make([]interface{}, len(arr)) + v.EachInt8(func(index int, val int8) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Int16 (int16 and []int16) +*/ + +// Int16 gets the value as a int16, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int16(optionalDefault ...int16) int16 { + if s, ok := v.data.(int16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt16 gets the value as a int16. +// +// Panics if the object is not a int16. +func (v *Value) MustInt16() int16 { + return v.data.(int16) +} + +// Int16Slice gets the value as a []int16, returns the optionalDefault +// value or nil if the value is not a []int16. +func (v *Value) Int16Slice(optionalDefault ...[]int16) []int16 { + if s, ok := v.data.([]int16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt16Slice gets the value as a []int16. +// +// Panics if the object is not a []int16. +func (v *Value) MustInt16Slice() []int16 { + return v.data.([]int16) +} + +// IsInt16 gets whether the object contained is a int16 or not. +func (v *Value) IsInt16() bool { + _, ok := v.data.(int16) + return ok +} + +// IsInt16Slice gets whether the object contained is a []int16 or not. +func (v *Value) IsInt16Slice() bool { + _, ok := v.data.([]int16) + return ok +} + +// EachInt16 calls the specified callback for each object +// in the []int16. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt16(callback func(int, int16) bool) *Value { + for index, val := range v.MustInt16Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInt16 uses the specified decider function to select items +// from the []int16. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt16(decider func(int, int16) bool) *Value { + var selected []int16 + v.EachInt16(func(index int, val int16) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInt16 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int16. +func (v *Value) GroupInt16(grouper func(int, int16) string) *Value { + groups := make(map[string][]int16) + v.EachInt16(func(index int, val int16) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int16, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInt16 uses the specified function to replace each int16s +// by iterating each item. The data in the returned result will be a +// []int16 containing the replaced items. +func (v *Value) ReplaceInt16(replacer func(int, int16) int16) *Value { + arr := v.MustInt16Slice() + replaced := make([]int16, len(arr)) + v.EachInt16(func(index int, val int16) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInt16 uses the specified collector function to collect a value +// for each of the int16s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt16(collector func(int, int16) interface{}) *Value { + arr := v.MustInt16Slice() + collected := make([]interface{}, len(arr)) + v.EachInt16(func(index int, val int16) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Int32 (int32 and []int32) +*/ + +// Int32 gets the value as a int32, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int32(optionalDefault ...int32) int32 { + if s, ok := v.data.(int32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt32 gets the value as a int32. +// +// Panics if the object is not a int32. +func (v *Value) MustInt32() int32 { + return v.data.(int32) +} + +// Int32Slice gets the value as a []int32, returns the optionalDefault +// value or nil if the value is not a []int32. +func (v *Value) Int32Slice(optionalDefault ...[]int32) []int32 { + if s, ok := v.data.([]int32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt32Slice gets the value as a []int32. +// +// Panics if the object is not a []int32. +func (v *Value) MustInt32Slice() []int32 { + return v.data.([]int32) +} + +// IsInt32 gets whether the object contained is a int32 or not. +func (v *Value) IsInt32() bool { + _, ok := v.data.(int32) + return ok +} + +// IsInt32Slice gets whether the object contained is a []int32 or not. +func (v *Value) IsInt32Slice() bool { + _, ok := v.data.([]int32) + return ok +} + +// EachInt32 calls the specified callback for each object +// in the []int32. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt32(callback func(int, int32) bool) *Value { + for index, val := range v.MustInt32Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInt32 uses the specified decider function to select items +// from the []int32. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt32(decider func(int, int32) bool) *Value { + var selected []int32 + v.EachInt32(func(index int, val int32) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInt32 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int32. +func (v *Value) GroupInt32(grouper func(int, int32) string) *Value { + groups := make(map[string][]int32) + v.EachInt32(func(index int, val int32) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int32, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInt32 uses the specified function to replace each int32s +// by iterating each item. The data in the returned result will be a +// []int32 containing the replaced items. +func (v *Value) ReplaceInt32(replacer func(int, int32) int32) *Value { + arr := v.MustInt32Slice() + replaced := make([]int32, len(arr)) + v.EachInt32(func(index int, val int32) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInt32 uses the specified collector function to collect a value +// for each of the int32s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt32(collector func(int, int32) interface{}) *Value { + arr := v.MustInt32Slice() + collected := make([]interface{}, len(arr)) + v.EachInt32(func(index int, val int32) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Int64 (int64 and []int64) +*/ + +// Int64 gets the value as a int64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int64(optionalDefault ...int64) int64 { + if s, ok := v.data.(int64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt64 gets the value as a int64. +// +// Panics if the object is not a int64. +func (v *Value) MustInt64() int64 { + return v.data.(int64) +} + +// Int64Slice gets the value as a []int64, returns the optionalDefault +// value or nil if the value is not a []int64. +func (v *Value) Int64Slice(optionalDefault ...[]int64) []int64 { + if s, ok := v.data.([]int64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt64Slice gets the value as a []int64. +// +// Panics if the object is not a []int64. +func (v *Value) MustInt64Slice() []int64 { + return v.data.([]int64) +} + +// IsInt64 gets whether the object contained is a int64 or not. +func (v *Value) IsInt64() bool { + _, ok := v.data.(int64) + return ok +} + +// IsInt64Slice gets whether the object contained is a []int64 or not. +func (v *Value) IsInt64Slice() bool { + _, ok := v.data.([]int64) + return ok +} + +// EachInt64 calls the specified callback for each object +// in the []int64. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt64(callback func(int, int64) bool) *Value { + for index, val := range v.MustInt64Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInt64 uses the specified decider function to select items +// from the []int64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt64(decider func(int, int64) bool) *Value { + var selected []int64 + v.EachInt64(func(index int, val int64) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInt64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int64. +func (v *Value) GroupInt64(grouper func(int, int64) string) *Value { + groups := make(map[string][]int64) + v.EachInt64(func(index int, val int64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInt64 uses the specified function to replace each int64s +// by iterating each item. The data in the returned result will be a +// []int64 containing the replaced items. +func (v *Value) ReplaceInt64(replacer func(int, int64) int64) *Value { + arr := v.MustInt64Slice() + replaced := make([]int64, len(arr)) + v.EachInt64(func(index int, val int64) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInt64 uses the specified collector function to collect a value +// for each of the int64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt64(collector func(int, int64) interface{}) *Value { + arr := v.MustInt64Slice() + collected := make([]interface{}, len(arr)) + v.EachInt64(func(index int, val int64) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uint (uint and []uint) +*/ + +// Uint gets the value as a uint, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint(optionalDefault ...uint) uint { + if s, ok := v.data.(uint); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint gets the value as a uint. +// +// Panics if the object is not a uint. +func (v *Value) MustUint() uint { + return v.data.(uint) +} + +// UintSlice gets the value as a []uint, returns the optionalDefault +// value or nil if the value is not a []uint. +func (v *Value) UintSlice(optionalDefault ...[]uint) []uint { + if s, ok := v.data.([]uint); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUintSlice gets the value as a []uint. +// +// Panics if the object is not a []uint. +func (v *Value) MustUintSlice() []uint { + return v.data.([]uint) +} + +// IsUint gets whether the object contained is a uint or not. +func (v *Value) IsUint() bool { + _, ok := v.data.(uint) + return ok +} + +// IsUintSlice gets whether the object contained is a []uint or not. +func (v *Value) IsUintSlice() bool { + _, ok := v.data.([]uint) + return ok +} + +// EachUint calls the specified callback for each object +// in the []uint. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint(callback func(int, uint) bool) *Value { + for index, val := range v.MustUintSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUint uses the specified decider function to select items +// from the []uint. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint(decider func(int, uint) bool) *Value { + var selected []uint + v.EachUint(func(index int, val uint) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUint uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint. +func (v *Value) GroupUint(grouper func(int, uint) string) *Value { + groups := make(map[string][]uint) + v.EachUint(func(index int, val uint) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUint uses the specified function to replace each uints +// by iterating each item. The data in the returned result will be a +// []uint containing the replaced items. +func (v *Value) ReplaceUint(replacer func(int, uint) uint) *Value { + arr := v.MustUintSlice() + replaced := make([]uint, len(arr)) + v.EachUint(func(index int, val uint) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUint uses the specified collector function to collect a value +// for each of the uints in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint(collector func(int, uint) interface{}) *Value { + arr := v.MustUintSlice() + collected := make([]interface{}, len(arr)) + v.EachUint(func(index int, val uint) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uint8 (uint8 and []uint8) +*/ + +// Uint8 gets the value as a uint8, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint8(optionalDefault ...uint8) uint8 { + if s, ok := v.data.(uint8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint8 gets the value as a uint8. +// +// Panics if the object is not a uint8. +func (v *Value) MustUint8() uint8 { + return v.data.(uint8) +} + +// Uint8Slice gets the value as a []uint8, returns the optionalDefault +// value or nil if the value is not a []uint8. +func (v *Value) Uint8Slice(optionalDefault ...[]uint8) []uint8 { + if s, ok := v.data.([]uint8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint8Slice gets the value as a []uint8. +// +// Panics if the object is not a []uint8. +func (v *Value) MustUint8Slice() []uint8 { + return v.data.([]uint8) +} + +// IsUint8 gets whether the object contained is a uint8 or not. +func (v *Value) IsUint8() bool { + _, ok := v.data.(uint8) + return ok +} + +// IsUint8Slice gets whether the object contained is a []uint8 or not. +func (v *Value) IsUint8Slice() bool { + _, ok := v.data.([]uint8) + return ok +} + +// EachUint8 calls the specified callback for each object +// in the []uint8. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint8(callback func(int, uint8) bool) *Value { + for index, val := range v.MustUint8Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUint8 uses the specified decider function to select items +// from the []uint8. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint8(decider func(int, uint8) bool) *Value { + var selected []uint8 + v.EachUint8(func(index int, val uint8) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUint8 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint8. +func (v *Value) GroupUint8(grouper func(int, uint8) string) *Value { + groups := make(map[string][]uint8) + v.EachUint8(func(index int, val uint8) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint8, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUint8 uses the specified function to replace each uint8s +// by iterating each item. The data in the returned result will be a +// []uint8 containing the replaced items. +func (v *Value) ReplaceUint8(replacer func(int, uint8) uint8) *Value { + arr := v.MustUint8Slice() + replaced := make([]uint8, len(arr)) + v.EachUint8(func(index int, val uint8) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUint8 uses the specified collector function to collect a value +// for each of the uint8s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint8(collector func(int, uint8) interface{}) *Value { + arr := v.MustUint8Slice() + collected := make([]interface{}, len(arr)) + v.EachUint8(func(index int, val uint8) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uint16 (uint16 and []uint16) +*/ + +// Uint16 gets the value as a uint16, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint16(optionalDefault ...uint16) uint16 { + if s, ok := v.data.(uint16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint16 gets the value as a uint16. +// +// Panics if the object is not a uint16. +func (v *Value) MustUint16() uint16 { + return v.data.(uint16) +} + +// Uint16Slice gets the value as a []uint16, returns the optionalDefault +// value or nil if the value is not a []uint16. +func (v *Value) Uint16Slice(optionalDefault ...[]uint16) []uint16 { + if s, ok := v.data.([]uint16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint16Slice gets the value as a []uint16. +// +// Panics if the object is not a []uint16. +func (v *Value) MustUint16Slice() []uint16 { + return v.data.([]uint16) +} + +// IsUint16 gets whether the object contained is a uint16 or not. +func (v *Value) IsUint16() bool { + _, ok := v.data.(uint16) + return ok +} + +// IsUint16Slice gets whether the object contained is a []uint16 or not. +func (v *Value) IsUint16Slice() bool { + _, ok := v.data.([]uint16) + return ok +} + +// EachUint16 calls the specified callback for each object +// in the []uint16. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint16(callback func(int, uint16) bool) *Value { + for index, val := range v.MustUint16Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUint16 uses the specified decider function to select items +// from the []uint16. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint16(decider func(int, uint16) bool) *Value { + var selected []uint16 + v.EachUint16(func(index int, val uint16) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUint16 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint16. +func (v *Value) GroupUint16(grouper func(int, uint16) string) *Value { + groups := make(map[string][]uint16) + v.EachUint16(func(index int, val uint16) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint16, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUint16 uses the specified function to replace each uint16s +// by iterating each item. The data in the returned result will be a +// []uint16 containing the replaced items. +func (v *Value) ReplaceUint16(replacer func(int, uint16) uint16) *Value { + arr := v.MustUint16Slice() + replaced := make([]uint16, len(arr)) + v.EachUint16(func(index int, val uint16) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUint16 uses the specified collector function to collect a value +// for each of the uint16s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint16(collector func(int, uint16) interface{}) *Value { + arr := v.MustUint16Slice() + collected := make([]interface{}, len(arr)) + v.EachUint16(func(index int, val uint16) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uint32 (uint32 and []uint32) +*/ + +// Uint32 gets the value as a uint32, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint32(optionalDefault ...uint32) uint32 { + if s, ok := v.data.(uint32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint32 gets the value as a uint32. +// +// Panics if the object is not a uint32. +func (v *Value) MustUint32() uint32 { + return v.data.(uint32) +} + +// Uint32Slice gets the value as a []uint32, returns the optionalDefault +// value or nil if the value is not a []uint32. +func (v *Value) Uint32Slice(optionalDefault ...[]uint32) []uint32 { + if s, ok := v.data.([]uint32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint32Slice gets the value as a []uint32. +// +// Panics if the object is not a []uint32. +func (v *Value) MustUint32Slice() []uint32 { + return v.data.([]uint32) +} + +// IsUint32 gets whether the object contained is a uint32 or not. +func (v *Value) IsUint32() bool { + _, ok := v.data.(uint32) + return ok +} + +// IsUint32Slice gets whether the object contained is a []uint32 or not. +func (v *Value) IsUint32Slice() bool { + _, ok := v.data.([]uint32) + return ok +} + +// EachUint32 calls the specified callback for each object +// in the []uint32. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint32(callback func(int, uint32) bool) *Value { + for index, val := range v.MustUint32Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUint32 uses the specified decider function to select items +// from the []uint32. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint32(decider func(int, uint32) bool) *Value { + var selected []uint32 + v.EachUint32(func(index int, val uint32) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUint32 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint32. +func (v *Value) GroupUint32(grouper func(int, uint32) string) *Value { + groups := make(map[string][]uint32) + v.EachUint32(func(index int, val uint32) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint32, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUint32 uses the specified function to replace each uint32s +// by iterating each item. The data in the returned result will be a +// []uint32 containing the replaced items. +func (v *Value) ReplaceUint32(replacer func(int, uint32) uint32) *Value { + arr := v.MustUint32Slice() + replaced := make([]uint32, len(arr)) + v.EachUint32(func(index int, val uint32) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUint32 uses the specified collector function to collect a value +// for each of the uint32s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint32(collector func(int, uint32) interface{}) *Value { + arr := v.MustUint32Slice() + collected := make([]interface{}, len(arr)) + v.EachUint32(func(index int, val uint32) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uint64 (uint64 and []uint64) +*/ + +// Uint64 gets the value as a uint64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint64(optionalDefault ...uint64) uint64 { + if s, ok := v.data.(uint64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint64 gets the value as a uint64. +// +// Panics if the object is not a uint64. +func (v *Value) MustUint64() uint64 { + return v.data.(uint64) +} + +// Uint64Slice gets the value as a []uint64, returns the optionalDefault +// value or nil if the value is not a []uint64. +func (v *Value) Uint64Slice(optionalDefault ...[]uint64) []uint64 { + if s, ok := v.data.([]uint64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint64Slice gets the value as a []uint64. +// +// Panics if the object is not a []uint64. +func (v *Value) MustUint64Slice() []uint64 { + return v.data.([]uint64) +} + +// IsUint64 gets whether the object contained is a uint64 or not. +func (v *Value) IsUint64() bool { + _, ok := v.data.(uint64) + return ok +} + +// IsUint64Slice gets whether the object contained is a []uint64 or not. +func (v *Value) IsUint64Slice() bool { + _, ok := v.data.([]uint64) + return ok +} + +// EachUint64 calls the specified callback for each object +// in the []uint64. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint64(callback func(int, uint64) bool) *Value { + for index, val := range v.MustUint64Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUint64 uses the specified decider function to select items +// from the []uint64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint64(decider func(int, uint64) bool) *Value { + var selected []uint64 + v.EachUint64(func(index int, val uint64) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUint64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint64. +func (v *Value) GroupUint64(grouper func(int, uint64) string) *Value { + groups := make(map[string][]uint64) + v.EachUint64(func(index int, val uint64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUint64 uses the specified function to replace each uint64s +// by iterating each item. The data in the returned result will be a +// []uint64 containing the replaced items. +func (v *Value) ReplaceUint64(replacer func(int, uint64) uint64) *Value { + arr := v.MustUint64Slice() + replaced := make([]uint64, len(arr)) + v.EachUint64(func(index int, val uint64) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUint64 uses the specified collector function to collect a value +// for each of the uint64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint64(collector func(int, uint64) interface{}) *Value { + arr := v.MustUint64Slice() + collected := make([]interface{}, len(arr)) + v.EachUint64(func(index int, val uint64) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uintptr (uintptr and []uintptr) +*/ + +// Uintptr gets the value as a uintptr, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uintptr(optionalDefault ...uintptr) uintptr { + if s, ok := v.data.(uintptr); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUintptr gets the value as a uintptr. +// +// Panics if the object is not a uintptr. +func (v *Value) MustUintptr() uintptr { + return v.data.(uintptr) +} + +// UintptrSlice gets the value as a []uintptr, returns the optionalDefault +// value or nil if the value is not a []uintptr. +func (v *Value) UintptrSlice(optionalDefault ...[]uintptr) []uintptr { + if s, ok := v.data.([]uintptr); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUintptrSlice gets the value as a []uintptr. +// +// Panics if the object is not a []uintptr. +func (v *Value) MustUintptrSlice() []uintptr { + return v.data.([]uintptr) +} + +// IsUintptr gets whether the object contained is a uintptr or not. +func (v *Value) IsUintptr() bool { + _, ok := v.data.(uintptr) + return ok +} + +// IsUintptrSlice gets whether the object contained is a []uintptr or not. +func (v *Value) IsUintptrSlice() bool { + _, ok := v.data.([]uintptr) + return ok +} + +// EachUintptr calls the specified callback for each object +// in the []uintptr. +// +// Panics if the object is the wrong type. +func (v *Value) EachUintptr(callback func(int, uintptr) bool) *Value { + for index, val := range v.MustUintptrSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUintptr uses the specified decider function to select items +// from the []uintptr. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUintptr(decider func(int, uintptr) bool) *Value { + var selected []uintptr + v.EachUintptr(func(index int, val uintptr) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUintptr uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uintptr. +func (v *Value) GroupUintptr(grouper func(int, uintptr) string) *Value { + groups := make(map[string][]uintptr) + v.EachUintptr(func(index int, val uintptr) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uintptr, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUintptr uses the specified function to replace each uintptrs +// by iterating each item. The data in the returned result will be a +// []uintptr containing the replaced items. +func (v *Value) ReplaceUintptr(replacer func(int, uintptr) uintptr) *Value { + arr := v.MustUintptrSlice() + replaced := make([]uintptr, len(arr)) + v.EachUintptr(func(index int, val uintptr) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUintptr uses the specified collector function to collect a value +// for each of the uintptrs in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUintptr(collector func(int, uintptr) interface{}) *Value { + arr := v.MustUintptrSlice() + collected := make([]interface{}, len(arr)) + v.EachUintptr(func(index int, val uintptr) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Float32 (float32 and []float32) +*/ + +// Float32 gets the value as a float32, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Float32(optionalDefault ...float32) float32 { + if s, ok := v.data.(float32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustFloat32 gets the value as a float32. +// +// Panics if the object is not a float32. +func (v *Value) MustFloat32() float32 { + return v.data.(float32) +} + +// Float32Slice gets the value as a []float32, returns the optionalDefault +// value or nil if the value is not a []float32. +func (v *Value) Float32Slice(optionalDefault ...[]float32) []float32 { + if s, ok := v.data.([]float32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustFloat32Slice gets the value as a []float32. +// +// Panics if the object is not a []float32. +func (v *Value) MustFloat32Slice() []float32 { + return v.data.([]float32) +} + +// IsFloat32 gets whether the object contained is a float32 or not. +func (v *Value) IsFloat32() bool { + _, ok := v.data.(float32) + return ok +} + +// IsFloat32Slice gets whether the object contained is a []float32 or not. +func (v *Value) IsFloat32Slice() bool { + _, ok := v.data.([]float32) + return ok +} + +// EachFloat32 calls the specified callback for each object +// in the []float32. +// +// Panics if the object is the wrong type. +func (v *Value) EachFloat32(callback func(int, float32) bool) *Value { + for index, val := range v.MustFloat32Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereFloat32 uses the specified decider function to select items +// from the []float32. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereFloat32(decider func(int, float32) bool) *Value { + var selected []float32 + v.EachFloat32(func(index int, val float32) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupFloat32 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]float32. +func (v *Value) GroupFloat32(grouper func(int, float32) string) *Value { + groups := make(map[string][]float32) + v.EachFloat32(func(index int, val float32) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]float32, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceFloat32 uses the specified function to replace each float32s +// by iterating each item. The data in the returned result will be a +// []float32 containing the replaced items. +func (v *Value) ReplaceFloat32(replacer func(int, float32) float32) *Value { + arr := v.MustFloat32Slice() + replaced := make([]float32, len(arr)) + v.EachFloat32(func(index int, val float32) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectFloat32 uses the specified collector function to collect a value +// for each of the float32s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectFloat32(collector func(int, float32) interface{}) *Value { + arr := v.MustFloat32Slice() + collected := make([]interface{}, len(arr)) + v.EachFloat32(func(index int, val float32) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Float64 (float64 and []float64) +*/ + +// Float64 gets the value as a float64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Float64(optionalDefault ...float64) float64 { + if s, ok := v.data.(float64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustFloat64 gets the value as a float64. +// +// Panics if the object is not a float64. +func (v *Value) MustFloat64() float64 { + return v.data.(float64) +} + +// Float64Slice gets the value as a []float64, returns the optionalDefault +// value or nil if the value is not a []float64. +func (v *Value) Float64Slice(optionalDefault ...[]float64) []float64 { + if s, ok := v.data.([]float64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustFloat64Slice gets the value as a []float64. +// +// Panics if the object is not a []float64. +func (v *Value) MustFloat64Slice() []float64 { + return v.data.([]float64) +} + +// IsFloat64 gets whether the object contained is a float64 or not. +func (v *Value) IsFloat64() bool { + _, ok := v.data.(float64) + return ok +} + +// IsFloat64Slice gets whether the object contained is a []float64 or not. +func (v *Value) IsFloat64Slice() bool { + _, ok := v.data.([]float64) + return ok +} + +// EachFloat64 calls the specified callback for each object +// in the []float64. +// +// Panics if the object is the wrong type. +func (v *Value) EachFloat64(callback func(int, float64) bool) *Value { + for index, val := range v.MustFloat64Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereFloat64 uses the specified decider function to select items +// from the []float64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereFloat64(decider func(int, float64) bool) *Value { + var selected []float64 + v.EachFloat64(func(index int, val float64) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupFloat64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]float64. +func (v *Value) GroupFloat64(grouper func(int, float64) string) *Value { + groups := make(map[string][]float64) + v.EachFloat64(func(index int, val float64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]float64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceFloat64 uses the specified function to replace each float64s +// by iterating each item. The data in the returned result will be a +// []float64 containing the replaced items. +func (v *Value) ReplaceFloat64(replacer func(int, float64) float64) *Value { + arr := v.MustFloat64Slice() + replaced := make([]float64, len(arr)) + v.EachFloat64(func(index int, val float64) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectFloat64 uses the specified collector function to collect a value +// for each of the float64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectFloat64(collector func(int, float64) interface{}) *Value { + arr := v.MustFloat64Slice() + collected := make([]interface{}, len(arr)) + v.EachFloat64(func(index int, val float64) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Complex64 (complex64 and []complex64) +*/ + +// Complex64 gets the value as a complex64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Complex64(optionalDefault ...complex64) complex64 { + if s, ok := v.data.(complex64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustComplex64 gets the value as a complex64. +// +// Panics if the object is not a complex64. +func (v *Value) MustComplex64() complex64 { + return v.data.(complex64) +} + +// Complex64Slice gets the value as a []complex64, returns the optionalDefault +// value or nil if the value is not a []complex64. +func (v *Value) Complex64Slice(optionalDefault ...[]complex64) []complex64 { + if s, ok := v.data.([]complex64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustComplex64Slice gets the value as a []complex64. +// +// Panics if the object is not a []complex64. +func (v *Value) MustComplex64Slice() []complex64 { + return v.data.([]complex64) +} + +// IsComplex64 gets whether the object contained is a complex64 or not. +func (v *Value) IsComplex64() bool { + _, ok := v.data.(complex64) + return ok +} + +// IsComplex64Slice gets whether the object contained is a []complex64 or not. +func (v *Value) IsComplex64Slice() bool { + _, ok := v.data.([]complex64) + return ok +} + +// EachComplex64 calls the specified callback for each object +// in the []complex64. +// +// Panics if the object is the wrong type. +func (v *Value) EachComplex64(callback func(int, complex64) bool) *Value { + for index, val := range v.MustComplex64Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereComplex64 uses the specified decider function to select items +// from the []complex64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereComplex64(decider func(int, complex64) bool) *Value { + var selected []complex64 + v.EachComplex64(func(index int, val complex64) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupComplex64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]complex64. +func (v *Value) GroupComplex64(grouper func(int, complex64) string) *Value { + groups := make(map[string][]complex64) + v.EachComplex64(func(index int, val complex64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]complex64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceComplex64 uses the specified function to replace each complex64s +// by iterating each item. The data in the returned result will be a +// []complex64 containing the replaced items. +func (v *Value) ReplaceComplex64(replacer func(int, complex64) complex64) *Value { + arr := v.MustComplex64Slice() + replaced := make([]complex64, len(arr)) + v.EachComplex64(func(index int, val complex64) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectComplex64 uses the specified collector function to collect a value +// for each of the complex64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectComplex64(collector func(int, complex64) interface{}) *Value { + arr := v.MustComplex64Slice() + collected := make([]interface{}, len(arr)) + v.EachComplex64(func(index int, val complex64) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Complex128 (complex128 and []complex128) +*/ + +// Complex128 gets the value as a complex128, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Complex128(optionalDefault ...complex128) complex128 { + if s, ok := v.data.(complex128); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustComplex128 gets the value as a complex128. +// +// Panics if the object is not a complex128. +func (v *Value) MustComplex128() complex128 { + return v.data.(complex128) +} + +// Complex128Slice gets the value as a []complex128, returns the optionalDefault +// value or nil if the value is not a []complex128. +func (v *Value) Complex128Slice(optionalDefault ...[]complex128) []complex128 { + if s, ok := v.data.([]complex128); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustComplex128Slice gets the value as a []complex128. +// +// Panics if the object is not a []complex128. +func (v *Value) MustComplex128Slice() []complex128 { + return v.data.([]complex128) +} + +// IsComplex128 gets whether the object contained is a complex128 or not. +func (v *Value) IsComplex128() bool { + _, ok := v.data.(complex128) + return ok +} + +// IsComplex128Slice gets whether the object contained is a []complex128 or not. +func (v *Value) IsComplex128Slice() bool { + _, ok := v.data.([]complex128) + return ok +} + +// EachComplex128 calls the specified callback for each object +// in the []complex128. +// +// Panics if the object is the wrong type. +func (v *Value) EachComplex128(callback func(int, complex128) bool) *Value { + for index, val := range v.MustComplex128Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereComplex128 uses the specified decider function to select items +// from the []complex128. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereComplex128(decider func(int, complex128) bool) *Value { + var selected []complex128 + v.EachComplex128(func(index int, val complex128) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupComplex128 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]complex128. +func (v *Value) GroupComplex128(grouper func(int, complex128) string) *Value { + groups := make(map[string][]complex128) + v.EachComplex128(func(index int, val complex128) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]complex128, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceComplex128 uses the specified function to replace each complex128s +// by iterating each item. The data in the returned result will be a +// []complex128 containing the replaced items. +func (v *Value) ReplaceComplex128(replacer func(int, complex128) complex128) *Value { + arr := v.MustComplex128Slice() + replaced := make([]complex128, len(arr)) + v.EachComplex128(func(index int, val complex128) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectComplex128 uses the specified collector function to collect a value +// for each of the complex128s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectComplex128(collector func(int, complex128) interface{}) *Value { + arr := v.MustComplex128Slice() + collected := make([]interface{}, len(arr)) + v.EachComplex128(func(index int, val complex128) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} diff --git a/vendor/github.com/stretchr/objx/value.go b/vendor/github.com/stretchr/objx/value.go new file mode 100644 index 000000000..956a2211d --- /dev/null +++ b/vendor/github.com/stretchr/objx/value.go @@ -0,0 +1,56 @@ +package objx + +import ( + "fmt" + "strconv" +) + +// Value provides methods for extracting interface{} data in various +// types. +type Value struct { + // data contains the raw data being managed by this Value + data interface{} +} + +// Data returns the raw data contained by this Value +func (v *Value) Data() interface{} { + return v.data +} + +// String returns the value always as a string +func (v *Value) String() string { + switch { + case v.IsStr(): + return v.Str() + case v.IsBool(): + return strconv.FormatBool(v.Bool()) + case v.IsFloat32(): + return strconv.FormatFloat(float64(v.Float32()), 'f', -1, 32) + case v.IsFloat64(): + return strconv.FormatFloat(v.Float64(), 'f', -1, 64) + case v.IsInt(): + return strconv.FormatInt(int64(v.Int()), 10) + case v.IsInt(): + return strconv.FormatInt(int64(v.Int()), 10) + case v.IsInt8(): + return strconv.FormatInt(int64(v.Int8()), 10) + case v.IsInt16(): + return strconv.FormatInt(int64(v.Int16()), 10) + case v.IsInt32(): + return strconv.FormatInt(int64(v.Int32()), 10) + case v.IsInt64(): + return strconv.FormatInt(v.Int64(), 10) + case v.IsUint(): + return strconv.FormatUint(uint64(v.Uint()), 10) + case v.IsUint8(): + return strconv.FormatUint(uint64(v.Uint8()), 10) + case v.IsUint16(): + return strconv.FormatUint(uint64(v.Uint16()), 10) + case v.IsUint32(): + return strconv.FormatUint(uint64(v.Uint32()), 10) + case v.IsUint64(): + return strconv.FormatUint(v.Uint64(), 10) + } + + return fmt.Sprintf("%#v", v.Data()) +} diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go b/vendor/github.com/stretchr/testify/assert/assertion_format.go new file mode 100644 index 000000000..ae06a54e2 --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go @@ -0,0 +1,349 @@ +/* +* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen +* THIS FILE MUST NOT BE EDITED BY HAND + */ + +package assert + +import ( + http "net/http" + url "net/url" + time "time" +) + +// Conditionf uses a Comparison to assert a complex condition. +func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bool { + return Condition(t, comp, append([]interface{}{msg}, args...)...) +} + +// Containsf asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted") +// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted") +// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted") +func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool { + return Contains(t, s, contains, append([]interface{}{msg}, args...)...) +} + +// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func DirExistsf(t TestingT, path string, msg string, args ...interface{}) bool { + return DirExists(t, path, append([]interface{}{msg}, args...)...) +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool { + return ElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...) +} + +// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// assert.Emptyf(t, obj, "error message %s", "formatted") +func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool { + return Empty(t, object, append([]interface{}{msg}, args...)...) +} + +// Equalf asserts that two objects are equal. +// +// assert.Equalf(t, 123, 123, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + return Equal(t, expected, actual, append([]interface{}{msg}, args...)...) +} + +// EqualErrorf asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted") +func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool { + return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...) +} + +// EqualValuesf asserts that two objects are equal or convertable to the same types +// and equal. +// +// assert.EqualValuesf(t, uint32(123, "error message %s", "formatted"), int32(123)) +func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + return EqualValues(t, expected, actual, append([]interface{}{msg}, args...)...) +} + +// Errorf asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if assert.Errorf(t, err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } +func Errorf(t TestingT, err error, msg string, args ...interface{}) bool { + return Error(t, err, append([]interface{}{msg}, args...)...) +} + +// Exactlyf asserts that two objects are equal in value and type. +// +// assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123)) +func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + return Exactly(t, expected, actual, append([]interface{}{msg}, args...)...) +} + +// Failf reports a failure through +func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) bool { + return Fail(t, failureMessage, append([]interface{}{msg}, args...)...) +} + +// FailNowf fails test +func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) bool { + return FailNow(t, failureMessage, append([]interface{}{msg}, args...)...) +} + +// Falsef asserts that the specified value is false. +// +// assert.Falsef(t, myBool, "error message %s", "formatted") +func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool { + return False(t, value, append([]interface{}{msg}, args...)...) +} + +// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool { + return FileExists(t, path, append([]interface{}{msg}, args...)...) +} + +// HTTPBodyContainsf asserts that a specified handler returns a +// body that contains a string. +// +// assert.HTTPBodyContainsf(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { + return HTTPBodyContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...) +} + +// HTTPBodyNotContainsf asserts that a specified handler returns a +// body that does not contain a string. +// +// assert.HTTPBodyNotContainsf(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { + return HTTPBodyNotContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...) +} + +// HTTPErrorf asserts that a specified handler returns an error status code. +// +// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). +func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + return HTTPError(t, handler, method, url, values, append([]interface{}{msg}, args...)...) +} + +// HTTPRedirectf asserts that a specified handler returns a redirect status code. +// +// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). +func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + return HTTPRedirect(t, handler, method, url, values, append([]interface{}{msg}, args...)...) +} + +// HTTPSuccessf asserts that a specified handler returns a success status code. +// +// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + return HTTPSuccess(t, handler, method, url, values, append([]interface{}{msg}, args...)...) +} + +// Implementsf asserts that an object is implemented by the specified interface. +// +// assert.Implementsf(t, (*MyInterface, "error message %s", "formatted")(nil), new(MyObject)) +func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool { + return Implements(t, interfaceObject, object, append([]interface{}{msg}, args...)...) +} + +// InDeltaf asserts that the two numerals are within delta of each other. +// +// assert.InDeltaf(t, math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01) +func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + return InDelta(t, expected, actual, delta, append([]interface{}{msg}, args...)...) +} + +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + return InDeltaMapValues(t, expected, actual, delta, append([]interface{}{msg}, args...)...) +} + +// InDeltaSlicef is the same as InDelta, except it compares two slices. +func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + return InDeltaSlice(t, expected, actual, delta, append([]interface{}{msg}, args...)...) +} + +// InEpsilonf asserts that expected and actual have a relative error less than epsilon +func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool { + return InEpsilon(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...) +} + +// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices. +func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool { + return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...) +} + +// IsTypef asserts that the specified objects are of the same type. +func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool { + return IsType(t, expectedType, object, append([]interface{}{msg}, args...)...) +} + +// JSONEqf asserts that two JSON strings are equivalent. +// +// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool { + return JSONEq(t, expected, actual, append([]interface{}{msg}, args...)...) +} + +// Lenf asserts that the specified object has specific length. +// Lenf also fails if the object has a type that len() not accept. +// +// assert.Lenf(t, mySlice, 3, "error message %s", "formatted") +func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool { + return Len(t, object, length, append([]interface{}{msg}, args...)...) +} + +// Nilf asserts that the specified object is nil. +// +// assert.Nilf(t, err, "error message %s", "formatted") +func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool { + return Nil(t, object, append([]interface{}{msg}, args...)...) +} + +// NoErrorf asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if assert.NoErrorf(t, err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } +func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool { + return NoError(t, err, append([]interface{}{msg}, args...)...) +} + +// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted") +func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool { + return NotContains(t, s, contains, append([]interface{}{msg}, args...)...) +} + +// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if assert.NotEmptyf(t, obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } +func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool { + return NotEmpty(t, object, append([]interface{}{msg}, args...)...) +} + +// NotEqualf asserts that the specified values are NOT equal. +// +// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + return NotEqual(t, expected, actual, append([]interface{}{msg}, args...)...) +} + +// NotNilf asserts that the specified object is not nil. +// +// assert.NotNilf(t, err, "error message %s", "formatted") +func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bool { + return NotNil(t, object, append([]interface{}{msg}, args...)...) +} + +// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted") +func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool { + return NotPanics(t, f, append([]interface{}{msg}, args...)...) +} + +// NotRegexpf asserts that a specified regexp does not match a string. +// +// assert.NotRegexpf(t, regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting") +// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") +func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool { + return NotRegexp(t, rx, str, append([]interface{}{msg}, args...)...) +} + +// NotSubsetf asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") +func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool { + return NotSubset(t, list, subset, append([]interface{}{msg}, args...)...) +} + +// NotZerof asserts that i is not the zero value for its type. +func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool { + return NotZero(t, i, append([]interface{}{msg}, args...)...) +} + +// Panicsf asserts that the code inside the specified PanicTestFunc panics. +// +// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted") +func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool { + return Panics(t, f, append([]interface{}{msg}, args...)...) +} + +// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool { + return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...) +} + +// Regexpf asserts that a specified regexp matches a string. +// +// assert.Regexpf(t, regexp.MustCompile("start", "error message %s", "formatted"), "it's starting") +// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") +func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool { + return Regexp(t, rx, str, append([]interface{}{msg}, args...)...) +} + +// Subsetf asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") +func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool { + return Subset(t, list, subset, append([]interface{}{msg}, args...)...) +} + +// Truef asserts that the specified value is true. +// +// assert.Truef(t, myBool, "error message %s", "formatted") +func Truef(t TestingT, value bool, msg string, args ...interface{}) bool { + return True(t, value, append([]interface{}{msg}, args...)...) +} + +// WithinDurationf asserts that the two times are within duration delta of each other. +// +// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool { + return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...) +} + +// Zerof asserts that i is the zero value for its type. +func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) bool { + return Zero(t, i, append([]interface{}{msg}, args...)...) +} diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl b/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl new file mode 100644 index 000000000..c5cc66f43 --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl @@ -0,0 +1,4 @@ +{{.CommentFormat}} +func {{.DocInfo.Name}}f(t TestingT, {{.ParamsFormat}}) bool { + return {{.DocInfo.Name}}(t, {{.ForwardedParamsFormat}}) +} diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go b/vendor/github.com/stretchr/testify/assert/assertion_forward.go index e6a796046..ffa5428f3 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_forward.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_forward.go @@ -1,387 +1,686 @@ /* * CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen * THIS FILE MUST NOT BE EDITED BY HAND -*/ + */ package assert import ( - http "net/http" url "net/url" time "time" ) - // Condition uses a Comparison to assert a complex condition. func (a *Assertions) Condition(comp Comparison, msgAndArgs ...interface{}) bool { return Condition(a.t, comp, msgAndArgs...) } +// Conditionf uses a Comparison to assert a complex condition. +func (a *Assertions) Conditionf(comp Comparison, msg string, args ...interface{}) bool { + return Conditionf(a.t, comp, msg, args...) +} // Contains asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. -// -// a.Contains("Hello World", "World", "But 'Hello World' does contain 'World'") -// a.Contains(["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'") -// a.Contains({"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.Contains("Hello World", "World") +// a.Contains(["Hello", "World"], "World") +// a.Contains({"Hello": "World"}, "Hello") func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool { return Contains(a.t, s, contains, msgAndArgs...) } +// Containsf asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// a.Containsf("Hello World", "World", "error message %s", "formatted") +// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted") +// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted") +func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool { + return Containsf(a.t, s, contains, msg, args...) +} + +// DirExists checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExists(path string, msgAndArgs ...interface{}) bool { + return DirExists(a.t, path, msgAndArgs...) +} + +// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExistsf(path string, msg string, args ...interface{}) bool { + return DirExistsf(a.t, path, msg, args...) +} + +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatch([1, 3, 2, 3], [1, 3, 3, 2]) +func (a *Assertions) ElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) bool { + return ElementsMatch(a.t, listA, listB, msgAndArgs...) +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatchf([1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) bool { + return ElementsMatchf(a.t, listA, listB, msg, args...) +} // Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. -// +// // a.Empty(obj) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool { return Empty(a.t, object, msgAndArgs...) } +// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// a.Emptyf(obj, "error message %s", "formatted") +func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) bool { + return Emptyf(a.t, object, msg, args...) +} // Equal asserts that two objects are equal. -// -// a.Equal(123, 123, "123 and 123 should be equal") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.Equal(123, 123) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { return Equal(a.t, expected, actual, msgAndArgs...) } - // EqualError asserts that a function returned an error (i.e. not `nil`) // and that it is equal to the provided error. -// +// // actualObj, err := SomeFunction() -// if assert.Error(t, err, "An error was expected") { -// assert.Equal(t, err, expectedError) -// } -// -// Returns whether the assertion was successful (true) or not (false). +// a.EqualError(err, expectedErrorString) func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) bool { return EqualError(a.t, theError, errString, msgAndArgs...) } +// EqualErrorf asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted") +func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) bool { + return EqualErrorf(a.t, theError, errString, msg, args...) +} // EqualValues asserts that two objects are equal or convertable to the same types // and equal. -// -// a.EqualValues(uint32(123), int32(123), "123 and 123 should be equal") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.EqualValues(uint32(123), int32(123)) func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { return EqualValues(a.t, expected, actual, msgAndArgs...) } +// EqualValuesf asserts that two objects are equal or convertable to the same types +// and equal. +// +// a.EqualValuesf(uint32(123, "error message %s", "formatted"), int32(123)) +func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + return EqualValuesf(a.t, expected, actual, msg, args...) +} + +// Equalf asserts that two objects are equal. +// +// a.Equalf(123, 123, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + return Equalf(a.t, expected, actual, msg, args...) +} // Error asserts that a function returned an error (i.e. not `nil`). -// +// // actualObj, err := SomeFunction() -// if a.Error(err, "An error was expected") { -// assert.Equal(t, err, expectedError) +// if a.Error(err) { +// assert.Equal(t, expectedError, err) // } -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool { return Error(a.t, err, msgAndArgs...) } +// Errorf asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if a.Errorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } +func (a *Assertions) Errorf(err error, msg string, args ...interface{}) bool { + return Errorf(a.t, err, msg, args...) +} -// Exactly asserts that two objects are equal is value and type. -// -// a.Exactly(int32(123), int64(123), "123 and 123 should NOT be equal") -// -// Returns whether the assertion was successful (true) or not (false). +// Exactly asserts that two objects are equal in value and type. +// +// a.Exactly(int32(123), int64(123)) func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { return Exactly(a.t, expected, actual, msgAndArgs...) } +// Exactlyf asserts that two objects are equal in value and type. +// +// a.Exactlyf(int32(123, "error message %s", "formatted"), int64(123)) +func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + return Exactlyf(a.t, expected, actual, msg, args...) +} // Fail reports a failure through func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) bool { return Fail(a.t, failureMessage, msgAndArgs...) } - // FailNow fails test func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) bool { return FailNow(a.t, failureMessage, msgAndArgs...) } +// FailNowf fails test +func (a *Assertions) FailNowf(failureMessage string, msg string, args ...interface{}) bool { + return FailNowf(a.t, failureMessage, msg, args...) +} + +// Failf reports a failure through +func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{}) bool { + return Failf(a.t, failureMessage, msg, args...) +} // False asserts that the specified value is false. -// -// a.False(myBool, "myBool should be false") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.False(myBool) func (a *Assertions) False(value bool, msgAndArgs ...interface{}) bool { return False(a.t, value, msgAndArgs...) } +// Falsef asserts that the specified value is false. +// +// a.Falsef(myBool, "error message %s", "formatted") +func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) bool { + return Falsef(a.t, value, msg, args...) +} + +// FileExists checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExists(path string, msgAndArgs ...interface{}) bool { + return FileExists(a.t, path, msgAndArgs...) +} + +// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) bool { + return FileExistsf(a.t, path, msg, args...) +} // HTTPBodyContains asserts that a specified handler returns a // body that contains a string. -// +// // a.HTTPBodyContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") -// +// // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { - return HTTPBodyContains(a.t, handler, method, url, values, str) +func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { + return HTTPBodyContains(a.t, handler, method, url, values, str, msgAndArgs...) } +// HTTPBodyContainsf asserts that a specified handler returns a +// body that contains a string. +// +// a.HTTPBodyContainsf(myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { + return HTTPBodyContainsf(a.t, handler, method, url, values, str, msg, args...) +} // HTTPBodyNotContains asserts that a specified handler returns a // body that does not contain a string. -// +// // a.HTTPBodyNotContains(myHandler, "www.google.com", nil, "I'm Feeling Lucky") -// +// // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}) bool { - return HTTPBodyNotContains(a.t, handler, method, url, values, str) +func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { + return HTTPBodyNotContains(a.t, handler, method, url, values, str, msgAndArgs...) } +// HTTPBodyNotContainsf asserts that a specified handler returns a +// body that does not contain a string. +// +// a.HTTPBodyNotContainsf(myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { + return HTTPBodyNotContainsf(a.t, handler, method, url, values, str, msg, args...) +} // HTTPError asserts that a specified handler returns an error status code. -// +// // a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} -// +// // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPError(a.t, handler, method, url, values) +func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool { + return HTTPError(a.t, handler, method, url, values, msgAndArgs...) } +// HTTPErrorf asserts that a specified handler returns an error status code. +// +// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). +func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + return HTTPErrorf(a.t, handler, method, url, values, msg, args...) +} // HTTPRedirect asserts that a specified handler returns a redirect status code. -// +// // a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} -// +// // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPRedirect(a.t, handler, method, url, values) +func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool { + return HTTPRedirect(a.t, handler, method, url, values, msgAndArgs...) } +// HTTPRedirectf asserts that a specified handler returns a redirect status code. +// +// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). +func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + return HTTPRedirectf(a.t, handler, method, url, values, msg, args...) +} // HTTPSuccess asserts that a specified handler returns a success status code. -// +// // a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) -// +// // Returns whether the assertion was successful (true) or not (false). -func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values) bool { - return HTTPSuccess(a.t, handler, method, url, values) +func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool { + return HTTPSuccess(a.t, handler, method, url, values, msgAndArgs...) } +// HTTPSuccessf asserts that a specified handler returns a success status code. +// +// a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + return HTTPSuccessf(a.t, handler, method, url, values, msg, args...) +} // Implements asserts that an object is implemented by the specified interface. -// -// a.Implements((*MyInterface)(nil), new(MyObject), "MyObject") +// +// a.Implements((*MyInterface)(nil), new(MyObject)) func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool { return Implements(a.t, interfaceObject, object, msgAndArgs...) } +// Implementsf asserts that an object is implemented by the specified interface. +// +// a.Implementsf((*MyInterface, "error message %s", "formatted")(nil), new(MyObject)) +func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool { + return Implementsf(a.t, interfaceObject, object, msg, args...) +} // InDelta asserts that the two numerals are within delta of each other. -// +// // a.InDelta(math.Pi, (22 / 7.0), 0.01) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { return InDelta(a.t, expected, actual, delta, msgAndArgs...) } +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + return InDeltaMapValues(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValuesf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + return InDeltaMapValuesf(a.t, expected, actual, delta, msg, args...) +} // InDeltaSlice is the same as InDelta, except it compares two slices. func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { return InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...) } +// InDeltaSlicef is the same as InDelta, except it compares two slices. +func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + return InDeltaSlicef(a.t, expected, actual, delta, msg, args...) +} + +// InDeltaf asserts that the two numerals are within delta of each other. +// +// a.InDeltaf(math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01) +func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + return InDeltaf(a.t, expected, actual, delta, msg, args...) +} // InEpsilon asserts that expected and actual have a relative error less than epsilon -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { return InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...) } - -// InEpsilonSlice is the same as InEpsilon, except it compares two slices. -func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { - return InEpsilonSlice(a.t, expected, actual, delta, msgAndArgs...) +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { + return InEpsilonSlice(a.t, expected, actual, epsilon, msgAndArgs...) } +// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices. +func (a *Assertions) InEpsilonSlicef(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool { + return InEpsilonSlicef(a.t, expected, actual, epsilon, msg, args...) +} + +// InEpsilonf asserts that expected and actual have a relative error less than epsilon +func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool { + return InEpsilonf(a.t, expected, actual, epsilon, msg, args...) +} // IsType asserts that the specified objects are of the same type. func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool { return IsType(a.t, expectedType, object, msgAndArgs...) } +// IsTypef asserts that the specified objects are of the same type. +func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) bool { + return IsTypef(a.t, expectedType, object, msg, args...) +} // JSONEq asserts that two JSON strings are equivalent. -// +// // a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) bool { return JSONEq(a.t, expected, actual, msgAndArgs...) } +// JSONEqf asserts that two JSON strings are equivalent. +// +// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) bool { + return JSONEqf(a.t, expected, actual, msg, args...) +} // Len asserts that the specified object has specific length. // Len also fails if the object has a type that len() not accept. -// -// a.Len(mySlice, 3, "The size of slice is not 3") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.Len(mySlice, 3) func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) bool { return Len(a.t, object, length, msgAndArgs...) } +// Lenf asserts that the specified object has specific length. +// Lenf also fails if the object has a type that len() not accept. +// +// a.Lenf(mySlice, 3, "error message %s", "formatted") +func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) bool { + return Lenf(a.t, object, length, msg, args...) +} // Nil asserts that the specified object is nil. -// -// a.Nil(err, "err should be nothing") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.Nil(err) func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) bool { return Nil(a.t, object, msgAndArgs...) } +// Nilf asserts that the specified object is nil. +// +// a.Nilf(err, "error message %s", "formatted") +func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) bool { + return Nilf(a.t, object, msg, args...) +} // NoError asserts that a function returned no error (i.e. `nil`). -// +// // actualObj, err := SomeFunction() // if a.NoError(err) { -// assert.Equal(t, actualObj, expectedObj) +// assert.Equal(t, expectedObj, actualObj) // } -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) bool { return NoError(a.t, err, msgAndArgs...) } +// NoErrorf asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if a.NoErrorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } +func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) bool { + return NoErrorf(a.t, err, msg, args...) +} // NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. -// -// a.NotContains("Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'") -// a.NotContains(["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'") -// a.NotContains({"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.NotContains("Hello World", "Earth") +// a.NotContains(["Hello", "World"], "Earth") +// a.NotContains({"Hello": "World"}, "Earth") func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool { return NotContains(a.t, s, contains, msgAndArgs...) } +// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// a.NotContainsf("Hello World", "Earth", "error message %s", "formatted") +// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted") +// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted") +func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool { + return NotContainsf(a.t, s, contains, msg, args...) +} // NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. -// +// // if a.NotEmpty(obj) { // assert.Equal(t, "two", obj[1]) // } -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) bool { return NotEmpty(a.t, object, msgAndArgs...) } +// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if a.NotEmptyf(obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } +func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) bool { + return NotEmptyf(a.t, object, msg, args...) +} // NotEqual asserts that the specified values are NOT equal. -// -// a.NotEqual(obj1, obj2, "two objects shouldn't be equal") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.NotEqual(obj1, obj2) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { return NotEqual(a.t, expected, actual, msgAndArgs...) } +// NotEqualf asserts that the specified values are NOT equal. +// +// a.NotEqualf(obj1, obj2, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + return NotEqualf(a.t, expected, actual, msg, args...) +} // NotNil asserts that the specified object is not nil. -// -// a.NotNil(err, "err should be something") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.NotNil(err) func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) bool { return NotNil(a.t, object, msgAndArgs...) } +// NotNilf asserts that the specified object is not nil. +// +// a.NotNilf(err, "error message %s", "formatted") +func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) bool { + return NotNilf(a.t, object, msg, args...) +} // NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. -// -// a.NotPanics(func(){ -// RemainCalm() -// }, "Calling RemainCalm() should NOT panic") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.NotPanics(func(){ RemainCalm() }) func (a *Assertions) NotPanics(f PanicTestFunc, msgAndArgs ...interface{}) bool { return NotPanics(a.t, f, msgAndArgs...) } +// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted") +func (a *Assertions) NotPanicsf(f PanicTestFunc, msg string, args ...interface{}) bool { + return NotPanicsf(a.t, f, msg, args...) +} // NotRegexp asserts that a specified regexp does not match a string. -// +// // a.NotRegexp(regexp.MustCompile("starts"), "it's starting") // a.NotRegexp("^start", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { return NotRegexp(a.t, rx, str, msgAndArgs...) } +// NotRegexpf asserts that a specified regexp does not match a string. +// +// a.NotRegexpf(regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting") +// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted") +func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool { + return NotRegexpf(a.t, rx, str, msg, args...) +} -// NotZero asserts that i is not the zero value for its type and returns the truth. +// NotSubset asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") +func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool { + return NotSubset(a.t, list, subset, msgAndArgs...) +} + +// NotSubsetf asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") +func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool { + return NotSubsetf(a.t, list, subset, msg, args...) +} + +// NotZero asserts that i is not the zero value for its type. func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) bool { return NotZero(a.t, i, msgAndArgs...) } +// NotZerof asserts that i is not the zero value for its type. +func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) bool { + return NotZerof(a.t, i, msg, args...) +} // Panics asserts that the code inside the specified PanicTestFunc panics. -// -// a.Panics(func(){ -// GoCrazy() -// }, "Calling GoCrazy() should panic") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.Panics(func(){ GoCrazy() }) func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool { return Panics(a.t, f, msgAndArgs...) } +// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// a.PanicsWithValue("crazy error", func(){ GoCrazy() }) +func (a *Assertions) PanicsWithValue(expected interface{}, f PanicTestFunc, msgAndArgs ...interface{}) bool { + return PanicsWithValue(a.t, expected, f, msgAndArgs...) +} + +// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) PanicsWithValuef(expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool { + return PanicsWithValuef(a.t, expected, f, msg, args...) +} + +// Panicsf asserts that the code inside the specified PanicTestFunc panics. +// +// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) Panicsf(f PanicTestFunc, msg string, args ...interface{}) bool { + return Panicsf(a.t, f, msg, args...) +} // Regexp asserts that a specified regexp matches a string. -// +// // a.Regexp(regexp.MustCompile("start"), "it's starting") // a.Regexp("start...$", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { return Regexp(a.t, rx, str, msgAndArgs...) } +// Regexpf asserts that a specified regexp matches a string. +// +// a.Regexpf(regexp.MustCompile("start", "error message %s", "formatted"), "it's starting") +// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted") +func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool { + return Regexpf(a.t, rx, str, msg, args...) +} + +// Subset asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") +func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool { + return Subset(a.t, list, subset, msgAndArgs...) +} + +// Subsetf asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") +func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool { + return Subsetf(a.t, list, subset, msg, args...) +} // True asserts that the specified value is true. -// -// a.True(myBool, "myBool should be true") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.True(myBool) func (a *Assertions) True(value bool, msgAndArgs ...interface{}) bool { return True(a.t, value, msgAndArgs...) } +// Truef asserts that the specified value is true. +// +// a.Truef(myBool, "error message %s", "formatted") +func (a *Assertions) Truef(value bool, msg string, args ...interface{}) bool { + return Truef(a.t, value, msg, args...) +} // WithinDuration asserts that the two times are within duration delta of each other. -// -// a.WithinDuration(time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s") -// -// Returns whether the assertion was successful (true) or not (false). +// +// a.WithinDuration(time.Now(), time.Now(), 10*time.Second) func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool { return WithinDuration(a.t, expected, actual, delta, msgAndArgs...) } +// WithinDurationf asserts that the two times are within duration delta of each other. +// +// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool { + return WithinDurationf(a.t, expected, actual, delta, msg, args...) +} -// Zero asserts that i is the zero value for its type and returns the truth. +// Zero asserts that i is the zero value for its type. func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) bool { return Zero(a.t, i, msgAndArgs...) } + +// Zerof asserts that i is the zero value for its type. +func (a *Assertions) Zerof(i interface{}, msg string, args ...interface{}) bool { + return Zerof(a.t, i, msg, args...) +} diff --git a/vendor/github.com/stretchr/testify/assert/assertions.go b/vendor/github.com/stretchr/testify/assert/assertions.go index 348d5f1bc..47bda7786 100644 --- a/vendor/github.com/stretchr/testify/assert/assertions.go +++ b/vendor/github.com/stretchr/testify/assert/assertions.go @@ -4,8 +4,10 @@ import ( "bufio" "bytes" "encoding/json" + "errors" "fmt" "math" + "os" "reflect" "regexp" "runtime" @@ -18,6 +20,8 @@ import ( "github.com/pmezard/go-difflib/difflib" ) +//go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_format.go.tmpl + // TestingT is an interface wrapper around *testing.T type TestingT interface { Errorf(format string, args ...interface{}) @@ -38,7 +42,15 @@ func ObjectsAreEqual(expected, actual interface{}) bool { if expected == nil || actual == nil { return expected == actual } - + if exp, ok := expected.([]byte); ok { + act, ok := actual.([]byte) + if !ok { + return false + } else if exp == nil || act == nil { + return exp == nil && act == nil + } + return bytes.Equal(exp, act) + } return reflect.DeepEqual(expected, actual) } @@ -65,7 +77,7 @@ func ObjectsAreEqualValues(expected, actual interface{}) bool { /* CallerInfo is necessary because the assert functions use the testing object internally, causing it to print the file:line of the assert method, rather than where -the problem actually occured in calling code.*/ +the problem actually occurred in calling code.*/ // CallerInfo returns an array of strings containing the file and line number // of each stack frame leading from the current test to the assert call that @@ -82,7 +94,9 @@ func CallerInfo() []string { for i := 0; ; i++ { pc, file, line, ok = runtime.Caller(i) if !ok { - return nil + // The breaks below failed to terminate the loop, and we ran off the + // end of the call stack. + break } // This is a huge edge case, but it will panic if this is the case, see #180 @@ -90,18 +104,30 @@ func CallerInfo() []string { break } - parts := strings.Split(file, "/") - dir := parts[len(parts)-2] - file = parts[len(parts)-1] - if (dir != "assert" && dir != "mock" && dir != "require") || file == "mock_test.go" { - callers = append(callers, fmt.Sprintf("%s:%d", file, line)) - } - f := runtime.FuncForPC(pc) if f == nil { break } name = f.Name() + + // testing.tRunner is the standard library function that calls + // tests. Subtests are called directly by tRunner, without going through + // the Test/Benchmark/Example function that contains the t.Run calls, so + // with subtests we should break when we hit tRunner, without adding it + // to the list of callers. + if name == "testing.tRunner" { + break + } + + parts := strings.Split(file, "/") + file = parts[len(parts)-1] + if len(parts) > 1 { + dir := parts[len(parts)-2] + if (dir != "assert" && dir != "mock" && dir != "require") || file == "mock_test.go" { + callers = append(callers, fmt.Sprintf("%s:%d", file, line)) + } + } + // Drop the package segments := strings.Split(name, ".") name = segments[len(segments)-1] @@ -141,7 +167,7 @@ func getWhitespaceString() string { parts := strings.Split(file, "/") file = parts[len(parts)-1] - return strings.Repeat(" ", len(fmt.Sprintf("%s:%d: ", file, line))) + return strings.Repeat(" ", len(fmt.Sprintf("%s:%d: ", file, line))) } @@ -158,22 +184,18 @@ func messageFromMsgAndArgs(msgAndArgs ...interface{}) string { return "" } -// Indents all lines of the message by appending a number of tabs to each line, in an output format compatible with Go's -// test printing (see inner comment for specifics) -func indentMessageLines(message string, tabs int) string { +// Aligns the provided message so that all lines after the first line start at the same location as the first line. +// Assumes that the first line starts at the correct location (after carriage return, tab, label, spacer and tab). +// The longestLabelLen parameter specifies the length of the longest label in the output (required becaues this is the +// basis on which the alignment occurs). +func indentMessageLines(message string, longestLabelLen int) string { outBuf := new(bytes.Buffer) for i, scanner := 0, bufio.NewScanner(strings.NewReader(message)); scanner.Scan(); i++ { + // no need to align first line because it starts at the correct location (after the label) if i != 0 { - outBuf.WriteRune('\n') - } - for ii := 0; ii < tabs; ii++ { - outBuf.WriteRune('\t') - // Bizarrely, all lines except the first need one fewer tabs prepended, so deliberately advance the counter - // by 1 prematurely. - if ii == 0 && i > 0 { - ii++ - } + // append alignLen+1 spaces to align with "{{longestLabel}}:" before adding tab + outBuf.WriteString("\n\r\t" + strings.Repeat(" ", longestLabelLen+1) + "\t") } outBuf.WriteString(scanner.Text()) } @@ -205,42 +227,70 @@ func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool // Fail reports a failure through func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool { + content := []labeledContent{ + {"Error Trace", strings.Join(CallerInfo(), "\n\r\t\t\t")}, + {"Error", failureMessage}, + } + + // Add test name if the Go version supports it + if n, ok := t.(interface { + Name() string + }); ok { + content = append(content, labeledContent{"Test", n.Name()}) + } message := messageFromMsgAndArgs(msgAndArgs...) - - errorTrace := strings.Join(CallerInfo(), "\n\r\t\t\t") if len(message) > 0 { - t.Errorf("\r%s\r\tError Trace:\t%s\n"+ - "\r\tError:%s\n"+ - "\r\tMessages:\t%s\n\r", - getWhitespaceString(), - errorTrace, - indentMessageLines(failureMessage, 2), - message) - } else { - t.Errorf("\r%s\r\tError Trace:\t%s\n"+ - "\r\tError:%s\n\r", - getWhitespaceString(), - errorTrace, - indentMessageLines(failureMessage, 2)) + content = append(content, labeledContent{"Messages", message}) } + t.Errorf("%s", "\r"+getWhitespaceString()+labeledOutput(content...)) + return false } +type labeledContent struct { + label string + content string +} + +// labeledOutput returns a string consisting of the provided labeledContent. Each labeled output is appended in the following manner: +// +// \r\t{{label}}:{{align_spaces}}\t{{content}}\n +// +// The initial carriage return is required to undo/erase any padding added by testing.T.Errorf. The "\t{{label}}:" is for the label. +// If a label is shorter than the longest label provided, padding spaces are added to make all the labels match in length. Once this +// alignment is achieved, "\t{{content}}\n" is added for the output. +// +// If the content of the labeledOutput contains line breaks, the subsequent lines are aligned so that they start at the same location as the first line. +func labeledOutput(content ...labeledContent) string { + longestLabel := 0 + for _, v := range content { + if len(v.label) > longestLabel { + longestLabel = len(v.label) + } + } + var output string + for _, v := range content { + output += "\r\t" + v.label + ":" + strings.Repeat(" ", longestLabel-len(v.label)) + "\t" + indentMessageLines(v.content, longestLabel) + "\n" + } + return output +} + // Implements asserts that an object is implemented by the specified interface. // -// assert.Implements(t, (*MyInterface)(nil), new(MyObject), "MyObject") +// assert.Implements(t, (*MyInterface)(nil), new(MyObject)) func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool { - interfaceType := reflect.TypeOf(interfaceObject).Elem() + if object == nil { + return Fail(t, fmt.Sprintf("Cannot check if nil implements %v", interfaceType), msgAndArgs...) + } if !reflect.TypeOf(object).Implements(interfaceType) { return Fail(t, fmt.Sprintf("%T must implement %v", object, interfaceType), msgAndArgs...) } return true - } // IsType asserts that the specified objects are of the same type. @@ -255,43 +305,66 @@ func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs // Equal asserts that two objects are equal. // -// assert.Equal(t, 123, 123, "123 and 123 should be equal") +// assert.Equal(t, 123, 123) // -// Returns whether the assertion was successful (true) or not (false). +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + if err := validateEqualArgs(expected, actual); err != nil { + return Fail(t, fmt.Sprintf("Invalid operation: %#v == %#v (%s)", + expected, actual, err), msgAndArgs...) + } if !ObjectsAreEqual(expected, actual) { diff := diff(expected, actual) - return Fail(t, fmt.Sprintf("Not equal: %#v (expected)\n"+ - " != %#v (actual)%s", expected, actual, diff), msgAndArgs...) + expected, actual = formatUnequalValues(expected, actual) + return Fail(t, fmt.Sprintf("Not equal: \n"+ + "expected: %s\n"+ + "actual : %s%s", expected, actual, diff), msgAndArgs...) } return true } +// formatUnequalValues takes two values of arbitrary types and returns string +// representations appropriate to be presented to the user. +// +// If the values are not of like type, the returned strings will be prefixed +// with the type name, and the value will be enclosed in parenthesis similar +// to a type conversion in the Go grammar. +func formatUnequalValues(expected, actual interface{}) (e string, a string) { + if reflect.TypeOf(expected) != reflect.TypeOf(actual) { + return fmt.Sprintf("%T(%#v)", expected, expected), + fmt.Sprintf("%T(%#v)", actual, actual) + } + + return fmt.Sprintf("%#v", expected), + fmt.Sprintf("%#v", actual) +} + // EqualValues asserts that two objects are equal or convertable to the same types // and equal. // -// assert.EqualValues(t, uint32(123), int32(123), "123 and 123 should be equal") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.EqualValues(t, uint32(123), int32(123)) func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { if !ObjectsAreEqualValues(expected, actual) { - return Fail(t, fmt.Sprintf("Not equal: %#v (expected)\n"+ - " != %#v (actual)", expected, actual), msgAndArgs...) + diff := diff(expected, actual) + expected, actual = formatUnequalValues(expected, actual) + return Fail(t, fmt.Sprintf("Not equal: \n"+ + "expected: %s\n"+ + "actual : %s%s", expected, actual, diff), msgAndArgs...) } return true } -// Exactly asserts that two objects are equal is value and type. +// Exactly asserts that two objects are equal in value and type. // -// assert.Exactly(t, int32(123), int64(123), "123 and 123 should NOT be equal") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.Exactly(t, int32(123), int64(123)) func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { aType := reflect.TypeOf(expected) @@ -307,9 +380,7 @@ func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{} // NotNil asserts that the specified object is not nil. // -// assert.NotNil(t, err, "err should be something") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.NotNil(t, err) func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { if !isNil(object) { return true @@ -334,9 +405,7 @@ func isNil(object interface{}) bool { // Nil asserts that the specified object is nil. // -// assert.Nil(t, err, "err should be nothing") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.Nil(t, err) func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { if isNil(object) { return true @@ -344,74 +413,38 @@ func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { return Fail(t, fmt.Sprintf("Expected nil, but got: %#v", object), msgAndArgs...) } -var numericZeros = []interface{}{ - int(0), - int8(0), - int16(0), - int32(0), - int64(0), - uint(0), - uint8(0), - uint16(0), - uint32(0), - uint64(0), - float32(0), - float64(0), -} - // isEmpty gets whether the specified object is considered empty or not. func isEmpty(object interface{}) bool { + // get nil case out of the way if object == nil { return true - } else if object == "" { - return true - } else if object == false { - return true - } - - for _, v := range numericZeros { - if object == v { - return true - } } objValue := reflect.ValueOf(object) switch objValue.Kind() { - case reflect.Map: - fallthrough - case reflect.Slice, reflect.Chan: - { - return (objValue.Len() == 0) - } - case reflect.Struct: - switch object.(type) { - case time.Time: - return object.(time.Time).IsZero() - } + // collection types are empty when they have no element + case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: + return objValue.Len() == 0 + // pointers are empty if nil or if the value they point to is empty case reflect.Ptr: - { - if objValue.IsNil() { - return true - } - switch object.(type) { - case *time.Time: - return object.(*time.Time).IsZero() - default: - return false - } + if objValue.IsNil() { + return true } + deref := objValue.Elem().Interface() + return isEmpty(deref) + // for all other types, compare against the zero value + default: + zero := reflect.Zero(objValue.Type()) + return reflect.DeepEqual(object, zero.Interface()) } - return false } // Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either // a slice or a channel with len == 0. // // assert.Empty(t, obj) -// -// Returns whether the assertion was successful (true) or not (false). func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { pass := isEmpty(object) @@ -429,8 +462,6 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { // if assert.NotEmpty(t, obj) { // assert.Equal(t, "two", obj[1]) // } -// -// Returns whether the assertion was successful (true) or not (false). func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { pass := !isEmpty(object) @@ -457,9 +488,7 @@ func getLen(x interface{}) (ok bool, length int) { // Len asserts that the specified object has specific length. // Len also fails if the object has a type that len() not accept. // -// assert.Len(t, mySlice, 3, "The size of slice is not 3") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.Len(t, mySlice, 3) func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) bool { ok, l := getLen(object) if !ok { @@ -474,9 +503,7 @@ func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) // True asserts that the specified value is true. // -// assert.True(t, myBool, "myBool should be true") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.True(t, myBool) func True(t TestingT, value bool, msgAndArgs ...interface{}) bool { if value != true { @@ -489,9 +516,7 @@ func True(t TestingT, value bool, msgAndArgs ...interface{}) bool { // False asserts that the specified value is false. // -// assert.False(t, myBool, "myBool should be false") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.False(t, myBool) func False(t TestingT, value bool, msgAndArgs ...interface{}) bool { if value != false { @@ -504,10 +529,15 @@ func False(t TestingT, value bool, msgAndArgs ...interface{}) bool { // NotEqual asserts that the specified values are NOT equal. // -// assert.NotEqual(t, obj1, obj2, "two objects shouldn't be equal") +// assert.NotEqual(t, obj1, obj2) // -// Returns whether the assertion was successful (true) or not (false). +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + if err := validateEqualArgs(expected, actual); err != nil { + return Fail(t, fmt.Sprintf("Invalid operation: %#v != %#v (%s)", + expected, actual, err), msgAndArgs...) + } if ObjectsAreEqual(expected, actual) { return Fail(t, fmt.Sprintf("Should not be: %#v\n", actual), msgAndArgs...) @@ -558,11 +588,9 @@ func includeElement(list interface{}, element interface{}) (ok, found bool) { // Contains asserts that the specified string, list(array, slice...) or map contains the // specified substring or element. // -// assert.Contains(t, "Hello World", "World", "But 'Hello World' does contain 'World'") -// assert.Contains(t, ["Hello", "World"], "World", "But ["Hello", "World"] does contain 'World'") -// assert.Contains(t, {"Hello": "World"}, "Hello", "But {'Hello': 'World'} does contain 'Hello'") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.Contains(t, "Hello World", "World") +// assert.Contains(t, ["Hello", "World"], "World") +// assert.Contains(t, {"Hello": "World"}, "Hello") func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool { ok, found := includeElement(s, contains) @@ -580,11 +608,9 @@ func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bo // NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the // specified substring or element. // -// assert.NotContains(t, "Hello World", "Earth", "But 'Hello World' does NOT contain 'Earth'") -// assert.NotContains(t, ["Hello", "World"], "Earth", "But ['Hello', 'World'] does NOT contain 'Earth'") -// assert.NotContains(t, {"Hello": "World"}, "Earth", "But {'Hello': 'World'} does NOT contain 'Earth'") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.NotContains(t, "Hello World", "Earth") +// assert.NotContains(t, ["Hello", "World"], "Earth") +// assert.NotContains(t, {"Hello": "World"}, "Earth") func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool { ok, found := includeElement(s, contains) @@ -599,6 +625,142 @@ func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) } +// Subset asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") +func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) { + if subset == nil { + return true // we consider nil to be equal to the nil set + } + + subsetValue := reflect.ValueOf(subset) + defer func() { + if e := recover(); e != nil { + ok = false + } + }() + + listKind := reflect.TypeOf(list).Kind() + subsetKind := reflect.TypeOf(subset).Kind() + + if listKind != reflect.Array && listKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...) + } + + if subsetKind != reflect.Array && subsetKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...) + } + + for i := 0; i < subsetValue.Len(); i++ { + element := subsetValue.Index(i).Interface() + ok, found := includeElement(list, element) + if !ok { + return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", list), msgAndArgs...) + } + if !found { + return Fail(t, fmt.Sprintf("\"%s\" does not contain \"%s\"", list, element), msgAndArgs...) + } + } + + return true +} + +// NotSubset asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// assert.NotSubset(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") +func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) { + if subset == nil { + return Fail(t, fmt.Sprintf("nil is the empty set which is a subset of every set"), msgAndArgs...) + } + + subsetValue := reflect.ValueOf(subset) + defer func() { + if e := recover(); e != nil { + ok = false + } + }() + + listKind := reflect.TypeOf(list).Kind() + subsetKind := reflect.TypeOf(subset).Kind() + + if listKind != reflect.Array && listKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...) + } + + if subsetKind != reflect.Array && subsetKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...) + } + + for i := 0; i < subsetValue.Len(); i++ { + element := subsetValue.Index(i).Interface() + ok, found := includeElement(list, element) + if !ok { + return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", list), msgAndArgs...) + } + if !found { + return true + } + } + + return Fail(t, fmt.Sprintf("%q is a subset of %q", subset, list), msgAndArgs...) +} + +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// assert.ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2]) +func ElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface{}) (ok bool) { + if isEmpty(listA) && isEmpty(listB) { + return true + } + + aKind := reflect.TypeOf(listA).Kind() + bKind := reflect.TypeOf(listB).Kind() + + if aKind != reflect.Array && aKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", listA, aKind), msgAndArgs...) + } + + if bKind != reflect.Array && bKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", listB, bKind), msgAndArgs...) + } + + aValue := reflect.ValueOf(listA) + bValue := reflect.ValueOf(listB) + + aLen := aValue.Len() + bLen := bValue.Len() + + if aLen != bLen { + return Fail(t, fmt.Sprintf("lengths don't match: %d != %d", aLen, bLen), msgAndArgs...) + } + + // Mark indexes in bValue that we already used + visited := make([]bool, bLen) + for i := 0; i < aLen; i++ { + element := aValue.Index(i).Interface() + found := false + for j := 0; j < bLen; j++ { + if visited[j] { + continue + } + if ObjectsAreEqual(bValue.Index(j).Interface(), element) { + visited[j] = true + found = true + break + } + } + if !found { + return Fail(t, fmt.Sprintf("element %s appears more times in %s than in %s", element, aValue, bValue), msgAndArgs...) + } + } + + return true +} + // Condition uses a Comparison to assert a complex condition. func Condition(t TestingT, comp Comparison, msgAndArgs ...interface{}) bool { result := comp() @@ -636,11 +798,7 @@ func didPanic(f PanicTestFunc) (bool, interface{}) { // Panics asserts that the code inside the specified PanicTestFunc panics. // -// assert.Panics(t, func(){ -// GoCrazy() -// }, "Calling GoCrazy() should panic") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.Panics(t, func(){ GoCrazy() }) func Panics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { if funcDidPanic, panicValue := didPanic(f); !funcDidPanic { @@ -650,13 +808,26 @@ func Panics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { return true } +// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() }) +func PanicsWithValue(t TestingT, expected interface{}, f PanicTestFunc, msgAndArgs ...interface{}) bool { + + funcDidPanic, panicValue := didPanic(f) + if !funcDidPanic { + return Fail(t, fmt.Sprintf("func %#v should panic\n\r\tPanic value:\t%v", f, panicValue), msgAndArgs...) + } + if panicValue != expected { + return Fail(t, fmt.Sprintf("func %#v should panic with value:\t%v\n\r\tPanic value:\t%v", f, expected, panicValue), msgAndArgs...) + } + + return true +} + // NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. // -// assert.NotPanics(t, func(){ -// RemainCalm() -// }, "Calling RemainCalm() should NOT panic") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.NotPanics(t, func(){ RemainCalm() }) func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { if funcDidPanic, panicValue := didPanic(f); funcDidPanic { @@ -668,9 +839,7 @@ func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { // WithinDuration asserts that the two times are within duration delta of each other. // -// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second, "The difference should not be more than 10s") -// -// Returns whether the assertion was successful (true) or not (false). +// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second) func WithinDuration(t TestingT, expected, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool { dt := expected.Sub(actual) @@ -708,6 +877,8 @@ func toFloat(x interface{}) (float64, bool) { xf = float64(xn) case float64: xf = float64(xn) + case time.Duration: + xf = float64(xn) default: xok = false } @@ -718,8 +889,6 @@ func toFloat(x interface{}) (float64, bool) { // InDelta asserts that the two numerals are within delta of each other. // // assert.InDelta(t, math.Pi, (22 / 7.0), 0.01) -// -// Returns whether the assertion was successful (true) or not (false). func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { af, aok := toFloat(expected) @@ -730,7 +899,7 @@ func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs } if math.IsNaN(af) { - return Fail(t, fmt.Sprintf("Actual must not be NaN"), msgAndArgs...) + return Fail(t, fmt.Sprintf("Expected must not be NaN"), msgAndArgs...) } if math.IsNaN(bf) { @@ -757,7 +926,7 @@ func InDeltaSlice(t TestingT, expected, actual interface{}, delta float64, msgAn expectedSlice := reflect.ValueOf(expected) for i := 0; i < actualSlice.Len(); i++ { - result := InDelta(t, actualSlice.Index(i).Interface(), expectedSlice.Index(i).Interface(), delta) + result := InDelta(t, actualSlice.Index(i).Interface(), expectedSlice.Index(i).Interface(), delta, msgAndArgs...) if !result { return result } @@ -766,6 +935,47 @@ func InDeltaSlice(t TestingT, expected, actual interface{}, delta float64, msgAn return true } +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValues(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + if expected == nil || actual == nil || + reflect.TypeOf(actual).Kind() != reflect.Map || + reflect.TypeOf(expected).Kind() != reflect.Map { + return Fail(t, "Arguments must be maps", msgAndArgs...) + } + + expectedMap := reflect.ValueOf(expected) + actualMap := reflect.ValueOf(actual) + + if expectedMap.Len() != actualMap.Len() { + return Fail(t, "Arguments must have the same number of keys", msgAndArgs...) + } + + for _, k := range expectedMap.MapKeys() { + ev := expectedMap.MapIndex(k) + av := actualMap.MapIndex(k) + + if !ev.IsValid() { + return Fail(t, fmt.Sprintf("missing key %q in expected map", k), msgAndArgs...) + } + + if !av.IsValid() { + return Fail(t, fmt.Sprintf("missing key %q in actual map", k), msgAndArgs...) + } + + if !InDelta( + t, + ev.Interface(), + av.Interface(), + delta, + msgAndArgs..., + ) { + return false + } + } + + return true +} + func calcRelativeError(expected, actual interface{}) (float64, error) { af, aok := toFloat(expected) if !aok { @@ -776,15 +986,13 @@ func calcRelativeError(expected, actual interface{}) (float64, error) { } bf, bok := toFloat(actual) if !bok { - return 0, fmt.Errorf("expected value %q cannot be converted to float", actual) + return 0, fmt.Errorf("actual value %q cannot be converted to float", actual) } return math.Abs(af-bf) / math.Abs(af), nil } // InEpsilon asserts that expected and actual have a relative error less than epsilon -// -// Returns whether the assertion was successful (true) or not (false). func InEpsilon(t TestingT, expected, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { actualEpsilon, err := calcRelativeError(expected, actual) if err != nil { @@ -792,7 +1000,7 @@ func InEpsilon(t TestingT, expected, actual interface{}, epsilon float64, msgAnd } if actualEpsilon > epsilon { return Fail(t, fmt.Sprintf("Relative error is too high: %#v (expected)\n"+ - " < %#v (actual)", actualEpsilon, epsilon), msgAndArgs...) + " < %#v (actual)", epsilon, actualEpsilon), msgAndArgs...) } return true @@ -827,13 +1035,11 @@ func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, m // // actualObj, err := SomeFunction() // if assert.NoError(t, err) { -// assert.Equal(t, actualObj, expectedObj) +// assert.Equal(t, expectedObj, actualObj) // } -// -// Returns whether the assertion was successful (true) or not (false). func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool { if err != nil { - return Fail(t, fmt.Sprintf("Received unexpected error %q", err), msgAndArgs...) + return Fail(t, fmt.Sprintf("Received unexpected error:\n%+v", err), msgAndArgs...) } return true @@ -842,16 +1048,13 @@ func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool { // Error asserts that a function returned an error (i.e. not `nil`). // // actualObj, err := SomeFunction() -// if assert.Error(t, err, "An error was expected") { -// assert.Equal(t, err, expectedError) +// if assert.Error(t, err) { +// assert.Equal(t, expectedError, err) // } -// -// Returns whether the assertion was successful (true) or not (false). func Error(t TestingT, err error, msgAndArgs ...interface{}) bool { - message := messageFromMsgAndArgs(msgAndArgs...) if err == nil { - return Fail(t, "An error is expected but got nil. %s", message) + return Fail(t, "An error is expected but got nil.", msgAndArgs...) } return true @@ -861,20 +1064,20 @@ func Error(t TestingT, err error, msgAndArgs ...interface{}) bool { // and that it is equal to the provided error. // // actualObj, err := SomeFunction() -// if assert.Error(t, err, "An error was expected") { -// assert.Equal(t, err, expectedError) -// } -// -// Returns whether the assertion was successful (true) or not (false). +// assert.EqualError(t, err, expectedErrorString) func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) bool { - - message := messageFromMsgAndArgs(msgAndArgs...) - if !NotNil(t, theError, "An error is expected but got nil. %s", message) { + if !Error(t, theError, msgAndArgs...) { return false } - s := "An error with value \"%s\" is expected but got \"%s\". %s" - return Equal(t, errString, theError.Error(), - s, errString, theError.Error(), message) + expected := errString + actual := theError.Error() + // don't need to use deep equals here, we know they are both strings + if expected != actual { + return Fail(t, fmt.Sprintf("Error message not equal:\n"+ + "expected: %q\n"+ + "actual : %q", expected, actual), msgAndArgs...) + } + return true } // matchRegexp return true if a specified regexp matches a string. @@ -895,8 +1098,6 @@ func matchRegexp(rx interface{}, str interface{}) bool { // // assert.Regexp(t, regexp.MustCompile("start"), "it's starting") // assert.Regexp(t, "start...$", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { match := matchRegexp(rx, str) @@ -912,8 +1113,6 @@ func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface // // assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") // assert.NotRegexp(t, "^start", "it's not starting") -// -// Returns whether the assertion was successful (true) or not (false). func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { match := matchRegexp(rx, str) @@ -925,7 +1124,7 @@ func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interf } -// Zero asserts that i is the zero value for its type and returns the truth. +// Zero asserts that i is the zero value for its type. func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { if i != nil && !reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) { return Fail(t, fmt.Sprintf("Should be zero, but was %v", i), msgAndArgs...) @@ -933,7 +1132,7 @@ func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { return true } -// NotZero asserts that i is not the zero value for its type and returns the truth. +// NotZero asserts that i is not the zero value for its type. func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { if i == nil || reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) { return Fail(t, fmt.Sprintf("Should not be zero, but was %v", i), msgAndArgs...) @@ -941,11 +1140,39 @@ func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { return true } +// FileExists checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func FileExists(t TestingT, path string, msgAndArgs ...interface{}) bool { + info, err := os.Lstat(path) + if err != nil { + if os.IsNotExist(err) { + return Fail(t, fmt.Sprintf("unable to find file %q", path), msgAndArgs...) + } + return Fail(t, fmt.Sprintf("error when running os.Lstat(%q): %s", path, err), msgAndArgs...) + } + if info.IsDir() { + return Fail(t, fmt.Sprintf("%q is a directory", path), msgAndArgs...) + } + return true +} + +// DirExists checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func DirExists(t TestingT, path string, msgAndArgs ...interface{}) bool { + info, err := os.Lstat(path) + if err != nil { + if os.IsNotExist(err) { + return Fail(t, fmt.Sprintf("unable to find file %q", path), msgAndArgs...) + } + return Fail(t, fmt.Sprintf("error when running os.Lstat(%q): %s", path, err), msgAndArgs...) + } + if !info.IsDir() { + return Fail(t, fmt.Sprintf("%q is a file", path), msgAndArgs...) + } + return true +} + // JSONEq asserts that two JSON strings are equivalent. // // assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) -// -// Returns whether the assertion was successful (true) or not (false). func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) bool { var expectedJSONAsInterface, actualJSONAsInterface interface{} @@ -989,9 +1216,8 @@ func diff(expected interface{}, actual interface{}) string { return "" } - spew.Config.SortKeys = true - e := spew.Sdump(expected) - a := spew.Sdump(actual) + e := spewConfig.Sdump(expected) + a := spewConfig.Sdump(actual) diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ A: difflib.SplitLines(e), @@ -1005,3 +1231,26 @@ func diff(expected interface{}, actual interface{}) string { return "\n\nDiff:\n" + diff } + +// validateEqualArgs checks whether provided arguments can be safely used in the +// Equal/NotEqual functions. +func validateEqualArgs(expected, actual interface{}) error { + if isFunction(expected) || isFunction(actual) { + return errors.New("cannot take func type as argument") + } + return nil +} + +func isFunction(arg interface{}) bool { + if arg == nil { + return false + } + return reflect.TypeOf(arg).Kind() == reflect.Func +} + +var spewConfig = spew.ConfigState{ + Indent: " ", + DisablePointerAddresses: true, + DisableCapacities: true, + SortKeys: true, +} diff --git a/vendor/github.com/stretchr/testify/assert/forward_assertions.go b/vendor/github.com/stretchr/testify/assert/forward_assertions.go index b867e95ea..9ad56851d 100644 --- a/vendor/github.com/stretchr/testify/assert/forward_assertions.go +++ b/vendor/github.com/stretchr/testify/assert/forward_assertions.go @@ -13,4 +13,4 @@ func New(t TestingT) *Assertions { } } -//go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_forward.go.tmpl +//go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_forward.go.tmpl -include-format-funcs diff --git a/vendor/github.com/stretchr/testify/assert/http_assertions.go b/vendor/github.com/stretchr/testify/assert/http_assertions.go index e1b9442b5..3101e78dd 100644 --- a/vendor/github.com/stretchr/testify/assert/http_assertions.go +++ b/vendor/github.com/stretchr/testify/assert/http_assertions.go @@ -8,16 +8,16 @@ import ( "strings" ) -// httpCode is a helper that returns HTTP code of the response. It returns -1 -// if building a new request fails. -func httpCode(handler http.HandlerFunc, method, url string, values url.Values) int { +// httpCode is a helper that returns HTTP code of the response. It returns -1 and +// an error if building a new request fails. +func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) { w := httptest.NewRecorder() req, err := http.NewRequest(method, url+"?"+values.Encode(), nil) if err != nil { - return -1 + return -1, err } handler(w, req) - return w.Code + return w.Code, nil } // HTTPSuccess asserts that a specified handler returns a success status code. @@ -25,12 +25,19 @@ func httpCode(handler http.HandlerFunc, method, url string, values url.Values) i // assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) // // Returns whether the assertion was successful (true) or not (false). -func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { - code := httpCode(handler, method, url, values) - if code == -1 { +func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { + code, err := httpCode(handler, method, url, values) + if err != nil { + Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) return false } - return code >= http.StatusOK && code <= http.StatusPartialContent + + isSuccessCode := code >= http.StatusOK && code <= http.StatusPartialContent + if !isSuccessCode { + Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), code)) + } + + return isSuccessCode } // HTTPRedirect asserts that a specified handler returns a redirect status code. @@ -38,12 +45,19 @@ func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, value // assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). -func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { - code := httpCode(handler, method, url, values) - if code == -1 { +func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { + code, err := httpCode(handler, method, url, values) + if err != nil { + Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) return false } - return code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect + + isRedirectCode := code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect + if !isRedirectCode { + Fail(t, fmt.Sprintf("Expected HTTP redirect status code for %q but received %d", url+"?"+values.Encode(), code)) + } + + return isRedirectCode } // HTTPError asserts that a specified handler returns an error status code. @@ -51,12 +65,19 @@ func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, valu // assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} // // Returns whether the assertion was successful (true) or not (false). -func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { - code := httpCode(handler, method, url, values) - if code == -1 { +func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { + code, err := httpCode(handler, method, url, values) + if err != nil { + Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) return false } - return code >= http.StatusBadRequest + + isErrorCode := code >= http.StatusBadRequest + if !isErrorCode { + Fail(t, fmt.Sprintf("Expected HTTP error status code for %q but received %d", url+"?"+values.Encode(), code)) + } + + return isErrorCode } // HTTPBody is a helper that returns HTTP body of the response. It returns @@ -77,7 +98,7 @@ func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) s // assert.HTTPBodyContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). -func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool { +func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { body := HTTPBody(handler, method, url, values) contains := strings.Contains(body, fmt.Sprint(str)) @@ -94,12 +115,12 @@ func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, // assert.HTTPBodyNotContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") // // Returns whether the assertion was successful (true) or not (false). -func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool { +func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { body := HTTPBody(handler, method, url, values) contains := strings.Contains(body, fmt.Sprint(str)) if contains { - Fail(t, "Expected response body for %s to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body) + Fail(t, fmt.Sprintf("Expected response body for \"%s\" to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body)) } return !contains diff --git a/vendor/vendor.json b/vendor/vendor.json index 8a672aeb7..5bf27241f 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -596,16 +596,19 @@ "revisionTime": "2018-02-10T03:43:46Z" }, { - "checksumSHA1": "Lf3uUXTkKK5DJ37BxQvxO1Fq+K8=", + "checksumSHA1": "/5cvgU+J4l7EhMXTK76KaCAfOuU=", "comment": "v1.0.0-3-g6d21280", "path": "github.com/davecgh/go-spew/spew", - "revision": "6d212800a42e8ab5c146b8ace3490ee17e5225f9" + "revision": "346938d642f2ec3594ed81d874461961cd0faa76", + "revisionTime": "2016-10-29T20:57:26Z", + "version": "v1.1.0", + "versionExact": "v1.1.0" }, { - "checksumSHA1": "2+1TPdvFj4W1QS5drkFr+ibM3G0=", + "checksumSHA1": "5k4kiVJsn0CilLDx+gMjglXY6vs=", "path": "github.com/denverdino/aliyungo/common", - "revision": "ec0e57291175fc9b06c62977f384756642285aab", - "revisionTime": "2017-11-27T16:20:29Z" + "revision": "ebad04655e0385f021ed264c89ef4b93958e7204", + "revisionTime": "2018-04-17T07:55:37Z" }, { "checksumSHA1": "y4Ay4E5HqT25sweC/Bl9/odNWaI=", @@ -1196,17 +1199,27 @@ "revisionTime": "2018-03-05T22:44:21Z" }, { - "checksumSHA1": "ByFN6xh/YGP/D3DM9c8p0D9D1XM=", + "checksumSHA1": "hU3ibLi5mZBayj0HPIVpcMSvRgU=", "path": "github.com/sirupsen/logrus", "revision": "90150a8ed11b6ce285e77e8af2b0109559ce4777", "revisionTime": "2018-03-15T01:07:03Z" }, { - "checksumSHA1": "iydUphwYqZRq3WhstEdGsbvBAKs=", + "checksumSHA1": "xqtDGN726+wHn43myII/VQ4vQn8=", + "path": "github.com/stretchr/objx", + "revision": "facf9a85c22f48d2f52f2380e4efce1768749a89", + "revisionTime": "2018-01-06T01:13:53Z", + "version": "v0.1", + "versionExact": "v0.1" + }, + { + "checksumSHA1": "pbq5LYckA7/rqe03l9MtXDekmRg=", "comment": "v1.1.4-4-g976c720", "path": "github.com/stretchr/testify/assert", - "revision": "d77da356e56a7428ad25149ca77381849a6a5232", - "revisionTime": "2016-06-15T09:26:46Z" + "revision": "12b6f73e6084dad08a7c6e575284b177ecafbc71", + "revisionTime": "2018-01-31T22:23:50Z", + "version": "v1.2.1", + "versionExact": "v1.2.1" }, { "checksumSHA1": "GQ9bu6PuydK3Yor1JgtVKUfEJm8=", diff --git a/website/Gemfile b/website/Gemfile index 177bf1774..f8a10cb94 100644 --- a/website/Gemfile +++ b/website/Gemfile @@ -1,3 +1,3 @@ source "https://rubygems.org" -gem "middleman-hashicorp", "0.3.34" +gem "middleman-hashicorp", "0.3.37" diff --git a/website/Gemfile.lock b/website/Gemfile.lock index 15173900a..040e5237a 100644 --- a/website/Gemfile.lock +++ b/website/Gemfile.lock @@ -6,7 +6,7 @@ GEM minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - autoprefixer-rails (8.1.0.1) + autoprefixer-rails (8.3.0) execjs bootstrap-sass (3.3.7) autoprefixer-rails (>= 5.2.1) @@ -78,7 +78,7 @@ GEM rack (>= 1.4.5, < 2.0) thor (>= 0.15.2, < 2.0) tilt (~> 1.4.1, < 2.0) - middleman-hashicorp (0.3.34) + middleman-hashicorp (0.3.37) bootstrap-sass (~> 3.3) builder (~> 3.2) middleman (~> 3.4) @@ -113,9 +113,9 @@ GEM padrino-support (0.12.9) activesupport (>= 3.1) rack (1.6.9) - rack-livereload (0.3.16) + rack-livereload (0.3.17) rack - rack-test (0.8.3) + rack-test (1.0.0) rack (>= 1.0, < 3) rb-fsevent (0.10.3) rb-inotify (0.9.10) @@ -137,7 +137,7 @@ GEM thor (0.20.0) thread_safe (0.3.6) tilt (1.4.1) - turbolinks (5.1.0) + turbolinks (5.1.1) turbolinks-source (~> 5.1) turbolinks-source (5.1.0) tzinfo (1.2.5) @@ -153,7 +153,7 @@ PLATFORMS ruby DEPENDENCIES - middleman-hashicorp (= 0.3.34) + middleman-hashicorp (= 0.3.37) BUNDLED WITH 1.16.1 diff --git a/website/Makefile b/website/Makefile index 0991a7753..7f14cadf9 100644 --- a/website/Makefile +++ b/website/Makefile @@ -1,4 +1,4 @@ -VERSION?="0.3.34" +VERSION?="0.3.37" build: @echo "==> Starting build in Docker..." diff --git a/website/packer.json b/website/packer.json index 4ac70d626..e1e5f77c8 100644 --- a/website/packer.json +++ b/website/packer.json @@ -9,7 +9,7 @@ "builders": [ { "type": "docker", - "image": "hashicorp/middleman-hashicorp:0.3.32", + "image": "hashicorp/middleman-hashicorp:0.3.37", "discard": "true", "volumes": { "{{ pwd }}": "/website" diff --git a/website/source/assets/javascripts/analytics.js b/website/source/assets/javascripts/analytics.js index 0e7dff179..e6ad037bc 100644 --- a/website/source/assets/javascripts/analytics.js +++ b/website/source/assets/javascripts/analytics.js @@ -1,4 +1,6 @@ -document.addEventListener('DOMContentLoaded', function() { +document.addEventListener('turbolinks:load', function() { + analytics.page() + track('.downloads .download .details li a', function(el) { var m = el.href.match(/packer_(\d+\.\d+\.\d+)_(.*?)_(.*?)\.zip/) return { diff --git a/website/source/docs/builders/amazon.html.md b/website/source/docs/builders/amazon.html.md index 8af3f101f..9bff6561f 100644 --- a/website/source/docs/builders/amazon.html.md +++ b/website/source/docs/builders/amazon.html.md @@ -141,7 +141,7 @@ for Packer to work: "ec2:CreateSnapshot", "ec2:CreateTags", "ec2:CreateVolume", - "ec2:DeleteKeypair", + "ec2:DeleteKeyPair", "ec2:DeleteSecurityGroup", "ec2:DeleteSnapshot", "ec2:DeleteVolume", diff --git a/website/source/docs/builders/googlecompute.html.md b/website/source/docs/builders/googlecompute.html.md index c54ca367c..005258f12 100644 --- a/website/source/docs/builders/googlecompute.html.md +++ b/website/source/docs/builders/googlecompute.html.md @@ -120,8 +120,12 @@ is assumed to be the path to the file containing the JSON. ### Windows Example -Running WinRM requires that it is opened in the firewall and that the VM enables WinRM for the -user used to connect in a startup-script. +Before you can provision using the winrm communicator, you need to navigate to +https://console.cloud.google.com/networking/firewalls/list to allow traffic +through google's firewall on the winrm port (tcp:5986). + +Once this is set up, the following is a complete working packer config after +setting a valid `account_file` and `project_id`: ``` {.json} { diff --git a/website/source/docs/builders/hyperv-iso.html.md b/website/source/docs/builders/hyperv-iso.html.md.erb similarity index 95% rename from website/source/docs/builders/hyperv-iso.html.md rename to website/source/docs/builders/hyperv-iso.html.md.erb index 3e919c925..95bfa6293 100644 --- a/website/source/docs/builders/hyperv-iso.html.md +++ b/website/source/docs/builders/hyperv-iso.html.md.erb @@ -1,4 +1,6 @@ --- +modeline: | + vim: set ft=pandoc: description: | The Hyper-V Packer builder is able to create Hyper-V virtual machines and export them. @@ -102,7 +104,7 @@ can be configured for this builder. - `skip_export` (boolean) - If true skips VM export. If you are interested only in the vhd/vhdx files, you can enable this option. This will create inline disks which improves the build performance. There will not be any copying of source vhds to temp directory. This defaults to false. - + - `enable_dynamic_memory` (boolean) - If true enable dynamic memory for virtual machine. This defaults to false. @@ -230,6 +232,9 @@ can be configured for this builder. - `temp_path` (string) - This is the temporary path in which Packer will create the virtual machine. Default value is system `%temp%` +- `disk_block_size` (string) - The block size of the VHD to be created. + Recommended disk block size for Linux hyper-v guests is 1 MiB. This defaults to "32 MiB". + ## Boot Command The `boot_command` configuration is very important: it specifies the keys @@ -242,67 +247,9 @@ strings are all typed in sequence. It is an array only to improve readability within the template. The boot command is "typed" character for character over the virtual keyboard -to the machine, simulating a human actually typing the keyboard. There are -a set of special keys available. If these are in your boot command, they -will be replaced by the proper key: +to the machine, simulating a human actually typing the keyboard. -- `` - Backspace - -- `` - Delete - -- `` and `` - Simulates an actual "enter" or "return" keypress. - -- `` - Simulates pressing the escape key. - -- `` - Simulates pressing the tab key. - -- `` - `` - Simulates pressing a function key. - -- `` `` `` `` - Simulates pressing an arrow key. - -- `` - Simulates pressing the spacebar. - -- `` - Simulates pressing the insert key. - -- `` `` - Simulates pressing the home and end keys. - -- `` `` - Simulates pressing the page up and page down keys. - -- `` `` - Simulates pressing the alt key. - -- `` `` - Simulates pressing the ctrl key. - -- `` `` - Simulates pressing the shift key. - -- `` `` - Simulates pressing and holding the alt key. - -- `` `` - Simulates pressing and holding the ctrl key. - -- `` `` - Simulates pressing and holding the shift key. - -- `` `` - Simulates releasing a held alt key. - -- `` `` - Simulates releasing a held ctrl key. - -- `` `` - Simulates releasing a held shift key. - -- `` `` `` - Adds a 1, 5 or 10 second pause before - sending any additional keys. This is useful if you have to generally wait - for the UI to update before typing more. - -When using modifier keys `ctrl`, `alt`, `shift` ensure that you release them, -otherwise they will be held down until the machine reboots. Use lowercase -characters as well inside modifiers. For example: to simulate ctrl+c use -`c`. - -In addition to the special keys, each command to type is treated as a -[template engine](/docs/templates/engine.html). -The available variables are: - -- `HTTPIP` and `HTTPPort` - The IP and port, respectively of an HTTP server - that is started serving the directory specified by the `http_directory` - configuration parameter. If `http_directory` isn't specified, these will - be blank! +<%= partial "partials/builders/boot-command" %> Example boot command. This is actually a working boot command used to start an Ubuntu 12.04 installer: diff --git a/website/source/docs/builders/hyperv-vmcx.html.md b/website/source/docs/builders/hyperv-vmcx.html.md.erb similarity index 95% rename from website/source/docs/builders/hyperv-vmcx.html.md rename to website/source/docs/builders/hyperv-vmcx.html.md.erb index 5ff7b604d..9f4da1f20 100644 --- a/website/source/docs/builders/hyperv-vmcx.html.md +++ b/website/source/docs/builders/hyperv-vmcx.html.md.erb @@ -1,4 +1,6 @@ --- +modeline: | + vim: set ft=pandoc: description: |- The Hyper-V Packer builder is able to clone an existing Hyper-V virtual machine and export them. layout: "docs" @@ -245,64 +247,9 @@ strings are all typed in sequence. It is an array only to improve readability within the template. The boot command is "typed" character for character over the virtual keyboard -to the machine, simulating a human actually typing the keyboard. There are -a set of special keys available. If these are in your boot command, they -will be replaced by the proper key: +to the machine, simulating a human actually typing the keyboard. -- `` - Backspace - -- `` - Delete - -- `` and `` - Simulates an actual "enter" or "return" keypress. - -- `` - Simulates pressing the escape key. - -- `` - Simulates pressing the tab key. - -- `` - `` - Simulates pressing a function key. - -- `` `` `` `` - Simulates pressing an arrow key. - -- `` - Simulates pressing the spacebar. - -- `` - Simulates pressing the insert key. - -- `` `` - Simulates pressing the home and end keys. - -- `` `` - Simulates pressing the page up and page down keys. - -- `` `` - Simulates pressing the alt key. - -- `` `` - Simulates pressing the ctrl key. - -- `` `` - Simulates pressing the shift key. - -- `` `` - Simulates pressing and holding the alt key. - -- `` `` - Simulates pressing and holding the ctrl key. - -- `` `` - Simulates pressing and holding the shift key. - -- `` `` - Simulates releasing a held alt key. - -- `` `` - Simulates releasing a held ctrl key. - -- `` `` - Simulates releasing a held shift key. - -- `` `` `` - Adds a 1, 5 or 10 second pause before - sending any additional keys. This is useful if you have to generally wait - for the UI to update before typing more. - -When using modifier keys `ctrl`, `alt`, `shift` ensure that you release them, otherwise they will be held down until the machine reboots. Use lowercase characters as well inside modifiers. For example: to simulate ctrl+c use `c`. - -In addition to the special keys, each command to type is treated as a -[configuration template](/docs/templates/configuration-templates.html). -The available variables are: - -* `HTTPIP` and `HTTPPort` - The IP and port, respectively of an HTTP server - that is started serving the directory specified by the `http_directory` - configuration parameter. If `http_directory` isn't specified, these will - be blank! +<%= partial "partials/builders/boot-command" %> Example boot command. This is actually a working boot command used to start an Ubuntu 12.04 installer: diff --git a/website/source/docs/builders/lxd.html.md b/website/source/docs/builders/lxd.html.md index 019b97534..4f8cf9c84 100644 --- a/website/source/docs/builders/lxd.html.md +++ b/website/source/docs/builders/lxd.html.md @@ -30,7 +30,7 @@ Below is a fully functioning example. "type": "lxd", "name": "lxd-xenial", "image": "ubuntu-daily:xenial", - "output_image": "ubuntu-xenial" + "output_image": "ubuntu-xenial", "publish_properties": { "description": "Trivial repackage with Packer" } diff --git a/website/source/docs/builders/openstack.html.md b/website/source/docs/builders/openstack.html.md old mode 100755 new mode 100644 index 0809b6545..37cc0de71 --- a/website/source/docs/builders/openstack.html.md +++ b/website/source/docs/builders/openstack.html.md @@ -118,6 +118,9 @@ builder. - `metadata` (object of key/value strings) - Glance metadata that will be applied to the image. +- `instance_name` (string) - Name that is applied to the server instance + created by Packer. If this isn't specified, the default is same as `image_name`. + - `instance_metadata` (object of key/value strings) - Metadata that is applied to the server instance created by Packer. Also called server properties in some documentation. The strings have a max size of 255 bytes diff --git a/website/source/docs/builders/parallels-iso.html.md b/website/source/docs/builders/parallels-iso.html.md.erb similarity index 86% rename from website/source/docs/builders/parallels-iso.html.md rename to website/source/docs/builders/parallels-iso.html.md.erb index e7f0dd4a4..5e1b19da4 100644 --- a/website/source/docs/builders/parallels-iso.html.md +++ b/website/source/docs/builders/parallels-iso.html.md.erb @@ -1,4 +1,6 @@ --- +modeline: | + vim: set ft=pandoc: description: | The Parallels Packer builder is able to create Parallels Desktop for Mac virtual machines and export them in the PVM format, starting from an ISO @@ -246,68 +248,9 @@ template. The boot command is "typed" character for character (using the Parallels Virtualization SDK, see [Parallels Builder](/docs/builders/parallels.html)) -simulating a human actually typing the keyboard. There are a set of special keys -available. If these are in your boot command, they will be replaced by the -proper key: +simulating a human actually typing the keyboard. -- `` - Backspace - -- `` - Delete - -- `` and `` - Simulates an actual "enter" or "return" keypress. - -- `` - Simulates pressing the escape key. - -- `` - Simulates pressing the tab key. - -- `` - `` - Simulates pressing a function key. - -- `` `` `` `` - Simulates pressing an arrow key. - -- `` - Simulates pressing the spacebar. - -- `` - Simulates pressing the insert key. - -- `` `` - Simulates pressing the home and end keys. - -- `` `` - Simulates pressing the page up and page down keys. - -- `` `` - Simulates pressing the alt key. - -- `` `` - Simulates pressing the ctrl key. - -- `` `` - Simulates pressing the shift key. - -- `` `` - Simulates pressing and holding the alt key. - -- `` `` - Simulates pressing and holding the ctrl key. - -- `` `` - Simulates pressing and holding the shift key. - -- `` `` - Simulates releasing a held alt key. - -- `` `` - Simulates releasing a held ctrl key. - -- `` `` - Simulates releasing a held shift key. - -- `` `` `` - Adds a 1, 5 or 10 second pause before - sending any additional keys. This is useful if you have to generally wait - for the UI to update before typing more. - -When using modifier keys `ctrl`, `alt`, `shift` ensure that you release them, -otherwise they will be held down until the machine reboots. Use lowercase -characters as well inside modifiers. - -For example: to simulate ctrl+c use `c`. - -In addition to the special keys, each command to type is treated as a -[template engine](/docs/templates/engine.html). The -available variables are: - -- `HTTPIP` and `HTTPPort` - The IP and port, respectively of an HTTP server - that is started serving the directory specified by the `http_directory` - configuration parameter. If `http_directory` isn't specified, these will be - blank! +<%= partial "partials/builders/boot-command" %> Example boot command. This is actually a working boot command used to start an Ubuntu 12.04 installer: diff --git a/website/source/docs/builders/parallels-pvm.html.md b/website/source/docs/builders/parallels-pvm.html.md.erb similarity index 83% rename from website/source/docs/builders/parallels-pvm.html.md rename to website/source/docs/builders/parallels-pvm.html.md.erb index 59accdcd1..9cc9ab14a 100644 --- a/website/source/docs/builders/parallels-pvm.html.md +++ b/website/source/docs/builders/parallels-pvm.html.md.erb @@ -1,4 +1,6 @@ --- +modeline: | + vim: set ft=pandoc: description: | This Parallels builder is able to create Parallels Desktop for Mac virtual machines and export them in the PVM format, starting from an existing PVM @@ -179,60 +181,9 @@ template. The boot command is "typed" character for character (using the Parallels Virtualization SDK, see [Parallels Builder](/docs/builders/parallels.html)) -simulating a human actually typing the keyboard. There are a set of special keys -available. If these are in your boot command, they will be replaced by the -proper key: +simulating a human actually typing the keyboard. -- `` - Backspace - -- `` - Delete - -- `` and `` - Simulates an actual "enter" or "return" keypress. - -- `` - Simulates pressing the escape key. - -- `` - Simulates pressing the tab key. - -- `` - `` - Simulates pressing a function key. - -- `` `` `` `` - Simulates pressing an arrow key. - -- `` - Simulates pressing the spacebar. - -- `` - Simulates pressing the insert key. - -- `` `` - Simulates pressing the home and end keys. - -- `` `` - Simulates pressing the page up and page down keys. - -- `` `` - Simulates pressing the alt key. - -- `` `` - Simulates pressing the ctrl key. - -- `` `` - Simulates pressing the shift key. - -- `` `` - Simulates pressing and holding the alt key. - -- `` `` - Simulates pressing and holding the ctrl key. - -- `` `` - Simulates pressing and holding the shift key. - -- `` `` - Simulates releasing a held alt key. - -- `` `` - Simulates releasing a held ctrl key. - -- `` `` - Simulates releasing a held shift key. - -- `` `` `` - Adds a 1, 5 or 10 second pause before - sending any additional keys. This is useful if you have to generally wait - for the UI to update before typing more. - -In addition to the special keys, each command to type is treated as a -[template engine](/docs/templates/engine.html). The -available variables are: - -For more examples of various boot commands, see the sample projects from our -[community templates page](/community-tools.html#templates). +<%= partial "partials/builders/boot-command" %> ## prlctl Commands diff --git a/website/source/docs/builders/qemu.html.md b/website/source/docs/builders/qemu.html.md.erb similarity index 76% rename from website/source/docs/builders/qemu.html.md rename to website/source/docs/builders/qemu.html.md.erb index 91b0e09a2..c47cd1e51 100644 --- a/website/source/docs/builders/qemu.html.md +++ b/website/source/docs/builders/qemu.html.md.erb @@ -1,4 +1,6 @@ --- +modeline: | + vim: set ft=pandoc: description: | The Qemu Packer builder is able to create KVM and Xen virtual machine images. layout: docs @@ -90,8 +92,8 @@ Linux server and have not enabled X11 forwarding (`ssh -X`). over `iso_checksum_url` type. - `iso_checksum_type` (string) - The type of the checksum specified in - `iso_checksum`. Valid values are "none", "md5", "sha1", "sha256", or - "sha512" currently. While "none" will skip checksumming, this is not + `iso_checksum`. Valid values are `none`, `md5`, `sha1`, `sha256`, or + `sha512` currently. While `none` will skip checksumming, this is not recommended since ISO files are generally large and corruption does happen from time to time. @@ -105,15 +107,15 @@ Linux server and have not enabled X11 forwarding (`ssh -X`). this is an HTTP URL, Packer will download it and cache it between runs. This can also be a URL to an IMG or QCOW2 file, in which case QEMU will boot directly from it. When passing a path to an IMG or QCOW2 file, you - should set `disk_image` to "true". + should set `disk_image` to `true`. ### Optional: - `accelerator` (string) - The accelerator type to use when running the VM. - This may be `none`, `kvm`, `tcg`, `hax`, or `xen`. The appropriate software - must have already been installed on your build machine to use the accelerator - you specified. When no accelerator is specified, Packer will try to use `kvm` - if it is available but will default to `tcg` otherwise. + This may be `none`, `kvm`, `tcg`, `hax`, or `xen`. The appropriate software + must have already been installed on your build machine to use the + accelerator you specified. When no accelerator is specified, Packer will try + to use `kvm` if it is available but will default to `tcg` otherwise. -> The `hax` accelerator has issues attaching CDROM ISOs. This is an upstream issue which can be tracked @@ -128,19 +130,19 @@ Linux server and have not enabled X11 forwarding (`ssh -X`). - `boot_wait` (string) - The time to wait after booting the initial virtual machine before typing the `boot_command`. The value of this should be - a duration. Examples are "5s" and "1m30s" which will cause Packer to wait + a duration. Examples are `5s` and `1m30s` which will cause Packer to wait five seconds and one minute 30 seconds, respectively. If this isn't - specified, the default is 10 seconds. + specified, the default is `10s` or 10 seconds. - `disk_cache` (string) - The cache mode to use for disk. Allowed values - include any of "writethrough", "writeback", "none", "unsafe" - or "directsync". By default, this is set to "writeback". + include any of `writethrough`, `writeback`, `none`, `unsafe` + or `directsync`. By default, this is set to `writeback`. - `disk_compression` (boolean) - Apply compression to the QCOW2 disk file using `qemu-img convert`. Defaults to `false`. - `disk_discard` (string) - The discard mode to use for disk. Allowed values - include any of "unmap" or "ignore". By default, this is set to "ignore". + include any of `unmap` or `ignore`. By default, this is set to `ignore`. - `disk_image` (boolean) - Packer defaults to building from an ISO file, this parameter controls whether the ISO URL supplied is actually a bootable @@ -148,20 +150,28 @@ Linux server and have not enabled X11 forwarding (`ssh -X`). source, resize it according to `disk_size` and boot the image. - `disk_interface` (string) - The interface to use for the disk. Allowed - values include any of "ide", "scsi", "virtio" or "virtio-scsi"^\* . Note also - that any boot commands or kickstart type scripts must have proper - adjustments for resulting device names. The Qemu builder uses "virtio" by + values include any of `ide`, `scsi`, `virtio` or `virtio-scsi`^\*. Note + also that any boot commands or kickstart type scripts must have proper + adjustments for resulting device names. The Qemu builder uses `virtio` by default. - ^\* Please be aware that use of the "scsi" disk interface has been disabled + ^\* Please be aware that use of the `scsi` disk interface has been disabled by Red Hat due to a bug described [here](https://bugzilla.redhat.com/show_bug.cgi?id=1019220). If you are running Qemu on RHEL or a RHEL variant such as CentOS, you - *must* choose one of the other listed interfaces. Using the "scsi" + *must* choose one of the other listed interfaces. Using the `scsi` interface under these circumstances will cause the build to fail. - `disk_size` (number) - The size, in megabytes, of the hard disk to create - for the VM. By default, this is 40960 (40 GB). + for the VM. By default, this is `40960` (40 GB). + +- `floppy_dirs` (array of strings) - A list of directories to place onto + the floppy disk recursively. This is similar to the `floppy_files` option + except that the directory structure is preserved. This is useful for when + your floppy disk includes drivers or if you just want to organize it's + contents as a hierarchy. Wildcard characters (\*, ?, and \[\]) are allowed. + The maximum summary size of all files in the listed directories are the + same as in `floppy_files`. - `floppy_files` (array of strings) - A list of files to place onto a floppy disk that is attached when the VM is booted. This is most useful for @@ -175,20 +185,12 @@ Linux server and have not enabled X11 forwarding (`ssh -X`). listed files must not exceed 1.44 MB. The supported ways to move large files into the OS are using `http_directory` or [the file provisioner](https://www.packer.io/docs/provisioners/file.html). -- `floppy_dirs` (array of strings) - A list of directories to place onto - the floppy disk recursively. This is similar to the `floppy_files` option - except that the directory structure is preserved. This is useful for when - your floppy disk includes drivers or if you just want to organize it's - contents as a hierarchy. Wildcard characters (\*, ?, and \[\]) are allowed. - The maximum summary size of all files in the listed directories are the - same as in `floppy_files`. - -- `format` (string) - Either "qcow2" or "raw", this specifies the output +- `format` (string) - Either `qcow2` or `raw`, this specifies the output format of the virtual machine image. This defaults to `qcow2`. - `headless` (boolean) - Packer defaults to building QEMU virtual machines by launching a GUI that shows the console of the machine being built. When this - value is set to true, the machine will start without a console. + value is set to `true`, the machine will start without a console. You can still see the console if you make a note of the VNC display number chosen, and then connect using `vncviewer -Shared :` @@ -196,22 +198,23 @@ Linux server and have not enabled X11 forwarding (`ssh -X`). - `http_directory` (string) - Path to a directory to serve using an HTTP server. The files in this directory will be available over HTTP that will be requestable from the virtual machine. This is useful for hosting - kickstart files and so on. By default this is "", which means no HTTP server - will be started. The address and port of the HTTP server will be available - as variables in `boot_command`. This is covered in more detail below. + kickstart files and so on. By default this is an empty string, which means + no HTTP server will be started. The address and port of the HTTP server will + be available as variables in `boot_command`. This is covered in more detail + below. - `http_port_min` and `http_port_max` (number) - These are the minimum and maximum port to use for the HTTP server started to serve the `http_directory`. Because Packer often runs in parallel, Packer will choose a randomly available port in this range to run the HTTP server. If you want to force the HTTP server to be on one port, make this minimum and maximum - port the same. By default the values are 8000 and 9000, respectively. + port the same. By default the values are `8000` and `9000`, respectively. - `iso_skip_cache` (boolean) - Use iso from provided url. Qemu must support curl block device. This defaults to `false`. - `iso_target_extension` (string) - The extension of the iso file after - download. This defaults to "iso". + download. This defaults to `iso`. - `iso_target_path` (string) - The path where the iso should be saved after download. By default will go in the packer cache, with a hash of the @@ -225,25 +228,25 @@ Linux server and have not enabled X11 forwarding (`ssh -X`). - `machine_type` (string) - The type of machine emulation to use. Run your qemu binary with the flags `-machine help` to list available types for - your system. This defaults to "pc". + your system. This defaults to `pc`. - `net_device` (string) - The driver to use for the network interface. Allowed - values "ne2k\_pci", "i82551", "i82557b", "i82559er", "rtl8139", "e1000", - "pcnet", "virtio", "virtio-net", "virtio-net-pci", "usb-net", "i82559a", - "i82559b", "i82559c", "i82550", "i82562", "i82557a", "i82557c", "i82801", - "vmxnet3", "i82558a" or "i82558b". The Qemu builder uses "virtio-net" by + values `ne2k_pci`, `i82551`, `i82557b`, `i82559er`, `rtl8139`, `e1000`, + `pcnet`, `virtio`, `virtio-net`, `virtio-net-pci`, `usb-net`, `i82559a`, + `i82559b`, `i82559c`, `i82550`, `i82562`, `i82557a`, `i82557c`, `i82801`, + `vmxnet3`, `i82558a` or `i82558b`. The Qemu builder uses `virtio-net` by default. - `output_directory` (string) - This is the path to the directory where the resulting virtual machine will be created. This may be relative or absolute. If relative, the path is relative to the working directory when `packer` is executed. This directory must not exist or be empty prior to running - the builder. By default this is "output-BUILDNAME" where "BUILDNAME" is the + the builder. By default this is `output-BUILDNAME` where "BUILDNAME" is the name of the build. - `qemu_binary` (string) - The name of the Qemu binary to look for. This - defaults to "qemu-system-x86\_64", but may need to be changed for - some platforms. For example "qemu-kvm", or "qemu-system-i386" may be a + defaults to `qemu-system-x86_64`, but may need to be changed for + some platforms. For example `qemu-kvm`, or `qemu-system-i386` may be a better choice for some systems. - `qemuargs` (array of array of strings) - Allows complete control over the @@ -312,7 +315,7 @@ default port of `5985` or whatever value you have the service set to listen on. - `use_default_display` (boolean) - If true, do not pass a `-display` option to qemu, allowing it to choose the default. This may be needed when running - under OS X. + under macOS, and getting errors about `sdl` not being available. - `shutdown_command` (string) - The command to use to gracefully shut down the machine once all the provisioning is done. By default this is an empty @@ -325,31 +328,32 @@ default port of `5985` or whatever value you have the service set to listen on. - `shutdown_timeout` (string) - The amount of time to wait after executing the `shutdown_command` for the virtual machine to actually shut down. If it doesn't shut down in this time, it is an error. By default, the timeout is - `5m`, or five minutes. + `5m` or five minutes. -- `skip_compaction` (boolean) - Packer compacts the QCOW2 image using `qemu-img convert`. - Set this option to `true` to disable compacting. Defaults to `false`. +- `skip_compaction` (boolean) - Packer compacts the QCOW2 image using + `qemu-img convert`. Set this option to `true` to disable compacting. + Defaults to `false`. - `ssh_host_port_min` and `ssh_host_port_max` (number) - The minimum and maximum port to use for the SSH port on the host machine which is forwarded to the SSH port on the guest machine. Because Packer often runs in parallel, Packer will choose a randomly available port in this range to use as the - host port. By default this is 2222 to 4444. + host port. By default this is `2222` to `4444`. - `vm_name` (string) - This is the name of the image (QCOW2 or IMG) file for - the new virtual machine. By default this is "packer-BUILDNAME", where - `BUILDNAME` is the name of the build. Currently, no file extension will be + the new virtual machine. By default this is `packer-BUILDNAME`, where + "BUILDNAME" is the name of the build. Currently, no file extension will be used unless it is specified in this option. -- `vnc_bind_address` (string / IP address) - The IP address that should be binded - to for VNC. By default packer will use 127.0.0.1 for this. If you wish to bind - to all interfaces use 0.0.0.0 +- `vnc_bind_address` (string / IP address) - The IP address that should be + binded to for VNC. By default packer will use `127.0.0.1` for this. If you + wish to bind to all interfaces use `0.0.0.0`. - `vnc_port_min` and `vnc_port_max` (number) - The minimum and maximum port to use for VNC access to the virtual machine. The builder uses VNC to type the initial `boot_command`. Because Packer generally runs in parallel, Packer uses a randomly chosen port in this range that appears available. By - default this is 5900 to 6000. The minimum and maximum ports are inclusive. + default this is `5900` to `6000`. The minimum and maximum ports are inclusive. ## Boot Command @@ -370,69 +374,7 @@ default 100ms delay. The delay alleviates issues with latency and CPU contention. For local builds you can tune this delay by specifying e.g. `PACKER_KEY_INTERVAL=10ms` to speed through the boot command. -There are a set of special keys available. If these are in your boot -command, they will be replaced by the proper key: - -- `` - Backspace - -- `` - Delete - -- `` and `` - Simulates an actual "enter" or "return" keypress. - -- `` - Simulates pressing the escape key. - -- `` - Simulates pressing the tab key. - -- `` - `` - Simulates pressing a function key. - -- `` `` `` `` - Simulates pressing an arrow key. - -- `` - Simulates pressing the spacebar. - -- `` - Simulates pressing the insert key. - -- `` `` - Simulates pressing the home and end keys. - -- `` `` - Simulates pressing the page up and page down keys. - -- `` `` - Simulates pressing the alt key. - -- `` `` - Simulates pressing the ctrl key. - -- `` `` - Simulates pressing the shift key. - -- `` `` - Simulates pressing and holding the alt key. - -- `` `` - Simulates pressing and holding the ctrl key. - -- `` `` - Simulates pressing and holding the shift key. - -- `` `` - Simulates releasing a held alt key. - -- `` `` - Simulates releasing a held ctrl key. - -- `` `` - Simulates releasing a held shift key. - -- `` `` `` - Adds a 1, 5 or 10 second pause before - sending any additional keys. This is useful if you have to generally wait - for the UI to update before typing more. - -- `` - Add user defined time.Duration pause before sending any - additional keys. For example `` or `` - -When using modifier keys `ctrl`, `alt`, `shift` ensure that you release them, -otherwise they will be held down until the machine reboots. Use lowercase -characters as well inside modifiers. For example: to simulate ctrl+c use -`c`. - -In addition to the special keys, each command to type is treated as a -[template engine](/docs/templates/engine.html). The -available variables are: - -- `HTTPIP` and `HTTPPort` - The IP and port, respectively of an HTTP server - that is started serving the directory specified by the `http_directory` - configuration parameter. If `http_directory` isn't specified, these will be - blank! +<%= partial "partials/builders/boot-command" %> Example boot command. This is actually a working boot command used to start an CentOS 6.4 installer: diff --git a/website/source/docs/builders/triton.html.md b/website/source/docs/builders/triton.html.md index 2a62dbe12..889b0d9d8 100644 --- a/website/source/docs/builders/triton.html.md +++ b/website/source/docs/builders/triton.html.md @@ -95,6 +95,11 @@ builder. - `triton_user` (string) - The username of a user who has access to your Triton account. + +- `insecure_skip_tls_verify` - (bool) This allows skipping TLS verification of + the Triton endpoint. It is useful when connecting to a temporary Triton + installation such as Cloud-On-A-Laptop which does not generally use a + certificate signed by a trusted root CA. The default is `false`. - `source_machine_firewall_enabled` (boolean) - Whether or not the firewall of the VM used to create an image of is enabled. The Triton firewall only diff --git a/website/source/docs/builders/virtualbox-iso.html.md b/website/source/docs/builders/virtualbox-iso.html.md.erb similarity index 79% rename from website/source/docs/builders/virtualbox-iso.html.md rename to website/source/docs/builders/virtualbox-iso.html.md.erb index 85d4905ed..78de79af8 100644 --- a/website/source/docs/builders/virtualbox-iso.html.md +++ b/website/source/docs/builders/virtualbox-iso.html.md.erb @@ -1,4 +1,6 @@ --- +modeline: | + vim: set ft=pandoc: description: | The VirtualBox Packer builder is able to create VirtualBox virtual machines and export them in the OVF format, starting from an ISO image. @@ -63,8 +65,8 @@ builder. over `iso_checksum_url` type. - `iso_checksum_type` (string) - The type of the checksum specified in - `iso_checksum`. Valid values are "none", "md5", "sha1", "sha256", or - "sha512" currently. While "none" will skip checksumming, this is not + `iso_checksum`. Valid values are `none`, `md5`, `sha1`, `sha256`, or + `sha512` currently. While `none` will skip checksumming, this is not recommended since ISO files are generally large and corruption does happen from time to time. @@ -88,12 +90,12 @@ builder. - `boot_wait` (string) - The time to wait after booting the initial virtual machine before typing the `boot_command`. The value of this should be - a duration. Examples are "5s" and "1m30s" which will cause Packer to wait + a duration. Examples are `5s` and `1m30s` which will cause Packer to wait five seconds and one minute 30 seconds, respectively. If this isn't - specified, the default is 10 seconds. + specified, the default is `10s` or 10 seconds. - `disk_size` (number) - The size, in megabytes, of the hard disk to create - for the VM. By default, this is 40000 (about 40 GB). + for the VM. By default, this is `40000` (about 40 GB). - `export_opts` (array of strings) - Additional options to pass to the [VBoxManage @@ -137,6 +139,12 @@ builder. "packer_conf.json" ``` +- `floppy_dirs` (array of strings) - A list of directories to place onto + the floppy disk recursively. This is similar to the `floppy_files` option + except that the directory structure is preserved. This is useful for when + your floppy disk includes drivers or if you just want to organize it's + contents as a hierarchy. Wildcard characters (\*, ?, and \[\]) are allowed. + - `floppy_files` (array of strings) - A list of files to place onto a floppy disk that is attached when the VM is booted. This is most useful for unattended Windows installs, which look for an `Autounattend.xml` file on @@ -147,26 +155,20 @@ builder. and \[\]) are allowed. Directory names are also allowed, which will add all the files found in the directory to the floppy. -- `floppy_dirs` (array of strings) - A list of directories to place onto - the floppy disk recursively. This is similar to the `floppy_files` option - except that the directory structure is preserved. This is useful for when - your floppy disk includes drivers or if you just want to organize it's - contents as a hierarchy. Wildcard characters (\*, ?, and \[\]) are allowed. - -- `format` (string) - Either "ovf" or "ova", this specifies the output format - of the exported virtual machine. This defaults to "ovf". +- `format` (string) - Either `ovf` or `ova`, this specifies the output format + of the exported virtual machine. This defaults to `ovf`. - `guest_additions_mode` (string) - The method by which guest additions are - made available to the guest for installation. Valid options are "upload", - "attach", or "disable". If the mode is "attach" the guest additions ISO will - be attached as a CD device to the virtual machine. If the mode is "upload" + made available to the guest for installation. Valid options are `upload`, + `attach`, or `disable`. If the mode is `attach` the guest additions ISO will + be attached as a CD device to the virtual machine. If the mode is `upload` the guest additions ISO will be uploaded to the path specified by - `guest_additions_path`. The default value is "upload". If "disable" is used, + `guest_additions_path`. The default value is `upload`. If `disable` is used, guest additions won't be downloaded, either. - `guest_additions_path` (string) - The path on the guest virtual machine where the VirtualBox guest additions ISO will be uploaded. By default this - is "VBoxGuestAdditions.iso" which should upload into the login directory of + is `VBoxGuestAdditions.iso` which should upload into the login directory of the user. This is a [configuration template](/docs/templates/engine.html) where the `Version` variable is replaced with the VirtualBox version. @@ -183,24 +185,24 @@ builder. download the proper guest additions ISO from the internet. - `guest_os_type` (string) - The guest OS type being installed. By default - this is "other", but you can get *dramatic* performance improvements by + this is `other`, but you can get *dramatic* performance improvements by setting this to the proper value. To view all available values for this run `VBoxManage list ostypes`. Setting the correct value hints to VirtualBox how to optimize the virtual hardware to work best with that operating system. - `hard_drive_interface` (string) - The type of controller that the primary - hard drive is attached to, defaults to "ide". When set to "sata", the drive - is attached to an AHCI SATA controller. When set to "scsi", the drive is + hard drive is attached to, defaults to `ide`. When set to `sata`, the drive + is attached to an AHCI SATA controller. When set to `scsi`, the drive is attached to an LsiLogic SCSI controller. - `sata_port_count` (number) - The number of ports available on any SATA - controller created, defaults to 1. VirtualBox supports up to 30 ports on a + controller created, defaults to `1`. VirtualBox supports up to 30 ports on a maximum of 1 SATA controller. Increasing this value can be useful if you want to attach additional drives. - `hard_drive_nonrotational` (boolean) - Forces some guests (i.e. Windows 7+) to treat disks as SSDs and stops them from performing disk fragmentation. - Also set `hard_drive_Discard` to `true` to enable TRIM support. + Also set `hard_drive_discard` to `true` to enable TRIM support. - `hard_drive_discard` (boolean) - When this value is set to `true`, a VDI image will be shrunk in response to the trim command from the guest OS. @@ -209,29 +211,30 @@ builder. - `headless` (boolean) - Packer defaults to building VirtualBox virtual machines by launching a GUI that shows the console of the machine - being built. When this value is set to `true`, the machine will start without - a console. + being built. When this value is set to `true`, the machine will start + without a console. - `http_directory` (string) - Path to a directory to serve using an HTTP server. The files in this directory will be available over HTTP that will be requestable from the virtual machine. This is useful for hosting - kickstart files and so on. By default this is "", which means no HTTP server - will be started. The address and port of the HTTP server will be available - as variables in `boot_command`. This is covered in more detail below. + kickstart files and so on. By default this is an empty string, which means + no HTTP server will be started. The address and port of the HTTP server will + be available as variables in `boot_command`. This is covered in more detail + below. - `http_port_min` and `http_port_max` (number) - These are the minimum and maximum port to use for the HTTP server started to serve the `http_directory`. Because Packer often runs in parallel, Packer will choose a randomly available port in this range to run the HTTP server. If you want to force the HTTP server to be on one port, make this minimum and maximum - port the same. By default the values are 8000 and 9000, respectively. + port the same. By default the values are `8000` and `9000`, respectively. - `iso_interface` (string) - The type of controller that the ISO is attached - to, defaults to "ide". When set to "sata", the drive is attached to an AHCI + to, defaults to `ide`. When set to `sata`, the drive is attached to an AHCI SATA controller. - `iso_target_extension` (string) - The extension of the iso file after - download. This defaults to "iso". + download. This defaults to `iso`. - `iso_target_path` (string) - The path where the iso should be saved after download. By default will go in the packer cache, with a hash of the @@ -250,13 +253,13 @@ builder. resulting virtual machine will be created. This may be relative or absolute. If relative, the path is relative to the working directory when `packer` is executed. This directory must not exist or be empty prior to running - the builder. By default this is "output-BUILDNAME" where "BUILDNAME" is the + the builder. By default this is `output-BUILDNAME` where "BUILDNAME" is the name of the build. - `post_shutdown_delay` (string) - The amount of time to wait after shutting down the virtual machine. If you get the error `Error removing floppy controller`, you might need to set this to `5m` - or so. By default, the delay is `0s`, or disabled. + or so. By default, the delay is `0s` or disabled. - `shutdown_command` (string) - The command to use to gracefully shut down the machine once all the provisioning is done. By default this is an empty @@ -269,7 +272,7 @@ builder. - `shutdown_timeout` (string) - The amount of time to wait after executing the `shutdown_command` for the virtual machine to actually shut down. If it doesn't shut down in this time, it is an error. By default, the timeout is - `5m`, or five minutes. + `5m` or five minutes. - `skip_export` (boolean) - Defaults to `false`. When enabled, Packer will not export the VM. Useful if the build output is not the resultant image, @@ -279,11 +282,11 @@ builder. maximum port to use for the SSH port on the host machine which is forwarded to the SSH port on the guest machine. Because Packer often runs in parallel, Packer will choose a randomly available port in this range to use as the - host port. By default this is 2222 to 4444. + host port. By default this is `2222` to `4444`. - `ssh_skip_nat_mapping` (boolean) - Defaults to `false`. When enabled, Packer does not setup forwarded port mapping for SSH requests and uses `ssh_port` - on the host to communicate to the virtual machine + on the host to communicate to the virtual machine. - `vboxmanage` (array of array of strings) - Custom `VBoxManage` commands to execute in order to further customize the virtual machine being created. The @@ -303,22 +306,22 @@ builder. - `virtualbox_version_file` (string) - The path within the virtual machine to upload a file that contains the VirtualBox version that was used to create the machine. This information can be useful for provisioning. By default - this is ".vbox\_version", which will generally be upload it into the + this is `.vbox_version`, which will generally be upload it into the home directory. Set to an empty string to skip uploading this file, which can be useful when using the `none` communicator. - `vm_name` (string) - This is the name of the OVF file for the new virtual - machine, without the file extension. By default this is "packer-BUILDNAME", + machine, without the file extension. By default this is `packer-BUILDNAME`, where "BUILDNAME" is the name of the build. - `vrdp_bind_address` (string / IP address) - The IP address that should be - binded to for VRDP. By default packer will use 127.0.0.1 for this. If you - wish to bind to all interfaces use 0.0.0.0 + binded to for VRDP. By default packer will use `127.0.0.1` for this. If you + wish to bind to all interfaces use `0.0.0.0`. - `vrdp_port_min` and `vrdp_port_max` (number) - The minimum and maximum port to use for VRDP access to the virtual machine. Packer uses a randomly chosen - port in this range that appears available. By default this is 5900 to 6000. - The minimum and maximum ports are inclusive. + port in this range that appears available. By default this is `5900` to + `6000`. The minimum and maximum ports are inclusive. ## Boot Command @@ -331,71 +334,10 @@ As documented above, the `boot_command` is an array of strings. The strings are all typed in sequence. It is an array only to improve readability within the template. -The boot command is "typed" character for character over a VNC connection to the -machine, simulating a human actually typing the keyboard. There are a set of -special keys available. If these are in your boot command, they will be replaced -by the proper key: +The boot command is sent to the VM through the `VBoxManage` utility in as few +invocations as possible. -- `` - Backspace - -- `` - Delete - -- `` and `` - Simulates an actual "enter" or "return" keypress. - -- `` - Simulates pressing the escape key. - -- `` - Simulates pressing the tab key. - -- `` - `` - Simulates pressing a function key. - -- `` `` `` `` - Simulates pressing an arrow key. - -- `` - Simulates pressing the spacebar. - -- `` - Simulates pressing the insert key. - -- `` `` - Simulates pressing the home and end keys. - -- `` `` - Simulates pressing the page up and page down keys. - -- `` `` - Simulates pressing the alt key. - -- `` `` - Simulates pressing the ctrl key. - -- `` `` - Simulates pressing the shift key. - -- `` `` - Simulates pressing and holding the alt key. - -- `` `` - Simulates pressing and holding the - ctrl key. - -- `` `` - Simulates pressing and holding the - shift key. - -- `` `` - Simulates releasing a held alt key. - -- `` `` - Simulates releasing a held ctrl key. - -- `` `` - Simulates releasing a held shift key. - -- `` `` `` - Adds a 1, 5 or 10 second pause before - sending any additional keys. This is useful if you have to generally wait - for the UI to update before typing more. - -When using modifier keys `ctrl`, `alt`, `shift` ensure that you release them, -otherwise they will be held down until the machine reboots. Use lowercase -characters as well inside modifiers. - -For example: to simulate ctrl+c use `c`. - -In addition to the special keys, each command to type is treated as a -[template engine](/docs/templates/engine.html). The -available variables are: - -- `HTTPIP` and `HTTPPort` - The IP and port, respectively of an HTTP server - that is started serving the directory specified by the `http_directory` - configuration parameter. If `http_directory` isn't specified, these will be - blank! +<%= partial "partials/builders/boot-command" %> Example boot command. This is actually a working boot command used to start an Ubuntu 12.04 installer: diff --git a/website/source/docs/builders/virtualbox-ovf.html.md b/website/source/docs/builders/virtualbox-ovf.html.md.erb similarity index 80% rename from website/source/docs/builders/virtualbox-ovf.html.md rename to website/source/docs/builders/virtualbox-ovf.html.md.erb index d58101100..89edcd89f 100644 --- a/website/source/docs/builders/virtualbox-ovf.html.md +++ b/website/source/docs/builders/virtualbox-ovf.html.md.erb @@ -1,4 +1,6 @@ --- +modeline: | + vim: set ft=pandoc: description: | This VirtualBox Packer builder is able to create VirtualBox virtual machines and export them in the OVF format, starting from an existing OVF/OVA (exported @@ -76,15 +78,15 @@ builder. - `boot_wait` (string) - The time to wait after booting the initial virtual machine before typing the `boot_command`. The value of this should be - a duration. Examples are "5s" and "1m30s" which will cause Packer to wait + a duration. Examples are `5s` and `1m30s` which will cause Packer to wait five seconds and one minute 30 seconds, respectively. If this isn't - specified, the default is 10 seconds. + specified, the default is `10s` or 10 seconds. - `checksum` (string) - The checksum for the OVA file. The type of the checksum is specified with `checksum_type`, documented below. - `checksum_type` (string) - The type of the checksum specified in `checksum`. - Valid values are "none", "md5", "sha1", "sha256", or "sha512". Although the + Valid values are `none`, `md5`, `sha1`, `sha256`, or `sha512`. Although the checksum will not be verified when `checksum_type` is set to "none", this is not recommended since OVA files can be very large and corruption does happen from time to time. @@ -131,6 +133,12 @@ builder. "packer_conf.json" ``` +- `floppy_dirs` (array of strings) - A list of directories to place onto the + floppy disk recursively. This is similar to the `floppy_files` option except + that the directory structure is preserved. This is useful for when your + floppy disk includes drivers or if you just want to organize it's contents + as a hierarchy. Wildcard characters (\*, ?, and \[\]) are allowed. + - `floppy_files` (array of strings) - A list of files to place onto a floppy disk that is attached when the VM is booted. This is most useful for unattended Windows installs, which look for an `Autounattend.xml` file on @@ -141,26 +149,20 @@ builder. and \[\]) are allowed. Directory names are also allowed, which will add all the files found in the directory to the floppy. -- `floppy_dirs` (array of strings) - A list of directories to place onto the - floppy disk recursively. This is similar to the `floppy_files` option except - that the directory structure is preserved. This is useful for when your - floppy disk includes drivers or if you just want to organize it's contents - as a hierarchy. Wildcard characters (\*, ?, and \[\]) are allowed. - -- `format` (string) - Either "ovf" or "ova", this specifies the output format - of the exported virtual machine. This defaults to "ovf". +- `format` (string) - Either `ovf` or `ova`, this specifies the output format + of the exported virtual machine. This defaults to `ovf`. - `guest_additions_mode` (string) - The method by which guest additions are - made available to the guest for installation. Valid options are "upload", - "attach", or "disable". If the mode is "attach" the guest additions ISO will - be attached as a CD device to the virtual machine. If the mode is "upload" + made available to the guest for installation. Valid options are `upload`, + `attach`, or `disable`. If the mode is `attach` the guest additions ISO will + be attached as a CD device to the virtual machine. If the mode is `upload` the guest additions ISO will be uploaded to the path specified by - `guest_additions_path`. The default value is "upload". If "disable" is used, + `guest_additions_path`. The default value is `upload`. If `disable` is used, guest additions won't be downloaded, either. - `guest_additions_path` (string) - The path on the guest virtual machine where the VirtualBox guest additions ISO will be uploaded. By default this - is "VBoxGuestAdditions.iso" which should upload into the login directory of + is `VBoxGuestAdditions.iso` which should upload into the login directory of the user. This is a [configuration template](/docs/templates/engine.html) where the `Version` variable is replaced with the VirtualBox version. @@ -177,30 +179,31 @@ builder. - `headless` (boolean) - Packer defaults to building VirtualBox virtual machines by launching a GUI that shows the console of the machine - being built. When this value is set to true, the machine will start without - a console. + being built. When this value is set to `true`, the machine will start + without a console. - `http_directory` (string) - Path to a directory to serve using an HTTP server. The files in this directory will be available over HTTP that will be requestable from the virtual machine. This is useful for hosting - kickstart files and so on. By default this is "", which means no HTTP server - will be started. The address and port of the HTTP server will be available - as variables in `boot_command`. This is covered in more detail below. + kickstart files and so on. By default this is an empty string, which means + no HTTP server will be started. The address and port of the HTTP server will + be available as variables in `boot_command`. This is covered in more detail + below. - `http_port_min` and `http_port_max` (number) - These are the minimum and maximum port to use for the HTTP server started to serve the `http_directory`. Because Packer often runs in parallel, Packer will choose a randomly available port in this range to run the HTTP server. If you want to force the HTTP server to be on one port, make this minimum and maximum - port the same. By default the values are 8000 and 9000, respectively. + port the same. By default the values are `8000` and `9000`, respectively. - `import_flags` (array of strings) - Additional flags to pass to `VBoxManage import`. This can be used to add additional command-line flags such as `--eula-accept` to accept a EULA in the OVF. - `import_opts` (string) - Additional options to pass to the - `VBoxManage import`. This can be useful for passing "keepallmacs" or - "keepnatmacs" options for existing ovf images. + `VBoxManage import`. This can be useful for passing `keepallmacs` or + `keepnatmacs` options for existing ovf images. - `keep_registered` (boolean) - Set this to `true` if you would like to keep the VM registered with virtualbox. Defaults to `false`. @@ -209,13 +212,13 @@ builder. resulting virtual machine will be created. This may be relative or absolute. If relative, the path is relative to the working directory when `packer` is executed. This directory must not exist or be empty prior to running - the builder. By default this is "output-BUILDNAME" where "BUILDNAME" is the + the builder. By default this is `output-BUILDNAME` where "BUILDNAME" is the name of the build. - `post_shutdown_delay` (string) - The amount of time to wait after shutting down the virtual machine. If you get the error `Error removing floppy controller`, you might need to set this to `5m` - or so. By default, the delay is `0s`, or disabled. + or so. By default, the delay is `0s` or disabled. - `shutdown_command` (string) - The command to use to gracefully shut down the machine once all the provisioning is done. By default this is an empty @@ -228,7 +231,7 @@ builder. - `shutdown_timeout` (string) - The amount of time to wait after executing the `shutdown_command` for the virtual machine to actually shut down. If it doesn't shut down in this time, it is an error. By default, the timeout is - "5m", or five minutes. + `5m` or five minutes. - `skip_export` (boolean) - Defaults to `false`. When enabled, Packer will not export the VM. Useful if the build output is not the resultant image, @@ -238,11 +241,11 @@ builder. maximum port to use for the SSH port on the host machine which is forwarded to the SSH port on the guest machine. Because Packer often runs in parallel, Packer will choose a randomly available port in this range to use as the - host port. + host port. By default this is `2222` to `4444`. - `ssh_skip_nat_mapping` (boolean) - Defaults to false. When enabled, Packer does not setup forwarded port mapping for SSH requests and uses `ssh_port` - on the host to communicate to the virtual machine + on the host to communicate to the virtual machine. - `target_path` (string) - The path where the OVA should be saved after download. By default, it will go in the packer cache, with a hash of @@ -266,22 +269,23 @@ builder. - `virtualbox_version_file` (string) - The path within the virtual machine to upload a file that contains the VirtualBox version that was used to create the machine. This information can be useful for provisioning. By default - this is ".vbox\_version", which will generally be upload it into the + this is `.vbox_version`, which will generally be upload it into the home directory. Set to an empty string to skip uploading this file, which can be useful when using the `none` communicator. - `vm_name` (string) - This is the name of the virtual machine when it is imported as well as the name of the OVF file when the virtual machine - is exported. By default this is "packer-BUILDNAME", where "BUILDNAME" is the + is exported. By default this is `packer-BUILDNAME`, where "BUILDNAME" is the name of the build. - `vrdp_bind_address` (string / IP address) - The IP address that should be - binded to for VRDP. By default packer will use 127.0.0.1 for this. + binded to for VRDP. By default packer will use `127.0.0.1` for this. If you + wish to bind to all interfaces use `0.0.0.0`. - `vrdp_port_min` and `vrdp_port_max` (number) - The minimum and maximum port to use for VRDP access to the virtual machine. Packer uses a randomly chosen - port in this range that appears available. By default this is 5900 to 6000. - The minimum and maximum ports are inclusive. + port in this range that appears available. By default this is `5900` to + `6000`. The minimum and maximum ports are inclusive. ## Boot Command @@ -293,65 +297,10 @@ As documented above, the `boot_command` is an array of strings. The strings are all typed in sequence. It is an array only to improve readability within the template. -The boot command is "typed" character for character over a VNC connection to the -machine, simulating a human actually typing the keyboard. There are a set of -special keys available. If these are in your boot command, they will be replaced -by the proper key: +The boot command is sent to the VM through the `VBoxManage` utility in as few +invocations as possible. -- `` - Backspace - -- `` - Delete - -- `` and `` - Simulates an actual "enter" or "return" keypress. - -- `` - Simulates pressing the escape key. - -- `` - Simulates pressing the tab key. - -- `` - `` - Simulates pressing a function key. - -- `` `` `` `` - Simulates pressing an arrow key. - -- `` - Simulates pressing the spacebar. - -- `` - Simulates pressing the insert key. - -- `` `` - Simulates pressing the home and end keys. - -- `` `` - Simulates pressing the page up and page down keys. - -- `` `` - Simulates pressing the alt key. - -- `` `` - Simulates pressing the ctrl key. - -- `` `` - Simulates pressing the shift key. - -- `` `` - Simulates pressing and holding the alt key. - -- `` `` - Simulates pressing and holding the - ctrl key. - -- `` `` - Simulates pressing and holding the - shift key. - -- `` `` - Simulates releasing a held alt key. - -- `` `` - Simulates releasing a held ctrl key. - -- `` `` - Simulates releasing a held shift key. - -- `` `` `` - Adds a 1, 5 or 10 second pause before - sending any additional keys. This is useful if you have to generally wait - for the UI to update before typing more. - -In addition to the special keys, each command to type is treated as a -[template engine](/docs/templates/engine.html). The -available variables are: - -- `HTTPIP` and `HTTPPort` - The IP and port, respectively of an HTTP server - that is started serving the directory specified by the `http_directory` - configuration parameter. If `http_directory` isn't specified, these will be - blank! +<%= partial "partials/builders/boot-command" %> Example boot command. This is actually a working boot command used to start an Ubuntu 12.04 installer: diff --git a/website/source/docs/builders/vmware-iso.html.md b/website/source/docs/builders/vmware-iso.html.md.erb similarity index 84% rename from website/source/docs/builders/vmware-iso.html.md rename to website/source/docs/builders/vmware-iso.html.md.erb index 86d79e93b..21d19c183 100644 --- a/website/source/docs/builders/vmware-iso.html.md +++ b/website/source/docs/builders/vmware-iso.html.md.erb @@ -1,4 +1,6 @@ --- +modeline: | + vim: set ft=pandoc: description: | This VMware Packer builder is able to create VMware virtual machines from an ISO file as a source. It currently supports building virtual machines on hosts @@ -67,8 +69,8 @@ builder. over `iso_checksum_url` type. - `iso_checksum_type` (string) - The type of the checksum specified in - `iso_checksum`. Valid values are "none", "md5", "sha1", "sha256", or - "sha512" currently. While "none" will skip checksumming, this is not + `iso_checksum`. Valid values are `none`, `md5`, `sha1`, `sha256`, or + `sha512` currently. While `none` will skip checksumming, this is not recommended since ISO files are generally large and corruption does happen from time to time. @@ -92,9 +94,29 @@ builder. - `boot_wait` (string) - The time to wait after booting the initial virtual machine before typing the `boot_command`. The value of this should be - a duration. Examples are "5s" and "1m30s" which will cause Packer to wait + a duration. Examples are `5s` and `1m30s` which will cause Packer to wait five seconds and one minute 30 seconds, respectively. If this isn't - specified, the default is 10 seconds. + specified, the default is `10s` or 10 seconds. + +- `cdrom_adapter_type` (string) - The adapter type (or bus) that will be used + by the cdrom device. This is chosen by default based on the disk adapter + type. VMware tends to lean towards `ide` for the cdrom device unless + `sata` is chosen for the disk adapter and so Packer attempts to mirror + this logic. This field can be specified as either `ide`, `sata`, or `scsi`. + +- `disable_vnc` (boolean) - Whether to create a VNC connection or not. + A `boot_command` cannot be used when this is `false`. Defaults to `false`. + +- `disk_adapter_type` (string) - The adapter type of the VMware virtual disk + to create. This option is for advanced usage, modify only if you know what + you're doing. Some of the options you can specify are `ide`, `sata`, `nvme` + or `scsi` (which uses the "lsilogic" scsi interface by default). If you + specify another option, Packer will assume that you're specifying a `scsi` + interface of that specified type. For more information, please consult the + + Virtual Disk Manager User's Guide for desktop VMware clients. + For ESXi, refer to the proper ESXi documentation. - `disk_additional_size` (array of integers) - The size(s) of any additional hard disks for the VM in megabytes. If this is not specified then the VM @@ -105,10 +127,10 @@ builder. - `disk_size` (number) - The size of the hard disk for the VM in megabytes. The builder uses expandable, not fixed-size virtual hard disks, so the actual file representing the disk will not use the full size unless it - is full. By default this is set to 40,000 (about 40 GB). + is full. By default this is set to `40000` (about 40 GB). - `disk_type_id` (string) - The type of VMware virtual disk to create. This - option is for advanced usage. + option is for advanced usage. For desktop VMware clients: @@ -121,9 +143,9 @@ builder. `4` | Preallocated virtual disk compatible with ESX server (VMFS flat). `5` | Compressed disk optimized for streaming. - The default is "1". - - For ESXi, this defaults to "zeroedthick". The available options for ESXi + The default is `1`. + + For ESXi, this defaults to `zeroedthick`. The available options for ESXi are: `zeroedthick`, `eagerzeroedthick`, `thin`, `rdm:dev`, `rdmp:dev`, `2gbsparse`. @@ -131,25 +153,11 @@ builder. Guide](https://www.vmware.com/pdf/VirtualDiskManager.pdf) for desktop VMware clients. For ESXi, refer to the proper ESXi documentation. -- `disk_adapter_type` (string) - The adapter type of the VMware virtual disk - to create. This option is for advanced usage, modify only if you know what - you're doing. Some of the options you can specify are "ide", "sata", "nvme" - or "scsi" (which uses the "lsilogic" scsi interface by default). If you - specify another option, Packer will assume that you're specifying a "scsi" - interface of that specified type. For more information, please consult the - - Virtual Disk Manager User's Guide for desktop VMware clients. - For ESXi, refer to the proper ESXi documentation. - -- `cdrom_adapter_type` (string) - The adapter type (or bus) that will be used - by the cdrom device. This is chosen by default based on the disk adapter - type. VMware tends to lean towards "ide" for the cdrom device unless - "sata" is chosen for the disk adapter and so Packer attempts to mirror - this logic. This field can be specified as either "ide", "sata", or "scsi". - -- `disable_vnc` (boolean) - Whether to create a VNC connection or not. - A `boot_command` cannot be used when this is `false`. Defaults to `false`. +- `floppy_dirs` (array of strings) - A list of directories to place onto + the floppy disk recursively. This is similar to the `floppy_files` option + except that the directory structure is preserved. This is useful for when + your floppy disk includes drivers or if you just want to organize it's + contents as a hierarchy. Wildcard characters (\*, ?, and \[\]) are allowed. - `floppy_files` (array of strings) - A list of files to place onto a floppy disk that is attached when the VM is booted. This is most useful for @@ -161,44 +169,39 @@ builder. and \[\]) are allowed. Directory names are also allowed, which will add all the files found in the directory to the floppy. -- `floppy_dirs` (array of strings) - A list of directories to place onto - the floppy disk recursively. This is similar to the `floppy_files` option - except that the directory structure is preserved. This is useful for when - your floppy disk includes drivers or if you just want to organize it's - contents as a hierarchy. Wildcard characters (\*, ?, and \[\]) are allowed. - - `fusion_app_path` (string) - Path to "VMware Fusion.app". By default this is - "/Applications/VMware Fusion.app" but this setting allows you to + `/Applications/VMware Fusion.app` but this setting allows you to customize this. - `guest_os_type` (string) - The guest OS type being installed. This will be - set in the VMware VMX. By default this is "other". By specifying a more + set in the VMware VMX. By default this is `other`. By specifying a more specific OS type, VMware may perform some optimizations or virtual hardware changes to better support the operating system running in the virtual machine. - `headless` (boolean) - Packer defaults to building VMware virtual machines by launching a GUI that shows the console of the machine being built. When - this value is set to true, the machine will start without a console. For + this value is set to `true`, the machine will start without a console. For VMware machines, Packer will output VNC connection information in case you need to connect to the console to debug the build process. - `http_directory` (string) - Path to a directory to serve using an HTTP server. The files in this directory will be available over HTTP that will be requestable from the virtual machine. This is useful for hosting - kickstart files and so on. By default this is "", which means no HTTP server - will be started. The address and port of the HTTP server will be available - as variables in `boot_command`. This is covered in more detail below. + kickstart files and so on. By default this is an empty string, which means + no HTTP server will be started. The address and port of the HTTP server will + be available as variables in `boot_command`. This is covered in more detail + below. - `http_port_min` and `http_port_max` (number) - These are the minimum and maximum port to use for the HTTP server started to serve the `http_directory`. Because Packer often runs in parallel, Packer will choose a randomly available port in this range to run the HTTP server. If you want to force the HTTP server to be on one port, make this minimum and maximum - port the same. By default the values are 8000 and 9000, respectively. + port the same. By default the values are `8000` and `9000`, respectively. - `iso_target_extension` (string) - The extension of the iso file after - download. This defaults to "iso". + download. This defaults to `iso`. - `iso_target_path` (string) - The path where the iso should be saved after download. By default will go in the packer cache, with a hash of the @@ -212,11 +215,11 @@ builder. - `network` (string) - This is the network type that the virtual machine will be created with. This can be one of the generic values that map to a device - such as "hostonly", "nat", or "bridged". If the network is not one of these + such as `hostonly`, `nat`, or `bridged`. If the network is not one of these values, then it is assumed to be a VMware network device. (VMnet0..x) - `network_adapter_type` (string) - This is the ethernet adapter type the the - virtual machine will be created with. By default the "e1000" network adapter + virtual machine will be created with. By default the `e1000` network adapter type will be used by Packer. For more information, please consult the @@ -227,12 +230,12 @@ builder. resulting virtual machine will be created. This may be relative or absolute. If relative, the path is relative to the working directory when `packer` is executed. This directory must not exist or be empty prior to running - the builder. By default this is "output-BUILDNAME" where "BUILDNAME" is the + the builder. By default this is `output-BUILDNAME` where "BUILDNAME" is the name of the build. - `parallel` (string) - This specifies a parallel port to add to the VM. It has the format of `Type:option1,option2,...`. Type can be one of the - following values: "FILE", "DEVICE", "AUTO", or "NONE". + following values: `FILE`, `DEVICE`, `AUTO`, or `NONE`. * `FILE:path` - Specifies the path to the local file to be used for the parallel port. @@ -252,12 +255,12 @@ builder. - `remote_cache_directory` (string) - The path where the ISO and/or floppy files will be stored during the build on the remote machine. The path is relative to the `remote_cache_datastore` on the remote machine. By default - this is "packer\_cache". This only has an effect if `remote_type` + this is `packer_cache`. This only has an effect if `remote_type` is enabled. - `remote_datastore` (string) - The path to the datastore where the resulting VM will be stored when it is built on the remote machine. By default this - is "datastore1". This only has an effect if `remote_type` is enabled. + is `datastore1`. This only has an effect if `remote_type` is enabled. - `remote_host` (string) - The host of the remote machine used for access. This is only required if `remote_type` is enabled. @@ -272,7 +275,7 @@ builder. - `remote_type` (string) - The type of remote machine that will be used to build this VM rather than a local desktop product. The only value accepted - for this currently is "esx5". If this is not set, a desktop product will + for this currently is `esx5`. If this is not set, a desktop product will be used. By default, this is not set. - `remote_username` (string) - The username for the SSH user that will access @@ -320,7 +323,7 @@ builder. - `shutdown_timeout` (string) - The amount of time to wait after executing the `shutdown_command` for the virtual machine to actually shut down. If it doesn't shut down in this time, it is an error. By default, the timeout is - "5m", or five minutes. + `5m` or five minutes. - `skip_compaction` (boolean) - VMware-created disks are defragmented and compacted at the end of the build process using `vmware-vdiskmanager`. In @@ -346,7 +349,7 @@ builder. - `sound` (boolean) - Enable VMware's virtual soundcard device for the VM. - `tools_upload_flavor` (string) - The flavor of the VMware Tools ISO to - upload into the VM. Valid values are "darwin", "linux", and "windows". By + upload into the VM. Valid values are `darwin`, `linux`, and `windows`. By default, this is empty, which means VMware tools won't be uploaded. - `tools_upload_path` (string) - The path in the VM to upload the @@ -355,23 +358,23 @@ builder. template](/docs/templates/engine.html) that has a single valid variable: `Flavor`, which will be the value of `tools_upload_flavor`. By default the upload path is set to `{{.Flavor}}.iso`. This setting is not - used when `remote_type` is "esx5". + used when `remote_type` is `esx5`. - `usb` (boolean) - Enable VMware's USB bus for the guest VM. To enable usage of the XHCI bus for USB 3 (5 Gbit/s), one can use the `vmx_data` option to - enable it by specifying "true" for the `usb_xhci.present` property. + enable it by specifying `true` for the `usb_xhci.present` property. - `version` (string) - The [vmx hardware version](http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1003746) for the new virtual machine. Only the default value has been tested, any - other value is experimental. Default value is '9'. + other value is experimental. Default value is `9`. - `vm_name` (string) - This is the name of the VMX file for the new virtual - machine, without the file extension. By default this is "packer-BUILDNAME", + machine, without the file extension. By default this is `packer-BUILDNAME`, where "BUILDNAME" is the name of the build. - `vmdk_name` (string) - The filename of the virtual disk that'll be created, - without the extension. This defaults to "packer". + without the extension. This defaults to `packer`. - `vmx_data` (object of key/value strings) - Arbitrary key/values to enter into the virtual machine VMX file. This is for advanced users who want to @@ -381,10 +384,11 @@ builder. except that it is run after the virtual machine is shutdown, and before the virtual machine is exported. -- `vmx_remove_ethernet_interfaces` (boolean) - Remove all ethernet interfaces from - the VMX file after building. This is for advanced users who understand the - ramifications, but is useful for building Vagrant boxes since Vagrant will - create ethernet interfaces when provisioning a box. +- `vmx_remove_ethernet_interfaces` (boolean) - Remove all ethernet interfaces + from the VMX file after building. This is for advanced users who understand + the ramifications, but is useful for building Vagrant boxes since Vagrant + will create ethernet interfaces when provisioning a box. Defaults to + `false`. - `vmx_template_path` (string) - Path to a [configuration template](/docs/templates/engine.html) that defines the @@ -393,18 +397,19 @@ builder. below for more information. For basic VMX modifications, try `vmx_data` first. -- `vnc_bind_address` (string / IP address) - The IP address that should be binded - to for VNC. By default packer will use 127.0.0.1 for this. If you wish to bind - to all interfaces use 0.0.0.0 +- `vnc_bind_address` (string / IP address) - The IP address that should be + binded to for VNC. By default packer will use `127.0.0.1` for this. If you + wish to bind to all interfaces use `0.0.0.0`. -- `vnc_disable_password` (boolean) - Don't auto-generate a VNC password that is - used to secure the VNC communication with the VM. +- `vnc_disable_password` (boolean) - Don't auto-generate a VNC password that + is used to secure the VNC communication with the VM. - `vnc_port_min` and `vnc_port_max` (number) - The minimum and maximum port to use for VNC access to the virtual machine. The builder uses VNC to type the initial `boot_command`. Because Packer generally runs in parallel, Packer uses a randomly chosen port in this range that appears available. By - default this is 5900 to 6000. The minimum and maximum ports are inclusive. + default this is `5900` to `6000`. The minimum and maximum ports are + inclusive. ## Boot Command @@ -425,67 +430,13 @@ default 100ms delay. The delay alleviates issues with latency and CPU contention. For local builds you can tune this delay by specifying e.g. `PACKER_KEY_INTERVAL=10ms` to speed through the boot command. -There are a set of special keys available. If these are in your boot -command, they will be replaced by the proper key: +<%= partial "partials/builders/boot-command" %> -- `` - Backspace - -- `` - Delete - -- `` and `` - Simulates an actual "enter" or "return" keypress. - -- `` - Simulates pressing the escape key. - -- `` - Simulates pressing the tab key. - -- `` - `` - Simulates pressing a function key. - -- `` `` `` `` - Simulates pressing an arrow key. - -- `` - Simulates pressing the spacebar. - -- `` - Simulates pressing the insert key. - -- `` `` - Simulates pressing the home and end keys. - -- `` `` - Simulates pressing the page up and page down keys. - -- `` `` - Simulates pressing the alt key. - -- `` `` - Simulates pressing the ctrl key. - -- `` `` - Simulates pressing the shift key. - -- `` `` - Simulates pressing and holding the alt key. - -- `` `` - Simulates pressing and holding the ctrl key. - -- `` `` - Simulates pressing and holding the shift key. - -- `` `` - Simulates releasing a held alt key. - -- `` `` - Simulates releasing a held ctrl key. - -- `` `` - Simulates releasing a held shift key. - -- `` `` `` - Adds a 1, 5 or 10 second pause before - sending any additional keys. This is useful if you have to generally wait - for the UI to update before typing more. - -When using modifier keys `ctrl`, `alt`, `shift` ensure that you release them, -otherwise they will be held down until the machine reboots. Use lowercase -characters as well inside modifiers. - -For example: to simulate ctrl+c use `c`. - -In addition to the special keys, each command to type is treated as a -[template engine](/docs/templates/engine.html). The -available variables are: - -- `HTTPIP` and `HTTPPort` - The IP and port, respectively of an HTTP server - that is started serving the directory specified by the `http_directory` - configuration parameter. If `http_directory` isn't specified, these will be - blank! +-> **Note**: for the `HTTPIP` to be resolved correctly, your VM's network +configuration has to include a `hostonly` or `nat` type network interface. +If you are using this feature, it is recommended to leave the default network +configuration while you are building the VM, and use the `vmx_data_post` hook +to modify the network configuration after the VM is done building. Example boot command. This is actually a working boot command used to start an Ubuntu 12.04 installer: diff --git a/website/source/docs/builders/vmware-vmx.html.md b/website/source/docs/builders/vmware-vmx.html.md.erb similarity index 73% rename from website/source/docs/builders/vmware-vmx.html.md rename to website/source/docs/builders/vmware-vmx.html.md.erb index ae5bfd29f..90880cb54 100644 --- a/website/source/docs/builders/vmware-vmx.html.md +++ b/website/source/docs/builders/vmware-vmx.html.md.erb @@ -1,4 +1,6 @@ --- +modeline: | + vim: set ft=pandoc: description: | This VMware Packer builder is able to create VMware virtual machines from an existing VMware virtual machine (a VMX file). It currently supports building @@ -67,13 +69,19 @@ builder. - `boot_wait` (string) - The time to wait after booting the initial virtual machine before typing the `boot_command`. The value of this should be - a duration. Examples are "5s" and "1m30s" which will cause Packer to wait + a duration. Examples are `5s` and `1m30s` which will cause Packer to wait five seconds and one minute 30 seconds, respectively. If this isn't - specified, the default is 10 seconds. + specified, the default is `10s` or 10 seconds. * `disable_vnc` (boolean) - Whether to create a VNC connection or not. A `boot_command` cannot be used when this is `false`. Defaults to `false`. +- `floppy_dirs` (array of strings) - A list of directories to place onto + the floppy disk recursively. This is similar to the `floppy_files` option + except that the directory structure is preserved. This is useful for when + your floppy disk includes drivers or if you just want to organize it's + contents as a hierarchy. Wildcard characters (\*, ?, and \[\]) are allowed. + - `floppy_files` (array of strings) - A list of files to place onto a floppy disk that is attached when the VM is booted. This is most useful for unattended Windows installs, which look for an `Autounattend.xml` file on @@ -84,41 +92,36 @@ builder. and \[\]) are allowed. Directory names are also allowed, which will add all the files found in the directory to the floppy. -- `floppy_dirs` (array of strings) - A list of directories to place onto - the floppy disk recursively. This is similar to the `floppy_files` option - except that the directory structure is preserved. This is useful for when - your floppy disk includes drivers or if you just want to organize it's - contents as a hierarchy. Wildcard characters (\*, ?, and \[\]) are allowed. - - `fusion_app_path` (string) - Path to "VMware Fusion.app". By default this is - "/Applications/VMware Fusion.app" but this setting allows you to + `/Applications/VMware Fusion.app` but this setting allows you to customize this. - `headless` (boolean) - Packer defaults to building VMware virtual machines by launching a GUI that shows the console of the machine being built. When - this value is set to true, the machine will start without a console. For + this value is set to `true`, the machine will start without a console. For VMware machines, Packer will output VNC connection information in case you need to connect to the console to debug the build process. - `http_directory` (string) - Path to a directory to serve using an HTTP server. The files in this directory will be available over HTTP that will be requestable from the virtual machine. This is useful for hosting - kickstart files and so on. By default this is "", which means no HTTP server - will be started. The address and port of the HTTP server will be available - as variables in `boot_command`. This is covered in more detail below. + kickstart files and so on. By default this is an empty string, which means + no HTTP server will be started. The address and port of the HTTP server will + be available as variables in `boot_command`. This is covered in more detail + below. - `http_port_min` and `http_port_max` (number) - These are the minimum and maximum port to use for the HTTP server started to serve the `http_directory`. Because Packer often runs in parallel, Packer will choose a randomly available port in this range to run the HTTP server. If you want to force the HTTP server to be on one port, make this minimum and maximum - port the same. By default the values are 8000 and 9000, respectively. + port the same. By default the values are `8000` and `9000`, respectively. - `output_directory` (string) - This is the path to the directory where the resulting virtual machine will be created. This may be relative or absolute. If relative, the path is relative to the working directory when `packer` is executed. This directory must not exist or be empty prior to running - the builder. By default this is "output-BUILDNAME" where "BUILDNAME" is the + the builder. By default this is `output-BUILDNAME` where "BUILDNAME" is the name of the build. - `shutdown_command` (string) - The command to use to gracefully shut down the @@ -132,16 +135,16 @@ builder. - `shutdown_timeout` (string) - The amount of time to wait after executing the `shutdown_command` for the virtual machine to actually shut down. If it doesn't shut down in this time, it is an error. By default, the timeout is - "5m", or five minutes. + `5m` or five minutes. - `skip_compaction` (boolean) - VMware-created disks are defragmented and compacted at the end of the build process using `vmware-vdiskmanager`. In certain rare cases, this might actually end up making the resulting disks slightly larger. If you find this to be the case, you can disable compaction - using this configuration value. + using this configuration value. Defaults to `false`. - `tools_upload_flavor` (string) - The flavor of the VMware Tools ISO to - upload into the VM. Valid values are "darwin", "linux", and "windows". By + upload into the VM. Valid values are `darwin`, `linux`, and `windows`. By default, this is empty, which means VMware tools won't be uploaded. - `tools_upload_path` (string) - The path in the VM to upload the @@ -152,7 +155,7 @@ builder. By default the upload path is set to `{{.Flavor}}.iso`. - `vm_name` (string) - This is the name of the VMX file for the new virtual - machine, without the file extension. By default this is "packer-BUILDNAME", + machine, without the file extension. By default this is `packer-BUILDNAME`, where "BUILDNAME" is the name of the build. - `vmx_data` (object of key/value strings) - Arbitrary key/values to enter @@ -163,22 +166,25 @@ builder. except that it is run after the virtual machine is shutdown, and before the virtual machine is exported. -- `vmx_remove_ethernet_interfaces` (boolean) - Remove all ethernet interfaces from - the VMX file after building. This is for advanced users who understand the - ramifications, but is useful for building Vagrant boxes since Vagrant will - create ethernet interfaces when provisioning a box. +- `vmx_remove_ethernet_interfaces` (boolean) - Remove all ethernet interfaces + from the VMX file after building. This is for advanced users who understand + the ramifications, but is useful for building Vagrant boxes since Vagrant + will create ethernet interfaces when provisioning a box. Defaults to + `false`. -- `vnc_bind_address` (string / IP address) - The IP address that should be binded - to for VNC. By default packer will use 127.0.0.1 for this. +- `vnc_bind_address` (string / IP address) - The IP address that should be + binded to for VNC. By default packer will use `127.0.0.1` for this. If you + wish to bind to all interfaces use `0.0.0.0`. -- `vnc_disable_password` (boolean) - Don't auto-generate a VNC password that is - used to secure the VNC communication with the VM. +- `vnc_disable_password` (boolean) - Don't auto-generate a VNC password that + is used to secure the VNC communication with the VM. - `vnc_port_min` and `vnc_port_max` (number) - The minimum and maximum port to use for VNC access to the virtual machine. The builder uses VNC to type the initial `boot_command`. Because Packer generally runs in parallel, Packer uses a randomly chosen port in this range that appears available. By - default this is 5900 to 6000. The minimum and maximum ports are inclusive. + default this is `5900` to `6000`. The minimum and maximum ports are + inclusive. ## Boot Command @@ -198,63 +204,7 @@ default 100ms delay. The delay alleviates issues with latency and CPU contention. For local builds you can tune this delay by specifying e.g. `PACKER_KEY_INTERVAL=10ms` to speed through the boot command. -There are a set of special keys available. If these are in your boot -command, they will be replaced by the proper key: - -- `` - Backspace - -- `` - Delete - -- `` and `` - Simulates an actual "enter" or "return" keypress. - -- `` - Simulates pressing the escape key. - -- `` - Simulates pressing the tab key. - -- `` - `` - Simulates pressing a function key. - -- `` `` `` `` - Simulates pressing an arrow key. - -- `` - Simulates pressing the spacebar. - -- `` - Simulates pressing the insert key. - -- `` `` - Simulates pressing the home and end keys. - -- `` `` - Simulates pressing the page up and page down keys. - -- `` `` - Simulates pressing the alt key. - -- `` `` - Simulates pressing the ctrl key. - -- `` `` - Simulates pressing the shift key. - -- `` `` - Simulates pressing and holding the alt key. - -- `` `` - Simulates pressing and holding the ctrl - key. - -- `` `` - Simulates pressing and holding the - shift key. - -- `` `` - Simulates releasing a held alt key. - -- `` `` - Simulates releasing a held ctrl key. - -- `` `` - Simulates releasing a held shift key. - -- `` `` `` - Adds a 1, 5 or 10 second pause before - sending any additional keys. This is useful if you have to generally wait - for the UI to update before typing more. - -In addition to the special keys, each command to type is treated as a -[template engine](/docs/templates/engine.html). The -available variables are: - -- `HTTPIP` and `HTTPPort` - The IP and port, respectively of an HTTP server - that is started serving the directory specified by the `http_directory` - configuration parameter. If `http_directory` isn't specified, these will be - blank! +<%= partial "partials/builders/boot-command" %> Example boot command. This is actually a working boot command used to start an Ubuntu 12.04 installer: diff --git a/website/source/docs/templates/communicator.html.md b/website/source/docs/templates/communicator.html.md index 0165ab5a1..352d370e8 100644 --- a/website/source/docs/templates/communicator.html.md +++ b/website/source/docs/templates/communicator.html.md @@ -59,11 +59,11 @@ to the remote host. The SSH communicator has the following options: -- `ssh_agent_auth` (boolean) - If true, the local SSH agent will be used to - authenticate connections to the remote host. Defaults to false. +- `ssh_agent_auth` (boolean) - If `true`, the local SSH agent will be used to + authenticate connections to the remote host. Defaults to `false`. -- `ssh_bastion_agent_auth` (boolean) - If true, the local SSH agent will - be used to authenticate with the bastion host. Defaults to false. +- `ssh_bastion_agent_auth` (boolean) - If `true`, the local SSH agent will + be used to authenticate with the bastion host. Defaults to `false`. - `ssh_bastion_host` (string) - A bastion host to use for the actual SSH connection. @@ -71,7 +71,7 @@ The SSH communicator has the following options: - `ssh_bastion_password` (string) - The password to use to authenticate with the bastion host. -- `ssh_bastion_port` (number) - The port of the bastion host. Defaults to 1. +- `ssh_bastion_port` (number) - The port of the bastion host. Defaults to `1`. - `ssh_bastion_private_key_file` (string) - A private key file to use to authenticate with the bastion host. @@ -80,51 +80,52 @@ The SSH communicator has the following options: host. - `ssh_disable_agent_forwarding` (boolean) - If true, SSH agent forwarding - will be disabled. Defaults to false. + will be disabled. Defaults to `false`. - `ssh_file_transfer_method` (`scp` or `sftp`) - How to transfer files, Secure copy (default) or SSH File Transfer Protocol. - `ssh_handshake_attempts` (number) - The number of handshakes to attempt - with SSH once it can connect. This defaults to 10. + with SSH once it can connect. This defaults to `10`. - `ssh_host` (string) - The address to SSH to. This usually is automatically configured by the builder. * `ssh_keep_alive_interval` (string) - How often to send "keep alive" - messages to the server. Set to a negative value (`-1s`) to disable. Example value: - "10s". Defaults to "5s". + messages to the server. Set to a negative value (`-1s`) to disable. Example + value: `10s`. Defaults to `5s`. - `ssh_password` (string) - A plaintext password to use to authenticate with SSH. -- `ssh_port` (number) - The port to connect to SSH. This defaults to 22. +- `ssh_port` (number) - The port to connect to SSH. This defaults to `22`. - `ssh_private_key_file` (string) - Path to a PEM encoded private key file to use to authenticate with SSH. -- `ssh_pty` (boolean) - If true, a PTY will be requested for the SSH - connection. This defaults to false. - -* `ssh_read_write_timeout` (string) - The amount of time to wait for a remote - command to end. This might be useful if, for example, packer hangs on - a connection after a reboot. Example: "5m". Disabled by default. - -- `ssh_timeout` (string) - The time to wait for SSH to become available. - Packer uses this to determine when the machine has booted so this is - usually quite long. Example value: "10m" - -- `ssh_username` (string) - The username to connect to SSH with. Required - if using SSH. - `ssh_proxy_host` (string) - A SOCKS proxy host to use for SSH connection -- `ssh_proxy_port` (Integer) - A port of the SOCKS proxy, defaults to 1080 +- `ssh_proxy_password` (string) - The password to use to authenticate with + the proxy server. Optional. + +- `ssh_proxy_port` (number) - A port of the SOCKS proxy. Defaults to `1080`. - `ssh_proxy_username` (string) - The username to authenticate with the proxy server. Optional. -- `ssh_proxy_password` (string) - The password to use to authenticate with - the proxy server. Optional. +- `ssh_pty` (boolean) - If `true`, a PTY will be requested for the SSH + connection. This defaults to `false`. + +* `ssh_read_write_timeout` (string) - The amount of time to wait for a remote + command to end. This might be useful if, for example, packer hangs on + a connection after a reboot. Example: `5m`. Disabled by default. + +- `ssh_timeout` (string) - The time to wait for SSH to become available. + Packer uses this to determine when the machine has booted so this is + usually quite long. Example value: `10m`. + +- `ssh_username` (string) - The username to connect to SSH with. Required + if using SSH. ## WinRM Communicator @@ -132,23 +133,24 @@ The WinRM communicator has the following options. - `winrm_host` (string) - The address for WinRM to connect to. -- `winrm_port` (number) - The WinRM port to connect to. This defaults to - 5985 for plain unencrypted connection and 5986 for SSL when `winrm_use_ssl` is set to true. - -- `winrm_username` (string) - The username to use to connect to WinRM. +- `winrm_insecure` (boolean) - If `true`, do not check server certificate + chain and host name. - `winrm_password` (string) - The password to use to connect to WinRM. +- `winrm_port` (number) - The WinRM port to connect to. This defaults to + `5985` for plain unencrypted connection and `5986` for SSL when + `winrm_use_ssl` is set to true. + - `winrm_timeout` (string) - The amount of time to wait for WinRM to - become available. This defaults to "30m" since setting up a Windows + become available. This defaults to `30m` since setting up a Windows machine generally takes a long time. -- `winrm_use_ssl` (boolean) - If true, use HTTPS for WinRM - -- `winrm_insecure` (boolean) - If true, do not check server certificate - chain and host name - -- `winrm_use_ntlm` (boolean) - If true, NTLM authentication will be used for WinRM, +- `winrm_use_ntlm` (boolean) - If `true`, NTLM authentication will be used for WinRM, rather than default (basic authentication), removing the requirement for basic authentication to be enabled within the target guest. Further reading for remote connection authentication can be found [here](https://msdn.microsoft.com/en-us/library/aa384295(v=vs.85).aspx). + +- `winrm_use_ssl` (boolean) - If `true`, use HTTPS for WinRM. + +- `winrm_username` (string) - The username to use to connect to WinRM. diff --git a/website/source/layouts/layout.erb b/website/source/layouts/layout.erb index 768ea0a83..90ab477e0 100644 --- a/website/source/layouts/layout.erb +++ b/website/source/layouts/layout.erb @@ -27,16 +27,18 @@ <%= title_for(current_page) %> + + <%= stylesheet_link_tag "application" %> - - <%= javascript_include_tag "application" %> + + + <%= javascript_include_tag "application", defer: true %> - - - + + <%= yield_content :head %> @@ -111,24 +113,6 @@ - -