Merge branch 'master' into allow_gcp_winrm_password

This commit is contained in:
Richard Nienaber 2018-04-24 10:25:27 +01:00
commit e2e7953fe4
160 changed files with 9084 additions and 3526 deletions

View file

@ -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)

View file

@ -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

View file

@ -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) {

View file

@ -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())

View file

@ -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())

View file

@ -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},
})

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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 == "" {

View file

@ -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)
}

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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 {

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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["<bs>"] = []string{"0e", "8e"}
special["<del>"] = []string{"53", "d3"}
special["<enter>"] = []string{"1c", "9c"}
special["<esc>"] = []string{"01", "81"}
special["<f1>"] = []string{"3b", "bb"}
special["<f2>"] = []string{"3c", "bc"}
special["<f3>"] = []string{"3d", "bd"}
special["<f4>"] = []string{"3e", "be"}
special["<f5>"] = []string{"3f", "bf"}
special["<f6>"] = []string{"40", "c0"}
special["<f7>"] = []string{"41", "c1"}
special["<f8>"] = []string{"42", "c2"}
special["<f9>"] = []string{"43", "c3"}
special["<f10>"] = []string{"44", "c4"}
special["<return>"] = []string{"1c", "9c"}
special["<tab>"] = []string{"0f", "8f"}
special["<up>"] = []string{"48", "c8"}
special["<down>"] = []string{"50", "d0"}
special["<left>"] = []string{"4b", "cb"}
special["<right>"] = []string{"4d", "cd"}
special["<spacebar>"] = []string{"39", "b9"}
special["<insert>"] = []string{"52", "d2"}
special["<home>"] = []string{"47", "c7"}
special["<end>"] = []string{"4f", "cf"}
special["<pageUp>"] = []string{"49", "c9"}
special["<pageDown>"] = []string{"51", "d1"}
special["<leftAlt>"] = []string{"38", "b8"}
special["<leftCtrl>"] = []string{"1d", "9d"}
special["<leftShift>"] = []string{"2a", "aa"}
special["<rightAlt>"] = []string{"e038", "e0b8"}
special["<rightCtrl>"] = []string{"e01d", "e09d"}
special["<rightShift>"] = []string{"36", "b6"}
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
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, "<leftAltOn>") {
scancode = []string{"38"}
message = message[len("<leftAltOn>"):]
log.Printf("Special code '<leftAltOn>' found, replacing with: 38")
}
if strings.HasPrefix(message, "<leftCtrlOn>") {
scancode = []string{"1d"}
message = message[len("<leftCtrlOn>"):]
log.Printf("Special code '<leftCtrlOn>' found, replacing with: 1d")
}
if strings.HasPrefix(message, "<leftShiftOn>") {
scancode = []string{"2a"}
message = message[len("<leftShiftOn>"):]
log.Printf("Special code '<leftShiftOn>' found, replacing with: 2a")
}
if strings.HasPrefix(message, "<leftAltOff>") {
scancode = []string{"b8"}
message = message[len("<leftAltOff>"):]
log.Printf("Special code '<leftAltOff>' found, replacing with: b8")
}
if strings.HasPrefix(message, "<leftCtrlOff>") {
scancode = []string{"9d"}
message = message[len("<leftCtrlOff>"):]
log.Printf("Special code '<leftCtrlOff>' found, replacing with: 9d")
}
if strings.HasPrefix(message, "<leftShiftOff>") {
scancode = []string{"aa"}
message = message[len("<leftShiftOff>"):]
log.Printf("Special code '<leftShiftOff>' found, replacing with: aa")
}
if strings.HasPrefix(message, "<rightAltOn>") {
scancode = []string{"e038"}
message = message[len("<rightAltOn>"):]
log.Printf("Special code '<rightAltOn>' found, replacing with: e038")
}
if strings.HasPrefix(message, "<rightCtrlOn>") {
scancode = []string{"e01d"}
message = message[len("<rightCtrlOn>"):]
log.Printf("Special code '<rightCtrlOn>' found, replacing with: e01d")
}
if strings.HasPrefix(message, "<rightShiftOn>") {
scancode = []string{"36"}
message = message[len("<rightShiftOn>"):]
log.Printf("Special code '<rightShiftOn>' found, replacing with: 36")
}
if strings.HasPrefix(message, "<rightAltOff>") {
scancode = []string{"e0b8"}
message = message[len("<rightAltOff>"):]
log.Printf("Special code '<rightAltOff>' found, replacing with: e0b8")
}
if strings.HasPrefix(message, "<rightCtrlOff>") {
scancode = []string{"e09d"}
message = message[len("<rightCtrlOff>"):]
log.Printf("Special code '<rightCtrlOff>' found, replacing with: e09d")
}
if strings.HasPrefix(message, "<rightShiftOff>") {
scancode = []string{"b6"}
message = message[len("<rightShiftOff>"):]
log.Printf("Special code '<rightShiftOff>' found, replacing with: b6")
}
if strings.HasPrefix(message, "<wait>") {
log.Printf("Special code <wait> found, will sleep 1 second at this point.")
scancode = []string{"wait"}
message = message[len("<wait>"):]
}
if strings.HasPrefix(message, "<wait5>") {
log.Printf("Special code <wait5> found, will sleep 5 seconds at this point.")
scancode = []string{"wait5"}
message = message[len("<wait5>"):]
}
if strings.HasPrefix(message, "<wait10>") {
log.Printf("Special code <wait10> found, will sleep 10 seconds at this point.")
scancode = []string{"wait10"}
message = message[len("<wait10>"):]
}
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
}

View file

@ -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

View file

@ -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,
}

View file

@ -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,
},

View file

@ -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,
}

View file

@ -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,

View file

@ -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"`

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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))
}
}

View file

@ -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:
// <nothing>
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["<bs>"] = []string{"0e", "8e"}
special["<del>"] = []string{"53", "d3"}
special["<enter>"] = []string{"1c", "9c"}
special["<esc>"] = []string{"01", "81"}
special["<f1>"] = []string{"3b", "bb"}
special["<f2>"] = []string{"3c", "bc"}
special["<f3>"] = []string{"3d", "bd"}
special["<f4>"] = []string{"3e", "be"}
special["<f5>"] = []string{"3f", "bf"}
special["<f6>"] = []string{"40", "c0"}
special["<f7>"] = []string{"41", "c1"}
special["<f8>"] = []string{"42", "c2"}
special["<f9>"] = []string{"43", "c3"}
special["<f10>"] = []string{"44", "c4"}
special["<return>"] = []string{"1c", "9c"}
special["<tab>"] = []string{"0f", "8f"}
special["<up>"] = []string{"48", "c8"}
special["<down>"] = []string{"50", "d0"}
special["<left>"] = []string{"4b", "cb"}
special["<right>"] = []string{"4d", "cd"}
special["<spacebar>"] = []string{"39", "b9"}
special["<insert>"] = []string{"52", "d2"}
special["<home>"] = []string{"47", "c7"}
special["<end>"] = []string{"4f", "cf"}
special["<pageUp>"] = []string{"49", "c9"}
special["<pageDown>"] = []string{"51", "d1"}
special["<leftAlt>"] = []string{"38", "b8"}
special["<leftCtrl>"] = []string{"1d", "9d"}
special["<leftShift>"] = []string{"2a", "aa"}
special["<rightAlt>"] = []string{"e038", "e0b8"}
special["<rightCtrl>"] = []string{"e01d", "e09d"}
special["<rightShift>"] = []string{"36", "b6"}
shiftedChars := "!@#$%^&*()_+{}:\"~|<>?"
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, "<leftAltOn>") {
scancode = []string{"38"}
message = message[len("<leftAltOn>"):]
log.Printf("Special code '<leftAltOn>' found, replacing with: 38")
}
if strings.HasPrefix(message, "<leftCtrlOn>") {
scancode = []string{"1d"}
message = message[len("<leftCtrlOn>"):]
log.Printf("Special code '<leftCtrlOn>' found, replacing with: 1d")
}
if strings.HasPrefix(message, "<leftShiftOn>") {
scancode = []string{"2a"}
message = message[len("<leftShiftOn>"):]
log.Printf("Special code '<leftShiftOn>' found, replacing with: 2a")
}
if strings.HasPrefix(message, "<leftAltOff>") {
scancode = []string{"b8"}
message = message[len("<leftAltOff>"):]
log.Printf("Special code '<leftAltOff>' found, replacing with: b8")
}
if strings.HasPrefix(message, "<leftCtrlOff>") {
scancode = []string{"9d"}
message = message[len("<leftCtrlOff>"):]
log.Printf("Special code '<leftCtrlOff>' found, replacing with: 9d")
}
if strings.HasPrefix(message, "<leftShiftOff>") {
scancode = []string{"aa"}
message = message[len("<leftShiftOff>"):]
log.Printf("Special code '<leftShiftOff>' found, replacing with: aa")
}
if strings.HasPrefix(message, "<rightAltOn>") {
scancode = []string{"e038"}
message = message[len("<rightAltOn>"):]
log.Printf("Special code '<rightAltOn>' found, replacing with: e038")
}
if strings.HasPrefix(message, "<rightCtrlOn>") {
scancode = []string{"e01d"}
message = message[len("<rightCtrlOn>"):]
log.Printf("Special code '<rightCtrlOn>' found, replacing with: e01d")
}
if strings.HasPrefix(message, "<rightShiftOn>") {
scancode = []string{"36"}
message = message[len("<rightShiftOn>"):]
log.Printf("Special code '<rightShiftOn>' found, replacing with: 36")
}
if strings.HasPrefix(message, "<rightAltOff>") {
scancode = []string{"e0b8"}
message = message[len("<rightAltOff>"):]
log.Printf("Special code '<rightAltOff>' found, replacing with: e0b8")
}
if strings.HasPrefix(message, "<rightCtrlOff>") {
scancode = []string{"e09d"}
message = message[len("<rightCtrlOff>"):]
log.Printf("Special code '<rightCtrlOff>' found, replacing with: e09d")
}
if strings.HasPrefix(message, "<rightShiftOff>") {
scancode = []string{"b6"}
message = message[len("<rightShiftOff>"):]
log.Printf("Special code '<rightShiftOff>' found, replacing with: b6")
}
if strings.HasPrefix(message, "<wait>") {
log.Printf("Special code <wait> found, will sleep 1 second at this point.")
scancode = []string{"wait"}
message = message[len("<wait>"):]
}
if strings.HasPrefix(message, "<wait5>") {
log.Printf("Special code <wait5> found, will sleep 5 seconds at this point.")
scancode = []string{"wait5"}
message = message[len("<wait5>"):]
}
if strings.HasPrefix(message, "<wait10>") {
log.Printf("Special code <wait10> found, will sleep 10 seconds at this point.")
scancode = []string{"wait10"}
message = message[len("<wait10>"):]
}
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
}

View file

@ -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-=<enter><wait>",
"!@#$%^&*()_+<enter>",
"qwertyuiop[]<enter>",
"QWERTYUIOP{}<enter>",
"asdfghjkl;'`<enter>",
`ASDFGHJKL:"~<enter>`,
"\\zxcvbnm,./<enter>",
"|ZXCVBNM<>?<enter>",
" <enter>",
}
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)
}
}

View file

@ -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,
},
&parallelscommon.StepRun{
BootWait: b.config.BootWait,
},
&parallelscommon.StepRun{},
&parallelscommon.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,

View file

@ -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,
},
&parallelscommon.StepRun{
BootWait: b.config.BootWait,
},
&parallelscommon.StepRun{},
&parallelscommon.StepTypeBootCommand{
BootCommand: b.config.BootCommand,
BootCommand: b.config.FlatBootCommand(),
BootWait: b.config.BootWait,
HostInterfaces: []string{},
VMName: b.config.VMName,
Ctx: b.config.ctx,

View file

@ -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)...)

View file

@ -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{},
)

View file

@ -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()

View file

@ -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) {}

View file

@ -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 {
// <nothing>
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["<bs>"] = 0xFF08
special["<del>"] = 0xFFFF
special["<enter>"] = 0xFF0D
special["<esc>"] = 0xFF1B
special["<f1>"] = 0xFFBE
special["<f2>"] = 0xFFBF
special["<f3>"] = 0xFFC0
special["<f4>"] = 0xFFC1
special["<f5>"] = 0xFFC2
special["<f6>"] = 0xFFC3
special["<f7>"] = 0xFFC4
special["<f8>"] = 0xFFC5
special["<f9>"] = 0xFFC6
special["<f10>"] = 0xFFC7
special["<f11>"] = 0xFFC8
special["<f12>"] = 0xFFC9
special["<return>"] = 0xFF0D
special["<tab>"] = 0xFF09
special["<up>"] = 0xFF52
special["<down>"] = 0xFF54
special["<left>"] = 0xFF51
special["<right>"] = 0xFF53
special["<spacebar>"] = 0x020
special["<insert>"] = 0xFF63
special["<home>"] = 0xFF50
special["<end>"] = 0xFF57
special["<pageUp>"] = 0xFF55
special["<pageDown>"] = 0xFF56
special["<leftAlt>"] = 0xFFE9
special["<leftCtrl>"] = 0xFFE3
special["<leftShift>"] = 0xFFE1
special["<rightAlt>"] = 0xFFEA
special["<rightCtrl>"] = 0xFFE4
special["<rightShift>"] = 0xFFE2
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
waitRe := regexp.MustCompile(`^<wait([0-9hms]+)>`)
// 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, "<leftAltOn>") {
keyCode = special["<leftAlt>"]
original = original[len("<leftAltOn>"):]
log.Printf("Special code '<leftAltOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<leftCtrlOn>") {
keyCode = special["<leftCtrl>"]
original = original[len("<leftCtrlOn>"):]
log.Printf("Special code '<leftCtrlOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<leftShiftOn>") {
keyCode = special["<leftShift>"]
original = original[len("<leftShiftOn>"):]
log.Printf("Special code '<leftShiftOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<leftAltOff>") {
keyCode = special["<leftAlt>"]
original = original[len("<leftAltOff>"):]
log.Printf("Special code '<leftAltOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<leftCtrlOff>") {
keyCode = special["<leftCtrl>"]
original = original[len("<leftCtrlOff>"):]
log.Printf("Special code '<leftCtrlOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<leftShiftOff>") {
keyCode = special["<leftShift>"]
original = original[len("<leftShiftOff>"):]
log.Printf("Special code '<leftShiftOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<rightAltOn>") {
keyCode = special["<rightAlt>"]
original = original[len("<rightAltOn>"):]
log.Printf("Special code '<rightAltOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<rightCtrlOn>") {
keyCode = special["<rightCtrl>"]
original = original[len("<rightCtrlOn>"):]
log.Printf("Special code '<rightCtrlOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<rightShiftOn>") {
keyCode = special["<rightShift>"]
original = original[len("<rightShiftOn>"):]
log.Printf("Special code '<rightShiftOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<rightAltOff>") {
keyCode = special["<rightAlt>"]
original = original[len("<rightAltOff>"):]
log.Printf("Special code '<rightAltOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<rightCtrlOff>") {
keyCode = special["<rightCtrl>"]
original = original[len("<rightCtrlOff>"):]
log.Printf("Special code '<rightCtrlOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<rightShiftOff>") {
keyCode = special["<rightShift>"]
original = original[len("<rightShiftOff>"):]
log.Printf("Special code '<rightShiftOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<wait>") {
log.Printf("Special code '<wait>' found, sleeping one second")
time.Sleep(1 * time.Second)
original = original[len("<wait>"):]
continue
}
if strings.HasPrefix(original, "<wait5>") {
log.Printf("Special code '<wait5>' found, sleeping 5 seconds")
time.Sleep(5 * time.Second)
original = original[len("<wait5>"):]
continue
}
if strings.HasPrefix(original, "<wait10>") {
log.Printf("Special code '<wait10>' found, sleeping 10 seconds")
time.Sleep(10 * time.Second)
original = original[len("<wait10>"):]
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)
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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

View file

@ -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
}

View file

@ -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:
// <nothing>
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["<bs>"] = []string{"0e", "8e"}
special["<del>"] = []string{"e053", "e0d3"}
special["<enter>"] = []string{"1c", "9c"}
special["<esc>"] = []string{"01", "81"}
special["<f1>"] = []string{"3b", "bb"}
special["<f2>"] = []string{"3c", "bc"}
special["<f3>"] = []string{"3d", "bd"}
special["<f4>"] = []string{"3e", "be"}
special["<f5>"] = []string{"3f", "bf"}
special["<f6>"] = []string{"40", "c0"}
special["<f7>"] = []string{"41", "c1"}
special["<f8>"] = []string{"42", "c2"}
special["<f9>"] = []string{"43", "c3"}
special["<f10>"] = []string{"44", "c4"}
special["<f11>"] = []string{"57", "d7"}
special["<f12>"] = []string{"58", "d8"}
special["<return>"] = []string{"1c", "9c"}
special["<tab>"] = []string{"0f", "8f"}
special["<up>"] = []string{"e048", "e0c8"}
special["<down>"] = []string{"e050", "e0d0"}
special["<left>"] = []string{"e04b", "e0cb"}
special["<right>"] = []string{"e04d", "e0cd"}
special["<spacebar>"] = []string{"39", "b9"}
special["<insert>"] = []string{"e052", "e0d2"}
special["<home>"] = []string{"e047", "e0c7"}
special["<end>"] = []string{"e04f", "e0cf"}
special["<pageUp>"] = []string{"e049", "e0c9"}
special["<pageDown>"] = []string{"e051", "e0d1"}
special["<leftAlt>"] = []string{"38", "b8"}
special["<leftCtrl>"] = []string{"1d", "9d"}
special["<leftShift>"] = []string{"2a", "aa"}
special["<rightAlt>"] = []string{"e038", "e0b8"}
special["<rightCtrl>"] = []string{"e01d", "e09d"}
special["<rightShift>"] = []string{"36", "b6"}
special["<leftSuper>"] = []string{"e05b", "e0db"}
special["<rightSuper>"] = []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<ordinary>[a-zA-Z])On>")
azOffRegex := regexp.MustCompile("^<(?P<ordinary>[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("<aOn>"):]
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("<aOff>"):]
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, "<f12On>") {
scancode = append(scancode, "58")
message = message[len("<f12On>"):]
log.Printf("Special code '<f12On>', replacing with: 58")
}
if strings.HasPrefix(message, "<leftAltOn>") {
scancode = append(scancode, "38")
message = message[len("<leftAltOn>"):]
log.Printf("Special code '<leftAltOn>' found, replacing with: 38")
}
if strings.HasPrefix(message, "<leftCtrlOn>") {
scancode = append(scancode, "1d")
message = message[len("<leftCtrlOn>"):]
log.Printf("Special code '<leftCtrlOn>' found, replacing with: 1d")
}
if strings.HasPrefix(message, "<leftShiftOn>") {
scancode = append(scancode, "2a")
message = message[len("<leftShiftOn>"):]
log.Printf("Special code '<leftShiftOn>' found, replacing with: 2a")
}
if strings.HasPrefix(message, "<leftSuperOn>") {
scancode = append(scancode, "e05b")
message = message[len("<leftSuperOn>"):]
log.Printf("Special code '<leftSuperOn>' found, replacing with: e05b")
}
if strings.HasPrefix(message, "<f12Off>") {
scancode = append(scancode, "d8")
message = message[len("<f12Off>"):]
log.Printf("Special code '<f12Off>' found, replacing with: d8")
}
if strings.HasPrefix(message, "<leftAltOff>") {
scancode = append(scancode, "b8")
message = message[len("<leftAltOff>"):]
log.Printf("Special code '<leftAltOff>' found, replacing with: b8")
}
if strings.HasPrefix(message, "<leftCtrlOff>") {
scancode = append(scancode, "9d")
message = message[len("<leftCtrlOff>"):]
log.Printf("Special code '<leftCtrlOff>' found, replacing with: 9d")
}
if strings.HasPrefix(message, "<leftShiftOff>") {
scancode = append(scancode, "aa")
message = message[len("<leftShiftOff>"):]
log.Printf("Special code '<leftShiftOff>' found, replacing with: aa")
}
if strings.HasPrefix(message, "<leftSuperOff>") {
scancode = append(scancode, "e0db")
message = message[len("<leftSuperOff>"):]
log.Printf("Special code '<leftSuperOff>' found, replacing with: e0db")
}
if strings.HasPrefix(message, "<rightAltOn>") {
scancode = append(scancode, "e038")
message = message[len("<rightAltOn>"):]
log.Printf("Special code '<rightAltOn>' found, replacing with: e038")
}
if strings.HasPrefix(message, "<rightCtrlOn>") {
scancode = append(scancode, "e01d")
message = message[len("<rightCtrlOn>"):]
log.Printf("Special code '<rightCtrlOn>' found, replacing with: e01d")
}
if strings.HasPrefix(message, "<rightShiftOn>") {
scancode = append(scancode, "36")
message = message[len("<rightShiftOn>"):]
log.Printf("Special code '<rightShiftOn>' found, replacing with: 36")
}
if strings.HasPrefix(message, "<rightSuperOn>") {
scancode = append(scancode, "e05c")
message = message[len("<rightSuperOn>"):]
log.Printf("Special code '<rightSuperOn>' found, replacing with: e05c")
}
if strings.HasPrefix(message, "<rightAltOff>") {
scancode = append(scancode, "e0b8")
message = message[len("<rightAltOff>"):]
log.Printf("Special code '<rightAltOff>' found, replacing with: e0b8")
}
if strings.HasPrefix(message, "<rightCtrlOff>") {
scancode = append(scancode, "e09d")
message = message[len("<rightCtrlOff>"):]
log.Printf("Special code '<rightCtrlOff>' found, replacing with: e09d")
}
if strings.HasPrefix(message, "<rightShiftOff>") {
scancode = append(scancode, "b6")
message = message[len("<rightShiftOff>"):]
log.Printf("Special code '<rightShiftOff>' found, replacing with: b6")
}
if strings.HasPrefix(message, "<rightSuperOff>") {
scancode = append(scancode, "e0dc")
message = message[len("<rightSuperOff>"):]
log.Printf("Special code '<rightSuperOff>' found, replacing with: e0dc")
}
if strings.HasPrefix(message, "<wait>") {
log.Printf("Special code <wait> found, will sleep 1 second at this point.")
scancode = append(scancode, "wait")
message = message[len("<wait>"):]
}
if strings.HasPrefix(message, "<wait5>") {
log.Printf("Special code <wait5> found, will sleep 5 seconds at this point.")
scancode = append(scancode, "wait5")
message = message[len("<wait5>"):]
}
if strings.HasPrefix(message, "<wait10>") {
log.Printf("Special code <wait10> found, will sleep 10 seconds at this point.")
scancode = append(scancode, "wait10")
message = message[len("<wait10>"):]
}
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
}

View file

@ -1,43 +0,0 @@
package common
import (
"testing"
)
func TestScancodes(t *testing.T) {
var bootcommand = []string{
"1234567890-=<enter><wait>",
"!@#$%^&*()_+<enter>",
"qwertyuiop[]<enter>",
"QWERTYUIOP{}<enter>",
"asdfghjkl;'`<enter>",
`ASDFGHJKL:"~<enter>`,
"\\zxcvbnm,./<enter>",
"|ZXCVBNM<>?<enter>",
"<enter>",
"<leftAltOn><esc><leftAltOff><wait5>",
"<tab><tab><tab><tab><tab><spacebar><wait5>",
}
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])
}
}
}
}

View file

@ -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,
},

View file

@ -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,
},

View file

@ -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)

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -19,7 +19,6 @@ import (
// Produces:
// <nothing>
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
}

View file

@ -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:
// <nothing>
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["<bs>"] = 0xFF08
special["<del>"] = 0xFFFF
special["<enter>"] = 0xFF0D
special["<esc>"] = 0xFF1B
special["<f1>"] = 0xFFBE
special["<f2>"] = 0xFFBF
special["<f3>"] = 0xFFC0
special["<f4>"] = 0xFFC1
special["<f5>"] = 0xFFC2
special["<f6>"] = 0xFFC3
special["<f7>"] = 0xFFC4
special["<f8>"] = 0xFFC5
special["<f9>"] = 0xFFC6
special["<f10>"] = 0xFFC7
special["<f11>"] = 0xFFC8
special["<f12>"] = 0xFFC9
special["<return>"] = 0xFF0D
special["<tab>"] = 0xFF09
special["<up>"] = 0xFF52
special["<down>"] = 0xFF54
special["<left>"] = 0xFF51
special["<right>"] = 0xFF53
special["<spacebar>"] = 0x020
special["<insert>"] = 0xFF63
special["<home>"] = 0xFF50
special["<end>"] = 0xFF57
special["<pageUp>"] = 0xFF55
special["<pageDown>"] = 0xFF56
special["<leftAlt>"] = 0xFFE9
special["<leftCtrl>"] = 0xFFE3
special["<leftShift>"] = 0xFFE1
special["<rightAlt>"] = 0xFFEA
special["<rightCtrl>"] = 0xFFE4
special["<rightShift>"] = 0xFFE2
special["<leftSuper>"] = 0xFFEB
special["<rightSuper>"] = 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<ordinary>[a-zA-Z])On>")
azOffRegex := regexp.MustCompile("^<(?P<ordinary>[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, "<leftAltOn>") {
keyCode = special["<leftAlt>"]
original = original[len("<leftAltOn>"):]
log.Printf("Special code '<leftAltOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<leftCtrlOn>") {
keyCode = special["<leftCtrl>"]
original = original[len("<leftCtrlOn>"):]
log.Printf("Special code '<leftCtrlOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<leftShiftOn>") {
keyCode = special["<leftShift>"]
original = original[len("<leftShiftOn>"):]
log.Printf("Special code '<leftShiftOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<leftSuperOn>") {
keyCode = special["<leftSuper>"]
original = original[len("<leftSuperOn>"):]
log.Printf("Special code '<leftSuperOn>' 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("<aOn>"):]
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, "<leftAltOff>") {
keyCode = special["<leftAlt>"]
original = original[len("<leftAltOff>"):]
log.Printf("Special code '<leftAltOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<leftCtrlOff>") {
keyCode = special["<leftCtrl>"]
original = original[len("<leftCtrlOff>"):]
log.Printf("Special code '<leftCtrlOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<leftShiftOff>") {
keyCode = special["<leftShift>"]
original = original[len("<leftShiftOff>"):]
log.Printf("Special code '<leftShiftOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<leftSuperOff>") {
keyCode = special["<leftSuper>"]
original = original[len("<leftSuperOff>"):]
log.Printf("Special code '<leftSuperOff>' 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("<aOff>"):]
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, "<rightAltOn>") {
keyCode = special["<rightAlt>"]
original = original[len("<rightAltOn>"):]
log.Printf("Special code '<rightAltOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<rightCtrlOn>") {
keyCode = special["<rightCtrl>"]
original = original[len("<rightCtrlOn>"):]
log.Printf("Special code '<rightCtrlOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<rightShiftOn>") {
keyCode = special["<rightShift>"]
original = original[len("<rightShiftOn>"):]
log.Printf("Special code '<rightShiftOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<rightSuperOn>") {
keyCode = special["<rightSuper>"]
original = original[len("<rightSuperOn>"):]
log.Printf("Special code '<rightSuperOn>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, true)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<rightAltOff>") {
keyCode = special["<rightAlt>"]
original = original[len("<rightAltOff>"):]
log.Printf("Special code '<rightAltOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<rightCtrlOff>") {
keyCode = special["<rightCtrl>"]
original = original[len("<rightCtrlOff>"):]
log.Printf("Special code '<rightCtrlOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<rightShiftOff>") {
keyCode = special["<rightShift>"]
original = original[len("<rightShiftOff>"):]
log.Printf("Special code '<rightShiftOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<rightSuperOff>") {
keyCode = special["<rightSuper>"]
original = original[len("<rightSuperOff>"):]
log.Printf("Special code '<rightSuperOff>' found, replacing with: %d", keyCode)
c.KeyEvent(keyCode, false)
time.Sleep(keyInterval)
continue
}
if strings.HasPrefix(original, "<wait>") {
log.Printf("Special code '<wait>' found, sleeping one second")
time.Sleep(1 * time.Second)
original = original[len("<wait>"):]
continue
}
if strings.HasPrefix(original, "<wait5>") {
log.Printf("Special code '<wait5>' found, sleeping 5 seconds")
time.Sleep(5 * time.Second)
original = original[len("<wait5>"):]
continue
}
if strings.HasPrefix(original, "<wait10>") {
log.Printf("Special code '<wait10>' found, sleeping 10 seconds")
time.Sleep(10 * time.Second)
original = original[len("<wait10>"):]
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)
}
}
}

View file

@ -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,
},

View file

@ -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{

View file

@ -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,
},

View file

@ -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"))

View file

@ -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
}

View file

@ -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"
`

File diff suppressed because it is too large Load diff

View file

@ -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 <- !.

View file

@ -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))
}

View file

@ -0,0 +1,134 @@
package bootcommand
import (
"fmt"
"log"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_parse(t *testing.T) {
in := "<wait><wait20><wait3s><wait4m2ns>"
in += "foo/bar > one 界"
in += "<fon> b<fOff>"
in += "<foo><f3><f12><spacebar><leftalt><rightshift><rightsuper>"
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
}{
{
"<rightShift><rightshift><RIGHTSHIFT>",
"Spec-Press(rightshift)",
},
{
"<delon><delON><deLoN><DELON>",
"Spec-On(del)",
},
{
"<enteroff><enterOFF><eNtErOfF><ENTEROFF>",
"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
}{
{
"<wait1m>",
true,
},
{
"<wait-1m>",
false,
},
{
"<f1>",
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)
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -0,0 +1,3 @@
package bootcommand
//go:generate pigeon -o boot_command.go boot_command.pigeon

View file

@ -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
}

View file

@ -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 := "<rightShift><rightshiftoff><RIGHTSHIFTON>"
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 := "<left>"
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 := "abc123<wait>098"
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)
}

View file

@ -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
}

View file

@ -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 := "<rightShift><rightshiftoff><RIGHTSHIFTON>"
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)
}

View file

@ -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
}

View file

@ -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", "")
}

View file

@ -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

View file

@ -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

View file

@ -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"
}]
}

View file

@ -1,8 +1,9 @@
package fix
import (
"github.com/mitchellh/mapstructure"
"strings"
"github.com/mitchellh/mapstructure"
)
// FixerPowerShellEscapes removes the PowerShell escape character from user

View file

@ -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))
}

View file

@ -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 (

View file

@ -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) {

View file

@ -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 {

View file

@ -1,8 +1,9 @@
package vsphere
import (
"github.com/hashicorp/packer/packer"
"testing"
"github.com/hashicorp/packer/packer"
)
func TestArtifact_ImplementsArtifact(t *testing.T) {

View file

@ -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)

View file

@ -1,6 +1,6 @@
ISC License
Copyright (c) 2012-2013 Dave Collins <dave@davec.name>
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above

View file

@ -1,4 +1,4 @@
// Copyright (c) 2015 Dave Collins <dave@davec.name>
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above

View file

@ -1,38 +0,0 @@
// Copyright (c) 2015 Dave Collins <dave@davec.name>
//
// 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
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* 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.

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* 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)
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 Dave Collins <dave@davec.name>
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above

44
vendor/github.com/denverdino/aliyungo/common/client.go generated vendored Executable file → Normal file
View file

@ -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)

View file

@ -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 {

View file

@ -1346,4 +1346,14 @@
<Product><ProductName>Slb</ProductName><DomainName>slb.cn-zhangjiakou.aliyuncs.com</DomainName></Product>
</Products>
</Endpoint>
<Endpoint name="cn-huhehaote">
<RegionIds><RegionId>cn-huhehaote</RegionId></RegionIds>
<Products>
<Product><ProductName>Rds</ProductName><DomainName>rds.cn-huhehaote.aliyuncs.com</DomainName></Product>
<Product><ProductName>Ecs</ProductName><DomainName>ecs.cn-huhehaote.aliyuncs.com</DomainName></Product>
<Product><ProductName>Vpc</ProductName><DomainName>vpc.cn-huhehaote.aliyuncs.com</DomainName></Product>
<Product><ProductName>Cms</ProductName><DomainName>metrics.cn-hangzhou.aliyuncs.com</DomainName></Product>
<Product><ProductName>Slb</ProductName><DomainName>slb.cn-huhehaote.aliyuncs.com</DomainName></Product>
</Products>
</Endpoint>
</Endpoints>

View file

@ -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,
}

View file

@ -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")
)

Some files were not shown because too many files have changed in this diff Show more