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)
| ... | ... |
@@ -25,6 +25,7 @@ import ( |
| 25 | 25 |
imagepkg "github.com/docker/docker/image" |
| 26 | 26 |
"github.com/docker/docker/pkg/archive" |
| 27 | 27 |
"github.com/docker/docker/pkg/chrootarchive" |
| 28 |
+ "github.com/docker/docker/pkg/ioutils" |
|
| 28 | 29 |
"github.com/docker/docker/pkg/parsers" |
| 29 | 30 |
"github.com/docker/docker/pkg/symlink" |
| 30 | 31 |
"github.com/docker/docker/pkg/system" |
| ... | ... |
@@ -433,7 +434,7 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) {
|
| 433 | 433 |
job.SetenvBool("json", b.StreamFormatter.Json())
|
| 434 | 434 |
job.SetenvBool("parallel", true)
|
| 435 | 435 |
job.SetenvJson("authConfig", pullRegistryAuth)
|
| 436 |
- job.Stdout.Add(b.OutOld) |
|
| 436 |
+ job.Stdout.Add(ioutils.NopWriteCloser(b.OutOld)) |
|
| 437 | 437 |
if err := job.Run(); err != nil {
|
| 438 | 438 |
return nil, err |
| 439 | 439 |
} |
| ... | ... |
@@ -4,6 +4,8 @@ import ( |
| 4 | 4 |
"bytes" |
| 5 | 5 |
"strings" |
| 6 | 6 |
"testing" |
| 7 |
+ |
|
| 8 |
+ "github.com/docker/docker/pkg/ioutils" |
|
| 7 | 9 |
) |
| 8 | 10 |
|
| 9 | 11 |
func TestRegister(t *testing.T) {
|
| ... | ... |
@@ -150,3 +152,85 @@ func TestCatchallEmptyName(t *testing.T) {
|
| 150 | 150 |
t.Fatalf("Engine.Job(\"\").Run() should return an error")
|
| 151 | 151 |
} |
| 152 | 152 |
} |
| 153 |
+ |
|
| 154 |
+// Ensure that a job within a job both using the same underlying standard |
|
| 155 |
+// output writer does not close the output of the outer job when the inner |
|
| 156 |
+// job's stdout is wrapped with a NopCloser. When not wrapped, it should |
|
| 157 |
+// close the outer job's output. |
|
| 158 |
+func TestNestedJobSharedOutput(t *testing.T) {
|
|
| 159 |
+ var ( |
|
| 160 |
+ outerHandler Handler |
|
| 161 |
+ innerHandler Handler |
|
| 162 |
+ wrapOutput bool |
|
| 163 |
+ ) |
|
| 164 |
+ |
|
| 165 |
+ outerHandler = func(job *Job) Status {
|
|
| 166 |
+ job.Stdout.Write([]byte("outer1"))
|
|
| 167 |
+ |
|
| 168 |
+ innerJob := job.Eng.Job("innerJob")
|
|
| 169 |
+ |
|
| 170 |
+ if wrapOutput {
|
|
| 171 |
+ innerJob.Stdout.Add(ioutils.NopWriteCloser(job.Stdout)) |
|
| 172 |
+ } else {
|
|
| 173 |
+ innerJob.Stdout.Add(job.Stdout) |
|
| 174 |
+ } |
|
| 175 |
+ |
|
| 176 |
+ if err := innerJob.Run(); err != nil {
|
|
| 177 |
+ t.Fatal(err) |
|
| 178 |
+ } |
|
| 179 |
+ |
|
| 180 |
+ // If wrapOutput was *false* this write will do nothing. |
|
| 181 |
+ // FIXME (jlhawn): It should cause an error to write to |
|
| 182 |
+ // closed output. |
|
| 183 |
+ job.Stdout.Write([]byte(" outer2"))
|
|
| 184 |
+ |
|
| 185 |
+ return StatusOK |
|
| 186 |
+ } |
|
| 187 |
+ |
|
| 188 |
+ innerHandler = func(job *Job) Status {
|
|
| 189 |
+ job.Stdout.Write([]byte(" inner"))
|
|
| 190 |
+ |
|
| 191 |
+ return StatusOK |
|
| 192 |
+ } |
|
| 193 |
+ |
|
| 194 |
+ eng := New() |
|
| 195 |
+ eng.Register("outerJob", outerHandler)
|
|
| 196 |
+ eng.Register("innerJob", innerHandler)
|
|
| 197 |
+ |
|
| 198 |
+ // wrapOutput starts *false* so the expected |
|
| 199 |
+ // output of running the outer job will be: |
|
| 200 |
+ // |
|
| 201 |
+ // "outer1 inner" |
|
| 202 |
+ // |
|
| 203 |
+ outBuf := new(bytes.Buffer) |
|
| 204 |
+ outerJob := eng.Job("outerJob")
|
|
| 205 |
+ outerJob.Stdout.Add(outBuf) |
|
| 206 |
+ |
|
| 207 |
+ if err := outerJob.Run(); err != nil {
|
|
| 208 |
+ t.Fatal(err) |
|
| 209 |
+ } |
|
| 210 |
+ |
|
| 211 |
+ expectedOutput := "outer1 inner" |
|
| 212 |
+ if outBuf.String() != expectedOutput {
|
|
| 213 |
+ t.Fatalf("expected job output to be %q, got %q", expectedOutput, outBuf.String())
|
|
| 214 |
+ } |
|
| 215 |
+ |
|
| 216 |
+ // Set wrapOutput to true so that the expected |
|
| 217 |
+ // output of running the outer job will be: |
|
| 218 |
+ // |
|
| 219 |
+ // "outer1 inner outer2" |
|
| 220 |
+ // |
|
| 221 |
+ wrapOutput = true |
|
| 222 |
+ outBuf.Reset() |
|
| 223 |
+ outerJob = eng.Job("outerJob")
|
|
| 224 |
+ outerJob.Stdout.Add(outBuf) |
|
| 225 |
+ |
|
| 226 |
+ if err := outerJob.Run(); err != nil {
|
|
| 227 |
+ t.Fatal(err) |
|
| 228 |
+ } |
|
| 229 |
+ |
|
| 230 |
+ expectedOutput = "outer1 inner outer2" |
|
| 231 |
+ if outBuf.String() != expectedOutput {
|
|
| 232 |
+ t.Fatalf("expected job output to be %q, got %q", expectedOutput, outBuf.String())
|
|
| 233 |
+ } |
|
| 234 |
+} |