allow a provisioner to timeout

* I had to contextualise Communicator.Start and RemoteCmd.StartWithUi
NOTE: Communicator.Start starts a RemoteCmd but RemoteCmd.StartWithUi will run the cmd and wait for a return, so I renamed StartWithUi to RunWithUi so that the intent is clearer.
Ideally in the future RunWithUi will be named back to StartWithUi and the exit status or wait funcs of the command will allow to wait for a return. If you do so please read carrefully https://golang.org/pkg/os/exec/#Cmd.Stdout to avoid a deadlock
* cmd.ExitStatus to cmd.ExitStatus() is now blocking to avoid race conditions
* also had to simplify StartWithUi
This commit is contained in:
Adrien Delorme 2019-04-03 17:14:55 +02:00
parent f7cd2b9334
commit f555e7a9f2
49 changed files with 292 additions and 245 deletions

View File

@ -2,6 +2,7 @@ package chroot
import (
"bytes"
"context"
"fmt"
"io"
"log"
@ -23,7 +24,7 @@ type Communicator struct {
CmdWrapper CommandWrapper
}
func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
func (c *Communicator) Start(ctx context.Context, cmd *packer.RemoteCmd) error {
// need extra escapes for the command since we're wrapping it in quotes
cmd.Command = strconv.Quote(cmd.Command)
command, err := c.CmdWrapper(

View File

@ -1,6 +1,7 @@
package chroot
import (
"context"
"fmt"
sl "github.com/hashicorp/packer/common/shell-local"
@ -9,6 +10,7 @@ import (
)
func RunLocalCommands(commands []string, wrappedCommand CommandWrapper, ictx interpolate.Context, ui packer.Ui) error {
ctx := context.TODO()
for _, rawCmd := range commands {
intCmd, err := interpolate.Render(rawCmd, &ictx)
if err != nil {
@ -25,13 +27,13 @@ func RunLocalCommands(commands []string, wrappedCommand CommandWrapper, ictx int
ExecuteCommand: []string{"sh", "-c", command},
}
cmd := &packer.RemoteCmd{Command: command}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return fmt.Errorf("Error executing command: %s", err)
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
return fmt.Errorf(
"Received non-zero exit code %d from command: %s",
cmd.ExitStatus,
cmd.ExitStatus(),
command)
}
}

View File

@ -59,13 +59,13 @@ func (s *StepBundleVolume) Run(ctx context.Context, state multistep.StateBag) mu
ui.Say(fmt.Sprintf("Running: %s", config.BundleVolCommand))
}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
state.Put("error", fmt.Errorf("Error bundling volume: %s", err))
ui.Error(state.Get("error").(error).Error())
return multistep.ActionHalt
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
state.Put("error", fmt.Errorf(
"Volume bundling failed. Please see the output above for more\n"+
"details on what went wrong.\n\n"+

View File

@ -69,14 +69,14 @@ func (s *StepUploadBundle) Run(ctx context.Context, state multistep.StateBag) mu
ui.Say(fmt.Sprintf("Running: %s", config.BundleUploadCommand))
}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
state.Put("error", fmt.Errorf("Error uploading volume: %s", err))
ui.Error(state.Get("error").(error).Error())
return multistep.ActionHalt
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus == 3 {
if cmd.ExitStatus() != 0 {
if cmd.ExitStatus() == 3 {
ui.Error(fmt.Sprintf("Please check that the bucket `%s` "+
"does not exist, or exists and is writable. This error "+
"indicates that the bucket may be owned by somebody else.",

View File

@ -2,6 +2,7 @@ package docker
import (
"archive/tar"
"context"
"fmt"
"io"
"io/ioutil"
@ -28,7 +29,9 @@ type Communicator struct {
EntryPoint []string
}
func (c *Communicator) Start(remote *packer.RemoteCmd) error {
var _ packer.Communicator = new(Communicator)
func (c *Communicator) Start(ctx context.Context, remote *packer.RemoteCmd) error {
dockerArgs := []string{
"exec",
"-i",

View File

@ -15,10 +15,6 @@ import (
"github.com/hashicorp/packer/template"
)
func TestCommunicator_impl(t *testing.T) {
var _ packer.Communicator = new(Communicator)
}
// TestUploadDownload verifies that basic upload / download functionality works
func TestUploadDownload(t *testing.T) {
ui := packer.TestUi(t)

View File

@ -1,6 +1,7 @@
package hyperone
import (
"context"
"fmt"
"io"
"os"
@ -20,7 +21,7 @@ type ChrootCommunicator struct {
Wrapped packer.Communicator
}
func (c *ChrootCommunicator) Start(cmd *packer.RemoteCmd) error {
func (c *ChrootCommunicator) Start(ctx context.Context, cmd *packer.RemoteCmd) error {
command := strconv.Quote(cmd.Command)
chrootCommand, err := c.CmdWrapper(
fmt.Sprintf("sudo chroot %s /bin/sh -c %s", c.Chroot, command))
@ -30,7 +31,7 @@ func (c *ChrootCommunicator) Start(cmd *packer.RemoteCmd) error {
cmd.Command = chrootCommand
return c.Wrapped.Start(cmd)
return c.Wrapped.Start(ctx, cmd)
}
func (c *ChrootCommunicator) Upload(dst string, r io.Reader, fi *os.FileInfo) error {

View File

@ -2,6 +2,7 @@ package hyperone
import (
"bytes"
"context"
"fmt"
"log"
"strings"
@ -22,6 +23,7 @@ func formatOpenAPIError(err error) string {
}
func runCommands(commands []string, ictx interpolate.Context, state multistep.StateBag) error {
ctx := context.TODO()
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
comm := state.Get("communicator").(packer.Communicator)
@ -43,15 +45,15 @@ func runCommands(commands []string, ictx interpolate.Context, state multistep.St
ui.Say(fmt.Sprintf("Executing command: %s", command))
err = remoteCmd.StartWithUi(comm, ui)
err = remoteCmd.RunWithUi(ctx, comm, ui)
if err != nil {
return fmt.Errorf("error running remote cmd: %s", err)
}
if remoteCmd.ExitStatus != 0 {
if remoteCmd.ExitStatus() != 0 {
return fmt.Errorf(
"received non-zero exit code %d from command: %s",
remoteCmd.ExitStatus,
remoteCmd.ExitStatus(),
command)
}
}
@ -59,6 +61,7 @@ func runCommands(commands []string, ictx interpolate.Context, state multistep.St
}
func captureOutput(command string, state multistep.StateBag) (string, error) {
ctx := context.TODO()
comm := state.Get("communicator").(packer.Communicator)
var stdout bytes.Buffer
@ -69,16 +72,16 @@ func captureOutput(command string, state multistep.StateBag) (string, error) {
log.Println(fmt.Sprintf("Executing command: %s", command))
err := comm.Start(remoteCmd)
err := comm.Start(ctx, remoteCmd)
if err != nil {
return "", fmt.Errorf("error running remote cmd: %s", err)
}
remoteCmd.Wait()
if remoteCmd.ExitStatus != 0 {
if remoteCmd.ExitStatus() != 0 {
return "", fmt.Errorf(
"received non-zero exit code %d from command: %s",
remoteCmd.ExitStatus,
remoteCmd.ExitStatus(),
command)
}

View File

@ -45,7 +45,7 @@ func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multis
Stdout: &stdout,
Stderr: &stderr,
}
if err := comm.Start(cmd); err != nil {
if err := comm.Start(ctx, cmd); err != nil {
err := fmt.Errorf("Failed to send shutdown command: %s", err)
state.Put("error", err)
ui.Error(err.Error())

View File

@ -1,6 +1,7 @@
package lxc
import (
"context"
"fmt"
"io"
"io/ioutil"
@ -22,7 +23,7 @@ type LxcAttachCommunicator struct {
CmdWrapper CommandWrapper
}
func (c *LxcAttachCommunicator) Start(cmd *packer.RemoteCmd) error {
func (c *LxcAttachCommunicator) Start(ctx context.Context, cmd *packer.RemoteCmd) error {
localCmd, err := c.Execute(cmd.Command)
if err != nil {

View File

@ -1,6 +1,7 @@
package lxd
import (
"context"
"fmt"
"io"
"log"
@ -17,7 +18,7 @@ type Communicator struct {
CmdWrapper CommandWrapper
}
func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
func (c *Communicator) Start(ctx context.Context, cmd *packer.RemoteCmd) error {
localCmd, err := c.Execute(cmd.Command)
if err != nil {
@ -55,11 +56,13 @@ func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
}
func (c *Communicator) Upload(dst string, r io.Reader, fi *os.FileInfo) error {
ctx := context.TODO()
fileDestination := filepath.Join(c.ContainerName, dst)
// find out if the place we are pushing to is a directory
testDirectoryCommand := fmt.Sprintf(`test -d "%s"`, dst)
cmd := &packer.RemoteCmd{Command: testDirectoryCommand}
err := c.Start(cmd)
err := c.Start(ctx, cmd)
if err != nil {
log.Printf("Unable to check whether remote path is a dir: %s", err)
@ -67,7 +70,7 @@ func (c *Communicator) Upload(dst string, r io.Reader, fi *os.FileInfo) error {
}
cmd.Wait()
if cmd.ExitStatus == 0 {
if cmd.ExitStatus() == 0 {
log.Printf("path is a directory; copying file into directory.")
fileDestination = filepath.Join(c.ContainerName, dst, (*fi).Name())
}

View File

@ -66,15 +66,15 @@ func (s *stepUploadImage) Run(ctx context.Context, state multistep.StateBag) mul
cmd := &packer.RemoteCmd{
Command: fmt.Sprintf("sudo /bin/sh %s", dest),
}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
err = fmt.Errorf("Problem creating image`: %s", err)
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
if cmd.ExitStatus != 0 {
err = fmt.Errorf("Create Disk Image command failed with exit code %d", cmd.ExitStatus)
if cmd.ExitStatus() != 0 {
err = fmt.Errorf("Create Disk Image command failed with exit code %d", cmd.ExitStatus())
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt

View File

@ -45,7 +45,7 @@ func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multis
Stdout: &stdout,
Stderr: &stderr,
}
if err := comm.Start(cmd); err != nil {
if err := comm.Start(ctx, cmd); err != nil {
err := fmt.Errorf("Failed to send shutdown command: %s", err)
state.Put("error", err)
ui.Error(err.Error())

View File

@ -52,7 +52,7 @@ func (s *stepShutdown) Run(ctx context.Context, state multistep.StateBag) multis
ui.Say("Gracefully halting virtual machine...")
log.Printf("Executing shutdown command: %s", config.ShutdownCommand)
cmd := &packer.RemoteCmd{Command: config.ShutdownCommand}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
err := fmt.Errorf("Failed to send shutdown command: %s", err)
state.Put("error", err)
ui.Error(err.Error())

View File

@ -38,7 +38,7 @@ func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multis
ui.Say("Gracefully halting virtual machine...")
log.Printf("Executing shutdown command: %s", s.Command)
cmd := &packer.RemoteCmd{Command: s.Command}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
err := fmt.Errorf("Failed to send shutdown command: %s", err)
state.Put("error", err)
ui.Error(err.Error())

View File

@ -687,6 +687,7 @@ func (d *ESX5Driver) VerifyChecksum(ctype string, hash string, file string) bool
}
func (d *ESX5Driver) ssh(command string, stdin io.Reader) (*bytes.Buffer, error) {
ctx := context.TODO()
var stdout, stderr bytes.Buffer
cmd := &packer.RemoteCmd{
@ -696,14 +697,14 @@ func (d *ESX5Driver) ssh(command string, stdin io.Reader) (*bytes.Buffer, error)
Stdin: stdin,
}
err := d.comm.Start(cmd)
err := d.comm.Start(ctx, cmd)
if err != nil {
return nil, err
}
cmd.Wait()
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
err = fmt.Errorf("'%s'\n\nStdout: %s\n\nStderr: %s",
cmd.Command, stdout.String(), stderr.String())
return nil, err

View File

@ -51,7 +51,7 @@ func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multis
Stdout: &stdout,
Stderr: &stderr,
}
if err := comm.Start(cmd); err != nil {
if err := comm.Start(ctx, cmd); err != nil {
err := fmt.Errorf("Failed to send shutdown command: %s", err)
state.Put("error", err)
ui.Error(err.Error())

View File

@ -2,6 +2,7 @@ package adapter
import (
"bytes"
"context"
"encoding/binary"
"errors"
"fmt"
@ -233,15 +234,15 @@ func (c *Adapter) remoteExec(command string, in io.Reader, out io.Writer, err io
Stderr: err,
Command: command,
}
ctx := context.TODO()
if err := c.comm.Start(cmd); err != nil {
if err := c.comm.Start(ctx, cmd); err != nil {
c.ui.Error(err.Error())
return cmd.ExitStatus
}
cmd.Wait()
return cmd.ExitStatus
return cmd.ExitStatus()
}
type envRequest struct {

View File

@ -1,6 +1,7 @@
package adapter
import (
"context"
"errors"
"io"
"log"
@ -95,7 +96,7 @@ func (a addr) String() string {
type communicator struct{}
func (c communicator) Start(*packer.RemoteCmd) error {
func (c communicator) Start(context.Context, *packer.RemoteCmd) error {
return errors.New("communicator not supported")
}

View File

@ -1,6 +1,7 @@
package shell_local
import (
"context"
"fmt"
"io"
"log"
@ -15,14 +16,14 @@ type Communicator struct {
ExecuteCommand []string
}
func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
func (c *Communicator) Start(ctx context.Context, cmd *packer.RemoteCmd) error {
if len(c.ExecuteCommand) == 0 {
return fmt.Errorf("Error launching command via shell-local communicator: No ExecuteCommand provided")
}
// Build the local command to execute
log.Printf("[INFO] (shell-local communicator): Executing local shell command %s", c.ExecuteCommand)
localCmd := exec.Command(c.ExecuteCommand[0], c.ExecuteCommand[1:]...)
localCmd := exec.CommandContext(ctx, c.ExecuteCommand[0], c.ExecuteCommand[1:]...)
localCmd.Stdin = cmd.Stdin
localCmd.Stdout = cmd.Stdout
localCmd.Stderr = cmd.Stderr

View File

@ -2,6 +2,7 @@ package shell_local
import (
"bytes"
"context"
"runtime"
"strings"
"testing"
@ -28,14 +29,15 @@ func TestCommunicator(t *testing.T) {
Stdout: &buf,
}
if err := c.Start(cmd); err != nil {
ctx := context.Background()
if err := c.Start(ctx, cmd); err != nil {
t.Fatalf("err: %s", err)
}
cmd.Wait()
if cmd.ExitStatus != 0 {
t.Fatalf("err bad exit status: %d", cmd.ExitStatus)
if cmd.ExitStatus() != 0 {
t.Fatalf("err bad exit status: %d", cmd.ExitStatus())
}
if strings.TrimSpace(buf.String()) != "foo" {

View File

@ -2,6 +2,7 @@ package shell_local
import (
"bufio"
"context"
"fmt"
"log"
"os"
@ -27,7 +28,7 @@ type EnvVarsTemplate struct {
WinRMPassword string
}
func Run(ui packer.Ui, config *Config) (bool, error) {
func Run(ctx context.Context, ui packer.Ui, config *Config) (bool, error) {
// Check if shell-local can even execute against this runtime OS
if len(config.OnlyOn) > 0 {
runCommand := false
@ -90,17 +91,17 @@ func Run(ui packer.Ui, config *Config) (bool, error) {
flattenedCmd := strings.Join(interpolatedCmds, " ")
cmd := &packer.RemoteCmd{Command: flattenedCmd}
log.Printf("[INFO] (shell-local): starting local command: %s", flattenedCmd)
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return false, fmt.Errorf(
"Error executing script: %s\n\n"+
"Please see output above for more information.",
script)
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
return false, fmt.Errorf(
"Erroneous exit code %d while executing script: %s\n\n"+
"Please see output above for more information.",
cmd.ExitStatus,
cmd.ExitStatus(),
script)
}
}

View File

@ -56,12 +56,12 @@ func (s *StepCleanupTempKeys) Run(ctx context.Context, state multistep.StateBag)
//
// TODO: Why create a backup file if you are going to remove it?
cmd.Command = fmt.Sprintf("sed -i.bak '/ %s$/d' ~/.ssh/authorized_keys; rm ~/.ssh/authorized_keys.bak", s.Comm.SSHTemporaryKeyPairName)
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
log.Printf("Error cleaning up ~/.ssh/authorized_keys; please clean up keys manually: %s", err)
}
cmd = new(packer.RemoteCmd)
cmd.Command = fmt.Sprintf("sudo sed -i.bak '/ %s$/d' /root/.ssh/authorized_keys; sudo rm /root/.ssh/authorized_keys.bak", s.Comm.SSHTemporaryKeyPairName)
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
log.Printf("Error cleaning up /root/.ssh/authorized_keys; please clean up keys manually: %s", err)
}

View File

@ -1,6 +1,7 @@
package none
import (
"context"
"errors"
"io"
"os"
@ -23,7 +24,7 @@ func New(config string) (result *comm, err error) {
return
}
func (c *comm) Start(cmd *packer.RemoteCmd) (err error) {
func (c *comm) Start(ctx context.Context, cmd *packer.RemoteCmd) (err error) {
cmd.SetExited(0)
return
}

View File

@ -3,6 +3,7 @@ package ssh
import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"io"
@ -82,7 +83,7 @@ func New(address string, config *Config) (result *comm, err error) {
return
}
func (c *comm) Start(cmd *packer.RemoteCmd) (err error) {
func (c *comm) Start(ctx context.Context, cmd *packer.RemoteCmd) (err error) {
session, err := c.newSession()
if err != nil {
return

View File

@ -4,6 +4,7 @@ package ssh
import (
"bytes"
"context"
"fmt"
"net"
"testing"
@ -188,7 +189,8 @@ func TestStart(t *testing.T) {
Stdout: new(bytes.Buffer),
}
client.Start(cmd)
ctx := context.Background()
client.Start(ctx, cmd)
}
func TestHandshakeTimeout(t *testing.T) {

View File

@ -1,6 +1,7 @@
package winrm
import (
"context"
"encoding/base64"
"fmt"
"io"
@ -74,7 +75,7 @@ func New(config *Config) (*Communicator, error) {
}
// Start implementation of communicator.Communicator interface
func (c *Communicator) Start(rc *packer.RemoteCmd) error {
func (c *Communicator) Start(ctx context.Context, rc *packer.RemoteCmd) error {
shell, err := c.client.CreateShell()
if err != nil {
return err

View File

@ -2,6 +2,7 @@ package winrm
import (
"bytes"
"context"
"io"
"strings"
"testing"
@ -77,8 +78,8 @@ func TestStart(t *testing.T) {
stdout := new(bytes.Buffer)
cmd.Command = "echo foo"
cmd.Stdout = stdout
err = c.Start(&cmd)
ctx := context.Background()
err = c.Start(ctx, &cmd)
if err != nil {
t.Fatalf("error executing remote command: %s", err)
}

View File

@ -33,7 +33,7 @@ type StepConnectWinRM struct {
WinRMPort func(multistep.StateBag) (int, error)
}
func (s *StepConnectWinRM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
func (s *StepConnectWinRM) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
var comm packer.Communicator
@ -87,6 +87,7 @@ func (s *StepConnectWinRM) Cleanup(multistep.StateBag) {
}
func (s *StepConnectWinRM) waitForWinRM(state multistep.StateBag, cancel <-chan struct{}) (packer.Communicator, error) {
ctx := context.TODO()
var comm packer.Communicator
for {
select {
@ -164,7 +165,7 @@ func (s *StepConnectWinRM) waitForWinRM(state multistep.StateBag, cancel <-chan
log.Printf("Checking that WinRM is connected with: '%s'", connectCheckCommand)
ui := state.Get("ui").(packer.Ui)
err := cmd.StartWithUi(comm, ui)
err := cmd.RunWithUi(ctx, comm, ui)
if err != nil {
log.Printf("Communication connection err: %s", err)

View File

@ -1,12 +1,15 @@
package packer
import (
"context"
"io"
"os"
"strings"
"sync"
"unicode"
"golang.org/x/sync/errgroup"
"github.com/mitchellh/iochan"
)
@ -32,19 +35,14 @@ type RemoteCmd struct {
Stdout io.Writer
Stderr io.Writer
// This will be set to true when the remote command has exited. It
// shouldn't be set manually by the user, but there is no harm in
// doing so.
Exited bool
// Once Exited is true, this will contain the exit code of the process.
ExitStatus int
// Internal fields
exitCh chan struct{}
exitStatus int
// This thing is a mutex, lock when making modifications concurrently
sync.Mutex
exitChInit sync.Once
exitCh chan interface{}
}
// A Communicator is the interface used to communicate with the machine
@ -59,7 +57,7 @@ type Communicator interface {
// Start again. The Start method returns immediately once the command
// is started. It does not wait for the command to complete. The
// RemoteCmd.Exited field should be used for this.
Start(*RemoteCmd) error
Start(context.Context, *RemoteCmd) error
// Upload uploads a file to the machine to the given path with the
// contents coming from the given reader. This method will block until
@ -84,10 +82,12 @@ type Communicator interface {
DownloadDir(src string, dst string, exclude []string) error
}
// StartWithUi runs the remote command and streams the output to any
// configured Writers for stdout/stderr, while also writing each line
// as it comes to a Ui.
func (r *RemoteCmd) StartWithUi(c Communicator, ui Ui) error {
// RunWithUi runs the remote command and streams the output to any configured
// Writers for stdout/stderr, while also writing each line as it comes to a Ui.
// RunWithUi will not return until the command finishes or is cancelled.
func (r *RemoteCmd) RunWithUi(ctx context.Context, c Communicator, ui Ui) error {
r.initchan()
stdout_r, stdout_w := io.Pipe()
stderr_r, stderr_w := io.Pipe()
defer stdout_w.Close()
@ -117,85 +117,69 @@ func (r *RemoteCmd) StartWithUi(c Communicator, ui Ui) error {
r.Stderr = io.MultiWriter(r.Stderr, stderr_w)
}
// Start the command
if err := c.Start(r); err != nil {
// Loop and get all our output until done.
printFn := func(in io.Reader, out func(string)) error {
for output := range iochan.DelimReader(in, '\n') {
if output != "" {
out(cleanOutputLine(output))
}
}
return nil
}
wg, ctx := errgroup.WithContext(ctx)
wg.Go(func() error { return printFn(stdout_r, ui.Message) })
wg.Go(func() error { return printFn(stderr_r, ui.Error) })
if err := c.Start(ctx, r); err != nil {
return err
}
// Create the channels we'll use for data
exitCh := make(chan struct{})
stdoutCh := iochan.DelimReader(stdout_r, '\n')
stderrCh := iochan.DelimReader(stderr_r, '\n')
// Start the goroutine to watch for the exit
go func() {
defer close(exitCh)
defer stdout_w.Close()
defer stderr_w.Close()
r.Wait()
}()
// Loop and get all our output
OutputLoop:
for {
select {
case output := <-stderrCh:
if output != "" {
ui.Message(r.cleanOutputLine(output))
}
case output := <-stdoutCh:
if output != "" {
ui.Message(r.cleanOutputLine(output))
}
case <-exitCh:
break OutputLoop
}
}
// Make sure we finish off stdout/stderr because we may have gotten
// a message from the exit channel before finishing these first.
for output := range stdoutCh {
ui.Message(r.cleanOutputLine(output))
}
for output := range stderrCh {
ui.Message(r.cleanOutputLine(output))
}
case <-ctx.Done():
return ctx.Err()
case <-r.exitCh:
return nil
}
}
// SetExited is a helper for setting that this process is exited. This
// should be called by communicators who are running a remote command in
// order to set that the command is done.
func (r *RemoteCmd) SetExited(status int) {
r.initchan()
r.Lock()
defer r.Unlock()
r.exitStatus = status
r.Unlock()
if r.exitCh == nil {
r.exitCh = make(chan struct{})
}
r.Exited = true
r.ExitStatus = status
close(r.exitCh)
}
// Wait waits for the remote command to complete.
func (r *RemoteCmd) Wait() {
// Make sure our condition variable is initialized.
r.Lock()
if r.exitCh == nil {
r.exitCh = make(chan struct{})
}
r.Unlock()
// Wait for command exit and return exit status
func (r *RemoteCmd) Wait() int {
r.initchan()
<-r.exitCh
r.Lock()
defer r.Unlock()
return r.exitStatus
}
func (r *RemoteCmd) ExitStatus() int {
return r.Wait()
}
func (r *RemoteCmd) initchan() {
r.exitChInit.Do(func() {
if r.exitCh == nil {
r.exitCh = make(chan interface{})
}
})
}
// cleanOutputLine cleans up a line so that '\r' don't muck up the
// UI output when we're reading from a remote command.
func (r *RemoteCmd) cleanOutputLine(line string) string {
func cleanOutputLine(line string) string {
// Trim surrounding whitespace
line = strings.TrimRightFunc(line, unicode.IsSpace)

View File

@ -2,6 +2,7 @@ package packer
import (
"bytes"
"context"
"io"
"os"
"sync"
@ -34,7 +35,7 @@ type MockCommunicator struct {
DownloadData string
}
func (c *MockCommunicator) Start(rc *RemoteCmd) error {
func (c *MockCommunicator) Start(ctx context.Context, rc *RemoteCmd) error {
c.StartCalled = true
c.StartCmd = rc

View File

@ -2,9 +2,12 @@ package packer
import (
"bytes"
"context"
"strings"
"testing"
"time"
"github.com/google/go-cmp/cmp"
)
func TestRemoteCmd_StartWithUi(t *testing.T) {
@ -24,17 +27,19 @@ func TestRemoteCmd_StartWithUi(t *testing.T) {
Command: "test",
Stdout: originalOutput,
}
ctx := context.TODO()
err := rc.StartWithUi(testComm, testUi)
err := rc.RunWithUi(ctx, testComm, testUi)
if err != nil {
t.Fatalf("err: %s", err)
}
rc.Wait()
// sometimes cmd has returned and everything can be printed later on
time.Sleep(1 * time.Second)
expected := strings.TrimSpace(data)
if strings.TrimSpace(uiOutput.String()) != expected {
t.Fatalf("bad output: '%s'", uiOutput.String())
if diff := cmp.Diff(strings.TrimSpace(uiOutput.String()), expected); diff != "" {
t.Fatalf("bad output: %s", diff)
}
if originalOutput.String() != expected {

View File

@ -16,10 +16,10 @@ const HookProvision = "packer_provision"
// in. In addition to that, the Hook is given access to a UI so that it can
// output things to the user.
//
// Cancel is called when the hook needs to be cancelled. This will usually
// The first context argument controlls cancellation, the context will usually
// be called when Run is still in progress so the mechanism that handles this
// must be race-free. Cancel should attempt to cancel the hook in the
// quickest, safest way possible.
// must be race-free. Cancel should attempt to cancel the hook in the quickest,
// safest way possible.
type Hook interface {
Run(context.Context, string, Ui, Communicator, interface{}) error
}

View File

@ -1,6 +1,7 @@
package rpc
import (
"context"
"encoding/gob"
"io"
"log"
@ -64,7 +65,7 @@ func Communicator(client *rpc.Client) *communicator {
return &communicator{client: client}
}
func (c *communicator) Start(cmd *packer.RemoteCmd) (err error) {
func (c *communicator) Start(ctx context.Context, cmd *packer.RemoteCmd) (err error) {
var args CommunicatorStartArgs
args.Command = cmd.Command
@ -201,6 +202,8 @@ func (c *communicator) Download(path string, w io.Writer) (err error) {
}
func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface{}) error {
ctx := context.TODO()
// Build the RemoteCmd on this side so that it all pipes over
// to the remote side.
var cmd packer.RemoteCmd
@ -260,7 +263,7 @@ func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface
responseWriter := gob.NewEncoder(responseC)
// Start the actual command
err = c.c.Start(&cmd)
err = c.c.Start(ctx, &cmd)
if err != nil {
close(doneCh)
return NewBasicError(err)
@ -272,8 +275,8 @@ func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface
defer close(doneCh)
defer responseC.Close()
cmd.Wait()
log.Printf("[INFO] RPC endpoint: Communicator ended with: %d", cmd.ExitStatus)
responseWriter.Encode(&CommandFinished{cmd.ExitStatus})
log.Printf("[INFO] RPC endpoint: Communicator ended with: %d", cmd.ExitStatus())
responseWriter.Encode(&CommandFinished{cmd.ExitStatus()})
}()
return nil

View File

@ -2,6 +2,7 @@ package rpc
import (
"bufio"
"context"
"io"
"reflect"
"testing"
@ -36,8 +37,10 @@ func TestCommunicatorRPC(t *testing.T) {
c.StartStderr = "errfoo\n"
c.StartExitStatus = 42
ctx := context.Background()
// Test Start
err := remote.Start(&cmd)
err := remote.Start(ctx, &cmd)
if err != nil {
t.Fatalf("err: %s", err)
}
@ -73,8 +76,8 @@ func TestCommunicatorRPC(t *testing.T) {
}
// Test that we can get the exit status properly
if cmd.ExitStatus != 42 {
t.Fatalf("bad exit: %d", cmd.ExitStatus)
if cmd.ExitStatus() != 42 {
t.Fatalf("bad exit: %d", cmd.ExitStatus())
}
// Test that we can upload things

View File

@ -41,7 +41,7 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact
// this particular post-processor doesn't do anything with the artifact
// except to return it.
success, retErr := sl.Run(ui, &p.config)
success, retErr := sl.Run(ctx, ui, &p.config)
if !success {
return nil, false, false, retErr
}

View File

@ -1,6 +1,7 @@
package ansiblelocal
import (
"context"
"io"
"os"
@ -12,7 +13,7 @@ type communicatorMock struct {
uploadDestination []string
}
func (c *communicatorMock) Start(cmd *packer.RemoteCmd) error {
func (c *communicatorMock) Start(ctx context.Context, cmd *packer.RemoteCmd) error {
c.startCommand = append(c.startCommand, cmd.Command)
cmd.SetExited(0)
return nil

View File

@ -348,6 +348,7 @@ func (p *Provisioner) provisionPlaybookFile(ui packer.Ui, comm packer.Communicat
}
func (p *Provisioner) executeGalaxy(ui packer.Ui, comm packer.Communicator) error {
ctx := context.TODO()
rolesDir := filepath.ToSlash(filepath.Join(p.config.StagingDir, "roles"))
galaxyFile := filepath.ToSlash(filepath.Join(p.config.StagingDir, filepath.Base(p.config.GalaxyFile)))
@ -358,12 +359,12 @@ func (p *Provisioner) executeGalaxy(ui packer.Ui, comm packer.Communicator) erro
cmd := &packer.RemoteCmd{
Command: command,
}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
// ansible-galaxy version 2.0.0.2 doesn't return exit codes on error..
return fmt.Errorf("Non-zero exit status: %d", cmd.ExitStatus)
return fmt.Errorf("Non-zero exit status: %d", cmd.ExitStatus())
}
return nil
}
@ -403,6 +404,7 @@ func (p *Provisioner) executeAnsible(ui packer.Ui, comm packer.Communicator) err
func (p *Provisioner) executeAnsiblePlaybook(
ui packer.Ui, comm packer.Communicator, playbookFile, extraArgs, inventory string,
) error {
ctx := context.TODO()
command := fmt.Sprintf("cd %s && %s %s%s -c local -i %s",
p.config.StagingDir, p.config.Command, playbookFile, extraArgs, inventory,
)
@ -410,17 +412,17 @@ func (p *Provisioner) executeAnsiblePlaybook(
cmd := &packer.RemoteCmd{
Command: command,
}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus == 127 {
if cmd.ExitStatus() != 0 {
if cmd.ExitStatus() == 127 {
return fmt.Errorf("%s could not be found. Verify that it is available on the\n"+
"PATH after connecting to the machine.",
p.config.Command)
}
return fmt.Errorf("Non-zero exit status: %d", cmd.ExitStatus)
return fmt.Errorf("Non-zero exit status: %d", cmd.ExitStatus())
}
return nil
}
@ -464,32 +466,34 @@ func (p *Provisioner) uploadFile(ui packer.Ui, comm packer.Communicator, dst, sr
}
func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
ctx := context.TODO()
cmd := &packer.RemoteCmd{
Command: fmt.Sprintf("mkdir -p '%s'", dir),
}
ui.Message(fmt.Sprintf("Creating directory: %s", dir))
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
return fmt.Errorf("Non-zero exit status. See output above for more information.")
}
return nil
}
func (p *Provisioner) removeDir(ui packer.Ui, comm packer.Communicator, dir string) error {
ctx := context.TODO()
cmd := &packer.RemoteCmd{
Command: fmt.Sprintf("rm -rf '%s'", dir),
}
ui.Message(fmt.Sprintf("Removing directory: %s", dir))
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
return fmt.Errorf("Non-zero exit status. See output above for more information.")
}
return nil

View File

@ -467,22 +467,23 @@ func (p *Provisioner) createJson(ui packer.Ui, comm packer.Communicator) (string
}
func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
ctx := context.TODO()
ui.Message(fmt.Sprintf("Creating directory: %s", dir))
cmd := &packer.RemoteCmd{Command: p.guestCommands.CreateDir(dir)}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
return fmt.Errorf("Non-zero exit status. See output above for more info.")
}
// Chmod the directory to 0777 just so that we can access it as our user
cmd = &packer.RemoteCmd{Command: p.guestCommands.Chmod(dir, "0777")}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
return fmt.Errorf("Non-zero exit status. See output above for more info.")
}
@ -514,6 +515,7 @@ func (p *Provisioner) knifeExec(ui packer.Ui, comm packer.Communicator, node str
"-y",
"-c", knifeConfigPath,
}
ctx := context.TODO()
p.config.ctx.Data = &KnifeTemplate{
Sudo: !p.config.PreventSudo,
@ -527,10 +529,10 @@ func (p *Provisioner) knifeExec(ui packer.Ui, comm packer.Communicator, node str
}
cmd := &packer.RemoteCmd{Command: command}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
return fmt.Errorf(
"Non-zero exit status. See output above for more info.\n\n"+
"Command: %s",
@ -542,9 +544,10 @@ func (p *Provisioner) knifeExec(ui packer.Ui, comm packer.Communicator, node str
func (p *Provisioner) removeDir(ui packer.Ui, comm packer.Communicator, dir string) error {
ui.Message(fmt.Sprintf("Removing directory: %s", dir))
ctx := context.TODO()
cmd := &packer.RemoteCmd{Command: p.guestCommands.RemoveDir(dir)}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
@ -557,6 +560,8 @@ func (p *Provisioner) executeChef(ui packer.Ui, comm packer.Communicator, config
JsonPath: json,
Sudo: !p.config.PreventSudo,
}
ctx := context.TODO()
command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
if err != nil {
return err
@ -575,12 +580,12 @@ func (p *Provisioner) executeChef(ui packer.Ui, comm packer.Communicator, config
Command: command,
}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
return fmt.Errorf("Non-zero exit status: %d", cmd.ExitStatus)
if cmd.ExitStatus() != 0 {
return fmt.Errorf("Non-zero exit status: %d", cmd.ExitStatus())
}
return nil
@ -588,6 +593,7 @@ func (p *Provisioner) executeChef(ui packer.Ui, comm packer.Communicator, config
func (p *Provisioner) installChef(ui packer.Ui, comm packer.Communicator) error {
ui.Message("Installing Chef...")
ctx := context.TODO()
p.config.ctx.Data = &InstallChefTemplate{
Sudo: !p.config.PreventSudo,
@ -600,13 +606,13 @@ func (p *Provisioner) installChef(ui packer.Ui, comm packer.Communicator) error
ui.Message(command)
cmd := &packer.RemoteCmd{Command: command}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
return fmt.Errorf(
"Install script exited with non-zero exit status %d", cmd.ExitStatus)
"Install script exited with non-zero exit status %d", cmd.ExitStatus())
}
return nil

View File

@ -410,21 +410,22 @@ func (p *Provisioner) createJson(ui packer.Ui, comm packer.Communicator) (string
func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
ui.Message(fmt.Sprintf("Creating directory: %s", dir))
ctx := context.TODO()
cmd := &packer.RemoteCmd{Command: p.guestCommands.CreateDir(dir)}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
return fmt.Errorf("Non-zero exit status. See output above for more info.")
}
// Chmod the directory to 0777 just so that we can access it as our user
cmd = &packer.RemoteCmd{Command: p.guestCommands.Chmod(dir, "0777")}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
return fmt.Errorf("Non-zero exit status. See output above for more info.")
}
@ -447,13 +448,13 @@ func (p *Provisioner) executeChef(ui packer.Ui, comm packer.Communicator, config
cmd := &packer.RemoteCmd{
Command: command,
}
if err := cmd.StartWithUi(comm, ui); err != nil {
ctx := context.TODO()
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
return fmt.Errorf("Non-zero exit status: %d", cmd.ExitStatus)
if cmd.ExitStatus() != 0 {
return fmt.Errorf("Non-zero exit status: %d", cmd.ExitStatus())
}
return nil
@ -461,6 +462,7 @@ func (p *Provisioner) executeChef(ui packer.Ui, comm packer.Communicator, config
func (p *Provisioner) installChef(ui packer.Ui, comm packer.Communicator, version string) error {
ui.Message("Installing Chef...")
ctx := context.TODO()
p.config.ctx.Data = &InstallChefTemplate{
Sudo: !p.config.PreventSudo,
@ -472,13 +474,13 @@ func (p *Provisioner) installChef(ui packer.Ui, comm packer.Communicator, versio
}
cmd := &packer.RemoteCmd{Command: command}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
return fmt.Errorf(
"Install script exited with non-zero exit status %d", cmd.ExitStatus)
"Install script exited with non-zero exit status %d", cmd.ExitStatus())
}
return nil

View File

@ -128,6 +128,7 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C
}
func (p *Provisioner) maybeBootstrap(ui packer.Ui, comm packer.Communicator) error {
ctx := context.TODO()
if !p.config.Bootstrap {
return nil
}
@ -153,12 +154,12 @@ func (p *Provisioner) maybeBootstrap(ui packer.Ui, comm packer.Communicator) err
Stderr: &outErr,
}
if err = comm.Start(cmd); err != nil {
if err = comm.Start(ctx, cmd); err != nil {
return fmt.Errorf("Error bootstrapping converge: %s", err)
}
cmd.Wait()
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
ui.Error(out.String())
ui.Error(outErr.String())
return errors.New("Error bootstrapping converge")
@ -180,6 +181,7 @@ func (p *Provisioner) sendModuleDirectories(ui packer.Ui, comm packer.Communicat
}
func (p *Provisioner) applyModules(ui packer.Ui, comm packer.Communicator) error {
ctx := context.TODO()
// create params JSON file
params, err := json.Marshal(p.config.Params)
if err != nil {
@ -208,12 +210,12 @@ func (p *Provisioner) applyModules(ui packer.Ui, comm packer.Communicator) error
Stdout: &runOut,
Stderr: &runErr,
}
if err := comm.Start(cmd); err != nil {
if err := comm.Start(ctx, cmd); err != nil {
return fmt.Errorf("Error applying %q: %s", p.config.Module, err)
}
cmd.Wait()
if cmd.ExitStatus == 127 {
if cmd.ExitStatus() == 127 {
ui.Error("Could not find Converge. Is it installed and in PATH?")
if !p.config.Bootstrap {
ui.Error("Bootstrapping was disabled for this run. That might be why Converge isn't present.")
@ -221,10 +223,10 @@ func (p *Provisioner) applyModules(ui packer.Ui, comm packer.Communicator) error
return errors.New("Could not find Converge")
} else if cmd.ExitStatus != 0 {
} else if cmd.ExitStatus() != 0 {
ui.Error(strings.TrimSpace(runOut.String()))
ui.Error(strings.TrimSpace(runErr.String()))
ui.Error(fmt.Sprintf("Exited with error code %d.", cmd.ExitStatus))
ui.Error(fmt.Sprintf("Exited with error code %d.", cmd.ExitStatus()))
return fmt.Errorf("Error applying %q", p.config.Module)
}

View File

@ -262,7 +262,7 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C
}
cmd = &packer.RemoteCmd{Command: command}
return cmd.StartWithUi(comm, ui)
return cmd.RunWithUi(ctx, comm, ui)
})
if err != nil {
return err
@ -271,7 +271,7 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C
// Close the original file since we copied it
f.Close()
if err := p.config.ValidExitCode(cmd.ExitStatus); err != nil {
if err := p.config.ValidExitCode(cmd.ExitStatus()); err != nil {
return err
}
}

View File

@ -348,12 +348,12 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C
}
ui.Message(fmt.Sprintf("Running Puppet: %s", command))
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return fmt.Errorf("Got an error starting command: %s", err)
}
if cmd.ExitStatus != 0 && cmd.ExitStatus != 2 && !p.config.IgnoreExitCodes {
return fmt.Errorf("Puppet exited with a non-zero exit status: %d", cmd.ExitStatus)
if cmd.ExitStatus() != 0 && cmd.ExitStatus() != 2 && !p.config.IgnoreExitCodes {
return fmt.Errorf("Puppet exited with a non-zero exit status: %d", cmd.ExitStatus())
}
if p.config.CleanStagingDir {
@ -431,21 +431,22 @@ func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir stri
ui.Message(fmt.Sprintf("Creating directory: %s", dir))
cmd := &packer.RemoteCmd{Command: p.guestCommands.CreateDir(dir)}
ctx := context.TODO()
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
return fmt.Errorf("Non-zero exit status.")
}
// Chmod the directory to 0777 just so that we can access it as our user
cmd = &packer.RemoteCmd{Command: p.guestCommands.Chmod(dir, "0777")}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
return fmt.Errorf("Non-zero exit status. See output above for more info.")
}
@ -453,12 +454,14 @@ func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir stri
}
func (p *Provisioner) removeDir(ui packer.Ui, comm packer.Communicator, dir string) error {
ctx := context.TODO()
cmd := &packer.RemoteCmd{Command: p.guestCommands.RemoveDir(dir)}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
return fmt.Errorf("Non-zero exit status.")
}

View File

@ -301,12 +301,12 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C
}
ui.Message(fmt.Sprintf("Running Puppet: %s", command))
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 && cmd.ExitStatus != 2 && !p.config.IgnoreExitCodes {
return fmt.Errorf("Puppet exited with a non-zero exit status: %d", cmd.ExitStatus)
if cmd.ExitStatus() != 0 && cmd.ExitStatus() != 2 && !p.config.IgnoreExitCodes {
return fmt.Errorf("Puppet exited with a non-zero exit status: %d", cmd.ExitStatus())
}
if p.config.CleanStagingDir {
@ -320,21 +320,22 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C
func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
ui.Message(fmt.Sprintf("Creating directory: %s", dir))
ctx := context.TODO()
cmd := &packer.RemoteCmd{Command: p.guestCommands.CreateDir(dir)}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
return fmt.Errorf("Non-zero exit status. See output above for more info.")
}
// Chmod the directory to 0777 just so that we can access it as our user
cmd = &packer.RemoteCmd{Command: p.guestCommands.Chmod(dir, "0777")}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
return fmt.Errorf("Non-zero exit status. See output above for more info.")
}
@ -342,12 +343,14 @@ func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir stri
}
func (p *Provisioner) removeDir(ui packer.Ui, comm packer.Communicator, dir string) error {
ctx := context.TODO()
cmd := &packer.RemoteCmd{Command: p.guestCommands.RemoveDir(dir)}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
return fmt.Errorf("Non-zero exit status.")
}

View File

@ -231,14 +231,14 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C
Command: fmt.Sprintf(p.guestOSTypeConfig.bootstrapFetchCmd),
}
ui.Message(fmt.Sprintf("Downloading saltstack bootstrap to /tmp/install_salt.sh"))
if err = cmd.StartWithUi(comm, ui); err != nil {
if err = cmd.RunWithUi(ctx, comm, ui); err != nil {
return fmt.Errorf("Unable to download Salt: %s", err)
}
cmd = &packer.RemoteCmd{
Command: fmt.Sprintf("%s %s", p.sudo(p.guestOSTypeConfig.bootstrapRunCmd), p.config.BootstrapArgs),
}
ui.Message(fmt.Sprintf("Installing Salt with command %s", cmd.Command))
if err = cmd.StartWithUi(comm, ui); err != nil {
if err = cmd.RunWithUi(ctx, comm, ui); err != nil {
return fmt.Errorf("Unable to install Salt: %s", err)
}
}
@ -342,9 +342,9 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C
ui.Message(fmt.Sprintf("Running: salt-call --local %s", p.config.CmdArgs))
cmd := &packer.RemoteCmd{Command: p.sudo(fmt.Sprintf("%s --local %s", filepath.Join(p.config.SaltBinDir, "salt-call"), p.config.CmdArgs))}
if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 {
if err = cmd.RunWithUi(ctx, comm, ui); err != nil || cmd.ExitStatus() != 0 {
if err == nil {
err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus)
err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus())
}
return fmt.Errorf("Error executing salt-call: %s", err)
@ -406,13 +406,15 @@ func (p *Provisioner) uploadFile(ui packer.Ui, comm packer.Communicator, dst, sr
}
func (p *Provisioner) moveFile(ui packer.Ui, comm packer.Communicator, dst string, src string) error {
ctx := context.TODO()
ui.Message(fmt.Sprintf("Moving %s to %s", src, dst))
cmd := &packer.RemoteCmd{
Command: p.sudo(p.guestCommands.MovePath(src, dst)),
}
if err := cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil || cmd.ExitStatus() != 0 {
if err == nil {
err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus)
err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus())
}
return fmt.Errorf("Unable to move %s to %s: %s", src, dst, err)
@ -425,38 +427,41 @@ func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir stri
cmd := &packer.RemoteCmd{
Command: p.guestCommands.CreateDir(dir),
}
if err := cmd.StartWithUi(comm, ui); err != nil {
ctx := context.TODO()
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
return fmt.Errorf("Non-zero exit status.")
}
return nil
}
func (p *Provisioner) statPath(ui packer.Ui, comm packer.Communicator, path string) error {
ctx := context.TODO()
ui.Message(fmt.Sprintf("Verifying Path: %s", path))
cmd := &packer.RemoteCmd{
Command: p.guestCommands.StatPath(path),
}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
return fmt.Errorf("Non-zero exit status.")
}
return nil
}
func (p *Provisioner) removeDir(ui packer.Ui, comm packer.Communicator, dir string) error {
ctx := context.TODO()
ui.Message(fmt.Sprintf("Removing directory: %s", dir))
cmd := &packer.RemoteCmd{
Command: p.guestCommands.RemoveDir(dir),
}
if err := cmd.StartWithUi(comm, ui); err != nil {
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
return fmt.Errorf("Non-zero exit status.")
}
return nil

View File

@ -26,7 +26,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
}
func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, _ packer.Communicator) error {
_, retErr := sl.Run(ui, &p.config)
_, retErr := sl.Run(ctx, ui, &p.config)
if retErr != nil {
return retErr
}

View File

@ -256,7 +256,7 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C
cmd = &packer.RemoteCmd{
Command: fmt.Sprintf("chmod 0600 %s", remoteVFName),
}
if err := comm.Start(cmd); err != nil {
if err := comm.Start(ctx, cmd); err != nil {
return fmt.Errorf(
"Error chmodding script file to 0600 in remote "+
"machine: %s", err)
@ -314,7 +314,7 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C
cmd = &packer.RemoteCmd{
Command: fmt.Sprintf("chmod 0755 %s", p.config.RemotePath),
}
if err := comm.Start(cmd); err != nil {
if err := comm.Start(ctx, cmd); err != nil {
return fmt.Errorf(
"Error chmodding script file to 0755 in remote "+
"machine: %s", err)
@ -322,7 +322,7 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C
cmd.Wait()
cmd = &packer.RemoteCmd{Command: command}
return cmd.StartWithUi(comm, ui)
return cmd.RunWithUi(ctx, comm, ui)
})
if err != nil {
@ -331,7 +331,7 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C
// If the exit code indicates a remote disconnect, fail unless
// we were expecting it.
if cmd.ExitStatus == packer.CmdDisconnect {
if cmd.ExitStatus() == packer.CmdDisconnect {
if !p.config.ExpectDisconnect {
return fmt.Errorf("Script disconnected unexpectedly. " +
"If you expected your script to disconnect, i.e. from a " +
@ -339,7 +339,7 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C
"or `\"valid_exit_codes\": [0, 2300218]` to the shell " +
"provisioner parameters.")
}
} else if err := p.config.ValidExitCode(cmd.ExitStatus); err != nil {
} else if err := p.config.ValidExitCode(cmd.ExitStatus()); err != nil {
return err
}
@ -371,21 +371,22 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C
}
func (p *Provisioner) cleanupRemoteFile(path string, comm packer.Communicator) error {
ctx := context.TODO()
err := p.retryable(func() error {
cmd := &packer.RemoteCmd{
Command: fmt.Sprintf("rm -f %s", path),
}
if err := comm.Start(cmd); err != nil {
if err := comm.Start(ctx, cmd); err != nil {
return fmt.Errorf(
"Error removing temporary script at %s: %s",
path, err)
}
cmd.Wait()
// treat disconnects as retryable by returning an error
if cmd.ExitStatus == packer.CmdDisconnect {
if cmd.ExitStatus() == packer.CmdDisconnect {
return fmt.Errorf("Disconnect while removing temporary script.")
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus() != 0 {
return fmt.Errorf(
"Error removing temporary script at %s!",
path)

View File

@ -106,15 +106,15 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C
command := p.config.RestartCommand
err := p.retryable(func() error {
cmd = &packer.RemoteCmd{Command: command}
return cmd.StartWithUi(comm, ui)
return cmd.RunWithUi(ctx, comm, ui)
})
if err != nil {
return err
}
if cmd.ExitStatus != 0 && cmd.ExitStatus != 1115 && cmd.ExitStatus != 1190 {
return fmt.Errorf("Restart script exited with non-zero exit status: %d", cmd.ExitStatus)
if cmd.ExitStatus() != 0 && cmd.ExitStatus() != 1115 && cmd.ExitStatus() != 1190 {
return fmt.Errorf("Restart script exited with non-zero exit status: %d", cmd.ExitStatus())
}
return waitForRestart(ctx, p, comm)
@ -141,25 +141,25 @@ var waitForRestart = func(ctx context.Context, p *Provisioner, comm packer.Commu
for {
log.Printf("Check if machine is rebooting...")
cmd = &packer.RemoteCmd{Command: trycommand}
err = cmd.StartWithUi(comm, ui)
err = cmd.RunWithUi(ctx, comm, ui)
if err != nil {
// Couldn't execute, we assume machine is rebooting already
break
}
if cmd.ExitStatus == 1 {
if cmd.ExitStatus() == 1 {
// SSH provisioner, and we're already rebooting. SSH can reconnect
// without our help; exit this wait loop.
break
}
if cmd.ExitStatus == 1115 || cmd.ExitStatus == 1190 || cmd.ExitStatus == 1717 {
if cmd.ExitStatus() == 1115 || cmd.ExitStatus() == 1190 || cmd.ExitStatus() == 1717 {
// Reboot already in progress but not completed
log.Printf("Reboot already in progress, waiting...")
time.Sleep(10 * time.Second)
}
if cmd.ExitStatus == 0 {
if cmd.ExitStatus() == 0 {
// Cancel reboot we created to test if machine was already rebooting
cmd = &packer.RemoteCmd{Command: abortcommand}
cmd.StartWithUi(comm, ui)
cmd.RunWithUi(ctx, comm, ui)
break
}
}
@ -221,7 +221,7 @@ var waitForCommunicator = func(ctx context.Context, p *Provisioner) error {
}
if runCustomRestartCheck {
// run user-configured restart check
err := cmdRestartCheck.StartWithUi(p.comm, p.ui)
err := cmdRestartCheck.RunWithUi(ctx, p.comm, p.ui)
if err != nil {
log.Printf("Communication connection err: %s", err)
continue
@ -243,7 +243,7 @@ var waitForCommunicator = func(ctx context.Context, p *Provisioner) error {
cmdModuleLoad.Stdout = &buf
cmdModuleLoad.Stdout = io.MultiWriter(cmdModuleLoad.Stdout, &buf2)
cmdModuleLoad.StartWithUi(p.comm, p.ui)
cmdModuleLoad.RunWithUi(ctx, p.comm, p.ui)
stdoutToRead := buf2.String()
if !strings.Contains(stdoutToRead, "restarted.") {
@ -262,7 +262,7 @@ var waitForCommunicator = func(ctx context.Context, p *Provisioner) error {
cmdKeyCheck.Stdout = &buf
cmdKeyCheck.Stdout = io.MultiWriter(cmdKeyCheck.Stdout, &buf2)
err := p.comm.Start(cmdKeyCheck)
err := p.comm.Start(ctx, cmdKeyCheck)
if err != nil {
log.Printf("Communication connection err: %s", err)
shouldContinue = true

View File

@ -214,7 +214,7 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C
}
cmd = &packer.RemoteCmd{Command: command}
return cmd.StartWithUi(comm, ui)
return cmd.RunWithUi(ctx, comm, ui)
})
if err != nil {
return err
@ -223,7 +223,7 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C
// Close the original file since we copied it
f.Close()
if err := p.config.ValidExitCode(cmd.ExitStatus); err != nil {
if err := p.config.ValidExitCode(cmd.ExitStatus()); err != nil {
return err
}
}