Fix premature close of build output on pull

The build job will sometimes trigger a pull job when the base image
does not exist. Now that engine jobs properly close their output by default
the pull job would also close the build job's stdout in a cascading close
upon completion of the pull.

This patch corrects this by wrapping the `pull` job's stdout with a
nopCloseWriter which will not close the stdout of the `build` job.

Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn)
This commit is contained in:
Josh Hawn 2015-01-26 20:56:34 -08:00
parent acb8e08296
commit e662775ffb
2 changed files with 86 additions and 1 deletions

View file

@ -25,6 +25,7 @@ import (
imagepkg "github.com/docker/docker/image"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/chrootarchive"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/symlink"
"github.com/docker/docker/pkg/system"
@ -433,7 +434,7 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) {
job.SetenvBool("json", b.StreamFormatter.Json())
job.SetenvBool("parallel", true)
job.SetenvJson("authConfig", pullRegistryAuth)
job.Stdout.Add(b.OutOld)
job.Stdout.Add(ioutils.NopWriteCloser(b.OutOld))
if err := job.Run(); err != nil {
return nil, err
}

View file

@ -4,6 +4,8 @@ import (
"bytes"
"strings"
"testing"
"github.com/docker/docker/pkg/ioutils"
)
func TestRegister(t *testing.T) {
@ -150,3 +152,85 @@ func TestCatchallEmptyName(t *testing.T) {
t.Fatalf("Engine.Job(\"\").Run() should return an error")
}
}
// Ensure that a job within a job both using the same underlying standard
// output writer does not close the output of the outer job when the inner
// job's stdout is wrapped with a NopCloser. When not wrapped, it should
// close the outer job's output.
func TestNestedJobSharedOutput(t *testing.T) {
var (
outerHandler Handler
innerHandler Handler
wrapOutput bool
)
outerHandler = func(job *Job) Status {
job.Stdout.Write([]byte("outer1"))
innerJob := job.Eng.Job("innerJob")
if wrapOutput {
innerJob.Stdout.Add(ioutils.NopWriteCloser(job.Stdout))
} else {
innerJob.Stdout.Add(job.Stdout)
}
if err := innerJob.Run(); err != nil {
t.Fatal(err)
}
// If wrapOutput was *false* this write will do nothing.
// FIXME (jlhawn): It should cause an error to write to
// closed output.
job.Stdout.Write([]byte(" outer2"))
return StatusOK
}
innerHandler = func(job *Job) Status {
job.Stdout.Write([]byte(" inner"))
return StatusOK
}
eng := New()
eng.Register("outerJob", outerHandler)
eng.Register("innerJob", innerHandler)
// wrapOutput starts *false* so the expected
// output of running the outer job will be:
//
// "outer1 inner"
//
outBuf := new(bytes.Buffer)
outerJob := eng.Job("outerJob")
outerJob.Stdout.Add(outBuf)
if err := outerJob.Run(); err != nil {
t.Fatal(err)
}
expectedOutput := "outer1 inner"
if outBuf.String() != expectedOutput {
t.Fatalf("expected job output to be %q, got %q", expectedOutput, outBuf.String())
}
// Set wrapOutput to true so that the expected
// output of running the outer job will be:
//
// "outer1 inner outer2"
//
wrapOutput = true
outBuf.Reset()
outerJob = eng.Job("outerJob")
outerJob.Stdout.Add(outBuf)
if err := outerJob.Run(); err != nil {
t.Fatal(err)
}
expectedOutput = "outer1 inner outer2"
if outBuf.String() != expectedOutput {
t.Fatalf("expected job output to be %q, got %q", expectedOutput, outBuf.String())
}
}