From 080687026111e6a03bc5d1030d9d231db03f74d5 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Wed, 22 Mar 2023 17:07:04 +0000 Subject: [PATCH] bake: generate implicit groups for matrixes Signed-off-by: Justin Chadwell --- bake/bake.go | 19 +++++++- bake/hcl_test.go | 92 +++++++++++++++++++++++++++++++++++-- bake/hclparser/hclparser.go | 37 ++++++++------- 3 files changed, 126 insertions(+), 22 deletions(-) diff --git a/bake/bake.go b/bake/bake.go index 470de87d..3760dae4 100644 --- a/bake/bake.go +++ b/bake/bake.go @@ -246,13 +246,28 @@ func ParseFiles(files []File, defaults map[string]string) (_ *Config, err error) } if len(hclFiles) > 0 { - if err := hclparser.Parse(hcl.MergeFiles(hclFiles), hclparser.Opt{ + renamed, err := hclparser.Parse(hcl.MergeFiles(hclFiles), hclparser.Opt{ LookupVar: os.LookupEnv, Vars: defaults, ValidateLabel: validateTargetName, - }, &c); err.HasErrors() { + }, &c) + if err.HasErrors() { return nil, err } + + for _, renamed := range renamed { + for oldName, newNames := range renamed { + newNames = dedupSlice(newNames) + if len(newNames) == 1 && oldName == newNames[0] { + continue + } + c.Groups = append(c.Groups, &Group{ + Name: oldName, + Targets: newNames, + }) + } + } + c = dedupeConfig(c) } return &c, nil diff --git a/bake/hcl_test.go b/bake/hcl_test.go index 95d3b193..1580c04f 100644 --- a/bake/hcl_test.go +++ b/bake/hcl_test.go @@ -666,6 +666,37 @@ func TestHCLRenameTarget(t *testing.T) { require.Equal(t, 1, len(c.Targets)) require.Equal(t, "xyz", c.Targets[0].Name) require.Equal(t, "foo", *c.Targets[0].Dockerfile) + + require.Equal(t, 1, len(c.Groups)) + require.Equal(t, "abc", c.Groups[0].Name) + require.Equal(t, []string{"xyz"}, c.Groups[0].Targets) +} + +func TestHCLRenameGroup(t *testing.T) { + dt := []byte(` + group "foo" { + name = "bar" + targets = ["x", "y"] + } + + target "x" { + } + target "y" { + } + `) + + c, err := ParseFile(dt, "docker-bake.hcl") + require.NoError(t, err) + + require.Equal(t, 2, len(c.Targets)) + require.Equal(t, "x", c.Targets[0].Name) + require.Equal(t, "y", c.Targets[1].Name) + + require.Equal(t, 2, len(c.Groups)) + require.Equal(t, "bar", c.Groups[0].Name) + require.Equal(t, []string{"x", "y"}, c.Groups[0].Targets) + require.Equal(t, "foo", c.Groups[1].Name) + require.Equal(t, []string{"bar"}, c.Groups[1].Targets) } func TestHCLRenameTargetAttrs(t *testing.T) { @@ -736,7 +767,7 @@ func TestHCLRenameTargetAttrs(t *testing.T) { require.Error(t, err) } -func TestHCLRenameMerge(t *testing.T) { +func TestHCLRenameSplit(t *testing.T) { dt := []byte(` target "x" { name = "y" @@ -757,6 +788,10 @@ func TestHCLRenameMerge(t *testing.T) { require.Equal(t, "foo", *c.Targets[0].Dockerfile) require.Equal(t, "z", c.Targets[1].Name) require.Equal(t, "bar", *c.Targets[1].Dockerfile) + + require.Equal(t, 1, len(c.Groups)) + require.Equal(t, "x", c.Groups[0].Name) + require.Equal(t, []string{"y", "z"}, c.Groups[0].Targets) } func TestHCLRenameMultiFile(t *testing.T) { @@ -813,9 +848,13 @@ func TestHCLMatrixBasic(t *testing.T) { require.Equal(t, c.Targets[1].Name, "y") require.Equal(t, *c.Targets[0].Dockerfile, "x.Dockerfile") require.Equal(t, *c.Targets[1].Dockerfile, "y.Dockerfile") + + require.Equal(t, 1, len(c.Groups)) + require.Equal(t, "default", c.Groups[0].Name) + require.Equal(t, []string{"x", "y"}, c.Groups[0].Targets) } -func TestHCLMatrixMultiple(t *testing.T) { +func TestHCLMatrixMultipleKeys(t *testing.T) { dt := []byte(` target "default" { matrix = { @@ -835,7 +874,54 @@ func TestHCLMatrixMultiple(t *testing.T) { for i, t := range c.Targets { names[i] = t.Name } - require.ElementsMatch(t, names, []string{"a-b-d", "a-b-e", "a-b-f", "a-c-d", "a-c-e", "a-c-f"}) + require.ElementsMatch(t, []string{"a-b-d", "a-b-e", "a-b-f", "a-c-d", "a-c-e", "a-c-f"}, names) + + require.Equal(t, 1, len(c.Groups)) + require.Equal(t, "default", c.Groups[0].Name) + 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) +} + +func TestHCLMatrixMultipleTargets(t *testing.T) { + dt := []byte(` + target "x" { + matrix = { + foo = ["a", "b"] + } + name = foo + } + target "y" { + matrix = { + bar = ["c", "d"] + } + name = bar + } + `) + + c, err := ParseFile(dt, "docker-bake.hcl") + require.NoError(t, err) + + require.Equal(t, 4, len(c.Targets)) + names := make([]string, len(c.Targets)) + for i, t := range c.Targets { + names[i] = t.Name + } + require.ElementsMatch(t, []string{"a", "b", "c", "d"}, names) + + require.Equal(t, 2, len(c.Groups)) + names = make([]string, len(c.Groups)) + for i, c := range c.Groups { + names[i] = c.Name + } + require.ElementsMatch(t, []string{"x", "y"}, names) + + for _, g := range c.Groups { + switch g.Name { + case "x": + require.Equal(t, []string{"a", "b"}, g.Targets) + case "y": + require.Equal(t, []string{"c", "d"}, g.Targets) + } + } } func TestHCLMatrixArgs(t *testing.T) { diff --git a/bake/hclparser/hclparser.go b/bake/hclparser/hclparser.go index d36708d0..04e11630 100644 --- a/bake/hclparser/hclparser.go +++ b/bake/hclparser/hclparser.go @@ -531,7 +531,7 @@ func (p *parser) resolveBlockNames(block *hcl.Block) ([]string, error) { return names, nil } -func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics { +func Parse(b hcl.Body, opt Opt, val interface{}) (map[string]map[string][]string, hcl.Diagnostics) { reserved := map[string]struct{}{} schema, _ := gohcl.ImpliedBodySchema(val) @@ -544,7 +544,7 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics { var defs inputs if err := gohcl.DecodeBody(b, nil, &defs); err != nil { - return err + return nil, err } defsSchema, _ := gohcl.ImpliedBodySchema(defs) @@ -599,18 +599,18 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics { content, b, diags := b.PartialContent(schema) if diags.HasErrors() { - return diags + return nil, diags } blocks, b, diags := b.PartialContent(defsSchema) if diags.HasErrors() { - return diags + return nil, diags } attrs, diags := b.JustAttributes() if diags.HasErrors() { if d := removeAttributesDiags(diags, reserved, p.vars); len(d) > 0 { - return d + return nil, d } } @@ -627,7 +627,7 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics { } for _, a := range content.Attributes { - return hcl.Diagnostics{ + return nil, hcl.Diagnostics{ &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid attribute", @@ -641,17 +641,17 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics { for k := range p.vars { if err := p.resolveValue(p.ectx, k); err != nil { if diags, ok := err.(hcl.Diagnostics); ok { - return diags + return nil, diags } r := p.vars[k].Body.MissingItemRange() - return wrapErrorDiagnostic("Invalid value", err, &r, &r) + return nil, wrapErrorDiagnostic("Invalid value", err, &r, &r) } } for k := range p.funcs { if err := p.resolveFunction(p.ectx, k); err != nil { if diags, ok := err.(hcl.Diagnostics); ok { - return diags + return nil, diags } var subject *hcl.Range var context *hcl.Range @@ -667,7 +667,7 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics { } } } - return wrapErrorDiagnostic("Invalid function", err, subject, context) + return nil, wrapErrorDiagnostic("Invalid function", err, subject, context) } } @@ -681,6 +681,7 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics { values map[string]value } types := map[string]field{} + renamed := map[string]map[string][]string{} vt := reflect.ValueOf(val).Elem().Type() for i := 0; i < vt.NumField(); i++ { tags := strings.Split(vt.Field(i).Tag.Get("hcl"), ",") @@ -691,12 +692,13 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics { typ: vt.Field(i).Type, values: make(map[string]value), } + renamed[tags[0]] = map[string][]string{} } tmpBlocks := map[string]map[string][]*hcl.Block{} for _, b := range content.Blocks { if len(b.Labels) == 0 || len(b.Labels) > 1 { - return hcl.Diagnostics{ + return nil, hcl.Diagnostics{ &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid block", @@ -715,10 +717,11 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics { names, err := p.resolveBlockNames(b) if err != nil { - return wrapErrorDiagnostic("Invalid name", err, &b.LabelRanges[0], &b.LabelRanges[0]) + return nil, wrapErrorDiagnostic("Invalid name", err, &b.LabelRanges[0], &b.LabelRanges[0]) } for _, name := range names { bm[name] = append(bm[name], b) + renamed[b.Type][b.Labels[0]] = append(renamed[b.Type][b.Labels[0]], name) } } p.blocks = tmpBlocks @@ -735,7 +738,7 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics { continue } } else { - return wrapErrorDiagnostic("Invalid block", err, &b.LabelRanges[0], &b.DefRange) + return nil, wrapErrorDiagnostic("Invalid block", err, &b.LabelRanges[0], &b.DefRange) } } @@ -773,19 +776,19 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics { } } if diags.HasErrors() { - return diags + return nil, diags } for k := range p.attrs { if err := p.resolveValue(p.ectx, k); err != nil { if diags, ok := err.(hcl.Diagnostics); ok { - return diags + return nil, diags } - return wrapErrorDiagnostic("Invalid attribute", err, &p.attrs[k].Range, &p.attrs[k].Range) + return nil, wrapErrorDiagnostic("Invalid attribute", err, &p.attrs[k].Range, &p.attrs[k].Range) } } - return nil + return renamed, nil } // wrapErrorDiagnostic wraps an error into a hcl.Diagnostics object.