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