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