github.com/jwhonce/docker@v0.6.7-0.20190327063223-da823cf3a5a3/integration-cli/docker_api_build_test.go (about) 1 package main 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "context" 7 "encoding/json" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "net/http" 12 "regexp" 13 "strings" 14 15 "github.com/docker/docker/api/types" 16 "github.com/docker/docker/integration-cli/checker" 17 "github.com/docker/docker/internal/test/fakecontext" 18 "github.com/docker/docker/internal/test/fakegit" 19 "github.com/docker/docker/internal/test/fakestorage" 20 "github.com/docker/docker/internal/test/request" 21 "github.com/go-check/check" 22 "gotest.tools/assert" 23 is "gotest.tools/assert/cmp" 24 ) 25 26 func (s *DockerSuite) TestBuildAPIDockerFileRemote(c *check.C) { 27 testRequires(c, NotUserNamespace) 28 29 var testD string 30 if testEnv.OSType == "windows" { 31 testD = `FROM busybox 32 RUN find / -name ba* 33 RUN find /tmp/` 34 } else { 35 // -xdev is required because sysfs can cause EPERM 36 testD = `FROM busybox 37 RUN find / -xdev -name ba* 38 RUN find /tmp/` 39 } 40 server := fakestorage.New(c, "", fakecontext.WithFiles(map[string]string{"testD": testD})) 41 defer server.Close() 42 43 res, body, err := request.Post("/build?dockerfile=baz&remote="+server.URL()+"/testD", request.JSON) 44 c.Assert(err, checker.IsNil) 45 c.Assert(res.StatusCode, checker.Equals, http.StatusOK) 46 47 buf, err := request.ReadBody(body) 48 c.Assert(err, checker.IsNil) 49 50 // Make sure Dockerfile exists. 51 // Make sure 'baz' doesn't exist ANYWHERE despite being mentioned in the URL 52 out := string(buf) 53 c.Assert(out, checker.Contains, "RUN find /tmp") 54 c.Assert(out, checker.Not(checker.Contains), "baz") 55 } 56 57 func (s *DockerSuite) TestBuildAPIRemoteTarballContext(c *check.C) { 58 buffer := new(bytes.Buffer) 59 tw := tar.NewWriter(buffer) 60 defer tw.Close() 61 62 dockerfile := []byte("FROM busybox") 63 err := tw.WriteHeader(&tar.Header{ 64 Name: "Dockerfile", 65 Size: int64(len(dockerfile)), 66 }) 67 // failed to write tar file header 68 c.Assert(err, checker.IsNil) 69 70 _, err = tw.Write(dockerfile) 71 // failed to write tar file content 72 c.Assert(err, checker.IsNil) 73 74 // failed to close tar archive 75 c.Assert(tw.Close(), checker.IsNil) 76 77 server := fakestorage.New(c, "", fakecontext.WithBinaryFiles(map[string]*bytes.Buffer{ 78 "testT.tar": buffer, 79 })) 80 defer server.Close() 81 82 res, b, err := request.Post("/build?remote="+server.URL()+"/testT.tar", request.ContentType("application/tar")) 83 c.Assert(err, checker.IsNil) 84 c.Assert(res.StatusCode, checker.Equals, http.StatusOK) 85 b.Close() 86 } 87 88 func (s *DockerSuite) TestBuildAPIRemoteTarballContextWithCustomDockerfile(c *check.C) { 89 buffer := new(bytes.Buffer) 90 tw := tar.NewWriter(buffer) 91 defer tw.Close() 92 93 dockerfile := []byte(`FROM busybox 94 RUN echo 'wrong'`) 95 err := tw.WriteHeader(&tar.Header{ 96 Name: "Dockerfile", 97 Size: int64(len(dockerfile)), 98 }) 99 // failed to write tar file header 100 c.Assert(err, checker.IsNil) 101 102 _, err = tw.Write(dockerfile) 103 // failed to write tar file content 104 c.Assert(err, checker.IsNil) 105 106 custom := []byte(`FROM busybox 107 RUN echo 'right' 108 `) 109 err = tw.WriteHeader(&tar.Header{ 110 Name: "custom", 111 Size: int64(len(custom)), 112 }) 113 114 // failed to write tar file header 115 c.Assert(err, checker.IsNil) 116 117 _, err = tw.Write(custom) 118 // failed to write tar file content 119 c.Assert(err, checker.IsNil) 120 121 // failed to close tar archive 122 c.Assert(tw.Close(), checker.IsNil) 123 124 server := fakestorage.New(c, "", fakecontext.WithBinaryFiles(map[string]*bytes.Buffer{ 125 "testT.tar": buffer, 126 })) 127 defer server.Close() 128 129 url := "/build?dockerfile=custom&remote=" + server.URL() + "/testT.tar" 130 res, body, err := request.Post(url, request.ContentType("application/tar")) 131 c.Assert(err, checker.IsNil) 132 c.Assert(res.StatusCode, checker.Equals, http.StatusOK) 133 134 defer body.Close() 135 content, err := request.ReadBody(body) 136 c.Assert(err, checker.IsNil) 137 138 // Build used the wrong dockerfile. 139 c.Assert(string(content), checker.Not(checker.Contains), "wrong") 140 } 141 142 func (s *DockerSuite) TestBuildAPILowerDockerfile(c *check.C) { 143 git := fakegit.New(c, "repo", map[string]string{ 144 "dockerfile": `FROM busybox 145 RUN echo from dockerfile`, 146 }, false) 147 defer git.Close() 148 149 res, body, err := request.Post("/build?remote="+git.RepoURL, request.JSON) 150 c.Assert(err, checker.IsNil) 151 c.Assert(res.StatusCode, checker.Equals, http.StatusOK) 152 153 buf, err := request.ReadBody(body) 154 c.Assert(err, checker.IsNil) 155 156 out := string(buf) 157 c.Assert(out, checker.Contains, "from dockerfile") 158 } 159 160 func (s *DockerSuite) TestBuildAPIBuildGitWithF(c *check.C) { 161 git := fakegit.New(c, "repo", map[string]string{ 162 "baz": `FROM busybox 163 RUN echo from baz`, 164 "Dockerfile": `FROM busybox 165 RUN echo from Dockerfile`, 166 }, false) 167 defer git.Close() 168 169 // Make sure it tries to 'dockerfile' query param value 170 res, body, err := request.Post("/build?dockerfile=baz&remote="+git.RepoURL, request.JSON) 171 c.Assert(err, checker.IsNil) 172 c.Assert(res.StatusCode, checker.Equals, http.StatusOK) 173 174 buf, err := request.ReadBody(body) 175 c.Assert(err, checker.IsNil) 176 177 out := string(buf) 178 c.Assert(out, checker.Contains, "from baz") 179 } 180 181 func (s *DockerSuite) TestBuildAPIDoubleDockerfile(c *check.C) { 182 testRequires(c, UnixCli) // dockerfile overwrites Dockerfile on Windows 183 git := fakegit.New(c, "repo", map[string]string{ 184 "Dockerfile": `FROM busybox 185 RUN echo from Dockerfile`, 186 "dockerfile": `FROM busybox 187 RUN echo from dockerfile`, 188 }, false) 189 defer git.Close() 190 191 // Make sure it tries to 'dockerfile' query param value 192 res, body, err := request.Post("/build?remote="+git.RepoURL, request.JSON) 193 c.Assert(err, checker.IsNil) 194 c.Assert(res.StatusCode, checker.Equals, http.StatusOK) 195 196 buf, err := request.ReadBody(body) 197 c.Assert(err, checker.IsNil) 198 199 out := string(buf) 200 c.Assert(out, checker.Contains, "from Dockerfile") 201 } 202 203 func (s *DockerSuite) TestBuildAPIUnnormalizedTarPaths(c *check.C) { 204 // Make sure that build context tars with entries of the form 205 // x/./y don't cause caching false positives. 206 207 buildFromTarContext := func(fileContents []byte) string { 208 buffer := new(bytes.Buffer) 209 tw := tar.NewWriter(buffer) 210 defer tw.Close() 211 212 dockerfile := []byte(`FROM busybox 213 COPY dir /dir/`) 214 err := tw.WriteHeader(&tar.Header{ 215 Name: "Dockerfile", 216 Size: int64(len(dockerfile)), 217 }) 218 //failed to write tar file header 219 c.Assert(err, checker.IsNil) 220 221 _, err = tw.Write(dockerfile) 222 // failed to write Dockerfile in tar file content 223 c.Assert(err, checker.IsNil) 224 225 err = tw.WriteHeader(&tar.Header{ 226 Name: "dir/./file", 227 Size: int64(len(fileContents)), 228 }) 229 //failed to write tar file header 230 c.Assert(err, checker.IsNil) 231 232 _, err = tw.Write(fileContents) 233 // failed to write file contents in tar file content 234 c.Assert(err, checker.IsNil) 235 236 // failed to close tar archive 237 c.Assert(tw.Close(), checker.IsNil) 238 239 res, body, err := request.Post("/build", request.RawContent(ioutil.NopCloser(buffer)), request.ContentType("application/x-tar")) 240 c.Assert(err, checker.IsNil) 241 c.Assert(res.StatusCode, checker.Equals, http.StatusOK) 242 243 out, err := request.ReadBody(body) 244 c.Assert(err, checker.IsNil) 245 lines := strings.Split(string(out), "\n") 246 c.Assert(len(lines), checker.GreaterThan, 1) 247 c.Assert(lines[len(lines)-2], checker.Matches, ".*Successfully built [0-9a-f]{12}.*") 248 249 re := regexp.MustCompile("Successfully built ([0-9a-f]{12})") 250 matches := re.FindStringSubmatch(lines[len(lines)-2]) 251 return matches[1] 252 } 253 254 imageA := buildFromTarContext([]byte("abc")) 255 imageB := buildFromTarContext([]byte("def")) 256 257 c.Assert(imageA, checker.Not(checker.Equals), imageB) 258 } 259 260 func (s *DockerSuite) TestBuildOnBuildWithCopy(c *check.C) { 261 dockerfile := ` 262 FROM ` + minimalBaseImage() + ` as onbuildbase 263 ONBUILD COPY file /file 264 265 FROM onbuildbase 266 ` 267 ctx := fakecontext.New(c, "", 268 fakecontext.WithDockerfile(dockerfile), 269 fakecontext.WithFile("file", "some content"), 270 ) 271 defer ctx.Close() 272 273 res, body, err := request.Post( 274 "/build", 275 request.RawContent(ctx.AsTarReader(c)), 276 request.ContentType("application/x-tar")) 277 c.Assert(err, checker.IsNil) 278 c.Assert(res.StatusCode, checker.Equals, http.StatusOK) 279 280 out, err := request.ReadBody(body) 281 c.Assert(err, checker.IsNil) 282 c.Assert(string(out), checker.Contains, "Successfully built") 283 } 284 285 func (s *DockerSuite) TestBuildOnBuildCache(c *check.C) { 286 build := func(dockerfile string) []byte { 287 ctx := fakecontext.New(c, "", 288 fakecontext.WithDockerfile(dockerfile), 289 ) 290 defer ctx.Close() 291 292 res, body, err := request.Post( 293 "/build", 294 request.RawContent(ctx.AsTarReader(c)), 295 request.ContentType("application/x-tar")) 296 assert.NilError(c, err) 297 assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode)) 298 299 out, err := request.ReadBody(body) 300 assert.NilError(c, err) 301 assert.Assert(c, is.Contains(string(out), "Successfully built")) 302 return out 303 } 304 305 dockerfile := ` 306 FROM ` + minimalBaseImage() + ` as onbuildbase 307 ENV something=bar 308 ONBUILD ENV foo=bar 309 ` 310 build(dockerfile) 311 312 dockerfile += "FROM onbuildbase" 313 out := build(dockerfile) 314 315 imageIDs := getImageIDsFromBuild(c, out) 316 assert.Assert(c, is.Len(imageIDs, 2)) 317 parentID, childID := imageIDs[0], imageIDs[1] 318 319 client := testEnv.APIClient() 320 321 // check parentID is correct 322 image, _, err := client.ImageInspectWithRaw(context.Background(), childID) 323 assert.NilError(c, err) 324 assert.Check(c, is.Equal(parentID, image.Parent)) 325 } 326 327 func (s *DockerRegistrySuite) TestBuildCopyFromForcePull(c *check.C) { 328 client := testEnv.APIClient() 329 330 repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) 331 // tag the image to upload it to the private registry 332 err := client.ImageTag(context.TODO(), "busybox", repoName) 333 assert.Check(c, err) 334 // push the image to the registry 335 rc, err := client.ImagePush(context.TODO(), repoName, types.ImagePushOptions{RegistryAuth: "{}"}) 336 assert.Check(c, err) 337 _, err = io.Copy(ioutil.Discard, rc) 338 assert.Check(c, err) 339 340 dockerfile := fmt.Sprintf(` 341 FROM %s AS foo 342 RUN touch abc 343 FROM %s 344 COPY --from=foo /abc / 345 `, repoName, repoName) 346 347 ctx := fakecontext.New(c, "", 348 fakecontext.WithDockerfile(dockerfile), 349 ) 350 defer ctx.Close() 351 352 res, body, err := request.Post( 353 "/build?pull=1", 354 request.RawContent(ctx.AsTarReader(c)), 355 request.ContentType("application/x-tar")) 356 assert.NilError(c, err) 357 assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode)) 358 359 out, err := request.ReadBody(body) 360 assert.NilError(c, err) 361 assert.Check(c, is.Contains(string(out), "Successfully built")) 362 } 363 364 func (s *DockerSuite) TestBuildAddRemoteNoDecompress(c *check.C) { 365 buffer := new(bytes.Buffer) 366 tw := tar.NewWriter(buffer) 367 dt := []byte("contents") 368 err := tw.WriteHeader(&tar.Header{ 369 Name: "foo", 370 Size: int64(len(dt)), 371 Mode: 0600, 372 Typeflag: tar.TypeReg, 373 }) 374 assert.NilError(c, err) 375 _, err = tw.Write(dt) 376 assert.NilError(c, err) 377 err = tw.Close() 378 assert.NilError(c, err) 379 380 server := fakestorage.New(c, "", fakecontext.WithBinaryFiles(map[string]*bytes.Buffer{ 381 "test.tar": buffer, 382 })) 383 defer server.Close() 384 385 dockerfile := fmt.Sprintf(` 386 FROM busybox 387 ADD %s/test.tar / 388 RUN [ -f test.tar ] 389 `, server.URL()) 390 391 ctx := fakecontext.New(c, "", 392 fakecontext.WithDockerfile(dockerfile), 393 ) 394 defer ctx.Close() 395 396 res, body, err := request.Post( 397 "/build", 398 request.RawContent(ctx.AsTarReader(c)), 399 request.ContentType("application/x-tar")) 400 assert.NilError(c, err) 401 assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode)) 402 403 out, err := request.ReadBody(body) 404 assert.NilError(c, err) 405 assert.Check(c, is.Contains(string(out), "Successfully built")) 406 } 407 408 func (s *DockerSuite) TestBuildChownOnCopy(c *check.C) { 409 // new feature added in 1.31 - https://github.com/moby/moby/pull/34263 410 testRequires(c, DaemonIsLinux, MinimumAPIVersion("1.31")) 411 dockerfile := `FROM busybox 412 RUN echo 'test1:x:1001:1001::/bin:/bin/false' >> /etc/passwd 413 RUN echo 'test1:x:1001:' >> /etc/group 414 RUN echo 'test2:x:1002:' >> /etc/group 415 COPY --chown=test1:1002 . /new_dir 416 RUN ls -l / 417 RUN [ $(ls -l / | grep new_dir | awk '{print $3":"$4}') = 'test1:test2' ] 418 RUN [ $(ls -nl / | grep new_dir | awk '{print $3":"$4}') = '1001:1002' ] 419 ` 420 ctx := fakecontext.New(c, "", 421 fakecontext.WithDockerfile(dockerfile), 422 fakecontext.WithFile("test_file1", "some test content"), 423 ) 424 defer ctx.Close() 425 426 res, body, err := request.Post( 427 "/build", 428 request.RawContent(ctx.AsTarReader(c)), 429 request.ContentType("application/x-tar")) 430 c.Assert(err, checker.IsNil) 431 c.Assert(res.StatusCode, checker.Equals, http.StatusOK) 432 433 out, err := request.ReadBody(body) 434 assert.NilError(c, err) 435 assert.Check(c, is.Contains(string(out), "Successfully built")) 436 } 437 438 func (s *DockerSuite) TestBuildCopyCacheOnFileChange(c *check.C) { 439 440 dockerfile := `FROM busybox 441 COPY file /file` 442 443 ctx1 := fakecontext.New(c, "", 444 fakecontext.WithDockerfile(dockerfile), 445 fakecontext.WithFile("file", "foo")) 446 ctx2 := fakecontext.New(c, "", 447 fakecontext.WithDockerfile(dockerfile), 448 fakecontext.WithFile("file", "bar")) 449 450 var build = func(ctx *fakecontext.Fake) string { 451 res, body, err := request.Post("/build", 452 request.RawContent(ctx.AsTarReader(c)), 453 request.ContentType("application/x-tar")) 454 455 assert.NilError(c, err) 456 assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode)) 457 458 out, err := request.ReadBody(body) 459 assert.NilError(c, err) 460 assert.Assert(c, is.Contains(string(out), "Successfully built")) 461 462 ids := getImageIDsFromBuild(c, out) 463 assert.Assert(c, is.Len(ids, 1)) 464 return ids[len(ids)-1] 465 } 466 467 id1 := build(ctx1) 468 id2 := build(ctx1) 469 id3 := build(ctx2) 470 471 if id1 != id2 { 472 c.Fatal("didn't use the cache") 473 } 474 if id1 == id3 { 475 c.Fatal("COPY With different source file should not share same cache") 476 } 477 } 478 479 func (s *DockerSuite) TestBuildAddCacheOnFileChange(c *check.C) { 480 481 dockerfile := `FROM busybox 482 ADD file /file` 483 484 ctx1 := fakecontext.New(c, "", 485 fakecontext.WithDockerfile(dockerfile), 486 fakecontext.WithFile("file", "foo")) 487 ctx2 := fakecontext.New(c, "", 488 fakecontext.WithDockerfile(dockerfile), 489 fakecontext.WithFile("file", "bar")) 490 491 var build = func(ctx *fakecontext.Fake) string { 492 res, body, err := request.Post("/build", 493 request.RawContent(ctx.AsTarReader(c)), 494 request.ContentType("application/x-tar")) 495 496 assert.NilError(c, err) 497 assert.Check(c, is.DeepEqual(http.StatusOK, res.StatusCode)) 498 499 out, err := request.ReadBody(body) 500 assert.NilError(c, err) 501 assert.Assert(c, is.Contains(string(out), "Successfully built")) 502 503 ids := getImageIDsFromBuild(c, out) 504 assert.Assert(c, is.Len(ids, 1)) 505 return ids[len(ids)-1] 506 } 507 508 id1 := build(ctx1) 509 id2 := build(ctx1) 510 id3 := build(ctx2) 511 512 if id1 != id2 { 513 c.Fatal("didn't use the cache") 514 } 515 if id1 == id3 { 516 c.Fatal("COPY With different source file should not share same cache") 517 } 518 } 519 520 func (s *DockerSuite) TestBuildScratchCopy(c *check.C) { 521 testRequires(c, DaemonIsLinux) 522 dockerfile := `FROM scratch 523 ADD Dockerfile / 524 ENV foo bar` 525 ctx := fakecontext.New(c, "", 526 fakecontext.WithDockerfile(dockerfile), 527 ) 528 defer ctx.Close() 529 530 res, body, err := request.Post( 531 "/build", 532 request.RawContent(ctx.AsTarReader(c)), 533 request.ContentType("application/x-tar")) 534 c.Assert(err, checker.IsNil) 535 c.Assert(res.StatusCode, checker.Equals, http.StatusOK) 536 537 out, err := request.ReadBody(body) 538 assert.NilError(c, err) 539 assert.Check(c, is.Contains(string(out), "Successfully built")) 540 } 541 542 type buildLine struct { 543 Stream string 544 Aux struct { 545 ID string 546 } 547 } 548 549 func getImageIDsFromBuild(c *check.C, output []byte) []string { 550 var ids []string 551 for _, line := range bytes.Split(output, []byte("\n")) { 552 if len(line) == 0 { 553 continue 554 } 555 entry := buildLine{} 556 assert.NilError(c, json.Unmarshal(line, &entry)) 557 if entry.Aux.ID != "" { 558 ids = append(ids, entry.Aux.ID) 559 } 560 } 561 return ids 562 }