2013-05-12 17:47:55 -04:00
|
|
|
package packer
|
|
|
|
|
2013-05-12 19:18:17 -04:00
|
|
|
import (
|
2019-04-03 11:14:55 -04:00
|
|
|
"context"
|
2013-05-12 19:18:17 -04:00
|
|
|
"io"
|
2014-05-10 00:03:35 -04:00
|
|
|
"os"
|
2019-04-26 18:09:06 -04:00
|
|
|
"strings"
|
2013-07-29 15:04:48 -04:00
|
|
|
"sync"
|
2019-04-26 18:09:06 -04:00
|
|
|
"unicode"
|
2015-06-03 20:13:26 -04:00
|
|
|
|
2019-04-26 18:09:06 -04:00
|
|
|
"github.com/mitchellh/iochan"
|
2013-05-12 19:18:17 -04:00
|
|
|
)
|
2013-05-12 17:47:55 -04:00
|
|
|
|
2017-01-20 04:44:18 -05:00
|
|
|
// CmdDisconnect is a sentinel value to indicate a RemoteCmd
|
2016-10-19 21:30:19 -04:00
|
|
|
// exited because the remote side disconnected us.
|
|
|
|
const CmdDisconnect int = 2300218
|
|
|
|
|
2013-06-03 00:20:27 -04:00
|
|
|
// RemoteCmd represents a remote command being prepared or run.
|
|
|
|
type RemoteCmd struct {
|
|
|
|
// Command is the command to run remotely. This is executed as if
|
|
|
|
// it were a shell command, so you are expected to do any shell escaping
|
|
|
|
// necessary.
|
|
|
|
Command string
|
|
|
|
|
|
|
|
// Stdin specifies the process's standard input. If Stdin is
|
|
|
|
// nil, the process reads from an empty bytes.Buffer.
|
|
|
|
Stdin io.Reader
|
|
|
|
|
|
|
|
// Stdout and Stderr represent the process's standard output and
|
|
|
|
// error.
|
|
|
|
//
|
|
|
|
// If either is nil, it will be set to ioutil.Discard.
|
|
|
|
Stdout io.Writer
|
|
|
|
Stderr io.Writer
|
|
|
|
|
|
|
|
// Once Exited is true, this will contain the exit code of the process.
|
2019-04-03 11:14:55 -04:00
|
|
|
exitStatus int
|
2013-08-21 13:16:20 -04:00
|
|
|
|
|
|
|
// This thing is a mutex, lock when making modifications concurrently
|
2019-12-17 05:25:56 -05:00
|
|
|
m sync.Mutex
|
2019-04-03 11:14:55 -04:00
|
|
|
|
|
|
|
exitChInit sync.Once
|
|
|
|
exitCh chan interface{}
|
2013-06-03 00:20:27 -04:00
|
|
|
}
|
|
|
|
|
2013-05-12 17:47:55 -04:00
|
|
|
// A Communicator is the interface used to communicate with the machine
|
|
|
|
// that exists that will eventually be packaged into an image. Communicators
|
|
|
|
// allow you to execute remote commands, upload files, etc.
|
|
|
|
//
|
|
|
|
// Communicators must be safe for concurrency, meaning multiple calls to
|
|
|
|
// Start or any other method may be called at the same time.
|
|
|
|
type Communicator interface {
|
2013-06-20 17:46:25 -04:00
|
|
|
// Start takes a RemoteCmd and starts it. The RemoteCmd must not be
|
|
|
|
// modified after being used with Start, and it must not be used with
|
|
|
|
// 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.
|
2019-04-03 11:14:55 -04:00
|
|
|
Start(context.Context, *RemoteCmd) error
|
2013-06-20 17:46:25 -04:00
|
|
|
|
|
|
|
// Upload uploads a file to the machine to the given path with the
|
|
|
|
// contents coming from the given reader. This method will block until
|
|
|
|
// it completes.
|
2014-05-10 00:03:35 -04:00
|
|
|
Upload(string, io.Reader, *os.FileInfo) error
|
2013-06-20 17:46:25 -04:00
|
|
|
|
2013-08-23 20:05:06 -04:00
|
|
|
// UploadDir uploads the contents of a directory recursively to
|
|
|
|
// the remote path. It also takes an optional slice of paths to
|
|
|
|
// ignore when uploading.
|
2013-08-25 23:47:10 -04:00
|
|
|
//
|
|
|
|
// The folder name of the source folder should be created unless there
|
|
|
|
// is a trailing slash on the source "/". For example: "/tmp/src" as
|
|
|
|
// the source will create a "src" directory in the destination unless
|
|
|
|
// a trailing slash is added. This is identical behavior to rsync(1).
|
2013-09-09 16:58:23 -04:00
|
|
|
UploadDir(dst string, src string, exclude []string) error
|
2013-08-23 20:05:06 -04:00
|
|
|
|
2013-06-20 17:46:25 -04:00
|
|
|
// Download downloads a file from the machine from the given remote path
|
|
|
|
// with the contents writing to the given writer. This method will
|
|
|
|
// block until it completes.
|
2013-05-12 19:25:40 -04:00
|
|
|
Download(string, io.Writer) error
|
2015-11-02 06:22:52 -05:00
|
|
|
|
|
|
|
DownloadDir(src string, dst string, exclude []string) error
|
2013-05-12 17:47:55 -04:00
|
|
|
}
|
2013-06-03 13:49:23 -04:00
|
|
|
|
2019-12-17 05:25:56 -05:00
|
|
|
type ConfigurableCommunicator interface {
|
|
|
|
HCL2Speccer
|
|
|
|
Configure(...interface{}) ([]string, error)
|
|
|
|
}
|
|
|
|
|
2019-04-03 11:14:55 -04:00
|
|
|
// 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.
|
2020-11-19 18:10:00 -05:00
|
|
|
func (r *RemoteCmd) RunWithUi(ctx context.Context, c Communicator, ui Ui) error {
|
2019-04-03 11:14:55 -04:00
|
|
|
r.initchan()
|
|
|
|
|
2013-07-23 23:35:05 -04:00
|
|
|
stdout_r, stdout_w := io.Pipe()
|
|
|
|
stderr_r, stderr_w := io.Pipe()
|
2013-07-23 23:46:51 -04:00
|
|
|
defer stdout_w.Close()
|
|
|
|
defer stderr_w.Close()
|
2013-07-23 23:35:05 -04:00
|
|
|
|
2013-08-12 18:17:17 -04:00
|
|
|
// Retain the original stdout/stderr that we can replace back in.
|
|
|
|
originalStdout := r.Stdout
|
|
|
|
originalStderr := r.Stderr
|
|
|
|
defer func() {
|
2019-12-17 05:25:56 -05:00
|
|
|
r.m.Lock()
|
|
|
|
defer r.m.Unlock()
|
2013-08-21 13:16:20 -04:00
|
|
|
|
2013-08-12 18:17:17 -04:00
|
|
|
r.Stdout = originalStdout
|
|
|
|
r.Stderr = originalStderr
|
|
|
|
}()
|
|
|
|
|
2013-07-23 23:35:05 -04:00
|
|
|
// Set the writers for the output so that we get it streamed to us
|
2013-07-23 23:38:03 -04:00
|
|
|
if r.Stdout == nil {
|
|
|
|
r.Stdout = stdout_w
|
|
|
|
} else {
|
|
|
|
r.Stdout = io.MultiWriter(r.Stdout, stdout_w)
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.Stderr == nil {
|
|
|
|
r.Stderr = stderr_w
|
|
|
|
} else {
|
|
|
|
r.Stderr = io.MultiWriter(r.Stderr, stderr_w)
|
|
|
|
}
|
2013-07-23 23:35:05 -04:00
|
|
|
|
2019-04-26 18:09:06 -04:00
|
|
|
// Start the command
|
|
|
|
if err := c.Start(ctx, r); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the channels we'll use for data
|
|
|
|
stdoutCh := iochan.DelimReader(stdout_r, '\n')
|
|
|
|
stderrCh := iochan.DelimReader(stderr_r, '\n')
|
|
|
|
|
|
|
|
// Start the goroutine to watch for the exit
|
|
|
|
go func() {
|
|
|
|
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.Error(r.cleanOutputLine(output))
|
|
|
|
}
|
|
|
|
case output := <-stdoutCh:
|
2015-06-03 20:13:26 -04:00
|
|
|
if output != "" {
|
2019-04-26 18:09:06 -04:00
|
|
|
ui.Message(r.cleanOutputLine(output))
|
2015-06-03 20:13:26 -04:00
|
|
|
}
|
2019-04-26 18:09:06 -04:00
|
|
|
case <-r.exitCh:
|
|
|
|
break OutputLoop
|
|
|
|
case <-ctx.Done():
|
|
|
|
return ctx.Err()
|
2013-07-23 23:35:05 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-26 18:09:06 -04:00
|
|
|
// 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))
|
2019-04-03 11:14:55 -04:00
|
|
|
}
|
2019-04-26 18:09:06 -04:00
|
|
|
|
|
|
|
for output := range stderrCh {
|
|
|
|
ui.Error(r.cleanOutputLine(output))
|
2019-04-03 11:14:55 -04:00
|
|
|
}
|
2019-04-26 18:09:06 -04:00
|
|
|
|
|
|
|
return nil
|
2013-07-23 23:35:05 -04:00
|
|
|
}
|
|
|
|
|
2013-07-29 15:04:48 -04:00
|
|
|
// 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) {
|
2019-04-03 11:14:55 -04:00
|
|
|
r.initchan()
|
2013-07-29 15:04:48 -04:00
|
|
|
|
2019-12-17 05:25:56 -05:00
|
|
|
r.m.Lock()
|
2019-04-03 11:14:55 -04:00
|
|
|
r.exitStatus = status
|
2019-12-17 05:25:56 -05:00
|
|
|
r.m.Unlock()
|
2013-07-29 15:04:48 -04:00
|
|
|
|
2013-07-29 16:20:57 -04:00
|
|
|
close(r.exitCh)
|
2013-07-29 15:04:48 -04:00
|
|
|
}
|
|
|
|
|
2019-04-03 11:14:55 -04:00
|
|
|
// Wait for command exit and return exit status
|
|
|
|
func (r *RemoteCmd) Wait() int {
|
|
|
|
r.initchan()
|
|
|
|
<-r.exitCh
|
2019-12-17 05:25:56 -05:00
|
|
|
r.m.Lock()
|
|
|
|
defer r.m.Unlock()
|
2019-04-03 11:14:55 -04:00
|
|
|
return r.exitStatus
|
|
|
|
}
|
2013-07-29 15:04:48 -04:00
|
|
|
|
2019-04-03 11:14:55 -04:00
|
|
|
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{})
|
|
|
|
}
|
|
|
|
})
|
2013-06-03 13:49:23 -04:00
|
|
|
}
|
2019-04-26 18:09:06 -04:00
|
|
|
|
|
|
|
// 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 {
|
|
|
|
// Trim surrounding whitespace
|
|
|
|
line = strings.TrimRightFunc(line, unicode.IsSpace)
|
|
|
|
|
|
|
|
// Trim up to the first carriage return, since that text would be
|
|
|
|
// lost anyways.
|
|
|
|
idx := strings.LastIndex(line, "\r")
|
|
|
|
if idx > -1 {
|
|
|
|
line = line[idx+1:]
|
|
|
|
}
|
|
|
|
|
|
|
|
return line
|
|
|
|
}
|