From 4edbd5322cd2f180610cd707f2d5585fb1977911 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 23 Aug 2017 14:09:39 -0700 Subject: [PATCH] docker: stream uploads over stdin Only write to a tempfile if we don't have a stat struct --- builder/docker/communicator.go | 90 +++++++++++++++++++++++-------- communicator/none/communicator.go | 3 +- 2 files changed, 70 insertions(+), 23 deletions(-) diff --git a/builder/docker/communicator.go b/builder/docker/communicator.go index d6b37a1a0..16fc405c1 100644 --- a/builder/docker/communicator.go +++ b/builder/docker/communicator.go @@ -60,39 +60,85 @@ func (c *Communicator) Start(remote *packer.RemoteCmd) error { return nil } +// Upload uploads a file to the docker container func (c *Communicator) Upload(dst string, src io.Reader, fi *os.FileInfo) error { + 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 { // Create a temporary file to store the upload tempfile, err := ioutil.TempFile(c.HostDir, "upload") if err != nil { - return err + return fmt.Errorf("Failed to open temp file for writing: %s", err) } defer os.Remove(tempfile.Name()) + defer tempfile.Close() - // Copy the contents to the temporary file - _, err = io.Copy(tempfile, src) + 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() + if err != nil { + return fmt.Errorf("Error getting tempfile info: %s", err) + } + return c.uploadFile(dst, tempfile, &fi) +} + +// 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 { + + // command format: docker cp /path/to/infile containerid:/path/to/outfile + log.Printf("Copying to %s on container %s.", dst, c.ContainerId) + + localCmd := exec.Command("docker", "cp", "-", + fmt.Sprintf("%s:%s", c.ContainerId, filepath.Dir(dst))) + + 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 { + return err + } + + archive := tar.NewWriter(stdin) + header, err := tar.FileInfoHeader(*fi, "") + if err != nil { + return err + } + 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 fi != nil { - tempfile.Chmod((*fi).Mode()) - } - tempfile.Close() - - // Use docker cp to copy the file from the host to the container - // command format: docker cp /path/to/infile containerid:/path/to/outfile - cmd := exec.Command("docker", "cp", tempfile.Name(), fmt.Sprintf("%s:%s", c.ContainerId, dst)) - - log.Printf("Copying %s to %s on container %s.", tempfile.Name(), dst, c.ContainerId) - if err := cmd.Start(); err != nil { - return err - } - - if err := cmd.Wait(); err != nil { - err = fmt.Errorf("Error uploading %s: %s", - tempfile.Name(), - err) - return err + if err := localCmd.Wait(); err != nil { + return fmt.Errorf("Failed to upload to '%s' in container: %s. %s.", dst, stderrOut, err) } return nil diff --git a/communicator/none/communicator.go b/communicator/none/communicator.go index 4e08aba1b..5c14a4283 100644 --- a/communicator/none/communicator.go +++ b/communicator/none/communicator.go @@ -2,9 +2,10 @@ package none import ( "errors" - "github.com/hashicorp/packer/packer" "io" "os" + + "github.com/hashicorp/packer/packer" ) type comm struct {