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