diff --git a/bake/bake.go b/bake/bake.go index 8322edbb3ae7..430c74777bd3 100644 --- a/bake/bake.go +++ b/bake/bake.go @@ -16,6 +16,7 @@ import ( composecli "github.com/compose-spec/compose-go/v2/cli" "github.com/docker/buildx/bake/hclparser" + "github.com/docker/buildx/bake/hclparser/gohcl" "github.com/docker/buildx/build" controllerapi "github.com/docker/buildx/controller/pb" "github.com/docker/buildx/util/buildflags" @@ -343,7 +344,7 @@ func ParseFiles(files []File, defaults map[string]string) (_ *Config, _ *hclpars var pm hclparser.ParseMeta if len(hclFiles) > 0 { - res, err := hclparser.Parse(hclparser.MergeFiles(hclFiles), hclparser.Opt{ + res, err := hclparser.Parse(gohcl.MergeFiles(hclFiles), hclparser.Opt{ LookupVar: os.LookupEnv, Vars: defaults, ValidateLabel: validateTargetName, diff --git a/bake/hclparser/merged.go b/bake/hclparser/gohcl/merged.go similarity index 98% rename from bake/hclparser/merged.go rename to bake/hclparser/gohcl/merged.go index 7fdf12347823..69c977ff15fb 100644 --- a/bake/hclparser/merged.go +++ b/bake/hclparser/gohcl/merged.go @@ -1,9 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -// Forked from https://github.com/hashicorp/hcl/blob/4679383728fe331fc8a6b46036a27b8f818d9bc0/merged.go - -package hclparser +package gohcl import ( "fmt" diff --git a/bake/hclparser/gohcl/merged_test.go b/bake/hclparser/gohcl/merged_test.go new file mode 100644 index 000000000000..881f67c13983 --- /dev/null +++ b/bake/hclparser/gohcl/merged_test.go @@ -0,0 +1,687 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package gohcl + +import ( + "fmt" + "reflect" + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/hashicorp/hcl/v2" +) + +func TestMergedBodiesContent(t *testing.T) { + tests := []struct { + Bodies []hcl.Body + Schema *hcl.BodySchema + Want *hcl.BodyContent + DiagCount int + }{ + { + []hcl.Body{}, + &hcl.BodySchema{}, + &hcl.BodyContent{ + Attributes: map[string]*hcl.Attribute{}, + }, + 0, + }, + { + []hcl.Body{}, + &hcl.BodySchema{ + Attributes: []hcl.AttributeSchema{ + { + Name: "name", + }, + }, + }, + &hcl.BodyContent{ + Attributes: map[string]*hcl.Attribute{}, + }, + 0, + }, + { + []hcl.Body{}, + &hcl.BodySchema{ + Attributes: []hcl.AttributeSchema{ + { + Name: "name", + Required: true, + }, + }, + }, + &hcl.BodyContent{ + Attributes: map[string]*hcl.Attribute{}, + }, + 1, + }, + { + []hcl.Body{ + &testMergedBodiesVictim{ + HasAttributes: []string{"name"}, + }, + }, + &hcl.BodySchema{ + Attributes: []hcl.AttributeSchema{ + { + Name: "name", + }, + }, + }, + &hcl.BodyContent{ + Attributes: map[string]*hcl.Attribute{ + "name": { + Name: "name", + }, + }, + }, + 0, + }, + { + []hcl.Body{ + &testMergedBodiesVictim{ + Name: "first", + HasAttributes: []string{"name"}, + }, + &testMergedBodiesVictim{ + Name: "second", + HasAttributes: []string{"name"}, + }, + }, + &hcl.BodySchema{ + Attributes: []hcl.AttributeSchema{ + { + Name: "name", + }, + }, + }, + &hcl.BodyContent{ + Attributes: map[string]*hcl.Attribute{ + "name": { + Name: "name", + NameRange: hcl.Range{Filename: "second"}, + }, + }, + }, + 1, + }, + { + []hcl.Body{ + &testMergedBodiesVictim{ + Name: "first", + HasAttributes: []string{"name"}, + }, + &testMergedBodiesVictim{ + Name: "second", + HasAttributes: []string{"age"}, + }, + }, + &hcl.BodySchema{ + Attributes: []hcl.AttributeSchema{ + { + Name: "name", + }, + { + Name: "age", + }, + }, + }, + &hcl.BodyContent{ + Attributes: map[string]*hcl.Attribute{ + "name": { + Name: "name", + NameRange: hcl.Range{Filename: "first"}, + }, + "age": { + Name: "age", + NameRange: hcl.Range{Filename: "second"}, + }, + }, + }, + 0, + }, + { + []hcl.Body{}, + &hcl.BodySchema{ + Blocks: []hcl.BlockHeaderSchema{ + { + Type: "pizza", + }, + }, + }, + &hcl.BodyContent{ + Attributes: map[string]*hcl.Attribute{}, + }, + 0, + }, + { + []hcl.Body{ + &testMergedBodiesVictim{ + HasBlocks: map[string]int{ + "pizza": 1, + }, + }, + }, + &hcl.BodySchema{ + Blocks: []hcl.BlockHeaderSchema{ + { + Type: "pizza", + }, + }, + }, + &hcl.BodyContent{ + Attributes: map[string]*hcl.Attribute{}, + Blocks: hcl.Blocks{ + { + Type: "pizza", + }, + }, + }, + 0, + }, + { + []hcl.Body{ + &testMergedBodiesVictim{ + HasBlocks: map[string]int{ + "pizza": 2, + }, + }, + }, + &hcl.BodySchema{ + Blocks: []hcl.BlockHeaderSchema{ + { + Type: "pizza", + }, + }, + }, + &hcl.BodyContent{ + Attributes: map[string]*hcl.Attribute{}, + Blocks: hcl.Blocks{ + { + Type: "pizza", + }, + { + Type: "pizza", + }, + }, + }, + 0, + }, + { + []hcl.Body{ + &testMergedBodiesVictim{ + Name: "first", + HasBlocks: map[string]int{ + "pizza": 1, + }, + }, + &testMergedBodiesVictim{ + Name: "second", + HasBlocks: map[string]int{ + "pizza": 1, + }, + }, + }, + &hcl.BodySchema{ + Blocks: []hcl.BlockHeaderSchema{ + { + Type: "pizza", + }, + }, + }, + &hcl.BodyContent{ + Attributes: map[string]*hcl.Attribute{}, + Blocks: hcl.Blocks{ + { + Type: "pizza", + DefRange: hcl.Range{Filename: "first"}, + }, + { + Type: "pizza", + DefRange: hcl.Range{Filename: "second"}, + }, + }, + }, + 0, + }, + { + []hcl.Body{ + &testMergedBodiesVictim{ + Name: "first", + }, + &testMergedBodiesVictim{ + Name: "second", + HasBlocks: map[string]int{ + "pizza": 2, + }, + }, + }, + &hcl.BodySchema{ + Blocks: []hcl.BlockHeaderSchema{ + { + Type: "pizza", + }, + }, + }, + &hcl.BodyContent{ + Attributes: map[string]*hcl.Attribute{}, + Blocks: hcl.Blocks{ + { + Type: "pizza", + DefRange: hcl.Range{Filename: "second"}, + }, + { + Type: "pizza", + DefRange: hcl.Range{Filename: "second"}, + }, + }, + }, + 0, + }, + { + []hcl.Body{ + &testMergedBodiesVictim{ + Name: "first", + HasBlocks: map[string]int{ + "pizza": 2, + }, + }, + &testMergedBodiesVictim{ + Name: "second", + }, + }, + &hcl.BodySchema{ + Blocks: []hcl.BlockHeaderSchema{ + { + Type: "pizza", + }, + }, + }, + &hcl.BodyContent{ + Attributes: map[string]*hcl.Attribute{}, + Blocks: hcl.Blocks{ + { + Type: "pizza", + DefRange: hcl.Range{Filename: "first"}, + }, + { + Type: "pizza", + DefRange: hcl.Range{Filename: "first"}, + }, + }, + }, + 0, + }, + { + []hcl.Body{ + &testMergedBodiesVictim{ + Name: "first", + }, + &testMergedBodiesVictim{ + Name: "second", + }, + }, + &hcl.BodySchema{ + Blocks: []hcl.BlockHeaderSchema{ + { + Type: "pizza", + }, + }, + }, + &hcl.BodyContent{ + Attributes: map[string]*hcl.Attribute{}, + }, + 0, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { + merged := MergeBodies(test.Bodies) + got, diags := merged.Content(test.Schema) + + if len(diags) != test.DiagCount { + t.Errorf("Wrong number of diagnostics %d; want %d", len(diags), test.DiagCount) + for _, diag := range diags { + t.Logf(" - %s", diag) + } + } + + if !reflect.DeepEqual(got, test.Want) { + t.Errorf("wrong result\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.Want)) + } + }) + } +} + +func TestMergeBodiesPartialContent(t *testing.T) { + tests := []struct { + Bodies []hcl.Body + Schema *hcl.BodySchema + WantContent *hcl.BodyContent + WantRemain hcl.Body + DiagCount int + }{ + { + []hcl.Body{}, + &hcl.BodySchema{}, + &hcl.BodyContent{ + Attributes: map[string]*hcl.Attribute{}, + }, + mergedBodies{}, + 0, + }, + { + []hcl.Body{ + &testMergedBodiesVictim{ + Name: "first", + HasAttributes: []string{"name", "age"}, + }, + }, + &hcl.BodySchema{ + Attributes: []hcl.AttributeSchema{ + { + Name: "name", + }, + }, + }, + &hcl.BodyContent{ + Attributes: map[string]*hcl.Attribute{ + "name": { + Name: "name", + NameRange: hcl.Range{Filename: "first"}, + }, + }, + }, + mergedBodies{ + &testMergedBodiesVictim{ + Name: "first", + HasAttributes: []string{"age"}, + }, + }, + 0, + }, + { + []hcl.Body{ + &testMergedBodiesVictim{ + Name: "first", + HasAttributes: []string{"name", "age"}, + }, + &testMergedBodiesVictim{ + Name: "second", + HasAttributes: []string{"name", "pizza"}, + }, + }, + &hcl.BodySchema{ + Attributes: []hcl.AttributeSchema{ + { + Name: "name", + }, + }, + }, + &hcl.BodyContent{ + Attributes: map[string]*hcl.Attribute{ + "name": { + Name: "name", + NameRange: hcl.Range{Filename: "second"}, + }, + }, + }, + mergedBodies{ + &testMergedBodiesVictim{ + Name: "first", + HasAttributes: []string{"age"}, + }, + &testMergedBodiesVictim{ + Name: "second", + HasAttributes: []string{"pizza"}, + }, + }, + 1, + }, + { + []hcl.Body{ + &testMergedBodiesVictim{ + Name: "first", + HasAttributes: []string{"name", "age"}, + }, + &testMergedBodiesVictim{ + Name: "second", + HasAttributes: []string{"pizza", "soda"}, + }, + }, + &hcl.BodySchema{ + Attributes: []hcl.AttributeSchema{ + { + Name: "name", + }, + { + Name: "soda", + }, + }, + }, + &hcl.BodyContent{ + Attributes: map[string]*hcl.Attribute{ + "name": { + Name: "name", + NameRange: hcl.Range{Filename: "first"}, + }, + "soda": { + Name: "soda", + NameRange: hcl.Range{Filename: "second"}, + }, + }, + }, + mergedBodies{ + &testMergedBodiesVictim{ + Name: "first", + HasAttributes: []string{"age"}, + }, + &testMergedBodiesVictim{ + Name: "second", + HasAttributes: []string{"pizza"}, + }, + }, + 0, + }, + { + []hcl.Body{ + &testMergedBodiesVictim{ + Name: "first", + HasBlocks: map[string]int{ + "pizza": 1, + }, + }, + &testMergedBodiesVictim{ + Name: "second", + HasBlocks: map[string]int{ + "pizza": 1, + "soda": 2, + }, + }, + }, + &hcl.BodySchema{ + Blocks: []hcl.BlockHeaderSchema{ + { + Type: "pizza", + }, + }, + }, + &hcl.BodyContent{ + Attributes: map[string]*hcl.Attribute{}, + Blocks: hcl.Blocks{ + { + Type: "pizza", + DefRange: hcl.Range{Filename: "first"}, + }, + { + Type: "pizza", + DefRange: hcl.Range{Filename: "second"}, + }, + }, + }, + mergedBodies{ + &testMergedBodiesVictim{ + Name: "first", + HasAttributes: []string{}, + HasBlocks: map[string]int{}, + }, + &testMergedBodiesVictim{ + Name: "second", + HasAttributes: []string{}, + HasBlocks: map[string]int{ + "soda": 2, + }, + }, + }, + 0, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { + merged := MergeBodies(test.Bodies) + got, gotRemain, diags := merged.PartialContent(test.Schema) + + if len(diags) != test.DiagCount { + t.Errorf("Wrong number of diagnostics %d; want %d", len(diags), test.DiagCount) + for _, diag := range diags { + t.Logf(" - %s", diag) + } + } + + if !reflect.DeepEqual(got, test.WantContent) { + t.Errorf("wrong content result\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.WantContent)) + } + + if !reflect.DeepEqual(gotRemain, test.WantRemain) { + t.Errorf("wrong remaining result\ngot: %s\nwant: %s", spew.Sdump(gotRemain), spew.Sdump(test.WantRemain)) + } + }) + } +} + +type testMergedBodiesVictim struct { + Name string + HasAttributes []string + HasBlocks map[string]int + DiagCount int +} + +func (v *testMergedBodiesVictim) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) { + c, _, d := v.PartialContent(schema) + return c, d +} + +func (v *testMergedBodiesVictim) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) { + remain := &testMergedBodiesVictim{ + Name: v.Name, + HasAttributes: []string{}, + } + + hasAttrs := map[string]struct{}{} + for _, n := range v.HasAttributes { + hasAttrs[n] = struct{}{} + + var found bool + for _, attrS := range schema.Attributes { + if n == attrS.Name { + found = true + break + } + } + if !found { + remain.HasAttributes = append(remain.HasAttributes, n) + } + } + + content := &hcl.BodyContent{ + Attributes: map[string]*hcl.Attribute{}, + } + + rng := hcl.Range{ + Filename: v.Name, + } + + for _, attrS := range schema.Attributes { + _, has := hasAttrs[attrS.Name] + if has { + content.Attributes[attrS.Name] = &hcl.Attribute{ + Name: attrS.Name, + NameRange: rng, + } + } + } + + if v.HasBlocks != nil { + for _, blockS := range schema.Blocks { + num := v.HasBlocks[blockS.Type] + for i := 0; i < num; i++ { + content.Blocks = append(content.Blocks, &hcl.Block{ + Type: blockS.Type, + DefRange: rng, + }) + } + } + + remain.HasBlocks = map[string]int{} + for n := range v.HasBlocks { + var found bool + for _, blockS := range schema.Blocks { + if blockS.Type == n { + found = true + break + } + } + if !found { + remain.HasBlocks[n] = v.HasBlocks[n] + } + } + } + + diags := make(hcl.Diagnostics, v.DiagCount) + for i := range diags { + diags[i] = &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: fmt.Sprintf("Fake diagnostic %d", i), + Detail: "For testing only.", + Context: &rng, + } + } + + return content, remain, diags +} + +func (v *testMergedBodiesVictim) JustAttributes() (hcl.Attributes, hcl.Diagnostics) { + attrs := make(map[string]*hcl.Attribute) + + rng := hcl.Range{ + Filename: v.Name, + } + + for _, name := range v.HasAttributes { + attrs[name] = &hcl.Attribute{ + Name: name, + NameRange: rng, + } + } + + diags := make(hcl.Diagnostics, v.DiagCount) + for i := range diags { + diags[i] = &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: fmt.Sprintf("Fake diagnostic %d", i), + Detail: "For testing only.", + Context: &rng, + } + } + + return attrs, diags +} + +func (v *testMergedBodiesVictim) MissingItemRange() hcl.Range { + return hcl.Range{ + Filename: v.Name, + } +}