mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-05-21 11:17:44 +08:00
bake: add matrix to target block
Signed-off-by: Justin Chadwell <me@jedevc.com>
This commit is contained in:
parent
4437802e63
commit
77252f161c
49
bake/bake.go
49
bake/bake.go
@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/moby/buildkit/client/llb"
|
"github.com/moby/buildkit/client/llb"
|
||||||
"github.com/moby/buildkit/session/auth/authprovider"
|
"github.com/moby/buildkit/session/auth/authprovider"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -779,6 +780,54 @@ func (t *Target) AddOverrides(overrides map[string]Override) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Target) EvalContexts(ectx *hcl.EvalContext, block *hcl.Block, loadDeps func(hcl.Expression) hcl.Diagnostics) ([]*hcl.EvalContext, error) {
|
||||||
|
content, _, err := block.Body.PartialContent(&hcl.BodySchema{
|
||||||
|
Attributes: []hcl.AttributeSchema{{Name: "matrix"}},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
attr, ok := content.Attributes["matrix"]
|
||||||
|
if !ok {
|
||||||
|
return []*hcl.EvalContext{ectx}, nil
|
||||||
|
}
|
||||||
|
if diags := loadDeps(attr.Expr); diags.HasErrors() {
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
value, err := attr.Expr.Value(ectx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !value.CanIterateElements() {
|
||||||
|
return nil, errors.Errorf("matrix must be a map")
|
||||||
|
}
|
||||||
|
matrix := value.AsValueMap()
|
||||||
|
|
||||||
|
ectxs := []*hcl.EvalContext{ectx}
|
||||||
|
for k, expr := range matrix {
|
||||||
|
if !expr.CanIterateElements() {
|
||||||
|
return nil, errors.Errorf("matrix values must be a list")
|
||||||
|
}
|
||||||
|
|
||||||
|
ectxs2 := []*hcl.EvalContext{}
|
||||||
|
for _, v := range expr.AsValueSlice() {
|
||||||
|
for _, e := range ectxs {
|
||||||
|
e2 := ectx.NewChild()
|
||||||
|
e2.Variables = make(map[string]cty.Value)
|
||||||
|
for k, v := range e.Variables {
|
||||||
|
e2.Variables[k] = v
|
||||||
|
}
|
||||||
|
e2.Variables[k] = v
|
||||||
|
ectxs2 = append(ectxs2, e2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ectxs = ectxs2
|
||||||
|
}
|
||||||
|
return ectxs, nil
|
||||||
|
}
|
||||||
|
|
||||||
func TargetsToBuildOpt(m map[string]*Target, inp *Input) (map[string]build.Options, error) {
|
func TargetsToBuildOpt(m map[string]*Target, inp *Input) (map[string]build.Options, error) {
|
||||||
m2 := make(map[string]build.Options, len(m))
|
m2 := make(map[string]build.Options, len(m))
|
||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
|
148
bake/hcl_test.go
148
bake/hcl_test.go
@ -652,6 +652,22 @@ func TestHCLDuplicateTarget(t *testing.T) {
|
|||||||
require.Equal(t, "y", *c.Targets[0].Dockerfile)
|
require.Equal(t, "y", *c.Targets[0].Dockerfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHCLRenameTarget(t *testing.T) {
|
||||||
|
dt := []byte(`
|
||||||
|
target "abc" {
|
||||||
|
name = "xyz"
|
||||||
|
dockerfile = "foo"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, "xyz", c.Targets[0].Name)
|
||||||
|
require.Equal(t, "foo", *c.Targets[0].Dockerfile)
|
||||||
|
}
|
||||||
|
|
||||||
func TestHCLRenameTargetAttrs(t *testing.T) {
|
func TestHCLRenameTargetAttrs(t *testing.T) {
|
||||||
dt := []byte(`
|
dt := []byte(`
|
||||||
target "abc" {
|
target "abc" {
|
||||||
@ -666,13 +682,31 @@ func TestHCLRenameTargetAttrs(t *testing.T) {
|
|||||||
|
|
||||||
c, err := ParseFile(dt, "docker-bake.hcl")
|
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, 2, len(c.Targets))
|
require.Equal(t, 2, len(c.Targets))
|
||||||
require.Equal(t, "xyz", c.Targets[0].Name)
|
require.Equal(t, "xyz", c.Targets[0].Name)
|
||||||
require.Equal(t, "foo", *c.Targets[0].Dockerfile)
|
require.Equal(t, "foo", *c.Targets[0].Dockerfile)
|
||||||
require.Equal(t, "def", c.Targets[1].Name)
|
require.Equal(t, "def", c.Targets[1].Name)
|
||||||
require.Equal(t, "foo", *c.Targets[1].Dockerfile)
|
require.Equal(t, "foo", *c.Targets[1].Dockerfile)
|
||||||
|
|
||||||
|
dt = []byte(`
|
||||||
|
target "def" {
|
||||||
|
dockerfile = target.xyz.dockerfile
|
||||||
|
}
|
||||||
|
|
||||||
|
target "abc" {
|
||||||
|
name = "xyz"
|
||||||
|
dockerfile = "foo"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
c, err = ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(c.Targets))
|
||||||
|
require.Equal(t, "def", c.Targets[0].Name)
|
||||||
|
require.Equal(t, "foo", *c.Targets[0].Dockerfile)
|
||||||
|
require.Equal(t, "xyz", c.Targets[1].Name)
|
||||||
|
require.Equal(t, "foo", *c.Targets[1].Dockerfile)
|
||||||
|
|
||||||
dt = []byte(`
|
dt = []byte(`
|
||||||
target "abc" {
|
target "abc" {
|
||||||
name = "xyz"
|
name = "xyz"
|
||||||
@ -686,22 +720,20 @@ func TestHCLRenameTargetAttrs(t *testing.T) {
|
|||||||
|
|
||||||
_, err = ParseFile(dt, "docker-bake.hcl")
|
_, err = ParseFile(dt, "docker-bake.hcl")
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
|
||||||
|
|
||||||
func TestHCLRenameTarget(t *testing.T) {
|
dt = []byte(`
|
||||||
dt := []byte(`
|
target "def" {
|
||||||
|
dockerfile = target.abc.dockerfile
|
||||||
|
}
|
||||||
|
|
||||||
target "abc" {
|
target "abc" {
|
||||||
name = "xyz"
|
name = "xyz"
|
||||||
dockerfile = "foo"
|
dockerfile = "foo"
|
||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseFile(dt, "docker-bake.hcl")
|
_, err = ParseFile(dt, "docker-bake.hcl")
|
||||||
require.NoError(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
|
||||||
require.Equal(t, "xyz", c.Targets[0].Name)
|
|
||||||
require.Equal(t, "foo", *c.Targets[0].Dockerfile)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHCLRenameMerge(t *testing.T) {
|
func TestHCLRenameMerge(t *testing.T) {
|
||||||
@ -762,6 +794,102 @@ func TestHCLRenameMultiFile(t *testing.T) {
|
|||||||
require.Equal(t, *c.Targets[1].Context, "y")
|
require.Equal(t, *c.Targets[1].Context, "y")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHCLMatrixBasic(t *testing.T) {
|
||||||
|
dt := []byte(`
|
||||||
|
target "default" {
|
||||||
|
matrix = {
|
||||||
|
foo = ["x", "y"]
|
||||||
|
}
|
||||||
|
name = foo
|
||||||
|
dockerfile = "${foo}.Dockerfile"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 2, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "x")
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHCLMatrixMultiple(t *testing.T) {
|
||||||
|
dt := []byte(`
|
||||||
|
target "default" {
|
||||||
|
matrix = {
|
||||||
|
foo = ["a"]
|
||||||
|
bar = ["b", "c"]
|
||||||
|
baz = ["d", "e", "f"]
|
||||||
|
}
|
||||||
|
name = "${foo}-${bar}-${baz}"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 6, len(c.Targets))
|
||||||
|
names := make([]string, len(c.Targets))
|
||||||
|
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"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHCLMatrixArgs(t *testing.T) {
|
||||||
|
dt := []byte(`
|
||||||
|
a = 1
|
||||||
|
variable "b" {
|
||||||
|
default = 2
|
||||||
|
}
|
||||||
|
target "default" {
|
||||||
|
matrix = {
|
||||||
|
foo = [a, b]
|
||||||
|
}
|
||||||
|
name = foo
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 2, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "1")
|
||||||
|
require.Equal(t, c.Targets[1].Name, "2")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHCLMatrixErrors(t *testing.T) {
|
||||||
|
dt := []byte(`
|
||||||
|
target "default" {
|
||||||
|
matrix = "test"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
_, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
dt = []byte(`
|
||||||
|
target "default" {
|
||||||
|
matrix = {
|
||||||
|
["a"] = ["b"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
_, err = ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
dt = []byte(`
|
||||||
|
target "default" {
|
||||||
|
matrix = {
|
||||||
|
a = "b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
_, err = ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestJSONAttributes(t *testing.T) {
|
func TestJSONAttributes(t *testing.T) {
|
||||||
dt := []byte(`{"FOO": "abc", "variable": {"BAR": {"default": "def"}}, "target": { "app": { "args": {"v1": "pre-${FOO}-${BAR}"}} } }`)
|
dt := []byte(`{"FOO": "abc", "variable": {"BAR": {"default": "def"}}, "target": { "app": { "args": {"v1": "pre-${FOO}-${BAR}"}} } }`)
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package hclparser
|
package hclparser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"hash/fnv"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -50,16 +52,16 @@ type parser struct {
|
|||||||
funcs map[string]*functionDef
|
funcs map[string]*functionDef
|
||||||
|
|
||||||
blocks map[string]map[string][]*hcl.Block
|
blocks map[string]map[string][]*hcl.Block
|
||||||
blockValues map[*hcl.Block]reflect.Value
|
blockValues map[*hcl.Block][]reflect.Value
|
||||||
blockEvalCtx map[*hcl.Block]*hcl.EvalContext
|
blockEvalCtx map[*hcl.Block][]*hcl.EvalContext
|
||||||
blockTypes map[string]reflect.Type
|
blockTypes map[string]reflect.Type
|
||||||
|
|
||||||
ectx *hcl.EvalContext
|
ectx *hcl.EvalContext
|
||||||
|
|
||||||
progress map[string]struct{}
|
progressV map[uint64]struct{}
|
||||||
progressF map[string]struct{}
|
progressF map[uint64]struct{}
|
||||||
progressB map[*hcl.Block]map[string]struct{}
|
progressB map[uint64]map[string]struct{}
|
||||||
doneB map[*hcl.Block]map[string]struct{}
|
doneB map[uint64]map[string]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var errUndefined = errors.New("undefined")
|
var errUndefined = errors.New("undefined")
|
||||||
@ -125,7 +127,7 @@ func (p *parser) loadDeps(ectx *hcl.EvalContext, exp hcl.Expression, exclude map
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, block := range blocks {
|
for _, block := range blocks {
|
||||||
if err := p.resolveBlock(block, target, true); err != nil {
|
if err := p.resolveBlock(block, target); err != nil {
|
||||||
if allowMissing && errors.Is(err, errUndefined) {
|
if allowMissing && errors.Is(err, errUndefined) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -158,10 +160,10 @@ func (p *parser) resolveFunction(ectx *hcl.EvalContext, name string) error {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return errors.Wrapf(errUndefined, "function %q does not exist", name)
|
return errors.Wrapf(errUndefined, "function %q does not exist", name)
|
||||||
}
|
}
|
||||||
if _, ok := p.progressF[name]; ok {
|
if _, ok := p.progressF[key(ectx, name)]; ok {
|
||||||
return errors.Errorf("function cycle not allowed for %s", name)
|
return errors.Errorf("function cycle not allowed for %s", name)
|
||||||
}
|
}
|
||||||
p.progressF[name] = struct{}{}
|
p.progressF[key(ectx, name)] = struct{}{}
|
||||||
|
|
||||||
if f.Result == nil {
|
if f.Result == nil {
|
||||||
return errors.Errorf("empty result not allowed for %s", name)
|
return errors.Errorf("empty result not allowed for %s", name)
|
||||||
@ -230,10 +232,10 @@ func (p *parser) resolveValue(ectx *hcl.EvalContext, name string) (err error) {
|
|||||||
if _, ok := ectx.Variables[name]; ok {
|
if _, ok := ectx.Variables[name]; ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if _, ok := p.progress[name]; ok {
|
if _, ok := p.progressV[key(ectx, name)]; ok {
|
||||||
return errors.Errorf("variable cycle not allowed for %s", name)
|
return errors.Errorf("variable cycle not allowed for %s", name)
|
||||||
}
|
}
|
||||||
p.progress[name] = struct{}{}
|
p.progressV[key(ectx, name)] = struct{}{}
|
||||||
|
|
||||||
var v *cty.Value
|
var v *cty.Value
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -303,20 +305,59 @@ func (p *parser) resolveValue(ectx *hcl.EvalContext, name string) (err error) {
|
|||||||
// resolveBlock force evaluates a block, storing the result in the parser. If a
|
// resolveBlock force evaluates a block, storing the result in the parser. If a
|
||||||
// target schema is provided, only the attributes and blocks present in the
|
// target schema is provided, only the attributes and blocks present in the
|
||||||
// schema will be evaluated.
|
// schema will be evaluated.
|
||||||
func (p *parser) resolveBlock(block *hcl.Block, target *hcl.BodySchema, resolveName bool) (err error) {
|
func (p *parser) resolveBlock(block *hcl.Block, target *hcl.BodySchema) (err error) {
|
||||||
if _, ok := p.doneB[block]; !ok {
|
// prepare the output destination and evaluation context
|
||||||
p.doneB[block] = map[string]struct{}{}
|
t, ok := p.blockTypes[block.Type]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
if _, ok := p.progressB[block]; !ok {
|
var outputs []reflect.Value
|
||||||
p.progressB[block] = map[string]struct{}{}
|
var ectxs []*hcl.EvalContext
|
||||||
|
if prev, ok := p.blockValues[block]; ok {
|
||||||
|
outputs = prev
|
||||||
|
ectxs = p.blockEvalCtx[block]
|
||||||
|
} else {
|
||||||
|
type ectxI interface {
|
||||||
|
EvalContexts(base *hcl.EvalContext, block *hcl.Block, loadDeps func(hcl.Expression) hcl.Diagnostics) ([]*hcl.EvalContext, error)
|
||||||
}
|
}
|
||||||
|
if v, ok := reflect.New(t).Interface().(ectxI); ok {
|
||||||
name, err := p.resolveBlockName(block, resolveName)
|
ectxs, err = v.EvalContexts(p.ectx, block, func(expr hcl.Expression) hcl.Diagnostics {
|
||||||
|
return p.loadDeps(p.ectx, expr, nil, true)
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
for _, ectx := range ectxs {
|
||||||
|
if ectx != p.ectx && ectx.Parent() != p.ectx {
|
||||||
|
return errors.Errorf("EvalContext must return a context with the correct parent")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ectxs = append([]*hcl.EvalContext{}, p.ectx)
|
||||||
|
}
|
||||||
|
for range ectxs {
|
||||||
|
outputs = append(outputs, reflect.New(t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.blockValues[block] = outputs
|
||||||
|
p.blockEvalCtx[block] = ectxs
|
||||||
|
|
||||||
|
for i, output := range outputs {
|
||||||
|
target := target
|
||||||
|
ectx := ectxs[i]
|
||||||
|
name, _ := getName(output)
|
||||||
|
if name == "" {
|
||||||
|
name = block.Labels[0]
|
||||||
|
}
|
||||||
if err := p.opt.ValidateLabel(name); err != nil {
|
if err := p.opt.ValidateLabel(name); err != nil {
|
||||||
return wrapErrorDiagnostic("Invalid name", err, &block.LabelRanges[0], &block.LabelRanges[0])
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := p.doneB[key(block, ectx)]; !ok {
|
||||||
|
p.doneB[key(block, ectx)] = map[string]struct{}{}
|
||||||
|
}
|
||||||
|
if _, ok := p.progressB[key(block, ectx)]; !ok {
|
||||||
|
p.progressB[key(block, ectx)] = map[string]struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if target != nil {
|
if target != nil {
|
||||||
@ -324,12 +365,12 @@ func (p *parser) resolveBlock(block *hcl.Block, target *hcl.BodySchema, resolveN
|
|||||||
original := target
|
original := target
|
||||||
target = &hcl.BodySchema{}
|
target = &hcl.BodySchema{}
|
||||||
for _, a := range original.Attributes {
|
for _, a := range original.Attributes {
|
||||||
if _, ok := p.doneB[block][a.Name]; !ok {
|
if _, ok := p.doneB[key(block, ectx)][a.Name]; !ok {
|
||||||
target.Attributes = append(target.Attributes, a)
|
target.Attributes = append(target.Attributes, a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, b := range original.Blocks {
|
for _, b := range original.Blocks {
|
||||||
if _, ok := p.doneB[block][b.Type]; !ok {
|
if _, ok := p.doneB[key(block, ectx)][b.Type]; !ok {
|
||||||
target.Blocks = append(target.Blocks, b)
|
target.Blocks = append(target.Blocks, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -341,20 +382,20 @@ func (p *parser) resolveBlock(block *hcl.Block, target *hcl.BodySchema, resolveN
|
|||||||
if target != nil {
|
if target != nil {
|
||||||
// detect reference cycles
|
// detect reference cycles
|
||||||
for _, a := range target.Attributes {
|
for _, a := range target.Attributes {
|
||||||
if _, ok := p.progressB[block][a.Name]; ok {
|
if _, ok := p.progressB[key(block, ectx)][a.Name]; ok {
|
||||||
return errors.Errorf("reference cycle not allowed for %s.%s.%s", block.Type, name, a.Name)
|
return errors.Errorf("reference cycle not allowed for %s.%s.%s", block.Type, name, a.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, b := range target.Blocks {
|
for _, b := range target.Blocks {
|
||||||
if _, ok := p.progressB[block][b.Type]; ok {
|
if _, ok := p.progressB[key(block, ectx)][b.Type]; ok {
|
||||||
return errors.Errorf("reference cycle not allowed for %s.%s.%s", block.Type, name, b.Type)
|
return errors.Errorf("reference cycle not allowed for %s.%s.%s", block.Type, name, b.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, a := range target.Attributes {
|
for _, a := range target.Attributes {
|
||||||
p.progressB[block][a.Name] = struct{}{}
|
p.progressB[key(block, ectx)][a.Name] = struct{}{}
|
||||||
}
|
}
|
||||||
for _, b := range target.Blocks {
|
for _, b := range target.Blocks {
|
||||||
p.progressB[block][b.Type] = struct{}{}
|
p.progressB[key(block, ectx)][b.Type] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,41 +406,13 @@ func (p *parser) resolveBlock(block *hcl.Block, target *hcl.BodySchema, resolveN
|
|||||||
}
|
}
|
||||||
|
|
||||||
filter := &hcl.BodySchema{}
|
filter := &hcl.BodySchema{}
|
||||||
for k := range p.doneB[block] {
|
for k := range p.doneB[key(block, ectx)] {
|
||||||
filter.Attributes = append(filter.Attributes, hcl.AttributeSchema{Name: k})
|
filter.Attributes = append(filter.Attributes, hcl.AttributeSchema{Name: k})
|
||||||
filter.Blocks = append(filter.Blocks, hcl.BlockHeaderSchema{Type: k})
|
filter.Blocks = append(filter.Blocks, hcl.BlockHeaderSchema{Type: k})
|
||||||
}
|
}
|
||||||
return FilterExcludeBody(block.Body, filter)
|
return FilterExcludeBody(block.Body, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
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
|
// load dependencies from all targeted properties
|
||||||
schema, _ := gohcl.ImpliedBodySchema(reflect.New(t).Interface())
|
schema, _ := gohcl.ImpliedBodySchema(reflect.New(t).Interface())
|
||||||
if nameKey, ok := getNameKey(output); ok {
|
if nameKey, ok := getNameKey(output); ok {
|
||||||
@ -416,7 +429,7 @@ func (p *parser) resolveBlock(block *hcl.Block, target *hcl.BodySchema, resolveN
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, b := range content.Blocks {
|
for _, b := range content.Blocks {
|
||||||
err := p.resolveBlock(b, nil, true)
|
err := p.resolveBlock(b, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -446,17 +459,17 @@ func (p *parser) resolveBlock(block *hcl.Block, target *hcl.BodySchema, resolveN
|
|||||||
|
|
||||||
// mark all targeted properties as done
|
// mark all targeted properties as done
|
||||||
for _, a := range content.Attributes {
|
for _, a := range content.Attributes {
|
||||||
p.doneB[block][a.Name] = struct{}{}
|
p.doneB[key(block, ectx)][a.Name] = struct{}{}
|
||||||
}
|
}
|
||||||
for _, b := range content.Blocks {
|
for _, b := range content.Blocks {
|
||||||
p.doneB[block][b.Type] = struct{}{}
|
p.doneB[key(block, ectx)][b.Type] = struct{}{}
|
||||||
}
|
}
|
||||||
if target != nil {
|
if target != nil {
|
||||||
for _, a := range target.Attributes {
|
for _, a := range target.Attributes {
|
||||||
p.doneB[block][a.Name] = struct{}{}
|
p.doneB[key(block, ectx)][a.Name] = struct{}{}
|
||||||
}
|
}
|
||||||
for _, b := range target.Blocks {
|
for _, b := range target.Blocks {
|
||||||
p.doneB[block][b.Type] = struct{}{}
|
p.doneB[key(block, ectx)][b.Type] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -478,68 +491,44 @@ func (p *parser) resolveBlock(block *hcl.Block, target *hcl.BodySchema, resolveN
|
|||||||
}
|
}
|
||||||
m[name] = outputValue
|
m[name] = outputValue
|
||||||
p.ectx.Variables[block.Type] = cty.MapVal(m)
|
p.ectx.Variables[block.Type] = cty.MapVal(m)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveBlockName returns the name of the block, calling resolveBlock to
|
// resolveBlockNames returns the names of the block, calling resolveBlock to
|
||||||
// evaluate any label fields to correctly resolve the name.
|
// evaluate any label fields to correctly resolve the name.
|
||||||
func (p *parser) resolveBlockName(block *hcl.Block, resolveName bool) (string, error) {
|
func (p *parser) resolveBlockNames(block *hcl.Block) ([]string, error) {
|
||||||
defaultName := block.Labels[0]
|
|
||||||
if !resolveName {
|
|
||||||
return defaultName, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
t, ok := p.blockTypes[block.Type]
|
t, ok := p.blockTypes[block.Type]
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", nil
|
return nil, errors.Errorf("internal error: unknown block type %s", block.Type)
|
||||||
}
|
|
||||||
lbl, ok := getNameKey(reflect.New(t))
|
|
||||||
if !ok {
|
|
||||||
return defaultName, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if prev, ok := p.blockValues[block]; ok {
|
nameKey, ok := getNameKey(reflect.New(t))
|
||||||
// will have previously set name
|
|
||||||
name, ok := getName(prev)
|
|
||||||
if ok {
|
if ok {
|
||||||
return name, nil
|
|
||||||
}
|
|
||||||
return "", errors.New("failed to get label")
|
|
||||||
}
|
|
||||||
|
|
||||||
target := &hcl.BodySchema{
|
target := &hcl.BodySchema{
|
||||||
Attributes: []hcl.AttributeSchema{{Name: lbl}},
|
Attributes: []hcl.AttributeSchema{{Name: nameKey}},
|
||||||
Blocks: []hcl.BlockHeaderSchema{{Type: lbl}},
|
Blocks: []hcl.BlockHeaderSchema{{Type: nameKey}},
|
||||||
}
|
}
|
||||||
if err := p.resolveBlock(block, target, false); err != nil {
|
if err := p.resolveBlock(block, target); err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
name, ok := getName(p.blockValues[block])
|
|
||||||
if !ok {
|
|
||||||
return "", errors.New("failed to get label")
|
|
||||||
}
|
|
||||||
|
|
||||||
// move block to new name
|
|
||||||
if name != defaultName {
|
|
||||||
p.blocks[block.Type][name] = append(p.blocks[block.Type][name], block)
|
|
||||||
|
|
||||||
filtered := make([]*hcl.Block, 0, len(p.blocks[block.Type][defaultName])-1)
|
|
||||||
for _, b := range p.blocks[block.Type][defaultName] {
|
|
||||||
if b == block {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
filtered = append(filtered, b)
|
|
||||||
}
|
|
||||||
if len(filtered) != 0 {
|
|
||||||
p.blocks[block.Type][defaultName] = filtered
|
|
||||||
} else {
|
} else {
|
||||||
delete(p.blocks[block.Type], defaultName)
|
if err := p.resolveBlock(block, &hcl.BodySchema{}); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return name, nil
|
names := make([]string, 0, len(p.blockValues[block]))
|
||||||
|
for _, prev := range p.blockValues[block] {
|
||||||
|
name, ok := getName(prev)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("internal error: failed to get name")
|
||||||
|
}
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return names, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
||||||
@ -579,19 +568,18 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
|||||||
funcs: map[string]*functionDef{},
|
funcs: map[string]*functionDef{},
|
||||||
|
|
||||||
blocks: map[string]map[string][]*hcl.Block{},
|
blocks: map[string]map[string][]*hcl.Block{},
|
||||||
blockValues: map[*hcl.Block]reflect.Value{},
|
blockValues: map[*hcl.Block][]reflect.Value{},
|
||||||
blockEvalCtx: map[*hcl.Block]*hcl.EvalContext{},
|
blockEvalCtx: map[*hcl.Block][]*hcl.EvalContext{},
|
||||||
blockTypes: map[string]reflect.Type{},
|
blockTypes: map[string]reflect.Type{},
|
||||||
|
|
||||||
ectx: &hcl.EvalContext{
|
ectx: &hcl.EvalContext{
|
||||||
Variables: map[string]cty.Value{},
|
Variables: map[string]cty.Value{},
|
||||||
Functions: Stdlib(),
|
Functions: Stdlib(),
|
||||||
},
|
},
|
||||||
|
|
||||||
progress: map[string]struct{}{},
|
progressV: map[uint64]struct{}{},
|
||||||
progressF: map[string]struct{}{},
|
progressF: map[uint64]struct{}{},
|
||||||
progressB: map[*hcl.Block]map[string]struct{}{},
|
progressB: map[uint64]map[string]struct{}{},
|
||||||
doneB: map[*hcl.Block]map[string]struct{}{},
|
doneB: map[uint64]map[string]struct{}{},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range defs.Variables {
|
for _, v := range defs.Variables {
|
||||||
@ -683,28 +671,6 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, b := range content.Blocks {
|
|
||||||
if len(b.Labels) == 0 || len(b.Labels) > 1 {
|
|
||||||
return hcl.Diagnostics{
|
|
||||||
&hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Invalid block",
|
|
||||||
Detail: fmt.Sprintf("invalid block label: %v", b.Labels),
|
|
||||||
Subject: &b.LabelRanges[0],
|
|
||||||
Context: &b.LabelRanges[0],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bm, ok := p.blocks[b.Type]
|
|
||||||
if !ok {
|
|
||||||
bm = map[string][]*hcl.Block{}
|
|
||||||
p.blocks[b.Type] = bm
|
|
||||||
}
|
|
||||||
|
|
||||||
lbl := b.Labels[0]
|
|
||||||
bm[lbl] = append(bm[lbl], b)
|
|
||||||
}
|
|
||||||
|
|
||||||
type value struct {
|
type value struct {
|
||||||
reflect.Value
|
reflect.Value
|
||||||
idx int
|
idx int
|
||||||
@ -715,7 +681,6 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
|||||||
values map[string]value
|
values map[string]value
|
||||||
}
|
}
|
||||||
types := map[string]field{}
|
types := map[string]field{}
|
||||||
|
|
||||||
vt := reflect.ValueOf(val).Elem().Type()
|
vt := reflect.ValueOf(val).Elem().Type()
|
||||||
for i := 0; i < vt.NumField(); i++ {
|
for i := 0; i < vt.NumField(); i++ {
|
||||||
tags := strings.Split(vt.Field(i).Tag.Get("hcl"), ",")
|
tags := strings.Split(vt.Field(i).Tag.Get("hcl"), ",")
|
||||||
@ -728,11 +693,41 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tmpBlocks := map[string]map[string][]*hcl.Block{}
|
||||||
|
for _, b := range content.Blocks {
|
||||||
|
if len(b.Labels) == 0 || len(b.Labels) > 1 {
|
||||||
|
return hcl.Diagnostics{
|
||||||
|
&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid block",
|
||||||
|
Detail: fmt.Sprintf("invalid block label: %v", b.Labels),
|
||||||
|
Subject: &b.LabelRanges[0],
|
||||||
|
Context: &b.LabelRanges[0],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bm, ok := tmpBlocks[b.Type]
|
||||||
|
if !ok {
|
||||||
|
bm = map[string][]*hcl.Block{}
|
||||||
|
tmpBlocks[b.Type] = bm
|
||||||
|
}
|
||||||
|
|
||||||
|
names, err := p.resolveBlockNames(b)
|
||||||
|
if err != nil {
|
||||||
|
return wrapErrorDiagnostic("Invalid name", err, &b.LabelRanges[0], &b.LabelRanges[0])
|
||||||
|
}
|
||||||
|
for _, name := range names {
|
||||||
|
bm[name] = append(bm[name], b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.blocks = tmpBlocks
|
||||||
|
|
||||||
diags = hcl.Diagnostics{}
|
diags = hcl.Diagnostics{}
|
||||||
for _, b := range content.Blocks {
|
for _, b := range content.Blocks {
|
||||||
v := reflect.ValueOf(val)
|
v := reflect.ValueOf(val)
|
||||||
|
|
||||||
err := p.resolveBlock(b, nil, true)
|
err := p.resolveBlock(b, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if diag, ok := err.(hcl.Diagnostics); ok {
|
if diag, ok := err.(hcl.Diagnostics); ok {
|
||||||
if diag.HasErrors() {
|
if diag.HasErrors() {
|
||||||
@ -744,8 +739,8 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vv := p.blockValues[b]
|
vvs := p.blockValues[b]
|
||||||
|
for _, vv := range vvs {
|
||||||
t := types[b.Type]
|
t := types[b.Type]
|
||||||
lblIndex, lblExists := getNameIndex(vv)
|
lblIndex, lblExists := getNameIndex(vv)
|
||||||
lblName, _ := getName(vv)
|
lblName, _ := getName(vv)
|
||||||
@ -776,6 +771,7 @@ func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
|||||||
v.Elem().Field(t.idx).Set(reflect.Append(slice, vv))
|
v.Elem().Field(t.idx).Set(reflect.Append(slice, vv))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
return diags
|
return diags
|
||||||
}
|
}
|
||||||
@ -893,3 +889,21 @@ func removeAttributesDiags(diags hcl.Diagnostics, reserved map[string]struct{},
|
|||||||
}
|
}
|
||||||
return fdiags
|
return fdiags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// key returns a unique hash for the given values
|
||||||
|
func key(ks ...any) uint64 {
|
||||||
|
hash := fnv.New64a()
|
||||||
|
for _, k := range ks {
|
||||||
|
v := reflect.ValueOf(k)
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
hash.Write([]byte(v.String()))
|
||||||
|
case reflect.Pointer:
|
||||||
|
ptr := reflect.ValueOf(k).Pointer()
|
||||||
|
binary.Write(hash, binary.LittleEndian, uint64(ptr))
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unknown key kind %s", v.Kind().String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hash.Sum64()
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user