terraform/builtin/providers/consul/utils.go

498 lines
14 KiB
Go

package consul
import (
"bytes"
"fmt"
"regexp"
"sort"
"strconv"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema"
)
// _APIAttr is the type used for constants representing well known keys within
// the API that are transmitted back to a resource over an API.
type _APIAttr string
// _SchemaAttr is the type used for constants representing well known keys
// within the schema for a resource.
type _SchemaAttr string
// _SourceFlags represent the ways in which an attribute can be written. Some
// sources are mutually exclusive, yet other flag combinations are composable.
type _SourceFlags int
// _TypeKey is the lookup mechanism for the generated schema.
type _TypeKey int
// An array of inputs used as typed arguments and converted from their type into
// function objects that are dynamically constructed and executed.
type _ValidatorInputs []interface{}
// _ValidateRegexp is a regexp pattern to use to validate schema input.
type _ValidateRegexp string
const (
// _SourceUserRequired indicates the parameter must be provided by the user in
// their configuration.
_SourceUserRequired _SourceFlags = 1 << iota
// _SourceUserOptional indicates the parameter may optionally be specified by
// the user in their configuration.
_SourceUserOptional
// _SourceAPIResult indicates the parameter may only be set by the return of
// an API call.
_SourceAPIResult
)
type _TypeEntry struct {
APIName _APIAttr
APIAliases []_APIAttr
Source _SourceFlags
Description string
SchemaName _SchemaAttr
Type schema.ValueType
ValidateFuncs []interface{}
SetMembers map[_TypeKey]*_TypeEntry
// APITest, if returns true, will call APIToState. The if the value was
// found, the second return parameter will include the value that should be
// set in the state store.
APITest func(*_TypeEntry, map[string]interface{}) (interface{}, bool)
// APIToState takes the value from APITest and writes it to the _AttrWriter
APIToState func(*_TypeEntry, interface{}, _AttrWriter) error
}
type _TypeHandlers struct {
APITest func(*_TypeEntry, map[string]interface{}) (interface{}, bool)
APIToState func(*_TypeEntry, interface{}, _AttrWriter) error
}
var _TypeHandlerLookupMap = map[schema.ValueType]*_TypeHandlers{
schema.TypeBool: &_TypeHandlers{
APITest: _APITestBool,
APIToState: _APIToStateBool,
},
schema.TypeFloat: &_TypeHandlers{
APITest: _APITestFloat64,
APIToState: _APIToStateFloat64,
},
schema.TypeList: &_TypeHandlers{
APITest: _APITestList,
APIToState: _APIToStateList,
},
schema.TypeMap: &_TypeHandlers{
APITest: _APITestMap,
APIToState: _APIToStateMap,
},
schema.TypeSet: &_TypeHandlers{
APITest: _APITestSet,
APIToState: _APIToStateSet,
},
schema.TypeString: &_TypeHandlers{
APITest: _APITestString,
APIToState: _APIToStateString,
},
}
func _APITestBool(e *_TypeEntry, self map[string]interface{}) (interface{}, bool) {
v, found := self[string(e.APIName)]
if found {
if b, ok := v.(bool); ok {
return b, true
} else {
panic(fmt.Sprintf("PROVIDER BUG: %q fails bool type assertion", e.SchemaName))
}
}
return false, false
}
func _APITestFloat64(e *_TypeEntry, self map[string]interface{}) (interface{}, bool) {
v, found := self[string(e.APIName)]
if found {
if f, ok := v.(float64); ok {
return f, true
} else {
panic(fmt.Sprintf("PROVIDER BUG: %q fails float64 type assertion", e.SchemaName))
}
}
return 0.0, false
}
func _APITestID(e *_TypeEntry, self map[string]interface{}) (interface{}, bool) {
v, _ := _APITestString(e, self)
// Unconditionally return true so that the call to the APIToState handler can
// return an error.
return v, true
}
func _APITestList(e *_TypeEntry, self map[string]interface{}) (interface{}, bool) {
names := append([]_APIAttr{e.APIName}, e.APIAliases...)
const defaultListLen = 8
l := make([]interface{}, 0, defaultListLen)
var foundName bool
for _, name := range names {
v, found := self[string(name)]
if found {
foundName = true
// TODO(sean@): should make a list writer that normalizes v.(type) to a
// string. For now we only accept strings and lists.
switch u := v.(type) {
case []interface{}:
l = append(l, u...)
case string:
l = append(l, u)
default:
panic(fmt.Sprintf("PROVIDER BUG: %q fails list type assertion", e.SchemaName))
}
}
}
if foundName {
return l, true
}
return []interface{}{}, false
}
func _APITestMap(e *_TypeEntry, self map[string]interface{}) (interface{}, bool) {
v, found := self[string(e.APIName)]
if found {
if m, ok := v.(map[string]interface{}); ok {
return m, true
} else {
panic(fmt.Sprintf("PROVIDER BUG: %q fails map type assertion", e.SchemaName))
}
}
return "", false
}
func _APITestSet(e *_TypeEntry, self map[string]interface{}) (interface{}, bool) {
v, found := self[string(e.APIName)]
if found {
if m, ok := v.(map[string]interface{}); ok {
return m, true
} else {
panic(fmt.Sprintf("PROVIDER BUG: %q fails map type assertion", e.SchemaName))
}
}
return "", false
}
func _APITestString(e *_TypeEntry, self map[string]interface{}) (interface{}, bool) {
v, found := self[string(e.APIName)]
if found {
if s, ok := v.(string); ok {
return s, true
} else {
panic(fmt.Sprintf("PROVIDER BUG: %q fails string type assertion", e.SchemaName))
}
}
return "", false
}
func _APIToStateBool(e *_TypeEntry, v interface{}, w _AttrWriter) error {
return w.SetBool(e.SchemaName, v.(bool))
}
func _APIToStateID(e *_TypeEntry, v interface{}, w _AttrWriter) error {
s, ok := v.(string)
if !ok || len(s) == 0 {
return fmt.Errorf("Unable to set %q's ID to an empty or non-string value: %#v", e.SchemaName, v)
}
stateWriter, ok := w.(*_AttrWriterState)
if !ok {
return fmt.Errorf("PROVIDER BUG: unable to SetID with a non-_AttrWriterState")
}
stateWriter.SetID(s)
return nil
}
func _APIToStateFloat64(e *_TypeEntry, v interface{}, w _AttrWriter) error {
f, ok := v.(float64)
if !ok {
return fmt.Errorf("PROVIDER BUG: unable to cast %s to a float64", e.SchemaName)
}
return w.SetFloat64(e.SchemaName, f)
}
func _APIToStateList(e *_TypeEntry, v interface{}, w _AttrWriter) error {
l, ok := v.([]interface{})
if !ok {
return fmt.Errorf("PROVIDER BUG: unable to cast %s to a list", e.SchemaName)
}
return w.SetList(e.SchemaName, l)
}
func _APIToStateMap(e *_TypeEntry, v interface{}, w _AttrWriter) error {
rawMap, ok := v.(map[string]interface{})
if !ok {
return fmt.Errorf("PROVIDER BUG: unable to cast %s to a map", e.SchemaName)
}
mWriter := _NewMapWriter(make(map[string]interface{}, len(rawMap)))
// Make a lookup map by API Schema Name
var setMembersLen int
if e.SetMembers != nil {
setMembersLen = len(e.SetMembers)
}
apiLookup := make(map[string]*_TypeEntry, setMembersLen)
for _, typeEntry := range e.SetMembers {
apiLookup[string(e.SchemaName)] = typeEntry
}
for k, v := range rawMap {
var usedSchemaHandler bool
if attrEntry, found := apiLookup[k]; found {
usedSchemaHandler = true
if err := attrEntry.APIToState(e, v, mWriter); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Error calling API to state handler on %s: {{err}}", k), err)
}
}
if !usedSchemaHandler {
if err := mWriter.Set(_SchemaAttr(k), v); err != nil {
return errwrap.Wrapf("Unable to store map in state: {{err}}", err)
}
}
}
return w.SetMap(e.SchemaName, mWriter.ToMap())
}
func _APIToStateSet(e *_TypeEntry, v interface{}, w _AttrWriter) error {
s, ok := v.([]map[string]interface{})
if !ok {
return fmt.Errorf("PROVIDER BUG: unable to cast %s to a set", e.SchemaName)
}
set := schema.NewSet(schema.HashResource(nil), nil)
set.Add(s)
return w.SetSet(e.SchemaName, set)
}
func _APIToStateString(e *_TypeEntry, v interface{}, w _AttrWriter) error {
s, ok := v.(string)
if !ok {
return fmt.Errorf("PROVIDER BUG: unable to cast %s to a float64", e.SchemaName)
}
return w.SetString(e.SchemaName, s)
}
func _HashMap(in interface{}) int {
return 0
m, ok := in.(map[string]interface{})
if !ok {
panic(fmt.Sprintf("PROVIDER BUG: Unable to cast %#v to a map", in))
}
keys := make([]string, 0, len(m))
for k, _ := range m {
keys = append(keys, k)
}
sort.Strings(keys)
b := &bytes.Buffer{}
const _DefaultHashBufSize = 4096
b.Grow(_DefaultHashBufSize)
for _, k := range keys {
v, found := m[k]
if !found {
panic("PROVIDER BUG: race condition: key should not be missing")
}
fmt.Fprintf(b, k)
switch u := v.(type) {
case string:
fmt.Fprint(b, u)
case bool:
fmt.Fprintf(b, "%t", u)
case float64:
fmt.Fprint(b, strconv.FormatFloat(u, 'g', -1, 64))
case int, uint:
fmt.Fprintf(b, "%d", u)
case nil:
default:
panic(fmt.Sprintf("Unsupported type %T in map hasher", v))
}
}
return hashcode.String(b.String())
}
func _Indirect(v interface{}) interface{} {
switch v.(type) {
case string:
return v
case *string:
p := v.(*string)
if p == nil {
return nil
}
return *p
default:
return v
}
}
func (e *_TypeEntry) LookupDefaultTypeHandler() *_TypeHandlers {
h, found := _TypeHandlerLookupMap[e.Type]
if !found {
panic(fmt.Sprintf("PROVIDER BUG: unable to lookup %q's type (%#v)", e.SchemaName, e.Type))
}
return h
}
// _NegateBoolToState is a factory function that creates a new function that
// negates whatever the bool is that's passed in as an argument.
func _NegateBoolToState(fn func(*_TypeEntry, interface{}, _AttrWriter) error) func(*_TypeEntry, interface{}, _AttrWriter) error {
return func(e *_TypeEntry, v interface{}, w _AttrWriter) error {
b, ok := v.(bool)
if !ok {
return fmt.Errorf("Unable to type assert non-bool value: %#v", v)
}
return fn(e, !b, w)
}
}
// _StateSet sets an attribute based on an attrName. Return an error if the
// Set() to schema.ResourceData fails.
func _StateSet(d *schema.ResourceData, attrName _SchemaAttr, v interface{}) error {
if err := d.Set(string(attrName), _Indirect(v)); err != nil {
return fmt.Errorf("PROVIDER BUG: failed set schema attribute %s to value %#v: %v", attrName, v, err)
}
return nil
}
func _TypeEntryMapToSchema(in map[_TypeKey]*_TypeEntry) map[string]*schema.Schema {
out := make(map[string]*schema.Schema, len(in))
for _, e := range in {
e.Validate()
attr := &schema.Schema{
Type: e.Type,
Description: e.Description,
Optional: e.Source&_SourceAPIResult == _SourceAPIResult,
Required: e.Source&_SourceUserRequired == _SourceUserRequired,
Computed: e.Source&_SourceAPIResult == _SourceAPIResult,
ValidateFunc: e.MakeValidationFunc(),
}
// Fixup the type: use the real type vs a surrogate type
switch e.Type {
case schema.TypeList:
attr.Elem = &schema.Schema{
Type: schema.TypeString,
}
case schema.TypeSet:
attr.Elem = &schema.Resource{
Schema: _TypeEntryMapToSchema(e.SetMembers),
}
}
out[string(e.SchemaName)] = attr
}
return out
}
func (e *_TypeEntry) Validate() {
if e.Source&_SourceAPIResult == _SourceAPIResult && e.Type == schema.TypeSet {
panic(fmt.Sprintf("PROVIDER BUG: %s can not be computed and of type Set", e.SchemaName))
}
if len(e.SetMembers) != 0 && !(e.Type == schema.TypeSet || e.Type == schema.TypeMap) {
panic(fmt.Sprintf("PROVIDER BUG: %s is not of type Set but has SetMembers set", e.SchemaName))
}
if e.Source&(_SourceUserRequired|_SourceAPIResult) == (_SourceUserRequired | _SourceAPIResult) {
panic(fmt.Sprintf("PROVIDER BUG: %#v and %#v are mutually exclusive Source flags", _SourceUserRequired, _SourceAPIResult))
}
}
// MakeValidateionFunc takes a list of typed validator inputs from the receiver
// and creates a validation closure that calls each validator in serial until
// either a warning or error is returned from the first validation function.
func (e *_TypeEntry) MakeValidationFunc() func(v interface{}, key string) (warnings []string, errors []error) {
if len(e.ValidateFuncs) == 0 {
return nil
}
fns := make([]func(v interface{}, key string) (warnings []string, errors []error), len(e.ValidateFuncs))
for _, v := range e.ValidateFuncs {
switch u := v.(type) {
case _ValidateRegexp:
fns = append(fns, _ValidateRegexpFactory(e, string(u)))
}
}
return func(v interface{}, key string) (warnings []string, errors []error) {
for _, fn := range fns {
warnings, errors = fn(v, key)
if len(warnings) > 0 || len(errors) > 0 {
break
}
}
return warnings, errors
}
}
// _ValidateFuncs takes a list of typed validator inputs, creates validation
// functions for each and then runs them in serial until either a warning or
// error is returned from the first validation function.
func _ValidateFuncs(e *_TypeEntry, in ...interface{}) func(v interface{}, key string) (warnings []string, errors []error) {
if len(in) == 0 {
return nil
}
fns := make([]func(v interface{}, key string) (warnings []string, errors []error), len(in))
for _, v := range in {
switch v.(type) {
case _ValidateRegexp:
fns = append(fns, _ValidateRegexpFactory(e, v.(string)))
}
}
return func(v interface{}, key string) (warnings []string, errors []error) {
for _, fn := range fns {
warnings, errors = fn(v, key)
if len(warnings) > 0 || len(errors) > 0 {
break
}
}
return warnings, errors
}
}
func _ValidateRegexpFactory(e *_TypeEntry, reString string) func(v interface{}, key string) (warnings []string, errors []error) {
re := regexp.MustCompile(reString)
return func(v interface{}, key string) (warnings []string, errors []error) {
if !re.MatchString(v.(string)) {
errors = append(errors, fmt.Errorf("Invalid %s specified (%q): regexp failed to match string", e.SchemaName, v.(string)))
}
return warnings, errors
}
}