From 36f26343528fce0b96521663935b11c2abbd507f Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Thu, 28 Mar 2019 09:38:17 -0700 Subject: [PATCH] can't use docker cp so call powershell to do this natively. Fix implementation for upload, uploadDir, and download in windows container communicator --- builder/docker/step_connect_docker.go | 3 +- .../docker/windows_container_communicator.go | 210 +++--------------- 2 files changed, 34 insertions(+), 179 deletions(-) diff --git a/builder/docker/step_connect_docker.go b/builder/docker/step_connect_docker.go index 3cd4bc173..6f08ac326 100644 --- a/builder/docker/step_connect_docker.go +++ b/builder/docker/step_connect_docker.go @@ -33,13 +33,14 @@ func (s *StepConnectDocker) Run(_ context.Context, state multistep.StateBag) mul // Create the communicator that talks to Docker via various // os/exec tricks. if config.WindowsContainer { - comm := &WindowsContainerCommunicator{ + comm := &WindowsContainerCommunicator{Communicator{ ContainerID: containerId, HostDir: tempDir, ContainerDir: config.ContainerDir, Version: version, Config: config, ContainerUser: containerUser, + }, } state.Put("communicator", comm) diff --git a/builder/docker/windows_container_communicator.go b/builder/docker/windows_container_communicator.go index cc93975a4..46650a103 100644 --- a/builder/docker/windows_container_communicator.go +++ b/builder/docker/windows_container_communicator.go @@ -1,7 +1,7 @@ package docker import ( - "archive/tar" + "bytes" "fmt" "io" "io/ioutil" @@ -9,22 +9,12 @@ import ( "os" "os/exec" "path/filepath" - "strings" - "sync" - "syscall" - "github.com/hashicorp/go-version" "github.com/hashicorp/packer/packer" ) type WindowsContainerCommunicator struct { - ContainerID string - HostDir string - ContainerDir string - Version *version.Version - Config *Config - ContainerUser string - lock sync.Mutex + Communicator } func (c *WindowsContainerCommunicator) Start(remote *packer.RemoteCmd) error { @@ -87,7 +77,6 @@ func (c *WindowsContainerCommunicator) Upload(dst string, src io.Reader, fi *os. if err != nil { return err } - if fi != nil { tempfile.Chmod((*fi).Mode()) } @@ -165,12 +154,7 @@ func (c *WindowsContainerCommunicator) UploadDir(dst string, src string, exclude return err } - si, err := src.Stat() - if err != nil { - return err - } - - return dst.Chmod(si.Mode()) + return nil } // Copy the entire directory tree to the temporary directory @@ -187,8 +171,8 @@ func (c *WindowsContainerCommunicator) UploadDir(dst string, src string, exclude // Make the directory, then copy into it cmd := &packer.RemoteCmd{ - Command: fmt.Sprintf("set -e; mkdir -p %s; command cp -R %s/ %s", - containerDst, containerSrc, containerDst), + Command: fmt.Sprintf("Copy-Item %s -Destination %s -Recurse", + containerSrc, containerDst), } if err := c.Start(cmd); err != nil { return err @@ -203,96 +187,40 @@ func (c *WindowsContainerCommunicator) UploadDir(dst string, src string, exclude return nil } -func (c *WindowsContainerCommunicator) uploadFileOld(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 err := localCmd.Wait(); err != nil { - return fmt.Errorf("Failed to upload to '%s' in container: %s. %s.", dst, stderrOut, err) - } - - if err := c.fixDestinationOwner(dst); err != nil { - return err - } - - return nil -} - // 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. +// path and want to write to an io.Writer func (c *WindowsContainerCommunicator) Download(src string, dst io.Writer) error { log.Printf("Downloading file from container: %s:%s", c.ContainerID, src) - localCmd := exec.Command("docker", "cp", fmt.Sprintf("%s:%s", c.ContainerID, src), "-") + // Copy file onto temp file on mounted volume inside container + var stdout, stderr bytes.Buffer + cmd := &packer.RemoteCmd{ + Command: fmt.Sprintf("Copy-Item -Path %s -Destination %s/%s", src, c.ContainerDir, + filepath.Base(src)), + Stdout: &stdout, + Stderr: &stderr, + } + if err := c.Start(cmd); err != nil { + return err + } - pipe, err := localCmd.StdoutPipe() + // Wait for the copy to complete + cmd.Wait() + + if cmd.ExitStatus != 0 { + return fmt.Errorf("Failed to copy file to shared drive: %s, %s, %d", stderr.String(), stdout.String(), cmd.ExitStatus) + } + + // Read that copied file into a new file opened on host machine + fsrc, err := os.Open(filepath.Join(c.HostDir, filepath.Base(src))) if err != nil { - return fmt.Errorf("Failed to open pipe: %s", err) + return err } + defer fsrc.Close() + defer os.Remove(fsrc.Name()) - if err = localCmd.Start(); err != nil { - return fmt.Errorf("Failed to start download: %s", err) - } - - // 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() + _, err = io.Copy(dst, fsrc) if err != nil { - return fmt.Errorf("Failed to read header from tar stream: %s", err) - } - - numBytes, err := io.Copy(dst, archive) - if err != nil { - return fmt.Errorf("Failed to pipe download: %s", err) - } - log.Printf("Copied %d bytes for %s", numBytes, src) - - if err = localCmd.Wait(); err != nil { - return fmt.Errorf("Failed to download '%s' from container: %s", src, err) + return err } return nil @@ -306,79 +234,5 @@ func (c *WindowsContainerCommunicator) DownloadDir(src string, dst string, exclu func (c *WindowsContainerCommunicator) run(cmd *exec.Cmd, remote *packer.RemoteCmd, stdin io.WriteCloser, stdout, stderr io.ReadCloser) { // For Docker, remote communication must be serialized since it // only supports single execution. - c.lock.Lock() - defer c.lock.Unlock() - - wg := sync.WaitGroup{} - repeat := func(w io.Writer, r io.ReadCloser) { - io.Copy(w, r) - r.Close() - wg.Done() - } - - if remote.Stdout != nil { - wg.Add(1) - go repeat(remote.Stdout, stdout) - } - - if remote.Stderr != nil { - wg.Add(1) - go repeat(remote.Stderr, stderr) - } - - // Start the command - log.Printf("Executing %s:", strings.Join(cmd.Args, " ")) - if err := cmd.Start(); err != nil { - log.Printf("Error executing: %s", err) - remote.SetExited(254) - return - } - - var exitStatus int - - 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() - - if exitErr, ok := err.(*exec.ExitError); ok { - exitStatus = 1 - - // 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() - } - } - - // Set the exit status which triggers waiters - remote.SetExited(exitStatus) -} - -// TODO Workaround for #5307. Remove once #5409 is fixed. -func (c *WindowsContainerCommunicator) fixDestinationOwner(destination string) error { - if !c.Config.FixUploadOwner { - return nil - } - - owner := c.ContainerUser - if owner == "" { - owner = "root" - } - - chownArgs := []string{ - "docker", "exec", "--user", "root", c.ContainerID, "/bin/sh", "-c", - fmt.Sprintf("chown -R %s %s", owner, destination), - } - if output, err := exec.Command(chownArgs[0], chownArgs[1:]...).CombinedOutput(); err != nil { - return fmt.Errorf("Failed to set owner of the uploaded file: %s, %s", err, output) - } - - return nil + c.Communicator.run(cmd, remote, stdin, stdout, stderr) }