diff --git a/bake/hclparser/hclparser.go b/bake/hclparser/hclparser.go index ec378cf9..e68c2a8c 100644 --- a/bake/hclparser/hclparser.go +++ b/bake/hclparser/hclparser.go @@ -49,29 +49,29 @@ type parser struct { attrs map[string]*hcl.Attribute funcs map[string]*functionDef - blocks map[string]map[string][]*hcl.Block - blockValues map[*hcl.Block]reflect.Value - blockTypes map[string]reflect.Type + blocks map[string]map[string][]*hcl.Block + blockValues map[*hcl.Block]reflect.Value + blockEvalCtx map[*hcl.Block]*hcl.EvalContext + blockTypes map[string]reflect.Type ectx *hcl.EvalContext progress map[string]struct{} progressF map[string]struct{} progressB map[*hcl.Block]map[string]struct{} - doneF map[string]struct{} doneB map[*hcl.Block]map[string]struct{} } var errUndefined = errors.New("undefined") -func (p *parser) loadDeps(exp hcl.Expression, exclude map[string]struct{}, allowMissing bool) hcl.Diagnostics { +func (p *parser) loadDeps(ectx *hcl.EvalContext, exp hcl.Expression, exclude map[string]struct{}, allowMissing bool) hcl.Diagnostics { fns, hcldiags := funcCalls(exp) if hcldiags.HasErrors() { return hcldiags } for _, fn := range fns { - if err := p.resolveFunction(fn); err != nil { + if err := p.resolveFunction(ectx, fn); err != nil { if allowMissing && errors.Is(err, errUndefined) { continue } @@ -131,7 +131,7 @@ func (p *parser) loadDeps(exp hcl.Expression, exclude map[string]struct{}, allow return wrapErrorDiagnostic("Invalid expression", err, exp.Range().Ptr(), exp.Range().Ptr()) } } else { - if err := p.resolveValue(v.RootName()); err != nil { + if err := p.resolveValue(ectx, v.RootName()); err != nil { if allowMissing && errors.Is(err, errUndefined) { continue } @@ -145,16 +145,16 @@ func (p *parser) loadDeps(exp hcl.Expression, exclude map[string]struct{}, allow // resolveFunction forces evaluation of a function, storing the result into the // parser. -func (p *parser) resolveFunction(name string) error { - if _, ok := p.doneF[name]; ok { +func (p *parser) resolveFunction(ectx *hcl.EvalContext, name string) error { + if _, ok := p.ectx.Functions[name]; ok { + return nil + } + if _, ok := ectx.Functions[name]; ok { return nil } f, ok := p.funcs[name] if !ok { - if _, ok := p.ectx.Functions[name]; ok { - return nil - } - return errors.Wrapf(errUndefined, "function %q does not exit", name) + return errors.Wrapf(errUndefined, "function %q does not exist", name) } if _, ok := p.progressF[name]; ok { return errors.Errorf("function cycle not allowed for %s", name) @@ -204,7 +204,7 @@ func (p *parser) resolveFunction(name string) error { return diags } - if diags := p.loadDeps(f.Result.Expr, params, false); diags.HasErrors() { + if diags := p.loadDeps(p.ectx, f.Result.Expr, params, false); diags.HasErrors() { return diags } @@ -214,7 +214,6 @@ func (p *parser) resolveFunction(name string) error { if diags.HasErrors() { return diags } - p.doneF[name] = struct{}{} p.ectx.Functions[name] = v return nil @@ -222,10 +221,13 @@ func (p *parser) resolveFunction(name string) error { // resolveValue forces evaluation of a named value, storing the result into the // parser. -func (p *parser) resolveValue(name string) (err error) { +func (p *parser) resolveValue(ectx *hcl.EvalContext, name string) (err error) { if _, ok := p.ectx.Variables[name]; ok { return nil } + if _, ok := ectx.Variables[name]; ok { + return nil + } if _, ok := p.progress[name]; ok { return errors.Errorf("variable cycle not allowed for %s", name) } @@ -242,9 +244,10 @@ func (p *parser) resolveValue(name string) (err error) { if _, builtin := p.opt.Vars[name]; !ok && !builtin { vr, ok := p.vars[name] if !ok { - return errors.Wrapf(errUndefined, "variable %q does not exit", name) + return errors.Wrapf(errUndefined, "variable %q does not exist", name) } def = vr.Default + ectx = p.ectx } if def == nil { @@ -257,10 +260,10 @@ func (p *parser) resolveValue(name string) (err error) { return } - if diags := p.loadDeps(def.Expr, nil, true); diags.HasErrors() { + if diags := p.loadDeps(ectx, def.Expr, nil, true); diags.HasErrors() { return diags } - vv, diags := def.Expr.Value(p.ectx) + vv, diags := def.Expr.Value(ectx) if diags.HasErrors() { return diags } @@ -364,18 +367,43 @@ func (p *parser) resolveBlock(block *hcl.Block, target *hcl.BodySchema) (err err return FilterExcludeBody(block.Body, filter) } - // load dependencies from all targeted properties + // prepare the output destination and evaluation context t, ok := p.blockTypes[block.Type] if !ok { return nil } + var output reflect.Value + var ectx *hcl.EvalContext + if prev, ok := p.blockValues[block]; ok { + output = prev + ectx = p.blockEvalCtx[block] + } else { + output = reflect.New(t) + setLabel(output, block.Labels[0]) // early attach labels, so we can reference them + + type ectxI interface { + EvalContext(base *hcl.EvalContext, block *hcl.Block) *hcl.EvalContext + } + if v, ok := output.Interface().(ectxI); ok { + ectx = v.EvalContext(p.ectx, block) + if ectx != p.ectx && ectx.Parent() != p.ectx { + return errors.Errorf("EvalContext must return a context with the correct parent") + } + } else { + ectx = p.ectx + } + } + p.blockValues[block] = output + p.blockEvalCtx[block] = ectx + + // load dependencies from all targeted properties schema, _ := gohcl.ImpliedBodySchema(reflect.New(t).Interface()) content, _, diag := body().PartialContent(schema) if diag.HasErrors() { return diag } for _, a := range content.Attributes { - diag := p.loadDeps(a.Expr, nil, true) + diag := p.loadDeps(ectx, a.Expr, nil, true) if diag.HasErrors() { return diag } @@ -388,18 +416,10 @@ func (p *parser) resolveBlock(block *hcl.Block, target *hcl.BodySchema) (err err } // decode! - var output reflect.Value - if prev, ok := p.blockValues[block]; ok { - output = prev - } else { - output = reflect.New(t) - setLabel(output, block.Labels[0]) // early attach labels, so we can reference them - } - diag = gohcl.DecodeBody(body(), p.ectx, output.Interface()) + diag = gohcl.DecodeBody(body(), ectx, output.Interface()) if diag.HasErrors() { return diag } - p.blockValues[block] = output // mark all targeted properties as done for _, a := range content.Attributes { @@ -417,7 +437,7 @@ func (p *parser) resolveBlock(block *hcl.Block, target *hcl.BodySchema) (err err } } - // store the result into the evaluation context (so if can be referenced) + // store the result into the evaluation context (so it can be referenced) outputType, err := gocty.ImpliedType(output.Interface()) if err != nil { return err @@ -475,20 +495,20 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics { attrs: map[string]*hcl.Attribute{}, funcs: map[string]*functionDef{}, - blocks: map[string]map[string][]*hcl.Block{}, - blockValues: map[*hcl.Block]reflect.Value{}, - blockTypes: map[string]reflect.Type{}, - - progress: map[string]struct{}{}, - progressF: map[string]struct{}{}, - progressB: map[*hcl.Block]map[string]struct{}{}, + blocks: map[string]map[string][]*hcl.Block{}, + blockValues: map[*hcl.Block]reflect.Value{}, + blockEvalCtx: map[*hcl.Block]*hcl.EvalContext{}, + blockTypes: map[string]reflect.Type{}, - doneF: map[string]struct{}{}, - doneB: map[*hcl.Block]map[string]struct{}{}, ectx: &hcl.EvalContext{ Variables: map[string]cty.Value{}, - Functions: stdlibFunctions, + Functions: Stdlib(), }, + + progress: map[string]struct{}{}, + progressF: map[string]struct{}{}, + progressB: map[*hcl.Block]map[string]struct{}{}, + doneB: map[*hcl.Block]map[string]struct{}{}, } for _, v := range defs.Variables { @@ -532,7 +552,7 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics { delete(p.attrs, "function") for k := range p.opt.Vars { - _ = p.resolveValue(k) + _ = p.resolveValue(p.ectx, k) } for _, a := range content.Attributes { @@ -548,7 +568,7 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics { } for k := range p.vars { - if err := p.resolveValue(k); err != nil { + if err := p.resolveValue(p.ectx, k); err != nil { if diags, ok := err.(hcl.Diagnostics); ok { return diags } @@ -558,7 +578,7 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics { } for k := range p.funcs { - if err := p.resolveFunction(k); err != nil { + if err := p.resolveFunction(p.ectx, k); err != nil { if diags, ok := err.(hcl.Diagnostics); ok { return diags } @@ -678,7 +698,7 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics { } for k := range p.attrs { - if err := p.resolveValue(k); err != nil { + if err := p.resolveValue(p.ectx, k); err != nil { if diags, ok := err.(hcl.Diagnostics); ok { return diags } diff --git a/bake/hclparser/stdlib.go b/bake/hclparser/stdlib.go index f78b6dce..53d1d416 100644 --- a/bake/hclparser/stdlib.go +++ b/bake/hclparser/stdlib.go @@ -124,3 +124,11 @@ var timestampFunc = function.New(&function.Spec{ return cty.StringVal(time.Now().UTC().Format(time.RFC3339)), nil }, }) + +func Stdlib() map[string]function.Function { + funcs := make(map[string]function.Function, len(stdlibFunctions)) + for k, v := range stdlibFunctions { + funcs[k] = v + } + return funcs +}