github.com/docker/buildx@v0.14.1-0.20240514123050-afcb609966dc/bake/bake_test.go (about) 1 package bake 2 3 import ( 4 "context" 5 "os" 6 "path/filepath" 7 "sort" 8 "strings" 9 "testing" 10 11 "github.com/stretchr/testify/assert" 12 "github.com/stretchr/testify/require" 13 ) 14 15 func TestReadTargets(t *testing.T) { 16 fp := File{ 17 Name: "config.hcl", 18 Data: []byte(` 19 target "webDEP" { 20 args = { 21 VAR_INHERITED = "webDEP" 22 VAR_BOTH = "webDEP" 23 } 24 no-cache = true 25 shm-size = "128m" 26 ulimits = ["nofile=1024:1024"] 27 } 28 29 target "webapp" { 30 dockerfile = "Dockerfile.webapp" 31 args = { 32 VAR_BOTH = "webapp" 33 } 34 inherits = ["webDEP"] 35 }`), 36 } 37 38 ctx := context.TODO() 39 40 t.Run("NoOverrides", func(t *testing.T) { 41 t.Parallel() 42 m, g, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, nil, nil) 43 require.NoError(t, err) 44 require.Equal(t, 1, len(m)) 45 46 require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile) 47 require.Equal(t, ".", *m["webapp"].Context) 48 require.Equal(t, ptrstr("webDEP"), m["webapp"].Args["VAR_INHERITED"]) 49 require.Equal(t, true, *m["webapp"].NoCache) 50 require.Equal(t, "128m", *m["webapp"].ShmSize) 51 require.Equal(t, []string{"nofile=1024:1024"}, m["webapp"].Ulimits) 52 require.Nil(t, m["webapp"].Pull) 53 54 require.Equal(t, 1, len(g)) 55 require.Equal(t, []string{"webapp"}, g["default"].Targets) 56 }) 57 58 t.Run("InvalidTargetOverrides", func(t *testing.T) { 59 t.Parallel() 60 _, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"nosuchtarget.context=foo"}, nil) 61 require.NotNil(t, err) 62 require.Equal(t, err.Error(), "could not find any target matching 'nosuchtarget'") 63 }) 64 65 t.Run("ArgsOverrides", func(t *testing.T) { 66 t.Run("leaf", func(t *testing.T) { 67 t.Setenv("VAR_FROMENV"+t.Name(), "fromEnv") 68 69 m, g, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{ 70 "webapp.args.VAR_UNSET", 71 "webapp.args.VAR_EMPTY=", 72 "webapp.args.VAR_SET=bananas", 73 "webapp.args.VAR_FROMENV" + t.Name(), 74 "webapp.args.VAR_INHERITED=override", 75 // not overriding VAR_BOTH on purpose 76 }, nil) 77 require.NoError(t, err) 78 79 require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile) 80 require.Equal(t, ".", *m["webapp"].Context) 81 82 _, isSet := m["webapp"].Args["VAR_UNSET"] 83 require.False(t, isSet, m["webapp"].Args["VAR_UNSET"]) 84 85 _, isSet = m["webapp"].Args["VAR_EMPTY"] 86 require.True(t, isSet, m["webapp"].Args["VAR_EMPTY"]) 87 88 require.Equal(t, ptrstr("bananas"), m["webapp"].Args["VAR_SET"]) 89 90 require.Equal(t, ptrstr("fromEnv"), m["webapp"].Args["VAR_FROMENV"+t.Name()]) 91 92 require.Equal(t, ptrstr("webapp"), m["webapp"].Args["VAR_BOTH"]) 93 require.Equal(t, ptrstr("override"), m["webapp"].Args["VAR_INHERITED"]) 94 95 require.Equal(t, 1, len(g)) 96 require.Equal(t, []string{"webapp"}, g["default"].Targets) 97 }) 98 99 // building leaf but overriding parent fields 100 t.Run("parent", func(t *testing.T) { 101 t.Parallel() 102 m, g, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{ 103 "webDEP.args.VAR_INHERITED=override", 104 "webDEP.args.VAR_BOTH=override", 105 }, nil) 106 107 require.NoError(t, err) 108 require.Equal(t, ptrstr("override"), m["webapp"].Args["VAR_INHERITED"]) 109 require.Equal(t, ptrstr("webapp"), m["webapp"].Args["VAR_BOTH"]) 110 require.Equal(t, 1, len(g)) 111 require.Equal(t, []string{"webapp"}, g["default"].Targets) 112 }) 113 }) 114 115 t.Run("ContextOverride", func(t *testing.T) { 116 t.Parallel() 117 _, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.context"}, nil) 118 require.NotNil(t, err) 119 120 m, g, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.context=foo"}, nil) 121 require.NoError(t, err) 122 require.Equal(t, "foo", *m["webapp"].Context) 123 require.Equal(t, 1, len(g)) 124 require.Equal(t, []string{"webapp"}, g["default"].Targets) 125 }) 126 127 t.Run("NoCacheOverride", func(t *testing.T) { 128 t.Parallel() 129 m, g, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.no-cache=false"}, nil) 130 require.NoError(t, err) 131 require.Equal(t, false, *m["webapp"].NoCache) 132 require.Equal(t, 1, len(g)) 133 require.Equal(t, []string{"webapp"}, g["default"].Targets) 134 }) 135 136 t.Run("ShmSizeOverride", func(t *testing.T) { 137 m, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.shm-size=256m"}, nil) 138 require.NoError(t, err) 139 require.Equal(t, "256m", *m["webapp"].ShmSize) 140 }) 141 142 t.Run("PullOverride", func(t *testing.T) { 143 t.Parallel() 144 m, g, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.pull=false"}, nil) 145 require.NoError(t, err) 146 require.Equal(t, false, *m["webapp"].Pull) 147 require.Equal(t, 1, len(g)) 148 require.Equal(t, []string{"webapp"}, g["default"].Targets) 149 }) 150 151 t.Run("PatternOverride", func(t *testing.T) { 152 t.Parallel() 153 // same check for two cases 154 multiTargetCheck := func(t *testing.T, m map[string]*Target, g map[string]*Group, err error) { 155 require.NoError(t, err) 156 require.Equal(t, 2, len(m)) 157 require.Equal(t, "foo", *m["webapp"].Dockerfile) 158 require.Equal(t, ptrstr("webDEP"), m["webapp"].Args["VAR_INHERITED"]) 159 require.Equal(t, "foo", *m["webDEP"].Dockerfile) 160 require.Equal(t, ptrstr("webDEP"), m["webDEP"].Args["VAR_INHERITED"]) 161 require.Equal(t, 1, len(g)) 162 sort.Strings(g["default"].Targets) 163 require.Equal(t, []string{"webDEP", "webapp"}, g["default"].Targets) 164 } 165 166 cases := []struct { 167 name string 168 targets []string 169 overrides []string 170 check func(*testing.T, map[string]*Target, map[string]*Group, error) 171 }{ 172 { 173 name: "multi target single pattern", 174 targets: []string{"webapp", "webDEP"}, 175 overrides: []string{"web*.dockerfile=foo"}, 176 check: multiTargetCheck, 177 }, 178 { 179 name: "multi target multi pattern", 180 targets: []string{"webapp", "webDEP"}, 181 overrides: []string{"web*.dockerfile=foo", "*.args.VAR_BOTH=bar"}, 182 check: multiTargetCheck, 183 }, 184 { 185 name: "single target", 186 targets: []string{"webapp"}, 187 overrides: []string{"web*.dockerfile=foo"}, 188 check: func(t *testing.T, m map[string]*Target, g map[string]*Group, err error) { 189 require.NoError(t, err) 190 require.Equal(t, 1, len(m)) 191 require.Equal(t, "foo", *m["webapp"].Dockerfile) 192 require.Equal(t, ptrstr("webDEP"), m["webapp"].Args["VAR_INHERITED"]) 193 require.Equal(t, 1, len(g)) 194 require.Equal(t, []string{"webapp"}, g["default"].Targets) 195 }, 196 }, 197 { 198 name: "nomatch", 199 targets: []string{"webapp"}, 200 overrides: []string{"nomatch*.dockerfile=foo"}, 201 check: func(t *testing.T, m map[string]*Target, g map[string]*Group, err error) { 202 // NOTE: I am unsure whether failing to match should always error out 203 // instead of simply skipping that override. 204 // Let's enforce the error and we can relax it later if users complain. 205 require.NotNil(t, err) 206 require.Equal(t, err.Error(), "could not find any target matching 'nomatch*'") 207 }, 208 }, 209 } 210 for _, test := range cases { 211 t.Run(test.name, func(t *testing.T) { 212 m, g, err := ReadTargets(ctx, []File{fp}, test.targets, test.overrides, nil) 213 test.check(t, m, g, err) 214 }) 215 } 216 }) 217 } 218 219 func TestPushOverride(t *testing.T) { 220 t.Run("empty output", func(t *testing.T) { 221 fp := File{ 222 Name: "docker-bake.hcl", 223 Data: []byte( 224 `target "app" { 225 }`), 226 } 227 m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.push=true"}, nil) 228 require.NoError(t, err) 229 require.Equal(t, 1, len(m["app"].Outputs)) 230 require.Equal(t, "type=image,push=true", m["app"].Outputs[0]) 231 }) 232 233 t.Run("type image", func(t *testing.T) { 234 fp := File{ 235 Name: "docker-bake.hcl", 236 Data: []byte( 237 `target "app" { 238 output = ["type=image,compression=zstd"] 239 }`), 240 } 241 m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.push=true"}, nil) 242 require.NoError(t, err) 243 require.Equal(t, 1, len(m["app"].Outputs)) 244 require.Equal(t, "type=image,compression=zstd,push=true", m["app"].Outputs[0]) 245 }) 246 247 t.Run("type image push false", func(t *testing.T) { 248 fp := File{ 249 Name: "docker-bake.hcl", 250 Data: []byte( 251 `target "app" { 252 output = ["type=image,compression=zstd"] 253 }`), 254 } 255 m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.push=false"}, nil) 256 require.NoError(t, err) 257 require.Equal(t, 1, len(m["app"].Outputs)) 258 require.Equal(t, "type=image,compression=zstd,push=false", m["app"].Outputs[0]) 259 }) 260 261 t.Run("type registry", func(t *testing.T) { 262 fp := File{ 263 Name: "docker-bake.hcl", 264 Data: []byte( 265 `target "app" { 266 output = ["type=registry"] 267 }`), 268 } 269 m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.push=true"}, nil) 270 require.NoError(t, err) 271 require.Equal(t, 1, len(m["app"].Outputs)) 272 require.Equal(t, "type=registry", m["app"].Outputs[0]) 273 }) 274 275 t.Run("type registry push false", func(t *testing.T) { 276 fp := File{ 277 Name: "docker-bake.hcl", 278 Data: []byte( 279 `target "app" { 280 output = ["type=registry"] 281 }`), 282 } 283 m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.push=false"}, nil) 284 require.NoError(t, err) 285 require.Equal(t, 0, len(m["app"].Outputs)) 286 }) 287 288 t.Run("type local and empty target", func(t *testing.T) { 289 fp := File{ 290 Name: "docker-bake.hcl", 291 Data: []byte( 292 `target "foo" { 293 output = [ "type=local,dest=out" ] 294 } 295 target "bar" { 296 }`), 297 } 298 m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"foo", "bar"}, []string{"*.push=true"}, nil) 299 require.NoError(t, err) 300 require.Equal(t, 2, len(m)) 301 require.Equal(t, 1, len(m["foo"].Outputs)) 302 require.Equal(t, []string{"type=local,dest=out"}, m["foo"].Outputs) 303 require.Equal(t, 1, len(m["bar"].Outputs)) 304 require.Equal(t, []string{"type=image,push=true"}, m["bar"].Outputs) 305 }) 306 } 307 308 func TestLoadOverride(t *testing.T) { 309 t.Run("empty output", func(t *testing.T) { 310 fp := File{ 311 Name: "docker-bake.hcl", 312 Data: []byte( 313 `target "app" { 314 }`), 315 } 316 m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil) 317 require.NoError(t, err) 318 require.Equal(t, 1, len(m["app"].Outputs)) 319 require.Equal(t, "type=docker", m["app"].Outputs[0]) 320 }) 321 322 t.Run("type docker", func(t *testing.T) { 323 fp := File{ 324 Name: "docker-bake.hcl", 325 Data: []byte( 326 `target "app" { 327 output = ["type=docker"] 328 }`), 329 } 330 m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil) 331 require.NoError(t, err) 332 require.Equal(t, 1, len(m["app"].Outputs)) 333 require.Equal(t, []string{"type=docker"}, m["app"].Outputs) 334 }) 335 336 t.Run("type image", func(t *testing.T) { 337 fp := File{ 338 Name: "docker-bake.hcl", 339 Data: []byte( 340 `target "app" { 341 output = ["type=image"] 342 }`), 343 } 344 m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil) 345 require.NoError(t, err) 346 require.Equal(t, 2, len(m["app"].Outputs)) 347 require.Equal(t, []string{"type=image", "type=docker"}, m["app"].Outputs) 348 }) 349 350 t.Run("type image load false", func(t *testing.T) { 351 fp := File{ 352 Name: "docker-bake.hcl", 353 Data: []byte( 354 `target "app" { 355 output = ["type=image"] 356 }`), 357 } 358 m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=false"}, nil) 359 require.NoError(t, err) 360 require.Equal(t, 1, len(m["app"].Outputs)) 361 require.Equal(t, []string{"type=image"}, m["app"].Outputs) 362 }) 363 364 t.Run("type registry", func(t *testing.T) { 365 fp := File{ 366 Name: "docker-bake.hcl", 367 Data: []byte( 368 `target "app" { 369 output = ["type=registry"] 370 }`), 371 } 372 m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil) 373 require.NoError(t, err) 374 require.Equal(t, 2, len(m["app"].Outputs)) 375 require.Equal(t, []string{"type=registry", "type=docker"}, m["app"].Outputs) 376 }) 377 378 t.Run("type oci", func(t *testing.T) { 379 fp := File{ 380 Name: "docker-bake.hcl", 381 Data: []byte( 382 `target "app" { 383 output = ["type=oci,dest=out"] 384 }`), 385 } 386 m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil) 387 require.NoError(t, err) 388 require.Equal(t, 2, len(m["app"].Outputs)) 389 require.Equal(t, []string{"type=oci,dest=out", "type=docker"}, m["app"].Outputs) 390 }) 391 392 t.Run("type docker with dest", func(t *testing.T) { 393 fp := File{ 394 Name: "docker-bake.hcl", 395 Data: []byte( 396 `target "app" { 397 output = ["type=docker,dest=out"] 398 }`), 399 } 400 m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil) 401 require.NoError(t, err) 402 require.Equal(t, 2, len(m["app"].Outputs)) 403 require.Equal(t, []string{"type=docker,dest=out", "type=docker"}, m["app"].Outputs) 404 }) 405 406 t.Run("type local and empty target", func(t *testing.T) { 407 fp := File{ 408 Name: "docker-bake.hcl", 409 Data: []byte( 410 `target "foo" { 411 output = [ "type=local,dest=out" ] 412 } 413 target "bar" { 414 }`), 415 } 416 m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"foo", "bar"}, []string{"*.load=true"}, nil) 417 require.NoError(t, err) 418 require.Equal(t, 2, len(m)) 419 require.Equal(t, 1, len(m["foo"].Outputs)) 420 require.Equal(t, []string{"type=local,dest=out"}, m["foo"].Outputs) 421 require.Equal(t, 1, len(m["bar"].Outputs)) 422 require.Equal(t, []string{"type=docker"}, m["bar"].Outputs) 423 }) 424 } 425 426 func TestLoadAndPushOverride(t *testing.T) { 427 t.Run("type local and empty target", func(t *testing.T) { 428 fp := File{ 429 Name: "docker-bake.hcl", 430 Data: []byte( 431 `target "foo" { 432 output = [ "type=local,dest=out" ] 433 } 434 target "bar" { 435 }`), 436 } 437 m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"foo", "bar"}, []string{"*.load=true", "*.push=true"}, nil) 438 require.NoError(t, err) 439 require.Equal(t, 2, len(m)) 440 441 require.Equal(t, 1, len(m["foo"].Outputs)) 442 sort.Strings(m["foo"].Outputs) 443 require.Equal(t, []string{"type=local,dest=out"}, m["foo"].Outputs) 444 445 require.Equal(t, 2, len(m["bar"].Outputs)) 446 sort.Strings(m["bar"].Outputs) 447 require.Equal(t, []string{"type=docker", "type=image,push=true"}, m["bar"].Outputs) 448 }) 449 450 t.Run("type registry", func(t *testing.T) { 451 fp := File{ 452 Name: "docker-bake.hcl", 453 Data: []byte( 454 `target "foo" { 455 output = [ "type=registry" ] 456 }`), 457 } 458 m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"foo"}, []string{"*.load=true", "*.push=true"}, nil) 459 require.NoError(t, err) 460 require.Equal(t, 1, len(m)) 461 462 require.Equal(t, 2, len(m["foo"].Outputs)) 463 sort.Strings(m["foo"].Outputs) 464 require.Equal(t, []string{"type=docker", "type=registry"}, m["foo"].Outputs) 465 }) 466 } 467 468 func TestReadTargetsCompose(t *testing.T) { 469 t.Parallel() 470 471 fp := File{ 472 Name: "docker-compose.yml", 473 Data: []byte( 474 `version: "3" 475 services: 476 db: 477 build: . 478 command: ./entrypoint.sh 479 image: docker.io/tonistiigi/db 480 webapp: 481 build: 482 dockerfile: Dockerfile.webapp 483 args: 484 buildno: 1 485 `), 486 } 487 488 fp2 := File{ 489 Name: "docker-compose2.yml", 490 Data: []byte( 491 `version: "3" 492 services: 493 newservice: 494 build: . 495 webapp: 496 build: 497 args: 498 buildno2: 12 499 `), 500 } 501 502 fp3 := File{ 503 Name: "docker-compose3.yml", 504 Data: []byte( 505 `version: "3" 506 services: 507 webapp: 508 entrypoint: echo 1 509 `), 510 } 511 512 ctx := context.TODO() 513 514 m, g, err := ReadTargets(ctx, []File{fp, fp2, fp3}, []string{"default"}, nil, nil) 515 require.NoError(t, err) 516 517 require.Equal(t, 3, len(m)) 518 _, ok := m["newservice"] 519 520 require.True(t, ok) 521 require.Equal(t, "Dockerfile.webapp", *m["webapp"].Dockerfile) 522 require.Equal(t, ".", *m["webapp"].Context) 523 require.Equal(t, ptrstr("1"), m["webapp"].Args["buildno"]) 524 require.Equal(t, ptrstr("12"), m["webapp"].Args["buildno2"]) 525 526 require.Equal(t, 1, len(g)) 527 sort.Strings(g["default"].Targets) 528 require.Equal(t, []string{"db", "newservice", "webapp"}, g["default"].Targets) 529 } 530 531 func TestReadTargetsWithDotCompose(t *testing.T) { 532 t.Parallel() 533 534 fp := File{ 535 Name: "docker-compose.yml", 536 Data: []byte( 537 `version: "3" 538 services: 539 web.app: 540 build: 541 dockerfile: Dockerfile.webapp 542 args: 543 buildno: 1 544 `), 545 } 546 547 fp2 := File{ 548 Name: "docker-compose2.yml", 549 Data: []byte( 550 `version: "3" 551 services: 552 web_app: 553 build: 554 args: 555 buildno2: 12 556 `), 557 } 558 559 ctx := context.TODO() 560 561 m, _, err := ReadTargets(ctx, []File{fp}, []string{"web.app"}, nil, nil) 562 require.NoError(t, err) 563 require.Equal(t, 1, len(m)) 564 _, ok := m["web_app"] 565 require.True(t, ok) 566 require.Equal(t, "Dockerfile.webapp", *m["web_app"].Dockerfile) 567 require.Equal(t, ptrstr("1"), m["web_app"].Args["buildno"]) 568 569 m, _, err = ReadTargets(ctx, []File{fp2}, []string{"web_app"}, nil, nil) 570 require.NoError(t, err) 571 require.Equal(t, 1, len(m)) 572 _, ok = m["web_app"] 573 require.True(t, ok) 574 require.Equal(t, "Dockerfile", *m["web_app"].Dockerfile) 575 require.Equal(t, ptrstr("12"), m["web_app"].Args["buildno2"]) 576 577 m, g, err := ReadTargets(ctx, []File{fp, fp2}, []string{"default"}, nil, nil) 578 require.NoError(t, err) 579 require.Equal(t, 1, len(m)) 580 _, ok = m["web_app"] 581 require.True(t, ok) 582 require.Equal(t, "Dockerfile.webapp", *m["web_app"].Dockerfile) 583 require.Equal(t, ".", *m["web_app"].Context) 584 require.Equal(t, ptrstr("1"), m["web_app"].Args["buildno"]) 585 require.Equal(t, ptrstr("12"), m["web_app"].Args["buildno2"]) 586 587 require.Equal(t, 1, len(g)) 588 sort.Strings(g["default"].Targets) 589 require.Equal(t, []string{"web_app"}, g["default"].Targets) 590 } 591 592 func TestHCLContextCwdPrefix(t *testing.T) { 593 fp := File{ 594 Name: "docker-bake.hcl", 595 Data: []byte( 596 `target "app" { 597 context = "cwd://foo" 598 dockerfile = "test" 599 }`), 600 } 601 ctx := context.TODO() 602 m, g, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil) 603 require.NoError(t, err) 604 605 bo, err := TargetsToBuildOpt(m, &Input{}) 606 require.NoError(t, err) 607 608 require.Equal(t, 1, len(g)) 609 require.Equal(t, []string{"app"}, g["default"].Targets) 610 611 require.Equal(t, 1, len(m)) 612 require.Contains(t, m, "app") 613 assert.Equal(t, "test", *m["app"].Dockerfile) 614 assert.Equal(t, "foo", *m["app"].Context) 615 assert.Equal(t, "foo/test", bo["app"].Inputs.DockerfilePath) 616 assert.Equal(t, "foo", bo["app"].Inputs.ContextPath) 617 } 618 619 func TestHCLDockerfileCwdPrefix(t *testing.T) { 620 fp := File{ 621 Name: "docker-bake.hcl", 622 Data: []byte( 623 `target "app" { 624 context = "." 625 dockerfile = "cwd://Dockerfile.app" 626 }`), 627 } 628 ctx := context.TODO() 629 630 cwd, err := os.Getwd() 631 require.NoError(t, err) 632 633 m, g, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil) 634 require.NoError(t, err) 635 636 bo, err := TargetsToBuildOpt(m, &Input{}) 637 require.NoError(t, err) 638 639 require.Equal(t, 1, len(g)) 640 require.Equal(t, []string{"app"}, g["default"].Targets) 641 642 require.Equal(t, 1, len(m)) 643 require.Contains(t, m, "app") 644 assert.Equal(t, "cwd://Dockerfile.app", *m["app"].Dockerfile) 645 assert.Equal(t, ".", *m["app"].Context) 646 assert.Equal(t, filepath.Join(cwd, "Dockerfile.app"), bo["app"].Inputs.DockerfilePath) 647 assert.Equal(t, ".", bo["app"].Inputs.ContextPath) 648 } 649 650 func TestOverrideMerge(t *testing.T) { 651 fp := File{ 652 Name: "docker-bake.hcl", 653 Data: []byte( 654 `target "app" { 655 platforms = ["linux/amd64"] 656 output = ["foo"] 657 }`), 658 } 659 ctx := context.TODO() 660 m, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, []string{ 661 "app.platform=linux/arm", 662 "app.platform=linux/ppc64le", 663 "app.output=type=registry", 664 }, nil) 665 require.NoError(t, err) 666 667 require.Equal(t, 1, len(m)) 668 _, ok := m["app"] 669 require.True(t, ok) 670 671 _, err = TargetsToBuildOpt(m, &Input{}) 672 require.NoError(t, err) 673 674 require.Equal(t, []string{"linux/arm", "linux/ppc64le"}, m["app"].Platforms) 675 require.Equal(t, 1, len(m["app"].Outputs)) 676 require.Equal(t, "type=registry", m["app"].Outputs[0]) 677 } 678 679 func TestReadContexts(t *testing.T) { 680 fp := File{ 681 Name: "docker-bake.hcl", 682 Data: []byte(` 683 target "base" { 684 contexts = { 685 foo: "bar" 686 abc: "def" 687 } 688 } 689 target "app" { 690 inherits = ["base"] 691 contexts = { 692 foo: "baz" 693 } 694 } 695 `), 696 } 697 698 ctx := context.TODO() 699 m, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, []string{}, nil) 700 require.NoError(t, err) 701 702 require.Equal(t, 1, len(m)) 703 _, ok := m["app"] 704 require.True(t, ok) 705 706 bo, err := TargetsToBuildOpt(m, &Input{}) 707 require.NoError(t, err) 708 709 ctxs := bo["app"].Inputs.NamedContexts 710 require.Equal(t, 2, len(ctxs)) 711 712 require.Equal(t, "baz", ctxs["foo"].Path) 713 require.Equal(t, "def", ctxs["abc"].Path) 714 715 m, _, err = ReadTargets(ctx, []File{fp}, []string{"app"}, []string{"app.contexts.foo=bay", "base.contexts.ghi=jkl"}, nil) 716 require.NoError(t, err) 717 718 require.Equal(t, 1, len(m)) 719 _, ok = m["app"] 720 require.True(t, ok) 721 722 bo, err = TargetsToBuildOpt(m, &Input{}) 723 require.NoError(t, err) 724 725 ctxs = bo["app"].Inputs.NamedContexts 726 require.Equal(t, 3, len(ctxs)) 727 728 require.Equal(t, "bay", ctxs["foo"].Path) 729 require.Equal(t, "def", ctxs["abc"].Path) 730 require.Equal(t, "jkl", ctxs["ghi"].Path) 731 732 // test resetting base values 733 m, _, err = ReadTargets(ctx, []File{fp}, []string{"app"}, []string{"app.contexts.foo="}, nil) 734 require.NoError(t, err) 735 736 require.Equal(t, 1, len(m)) 737 _, ok = m["app"] 738 require.True(t, ok) 739 740 bo, err = TargetsToBuildOpt(m, &Input{}) 741 require.NoError(t, err) 742 743 ctxs = bo["app"].Inputs.NamedContexts 744 require.Equal(t, 1, len(ctxs)) 745 require.Equal(t, "def", ctxs["abc"].Path) 746 } 747 748 func TestReadContextFromTargetUnknown(t *testing.T) { 749 fp := File{ 750 Name: "docker-bake.hcl", 751 Data: []byte(` 752 target "base" { 753 contexts = { 754 foo: "bar" 755 abc: "def" 756 } 757 } 758 target "app" { 759 contexts = { 760 foo: "baz" 761 bar: "target:bar" 762 } 763 } 764 `), 765 } 766 767 ctx := context.TODO() 768 _, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, []string{}, nil) 769 require.Error(t, err) 770 require.Contains(t, err.Error(), "failed to find target bar") 771 } 772 773 func TestReadEmptyTargets(t *testing.T) { 774 t.Parallel() 775 776 fp := File{ 777 Name: "docker-bake.hcl", 778 Data: []byte(`target "app1" {}`), 779 } 780 781 fp2 := File{ 782 Name: "docker-compose.yml", 783 Data: []byte(` 784 services: 785 app2: 786 build: {} 787 `), 788 } 789 790 ctx := context.TODO() 791 792 m, _, err := ReadTargets(ctx, []File{fp, fp2}, []string{"app1", "app2"}, nil, nil) 793 require.NoError(t, err) 794 795 require.Equal(t, 2, len(m)) 796 _, ok := m["app1"] 797 require.True(t, ok) 798 _, ok = m["app2"] 799 require.True(t, ok) 800 801 require.Equal(t, "Dockerfile", *m["app1"].Dockerfile) 802 require.Equal(t, ".", *m["app1"].Context) 803 require.Equal(t, "Dockerfile", *m["app2"].Dockerfile) 804 require.Equal(t, ".", *m["app2"].Context) 805 } 806 807 func TestReadContextFromTargetChain(t *testing.T) { 808 ctx := context.TODO() 809 fp := File{ 810 Name: "docker-bake.hcl", 811 Data: []byte(` 812 target "base" { 813 } 814 target "mid" { 815 output = ["foo"] 816 contexts = { 817 parent: "target:base" 818 } 819 } 820 target "app" { 821 contexts = { 822 foo: "baz" 823 bar: "target:mid" 824 } 825 } 826 target "unused" {} 827 `), 828 } 829 830 m, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, []string{}, nil) 831 require.NoError(t, err) 832 833 require.Equal(t, 3, len(m)) 834 app, ok := m["app"] 835 require.True(t, ok) 836 837 require.Equal(t, 2, len(app.Contexts)) 838 839 mid, ok := m["mid"] 840 require.True(t, ok) 841 require.Equal(t, 0, len(mid.Outputs)) 842 require.Equal(t, 1, len(mid.Contexts)) 843 844 base, ok := m["base"] 845 require.True(t, ok) 846 require.Equal(t, 0, len(base.Contexts)) 847 } 848 849 func TestReadContextFromTargetInfiniteLoop(t *testing.T) { 850 ctx := context.TODO() 851 fp := File{ 852 Name: "docker-bake.hcl", 853 Data: []byte(` 854 target "mid" { 855 output = ["foo"] 856 contexts = { 857 parent: "target:app" 858 } 859 } 860 target "app" { 861 contexts = { 862 foo: "baz" 863 bar: "target:mid" 864 } 865 } 866 `), 867 } 868 _, _, err := ReadTargets(ctx, []File{fp}, []string{"app", "mid"}, []string{}, nil) 869 require.Error(t, err) 870 require.Contains(t, err.Error(), "infinite loop from") 871 } 872 873 func TestReadContextFromTargetMultiPlatform(t *testing.T) { 874 ctx := context.TODO() 875 fp := File{ 876 Name: "docker-bake.hcl", 877 Data: []byte(` 878 target "mid" { 879 output = ["foo"] 880 platforms = ["linux/amd64", "linux/arm64"] 881 } 882 target "app" { 883 contexts = { 884 bar: "target:mid" 885 } 886 platforms = ["linux/amd64", "linux/arm64"] 887 } 888 `), 889 } 890 _, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, []string{}, nil) 891 require.NoError(t, err) 892 } 893 894 func TestReadContextFromTargetInvalidPlatforms(t *testing.T) { 895 ctx := context.TODO() 896 fp := File{ 897 Name: "docker-bake.hcl", 898 Data: []byte(` 899 target "mid" { 900 output = ["foo"] 901 platforms = ["linux/amd64", "linux/riscv64"] 902 } 903 target "app" { 904 contexts = { 905 bar: "target:mid" 906 } 907 platforms = ["linux/amd64", "linux/arm64"] 908 } 909 `), 910 } 911 _, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, []string{}, nil) 912 require.Error(t, err) 913 require.Contains(t, err.Error(), "defined for different platforms") 914 } 915 916 func TestReadTargetsDefault(t *testing.T) { 917 t.Parallel() 918 ctx := context.TODO() 919 920 f := File{ 921 Name: "docker-bake.hcl", 922 Data: []byte(` 923 target "default" { 924 dockerfile = "test" 925 }`)} 926 927 m, g, err := ReadTargets(ctx, []File{f}, []string{"default"}, nil, nil) 928 require.NoError(t, err) 929 require.Equal(t, 0, len(g)) 930 require.Equal(t, 1, len(m)) 931 require.Equal(t, "test", *m["default"].Dockerfile) 932 } 933 934 func TestReadTargetsSpecified(t *testing.T) { 935 t.Parallel() 936 ctx := context.TODO() 937 938 f := File{ 939 Name: "docker-bake.hcl", 940 Data: []byte(` 941 target "image" { 942 dockerfile = "test" 943 }`)} 944 945 _, _, err := ReadTargets(ctx, []File{f}, []string{"default"}, nil, nil) 946 require.Error(t, err) 947 948 m, g, err := ReadTargets(ctx, []File{f}, []string{"image"}, nil, nil) 949 require.NoError(t, err) 950 require.Equal(t, 1, len(g)) 951 require.Equal(t, []string{"image"}, g["default"].Targets) 952 require.Equal(t, 1, len(m)) 953 require.Equal(t, "test", *m["image"].Dockerfile) 954 } 955 956 func TestReadTargetsGroup(t *testing.T) { 957 t.Parallel() 958 ctx := context.TODO() 959 960 f := File{ 961 Name: "docker-bake.hcl", 962 Data: []byte(` 963 group "foo" { 964 targets = ["image"] 965 } 966 target "image" { 967 dockerfile = "test" 968 }`)} 969 970 m, g, err := ReadTargets(ctx, []File{f}, []string{"foo"}, nil, nil) 971 require.NoError(t, err) 972 require.Equal(t, 2, len(g)) 973 require.Equal(t, []string{"foo"}, g["default"].Targets) 974 require.Equal(t, []string{"image"}, g["foo"].Targets) 975 require.Equal(t, 1, len(m)) 976 require.Equal(t, "test", *m["image"].Dockerfile) 977 } 978 979 func TestReadTargetsGroupAndTarget(t *testing.T) { 980 t.Parallel() 981 ctx := context.TODO() 982 983 f := File{ 984 Name: "docker-bake.hcl", 985 Data: []byte(` 986 group "foo" { 987 targets = ["image"] 988 } 989 target "foo" { 990 dockerfile = "bar" 991 } 992 target "image" { 993 dockerfile = "test" 994 }`)} 995 996 m, g, err := ReadTargets(ctx, []File{f}, []string{"foo"}, nil, nil) 997 require.NoError(t, err) 998 require.Equal(t, 2, len(g)) 999 require.Equal(t, []string{"foo"}, g["default"].Targets) 1000 require.Equal(t, []string{"image"}, g["foo"].Targets) 1001 require.Equal(t, 1, len(m)) 1002 require.Equal(t, "test", *m["image"].Dockerfile) 1003 1004 m, g, err = ReadTargets(ctx, []File{f}, []string{"foo", "foo"}, nil, nil) 1005 require.NoError(t, err) 1006 require.Equal(t, 2, len(g)) 1007 require.Equal(t, []string{"foo"}, g["default"].Targets) 1008 require.Equal(t, []string{"image"}, g["foo"].Targets) 1009 require.Equal(t, 1, len(m)) 1010 require.Equal(t, "test", *m["image"].Dockerfile) 1011 } 1012 1013 func TestReadTargetsMixed(t *testing.T) { 1014 t.Parallel() 1015 ctx := context.TODO() 1016 1017 fhcl := File{ 1018 Name: "docker-bake.hcl", 1019 Data: []byte(` 1020 group "default" { 1021 targets = ["image"] 1022 } 1023 target "nocache" { 1024 no-cache = true 1025 } 1026 group "release" { 1027 targets = ["image-release"] 1028 } 1029 target "image" { 1030 inherits = ["nocache"] 1031 output = ["type=docker"] 1032 } 1033 target "image-release" { 1034 inherits = ["image"] 1035 output = ["type=image,push=true"] 1036 tags = ["user/app:latest"] 1037 }`)} 1038 1039 fyml := File{ 1040 Name: "docker-compose.yml", 1041 Data: []byte(` 1042 services: 1043 addon: 1044 build: 1045 context: . 1046 dockerfile: ./Dockerfile 1047 args: 1048 CT_ECR: foo 1049 CT_TAG: bar 1050 image: ct-addon:bar 1051 environment: 1052 - NODE_ENV=test 1053 - AWS_ACCESS_KEY_ID=dummy 1054 - AWS_SECRET_ACCESS_KEY=dummy 1055 aws: 1056 build: 1057 dockerfile: ./aws.Dockerfile 1058 args: 1059 CT_ECR: foo 1060 CT_TAG: bar 1061 image: ct-fake-aws:bar`)} 1062 1063 fjson := File{ 1064 Name: "docker-bake.json", 1065 Data: []byte(`{ 1066 "group": { 1067 "default": { 1068 "targets": [ 1069 "image" 1070 ] 1071 } 1072 }, 1073 "target": { 1074 "image": { 1075 "context": ".", 1076 "dockerfile": "Dockerfile", 1077 "output": [ 1078 "type=docker" 1079 ] 1080 } 1081 } 1082 }`)} 1083 1084 m, g, err := ReadTargets(ctx, []File{fhcl}, []string{"default"}, nil, nil) 1085 require.NoError(t, err) 1086 require.Equal(t, 1, len(g)) 1087 require.Equal(t, []string{"image"}, g["default"].Targets) 1088 require.Equal(t, 1, len(m)) 1089 require.Equal(t, 1, len(m["image"].Outputs)) 1090 require.Equal(t, "type=docker", m["image"].Outputs[0]) 1091 1092 m, g, err = ReadTargets(ctx, []File{fhcl}, []string{"image-release"}, nil, nil) 1093 require.NoError(t, err) 1094 require.Equal(t, 1, len(g)) 1095 require.Equal(t, []string{"image-release"}, g["default"].Targets) 1096 require.Equal(t, 1, len(m)) 1097 require.Equal(t, 1, len(m["image-release"].Outputs)) 1098 require.Equal(t, "type=image,push=true", m["image-release"].Outputs[0]) 1099 1100 m, g, err = ReadTargets(ctx, []File{fhcl}, []string{"image", "image-release"}, nil, nil) 1101 require.NoError(t, err) 1102 require.Equal(t, 1, len(g)) 1103 require.Equal(t, []string{"image", "image-release"}, g["default"].Targets) 1104 require.Equal(t, 2, len(m)) 1105 require.Equal(t, ".", *m["image"].Context) 1106 require.Equal(t, 1, len(m["image-release"].Outputs)) 1107 require.Equal(t, "type=image,push=true", m["image-release"].Outputs[0]) 1108 1109 m, g, err = ReadTargets(ctx, []File{fyml, fhcl}, []string{"default"}, nil, nil) 1110 require.NoError(t, err) 1111 require.Equal(t, 1, len(g)) 1112 require.Equal(t, []string{"image"}, g["default"].Targets) 1113 require.Equal(t, 1, len(m)) 1114 require.Equal(t, ".", *m["image"].Context) 1115 1116 m, g, err = ReadTargets(ctx, []File{fjson}, []string{"default"}, nil, nil) 1117 require.NoError(t, err) 1118 require.Equal(t, 1, len(g)) 1119 require.Equal(t, []string{"image"}, g["default"].Targets) 1120 require.Equal(t, 1, len(m)) 1121 require.Equal(t, ".", *m["image"].Context) 1122 1123 m, g, err = ReadTargets(ctx, []File{fyml}, []string{"default"}, nil, nil) 1124 require.NoError(t, err) 1125 require.Equal(t, 1, len(g)) 1126 sort.Strings(g["default"].Targets) 1127 require.Equal(t, []string{"addon", "aws"}, g["default"].Targets) 1128 require.Equal(t, 2, len(m)) 1129 require.Equal(t, "./Dockerfile", *m["addon"].Dockerfile) 1130 require.Equal(t, "./aws.Dockerfile", *m["aws"].Dockerfile) 1131 1132 m, g, err = ReadTargets(ctx, []File{fyml, fhcl}, []string{"addon", "aws"}, nil, nil) 1133 require.NoError(t, err) 1134 require.Equal(t, 1, len(g)) 1135 sort.Strings(g["default"].Targets) 1136 require.Equal(t, []string{"addon", "aws"}, g["default"].Targets) 1137 require.Equal(t, 2, len(m)) 1138 require.Equal(t, "./Dockerfile", *m["addon"].Dockerfile) 1139 require.Equal(t, "./aws.Dockerfile", *m["aws"].Dockerfile) 1140 1141 m, g, err = ReadTargets(ctx, []File{fyml, fhcl}, []string{"addon", "aws", "image"}, nil, nil) 1142 require.NoError(t, err) 1143 require.Equal(t, 1, len(g)) 1144 sort.Strings(g["default"].Targets) 1145 require.Equal(t, []string{"addon", "aws", "image"}, g["default"].Targets) 1146 require.Equal(t, 3, len(m)) 1147 require.Equal(t, ".", *m["image"].Context) 1148 require.Equal(t, "./Dockerfile", *m["addon"].Dockerfile) 1149 require.Equal(t, "./aws.Dockerfile", *m["aws"].Dockerfile) 1150 } 1151 1152 func TestReadTargetsSameGroupTarget(t *testing.T) { 1153 t.Parallel() 1154 ctx := context.TODO() 1155 1156 f := File{ 1157 Name: "docker-bake.hcl", 1158 Data: []byte(` 1159 group "foo" { 1160 targets = ["foo"] 1161 } 1162 target "foo" { 1163 dockerfile = "bar" 1164 } 1165 target "image" { 1166 output = ["type=docker"] 1167 }`)} 1168 1169 m, g, err := ReadTargets(ctx, []File{f}, []string{"foo"}, nil, nil) 1170 require.NoError(t, err) 1171 require.Equal(t, 2, len(g)) 1172 require.Equal(t, []string{"foo"}, g["default"].Targets) 1173 require.Equal(t, []string{"foo"}, g["foo"].Targets) 1174 require.Equal(t, 1, len(m)) 1175 require.Equal(t, "bar", *m["foo"].Dockerfile) 1176 1177 m, g, err = ReadTargets(ctx, []File{f}, []string{"foo", "foo"}, nil, nil) 1178 require.NoError(t, err) 1179 require.Equal(t, 2, len(g)) 1180 require.Equal(t, []string{"foo"}, g["default"].Targets) 1181 require.Equal(t, []string{"foo"}, g["foo"].Targets) 1182 require.Equal(t, 1, len(m)) 1183 require.Equal(t, "bar", *m["foo"].Dockerfile) 1184 } 1185 1186 func TestReadTargetsSameGroupTargetMulti(t *testing.T) { 1187 t.Parallel() 1188 ctx := context.TODO() 1189 1190 f := File{ 1191 Name: "docker-bake.hcl", 1192 Data: []byte(` 1193 group "foo" { 1194 targets = ["foo", "image"] 1195 } 1196 target "foo" { 1197 dockerfile = "bar" 1198 } 1199 target "image" { 1200 output = ["type=docker"] 1201 }`)} 1202 1203 m, g, err := ReadTargets(ctx, []File{f}, []string{"foo"}, nil, nil) 1204 require.NoError(t, err) 1205 require.Equal(t, 2, len(g)) 1206 require.Equal(t, []string{"foo"}, g["default"].Targets) 1207 require.Equal(t, []string{"foo", "image"}, g["foo"].Targets) 1208 require.Equal(t, 2, len(m)) 1209 require.Equal(t, "bar", *m["foo"].Dockerfile) 1210 require.Equal(t, "type=docker", m["image"].Outputs[0]) 1211 1212 m, g, err = ReadTargets(ctx, []File{f}, []string{"foo", "image"}, nil, nil) 1213 require.NoError(t, err) 1214 require.Equal(t, 2, len(g)) 1215 require.Equal(t, []string{"foo", "image"}, g["default"].Targets) 1216 require.Equal(t, []string{"foo", "image"}, g["foo"].Targets) 1217 require.Equal(t, 2, len(m)) 1218 require.Equal(t, "bar", *m["foo"].Dockerfile) 1219 require.Equal(t, "type=docker", m["image"].Outputs[0]) 1220 } 1221 1222 func TestNestedInherits(t *testing.T) { 1223 ctx := context.TODO() 1224 1225 f := File{ 1226 Name: "docker-bake.hcl", 1227 Data: []byte(` 1228 target "a" { 1229 args = { 1230 foo = "123" 1231 bar = "234" 1232 } 1233 } 1234 target "b" { 1235 inherits = ["a"] 1236 args = { 1237 bar = "567" 1238 } 1239 } 1240 target "c" { 1241 inherits = ["a"] 1242 args = { 1243 baz = "890" 1244 } 1245 } 1246 target "d" { 1247 inherits = ["b", "c"] 1248 }`)} 1249 1250 cases := []struct { 1251 name string 1252 overrides []string 1253 want map[string]*string 1254 }{ 1255 { 1256 name: "nested simple", 1257 overrides: nil, 1258 want: map[string]*string{"bar": ptrstr("234"), "baz": ptrstr("890"), "foo": ptrstr("123")}, 1259 }, 1260 { 1261 name: "nested with overrides first", 1262 overrides: []string{"a.args.foo=321", "b.args.bar=432"}, 1263 want: map[string]*string{"bar": ptrstr("234"), "baz": ptrstr("890"), "foo": ptrstr("321")}, 1264 }, 1265 { 1266 name: "nested with overrides last", 1267 overrides: []string{"a.args.foo=321", "c.args.bar=432"}, 1268 want: map[string]*string{"bar": ptrstr("432"), "baz": ptrstr("890"), "foo": ptrstr("321")}, 1269 }, 1270 } 1271 for _, tt := range cases { 1272 tt := tt 1273 t.Run(tt.name, func(t *testing.T) { 1274 m, g, err := ReadTargets(ctx, []File{f}, []string{"d"}, tt.overrides, nil) 1275 require.NoError(t, err) 1276 require.Equal(t, 1, len(g)) 1277 require.Equal(t, []string{"d"}, g["default"].Targets) 1278 require.Equal(t, 1, len(m)) 1279 require.Equal(t, tt.want, m["d"].Args) 1280 }) 1281 } 1282 } 1283 1284 func TestNestedInheritsWithGroup(t *testing.T) { 1285 ctx := context.TODO() 1286 1287 f := File{ 1288 Name: "docker-bake.hcl", 1289 Data: []byte(` 1290 target "grandparent" { 1291 output = ["type=docker"] 1292 args = { 1293 BAR = "fuu" 1294 } 1295 } 1296 target "parent" { 1297 inherits = ["grandparent"] 1298 args = { 1299 FOO = "bar" 1300 } 1301 } 1302 target "child1" { 1303 inherits = ["parent"] 1304 } 1305 target "child2" { 1306 inherits = ["parent"] 1307 args = { 1308 FOO2 = "bar2" 1309 } 1310 } 1311 group "default" { 1312 targets = [ 1313 "child1", 1314 "child2" 1315 ] 1316 }`)} 1317 1318 cases := []struct { 1319 name string 1320 overrides []string 1321 wantch1 map[string]*string 1322 wantch2 map[string]*string 1323 }{ 1324 { 1325 name: "nested simple", 1326 overrides: nil, 1327 wantch1: map[string]*string{"BAR": ptrstr("fuu"), "FOO": ptrstr("bar")}, 1328 wantch2: map[string]*string{"BAR": ptrstr("fuu"), "FOO": ptrstr("bar"), "FOO2": ptrstr("bar2")}, 1329 }, 1330 { 1331 name: "nested with overrides first", 1332 overrides: []string{"grandparent.args.BAR=fii", "child1.args.FOO=baaar"}, 1333 wantch1: map[string]*string{"BAR": ptrstr("fii"), "FOO": ptrstr("baaar")}, 1334 wantch2: map[string]*string{"BAR": ptrstr("fii"), "FOO": ptrstr("bar"), "FOO2": ptrstr("bar2")}, 1335 }, 1336 { 1337 name: "nested with overrides last", 1338 overrides: []string{"grandparent.args.BAR=fii", "child2.args.FOO=baaar"}, 1339 wantch1: map[string]*string{"BAR": ptrstr("fii"), "FOO": ptrstr("bar")}, 1340 wantch2: map[string]*string{"BAR": ptrstr("fii"), "FOO": ptrstr("baaar"), "FOO2": ptrstr("bar2")}, 1341 }, 1342 } 1343 for _, tt := range cases { 1344 tt := tt 1345 t.Run(tt.name, func(t *testing.T) { 1346 m, g, err := ReadTargets(ctx, []File{f}, []string{"default"}, tt.overrides, nil) 1347 require.NoError(t, err) 1348 require.Equal(t, 1, len(g)) 1349 require.Equal(t, []string{"child1", "child2"}, g["default"].Targets) 1350 require.Equal(t, 2, len(m)) 1351 require.Equal(t, tt.wantch1, m["child1"].Args) 1352 require.Equal(t, []string{"type=docker"}, m["child1"].Outputs) 1353 require.Equal(t, tt.wantch2, m["child2"].Args) 1354 require.Equal(t, []string{"type=docker"}, m["child2"].Outputs) 1355 }) 1356 } 1357 } 1358 1359 func TestTargetName(t *testing.T) { 1360 ctx := context.TODO() 1361 cases := []struct { 1362 target string 1363 wantErr bool 1364 }{ 1365 { 1366 target: "a", 1367 wantErr: false, 1368 }, 1369 { 1370 target: "abc", 1371 wantErr: false, 1372 }, 1373 { 1374 target: "a/b", 1375 wantErr: true, 1376 }, 1377 { 1378 target: "a.b", 1379 wantErr: true, 1380 }, 1381 { 1382 target: "_a", 1383 wantErr: false, 1384 }, 1385 { 1386 target: "a_b", 1387 wantErr: false, 1388 }, 1389 { 1390 target: "AbC", 1391 wantErr: false, 1392 }, 1393 { 1394 target: "AbC-0123", 1395 wantErr: false, 1396 }, 1397 } 1398 for _, tt := range cases { 1399 tt := tt 1400 t.Run(tt.target, func(t *testing.T) { 1401 _, _, err := ReadTargets(ctx, []File{{ 1402 Name: "docker-bake.hcl", 1403 Data: []byte(`target "` + tt.target + `" {}`), 1404 }}, []string{tt.target}, nil, nil) 1405 if tt.wantErr { 1406 require.Error(t, err) 1407 } else { 1408 require.NoError(t, err) 1409 } 1410 }) 1411 } 1412 } 1413 1414 func TestNestedGroupsWithSameTarget(t *testing.T) { 1415 ctx := context.TODO() 1416 1417 f := File{ 1418 Name: "docker-bake.hcl", 1419 Data: []byte(` 1420 group "a" { 1421 targets = ["b", "c"] 1422 } 1423 1424 group "b" { 1425 targets = ["d"] 1426 } 1427 1428 group "c" { 1429 targets = ["b"] 1430 } 1431 1432 target "d" { 1433 context = "." 1434 dockerfile = "./testdockerfile" 1435 } 1436 1437 group "e" { 1438 targets = ["a", "f"] 1439 } 1440 1441 target "f" { 1442 context = "./foo" 1443 }`)} 1444 1445 cases := []struct { 1446 names []string 1447 targets []string 1448 groups []string 1449 count int 1450 }{ 1451 { 1452 names: []string{"a"}, 1453 targets: []string{"a"}, 1454 groups: []string{"default", "a", "b", "c"}, 1455 count: 1, 1456 }, 1457 { 1458 names: []string{"b"}, 1459 targets: []string{"b"}, 1460 groups: []string{"default", "b"}, 1461 count: 1, 1462 }, 1463 { 1464 names: []string{"c"}, 1465 targets: []string{"c"}, 1466 groups: []string{"default", "b", "c"}, 1467 count: 1, 1468 }, 1469 { 1470 names: []string{"d"}, 1471 targets: []string{"d"}, 1472 groups: []string{"default"}, 1473 count: 1, 1474 }, 1475 { 1476 names: []string{"e"}, 1477 targets: []string{"e"}, 1478 groups: []string{"default", "a", "b", "c", "e"}, 1479 count: 2, 1480 }, 1481 { 1482 names: []string{"a", "e"}, 1483 targets: []string{"a", "e"}, 1484 groups: []string{"default", "a", "b", "c", "e"}, 1485 count: 2, 1486 }, 1487 } 1488 for _, tt := range cases { 1489 tt := tt 1490 t.Run(strings.Join(tt.names, "+"), func(t *testing.T) { 1491 m, g, err := ReadTargets(ctx, []File{f}, tt.names, nil, nil) 1492 require.NoError(t, err) 1493 1494 var gnames []string 1495 for _, g := range g { 1496 gnames = append(gnames, g.Name) 1497 } 1498 sort.Strings(gnames) 1499 sort.Strings(tt.groups) 1500 require.Equal(t, tt.groups, gnames) 1501 1502 sort.Strings(g["default"].Targets) 1503 sort.Strings(tt.targets) 1504 require.Equal(t, tt.targets, g["default"].Targets) 1505 1506 require.Equal(t, tt.count, len(m)) 1507 require.Equal(t, ".", *m["d"].Context) 1508 require.Equal(t, "./testdockerfile", *m["d"].Dockerfile) 1509 }) 1510 } 1511 } 1512 1513 func TestUnknownExt(t *testing.T) { 1514 dt := []byte(` 1515 target "app" { 1516 context = "dir" 1517 args = { 1518 v1 = "foo" 1519 } 1520 } 1521 `) 1522 dt2 := []byte(` 1523 services: 1524 app: 1525 build: 1526 dockerfile: Dockerfile-alternate 1527 args: 1528 v2: "bar" 1529 `) 1530 1531 c, err := ParseFiles([]File{ 1532 {Data: dt, Name: "c1.foo"}, 1533 {Data: dt2, Name: "c2.bar"}, 1534 }, nil) 1535 require.NoError(t, err) 1536 1537 require.Equal(t, 1, len(c.Targets)) 1538 require.Equal(t, "app", c.Targets[0].Name) 1539 require.Equal(t, ptrstr("foo"), c.Targets[0].Args["v1"]) 1540 require.Equal(t, ptrstr("bar"), c.Targets[0].Args["v2"]) 1541 require.Equal(t, "dir", *c.Targets[0].Context) 1542 require.Equal(t, "Dockerfile-alternate", *c.Targets[0].Dockerfile) 1543 } 1544 1545 func TestHCLNullVars(t *testing.T) { 1546 fp := File{ 1547 Name: "docker-bake.hcl", 1548 Data: []byte( 1549 `variable "FOO" { 1550 default = null 1551 } 1552 variable "BAR" { 1553 default = null 1554 } 1555 target "default" { 1556 args = { 1557 foo = FOO 1558 bar = "baz" 1559 } 1560 labels = { 1561 "com.docker.app.bar" = BAR 1562 "com.docker.app.baz" = "foo" 1563 } 1564 }`), 1565 } 1566 1567 ctx := context.TODO() 1568 m, _, err := ReadTargets(ctx, []File{fp}, []string{"default"}, nil, nil) 1569 require.NoError(t, err) 1570 1571 require.Equal(t, 1, len(m)) 1572 _, ok := m["default"] 1573 require.True(t, ok) 1574 1575 _, err = TargetsToBuildOpt(m, &Input{}) 1576 require.NoError(t, err) 1577 require.Equal(t, map[string]*string{"bar": ptrstr("baz")}, m["default"].Args) 1578 require.Equal(t, map[string]*string{"com.docker.app.baz": ptrstr("foo")}, m["default"].Labels) 1579 } 1580 1581 func TestJSONNullVars(t *testing.T) { 1582 fp := File{ 1583 Name: "docker-bake.json", 1584 Data: []byte( 1585 `{ 1586 "variable": { 1587 "FOO": { 1588 "default": null 1589 } 1590 }, 1591 "target": { 1592 "default": { 1593 "args": { 1594 "foo": "${FOO}", 1595 "bar": "baz" 1596 } 1597 } 1598 } 1599 }`), 1600 } 1601 1602 ctx := context.TODO() 1603 m, _, err := ReadTargets(ctx, []File{fp}, []string{"default"}, nil, nil) 1604 require.NoError(t, err) 1605 1606 require.Equal(t, 1, len(m)) 1607 _, ok := m["default"] 1608 require.True(t, ok) 1609 1610 _, err = TargetsToBuildOpt(m, &Input{}) 1611 require.NoError(t, err) 1612 require.Equal(t, map[string]*string{"bar": ptrstr("baz")}, m["default"].Args) 1613 } 1614 1615 func TestReadLocalFilesDefault(t *testing.T) { 1616 tests := []struct { 1617 filenames []string 1618 expected []string 1619 }{ 1620 { 1621 filenames: []string{"abc.yml", "docker-compose.yml"}, 1622 expected: []string{"docker-compose.yml"}, 1623 }, 1624 { 1625 filenames: []string{"test.foo", "compose.yml", "docker-bake.hcl"}, 1626 expected: []string{"compose.yml", "docker-bake.hcl"}, 1627 }, 1628 { 1629 filenames: []string{"compose.yaml", "docker-compose.yml", "docker-bake.hcl"}, 1630 expected: []string{"compose.yaml", "docker-compose.yml", "docker-bake.hcl"}, 1631 }, 1632 { 1633 filenames: []string{"test.txt", "compsoe.yaml"}, // intentional misspell 1634 expected: []string{}, 1635 }, 1636 } 1637 pwd, err := os.Getwd() 1638 require.NoError(t, err) 1639 1640 for _, tt := range tests { 1641 t.Run(strings.Join(tt.filenames, "-"), func(t *testing.T) { 1642 dir := t.TempDir() 1643 t.Cleanup(func() { _ = os.Chdir(pwd) }) 1644 require.NoError(t, os.Chdir(dir)) 1645 for _, tf := range tt.filenames { 1646 require.NoError(t, os.WriteFile(tf, []byte(tf), 0644)) 1647 } 1648 files, err := ReadLocalFiles(nil, nil, nil) 1649 require.NoError(t, err) 1650 if len(files) == 0 { 1651 require.Equal(t, len(tt.expected), len(files)) 1652 } else { 1653 found := false 1654 for _, exp := range tt.expected { 1655 for _, f := range files { 1656 if f.Name == exp { 1657 found = true 1658 break 1659 } 1660 } 1661 require.True(t, found, exp) 1662 } 1663 } 1664 }) 1665 } 1666 } 1667 1668 func TestAttestDuplicates(t *testing.T) { 1669 fp := File{ 1670 Name: "docker-bake.hcl", 1671 Data: []byte( 1672 `target "default" { 1673 attest = ["type=sbom", "type=sbom,generator=custom", "type=sbom,foo=bar", "type=provenance,mode=max"] 1674 }`), 1675 } 1676 ctx := context.TODO() 1677 1678 m, _, err := ReadTargets(ctx, []File{fp}, []string{"default"}, nil, nil) 1679 require.Equal(t, []string{"type=sbom,foo=bar", "type=provenance,mode=max"}, m["default"].Attest) 1680 require.NoError(t, err) 1681 1682 opts, err := TargetsToBuildOpt(m, &Input{}) 1683 require.NoError(t, err) 1684 require.Equal(t, map[string]*string{ 1685 "sbom": ptrstr("type=sbom,foo=bar"), 1686 "provenance": ptrstr("type=provenance,mode=max"), 1687 }, opts["default"].Attests) 1688 1689 m, _, err = ReadTargets(ctx, []File{fp}, []string{"default"}, []string{"*.attest=type=sbom,disabled=true"}, nil) 1690 require.Equal(t, []string{"type=sbom,disabled=true", "type=provenance,mode=max"}, m["default"].Attest) 1691 require.NoError(t, err) 1692 1693 opts, err = TargetsToBuildOpt(m, &Input{}) 1694 require.NoError(t, err) 1695 require.Equal(t, map[string]*string{ 1696 "sbom": nil, 1697 "provenance": ptrstr("type=provenance,mode=max"), 1698 }, opts["default"].Attests) 1699 } 1700 1701 func TestAnnotations(t *testing.T) { 1702 fp := File{ 1703 Name: "docker-bake.hcl", 1704 Data: []byte( 1705 `target "app" { 1706 output = ["type=image,name=foo"] 1707 annotations = ["manifest[linux/amd64]:foo=bar"] 1708 }`), 1709 } 1710 ctx := context.TODO() 1711 m, g, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil) 1712 require.NoError(t, err) 1713 1714 bo, err := TargetsToBuildOpt(m, &Input{}) 1715 require.NoError(t, err) 1716 1717 require.Equal(t, 1, len(g)) 1718 require.Equal(t, []string{"app"}, g["default"].Targets) 1719 1720 require.Equal(t, 1, len(m)) 1721 require.Contains(t, m, "app") 1722 require.Equal(t, "type=image,name=foo", m["app"].Outputs[0]) 1723 require.Equal(t, "manifest[linux/amd64]:foo=bar", m["app"].Annotations[0]) 1724 1725 require.Len(t, bo["app"].Exports, 1) 1726 require.Equal(t, "bar", bo["app"].Exports[0].Attrs["annotation-manifest[linux/amd64].foo"]) 1727 }