diff --git a/bake/hcl.go b/bake/hcl.go index de9d1840..e24110ae 100644 --- a/bake/hcl.go +++ b/bake/hcl.go @@ -133,6 +133,8 @@ type StaticConfig struct { Variables []*Variable `hcl:"variable,block"` Remain hcl.Body `hcl:",remain"` + attrs hcl.Attributes + defaults map[string]*hcl.Attribute env map[string]string values map[string]cty.Value @@ -146,6 +148,9 @@ func mergeStaticConfig(scs []*StaticConfig) *StaticConfig { sc := scs[0] for _, s := range scs[1:] { sc.Variables = append(sc.Variables, s.Variables...) + for k, v := range s.attrs { + sc.attrs[k] = v + } } return sc } @@ -169,6 +174,12 @@ func (sc *StaticConfig) Values(withEnv bool) (map[string]cty.Value, error) { sc.values = map[string]cty.Value{} sc.progress = map[string]struct{}{} + for k := range sc.attrs { + if _, err := sc.resolveValue(k); err != nil { + return nil, err + } + } + for k := range sc.defaults { if _, err := sc.resolveValue(k); err != nil { return nil, err @@ -192,10 +203,12 @@ func (sc *StaticConfig) resolveValue(name string) (v *cty.Value, err error) { } }() - def, ok := sc.defaults[name] - + def, ok := sc.attrs[name] if !ok { - return nil, errors.Errorf("undefined variable %q", name) + def, ok = sc.defaults[name] + if !ok { + return nil, errors.Errorf("undefined variable %q", name) + } } if def == nil { @@ -233,7 +246,9 @@ func (sc *StaticConfig) resolveValue(name string) (v *cty.Value, err error) { return nil, diags } - if envv, ok := sc.env[name]; ok { + _, isVar := sc.defaults[name] + + if envv, ok := sc.env[name]; ok && isVar { if vv.Type().Equals(cty.Bool) { b, err := strconv.ParseBool(envv) if err != nil { @@ -302,6 +317,16 @@ func parseHCLFile(dt []byte, fn string) (f *hcl.File, _ *StaticConfig, err error return nil, nil, err } + attrs, diags := f.Body.JustAttributes() + if diags.HasErrors() { + for _, d := range diags { + if d.Detail != "Blocks are not allowed here." { + return nil, nil, diags + } + } + } + sc.attrs = attrs + return f, &sc, nil } diff --git a/bake/hcl_test.go b/bake/hcl_test.go index 1429f025..70af0e3d 100644 --- a/bake/hcl_test.go +++ b/bake/hcl_test.go @@ -415,3 +415,101 @@ func TestHCLVariableCycle(t *testing.T) { require.Error(t, err) require.Contains(t, err.Error(), "variable cycle not allowed") } + +func TestHCLAttrs(t *testing.T) { + dt := []byte(` + FOO="abc" + BAR="attr-${FOO}def" + target "app" { + args = { + "v1": BAR + } + } + `) + + c, err := ParseFile(dt, "docker-bake.hcl") + require.NoError(t, err) + + require.Equal(t, 1, len(c.Targets)) + require.Equal(t, c.Targets[0].Name, "app") + require.Equal(t, "attr-abcdef", c.Targets[0].Args["v1"]) + + // env does not apply if no variable + os.Setenv("FOO", "bar") + c, err = ParseFile(dt, "docker-bake.hcl") + require.NoError(t, err) + + require.Equal(t, 1, len(c.Targets)) + require.Equal(t, c.Targets[0].Name, "app") + require.Equal(t, "attr-abcdef", c.Targets[0].Args["v1"]) + // attr-multifile +} + +func TestHCLAttrsCustomType(t *testing.T) { + dt := []byte(` + platforms=["linux/arm64", "linux/amd64"] + target "app" { + platforms = platforms + args = { + "v1": platforms[0] + } + } + `) + + c, err := ParseFile(dt, "docker-bake.hcl") + require.NoError(t, err) + + require.Equal(t, 1, len(c.Targets)) + require.Equal(t, c.Targets[0].Name, "app") + require.Equal(t, []string{"linux/arm64", "linux/amd64"}, c.Targets[0].Platforms) + require.Equal(t, "linux/arm64", c.Targets[0].Args["v1"]) +} + +func TestHCLMultiFileAttrs(t *testing.T) { + os.Unsetenv("FOO") + dt := []byte(` + variable "FOO" { + default = "abc" + } + target "app" { + args = { + v1 = "pre-${FOO}" + } + } + `) + dt2 := []byte(` + FOO="def" + `) + + c, err := parseFiles([]File{ + {Data: dt, Name: "c1.hcl"}, + {Data: dt2, Name: "c2.hcl"}, + }) + require.NoError(t, err) + require.Equal(t, 1, len(c.Targets)) + require.Equal(t, c.Targets[0].Name, "app") + require.Equal(t, "pre-def", c.Targets[0].Args["v1"]) + + os.Setenv("FOO", "ghi") + + c, err = parseFiles([]File{ + {Data: dt, Name: "c1.hcl"}, + {Data: dt2, Name: "c2.hcl"}, + }) + require.NoError(t, err) + + require.Equal(t, 1, len(c.Targets)) + require.Equal(t, c.Targets[0].Name, "app") + require.Equal(t, "pre-ghi", c.Targets[0].Args["v1"]) +} + +func TestJSONAttributes(t *testing.T) { + dt := []byte(`{"FOO": "abc", "variable": {"BAR": {"default": "def"}}, "target": { "app": { "args": {"v1": "pre-${FOO}-${BAR}"}} } }`) + + c, err := ParseFile(dt, "docker-bake.json") + require.NoError(t, err) + + require.Equal(t, 1, len(c.Targets)) + require.Equal(t, c.Targets[0].Name, "app") + require.Equal(t, "pre-abc-def", c.Targets[0].Args["v1"]) +}