2013-11-09 02:43:41 -05:00
|
|
|
package docker
|
|
|
|
|
|
|
|
import (
|
2015-08-12 15:16:26 -04:00
|
|
|
"archive/tar"
|
2013-11-09 02:43:41 -05:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
2013-11-09 03:05:44 -05:00
|
|
|
"path/filepath"
|
2016-04-27 16:54:40 -04:00
|
|
|
"strings"
|
2013-11-09 13:13:27 -05:00
|
|
|
"sync"
|
2013-11-09 02:43:41 -05:00
|
|
|
"syscall"
|
2014-09-04 20:07:21 -04:00
|
|
|
|
2015-03-20 12:04:38 -04:00
|
|
|
"github.com/hashicorp/go-version"
|
2017-04-04 16:39:01 -04:00
|
|
|
"github.com/hashicorp/packer/packer"
|
2013-11-09 02:43:41 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
type Communicator struct {
|
2017-10-02 16:03:42 -04:00
|
|
|
ContainerID string
|
|
|
|
HostDir string
|
|
|
|
ContainerDir string
|
|
|
|
Version *version.Version
|
|
|
|
Config *Config
|
|
|
|
containerUser *string
|
|
|
|
lock sync.Mutex
|
2013-11-09 02:43:41 -05:00
|
|
|
}
|
|
|
|
|
2015-05-29 12:29:59 -04:00
|
|
|
func (c *Communicator) Start(remote *packer.RemoteCmd) error {
|
2017-09-28 19:37:33 -04:00
|
|
|
dockerArgs := []string{
|
|
|
|
"exec",
|
|
|
|
"-i",
|
|
|
|
c.ContainerID,
|
|
|
|
"/bin/sh",
|
|
|
|
"-c",
|
|
|
|
fmt.Sprintf("(%s)", remote.Command),
|
|
|
|
}
|
|
|
|
|
2016-04-27 16:54:40 -04:00
|
|
|
if c.Config.Pty {
|
2017-09-28 19:37:33 -04:00
|
|
|
dockerArgs = append(dockerArgs[:2], append([]string{"-t"}, dockerArgs[2:]...)...)
|
2013-11-09 03:33:36 -05:00
|
|
|
}
|
|
|
|
|
2017-09-28 19:37:33 -04:00
|
|
|
if c.Config.ExecUser != "" {
|
|
|
|
dockerArgs = append(dockerArgs[:2],
|
|
|
|
append([]string{"-u", c.Config.ExecUser}, dockerArgs[2:]...)...)
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd := exec.Command("docker", dockerArgs...)
|
|
|
|
|
2016-04-27 16:54:40 -04:00
|
|
|
var (
|
|
|
|
stdin_w io.WriteCloser
|
|
|
|
err error
|
|
|
|
)
|
2013-11-09 03:33:36 -05:00
|
|
|
|
2016-04-27 16:54:40 -04:00
|
|
|
stdin_w, err = cmd.StdinPipe()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2015-03-20 12:04:38 -04:00
|
|
|
}
|
|
|
|
|
2016-04-27 16:54:40 -04:00
|
|
|
stderr_r, err := cmd.StderrPipe()
|
2013-11-09 13:13:27 -05:00
|
|
|
if err != nil {
|
2016-04-27 16:54:40 -04:00
|
|
|
return err
|
|
|
|
}
|
2013-11-09 13:15:25 -05:00
|
|
|
|
2016-04-27 16:54:40 -04:00
|
|
|
stdout_r, err := cmd.StdoutPipe()
|
|
|
|
if err != nil {
|
2013-11-09 13:13:27 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run the actual command in a goroutine so that Start doesn't block
|
2016-04-27 16:54:40 -04:00
|
|
|
go c.run(cmd, remote, stdin_w, stdout_r, stderr_r)
|
2013-11-09 13:13:27 -05:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-08-23 17:09:39 -04:00
|
|
|
// Upload uploads a file to the docker container
|
2014-05-10 00:03:35 -04:00
|
|
|
func (c *Communicator) Upload(dst string, src io.Reader, fi *os.FileInfo) error {
|
2017-08-23 17:09:39 -04:00
|
|
|
if fi == nil {
|
|
|
|
return c.uploadReader(dst, src)
|
|
|
|
}
|
|
|
|
return c.uploadFile(dst, src, fi)
|
|
|
|
}
|
|
|
|
|
|
|
|
// uploadReader writes an io.Reader to a temporary file before uploading
|
|
|
|
func (c *Communicator) uploadReader(dst string, src io.Reader) error {
|
2013-11-09 13:13:27 -05:00
|
|
|
// Create a temporary file to store the upload
|
|
|
|
tempfile, err := ioutil.TempFile(c.HostDir, "upload")
|
|
|
|
if err != nil {
|
2017-08-23 17:09:39 -04:00
|
|
|
return fmt.Errorf("Failed to open temp file for writing: %s", err)
|
2013-11-09 13:13:27 -05:00
|
|
|
}
|
|
|
|
defer os.Remove(tempfile.Name())
|
2017-08-23 17:09:39 -04:00
|
|
|
defer tempfile.Close()
|
2013-11-09 13:13:27 -05:00
|
|
|
|
2017-08-23 17:09:39 -04:00
|
|
|
if _, err := io.Copy(tempfile, src); err != nil {
|
|
|
|
return fmt.Errorf("Failed to copy upload file to tempfile: %s", err)
|
|
|
|
}
|
|
|
|
tempfile.Seek(0, 0)
|
|
|
|
fi, err := tempfile.Stat()
|
2013-11-09 13:13:27 -05:00
|
|
|
if err != nil {
|
2017-08-23 17:09:39 -04:00
|
|
|
return fmt.Errorf("Error getting tempfile info: %s", err)
|
2013-11-09 13:13:27 -05:00
|
|
|
}
|
2017-08-23 17:09:39 -04:00
|
|
|
return c.uploadFile(dst, tempfile, &fi)
|
|
|
|
}
|
2013-11-09 13:13:27 -05:00
|
|
|
|
2017-08-23 17:09:39 -04:00
|
|
|
// uploadFile uses docker cp to copy the file from the host to the container
|
|
|
|
func (c *Communicator) uploadFile(dst string, src io.Reader, fi *os.FileInfo) error {
|
2017-01-23 16:47:22 -05:00
|
|
|
|
2017-08-22 04:43:11 -04:00
|
|
|
// command format: docker cp /path/to/infile containerid:/path/to/outfile
|
2017-09-12 17:39:36 -04:00
|
|
|
log.Printf("Copying to %s on container %s.", dst, c.ContainerID)
|
2017-08-23 17:09:39 -04:00
|
|
|
|
|
|
|
localCmd := exec.Command("docker", "cp", "-",
|
2017-09-12 17:39:36 -04:00
|
|
|
fmt.Sprintf("%s:%s", c.ContainerID, filepath.Dir(dst)))
|
2013-11-09 13:13:27 -05:00
|
|
|
|
2017-08-23 17:09:39 -04:00
|
|
|
stderrP, err := localCmd.StderrPipe()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to open pipe: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
stdin, err := localCmd.StdinPipe()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to open pipe: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := localCmd.Start(); err != nil {
|
2013-11-09 13:13:27 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-08-23 17:09:39 -04:00
|
|
|
archive := tar.NewWriter(stdin)
|
|
|
|
header, err := tar.FileInfoHeader(*fi, "")
|
|
|
|
if err != nil {
|
2017-08-22 04:43:11 -04:00
|
|
|
return err
|
2013-11-09 13:13:27 -05:00
|
|
|
}
|
2017-08-23 17:09:39 -04:00
|
|
|
header.Name = filepath.Base(dst)
|
|
|
|
archive.WriteHeader(header)
|
|
|
|
numBytes, err := io.Copy(archive, src)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to pipe upload: %s", err)
|
|
|
|
}
|
|
|
|
log.Printf("Copied %d bytes for %s", numBytes, dst)
|
|
|
|
|
|
|
|
if err := archive.Close(); err != nil {
|
|
|
|
return fmt.Errorf("Failed to close archive: %s", err)
|
|
|
|
}
|
|
|
|
if err := stdin.Close(); err != nil {
|
|
|
|
return fmt.Errorf("Failed to close stdin: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
stderrOut, err := ioutil.ReadAll(stderrP)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := localCmd.Wait(); err != nil {
|
|
|
|
return fmt.Errorf("Failed to upload to '%s' in container: %s. %s.", dst, stderrOut, err)
|
|
|
|
}
|
2013-11-09 13:13:27 -05:00
|
|
|
|
2017-10-02 16:03:42 -04:00
|
|
|
if err := c.fixDestinationOwner(dst); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2013-11-09 13:13:27 -05:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Communicator) UploadDir(dst string, src string, exclude []string) error {
|
2017-09-12 17:39:36 -04:00
|
|
|
/*
|
|
|
|
from https://docs.docker.com/engine/reference/commandline/cp/#extended-description
|
|
|
|
SRC_PATH specifies a directory
|
|
|
|
DEST_PATH does not exist
|
|
|
|
DEST_PATH is created as a directory and the contents of the source directory are copied into this directory
|
|
|
|
DEST_PATH exists and is a file
|
|
|
|
Error condition: cannot copy a directory to a file
|
|
|
|
DEST_PATH exists and is a directory
|
|
|
|
SRC_PATH does not end with /. (that is: slash followed by dot)
|
|
|
|
the source directory is copied into this directory
|
|
|
|
SRC_PATH does end with /. (that is: slash followed by dot)
|
|
|
|
the content of the source directory is copied into this directory
|
|
|
|
|
|
|
|
translating that in to our semantics:
|
|
|
|
|
|
|
|
if source ends in /
|
|
|
|
docker cp src. dest
|
|
|
|
otherwise, cp source dest
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
var dockerSource string
|
|
|
|
|
|
|
|
if src[len(src)-1] == '/' {
|
|
|
|
dockerSource = fmt.Sprintf("%s.", src)
|
|
|
|
} else {
|
|
|
|
dockerSource = fmt.Sprintf("%s", src)
|
2013-11-09 21:44:43 -05:00
|
|
|
}
|
|
|
|
|
2017-09-12 17:39:36 -04:00
|
|
|
// Make the directory, then copy into it
|
|
|
|
localCmd := exec.Command("docker", "cp", dockerSource, fmt.Sprintf("%s:%s", c.ContainerID, dst))
|
2013-11-09 21:44:43 -05:00
|
|
|
|
2017-09-12 17:39:36 -04:00
|
|
|
stderrP, err := localCmd.StderrPipe()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to open pipe: %s", err)
|
2013-11-09 21:44:43 -05:00
|
|
|
}
|
2017-09-12 17:39:36 -04:00
|
|
|
if err := localCmd.Start(); err != nil {
|
|
|
|
return fmt.Errorf("Failed to copy: %s", err)
|
2013-11-09 21:44:43 -05:00
|
|
|
}
|
2017-09-12 17:39:36 -04:00
|
|
|
stderrOut, err := ioutil.ReadAll(stderrP)
|
|
|
|
if err != nil {
|
2013-11-09 21:44:43 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for the copy to complete
|
2017-09-12 17:39:36 -04:00
|
|
|
if err := localCmd.Wait(); err != nil {
|
|
|
|
return fmt.Errorf("Failed to upload to '%s' in container: %s. %s.", dst, stderrOut, err)
|
2013-11-09 21:44:43 -05:00
|
|
|
}
|
|
|
|
|
2017-10-02 16:03:42 -04:00
|
|
|
if err := c.fixDestinationOwner(dst); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2013-11-09 13:13:27 -05:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-08-12 01:22:52 -04:00
|
|
|
// Download pulls a file out of a container using `docker cp`. We have a source
|
|
|
|
// path and want to write to an io.Writer, not a file. We use - to make docker
|
|
|
|
// cp to write to stdout, and then copy the stream to our destination io.Writer.
|
2013-11-09 13:13:27 -05:00
|
|
|
func (c *Communicator) Download(src string, dst io.Writer) error {
|
2017-09-12 17:39:36 -04:00
|
|
|
log.Printf("Downloading file from container: %s:%s", c.ContainerID, src)
|
|
|
|
localCmd := exec.Command("docker", "cp", fmt.Sprintf("%s:%s", c.ContainerID, src), "-")
|
2015-08-12 01:22:52 -04:00
|
|
|
|
|
|
|
pipe, err := localCmd.StdoutPipe()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to open pipe: %s", err)
|
|
|
|
}
|
|
|
|
|
2015-08-12 01:30:19 -04:00
|
|
|
if err = localCmd.Start(); err != nil {
|
2015-08-12 01:22:52 -04:00
|
|
|
return fmt.Errorf("Failed to start download: %s", err)
|
|
|
|
}
|
|
|
|
|
2015-08-12 15:16:26 -04:00
|
|
|
// When you use - to send docker cp to stdout it is streamed as a tar; this
|
|
|
|
// enables it to work with directories. We don't actually support
|
|
|
|
// directories in Download() but we still need to handle the tar format.
|
|
|
|
archive := tar.NewReader(pipe)
|
|
|
|
_, err = archive.Next()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to read header from tar stream: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
numBytes, err := io.Copy(dst, archive)
|
2015-08-12 01:22:52 -04:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to pipe download: %s", err)
|
|
|
|
}
|
2015-08-12 01:30:19 -04:00
|
|
|
log.Printf("Copied %d bytes for %s", numBytes, src)
|
2015-08-12 01:22:52 -04:00
|
|
|
|
|
|
|
if err = localCmd.Wait(); err != nil {
|
|
|
|
return fmt.Errorf("Failed to download '%s' from container: %s", src, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2013-11-09 13:13:27 -05:00
|
|
|
}
|
|
|
|
|
2015-11-02 06:22:52 -05:00
|
|
|
func (c *Communicator) DownloadDir(src string, dst string, exclude []string) error {
|
|
|
|
return fmt.Errorf("DownloadDir is not implemented for docker")
|
|
|
|
}
|
|
|
|
|
2013-11-09 13:13:27 -05:00
|
|
|
// Runs the given command and blocks until completion
|
2016-04-27 16:54:40 -04:00
|
|
|
func (c *Communicator) run(cmd *exec.Cmd, remote *packer.RemoteCmd, stdin io.WriteCloser, stdout, stderr io.ReadCloser) {
|
2013-11-09 13:13:27 -05:00
|
|
|
// For Docker, remote communication must be serialized since it
|
|
|
|
// only supports single execution.
|
|
|
|
c.lock.Lock()
|
|
|
|
defer c.lock.Unlock()
|
|
|
|
|
2016-04-27 16:54:40 -04:00
|
|
|
wg := sync.WaitGroup{}
|
|
|
|
repeat := func(w io.Writer, r io.ReadCloser) {
|
|
|
|
io.Copy(w, r)
|
|
|
|
r.Close()
|
|
|
|
wg.Done()
|
|
|
|
}
|
2013-11-09 13:15:25 -05:00
|
|
|
|
2016-04-27 16:54:40 -04:00
|
|
|
if remote.Stdout != nil {
|
|
|
|
wg.Add(1)
|
|
|
|
go repeat(remote.Stdout, stdout)
|
|
|
|
}
|
|
|
|
|
|
|
|
if remote.Stderr != nil {
|
|
|
|
wg.Add(1)
|
|
|
|
go repeat(remote.Stderr, stderr)
|
2013-11-09 22:06:03 -05:00
|
|
|
}
|
2013-11-09 03:33:36 -05:00
|
|
|
|
2013-11-09 13:13:27 -05:00
|
|
|
// Start the command
|
2016-04-27 16:54:40 -04:00
|
|
|
log.Printf("Executing %s:", strings.Join(cmd.Args, " "))
|
2013-11-09 02:43:41 -05:00
|
|
|
if err := cmd.Start(); err != nil {
|
2013-11-09 13:13:27 -05:00
|
|
|
log.Printf("Error executing: %s", err)
|
|
|
|
remote.SetExited(254)
|
|
|
|
return
|
2013-11-09 02:43:41 -05:00
|
|
|
}
|
|
|
|
|
2014-09-04 20:24:09 -04:00
|
|
|
var exitStatus int
|
2016-04-27 16:54:40 -04:00
|
|
|
|
|
|
|
if remote.Stdin != nil {
|
|
|
|
go func() {
|
|
|
|
io.Copy(stdin, remote.Stdin)
|
|
|
|
// close stdin to support commands that wait for stdin to be closed before exiting.
|
|
|
|
stdin.Close()
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
err := cmd.Wait()
|
|
|
|
|
2013-11-09 02:43:41 -05:00
|
|
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
2014-09-04 20:24:09 -04:00
|
|
|
exitStatus = 1
|
2013-11-09 02:43:41 -05:00
|
|
|
|
|
|
|
// There is no process-independent way to get the REAL
|
|
|
|
// exit status so we just try to go deeper.
|
|
|
|
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
|
|
|
exitStatus = status.ExitStatus()
|
|
|
|
}
|
2013-11-09 03:33:36 -05:00
|
|
|
}
|
|
|
|
|
2014-09-04 20:24:09 -04:00
|
|
|
// Set the exit status which triggers waiters
|
|
|
|
remote.SetExited(exitStatus)
|
2013-11-09 02:43:41 -05:00
|
|
|
}
|
2017-10-02 16:03:42 -04:00
|
|
|
|
|
|
|
// TODO Workaround for #5307. Remove once #5409 is fixed.
|
|
|
|
func (c *Communicator) fixDestinationOwner(destination string) error {
|
|
|
|
if c.containerUser == nil {
|
|
|
|
containerUser, err := c.discoverContainerUser()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
c.containerUser = &containerUser
|
|
|
|
}
|
|
|
|
|
|
|
|
if *c.containerUser != "" {
|
|
|
|
chownArgs := []string{
|
|
|
|
"docker", "exec", "--user", "root", c.ContainerID, "/bin/sh", "-c",
|
|
|
|
fmt.Sprintf("chown -R %s %s", *c.containerUser, destination),
|
|
|
|
}
|
|
|
|
if _, err := c.runLocalCommand(chownArgs[0], chownArgs[1:]...); err != nil {
|
|
|
|
return fmt.Errorf("Failed to set owner of the uploaded file: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Communicator) discoverContainerUser() (string, error) {
|
|
|
|
var err error
|
|
|
|
var stdout []byte
|
|
|
|
inspectArgs := []string{"docker", "inspect", "--format", "{{.Config.User}}", c.ContainerID}
|
|
|
|
if stdout, err = c.runLocalCommand(inspectArgs[0], inspectArgs[1:]...); err != nil {
|
|
|
|
return "", fmt.Errorf("Failed to inspect the container: %s", err)
|
|
|
|
}
|
|
|
|
return strings.TrimSpace(string(stdout)), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Communicator) runLocalCommand(name string, arg ...string) (stdout []byte, err error) {
|
|
|
|
localCmd := exec.Command(name, arg...)
|
|
|
|
|
|
|
|
stdoutP, err := localCmd.StdoutPipe()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to open stdout pipe, %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
stderrP, err := localCmd.StderrPipe()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to open stderr pipe, %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = localCmd.Start(); err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to start command, %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
stdout, err = ioutil.ReadAll(stdoutP)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to read from stdout pipe, %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
stderr, err := ioutil.ReadAll(stderrP)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to read from stderr pipe, %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := localCmd.Wait(); err != nil {
|
|
|
|
return nil, fmt.Errorf("%s, %s", stderr, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return stdout, nil
|
|
|
|
}
|