Support for boot_command
Setup local http server Add compaction of hard drive GetHostAdapterIpAddress function added for hyperv renamed step step_start_vm to step_run to fall in ine with naming conventions of other builders
This commit is contained in:
parent
03b0698edd
commit
aa1f1da1ff
|
@ -31,4 +31,10 @@ type Driver interface {
|
|||
|
||||
// Finds the IP address of a VM connected that uses DHCP by its MAC address
|
||||
IpAddress(string) (string, error)
|
||||
|
||||
// Finds the IP address of a host adapter connected to switch
|
||||
GetHostAdapterIpAddressForSwitch(string) (string, error)
|
||||
|
||||
// Type scan codes to virtual keyboard of vm
|
||||
TypeScanCodes(string, string) error
|
||||
}
|
||||
|
|
|
@ -41,12 +41,12 @@ func (d *HypervPS4Driver) IsRunning(vmName string) (bool, error) {
|
|||
|
||||
// Start starts a VM specified by the name given.
|
||||
func (d *HypervPS4Driver) Start(vmName string) error {
|
||||
return hyperv.Start(vmName)
|
||||
return hyperv.StartVirtualMachine(vmName)
|
||||
}
|
||||
|
||||
// Stop stops a VM specified by the name given.
|
||||
func (d *HypervPS4Driver) Stop(vmName string) error {
|
||||
return hyperv.TurnOff(vmName)
|
||||
return hyperv.StopVirtualMachine(vmName)
|
||||
}
|
||||
|
||||
func (d *HypervPS4Driver) Verify() error {
|
||||
|
@ -97,6 +97,26 @@ func (d *HypervPS4Driver) IpAddress(mac string) (string, error) {
|
|||
return res, err
|
||||
}
|
||||
|
||||
// Finds the IP address of a host adapter connected to switch
|
||||
func (d *HypervPS4Driver) GetHostAdapterIpAddressForSwitch(switchName string) (string, error) {
|
||||
res, err := hyperv.GetHostAdapterIpAddressForSwitch(switchName)
|
||||
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
if res == "" {
|
||||
err := fmt.Errorf("%s", "No ip address.")
|
||||
return res, err
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Type scan codes to virtual keyboard of vm
|
||||
func (d *HypervPS4Driver) TypeScanCodes(vmName string, scanCodes string) error {
|
||||
return hyperv.TypeScanCodes(vmName, scanCodes)
|
||||
}
|
||||
|
||||
func (d *HypervPS4Driver) verifyPSVersion() error {
|
||||
|
||||
log.Printf("Enter method: %s", "verifyPSVersion")
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RunConfig struct {
|
||||
Headless bool `mapstructure:"headless"`
|
||||
RawBootWait string `mapstructure:"boot_wait"`
|
||||
|
||||
HTTPDir string `mapstructure:"http_directory"`
|
||||
HTTPPortMin uint `mapstructure:"http_port_min"`
|
||||
HTTPPortMax uint `mapstructure:"http_port_max"`
|
||||
|
||||
BootWait time.Duration ``
|
||||
}
|
||||
|
||||
|
@ -19,11 +23,29 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
|||
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)}
|
||||
if c.HTTPPortMin == 0 {
|
||||
c.HTTPPortMin = 8000
|
||||
}
|
||||
|
||||
return nil
|
||||
if c.HTTPPortMax == 0 {
|
||||
c.HTTPPortMax = 9000
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
if c.HTTPPortMin > c.HTTPPortMax {
|
||||
errs = append(errs,
|
||||
errors.New("http_port_min must be less than http_port_max"))
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
|
|
@ -6,20 +6,21 @@ package common
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"io/ioutil"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/powershell/hyperv"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const(
|
||||
const (
|
||||
vhdDir string = "Virtual Hard Disks"
|
||||
vmDir string = "Virtual Machines"
|
||||
)
|
||||
|
||||
type StepExportVm struct {
|
||||
OutputDir string
|
||||
SkipCompaction bool
|
||||
}
|
||||
|
||||
func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction {
|
||||
|
@ -34,7 +35,7 @@ func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction {
|
|||
|
||||
// create temp path to export vm
|
||||
errorMsg = "Error creating temp export path: %s"
|
||||
vmExportPath , err := ioutil.TempDir(tmpPath, "export")
|
||||
vmExportPath, err := ioutil.TempDir(tmpPath, "export")
|
||||
if err != nil {
|
||||
err := fmt.Errorf(errorMsg, err)
|
||||
state.Put("error", err)
|
||||
|
@ -54,7 +55,21 @@ func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction {
|
|||
}
|
||||
|
||||
// copy to output dir
|
||||
expPath := filepath.Join(vmExportPath,vmName)
|
||||
expPath := filepath.Join(vmExportPath, vmName)
|
||||
|
||||
if s.SkipCompaction {
|
||||
ui.Say("Skipping disk compaction...")
|
||||
} else {
|
||||
ui.Say("Compacting disks...")
|
||||
err = hyperv.CompactDisks(expPath, vhdDir)
|
||||
if err != nil {
|
||||
errorMsg = "Error compacting disks: %s"
|
||||
err := fmt.Errorf(errorMsg, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
ui.Say("Coping to output dir...")
|
||||
err = hyperv.CopyExportedVirtualMachine(expPath, outputPath, vhdDir, vmDir)
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// This step creates and runs the HTTP server that is serving files from the
|
||||
// directory specified by the 'http_directory` configuration parameter in the
|
||||
// template.
|
||||
//
|
||||
// Uses:
|
||||
// ui packer.Ui
|
||||
//
|
||||
// Produces:
|
||||
// http_port int - The port the HTTP server started on.
|
||||
type StepHTTPServer struct {
|
||||
HTTPDir string
|
||||
HTTPPortMin uint
|
||||
HTTPPortMax uint
|
||||
|
||||
l net.Listener
|
||||
}
|
||||
|
||||
func (s *StepHTTPServer) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
var httpPort uint = 0
|
||||
if s.HTTPDir == "" {
|
||||
state.Put("http_port", httpPort)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
// Find an available TCP port for our HTTP server
|
||||
var httpAddr string
|
||||
portRange := int(s.HTTPPortMax - s.HTTPPortMin)
|
||||
for {
|
||||
var err error
|
||||
var offset uint = 0
|
||||
|
||||
if portRange > 0 {
|
||||
// Intn will panic if portRange == 0, so we do a check.
|
||||
offset = uint(rand.Intn(portRange))
|
||||
}
|
||||
|
||||
httpPort = offset + s.HTTPPortMin
|
||||
httpAddr = fmt.Sprintf("0.0.0.0:%d", httpPort)
|
||||
log.Printf("Trying port: %d", httpPort)
|
||||
s.l, err = net.Listen("tcp", httpAddr)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Starting HTTP server on port %d", httpPort))
|
||||
|
||||
// Start the HTTP server and run it in the background
|
||||
fileServer := http.FileServer(http.Dir(s.HTTPDir))
|
||||
server := &http.Server{Addr: httpAddr, Handler: fileServer}
|
||||
go server.Serve(s.l)
|
||||
|
||||
// Save the address into the state so it can be accessed in the future
|
||||
state.Put("http_port", httpPort)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepHTTPServer) Cleanup(multistep.StateBag) {
|
||||
if s.l != nil {
|
||||
// Close the listener so that the HTTP server stops
|
||||
s.l.Close()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc.
|
||||
// All Rights Reserved.
|
||||
// Licensed under the Apache License, Version 2.0.
|
||||
// See License.txt in the project root for license information.
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"time"
|
||||
)
|
||||
|
||||
type StepRun struct {
|
||||
BootWait time.Duration
|
||||
Headless bool
|
||||
|
||||
vmName string
|
||||
}
|
||||
|
||||
func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction {
|
||||
driver := state.Get("driver").(Driver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
vmName := state.Get("vmName").(string)
|
||||
|
||||
ui.Say("Starting the virtual machine...")
|
||||
|
||||
err := driver.Start(vmName)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error starting vm: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.vmName = vmName
|
||||
|
||||
if int64(s.BootWait) > 0 {
|
||||
ui.Say(fmt.Sprintf("Waiting %s for boot...", s.BootWait))
|
||||
wait := time.After(s.BootWait)
|
||||
WAITLOOP:
|
||||
for {
|
||||
select {
|
||||
case <-wait:
|
||||
break WAITLOOP
|
||||
case <-time.After(1 * time.Second):
|
||||
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepRun) Cleanup(state multistep.StateBag) {
|
||||
if s.vmName == "" {
|
||||
return
|
||||
}
|
||||
|
||||
driver := state.Get("driver").(Driver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if running, _ := driver.IsRunning(s.vmName); running {
|
||||
if err := driver.Stop(s.vmName); err != nil {
|
||||
ui.Error(fmt.Sprintf("Error shutting down VM: %s", err))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc.
|
||||
// All Rights Reserved.
|
||||
// Licensed under the Apache License, Version 2.0.
|
||||
// See License.txt in the project root for license information.
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"time"
|
||||
"github.com/mitchellh/packer/powershell/hyperv"
|
||||
)
|
||||
|
||||
type StepStartVm struct {
|
||||
Reason string
|
||||
StartUpDelay int
|
||||
}
|
||||
|
||||
func (s *StepStartVm) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
errorMsg := "Error starting vm: %s"
|
||||
vmName := state.Get("vmName").(string)
|
||||
|
||||
ui.Say("Starting vm for " + s.Reason + "...")
|
||||
|
||||
err := hyperv.StartVirtualMachine(vmName)
|
||||
if err != nil {
|
||||
err := fmt.Errorf(errorMsg, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if s.StartUpDelay != 0 {
|
||||
//sleepTime := s.StartUpDelay * time.Second
|
||||
sleepTime := 60 * time.Second
|
||||
|
||||
ui.Say(fmt.Sprintf(" Waiting %v for vm to start...", sleepTime))
|
||||
time.Sleep(sleepTime);
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepStartVm) Cleanup(state multistep.StateBag) {
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type bootCommandTemplateData struct {
|
||||
HTTPIP string
|
||||
HTTPPort uint
|
||||
Name string
|
||||
}
|
||||
|
||||
// This step "types" the boot command into the VM via the 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
|
||||
SwitchName string
|
||||
VMName string
|
||||
Ctx interpolate.Context
|
||||
}
|
||||
|
||||
func (s *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction {
|
||||
httpPort := state.Get("http_port").(uint)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
driver := state.Get("driver").(Driver)
|
||||
|
||||
hostIp, err := driver.GetHostAdapterIpAddressForSwitch(s.SwitchName)
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error getting host adapter ip address: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Host IP for the HyperV machine: %s", hostIp))
|
||||
|
||||
s.Ctx.Data = &bootCommandTemplateData{
|
||||
hostIp,
|
||||
httpPort,
|
||||
s.VMName,
|
||||
}
|
||||
|
||||
ui.Say("Typing the boot command...")
|
||||
scanCodesToSend := []string{}
|
||||
|
||||
for _, command := range s.BootCommand {
|
||||
command, err := interpolate.Render(command, &s.Ctx)
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error preparing boot command: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
scanCodesToSend = append(scanCodesToSend, scancodes(command)...)
|
||||
}
|
||||
|
||||
scanCodesToSendString := strings.Join(scanCodesToSend, " ")
|
||||
|
||||
if err := driver.TypeScanCodes(s.VMName, scanCodesToSendString); err != nil {
|
||||
err := fmt.Errorf("Error sending boot command: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (*StepTypeBootCommand) Cleanup(multistep.StateBag) {}
|
||||
|
||||
func scancodes(message string) []string {
|
||||
// Scancodes reference: http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html
|
||||
//
|
||||
// Scancodes represent raw keyboard output and are fed to the VM by 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{"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"}
|
||||
|
||||
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, "<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
|
||||
}
|
|
@ -48,8 +48,8 @@ type Config struct {
|
|||
hypervcommon.FloppyConfig `mapstructure:",squash"`
|
||||
hypervcommon.OutputConfig `mapstructure:",squash"`
|
||||
hypervcommon.SSHConfig `mapstructure:",squash"`
|
||||
hypervcommon.ShutdownConfig `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).
|
||||
|
@ -95,6 +95,7 @@ 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"`
|
||||
Cpu uint `mapstructure:"cpu"`
|
||||
Generation uint `mapstructure:"generation"`
|
||||
|
@ -107,6 +108,8 @@ type Config struct {
|
|||
|
||||
SSHWaitTimeout time.Duration
|
||||
|
||||
SkipCompaction bool `mapstructure:"skip_compaction"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
|
@ -115,7 +118,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{},
|
||||
Exclude: []string{
|
||||
"boot_command",
|
||||
},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
|
@ -271,6 +276,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
&common.StepCreateFloppy{
|
||||
Files: b.config.FloppyFiles,
|
||||
},
|
||||
&hypervcommon.StepHTTPServer{
|
||||
HTTPDir: b.config.HTTPDir,
|
||||
HTTPPortMin: b.config.HTTPPortMin,
|
||||
HTTPPortMax: b.config.HTTPPortMax,
|
||||
},
|
||||
&hypervcommon.StepCreateSwitch{
|
||||
SwitchName: b.config.SwitchName,
|
||||
},
|
||||
|
@ -291,8 +301,16 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
|
||||
&hypervcommon.StepMountSecondaryDvdImages{},
|
||||
|
||||
&hypervcommon.StepStartVm{
|
||||
Reason: "OS installation",
|
||||
&hypervcommon.StepRun{
|
||||
BootWait: b.config.BootWait,
|
||||
Headless: b.config.Headless,
|
||||
},
|
||||
|
||||
&hypervcommon.StepTypeBootCommand{
|
||||
BootCommand: b.config.BootCommand,
|
||||
SwitchName: b.config.SwitchName,
|
||||
VMName: b.config.VMName,
|
||||
Ctx: b.config.ctx,
|
||||
},
|
||||
|
||||
// configure the communicator ssh, winrm
|
||||
|
@ -321,6 +339,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
|
||||
&hypervcommon.StepExportVm{
|
||||
OutputDir: b.config.OutputDir,
|
||||
SkipCompaction: b.config.SkipCompaction,
|
||||
},
|
||||
|
||||
// the clean up actions for each step will be executed reverse order
|
||||
|
|
|
@ -5,6 +5,29 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
func GetHostAdapterIpAddressForSwitch(switchName string) (string, error) {
|
||||
var script = `
|
||||
param([string]$switchName, [int]$addressIndex)
|
||||
|
||||
$HostVMAdapter = Get-VMNetworkAdapter -ManagementOS -SwitchName $switchName
|
||||
if ($HostVMAdapter){
|
||||
$HostNetAdapter = Get-NetAdapter | ?{ $_.DeviceID -eq $HostVMAdapter.DeviceId }
|
||||
if ($HostNetAdapter){
|
||||
$HostNetAdapterConfiguration = @(get-wmiobject win32_networkadapterconfiguration -filter "IPEnabled = 'TRUE' AND InterfaceIndex=$($HostNetAdapter.ifIndex)")
|
||||
if ($HostNetAdapterConfiguration){
|
||||
return $HostNetAdapterConfiguration.IpAddress[$addressIndex]
|
||||
}
|
||||
}
|
||||
}
|
||||
return $false
|
||||
`
|
||||
|
||||
var ps powershell.PowerShellCmd
|
||||
cmdOut, err := ps.Output(script, switchName, "0")
|
||||
|
||||
return cmdOut, err
|
||||
}
|
||||
|
||||
func GetVirtualMachineNetworkAdapterAddress(vmName string) (string, error) {
|
||||
|
||||
var script = `
|
||||
|
@ -106,7 +129,7 @@ param([string]$vmName)
|
|||
|
||||
$vm = Get-VM -Name $vmName
|
||||
if (($vm.State -ne [Microsoft.HyperV.PowerShell.VMState]::Off) -and ($vm.State -ne [Microsoft.HyperV.PowerShell.VMState]::OffCritical)) {
|
||||
Stop-VM -VM $vm -TurnOff -Force
|
||||
Stop-VM -VM $vm -TurnOff -Force -Confirm:$false
|
||||
}
|
||||
|
||||
Remove-VM -Name $vmName -Force
|
||||
|
@ -129,6 +152,19 @@ Export-VM -Name $vmName -Path $path
|
|||
return err
|
||||
}
|
||||
|
||||
func CompactDisks(expPath string, vhdDir string) error {
|
||||
var script = `
|
||||
param([string]$srcPath, [string]$vhdDirName)
|
||||
Get-ChildItem "$srcPath/$vhdDirName" -Filter *.vhd* | %{
|
||||
Optimize-VHD -Path $_.FullName -Mode Full
|
||||
}
|
||||
`
|
||||
|
||||
var ps powershell.PowerShellCmd
|
||||
err := ps.Run(script, expPath, vhdDir)
|
||||
return err
|
||||
}
|
||||
|
||||
func CopyExportedVirtualMachine(expPath string, outputPath string, vhdDir string, vmDir string) error {
|
||||
|
||||
var script = `
|
||||
|
@ -180,7 +216,10 @@ func StartVirtualMachine(vmName string) error {
|
|||
|
||||
var script = `
|
||||
param([string]$vmName)
|
||||
Start-VM -Name $vmName
|
||||
$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue
|
||||
if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Off) {
|
||||
Start-VM -Name $vmName
|
||||
}
|
||||
`
|
||||
|
||||
var ps powershell.PowerShellCmd
|
||||
|
@ -206,7 +245,7 @@ func StopVirtualMachine(vmName string) error {
|
|||
param([string]$vmName)
|
||||
$vm = Get-VM -Name $vmName
|
||||
if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) {
|
||||
Stop-VM -VM $vm
|
||||
Stop-VM -VM $vm -Confirm:$false
|
||||
}
|
||||
`
|
||||
|
||||
|
@ -297,7 +336,7 @@ foreach ($adapter in $adapters) {
|
|||
}
|
||||
|
||||
if($switch -ne $null) {
|
||||
Get-VMNetworkAdapter –VMName $vmName | Connect-VMNetworkAdapter -VMSwitch $switch
|
||||
Get-VMNetworkAdapter -VMName $vmName | Connect-VMNetworkAdapter -VMSwitch $switch
|
||||
} else {
|
||||
Write-Error 'No internet adapters found'
|
||||
}
|
||||
|
@ -327,7 +366,7 @@ func ConnectVirtualMachineNetworkAdapterToSwitch(vmName string, switchName strin
|
|||
|
||||
var script = `
|
||||
param([string]$vmName,[string]$switchName)
|
||||
Get-VMNetworkAdapter –VMName $vmName | Connect-VMNetworkAdapter –SwitchName $switchName
|
||||
Get-VMNetworkAdapter -VMName $vmName | Connect-VMNetworkAdapter -SwitchName $switchName
|
||||
`
|
||||
|
||||
var ps powershell.PowerShellCmd
|
||||
|
@ -404,28 +443,13 @@ $ip
|
|||
return cmdOut, err
|
||||
}
|
||||
|
||||
func Start(vmName string) error {
|
||||
|
||||
var script = `
|
||||
param([string]$vmName)
|
||||
$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue
|
||||
if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Off) {
|
||||
Start-VM –Name $vmName
|
||||
}
|
||||
`
|
||||
|
||||
var ps powershell.PowerShellCmd
|
||||
err := ps.Run(script, vmName)
|
||||
return err
|
||||
}
|
||||
|
||||
func TurnOff(vmName string) error {
|
||||
|
||||
var script = `
|
||||
param([string]$vmName)
|
||||
$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue
|
||||
if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) {
|
||||
Stop-VM -Name $vmName -TurnOff
|
||||
Stop-VM -Name $vmName -TurnOff -Confirm:$false
|
||||
}
|
||||
`
|
||||
|
||||
|
@ -440,7 +464,7 @@ func ShutDown(vmName string) error {
|
|||
param([string]$vmName)
|
||||
$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue
|
||||
if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) {
|
||||
Stop-VM –Name $vmName
|
||||
Stop-VM -Name $vmName -Confirm:$false
|
||||
}
|
||||
`
|
||||
|
||||
|
@ -448,3 +472,174 @@ if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) {
|
|||
err := ps.Run(script, vmName)
|
||||
return err
|
||||
}
|
||||
|
||||
func TypeScanCodes(vmName string, scanCodes string) error {
|
||||
var script = `
|
||||
param([string]$vmName, [string]$scanCodes)
|
||||
#Requires -Version 3
|
||||
#Requires -RunAsAdministrator
|
||||
|
||||
function Get-VMConsole
|
||||
{
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[string] $VMName
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$vm = Get-CimInstance -ComputerName localhost -Namespace "root\virtualization\v2" -ClassName Msvm_ComputerSystem -ErrorAction Ignore -Verbose:$false | where ElementName -eq $VMName | select -first 1
|
||||
if ($vm -eq $null){
|
||||
Write-Error ("VirtualMachine({0}) is not found!" -f $VMName)
|
||||
}
|
||||
|
||||
$vmKeyboard = $vm | Get-CimAssociatedInstance -ResultClassName "Msvm_Keyboard" -ErrorAction Ignore -Verbose:$false
|
||||
if ($vmKeyboard -eq $null){
|
||||
Write-Error ("VirtualMachine({0}) keyboard class is not found!" -f $VMName)
|
||||
}
|
||||
|
||||
#TODO: It may be better using New-Module -AsCustomObject to return console object?
|
||||
|
||||
#Console object to return
|
||||
$console = [pscustomobject] @{
|
||||
Msvm_ComputerSystem = $vm
|
||||
Msvm_Keyboard = $vmKeyboard
|
||||
}
|
||||
|
||||
#Need to import assembly to use System.Windows.Input.Key
|
||||
Add-Type -AssemblyName WindowsBase
|
||||
|
||||
#region Add Console Members
|
||||
$console | Add-Member -MemberType ScriptMethod -Name TypeText -Value {
|
||||
[OutputType([bool])]
|
||||
param (
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[Parameter(Mandatory)]
|
||||
[string] $AsciiText
|
||||
)
|
||||
$result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeText" -Arguments @{ asciiText = $AsciiText }
|
||||
return (0 -eq $result.ReturnValue)
|
||||
}
|
||||
|
||||
#Define method:TypeCtrlAltDel
|
||||
$console | Add-Member -MemberType ScriptMethod -Name TypeCtrlAltDel -Value {
|
||||
$result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeCtrlAltDel"
|
||||
return (0 -eq $result.ReturnValue)
|
||||
}
|
||||
|
||||
#Define method:TypeKey
|
||||
$console | Add-Member -MemberType ScriptMethod -Name TypeKey -Value {
|
||||
[OutputType([bool])]
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[Windows.Input.Key] $Key,
|
||||
[Windows.Input.ModifierKeys] $ModifierKey = [Windows.Input.ModifierKeys]::None
|
||||
)
|
||||
|
||||
$keyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey($Key)
|
||||
|
||||
switch ($ModifierKey)
|
||||
{
|
||||
([Windows.Input.ModifierKeys]::Control){ $modifierKeyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey([Windows.Input.Key]::LeftCtrl)}
|
||||
([Windows.Input.ModifierKeys]::Alt){ $modifierKeyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey([Windows.Input.Key]::LeftAlt)}
|
||||
([Windows.Input.ModifierKeys]::Shift){ $modifierKeyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey([Windows.Input.Key]::LeftShift)}
|
||||
([Windows.Input.ModifierKeys]::Windows){ $modifierKeyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey([Windows.Input.Key]::LWin)}
|
||||
}
|
||||
|
||||
if ($ModifierKey -eq [Windows.Input.ModifierKeys]::None)
|
||||
{
|
||||
$result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeKey" -Arguments @{ keyCode = $keyCode }
|
||||
}
|
||||
else
|
||||
{
|
||||
$this.Msvm_Keyboard | Invoke-CimMethod -MethodName "PressKey" -Arguments @{ keyCode = $modifierKeyCode }
|
||||
$result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeKey" -Arguments @{ keyCode = $keyCode }
|
||||
$this.Msvm_Keyboard | Invoke-CimMethod -MethodName "ReleaseKey" -Arguments @{ keyCode = $modifierKeyCode }
|
||||
}
|
||||
$result = return (0 -eq $result.ReturnValue)
|
||||
}
|
||||
|
||||
#Define method:Scancodes
|
||||
$console | Add-Member -MemberType ScriptMethod -Name TypeScancodes -Value {
|
||||
[OutputType([bool])]
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[byte[]] $ScanCodes
|
||||
)
|
||||
$result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeScancodes" -Arguments @{ ScanCodes = $ScanCodes }
|
||||
return (0 -eq $result.ReturnValue)
|
||||
}
|
||||
|
||||
#Define method:ExecCommand
|
||||
$console | Add-Member -MemberType ScriptMethod -Name ExecCommand -Value {
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[string] $Command
|
||||
)
|
||||
if ([String]::IsNullOrEmpty($Command)){
|
||||
return
|
||||
}
|
||||
|
||||
$console.TypeText($Command) > $null
|
||||
$console.TypeKey([Windows.Input.Key]::Enter) > $null
|
||||
#sleep -Milliseconds 100
|
||||
}
|
||||
|
||||
#Define method:Dispose
|
||||
$console | Add-Member -MemberType ScriptMethod -Name Dispose -Value {
|
||||
$this.Msvm_ComputerSystem.Dispose()
|
||||
$this.Msvm_Keyboard.Dispose()
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
return $console
|
||||
}
|
||||
|
||||
$vmConsole = Get-VMConsole -VMName $vmName
|
||||
$scanCodesToSend = ''
|
||||
$scanCodes.Split(' ') | %{
|
||||
$scanCode = $_
|
||||
|
||||
if ($scanCode.StartsWith('wait')){
|
||||
$timeToWait = $scanCode.Substring(4)
|
||||
if (!$timeToWait){
|
||||
$timeToWait = "10"
|
||||
}
|
||||
|
||||
Start-Sleep -s $timeToWait
|
||||
|
||||
if ($scanCodesToSend){
|
||||
$scanCodesToSendByteArray = [byte[]]@($scanCodesToSend.Split(' ') | %{"0x$_"})
|
||||
|
||||
$scanCodesToSendByteArray | %{
|
||||
$vmConsole.TypeScancodes($_)
|
||||
}
|
||||
}
|
||||
|
||||
$scanCodesToSend = ''
|
||||
} else {
|
||||
if ($scanCodesToSend){
|
||||
$scanCodesToSend = "$scanCodesToSend $scanCode"
|
||||
} else {
|
||||
$scanCodesToSend = "$scanCode"
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($scanCodesToSend){
|
||||
$scanCodesToSendByteArray = [byte[]]@($scanCodesToSend.Split(' ') | %{"0x$_"})
|
||||
|
||||
$scanCodesToSendByteArray | %{
|
||||
$vmConsole.TypeScancodes($_)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
`
|
||||
|
||||
var ps powershell.PowerShellCmd
|
||||
err := ps.Run(script, vmName, scanCodes)
|
||||
return err
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue