diff --git a/builder/internals.go b/builder/internals.go index 830da72725..ddbef108a0 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -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 } diff --git a/engine/engine_test.go b/engine/engine_test.go index 7ab2f8fc0d..96c3f0df30 100644 --- a/engine/engine_test.go +++ b/engine/engine_test.go @@ -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()) + } +}