can't use docker cp so call powershell to do this natively. Fix implementation for upload, uploadDir, and download in windows container communicator
This commit is contained in:
parent
af01860fa9
commit
36f2634352
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue