mirror of
https://github.com/hashicorp/packer.git
synced 2026-06-11 01:30:06 -04:00
Merge branch 'master' into allow_gcp_winrm_password
This commit is contained in:
commit
e2e7953fe4
160 changed files with 9084 additions and 3526 deletions
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
3
Makefile
3
Makefile
|
|
@ -42,6 +42,7 @@ package:
|
|||
|
||||
deps:
|
||||
@go get golang.org/x/tools/cmd/stringer
|
||||
@go get -u github.com/mna/pigeon
|
||||
@go get github.com/kardianos/govendor
|
||||
@govendor sync
|
||||
|
||||
|
|
@ -73,6 +74,8 @@ fmt-examples:
|
|||
# source files.
|
||||
generate: deps ## Generate dynamically generated code
|
||||
go generate .
|
||||
gofmt -w common/bootcommand/boot_command.go
|
||||
goimports -w common/bootcommand/boot_command.go
|
||||
gofmt -w command/plugin.go
|
||||
|
||||
test: deps fmt-check ## Run unit tests
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 == "" {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
parallelscommon "github.com/hashicorp/packer/builder/parallels/common"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/common/bootcommand"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
|
|
@ -26,16 +27,15 @@ type Config struct {
|
|||
common.HTTPConfig `mapstructure:",squash"`
|
||||
common.ISOConfig `mapstructure:",squash"`
|
||||
common.FloppyConfig `mapstructure:",squash"`
|
||||
bootcommand.BootConfig `mapstructure:",squash"`
|
||||
parallelscommon.OutputConfig `mapstructure:",squash"`
|
||||
parallelscommon.PrlctlConfig `mapstructure:",squash"`
|
||||
parallelscommon.PrlctlPostConfig `mapstructure:",squash"`
|
||||
parallelscommon.PrlctlVersionConfig `mapstructure:",squash"`
|
||||
parallelscommon.RunConfig `mapstructure:",squash"`
|
||||
parallelscommon.ShutdownConfig `mapstructure:",squash"`
|
||||
parallelscommon.SSHConfig `mapstructure:",squash"`
|
||||
parallelscommon.ToolsConfig `mapstructure:",squash"`
|
||||
|
||||
BootCommand []string `mapstructure:"boot_command"`
|
||||
DiskSize uint `mapstructure:"disk_size"`
|
||||
DiskType string `mapstructure:"disk_type"`
|
||||
GuestOSType string `mapstructure:"guest_os_type"`
|
||||
|
|
@ -76,13 +76,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, b.config.OutputConfig.Prepare(&b.config.ctx, &b.config.PackerConfig)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.PrlctlConfig.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.PrlctlPostConfig.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.PrlctlVersionConfig.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.ToolsConfig.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.BootConfig.Prepare(&b.config.ctx)...)
|
||||
|
||||
if b.config.DiskSize == 0 {
|
||||
b.config.DiskSize = 40000
|
||||
|
|
@ -185,11 +185,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
Commands: b.config.Prlctl,
|
||||
Ctx: b.config.ctx,
|
||||
},
|
||||
¶llelscommon.StepRun{
|
||||
BootWait: b.config.BootWait,
|
||||
},
|
||||
¶llelscommon.StepRun{},
|
||||
¶llelscommon.StepTypeBootCommand{
|
||||
BootCommand: b.config.BootCommand,
|
||||
BootWait: b.config.BootWait,
|
||||
BootCommand: b.config.FlatBootCommand(),
|
||||
HostInterfaces: b.config.HostInterfaces,
|
||||
VMName: b.config.VMName,
|
||||
Ctx: b.config.ctx,
|
||||
|
|
|
|||
|
|
@ -74,11 +74,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
Commands: b.config.Prlctl,
|
||||
Ctx: b.config.ctx,
|
||||
},
|
||||
¶llelscommon.StepRun{
|
||||
BootWait: b.config.BootWait,
|
||||
},
|
||||
¶llelscommon.StepRun{},
|
||||
¶llelscommon.StepTypeBootCommand{
|
||||
BootCommand: b.config.BootCommand,
|
||||
BootCommand: b.config.FlatBootCommand(),
|
||||
BootWait: b.config.BootWait,
|
||||
HostInterfaces: []string{},
|
||||
VMName: b.config.VMName,
|
||||
Ctx: b.config.ctx,
|
||||
|
|
|
|||
|
|
@ -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)...)
|
||||
|
|
|
|||
|
|
@ -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{},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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) {}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
`
|
||||
|
|
|
|||
2072
common/bootcommand/boot_command.go
Normal file
2072
common/bootcommand/boot_command.go
Normal file
File diff suppressed because it is too large
Load diff
79
common/bootcommand/boot_command.pigeon
Normal file
79
common/bootcommand/boot_command.pigeon
Normal 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 <- !.
|
||||
157
common/bootcommand/boot_command_ast.go
Normal file
157
common/bootcommand/boot_command_ast.go
Normal 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))
|
||||
}
|
||||
134
common/bootcommand/boot_command_ast_test.go
Normal file
134
common/bootcommand/boot_command_ast_test.go
Normal 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)
|
||||
}
|
||||
60
common/bootcommand/config.go
Normal file
60
common/bootcommand/config.go
Normal 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
|
||||
}
|
||||
65
common/bootcommand/config_test.go
Normal file
65
common/bootcommand/config_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
11
common/bootcommand/driver.go
Normal file
11
common/bootcommand/driver.go
Normal 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
|
||||
}
|
||||
3
common/bootcommand/gen.go
Normal file
3
common/bootcommand/gen.go
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
package bootcommand
|
||||
|
||||
//go:generate pigeon -o boot_command.go boot_command.pigeon
|
||||
211
common/bootcommand/pc_xt_driver.go
Normal file
211
common/bootcommand/pc_xt_driver.go
Normal 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
|
||||
}
|
||||
123
common/bootcommand/pc_xt_driver_test.go
Normal file
123
common/bootcommand/pc_xt_driver_test.go
Normal 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)
|
||||
}
|
||||
147
common/bootcommand/vnc_driver.go
Normal file
147
common/bootcommand/vnc_driver.go
Normal 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
|
||||
}
|
||||
39
common/bootcommand/vnc_driver_test.go
Normal file
39
common/bootcommand/vnc_driver_test.go
Normal 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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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", "")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
47
examples/azure/freebsd.json
Normal file
47
examples/azure/freebsd.json
Normal 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"
|
||||
}]
|
||||
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
package fix
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// FixerPowerShellEscapes removes the PowerShell escape character from user
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
1
main.go
1
main.go
|
|
@ -1,6 +1,7 @@
|
|||
// This is the main package for the `packer` application.
|
||||
|
||||
//go:generate go run ./scripts/generate-plugins.go
|
||||
//go:generate go generate ./common/bootcommand/...
|
||||
package main
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
package vsphere
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func TestArtifact_ImplementsArtifact(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
2
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
2
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
2
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
|
|
@ -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
|
||||
|
|
|
|||
38
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
38
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
|
|
@ -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
|
||||
}
|
||||
2
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
2
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
|
|
@ -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
|
||||
|
|
|
|||
11
vendor/github.com/davecgh/go-spew/spew/config.go
generated
vendored
11
vendor/github.com/davecgh/go-spew/spew/config.go
generated
vendored
|
|
@ -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
|
||||
|
|
|
|||
11
vendor/github.com/davecgh/go-spew/spew/doc.go
generated
vendored
11
vendor/github.com/davecgh/go-spew/spew/doc.go
generated
vendored
|
|
@ -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.
|
||||
|
|
|
|||
8
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
8
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
2
vendor/github.com/davecgh/go-spew/spew/format.go
generated
vendored
2
vendor/github.com/davecgh/go-spew/spew/format.go
generated
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
vendor/github.com/davecgh/go-spew/spew/spew.go
generated
vendored
2
vendor/github.com/davecgh/go-spew/spew/spew.go
generated
vendored
|
|
@ -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
44
vendor/github.com/denverdino/aliyungo/common/client.go
generated
vendored
Executable file → Normal 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)
|
||||
|
||||
|
|
|
|||
97
vendor/github.com/denverdino/aliyungo/common/endpoint.go
generated
vendored
97
vendor/github.com/denverdino/aliyungo/common/endpoint.go
generated
vendored
|
|
@ -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 {
|
||||
|
|
|
|||
10
vendor/github.com/denverdino/aliyungo/common/endpoints.xml
generated
vendored
10
vendor/github.com/denverdino/aliyungo/common/endpoints.xml
generated
vendored
|
|
@ -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>
|
||||
|
|
|
|||
13
vendor/github.com/denverdino/aliyungo/common/regions.go
generated
vendored
13
vendor/github.com/denverdino/aliyungo/common/regions.go
generated
vendored
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
18
vendor/github.com/denverdino/aliyungo/common/types.go
generated
vendored
18
vendor/github.com/denverdino/aliyungo/common/types.go
generated
vendored
|
|
@ -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
Loading…
Reference in a new issue