mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-05-18 09:17:49 +08:00
Merge pull request #645 from tonistiigi/new-bake-parser
New bake parser
This commit is contained in:
commit
12db50748b
209
bake/bake.go
209
bake/bake.go
@ -2,6 +2,7 @@ package bake
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@ -9,6 +10,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/bake/hclparser"
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/build"
|
||||||
"github.com/docker/buildx/util/buildflags"
|
"github.com/docker/buildx/util/buildflags"
|
||||||
"github.com/docker/buildx/util/platformutil"
|
"github.com/docker/buildx/util/platformutil"
|
||||||
@ -60,7 +62,7 @@ func ReadLocalFiles(names []string) ([]File, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ReadTargets(ctx context.Context, files []File, targets, overrides []string) (map[string]*Target, error) {
|
func ReadTargets(ctx context.Context, files []File, targets, overrides []string) (map[string]*Target, error) {
|
||||||
c, err := parseFiles(files)
|
c, err := ParseFiles(files)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -84,29 +86,43 @@ func ReadTargets(ctx context.Context, files []File, targets, overrides []string)
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFiles(files []File) (*Config, error) {
|
func ParseFiles(files []File) (_ *Config, err error) {
|
||||||
|
defer func() {
|
||||||
|
err = formatHCLError(err, files)
|
||||||
|
}()
|
||||||
|
|
||||||
var c Config
|
var c Config
|
||||||
var fs []*hcl.File
|
var fs []*hcl.File
|
||||||
var scs []*StaticConfig
|
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
cfg, f, sc, err := parseFile(f.Data, f.Name)
|
cfg, isCompose, composeErr := ParseComposeFile(f.Data, f.Name)
|
||||||
if err != nil {
|
if isCompose {
|
||||||
return nil, err
|
if composeErr != nil {
|
||||||
}
|
return nil, composeErr
|
||||||
if cfg != nil {
|
}
|
||||||
c = mergeConfig(c, *cfg)
|
c = mergeConfig(c, *cfg)
|
||||||
} else {
|
c = dedupeConfig(c)
|
||||||
fs = append(fs, f)
|
}
|
||||||
scs = append(scs, sc)
|
if !isCompose {
|
||||||
|
hf, isHCL, err := ParseHCLFile(f.Data, f.Name)
|
||||||
|
if isHCL {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fs = append(fs, hf)
|
||||||
|
} else if composeErr != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse %s: parsing yaml: %v, parsing hcl: %w", f.Name, composeErr, err)
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(fs) > 0 {
|
if len(fs) > 0 {
|
||||||
cfg, err := ParseHCL(hcl.MergeFiles(fs), mergeStaticConfig(scs))
|
if err := hclparser.Parse(hcl.MergeFiles(fs), hclparser.Opt{
|
||||||
if err != nil {
|
LookupVar: os.LookupEnv,
|
||||||
|
}, &c); err.HasErrors() {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c = mergeConfig(c, dedupeConfig(*cfg))
|
|
||||||
}
|
}
|
||||||
return &c, nil
|
return &c, nil
|
||||||
}
|
}
|
||||||
@ -117,7 +133,7 @@ func dedupeConfig(c Config) Config {
|
|||||||
m := map[string]*Target{}
|
m := map[string]*Target{}
|
||||||
for _, t := range c.Targets {
|
for _, t := range c.Targets {
|
||||||
if t2, ok := m[t.Name]; ok {
|
if t2, ok := m[t.Name]; ok {
|
||||||
merge(t2, t)
|
t2.Merge(t)
|
||||||
} else {
|
} else {
|
||||||
m[t.Name] = t
|
m[t.Name] = t
|
||||||
c2.Targets = append(c2.Targets, t)
|
c2.Targets = append(c2.Targets, t)
|
||||||
@ -127,37 +143,25 @@ func dedupeConfig(c Config) Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ParseFile(dt []byte, fn string) (*Config, error) {
|
func ParseFile(dt []byte, fn string) (*Config, error) {
|
||||||
return parseFiles([]File{{Data: dt, Name: fn}})
|
return ParseFiles([]File{{Data: dt, Name: fn}})
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFile(dt []byte, fn string) (*Config, *hcl.File, *StaticConfig, error) {
|
func ParseComposeFile(dt []byte, fn string) (*Config, bool, error) {
|
||||||
fnl := strings.ToLower(fn)
|
fnl := strings.ToLower(fn)
|
||||||
if strings.HasSuffix(fnl, ".yml") || strings.HasSuffix(fnl, ".yaml") {
|
if strings.HasSuffix(fnl, ".yml") || strings.HasSuffix(fnl, ".yaml") {
|
||||||
c, err := ParseCompose(dt)
|
cfg, err := ParseCompose(dt)
|
||||||
return c, nil, nil, err
|
return cfg, true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasSuffix(fnl, ".json") || strings.HasSuffix(fnl, ".hcl") {
|
if strings.HasSuffix(fnl, ".json") || strings.HasSuffix(fnl, ".hcl") {
|
||||||
f, sc, err := ParseHCLFile(dt, fn)
|
return nil, false, nil
|
||||||
return nil, f, sc, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := ParseCompose(dt)
|
cfg, err := ParseCompose(dt)
|
||||||
if err != nil {
|
return cfg, err == nil, err
|
||||||
f, sc, err2 := ParseHCLFile(dt, fn)
|
|
||||||
if err2 != nil {
|
|
||||||
return nil, nil, nil, errors.Errorf("failed to parse %s: parsing yaml: %s, parsing hcl: %s", fn, err.Error(), err2.Error())
|
|
||||||
}
|
|
||||||
return nil, f, sc, nil
|
|
||||||
}
|
|
||||||
return cfg, nil, nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Variables []*Variable `json:"-" hcl:"variable,block"`
|
Groups []*Group `json:"group" hcl:"group,block"`
|
||||||
Groups []*Group `json:"group" hcl:"group,block"`
|
Targets []*Target `json:"target" hcl:"target,block"`
|
||||||
Targets []*Target `json:"target" hcl:"target,block"`
|
|
||||||
Remain hcl.Body `json:"-" hcl:",remain"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeConfig(c1, c2 Config) Config {
|
func mergeConfig(c1, c2 Config) Config {
|
||||||
@ -203,7 +207,8 @@ func mergeConfig(c1, c2 Config) Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if t1 != nil {
|
if t1 != nil {
|
||||||
t2 = merge(t1, t2)
|
t1.Merge(t2)
|
||||||
|
t2 = t1
|
||||||
}
|
}
|
||||||
c1.Targets = append(c1.Targets, t2)
|
c1.Targets = append(c1.Targets, t2)
|
||||||
}
|
}
|
||||||
@ -390,30 +395,21 @@ func (c Config) target(name string, visited map[string]struct{}, overrides map[s
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if t != nil {
|
if t != nil {
|
||||||
tt = merge(tt, t)
|
tt.Merge(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.Inherits = nil
|
t.Inherits = nil
|
||||||
tt = merge(merge(defaultTarget(), tt), t)
|
m := defaultTarget()
|
||||||
|
m.Merge(tt)
|
||||||
|
m.Merge(t)
|
||||||
|
tt = m
|
||||||
if override, ok := overrides[name]; ok {
|
if override, ok := overrides[name]; ok {
|
||||||
tt = merge(tt, override)
|
tt.Merge(override)
|
||||||
}
|
}
|
||||||
tt.normalize()
|
tt.normalize()
|
||||||
return tt, nil
|
return tt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Variable struct {
|
|
||||||
Name string `json:"-" hcl:"name,label"`
|
|
||||||
Default *hcl.Attribute `json:"default,omitempty" hcl:"default,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Function struct {
|
|
||||||
Name string `json:"-" hcl:"name,label"`
|
|
||||||
Params *hcl.Attribute `json:"params,omitempty" hcl:"params"`
|
|
||||||
Variadic *hcl.Attribute `json:"variadic_param,omitempty" hcl:"variadic_params"`
|
|
||||||
Result *hcl.Attribute `json:"result,omitempty" hcl:"result"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Group struct {
|
type Group struct {
|
||||||
Name string `json:"-" hcl:"name,label"`
|
Name string `json:"-" hcl:"name,label"`
|
||||||
Targets []string `json:"targets" hcl:"targets"`
|
Targets []string `json:"targets" hcl:"targets"`
|
||||||
@ -455,6 +451,61 @@ func (t *Target) normalize() {
|
|||||||
t.Outputs = removeDupes(t.Outputs)
|
t.Outputs = removeDupes(t.Outputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Target) Merge(t2 *Target) {
|
||||||
|
if t2.Context != nil {
|
||||||
|
t.Context = t2.Context
|
||||||
|
}
|
||||||
|
if t2.Dockerfile != nil {
|
||||||
|
t.Dockerfile = t2.Dockerfile
|
||||||
|
}
|
||||||
|
if t2.DockerfileInline != nil {
|
||||||
|
t.DockerfileInline = t2.DockerfileInline
|
||||||
|
}
|
||||||
|
for k, v := range t2.Args {
|
||||||
|
if t.Args == nil {
|
||||||
|
t.Args = map[string]string{}
|
||||||
|
}
|
||||||
|
t.Args[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range t2.Labels {
|
||||||
|
if t.Labels == nil {
|
||||||
|
t.Labels = map[string]string{}
|
||||||
|
}
|
||||||
|
t.Labels[k] = v
|
||||||
|
}
|
||||||
|
if t2.Tags != nil { // no merge
|
||||||
|
t.Tags = t2.Tags
|
||||||
|
}
|
||||||
|
if t2.Target != nil {
|
||||||
|
t.Target = t2.Target
|
||||||
|
}
|
||||||
|
if t2.Secrets != nil { // merge
|
||||||
|
t.Secrets = append(t.Secrets, t2.Secrets...)
|
||||||
|
}
|
||||||
|
if t2.SSH != nil { // merge
|
||||||
|
t.SSH = append(t.SSH, t2.SSH...)
|
||||||
|
}
|
||||||
|
if t2.Platforms != nil { // no merge
|
||||||
|
t.Platforms = t2.Platforms
|
||||||
|
}
|
||||||
|
if t2.CacheFrom != nil { // merge
|
||||||
|
t.CacheFrom = append(t.CacheFrom, t2.CacheFrom...)
|
||||||
|
}
|
||||||
|
if t2.CacheTo != nil { // no merge
|
||||||
|
t.CacheTo = t2.CacheTo
|
||||||
|
}
|
||||||
|
if t2.Outputs != nil { // no merge
|
||||||
|
t.Outputs = t2.Outputs
|
||||||
|
}
|
||||||
|
if t2.Pull != nil {
|
||||||
|
t.Pull = t2.Pull
|
||||||
|
}
|
||||||
|
if t2.NoCache != nil {
|
||||||
|
t.NoCache = t2.NoCache
|
||||||
|
}
|
||||||
|
t.Inherits = append(t.Inherits, t2.Inherits...)
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@ -581,62 +632,6 @@ func defaultTarget() *Target {
|
|||||||
return &Target{}
|
return &Target{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func merge(t1, t2 *Target) *Target {
|
|
||||||
if t2.Context != nil {
|
|
||||||
t1.Context = t2.Context
|
|
||||||
}
|
|
||||||
if t2.Dockerfile != nil {
|
|
||||||
t1.Dockerfile = t2.Dockerfile
|
|
||||||
}
|
|
||||||
if t2.DockerfileInline != nil {
|
|
||||||
t1.DockerfileInline = t2.DockerfileInline
|
|
||||||
}
|
|
||||||
for k, v := range t2.Args {
|
|
||||||
if t1.Args == nil {
|
|
||||||
t1.Args = map[string]string{}
|
|
||||||
}
|
|
||||||
t1.Args[k] = v
|
|
||||||
}
|
|
||||||
for k, v := range t2.Labels {
|
|
||||||
if t1.Labels == nil {
|
|
||||||
t1.Labels = map[string]string{}
|
|
||||||
}
|
|
||||||
t1.Labels[k] = v
|
|
||||||
}
|
|
||||||
if t2.Tags != nil { // no merge
|
|
||||||
t1.Tags = t2.Tags
|
|
||||||
}
|
|
||||||
if t2.Target != nil {
|
|
||||||
t1.Target = t2.Target
|
|
||||||
}
|
|
||||||
if t2.Secrets != nil { // merge
|
|
||||||
t1.Secrets = append(t1.Secrets, t2.Secrets...)
|
|
||||||
}
|
|
||||||
if t2.SSH != nil { // merge
|
|
||||||
t1.SSH = append(t1.SSH, t2.SSH...)
|
|
||||||
}
|
|
||||||
if t2.Platforms != nil { // no merge
|
|
||||||
t1.Platforms = t2.Platforms
|
|
||||||
}
|
|
||||||
if t2.CacheFrom != nil { // no merge
|
|
||||||
t1.CacheFrom = append(t1.CacheFrom, t2.CacheFrom...)
|
|
||||||
}
|
|
||||||
if t2.CacheTo != nil { // no merge
|
|
||||||
t1.CacheTo = t2.CacheTo
|
|
||||||
}
|
|
||||||
if t2.Outputs != nil { // no merge
|
|
||||||
t1.Outputs = t2.Outputs
|
|
||||||
}
|
|
||||||
if t2.Pull != nil {
|
|
||||||
t1.Pull = t2.Pull
|
|
||||||
}
|
|
||||||
if t2.NoCache != nil {
|
|
||||||
t1.NoCache = t2.NoCache
|
|
||||||
}
|
|
||||||
t1.Inherits = append(t1.Inherits, t2.Inherits...)
|
|
||||||
return t1
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeDupes(s []string) []string {
|
func removeDupes(s []string) []string {
|
||||||
i := 0
|
i := 0
|
||||||
seen := make(map[string]struct{}, len(s))
|
seen := make(map[string]struct{}, len(s))
|
||||||
|
625
bake/hcl.go
625
bake/hcl.go
@ -1,618 +1,42 @@
|
|||||||
package bake
|
package bake
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"math/big"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/docker/buildx/util/userfunc"
|
|
||||||
"github.com/hashicorp/go-cty-funcs/cidr"
|
|
||||||
"github.com/hashicorp/go-cty-funcs/crypto"
|
|
||||||
"github.com/hashicorp/go-cty-funcs/encoding"
|
|
||||||
"github.com/hashicorp/go-cty-funcs/uuid"
|
|
||||||
hcl "github.com/hashicorp/hcl/v2"
|
hcl "github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl/v2/ext/tryfunc"
|
"github.com/hashicorp/hcl/v2/hclparse"
|
||||||
"github.com/hashicorp/hcl/v2/ext/typeexpr"
|
|
||||||
"github.com/hashicorp/hcl/v2/gohcl"
|
|
||||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
|
||||||
hcljson "github.com/hashicorp/hcl/v2/json"
|
|
||||||
"github.com/moby/buildkit/solver/errdefs"
|
"github.com/moby/buildkit/solver/errdefs"
|
||||||
"github.com/moby/buildkit/solver/pb"
|
"github.com/moby/buildkit/solver/pb"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
"github.com/zclconf/go-cty/cty/function"
|
|
||||||
"github.com/zclconf/go-cty/cty/function/stdlib"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Collection of generally useful functions in cty-using applications, which
|
func ParseHCLFile(dt []byte, fn string) (*hcl.File, bool, error) {
|
||||||
// HCL supports. These functions are available for use in HCL files.
|
var err error
|
||||||
var (
|
if strings.HasSuffix(fn, ".json") {
|
||||||
stdlibFunctions = map[string]function.Function{
|
f, diags := hclparse.NewParser().ParseJSON(dt, fn)
|
||||||
"absolute": stdlib.AbsoluteFunc,
|
if diags.HasErrors() {
|
||||||
"add": stdlib.AddFunc,
|
err = diags
|
||||||
"and": stdlib.AndFunc,
|
|
||||||
"base64decode": encoding.Base64DecodeFunc,
|
|
||||||
"base64encode": encoding.Base64EncodeFunc,
|
|
||||||
"bcrypt": crypto.BcryptFunc,
|
|
||||||
"byteslen": stdlib.BytesLenFunc,
|
|
||||||
"bytesslice": stdlib.BytesSliceFunc,
|
|
||||||
"can": tryfunc.CanFunc,
|
|
||||||
"ceil": stdlib.CeilFunc,
|
|
||||||
"chomp": stdlib.ChompFunc,
|
|
||||||
"chunklist": stdlib.ChunklistFunc,
|
|
||||||
"cidrhost": cidr.HostFunc,
|
|
||||||
"cidrnetmask": cidr.NetmaskFunc,
|
|
||||||
"cidrsubnet": cidr.SubnetFunc,
|
|
||||||
"cidrsubnets": cidr.SubnetsFunc,
|
|
||||||
"csvdecode": stdlib.CSVDecodeFunc,
|
|
||||||
"coalesce": stdlib.CoalesceFunc,
|
|
||||||
"coalescelist": stdlib.CoalesceListFunc,
|
|
||||||
"compact": stdlib.CompactFunc,
|
|
||||||
"concat": stdlib.ConcatFunc,
|
|
||||||
"contains": stdlib.ContainsFunc,
|
|
||||||
"convert": typeexpr.ConvertFunc,
|
|
||||||
"distinct": stdlib.DistinctFunc,
|
|
||||||
"divide": stdlib.DivideFunc,
|
|
||||||
"element": stdlib.ElementFunc,
|
|
||||||
"equal": stdlib.EqualFunc,
|
|
||||||
"flatten": stdlib.FlattenFunc,
|
|
||||||
"floor": stdlib.FloorFunc,
|
|
||||||
"formatdate": stdlib.FormatDateFunc,
|
|
||||||
"format": stdlib.FormatFunc,
|
|
||||||
"formatlist": stdlib.FormatListFunc,
|
|
||||||
"greaterthan": stdlib.GreaterThanFunc,
|
|
||||||
"greaterthanorequalto": stdlib.GreaterThanOrEqualToFunc,
|
|
||||||
"hasindex": stdlib.HasIndexFunc,
|
|
||||||
"indent": stdlib.IndentFunc,
|
|
||||||
"index": stdlib.IndexFunc,
|
|
||||||
"int": stdlib.IntFunc,
|
|
||||||
"jsondecode": stdlib.JSONDecodeFunc,
|
|
||||||
"jsonencode": stdlib.JSONEncodeFunc,
|
|
||||||
"keys": stdlib.KeysFunc,
|
|
||||||
"join": stdlib.JoinFunc,
|
|
||||||
"length": stdlib.LengthFunc,
|
|
||||||
"lessthan": stdlib.LessThanFunc,
|
|
||||||
"lessthanorequalto": stdlib.LessThanOrEqualToFunc,
|
|
||||||
"log": stdlib.LogFunc,
|
|
||||||
"lookup": stdlib.LookupFunc,
|
|
||||||
"lower": stdlib.LowerFunc,
|
|
||||||
"max": stdlib.MaxFunc,
|
|
||||||
"md5": crypto.Md5Func,
|
|
||||||
"merge": stdlib.MergeFunc,
|
|
||||||
"min": stdlib.MinFunc,
|
|
||||||
"modulo": stdlib.ModuloFunc,
|
|
||||||
"multiply": stdlib.MultiplyFunc,
|
|
||||||
"negate": stdlib.NegateFunc,
|
|
||||||
"notequal": stdlib.NotEqualFunc,
|
|
||||||
"not": stdlib.NotFunc,
|
|
||||||
"or": stdlib.OrFunc,
|
|
||||||
"parseint": stdlib.ParseIntFunc,
|
|
||||||
"pow": stdlib.PowFunc,
|
|
||||||
"range": stdlib.RangeFunc,
|
|
||||||
"regexall": stdlib.RegexAllFunc,
|
|
||||||
"regex": stdlib.RegexFunc,
|
|
||||||
"regex_replace": stdlib.RegexReplaceFunc,
|
|
||||||
"reverse": stdlib.ReverseFunc,
|
|
||||||
"reverselist": stdlib.ReverseListFunc,
|
|
||||||
"rsadecrypt": crypto.RsaDecryptFunc,
|
|
||||||
"sethaselement": stdlib.SetHasElementFunc,
|
|
||||||
"setintersection": stdlib.SetIntersectionFunc,
|
|
||||||
"setproduct": stdlib.SetProductFunc,
|
|
||||||
"setsubtract": stdlib.SetSubtractFunc,
|
|
||||||
"setsymmetricdifference": stdlib.SetSymmetricDifferenceFunc,
|
|
||||||
"setunion": stdlib.SetUnionFunc,
|
|
||||||
"sha1": crypto.Sha1Func,
|
|
||||||
"sha256": crypto.Sha256Func,
|
|
||||||
"sha512": crypto.Sha512Func,
|
|
||||||
"signum": stdlib.SignumFunc,
|
|
||||||
"slice": stdlib.SliceFunc,
|
|
||||||
"sort": stdlib.SortFunc,
|
|
||||||
"split": stdlib.SplitFunc,
|
|
||||||
"strlen": stdlib.StrlenFunc,
|
|
||||||
"substr": stdlib.SubstrFunc,
|
|
||||||
"subtract": stdlib.SubtractFunc,
|
|
||||||
"timeadd": stdlib.TimeAddFunc,
|
|
||||||
"title": stdlib.TitleFunc,
|
|
||||||
"trim": stdlib.TrimFunc,
|
|
||||||
"trimprefix": stdlib.TrimPrefixFunc,
|
|
||||||
"trimspace": stdlib.TrimSpaceFunc,
|
|
||||||
"trimsuffix": stdlib.TrimSuffixFunc,
|
|
||||||
"try": tryfunc.TryFunc,
|
|
||||||
"upper": stdlib.UpperFunc,
|
|
||||||
"urlencode": encoding.URLEncodeFunc,
|
|
||||||
"uuidv4": uuid.V4Func,
|
|
||||||
"uuidv5": uuid.V5Func,
|
|
||||||
"values": stdlib.ValuesFunc,
|
|
||||||
"zipmap": stdlib.ZipmapFunc,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type StaticConfig struct {
|
|
||||||
Variables []*Variable `hcl:"variable,block"`
|
|
||||||
Functions []*Function `hcl:"function,block"`
|
|
||||||
Remain hcl.Body `hcl:",remain"`
|
|
||||||
|
|
||||||
attrs hcl.Attributes
|
|
||||||
|
|
||||||
defaults map[string]*hcl.Attribute
|
|
||||||
funcDefs map[string]*Function
|
|
||||||
funcs map[string]function.Function
|
|
||||||
env map[string]string
|
|
||||||
ectx hcl.EvalContext
|
|
||||||
progress map[string]struct{}
|
|
||||||
progressF map[string]struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeStaticConfig(scs []*StaticConfig) *StaticConfig {
|
|
||||||
if len(scs) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
sc := scs[0]
|
|
||||||
for _, s := range scs[1:] {
|
|
||||||
sc.Variables = append(sc.Variables, s.Variables...)
|
|
||||||
sc.Functions = append(sc.Functions, s.Functions...)
|
|
||||||
for k, v := range s.attrs {
|
|
||||||
sc.attrs[k] = v
|
|
||||||
}
|
}
|
||||||
|
return f, true, err
|
||||||
}
|
}
|
||||||
return sc
|
if strings.HasSuffix(fn, ".hcl") {
|
||||||
}
|
f, diags := hclparse.NewParser().ParseHCL(dt, fn)
|
||||||
|
if diags.HasErrors() {
|
||||||
func (sc *StaticConfig) EvalContext(withEnv bool) (*hcl.EvalContext, error) {
|
err = diags
|
||||||
// json parser also parses blocks as attributes
|
|
||||||
delete(sc.attrs, "target")
|
|
||||||
delete(sc.attrs, "function")
|
|
||||||
|
|
||||||
sc.defaults = map[string]*hcl.Attribute{}
|
|
||||||
for _, v := range sc.Variables {
|
|
||||||
if v.Name == "target" {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
sc.defaults[v.Name] = v.Default
|
return f, true, err
|
||||||
}
|
}
|
||||||
|
f, diags := hclparse.NewParser().ParseHCL(dt, fn+".hcl")
|
||||||
sc.env = map[string]string{}
|
|
||||||
if withEnv {
|
|
||||||
// Override default with values from environment.
|
|
||||||
for _, v := range os.Environ() {
|
|
||||||
parts := strings.SplitN(v, "=", 2)
|
|
||||||
name, value := parts[0], parts[1]
|
|
||||||
sc.env[name] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sc.funcDefs = map[string]*Function{}
|
|
||||||
for _, v := range sc.Functions {
|
|
||||||
sc.funcDefs[v.Name] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
sc.ectx = hcl.EvalContext{
|
|
||||||
Variables: map[string]cty.Value{},
|
|
||||||
Functions: stdlibFunctions,
|
|
||||||
}
|
|
||||||
sc.funcs = map[string]function.Function{}
|
|
||||||
sc.progress = map[string]struct{}{}
|
|
||||||
sc.progressF = 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for k := range sc.funcDefs {
|
|
||||||
if err := sc.resolveFunction(k); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &sc.ectx, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type jsonExp interface {
|
|
||||||
ExprList() []hcl.Expression
|
|
||||||
ExprMap() []hcl.KeyValuePair
|
|
||||||
}
|
|
||||||
|
|
||||||
func elementExpressions(je jsonExp, exp hcl.Expression) []hcl.Expression {
|
|
||||||
list := je.ExprList()
|
|
||||||
if len(list) != 0 {
|
|
||||||
exp := make([]hcl.Expression, 0, len(list))
|
|
||||||
for _, e := range list {
|
|
||||||
if je, ok := e.(jsonExp); ok {
|
|
||||||
exp = append(exp, elementExpressions(je, e)...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return exp
|
|
||||||
}
|
|
||||||
kvlist := je.ExprMap()
|
|
||||||
if len(kvlist) != 0 {
|
|
||||||
exp := make([]hcl.Expression, 0, len(kvlist)*2)
|
|
||||||
for _, p := range kvlist {
|
|
||||||
exp = append(exp, p.Key)
|
|
||||||
if je, ok := p.Value.(jsonExp); ok {
|
|
||||||
exp = append(exp, elementExpressions(je, p.Value)...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return exp
|
|
||||||
}
|
|
||||||
return []hcl.Expression{exp}
|
|
||||||
}
|
|
||||||
|
|
||||||
func jsonFuncCallsRecursive(exp hcl.Expression) ([]string, error) {
|
|
||||||
je, ok := exp.(jsonExp)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.Errorf("invalid expression type %T", exp)
|
|
||||||
}
|
|
||||||
m := map[string]struct{}{}
|
|
||||||
for _, e := range elementExpressions(je, exp) {
|
|
||||||
if err := appendJSONFuncCalls(e, m); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
arr := make([]string, 0, len(m))
|
|
||||||
for n := range m {
|
|
||||||
arr = append(arr, n)
|
|
||||||
}
|
|
||||||
return arr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendJSONFuncCalls(exp hcl.Expression, m map[string]struct{}) error {
|
|
||||||
v := reflect.ValueOf(exp)
|
|
||||||
if v.Kind() != reflect.Ptr || v.IsNil() {
|
|
||||||
return errors.Errorf("invalid json expression kind %T %v", exp, v.Kind())
|
|
||||||
}
|
|
||||||
if v.Elem().Kind() != reflect.Struct {
|
|
||||||
return errors.Errorf("invalid json expression pointer to %T %v", exp, v.Elem().Kind())
|
|
||||||
}
|
|
||||||
src := v.Elem().FieldByName("src")
|
|
||||||
if src.IsZero() {
|
|
||||||
return errors.Errorf("%v has no property src", v.Elem().Type())
|
|
||||||
}
|
|
||||||
if src.Kind() != reflect.Interface {
|
|
||||||
return errors.Errorf("%v src is not interface: %v", src.Type(), src.Kind())
|
|
||||||
}
|
|
||||||
src = src.Elem()
|
|
||||||
if src.IsNil() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if src.Kind() == reflect.Ptr {
|
|
||||||
src = src.Elem()
|
|
||||||
}
|
|
||||||
if src.Kind() != reflect.Struct {
|
|
||||||
return errors.Errorf("%v is not struct: %v", src.Type(), src.Kind())
|
|
||||||
}
|
|
||||||
|
|
||||||
// hcl/v2/json/ast#stringVal
|
|
||||||
val := src.FieldByName("Value")
|
|
||||||
if val.IsZero() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
rng := src.FieldByName("SrcRange")
|
|
||||||
if val.IsZero() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var stringVal struct {
|
|
||||||
Value string
|
|
||||||
SrcRange hcl.Range
|
|
||||||
}
|
|
||||||
|
|
||||||
if !val.Type().AssignableTo(reflect.ValueOf(stringVal.Value).Type()) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if !rng.Type().AssignableTo(reflect.ValueOf(stringVal.SrcRange).Type()) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// reflect.Set does not work for unexported fields
|
|
||||||
stringVal.Value = *(*string)(unsafe.Pointer(val.UnsafeAddr()))
|
|
||||||
stringVal.SrcRange = *(*hcl.Range)(unsafe.Pointer(rng.UnsafeAddr()))
|
|
||||||
|
|
||||||
expr, diags := hclsyntax.ParseExpression([]byte(stringVal.Value), stringVal.SrcRange.Filename, stringVal.SrcRange.Start)
|
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
return nil
|
f, diags2 := hclparse.NewParser().ParseJSON(dt, fn+".json")
|
||||||
|
if !diags2.HasErrors() {
|
||||||
|
return f, true, nil
|
||||||
|
}
|
||||||
|
return nil, false, diags
|
||||||
}
|
}
|
||||||
|
return f, true, nil
|
||||||
fns, err := funcCalls(expr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fn := range fns {
|
|
||||||
m[fn] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func funcCalls(exp hcl.Expression) ([]string, hcl.Diagnostics) {
|
func formatHCLError(err error, files []File) error {
|
||||||
node, ok := exp.(hclsyntax.Node)
|
|
||||||
if !ok {
|
|
||||||
fns, err := jsonFuncCallsRecursive(exp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, hcl.Diagnostics{
|
|
||||||
&hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Invalid expression",
|
|
||||||
Detail: err.Error(),
|
|
||||||
Subject: exp.Range().Ptr(),
|
|
||||||
Context: exp.Range().Ptr(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fns, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var funcnames []string
|
|
||||||
hcldiags := hclsyntax.VisitAll(node, func(n hclsyntax.Node) hcl.Diagnostics {
|
|
||||||
if fe, ok := n.(*hclsyntax.FunctionCallExpr); ok {
|
|
||||||
funcnames = append(funcnames, fe.Name)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if hcldiags.HasErrors() {
|
|
||||||
return nil, hcldiags
|
|
||||||
}
|
|
||||||
return funcnames, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sc *StaticConfig) loadDeps(exp hcl.Expression, exclude map[string]struct{}) hcl.Diagnostics {
|
|
||||||
fns, hcldiags := funcCalls(exp)
|
|
||||||
if hcldiags.HasErrors() {
|
|
||||||
return hcldiags
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fn := range fns {
|
|
||||||
if err := sc.resolveFunction(fn); err != nil {
|
|
||||||
return hcl.Diagnostics{
|
|
||||||
&hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Invalid expression",
|
|
||||||
Detail: err.Error(),
|
|
||||||
Subject: exp.Range().Ptr(),
|
|
||||||
Context: exp.Range().Ptr(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range exp.Variables() {
|
|
||||||
if _, ok := exclude[v.RootName()]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := sc.resolveValue(v.RootName()); err != nil {
|
|
||||||
return hcl.Diagnostics{
|
|
||||||
&hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Invalid expression",
|
|
||||||
Detail: err.Error(),
|
|
||||||
Subject: v.SourceRange().Ptr(),
|
|
||||||
Context: v.SourceRange().Ptr(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sc *StaticConfig) resolveFunction(name string) error {
|
|
||||||
if _, ok := sc.funcs[name]; ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
f, ok := sc.funcDefs[name]
|
|
||||||
if !ok {
|
|
||||||
if _, ok := sc.ectx.Functions[name]; ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.Errorf("undefined function %s", name)
|
|
||||||
}
|
|
||||||
if _, ok := sc.progressF[name]; ok {
|
|
||||||
return errors.Errorf("function cycle not allowed for %s", name)
|
|
||||||
}
|
|
||||||
sc.progressF[name] = struct{}{}
|
|
||||||
|
|
||||||
paramExprs, paramsDiags := hcl.ExprList(f.Params.Expr)
|
|
||||||
if paramsDiags.HasErrors() {
|
|
||||||
return paramsDiags
|
|
||||||
}
|
|
||||||
var diags hcl.Diagnostics
|
|
||||||
params := map[string]struct{}{}
|
|
||||||
for _, paramExpr := range paramExprs {
|
|
||||||
param := hcl.ExprAsKeyword(paramExpr)
|
|
||||||
if param == "" {
|
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Invalid param element",
|
|
||||||
Detail: "Each parameter name must be an identifier.",
|
|
||||||
Subject: paramExpr.Range().Ptr(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
params[param] = struct{}{}
|
|
||||||
}
|
|
||||||
var variadic hcl.Expression
|
|
||||||
if f.Variadic != nil {
|
|
||||||
variadic = f.Variadic.Expr
|
|
||||||
param := hcl.ExprAsKeyword(variadic)
|
|
||||||
if param == "" {
|
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Invalid param element",
|
|
||||||
Detail: "Each parameter name must be an identifier.",
|
|
||||||
Subject: f.Variadic.Range.Ptr(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
params[param] = struct{}{}
|
|
||||||
}
|
|
||||||
if diags.HasErrors() {
|
|
||||||
return diags
|
|
||||||
}
|
|
||||||
|
|
||||||
if diags := sc.loadDeps(f.Result.Expr, params); diags.HasErrors() {
|
|
||||||
return diags
|
|
||||||
}
|
|
||||||
|
|
||||||
v, diags := userfunc.NewFunction(f.Params.Expr, variadic, f.Result.Expr, func() *hcl.EvalContext {
|
|
||||||
return &sc.ectx
|
|
||||||
})
|
|
||||||
if diags.HasErrors() {
|
|
||||||
return diags
|
|
||||||
}
|
|
||||||
sc.funcs[name] = v
|
|
||||||
sc.ectx.Functions[name] = v
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sc *StaticConfig) resolveValue(name string) (err error) {
|
|
||||||
if _, ok := sc.ectx.Variables[name]; ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if _, ok := sc.progress[name]; ok {
|
|
||||||
return errors.Errorf("variable cycle not allowed for %s", name)
|
|
||||||
}
|
|
||||||
sc.progress[name] = struct{}{}
|
|
||||||
|
|
||||||
var v *cty.Value
|
|
||||||
defer func() {
|
|
||||||
if v != nil {
|
|
||||||
sc.ectx.Variables[name] = *v
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
def, ok := sc.attrs[name]
|
|
||||||
if !ok {
|
|
||||||
def, ok = sc.defaults[name]
|
|
||||||
if !ok {
|
|
||||||
return errors.Errorf("undefined variable %q", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if def == nil {
|
|
||||||
vv := cty.StringVal(sc.env[name])
|
|
||||||
v = &vv
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if diags := sc.loadDeps(def.Expr, nil); diags.HasErrors() {
|
|
||||||
return diags
|
|
||||||
}
|
|
||||||
vv, diags := def.Expr.Value(&sc.ectx)
|
|
||||||
if diags.HasErrors() {
|
|
||||||
return diags
|
|
||||||
}
|
|
||||||
|
|
||||||
_, 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 {
|
|
||||||
return errors.Wrapf(err, "failed to parse %s as bool", name)
|
|
||||||
}
|
|
||||||
vv := cty.BoolVal(b)
|
|
||||||
v = &vv
|
|
||||||
return nil
|
|
||||||
} else if vv.Type().Equals(cty.String) {
|
|
||||||
vv := cty.StringVal(envv)
|
|
||||||
v = &vv
|
|
||||||
return nil
|
|
||||||
} else if vv.Type().Equals(cty.Number) {
|
|
||||||
n, err := strconv.ParseFloat(envv, 64)
|
|
||||||
if err == nil && (math.IsNaN(n) || math.IsInf(n, 0)) {
|
|
||||||
err = errors.Errorf("invalid number value")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to parse %s as number", name)
|
|
||||||
}
|
|
||||||
vv := cty.NumberVal(big.NewFloat(n))
|
|
||||||
v = &vv
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
// TODO: support lists with csv values
|
|
||||||
return errors.Errorf("unsupported type %s for variable %s", v.Type(), name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v = &vv
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseHCLFile(dt []byte, fn string) (*hcl.File, *StaticConfig, error) {
|
|
||||||
if strings.HasSuffix(fn, ".json") || strings.HasSuffix(fn, ".hcl") {
|
|
||||||
return parseHCLFile(dt, fn)
|
|
||||||
}
|
|
||||||
f, sc, err := parseHCLFile(dt, fn+".hcl")
|
|
||||||
if err != nil {
|
|
||||||
f, sc, err2 := parseHCLFile(dt, fn+".json")
|
|
||||||
if err2 == nil {
|
|
||||||
return f, sc, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return f, sc, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseHCLFile(dt []byte, fn string) (f *hcl.File, _ *StaticConfig, err error) {
|
|
||||||
defer func() {
|
|
||||||
err = formatHCLError(dt, err)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Decode user defined functions, first parsing as hcl and falling back to
|
|
||||||
// json, returning errors based on the file suffix.
|
|
||||||
f, hcldiags := hclsyntax.ParseConfig(dt, fn, hcl.Pos{Line: 1, Column: 1})
|
|
||||||
if hcldiags.HasErrors() {
|
|
||||||
var jsondiags hcl.Diagnostics
|
|
||||||
f, jsondiags = hcljson.Parse(dt, fn)
|
|
||||||
if jsondiags.HasErrors() {
|
|
||||||
fnl := strings.ToLower(fn)
|
|
||||||
if strings.HasSuffix(fnl, ".json") {
|
|
||||||
return nil, nil, jsondiags
|
|
||||||
}
|
|
||||||
return nil, nil, hcldiags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var sc StaticConfig
|
|
||||||
// Decode only variable blocks without interpolation.
|
|
||||||
if err := gohcl.DecodeBody(f.Body, nil, &sc); err != nil {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseHCL(b hcl.Body, sc *StaticConfig) (_ *Config, err error) {
|
|
||||||
ctx, err := sc.EvalContext(true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var c Config
|
|
||||||
|
|
||||||
// Decode with variables and functions.
|
|
||||||
if err := gohcl.DecodeBody(b, ctx, &c); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatHCLError(dt []byte, err error) error {
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -625,6 +49,13 @@ func formatHCLError(dt []byte, err error) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if d.Subject != nil {
|
if d.Subject != nil {
|
||||||
|
var dt []byte
|
||||||
|
for _, f := range files {
|
||||||
|
if d.Subject.Filename == f.Name {
|
||||||
|
dt = f.Data
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
src := errdefs.Source{
|
src := errdefs.Source{
|
||||||
Info: &pb.SourceInfo{
|
Info: &pb.SourceInfo{
|
||||||
Filename: d.Subject.Filename,
|
Filename: d.Subject.Filename,
|
||||||
|
@ -44,7 +44,6 @@ func TestHCLBasic(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, 1, len(c.Groups))
|
require.Equal(t, 1, len(c.Groups))
|
||||||
require.Equal(t, "default", c.Groups[0].Name)
|
require.Equal(t, "default", c.Groups[0].Name)
|
||||||
require.Equal(t, []string{"db", "webapp"}, c.Groups[0].Targets)
|
require.Equal(t, []string{"db", "webapp"}, c.Groups[0].Targets)
|
||||||
@ -274,7 +273,7 @@ func TestHCLMultiFileSharedVariables(t *testing.T) {
|
|||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := parseFiles([]File{
|
c, err := ParseFiles([]File{
|
||||||
{Data: dt, Name: "c1.hcl"},
|
{Data: dt, Name: "c1.hcl"},
|
||||||
{Data: dt2, Name: "c2.hcl"},
|
{Data: dt2, Name: "c2.hcl"},
|
||||||
})
|
})
|
||||||
@ -286,7 +285,7 @@ func TestHCLMultiFileSharedVariables(t *testing.T) {
|
|||||||
|
|
||||||
os.Setenv("FOO", "def")
|
os.Setenv("FOO", "def")
|
||||||
|
|
||||||
c, err = parseFiles([]File{
|
c, err = ParseFiles([]File{
|
||||||
{Data: dt, Name: "c1.hcl"},
|
{Data: dt, Name: "c1.hcl"},
|
||||||
{Data: dt2, Name: "c2.hcl"},
|
{Data: dt2, Name: "c2.hcl"},
|
||||||
})
|
})
|
||||||
@ -324,7 +323,7 @@ func TestHCLVarsWithVars(t *testing.T) {
|
|||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := parseFiles([]File{
|
c, err := ParseFiles([]File{
|
||||||
{Data: dt, Name: "c1.hcl"},
|
{Data: dt, Name: "c1.hcl"},
|
||||||
{Data: dt2, Name: "c2.hcl"},
|
{Data: dt2, Name: "c2.hcl"},
|
||||||
})
|
})
|
||||||
@ -336,7 +335,7 @@ func TestHCLVarsWithVars(t *testing.T) {
|
|||||||
|
|
||||||
os.Setenv("BASE", "new")
|
os.Setenv("BASE", "new")
|
||||||
|
|
||||||
c, err = parseFiles([]File{
|
c, err = ParseFiles([]File{
|
||||||
{Data: dt, Name: "c1.hcl"},
|
{Data: dt, Name: "c1.hcl"},
|
||||||
{Data: dt2, Name: "c2.hcl"},
|
{Data: dt2, Name: "c2.hcl"},
|
||||||
})
|
})
|
||||||
@ -481,7 +480,7 @@ func TestHCLMultiFileAttrs(t *testing.T) {
|
|||||||
FOO="def"
|
FOO="def"
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := parseFiles([]File{
|
c, err := ParseFiles([]File{
|
||||||
{Data: dt, Name: "c1.hcl"},
|
{Data: dt, Name: "c1.hcl"},
|
||||||
{Data: dt2, Name: "c2.hcl"},
|
{Data: dt2, Name: "c2.hcl"},
|
||||||
})
|
})
|
||||||
@ -492,7 +491,7 @@ func TestHCLMultiFileAttrs(t *testing.T) {
|
|||||||
|
|
||||||
os.Setenv("FOO", "ghi")
|
os.Setenv("FOO", "ghi")
|
||||||
|
|
||||||
c, err = parseFiles([]File{
|
c, err = ParseFiles([]File{
|
||||||
{Data: dt, Name: "c1.hcl"},
|
{Data: dt, Name: "c1.hcl"},
|
||||||
{Data: dt2, Name: "c2.hcl"},
|
{Data: dt2, Name: "c2.hcl"},
|
||||||
})
|
})
|
||||||
@ -566,3 +565,37 @@ func TestHCLFunctionInAttr(t *testing.T) {
|
|||||||
require.Equal(t, c.Targets[0].Name, "app")
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
require.Equal(t, "FOO <> [baz]", c.Targets[0].Args["v1"])
|
require.Equal(t, "FOO <> [baz]", c.Targets[0].Args["v1"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHCLCombineCompose(t *testing.T) {
|
||||||
|
dt := []byte(`
|
||||||
|
target "app" {
|
||||||
|
context = "dir"
|
||||||
|
args = {
|
||||||
|
v1 = "foo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
dt2 := []byte(`
|
||||||
|
version: "3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
dockerfile: Dockerfile-alternate
|
||||||
|
args:
|
||||||
|
v2: "bar"
|
||||||
|
`)
|
||||||
|
|
||||||
|
c, err := ParseFiles([]File{
|
||||||
|
{Data: dt, Name: "c1.hcl"},
|
||||||
|
{Data: dt2, Name: "c2.yml"},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
|
require.Equal(t, "foo", c.Targets[0].Args["v1"])
|
||||||
|
require.Equal(t, "bar", c.Targets[0].Args["v2"])
|
||||||
|
require.Equal(t, "dir", *c.Targets[0].Context)
|
||||||
|
require.Equal(t, "Dockerfile-alternate", *c.Targets[0].Dockerfile)
|
||||||
|
}
|
||||||
|
153
bake/hclparser/expr.go
Normal file
153
bake/hclparser/expr.go
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
package hclparser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func funcCalls(exp hcl.Expression) ([]string, hcl.Diagnostics) {
|
||||||
|
node, ok := exp.(hclsyntax.Node)
|
||||||
|
if !ok {
|
||||||
|
fns, err := jsonFuncCallsRecursive(exp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, hcl.Diagnostics{
|
||||||
|
&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid expression",
|
||||||
|
Detail: err.Error(),
|
||||||
|
Subject: exp.Range().Ptr(),
|
||||||
|
Context: exp.Range().Ptr(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fns, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var funcnames []string
|
||||||
|
hcldiags := hclsyntax.VisitAll(node, func(n hclsyntax.Node) hcl.Diagnostics {
|
||||||
|
if fe, ok := n.(*hclsyntax.FunctionCallExpr); ok {
|
||||||
|
funcnames = append(funcnames, fe.Name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if hcldiags.HasErrors() {
|
||||||
|
return nil, hcldiags
|
||||||
|
}
|
||||||
|
return funcnames, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsonFuncCallsRecursive(exp hcl.Expression) ([]string, error) {
|
||||||
|
je, ok := exp.(jsonExp)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.Errorf("invalid expression type %T", exp)
|
||||||
|
}
|
||||||
|
m := map[string]struct{}{}
|
||||||
|
for _, e := range elementExpressions(je, exp) {
|
||||||
|
if err := appendJSONFuncCalls(e, m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arr := make([]string, 0, len(m))
|
||||||
|
for n := range m {
|
||||||
|
arr = append(arr, n)
|
||||||
|
}
|
||||||
|
return arr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendJSONFuncCalls(exp hcl.Expression, m map[string]struct{}) error {
|
||||||
|
v := reflect.ValueOf(exp)
|
||||||
|
if v.Kind() != reflect.Ptr || v.IsNil() {
|
||||||
|
return errors.Errorf("invalid json expression kind %T %v", exp, v.Kind())
|
||||||
|
}
|
||||||
|
src := v.Elem().FieldByName("src")
|
||||||
|
if src.IsZero() {
|
||||||
|
return errors.Errorf("%v has no property src", v.Elem().Type())
|
||||||
|
}
|
||||||
|
if src.Kind() != reflect.Interface {
|
||||||
|
return errors.Errorf("%v src is not interface: %v", src.Type(), src.Kind())
|
||||||
|
}
|
||||||
|
src = src.Elem()
|
||||||
|
if src.IsNil() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if src.Kind() == reflect.Ptr {
|
||||||
|
src = src.Elem()
|
||||||
|
}
|
||||||
|
if src.Kind() != reflect.Struct {
|
||||||
|
return errors.Errorf("%v is not struct: %v", src.Type(), src.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
// hcl/v2/json/ast#stringVal
|
||||||
|
val := src.FieldByName("Value")
|
||||||
|
if val.IsZero() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rng := src.FieldByName("SrcRange")
|
||||||
|
if val.IsZero() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var stringVal struct {
|
||||||
|
Value string
|
||||||
|
SrcRange hcl.Range
|
||||||
|
}
|
||||||
|
|
||||||
|
if !val.Type().AssignableTo(reflect.ValueOf(stringVal.Value).Type()) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !rng.Type().AssignableTo(reflect.ValueOf(stringVal.SrcRange).Type()) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// reflect.Set does not work for unexported fields
|
||||||
|
stringVal.Value = *(*string)(unsafe.Pointer(val.UnsafeAddr()))
|
||||||
|
stringVal.SrcRange = *(*hcl.Range)(unsafe.Pointer(rng.UnsafeAddr()))
|
||||||
|
|
||||||
|
expr, diags := hclsyntax.ParseExpression([]byte(stringVal.Value), stringVal.SrcRange.Filename, stringVal.SrcRange.Start)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fns, err := funcCalls(expr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fn := range fns {
|
||||||
|
m[fn] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonExp interface {
|
||||||
|
ExprList() []hcl.Expression
|
||||||
|
ExprMap() []hcl.KeyValuePair
|
||||||
|
}
|
||||||
|
|
||||||
|
func elementExpressions(je jsonExp, exp hcl.Expression) []hcl.Expression {
|
||||||
|
list := je.ExprList()
|
||||||
|
if len(list) != 0 {
|
||||||
|
exp := make([]hcl.Expression, 0, len(list))
|
||||||
|
for _, e := range list {
|
||||||
|
if je, ok := e.(jsonExp); ok {
|
||||||
|
exp = append(exp, elementExpressions(je, e)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return exp
|
||||||
|
}
|
||||||
|
kvlist := je.ExprMap()
|
||||||
|
if len(kvlist) != 0 {
|
||||||
|
exp := make([]hcl.Expression, 0, len(kvlist)*2)
|
||||||
|
for _, p := range kvlist {
|
||||||
|
exp = append(exp, p.Key)
|
||||||
|
if je, ok := p.Value.(jsonExp); ok {
|
||||||
|
exp = append(exp, elementExpressions(je, p.Value)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return exp
|
||||||
|
}
|
||||||
|
return []hcl.Expression{exp}
|
||||||
|
}
|
487
bake/hclparser/hclparser.go
Normal file
487
bake/hclparser/hclparser.go
Normal file
@ -0,0 +1,487 @@
|
|||||||
|
package hclparser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/userfunc"
|
||||||
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
"github.com/hashicorp/hcl/v2/gohcl"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Opt struct {
|
||||||
|
LookupVar func(string) (string, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type variable struct {
|
||||||
|
Name string `json:"-" hcl:"name,label"`
|
||||||
|
Default *hcl.Attribute `json:"default,omitempty" hcl:"default,optional"`
|
||||||
|
Body hcl.Body `json:"-" hcl:",body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type functionDef struct {
|
||||||
|
Name string `json:"-" hcl:"name,label"`
|
||||||
|
Params *hcl.Attribute `json:"params,omitempty" hcl:"params"`
|
||||||
|
Variadic *hcl.Attribute `json:"variadic_param,omitempty" hcl:"variadic_params"`
|
||||||
|
Result *hcl.Attribute `json:"result,omitempty" hcl:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type inputs struct {
|
||||||
|
Variables []*variable `hcl:"variable,block"`
|
||||||
|
Functions []*functionDef `hcl:"function,block"`
|
||||||
|
|
||||||
|
Remain hcl.Body `json:"-" hcl:",remain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type parser struct {
|
||||||
|
opt Opt
|
||||||
|
|
||||||
|
vars map[string]*variable
|
||||||
|
attrs map[string]*hcl.Attribute
|
||||||
|
funcs map[string]*functionDef
|
||||||
|
|
||||||
|
ectx *hcl.EvalContext
|
||||||
|
|
||||||
|
progress map[string]struct{}
|
||||||
|
progressF map[string]struct{}
|
||||||
|
doneF map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) loadDeps(exp hcl.Expression, exclude map[string]struct{}) hcl.Diagnostics {
|
||||||
|
fns, hcldiags := funcCalls(exp)
|
||||||
|
if hcldiags.HasErrors() {
|
||||||
|
return hcldiags
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fn := range fns {
|
||||||
|
if err := p.resolveFunction(fn); err != nil {
|
||||||
|
return hcl.Diagnostics{
|
||||||
|
&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid expression",
|
||||||
|
Detail: err.Error(),
|
||||||
|
Subject: exp.Range().Ptr(),
|
||||||
|
Context: exp.Range().Ptr(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range exp.Variables() {
|
||||||
|
if _, ok := exclude[v.RootName()]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := p.resolveValue(v.RootName()); err != nil {
|
||||||
|
return hcl.Diagnostics{
|
||||||
|
&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid expression",
|
||||||
|
Detail: err.Error(),
|
||||||
|
Subject: v.SourceRange().Ptr(),
|
||||||
|
Context: v.SourceRange().Ptr(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) resolveFunction(name string) error {
|
||||||
|
if _, ok := p.doneF[name]; ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
f, ok := p.funcs[name]
|
||||||
|
if !ok {
|
||||||
|
if _, ok := p.ectx.Functions[name]; ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.Errorf("undefined function %s", name)
|
||||||
|
}
|
||||||
|
if _, ok := p.progressF[name]; ok {
|
||||||
|
return errors.Errorf("function cycle not allowed for %s", name)
|
||||||
|
}
|
||||||
|
p.progressF[name] = struct{}{}
|
||||||
|
|
||||||
|
paramExprs, paramsDiags := hcl.ExprList(f.Params.Expr)
|
||||||
|
if paramsDiags.HasErrors() {
|
||||||
|
return paramsDiags
|
||||||
|
}
|
||||||
|
var diags hcl.Diagnostics
|
||||||
|
params := map[string]struct{}{}
|
||||||
|
for _, paramExpr := range paramExprs {
|
||||||
|
param := hcl.ExprAsKeyword(paramExpr)
|
||||||
|
if param == "" {
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid param element",
|
||||||
|
Detail: "Each parameter name must be an identifier.",
|
||||||
|
Subject: paramExpr.Range().Ptr(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
params[param] = struct{}{}
|
||||||
|
}
|
||||||
|
var variadic hcl.Expression
|
||||||
|
if f.Variadic != nil {
|
||||||
|
variadic = f.Variadic.Expr
|
||||||
|
param := hcl.ExprAsKeyword(variadic)
|
||||||
|
if param == "" {
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid param element",
|
||||||
|
Detail: "Each parameter name must be an identifier.",
|
||||||
|
Subject: f.Variadic.Range.Ptr(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
params[param] = struct{}{}
|
||||||
|
}
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
|
||||||
|
if diags := p.loadDeps(f.Result.Expr, params); diags.HasErrors() {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
|
||||||
|
v, diags := userfunc.NewFunction(f.Params.Expr, variadic, f.Result.Expr, func() *hcl.EvalContext {
|
||||||
|
return p.ectx
|
||||||
|
})
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
p.doneF[name] = struct{}{}
|
||||||
|
p.ectx.Functions[name] = v
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) resolveValue(name string) (err error) {
|
||||||
|
if _, ok := p.ectx.Variables[name]; ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if _, ok := p.progress[name]; ok {
|
||||||
|
return errors.Errorf("variable cycle not allowed for %s", name)
|
||||||
|
}
|
||||||
|
p.progress[name] = struct{}{}
|
||||||
|
|
||||||
|
var v *cty.Value
|
||||||
|
defer func() {
|
||||||
|
if v != nil {
|
||||||
|
p.ectx.Variables[name] = *v
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
def, ok := p.attrs[name]
|
||||||
|
if !ok {
|
||||||
|
vr, ok := p.vars[name]
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("undefined variable %q", name)
|
||||||
|
}
|
||||||
|
def = vr.Default
|
||||||
|
}
|
||||||
|
|
||||||
|
if def == nil {
|
||||||
|
val, _ := p.opt.LookupVar(name)
|
||||||
|
vv := cty.StringVal(val)
|
||||||
|
v = &vv
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if diags := p.loadDeps(def.Expr, nil); diags.HasErrors() {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
vv, diags := def.Expr.Value(p.ectx)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
|
||||||
|
_, isVar := p.vars[name]
|
||||||
|
|
||||||
|
if envv, ok := p.opt.LookupVar(name); ok && isVar {
|
||||||
|
if vv.Type().Equals(cty.Bool) {
|
||||||
|
b, err := strconv.ParseBool(envv)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to parse %s as bool", name)
|
||||||
|
}
|
||||||
|
vv := cty.BoolVal(b)
|
||||||
|
v = &vv
|
||||||
|
return nil
|
||||||
|
} else if vv.Type().Equals(cty.String) {
|
||||||
|
vv := cty.StringVal(envv)
|
||||||
|
v = &vv
|
||||||
|
return nil
|
||||||
|
} else if vv.Type().Equals(cty.Number) {
|
||||||
|
n, err := strconv.ParseFloat(envv, 64)
|
||||||
|
if err == nil && (math.IsNaN(n) || math.IsInf(n, 0)) {
|
||||||
|
err = errors.Errorf("invalid number value")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to parse %s as number", name)
|
||||||
|
}
|
||||||
|
vv := cty.NumberVal(big.NewFloat(n))
|
||||||
|
v = &vv
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
// TODO: support lists with csv values
|
||||||
|
return errors.Errorf("unsupported type %s for variable %s", v.Type(), name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v = &vv
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parse(b hcl.Body, opt Opt, val interface{}) hcl.Diagnostics {
|
||||||
|
reserved := map[string]struct{}{}
|
||||||
|
schema, _ := gohcl.ImpliedBodySchema(val)
|
||||||
|
|
||||||
|
for _, bs := range schema.Blocks {
|
||||||
|
reserved[bs.Type] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var defs inputs
|
||||||
|
if err := gohcl.DecodeBody(b, nil, &defs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.LookupVar == nil {
|
||||||
|
opt.LookupVar = func(string) (string, bool) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &parser{
|
||||||
|
opt: opt,
|
||||||
|
|
||||||
|
vars: map[string]*variable{},
|
||||||
|
attrs: map[string]*hcl.Attribute{},
|
||||||
|
funcs: map[string]*functionDef{},
|
||||||
|
|
||||||
|
progress: map[string]struct{}{},
|
||||||
|
progressF: map[string]struct{}{},
|
||||||
|
doneF: map[string]struct{}{},
|
||||||
|
ectx: &hcl.EvalContext{
|
||||||
|
Variables: map[string]cty.Value{},
|
||||||
|
Functions: stdlibFunctions,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range defs.Variables {
|
||||||
|
// TODO: validate name
|
||||||
|
if _, ok := reserved[v.Name]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.vars[v.Name] = v
|
||||||
|
}
|
||||||
|
for _, v := range defs.Functions {
|
||||||
|
// TODO: validate name
|
||||||
|
if _, ok := reserved[v.Name]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.funcs[v.Name] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs, diags := b.JustAttributes()
|
||||||
|
if diags.HasErrors() {
|
||||||
|
for _, d := range diags {
|
||||||
|
if d.Detail != "Blocks are not allowed here." {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range attrs {
|
||||||
|
if _, ok := reserved[v.Name]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.attrs[v.Name] = v
|
||||||
|
}
|
||||||
|
delete(p.attrs, "function")
|
||||||
|
|
||||||
|
for k := range p.attrs {
|
||||||
|
if err := p.resolveValue(k); err != nil {
|
||||||
|
if diags, ok := err.(hcl.Diagnostics); ok {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
return hcl.Diagnostics{
|
||||||
|
&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid attribute",
|
||||||
|
Detail: err.Error(),
|
||||||
|
Subject: &p.attrs[k].Range,
|
||||||
|
Context: &p.attrs[k].Range,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range p.vars {
|
||||||
|
if err := p.resolveValue(k); err != nil {
|
||||||
|
if diags, ok := err.(hcl.Diagnostics); ok {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
r := p.vars[k].Body.MissingItemRange()
|
||||||
|
return hcl.Diagnostics{
|
||||||
|
&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid value",
|
||||||
|
Detail: err.Error(),
|
||||||
|
Subject: &r,
|
||||||
|
Context: &r,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range p.funcs {
|
||||||
|
if err := p.resolveFunction(k); err != nil {
|
||||||
|
if diags, ok := err.(hcl.Diagnostics); ok {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
return hcl.Diagnostics{
|
||||||
|
&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid function",
|
||||||
|
Detail: err.Error(),
|
||||||
|
Subject: &p.funcs[k].Params.Range,
|
||||||
|
Context: &p.funcs[k].Params.Range,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content, _, diags := b.PartialContent(schema)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range content.Attributes {
|
||||||
|
return hcl.Diagnostics{
|
||||||
|
&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid attribute",
|
||||||
|
Detail: "global attributes currently not supported",
|
||||||
|
Subject: &a.Range,
|
||||||
|
Context: &a.Range,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m := 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 := m[b.Type]
|
||||||
|
if !ok {
|
||||||
|
bm = map[string][]*hcl.Block{}
|
||||||
|
m[b.Type] = bm
|
||||||
|
}
|
||||||
|
|
||||||
|
lbl := b.Labels[0]
|
||||||
|
bm[lbl] = append(bm[lbl], b)
|
||||||
|
}
|
||||||
|
|
||||||
|
vt := reflect.ValueOf(val).Elem().Type()
|
||||||
|
numFields := vt.NumField()
|
||||||
|
|
||||||
|
type value struct {
|
||||||
|
reflect.Value
|
||||||
|
idx int
|
||||||
|
}
|
||||||
|
type field struct {
|
||||||
|
idx int
|
||||||
|
typ reflect.Type
|
||||||
|
values map[string]value
|
||||||
|
}
|
||||||
|
types := map[string]field{}
|
||||||
|
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
tags := strings.Split(vt.Field(i).Tag.Get("hcl"), ",")
|
||||||
|
|
||||||
|
types[tags[0]] = field{
|
||||||
|
idx: i,
|
||||||
|
typ: vt.Field(i).Type,
|
||||||
|
values: make(map[string]value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diags = hcl.Diagnostics{}
|
||||||
|
for _, b := range content.Blocks {
|
||||||
|
v := reflect.ValueOf(val)
|
||||||
|
|
||||||
|
t, ok := types[b.Type]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
vv := reflect.New(t.typ.Elem().Elem())
|
||||||
|
diag := gohcl.DecodeBody(b.Body, p.ectx, vv.Interface())
|
||||||
|
if diag.HasErrors() {
|
||||||
|
diags = append(diags, diag...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
lblIndex := setLabel(vv, b.Labels[0])
|
||||||
|
|
||||||
|
oldValue, exists := t.values[b.Labels[0]]
|
||||||
|
if !exists && lblIndex != -1 {
|
||||||
|
if v.Elem().Field(t.idx).Type().Kind() == reflect.Slice {
|
||||||
|
for i := 0; i < v.Elem().Field(t.idx).Len(); i++ {
|
||||||
|
if b.Labels[0] == v.Elem().Field(t.idx).Index(i).Elem().Field(lblIndex).String() {
|
||||||
|
exists = true
|
||||||
|
oldValue = value{Value: v.Elem().Field(t.idx).Index(i), idx: i}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
if m := oldValue.Value.MethodByName("Merge"); m.IsValid() {
|
||||||
|
m.Call([]reflect.Value{vv})
|
||||||
|
} else {
|
||||||
|
v.Elem().Field(t.idx).Index(oldValue.idx).Set(vv)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
slice := v.Elem().Field(t.idx)
|
||||||
|
if slice.IsNil() {
|
||||||
|
slice = reflect.New(t.typ).Elem()
|
||||||
|
}
|
||||||
|
t.values[b.Labels[0]] = value{Value: vv, idx: slice.Len()}
|
||||||
|
v.Elem().Field(t.idx).Set(reflect.Append(slice, vv))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setLabel(v reflect.Value, lbl string) int {
|
||||||
|
// cache field index?
|
||||||
|
numFields := v.Elem().Type().NumField()
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
for _, t := range strings.Split(v.Elem().Type().Field(i).Tag.Get("hcl"), ",") {
|
||||||
|
if t == "label" {
|
||||||
|
v.Elem().Field(i).Set(reflect.ValueOf(lbl))
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
111
bake/hclparser/stdlib.go
Normal file
111
bake/hclparser/stdlib.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package hclparser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/go-cty-funcs/cidr"
|
||||||
|
"github.com/hashicorp/go-cty-funcs/crypto"
|
||||||
|
"github.com/hashicorp/go-cty-funcs/encoding"
|
||||||
|
"github.com/hashicorp/go-cty-funcs/uuid"
|
||||||
|
"github.com/hashicorp/hcl/v2/ext/tryfunc"
|
||||||
|
"github.com/hashicorp/hcl/v2/ext/typeexpr"
|
||||||
|
"github.com/zclconf/go-cty/cty/function"
|
||||||
|
"github.com/zclconf/go-cty/cty/function/stdlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
var stdlibFunctions = map[string]function.Function{
|
||||||
|
"absolute": stdlib.AbsoluteFunc,
|
||||||
|
"add": stdlib.AddFunc,
|
||||||
|
"and": stdlib.AndFunc,
|
||||||
|
"base64decode": encoding.Base64DecodeFunc,
|
||||||
|
"base64encode": encoding.Base64EncodeFunc,
|
||||||
|
"bcrypt": crypto.BcryptFunc,
|
||||||
|
"byteslen": stdlib.BytesLenFunc,
|
||||||
|
"bytesslice": stdlib.BytesSliceFunc,
|
||||||
|
"can": tryfunc.CanFunc,
|
||||||
|
"ceil": stdlib.CeilFunc,
|
||||||
|
"chomp": stdlib.ChompFunc,
|
||||||
|
"chunklist": stdlib.ChunklistFunc,
|
||||||
|
"cidrhost": cidr.HostFunc,
|
||||||
|
"cidrnetmask": cidr.NetmaskFunc,
|
||||||
|
"cidrsubnet": cidr.SubnetFunc,
|
||||||
|
"cidrsubnets": cidr.SubnetsFunc,
|
||||||
|
"csvdecode": stdlib.CSVDecodeFunc,
|
||||||
|
"coalesce": stdlib.CoalesceFunc,
|
||||||
|
"coalescelist": stdlib.CoalesceListFunc,
|
||||||
|
"compact": stdlib.CompactFunc,
|
||||||
|
"concat": stdlib.ConcatFunc,
|
||||||
|
"contains": stdlib.ContainsFunc,
|
||||||
|
"convert": typeexpr.ConvertFunc,
|
||||||
|
"distinct": stdlib.DistinctFunc,
|
||||||
|
"divide": stdlib.DivideFunc,
|
||||||
|
"element": stdlib.ElementFunc,
|
||||||
|
"equal": stdlib.EqualFunc,
|
||||||
|
"flatten": stdlib.FlattenFunc,
|
||||||
|
"floor": stdlib.FloorFunc,
|
||||||
|
"formatdate": stdlib.FormatDateFunc,
|
||||||
|
"format": stdlib.FormatFunc,
|
||||||
|
"formatlist": stdlib.FormatListFunc,
|
||||||
|
"greaterthan": stdlib.GreaterThanFunc,
|
||||||
|
"greaterthanorequalto": stdlib.GreaterThanOrEqualToFunc,
|
||||||
|
"hasindex": stdlib.HasIndexFunc,
|
||||||
|
"indent": stdlib.IndentFunc,
|
||||||
|
"index": stdlib.IndexFunc,
|
||||||
|
"int": stdlib.IntFunc,
|
||||||
|
"jsondecode": stdlib.JSONDecodeFunc,
|
||||||
|
"jsonencode": stdlib.JSONEncodeFunc,
|
||||||
|
"keys": stdlib.KeysFunc,
|
||||||
|
"join": stdlib.JoinFunc,
|
||||||
|
"length": stdlib.LengthFunc,
|
||||||
|
"lessthan": stdlib.LessThanFunc,
|
||||||
|
"lessthanorequalto": stdlib.LessThanOrEqualToFunc,
|
||||||
|
"log": stdlib.LogFunc,
|
||||||
|
"lookup": stdlib.LookupFunc,
|
||||||
|
"lower": stdlib.LowerFunc,
|
||||||
|
"max": stdlib.MaxFunc,
|
||||||
|
"md5": crypto.Md5Func,
|
||||||
|
"merge": stdlib.MergeFunc,
|
||||||
|
"min": stdlib.MinFunc,
|
||||||
|
"modulo": stdlib.ModuloFunc,
|
||||||
|
"multiply": stdlib.MultiplyFunc,
|
||||||
|
"negate": stdlib.NegateFunc,
|
||||||
|
"notequal": stdlib.NotEqualFunc,
|
||||||
|
"not": stdlib.NotFunc,
|
||||||
|
"or": stdlib.OrFunc,
|
||||||
|
"parseint": stdlib.ParseIntFunc,
|
||||||
|
"pow": stdlib.PowFunc,
|
||||||
|
"range": stdlib.RangeFunc,
|
||||||
|
"regexall": stdlib.RegexAllFunc,
|
||||||
|
"regex": stdlib.RegexFunc,
|
||||||
|
"regex_replace": stdlib.RegexReplaceFunc,
|
||||||
|
"reverse": stdlib.ReverseFunc,
|
||||||
|
"reverselist": stdlib.ReverseListFunc,
|
||||||
|
"rsadecrypt": crypto.RsaDecryptFunc,
|
||||||
|
"sethaselement": stdlib.SetHasElementFunc,
|
||||||
|
"setintersection": stdlib.SetIntersectionFunc,
|
||||||
|
"setproduct": stdlib.SetProductFunc,
|
||||||
|
"setsubtract": stdlib.SetSubtractFunc,
|
||||||
|
"setsymmetricdifference": stdlib.SetSymmetricDifferenceFunc,
|
||||||
|
"setunion": stdlib.SetUnionFunc,
|
||||||
|
"sha1": crypto.Sha1Func,
|
||||||
|
"sha256": crypto.Sha256Func,
|
||||||
|
"sha512": crypto.Sha512Func,
|
||||||
|
"signum": stdlib.SignumFunc,
|
||||||
|
"slice": stdlib.SliceFunc,
|
||||||
|
"sort": stdlib.SortFunc,
|
||||||
|
"split": stdlib.SplitFunc,
|
||||||
|
"strlen": stdlib.StrlenFunc,
|
||||||
|
"substr": stdlib.SubstrFunc,
|
||||||
|
"subtract": stdlib.SubtractFunc,
|
||||||
|
"timeadd": stdlib.TimeAddFunc,
|
||||||
|
"title": stdlib.TitleFunc,
|
||||||
|
"trim": stdlib.TrimFunc,
|
||||||
|
"trimprefix": stdlib.TrimPrefixFunc,
|
||||||
|
"trimspace": stdlib.TrimSpaceFunc,
|
||||||
|
"trimsuffix": stdlib.TrimSuffixFunc,
|
||||||
|
"try": tryfunc.TryFunc,
|
||||||
|
"upper": stdlib.UpperFunc,
|
||||||
|
"urlencode": encoding.URLEncodeFunc,
|
||||||
|
"uuidv4": uuid.V4Func,
|
||||||
|
"uuidv5": uuid.V5Func,
|
||||||
|
"values": stdlib.ValuesFunc,
|
||||||
|
"zipmap": stdlib.ZipmapFunc,
|
||||||
|
}
|
135
vendor/github.com/hashicorp/hcl/v2/hclparse/parser.go
generated
vendored
Normal file
135
vendor/github.com/hashicorp/hcl/v2/hclparse/parser.go
generated
vendored
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
// Package hclparse has the main API entry point for parsing both HCL native
|
||||||
|
// syntax and HCL JSON.
|
||||||
|
//
|
||||||
|
// The main HCL package also includes SimpleParse and SimpleParseFile which
|
||||||
|
// can be a simpler interface for the common case where an application just
|
||||||
|
// needs to parse a single file. The gohcl package simplifies that further
|
||||||
|
// in its SimpleDecode function, which combines hcl.SimpleParse with decoding
|
||||||
|
// into Go struct values
|
||||||
|
//
|
||||||
|
// Package hclparse, then, is useful for applications that require more fine
|
||||||
|
// control over parsing or which need to load many separate files and keep
|
||||||
|
// track of them for possible error reporting or other analysis.
|
||||||
|
package hclparse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
|
"github.com/hashicorp/hcl/v2/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NOTE: This is the public interface for parsing. The actual parsers are
|
||||||
|
// in other packages alongside this one, with this package just wrapping them
|
||||||
|
// to provide a unified interface for the caller across all supported formats.
|
||||||
|
|
||||||
|
// Parser is the main interface for parsing configuration files. As well as
|
||||||
|
// parsing files, a parser also retains a registry of all of the files it
|
||||||
|
// has parsed so that multiple attempts to parse the same file will return
|
||||||
|
// the same object and so the collected files can be used when printing
|
||||||
|
// diagnostics.
|
||||||
|
//
|
||||||
|
// Any diagnostics for parsing a file are only returned once on the first
|
||||||
|
// call to parse that file. Callers are expected to collect up diagnostics
|
||||||
|
// and present them together, so returning diagnostics for the same file
|
||||||
|
// multiple times would create a confusing result.
|
||||||
|
type Parser struct {
|
||||||
|
files map[string]*hcl.File
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new parser, ready to parse configuration files.
|
||||||
|
func NewParser() *Parser {
|
||||||
|
return &Parser{
|
||||||
|
files: map[string]*hcl.File{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseHCL parses the given buffer (which is assumed to have been loaded from
|
||||||
|
// the given filename) as a native-syntax configuration file and returns the
|
||||||
|
// hcl.File object representing it.
|
||||||
|
func (p *Parser) ParseHCL(src []byte, filename string) (*hcl.File, hcl.Diagnostics) {
|
||||||
|
if existing := p.files[filename]; existing != nil {
|
||||||
|
return existing, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
file, diags := hclsyntax.ParseConfig(src, filename, hcl.Pos{Byte: 0, Line: 1, Column: 1})
|
||||||
|
p.files[filename] = file
|
||||||
|
return file, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseHCLFile reads the given filename and parses it as a native-syntax HCL
|
||||||
|
// configuration file. An error diagnostic is returned if the given file
|
||||||
|
// cannot be read.
|
||||||
|
func (p *Parser) ParseHCLFile(filename string) (*hcl.File, hcl.Diagnostics) {
|
||||||
|
if existing := p.files[filename]; existing != nil {
|
||||||
|
return existing, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
src, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, hcl.Diagnostics{
|
||||||
|
{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Failed to read file",
|
||||||
|
Detail: fmt.Sprintf("The configuration file %q could not be read.", filename),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.ParseHCL(src, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseJSON parses the given JSON buffer (which is assumed to have been loaded
|
||||||
|
// from the given filename) and returns the hcl.File object representing it.
|
||||||
|
func (p *Parser) ParseJSON(src []byte, filename string) (*hcl.File, hcl.Diagnostics) {
|
||||||
|
if existing := p.files[filename]; existing != nil {
|
||||||
|
return existing, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
file, diags := json.Parse(src, filename)
|
||||||
|
p.files[filename] = file
|
||||||
|
return file, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseJSONFile reads the given filename and parses it as JSON, similarly to
|
||||||
|
// ParseJSON. An error diagnostic is returned if the given file cannot be read.
|
||||||
|
func (p *Parser) ParseJSONFile(filename string) (*hcl.File, hcl.Diagnostics) {
|
||||||
|
if existing := p.files[filename]; existing != nil {
|
||||||
|
return existing, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
file, diags := json.ParseFile(filename)
|
||||||
|
p.files[filename] = file
|
||||||
|
return file, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFile allows a caller to record in a parser a file that was parsed some
|
||||||
|
// other way, thus allowing it to be included in the registry of sources.
|
||||||
|
func (p *Parser) AddFile(filename string, file *hcl.File) {
|
||||||
|
p.files[filename] = file
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sources returns a map from filenames to the raw source code that was
|
||||||
|
// read from them. This is intended to be used, for example, to print
|
||||||
|
// diagnostics with contextual information.
|
||||||
|
//
|
||||||
|
// The arrays underlying the returned slices should not be modified.
|
||||||
|
func (p *Parser) Sources() map[string][]byte {
|
||||||
|
ret := make(map[string][]byte)
|
||||||
|
for fn, f := range p.files {
|
||||||
|
ret[fn] = f.Bytes
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Files returns a map from filenames to the File objects produced from them.
|
||||||
|
// This is intended to be used, for example, to print diagnostics with
|
||||||
|
// contextual information.
|
||||||
|
//
|
||||||
|
// The returned map and all of the objects it refers to directly or indirectly
|
||||||
|
// must not be modified.
|
||||||
|
func (p *Parser) Files() map[string]*hcl.File {
|
||||||
|
return p.files
|
||||||
|
}
|
1
vendor/modules.txt
vendored
1
vendor/modules.txt
vendored
@ -228,6 +228,7 @@ github.com/hashicorp/hcl/v2/ext/customdecode
|
|||||||
github.com/hashicorp/hcl/v2/ext/tryfunc
|
github.com/hashicorp/hcl/v2/ext/tryfunc
|
||||||
github.com/hashicorp/hcl/v2/ext/typeexpr
|
github.com/hashicorp/hcl/v2/ext/typeexpr
|
||||||
github.com/hashicorp/hcl/v2/gohcl
|
github.com/hashicorp/hcl/v2/gohcl
|
||||||
|
github.com/hashicorp/hcl/v2/hclparse
|
||||||
github.com/hashicorp/hcl/v2/hclsyntax
|
github.com/hashicorp/hcl/v2/hclsyntax
|
||||||
github.com/hashicorp/hcl/v2/hclwrite
|
github.com/hashicorp/hcl/v2/hclwrite
|
||||||
github.com/hashicorp/hcl/v2/json
|
github.com/hashicorp/hcl/v2/json
|
||||||
|
Loading…
x
Reference in New Issue
Block a user