github.com/pdmccormick/importable-docker-buildx@v0.0.0-20240426161518-e47091289030/bake/hcl_test.go (about) 1 package bake 2 3 import ( 4 "reflect" 5 "testing" 6 7 "github.com/stretchr/testify/require" 8 ) 9 10 func TestHCLBasic(t *testing.T) { 11 t.Parallel() 12 dt := []byte(` 13 group "default" { 14 targets = ["db", "webapp"] 15 } 16 17 target "db" { 18 context = "./db" 19 tags = ["docker.io/tonistiigi/db"] 20 } 21 22 target "webapp" { 23 context = "./dir" 24 dockerfile = "Dockerfile-alternate" 25 args = { 26 buildno = "123" 27 } 28 } 29 30 target "cross" { 31 platforms = [ 32 "linux/amd64", 33 "linux/arm64" 34 ] 35 } 36 37 target "webapp-plus" { 38 inherits = ["webapp", "cross"] 39 args = { 40 IAMCROSS = "true" 41 } 42 } 43 `) 44 45 c, err := ParseFile(dt, "docker-bake.hcl") 46 require.NoError(t, err) 47 require.Equal(t, 1, len(c.Groups)) 48 require.Equal(t, "default", c.Groups[0].Name) 49 require.Equal(t, []string{"db", "webapp"}, c.Groups[0].Targets) 50 51 require.Equal(t, 4, len(c.Targets)) 52 require.Equal(t, c.Targets[0].Name, "db") 53 require.Equal(t, "./db", *c.Targets[0].Context) 54 55 require.Equal(t, c.Targets[1].Name, "webapp") 56 require.Equal(t, 1, len(c.Targets[1].Args)) 57 require.Equal(t, ptrstr("123"), c.Targets[1].Args["buildno"]) 58 59 require.Equal(t, c.Targets[2].Name, "cross") 60 require.Equal(t, 2, len(c.Targets[2].Platforms)) 61 require.Equal(t, []string{"linux/amd64", "linux/arm64"}, c.Targets[2].Platforms) 62 63 require.Equal(t, c.Targets[3].Name, "webapp-plus") 64 require.Equal(t, 1, len(c.Targets[3].Args)) 65 require.Equal(t, map[string]*string{"IAMCROSS": ptrstr("true")}, c.Targets[3].Args) 66 } 67 68 func TestHCLBasicInJSON(t *testing.T) { 69 dt := []byte(` 70 { 71 "group": { 72 "default": { 73 "targets": ["db", "webapp"] 74 } 75 }, 76 "target": { 77 "db": { 78 "context": "./db", 79 "tags": ["docker.io/tonistiigi/db"] 80 }, 81 "webapp": { 82 "context": "./dir", 83 "dockerfile": "Dockerfile-alternate", 84 "args": { 85 "buildno": "123" 86 } 87 }, 88 "cross": { 89 "platforms": [ 90 "linux/amd64", 91 "linux/arm64" 92 ] 93 }, 94 "webapp-plus": { 95 "inherits": ["webapp", "cross"], 96 "args": { 97 "IAMCROSS": "true" 98 } 99 } 100 } 101 } 102 `) 103 104 c, err := ParseFile(dt, "docker-bake.json") 105 require.NoError(t, err) 106 107 require.Equal(t, 1, len(c.Groups)) 108 require.Equal(t, "default", c.Groups[0].Name) 109 require.Equal(t, []string{"db", "webapp"}, c.Groups[0].Targets) 110 111 require.Equal(t, 4, len(c.Targets)) 112 require.Equal(t, c.Targets[0].Name, "db") 113 require.Equal(t, "./db", *c.Targets[0].Context) 114 115 require.Equal(t, c.Targets[1].Name, "webapp") 116 require.Equal(t, 1, len(c.Targets[1].Args)) 117 require.Equal(t, ptrstr("123"), c.Targets[1].Args["buildno"]) 118 119 require.Equal(t, c.Targets[2].Name, "cross") 120 require.Equal(t, 2, len(c.Targets[2].Platforms)) 121 require.Equal(t, []string{"linux/amd64", "linux/arm64"}, c.Targets[2].Platforms) 122 123 require.Equal(t, c.Targets[3].Name, "webapp-plus") 124 require.Equal(t, 1, len(c.Targets[3].Args)) 125 require.Equal(t, map[string]*string{"IAMCROSS": ptrstr("true")}, c.Targets[3].Args) 126 } 127 128 func TestHCLWithFunctions(t *testing.T) { 129 dt := []byte(` 130 group "default" { 131 targets = ["webapp"] 132 } 133 134 target "webapp" { 135 args = { 136 buildno = "${add(123, 1)}" 137 } 138 } 139 `) 140 141 c, err := ParseFile(dt, "docker-bake.hcl") 142 require.NoError(t, err) 143 144 require.Equal(t, 1, len(c.Groups)) 145 require.Equal(t, "default", c.Groups[0].Name) 146 require.Equal(t, []string{"webapp"}, c.Groups[0].Targets) 147 148 require.Equal(t, 1, len(c.Targets)) 149 require.Equal(t, c.Targets[0].Name, "webapp") 150 require.Equal(t, ptrstr("124"), c.Targets[0].Args["buildno"]) 151 } 152 153 func TestHCLWithUserDefinedFunctions(t *testing.T) { 154 dt := []byte(` 155 function "increment" { 156 params = [number] 157 result = number + 1 158 } 159 160 group "default" { 161 targets = ["webapp"] 162 } 163 164 target "webapp" { 165 args = { 166 buildno = "${increment(123)}" 167 } 168 } 169 `) 170 171 c, err := ParseFile(dt, "docker-bake.hcl") 172 require.NoError(t, err) 173 174 require.Equal(t, 1, len(c.Groups)) 175 require.Equal(t, "default", c.Groups[0].Name) 176 require.Equal(t, []string{"webapp"}, c.Groups[0].Targets) 177 178 require.Equal(t, 1, len(c.Targets)) 179 require.Equal(t, c.Targets[0].Name, "webapp") 180 require.Equal(t, ptrstr("124"), c.Targets[0].Args["buildno"]) 181 } 182 183 func TestHCLWithVariables(t *testing.T) { 184 dt := []byte(` 185 variable "BUILD_NUMBER" { 186 default = "123" 187 } 188 189 group "default" { 190 targets = ["webapp"] 191 } 192 193 target "webapp" { 194 args = { 195 buildno = "${BUILD_NUMBER}" 196 } 197 } 198 `) 199 200 c, err := ParseFile(dt, "docker-bake.hcl") 201 require.NoError(t, err) 202 203 require.Equal(t, 1, len(c.Groups)) 204 require.Equal(t, "default", c.Groups[0].Name) 205 require.Equal(t, []string{"webapp"}, c.Groups[0].Targets) 206 207 require.Equal(t, 1, len(c.Targets)) 208 require.Equal(t, c.Targets[0].Name, "webapp") 209 require.Equal(t, ptrstr("123"), c.Targets[0].Args["buildno"]) 210 211 t.Setenv("BUILD_NUMBER", "456") 212 213 c, err = ParseFile(dt, "docker-bake.hcl") 214 require.NoError(t, err) 215 216 require.Equal(t, 1, len(c.Groups)) 217 require.Equal(t, "default", c.Groups[0].Name) 218 require.Equal(t, []string{"webapp"}, c.Groups[0].Targets) 219 220 require.Equal(t, 1, len(c.Targets)) 221 require.Equal(t, c.Targets[0].Name, "webapp") 222 require.Equal(t, ptrstr("456"), c.Targets[0].Args["buildno"]) 223 } 224 225 func TestHCLWithVariablesInFunctions(t *testing.T) { 226 dt := []byte(` 227 variable "REPO" { 228 default = "user/repo" 229 } 230 function "tag" { 231 params = [tag] 232 result = ["${REPO}:${tag}"] 233 } 234 235 target "webapp" { 236 tags = tag("v1") 237 } 238 `) 239 240 c, err := ParseFile(dt, "docker-bake.hcl") 241 require.NoError(t, err) 242 243 require.Equal(t, 1, len(c.Targets)) 244 require.Equal(t, c.Targets[0].Name, "webapp") 245 require.Equal(t, []string{"user/repo:v1"}, c.Targets[0].Tags) 246 247 t.Setenv("REPO", "docker/buildx") 248 249 c, err = ParseFile(dt, "docker-bake.hcl") 250 require.NoError(t, err) 251 252 require.Equal(t, 1, len(c.Targets)) 253 require.Equal(t, c.Targets[0].Name, "webapp") 254 require.Equal(t, []string{"docker/buildx:v1"}, c.Targets[0].Tags) 255 } 256 257 func TestHCLMultiFileSharedVariables(t *testing.T) { 258 dt := []byte(` 259 variable "FOO" { 260 default = "abc" 261 } 262 target "app" { 263 args = { 264 v1 = "pre-${FOO}" 265 } 266 } 267 `) 268 dt2 := []byte(` 269 target "app" { 270 args = { 271 v2 = "${FOO}-post" 272 } 273 } 274 `) 275 276 c, err := ParseFiles([]File{ 277 {Data: dt, Name: "c1.hcl"}, 278 {Data: dt2, Name: "c2.hcl"}, 279 }, nil) 280 require.NoError(t, err) 281 require.Equal(t, 1, len(c.Targets)) 282 require.Equal(t, c.Targets[0].Name, "app") 283 require.Equal(t, ptrstr("pre-abc"), c.Targets[0].Args["v1"]) 284 require.Equal(t, ptrstr("abc-post"), c.Targets[0].Args["v2"]) 285 286 t.Setenv("FOO", "def") 287 288 c, err = ParseFiles([]File{ 289 {Data: dt, Name: "c1.hcl"}, 290 {Data: dt2, Name: "c2.hcl"}, 291 }, nil) 292 require.NoError(t, err) 293 294 require.Equal(t, 1, len(c.Targets)) 295 require.Equal(t, c.Targets[0].Name, "app") 296 require.Equal(t, ptrstr("pre-def"), c.Targets[0].Args["v1"]) 297 require.Equal(t, ptrstr("def-post"), c.Targets[0].Args["v2"]) 298 } 299 300 func TestHCLVarsWithVars(t *testing.T) { 301 dt := []byte(` 302 variable "FOO" { 303 default = upper("${BASE}def") 304 } 305 variable "BAR" { 306 default = "-${FOO}-" 307 } 308 target "app" { 309 args = { 310 v1 = "pre-${BAR}" 311 } 312 } 313 `) 314 dt2 := []byte(` 315 variable "BASE" { 316 default = "abc" 317 } 318 target "app" { 319 args = { 320 v2 = "${FOO}-post" 321 } 322 } 323 `) 324 325 c, err := ParseFiles([]File{ 326 {Data: dt, Name: "c1.hcl"}, 327 {Data: dt2, Name: "c2.hcl"}, 328 }, nil) 329 require.NoError(t, err) 330 require.Equal(t, 1, len(c.Targets)) 331 require.Equal(t, c.Targets[0].Name, "app") 332 require.Equal(t, ptrstr("pre--ABCDEF-"), c.Targets[0].Args["v1"]) 333 require.Equal(t, ptrstr("ABCDEF-post"), c.Targets[0].Args["v2"]) 334 335 t.Setenv("BASE", "new") 336 337 c, err = ParseFiles([]File{ 338 {Data: dt, Name: "c1.hcl"}, 339 {Data: dt2, Name: "c2.hcl"}, 340 }, nil) 341 require.NoError(t, err) 342 343 require.Equal(t, 1, len(c.Targets)) 344 require.Equal(t, c.Targets[0].Name, "app") 345 require.Equal(t, ptrstr("pre--NEWDEF-"), c.Targets[0].Args["v1"]) 346 require.Equal(t, ptrstr("NEWDEF-post"), c.Targets[0].Args["v2"]) 347 } 348 349 func TestHCLTypedVariables(t *testing.T) { 350 dt := []byte(` 351 variable "FOO" { 352 default = 3 353 } 354 variable "IS_FOO" { 355 default = true 356 } 357 target "app" { 358 args = { 359 v1 = FOO > 5 ? "higher" : "lower" 360 v2 = IS_FOO ? "yes" : "no" 361 } 362 } 363 `) 364 365 c, err := ParseFile(dt, "docker-bake.hcl") 366 require.NoError(t, err) 367 368 require.Equal(t, 1, len(c.Targets)) 369 require.Equal(t, c.Targets[0].Name, "app") 370 require.Equal(t, ptrstr("lower"), c.Targets[0].Args["v1"]) 371 require.Equal(t, ptrstr("yes"), c.Targets[0].Args["v2"]) 372 373 t.Setenv("FOO", "5.1") 374 t.Setenv("IS_FOO", "0") 375 376 c, err = ParseFile(dt, "docker-bake.hcl") 377 require.NoError(t, err) 378 379 require.Equal(t, 1, len(c.Targets)) 380 require.Equal(t, c.Targets[0].Name, "app") 381 require.Equal(t, ptrstr("higher"), c.Targets[0].Args["v1"]) 382 require.Equal(t, ptrstr("no"), c.Targets[0].Args["v2"]) 383 384 t.Setenv("FOO", "NaN") 385 _, err = ParseFile(dt, "docker-bake.hcl") 386 require.Error(t, err) 387 require.Contains(t, err.Error(), "failed to parse FOO as number") 388 389 t.Setenv("FOO", "0") 390 t.Setenv("IS_FOO", "maybe") 391 392 _, err = ParseFile(dt, "docker-bake.hcl") 393 require.Error(t, err) 394 require.Contains(t, err.Error(), "failed to parse IS_FOO as bool") 395 } 396 397 func TestHCLNullVariables(t *testing.T) { 398 dt := []byte(` 399 variable "FOO" { 400 default = null 401 } 402 target "default" { 403 args = { 404 foo = FOO 405 } 406 }`) 407 408 c, err := ParseFile(dt, "docker-bake.hcl") 409 require.NoError(t, err) 410 require.Equal(t, ptrstr(nil), c.Targets[0].Args["foo"]) 411 412 t.Setenv("FOO", "bar") 413 c, err = ParseFile(dt, "docker-bake.hcl") 414 require.NoError(t, err) 415 require.Equal(t, ptrstr("bar"), c.Targets[0].Args["foo"]) 416 } 417 418 func TestJSONNullVariables(t *testing.T) { 419 dt := []byte(`{ 420 "variable": { 421 "FOO": { 422 "default": null 423 } 424 }, 425 "target": { 426 "default": { 427 "args": { 428 "foo": "${FOO}" 429 } 430 } 431 } 432 }`) 433 434 c, err := ParseFile(dt, "docker-bake.json") 435 require.NoError(t, err) 436 require.Equal(t, ptrstr(nil), c.Targets[0].Args["foo"]) 437 438 t.Setenv("FOO", "bar") 439 c, err = ParseFile(dt, "docker-bake.json") 440 require.NoError(t, err) 441 require.Equal(t, ptrstr("bar"), c.Targets[0].Args["foo"]) 442 } 443 444 func TestHCLVariableCycle(t *testing.T) { 445 dt := []byte(` 446 variable "FOO" { 447 default = BAR 448 } 449 variable "FOO2" { 450 default = FOO 451 } 452 variable "BAR" { 453 default = FOO 454 } 455 target "app" {} 456 `) 457 458 _, err := ParseFile(dt, "docker-bake.hcl") 459 require.Error(t, err) 460 require.Contains(t, err.Error(), "variable cycle not allowed") 461 } 462 463 func TestHCLAttrs(t *testing.T) { 464 dt := []byte(` 465 FOO="abc" 466 BAR="attr-${FOO}def" 467 target "app" { 468 args = { 469 "v1": BAR 470 } 471 } 472 `) 473 474 c, err := ParseFile(dt, "docker-bake.hcl") 475 require.NoError(t, err) 476 477 require.Equal(t, 1, len(c.Targets)) 478 require.Equal(t, c.Targets[0].Name, "app") 479 require.Equal(t, ptrstr("attr-abcdef"), c.Targets[0].Args["v1"]) 480 481 // env does not apply if no variable 482 t.Setenv("FOO", "bar") 483 c, err = ParseFile(dt, "docker-bake.hcl") 484 require.NoError(t, err) 485 486 require.Equal(t, 1, len(c.Targets)) 487 require.Equal(t, c.Targets[0].Name, "app") 488 require.Equal(t, ptrstr("attr-abcdef"), c.Targets[0].Args["v1"]) 489 // attr-multifile 490 } 491 492 func TestHCLTargetAttrs(t *testing.T) { 493 dt := []byte(` 494 target "foo" { 495 dockerfile = "xxx" 496 context = target.bar.context 497 target = target.foo.dockerfile 498 } 499 500 target "bar" { 501 dockerfile = target.foo.dockerfile 502 context = "yyy" 503 target = target.bar.context 504 } 505 `) 506 507 c, err := ParseFile(dt, "docker-bake.hcl") 508 require.NoError(t, err) 509 510 require.Equal(t, 2, len(c.Targets)) 511 require.Equal(t, "foo", c.Targets[0].Name) 512 require.Equal(t, "bar", c.Targets[1].Name) 513 514 require.Equal(t, "xxx", *c.Targets[0].Dockerfile) 515 require.Equal(t, "yyy", *c.Targets[0].Context) 516 require.Equal(t, "xxx", *c.Targets[0].Target) 517 518 require.Equal(t, "xxx", *c.Targets[1].Dockerfile) 519 require.Equal(t, "yyy", *c.Targets[1].Context) 520 require.Equal(t, "yyy", *c.Targets[1].Target) 521 } 522 523 func TestHCLTargetGlobal(t *testing.T) { 524 dt := []byte(` 525 target "foo" { 526 dockerfile = "x" 527 } 528 x = target.foo.dockerfile 529 y = x 530 target "bar" { 531 dockerfile = y 532 } 533 `) 534 535 c, err := ParseFile(dt, "docker-bake.hcl") 536 require.NoError(t, err) 537 538 require.Equal(t, 2, len(c.Targets)) 539 require.Equal(t, "foo", c.Targets[0].Name) 540 require.Equal(t, "bar", c.Targets[1].Name) 541 542 require.Equal(t, "x", *c.Targets[0].Dockerfile) 543 require.Equal(t, "x", *c.Targets[1].Dockerfile) 544 } 545 546 func TestHCLTargetAttrName(t *testing.T) { 547 dt := []byte(` 548 target "foo" { 549 dockerfile = target.foo.name 550 } 551 `) 552 553 c, err := ParseFile(dt, "docker-bake.hcl") 554 require.NoError(t, err) 555 556 require.Equal(t, 1, len(c.Targets)) 557 require.Equal(t, "foo", c.Targets[0].Name) 558 require.Equal(t, "foo", *c.Targets[0].Dockerfile) 559 } 560 561 func TestHCLTargetAttrEmptyChain(t *testing.T) { 562 dt := []byte(` 563 target "foo" { 564 # dockerfile = Dockerfile 565 context = target.foo.dockerfile 566 target = target.foo.context 567 } 568 `) 569 570 c, err := ParseFile(dt, "docker-bake.hcl") 571 require.NoError(t, err) 572 573 require.Equal(t, 1, len(c.Targets)) 574 require.Equal(t, "foo", c.Targets[0].Name) 575 require.Nil(t, c.Targets[0].Dockerfile) 576 require.Nil(t, c.Targets[0].Context) 577 require.Nil(t, c.Targets[0].Target) 578 } 579 580 func TestHCLAttrsCustomType(t *testing.T) { 581 dt := []byte(` 582 platforms=["linux/arm64", "linux/amd64"] 583 target "app" { 584 platforms = platforms 585 args = { 586 "v1": platforms[0] 587 } 588 } 589 `) 590 591 c, err := ParseFile(dt, "docker-bake.hcl") 592 require.NoError(t, err) 593 594 require.Equal(t, 1, len(c.Targets)) 595 require.Equal(t, c.Targets[0].Name, "app") 596 require.Equal(t, []string{"linux/arm64", "linux/amd64"}, c.Targets[0].Platforms) 597 require.Equal(t, ptrstr("linux/arm64"), c.Targets[0].Args["v1"]) 598 } 599 600 func TestHCLMultiFileAttrs(t *testing.T) { 601 dt := []byte(` 602 variable "FOO" { 603 default = "abc" 604 } 605 target "app" { 606 args = { 607 v1 = "pre-${FOO}" 608 } 609 } 610 `) 611 dt2 := []byte(` 612 FOO="def" 613 `) 614 615 c, err := ParseFiles([]File{ 616 {Data: dt, Name: "c1.hcl"}, 617 {Data: dt2, Name: "c2.hcl"}, 618 }, nil) 619 require.NoError(t, err) 620 require.Equal(t, 1, len(c.Targets)) 621 require.Equal(t, c.Targets[0].Name, "app") 622 require.Equal(t, ptrstr("pre-def"), c.Targets[0].Args["v1"]) 623 624 t.Setenv("FOO", "ghi") 625 626 c, err = ParseFiles([]File{ 627 {Data: dt, Name: "c1.hcl"}, 628 {Data: dt2, Name: "c2.hcl"}, 629 }, nil) 630 require.NoError(t, err) 631 632 require.Equal(t, 1, len(c.Targets)) 633 require.Equal(t, c.Targets[0].Name, "app") 634 require.Equal(t, ptrstr("pre-ghi"), c.Targets[0].Args["v1"]) 635 } 636 637 func TestHCLMultiFileGlobalAttrs(t *testing.T) { 638 dt := []byte(` 639 FOO = "abc" 640 target "app" { 641 args = { 642 v1 = "pre-${FOO}" 643 } 644 } 645 `) 646 dt2 := []byte(` 647 FOO = "def" 648 `) 649 650 c, err := ParseFiles([]File{ 651 {Data: dt, Name: "c1.hcl"}, 652 {Data: dt2, Name: "c2.hcl"}, 653 }, nil) 654 require.NoError(t, err) 655 require.Equal(t, 1, len(c.Targets)) 656 require.Equal(t, c.Targets[0].Name, "app") 657 require.Equal(t, "pre-def", *c.Targets[0].Args["v1"]) 658 } 659 660 func TestHCLDuplicateTarget(t *testing.T) { 661 dt := []byte(` 662 target "app" { 663 dockerfile = "x" 664 } 665 target "app" { 666 dockerfile = "y" 667 } 668 `) 669 670 c, err := ParseFile(dt, "docker-bake.hcl") 671 require.NoError(t, err) 672 673 require.Equal(t, 1, len(c.Targets)) 674 require.Equal(t, "app", c.Targets[0].Name) 675 require.Equal(t, "y", *c.Targets[0].Dockerfile) 676 } 677 678 func TestHCLRenameTarget(t *testing.T) { 679 dt := []byte(` 680 target "abc" { 681 name = "xyz" 682 dockerfile = "foo" 683 } 684 `) 685 686 _, err := ParseFile(dt, "docker-bake.hcl") 687 require.ErrorContains(t, err, "requires matrix") 688 } 689 690 func TestHCLRenameGroup(t *testing.T) { 691 dt := []byte(` 692 group "foo" { 693 name = "bar" 694 targets = ["x", "y"] 695 } 696 `) 697 698 _, err := ParseFile(dt, "docker-bake.hcl") 699 require.ErrorContains(t, err, "not supported") 700 701 dt = []byte(` 702 group "foo" { 703 matrix = { 704 name = ["x", "y"] 705 } 706 } 707 `) 708 709 _, err = ParseFile(dt, "docker-bake.hcl") 710 require.ErrorContains(t, err, "not supported") 711 } 712 713 func TestHCLRenameTargetAttrs(t *testing.T) { 714 dt := []byte(` 715 target "abc" { 716 name = "xyz" 717 matrix = {} 718 dockerfile = "foo" 719 } 720 721 target "def" { 722 dockerfile = target.xyz.dockerfile 723 } 724 `) 725 726 c, err := ParseFile(dt, "docker-bake.hcl") 727 require.NoError(t, err) 728 require.Equal(t, 2, len(c.Targets)) 729 require.Equal(t, "xyz", c.Targets[0].Name) 730 require.Equal(t, "foo", *c.Targets[0].Dockerfile) 731 require.Equal(t, "def", c.Targets[1].Name) 732 require.Equal(t, "foo", *c.Targets[1].Dockerfile) 733 734 dt = []byte(` 735 target "def" { 736 dockerfile = target.xyz.dockerfile 737 } 738 739 target "abc" { 740 name = "xyz" 741 matrix = {} 742 dockerfile = "foo" 743 } 744 `) 745 746 c, err = ParseFile(dt, "docker-bake.hcl") 747 require.NoError(t, err) 748 require.Equal(t, 2, len(c.Targets)) 749 require.Equal(t, "def", c.Targets[0].Name) 750 require.Equal(t, "foo", *c.Targets[0].Dockerfile) 751 require.Equal(t, "xyz", c.Targets[1].Name) 752 require.Equal(t, "foo", *c.Targets[1].Dockerfile) 753 754 dt = []byte(` 755 target "abc" { 756 name = "xyz" 757 matrix = {} 758 dockerfile = "foo" 759 } 760 761 target "def" { 762 dockerfile = target.abc.dockerfile 763 } 764 `) 765 766 _, err = ParseFile(dt, "docker-bake.hcl") 767 require.ErrorContains(t, err, "abc") 768 769 dt = []byte(` 770 target "def" { 771 dockerfile = target.abc.dockerfile 772 } 773 774 target "abc" { 775 name = "xyz" 776 matrix = {} 777 dockerfile = "foo" 778 } 779 `) 780 781 _, err = ParseFile(dt, "docker-bake.hcl") 782 require.ErrorContains(t, err, "abc") 783 } 784 785 func TestHCLRenameSplit(t *testing.T) { 786 dt := []byte(` 787 target "x" { 788 name = "y" 789 matrix = {} 790 dockerfile = "foo" 791 } 792 793 target "x" { 794 name = "z" 795 matrix = {} 796 dockerfile = "bar" 797 } 798 `) 799 800 c, err := ParseFile(dt, "docker-bake.hcl") 801 require.NoError(t, err) 802 803 require.Equal(t, 2, len(c.Targets)) 804 require.Equal(t, "y", c.Targets[0].Name) 805 require.Equal(t, "foo", *c.Targets[0].Dockerfile) 806 require.Equal(t, "z", c.Targets[1].Name) 807 require.Equal(t, "bar", *c.Targets[1].Dockerfile) 808 809 require.Equal(t, 1, len(c.Groups)) 810 require.Equal(t, "x", c.Groups[0].Name) 811 require.Equal(t, []string{"y", "z"}, c.Groups[0].Targets) 812 } 813 814 func TestHCLRenameMultiFile(t *testing.T) { 815 dt := []byte(` 816 target "foo" { 817 name = "bar" 818 matrix = {} 819 dockerfile = "x" 820 } 821 `) 822 dt2 := []byte(` 823 target "foo" { 824 context = "y" 825 } 826 `) 827 dt3 := []byte(` 828 target "bar" { 829 target = "z" 830 } 831 `) 832 833 c, err := ParseFiles([]File{ 834 {Data: dt, Name: "c1.hcl"}, 835 {Data: dt2, Name: "c2.hcl"}, 836 {Data: dt3, Name: "c3.hcl"}, 837 }, nil) 838 require.NoError(t, err) 839 840 require.Equal(t, 2, len(c.Targets)) 841 842 require.Equal(t, c.Targets[0].Name, "bar") 843 require.Equal(t, *c.Targets[0].Dockerfile, "x") 844 require.Equal(t, *c.Targets[0].Target, "z") 845 846 require.Equal(t, c.Targets[1].Name, "foo") 847 require.Equal(t, *c.Targets[1].Context, "y") 848 } 849 850 func TestHCLMatrixBasic(t *testing.T) { 851 dt := []byte(` 852 target "default" { 853 matrix = { 854 foo = ["x", "y"] 855 } 856 name = foo 857 dockerfile = "${foo}.Dockerfile" 858 } 859 `) 860 861 c, err := ParseFile(dt, "docker-bake.hcl") 862 require.NoError(t, err) 863 864 require.Equal(t, 2, len(c.Targets)) 865 require.Equal(t, c.Targets[0].Name, "x") 866 require.Equal(t, c.Targets[1].Name, "y") 867 require.Equal(t, *c.Targets[0].Dockerfile, "x.Dockerfile") 868 require.Equal(t, *c.Targets[1].Dockerfile, "y.Dockerfile") 869 870 require.Equal(t, 1, len(c.Groups)) 871 require.Equal(t, "default", c.Groups[0].Name) 872 require.Equal(t, []string{"x", "y"}, c.Groups[0].Targets) 873 } 874 875 func TestHCLMatrixMultipleKeys(t *testing.T) { 876 dt := []byte(` 877 target "default" { 878 matrix = { 879 foo = ["a"] 880 bar = ["b", "c"] 881 baz = ["d", "e", "f"] 882 } 883 name = "${foo}-${bar}-${baz}" 884 } 885 `) 886 887 c, err := ParseFile(dt, "docker-bake.hcl") 888 require.NoError(t, err) 889 890 require.Equal(t, 6, len(c.Targets)) 891 names := make([]string, len(c.Targets)) 892 for i, t := range c.Targets { 893 names[i] = t.Name 894 } 895 require.ElementsMatch(t, []string{"a-b-d", "a-b-e", "a-b-f", "a-c-d", "a-c-e", "a-c-f"}, names) 896 897 require.Equal(t, 1, len(c.Groups)) 898 require.Equal(t, "default", c.Groups[0].Name) 899 require.ElementsMatch(t, []string{"a-b-d", "a-b-e", "a-b-f", "a-c-d", "a-c-e", "a-c-f"}, c.Groups[0].Targets) 900 } 901 902 func TestHCLMatrixLists(t *testing.T) { 903 dt := []byte(` 904 target "foo" { 905 matrix = { 906 aa = [["aa", "bb"], ["cc", "dd"]] 907 } 908 name = aa[0] 909 args = { 910 target = "val${aa[1]}" 911 } 912 } 913 `) 914 915 c, err := ParseFile(dt, "docker-bake.hcl") 916 require.NoError(t, err) 917 918 require.Equal(t, 2, len(c.Targets)) 919 require.Equal(t, "aa", c.Targets[0].Name) 920 require.Equal(t, ptrstr("valbb"), c.Targets[0].Args["target"]) 921 require.Equal(t, "cc", c.Targets[1].Name) 922 require.Equal(t, ptrstr("valdd"), c.Targets[1].Args["target"]) 923 } 924 925 func TestHCLMatrixMaps(t *testing.T) { 926 dt := []byte(` 927 target "foo" { 928 matrix = { 929 aa = [ 930 { 931 foo = "aa" 932 bar = "bb" 933 }, 934 { 935 foo = "cc" 936 bar = "dd" 937 } 938 ] 939 } 940 name = aa.foo 941 args = { 942 target = "val${aa.bar}" 943 } 944 } 945 `) 946 947 c, err := ParseFile(dt, "docker-bake.hcl") 948 require.NoError(t, err) 949 950 require.Equal(t, 2, len(c.Targets)) 951 require.Equal(t, c.Targets[0].Name, "aa") 952 require.Equal(t, c.Targets[0].Args["target"], ptrstr("valbb")) 953 require.Equal(t, c.Targets[1].Name, "cc") 954 require.Equal(t, c.Targets[1].Args["target"], ptrstr("valdd")) 955 } 956 957 func TestHCLMatrixMultipleTargets(t *testing.T) { 958 dt := []byte(` 959 target "x" { 960 matrix = { 961 foo = ["a", "b"] 962 } 963 name = foo 964 } 965 target "y" { 966 matrix = { 967 bar = ["c", "d"] 968 } 969 name = bar 970 } 971 `) 972 973 c, err := ParseFile(dt, "docker-bake.hcl") 974 require.NoError(t, err) 975 976 require.Equal(t, 4, len(c.Targets)) 977 names := make([]string, len(c.Targets)) 978 for i, t := range c.Targets { 979 names[i] = t.Name 980 } 981 require.ElementsMatch(t, []string{"a", "b", "c", "d"}, names) 982 983 require.Equal(t, 2, len(c.Groups)) 984 names = make([]string, len(c.Groups)) 985 for i, c := range c.Groups { 986 names[i] = c.Name 987 } 988 require.ElementsMatch(t, []string{"x", "y"}, names) 989 990 for _, g := range c.Groups { 991 switch g.Name { 992 case "x": 993 require.Equal(t, []string{"a", "b"}, g.Targets) 994 case "y": 995 require.Equal(t, []string{"c", "d"}, g.Targets) 996 } 997 } 998 } 999 1000 func TestHCLMatrixDuplicateNames(t *testing.T) { 1001 dt := []byte(` 1002 target "default" { 1003 matrix = { 1004 foo = ["a", "b"] 1005 } 1006 name = "c" 1007 } 1008 `) 1009 1010 _, err := ParseFile(dt, "docker-bake.hcl") 1011 require.Error(t, err) 1012 } 1013 1014 func TestHCLMatrixArgs(t *testing.T) { 1015 dt := []byte(` 1016 a = 1 1017 variable "b" { 1018 default = 2 1019 } 1020 target "default" { 1021 matrix = { 1022 foo = [a, b] 1023 } 1024 name = foo 1025 } 1026 `) 1027 1028 c, err := ParseFile(dt, "docker-bake.hcl") 1029 require.NoError(t, err) 1030 1031 require.Equal(t, 2, len(c.Targets)) 1032 require.Equal(t, "1", c.Targets[0].Name) 1033 require.Equal(t, "2", c.Targets[1].Name) 1034 } 1035 1036 func TestHCLMatrixArgsOverride(t *testing.T) { 1037 dt := []byte(` 1038 variable "ABC" { 1039 default = "def" 1040 } 1041 1042 target "bar" { 1043 matrix = { 1044 aa = split(",", ABC) 1045 } 1046 name = "bar-${aa}" 1047 args = { 1048 foo = aa 1049 } 1050 } 1051 `) 1052 1053 c, err := ParseFiles([]File{ 1054 {Data: dt, Name: "docker-bake.hcl"}, 1055 }, map[string]string{"ABC": "11,22,33"}) 1056 require.NoError(t, err) 1057 1058 require.Equal(t, 3, len(c.Targets)) 1059 require.Equal(t, "bar-11", c.Targets[0].Name) 1060 require.Equal(t, "bar-22", c.Targets[1].Name) 1061 require.Equal(t, "bar-33", c.Targets[2].Name) 1062 1063 require.Equal(t, ptrstr("11"), c.Targets[0].Args["foo"]) 1064 require.Equal(t, ptrstr("22"), c.Targets[1].Args["foo"]) 1065 require.Equal(t, ptrstr("33"), c.Targets[2].Args["foo"]) 1066 } 1067 1068 func TestHCLMatrixBadTypes(t *testing.T) { 1069 dt := []byte(` 1070 target "default" { 1071 matrix = "test" 1072 } 1073 `) 1074 _, err := ParseFile(dt, "docker-bake.hcl") 1075 require.Error(t, err) 1076 1077 dt = []byte(` 1078 target "default" { 1079 matrix = ["test"] 1080 } 1081 `) 1082 _, err = ParseFile(dt, "docker-bake.hcl") 1083 require.Error(t, err) 1084 1085 dt = []byte(` 1086 target "default" { 1087 matrix = { 1088 ["a"] = ["b"] 1089 } 1090 } 1091 `) 1092 _, err = ParseFile(dt, "docker-bake.hcl") 1093 require.Error(t, err) 1094 1095 dt = []byte(` 1096 target "default" { 1097 matrix = { 1098 1 = 2 1099 } 1100 } 1101 `) 1102 _, err = ParseFile(dt, "docker-bake.hcl") 1103 require.Error(t, err) 1104 1105 dt = []byte(` 1106 target "default" { 1107 matrix = { 1108 a = "b" 1109 } 1110 } 1111 `) 1112 _, err = ParseFile(dt, "docker-bake.hcl") 1113 require.Error(t, err) 1114 } 1115 1116 func TestHCLMatrixWithGlobalTarget(t *testing.T) { 1117 dt := []byte(` 1118 target "x" { 1119 tags = ["a", "b"] 1120 } 1121 1122 target "default" { 1123 tags = target.x.tags 1124 matrix = { 1125 dummy = [""] 1126 } 1127 } 1128 `) 1129 c, err := ParseFile(dt, "docker-bake.hcl") 1130 require.NoError(t, err) 1131 require.Equal(t, 2, len(c.Targets)) 1132 require.Equal(t, "x", c.Targets[0].Name) 1133 require.Equal(t, "default", c.Targets[1].Name) 1134 require.Equal(t, []string{"a", "b"}, c.Targets[1].Tags) 1135 } 1136 1137 func TestJSONAttributes(t *testing.T) { 1138 dt := []byte(`{"FOO": "abc", "variable": {"BAR": {"default": "def"}}, "target": { "app": { "args": {"v1": "pre-${FOO}-${BAR}"}} } }`) 1139 1140 c, err := ParseFile(dt, "docker-bake.json") 1141 require.NoError(t, err) 1142 1143 require.Equal(t, 1, len(c.Targets)) 1144 require.Equal(t, c.Targets[0].Name, "app") 1145 require.Equal(t, ptrstr("pre-abc-def"), c.Targets[0].Args["v1"]) 1146 } 1147 1148 func TestJSONFunctions(t *testing.T) { 1149 dt := []byte(`{ 1150 "FOO": "abc", 1151 "function": { 1152 "myfunc": { 1153 "params": ["inp"], 1154 "result": "<${upper(inp)}-${FOO}>" 1155 } 1156 }, 1157 "target": { 1158 "app": { 1159 "args": { 1160 "v1": "pre-${myfunc(\"foo\")}" 1161 } 1162 } 1163 }}`) 1164 1165 c, err := ParseFile(dt, "docker-bake.json") 1166 require.NoError(t, err) 1167 1168 require.Equal(t, 1, len(c.Targets)) 1169 require.Equal(t, c.Targets[0].Name, "app") 1170 require.Equal(t, ptrstr("pre-<FOO-abc>"), c.Targets[0].Args["v1"]) 1171 } 1172 1173 func TestJSONInvalidFunctions(t *testing.T) { 1174 dt := []byte(`{ 1175 "target": { 1176 "app": { 1177 "args": { 1178 "v1": "myfunc(\"foo\")" 1179 } 1180 } 1181 }}`) 1182 1183 c, err := ParseFile(dt, "docker-bake.json") 1184 require.NoError(t, err) 1185 1186 require.Equal(t, 1, len(c.Targets)) 1187 require.Equal(t, c.Targets[0].Name, "app") 1188 require.Equal(t, ptrstr(`myfunc("foo")`), c.Targets[0].Args["v1"]) 1189 } 1190 1191 func TestHCLFunctionInAttr(t *testing.T) { 1192 dt := []byte(` 1193 function "brace" { 1194 params = [inp] 1195 result = "[${inp}]" 1196 } 1197 function "myupper" { 1198 params = [val] 1199 result = "${upper(val)} <> ${brace(v2)}" 1200 } 1201 1202 v1=myupper("foo") 1203 v2=lower("BAZ") 1204 target "app" { 1205 args = { 1206 "v1": v1 1207 } 1208 } 1209 `) 1210 1211 c, err := ParseFile(dt, "docker-bake.hcl") 1212 require.NoError(t, err) 1213 1214 require.Equal(t, 1, len(c.Targets)) 1215 require.Equal(t, c.Targets[0].Name, "app") 1216 require.Equal(t, ptrstr("FOO <> [baz]"), c.Targets[0].Args["v1"]) 1217 } 1218 1219 func TestHCLCombineCompose(t *testing.T) { 1220 dt := []byte(` 1221 target "app" { 1222 context = "dir" 1223 args = { 1224 v1 = "foo" 1225 } 1226 } 1227 `) 1228 dt2 := []byte(` 1229 version: "3" 1230 1231 services: 1232 app: 1233 build: 1234 dockerfile: Dockerfile-alternate 1235 args: 1236 v2: "bar" 1237 `) 1238 1239 c, err := ParseFiles([]File{ 1240 {Data: dt, Name: "c1.hcl"}, 1241 {Data: dt2, Name: "c2.yml"}, 1242 }, nil) 1243 require.NoError(t, err) 1244 1245 require.Equal(t, 1, len(c.Targets)) 1246 require.Equal(t, c.Targets[0].Name, "app") 1247 require.Equal(t, ptrstr("foo"), c.Targets[0].Args["v1"]) 1248 require.Equal(t, ptrstr("bar"), c.Targets[0].Args["v2"]) 1249 require.Equal(t, "dir", *c.Targets[0].Context) 1250 require.Equal(t, "Dockerfile-alternate", *c.Targets[0].Dockerfile) 1251 } 1252 1253 func TestHCLBuiltinVars(t *testing.T) { 1254 dt := []byte(` 1255 target "app" { 1256 context = BAKE_CMD_CONTEXT 1257 dockerfile = "test" 1258 } 1259 `) 1260 1261 c, err := ParseFiles([]File{ 1262 {Data: dt, Name: "c1.hcl"}, 1263 }, map[string]string{ 1264 "BAKE_CMD_CONTEXT": "foo", 1265 }) 1266 require.NoError(t, err) 1267 1268 require.Equal(t, 1, len(c.Targets)) 1269 require.Equal(t, c.Targets[0].Name, "app") 1270 require.Equal(t, "foo", *c.Targets[0].Context) 1271 require.Equal(t, "test", *c.Targets[0].Dockerfile) 1272 } 1273 1274 func TestCombineHCLAndJSONTargets(t *testing.T) { 1275 c, err := ParseFiles([]File{ 1276 { 1277 Name: "docker-bake.hcl", 1278 Data: []byte(` 1279 group "default" { 1280 targets = ["a"] 1281 } 1282 1283 target "metadata-a" {} 1284 target "metadata-b" {} 1285 1286 target "a" { 1287 inherits = ["metadata-a"] 1288 context = "." 1289 target = "a" 1290 } 1291 1292 target "b" { 1293 inherits = ["metadata-b"] 1294 context = "." 1295 target = "b" 1296 }`), 1297 }, 1298 { 1299 Name: "metadata-a.json", 1300 Data: []byte(` 1301 { 1302 "target": [{ 1303 "metadata-a": [{ 1304 "tags": [ 1305 "app/a:1.0.0", 1306 "app/a:latest" 1307 ] 1308 }] 1309 }] 1310 }`), 1311 }, 1312 { 1313 Name: "metadata-b.json", 1314 Data: []byte(` 1315 { 1316 "target": [{ 1317 "metadata-b": [{ 1318 "tags": [ 1319 "app/b:1.0.0", 1320 "app/b:latest" 1321 ] 1322 }] 1323 }] 1324 }`), 1325 }, 1326 }, nil) 1327 require.NoError(t, err) 1328 1329 require.Equal(t, 1, len(c.Groups)) 1330 require.Equal(t, "default", c.Groups[0].Name) 1331 require.Equal(t, []string{"a"}, c.Groups[0].Targets) 1332 1333 require.Equal(t, 4, len(c.Targets)) 1334 1335 require.Equal(t, c.Targets[0].Name, "metadata-a") 1336 require.Equal(t, []string{"app/a:1.0.0", "app/a:latest"}, c.Targets[0].Tags) 1337 1338 require.Equal(t, c.Targets[1].Name, "metadata-b") 1339 require.Equal(t, []string{"app/b:1.0.0", "app/b:latest"}, c.Targets[1].Tags) 1340 1341 require.Equal(t, c.Targets[2].Name, "a") 1342 require.Equal(t, ".", *c.Targets[2].Context) 1343 require.Equal(t, "a", *c.Targets[2].Target) 1344 1345 require.Equal(t, c.Targets[3].Name, "b") 1346 require.Equal(t, ".", *c.Targets[3].Context) 1347 require.Equal(t, "b", *c.Targets[3].Target) 1348 } 1349 1350 func TestCombineHCLAndJSONVars(t *testing.T) { 1351 c, err := ParseFiles([]File{ 1352 { 1353 Name: "docker-bake.hcl", 1354 Data: []byte(` 1355 variable "ABC" { 1356 default = "foo" 1357 } 1358 variable "DEF" { 1359 default = "" 1360 } 1361 group "default" { 1362 targets = ["one"] 1363 } 1364 target "one" { 1365 args = { 1366 a = "pre-${ABC}" 1367 } 1368 } 1369 target "two" { 1370 args = { 1371 b = "pre-${DEF}" 1372 } 1373 }`), 1374 }, 1375 { 1376 Name: "foo.json", 1377 Data: []byte(`{"variable": {"DEF": {"default": "bar"}}, "target": { "one": { "args": {"a": "pre-${ABC}-${DEF}"}} } }`), 1378 }, 1379 { 1380 Name: "bar.json", 1381 Data: []byte(`{"ABC": "ghi", "DEF": "jkl"}`), 1382 }, 1383 }, nil) 1384 require.NoError(t, err) 1385 1386 require.Equal(t, 1, len(c.Groups)) 1387 require.Equal(t, "default", c.Groups[0].Name) 1388 require.Equal(t, []string{"one"}, c.Groups[0].Targets) 1389 1390 require.Equal(t, 2, len(c.Targets)) 1391 1392 require.Equal(t, c.Targets[0].Name, "one") 1393 require.Equal(t, map[string]*string{"a": ptrstr("pre-ghi-jkl")}, c.Targets[0].Args) 1394 1395 require.Equal(t, c.Targets[1].Name, "two") 1396 require.Equal(t, map[string]*string{"b": ptrstr("pre-jkl")}, c.Targets[1].Args) 1397 } 1398 1399 func TestEmptyVariableJSON(t *testing.T) { 1400 dt := []byte(`{ 1401 "variable": { 1402 "VAR": {} 1403 } 1404 }`) 1405 _, err := ParseFile(dt, "docker-bake.json") 1406 require.NoError(t, err) 1407 } 1408 1409 func TestFunctionNoParams(t *testing.T) { 1410 dt := []byte(` 1411 function "foo" { 1412 result = "bar" 1413 } 1414 target "foo_target" { 1415 args = { 1416 test = foo() 1417 } 1418 } 1419 `) 1420 1421 _, err := ParseFile(dt, "docker-bake.hcl") 1422 require.Error(t, err) 1423 } 1424 1425 func TestFunctionNoResult(t *testing.T) { 1426 dt := []byte(` 1427 function "foo" { 1428 params = ["a"] 1429 } 1430 `) 1431 1432 _, err := ParseFile(dt, "docker-bake.hcl") 1433 require.Error(t, err) 1434 } 1435 1436 func TestVarUnsupportedType(t *testing.T) { 1437 dt := []byte(` 1438 variable "FOO" { 1439 default = [] 1440 } 1441 target "default" {}`) 1442 1443 t.Setenv("FOO", "bar") 1444 _, err := ParseFile(dt, "docker-bake.hcl") 1445 require.Error(t, err) 1446 } 1447 1448 func TestHCLIndexOfFunc(t *testing.T) { 1449 dt := []byte(` 1450 variable "APP_VERSIONS" { 1451 default = [ 1452 "1.42.4", 1453 "1.42.3" 1454 ] 1455 } 1456 target "default" { 1457 args = { 1458 APP_VERSION = app_version 1459 } 1460 matrix = { 1461 app_version = APP_VERSIONS 1462 } 1463 name="app-${replace(app_version, ".", "-")}" 1464 tags = [ 1465 "app:${app_version}", 1466 indexof(APP_VERSIONS, app_version) == 0 ? "app:latest" : "", 1467 ] 1468 } 1469 `) 1470 1471 c, err := ParseFile(dt, "docker-bake.hcl") 1472 require.NoError(t, err) 1473 1474 require.Equal(t, 2, len(c.Targets)) 1475 require.Equal(t, "app-1-42-4", c.Targets[0].Name) 1476 require.Equal(t, "app:latest", c.Targets[0].Tags[1]) 1477 require.Equal(t, "app-1-42-3", c.Targets[1].Name) 1478 require.Empty(t, c.Targets[1].Tags[1]) 1479 } 1480 1481 func ptrstr(s interface{}) *string { 1482 var n *string 1483 if reflect.ValueOf(s).Kind() == reflect.String { 1484 ss := s.(string) 1485 n = &ss 1486 } 1487 return n 1488 }