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