github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/integration/build/build_test.go (about) 1 package build // import "github.com/docker/docker/integration/build" 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "context" 7 "encoding/json" 8 "io" 9 "io/ioutil" 10 "strings" 11 "testing" 12 "time" 13 14 "github.com/docker/docker/api/types" 15 "github.com/docker/docker/api/types/filters" 16 "github.com/docker/docker/api/types/versions" 17 "github.com/docker/docker/internal/test/fakecontext" 18 "github.com/docker/docker/pkg/jsonmessage" 19 "gotest.tools/assert" 20 is "gotest.tools/assert/cmp" 21 "gotest.tools/skip" 22 ) 23 24 func TestBuildWithRemoveAndForceRemove(t *testing.T) { 25 skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME") 26 defer setupTest(t)() 27 28 cases := []struct { 29 name string 30 dockerfile string 31 numberOfIntermediateContainers int 32 rm bool 33 forceRm bool 34 }{ 35 { 36 name: "successful build with no removal", 37 dockerfile: `FROM busybox 38 RUN exit 0 39 RUN exit 0`, 40 numberOfIntermediateContainers: 2, 41 rm: false, 42 forceRm: false, 43 }, 44 { 45 name: "successful build with remove", 46 dockerfile: `FROM busybox 47 RUN exit 0 48 RUN exit 0`, 49 numberOfIntermediateContainers: 0, 50 rm: true, 51 forceRm: false, 52 }, 53 { 54 name: "successful build with remove and force remove", 55 dockerfile: `FROM busybox 56 RUN exit 0 57 RUN exit 0`, 58 numberOfIntermediateContainers: 0, 59 rm: true, 60 forceRm: true, 61 }, 62 { 63 name: "failed build with no removal", 64 dockerfile: `FROM busybox 65 RUN exit 0 66 RUN exit 1`, 67 numberOfIntermediateContainers: 2, 68 rm: false, 69 forceRm: false, 70 }, 71 { 72 name: "failed build with remove", 73 dockerfile: `FROM busybox 74 RUN exit 0 75 RUN exit 1`, 76 numberOfIntermediateContainers: 1, 77 rm: true, 78 forceRm: false, 79 }, 80 { 81 name: "failed build with remove and force remove", 82 dockerfile: `FROM busybox 83 RUN exit 0 84 RUN exit 1`, 85 numberOfIntermediateContainers: 0, 86 rm: true, 87 forceRm: true, 88 }, 89 } 90 91 client := testEnv.APIClient() 92 ctx := context.Background() 93 for _, c := range cases { 94 t.Run(c.name, func(t *testing.T) { 95 t.Parallel() 96 dockerfile := []byte(c.dockerfile) 97 98 buff := bytes.NewBuffer(nil) 99 tw := tar.NewWriter(buff) 100 assert.NilError(t, tw.WriteHeader(&tar.Header{ 101 Name: "Dockerfile", 102 Size: int64(len(dockerfile)), 103 })) 104 _, err := tw.Write(dockerfile) 105 assert.NilError(t, err) 106 assert.NilError(t, tw.Close()) 107 resp, err := client.ImageBuild(ctx, buff, types.ImageBuildOptions{Remove: c.rm, ForceRemove: c.forceRm, NoCache: true}) 108 assert.NilError(t, err) 109 defer resp.Body.Close() 110 filter, err := buildContainerIdsFilter(resp.Body) 111 assert.NilError(t, err) 112 remainingContainers, err := client.ContainerList(ctx, types.ContainerListOptions{Filters: filter, All: true}) 113 assert.NilError(t, err) 114 assert.Equal(t, c.numberOfIntermediateContainers, len(remainingContainers), "Expected %v remaining intermediate containers, got %v", c.numberOfIntermediateContainers, len(remainingContainers)) 115 }) 116 } 117 } 118 119 func buildContainerIdsFilter(buildOutput io.Reader) (filters.Args, error) { 120 const intermediateContainerPrefix = " ---> Running in " 121 filter := filters.NewArgs() 122 123 dec := json.NewDecoder(buildOutput) 124 for { 125 m := jsonmessage.JSONMessage{} 126 err := dec.Decode(&m) 127 if err == io.EOF { 128 return filter, nil 129 } 130 if err != nil { 131 return filter, err 132 } 133 if ix := strings.Index(m.Stream, intermediateContainerPrefix); ix != -1 { 134 filter.Add("id", strings.TrimSpace(m.Stream[ix+len(intermediateContainerPrefix):])) 135 } 136 } 137 } 138 139 func TestBuildMultiStageParentConfig(t *testing.T) { 140 skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.35"), "broken in earlier versions") 141 skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME") 142 dockerfile := ` 143 FROM busybox AS stage0 144 ENV WHO=parent 145 WORKDIR /foo 146 147 FROM stage0 148 ENV WHO=sibling1 149 WORKDIR sub1 150 151 FROM stage0 152 WORKDIR sub2 153 ` 154 ctx := context.Background() 155 source := fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile)) 156 defer source.Close() 157 158 apiclient := testEnv.APIClient() 159 resp, err := apiclient.ImageBuild(ctx, 160 source.AsTarReader(t), 161 types.ImageBuildOptions{ 162 Remove: true, 163 ForceRemove: true, 164 Tags: []string{"build1"}, 165 }) 166 assert.NilError(t, err) 167 _, err = io.Copy(ioutil.Discard, resp.Body) 168 resp.Body.Close() 169 assert.NilError(t, err) 170 171 time.Sleep(30 * time.Second) 172 173 imgs, err := apiclient.ImageList(ctx, types.ImageListOptions{}) 174 assert.NilError(t, err) 175 t.Log(imgs) 176 177 image, _, err := apiclient.ImageInspectWithRaw(ctx, "build1") 178 assert.NilError(t, err) 179 180 expected := "/foo/sub2" 181 if testEnv.DaemonInfo.OSType == "windows" { 182 expected = `C:\foo\sub2` 183 } 184 assert.Check(t, is.Equal(expected, image.Config.WorkingDir)) 185 assert.Check(t, is.Contains(image.Config.Env, "WHO=parent")) 186 } 187 188 // Test cases in #36996 189 func TestBuildLabelWithTargets(t *testing.T) { 190 skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.38"), "test added after 1.38") 191 skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME") 192 bldName := "build-a" 193 testLabels := map[string]string{ 194 "foo": "bar", 195 "dead": "beef", 196 } 197 198 dockerfile := ` 199 FROM busybox AS target-a 200 CMD ["/dev"] 201 LABEL label-a=inline-a 202 FROM busybox AS target-b 203 CMD ["/dist"] 204 LABEL label-b=inline-b 205 ` 206 207 ctx := context.Background() 208 source := fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile)) 209 defer source.Close() 210 211 apiclient := testEnv.APIClient() 212 // For `target-a` build 213 resp, err := apiclient.ImageBuild(ctx, 214 source.AsTarReader(t), 215 types.ImageBuildOptions{ 216 Remove: true, 217 ForceRemove: true, 218 Tags: []string{bldName}, 219 Labels: testLabels, 220 Target: "target-a", 221 }) 222 assert.NilError(t, err) 223 _, err = io.Copy(ioutil.Discard, resp.Body) 224 resp.Body.Close() 225 assert.NilError(t, err) 226 227 image, _, err := apiclient.ImageInspectWithRaw(ctx, bldName) 228 assert.NilError(t, err) 229 230 testLabels["label-a"] = "inline-a" 231 for k, v := range testLabels { 232 x, ok := image.Config.Labels[k] 233 assert.Assert(t, ok) 234 assert.Assert(t, x == v) 235 } 236 237 // For `target-b` build 238 bldName = "build-b" 239 delete(testLabels, "label-a") 240 resp, err = apiclient.ImageBuild(ctx, 241 source.AsTarReader(t), 242 types.ImageBuildOptions{ 243 Remove: true, 244 ForceRemove: true, 245 Tags: []string{bldName}, 246 Labels: testLabels, 247 Target: "target-b", 248 }) 249 assert.NilError(t, err) 250 _, err = io.Copy(ioutil.Discard, resp.Body) 251 resp.Body.Close() 252 assert.NilError(t, err) 253 254 image, _, err = apiclient.ImageInspectWithRaw(ctx, bldName) 255 assert.NilError(t, err) 256 257 testLabels["label-b"] = "inline-b" 258 for k, v := range testLabels { 259 x, ok := image.Config.Labels[k] 260 assert.Assert(t, ok) 261 assert.Assert(t, x == v) 262 } 263 } 264 265 func TestBuildWithEmptyLayers(t *testing.T) { 266 dockerfile := ` 267 FROM busybox 268 COPY 1/ /target/ 269 COPY 2/ /target/ 270 COPY 3/ /target/ 271 ` 272 ctx := context.Background() 273 source := fakecontext.New(t, "", 274 fakecontext.WithDockerfile(dockerfile), 275 fakecontext.WithFile("1/a", "asdf"), 276 fakecontext.WithFile("2/a", "asdf"), 277 fakecontext.WithFile("3/a", "asdf")) 278 defer source.Close() 279 280 apiclient := testEnv.APIClient() 281 resp, err := apiclient.ImageBuild(ctx, 282 source.AsTarReader(t), 283 types.ImageBuildOptions{ 284 Remove: true, 285 ForceRemove: true, 286 }) 287 assert.NilError(t, err) 288 _, err = io.Copy(ioutil.Discard, resp.Body) 289 resp.Body.Close() 290 assert.NilError(t, err) 291 } 292 293 // TestBuildMultiStageOnBuild checks that ONBUILD commands are applied to 294 // multiple subsequent stages 295 // #35652 296 func TestBuildMultiStageOnBuild(t *testing.T) { 297 skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.33"), "broken in earlier versions") 298 skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME") 299 defer setupTest(t)() 300 // test both metadata and layer based commands as they may be implemented differently 301 dockerfile := `FROM busybox AS stage1 302 ONBUILD RUN echo 'foo' >somefile 303 ONBUILD ENV bar=baz 304 305 FROM stage1 306 # fails if ONBUILD RUN fails 307 RUN cat somefile 308 309 FROM stage1 310 RUN cat somefile` 311 312 ctx := context.Background() 313 source := fakecontext.New(t, "", 314 fakecontext.WithDockerfile(dockerfile)) 315 defer source.Close() 316 317 apiclient := testEnv.APIClient() 318 resp, err := apiclient.ImageBuild(ctx, 319 source.AsTarReader(t), 320 types.ImageBuildOptions{ 321 Remove: true, 322 ForceRemove: true, 323 }) 324 325 out := bytes.NewBuffer(nil) 326 assert.NilError(t, err) 327 _, err = io.Copy(out, resp.Body) 328 resp.Body.Close() 329 assert.NilError(t, err) 330 331 assert.Check(t, is.Contains(out.String(), "Successfully built")) 332 333 imageIDs, err := getImageIDsFromBuild(out.Bytes()) 334 assert.NilError(t, err) 335 assert.Check(t, is.Equal(3, len(imageIDs))) 336 337 image, _, err := apiclient.ImageInspectWithRaw(context.Background(), imageIDs[2]) 338 assert.NilError(t, err) 339 assert.Check(t, is.Contains(image.Config.Env, "bar=baz")) 340 } 341 342 // #35403 #36122 343 func TestBuildUncleanTarFilenames(t *testing.T) { 344 skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.37"), "broken in earlier versions") 345 skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME") 346 347 ctx := context.TODO() 348 defer setupTest(t)() 349 350 dockerfile := `FROM scratch 351 COPY foo / 352 FROM scratch 353 COPY bar /` 354 355 buf := bytes.NewBuffer(nil) 356 w := tar.NewWriter(buf) 357 writeTarRecord(t, w, "Dockerfile", dockerfile) 358 writeTarRecord(t, w, "../foo", "foocontents0") 359 writeTarRecord(t, w, "/bar", "barcontents0") 360 err := w.Close() 361 assert.NilError(t, err) 362 363 apiclient := testEnv.APIClient() 364 resp, err := apiclient.ImageBuild(ctx, 365 buf, 366 types.ImageBuildOptions{ 367 Remove: true, 368 ForceRemove: true, 369 }) 370 371 out := bytes.NewBuffer(nil) 372 assert.NilError(t, err) 373 _, err = io.Copy(out, resp.Body) 374 resp.Body.Close() 375 assert.NilError(t, err) 376 377 // repeat with changed data should not cause cache hits 378 379 buf = bytes.NewBuffer(nil) 380 w = tar.NewWriter(buf) 381 writeTarRecord(t, w, "Dockerfile", dockerfile) 382 writeTarRecord(t, w, "../foo", "foocontents1") 383 writeTarRecord(t, w, "/bar", "barcontents1") 384 err = w.Close() 385 assert.NilError(t, err) 386 387 resp, err = apiclient.ImageBuild(ctx, 388 buf, 389 types.ImageBuildOptions{ 390 Remove: true, 391 ForceRemove: true, 392 }) 393 394 out = bytes.NewBuffer(nil) 395 assert.NilError(t, err) 396 _, err = io.Copy(out, resp.Body) 397 resp.Body.Close() 398 assert.NilError(t, err) 399 assert.Assert(t, !strings.Contains(out.String(), "Using cache")) 400 } 401 402 // docker/for-linux#135 403 // #35641 404 func TestBuildMultiStageLayerLeak(t *testing.T) { 405 skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME") 406 skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.37"), "broken in earlier versions") 407 ctx := context.TODO() 408 defer setupTest(t)() 409 410 // all commands need to match until COPY 411 dockerfile := `FROM busybox 412 WORKDIR /foo 413 COPY foo . 414 FROM busybox 415 WORKDIR /foo 416 COPY bar . 417 RUN [ -f bar ] 418 RUN [ ! -f foo ] 419 ` 420 421 source := fakecontext.New(t, "", 422 fakecontext.WithFile("foo", "0"), 423 fakecontext.WithFile("bar", "1"), 424 fakecontext.WithDockerfile(dockerfile)) 425 defer source.Close() 426 427 apiclient := testEnv.APIClient() 428 resp, err := apiclient.ImageBuild(ctx, 429 source.AsTarReader(t), 430 types.ImageBuildOptions{ 431 Remove: true, 432 ForceRemove: true, 433 }) 434 435 out := bytes.NewBuffer(nil) 436 assert.NilError(t, err) 437 _, err = io.Copy(out, resp.Body) 438 resp.Body.Close() 439 assert.NilError(t, err) 440 441 assert.Check(t, is.Contains(out.String(), "Successfully built")) 442 } 443 444 // #37581 445 func TestBuildWithHugeFile(t *testing.T) { 446 skip.If(t, testEnv.OSType == "windows") 447 ctx := context.TODO() 448 defer setupTest(t)() 449 450 dockerfile := `FROM busybox 451 # create a sparse file with size over 8GB 452 RUN for g in $(seq 0 8); do dd if=/dev/urandom of=rnd bs=1K count=1 seek=$((1024*1024*g)) status=none; done && \ 453 ls -la rnd && du -sk rnd` 454 455 buf := bytes.NewBuffer(nil) 456 w := tar.NewWriter(buf) 457 writeTarRecord(t, w, "Dockerfile", dockerfile) 458 err := w.Close() 459 assert.NilError(t, err) 460 461 apiclient := testEnv.APIClient() 462 resp, err := apiclient.ImageBuild(ctx, 463 buf, 464 types.ImageBuildOptions{ 465 Remove: true, 466 ForceRemove: true, 467 }) 468 469 out := bytes.NewBuffer(nil) 470 assert.NilError(t, err) 471 _, err = io.Copy(out, resp.Body) 472 resp.Body.Close() 473 assert.NilError(t, err) 474 assert.Check(t, is.Contains(out.String(), "Successfully built")) 475 } 476 477 func TestBuildWithEmptyDockerfile(t *testing.T) { 478 ctx := context.TODO() 479 defer setupTest(t)() 480 481 tests := []struct { 482 name string 483 dockerfile string 484 expectedErr string 485 }{ 486 { 487 name: "empty-dockerfile", 488 dockerfile: "", 489 expectedErr: "cannot be empty", 490 }, 491 { 492 name: "empty-lines-dockerfile", 493 dockerfile: ` 494 495 496 497 `, 498 expectedErr: "file with no instructions", 499 }, 500 { 501 name: "comment-only-dockerfile", 502 dockerfile: `# this is a comment`, 503 expectedErr: "file with no instructions", 504 }, 505 } 506 507 apiclient := testEnv.APIClient() 508 509 for _, tc := range tests { 510 tc := tc 511 t.Run(tc.name, func(t *testing.T) { 512 t.Parallel() 513 514 buf := bytes.NewBuffer(nil) 515 w := tar.NewWriter(buf) 516 writeTarRecord(t, w, "Dockerfile", tc.dockerfile) 517 err := w.Close() 518 assert.NilError(t, err) 519 520 _, err = apiclient.ImageBuild(ctx, 521 buf, 522 types.ImageBuildOptions{ 523 Remove: true, 524 ForceRemove: true, 525 }) 526 527 assert.Check(t, is.Contains(err.Error(), tc.expectedErr)) 528 }) 529 } 530 } 531 532 func writeTarRecord(t *testing.T, w *tar.Writer, fn, contents string) { 533 err := w.WriteHeader(&tar.Header{ 534 Name: fn, 535 Mode: 0600, 536 Size: int64(len(contents)), 537 Typeflag: '0', 538 }) 539 assert.NilError(t, err) 540 _, err = w.Write([]byte(contents)) 541 assert.NilError(t, err) 542 } 543 544 type buildLine struct { 545 Stream string 546 Aux struct { 547 ID string 548 } 549 } 550 551 func getImageIDsFromBuild(output []byte) ([]string, error) { 552 var ids []string 553 for _, line := range bytes.Split(output, []byte("\n")) { 554 if len(line) == 0 { 555 continue 556 } 557 entry := buildLine{} 558 if err := json.Unmarshal(line, &entry); err != nil { 559 return nil, err 560 } 561 if entry.Aux.ID != "" { 562 ids = append(ids, entry.Aux.ID) 563 } 564 } 565 return ids, nil 566 }