Merge branch 'master' into f-vtolstov-compress
This commit is contained in:
commit
f06847ff10
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -22,6 +22,10 @@ FEATURES:
|
|||
connections. Note that provisioners won't work if this is done. [GH-1591]
|
||||
* **SSH Agent Forwarding:** SSH Agent Forwarding will now be enabled
|
||||
to allow access to remote servers such as private git repos. [GH-1066]
|
||||
* **SSH Bastion Hosts:** You can now specify a bastion host for
|
||||
SSH access (works with all builders). [GH-387]
|
||||
* **OpenStack v3 Identity:** The OpenStack builder now supports the
|
||||
v3 identity API.
|
||||
* **Docker builder supports SSH**: The Docker builder now supports containers
|
||||
with SSH, just set `communicator` to "ssh" [GH-2244]
|
||||
* **File provisioner can download**: The file provisioner can now download
|
||||
|
@ -32,6 +36,12 @@ FEATURES:
|
|||
builder. This is useful for provisioners. [GH-2232]
|
||||
* **New config function: `template_dir`**: The directory to the template
|
||||
being built. This should be used for template-relative paths. [GH-54]
|
||||
* **New provisioner: powershell**: Provision Windows machines
|
||||
with PowerShell scripts. [GH-2243]
|
||||
* **New provisioner: windows-shell**: Provision Windows machines with
|
||||
batch files. [GH-2243]
|
||||
* **New provisioner: windows-restart**: Restart a Windows machines and
|
||||
wait for it to come back online. [GH-2243]
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
|
@ -44,6 +54,7 @@ IMPROVEMENTS:
|
|||
* builder/amazon: Support custom keypairs [GH-1837]
|
||||
* builder/digitalocean: Save SSH key to pwd if debug mode is on. [GH-1829]
|
||||
* builder/digitalocean: User data support [GH-2113]
|
||||
* builder/googlecompute: Option to use internal IP for connections. [GH-2152]
|
||||
* builder/parallels: Support Parallels Desktop 11 [GH-2199]
|
||||
* builder/openstack: Add `rackconnect_wait` for Rackspace customers to wait for
|
||||
RackConnect data to appear
|
||||
|
@ -65,6 +76,8 @@ IMPROVEMENTS:
|
|||
* post-processor/docker-save: Can be chained [GH-2179]
|
||||
* post-processor/docker-tag: Support `force` option [GH-2055]
|
||||
* post-processor/docker-tag: Can be chained [GH-2179]
|
||||
* post-processor/vsphere: Make more fields optional, support empty
|
||||
resource pools. [GH-1868]
|
||||
* provisioner/puppet-masterless: `working_directory` option [GH-1831]
|
||||
* provisioner/puppet-masterless: `packer_build_name` and
|
||||
`packer_build_type` are default facts. [GH-1878]
|
||||
|
@ -88,6 +101,7 @@ BUG FIXES:
|
|||
* builder/amazon: Improved retry logic around waiting for instances. [GH-1764]
|
||||
* builder/amazon: Fix issues with creating Block Devices. [GH-2195]
|
||||
* builder/amazon/chroot: Retry waiting for disk attachments [GH-2046]
|
||||
* builder/amazon/chroot: Only unmount path if it is mounted [GH-2054]
|
||||
* builder/amazon/instance: Use `-i` in sudo commands so PATH is inherited. [GH-1930]
|
||||
* builder/amazon/instance: Use `--region` flag for bundle upload command. [GH-1931]
|
||||
* builder/digitalocean: Wait for droplet to unlock before changing state,
|
||||
|
@ -104,6 +118,7 @@ BUG FIXES:
|
|||
to retrieve the SSH IP from. [GH-2220]
|
||||
* builder/qemu: Add `disk_discard` option [GH-2120]
|
||||
* builder/qemu: Use proper SSH port, not hardcoded to 22. [GH-2236]
|
||||
* builder/qemu: Find unused SSH port if SSH port is taken. [GH-2032]
|
||||
* builder/virtualbox: Bind HTTP server to IPv4, which is more compatible with
|
||||
OS installers. [GH-1709]
|
||||
* builder/virtualbox: Remove the floppy controller in addition to the
|
||||
|
@ -112,6 +127,7 @@ BUG FIXES:
|
|||
".iso" extension didn't work. [GH-1839]
|
||||
* builder/virtualbox: Output dir is verified at runtime, not template
|
||||
validation time. [GH-2233]
|
||||
* builder/virtualbox: Find unused SSH port if SSH port is taken. [GH-2032]
|
||||
* builder/vmware: Add 100ms delay between keystrokes to avoid subtle
|
||||
timing issues in most cases. [GH-1663]
|
||||
* builder/vmware: Bind HTTP server to IPv4, which is more compatible with
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// StepMountExtra mounts the attached device.
|
||||
|
@ -90,13 +92,37 @@ func (s *StepMountExtra) CleanupFunc(state multistep.StateBag) error {
|
|||
var path string
|
||||
lastIndex := len(s.mounts) - 1
|
||||
path, s.mounts = s.mounts[lastIndex], s.mounts[:lastIndex]
|
||||
|
||||
grepCommand, err := wrappedCommand(fmt.Sprintf("grep %s /proc/mounts", path))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating grep command: %s", err)
|
||||
}
|
||||
|
||||
// Before attempting to unmount,
|
||||
// check to see if path is already unmounted
|
||||
stderr := new(bytes.Buffer)
|
||||
cmd := ShellCommand(grepCommand)
|
||||
cmd.Stderr = stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
if status, ok := exitError.Sys().(syscall.WaitStatus); ok {
|
||||
exitStatus := status.ExitStatus()
|
||||
if exitStatus == 1 {
|
||||
// path has already been unmounted
|
||||
// just skip this path
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unmountCommand, err := wrappedCommand(fmt.Sprintf("umount %s", path))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating unmount command: %s", err)
|
||||
}
|
||||
|
||||
stderr := new(bytes.Buffer)
|
||||
cmd := ShellCommand(unmountCommand)
|
||||
stderr = new(bytes.Buffer)
|
||||
cmd = ShellCommand(unmountCommand)
|
||||
cmd.Stderr = stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf(
|
||||
|
|
|
@ -38,8 +38,8 @@ func TestBlockDevice(t *testing.T) {
|
|||
},
|
||||
{
|
||||
Config: &BlockDevice{
|
||||
DeviceName: "/dev/sdb",
|
||||
VolumeSize: 8,
|
||||
DeviceName: "/dev/sdb",
|
||||
VolumeSize: 8,
|
||||
},
|
||||
|
||||
Result: &ec2.BlockDeviceMapping{
|
||||
|
|
|
@ -35,6 +35,7 @@ type Config struct {
|
|||
SourceImageProjectId string `mapstructure:"source_image_project_id"`
|
||||
RawStateTimeout string `mapstructure:"state_timeout"`
|
||||
Tags []string `mapstructure:"tags"`
|
||||
UseInternalIP bool `mapstructure:"use_internal_ip"`
|
||||
Zone string `mapstructure:"zone"`
|
||||
|
||||
account accountFile
|
||||
|
|
|
@ -116,6 +116,21 @@ func TestConfigPrepare(t *testing.T) {
|
|||
"5s",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"use_internal_ip",
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"use_internal_ip",
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"use_internal_ip",
|
||||
"SO VERY BAD",
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
|
|
@ -24,6 +24,9 @@ type Driver interface {
|
|||
// GetNatIP gets the NAT IP address for the instance.
|
||||
GetNatIP(zone, name string) (string, error)
|
||||
|
||||
// GetInternalIP gets the GCE-internal IP address for the instance.
|
||||
GetInternalIP(zone, name string) (string, error)
|
||||
|
||||
// RunInstance takes the given config and launches an instance.
|
||||
RunInstance(*InstanceConfig) (<-chan error, error)
|
||||
|
||||
|
|
|
@ -157,7 +157,6 @@ func (d *driverGCE) GetNatIP(zone, name string) (string, error) {
|
|||
if ni.AccessConfigs == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ac := range ni.AccessConfigs {
|
||||
if ac.NatIP != "" {
|
||||
return ac.NatIP, nil
|
||||
|
@ -168,6 +167,22 @@ func (d *driverGCE) GetNatIP(zone, name string) (string, error) {
|
|||
return "", nil
|
||||
}
|
||||
|
||||
func (d *driverGCE) GetInternalIP(zone, name string) (string, error) {
|
||||
instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, ni := range instance.NetworkInterfaces {
|
||||
if ni.NetworkIP == "" {
|
||||
continue
|
||||
}
|
||||
return ni.NetworkIP, nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) {
|
||||
// Get the zone
|
||||
d.ui.Message(fmt.Sprintf("Loading zone: %s", c.Zone))
|
||||
|
|
|
@ -30,6 +30,11 @@ type DriverMock struct {
|
|||
GetNatIPResult string
|
||||
GetNatIPErr error
|
||||
|
||||
GetInternalIPZone string
|
||||
GetInternalIPName string
|
||||
GetInternalIPResult string
|
||||
GetInternalIPErr error
|
||||
|
||||
RunInstanceConfig *InstanceConfig
|
||||
RunInstanceErrCh <-chan error
|
||||
RunInstanceErr error
|
||||
|
@ -108,6 +113,12 @@ func (d *DriverMock) GetNatIP(zone, name string) (string, error) {
|
|||
return d.GetNatIPResult, d.GetNatIPErr
|
||||
}
|
||||
|
||||
func (d *DriverMock) GetInternalIP(zone, name string) (string, error) {
|
||||
d.GetInternalIPZone = zone
|
||||
d.GetInternalIPName = name
|
||||
return d.GetInternalIPResult, d.GetInternalIPErr
|
||||
}
|
||||
|
||||
func (d *DriverMock) RunInstance(c *InstanceConfig) (<-chan error, error) {
|
||||
d.RunInstanceConfig = c
|
||||
|
||||
|
|
|
@ -40,23 +40,41 @@ func (s *StepInstanceInfo) Run(state multistep.StateBag) multistep.StepAction {
|
|||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ip, err := driver.GetNatIP(config.Zone, instanceName)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error retrieving instance nat ip address: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if s.Debug {
|
||||
if ip != "" {
|
||||
ui.Message(fmt.Sprintf("Public IP: %s", ip))
|
||||
if config.UseInternalIP {
|
||||
ip, err := driver.GetInternalIP(config.Zone, instanceName)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error retrieving instance internal ip address: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("IP: %s", ip))
|
||||
state.Put("instance_ip", ip)
|
||||
return multistep.ActionContinue
|
||||
if s.Debug {
|
||||
if ip != "" {
|
||||
ui.Message(fmt.Sprintf("Internal IP: %s", ip))
|
||||
}
|
||||
}
|
||||
ui.Message(fmt.Sprintf("IP: %s", ip))
|
||||
state.Put("instance_ip", ip)
|
||||
return multistep.ActionContinue
|
||||
} else {
|
||||
ip, err := driver.GetNatIP(config.Zone, instanceName)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error retrieving instance nat ip address: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if s.Debug {
|
||||
if ip != "" {
|
||||
ui.Message(fmt.Sprintf("Public IP: %s", ip))
|
||||
}
|
||||
}
|
||||
ui.Message(fmt.Sprintf("IP: %s", ip))
|
||||
state.Put("instance_ip", ip)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup.
|
||||
|
|
|
@ -49,6 +49,46 @@ func TestStepInstanceInfo(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestStepInstanceInfo_InternalIP(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepInstanceInfo)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
state.Put("instance_name", "foo")
|
||||
|
||||
config := state.Get("config").(*Config)
|
||||
config.UseInternalIP = true
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
driver.GetNatIPResult = "1.2.3.4"
|
||||
driver.GetInternalIPResult = "5.6.7.8"
|
||||
|
||||
// run the step
|
||||
if action := step.Run(state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
// Verify state
|
||||
if driver.WaitForInstanceState != "RUNNING" {
|
||||
t.Fatalf("bad: %#v", driver.WaitForInstanceState)
|
||||
}
|
||||
if driver.WaitForInstanceZone != config.Zone {
|
||||
t.Fatalf("bad: %#v", driver.WaitForInstanceZone)
|
||||
}
|
||||
if driver.WaitForInstanceName != "foo" {
|
||||
t.Fatalf("bad: %#v", driver.WaitForInstanceName)
|
||||
}
|
||||
|
||||
ipRaw, ok := state.GetOk("instance_ip")
|
||||
if !ok {
|
||||
t.Fatal("should have ip")
|
||||
}
|
||||
if ip, ok := ipRaw.(string); !ok {
|
||||
t.Fatal("ip is not a string")
|
||||
} else if ip != "5.6.7.8" {
|
||||
t.Fatalf("bad ip: %s", ip)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepInstanceInfo_getNatIPError(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepInstanceInfo)
|
||||
|
|
|
@ -44,6 +44,9 @@ type Driver interface {
|
|||
// Send scancodes to the vm using the prltype python script.
|
||||
SendKeyScanCodes(string, ...string) error
|
||||
|
||||
// Apply default сonfiguration settings to the virtual machine
|
||||
SetDefaultConfiguration(string) error
|
||||
|
||||
// Finds the MAC address of the NIC nic0
|
||||
Mac(string) (string, error)
|
||||
|
||||
|
|
|
@ -5,3 +5,27 @@ package common
|
|||
type Parallels10Driver struct {
|
||||
Parallels9Driver
|
||||
}
|
||||
|
||||
func (d *Parallels10Driver) SetDefaultConfiguration(vmName string) error {
|
||||
commands := make([][]string, 12)
|
||||
commands[0] = []string{"set", vmName, "--cpus", "1"}
|
||||
commands[1] = []string{"set", vmName, "--memsize", "512"}
|
||||
commands[2] = []string{"set", vmName, "--startup-view", "same"}
|
||||
commands[3] = []string{"set", vmName, "--on-shutdown", "close"}
|
||||
commands[4] = []string{"set", vmName, "--on-window-close", "keep-running"}
|
||||
commands[5] = []string{"set", vmName, "--auto-share-camera", "off"}
|
||||
commands[6] = []string{"set", vmName, "--smart-guard", "off"}
|
||||
commands[7] = []string{"set", vmName, "--shared-cloud", "off"}
|
||||
commands[8] = []string{"set", vmName, "--shared-profile", "off"}
|
||||
commands[9] = []string{"set", vmName, "--smart-mount", "off"}
|
||||
commands[10] = []string{"set", vmName, "--sh-app-guest-to-host", "off"}
|
||||
commands[11] = []string{"set", vmName, "--sh-app-host-to-guest", "off"}
|
||||
|
||||
for _, command := range commands {
|
||||
err := d.Prlctl(command...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -255,6 +255,25 @@ func prepend(head string, tail []string) []string {
|
|||
return tmp
|
||||
}
|
||||
|
||||
func (d *Parallels9Driver) SetDefaultConfiguration(vmName string) error {
|
||||
commands := make([][]string, 7)
|
||||
commands[0] = []string{"set", vmName, "--cpus", "1"}
|
||||
commands[1] = []string{"set", vmName, "--memsize", "512"}
|
||||
commands[2] = []string{"set", vmName, "--startup-view", "same"}
|
||||
commands[3] = []string{"set", vmName, "--on-shutdown", "close"}
|
||||
commands[4] = []string{"set", vmName, "--on-window-close", "keep-running"}
|
||||
commands[5] = []string{"set", vmName, "--auto-share-camera", "off"}
|
||||
commands[6] = []string{"set", vmName, "--smart-guard", "off"}
|
||||
|
||||
for _, command := range commands {
|
||||
err := d.Prlctl(command...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Parallels9Driver) Mac(vmName string) (string, error) {
|
||||
var stdout bytes.Buffer
|
||||
|
||||
|
|
|
@ -23,37 +23,33 @@ func (s *stepCreateVM) Run(state multistep.StateBag) multistep.StepAction {
|
|||
ui := state.Get("ui").(packer.Ui)
|
||||
name := config.VMName
|
||||
|
||||
commands := make([][]string, 8)
|
||||
commands[0] = []string{
|
||||
command := []string{
|
||||
"create", name,
|
||||
"--distribution", config.GuestOSType,
|
||||
"--dst", config.OutputDir,
|
||||
"--vmtype", "vm",
|
||||
"--no-hdd",
|
||||
}
|
||||
commands[1] = []string{"set", name, "--cpus", "1"}
|
||||
commands[2] = []string{"set", name, "--memsize", "512"}
|
||||
commands[3] = []string{"set", name, "--startup-view", "same"}
|
||||
commands[4] = []string{"set", name, "--on-shutdown", "close"}
|
||||
commands[5] = []string{"set", name, "--on-window-close", "keep-running"}
|
||||
commands[6] = []string{"set", name, "--auto-share-camera", "off"}
|
||||
commands[7] = []string{"set", name, "--smart-guard", "off"}
|
||||
|
||||
ui.Say("Creating virtual machine...")
|
||||
for _, command := range commands {
|
||||
err := driver.Prlctl(command...)
|
||||
ui.Say(fmt.Sprintf("Executing: prlctl %s", command))
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating VM: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if err := driver.Prlctl(command...); err != nil {
|
||||
err := fmt.Errorf("Error creating VM: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Set the VM name property on the first command
|
||||
if s.vmName == "" {
|
||||
s.vmName = name
|
||||
}
|
||||
ui.Say("Applying default settings...")
|
||||
if err := driver.SetDefaultConfiguration(name); err != nil {
|
||||
err := fmt.Errorf("Error VM configuration: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Set the VM name property on the first command
|
||||
if s.vmName == "" {
|
||||
s.vmName = name
|
||||
}
|
||||
|
||||
// Set the final name in the state bag so others can use it
|
||||
|
|
|
@ -32,7 +32,12 @@ func (stepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction {
|
|||
var vncPort uint
|
||||
portRange := int(config.VNCPortMax - config.VNCPortMin)
|
||||
for {
|
||||
vncPort = uint(rand.Intn(portRange)) + config.VNCPortMin
|
||||
if portRange > 0 {
|
||||
vncPort = uint(rand.Intn(portRange)) + config.VNCPortMin
|
||||
} else {
|
||||
vncPort = config.VNCPortMin
|
||||
}
|
||||
|
||||
log.Printf("Trying port: %d", vncPort)
|
||||
l, err := net.Listen("tcp", fmt.Sprintf(":%d", vncPort))
|
||||
if err == nil {
|
||||
|
|
|
@ -3,7 +3,6 @@ package qemu
|
|||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
|
@ -18,13 +17,13 @@ func (s *stepCopyDisk) Run(state multistep.StateBag) multistep.StepAction {
|
|||
driver := state.Get("driver").(Driver)
|
||||
isoPath := state.Get("iso_path").(string)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
path := filepath.Join(config.OutputDir, fmt.Sprintf("%s.%s", config.VMName,
|
||||
strings.ToLower(config.Format)))
|
||||
name := config.VMName + "." + strings.ToLower(config.Format)
|
||||
path := filepath.Join(config.OutputDir, fmt.Sprintf("%s", config.VMName))
|
||||
name := config.VMName
|
||||
|
||||
command := []string{
|
||||
"convert",
|
||||
"-f", config.Format,
|
||||
"-O", config.Format,
|
||||
isoPath,
|
||||
path,
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@ package qemu
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// This step creates the virtual disk that will be used as the
|
||||
|
@ -16,7 +16,7 @@ func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction {
|
|||
config := state.Get("config").(*Config)
|
||||
driver := state.Get("driver").(Driver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
name := config.VMName + "." + strings.ToLower(config.Format)
|
||||
name := config.VMName
|
||||
path := filepath.Join(config.OutputDir, name)
|
||||
|
||||
command := []string{
|
||||
|
|
|
@ -34,12 +34,17 @@ func (s *stepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
|
|||
|
||||
for {
|
||||
sshHostPort = offset + config.SSHHostPortMin
|
||||
if sshHostPort >= config.SSHHostPortMax {
|
||||
offset = 0
|
||||
sshHostPort = config.SSHHostPortMin
|
||||
}
|
||||
log.Printf("Trying port: %d", sshHostPort)
|
||||
l, err := net.Listen("tcp", fmt.Sprintf(":%d", sshHostPort))
|
||||
if err == nil {
|
||||
defer l.Close()
|
||||
break
|
||||
}
|
||||
offset++
|
||||
}
|
||||
ui.Say(fmt.Sprintf("Found port for SSH: %d.", sshHostPort))
|
||||
|
||||
|
|
|
@ -2,10 +2,10 @@ package qemu
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// This step resizes the virtual disk that will be used as the
|
||||
|
@ -16,8 +16,7 @@ func (s *stepResizeDisk) Run(state multistep.StateBag) multistep.StepAction {
|
|||
config := state.Get("config").(*Config)
|
||||
driver := state.Get("driver").(Driver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
path := filepath.Join(config.OutputDir, fmt.Sprintf("%s.%s", config.VMName,
|
||||
strings.ToLower(config.Format)))
|
||||
path := filepath.Join(config.OutputDir, config.VMName)
|
||||
|
||||
command := []string{
|
||||
"resize",
|
||||
|
|
|
@ -65,8 +65,7 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
|
|||
|
||||
vnc := fmt.Sprintf("0.0.0.0:%d", vncPort-5900)
|
||||
vmName := config.VMName
|
||||
imgPath := filepath.Join(config.OutputDir,
|
||||
fmt.Sprintf("%s.%s", vmName, strings.ToLower(config.Format)))
|
||||
imgPath := filepath.Join(config.OutputDir, vmName)
|
||||
|
||||
defaultArgs := make(map[string]string)
|
||||
|
||||
|
|
|
@ -47,12 +47,17 @@ func (s *StepForwardSSH) Run(state multistep.StateBag) multistep.StepAction {
|
|||
|
||||
for {
|
||||
sshHostPort = offset + int(s.HostPortMin)
|
||||
if sshHostPort >= int(s.HostPortMax) {
|
||||
offset = 0
|
||||
sshHostPort = int(s.HostPortMin)
|
||||
}
|
||||
log.Printf("Trying port: %d", sshHostPort)
|
||||
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", sshHostPort))
|
||||
if err == nil {
|
||||
defer l.Close()
|
||||
break
|
||||
}
|
||||
offset++
|
||||
}
|
||||
|
||||
// Create a forwarded port mapping to the VM
|
||||
|
|
|
@ -36,11 +36,11 @@ func (s StepCompactDisk) Run(state multistep.StateBag) multistep.StepAction {
|
|||
state.Put("error", fmt.Errorf("Error compacting disk: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
|
||||
if state.Get("additional_disk_paths") != nil {
|
||||
if moreDisks := state.Get("additional_disk_paths").([]string); len(moreDisks) > 0 {
|
||||
for i, path := range moreDisks {
|
||||
ui.Say(fmt.Sprintf("Compacting additional disk image %d",i+1))
|
||||
ui.Say(fmt.Sprintf("Compacting additional disk image %d", i+1))
|
||||
if err := driver.CompactDisk(path); err != nil {
|
||||
state.Put("error", fmt.Errorf("Error compacting additional disk %d: %s", i+1, err))
|
||||
return multistep.ActionHalt
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package ssh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// ConnectFunc is a convenience method for returning a function
|
||||
|
@ -23,3 +26,43 @@ func ConnectFunc(network, addr string) func() (net.Conn, error) {
|
|||
return c, nil
|
||||
}
|
||||
}
|
||||
|
||||
// BastionConnectFunc is a convenience method for returning a function
|
||||
// that connects to a host over a bastion connection.
|
||||
func BastionConnectFunc(
|
||||
bProto string,
|
||||
bAddr string,
|
||||
bConf *ssh.ClientConfig,
|
||||
proto string,
|
||||
addr string) func() (net.Conn, error) {
|
||||
return func() (net.Conn, error) {
|
||||
// Connect to the bastion
|
||||
bastion, err := ssh.Dial(bProto, bAddr, bConf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error connecting to bastion: %s", err)
|
||||
}
|
||||
|
||||
// Connect through to the end host
|
||||
conn, err := bastion.Dial(proto, addr)
|
||||
if err != nil {
|
||||
bastion.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wrap it up so we close both things properly
|
||||
return &bastionConn{
|
||||
Conn: conn,
|
||||
Bastion: bastion,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
type bastionConn struct {
|
||||
net.Conn
|
||||
Bastion *ssh.Client
|
||||
}
|
||||
|
||||
func (c *bastionConn) Close() error {
|
||||
c.Conn.Close()
|
||||
return c.Bastion.Close()
|
||||
}
|
||||
|
|
|
@ -89,7 +89,10 @@ func runCommand(shell *winrm.Shell, cmd *winrm.Command, rc *packer.RemoteCmd) {
|
|||
go io.Copy(rc.Stderr, cmd.Stderr)
|
||||
|
||||
cmd.Wait()
|
||||
rc.SetExited(cmd.ExitCode())
|
||||
|
||||
code := cmd.ExitCode()
|
||||
log.Printf("[INFO] command '%s' exited with code: %d", rc.Command, code)
|
||||
rc.SetExited(code)
|
||||
}
|
||||
|
||||
// Upload implementation of communicator.Communicator interface
|
||||
|
|
|
@ -23,6 +23,11 @@ type Config struct {
|
|||
SSHPty bool `mapstructure:"ssh_pty"`
|
||||
SSHTimeout time.Duration `mapstructure:"ssh_timeout"`
|
||||
SSHHandshakeAttempts int `mapstructure:"ssh_handshake_attempts"`
|
||||
SSHBastionHost string `mapstructure:"ssh_bastion_host"`
|
||||
SSHBastionPort int `mapstructure:"ssh_bastion_port"`
|
||||
SSHBastionUsername string `mapstructure:"ssh_bastion_username"`
|
||||
SSHBastionPassword string `mapstructure:"ssh_bastion_password"`
|
||||
SSHBastionPrivateKey string `mapstructure:"ssh_bastion_private_key_file"`
|
||||
|
||||
// WinRM
|
||||
WinRMUser string `mapstructure:"winrm_username"`
|
||||
|
@ -77,6 +82,16 @@ func (c *Config) prepareSSH(ctx *interpolate.Context) []error {
|
|||
c.SSHHandshakeAttempts = 10
|
||||
}
|
||||
|
||||
if c.SSHBastionHost != "" {
|
||||
if c.SSHBastionPort == 0 {
|
||||
c.SSHBastionPort = 22
|
||||
}
|
||||
|
||||
if c.SSHBastionPrivateKey == "" && c.SSHPrivateKey != "" {
|
||||
c.SSHBastionPrivateKey = c.SSHPrivateKey
|
||||
}
|
||||
}
|
||||
|
||||
// Validation
|
||||
var errs []error
|
||||
if c.SSHUsername == "" {
|
||||
|
@ -93,6 +108,13 @@ func (c *Config) prepareSSH(ctx *interpolate.Context) []error {
|
|||
}
|
||||
}
|
||||
|
||||
if c.SSHBastionHost != "" {
|
||||
if c.SSHBastionPassword == "" && c.SSHBastionPrivateKey == "" {
|
||||
errs = append(errs, errors.New(
|
||||
"ssh_bastion_password or ssh_bastion_private_key_file must be specified"))
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
|
|
|
@ -4,10 +4,12 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
commonssh "github.com/mitchellh/packer/common/ssh"
|
||||
"github.com/mitchellh/packer/communicator/ssh"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
|
@ -79,6 +81,24 @@ func (s *StepConnectSSH) Cleanup(multistep.StateBag) {
|
|||
}
|
||||
|
||||
func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan struct{}) (packer.Communicator, error) {
|
||||
// Determine if we're using a bastion host, and if so, retrieve
|
||||
// that configuration. This configuration doesn't change so we
|
||||
// do this one before entering the retry loop.
|
||||
var bProto, bAddr string
|
||||
var bConf *gossh.ClientConfig
|
||||
if s.Config.SSHBastionHost != "" {
|
||||
// The protocol is hardcoded for now, but may be configurable one day
|
||||
bProto = "tcp"
|
||||
bAddr = fmt.Sprintf(
|
||||
"%s:%d", s.Config.SSHBastionHost, s.Config.SSHBastionPort)
|
||||
|
||||
conf, err := sshBastionConfig(s.Config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error configuring bastion: %s", err)
|
||||
}
|
||||
bConf = conf
|
||||
}
|
||||
|
||||
handshakeAttempts := 0
|
||||
|
||||
var comm packer.Communicator
|
||||
|
@ -117,10 +137,18 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru
|
|||
continue
|
||||
}
|
||||
|
||||
address := fmt.Sprintf("%s:%d", host, port)
|
||||
|
||||
// Attempt to connect to SSH port
|
||||
connFunc := ssh.ConnectFunc("tcp", address)
|
||||
var connFunc func() (net.Conn, error)
|
||||
address := fmt.Sprintf("%s:%d", host, port)
|
||||
if bAddr != "" {
|
||||
// We're using a bastion host, so use the bastion connfunc
|
||||
connFunc = ssh.BastionConnectFunc(
|
||||
bProto, bAddr, bConf, "tcp", address)
|
||||
} else {
|
||||
// No bastion host, connect directly
|
||||
connFunc = ssh.ConnectFunc("tcp", address)
|
||||
}
|
||||
|
||||
nc, err := connFunc()
|
||||
if err != nil {
|
||||
log.Printf("[DEBUG] TCP connection to SSH ip/port failed: %s", err)
|
||||
|
@ -164,3 +192,27 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru
|
|||
|
||||
return comm, nil
|
||||
}
|
||||
|
||||
func sshBastionConfig(config *Config) (*gossh.ClientConfig, error) {
|
||||
auth := make([]gossh.AuthMethod, 0, 2)
|
||||
if config.SSHBastionPassword != "" {
|
||||
auth = append(auth,
|
||||
gossh.Password(config.SSHBastionPassword),
|
||||
gossh.KeyboardInteractive(
|
||||
ssh.PasswordKeyboardInteractive(config.SSHBastionPassword)))
|
||||
}
|
||||
|
||||
if config.SSHBastionPrivateKey != "" {
|
||||
signer, err := commonssh.FileSigner(config.SSHBastionPrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auth = append(auth, gossh.PublicKeys(signer))
|
||||
}
|
||||
|
||||
return &gossh.ClientConfig{
|
||||
User: config.SSHBastionUsername,
|
||||
Auth: auth,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer/plugin"
|
||||
"github.com/mitchellh/packer/provisioner/powershell"
|
||||
)
|
||||
|
||||
func main() {
|
||||
server, err := plugin.Server()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
server.RegisterProvisioner(new(powershell.Provisioner))
|
||||
server.Serve()
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer/plugin"
|
||||
"github.com/mitchellh/packer/provisioner/windows-restart"
|
||||
)
|
||||
|
||||
func main() {
|
||||
server, err := plugin.Server()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
server.RegisterProvisioner(new(restart.Provisioner))
|
||||
server.Serve()
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer/plugin"
|
||||
"github.com/mitchellh/packer/provisioner/windows-shell"
|
||||
)
|
||||
|
||||
func main() {
|
||||
server, err := plugin.Server()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
server.RegisterProvisioner(new(shell.Provisioner))
|
||||
server.Serve()
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package powershell
|
||||
|
||||
import (
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type elevatedOptions struct {
|
||||
User string
|
||||
Password string
|
||||
TaskName string
|
||||
TaskDescription string
|
||||
EncodedCommand string
|
||||
}
|
||||
|
||||
var elevatedTemplate = template.Must(template.New("ElevatedCommand").Parse(`
|
||||
$name = "{{.TaskName}}"
|
||||
$log = "$env:TEMP\$name.out"
|
||||
$s = New-Object -ComObject "Schedule.Service"
|
||||
$s.Connect()
|
||||
$t = $s.NewTask($null)
|
||||
$t.XmlText = @'
|
||||
<?xml version="1.0" encoding="UTF-16"?>
|
||||
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
||||
<RegistrationInfo>
|
||||
<Description>{{.TaskDescription}}</Description>
|
||||
</RegistrationInfo>
|
||||
<Principals>
|
||||
<Principal id="Author">
|
||||
<UserId>{{.User}}</UserId>
|
||||
<LogonType>Password</LogonType>
|
||||
<RunLevel>HighestAvailable</RunLevel>
|
||||
</Principal>
|
||||
</Principals>
|
||||
<Settings>
|
||||
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
||||
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
||||
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
||||
<AllowHardTerminate>true</AllowHardTerminate>
|
||||
<StartWhenAvailable>false</StartWhenAvailable>
|
||||
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
|
||||
<IdleSettings>
|
||||
<StopOnIdleEnd>false</StopOnIdleEnd>
|
||||
<RestartOnIdle>false</RestartOnIdle>
|
||||
</IdleSettings>
|
||||
<AllowStartOnDemand>true</AllowStartOnDemand>
|
||||
<Enabled>true</Enabled>
|
||||
<Hidden>false</Hidden>
|
||||
<RunOnlyIfIdle>false</RunOnlyIfIdle>
|
||||
<WakeToRun>false</WakeToRun>
|
||||
<ExecutionTimeLimit>PT24H</ExecutionTimeLimit>
|
||||
<Priority>4</Priority>
|
||||
</Settings>
|
||||
<Actions Context="Author">
|
||||
<Exec>
|
||||
<Command>cmd</Command>
|
||||
<Arguments>/c powershell.exe -EncodedCommand {{.EncodedCommand}} > %TEMP%\{{.TaskName}}.out 2>&1</Arguments>
|
||||
</Exec>
|
||||
</Actions>
|
||||
</Task>
|
||||
'@
|
||||
$f = $s.GetFolder("\")
|
||||
$f.RegisterTaskDefinition($name, $t, 6, "{{.User}}", "{{.Password}}", 1, $null) | Out-Null
|
||||
$t = $f.GetTask("\$name")
|
||||
$t.Run($null) | Out-Null
|
||||
$timeout = 10
|
||||
$sec = 0
|
||||
while ((!($t.state -eq 4)) -and ($sec -lt $timeout)) {
|
||||
Start-Sleep -s 1
|
||||
$sec++
|
||||
}
|
||||
function SlurpOutput($l) {
|
||||
if (Test-Path $log) {
|
||||
Get-Content $log | select -skip $l | ForEach {
|
||||
$l += 1
|
||||
Write-Host "$_"
|
||||
}
|
||||
}
|
||||
return $l
|
||||
}
|
||||
$line = 0
|
||||
do {
|
||||
Start-Sleep -m 100
|
||||
$line = SlurpOutput $line
|
||||
} while (!($t.state -eq 3))
|
||||
$result = $t.LastTaskResult
|
||||
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($s) | Out-Null
|
||||
exit $result`))
|
|
@ -0,0 +1,17 @@
|
|||
package powershell
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
func powershellEncode(buffer []byte) string {
|
||||
// 2 byte chars to make PowerShell happy
|
||||
wideCmd := ""
|
||||
for _, b := range buffer {
|
||||
wideCmd += string(b) + "\x00"
|
||||
}
|
||||
|
||||
// Base64 encode the command
|
||||
input := []uint8(wideCmd)
|
||||
return base64.StdEncoding.EncodeToString(input)
|
||||
}
|
|
@ -0,0 +1,461 @@
|
|||
// This package implements a provisioner for Packer that executes
|
||||
// shell scripts within the remote machine.
|
||||
package powershell
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/common/uuid"
|
||||
"github.com/mitchellh/packer/helper/config"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
const DefaultRemotePath = "c:/Windows/Temp/script.ps1"
|
||||
|
||||
var retryableSleep = 2 * time.Second
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
// If true, the script contains binary and line endings will not be
|
||||
// converted from Windows to Unix-style.
|
||||
Binary bool
|
||||
|
||||
// An inline script to execute. Multiple strings are all executed
|
||||
// in the context of a single shell.
|
||||
Inline []string
|
||||
|
||||
// The local path of the shell script to upload and execute.
|
||||
Script string
|
||||
|
||||
// An array of multiple scripts to run.
|
||||
Scripts []string
|
||||
|
||||
// An array of environment variables that will be injected before
|
||||
// your command(s) are executed.
|
||||
Vars []string `mapstructure:"environment_vars"`
|
||||
|
||||
// The remote path where the local shell script will be uploaded to.
|
||||
// This should be set to a writable file that is in a pre-existing directory.
|
||||
RemotePath string `mapstructure:"remote_path"`
|
||||
|
||||
// The command used to execute the script. The '{{ .Path }}' variable
|
||||
// should be used to specify where the script goes, {{ .Vars }}
|
||||
// can be used to inject the environment_vars into the environment.
|
||||
ExecuteCommand string `mapstructure:"execute_command"`
|
||||
|
||||
// The command used to execute the elevated script. The '{{ .Path }}' variable
|
||||
// should be used to specify where the script goes, {{ .Vars }}
|
||||
// can be used to inject the environment_vars into the environment.
|
||||
ElevatedExecuteCommand string `mapstructure:"elevated_execute_command"`
|
||||
|
||||
// The timeout for retrying to start the process. Until this timeout
|
||||
// is reached, if the provisioner can't start a process, it retries.
|
||||
// This can be set high to allow for reboots.
|
||||
StartRetryTimeout time.Duration `mapstructure:"start_retry_timeout"`
|
||||
|
||||
// This is used in the template generation to format environment variables
|
||||
// inside the `ExecuteCommand` template.
|
||||
EnvVarFormat string
|
||||
|
||||
// This is used in the template generation to format environment variables
|
||||
// inside the `ElevatedExecuteCommand` template.
|
||||
ElevatedEnvVarFormat string `mapstructure:"elevated_env_var_format"`
|
||||
|
||||
// Instructs the communicator to run the remote script as a
|
||||
// Windows scheduled task, effectively elevating the remote
|
||||
// user by impersonating a logged-in user
|
||||
ElevatedUser string `mapstructure:"elevated_user"`
|
||||
ElevatedPassword string `mapstructure:"elevated_password"`
|
||||
|
||||
// Valid Exit Codes - 0 is not always the only valid error code!
|
||||
// See http://www.symantec.com/connect/articles/windows-system-error-codes-exit-codes-description for examples
|
||||
// such as 3010 - "The requested operation is successful. Changes will not be effective until the system is rebooted."
|
||||
ValidExitCodes []int `mapstructure:"valid_exit_codes"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
type Provisioner struct {
|
||||
config Config
|
||||
communicator packer.Communicator
|
||||
}
|
||||
|
||||
type ExecuteCommandTemplate struct {
|
||||
Vars string
|
||||
Path string
|
||||
}
|
||||
|
||||
func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||
err := config.Decode(&p.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"execute_command",
|
||||
},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.config.EnvVarFormat == "" {
|
||||
p.config.EnvVarFormat = `$env:%s=\"%s\"; `
|
||||
}
|
||||
|
||||
if p.config.ElevatedEnvVarFormat == "" {
|
||||
p.config.ElevatedEnvVarFormat = `$env:%s="%s"; `
|
||||
}
|
||||
|
||||
if p.config.ExecuteCommand == "" {
|
||||
p.config.ExecuteCommand = `powershell "& { {{.Vars}}{{.Path}}; exit $LastExitCode}"`
|
||||
}
|
||||
|
||||
if p.config.ElevatedExecuteCommand == "" {
|
||||
p.config.ElevatedExecuteCommand = `{{.Vars}}{{.Path}}`
|
||||
}
|
||||
|
||||
if p.config.Inline != nil && len(p.config.Inline) == 0 {
|
||||
p.config.Inline = nil
|
||||
}
|
||||
|
||||
if p.config.StartRetryTimeout == 0 {
|
||||
p.config.StartRetryTimeout = 5 * time.Minute
|
||||
}
|
||||
|
||||
if p.config.RemotePath == "" {
|
||||
p.config.RemotePath = DefaultRemotePath
|
||||
}
|
||||
|
||||
if p.config.Scripts == nil {
|
||||
p.config.Scripts = make([]string, 0)
|
||||
}
|
||||
|
||||
if p.config.Vars == nil {
|
||||
p.config.Vars = make([]string, 0)
|
||||
}
|
||||
|
||||
if p.config.ValidExitCodes == nil {
|
||||
p.config.ValidExitCodes = []int{0}
|
||||
}
|
||||
|
||||
var errs error
|
||||
if p.config.Script != "" && len(p.config.Scripts) > 0 {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
errors.New("Only one of script or scripts can be specified."))
|
||||
}
|
||||
|
||||
if p.config.ElevatedUser != "" && p.config.ElevatedPassword == "" {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
errors.New("Must supply an 'elevated_password' if 'elevated_user' provided"))
|
||||
}
|
||||
|
||||
if p.config.ElevatedUser == "" && p.config.ElevatedPassword != "" {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
errors.New("Must supply an 'elevated_user' if 'elevated_password' provided"))
|
||||
}
|
||||
|
||||
if p.config.Script != "" {
|
||||
p.config.Scripts = []string{p.config.Script}
|
||||
}
|
||||
|
||||
if len(p.config.Scripts) == 0 && p.config.Inline == nil {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
errors.New("Either a script file or inline script must be specified."))
|
||||
} else if len(p.config.Scripts) > 0 && p.config.Inline != nil {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
errors.New("Only a script file or an inline script can be specified, not both."))
|
||||
}
|
||||
|
||||
for _, path := range p.config.Scripts {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Bad script '%s': %s", path, err))
|
||||
}
|
||||
}
|
||||
|
||||
// Do a check for bad environment variables, such as '=foo', 'foobar'
|
||||
for _, kv := range p.config.Vars {
|
||||
vs := strings.SplitN(kv, "=", 2)
|
||||
if len(vs) != 2 || vs[0] == "" {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Environment variable not in format 'key=value': %s", kv))
|
||||
}
|
||||
}
|
||||
|
||||
if errs != nil {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Takes the inline scripts, concatenates them
|
||||
// into a temporary file and returns a string containing the location
|
||||
// of said file.
|
||||
func extractScript(p *Provisioner) (string, error) {
|
||||
temp, err := ioutil.TempFile(os.TempDir(), "packer-powershell-provisioner")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer temp.Close()
|
||||
writer := bufio.NewWriter(temp)
|
||||
for _, command := range p.config.Inline {
|
||||
log.Printf("Found command: %s", command)
|
||||
if _, err := writer.WriteString(command + "\n"); err != nil {
|
||||
return "", fmt.Errorf("Error preparing shell script: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := writer.Flush(); err != nil {
|
||||
return "", fmt.Errorf("Error preparing shell script: %s", err)
|
||||
}
|
||||
|
||||
return temp.Name(), nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
||||
ui.Say(fmt.Sprintf("Provisioning with Powershell..."))
|
||||
p.communicator = comm
|
||||
|
||||
scripts := make([]string, len(p.config.Scripts))
|
||||
copy(scripts, p.config.Scripts)
|
||||
|
||||
// Build our variables up by adding in the build name and builder type
|
||||
envVars := make([]string, len(p.config.Vars)+2)
|
||||
envVars[0] = "PACKER_BUILD_NAME=" + p.config.PackerBuildName
|
||||
envVars[1] = "PACKER_BUILDER_TYPE=" + p.config.PackerBuilderType
|
||||
copy(envVars, p.config.Vars)
|
||||
|
||||
if p.config.Inline != nil {
|
||||
temp, err := extractScript(p)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to extract inline scripts into a file: %s", err))
|
||||
}
|
||||
scripts = append(scripts, temp)
|
||||
}
|
||||
|
||||
for _, path := range scripts {
|
||||
ui.Say(fmt.Sprintf("Provisioning with shell script: %s", path))
|
||||
|
||||
log.Printf("Opening %s for reading", path)
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error opening shell script: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
command, err := p.createCommandText()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error processing command: %s", err)
|
||||
}
|
||||
|
||||
// Upload the file and run the command. Do this in the context of
|
||||
// a single retryable function so that we don't end up with
|
||||
// the case that the upload succeeded, a restart is initiated,
|
||||
// and then the command is executed but the file doesn't exist
|
||||
// any longer.
|
||||
var cmd *packer.RemoteCmd
|
||||
err = p.retryable(func() error {
|
||||
if _, err := f.Seek(0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := comm.Upload(p.config.RemotePath, f, nil); err != nil {
|
||||
return fmt.Errorf("Error uploading script: %s", err)
|
||||
}
|
||||
|
||||
cmd = &packer.RemoteCmd{Command: command}
|
||||
return cmd.StartWithUi(comm, ui)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Close the original file since we copied it
|
||||
f.Close()
|
||||
|
||||
// Check exit code against allowed codes (likely just 0)
|
||||
validExitCode := false
|
||||
for _, v := range p.config.ValidExitCodes {
|
||||
if cmd.ExitStatus == v {
|
||||
validExitCode = true
|
||||
}
|
||||
}
|
||||
if !validExitCode {
|
||||
return fmt.Errorf(
|
||||
"Script exited with non-zero exit status: %d. Allowed exit codes are: %v",
|
||||
cmd.ExitStatus, p.config.ValidExitCodes)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) Cancel() {
|
||||
// Just hard quit. It isn't a big deal if what we're doing keeps
|
||||
// running on the other side.
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// retryable will retry the given function over and over until a
|
||||
// non-error is returned.
|
||||
func (p *Provisioner) retryable(f func() error) error {
|
||||
startTimeout := time.After(p.config.StartRetryTimeout)
|
||||
for {
|
||||
var err error
|
||||
if err = f(); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create an error and log it
|
||||
err = fmt.Errorf("Retryable error: %s", err)
|
||||
log.Printf(err.Error())
|
||||
|
||||
// Check if we timed out, otherwise we retry. It is safe to
|
||||
// retry since the only error case above is if the command
|
||||
// failed to START.
|
||||
select {
|
||||
case <-startTimeout:
|
||||
return err
|
||||
default:
|
||||
time.Sleep(retryableSleep)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string, err error) {
|
||||
flattened = ""
|
||||
envVars := make(map[string]string)
|
||||
|
||||
// Always available Packer provided env vars
|
||||
envVars["PACKER_BUILD_NAME"] = p.config.PackerBuildName
|
||||
envVars["PACKER_BUILDER_TYPE"] = p.config.PackerBuilderType
|
||||
|
||||
// Split vars into key/value components
|
||||
for _, envVar := range p.config.Vars {
|
||||
keyValue := strings.Split(envVar, "=")
|
||||
if len(keyValue) != 2 {
|
||||
err = errors.New("Shell provisioner environment variables must be in key=value format")
|
||||
return
|
||||
}
|
||||
envVars[keyValue[0]] = keyValue[1]
|
||||
}
|
||||
|
||||
// Create a list of env var keys in sorted order
|
||||
var keys []string
|
||||
for k := range envVars {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
format := p.config.EnvVarFormat
|
||||
if elevated {
|
||||
format = p.config.ElevatedEnvVarFormat
|
||||
}
|
||||
|
||||
// Re-assemble vars using OS specific format pattern and flatten
|
||||
for _, key := range keys {
|
||||
flattened += fmt.Sprintf(format, key, envVars[key])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Provisioner) createCommandText() (command string, err error) {
|
||||
// Create environment variables to set before executing the command
|
||||
flattenedEnvVars, err := p.createFlattenedEnvVars(false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
p.config.ctx.Data = &ExecuteCommandTemplate{
|
||||
Vars: flattenedEnvVars,
|
||||
Path: p.config.RemotePath,
|
||||
}
|
||||
command, err = interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error processing command: %s", err)
|
||||
}
|
||||
|
||||
// Return the interpolated command
|
||||
if p.config.ElevatedUser == "" {
|
||||
return command, nil
|
||||
}
|
||||
|
||||
// Can't double escape the env vars, lets create shiny new ones
|
||||
flattenedEnvVars, err = p.createFlattenedEnvVars(true)
|
||||
p.config.ctx.Data = &ExecuteCommandTemplate{
|
||||
Vars: flattenedEnvVars,
|
||||
Path: p.config.RemotePath,
|
||||
}
|
||||
command, err = interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error processing command: %s", err)
|
||||
}
|
||||
|
||||
// OK so we need an elevated shell runner to wrap our command, this is going to have its own path
|
||||
// generate the script and update the command runner in the process
|
||||
path, err := p.generateElevatedRunner(command)
|
||||
|
||||
// Return the path to the elevated shell wrapper
|
||||
command = fmt.Sprintf("powershell -executionpolicy bypass -file \"%s\"", path)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath string, err error) {
|
||||
log.Printf("Building elevated command wrapper for: %s", command)
|
||||
|
||||
// generate command
|
||||
var buffer bytes.Buffer
|
||||
err = elevatedTemplate.Execute(&buffer, elevatedOptions{
|
||||
User: p.config.ElevatedUser,
|
||||
Password: p.config.ElevatedPassword,
|
||||
TaskDescription: "Packer elevated task",
|
||||
TaskName: fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()),
|
||||
EncodedCommand: powershellEncode([]byte(command + "; exit $LASTEXITCODE")),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating elevated template: %s", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
tmpFile, err := ioutil.TempFile(os.TempDir(), "packer-elevated-shell.ps1")
|
||||
writer := bufio.NewWriter(tmpFile)
|
||||
if _, err := writer.WriteString(string(buffer.Bytes())); err != nil {
|
||||
return "", fmt.Errorf("Error preparing elevated shell script: %s", err)
|
||||
}
|
||||
|
||||
if err := writer.Flush(); err != nil {
|
||||
return "", fmt.Errorf("Error preparing elevated shell script: %s", err)
|
||||
}
|
||||
tmpFile.Close()
|
||||
f, err := os.Open(tmpFile.Name())
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error opening temporary elevated shell script: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
uuid := uuid.TimeOrderedUUID()
|
||||
path := fmt.Sprintf(`${env:TEMP}\packer-elevated-shell-%s.ps1`, uuid)
|
||||
log.Printf("Uploading elevated shell wrapper for command [%s] to [%s] from [%s]", command, path, tmpFile.Name())
|
||||
err = p.communicator.Upload(path, f, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error preparing elevated shell script: %s", err)
|
||||
}
|
||||
|
||||
// CMD formatted Path required for this op
|
||||
path = fmt.Sprintf("%s-%s.ps1", "%TEMP%\\packer-elevated-shell", uuid)
|
||||
return path, err
|
||||
}
|
|
@ -0,0 +1,656 @@
|
|||
package powershell
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
//"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"inline": []interface{}{"foo", "bar"},
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
//log.SetOutput(ioutil.Discard)
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_extractScript(t *testing.T) {
|
||||
config := testConfig()
|
||||
p := new(Provisioner)
|
||||
_ = p.Prepare(config)
|
||||
file, err := extractScript(p)
|
||||
if err != nil {
|
||||
t.Fatalf("Should not be error: %s", err)
|
||||
}
|
||||
t.Logf("File: %s", file)
|
||||
if strings.Index(file, os.TempDir()) != 0 {
|
||||
t.Fatalf("Temp file should reside in %s. File location: %s", os.TempDir(), file)
|
||||
}
|
||||
|
||||
// File contents should contain 2 lines concatenated by newlines: foo\nbar
|
||||
readFile, err := ioutil.ReadFile(file)
|
||||
expectedContents := "foo\nbar\n"
|
||||
s := string(readFile[:])
|
||||
if s != expectedContents {
|
||||
t.Fatalf("Expected generated inlineScript to equal '%s', got '%s'", expectedContents, s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisioner_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Provisioner{}
|
||||
if _, ok := raw.(packer.Provisioner); !ok {
|
||||
t.Fatalf("must be a Provisioner")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_Defaults(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.config.RemotePath != DefaultRemotePath {
|
||||
t.Errorf("unexpected remote path: %s", p.config.RemotePath)
|
||||
}
|
||||
|
||||
if p.config.ElevatedUser != "" {
|
||||
t.Error("expected elevated_user to be empty")
|
||||
}
|
||||
if p.config.ElevatedPassword != "" {
|
||||
t.Error("expected elevated_password to be empty")
|
||||
}
|
||||
|
||||
if p.config.ExecuteCommand != "powershell \"& { {{.Vars}}{{.Path}}; exit $LastExitCode}\"" {
|
||||
t.Fatalf("Default command should be powershell \"& { {{.Vars}}{{.Path}}; exit $LastExitCode}\", but got %s", p.config.ExecuteCommand)
|
||||
}
|
||||
|
||||
if p.config.ElevatedExecuteCommand != "{{.Vars}}{{.Path}}" {
|
||||
t.Fatalf("Default command should be powershell {{.Vars}}{{.Path}}, but got %s", p.config.ElevatedExecuteCommand)
|
||||
}
|
||||
|
||||
if p.config.ValidExitCodes == nil {
|
||||
t.Fatalf("ValidExitCodes should not be nil")
|
||||
}
|
||||
if p.config.ValidExitCodes != nil {
|
||||
expCodes := []int{0}
|
||||
for i, v := range p.config.ValidExitCodes {
|
||||
if v != expCodes[i] {
|
||||
t.Fatalf("Expected ValidExitCodes don't match actual")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.ElevatedEnvVarFormat != `$env:%s="%s"; ` {
|
||||
t.Fatalf("Default command should be powershell \"{{.Vars}}{{.Path}}\", but got %s", p.config.ElevatedEnvVarFormat)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_Config(t *testing.T) {
|
||||
config := testConfig()
|
||||
config["elevated_user"] = "{{user `user`}}"
|
||||
config["elevated_password"] = "{{user `password`}}"
|
||||
config[packer.UserVariablesConfigKey] = map[string]string{
|
||||
"user": "myusername",
|
||||
"password": "mypassword",
|
||||
}
|
||||
|
||||
var p Provisioner
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.config.ElevatedUser != "myusername" {
|
||||
t.Fatalf("Expected 'myusername' for key `elevated_user`: %s", p.config.ElevatedUser)
|
||||
}
|
||||
if p.config.ElevatedPassword != "mypassword" {
|
||||
t.Fatalf("Expected 'mypassword' for key `elevated_password`: %s", p.config.ElevatedPassword)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_InvalidKey(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
// Add a random key
|
||||
config["i_should_not_be_valid"] = true
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_Elevated(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
// Add a random key
|
||||
config["elevated_user"] = "vagrant"
|
||||
err := p.Prepare(config)
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("should have error (only provided elevated_user)")
|
||||
}
|
||||
|
||||
config["elevated_password"] = "vagrant"
|
||||
err = p.Prepare(config)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("should not have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_Script(t *testing.T) {
|
||||
config := testConfig()
|
||||
delete(config, "inline")
|
||||
|
||||
config["script"] = "/this/should/not/exist"
|
||||
p := new(Provisioner)
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test with a good one
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("error tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config["script"] = tf.Name()
|
||||
p = new(Provisioner)
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_ScriptAndInline(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
delete(config, "inline")
|
||||
delete(config, "script")
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test with both
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("error tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config["inline"] = []interface{}{"foo"}
|
||||
config["script"] = tf.Name()
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_ScriptAndScripts(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
// Test with both
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("error tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config["inline"] = []interface{}{"foo"}
|
||||
config["scripts"] = []string{tf.Name()}
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_Scripts(t *testing.T) {
|
||||
config := testConfig()
|
||||
delete(config, "inline")
|
||||
|
||||
config["scripts"] = []string{}
|
||||
p := new(Provisioner)
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test with a good one
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("error tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config["scripts"] = []string{tf.Name()}
|
||||
p = new(Provisioner)
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_EnvironmentVars(t *testing.T) {
|
||||
config := testConfig()
|
||||
|
||||
// Test with a bad case
|
||||
config["environment_vars"] = []string{"badvar", "good=var"}
|
||||
p := new(Provisioner)
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test with a trickier case
|
||||
config["environment_vars"] = []string{"=bad"}
|
||||
p = new(Provisioner)
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test with a good case
|
||||
// Note: baz= is a real env variable, just empty
|
||||
config["environment_vars"] = []string{"FOO=bar", "baz="}
|
||||
p = new(Provisioner)
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerQuote_EnvironmentVars(t *testing.T) {
|
||||
config := testConfig()
|
||||
|
||||
config["environment_vars"] = []string{"keyone=valueone", "keytwo=value\ntwo", "keythree='valuethree'", "keyfour='value\nfour'"}
|
||||
p := new(Provisioner)
|
||||
p.Prepare(config)
|
||||
|
||||
expectedValue := "keyone=valueone"
|
||||
if p.config.Vars[0] != expectedValue {
|
||||
t.Fatalf("%s should be equal to %s", p.config.Vars[0], expectedValue)
|
||||
}
|
||||
|
||||
expectedValue = "keytwo=value\ntwo"
|
||||
if p.config.Vars[1] != expectedValue {
|
||||
t.Fatalf("%s should be equal to %s", p.config.Vars[1], expectedValue)
|
||||
}
|
||||
|
||||
expectedValue = "keythree='valuethree'"
|
||||
if p.config.Vars[2] != expectedValue {
|
||||
t.Fatalf("%s should be equal to %s", p.config.Vars[2], expectedValue)
|
||||
}
|
||||
|
||||
expectedValue = "keyfour='value\nfour'"
|
||||
if p.config.Vars[3] != expectedValue {
|
||||
t.Fatalf("%s should be equal to %s", p.config.Vars[3], expectedValue)
|
||||
}
|
||||
}
|
||||
|
||||
func testUi() *packer.BasicUi {
|
||||
return &packer.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
ErrorWriter: new(bytes.Buffer),
|
||||
}
|
||||
}
|
||||
|
||||
func testObjects() (packer.Ui, packer.Communicator) {
|
||||
ui := testUi()
|
||||
return ui, new(packer.MockCommunicator)
|
||||
}
|
||||
|
||||
func TestProvisionerProvision_ValidExitCodes(t *testing.T) {
|
||||
config := testConfig()
|
||||
delete(config, "inline")
|
||||
|
||||
// Defaults provided by Packer
|
||||
config["remote_path"] = "c:/Windows/Temp/inlineScript.bat"
|
||||
config["inline"] = []string{"whoami"}
|
||||
ui := testUi()
|
||||
p := new(Provisioner)
|
||||
|
||||
// Defaults provided by Packer
|
||||
p.config.PackerBuildName = "vmware"
|
||||
p.config.PackerBuilderType = "iso"
|
||||
p.config.ValidExitCodes = []int{0, 200}
|
||||
comm := new(packer.MockCommunicator)
|
||||
comm.StartExitStatus = 200
|
||||
p.Prepare(config)
|
||||
err := p.Provision(ui, comm)
|
||||
if err != nil {
|
||||
t.Fatal("should not have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerProvision_InvalidExitCodes(t *testing.T) {
|
||||
config := testConfig()
|
||||
delete(config, "inline")
|
||||
|
||||
// Defaults provided by Packer
|
||||
config["remote_path"] = "c:/Windows/Temp/inlineScript.bat"
|
||||
config["inline"] = []string{"whoami"}
|
||||
ui := testUi()
|
||||
p := new(Provisioner)
|
||||
|
||||
// Defaults provided by Packer
|
||||
p.config.PackerBuildName = "vmware"
|
||||
p.config.PackerBuilderType = "iso"
|
||||
p.config.ValidExitCodes = []int{0, 200}
|
||||
comm := new(packer.MockCommunicator)
|
||||
comm.StartExitStatus = 201 // Invalid!
|
||||
p.Prepare(config)
|
||||
err := p.Provision(ui, comm)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerProvision_Inline(t *testing.T) {
|
||||
config := testConfig()
|
||||
delete(config, "inline")
|
||||
|
||||
// Defaults provided by Packer
|
||||
config["remote_path"] = "c:/Windows/Temp/inlineScript.bat"
|
||||
config["inline"] = []string{"whoami"}
|
||||
ui := testUi()
|
||||
p := new(Provisioner)
|
||||
|
||||
// Defaults provided by Packer
|
||||
p.config.PackerBuildName = "vmware"
|
||||
p.config.PackerBuilderType = "iso"
|
||||
comm := new(packer.MockCommunicator)
|
||||
p.Prepare(config)
|
||||
err := p.Provision(ui, comm)
|
||||
if err != nil {
|
||||
t.Fatal("should not have error")
|
||||
}
|
||||
|
||||
expectedCommand := `powershell "& { $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}"`
|
||||
|
||||
// Should run the command without alteration
|
||||
if comm.StartCmd.Command != expectedCommand {
|
||||
t.Fatalf("Expect command to be: %s, got %s", expectedCommand, comm.StartCmd.Command)
|
||||
}
|
||||
|
||||
envVars := make([]string, 2)
|
||||
envVars[0] = "FOO=BAR"
|
||||
envVars[1] = "BAR=BAZ"
|
||||
config["environment_vars"] = envVars
|
||||
config["remote_path"] = "c:/Windows/Temp/inlineScript.bat"
|
||||
|
||||
p.Prepare(config)
|
||||
err = p.Provision(ui, comm)
|
||||
if err != nil {
|
||||
t.Fatal("should not have error")
|
||||
}
|
||||
|
||||
expectedCommand = `powershell "& { $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}"`
|
||||
|
||||
// Should run the command without alteration
|
||||
if comm.StartCmd.Command != expectedCommand {
|
||||
t.Fatalf("Expect command to be: %s, got: %s", expectedCommand, comm.StartCmd.Command)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerProvision_Scripts(t *testing.T) {
|
||||
tempFile, _ := ioutil.TempFile("", "packer")
|
||||
defer os.Remove(tempFile.Name())
|
||||
config := testConfig()
|
||||
delete(config, "inline")
|
||||
config["scripts"] = []string{tempFile.Name()}
|
||||
config["packer_build_name"] = "foobuild"
|
||||
config["packer_builder_type"] = "footype"
|
||||
ui := testUi()
|
||||
|
||||
p := new(Provisioner)
|
||||
comm := new(packer.MockCommunicator)
|
||||
p.Prepare(config)
|
||||
err := p.Provision(ui, comm)
|
||||
if err != nil {
|
||||
t.Fatal("should not have error")
|
||||
}
|
||||
|
||||
//powershell -Command "$env:PACKER_BUILDER_TYPE=''"; powershell -Command "$env:PACKER_BUILD_NAME='foobuild'"; powershell -Command c:/Windows/Temp/script.ps1
|
||||
expectedCommand := `powershell "& { $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}"`
|
||||
|
||||
// Should run the command without alteration
|
||||
if comm.StartCmd.Command != expectedCommand {
|
||||
t.Fatalf("Expect command to be %s NOT %s", expectedCommand, comm.StartCmd.Command)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) {
|
||||
tempFile, _ := ioutil.TempFile("", "packer")
|
||||
config := testConfig()
|
||||
ui := testUi()
|
||||
defer os.Remove(tempFile.Name())
|
||||
delete(config, "inline")
|
||||
|
||||
config["scripts"] = []string{tempFile.Name()}
|
||||
config["packer_build_name"] = "foobuild"
|
||||
config["packer_builder_type"] = "footype"
|
||||
|
||||
// Env vars - currently should not effect them
|
||||
envVars := make([]string, 2)
|
||||
envVars[0] = "FOO=BAR"
|
||||
envVars[1] = "BAR=BAZ"
|
||||
config["environment_vars"] = envVars
|
||||
|
||||
p := new(Provisioner)
|
||||
comm := new(packer.MockCommunicator)
|
||||
p.Prepare(config)
|
||||
err := p.Provision(ui, comm)
|
||||
if err != nil {
|
||||
t.Fatal("should not have error")
|
||||
}
|
||||
|
||||
expectedCommand := `powershell "& { $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}"`
|
||||
|
||||
// Should run the command without alteration
|
||||
if comm.StartCmd.Command != expectedCommand {
|
||||
t.Fatalf("Expect command to be %s NOT %s", expectedCommand, comm.StartCmd.Command)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerProvision_UISlurp(t *testing.T) {
|
||||
// UI should be called n times
|
||||
|
||||
// UI should receive following messages / output
|
||||
}
|
||||
|
||||
func TestProvisioner_createFlattenedElevatedEnvVars_windows(t *testing.T) {
|
||||
config := testConfig()
|
||||
|
||||
p := new(Provisioner)
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error preparing config: %s", err)
|
||||
}
|
||||
|
||||
// Defaults provided by Packer
|
||||
p.config.PackerBuildName = "vmware"
|
||||
p.config.PackerBuilderType = "iso"
|
||||
|
||||
// no user env var
|
||||
flattenedEnvVars, err := p.createFlattenedEnvVars(true)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error creating flattened env vars: %s", err)
|
||||
}
|
||||
if flattenedEnvVars != "$env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; " {
|
||||
t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars)
|
||||
}
|
||||
|
||||
// single user env var
|
||||
p.config.Vars = []string{"FOO=bar"}
|
||||
|
||||
flattenedEnvVars, err = p.createFlattenedEnvVars(true)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error creating flattened env vars: %s", err)
|
||||
}
|
||||
if flattenedEnvVars != "$env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; " {
|
||||
t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars)
|
||||
}
|
||||
|
||||
// multiple user env vars
|
||||
p.config.Vars = []string{"FOO=bar", "BAZ=qux"}
|
||||
|
||||
flattenedEnvVars, err = p.createFlattenedEnvVars(true)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error creating flattened env vars: %s", err)
|
||||
}
|
||||
if flattenedEnvVars != "$env:BAZ=\"qux\"; $env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; " {
|
||||
t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) {
|
||||
config := testConfig()
|
||||
|
||||
p := new(Provisioner)
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error preparing config: %s", err)
|
||||
}
|
||||
|
||||
// Defaults provided by Packer
|
||||
p.config.PackerBuildName = "vmware"
|
||||
p.config.PackerBuilderType = "iso"
|
||||
|
||||
// no user env var
|
||||
flattenedEnvVars, err := p.createFlattenedEnvVars(false)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error creating flattened env vars: %s", err)
|
||||
}
|
||||
if flattenedEnvVars != "$env:PACKER_BUILDER_TYPE=\\\"iso\\\"; $env:PACKER_BUILD_NAME=\\\"vmware\\\"; " {
|
||||
t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars)
|
||||
}
|
||||
|
||||
// single user env var
|
||||
p.config.Vars = []string{"FOO=bar"}
|
||||
|
||||
flattenedEnvVars, err = p.createFlattenedEnvVars(false)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error creating flattened env vars: %s", err)
|
||||
}
|
||||
if flattenedEnvVars != "$env:FOO=\\\"bar\\\"; $env:PACKER_BUILDER_TYPE=\\\"iso\\\"; $env:PACKER_BUILD_NAME=\\\"vmware\\\"; " {
|
||||
t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars)
|
||||
}
|
||||
|
||||
// multiple user env vars
|
||||
p.config.Vars = []string{"FOO=bar", "BAZ=qux"}
|
||||
|
||||
flattenedEnvVars, err = p.createFlattenedEnvVars(false)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error creating flattened env vars: %s", err)
|
||||
}
|
||||
if flattenedEnvVars != "$env:BAZ=\\\"qux\\\"; $env:FOO=\\\"bar\\\"; $env:PACKER_BUILDER_TYPE=\\\"iso\\\"; $env:PACKER_BUILD_NAME=\\\"vmware\\\"; " {
|
||||
t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvision_createCommandText(t *testing.T) {
|
||||
|
||||
config := testConfig()
|
||||
p := new(Provisioner)
|
||||
comm := new(packer.MockCommunicator)
|
||||
p.communicator = comm
|
||||
_ = p.Prepare(config)
|
||||
|
||||
// Non-elevated
|
||||
cmd, _ := p.createCommandText()
|
||||
if cmd != "powershell \"& { $env:PACKER_BUILDER_TYPE=\\\"\\\"; $env:PACKER_BUILD_NAME=\\\"\\\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}\"" {
|
||||
t.Fatalf("Got unexpected non-elevated command: %s", cmd)
|
||||
}
|
||||
|
||||
// Elevated
|
||||
p.config.ElevatedUser = "vagrant"
|
||||
p.config.ElevatedPassword = "vagrant"
|
||||
cmd, _ = p.createCommandText()
|
||||
matched, _ := regexp.MatchString("powershell -executionpolicy bypass -file \"%TEMP%(.{1})packer-elevated-shell.*", cmd)
|
||||
if !matched {
|
||||
t.Fatalf("Got unexpected elevated command: %s", cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvision_generateElevatedShellRunner(t *testing.T) {
|
||||
|
||||
// Non-elevated
|
||||
config := testConfig()
|
||||
p := new(Provisioner)
|
||||
p.Prepare(config)
|
||||
comm := new(packer.MockCommunicator)
|
||||
p.communicator = comm
|
||||
path, err := p.generateElevatedRunner("whoami")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Did not expect error: %s", err.Error())
|
||||
}
|
||||
|
||||
if comm.UploadCalled != true {
|
||||
t.Fatalf("Should have uploaded file")
|
||||
}
|
||||
|
||||
matched, _ := regexp.MatchString("%TEMP%(.{1})packer-elevated-shell.*", path)
|
||||
if !matched {
|
||||
t.Fatalf("Got unexpected file: %s", path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetryable(t *testing.T) {
|
||||
config := testConfig()
|
||||
|
||||
count := 0
|
||||
retryMe := func() error {
|
||||
t.Logf("RetryMe, attempt number %d", count)
|
||||
if count == 2 {
|
||||
return nil
|
||||
}
|
||||
count++
|
||||
return errors.New(fmt.Sprintf("Still waiting %d more times...", 2-count))
|
||||
}
|
||||
retryableSleep = 50 * time.Millisecond
|
||||
p := new(Provisioner)
|
||||
p.config.StartRetryTimeout = 155 * time.Millisecond
|
||||
err := p.Prepare(config)
|
||||
err = p.retryable(retryMe)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error retrying funuction")
|
||||
}
|
||||
|
||||
count = 0
|
||||
p.config.StartRetryTimeout = 10 * time.Millisecond
|
||||
err = p.Prepare(config)
|
||||
err = p.retryable(retryMe)
|
||||
if err == nil {
|
||||
t.Fatalf("should have error retrying funuction")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancel(t *testing.T) {
|
||||
// Don't actually call Cancel() as it performs an os.Exit(0)
|
||||
// which kills the 'go test' tool
|
||||
}
|
|
@ -276,7 +276,15 @@ func (p *Provisioner) uploadManifests(ui packer.Ui, comm packer.Communicator) (s
|
|||
}
|
||||
defer f.Close()
|
||||
|
||||
manifestFilename := filepath.Base(p.config.ManifestFile)
|
||||
manifestFilename := p.config.ManifestFile
|
||||
if fi, err := os.Stat(p.config.ManifestFile); err != nil {
|
||||
return "", fmt.Errorf("Error inspecting manifest file: %s", err)
|
||||
} else if !fi.IsDir() {
|
||||
manifestFilename = filepath.Base(manifestFilename)
|
||||
} else {
|
||||
ui.Say("WARNING: manifest_file should be a file. Use manifest_dir for directories")
|
||||
}
|
||||
|
||||
remoteManifestFile := fmt.Sprintf("%s/%s", remoteManifestsPath, manifestFilename)
|
||||
if err := comm.Upload(remoteManifestFile, f, nil); err != nil {
|
||||
return "", err
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
package restart
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/masterzen/winrm/winrm"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/config"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
var DefaultRestartCommand = "powershell \"& {Restart-Computer -force }\""
|
||||
var DefaultRestartCheckCommand = winrm.Powershell(`echo "${env:COMPUTERNAME} restarted."`)
|
||||
var retryableSleep = 5 * time.Second
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
// The command used to restart the guest machine
|
||||
RestartCommand string `mapstructure:"restart_command"`
|
||||
|
||||
// The command used to check if the guest machine has restarted
|
||||
// The output of this command will be displayed to the user
|
||||
RestartCheckCommand string `mapstructure:"restart_check_command"`
|
||||
|
||||
// The timeout for waiting for the machine to restart
|
||||
RestartTimeout time.Duration `mapstructure:"restart_timeout"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
type Provisioner struct {
|
||||
config Config
|
||||
comm packer.Communicator
|
||||
ui packer.Ui
|
||||
cancel chan struct{}
|
||||
cancelLock sync.Mutex
|
||||
}
|
||||
|
||||
func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||
err := config.Decode(&p.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"execute_command",
|
||||
},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.config.RestartCommand == "" {
|
||||
p.config.RestartCommand = DefaultRestartCommand
|
||||
}
|
||||
|
||||
if p.config.RestartCheckCommand == "" {
|
||||
p.config.RestartCheckCommand = DefaultRestartCheckCommand
|
||||
}
|
||||
|
||||
if p.config.RestartTimeout == 0 {
|
||||
p.config.RestartTimeout = 5 * time.Minute
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
||||
p.cancelLock.Lock()
|
||||
p.cancel = make(chan struct{})
|
||||
p.cancelLock.Unlock()
|
||||
|
||||
ui.Say("Restarting Machine")
|
||||
p.comm = comm
|
||||
p.ui = ui
|
||||
|
||||
var cmd *packer.RemoteCmd
|
||||
command := p.config.RestartCommand
|
||||
err := p.retryable(func() error {
|
||||
cmd = &packer.RemoteCmd{Command: command}
|
||||
return cmd.StartWithUi(comm, ui)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cmd.ExitStatus != 0 {
|
||||
return fmt.Errorf("Restart script exited with non-zero exit status: %d", cmd.ExitStatus)
|
||||
}
|
||||
|
||||
return waitForRestart(p)
|
||||
}
|
||||
|
||||
var waitForRestart = func(p *Provisioner) error {
|
||||
ui := p.ui
|
||||
ui.Say("Waiting for machine to restart...")
|
||||
waitDone := make(chan bool, 1)
|
||||
timeout := time.After(p.config.RestartTimeout)
|
||||
var err error
|
||||
|
||||
go func() {
|
||||
log.Printf("Waiting for machine to become available...")
|
||||
err = waitForCommunicator(p)
|
||||
waitDone <- true
|
||||
}()
|
||||
|
||||
log.Printf("Waiting for machine to reboot with timeout: %s", p.config.RestartTimeout)
|
||||
|
||||
WaitLoop:
|
||||
for {
|
||||
// Wait for either WinRM to become available, a timeout to occur,
|
||||
// or an interrupt to come through.
|
||||
select {
|
||||
case <-waitDone:
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Error waiting for WinRM: %s", err))
|
||||
return err
|
||||
}
|
||||
|
||||
ui.Say("Machine successfully restarted, moving on")
|
||||
close(p.cancel)
|
||||
break WaitLoop
|
||||
case <-timeout:
|
||||
err := fmt.Errorf("Timeout waiting for WinRM.")
|
||||
ui.Error(err.Error())
|
||||
close(p.cancel)
|
||||
return err
|
||||
case <-p.cancel:
|
||||
close(waitDone)
|
||||
return fmt.Errorf("Interrupt detected, quitting waiting for machine to restart")
|
||||
break WaitLoop
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
var waitForCommunicator = func(p *Provisioner) error {
|
||||
cmd := &packer.RemoteCmd{Command: p.config.RestartCheckCommand}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-p.cancel:
|
||||
log.Println("Communicator wait cancelled, exiting loop")
|
||||
return fmt.Errorf("Communicator wait cancelled")
|
||||
case <-time.After(retryableSleep):
|
||||
}
|
||||
|
||||
log.Printf("Attempting to communicator to machine with: '%s'", cmd.Command)
|
||||
|
||||
err := cmd.StartWithUi(p.comm, p.ui)
|
||||
if err != nil {
|
||||
log.Printf("Communication connection err: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("Connected to machine")
|
||||
break
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) Cancel() {
|
||||
log.Printf("Received interrupt Cancel()")
|
||||
|
||||
p.cancelLock.Lock()
|
||||
defer p.cancelLock.Unlock()
|
||||
if p.cancel != nil {
|
||||
close(p.cancel)
|
||||
}
|
||||
}
|
||||
|
||||
// retryable will retry the given function over and over until a
|
||||
// non-error is returned.
|
||||
func (p *Provisioner) retryable(f func() error) error {
|
||||
startTimeout := time.After(p.config.RestartTimeout)
|
||||
for {
|
||||
var err error
|
||||
if err = f(); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create an error and log it
|
||||
err = fmt.Errorf("Retryable error: %s", err)
|
||||
log.Printf(err.Error())
|
||||
|
||||
// Check if we timed out, otherwise we retry. It is safe to
|
||||
// retry since the only error case above is if the command
|
||||
// failed to START.
|
||||
select {
|
||||
case <-startTimeout:
|
||||
return err
|
||||
default:
|
||||
time.Sleep(retryableSleep)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,355 @@
|
|||
package restart
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
func TestProvisioner_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Provisioner{}
|
||||
if _, ok := raw.(packer.Provisioner); !ok {
|
||||
t.Fatalf("must be a Provisioner")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_Defaults(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.config.RestartTimeout != 5*time.Minute {
|
||||
t.Errorf("unexpected remote path: %s", p.config.RestartTimeout)
|
||||
}
|
||||
|
||||
if p.config.RestartCommand != "powershell \"& {Restart-Computer -force }\"" {
|
||||
t.Errorf("unexpected remote path: %s", p.config.RestartCommand)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_ConfigRetryTimeout(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
config["restart_timeout"] = "1m"
|
||||
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.config.RestartTimeout != 1*time.Minute {
|
||||
t.Errorf("unexpected remote path: %s", p.config.RestartTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_ConfigErrors(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
config["restart_timeout"] = "m"
|
||||
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error parsing restart_timeout but did not receive one.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_InvalidKey(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
// Add a random key
|
||||
config["i_should_not_be_valid"] = true
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func testUi() *packer.BasicUi {
|
||||
return &packer.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
ErrorWriter: new(bytes.Buffer),
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerProvision_Success(t *testing.T) {
|
||||
config := testConfig()
|
||||
|
||||
// Defaults provided by Packer
|
||||
ui := testUi()
|
||||
p := new(Provisioner)
|
||||
|
||||
// Defaults provided by Packer
|
||||
comm := new(packer.MockCommunicator)
|
||||
p.Prepare(config)
|
||||
waitForCommunicatorOld := waitForCommunicator
|
||||
waitForCommunicator = func(p *Provisioner) error {
|
||||
return nil
|
||||
}
|
||||
err := p.Provision(ui, comm)
|
||||
if err != nil {
|
||||
t.Fatal("should not have error")
|
||||
}
|
||||
|
||||
expectedCommand := DefaultRestartCommand
|
||||
|
||||
// Should run the command without alteration
|
||||
if comm.StartCmd.Command != expectedCommand {
|
||||
t.Fatalf("Expect command to be: %s, got %s", expectedCommand, comm.StartCmd.Command)
|
||||
}
|
||||
// Set this back!
|
||||
waitForCommunicator = waitForCommunicatorOld
|
||||
}
|
||||
|
||||
func TestProvisionerProvision_CustomCommand(t *testing.T) {
|
||||
config := testConfig()
|
||||
|
||||
// Defaults provided by Packer
|
||||
ui := testUi()
|
||||
p := new(Provisioner)
|
||||
expectedCommand := "specialrestart.exe -NOW"
|
||||
config["restart_command"] = expectedCommand
|
||||
|
||||
// Defaults provided by Packer
|
||||
comm := new(packer.MockCommunicator)
|
||||
p.Prepare(config)
|
||||
waitForCommunicatorOld := waitForCommunicator
|
||||
waitForCommunicator = func(p *Provisioner) error {
|
||||
return nil
|
||||
}
|
||||
err := p.Provision(ui, comm)
|
||||
if err != nil {
|
||||
t.Fatal("should not have error")
|
||||
}
|
||||
|
||||
// Should run the command without alteration
|
||||
if comm.StartCmd.Command != expectedCommand {
|
||||
t.Fatalf("Expect command to be: %s, got %s", expectedCommand, comm.StartCmd.Command)
|
||||
}
|
||||
// Set this back!
|
||||
waitForCommunicator = waitForCommunicatorOld
|
||||
}
|
||||
|
||||
func TestProvisionerProvision_RestartCommandFail(t *testing.T) {
|
||||
config := testConfig()
|
||||
ui := testUi()
|
||||
p := new(Provisioner)
|
||||
comm := new(packer.MockCommunicator)
|
||||
comm.StartStderr = "WinRM terminated"
|
||||
comm.StartExitStatus = 1
|
||||
|
||||
p.Prepare(config)
|
||||
err := p.Provision(ui, comm)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
func TestProvisionerProvision_WaitForRestartFail(t *testing.T) {
|
||||
config := testConfig()
|
||||
|
||||
// Defaults provided by Packer
|
||||
ui := testUi()
|
||||
p := new(Provisioner)
|
||||
|
||||
// Defaults provided by Packer
|
||||
comm := new(packer.MockCommunicator)
|
||||
p.Prepare(config)
|
||||
waitForCommunicatorOld := waitForCommunicator
|
||||
waitForCommunicator = func(p *Provisioner) error {
|
||||
return fmt.Errorf("Machine did not restart properly")
|
||||
}
|
||||
err := p.Provision(ui, comm)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Set this back!
|
||||
waitForCommunicator = waitForCommunicatorOld
|
||||
}
|
||||
|
||||
func TestProvision_waitForRestartTimeout(t *testing.T) {
|
||||
retryableSleep = 10 * time.Millisecond
|
||||
config := testConfig()
|
||||
config["restart_timeout"] = "1ms"
|
||||
ui := testUi()
|
||||
p := new(Provisioner)
|
||||
comm := new(packer.MockCommunicator)
|
||||
var err error
|
||||
|
||||
p.Prepare(config)
|
||||
waitForCommunicatorOld := waitForCommunicator
|
||||
waitDone := make(chan bool)
|
||||
|
||||
// Block until cancel comes through
|
||||
waitForCommunicator = func(p *Provisioner) error {
|
||||
for {
|
||||
select {
|
||||
case <-waitDone:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
err = p.Provision(ui, comm)
|
||||
waitDone <- true
|
||||
}()
|
||||
<-waitDone
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("should not have error")
|
||||
}
|
||||
|
||||
// Set this back!
|
||||
waitForCommunicator = waitForCommunicatorOld
|
||||
|
||||
}
|
||||
|
||||
func TestProvision_waitForCommunicator(t *testing.T) {
|
||||
config := testConfig()
|
||||
|
||||
// Defaults provided by Packer
|
||||
ui := testUi()
|
||||
p := new(Provisioner)
|
||||
|
||||
// Defaults provided by Packer
|
||||
comm := new(packer.MockCommunicator)
|
||||
p.comm = comm
|
||||
p.ui = ui
|
||||
comm.StartStderr = "WinRM terminated"
|
||||
comm.StartExitStatus = 1
|
||||
p.Prepare(config)
|
||||
err := waitForCommunicator(p)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error, got: %s", err.Error())
|
||||
}
|
||||
|
||||
expectedCommand := DefaultRestartCheckCommand
|
||||
|
||||
// Should run the command without alteration
|
||||
if comm.StartCmd.Command != expectedCommand {
|
||||
t.Fatalf("Expect command to be: %s, got %s", expectedCommand, comm.StartCmd.Command)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvision_waitForCommunicatorWithCancel(t *testing.T) {
|
||||
config := testConfig()
|
||||
|
||||
// Defaults provided by Packer
|
||||
ui := testUi()
|
||||
p := new(Provisioner)
|
||||
|
||||
// Defaults provided by Packer
|
||||
comm := new(packer.MockCommunicator)
|
||||
p.comm = comm
|
||||
p.ui = ui
|
||||
retryableSleep = 10 * time.Millisecond
|
||||
p.cancel = make(chan struct{})
|
||||
var err error
|
||||
|
||||
comm.StartStderr = "WinRM terminated"
|
||||
comm.StartExitStatus = 1 // Always fail
|
||||
p.Prepare(config)
|
||||
|
||||
// Run 2 goroutines;
|
||||
// 1st to call waitForCommunicator (that will always fail)
|
||||
// 2nd to cancel the operation
|
||||
waitDone := make(chan bool)
|
||||
go func() {
|
||||
err = waitForCommunicator(p)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
p.Cancel()
|
||||
waitDone <- true
|
||||
}()
|
||||
<-waitDone
|
||||
|
||||
// Expect a Cancel error
|
||||
if err == nil {
|
||||
t.Fatalf("Should have err")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetryable(t *testing.T) {
|
||||
config := testConfig()
|
||||
|
||||
count := 0
|
||||
retryMe := func() error {
|
||||
t.Logf("RetryMe, attempt number %d", count)
|
||||
if count == 2 {
|
||||
return nil
|
||||
}
|
||||
count++
|
||||
return errors.New(fmt.Sprintf("Still waiting %d more times...", 2-count))
|
||||
}
|
||||
retryableSleep = 50 * time.Millisecond
|
||||
p := new(Provisioner)
|
||||
p.config.RestartTimeout = 155 * time.Millisecond
|
||||
err := p.Prepare(config)
|
||||
err = p.retryable(retryMe)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error retrying funuction")
|
||||
}
|
||||
|
||||
count = 0
|
||||
p.config.RestartTimeout = 10 * time.Millisecond
|
||||
err = p.Prepare(config)
|
||||
err = p.retryable(retryMe)
|
||||
if err == nil {
|
||||
t.Fatalf("should have error retrying funuction")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvision_Cancel(t *testing.T) {
|
||||
config := testConfig()
|
||||
|
||||
// Defaults provided by Packer
|
||||
ui := testUi()
|
||||
p := new(Provisioner)
|
||||
|
||||
var err error
|
||||
|
||||
comm := new(packer.MockCommunicator)
|
||||
p.Prepare(config)
|
||||
waitDone := make(chan bool)
|
||||
|
||||
// Block until cancel comes through
|
||||
waitForCommunicator = func(p *Provisioner) error {
|
||||
for {
|
||||
select {
|
||||
case <-waitDone:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create two go routines to provision and cancel in parallel
|
||||
// Provision will block until cancel happens
|
||||
go func() {
|
||||
err = p.Provision(ui, comm)
|
||||
waitDone <- true
|
||||
}()
|
||||
|
||||
go func() {
|
||||
p.Cancel()
|
||||
}()
|
||||
<-waitDone
|
||||
|
||||
// Expect interupt error
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,324 @@
|
|||
// This package implements a provisioner for Packer that executes
|
||||
// shell scripts within the remote machine.
|
||||
package shell
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/config"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
const DefaultRemotePath = "c:/Windows/Temp/script.bat"
|
||||
|
||||
var retryableSleep = 2 * time.Second
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
// If true, the script contains binary and line endings will not be
|
||||
// converted from Windows to Unix-style.
|
||||
Binary bool
|
||||
|
||||
// An inline script to execute. Multiple strings are all executed
|
||||
// in the context of a single shell.
|
||||
Inline []string
|
||||
|
||||
// The local path of the shell script to upload and execute.
|
||||
Script string
|
||||
|
||||
// An array of multiple scripts to run.
|
||||
Scripts []string
|
||||
|
||||
// An array of environment variables that will be injected before
|
||||
// your command(s) are executed.
|
||||
Vars []string `mapstructure:"environment_vars"`
|
||||
|
||||
// The remote path where the local shell script will be uploaded to.
|
||||
// This should be set to a writable file that is in a pre-existing directory.
|
||||
RemotePath string `mapstructure:"remote_path"`
|
||||
|
||||
// The command used to execute the script. The '{{ .Path }}' variable
|
||||
// should be used to specify where the script goes, {{ .Vars }}
|
||||
// can be used to inject the environment_vars into the environment.
|
||||
ExecuteCommand string `mapstructure:"execute_command"`
|
||||
|
||||
// The timeout for retrying to start the process. Until this timeout
|
||||
// is reached, if the provisioner can't start a process, it retries.
|
||||
// This can be set high to allow for reboots.
|
||||
StartRetryTimeout time.Duration `mapstructure:"start_retry_timeout"`
|
||||
|
||||
// This is used in the template generation to format environment variables
|
||||
// inside the `ExecuteCommand` template.
|
||||
EnvVarFormat string
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
type Provisioner struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
type ExecuteCommandTemplate struct {
|
||||
Vars string
|
||||
Path string
|
||||
}
|
||||
|
||||
func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||
err := config.Decode(&p.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"execute_command",
|
||||
},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.config.EnvVarFormat == "" {
|
||||
p.config.EnvVarFormat = `set "%s=%s" && `
|
||||
}
|
||||
|
||||
if p.config.ExecuteCommand == "" {
|
||||
p.config.ExecuteCommand = `{{.Vars}}"{{.Path}}"`
|
||||
}
|
||||
|
||||
if p.config.Inline != nil && len(p.config.Inline) == 0 {
|
||||
p.config.Inline = nil
|
||||
}
|
||||
|
||||
if p.config.StartRetryTimeout == 0 {
|
||||
p.config.StartRetryTimeout = 5 * time.Minute
|
||||
}
|
||||
|
||||
if p.config.RemotePath == "" {
|
||||
p.config.RemotePath = DefaultRemotePath
|
||||
}
|
||||
|
||||
if p.config.Scripts == nil {
|
||||
p.config.Scripts = make([]string, 0)
|
||||
}
|
||||
|
||||
if p.config.Vars == nil {
|
||||
p.config.Vars = make([]string, 0)
|
||||
}
|
||||
|
||||
var errs error
|
||||
if p.config.Script != "" && len(p.config.Scripts) > 0 {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
errors.New("Only one of script or scripts can be specified."))
|
||||
}
|
||||
|
||||
if p.config.Script != "" {
|
||||
p.config.Scripts = []string{p.config.Script}
|
||||
}
|
||||
|
||||
if len(p.config.Scripts) == 0 && p.config.Inline == nil {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
errors.New("Either a script file or inline script must be specified."))
|
||||
} else if len(p.config.Scripts) > 0 && p.config.Inline != nil {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
errors.New("Only a script file or an inline script can be specified, not both."))
|
||||
}
|
||||
|
||||
for _, path := range p.config.Scripts {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Bad script '%s': %s", path, err))
|
||||
}
|
||||
}
|
||||
|
||||
// Do a check for bad environment variables, such as '=foo', 'foobar'
|
||||
for _, kv := range p.config.Vars {
|
||||
vs := strings.SplitN(kv, "=", 2)
|
||||
if len(vs) != 2 || vs[0] == "" {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Environment variable not in format 'key=value': %s", kv))
|
||||
}
|
||||
}
|
||||
|
||||
if errs != nil {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// This function takes the inline scripts, concatenates them
|
||||
// into a temporary file and returns a string containing the location
|
||||
// of said file.
|
||||
func extractScript(p *Provisioner) (string, error) {
|
||||
temp, err := ioutil.TempFile(os.TempDir(), "packer-windows-shell-provisioner")
|
||||
if err != nil {
|
||||
log.Printf("Unable to create temporary file for inline scripts: %s", err)
|
||||
return "", err
|
||||
}
|
||||
writer := bufio.NewWriter(temp)
|
||||
for _, command := range p.config.Inline {
|
||||
log.Printf("Found command: %s", command)
|
||||
if _, err := writer.WriteString(command + "\n"); err != nil {
|
||||
return "", fmt.Errorf("Error preparing shell script: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := writer.Flush(); err != nil {
|
||||
return "", fmt.Errorf("Error preparing shell script: %s", err)
|
||||
}
|
||||
|
||||
temp.Close()
|
||||
|
||||
return temp.Name(), nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
||||
ui.Say(fmt.Sprintf("Provisioning with windows-shell..."))
|
||||
scripts := make([]string, len(p.config.Scripts))
|
||||
copy(scripts, p.config.Scripts)
|
||||
|
||||
// Build our variables up by adding in the build name and builder type
|
||||
envVars := make([]string, len(p.config.Vars)+2)
|
||||
envVars[0] = "PACKER_BUILD_NAME=" + p.config.PackerBuildName
|
||||
envVars[1] = "PACKER_BUILDER_TYPE=" + p.config.PackerBuilderType
|
||||
|
||||
copy(envVars, p.config.Vars)
|
||||
|
||||
if p.config.Inline != nil {
|
||||
temp, err := extractScript(p)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Unable to extract inline scripts into a file: %s", err))
|
||||
}
|
||||
scripts = append(scripts, temp)
|
||||
}
|
||||
|
||||
for _, path := range scripts {
|
||||
ui.Say(fmt.Sprintf("Provisioning with shell script: %s", path))
|
||||
|
||||
log.Printf("Opening %s for reading", path)
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error opening shell script: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Create environment variables to set before executing the command
|
||||
flattendVars, err := p.createFlattenedEnvVars()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Compile the command
|
||||
p.config.ctx.Data = &ExecuteCommandTemplate{
|
||||
Vars: flattendVars,
|
||||
Path: p.config.RemotePath,
|
||||
}
|
||||
command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error processing command: %s", err)
|
||||
}
|
||||
|
||||
// Upload the file and run the command. Do this in the context of
|
||||
// a single retryable function so that we don't end up with
|
||||
// the case that the upload succeeded, a restart is initiated,
|
||||
// and then the command is executed but the file doesn't exist
|
||||
// any longer.
|
||||
var cmd *packer.RemoteCmd
|
||||
err = p.retryable(func() error {
|
||||
if _, err := f.Seek(0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := comm.Upload(p.config.RemotePath, f, nil); err != nil {
|
||||
return fmt.Errorf("Error uploading script: %s", err)
|
||||
}
|
||||
|
||||
cmd = &packer.RemoteCmd{Command: command}
|
||||
return cmd.StartWithUi(comm, ui)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Close the original file since we copied it
|
||||
f.Close()
|
||||
|
||||
if cmd.ExitStatus != 0 {
|
||||
return fmt.Errorf("Script exited with non-zero exit status: %d", cmd.ExitStatus)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) Cancel() {
|
||||
// Just hard quit. It isn't a big deal if what we're doing keeps
|
||||
// running on the other side.
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// retryable will retry the given function over and over until a
|
||||
// non-error is returned.
|
||||
func (p *Provisioner) retryable(f func() error) error {
|
||||
startTimeout := time.After(p.config.StartRetryTimeout)
|
||||
for {
|
||||
var err error
|
||||
if err = f(); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create an error and log it
|
||||
err = fmt.Errorf("Retryable error: %s", err)
|
||||
log.Printf(err.Error())
|
||||
|
||||
// Check if we timed out, otherwise we retry. It is safe to
|
||||
// retry since the only error case above is if the command
|
||||
// failed to START.
|
||||
select {
|
||||
case <-startTimeout:
|
||||
return err
|
||||
default:
|
||||
time.Sleep(retryableSleep)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provisioner) createFlattenedEnvVars() (flattened string, err error) {
|
||||
flattened = ""
|
||||
envVars := make(map[string]string)
|
||||
|
||||
// Always available Packer provided env vars
|
||||
envVars["PACKER_BUILD_NAME"] = p.config.PackerBuildName
|
||||
envVars["PACKER_BUILDER_TYPE"] = p.config.PackerBuilderType
|
||||
|
||||
// Split vars into key/value components
|
||||
for _, envVar := range p.config.Vars {
|
||||
keyValue := strings.Split(envVar, "=")
|
||||
if len(keyValue) != 2 {
|
||||
err = errors.New("Shell provisioner environment variables must be in key=value format")
|
||||
return
|
||||
}
|
||||
envVars[keyValue[0]] = keyValue[1]
|
||||
}
|
||||
// Create a list of env var keys in sorted order
|
||||
var keys []string
|
||||
for k := range envVars {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
// Re-assemble vars using OS specific format pattern and flatten
|
||||
for _, key := range keys {
|
||||
flattened += fmt.Sprintf(p.config.EnvVarFormat, key, envVars[key])
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,441 @@
|
|||
package shell
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"inline": []interface{}{"foo", "bar"},
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_extractScript(t *testing.T) {
|
||||
config := testConfig()
|
||||
p := new(Provisioner)
|
||||
_ = p.Prepare(config)
|
||||
file, err := extractScript(p)
|
||||
if err != nil {
|
||||
t.Fatalf("Should not be error: %s", err)
|
||||
}
|
||||
log.Printf("File: %s", file)
|
||||
if strings.Index(file, os.TempDir()) != 0 {
|
||||
t.Fatalf("Temp file should reside in %s. File location: %s", os.TempDir(), file)
|
||||
}
|
||||
|
||||
// File contents should contain 2 lines concatenated by newlines: foo\nbar
|
||||
readFile, err := ioutil.ReadFile(file)
|
||||
expectedContents := "foo\nbar\n"
|
||||
s := string(readFile[:])
|
||||
if s != expectedContents {
|
||||
t.Fatalf("Expected generated inlineScript to equal '%s', got '%s'", expectedContents, s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisioner_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Provisioner{}
|
||||
if _, ok := raw.(packer.Provisioner); !ok {
|
||||
t.Fatalf("must be a Provisioner")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_Defaults(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.config.RemotePath != DefaultRemotePath {
|
||||
t.Errorf("unexpected remote path: %s", p.config.RemotePath)
|
||||
}
|
||||
|
||||
if p.config.ExecuteCommand != "{{.Vars}}\"{{.Path}}\"" {
|
||||
t.Fatalf("Default command should be powershell {{.Vars}}\"{{.Path}}\", but got %s", p.config.ExecuteCommand)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_Config(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_InvalidKey(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
// Add a random key
|
||||
config["i_should_not_be_valid"] = true
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_Script(t *testing.T) {
|
||||
config := testConfig()
|
||||
delete(config, "inline")
|
||||
|
||||
config["script"] = "/this/should/not/exist"
|
||||
p := new(Provisioner)
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test with a good one
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("error tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config["script"] = tf.Name()
|
||||
p = new(Provisioner)
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_ScriptAndInline(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
delete(config, "inline")
|
||||
delete(config, "script")
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test with both
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("error tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config["inline"] = []interface{}{"foo"}
|
||||
config["script"] = tf.Name()
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_ScriptAndScripts(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig()
|
||||
|
||||
// Test with both
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("error tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config["inline"] = []interface{}{"foo"}
|
||||
config["scripts"] = []string{tf.Name()}
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_Scripts(t *testing.T) {
|
||||
config := testConfig()
|
||||
delete(config, "inline")
|
||||
|
||||
config["scripts"] = []string{}
|
||||
p := new(Provisioner)
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test with a good one
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("error tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
config["scripts"] = []string{tf.Name()}
|
||||
p = new(Provisioner)
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_EnvironmentVars(t *testing.T) {
|
||||
config := testConfig()
|
||||
|
||||
// Test with a bad case
|
||||
config["environment_vars"] = []string{"badvar", "good=var"}
|
||||
p := new(Provisioner)
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test with a trickier case
|
||||
config["environment_vars"] = []string{"=bad"}
|
||||
p = new(Provisioner)
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test with a good case
|
||||
// Note: baz= is a real env variable, just empty
|
||||
config["environment_vars"] = []string{"FOO=bar", "baz="}
|
||||
p = new(Provisioner)
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerQuote_EnvironmentVars(t *testing.T) {
|
||||
config := testConfig()
|
||||
|
||||
config["environment_vars"] = []string{"keyone=valueone", "keytwo=value\ntwo", "keythree='valuethree'", "keyfour='value\nfour'"}
|
||||
p := new(Provisioner)
|
||||
p.Prepare(config)
|
||||
|
||||
expectedValue := "keyone=valueone"
|
||||
if p.config.Vars[0] != expectedValue {
|
||||
t.Fatalf("%s should be equal to %s", p.config.Vars[0], expectedValue)
|
||||
}
|
||||
|
||||
expectedValue = "keytwo=value\ntwo"
|
||||
if p.config.Vars[1] != expectedValue {
|
||||
t.Fatalf("%s should be equal to %s", p.config.Vars[1], expectedValue)
|
||||
}
|
||||
|
||||
expectedValue = "keythree='valuethree'"
|
||||
if p.config.Vars[2] != expectedValue {
|
||||
t.Fatalf("%s should be equal to %s", p.config.Vars[2], expectedValue)
|
||||
}
|
||||
|
||||
expectedValue = "keyfour='value\nfour'"
|
||||
if p.config.Vars[3] != expectedValue {
|
||||
t.Fatalf("%s should be equal to %s", p.config.Vars[3], expectedValue)
|
||||
}
|
||||
}
|
||||
|
||||
func testUi() *packer.BasicUi {
|
||||
return &packer.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
ErrorWriter: new(bytes.Buffer),
|
||||
}
|
||||
}
|
||||
|
||||
func testObjects() (packer.Ui, packer.Communicator) {
|
||||
ui := testUi()
|
||||
return ui, new(packer.MockCommunicator)
|
||||
}
|
||||
|
||||
func TestProvisionerProvision_Inline(t *testing.T) {
|
||||
config := testConfig()
|
||||
delete(config, "inline")
|
||||
|
||||
// Defaults provided by Packer
|
||||
config["remote_path"] = "c:/Windows/Temp/inlineScript.bat"
|
||||
config["inline"] = []string{"whoami"}
|
||||
ui := testUi()
|
||||
p := new(Provisioner)
|
||||
|
||||
// Defaults provided by Packer
|
||||
p.config.PackerBuildName = "vmware"
|
||||
p.config.PackerBuilderType = "iso"
|
||||
comm := new(packer.MockCommunicator)
|
||||
p.Prepare(config)
|
||||
err := p.Provision(ui, comm)
|
||||
if err != nil {
|
||||
t.Fatal("should not have error")
|
||||
}
|
||||
|
||||
expectedCommand := `set "PACKER_BUILDER_TYPE=iso" && set "PACKER_BUILD_NAME=vmware" && "c:/Windows/Temp/inlineScript.bat"`
|
||||
|
||||
// Should run the command without alteration
|
||||
if comm.StartCmd.Command != expectedCommand {
|
||||
t.Fatalf("Expect command to be: %s, got %s", expectedCommand, comm.StartCmd.Command)
|
||||
}
|
||||
|
||||
envVars := make([]string, 2)
|
||||
envVars[0] = "FOO=BAR"
|
||||
envVars[1] = "BAR=BAZ"
|
||||
config["environment_vars"] = envVars
|
||||
config["remote_path"] = "c:/Windows/Temp/inlineScript.bat"
|
||||
|
||||
p.Prepare(config)
|
||||
err = p.Provision(ui, comm)
|
||||
if err != nil {
|
||||
t.Fatal("should not have error")
|
||||
}
|
||||
|
||||
expectedCommand = `set "BAR=BAZ" && set "FOO=BAR" && set "PACKER_BUILDER_TYPE=iso" && set "PACKER_BUILD_NAME=vmware" && "c:/Windows/Temp/inlineScript.bat"`
|
||||
|
||||
// Should run the command without alteration
|
||||
if comm.StartCmd.Command != expectedCommand {
|
||||
t.Fatalf("Expect command to be: %s, got: %s", expectedCommand, comm.StartCmd.Command)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerProvision_Scripts(t *testing.T) {
|
||||
tempFile, _ := ioutil.TempFile("", "packer")
|
||||
defer os.Remove(tempFile.Name())
|
||||
config := testConfig()
|
||||
delete(config, "inline")
|
||||
config["scripts"] = []string{tempFile.Name()}
|
||||
config["packer_build_name"] = "foobuild"
|
||||
config["packer_builder_type"] = "footype"
|
||||
ui := testUi()
|
||||
|
||||
p := new(Provisioner)
|
||||
comm := new(packer.MockCommunicator)
|
||||
p.Prepare(config)
|
||||
err := p.Provision(ui, comm)
|
||||
if err != nil {
|
||||
t.Fatal("should not have error")
|
||||
}
|
||||
|
||||
//powershell -Command "$env:PACKER_BUILDER_TYPE=''"; powershell -Command "$env:PACKER_BUILD_NAME='foobuild'"; powershell -Command c:/Windows/Temp/script.ps1
|
||||
expectedCommand := `set "PACKER_BUILDER_TYPE=footype" && set "PACKER_BUILD_NAME=foobuild" && "c:/Windows/Temp/script.bat"`
|
||||
|
||||
// Should run the command without alteration
|
||||
if comm.StartCmd.Command != expectedCommand {
|
||||
t.Fatalf("Expect command to be %s NOT %s", expectedCommand, comm.StartCmd.Command)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) {
|
||||
tempFile, _ := ioutil.TempFile("", "packer")
|
||||
config := testConfig()
|
||||
ui := testUi()
|
||||
defer os.Remove(tempFile.Name())
|
||||
delete(config, "inline")
|
||||
|
||||
config["scripts"] = []string{tempFile.Name()}
|
||||
config["packer_build_name"] = "foobuild"
|
||||
config["packer_builder_type"] = "footype"
|
||||
|
||||
// Env vars - currently should not effect them
|
||||
envVars := make([]string, 2)
|
||||
envVars[0] = "FOO=BAR"
|
||||
envVars[1] = "BAR=BAZ"
|
||||
config["environment_vars"] = envVars
|
||||
|
||||
p := new(Provisioner)
|
||||
comm := new(packer.MockCommunicator)
|
||||
p.Prepare(config)
|
||||
err := p.Provision(ui, comm)
|
||||
if err != nil {
|
||||
t.Fatal("should not have error")
|
||||
}
|
||||
|
||||
expectedCommand := `set "BAR=BAZ" && set "FOO=BAR" && set "PACKER_BUILDER_TYPE=footype" && set "PACKER_BUILD_NAME=foobuild" && "c:/Windows/Temp/script.bat"`
|
||||
|
||||
// Should run the command without alteration
|
||||
if comm.StartCmd.Command != expectedCommand {
|
||||
t.Fatalf("Expect command to be %s NOT %s", expectedCommand, comm.StartCmd.Command)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) {
|
||||
config := testConfig()
|
||||
|
||||
p := new(Provisioner)
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error preparing config: %s", err)
|
||||
}
|
||||
|
||||
// Defaults provided by Packer
|
||||
p.config.PackerBuildName = "vmware"
|
||||
p.config.PackerBuilderType = "iso"
|
||||
|
||||
// no user env var
|
||||
flattenedEnvVars, err := p.createFlattenedEnvVars()
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error creating flattened env vars: %s", err)
|
||||
}
|
||||
expectedEnvVars := `set "PACKER_BUILDER_TYPE=iso" && set "PACKER_BUILD_NAME=vmware" && `
|
||||
if flattenedEnvVars != expectedEnvVars {
|
||||
t.Fatalf("expected flattened env vars to be: %s, got: %s", expectedEnvVars, flattenedEnvVars)
|
||||
}
|
||||
|
||||
// single user env var
|
||||
p.config.Vars = []string{"FOO=bar"}
|
||||
|
||||
flattenedEnvVars, err = p.createFlattenedEnvVars()
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error creating flattened env vars: %s", err)
|
||||
}
|
||||
expectedEnvVars = `set "FOO=bar" && set "PACKER_BUILDER_TYPE=iso" && set "PACKER_BUILD_NAME=vmware" && `
|
||||
if flattenedEnvVars != expectedEnvVars {
|
||||
t.Fatalf("expected flattened env vars to be: %s, got: %s", expectedEnvVars, flattenedEnvVars)
|
||||
}
|
||||
|
||||
// multiple user env vars
|
||||
p.config.Vars = []string{"FOO=bar", "BAZ=qux"}
|
||||
|
||||
flattenedEnvVars, err = p.createFlattenedEnvVars()
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error creating flattened env vars: %s", err)
|
||||
}
|
||||
expectedEnvVars = `set "BAZ=qux" && set "FOO=bar" && set "PACKER_BUILDER_TYPE=iso" && set "PACKER_BUILD_NAME=vmware" && `
|
||||
if flattenedEnvVars != expectedEnvVars {
|
||||
t.Fatalf("expected flattened env vars to be: %s, got: %s", expectedEnvVars, flattenedEnvVars)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetryable(t *testing.T) {
|
||||
config := testConfig()
|
||||
|
||||
count := 0
|
||||
retryMe := func() error {
|
||||
log.Printf("RetryMe, attempt number %d", count)
|
||||
if count == 2 {
|
||||
return nil
|
||||
}
|
||||
count++
|
||||
return errors.New(fmt.Sprintf("Still waiting %d more times...", 2-count))
|
||||
}
|
||||
retryableSleep = 50 * time.Millisecond
|
||||
p := new(Provisioner)
|
||||
p.config.StartRetryTimeout = 155 * time.Millisecond
|
||||
err := p.Prepare(config)
|
||||
err = p.retryable(retryMe)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error retrying funuction")
|
||||
}
|
||||
|
||||
count = 0
|
||||
p.config.StartRetryTimeout = 10 * time.Millisecond
|
||||
err = p.Prepare(config)
|
||||
err = p.retryable(retryMe)
|
||||
if err == nil {
|
||||
t.Fatalf("should have error retrying funuction")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancel(t *testing.T) {
|
||||
// Don't actually call Cancel() as it performs an os.Exit(0)
|
||||
// which kills the 'go test' tool
|
||||
}
|
|
@ -88,6 +88,7 @@ each category, the available configuration keys are alphabetized.
|
|||
* `ami_groups` (array of strings) - A list of groups that have access
|
||||
to launch the resulting AMI(s). By default no groups have permission
|
||||
to launch the AMI. `all` will make the AMI publicly accessible.
|
||||
AWS currently doesn't accept any value other than "all".
|
||||
|
||||
* `ami_product_codes` (array of strings) - A list of product codes to
|
||||
associate with the AMI. By default no product codes are associated with
|
||||
|
|
|
@ -107,6 +107,7 @@ each category, the available configuration keys are alphabetized.
|
|||
* `ami_groups` (array of strings) - A list of groups that have access
|
||||
to launch the resulting AMI(s). By default no groups have permission
|
||||
to launch the AMI. `all` will make the AMI publicly accessible.
|
||||
AWS currently doesn't accept any value other than "all".
|
||||
|
||||
* `ami_product_codes` (array of strings) - A list of product codes to
|
||||
associate with the AMI. By default no product codes are associated with
|
||||
|
|
|
@ -25,14 +25,17 @@ Required:
|
|||
* `datacenter` (string) - The name of the datacenter within vSphere to
|
||||
add the VM to.
|
||||
|
||||
* `datastore` (string) - The name of the datastore to store this VM.
|
||||
This is _not required_ if `resource_pool` is specified.
|
||||
|
||||
* `host` (string) - The vSphere host that will be contacted to perform
|
||||
the VM upload.
|
||||
|
||||
* `password` (string) - Password to use to authenticate to the vSphere
|
||||
endpoint.
|
||||
|
||||
* `resource_pool` (string) - The resource pool to upload the VM to. This can be
|
||||
" " if you do not have resource pools configured
|
||||
* `resource_pool` (string) - The resource pool to upload the VM to.
|
||||
This is _not required_ if `datastore` is specified.
|
||||
|
||||
* `username` (string) - The username to use to authenticate to the vSphere
|
||||
endpoint.
|
||||
|
@ -41,8 +44,6 @@ Required:
|
|||
|
||||
Optional:
|
||||
|
||||
* `datastore` (string) - The name of the datastore to store this VM.
|
||||
|
||||
* `disk_mode` (string) - Target disk format. See `ovftool` manual for
|
||||
available options. By default, "thick" will be used.
|
||||
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
---
|
||||
layout: "docs"
|
||||
page_title: "PowerShell Provisioner"
|
||||
description: |-
|
||||
The shell Packer provisioner provisions machines built by Packer using shell scripts. Shell provisioning is the easiest way to get software installed and configured on a machine.
|
||||
---
|
||||
|
||||
# PowerShell Provisioner
|
||||
|
||||
Type: `powershell`
|
||||
|
||||
The PowerShell Packer provisioner runs PowerShell scripts on Windows machines.
|
||||
It assumes that the communicator in use is WinRM.
|
||||
|
||||
## Basic Example
|
||||
|
||||
The example below is fully functional.
|
||||
|
||||
```javascript
|
||||
{
|
||||
"type": "powershell",
|
||||
"inline": ["dir c:\\"]
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
The reference of available configuration options is listed below. The only
|
||||
required element is either "inline" or "script". Every other option is optional.
|
||||
|
||||
Exactly _one_ of the following is required:
|
||||
|
||||
* `inline` (array of strings) - This is an array of commands to execute.
|
||||
The commands are concatenated by newlines and turned into a single file,
|
||||
so they are all executed within the same context. This allows you to
|
||||
change directories in one command and use something in the directory in
|
||||
the next and so on. Inline scripts are the easiest way to pull off simple
|
||||
tasks within the machine.
|
||||
|
||||
* `script` (string) - The path to a script to upload and execute in the machine.
|
||||
This path can be absolute or relative. If it is relative, it is relative
|
||||
to the working directory when Packer is executed.
|
||||
|
||||
* `scripts` (array of strings) - An array of scripts to execute. The scripts
|
||||
will be uploaded and executed in the order specified. Each script is executed
|
||||
in isolation, so state such as variables from one script won't carry on to
|
||||
the next.
|
||||
|
||||
Optional parameters:
|
||||
|
||||
* `binary` (boolean) - If true, specifies that the script(s) are binary
|
||||
files, and Packer should therefore not convert Windows line endings to
|
||||
Unix line endings (if there are any). By default this is false.
|
||||
|
||||
* `environment_vars` (array of strings) - An array of key/value pairs
|
||||
to inject prior to the execute_command. The format should be
|
||||
`key=value`. Packer injects some environmental variables by default
|
||||
into the environment, as well, which are covered in the section below.
|
||||
|
||||
* `execute_command` (string) - The command to use to execute the script.
|
||||
By default this is `powershell "& { {{.Vars}}{{.Path}}; exit $LastExitCode}"`.
|
||||
The value of this is treated as [configuration template](/docs/templates/configuration-templates.html).
|
||||
There are two available variables: `Path`, which is
|
||||
the path to the script to run, and `Vars`, which is the list of
|
||||
`environment_vars`, if configured.
|
||||
|
||||
* `elevated_user` and `elevated_password` (string) - If specified,
|
||||
the PowerShell script will be run with elevated privileges using
|
||||
the given Windows user.
|
||||
|
||||
* `remote_path` (string) - The path where the script will be uploaded to
|
||||
in the machine. This defaults to "/tmp/script.sh". This value must be
|
||||
a writable location and any parent directories must already exist.
|
||||
|
||||
* `start_retry_timeout` (string) - The amount of time to attempt to
|
||||
_start_ the remote process. By default this is "5m" or 5 minutes. This
|
||||
setting exists in order to deal with times when SSH may restart, such as
|
||||
a system reboot. Set this to a higher value if reboots take a longer
|
||||
amount of time.
|
||||
|
||||
* `valid_exit_codes` (list of ints) - Valid exit codes for the script.
|
||||
By default this is just 0.
|
|
@ -13,6 +13,10 @@ The shell Packer provisioner provisions machines built by Packer using shell scr
|
|||
Shell provisioning is the easiest way to get software installed and configured
|
||||
on a machine.
|
||||
|
||||
-> **Building Windows images?** You probably want to use the
|
||||
[PowerShell](/docs/provisioners/powershell.html) or
|
||||
[Windows Shell](/docs/provisioners/windows-shell.html) provisioners.
|
||||
|
||||
## Basic Example
|
||||
|
||||
The example below is fully functional.
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
layout: "docs"
|
||||
page_title: "Windows Restart Provisioner"
|
||||
description: |-
|
||||
The Windows restart provisioner restarts a Windows machine and waits for it to come back up.
|
||||
---
|
||||
|
||||
# Windows Restart Provisioner
|
||||
|
||||
Type: `windows-restart`
|
||||
|
||||
The Windows restart provisioner initiates a reboot on a Windows machine
|
||||
and waits for the machine to come back online.
|
||||
|
||||
The Windows provisioning process often requires multiple reboots, and this
|
||||
provisioner helps to ease that process.
|
||||
|
||||
## Basic Example
|
||||
|
||||
The example below is fully functional.
|
||||
|
||||
```javascript
|
||||
{
|
||||
"type": "windows-restart"
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
The reference of available configuration options is listed below.
|
||||
|
||||
Optional parameters:
|
||||
|
||||
* `restart_command` (string) - The command to execute to initiate the
|
||||
restart. By default this is `shutdown /r /c "packer restart" /t 5 && net stop winrm`.
|
||||
A key action of this is to stop WinRM so that Packer can detect it
|
||||
is rebooting.
|
||||
|
||||
* `restart_check_command` (string) - A command to execute to check if the
|
||||
restart succeeded. This will be done in a loop.
|
||||
|
||||
* `restart_timeout` (string) - The timeout to wait for the restart.
|
||||
By default this is 5 minutes. Example value: "5m"
|
|
@ -0,0 +1,75 @@
|
|||
---
|
||||
layout: "docs"
|
||||
page_title: "Windows Shell Provisioner"
|
||||
description: |-
|
||||
The windows-shell Packer provisioner runs commands on Windows using the cmd shell.
|
||||
---
|
||||
|
||||
# Windows Shell Provisioner
|
||||
|
||||
Type: `windows-shell`
|
||||
|
||||
The windows-shell Packer provisioner runs commands on a Windows machine
|
||||
using `cmd`. It assumes it is running over WinRM.
|
||||
|
||||
## Basic Example
|
||||
|
||||
The example below is fully functional.
|
||||
|
||||
```javascript
|
||||
{
|
||||
"type": "windows-shell",
|
||||
"inline": ["dir c:\\"]
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
The reference of available configuration options is listed below. The only
|
||||
required element is either "inline" or "script". Every other option is optional.
|
||||
|
||||
Exactly _one_ of the following is required:
|
||||
|
||||
* `inline` (array of strings) - This is an array of commands to execute.
|
||||
The commands are concatenated by newlines and turned into a single file,
|
||||
so they are all executed within the same context. This allows you to
|
||||
change directories in one command and use something in the directory in
|
||||
the next and so on. Inline scripts are the easiest way to pull off simple
|
||||
tasks within the machine.
|
||||
|
||||
* `script` (string) - The path to a script to upload and execute in the machine.
|
||||
This path can be absolute or relative. If it is relative, it is relative
|
||||
to the working directory when Packer is executed.
|
||||
|
||||
* `scripts` (array of strings) - An array of scripts to execute. The scripts
|
||||
will be uploaded and executed in the order specified. Each script is executed
|
||||
in isolation, so state such as variables from one script won't carry on to
|
||||
the next.
|
||||
|
||||
Optional parameters:
|
||||
|
||||
* `binary` (boolean) - If true, specifies that the script(s) are binary
|
||||
files, and Packer should therefore not convert Windows line endings to
|
||||
Unix line endings (if there are any). By default this is false.
|
||||
|
||||
* `environment_vars` (array of strings) - An array of key/value pairs
|
||||
to inject prior to the execute_command. The format should be
|
||||
`key=value`. Packer injects some environmental variables by default
|
||||
into the environment, as well, which are covered in the section below.
|
||||
|
||||
* `execute_command` (string) - The command to use to execute the script.
|
||||
By default this is `{{ .Vars }}"{{ .Path }}"`. The value of this is
|
||||
treated as [configuration template](/docs/templates/configuration-templates.html).
|
||||
There are two available variables: `Path`, which is
|
||||
the path to the script to run, and `Vars`, which is the list of
|
||||
`environment_vars`, if configured.
|
||||
|
||||
* `remote_path` (string) - The path where the script will be uploaded to
|
||||
in the machine. This defaults to "/tmp/script.sh". This value must be
|
||||
a writable location and any parent directories must already exist.
|
||||
|
||||
* `start_retry_timeout` (string) - The amount of time to attempt to
|
||||
_start_ the remote process. By default this is "5m" or 5 minutes. This
|
||||
setting exists in order to deal with times when SSH may restart, such as
|
||||
a system reboot. Set this to a higher value if reboots take a longer
|
||||
amount of time.
|
|
@ -49,12 +49,15 @@
|
|||
<li><h4>Provisioners</h4></li>
|
||||
<li><a href="/docs/provisioners/shell.html">Shell Scripts</a></li>
|
||||
<li><a href="/docs/provisioners/file.html">File Uploads</a></li>
|
||||
<li><a href="/docs/provisioners/powershell.html">PowerShell</a></li>
|
||||
<li><a href="/docs/provisioners/windows-shell.html">Windows Shell</a></li>
|
||||
<li><a href="/docs/provisioners/ansible-local.html">Ansible</a></li>
|
||||
<li><a href="/docs/provisioners/chef-client.html">Chef Client</a></li>
|
||||
<li><a href="/docs/provisioners/chef-solo.html">Chef Solo</a></li>
|
||||
<li><a href="/docs/provisioners/puppet-masterless.html">Puppet Masterless</a></li>
|
||||
<li><a href="/docs/provisioners/puppet-server.html">Puppet Server</a></li>
|
||||
<li><a href="/docs/provisioners/salt-masterless.html">Salt</a></li>
|
||||
<li><a href="/docs/provisioners/windows-restart.html">Windows Restart</a></li>
|
||||
<li><a href="/docs/provisioners/custom.html">Custom</a></li>
|
||||
</ul>
|
||||
|
||||
|
|
Loading…
Reference in New Issue