Remove steps that are windows specific
This commit is contained in:
Taliesin Sisson 2015-06-21 12:36:07 +01:00
parent d632f3b574
commit 1fe4c501e4
42 changed files with 3621 additions and 0 deletions

View File

@ -0,0 +1,65 @@
package common
import (
"fmt"
"os"
"path/filepath"
"github.com/mitchellh/packer/packer"
)
// This is the common builder ID to all of these artifacts.
const BuilderId = "mitchellh.hyperv"
// Artifact is the result of running the hyperv builder, namely a set
// of files associated with the resulting machine.
type artifact struct {
dir string
f []string
}
// NewArtifact returns a hyperv artifact containing the files
// in the given directory.
func NewArtifact(dir string) (packer.Artifact, error) {
files := make([]string, 0, 5)
visit := func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
files = append(files, path)
}
return err
}
if err := filepath.Walk(dir, visit); err != nil {
return nil, err
}
return &artifact{
dir: dir,
f: files,
}, nil
}
func (*artifact) BuilderId() string {
return BuilderId
}
func (a *artifact) Files() []string {
return a.f
}
func (*artifact) Id() string {
return "VM"
}
func (a *artifact) String() string {
return fmt.Sprintf("VM files in directory: %s", a.dir)
}
func (a *artifact) State(name string) interface{} {
return nil
}
func (a *artifact) Destroy() error {
return os.RemoveAll(a.dir)
}

View File

@ -0,0 +1,43 @@
package common
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/mitchellh/packer/packer"
)
func TestArtifact_impl(t *testing.T) {
var _ packer.Artifact = new(artifact)
}
func TestNewArtifact(t *testing.T) {
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
err = ioutil.WriteFile(filepath.Join(td, "a"), []byte("foo"), 0644)
if err != nil {
t.Fatalf("err: %s", err)
}
if err := os.Mkdir(filepath.Join(td, "b"), 0755); err != nil {
t.Fatalf("err: %s", err)
}
a, err := NewArtifact(td)
if err != nil {
t.Fatalf("err: %s", err)
}
if a.BuilderId() != BuilderId {
t.Fatalf("bad: %#v", a.BuilderId())
}
if len(a.Files()) != 1 {
t.Fatalf("should length 1: %d", len(a.Files()))
}
}

View File

@ -0,0 +1,11 @@
package common
import (
"testing"
"github.com/mitchellh/packer/template/interpolate"
)
func testConfigTemplate(t *testing.T) *interpolate.Context {
return &interpolate.Context{}
}

View File

@ -0,0 +1,34 @@
// 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
// A driver is able to talk to HyperV and perform certain
// operations with it. Some of the operations on here may seem overly
// specific, but they were built specifically in mind to handle features
// of the HyperV builder for Packer, and to abstract differences in
// versions out of the builder steps, so sometimes the methods are
// extremely specific.
type Driver interface {
// Checks if the VM named is running.
IsRunning(string) (bool, error)
// Start starts a VM specified by the name given.
Start(string) error
// Stop stops a VM specified by the name given.
Stop(string) error
// Verify checks to make sure that this driver should function
// properly. If there is any indication the driver can't function,
// this will return an error.
Verify() error
// Finds the MAC address of the NIC nic0
Mac(string) (string, error)
// Finds the IP address of a VM connected that uses DHCP by its MAC address
IpAddress(string) (string, error)
}

View File

@ -0,0 +1,142 @@
// 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/packer/powershell"
"github.com/mitchellh/packer/powershell/hyperv"
"log"
"runtime"
"strconv"
"strings"
)
type HypervPS4Driver struct {
}
func NewHypervPS4Driver() (Driver, error) {
appliesTo := "Applies to Windows 8.1, Windows PowerShell 4.0, Windows Server 2012 R2 only"
// Check this is Windows
if runtime.GOOS != "windows" {
err := fmt.Errorf("%s", appliesTo)
return nil, err
}
ps4Driver := &HypervPS4Driver{}
if err := ps4Driver.Verify(); err != nil {
return nil, err
}
return ps4Driver, nil
}
func (d *HypervPS4Driver) IsRunning(vmName string) (bool, error) {
return hyperv.IsRunning(vmName)
}
// Start starts a VM specified by the name given.
func (d *HypervPS4Driver) Start(vmName string) error {
return hyperv.Start(vmName)
}
// Stop stops a VM specified by the name given.
func (d *HypervPS4Driver) Stop(vmName string) error {
return hyperv.TurnOff(vmName)
}
func (d *HypervPS4Driver) Verify() error {
if err := d.verifyPSVersion(); err != nil {
return err
}
if err := d.verifyPSHypervModule(); err != nil {
return err
}
if err := d.verifyElevatedMode(); err != nil {
return err
}
return nil
}
// Get mac address for VM.
func (d *HypervPS4Driver) Mac(vmName string) (string, error) {
return hyperv.Mac(vmName)
}
// Get ip address for mac address.
func (d *HypervPS4Driver) IpAddress(mac string) (string, error) {
return hyperv.IpAddress(mac)
}
func (d *HypervPS4Driver) verifyPSVersion() error {
log.Printf("Enter method: %s", "verifyPSVersion")
// check PS is available and is of proper version
versionCmd := "$host.version.Major"
var ps powershell.PowerShellCmd
cmdOut, err := ps.Output(versionCmd)
if err != nil {
return err
}
versionOutput := strings.TrimSpace(string(cmdOut))
log.Printf("%s output: %s", versionCmd, versionOutput)
ver, err := strconv.ParseInt(versionOutput, 10, 32)
if err != nil {
return err
}
if ver < 4 {
err := fmt.Errorf("%s", "Windows PowerShell version 4.0 or higher is expected")
return err
}
return nil
}
func (d *HypervPS4Driver) verifyPSHypervModule() error {
log.Printf("Enter method: %s", "verifyPSHypervModule")
versionCmd := "function foo(){try{ $commands = Get-Command -Module Hyper-V;if($commands.Length -eq 0){return $false} }catch{return $false}; return $true} foo"
var ps powershell.PowerShellCmd
cmdOut, err := ps.Output(versionCmd)
if err != nil {
return err
}
res := strings.TrimSpace(string(cmdOut))
if res == "False" {
err := fmt.Errorf("%s", "PS Hyper-V module is not loaded. Make sure Hyper-V feature is on.")
return err
}
return nil
}
func (d *HypervPS4Driver) verifyElevatedMode() error {
log.Printf("Enter method: %s", "verifyElevatedMode")
isAdmin, _ := powershell.IsCurrentUserAnAdministrator()
if !isAdmin {
err := fmt.Errorf("%s", "Please restart your shell in elevated mode")
return err
}
return nil
}

View File

@ -0,0 +1,28 @@
package common
import (
"fmt"
"os"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/template/interpolate"
)
type OutputConfig struct {
OutputDir string `mapstructure:"output_directory"`
}
func (c *OutputConfig) Prepare(ctx *interpolate.Context, pc *common.PackerConfig) []error {
if c.OutputDir == "" {
c.OutputDir = fmt.Sprintf("output-%s", pc.PackerBuildName)
}
var errs []error
if !pc.PackerForce {
if _, err := os.Stat(c.OutputDir); err == nil {
errs = append(errs, fmt.Errorf(
"Output directory '%s' already exists. It must not exist.", c.OutputDir))
}
}
return errs
}

View File

@ -0,0 +1,45 @@
package common
import (
"github.com/mitchellh/packer/common"
"io/ioutil"
"os"
"testing"
)
func TestOutputConfigPrepare(t *testing.T) {
c := new(OutputConfig)
if c.OutputDir != "" {
t.Fatalf("what: %s", c.OutputDir)
}
pc := &common.PackerConfig{PackerBuildName: "foo"}
errs := c.Prepare(testConfigTemplate(t), pc)
if len(errs) > 0 {
t.Fatalf("err: %#v", errs)
}
if c.OutputDir == "" {
t.Fatal("should have output dir")
}
}
func TestOutputConfigPrepare_exists(t *testing.T) {
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
c := new(OutputConfig)
c.OutputDir = td
pc := &common.PackerConfig{
PackerBuildName: "foo",
PackerForce: false,
}
errs := c.Prepare(testConfigTemplate(t), pc)
if len(errs) != 0 {
t.Fatal("should not have errors")
}
}

View File

@ -0,0 +1,51 @@
package common
import (
"github.com/mitchellh/multistep"
commonssh "github.com/mitchellh/packer/common/ssh"
packerssh "github.com/mitchellh/packer/communicator/ssh"
"golang.org/x/crypto/ssh"
)
func CommHost(state multistep.StateBag) (string, error) {
vmName := state.Get("vmName").(string)
driver := state.Get("driver").(Driver)
mac, err := driver.Mac(vmName)
if err != nil {
return "", err
}
ip, err := driver.IpAddress(mac)
if err != nil {
return "", err
}
return ip, nil
}
func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*ssh.ClientConfig, error) {
return func(state multistep.StateBag) (*ssh.ClientConfig, error) {
auth := []ssh.AuthMethod{
ssh.Password(config.Comm.SSHPassword),
ssh.KeyboardInteractive(
packerssh.PasswordKeyboardInteractive(config.Comm.SSHPassword)),
}
if config.SSHKeyPath != "" {
signer, err := commonssh.FileSigner(config.Comm.SSHPrivateKey)
if err != nil {
return nil, err
}
auth = append(auth, ssh.PublicKeys(signer))
}
return &ssh.ClientConfig{
User: config.Comm.SSHUsername,
Auth: auth,
}, nil
}
}

View File

@ -0,0 +1,29 @@
package common
import (
"time"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/template/interpolate"
)
type SSHConfig struct {
Comm communicator.Config `mapstructure:",squash"`
// These are deprecated, but we keep them around for BC
// TODO(@mitchellh): remove
SSHKeyPath string `mapstructure:"ssh_key_path"`
SSHWaitTimeout time.Duration `mapstructure:"ssh_wait_timeout"`
}
func (c *SSHConfig) Prepare(ctx *interpolate.Context) []error {
// TODO: backwards compatibility, write fixer instead
if c.SSHKeyPath != "" {
c.Comm.SSHPrivateKey = c.SSHKeyPath
}
if c.SSHWaitTimeout != 0 {
c.Comm.SSHTimeout = c.SSHWaitTimeout
}
return c.Comm.Prepare(ctx)
}

View File

@ -0,0 +1,83 @@
// 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"
"strings"
"time"
"log"
powershell "github.com/mitchellh/packer/powershell"
"github.com/mitchellh/packer/powershell/hyperv"
)
type StepConfigureIp struct {
}
func (s *StepConfigureIp) Run(state multistep.StateBag) multistep.StepAction {
// driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
errorMsg := "Error configuring ip address: %s"
vmName := state.Get("vmName").(string)
ui.Say("Configuring ip address...")
count := 60
var duration time.Duration = 1
sleepTime := time.Minute * duration
var ip string
for count != 0 {
cmdOut, err := hyperv.GetVirtualMachineNetworkAdapterAddress(vmName)
if err != nil {
err := fmt.Errorf(errorMsg, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ip = strings.TrimSpace(string(cmdOut))
if ip != "False" {
break;
}
log.Println(fmt.Sprintf("Waiting for another %v minutes...", uint(duration)))
time.Sleep(sleepTime)
count--
}
if(count == 0){
err := fmt.Errorf(errorMsg, "IP address assigned to the adapter is empty")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("ip address is " + ip)
hostName, err := powershell.GetHostName(ip);
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("hostname is " + hostName)
state.Put("ip", ip)
state.Put("hostname", hostName)
return multistep.ActionContinue
}
func (s *StepConfigureIp) Cleanup(state multistep.StateBag) {
// do nothing
}

View File

@ -0,0 +1,54 @@
// 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"
"github.com/mitchellh/packer/powershell/hyperv"
)
const(
vlanId = "1724"
)
type StepConfigureVlan struct {
}
func (s *StepConfigureVlan) Run(state multistep.StateBag) multistep.StepAction {
//config := state.Get("config").(*config)
//driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
errorMsg := "Error configuring vlan: %s"
vmName := state.Get("vmName").(string)
switchName := state.Get("SwitchName").(string)
ui.Say("Configuring vlan...")
err := hyperv.SetNetworkAdapterVlanId(switchName, vlanId)
if err != nil {
err := fmt.Errorf(errorMsg, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
err = hyperv.SetVirtualMachineVlanId(vmName, vlanId)
if err != nil {
err := fmt.Errorf(errorMsg, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepConfigureVlan) Cleanup(state multistep.StateBag) {
//do nothing
}

View File

@ -0,0 +1,107 @@
// 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"
"code.google.com/p/go-uuid/uuid"
"github.com/mitchellh/packer/powershell/hyperv"
)
// This step creates switch for VM.
//
// Produces:
// SwitchName string - The name of the Switch
type StepCreateExternalSwitch struct {
SwitchName string
oldSwitchName string
}
func (s *StepCreateExternalSwitch) Run(state multistep.StateBag) multistep.StepAction {
//driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
errorMsg := "Error createing external switch: %s"
var err error
ui.Say("Creating external switch...")
packerExternalSwitchName := "paes_" + uuid.New()
err = hyperv.CreateExternalVirtualSwitch(vmName, packerExternalSwitchName)
if err != nil {
err := fmt.Errorf("Error creating switch: %s", err)
state.Put(errorMsg, err)
ui.Error(err.Error())
s.SwitchName = "";
return multistep.ActionHalt
}
switchName, err := hyperv.GetVirtualMachineSwitchName(vmName)
if err != nil {
err := fmt.Errorf(errorMsg, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(switchName) == 0 {
err := fmt.Errorf(errorMsg, err)
state.Put("error", "Can't get the VM switch name")
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("External switch name is: '" + switchName + "'")
if(switchName != packerExternalSwitchName){
s.SwitchName = ""
} else {
s.SwitchName = packerExternalSwitchName
s.oldSwitchName = state.Get("SwitchName").(string)
}
// Set the final name in the state bag so others can use it
state.Put("SwitchName", switchName)
return multistep.ActionContinue
}
func (s *StepCreateExternalSwitch) Cleanup(state multistep.StateBag) {
if s.SwitchName == "" {
return
}
//driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
ui.Say("Unregistering and deleting external switch...")
var err error = nil
errMsg := "Error deleting external switch: %s"
// connect the vm to the old switch
if s.oldSwitchName == "" {
ui.Error(fmt.Sprintf(errMsg, "the old switch name is empty"))
return
}
err = hyperv.ConnectVirtualMachineNetworkAdapterToSwitch(vmName, s.oldSwitchName)
if err != nil {
ui.Error(fmt.Sprintf(errMsg, err))
return
}
state.Put("SwitchName", s.oldSwitchName)
err = hyperv.DeleteVirtualSwitch(s.SwitchName)
if err != nil {
ui.Error(fmt.Sprintf(errMsg, err))
}
}

View File

@ -0,0 +1,83 @@
// 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"
"github.com/mitchellh/packer/powershell/hyperv"
)
const (
SwitchTypeInternal = "Internal"
SwitchTypePrivate = "Private"
DefaultSwitchType = SwitchTypeInternal
)
// This step creates switch for VM.
//
// Produces:
// SwitchName string - The name of the Switch
type StepCreateSwitch struct {
// Specifies the name of the switch to be created.
SwitchName string
// Specifies the type of the switch to be created. Allowed values are Internal and Private. To create an External
// virtual switch, specify either the NetAdapterInterfaceDescription or the NetAdapterName parameter, which
// implicitly set the type of the virtual switch to External.
SwitchType string
// Specifies the name of the network adapter to be bound to the switch to be created.
NetAdapterName string
// Specifies the interface description of the network adapter to be bound to the switch to be created.
NetAdapterInterfaceDescription string
createdSwitch bool
}
func (s *StepCreateSwitch) Run(state multistep.StateBag) multistep.StepAction {
//driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
if len(s.SwitchType) == 0 {
s.SwitchType = DefaultSwitchType
}
ui.Say(fmt.Sprintf("Creating switch '%v' if required...", s.SwitchName))
createdSwitch, err := hyperv.CreateVirtualSwitch(s.SwitchName, s.SwitchType)
if err != nil {
err := fmt.Errorf("Error creating switch: %s", err)
state.Put("error", err)
ui.Error(err.Error())
s.SwitchName = "";
return multistep.ActionHalt
}
s.createdSwitch = createdSwitch
if !s.createdSwitch {
ui.Say(fmt.Sprintf(" switch '%v' already exists. Will not delete on cleanup...", s.SwitchName))
}
// Set the final name in the state bag so others can use it
state.Put("SwitchName", s.SwitchName)
return multistep.ActionContinue
}
func (s *StepCreateSwitch) Cleanup(state multistep.StateBag) {
if len(s.SwitchName) == 0 || !s.createdSwitch {
return
}
//driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
ui.Say("Unregistering and deleting switch...")
err := hyperv.DeleteVirtualSwitch(s.SwitchName)
if err != nil {
ui.Error(fmt.Sprintf("Error deleting switch: %s", err))
}
}

View File

@ -0,0 +1,55 @@
// 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"
"io/ioutil"
"os"
)
type StepCreateTempDir struct {
dirPath string
}
func (s *StepCreateTempDir) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating temporary directory...")
tempDir := os.TempDir()
packerTempDir, err := ioutil.TempDir(tempDir, "packerhv")
if err != nil {
err := fmt.Errorf("Error creating temporary directory: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.dirPath = packerTempDir;
state.Put("packerTempDir", packerTempDir)
// ui.Say("packerTempDir = '" + packerTempDir + "'")
return multistep.ActionContinue
}
func (s *StepCreateTempDir) Cleanup(state multistep.StateBag) {
if s.dirPath == "" {
return
}
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting temporary directory...")
err := os.RemoveAll(s.dirPath)
if err != nil {
ui.Error(fmt.Sprintf("Error deleting temporary directory: %s", err))
}
}

View File

@ -0,0 +1,67 @@
// 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"
"strconv"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/powershell/hyperv"
)
// This step creates the actual virtual machine.
//
// Produces:
// VMName string - The name of the VM
type StepCreateVM struct {
VMName string
SwitchName string
RamSizeMB uint
DiskSize uint
}
func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating virtual machine...")
path := state.Get("packerTempDir").(string)
// convert the MB to bytes
ramBytes := int64(s.RamSizeMB * 1024 * 1024)
diskSizeBytes := int64(s.DiskSize * 1024 * 1024)
ram := strconv.FormatInt(ramBytes, 10)
diskSize := strconv.FormatInt(diskSizeBytes, 10)
switchName := s.SwitchName
err := hyperv.CreateVirtualMachine(s.VMName, path, ram, diskSize, switchName)
if err != nil {
err := fmt.Errorf("Error creating virtual machine: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Set the final name in the state bag so others can use it
state.Put("vmName", s.VMName)
return multistep.ActionContinue
}
func (s *StepCreateVM) Cleanup(state multistep.StateBag) {
if s.VMName == "" {
return
}
//driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
ui.Say("Unregistering and deleting virtual machine...")
err := hyperv.DeleteVirtualMachine(s.VMName)
if err != nil {
ui.Error(fmt.Sprintf("Error deleting virtual machine: %s", err))
}
}

View File

@ -0,0 +1,41 @@
// 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"
"github.com/mitchellh/packer/powershell/hyperv"
)
type StepDisableVlan struct {
}
func (s *StepDisableVlan) Run(state multistep.StateBag) multistep.StepAction {
//config := state.Get("config").(*config)
//driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
errorMsg := "Error disabling vlan: %s"
vmName := state.Get("vmName").(string)
switchName := state.Get("SwitchName").(string)
ui.Say("Disabling vlan...")
err := hyperv.UntagVirtualMachineNetworkAdapterVlan(vmName, switchName)
if err != nil {
err := fmt.Errorf(errorMsg, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepDisableVlan) Cleanup(state multistep.StateBag) {
//do nothing
}

View File

@ -0,0 +1,39 @@
// 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"
"github.com/mitchellh/packer/powershell/hyperv"
)
type StepEnableIntegrationService struct {
name string
}
func (s *StepEnableIntegrationService) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
ui.Say("Enabling Integration Service...")
vmName := state.Get("vmName").(string)
s.name = "Guest Service Interface"
err := hyperv.EnableVirtualMachineIntegrationService(vmName, s.name)
if err != nil {
err := fmt.Errorf("Error enabling Integration Service: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepEnableIntegrationService) Cleanup(state multistep.StateBag) {
// do nothing
}

View File

@ -0,0 +1,62 @@
// 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"
"bytes"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"strings"
"log"
)
type StepExecuteOnlineActivation struct {
}
func (s *StepExecuteOnlineActivation) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
comm := state.Get("communicator").(packer.Communicator)
errorMsg := "Error Executing Online Activation: %s"
var remoteCmd packer.RemoteCmd
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
var err error
ui.Say("Executing Online Activation...")
var blockBuffer bytes.Buffer
blockBuffer.WriteString("{ cscript \"$env:SystemRoot/system32/slmgr.vbs\" -ato //nologo }")
remoteCmd.Command = "-ScriptBlock " + blockBuffer.String()
remoteCmd.Stdout = stdout
remoteCmd.Stderr = stderr
err = comm.Start(&remoteCmd)
stderrString := strings.TrimSpace(stderr.String())
stdoutString := strings.TrimSpace(stdout.String())
log.Printf("stdout: %s", stdoutString)
log.Printf("stderr: %s", stderrString)
if len(stderrString) > 0 {
err = fmt.Errorf(errorMsg, stderrString)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(stdoutString)
return multistep.ActionContinue
}
func (s *StepExecuteOnlineActivation) Cleanup(state multistep.StateBag) {
// do nothing
}

View File

@ -0,0 +1,90 @@
// 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"
"bytes"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"strings"
"log"
)
type StepExecuteOnlineActivationFull struct {
Pk string
}
func (s *StepExecuteOnlineActivationFull) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
comm := state.Get("communicator").(packer.Communicator)
errorMsg := "Error Executing Online Activation: %s"
var remoteCmd packer.RemoteCmd
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
var err error
var stderrString string
var stdoutString string
ui.Say("Executing Online Activation Full version...")
var blockBuffer bytes.Buffer
blockBuffer.WriteString("{ cscript \"$env:SystemRoot/system32/slmgr.vbs\" /ipk "+ s.Pk +" //nologo }")
log.Printf("cmd: %s", blockBuffer.String())
remoteCmd.Command = "-ScriptBlock " + blockBuffer.String()
remoteCmd.Stdout = stdout
remoteCmd.Stderr = stderr
err = comm.Start(&remoteCmd)
stderrString = strings.TrimSpace(stderr.String())
stdoutString = strings.TrimSpace(stdout.String())
log.Printf("stdout: %s", stdoutString)
log.Printf("stderr: %s", stderrString)
if len(stderrString) > 0 {
err = fmt.Errorf(errorMsg, stderrString)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// ui.Say(stdoutString)
/*
blockBuffer.Reset()
blockBuffer.WriteString("{ cscript \"$env:SystemRoot/system32/slmgr.vbs\" -ato //nologo }")
log.Printf("cmd: %s", blockBuffer.String())
remoteCmd.Command = "-ScriptBlock " + blockBuffer.String()
err = comm.Start(&remoteCmd)
stderrString = strings.TrimSpace(stderr.String())
stdoutString = strings.TrimSpace(stdout.String())
log.Printf("stdout: %s", stdoutString)
log.Printf("stderr: %s", stderrString)
if len(stderrString) > 0 {
err = fmt.Errorf(errorMsg, stderrString)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(stdoutString)
*/
return multistep.ActionContinue
}
func (s *StepExecuteOnlineActivationFull) Cleanup(state multistep.StateBag) {
// do nothing
}

View File

@ -0,0 +1,74 @@
// 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"
"path/filepath"
"io/ioutil"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/powershell/hyperv"
)
const(
vhdDir string = "Virtual Hard Disks"
vmDir string = "Virtual Machines"
)
type StepExportVm struct {
OutputDir string
}
func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
var err error
var errorMsg string
vmName := state.Get("vmName").(string)
tmpPath := state.Get("packerTempDir").(string)
outputPath := s.OutputDir
// create temp path to export vm
errorMsg = "Error creating temp export path: %s"
vmExportPath , err := ioutil.TempDir(tmpPath, "export")
if err != nil {
err := fmt.Errorf(errorMsg, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Exporting vm...")
err = hyperv.ExportVirtualMachine(vmName, vmExportPath)
if err != nil {
errorMsg = "Error exporting vm: %s"
err := fmt.Errorf(errorMsg, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// copy to output dir
expPath := filepath.Join(vmExportPath,vmName)
ui.Say("Coping to output dir...")
err = hyperv.CopyExportedVirtualMachine(expPath, outputPath, vhdDir, vmDir)
if err != nil {
errorMsg = "Error exporting vm: %s"
err := fmt.Errorf(errorMsg, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepExportVm) Cleanup(state multistep.StateBag) {
// do nothing
}

View File

@ -0,0 +1,59 @@
// 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"
"github.com/mitchellh/packer/powershell/hyperv"
)
type StepMountDvdDrive struct {
RawSingleISOUrl string
path string
}
func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction {
//driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
errorMsg := "Error mounting dvd drive: %s"
vmName := state.Get("vmName").(string)
isoPath := s.RawSingleISOUrl
ui.Say("Mounting dvd drive...")
err := hyperv.MountDvdDrive(vmName, isoPath)
if err != nil {
err := fmt.Errorf(errorMsg, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.path = isoPath
return multistep.ActionContinue
}
func (s *StepMountDvdDrive) Cleanup(state multistep.StateBag) {
if s.path == "" {
return
}
errorMsg := "Error unmounting dvd drive: %s"
vmName := state.Get("vmName").(string)
ui := state.Get("ui").(packer.Ui)
ui.Say("Unmounting dvd drive...")
err := hyperv.UnmountDvdDrive(vmName)
if err != nil {
ui.Error(fmt.Sprintf(errorMsg, err))
}
}

View File

@ -0,0 +1,189 @@
// 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"
"os"
"strings"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/powershell"
"github.com/mitchellh/packer/powershell/hyperv"
"log"
"io"
"io/ioutil"
"path/filepath"
)
const(
FloppyFileName = "assets.vfd"
)
type StepSetUnattendedProductKey struct {
Files []string
ProductKey string
}
func (s *StepSetUnattendedProductKey) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
if s.ProductKey == "" {
ui.Say("No product key specified...")
return multistep.ActionContinue
}
index := -1
for i, value := range s.Files {
if s.caseInsensitiveContains(value, "Autounattend.xml") {
index = i
break
}
}
ui.Say("Setting product key in Autounattend.xml...")
copyOfAutounattend, err := s.copyAutounattend(s.Files[index])
if err != nil {
state.Put("error", fmt.Errorf("Error copying Autounattend.xml: %s", err))
return multistep.ActionHalt
}
powershell.SetUnattendedProductKey(copyOfAutounattend, s.ProductKey)
s.Files[index] = copyOfAutounattend
return multistep.ActionContinue
}
func (s *StepSetUnattendedProductKey) caseInsensitiveContains(str, substr string) bool {
str, substr = strings.ToUpper(str), strings.ToUpper(substr)
return strings.Contains(str, substr)
}
func (s *StepSetUnattendedProductKey) copyAutounattend(path string) (string, error) {
tempdir, err := ioutil.TempDir("", "packer")
if err != nil {
return "", err
}
autounattend := filepath.Join(tempdir, "Autounattend.xml")
f, err := os.Create(autounattend)
if err != nil {
return "", err
}
defer f.Close()
sourceF, err := os.Open(path)
if err != nil {
return "", err
}
defer sourceF.Close()
log.Printf("Copying %s to temp location: %s", path, autounattend)
if _, err := io.Copy(f, sourceF); err != nil {
return "", err
}
return autounattend, nil
}
func (s *StepSetUnattendedProductKey) Cleanup(state multistep.StateBag) {
}
type StepMountFloppydrive struct {
floppyPath string
}
func (s *StepMountFloppydrive) Run(state multistep.StateBag) multistep.StepAction {
// Determine if we even have a floppy disk to attach
var floppyPath string
if floppyPathRaw, ok := state.GetOk("floppy_path"); ok {
floppyPath = floppyPathRaw.(string)
} else {
log.Println("No floppy disk, not attaching.")
return multistep.ActionContinue
}
// Hyper-V is really dumb and can't figure out the format of the file
// without an extension, so we need to add the "vfd" extension to the
// floppy.
floppyPath, err := s.copyFloppy(floppyPath)
if err != nil {
state.Put("error", fmt.Errorf("Error preparing floppy: %s", err))
return multistep.ActionHalt
}
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
ui.Say("Mounting floppy drive...")
err = hyperv.MountFloppyDrive(vmName, floppyPath)
if err != nil {
state.Put("error", fmt.Errorf("Error mounting floppy drive: %s", err))
return multistep.ActionHalt
}
// Track the path so that we can unregister it from Hyper-V later
s.floppyPath = floppyPath
return multistep.ActionContinue}
func (s *StepMountFloppydrive) Cleanup(state multistep.StateBag) {
if s.floppyPath == "" {
return
}
errorMsg := "Error unmounting floppy drive: %s"
vmName := state.Get("vmName").(string)
ui := state.Get("ui").(packer.Ui)
ui.Say("Unmounting floppy drive (cleanup)...")
err := hyperv.UnmountFloppyDrive(vmName)
if err != nil {
ui.Error(fmt.Sprintf(errorMsg, err))
}
err = os.Remove(s.floppyPath)
if err != nil {
ui.Error(fmt.Sprintf(errorMsg, err))
}
}
func (s *StepMountFloppydrive) copyFloppy(path string) (string, error) {
tempdir, err := ioutil.TempDir("", "packer")
if err != nil {
return "", err
}
floppyPath := filepath.Join(tempdir, "floppy.vfd")
f, err := os.Create(floppyPath)
if err != nil {
return "", err
}
defer f.Close()
sourceF, err := os.Open(path)
if err != nil {
return "", err
}
defer sourceF.Close()
log.Printf("Copying floppy to temp location: %s", floppyPath)
if _, err := io.Copy(f, sourceF); err != nil {
return "", err
}
return floppyPath, nil
}

View File

@ -0,0 +1,142 @@
// 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"
"log"
"os"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
powershell "github.com/mitchellh/packer/powershell"
)
type StepMountSecondaryDvdImages struct {
Files [] string
dvdProperties []DvdControllerProperties
}
type DvdControllerProperties struct {
ControllerNumber string
ControllerLocation string
}
func (s *StepMountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
ui.Say("Mounting secondary DVD images...")
vmName := state.Get("vmName").(string)
// should be able to mount up to 60 additional iso images using SCSI
// but Windows would only allow a max of 22 due to available drive letters
// Will Windows assign DVD drives to A: and B: ?
// For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1)
dvdProperties, err := s.mountFiles(vmName);
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Println(fmt.Sprintf("Saving DVD properties %s DVDs", len(dvdProperties)))
state.Put("secondary.dvd.properties", dvdProperties)
return multistep.ActionContinue
}
func (s *StepMountSecondaryDvdImages) Cleanup(state multistep.StateBag) {
}
func (s *StepMountSecondaryDvdImages) mountFiles(vmName string) ([]DvdControllerProperties, error) {
var dvdProperties []DvdControllerProperties
properties, err := s.addAndMountIntegrationServicesSetupDisk(vmName)
if err != nil {
return dvdProperties, err
}
dvdProperties = append(dvdProperties, properties)
for _, value := range s.Files {
properties, err := s.addAndMountDvdDisk(vmName, value)
if err != nil {
return dvdProperties, err
}
dvdProperties = append(dvdProperties, properties)
}
return dvdProperties, nil
}
func (s *StepMountSecondaryDvdImages) addAndMountIntegrationServicesSetupDisk(vmName string) (DvdControllerProperties, error) {
isoPath := os.Getenv("WINDIR") + "\\system32\\vmguest.iso"
properties, err := s.addAndMountDvdDisk(vmName, isoPath)
if err != nil {
return properties, err
}
return properties, nil
}
func (s *StepMountSecondaryDvdImages) addAndMountDvdDisk(vmName string, isoPath string) (DvdControllerProperties, error) {
var properties DvdControllerProperties
var script powershell.ScriptBuilder
powershell := new(powershell.PowerShellCmd)
// get the controller number that the OS install disk is mounted on
script.Reset()
script.WriteLine("param([string]$vmName)")
script.WriteLine("(Get-VMDvdDrive -VMName $vmName).ControllerNumber")
controllerNumber, err := powershell.Output(script.String(), vmName)
if err != nil {
return properties, err
}
script.Reset()
script.WriteLine("param([string]$vmName,[int]$controllerNumber)")
script.WriteLine("Add-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber")
err = powershell.Run(script.String(), vmName, controllerNumber)
if err != nil {
return properties, err
}
// we could try to get the controller location and number in one call, but this way we do not
// need to parse the output
script.Reset()
script.WriteLine("param([string]$vmName)")
script.WriteLine("(Get-VMDvdDrive -VMName $vmName | Where-Object {$_.Path -eq $null}).ControllerLocation")
controllerLocation, err := powershell.Output(script.String(), vmName)
if err != nil {
return properties, err
}
script.Reset()
script.WriteLine("param([string]$vmName,[string]$path,[string]$controllerNumber,[string]$controllerLocation)")
script.WriteLine("Set-VMDvdDrive -VMName $vmName -Path $path -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation")
err = powershell.Run(script.String(), vmName, isoPath, controllerNumber, controllerLocation)
if err != nil {
return properties, err
}
log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v",isoPath, controllerNumber, controllerLocation))
properties.ControllerNumber = controllerNumber
properties.ControllerLocation = controllerLocation
return properties, nil
}

View File

@ -0,0 +1,71 @@
// 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"
"log"
"os"
"path/filepath"
"time"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
// StepOutputDir sets up the output directory by creating it if it does
// not exist, deleting it if it does exist and we're forcing, and cleaning
// it up when we're done with it.
type StepOutputDir struct {
Force bool
Path string
}
func (s *StepOutputDir) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
if _, err := os.Stat(s.Path); err == nil && s.Force {
ui.Say("Deleting previous output directory...")
os.RemoveAll(s.Path)
}
// Create the directory
if err := os.MkdirAll(s.Path, 0755); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
// Make sure we can write in the directory
f, err := os.Create(filepath.Join(s.Path, "_packer_perm_check"))
if err != nil {
err = fmt.Errorf("Couldn't write to output directory: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
f.Close()
os.Remove(f.Name())
return multistep.ActionContinue
}
func (s *StepOutputDir) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if cancelled || halted {
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting output directory...")
for i := 0; i < 5; i++ {
err := os.RemoveAll(s.Path)
if err == nil {
break
}
log.Printf("Error removing output dir: %s", err)
time.Sleep(2 * time.Second)
}
}
}

View File

@ -0,0 +1,114 @@
// 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"
// "net"
"log"
"os/exec"
"strings"
"bytes"
)
const port string = "13000"
type StepPollingInstalation struct {
step int
}
func (s *StepPollingInstalation) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
errorMsg := "Error polling VM: %s"
vmIp := state.Get("ip").(string)
ui.Say("Start polling VM to check the installation is complete...")
/*
count := 30
var minutes time.Duration = 1
sleepMin := time.Minute * minutes
host := vmIp + ":" + port
timeoutSec := time.Second * 15
for count > 0 {
ui.Say(fmt.Sprintf("Connecting vm (%s)...", host ))
conn, err := net.DialTimeout("tcp", host, timeoutSec)
if err == nil {
ui.Say("Done!")
conn.Close()
break;
}
log.Println(err)
ui.Say(fmt.Sprintf("Waiting more %v minutes...", uint(minutes)))
time.Sleep(sleepMin)
count--
}
if count == 0 {
err := fmt.Errorf(errorMsg, "a signal from vm was not received in a given time period ")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
*/
host := "'" + vmIp + "'," + port
var blockBuffer bytes.Buffer
blockBuffer.WriteString("Invoke-Command -scriptblock {function foo(){try{$client=New-Object System.Net.Sockets.TcpClient(")
blockBuffer.WriteString(host)
blockBuffer.WriteString(") -ErrorAction SilentlyContinue;if($client -eq $null){return $false}}catch{return $false}return $true} foo}")
count := 60
var duration time.Duration = 20
sleepTime := time.Second * duration
var res string
for count > 0 {
log.Println(fmt.Sprintf("Connecting vm (%s)...", host ))
cmd := exec.Command("powershell", blockBuffer.String())
cmdOut, err := cmd.Output()
if err != nil {
err := fmt.Errorf(errorMsg, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
res = strings.TrimSpace(string(cmdOut))
if res != "False" {
ui.Say("Signal was received from the VM")
// Sleep before starting provision
time.Sleep(time.Second*30)
break;
}
log.Println(fmt.Sprintf("Slipping for more %v seconds...", uint(duration)))
time.Sleep(sleepTime)
count--
}
if count == 0 {
err := fmt.Errorf(errorMsg, "a signal from vm was not received in a given time period ")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("The installation complete")
return multistep.ActionContinue
}
func (s *StepPollingInstalation) Cleanup(state multistep.StateBag) {
}

View File

@ -0,0 +1,45 @@
// 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 StepRebootVm struct {
}
func (s *StepRebootVm) Run(state multistep.StateBag) multistep.StepAction {
//driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
errorMsg := "Error rebooting vm: %s"
vmName := state.Get("vmName").(string)
ui.Say("Rebooting vm...")
err := hyperv.RestartVirtualMachine(vmName)
if err != nil {
err := fmt.Errorf(errorMsg, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Waiting the VM to complete rebooting (2 minutes)...")
sleepTime := time.Minute * 2
time.Sleep(sleepTime)
return multistep.ActionContinue
}
func (s *StepRebootVm) Cleanup(state multistep.StateBag) {
// do nothing
}

View File

@ -0,0 +1,141 @@
package common
import (
"bytes"
"errors"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"time"
)
type ShutdownConfig struct {
ShutdownCommand string `mapstructure:"shutdown_command"`
RawShutdownTimeout string `mapstructure:"shutdown_timeout"`
ShutdownTimeout time.Duration ``
}
func (c *ShutdownConfig) Prepare(t *packer.ConfigTemplate) []error {
if c.RawShutdownTimeout == "" {
c.RawShutdownTimeout = "5m"
}
templates := map[string]*string{
"shutdown_command": &c.ShutdownCommand,
"shutdown_timeout": &c.RawShutdownTimeout,
}
errs := make([]error, 0)
for n, ptr := range templates {
var err error
*ptr, err = t.Process(*ptr, nil)
if err != nil {
errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
var err error
c.ShutdownTimeout, err = time.ParseDuration(c.RawShutdownTimeout)
if err != nil {
errs = append(errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err))
}
return errs
}
// This step shuts down the machine. It first attempts to do so gracefully,
// but ultimately forcefully shuts it down if that fails.
//
// Uses:
// communicator packer.Communicator
// dir OutputDir
// driver Driver
// ui packer.Ui
// vmx_path string
//
// Produces:
// <nothing>
type StepShutdown struct {
Command string
Timeout time.Duration
}
func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction {
comm := state.Get("communicator").(packer.Communicator)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
if s.Command != "" {
ui.Say("Gracefully halting virtual machine...")
log.Printf("Executing shutdown command: %s", s.Command)
var stdout, stderr bytes.Buffer
cmd := &packer.RemoteCmd{
Command: s.Command,
Stdout: &stdout,
Stderr: &stderr,
}
if err := comm.Start(cmd); err != nil {
err := fmt.Errorf("Failed to send shutdown command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Wait for the command to run
cmd.Wait()
// If the command failed to run, notify the user in some way.
if cmd.ExitStatus != 0 {
state.Put("error", fmt.Errorf(
"Shutdown command has non-zero exit status.\n\nStdout: %s\n\nStderr: %s",
stdout.String(), stderr.String()))
return multistep.ActionHalt
}
if stdout.Len() > 0 {
log.Printf("Shutdown stdout: %s", stdout.String())
}
if stderr.Len() > 0 {
log.Printf("Shutdown stderr: %s", stderr.String())
}
// Wait for the machine to actually shut down
log.Printf("Waiting max %s for shutdown to complete", s.Timeout)
shutdownTimer := time.After(s.Timeout)
for {
running, _ := driver.IsRunning(vmName)
if !running {
break
}
select {
case <-shutdownTimer:
err := errors.New("Timeout while waiting for machine to shut down.")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
default:
time.Sleep(150 * time.Millisecond)
}
}
} else {
ui.Say("Forcibly halting virtual machine...")
if err := driver.Stop(vmName); err != nil {
err := fmt.Errorf("Error stopping VM: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
log.Println("VM shut down.")
return multistep.ActionContinue
}
func (s *StepShutdown) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,32 @@
// 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 StepSleep struct {
Minutes time.Duration
ActionName string
}
func (s *StepSleep) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
if(len(s.ActionName)>0){
ui.Say(s.ActionName + "! Waiting for "+ fmt.Sprintf("%v",uint(s.Minutes)) + " minutes to let the action to complete...")
}
time.Sleep(time.Minute*s.Minutes);
return multistep.ActionContinue
}
func (s *StepSleep) Cleanup(state multistep.StateBag) {
}

View File

@ -0,0 +1,48 @@
// 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) {
}

View File

@ -0,0 +1,38 @@
// 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"
"github.com/mitchellh/packer/powershell/hyperv"
)
type StepUnmountDvdDrive struct {
}
func (s *StepUnmountDvdDrive) Run(state multistep.StateBag) multistep.StepAction {
//driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
ui.Say("Unmounting dvd drive...")
err := hyperv.UnmountDvdDrive(vmName)
if err != nil {
err := fmt.Errorf("Error unmounting dvd drive: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepUnmountDvdDrive) Cleanup(state multistep.StateBag) {
}

View File

@ -0,0 +1,39 @@
// 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"
"github.com/mitchellh/packer/powershell/hyperv"
)
type StepUnmountFloppyDrive struct {
}
func (s *StepUnmountFloppyDrive) Run(state multistep.StateBag) multistep.StepAction {
//driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
errorMsg := "Error Unmounting floppy drive: %s"
vmName := state.Get("vmName").(string)
ui.Say("Unmounting floppy drive (Run)...")
err := hyperv.UnmountFloppyDrive(vmName)
if err != nil {
err := fmt.Errorf(errorMsg, err)
state.Put("error", err)
ui.Error(err.Error())
}
return multistep.ActionContinue
}
func (s *StepUnmountFloppyDrive) Cleanup(state multistep.StateBag) {
// do nothing
}

View File

@ -0,0 +1,52 @@
// 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"
"log"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
powershell "github.com/mitchellh/packer/powershell"
)
type StepUnmountSecondaryDvdImages struct {
}
func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
ui.Say("Unmounting Integration Services Setup Disk...")
vmName := state.Get("vmName").(string)
// todo: should this message say removing the dvd?
dvdProperties := state.Get("secondary.dvd.properties").([]DvdControllerProperties)
log.Println(fmt.Sprintf("Found DVD properties %s", len(dvdProperties)))
for _, dvdProperty := range dvdProperties {
controllerNumber := dvdProperty.ControllerNumber
controllerLocation := dvdProperty.ControllerLocation
var script powershell.ScriptBuilder
powershell := new(powershell.PowerShellCmd)
script.WriteLine("param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation)")
script.WriteLine("Remove-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation")
err := powershell.Run(script.String(), vmName, controllerNumber, controllerLocation)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepUnmountSecondaryDvdImages) Cleanup(state multistep.StateBag) {
}

View File

@ -0,0 +1,110 @@
// 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"
"os"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
powershell "github.com/mitchellh/packer/powershell"
)
type StepUpdateIntegrationServices struct {
Username string
Password string
newDvdDriveProperties dvdDriveProperties
}
type dvdDriveProperties struct {
ControllerNumber string
ControllerLocation string
}
func (s *StepUpdateIntegrationServices) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
ui.Say("Mounting Integration Services Setup Disk...")
_, err := s.mountIntegrationServicesSetupDisk(vmName);
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// dvdDriveLetter, err := s.getDvdDriveLetter(vmName)
// if err != nil {
// state.Put("error", err)
// ui.Error(err.Error())
// return multistep.ActionHalt
// }
// setup := dvdDriveLetter + ":\\support\\"+osArchitecture+"\\setup.exe /quiet /norestart"
// ui.Say("Run: " + setup)
return multistep.ActionContinue
}
func (s *StepUpdateIntegrationServices) Cleanup(state multistep.StateBag) {
vmName := state.Get("vmName").(string)
var script powershell.ScriptBuilder
script.WriteLine("param([string]$vmName)")
script.WriteLine("Set-VMDvdDrive -VMName $vmName -Path $null")
powershell := new(powershell.PowerShellCmd)
_ = powershell.Run(script.String(), vmName)
}
func (s *StepUpdateIntegrationServices) mountIntegrationServicesSetupDisk(vmName string) (dvdDriveProperties, error) {
var dvdProperties dvdDriveProperties
var script powershell.ScriptBuilder
script.WriteLine("param([string]$vmName)")
script.WriteLine("Add-VMDvdDrive -VMName $vmName")
powershell := new(powershell.PowerShellCmd)
err := powershell.Run(script.String(), vmName)
if err != nil {
return dvdProperties, err
}
script.Reset()
script.WriteLine("param([string]$vmName)")
script.WriteLine("(Get-VMDvdDrive -VMName $vmName | Where-Object {$_.Path -eq $null}).ControllerLocation")
controllerLocation, err := powershell.Output(script.String(), vmName)
if err != nil {
return dvdProperties, err
}
script.Reset()
script.WriteLine("param([string]$vmName)")
script.WriteLine("(Get-VMDvdDrive -VMName $vmName | Where-Object {$_.Path -eq $null}).ControllerNumber")
controllerNumber, err := powershell.Output(script.String(), vmName)
if err != nil {
return dvdProperties, err
}
isoPath := os.Getenv("WINDIR") + "\\system32\\vmguest.iso"
script.Reset()
script.WriteLine("param([string]$vmName,[string]$path,[string]$controllerNumber,[string]$controllerLocation)")
script.WriteLine("Set-VMDvdDrive -VMName $vmName -Path $path -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation")
err = powershell.Run(script.String(), vmName, isoPath, controllerNumber, controllerLocation)
if err != nil {
return dvdProperties, err
}
dvdProperties.ControllerNumber = controllerNumber
dvdProperties.ControllerLocation = controllerLocation
return dvdProperties, err
}

View File

@ -0,0 +1,130 @@
// 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"
"strings"
"strconv"
"time"
powershell "github.com/mitchellh/packer/powershell"
)
const (
SleepSeconds = 10
)
type StepWaitForPowerOff struct {
}
func (s *StepWaitForPowerOff) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
ui.Say("Waiting for vm to be powered down...")
// unless the person has a super fast disk, it should take at least 5 minutes
// for the install and post-install operations to take. Wait 5 minutes to
// avoid hammering on getting VM status via PowerShell
time.Sleep(time.Second * 300);
var script powershell.ScriptBuilder
script.WriteLine("param([string]$vmName)")
script.WriteLine("(Get-VM -Name $vmName).State -eq [Microsoft.HyperV.PowerShell.VMState]::Off")
isOffScript := script.String()
for {
powershell := new(powershell.PowerShellCmd)
cmdOut, err := powershell.Output(isOffScript, vmName);
if err != nil {
err := fmt.Errorf("Error checking VM's state: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if cmdOut == "True" {
break
} else {
time.Sleep(time.Second * SleepSeconds);
}
}
return multistep.ActionContinue
}
func (s *StepWaitForPowerOff) Cleanup(state multistep.StateBag) {
}
type StepWaitForInstallToComplete struct {
ExpectedRebootCount uint
ActionName string
}
func (s *StepWaitForInstallToComplete) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
if(len(s.ActionName)>0){
ui.Say(fmt.Sprintf("%v ! Waiting for VM to reboot %v times...",s.ActionName, s.ExpectedRebootCount))
}
var rebootCount uint
var lastUptime uint64
var script powershell.ScriptBuilder
script.WriteLine("param([string]$vmName)")
script.WriteLine("(Get-VM -Name $vmName).Uptime.TotalSeconds")
uptimeScript := script.String()
for rebootCount < s.ExpectedRebootCount {
powershell := new(powershell.PowerShellCmd)
cmdOut, err := powershell.Output(uptimeScript, vmName);
if err != nil {
err := fmt.Errorf("Error checking uptime: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
uptime, _ := strconv.ParseUint(strings.TrimSpace(string(cmdOut)), 10, 64)
if uint64(uptime) < lastUptime {
rebootCount++
ui.Say(fmt.Sprintf("%v -> Detected reboot %v after %v seconds...", s.ActionName, rebootCount, lastUptime))
}
lastUptime = uptime
if (rebootCount < s.ExpectedRebootCount) {
time.Sleep(time.Second * SleepSeconds);
}
}
return multistep.ActionContinue
}
func (s *StepWaitForInstallToComplete) Cleanup(state multistep.StateBag) {
}
type StepWaitForWinRm struct {
}
func (s *StepWaitForWinRm) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
//vmName := state.Get("vmName").(string)
ui.Say("Waiting for WinRM to be ready...")
return multistep.ActionContinue
}
func (s *StepWaitForWinRm) Cleanup(state multistep.StateBag) {
}

View File

@ -0,0 +1,391 @@
// 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 iso
import (
"code.google.com/p/go-uuid/uuid"
"errors"
"fmt"
"github.com/mitchellh/multistep"
hypervcommon "github.com/mitchellh/packer/builder/hyperv/common"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/helper/communicator"
powershell "github.com/mitchellh/packer/powershell"
"github.com/mitchellh/packer/powershell/hyperv"
"log"
"os"
"regexp"
"strings"
"time"
)
const (
DefaultDiskSize = 127 * 1024 // 127GB
MinDiskSize = 10 * 1024 // 10GB
MaxDiskSize = 65536 * 1024 // 64TB
DefaultRamSize = 1024 // 1GB
MinRamSize = 512 // 512MB
MaxRamSize = 32768 // 32GB
LowRam = 512 // 512MB
DefaultUsername = "vagrant"
DefaultPassword = "vagrant"
)
// Builder implements packer.Builder and builds the actual Hyperv
// images.
type Builder struct {
config config
runner multistep.Runner
}
type config struct {
// 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 computer memory in the VM.
// By default, this is 1024 (about 1 GB).
RamSizeMB uint `mapstructure:"ram_size_mb"`
// A list of files to place onto a floppy disk that is attached when the
// VM is booted. This is most useful for unattended Windows installs,
// which look for an Autounattend.xml file on removable media. By default,
// no floppy will be attached. All files listed in this setting get
// placed into the root directory of the floppy and the floppy is attached
// as the first floppy device. Currently, no support exists for creating
// sub-directories on the floppy. Wildcard characters (*, ?, and [])
// are allowed. Directory names are also allowed, which will add all
// the files found in the directory to the floppy.
FloppyFiles []string `mapstructure:"floppy_files"`
//
SecondaryDvdImages []string `mapstructure:"secondary_iso_images"`
// The checksum for the OS ISO file. Because ISO files are so large,
// this is required and Packer will verify it prior to booting a virtual
// machine with the ISO attached. The type of the checksum is specified
// with iso_checksum_type, documented below.
ISOChecksum string `mapstructure:"iso_checksum"`
// The type of the checksum specified in iso_checksum. Valid values are
// "none", "md5", "sha1", "sha256", or "sha512" currently. While "none"
// will skip checksumming, this is not recommended since ISO files are
// generally large and corruption does happen from time to time.
ISOChecksumType string `mapstructure:"iso_checksum_type"`
// A URL to the ISO containing the installation image. This URL can be
// either an HTTP URL or a file URL (or path to a file). If this is an
// HTTP URL, Packer will download it and cache it between runs.
RawSingleISOUrl string `mapstructure:"iso_url"`
// Multiple URLs for the ISO to download. Packer will try these in order.
// If anything goes wrong attempting to download or while downloading a
// single URL, it will move on to the next. All URLs must point to the
// same file (same checksum). By default this is empty and iso_url is
// used. Only one of iso_url or iso_urls can be specified.
ISOUrls []string `mapstructure:"iso_urls"`
// This is the name of the new virtual machine.
// By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build.
VMName string `mapstructure:"vm_name"`
common.PackerConfig `mapstructure:",squash"`
hypervcommon.OutputConfig `mapstructure:",squash"`
hypervcommon.SSHConfig `mapstructure:",squash"`
hypervcommon.ShutdownConfig `mapstructure:",squash"`
SwitchName string `mapstructure:"switch_name"`
Communicator string `mapstructure:"communicator"`
// The time in seconds to wait for the virtual machine to report an IP address.
// This defaults to 120 seconds. This may have to be increased if your VM takes longer to boot.
IPAddressTimeout time.Duration `mapstructure:"ip_address_timeout"`
SSHWaitTimeout time.Duration
tpl *packer.ConfigTemplate
}
// Prepare processes the build configuration parameters.
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
md, err := common.DecodeConfig(&b.config, raws...)
if err != nil {
return nil, err
}
b.config.tpl, err = packer.NewConfigTemplate()
if err != nil {
return nil, err
}
log.Println(fmt.Sprintf("%s: %v", "PackerUserVars", b.config.PackerUserVars))
b.config.tpl.UserVars = b.config.PackerUserVars
// Accumulate any errors and warnings
errs := common.CheckUnusedConfig(md)
errs = packer.MultiErrorAppend(errs, b.config.OutputConfig.Prepare(b.config.tpl, &b.config.PackerConfig)...)
errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...)
errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(b.config.tpl)...)
warnings := make([]string, 0)
err = b.checkDiskSize()
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
err = b.checkRamSize()
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
if b.config.VMName == "" {
b.config.VMName = fmt.Sprintf("pvm_%s", uuid.New())
}
if b.config.SwitchName == "" {
// no switch name, try to get one attached to a online network adapter
onlineSwitchName, err := hyperv.GetExternalOnlineVirtualSwitch()
if onlineSwitchName == "" || err != nil {
b.config.SwitchName = fmt.Sprintf("pis_%s", uuid.New())
} else {
b.config.SwitchName = onlineSwitchName
}
}
log.Println(fmt.Sprintf("Using switch %s", b.config.SwitchName))
if b.config.Communicator == "" {
b.config.Communicator = "ssh"
} else if b.config.Communicator == "ssh" || b.config.Communicator == "winrm" {
// good
} else {
err = errors.New("communicator must be either ssh or winrm")
errs = packer.MultiErrorAppend(errs, err)
}
// Errors
templates := map[string]*string{
"iso_url": &b.config.RawSingleISOUrl
}
for n, ptr := range templates {
var err error
*ptr, err = b.config.tpl.Process(*ptr, nil)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName))
log.Println(fmt.Sprintf("%s: %v", "SwitchName", b.config.SwitchName))
log.Println(fmt.Sprintf("%s: %v", "Communicator", b.config.Communicator))
if b.config.RawSingleISOUrl == "" {
errs = packer.MultiErrorAppend(errs, errors.New("iso_url: The option can't be missed and a path must be specified."))
} else if _, err := os.Stat(b.config.RawSingleISOUrl); err != nil {
errs = packer.MultiErrorAppend(errs, errors.New("iso_url: Check the path is correct"))
}
log.Println(fmt.Sprintf("%s: %v", "RawSingleISOUrl", b.config.RawSingleISOUrl))
b.config.SSHWaitTimeout, err = time.ParseDuration(b.config.RawSSHWaitTimeout)
// Warnings
warning := b.checkHostAvailableMemory()
if warning != "" {
warnings = appendWarnings(warnings, warning)
}
if b.config.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 errs != nil && len(errs.Errors) > 0 {
return warnings, errs
}
return warnings, nil
}
// Run executes a Packer build and returns a packer.Artifact representing
// a Hyperv appliance.
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
// Create the driver that we'll use to communicate with Hyperv
driver, err := hypervcommon.NewHypervPS4Driver()
if err != nil {
return nil, fmt.Errorf("Failed creating Hyper-V driver: %s", err)
}
// Set up the state.
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("driver", driver)
state.Put("hook", hook)
state.Put("ui", ui)
steps := []multistep.Step{
&hypervcommon.StepCreateTempDir{},
&hypervcommon.StepOutputDir{
Force: b.config.PackerForce,
Path: b.config.OutputDir,
},
&common.StepCreateFloppy{
Files: b.config.FloppyFiles,
},
&hypervcommon.StepCreateSwitch{
SwitchName: b.config.SwitchName,
},
&hypervcommon.StepCreateVM{
VMName: b.config.VMName,
SwitchName: b.config.SwitchName,
RamSizeMB: b.config.RamSizeMB,
DiskSize: b.config.DiskSize,
},
&hypervcommon.StepEnableIntegrationService{},
&hypervcommon.StepMountDvdDrive{
RawSingleISOUrl: b.config.RawSingleISOUrl,
},
&hypervcommon.StepMountFloppydrive{},
&hypervcommon.StepMountSecondaryDvdImages{},
&hypervcommon.StepStartVm{
Reason: "OS installation",
},
// wait for the vm to be powered off
&hypervcommon.StepWaitForPowerOff{},
// remove the integration services dvd drive
// after we power down
&hypervcommon.StepUnmountSecondaryDvdImages{},
//
&hypervcommon.StepStartVm{
Reason: "provisioning",
StartUpDelay: 60,
},
// configure the communicator ssh, winrm
&communicator.StepConnect{
Config: &b.config.SSHConfig.Comm,
Host: hypervcommon.CommHost,
SSHConfig: hypervcommon.SSHConfigFunc(b.config.SSHConfig),
SSHPort: hypervcommon.SSHPort,
},
// provision requires communicator to be setup
&common.StepProvision{},
&hypervcommon.StepUnmountFloppyDrive{},
&hypervcommon.StepUnmountDvdDrive{},
&hypervcommon.StepShutdown{
Command: b.config.ShutdownCommand,
Timeout: b.config.ShutdownTimeout,
},
&hypervcommon.StepExportVm{
OutputDir: b.config.OutputDir,
},
// the clean up actions for each step will be executed reverse order
}
// Run the steps.
if b.config.PackerDebug {
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner.Run(state)
// Report any errors.
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If we were interrupted or cancelled, then just exit.
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("Build was cancelled.")
}
if _, ok := state.GetOk(multistep.StateHalted); ok {
return nil, errors.New("Build was halted.")
}
return hypervcommon.NewArtifact(b.config.OutputDir)
}
// Cancel.
func (b *Builder) Cancel() {
if b.runner != nil {
log.Println("Cancelling the step runner...")
b.runner.Cancel()
}
}
func appendWarnings(slice []string, data ...string) []string {
m := len(slice)
n := m + len(data)
if n > cap(slice) { // if necessary, reallocate
// allocate double what's needed, for future growth.
newSlice := make([]string, (n+1)*2)
copy(newSlice, slice)
slice = newSlice
}
slice = slice[0:n]
copy(slice[m:n], data)
return slice
}
func (b *Builder) checkDiskSize() error {
if b.config.DiskSize == 0 {
b.config.DiskSize = DefaultDiskSize
}
log.Println(fmt.Sprintf("%s: %v", "DiskSize", b.config.DiskSize))
if b.config.DiskSize < MinDiskSize {
return fmt.Errorf("disk_size_gb: Windows server requires disk space >= %v GB, but defined: %v", MinDiskSize, b.config.DiskSize/1024)
} else if b.config.DiskSize > MaxDiskSize {
return fmt.Errorf("disk_size_gb: Windows server requires disk space <= %v GB, but defined: %v", MaxDiskSize, b.config.DiskSize/1024)
}
return nil
}
func (b *Builder) checkRamSize() error {
if b.config.RamSizeMB == 0 {
b.config.RamSizeMB = DefaultRamSize
}
log.Println(fmt.Sprintf("%s: %v", "RamSize", b.config.RamSizeMB))
if b.config.RamSizeMB < MinRamSize {
return fmt.Errorf("ram_size_mb: Windows server requires memory size >= %v MB, but defined: %v", MinRamSize, b.config.RamSizeMB)
} else if b.config.RamSizeMB > MaxRamSize {
return fmt.Errorf("ram_size_mb: Windows server requires memory size <= %v MB, but defined: %v", MaxRamSize, b.config.RamSizeMB)
}
return nil
}
func (b *Builder) checkHostAvailableMemory() string {
freeMB := powershell.GetHostAvailableMemory()
if (freeMB - float64(b.config.RamSizeMB)) < LowRam {
return fmt.Sprintf("Hyper-V might fail to create a VM if there is not enough free memory in the system.")
}
return ""
}

View File

@ -0,0 +1,3 @@
go build
cp packer-builder-hyperv-iso.exe ../../../bin/

View File

@ -0,0 +1,19 @@
// 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 main
import (
"github.com/mitchellh/packer/builder/hyperv/iso"
"github.com/mitchellh/packer/plugin"
)
func main() {
server, err := plugin.Server()
if err != nil {
panic(err)
}
server.RegisterBuilder(new(iso.Builder))
server.Serve()
}

View File

@ -0,0 +1,5 @@
// 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 main

433
powershell/hyperv/hyperv.go Normal file
View File

@ -0,0 +1,433 @@
package hyperv
import (
"github.com/mitchellh/packer/powershell"
"strings"
)
func GetVirtualMachineNetworkAdapterAddress(vmName string) (string, error) {
var script = `
param([string]$vmName, [int]$addressIndex)
try {
$adapter = Get-VMNetworkAdapter -VMName $vmName -ErrorAction SilentlyContinue
$ip = $adapter.IPAddresses[$addressIndex]
if($ip -eq $null) {
return $false
}
} catch {
return $false
}
$ip
`
var ps powershell.PowerShellCmd
cmdOut, err := ps.Output(script, vmName, "0")
return cmdOut, err
}
func MountDvdDrive(vmName string, path string) error {
var script = `
param([string]$vmName,[string]$path)
Set-VMDvdDrive -VMName $vmName -Path $path
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName, path)
return err
}
func UnmountDvdDrive(vmName string) error {
var script = `
param([string]$vmName)
Set-VMDvdDrive -VMName $vmName -Path $null
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName)
return err
}
func MountFloppyDrive(vmName string, path string) error {
var script = `
param([string]$vmName, [string]$path)
Set-VMFloppyDiskDrive -VMName $vmName -Path $path
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName, path)
return err
}
func UnmountFloppyDrive(vmName string) error {
var script = `
param([string]$vmName)
Set-VMFloppyDiskDrive -VMName $vmName -Path $null
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName)
return err
}
func CreateVirtualMachine(vmName string, path string, ram string, diskSize string, switchName string) error {
var script = `
param([string]$vmName, [string]$path, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName)
$vhdx = $vmName + '.vhdx'
$vhdPath = Join-Path -Path $path -ChildPath $vhdx
New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName, path, ram, diskSize, switchName)
return err
}
func DeleteVirtualMachine(vmName string) error {
var script = `
param([string]$vmName)
Remove-VM -Name $vmName -Force
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName)
return err
}
func ExportVirtualMachine(vmName string, path string) error {
var script = `
param([string]$vmName, [string]$path)
Export-VM -Name $vmName -Path $path
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName, path)
return err
}
func CopyExportedVirtualMachine(expPath string, outputPath string, vhdDir string, vmDir string) error {
var script = `
param([string]$srcPath, [string]$dstPath, [string]$vhdDirName, [string]$vmDir)
Copy-Item -Path $srcPath/$vhdDirName -Destination $dstPath -recurse
Copy-Item -Path $srcPath/$vmDir -Destination $dstPath
Copy-Item -Path $srcPath/$vmDir/*.xml -Destination $dstPath/$vmDir
`
var ps powershell.PowerShellCmd
err := ps.Run(script, expPath, outputPath, vhdDir, vmDir)
return err
}
func CreateVirtualSwitch(switchName string, switchType string) (bool, error) {
var script = `
param([string]$switchName,[string]$switchType)
$switches = Get-VMSwitch -Name $switchName -ErrorAction SilentlyContinue
if ($switches.Count -eq 0) {
New-VMSwitch -Name $switchName -SwitchType $switchType
return $true
}
return $false
`
var ps powershell.PowerShellCmd
cmdOut, err := ps.Output(script, switchName, switchType)
var created = strings.TrimSpace(cmdOut) == "True"
return created, err
}
func DeleteVirtualSwitch(switchName string) error {
var script = `
param([string]$switchName)
$switch = Get-VMSwitch -Name $switchName -ErrorAction SilentlyContinue
if ($switch -ne $null) {
$switch | Remove-VMSwitch -Force
}
`
var ps powershell.PowerShellCmd
err := ps.Run(script, switchName)
return err
}
func StartVirtualMachine(vmName string) error {
var script = `
param([string]$vmName)
Start-VM -Name $vmName
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName)
return err
}
func RestartVirtualMachine(vmName string) error {
var script = `
param([string]$vmName)
Restart-VM $vmName -Force
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName)
return err
}
func StopVirtualMachine(vmName string) error {
var script = `
param([string]$vmName)
$vm = Get-VM -Name $vmName
if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) {
Stop-VM -VM $vm
}
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName)
return err
}
func EnableVirtualMachineIntegrationService(vmName string, integrationServiceName string) error {
var script = `
param([string]$vmName,[string]$integrationServiceName)
Enable-VMIntegrationService -VMName $vmName -Name $integrationServiceName
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName, integrationServiceName)
return err
}
func SetNetworkAdapterVlanId(switchName string, vlanId string) error {
var script = `
param([string]$networkAdapterName,[string]$vlanId)
Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName $networkAdapterName -Access -VlanId $vlanId
`
var ps powershell.PowerShellCmd
err := ps.Run(script, switchName, vlanId)
return err
}
func SetVirtualMachineVlanId(vmName string, vlanId string) error {
var script = `
param([string]$vmName,[string]$vlanId)
Set-VMNetworkAdapterVlan -VMName $vmName -Access -VlanId $vlanId
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName, vlanId)
return err
}
func GetExternalOnlineVirtualSwitch() (string, error) {
var script = `
$adapters = Get-NetAdapter -Physical -ErrorAction SilentlyContinue | Where-Object { $_.Status -eq 'Up' } | Sort-Object -Descending -Property Speed
foreach ($adapter in $adapters) {
$switch = Get-VMSwitch -SwitchType External | Where-Object { $_.NetAdapterInterfaceDescription -eq $adapter.InterfaceDescription }
if ($switch -ne $null) {
$switch.Name
break
}
}
`
var ps powershell.PowerShellCmd
cmdOut, err := ps.Output(script)
if err != nil {
return "", err
}
var switchName = strings.TrimSpace(cmdOut)
return switchName, nil
}
func CreateExternalVirtualSwitch(vmName string, switchName string) error {
var script = `
param([string]$vmName,[string]$switchName)
$switch = $null
$names = @('ethernet','wi-fi','lan')
$adapters = foreach ($name in $names) {
Get-NetAdapter -Physical -Name $name -ErrorAction SilentlyContinue | where status -eq 'up'
}
foreach ($adapter in $adapters) {
$switch = Get-VMSwitch -SwitchType External | where { $_.NetAdapterInterfaceDescription -eq $adapter.InterfaceDescription }
if ($switch -eq $null) {
$switch = New-VMSwitch -Name $switchName -NetAdapterName $adapter.Name -AllowManagementOS $true -Notes 'Parent OS, VMs, WiFi'
}
if ($switch -ne $null) {
break
}
}
if($switch -ne $null) {
Get-VMNetworkAdapter VMName $vmName | Connect-VMNetworkAdapter -VMSwitch $switch
} else {
Write-Error 'No internet adapters found'
}
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName, switchName)
return err
}
func GetVirtualMachineSwitchName(vmName string) (string, error) {
var script = `
param([string]$vmName)
(Get-VMNetworkAdapter -VMName $vmName).SwitchName
`
var ps powershell.PowerShellCmd
cmdOut, err := ps.Output(script, vmName)
if err != nil {
return "", err
}
return strings.TrimSpace(cmdOut), nil
}
func ConnectVirtualMachineNetworkAdapterToSwitch(vmName string, switchName string) error {
var script = `
param([string]$vmName,[string]$switchName)
Get-VMNetworkAdapter VMName $vmName | Connect-VMNetworkAdapter SwitchName $switchName
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName, switchName)
return err
}
func UntagVirtualMachineNetworkAdapterVlan(vmName string, switchName string) error {
var script = `
param([string]$vmName,[string]$switchName)
Set-VMNetworkAdapterVlan -VMName $vmName -Untagged
Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName $switchName -Untagged
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName, switchName)
return err
}
func IsRunning(vmName string) (bool, error) {
var script = `
param([string]$vmName)
$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue
$vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running
`
var ps powershell.PowerShellCmd
cmdOut, err := ps.Output(script, vmName)
var isRunning = strings.TrimSpace(cmdOut) == "True"
return isRunning, err
}
func Mac(vmName string) (string, error) {
var script = `
param([string]$vmName, [int]$addressIndex)
try {
$adapter = Get-VMNetworkAdapter -VMName $vmName -ErrorAction SilentlyContinue
$mac = $adapter.MacAddress[$addressIndex]
if($mac -eq $null) {
return $false
}
} catch {
return $false
}
$mac
`
var ps powershell.PowerShellCmd
cmdOut, err := ps.Output(script, vmName, "0")
return cmdOut, err
}
func IpAddress(mac string) (string, error) {
var script = `
param([string]$mac, [int]$addressIndex)
try {
$ip = Get-Vm | %{$_.NetworkAdapters} | ?{$_.MacAddress -eq $mac} | %{$_.IpAddresses[$addressIndex]}
if($ip -eq $null) {
return $false
}
} catch {
return $false
}
$ip
`
var ps powershell.PowerShellCmd
cmdOut, err := ps.Output(script, mac, "0")
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
}
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName)
return err
}
func ShutDown(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
}
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName)
return err
}

255
powershell/powershell.go Normal file
View File

@ -0,0 +1,255 @@
// 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 powershell
import (
"fmt"
"log"
"io"
"os"
"os/exec"
"strings"
"bytes"
"io/ioutil"
"strconv"
)
const (
powerShellFalse = "False"
powerShellTrue = "True"
)
type PowerShellCmd struct {
Stdout io.Writer
Stderr io.Writer
}
func (ps *PowerShellCmd) Run(fileContents string, params ...string) error {
_, err := ps.Output(fileContents, params...)
return err
}
// Output runs the PowerShell command and returns its standard output.
func (ps *PowerShellCmd) Output(fileContents string, params ...string) (string, error) {
path, err := ps.getPowerShellPath();
if err != nil {
return "", nil
}
filename, err := saveScript(fileContents);
if err != nil {
return "", err
}
debug := os.Getenv("PACKER_POWERSHELL_DEBUG") != ""
verbose := debug || os.Getenv("PACKER_POWERSHELL_VERBOSE") != ""
if !debug {
defer os.Remove(filename)
}
args := createArgs(filename, params...)
if verbose {
log.Printf("Run: %s %s", path, args)
}
var stdout, stderr bytes.Buffer
command := exec.Command(path, args...)
command.Stdout = &stdout
command.Stderr = &stderr
err = command.Run()
if ps.Stdout != nil {
stdout.WriteTo(ps.Stdout)
}
if ps.Stderr != nil {
stderr.WriteTo(ps.Stderr)
}
stderrString := strings.TrimSpace(stderr.String())
if _, ok := err.(*exec.ExitError); ok {
err = fmt.Errorf("PowerShell error: %s", stderrString)
}
if len(stderrString) > 0 {
err = fmt.Errorf("PowerShell error: %s", stderrString)
}
stdoutString := strings.TrimSpace(stdout.String())
if verbose && stdoutString != "" {
log.Printf("stdout: %s", stdoutString)
}
// only write the stderr string if verbose because
// the error string will already be in the err return value.
if verbose && stderrString != "" {
log.Printf("stderr: %s", stderrString)
}
return stdoutString, err;
}
func (ps *PowerShellCmd) getPowerShellPath() (string, error) {
path, err := exec.LookPath("powershell")
if err != nil {
log.Fatal("Cannot find PowerShell in the path", err)
return "", err
}
return path, nil
}
func saveScript(fileContents string) (string, error) {
file, err := ioutil.TempFile(os.TempDir(), "ps")
if err != nil {
return "", err
}
_, err = file.Write([]byte(fileContents))
if err != nil {
return "", err
}
err = file.Close()
if err != nil {
return "", err
}
newFilename := file.Name() + ".ps1"
err = os.Rename(file.Name(), newFilename)
if err != nil {
return "", err
}
return newFilename, nil
}
func createArgs(filename string, params ...string) []string {
args := make([]string,len(params)+4)
args[0] = "-ExecutionPolicy"
args[1] = "Bypass"
args[2] = "-File"
args[3] = filename
for key, value := range params {
args[key+4] = value
}
return args;
}
func GetHostAvailableMemory() float64 {
var script = "(Get-WmiObject Win32_OperatingSystem).FreePhysicalMemory / 1024"
var ps PowerShellCmd
output, _ := ps.Output(script)
freeMB, _ := strconv.ParseFloat(output, 64)
return freeMB
}
func GetHostName(ip string) (string, error) {
var script = `
param([string]$ip)
try {
$HostName = [System.Net.Dns]::GetHostEntry($ip).HostName
if ($HostName -ne $null) {
$HostName = $HostName.Split('.')[0]
}
$HostName
} catch { }
`
//
var ps PowerShellCmd
cmdOut, err := ps.Output(script, ip);
if err != nil {
return "", err
}
return cmdOut, nil
}
func IsCurrentUserAnAdministrator() (bool, error) {
var script = `
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$principal = new-object System.Security.Principal.WindowsPrincipal($identity)
$administratorRole = [System.Security.Principal.WindowsBuiltInRole]::Administrator
return $principal.IsInRole($administratorRole)
`
var ps PowerShellCmd
cmdOut, err := ps.Output(script);
if err != nil {
return false, err
}
res := strings.TrimSpace(cmdOut)
return res == powerShellTrue, nil
}
func ModuleExists(moduleName string) (bool, error) {
var script = `
param([string]$moduleName)
(Get-Module -Name $moduleName) -ne $null
`
var ps PowerShellCmd
cmdOut, err := ps.Output(script)
if err != nil {
return false, err
}
res := strings.TrimSpace(string(cmdOut))
if(res == powerShellFalse){
err := fmt.Errorf("PowerShell %s module is not loaded. Make sure %s feature is on.", moduleName, moduleName)
return false, err
}
return true, nil
}
func SetUnattendedProductKey(path string, productKey string) error {
var script = `
param([string]$path,[string]$productKey)
$unattend = [xml](Get-Content -Path $path)
$ns = @{ un = 'urn:schemas-microsoft-com:unattend' }
$setupNode = $unattend |
Select-Xml -XPath '//un:settings[@pass = "specialize"]/un:component[@name = "Microsoft-Windows-Shell-Setup"]' -Namespace $ns |
Select-Object -ExpandProperty Node
$productKeyNode = $setupNode |
Select-Xml -XPath '//un:ProductKey' -Namespace $ns |
Select-Object -ExpandProperty Node
if ($productKeyNode -eq $null) {
$productKeyNode = $unattend.CreateElement('ProductKey', $ns.un)
[Void]$setupNode.AppendChild($productKeyNode)
}
$productKeyNode.InnerText = $productKey
$unattend.Save($path)
`
var ps PowerShellCmd
err := ps.Run(script, path, productKey)
return err
}

View File

@ -0,0 +1,72 @@
package powershell
import (
"bytes"
"testing"
)
func TestOutputScriptBlock(t *testing.T) {
ps, err := powershell.Command()
if err != nil {
t.Fatalf("should not have error: %s", err)
}
trueOutput, err := powershell.OutputScriptBlock("$True")
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if trueOutput != "True" {
t.Fatalf("output '%v' is not 'True'", trueOutput)
}
falseOutput, err := powershell.OutputScriptBlock("$False")
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if falseOutput != "False" {
t.Fatalf("output '%v' is not 'False'", falseOutput)
}
}
func TestRunScriptBlock(t *testing.T) {
powershell, err := powershell.Command()
if err != nil {
t.Fatalf("should not have error: %s", err)
}
err = powershell.RunScriptBlock("$True")
}
func TestVersion(t *testing.T) {
powershell, err := powershell.Command()
version, err := powershell.Version();
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if (version != 4) {
t.Fatalf("expected version 4")
}
}
func TestRunFile(t *testing.T) {
powershell, err := powershell.Command()
if err != nil {
t.Fatalf("should not have error: %s", err)
}
var blockBuffer bytes.Buffer
blockBuffer.WriteString("param([string]$a, [string]$b, [int]$x, [int]$y) $n = $x + $y; Write-Host $a, $b, $n")
err = powershell.Run(blockBuffer.String(), "a", "b", "5", "10")
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}

View File

@ -0,0 +1,30 @@
package powershell
import (
"bytes"
)
type ScriptBuilder struct {
buffer bytes.Buffer
}
func (b *ScriptBuilder) WriteLine(s string) (n int, err error) {
n, err = b.buffer.WriteString(s);
b.buffer.WriteString("\n")
return n+1, err
}
func (b *ScriptBuilder) WriteString(s string) (n int, err error) {
n, err = b.buffer.WriteString(s);
return n, err
}
func (b *ScriptBuilder) String() string {
return b.buffer.String()
}
func (b *ScriptBuilder) Reset() {
b.buffer.Reset()
}