1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
|
package docker
import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
packersdk "github.com/hashicorp/packer/packer-plugin-sdk/packer"
)
// Windows containers are a special beast in Docker; you can't use docker cp
// to move files between the container and host.
// This communicator works around that limitation by reusing all possible
// methods and fields of the normal Docker Communicator, but we overwrite the
// Upload, Download, and UploadDir methods to utilize a mounted directory and
// native powershell commands rather than relying on docker cp.
type WindowsContainerCommunicator struct {
Communicator
}
// Upload uses docker exec to copy the file from the host to the container
func (c *WindowsContainerCommunicator) Upload(dst string, src io.Reader, fi *os.FileInfo) error {
// Create a temporary file to store the upload
tempfile, err := ioutil.TempFile(c.HostDir, "upload")
if err != nil {
return err
}
defer os.Remove(tempfile.Name())
// Copy the contents to the temporary file
_, err = io.Copy(tempfile, src)
if err != nil {
return err
}
if fi != nil {
tempfile.Chmod((*fi).Mode())
}
tempfile.Close()
// Copy the file into place by copying the temporary file we put
// into the shared folder into the proper location in the container
cmd := &packersdk.RemoteCmd{
Command: fmt.Sprintf("Copy-Item -Path %s/%s -Destination %s", c.ContainerDir,
filepath.Base(tempfile.Name()), dst),
}
ctx := context.TODO()
if err := c.Start(ctx, cmd); err != nil {
return err
}
// Wait for the copy to complete
cmd.Wait()
if cmd.ExitStatus() != 0 {
return fmt.Errorf("Upload failed with non-zero exit status: %d", cmd.ExitStatus())
}
return nil
}
func (c *WindowsContainerCommunicator) UploadDir(dst string, src string, exclude []string) error {
// Create the temporary directory that will store the contents of "src"
// for copying into the container.
td, err := ioutil.TempDir(c.HostDir, "dirupload")
if err != nil {
return err
}
defer os.RemoveAll(td)
walkFn := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relpath, err := filepath.Rel(src, path)
if err != nil {
return err
}
hostpath := filepath.Join(td, relpath)
// If it is a directory, just create it
if info.IsDir() {
return os.MkdirAll(hostpath, info.Mode())
}
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
dest, err := os.Readlink(path)
if err != nil {
return err
}
return os.Symlink(dest, hostpath)
}
// It is a file, copy it over, including mode.
src, err := os.Open(path)
if err != nil {
return err
}
defer src.Close()
dst, err := os.Create(hostpath)
if err != nil {
return err
}
defer dst.Close()
if _, err := io.Copy(dst, src); err != nil {
return err
}
return nil
}
// Copy the entire directory tree to the temporary directory
if err := filepath.Walk(src, walkFn); err != nil {
return err
}
// Determine the destination directory
containerSrc := filepath.Join(c.ContainerDir, filepath.Base(td))
containerDst := dst
if src[len(src)-1] != '/' {
containerDst = filepath.Join(dst, filepath.Base(src))
}
// Make the directory, then copy into it
cmd := &packersdk.RemoteCmd{
Command: fmt.Sprintf("Copy-Item %s -Destination %s -Recurse",
containerSrc, containerDst),
}
ctx := context.TODO()
if err := c.Start(ctx, cmd); err != nil {
return err
}
// Wait for the copy to complete
cmd.Wait()
if cmd.ExitStatus() != 0 {
return fmt.Errorf("Upload failed with non-zero exit status: %d", cmd.ExitStatus())
}
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
func (c *WindowsContainerCommunicator) Download(src string, dst io.Writer) error {
log.Printf("Downloading file from container: %s:%s", c.ContainerID, src)
// Copy file onto temp file on mounted volume inside container
var stdout, stderr bytes.Buffer
cmd := &packersdk.RemoteCmd{
Command: fmt.Sprintf("Copy-Item -Path %s -Destination %s/%s", src, c.ContainerDir,
filepath.Base(src)),
Stdout: &stdout,
Stderr: &stderr,
}
ctx := context.TODO()
if err := c.Start(ctx, cmd); err != nil {
return err
}
// 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 err
}
defer fsrc.Close()
defer os.Remove(fsrc.Name())
_, err = io.Copy(dst, fsrc)
if err != nil {
return err
}
return nil
}
|