mirror of
https://github.com/letic/terraform-provider-google.git
synced 2024-09-18 17:19:58 +00:00
961c878e0d
Switch to using Go modules. This migrates our vendor.json to use Go 1.11's modules system, and replaces the vendor folder with the output of go mod vendor. The vendored code should remain basically the same; I believe some tree shaking of packages and support scripts/licenses/READMEs/etc. happened. This also fixes Travis and our Makefile to no longer use govendor.
175 lines
4.0 KiB
Go
175 lines
4.0 KiB
Go
package gohcl
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/hcl2/hcl"
|
|
)
|
|
|
|
// ImpliedBodySchema produces a hcl.BodySchema derived from the type of the
|
|
// given value, which must be a struct value or a pointer to one. If an
|
|
// inappropriate value is passed, this function will panic.
|
|
//
|
|
// The second return argument indicates whether the given struct includes
|
|
// a "remain" field, and thus the returned schema is non-exhaustive.
|
|
//
|
|
// This uses the tags on the fields of the struct to discover how each
|
|
// field's value should be expressed within configuration. If an invalid
|
|
// mapping is attempted, this function will panic.
|
|
func ImpliedBodySchema(val interface{}) (schema *hcl.BodySchema, partial bool) {
|
|
ty := reflect.TypeOf(val)
|
|
|
|
if ty.Kind() == reflect.Ptr {
|
|
ty = ty.Elem()
|
|
}
|
|
|
|
if ty.Kind() != reflect.Struct {
|
|
panic(fmt.Sprintf("given value must be struct, not %T", val))
|
|
}
|
|
|
|
var attrSchemas []hcl.AttributeSchema
|
|
var blockSchemas []hcl.BlockHeaderSchema
|
|
|
|
tags := getFieldTags(ty)
|
|
|
|
attrNames := make([]string, 0, len(tags.Attributes))
|
|
for n := range tags.Attributes {
|
|
attrNames = append(attrNames, n)
|
|
}
|
|
sort.Strings(attrNames)
|
|
for _, n := range attrNames {
|
|
idx := tags.Attributes[n]
|
|
optional := tags.Optional[n]
|
|
field := ty.Field(idx)
|
|
|
|
var required bool
|
|
|
|
switch {
|
|
case field.Type.AssignableTo(exprType):
|
|
// If we're decoding to hcl.Expression then absense can be
|
|
// indicated via a null value, so we don't specify that
|
|
// the field is required during decoding.
|
|
required = false
|
|
case field.Type.Kind() != reflect.Ptr && !optional:
|
|
required = true
|
|
default:
|
|
required = false
|
|
}
|
|
|
|
attrSchemas = append(attrSchemas, hcl.AttributeSchema{
|
|
Name: n,
|
|
Required: required,
|
|
})
|
|
}
|
|
|
|
blockNames := make([]string, 0, len(tags.Blocks))
|
|
for n := range tags.Blocks {
|
|
blockNames = append(blockNames, n)
|
|
}
|
|
sort.Strings(blockNames)
|
|
for _, n := range blockNames {
|
|
idx := tags.Blocks[n]
|
|
field := ty.Field(idx)
|
|
fty := field.Type
|
|
if fty.Kind() == reflect.Slice {
|
|
fty = fty.Elem()
|
|
}
|
|
if fty.Kind() == reflect.Ptr {
|
|
fty = fty.Elem()
|
|
}
|
|
if fty.Kind() != reflect.Struct {
|
|
panic(fmt.Sprintf(
|
|
"hcl 'block' tag kind cannot be applied to %s field %s: struct required", field.Type.String(), field.Name,
|
|
))
|
|
}
|
|
ftags := getFieldTags(fty)
|
|
var labelNames []string
|
|
if len(ftags.Labels) > 0 {
|
|
labelNames = make([]string, len(ftags.Labels))
|
|
for i, l := range ftags.Labels {
|
|
labelNames[i] = l.Name
|
|
}
|
|
}
|
|
|
|
blockSchemas = append(blockSchemas, hcl.BlockHeaderSchema{
|
|
Type: n,
|
|
LabelNames: labelNames,
|
|
})
|
|
}
|
|
|
|
partial = tags.Remain != nil
|
|
schema = &hcl.BodySchema{
|
|
Attributes: attrSchemas,
|
|
Blocks: blockSchemas,
|
|
}
|
|
return schema, partial
|
|
}
|
|
|
|
type fieldTags struct {
|
|
Attributes map[string]int
|
|
Blocks map[string]int
|
|
Labels []labelField
|
|
Remain *int
|
|
Optional map[string]bool
|
|
}
|
|
|
|
type labelField struct {
|
|
FieldIndex int
|
|
Name string
|
|
}
|
|
|
|
func getFieldTags(ty reflect.Type) *fieldTags {
|
|
ret := &fieldTags{
|
|
Attributes: map[string]int{},
|
|
Blocks: map[string]int{},
|
|
Optional: map[string]bool{},
|
|
}
|
|
|
|
ct := ty.NumField()
|
|
for i := 0; i < ct; i++ {
|
|
field := ty.Field(i)
|
|
tag := field.Tag.Get("hcl")
|
|
if tag == "" {
|
|
continue
|
|
}
|
|
|
|
comma := strings.Index(tag, ",")
|
|
var name, kind string
|
|
if comma != -1 {
|
|
name = tag[:comma]
|
|
kind = tag[comma+1:]
|
|
} else {
|
|
name = tag
|
|
kind = "attr"
|
|
}
|
|
|
|
switch kind {
|
|
case "attr":
|
|
ret.Attributes[name] = i
|
|
case "block":
|
|
ret.Blocks[name] = i
|
|
case "label":
|
|
ret.Labels = append(ret.Labels, labelField{
|
|
FieldIndex: i,
|
|
Name: name,
|
|
})
|
|
case "remain":
|
|
if ret.Remain != nil {
|
|
panic("only one 'remain' tag is permitted")
|
|
}
|
|
idx := i // copy, because this loop will continue assigning to i
|
|
ret.Remain = &idx
|
|
case "optional":
|
|
ret.Attributes[name] = i
|
|
ret.Optional[name] = true
|
|
default:
|
|
panic(fmt.Sprintf("invalid hcl field tag kind %q on %s %q", kind, field.Type.String(), field.Name))
|
|
}
|
|
}
|
|
|
|
return ret
|
|
}
|