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
|
// Create the communicator that talks to Docker via various
|
||||||
// os/exec tricks.
|
// os/exec tricks.
|
||||||
if config.WindowsContainer {
|
if config.WindowsContainer {
|
||||||
comm := &WindowsContainerCommunicator{
|
comm := &WindowsContainerCommunicator{Communicator{
|
||||||
ContainerID: containerId,
|
ContainerID: containerId,
|
||||||
HostDir: tempDir,
|
HostDir: tempDir,
|
||||||
ContainerDir: config.ContainerDir,
|
ContainerDir: config.ContainerDir,
|
||||||
Version: version,
|
Version: version,
|
||||||
Config: config,
|
Config: config,
|
||||||
ContainerUser: containerUser,
|
ContainerUser: containerUser,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
state.Put("communicator", comm)
|
state.Put("communicator", comm)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/tar"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -9,22 +9,12 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-version"
|
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WindowsContainerCommunicator struct {
|
type WindowsContainerCommunicator struct {
|
||||||
ContainerID string
|
Communicator
|
||||||
HostDir string
|
|
||||||
ContainerDir string
|
|
||||||
Version *version.Version
|
|
||||||
Config *Config
|
|
||||||
ContainerUser string
|
|
||||||
lock sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *WindowsContainerCommunicator) Start(remote *packer.RemoteCmd) error {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi != nil {
|
if fi != nil {
|
||||||
tempfile.Chmod((*fi).Mode())
|
tempfile.Chmod((*fi).Mode())
|
||||||
}
|
}
|
||||||
|
@ -165,12 +154,7 @@ func (c *WindowsContainerCommunicator) UploadDir(dst string, src string, exclude
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
si, err := src.Stat()
|
return nil
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return dst.Chmod(si.Mode())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the entire directory tree to the temporary directory
|
// 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
|
// Make the directory, then copy into it
|
||||||
cmd := &packer.RemoteCmd{
|
cmd := &packer.RemoteCmd{
|
||||||
Command: fmt.Sprintf("set -e; mkdir -p %s; command cp -R %s/ %s",
|
Command: fmt.Sprintf("Copy-Item %s -Destination %s -Recurse",
|
||||||
containerDst, containerSrc, containerDst),
|
containerSrc, containerDst),
|
||||||
}
|
}
|
||||||
if err := c.Start(cmd); err != nil {
|
if err := c.Start(cmd); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -203,96 +187,40 @@ func (c *WindowsContainerCommunicator) UploadDir(dst string, src string, exclude
|
||||||
return nil
|
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
|
// 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
|
// path and want to write to an io.Writer
|
||||||
// cp to write to stdout, and then copy the stream to our destination io.Writer.
|
|
||||||
func (c *WindowsContainerCommunicator) Download(src string, dst io.Writer) error {
|
func (c *WindowsContainerCommunicator) Download(src string, dst io.Writer) error {
|
||||||
log.Printf("Downloading file from container: %s:%s", c.ContainerID, src)
|
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 {
|
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 {
|
_, err = io.Copy(dst, fsrc)
|
||||||
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()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to read header from tar stream: %s", err)
|
return 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 nil
|
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) {
|
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
|
// For Docker, remote communication must be serialized since it
|
||||||
// only supports single execution.
|
// only supports single execution.
|
||||||
c.lock.Lock()
|
c.Communicator.run(cmd, remote, stdin, stdout, stderr)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue