github.com/jonsyu1/godel@v0.0.0-20171017211503-64567a0cf169/apps/distgo/cmd/docker/build_test.go (about) 1 // Copyright 2016 Palantir Technologies, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package docker_test 16 17 import ( 18 "context" 19 "encoding/base64" 20 "fmt" 21 "io/ioutil" 22 "math/rand" 23 "os" 24 "path" 25 "testing" 26 "time" 27 28 "github.com/docker/docker/api/types" 29 "github.com/docker/docker/api/types/filters" 30 dockercli "github.com/docker/docker/client" 31 "github.com/nmiyake/pkg/dirs" 32 "github.com/stretchr/testify/require" 33 34 "github.com/palantir/godel/apps/distgo/cmd/build" 35 "github.com/palantir/godel/apps/distgo/cmd/dist" 36 "github.com/palantir/godel/apps/distgo/cmd/docker" 37 "github.com/palantir/godel/apps/distgo/params" 38 "github.com/palantir/godel/apps/distgo/pkg/git" 39 "github.com/palantir/godel/apps/distgo/pkg/git/gittest" 40 "github.com/palantir/godel/apps/distgo/pkg/osarch" 41 ) 42 43 const ( 44 testMain = `package main 45 46 import "fmt" 47 48 var testVersionVar = "defaultVersion" 49 50 func main() { 51 fmt.Println(testVersionVar) 52 } 53 ` 54 dockerfile = `FROM alpine:3.5 55 ` 56 configFile = `test_key: 'test_value' 57 test_key_2: test_value_2 58 ` 59 slsDepDockerFile = `FROM alpine:3.5 60 61 COPY foo-sls.tgz . 62 COPY foo-bin.tgz . 63 COPY bar-sls.tgz . 64 ` 65 dockerRepoPrefix = "test-docker-dist" 66 ) 67 68 func TestDockerDist(t *testing.T) { 69 tmp, cleanup, err := dirs.TempDir("", "") 70 defer cleanup() 71 require.NoError(t, err) 72 73 for i, currCase := range []struct { 74 name string 75 spec func(projectDir string, randomPad string) []params.ProductBuildSpecWithDeps 76 setupProject func(projectDir, pad string) error 77 cleanup func(cli *dockercli.Client, projectDir, pad string) 78 preDistAction func(projectDir string, buildSpec []params.ProductBuildSpecWithDeps) 79 validate func(caseNum int, name string, pad string, cli *dockercli.Client) 80 }{ 81 { 82 name: "docker dist with dependent images", 83 setupProject: func(projectDir, pad string) error { 84 gittest.InitGitDir(t, projectDir) 85 // initialize foo 86 fooDir := path.Join(projectDir, "foo") 87 if err := os.Mkdir(fooDir, 0777); err != nil { 88 return err 89 } 90 if err := ioutil.WriteFile(path.Join(fooDir, "main.go"), []byte(testMain), 0644); err != nil { 91 return err 92 } 93 fooDockerDir := path.Join(fooDir, "docker") 94 if err = os.Mkdir(fooDockerDir, 0777); err != nil { 95 return err 96 } 97 fooDockerFile := fmt.Sprintf("FROM %v:0.1.0\n", fullRepoName("bar", pad)) 98 if err = ioutil.WriteFile(path.Join(fooDockerDir, "Dockerfile"), []byte(fooDockerFile), 0777); err != nil { 99 return err 100 } 101 102 // initialize bar 103 barDir := path.Join(projectDir, "bar") 104 if err := os.Mkdir(barDir, 0777); err != nil { 105 return err 106 } 107 if err = ioutil.WriteFile(path.Join(barDir, "main.go"), []byte(testMain), 0644); err != nil { 108 return err 109 } 110 barDockerDir := path.Join(barDir, "docker") 111 if err = os.Mkdir(barDockerDir, 0777); err != nil { 112 return err 113 } 114 if err = ioutil.WriteFile(path.Join(barDockerDir, "Dockerfile"), []byte(dockerfile), 0777); err != nil { 115 return err 116 } 117 118 // commit 119 gittest.CommitAllFiles(t, projectDir, "Commit") 120 return nil 121 }, 122 spec: func(projectDir string, pad string) []params.ProductBuildSpecWithDeps { 123 allSpec := make(map[string]params.ProductBuildSpec) 124 barSpec := params.NewProductBuildSpec( 125 projectDir, 126 "bar", 127 git.ProjectInfo{ 128 Version: "0.1.0", 129 }, 130 params.Product{ 131 Build: params.Build{ 132 MainPkg: "./bar", 133 OSArchs: []osarch.OSArch{ 134 { 135 OS: "linux", 136 Arch: "amd64", 137 }, 138 }, 139 }, 140 DockerImages: []params.DockerImage{ 141 { 142 Repository: fullRepoName("bar", pad), 143 Tag: "0.1.0", 144 ContextDir: "bar/docker", 145 Info: ¶ms.DefaultDockerImageInfo{}, 146 }, 147 }, 148 }, params.Project{ 149 GroupID: "com.test.group", 150 }, 151 ) 152 allSpec["bar"] = barSpec 153 barSpecWithDeps, err := params.NewProductBuildSpecWithDeps(barSpec, allSpec) 154 require.NoError(t, err) 155 fooSpec := params.NewProductBuildSpec( 156 projectDir, 157 "foo", 158 git.ProjectInfo{ 159 Version: "0.1.0", 160 }, 161 params.Product{ 162 Build: params.Build{ 163 MainPkg: "./foo", 164 OSArchs: []osarch.OSArch{ 165 { 166 OS: "linux", 167 Arch: "amd64", 168 }, 169 }, 170 }, 171 DockerImages: []params.DockerImage{ 172 { 173 Repository: fullRepoName("foo", pad), 174 Tag: "0.1.0", 175 ContextDir: "foo/docker", 176 Deps: []params.DockerDep{ 177 { 178 Product: "bar", 179 Type: params.DockerDepDocker, 180 TargetFile: "", 181 }, 182 }, 183 Info: ¶ms.DefaultDockerImageInfo{}, 184 }, 185 }, 186 }, 187 params.Project{ 188 GroupID: "com.test.group", 189 }, 190 ) 191 allSpec["foo"] = fooSpec 192 fooSpecWithDeps, err := params.NewProductBuildSpecWithDeps(fooSpec, allSpec) 193 require.NoError(t, err) 194 195 return []params.ProductBuildSpecWithDeps{fooSpecWithDeps, barSpecWithDeps} 196 }, 197 cleanup: func(cli *dockercli.Client, projectDir, pad string) { 198 images := []string{fmt.Sprintf("%v:0.1.0", fullRepoName("foo", pad)), 199 fmt.Sprintf("%v:0.1.0", fullRepoName("bar", pad))} 200 err := removeImages(cli, images) 201 if err != nil { 202 t.Logf("Failed to remove images: %v", err) 203 } 204 }, 205 preDistAction: func(projectDir string, buildSpec []params.ProductBuildSpecWithDeps) { 206 gittest.CreateGitTag(t, projectDir, "0.1.0") 207 }, 208 validate: func(caseNum int, name string, pad string, cli *dockercli.Client) { 209 filter := filters.NewArgs() 210 filter.Add("reference", fmt.Sprintf("%v:0.1.0", fullRepoName("foo", pad))) 211 images, err := cli.ImageList(context.Background(), types.ImageListOptions{Filters: filter}) 212 require.NoError(t, err, "Case %d: %s", caseNum, name) 213 require.True(t, len(images) > 0, "Case %d: %s", caseNum, name) 214 filter = filters.NewArgs() 215 filter.Add("reference", fmt.Sprintf("%v:0.1.0", fullRepoName("bar", pad))) 216 images, err = cli.ImageList(context.Background(), types.ImageListOptions{Filters: filter}) 217 require.NoError(t, err, "Case %d: %s", caseNum, name) 218 require.True(t, len(images) > 0, "Case %d: %s", caseNum, name) 219 }, 220 }, 221 { 222 name: "docker dist with build script args", 223 setupProject: func(projectDir, pad string) error { 224 gittest.InitGitDir(t, projectDir) 225 // initialize foo 226 fooDir := path.Join(projectDir, "foo") 227 if err := os.Mkdir(fooDir, 0777); err != nil { 228 return err 229 } 230 if err := ioutil.WriteFile(path.Join(fooDir, "main.go"), []byte(testMain), 0644); err != nil { 231 return err 232 } 233 fooDockerDir := path.Join(fooDir, "docker") 234 if err = os.Mkdir(fooDockerDir, 0777); err != nil { 235 return err 236 } 237 if err = ioutil.WriteFile(path.Join(fooDockerDir, "Dockerfile"), []byte(dockerfile), 0777); err != nil { 238 return err 239 } 240 241 // commit 242 gittest.CommitAllFiles(t, projectDir, "Commit") 243 return nil 244 }, 245 spec: func(projectDir string, pad string) []params.ProductBuildSpecWithDeps { 246 allSpec := make(map[string]params.ProductBuildSpec) 247 fooSpec := params.NewProductBuildSpec( 248 projectDir, 249 "foo", 250 git.ProjectInfo{ 251 Version: "0.1.0", 252 }, 253 params.Product{ 254 Build: params.Build{ 255 MainPkg: "./foo", 256 OSArchs: []osarch.OSArch{ 257 { 258 OS: "linux", 259 Arch: "amd64", 260 }, 261 }, 262 }, 263 DockerImages: []params.DockerImage{ 264 { 265 Repository: fullRepoName("foo", pad), 266 Tag: "0.1.0", 267 ContextDir: "foo/docker", 268 BuildArgsScript: `echo "--label" 269 echo "test_label=test_value"`, 270 Info: ¶ms.DefaultDockerImageInfo{}, 271 }, 272 }, 273 }, 274 params.Project{ 275 GroupID: "com.test.group", 276 }, 277 ) 278 allSpec["foo"] = fooSpec 279 fooSpecWithDeps, err := params.NewProductBuildSpecWithDeps(fooSpec, allSpec) 280 require.NoError(t, err) 281 282 return []params.ProductBuildSpecWithDeps{fooSpecWithDeps} 283 }, 284 cleanup: func(cli *dockercli.Client, projectDir, pad string) { 285 images := []string{fmt.Sprintf("%v:0.1.0", fullRepoName("foo", pad))} 286 err := removeImages(cli, images) 287 if err != nil { 288 t.Logf("Failed to remove images: %v", err) 289 } 290 }, 291 preDistAction: func(projectDir string, buildSpec []params.ProductBuildSpecWithDeps) { 292 gittest.CreateGitTag(t, projectDir, "0.1.0") 293 }, 294 validate: func(caseNum int, name string, pad string, cli *dockercli.Client) { 295 filter := filters.NewArgs() 296 filter.Add("reference", fmt.Sprintf("%v:0.1.0", fullRepoName("foo", pad))) 297 images, err := cli.ImageList(context.Background(), types.ImageListOptions{Filters: filter}) 298 require.NoError(t, err, "Case %d: %s", caseNum, name) 299 require.True(t, len(images) > 0, "Case %d: %s", caseNum, name) 300 image := images[0] 301 inspect, _, err := cli.ImageInspectWithRaw(context.Background(), image.ID) 302 require.NoError(t, err, "Case %d: %s", caseNum, name) 303 actualValue, ok := inspect.Config.Labels["test_label"] 304 require.True(t, ok, "Case %d: %s", caseNum, name) 305 require.Equal(t, "test_value", actualValue, "Case%d: %s", caseNum, name) 306 }, 307 }, 308 { 309 name: "sls docker dist", 310 setupProject: func(projectDir, pad string) error { 311 gittest.InitGitDir(t, projectDir) 312 // initialize foo 313 fooDir := path.Join(projectDir, "foo") 314 if err := os.Mkdir(fooDir, 0777); err != nil { 315 return err 316 } 317 if err := ioutil.WriteFile(path.Join(fooDir, "main.go"), []byte(testMain), 0644); err != nil { 318 return err 319 } 320 fooDockerDir := path.Join(fooDir, "docker") 321 if err = os.Mkdir(fooDockerDir, 0777); err != nil { 322 return err 323 } 324 if err = ioutil.WriteFile(path.Join(fooDockerDir, "Dockerfile"), []byte(dockerfile), 0777); err != nil { 325 return err 326 } 327 if err = ioutil.WriteFile(path.Join(fooDockerDir, docker.ConfigurationFileName), []byte(configFile), 0777); err != nil { 328 return err 329 } 330 331 // commit 332 gittest.CommitAllFiles(t, projectDir, "Commit") 333 return nil 334 }, 335 spec: func(projectDir string, pad string) []params.ProductBuildSpecWithDeps { 336 allSpec := make(map[string]params.ProductBuildSpec) 337 fooSpec := params.NewProductBuildSpec( 338 projectDir, 339 "foo", 340 git.ProjectInfo{ 341 Version: "0.1.0", 342 }, 343 params.Product{ 344 Build: params.Build{ 345 MainPkg: "./foo", 346 OSArchs: []osarch.OSArch{ 347 { 348 OS: "linux", 349 Arch: "amd64", 350 }, 351 }, 352 }, 353 DockerImages: []params.DockerImage{ 354 { 355 Repository: fullRepoName("foo", pad), 356 Tag: "0.1.0", 357 ContextDir: "foo/docker", 358 Deps: []params.DockerDep{ 359 { 360 Product: "bar", 361 Type: params.DockerDepDocker, 362 TargetFile: "", 363 }, 364 }, 365 Info: ¶ms.SLSDockerImageInfo{ 366 ProuductType: "test_type", 367 GroupID: "com.palantir.godel", 368 Extensions: map[string]interface{}{ 369 "test_key": "test_value", 370 }, 371 }, 372 }, 373 }, 374 }, 375 params.Project{ 376 GroupID: "com.test.group", 377 }, 378 ) 379 allSpec["foo"] = fooSpec 380 fooSpecWithDeps, err := params.NewProductBuildSpecWithDeps(fooSpec, allSpec) 381 require.NoError(t, err) 382 383 return []params.ProductBuildSpecWithDeps{fooSpecWithDeps} 384 }, 385 cleanup: func(cli *dockercli.Client, projectDir, pad string) { 386 images := []string{fmt.Sprintf("%v:0.1.0", fullRepoName("foo", pad))} 387 err := removeImages(cli, images) 388 if err != nil { 389 t.Logf("Failed to remove images: %v", err) 390 } 391 }, 392 preDistAction: func(projectDir string, buildSpec []params.ProductBuildSpecWithDeps) { 393 gittest.CreateGitTag(t, projectDir, "0.1.0") 394 }, 395 validate: func(caseNum int, name string, pad string, cli *dockercli.Client) { 396 filter := filters.NewArgs() 397 filter.Add("reference", fmt.Sprintf("%v:0.1.0", fullRepoName("foo", pad))) 398 images, err := cli.ImageList(context.Background(), types.ImageListOptions{Filters: filter}) 399 require.NoError(t, err, "Case %d: %s", caseNum, name) 400 require.True(t, len(images) > 0, "Case %d: %s", caseNum, name) 401 image := images[0] 402 inspect, _, err := cli.ImageInspectWithRaw(context.Background(), image.ID) 403 require.NoError(t, err, "Case %d: %s", caseNum, name) 404 actualManifestEncoded, ok := inspect.Config.Labels[docker.ManifestLabel] 405 require.True(t, ok, "Case %d: %s", caseNum, name) 406 actualManifest, err := base64.StdEncoding.DecodeString(actualManifestEncoded) 407 require.NoError(t, err, "Case %d: %s", caseNum, name) 408 expectedManifest := "manifest-version: \"1.0\"\n" + 409 "product-group: com.palantir.godel\n" + 410 "product-name: foo\n" + 411 "product-version: 0.1.0\n" + 412 "product-type: test_type\n" + 413 "extensions:\n test_key: test_value\n" 414 require.Equal(t, expectedManifest, string(actualManifest), "Case %d: %s", caseNum, name) 415 actualConfigEncoded, ok := inspect.Config.Labels[docker.ConfigurationLabel] 416 require.True(t, ok, "Case %d: %s", caseNum, name) 417 actualConfig, err := base64.StdEncoding.DecodeString(actualConfigEncoded) 418 require.NoError(t, err, "Case %d: %s", caseNum, name) 419 require.Equal(t, configFile, string(actualConfig), "Case %d: %s", caseNum, name) 420 }, 421 }, 422 { 423 name: "docker dist with dependent sls dist", 424 setupProject: func(projectDir, pad string) error { 425 gittest.InitGitDir(t, projectDir) 426 // initialize foo 427 fooDir := path.Join(projectDir, "foo") 428 if err := os.Mkdir(fooDir, 0777); err != nil { 429 return err 430 } 431 if err := ioutil.WriteFile(path.Join(fooDir, "main.go"), []byte(testMain), 0644); err != nil { 432 return err 433 } 434 435 // initialize bar 436 barDir := path.Join(projectDir, "bar") 437 if err := os.Mkdir(barDir, 0777); err != nil { 438 return err 439 } 440 if err = ioutil.WriteFile(path.Join(barDir, "main.go"), []byte(testMain), 0644); err != nil { 441 return err 442 } 443 barDockerDir := path.Join(barDir, "docker") 444 if err = os.Mkdir(barDockerDir, 0777); err != nil { 445 return err 446 } 447 if err = ioutil.WriteFile(path.Join(barDockerDir, "Dockerfile"), []byte(slsDepDockerFile), 0777); err != nil { 448 return err 449 } 450 451 // commit 452 gittest.CommitAllFiles(t, projectDir, "Commit") 453 return nil 454 }, 455 spec: func(projectDir string, pad string) []params.ProductBuildSpecWithDeps { 456 allSpec := make(map[string]params.ProductBuildSpec) 457 fooSpec := params.NewProductBuildSpec( 458 projectDir, 459 "foo", 460 git.ProjectInfo{ 461 Version: "0.1.0", 462 }, 463 params.Product{ 464 Build: params.Build{ 465 MainPkg: "./foo", 466 OSArchs: []osarch.OSArch{ 467 { 468 OS: "linux", 469 Arch: "amd64", 470 }, 471 }, 472 }, 473 Dist: []params.Dist{ 474 { 475 Info: ¶ms.SLSDistInfo{}, 476 }, 477 { 478 Info: ¶ms.BinDistInfo{}, 479 }, 480 }, 481 }, 482 params.Project{ 483 GroupID: "com.test.group", 484 }, 485 ) 486 allSpec["foo"] = fooSpec 487 fooSpecWithDeps, err := params.NewProductBuildSpecWithDeps(fooSpec, allSpec) 488 require.NoError(t, err) 489 barSpec := params.NewProductBuildSpec( 490 projectDir, 491 "bar", 492 git.ProjectInfo{ 493 Version: "0.1.0", 494 }, 495 params.Product{ 496 Build: params.Build{ 497 MainPkg: "./bar", 498 OSArchs: []osarch.OSArch{ 499 { 500 OS: "linux", 501 Arch: "amd64", 502 }, 503 }, 504 }, 505 DockerImages: []params.DockerImage{ 506 { 507 Repository: fullRepoName("bar", pad), 508 Tag: "0.1.0", 509 ContextDir: "bar/docker", 510 Info: ¶ms.DefaultDockerImageInfo{}, 511 Deps: []params.DockerDep{ 512 { 513 Product: "bar", 514 Type: params.DockerDepSLS, 515 TargetFile: "bar-sls.tgz", 516 }, 517 { 518 Product: "foo", 519 Type: params.DockerDepBin, 520 TargetFile: "foo-bin.tgz", 521 }, 522 { 523 Product: "foo", 524 Type: params.DockerDepSLS, 525 TargetFile: "foo-sls.tgz", 526 }, 527 }, 528 }, 529 }, 530 Dist: []params.Dist{ 531 { 532 Info: ¶ms.SLSDistInfo{}, 533 }, 534 }, 535 }, params.Project{ 536 GroupID: "com.test.group", 537 }, 538 ) 539 allSpec["bar"] = barSpec 540 barSpecWithDeps, err := params.NewProductBuildSpecWithDeps(barSpec, allSpec) 541 require.NoError(t, err) 542 return []params.ProductBuildSpecWithDeps{fooSpecWithDeps, barSpecWithDeps} 543 }, 544 cleanup: func(cli *dockercli.Client, projectDir, pad string) { 545 images := []string{fmt.Sprintf("%v:0.1.0", fullRepoName("bar", pad))} 546 err := removeImages(cli, images) 547 if err != nil { 548 t.Logf("Failed to remove images: %v", err) 549 } 550 }, 551 preDistAction: func(projectDir string, buildSpec []params.ProductBuildSpecWithDeps) { 552 gittest.CreateGitTag(t, projectDir, "0.1.0") 553 }, 554 validate: func(caseNum int, name string, pad string, cli *dockercli.Client) { 555 filter := filters.NewArgs() 556 filter.Add("reference", fmt.Sprintf("%v:0.1.0", fullRepoName("bar", pad))) 557 images, err := cli.ImageList(context.Background(), types.ImageListOptions{Filters: filter}) 558 require.NoError(t, err, "Case %d: %s", caseNum, name) 559 require.True(t, len(images) > 0, "Case %d: %s", caseNum, name) 560 }, 561 }, 562 } { 563 cli, err := dockercli.NewEnvClient() 564 require.NoError(t, err) 565 566 currTmpDir, err := ioutil.TempDir(tmp, "") 567 require.NoError(t, err, "Case %d: %s", i, currCase.name) 568 pad := randomPad(8) 569 570 err = currCase.setupProject(currTmpDir, pad) 571 require.NoError(t, err, "Case %d: %s", i, currCase.name) 572 spec := currCase.spec(currTmpDir, pad) 573 574 if currCase.preDistAction != nil { 575 currCase.preDistAction(currTmpDir, spec) 576 } 577 578 if currCase.cleanup != nil { 579 defer currCase.cleanup(cli, currTmpDir, pad) 580 } 581 582 for _, currSpecWithDeps := range spec { 583 err = build.Run(build.RequiresBuild(currSpecWithDeps, nil).Specs(), nil, build.Context{}, ioutil.Discard) 584 require.NoError(t, err, "Case %d: %s", i, currCase.name) 585 err = dist.Run(currSpecWithDeps, ioutil.Discard) 586 require.NoError(t, err, "Case %d: %s", i, currCase.name) 587 } 588 589 orderedSpecs, err := docker.OrderBuildSpecs(spec) 590 require.NoError(t, err, "Case %d: %s", i, currCase.name) 591 err = docker.RunBuild(orderedSpecs, false, os.Stdout) 592 require.NoError(t, err, "Case %d: %s", i, currCase.name) 593 594 if currCase.validate != nil { 595 currCase.validate(i, currCase.name, pad, cli) 596 } 597 } 598 } 599 600 func fullRepoName(product string, pad string) string { 601 return fmt.Sprintf("%v-%v-%v", dockerRepoPrefix, product, pad) 602 } 603 604 func randomPad(n int) string { 605 var letters = []rune("abcdefghijklmnopqrstuvwxyz1234567890") 606 b := make([]rune, n) 607 rand.Seed(time.Now().UTC().UnixNano()) 608 for i := range b { 609 b[i] = letters[rand.Intn(len(letters))] 610 } 611 return string(b) 612 } 613 614 func removeImages(cli *dockercli.Client, images []string) error { 615 for _, image := range images { 616 _, err := cli.ImageRemove(context.Background(), image, types.ImageRemoveOptions{}) 617 if err != nil { 618 return err 619 } 620 } 621 return nil 622 }