Add usb_scan_codes option to use USB scancodes instead of VNC for remote builds (#9895)
This commit is contained in:
parent
2837d56885
commit
31f08909b4
|
@ -0,0 +1,42 @@
|
|||
//go:generate struct-markdown
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/common/bootcommand"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type BootConfigWrapper struct {
|
||||
bootcommand.VNCConfig `mapstructure:",squash"`
|
||||
// If set to true, Packer will use USB HID Keyboard scan codes to send the boot command to the VM and
|
||||
// the [disable_vnc](#disable_vnc) option will be ignored and automatically set to true.
|
||||
// This option is not supported by hosts with free license.
|
||||
//
|
||||
// ~> **Note:** The ESXi 6.5+ removes support to VNC. In this case, the `usb_keyboard` should be set to true
|
||||
// in order to send boot command keystrokes to the VM.
|
||||
USBKeyBoard bool `mapstructure:"usb_keyboard"`
|
||||
}
|
||||
|
||||
func (c *BootConfigWrapper) Prepare(ctx *interpolate.Context, driverConfig *DriverConfig) (warnings []string, errs []error) {
|
||||
if c.USBKeyBoard {
|
||||
if driverConfig.RemoteType == "" {
|
||||
warnings = append(warnings, "[WARN] `usb_keyboard` can only be used with remote VMWare builds. "+
|
||||
"The `usb_keyboard` option will be ignored and automatically set to false.")
|
||||
c.USBKeyBoard = false
|
||||
} else if !c.DisableVNC {
|
||||
warnings = append(warnings, "[WARN] `usb_keyboard` is set to true then the remote VMWare builds "+
|
||||
"will not use VNC to connect to the host. The `disable_vnc` option will be ignored and automatically set to true.")
|
||||
c.DisableVNC = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/hashicorp/packer/common/bootcommand"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
func TestVNCConfigWrapper_Prepare(t *testing.T) {
|
||||
tc := []struct {
|
||||
name string
|
||||
config *BootConfigWrapper
|
||||
expectedConfig *BootConfigWrapper
|
||||
driver *DriverConfig
|
||||
errs []error
|
||||
warnings []string
|
||||
}{
|
||||
{
|
||||
name: "VNC and boot command for local build",
|
||||
config: &BootConfigWrapper{
|
||||
VNCConfig: bootcommand.VNCConfig{
|
||||
BootConfig: bootcommand.BootConfig{
|
||||
BootCommand: []string{"<boot><command>"},
|
||||
},
|
||||
DisableVNC: true,
|
||||
},
|
||||
USBKeyBoard: false,
|
||||
},
|
||||
expectedConfig: nil,
|
||||
driver: new(DriverConfig),
|
||||
errs: []error{fmt.Errorf("A boot command cannot be used when vnc is disabled.")},
|
||||
warnings: nil,
|
||||
},
|
||||
{
|
||||
name: "Disable VNC warning for remote build",
|
||||
config: &BootConfigWrapper{
|
||||
VNCConfig: bootcommand.VNCConfig{
|
||||
BootConfig: bootcommand.BootConfig{
|
||||
BootCommand: []string{"<boot><command>"},
|
||||
},
|
||||
DisableVNC: false,
|
||||
},
|
||||
USBKeyBoard: true,
|
||||
},
|
||||
expectedConfig: &BootConfigWrapper{
|
||||
VNCConfig: bootcommand.VNCConfig{
|
||||
DisableVNC: true,
|
||||
},
|
||||
USBKeyBoard: true,
|
||||
},
|
||||
driver: &DriverConfig{
|
||||
RemoteType: "esxi",
|
||||
},
|
||||
errs: nil,
|
||||
warnings: []string{"[WARN] `usb_keyboard` is set to true then the remote VMWare builds " +
|
||||
"will not use VNC to connect to the host. The `disable_vnc` option will be ignored and automatically set to true."},
|
||||
},
|
||||
{
|
||||
name: "Disable USBKeyBoard warning for local build",
|
||||
config: &BootConfigWrapper{
|
||||
VNCConfig: bootcommand.VNCConfig{
|
||||
BootConfig: bootcommand.BootConfig{
|
||||
BootCommand: []string{"<boot><command>"},
|
||||
},
|
||||
DisableVNC: false,
|
||||
},
|
||||
USBKeyBoard: true,
|
||||
},
|
||||
expectedConfig: &BootConfigWrapper{
|
||||
VNCConfig: bootcommand.VNCConfig{
|
||||
DisableVNC: false,
|
||||
},
|
||||
USBKeyBoard: false,
|
||||
},
|
||||
driver: &DriverConfig{},
|
||||
errs: nil,
|
||||
warnings: []string{"[WARN] `usb_keyboard` can only be used with remote VMWare builds. " +
|
||||
"The `usb_keyboard` option will be ignored and automatically set to false."},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range tc {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
warnings, errs := c.config.Prepare(interpolate.NewContext(), c.driver)
|
||||
if !reflect.DeepEqual(errs, c.errs) {
|
||||
t.Fatalf("bad: \n expected '%v' \nactual '%v'", c.errs, errs)
|
||||
}
|
||||
if diff := cmp.Diff(warnings, c.warnings); diff != "" {
|
||||
t.Fatalf("unexpected warnings: %s", diff)
|
||||
}
|
||||
if len(c.errs) == 0 {
|
||||
if diff := cmp.Diff(c.config, c.expectedConfig,
|
||||
cmpopts.IgnoreFields(bootcommand.VNCConfig{},
|
||||
"BootConfig",
|
||||
)); diff != "" {
|
||||
t.Fatalf("unexpected config: %s", diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -91,20 +91,11 @@ func NewDriver(dconfig *DriverConfig, config *SSHConfig, vmName string) (Driver,
|
|||
drivers := []Driver{}
|
||||
|
||||
if dconfig.RemoteType != "" {
|
||||
drivers = []Driver{
|
||||
&ESX5Driver{
|
||||
Host: dconfig.RemoteHost,
|
||||
Port: dconfig.RemotePort,
|
||||
Username: dconfig.RemoteUser,
|
||||
Password: dconfig.RemotePassword,
|
||||
PrivateKeyFile: dconfig.RemotePrivateKey,
|
||||
Datastore: dconfig.RemoteDatastore,
|
||||
CacheDatastore: dconfig.RemoteCacheDatastore,
|
||||
CacheDirectory: dconfig.RemoteCacheDirectory,
|
||||
VMName: vmName,
|
||||
CommConfig: config.Comm,
|
||||
},
|
||||
esx5Driver, err := NewESX5Driver(dconfig, config, vmName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
drivers = []Driver{esx5Driver}
|
||||
|
||||
} else {
|
||||
switch runtime.GOOS {
|
||||
|
|
|
@ -52,6 +52,8 @@ type DriverConfig struct {
|
|||
}
|
||||
|
||||
func (c *DriverConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
var errs []error
|
||||
|
||||
if c.FusionAppPath == "" {
|
||||
c.FusionAppPath = os.Getenv("FUSION_APP_PATH")
|
||||
}
|
||||
|
@ -74,7 +76,19 @@ func (c *DriverConfig) Prepare(ctx *interpolate.Context) []error {
|
|||
c.RemotePort = 22
|
||||
}
|
||||
|
||||
return nil
|
||||
if c.RemoteType != "" {
|
||||
if c.RemoteHost == "" {
|
||||
errs = append(errs,
|
||||
fmt.Errorf("remote_host must be specified"))
|
||||
}
|
||||
|
||||
if c.RemoteType != "esx5" {
|
||||
errs = append(errs,
|
||||
fmt.Errorf("Only 'esx5' value is accepted for remote_type"))
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func (c *DriverConfig) Validate(SkipExport bool) error {
|
||||
|
|
|
@ -1,32 +1,84 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
func TestDriverConfigPrepare(t *testing.T) {
|
||||
var c *DriverConfig
|
||||
|
||||
// Test a default boot_wait
|
||||
c = new(DriverConfig)
|
||||
errs := c.Prepare(interpolate.NewContext())
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("bad: %#v", errs)
|
||||
}
|
||||
if c.FusionAppPath != "/Applications/VMware Fusion.app" {
|
||||
t.Fatalf("bad value: %s", c.FusionAppPath)
|
||||
tc := []struct {
|
||||
name string
|
||||
config *DriverConfig
|
||||
expectedConfig *DriverConfig
|
||||
errs []error
|
||||
}{
|
||||
{
|
||||
name: "Set default values",
|
||||
config: new(DriverConfig),
|
||||
expectedConfig: &DriverConfig{
|
||||
FusionAppPath: "/Applications/VMware Fusion.app",
|
||||
RemoteDatastore: "datastore1",
|
||||
RemoteCacheDatastore: "datastore1",
|
||||
RemoteCacheDirectory: "packer_cache",
|
||||
RemotePort: 22,
|
||||
RemoteUser: "root",
|
||||
},
|
||||
errs: nil,
|
||||
},
|
||||
{
|
||||
name: "Override default values",
|
||||
config: &DriverConfig{
|
||||
FusionAppPath: "foo",
|
||||
RemoteDatastore: "set-datastore1",
|
||||
RemoteCacheDatastore: "set-datastore1",
|
||||
RemoteCacheDirectory: "set_packer_cache",
|
||||
RemotePort: 443,
|
||||
RemoteUser: "admin",
|
||||
},
|
||||
expectedConfig: &DriverConfig{
|
||||
FusionAppPath: "foo",
|
||||
RemoteDatastore: "set-datastore1",
|
||||
RemoteCacheDatastore: "set-datastore1",
|
||||
RemoteCacheDirectory: "set_packer_cache",
|
||||
RemotePort: 443,
|
||||
RemoteUser: "admin",
|
||||
},
|
||||
errs: nil,
|
||||
},
|
||||
{
|
||||
name: "Invalid remote type",
|
||||
config: &DriverConfig{
|
||||
RemoteType: "invalid",
|
||||
RemoteHost: "host",
|
||||
},
|
||||
expectedConfig: nil,
|
||||
errs: []error{fmt.Errorf("Only 'esx5' value is accepted for remote_type")},
|
||||
},
|
||||
{
|
||||
name: "Remote host not set",
|
||||
config: &DriverConfig{
|
||||
RemoteType: "esx5",
|
||||
},
|
||||
expectedConfig: nil,
|
||||
errs: []error{fmt.Errorf("remote_host must be specified")},
|
||||
},
|
||||
}
|
||||
|
||||
// Test with a good one
|
||||
c = new(DriverConfig)
|
||||
c.FusionAppPath = "foo"
|
||||
errs = c.Prepare(interpolate.NewContext())
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("bad: %#v", errs)
|
||||
for _, c := range tc {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
errs := c.config.Prepare(interpolate.NewContext())
|
||||
if !reflect.DeepEqual(errs, c.errs) {
|
||||
t.Fatalf("bad: \n expected '%v' \nactual '%v'", c.errs, errs)
|
||||
}
|
||||
if c.FusionAppPath != "foo" {
|
||||
t.Fatalf("bad value: %s", c.FusionAppPath)
|
||||
if len(c.errs) == 0 {
|
||||
if diff := cmp.Diff(c.config, c.expectedConfig); diff != "" {
|
||||
t.Fatalf("bad value: %s", diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,15 @@ import (
|
|||
"github.com/hashicorp/packer/helper/multistep"
|
||||
helperssh "github.com/hashicorp/packer/helper/ssh"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/vmware/govmomi"
|
||||
"github.com/vmware/govmomi/find"
|
||||
"github.com/vmware/govmomi/session"
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
"golang.org/x/mobile/event/key"
|
||||
)
|
||||
|
||||
// ESX5 driver talks to an ESXi5 hypervisor remotely over SSH to build
|
||||
|
@ -45,11 +53,66 @@ type ESX5Driver struct {
|
|||
VMName string
|
||||
CommConfig communicator.Config
|
||||
|
||||
ctx context.Context
|
||||
client *govmomi.Client
|
||||
finder *find.Finder
|
||||
|
||||
comm packer.Communicator
|
||||
outputDir string
|
||||
vmId string
|
||||
}
|
||||
|
||||
func NewESX5Driver(dconfig *DriverConfig, config *SSHConfig, vmName string) (Driver, error) {
|
||||
ctx := context.TODO()
|
||||
|
||||
vsphereUrl, err := url.Parse(fmt.Sprintf("https://%v/sdk", dconfig.RemoteHost))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
credentials := url.UserPassword(dconfig.RemoteUser, dconfig.RemotePassword)
|
||||
vsphereUrl.User = credentials
|
||||
|
||||
soapClient := soap.NewClient(vsphereUrl, true)
|
||||
vimClient, err := vim25.NewClient(ctx, soapClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vimClient.RoundTripper = session.KeepAlive(vimClient.RoundTripper, 10*time.Minute)
|
||||
client := &govmomi.Client{
|
||||
Client: vimClient,
|
||||
SessionManager: session.NewManager(vimClient),
|
||||
}
|
||||
|
||||
err = client.SessionManager.Login(ctx, credentials)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
finder := find.NewFinder(client.Client, false)
|
||||
datacenter, err := finder.DefaultDatacenter(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finder.SetDatacenter(datacenter)
|
||||
|
||||
return &ESX5Driver{
|
||||
Host: dconfig.RemoteHost,
|
||||
Port: dconfig.RemotePort,
|
||||
Username: dconfig.RemoteUser,
|
||||
Password: dconfig.RemotePassword,
|
||||
PrivateKeyFile: dconfig.RemotePrivateKey,
|
||||
Datastore: dconfig.RemoteDatastore,
|
||||
CacheDatastore: dconfig.RemoteCacheDatastore,
|
||||
CacheDirectory: dconfig.RemoteCacheDirectory,
|
||||
VMName: vmName,
|
||||
CommConfig: config.Comm,
|
||||
ctx: ctx,
|
||||
client: client,
|
||||
finder: finder,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *ESX5Driver) Clone(dst, src string, linked bool) error {
|
||||
|
||||
linesToArray := func(lines string) []string { return strings.Split(strings.Trim(lines, "\r\n"), "\n") }
|
||||
|
@ -887,3 +950,38 @@ func (r *esxcliReader) find(key, val string) (map[string]string, error) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
type KeyInput struct {
|
||||
Scancode key.Code
|
||||
Alt bool
|
||||
Ctrl bool
|
||||
Shift bool
|
||||
}
|
||||
|
||||
func (d *ESX5Driver) TypeOnKeyboard(input KeyInput) (int32, error) {
|
||||
vm, err := d.finder.VirtualMachine(d.ctx, d.VMName)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var spec types.UsbScanCodeSpec
|
||||
spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{
|
||||
UsbHidCode: int32(input.Scancode)<<16 | 7,
|
||||
Modifiers: &types.UsbScanCodeSpecModifierType{
|
||||
LeftControl: &input.Ctrl,
|
||||
LeftAlt: &input.Alt,
|
||||
LeftShift: &input.Shift,
|
||||
},
|
||||
})
|
||||
|
||||
req := &types.PutUsbScanCodes{
|
||||
This: vm.Reference(),
|
||||
Spec: spec,
|
||||
}
|
||||
|
||||
resp, err := methods.PutUsbScanCodes(d.ctx, d.client.RoundTripper, req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return resp.Returnval, nil
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// ~> **Note:** If [usb_keyboard](#usb_keyboard) is set to true, any VNC configuration will be ignored.
|
||||
type RunConfig struct {
|
||||
// Packer defaults to building VMware virtual machines
|
||||
// by launching a GUI that shows the console of the machine being built. When
|
||||
|
@ -39,7 +40,8 @@ type RunConfig struct {
|
|||
VNCDisablePassword bool `mapstructure:"vnc_disable_password" required:"false"`
|
||||
}
|
||||
|
||||
func (c *RunConfig) Prepare(ctx *interpolate.Context) (errs []error) {
|
||||
func (c *RunConfig) Prepare(_ *interpolate.Context, bootConfig *BootConfigWrapper) (errs []error) {
|
||||
if !bootConfig.USBKeyBoard {
|
||||
if c.VNCPortMin == 0 {
|
||||
c.VNCPortMin = 5900
|
||||
}
|
||||
|
@ -58,6 +60,6 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) (errs []error) {
|
|||
if c.VNCPortMin < 0 {
|
||||
errs = append(errs, fmt.Errorf("vnc_port_min must be positive"))
|
||||
}
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/common/bootcommand"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"golang.org/x/mobile/event/key"
|
||||
)
|
||||
|
||||
// This step "types" the boot command into the VM using USB Scan Codes.
|
||||
type StepUSBBootCommand struct {
|
||||
Config bootcommand.BootConfig
|
||||
KeyInterval time.Duration
|
||||
VMName string
|
||||
Ctx interpolate.Context
|
||||
}
|
||||
|
||||
type USBBootCommandTemplateData struct {
|
||||
HTTPIP string
|
||||
HTTPPort int
|
||||
Name string
|
||||
}
|
||||
|
||||
func (s *StepUSBBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
debug := state.Get("debug").(bool)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
driver := state.Get("driver").(*ESX5Driver)
|
||||
|
||||
if s.Config.BootCommand == nil {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
// Wait the for the vm to boot.
|
||||
if int64(s.Config.BootWait) > 0 {
|
||||
ui.Say(fmt.Sprintf("Waiting %s for boot...", s.Config.BootWait.String()))
|
||||
select {
|
||||
case <-time.After(s.Config.BootWait):
|
||||
break
|
||||
case <-ctx.Done():
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
var pauseFn multistep.DebugPauseFn
|
||||
if debug {
|
||||
pauseFn = state.Get("pauseFn").(multistep.DebugPauseFn)
|
||||
}
|
||||
|
||||
port := state.Get("http_port").(int)
|
||||
if port > 0 {
|
||||
ip := state.Get("http_ip").(string)
|
||||
s.Ctx.Data = &USBBootCommandTemplateData{
|
||||
HTTPIP: ip,
|
||||
HTTPPort: port,
|
||||
Name: s.VMName,
|
||||
}
|
||||
ui.Say(fmt.Sprintf("HTTP server is working at http://%v:%v/", ip, port))
|
||||
}
|
||||
|
||||
var keyAlt, keyCtrl, keyShift bool
|
||||
sendCodes := func(code key.Code, down bool) error {
|
||||
switch code {
|
||||
case key.CodeLeftAlt:
|
||||
keyAlt = down
|
||||
case key.CodeLeftControl:
|
||||
keyCtrl = down
|
||||
case key.CodeLeftShift:
|
||||
keyShift = down
|
||||
}
|
||||
|
||||
shift := down
|
||||
if keyShift {
|
||||
shift = keyShift
|
||||
}
|
||||
|
||||
_, err := driver.TypeOnKeyboard(KeyInput{
|
||||
Scancode: code,
|
||||
Ctrl: keyCtrl,
|
||||
Alt: keyAlt,
|
||||
Shift: shift,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error typing a boot command (code, down) `%d, %t`: %w", code, down, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
d := bootcommand.NewUSBDriver(sendCodes, s.KeyInterval)
|
||||
|
||||
ui.Say("Typing boot command...")
|
||||
flatBootCommand := s.Config.FlatBootCommand()
|
||||
command, err := interpolate.Render(flatBootCommand, &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
|
||||
}
|
||||
|
||||
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 := 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 (*StepUSBBootCommand) Cleanup(multistep.StateBag) {}
|
|
@ -23,22 +23,20 @@ import (
|
|||
//
|
||||
// Produces:
|
||||
// <nothing>
|
||||
type StepTypeBootCommand struct {
|
||||
BootCommand string
|
||||
VNCEnabled bool
|
||||
BootWait time.Duration
|
||||
type StepVNCBootCommand struct {
|
||||
Config bootcommand.VNCConfig
|
||||
VMName string
|
||||
Ctx interpolate.Context
|
||||
KeyInterval time.Duration
|
||||
}
|
||||
type bootCommandTemplateData struct {
|
||||
|
||||
type VNCBootCommandTemplateData struct {
|
||||
HTTPIP string
|
||||
HTTPPort int
|
||||
Name string
|
||||
}
|
||||
|
||||
func (s *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
if !s.VNCEnabled {
|
||||
func (s *StepVNCBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
if s.Config.DisableVNC {
|
||||
log.Println("Skipping boot command step...")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
@ -51,10 +49,10 @@ func (s *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag)
|
|||
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()))
|
||||
if int64(s.Config.BootWait) > 0 {
|
||||
ui.Say(fmt.Sprintf("Waiting %s for boot...", s.Config.BootWait.String()))
|
||||
select {
|
||||
case <-time.After(s.BootWait):
|
||||
case <-time.After(s.Config.BootWait):
|
||||
break
|
||||
case <-ctx.Done():
|
||||
return multistep.ActionHalt
|
||||
|
@ -98,16 +96,17 @@ func (s *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag)
|
|||
log.Printf("Connected to VNC desktop: %s", c.DesktopName)
|
||||
|
||||
hostIP := state.Get("http_ip").(string)
|
||||
s.Ctx.Data = &bootCommandTemplateData{
|
||||
hostIP,
|
||||
httpPort,
|
||||
s.VMName,
|
||||
s.Ctx.Data = &VNCBootCommandTemplateData{
|
||||
HTTPIP: hostIP,
|
||||
HTTPPort: httpPort,
|
||||
Name: s.VMName,
|
||||
}
|
||||
|
||||
d := bootcommand.NewVNCDriver(c, s.KeyInterval)
|
||||
d := bootcommand.NewVNCDriver(c, s.Config.BootKeyInterval)
|
||||
|
||||
ui.Say("Typing the boot command over VNC...")
|
||||
command, err := interpolate.Render(s.BootCommand, &s.Ctx)
|
||||
flatBootCommand := s.Config.FlatBootCommand()
|
||||
command, err := interpolate.Render(flatBootCommand, &s.Ctx)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error preparing boot command: %s", err)
|
||||
state.Put("error", err)
|
||||
|
@ -138,4 +137,4 @@ func (s *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag)
|
|||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*StepTypeBootCommand) Cleanup(multistep.StateBag) {}
|
||||
func (*StepVNCBootCommand) Cleanup(multistep.StateBag) {}
|
|
@ -52,6 +52,20 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
state.Put("driverConfig", &b.config.DriverConfig)
|
||||
state.Put("temporaryDevices", []string{}) // Devices (in .vmx) created by packer during building
|
||||
|
||||
var stepBootCommand multistep.Step = &vmwcommon.StepVNCBootCommand{
|
||||
Config: b.config.VNCConfig,
|
||||
VMName: b.config.VMName,
|
||||
Ctx: b.config.ctx,
|
||||
}
|
||||
if b.config.USBKeyBoard {
|
||||
stepBootCommand = &vmwcommon.StepUSBBootCommand{
|
||||
Config: b.config.VNCConfig.BootConfig,
|
||||
KeyInterval: b.config.VNCConfig.BootKeyInterval,
|
||||
VMName: b.config.VMName,
|
||||
Ctx: b.config.ctx,
|
||||
}
|
||||
}
|
||||
|
||||
steps := []multistep.Step{
|
||||
&vmwcommon.StepPrepareTools{
|
||||
RemoteType: b.config.RemoteType,
|
||||
|
@ -137,14 +151,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
DurationBeforeStop: 5 * time.Second,
|
||||
Headless: b.config.Headless,
|
||||
},
|
||||
&vmwcommon.StepTypeBootCommand{
|
||||
BootWait: b.config.BootWait,
|
||||
VNCEnabled: !b.config.DisableVNC,
|
||||
BootCommand: b.config.FlatBootCommand(),
|
||||
VMName: b.config.VMName,
|
||||
Ctx: b.config.ctx,
|
||||
KeyInterval: b.config.VNCConfig.BootKeyInterval,
|
||||
},
|
||||
stepBootCommand,
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.SSHConfig.Comm,
|
||||
Host: driver.CommHost,
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
|
||||
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/common/bootcommand"
|
||||
"github.com/hashicorp/packer/common/shutdowncommand"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
|
@ -24,7 +23,7 @@ type Config struct {
|
|||
common.ISOConfig `mapstructure:",squash"`
|
||||
common.FloppyConfig `mapstructure:",squash"`
|
||||
common.CDConfig `mapstructure:",squash"`
|
||||
bootcommand.VNCConfig `mapstructure:",squash"`
|
||||
vmwcommon.BootConfigWrapper `mapstructure:",squash"`
|
||||
vmwcommon.DriverConfig `mapstructure:",squash"`
|
||||
vmwcommon.HWConfig `mapstructure:",squash"`
|
||||
vmwcommon.OutputConfig `mapstructure:",squash"`
|
||||
|
@ -94,25 +93,27 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||
}
|
||||
|
||||
// Accumulate any errors and warnings
|
||||
var warnings []string
|
||||
var errs *packer.MultiError
|
||||
warnings := make([]string, 0)
|
||||
|
||||
vncWarnings, vncErrs := c.BootConfigWrapper.Prepare(&c.ctx, &c.DriverConfig)
|
||||
warnings = append(warnings, vncWarnings...)
|
||||
errs = packer.MultiErrorAppend(errs, vncErrs...)
|
||||
|
||||
isoWarnings, isoErrs := c.ISOConfig.Prepare(&c.ctx)
|
||||
warnings = append(warnings, isoWarnings...)
|
||||
errs = packer.MultiErrorAppend(errs, isoErrs...)
|
||||
errs = packer.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.HWConfig.Prepare(&c.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.OutputConfig.Prepare(&c.ctx, &c.PackerConfig)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.DriverConfig.Prepare(&c.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
c.OutputConfig.Prepare(&c.ctx, &c.PackerConfig)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.RunConfig.Prepare(&c.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.RunConfig.Prepare(&c.ctx, &c.BootConfigWrapper)...)
|
||||
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)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.CDConfig.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.CDConfig.Prepare(&c.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.VNCConfig.Prepare(&c.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.ExportConfig.Prepare(&c.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.DiskConfig.Prepare(&c.ctx)...)
|
||||
|
||||
|
@ -165,19 +166,6 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||
c.HWConfig.Network = "nat"
|
||||
}
|
||||
|
||||
// Remote configuration validation
|
||||
if c.RemoteType != "" {
|
||||
if c.RemoteHost == "" {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("remote_host must be specified"))
|
||||
}
|
||||
|
||||
if c.RemoteType != "esx5" {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Only 'esx5' value is accepted for remote_type"))
|
||||
}
|
||||
}
|
||||
|
||||
if c.Format == "" {
|
||||
if c.RemoteType == "" {
|
||||
c.Format = "vmx"
|
||||
|
@ -186,31 +174,31 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
if c.RemoteType == "" && c.Format == "vmx" {
|
||||
if c.RemoteType == "" {
|
||||
if c.Format == "vmx" {
|
||||
// if we're building locally and want a vmx, there's nothing to export.
|
||||
// Set skip export flag here to keep the export step from attempting
|
||||
// an unneded export
|
||||
c.SkipExport = true
|
||||
}
|
||||
if c.Headless && c.DisableVNC {
|
||||
warnings = append(warnings,
|
||||
"Headless mode uses VNC to retrieve output. Since VNC has been disabled,\n"+
|
||||
"you won't be able to see any output.")
|
||||
}
|
||||
}
|
||||
|
||||
err = c.DriverConfig.Validate(c.SkipExport)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
|
||||
// Warnings
|
||||
if c.ShutdownCommand == "" {
|
||||
warnings = append(warnings,
|
||||
"A shutdown_command was not specified. Without a shutdown command, Packer\n"+
|
||||
"will forcibly halt the virtual machine, which may result in data loss.")
|
||||
}
|
||||
|
||||
if c.Headless && c.DisableVNC {
|
||||
warnings = append(warnings,
|
||||
"Headless mode uses VNC to retrieve output. Since VNC has been disabled,\n"+
|
||||
"you won't be able to see any output.")
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return warnings, errs
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ type FlatConfig struct {
|
|||
BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"`
|
||||
DisableVNC *bool `mapstructure:"disable_vnc" cty:"disable_vnc" hcl:"disable_vnc"`
|
||||
BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"`
|
||||
USBKeyBoard *bool `mapstructure:"usb_keyboard" cty:"usb_keyboard" hcl:"usb_keyboard"`
|
||||
CleanUpRemoteCache *bool `mapstructure:"cleanup_remote_cache" required:"false" cty:"cleanup_remote_cache" hcl:"cleanup_remote_cache"`
|
||||
FusionAppPath *string `mapstructure:"fusion_app_path" required:"false" cty:"fusion_app_path" hcl:"fusion_app_path"`
|
||||
RemoteType *string `mapstructure:"remote_type" required:"false" cty:"remote_type" hcl:"remote_type"`
|
||||
|
@ -176,6 +177,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||
"boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false},
|
||||
"disable_vnc": &hcldec.AttrSpec{Name: "disable_vnc", Type: cty.Bool, Required: false},
|
||||
"boot_key_interval": &hcldec.AttrSpec{Name: "boot_key_interval", Type: cty.String, Required: false},
|
||||
"usb_keyboard": &hcldec.AttrSpec{Name: "usb_keyboard", Type: cty.Bool, Required: false},
|
||||
"cleanup_remote_cache": &hcldec.AttrSpec{Name: "cleanup_remote_cache", Type: cty.Bool, Required: false},
|
||||
"fusion_app_path": &hcldec.AttrSpec{Name: "fusion_app_path", Type: cty.String, Required: false},
|
||||
"remote_type": &hcldec.AttrSpec{Name: "remote_type", Type: cty.String, Required: false},
|
||||
|
|
|
@ -56,6 +56,20 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
state.Put("driverConfig", &b.config.DriverConfig)
|
||||
state.Put("temporaryDevices", []string{}) // Devices (in .vmx) created by packer during building
|
||||
|
||||
var stepBootCommand multistep.Step = &vmwcommon.StepVNCBootCommand{
|
||||
Config: b.config.VNCConfig,
|
||||
VMName: b.config.VMName,
|
||||
Ctx: b.config.ctx,
|
||||
}
|
||||
if b.config.USBKeyBoard {
|
||||
stepBootCommand = &vmwcommon.StepUSBBootCommand{
|
||||
Config: b.config.VNCConfig.BootConfig,
|
||||
KeyInterval: b.config.VNCConfig.BootKeyInterval,
|
||||
VMName: b.config.VMName,
|
||||
Ctx: b.config.ctx,
|
||||
}
|
||||
}
|
||||
|
||||
// Build the steps.
|
||||
steps := []multistep.Step{
|
||||
&vmwcommon.StepPrepareTools{
|
||||
|
@ -126,14 +140,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
DurationBeforeStop: 5 * time.Second,
|
||||
Headless: b.config.Headless,
|
||||
},
|
||||
&vmwcommon.StepTypeBootCommand{
|
||||
BootWait: b.config.BootWait,
|
||||
VNCEnabled: !b.config.DisableVNC,
|
||||
BootCommand: b.config.FlatBootCommand(),
|
||||
VMName: b.config.VMName,
|
||||
Ctx: b.config.ctx,
|
||||
KeyInterval: b.config.VNCConfig.BootKeyInterval,
|
||||
},
|
||||
stepBootCommand,
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.SSHConfig.Comm,
|
||||
Host: driver.CommHost,
|
||||
|
@ -203,5 +210,3 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
return vmwcommon.NewArtifact(b.config.RemoteType, b.config.Format, exportOutputPath,
|
||||
b.config.VMName, b.config.SkipExport, b.config.KeepRegistered, state)
|
||||
}
|
||||
|
||||
// Cancel.
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
|
||||
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/common/bootcommand"
|
||||
"github.com/hashicorp/packer/common/shutdowncommand"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
|
@ -21,7 +20,7 @@ type Config struct {
|
|||
common.PackerConfig `mapstructure:",squash"`
|
||||
common.HTTPConfig `mapstructure:",squash"`
|
||||
common.FloppyConfig `mapstructure:",squash"`
|
||||
bootcommand.VNCConfig `mapstructure:",squash"`
|
||||
vmwcommon.BootConfigWrapper `mapstructure:",squash"`
|
||||
vmwcommon.DriverConfig `mapstructure:",squash"`
|
||||
vmwcommon.OutputConfig `mapstructure:",squash"`
|
||||
vmwcommon.RunConfig `mapstructure:",squash"`
|
||||
|
@ -77,16 +76,20 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||
"packer-%s-%d", c.PackerBuildName, interpolate.InitTime.Unix())
|
||||
}
|
||||
|
||||
// Prepare the errors
|
||||
// Accumulate any errors and warnings
|
||||
var warnings []string
|
||||
var errs *packer.MultiError
|
||||
|
||||
vncWarnings, vncErrs := c.BootConfigWrapper.Prepare(&c.ctx, &c.DriverConfig)
|
||||
warnings = append(warnings, vncWarnings...)
|
||||
errs = packer.MultiErrorAppend(errs, vncErrs...)
|
||||
errs = packer.MultiErrorAppend(errs, c.DriverConfig.Prepare(&c.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.OutputConfig.Prepare(&c.ctx, &c.PackerConfig)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.RunConfig.Prepare(&c.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.RunConfig.Prepare(&c.ctx, &c.BootConfigWrapper)...)
|
||||
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)...)
|
||||
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)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.ExportConfig.Prepare(&c.ctx)...)
|
||||
|
@ -101,16 +104,10 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||
fmt.Errorf("source_path is invalid: %s", err))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Remote configuration validation
|
||||
if c.RemoteHost == "" {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("remote_host must be specified"))
|
||||
}
|
||||
|
||||
if c.RemoteType != "esx5" {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Only 'esx5' value is accepted for remote_type"))
|
||||
if c.Headless && c.DisableVNC {
|
||||
warnings = append(warnings,
|
||||
"Headless mode uses VNC to retrieve output. Since VNC has been disabled,\n"+
|
||||
"you won't be able to see any output.")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,20 +140,12 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
|
||||
// Warnings
|
||||
var warnings []string
|
||||
if c.ShutdownCommand == "" {
|
||||
warnings = append(warnings,
|
||||
"A shutdown_command was not specified. Without a shutdown command, Packer\n"+
|
||||
"will forcibly halt the virtual machine, which may result in data loss.")
|
||||
}
|
||||
|
||||
if c.Headless && c.DisableVNC {
|
||||
warnings = append(warnings,
|
||||
"Headless mode uses VNC to retrieve output. Since VNC has been disabled,\n"+
|
||||
"you won't be able to see any output.")
|
||||
}
|
||||
|
||||
// Check for any errors.
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return warnings, errs
|
||||
|
|
|
@ -28,6 +28,7 @@ type FlatConfig struct {
|
|||
BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"`
|
||||
DisableVNC *bool `mapstructure:"disable_vnc" cty:"disable_vnc" hcl:"disable_vnc"`
|
||||
BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"`
|
||||
USBKeyBoard *bool `mapstructure:"usb_keyboard" cty:"usb_keyboard" hcl:"usb_keyboard"`
|
||||
CleanUpRemoteCache *bool `mapstructure:"cleanup_remote_cache" required:"false" cty:"cleanup_remote_cache" hcl:"cleanup_remote_cache"`
|
||||
FusionAppPath *string `mapstructure:"fusion_app_path" required:"false" cty:"fusion_app_path" hcl:"fusion_app_path"`
|
||||
RemoteType *string `mapstructure:"remote_type" required:"false" cty:"remote_type" hcl:"remote_type"`
|
||||
|
@ -148,6 +149,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||
"boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false},
|
||||
"disable_vnc": &hcldec.AttrSpec{Name: "disable_vnc", Type: cty.Bool, Required: false},
|
||||
"boot_key_interval": &hcldec.AttrSpec{Name: "boot_key_interval", Type: cty.String, Required: false},
|
||||
"usb_keyboard": &hcldec.AttrSpec{Name: "usb_keyboard", Type: cty.Bool, Required: false},
|
||||
"cleanup_remote_cache": &hcldec.AttrSpec{Name: "cleanup_remote_cache", Type: cty.Bool, Required: false},
|
||||
"fusion_app_path": &hcldec.AttrSpec{Name: "fusion_app_path", Type: cty.String, Required: false},
|
||||
"remote_type": &hcldec.AttrSpec{Name: "remote_type", Type: cty.String, Required: false},
|
||||
|
|
|
@ -159,6 +159,8 @@ necessary for this build to succeed and can be found further down the page.
|
|||
|
||||
### Run configuration
|
||||
|
||||
@include 'builder/vmware/common/RunConfig.mdx'
|
||||
|
||||
#### Optional:
|
||||
|
||||
@include 'builder/vmware/common/RunConfig-not-required.mdx'
|
||||
|
@ -199,6 +201,8 @@ necessary for this build to succeed and can be found further down the page.
|
|||
|
||||
@include 'common/bootcommand/BootConfig.mdx'
|
||||
|
||||
@include 'builder/vmware/common/BootConfigWrapper-not-required.mdx'
|
||||
|
||||
@include 'common/bootcommand/VNCConfig.mdx'
|
||||
|
||||
-> **Note**: for the `HTTPIP` to be resolved correctly, your VM's network
|
||||
|
|
|
@ -133,6 +133,8 @@ necessary for this build to succeed and can be found further down the page.
|
|||
|
||||
### Run configuration
|
||||
|
||||
@include 'builder/vmware/common/RunConfig.mdx'
|
||||
|
||||
#### Optional:
|
||||
|
||||
@include 'builder/vmware/common/RunConfig-not-required.mdx'
|
||||
|
@ -177,6 +179,8 @@ necessary for this build to succeed and can be found further down the page.
|
|||
|
||||
@include 'common/bootcommand/BootConfig.mdx'
|
||||
|
||||
@include 'builder/vmware/common/BootConfigWrapper-not-required.mdx'
|
||||
|
||||
@include 'common/bootcommand/VNCConfig.mdx'
|
||||
|
||||
-> **Note**: for the `HTTPIP` to be resolved correctly, your VM's network
|
||||
|
@ -188,6 +192,7 @@ to modify the network configuration after the VM is done building.
|
|||
#### Optional:
|
||||
|
||||
@include 'common/bootcommand/VNCConfig-not-required.mdx'
|
||||
|
||||
@include 'common/bootcommand/BootConfig-not-required.mdx'
|
||||
|
||||
For more examples of various boot commands, see the sample projects from our
|
||||
|
@ -198,7 +203,7 @@ For more examples of various boot commands, see the sample projects from our
|
|||
"builders": [
|
||||
{
|
||||
"type": "vmware-vmx",
|
||||
"boot_key_interval": "10ms"
|
||||
"boot_key_interval": "10ms",
|
||||
...
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<!-- Code generated from the comments of the BootConfigWrapper struct in builder/vmware/common/boot_config.go; DO NOT EDIT MANUALLY -->
|
||||
|
||||
- `usb_keyboard` (bool) - If set to true, Packer will use USB HID Keyboard scan codes to send the boot command to the VM and
|
||||
the [disable_vnc](#disable_vnc) option will be ignored and automatically set to true.
|
||||
This option is not supported by hosts with free license.
|
||||
|
||||
~> **Note:** The ESXi 6.5+ removes support to VNC. In this case, the `usb_keyboard` should be set to true
|
||||
in order to send boot command keystrokes to the VM.
|
|
@ -0,0 +1,3 @@
|
|||
<!-- Code generated from the comments of the RunConfig struct in builder/vmware/common/run_config.go; DO NOT EDIT MANUALLY -->
|
||||
|
||||
~> **Note:** If [usb_keyboard](#usb_keyboard) is set to true, any VNC configuration will be ignored.
|
Loading…
Reference in New Issue