From 5f752269dfb19d462a9ea2df5f79acca0d7d6018 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 9 Nov 2013 10:13:27 -0800 Subject: [PATCH] builder/docker: Communicator.Start doesn't block --- builder/docker/communicator.go | 140 +++++++++++++++++++-------------- 1 file changed, 83 insertions(+), 57 deletions(-) diff --git a/builder/docker/communicator.go b/builder/docker/communicator.go index 45ad0ee92..d757e89cb 100644 --- a/builder/docker/communicator.go +++ b/builder/docker/communicator.go @@ -11,6 +11,7 @@ import ( "os/exec" "path/filepath" "strconv" + "sync" "syscall" "time" ) @@ -19,6 +20,8 @@ type Communicator struct { ContainerId string HostDir string ContainerDir string + + lock sync.Mutex } func (c *Communicator) Start(remote *packer.RemoteCmd) error { @@ -38,6 +41,68 @@ func (c *Communicator) Start(remote *packer.RemoteCmd) error { exitCodePath := outputFile.Name() + "-exit" defer os.Remove(exitCodePath) + cmd := exec.Command("docker", "attach", c.ContainerId) + stdin_w, err := cmd.StdinPipe() + if err != nil { + return err + } + + // Run the actual command in a goroutine so that Start doesn't block + go c.run(cmd, remote, stdin_w, outputFile, exitCodePath) + + return nil +} + +func (c *Communicator) Upload(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 + } + defer os.Remove(tempfile.Name()) + + // Copy the contents to the temporary file + _, err = io.Copy(tempfile, src) + tempfile.Close() + if err != nil { + return err + } + + // Copy the file into place by copying the temporary file we put + // into the shared folder into the proper location in the container + cmd := &packer.RemoteCmd{ + Command: fmt.Sprintf("cp %s/%s %s", c.ContainerDir, + filepath.Base(tempfile.Name()), dst), + } + + if err := c.Start(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 *Communicator) UploadDir(dst string, src string, exclude []string) error { + return nil +} + +func (c *Communicator) Download(src string, dst io.Writer) error { + return nil +} + +// Runs the given command and blocks until completion +func (c *Communicator) run(cmd *exec.Cmd, remote *packer.RemoteCmd, stdin_w io.WriteCloser, outputFile *os.File, exitCodePath string) { + // For Docker, remote communication must be serialized since it + // only supports single execution. + c.lock.Lock() + defer c.lock.Unlock() + // Modify the remote command so that all the output of the commands // go to a single file and so that the exit code is redirected to // a single file. This lets us determine both when the command @@ -49,15 +114,12 @@ func (c *Communicator) Start(remote *packer.RemoteCmd) error { filepath.Join(c.ContainerDir, filepath.Base(outputFile.Name())), filepath.Join(c.ContainerDir, filepath.Base(exitCodePath))) - cmd := exec.Command("docker", "attach", c.ContainerId) - stdin_w, err := cmd.StdinPipe() - if err != nil { - return err - } - + // Start the command log.Printf("Executing in container %s: %#v", c.ContainerId, remoteCmd) if err := cmd.Start(); err != nil { - return err + log.Printf("Error executing: %s", err) + remote.SetExited(254) + return } go func() { @@ -73,7 +135,7 @@ func (c *Communicator) Start(remote *packer.RemoteCmd) error { stdin_w.Write([]byte(remoteCmd + "\n")) }() - err = cmd.Wait() + err := cmd.Wait() if exitErr, ok := err.(*exec.ExitError); ok { exitStatus := 1 @@ -86,7 +148,7 @@ func (c *Communicator) Start(remote *packer.RemoteCmd) error { // Say that we ended, since if Docker itself failed, then // the command must've not run, or so we assume remote.SetExited(exitStatus) - return nil + return } // Wait for the exit code to appear in our file... @@ -103,19 +165,25 @@ func (c *Communicator) Start(remote *packer.RemoteCmd) error { // Read the exit code exitRaw, err := ioutil.ReadFile(exitCodePath) if err != nil { - return err + log.Printf("Error executing: %s", err) + remote.SetExited(254) + return } exitStatus, err := strconv.ParseInt(string(bytes.TrimSpace(exitRaw)), 10, 0) if err != nil { - return err + log.Printf("Error executing: %s", err) + remote.SetExited(254) + return } log.Printf("Executed command exit status: %d", exitStatus) // Read the output f, err := os.Open(outputFile.Name()) if err != nil { - return err + log.Printf("Error executing: %s", err) + remote.SetExited(254) + return } defer f.Close() @@ -124,7 +192,9 @@ func (c *Communicator) Start(remote *packer.RemoteCmd) error { } else { output, err := ioutil.ReadAll(f) if err != nil { - return err + log.Printf("Error executing: %s", err) + remote.SetExited(254) + return } log.Printf("Command output: %s", string(output)) @@ -132,48 +202,4 @@ func (c *Communicator) Start(remote *packer.RemoteCmd) error { // Finally, we're done remote.SetExited(int(exitStatus)) - - return nil -} - -func (c *Communicator) Upload(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 - } - defer os.Remove(tempfile.Name()) - - // Copy the contents to the temporary file - _, err = io.Copy(tempfile, src) - tempfile.Close() - if err != nil { - return err - } - - // TODO(mitchellh): Copy the file into place - cmd := &packer.RemoteCmd{ - Command: fmt.Sprintf("cp %s/%s %s", c.ContainerDir, - filepath.Base(tempfile.Name()), dst), - } - - if err := c.Start(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 *Communicator) UploadDir(dst string, src string, exclude []string) error { - return nil -} - -func (c *Communicator) Download(src string, dst io.Writer) error { - return nil }