diff --git a/CHANGELOG.md b/CHANGELOG.md index a42e46104..0a43b5f4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,8 @@ BUG FIXES: * builder/amazon/all: More desctriptive error messages if Amazon only sends an error code. [GH-1189] * builder/docker: Remove the container during cleanup. [GH-1206] + * builder/docker: Fix case where not all output would show up from + provisioners. * builder/googlecompute: add `disk_size` option. [GH-1397] * builder/openstack: Region is not required. [GH-1418] * builder/parallels-iso: ISO not removed from VM after install [GH-1338] diff --git a/builder/docker/communicator.go b/builder/docker/communicator.go index 3da0fd7f7..2cf538f4f 100644 --- a/builder/docker/communicator.go +++ b/builder/docker/communicator.go @@ -232,20 +232,42 @@ func (c *Communicator) run(cmd *exec.Cmd, remote *packer.RemoteCmd, stdin_w io.W stdin_w.Write([]byte(remoteCmd + "\n")) }() - // Start a goroutine to read all the lines out of the logs + // Start a goroutine to read all the lines out of the logs. These channels + // allow us to stop the go-routine and wait for it to be stopped. + stopTailCh := make(chan struct{}) + doneCh := make(chan struct{}) go func() { - for line := range tail.Lines { - if remote.Stdout != nil { - remote.Stdout.Write([]byte(line.Text + "\n")) - } else { - log.Printf("Command stdout: %#v", line.Text) + defer close(doneCh) + + for { + select { + case <-tail.Dead(): + return + case line := <-tail.Lines: + if remote.Stdout != nil { + remote.Stdout.Write([]byte(line.Text + "\n")) + } else { + log.Printf("Command stdout: %#v", line.Text) + } + case <-time.After(2 * time.Second): + // If we're done, then return. Otherwise, keep grabbing + // data. This gives us a chance to flush all the lines + // out of the tailed file. + select { + case <-stopTailCh: + return + default: + } } } }() + var exitRaw []byte + var exitStatus int + var exitStatusRaw int64 err = cmd.Wait() if exitErr, ok := err.(*exec.ExitError); ok { - exitStatus := 1 + exitStatus = 1 // There is no process-independent way to get the REAL // exit status so we just try to go deeper. @@ -255,8 +277,7 @@ func (c *Communicator) run(cmd *exec.Cmd, remote *packer.RemoteCmd, stdin_w io.W // Say that we ended, since if Docker itself failed, then // the command must've not run, or so we assume - remote.SetExited(exitStatus) - return + goto REMOTE_EXIT } // Wait for the exit code to appear in our file... @@ -271,21 +292,27 @@ func (c *Communicator) run(cmd *exec.Cmd, remote *packer.RemoteCmd, stdin_w io.W } // Read the exit code - exitRaw, err := ioutil.ReadFile(exitCodePath) + exitRaw, err = ioutil.ReadFile(exitCodePath) if err != nil { log.Printf("Error executing: %s", err) - remote.SetExited(254) - return + exitStatus = 254 + goto REMOTE_EXIT } - exitStatus, err := strconv.ParseInt(string(bytes.TrimSpace(exitRaw)), 10, 0) + exitStatusRaw, err = strconv.ParseInt(string(bytes.TrimSpace(exitRaw)), 10, 0) if err != nil { log.Printf("Error executing: %s", err) - remote.SetExited(254) - return + exitStatus = 254 + goto REMOTE_EXIT } + exitStatus = int(exitStatusRaw) log.Printf("Executed command exit status: %d", exitStatus) - // Finally, we're done - remote.SetExited(int(exitStatus)) +REMOTE_EXIT: + // Wait for the tail to finish + close(stopTailCh) + <-doneCh + + // Set the exit status which triggers waiters + remote.SetExited(exitStatus) }