Consul is a distributed, highly available, and data center aware solution to connect and configure applications across dynamic, distributed infrastructure.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

287 lines
7.4 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package protohcl
import (
"fmt"
"sort"
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
"google.golang.org/protobuf/reflect/protoreflect"
)
// MessageDecoder provides an abstract way to decode protobuf messages from HCL
// blocks or objects.
type MessageDecoder interface {
// EachField calls the given iterator for each field provided in the HCL source.
EachField(iter FieldIterator) error
// SkipFields returns a MessageDecoder that skips over the given fields. It is
// primarily used for doing two-pass decoding of protobuf `Any` fields.
SkipFields(fields ...string) MessageDecoder
}
// IterField represents a field discovered by the MessageDecoder.
type IterField struct {
// Name is the HCL name of the field.
Name string
// Desc is the protobuf field descriptor.
Desc protoreflect.FieldDescriptor
// Val is the field value, only if it was given using HCL attribute syntax.
Val *cty.Value
// Blocks contains the HCL blocks that were given for this field.
Blocks []*hcl.Block
// Range determines where in the HCL source the field was given, it is useful
// for error messages.
Range hcl.Range
}
// FieldIterator is given to MessageDecoder.EachField to iterate over all of the
// fields in a given HCL block or object.
type FieldIterator struct {
// IgnoreUnknown instructs the MessageDecoder to skip over any fields not
// included in Desc.
IgnoreUnknown bool
// Desc is the protobuf descriptor for the message the caller is decoding into.
// It is used to determine which fields are valid.
Desc protoreflect.MessageDescriptor
// Func is called for each field in the given HCL block or object.
Func func(field *IterField) error
}
func newBodyDecoder(
body hcl.Body,
namer FieldNamer,
functions map[string]function.Function,
) MessageDecoder {
return bodyDecoder{
body: body,
namer: namer,
functions: functions,
skipFields: make(map[string]struct{}),
}
}
type bodyDecoder struct {
body hcl.Body
namer FieldNamer
functions map[string]function.Function
skipFields map[string]struct{}
}
func (bd bodyDecoder) EachField(iter FieldIterator) error {
schema, err := bd.schema(iter.Desc)
if err != nil {
return err
}
var (
content *hcl.BodyContent
diags hcl.Diagnostics
)
if iter.IgnoreUnknown {
content, _, diags = bd.body.PartialContent(schema)
} else {
content, diags = bd.body.Content(schema)
}
if diags.HasErrors() {
return diags
}
fields := make([]*IterField, 0)
for _, attr := range content.Attributes {
if _, ok := bd.skipFields[attr.Name]; ok {
continue
}
desc := bd.namer.GetField(iter.Desc.Fields(), attr.Name)
val, err := attr.Expr.Value(&hcl.EvalContext{Functions: bd.functions})
if err != nil {
return err
}
fields = append(fields, &IterField{
Name: attr.Name,
Desc: desc,
Val: &val,
Range: attr.Expr.Range(),
})
}
for blockType, blocks := range content.Blocks.ByType() {
if _, ok := bd.skipFields[blockType]; ok {
continue
}
desc := bd.namer.GetField(iter.Desc.Fields(), blockType)
fields = append(fields, &IterField{
Name: blockType,
Desc: desc,
Blocks: blocks,
})
}
// Always handle Any fields last, as decoding them may require type information
// gathered from other fields (e.g. as in the case of Resource GVKs).
sort.Slice(fields, func(a, b int) bool {
if isAnyField(fields[b].Desc) && !isAnyField(fields[a].Desc) {
return true
}
return a < b
})
for _, field := range fields {
if err := iter.Func(field); err != nil {
return err
}
}
return nil
}
func (bd bodyDecoder) SkipFields(fields ...string) MessageDecoder {
skip := make(map[string]struct{}, len(fields)+len(bd.skipFields))
for k, v := range bd.skipFields {
skip[k] = v
}
for _, field := range fields {
skip[field] = struct{}{}
}
// Note: we rely on the fact bd isn't a pointer to copy the struct here.
bd.skipFields = skip
return bd
}
func (bd bodyDecoder) schema(desc protoreflect.MessageDescriptor) (*hcl.BodySchema, error) {
var schema hcl.BodySchema
fields := desc.Fields()
for i := 0; i < fields.Len(); i++ {
f := fields.Get(i)
kind := f.Kind()
// maps are special and whether they can use block syntax depends on the value type
if f.IsMap() {
valueDesc := f.MapValue()
valueKind := valueDesc.Kind()
wktHint := wellKnownTypeSchemaHint(valueDesc)
// Maps with values that are Messages can generally be decoded using the block syntax.
// The exception are some of the Well-Known-Types that appear as scalar values with
// either string or numeric encoding but get parsed into message types. It is still
// fine to also decode these from the attribute syntax.
if valueKind == protoreflect.MessageKind && wktHint != wellKnownAttribute {
schema.Blocks = append(schema.Blocks, hcl.BlockHeaderSchema{
Type: bd.namer.NameField(f),
LabelNames: []string{"key"},
})
}
// non-message types or Well Known Message types that need attribute encoding
// get decoded as attributes
schema.Attributes = append(schema.Attributes, hcl.AttributeSchema{
Name: bd.namer.NameField(f),
})
continue
}
wktHint := wellKnownTypeSchemaHint(f)
// message types generally will use block syntax unless its a well known
// message type that requires attribute syntax specifically.
if kind == protoreflect.MessageKind && wktHint != wellKnownAttribute {
schema.Blocks = append(schema.Blocks, hcl.BlockHeaderSchema{
Type: bd.namer.NameField(f),
})
}
// by default use attribute encoding
// - primitives
// - repeated primitives
// - Well Known Types requiring attribute syntax
// - repeated Well Known Types requiring attribute syntax
schema.Attributes = append(schema.Attributes, hcl.AttributeSchema{
Name: bd.namer.NameField(f),
})
continue
}
// Add skipped fields to the schema so HCL doesn't throw an error when it finds them.
for field := range bd.skipFields {
schema.Attributes = append(schema.Attributes, hcl.AttributeSchema{Name: field})
schema.Blocks = append(schema.Blocks, hcl.BlockHeaderSchema{Type: field})
}
return &schema, nil
}
func newObjectDecoder(object cty.Value, namer FieldNamer, rng hcl.Range) MessageDecoder {
return objectDecoder{
object: object,
namer: namer,
rng: rng,
skipFields: make(map[string]struct{}),
}
}
type objectDecoder struct {
object cty.Value
namer FieldNamer
rng hcl.Range
skipFields map[string]struct{}
}
func (od objectDecoder) EachField(iter FieldIterator) error {
for attr := range od.object.Type().AttributeTypes() {
if _, ok := od.skipFields[attr]; ok {
continue
}
desc := od.namer.GetField(iter.Desc.Fields(), attr)
if desc == nil {
if iter.IgnoreUnknown {
continue
} else {
return fmt.Errorf("%s: Unsupported argument; An argument named %q is not expected here.", od.rng, attr)
}
}
val := od.object.GetAttr(attr)
if err := iter.Func(&IterField{
Name: attr,
Desc: desc,
Val: &val,
}); err != nil {
return err
}
}
return nil
}
func (od objectDecoder) SkipFields(fields ...string) MessageDecoder {
skip := make(map[string]struct{}, len(fields)+len(od.skipFields))
for k, v := range od.skipFields {
skip[k] = v
}
for _, field := range fields {
skip[field] = struct{}{}
}
// Note: we rely on the fact od isn't a pointer to copy the struct here.
od.skipFields = skip
return od
}