Update the style of the `consul_catalog_nodes` data source.

This commit is contained in:
Sean Chittenden 2017-02-13 12:09:29 -08:00
parent 1476445593
commit efb76b3374
No known key found for this signature in database
GPG Key ID: 4EBC9DC16C2E5E16
5 changed files with 1524 additions and 1386 deletions

File diff suppressed because it is too large Load Diff

View File

@ -16,11 +16,52 @@ func TestAccDataConsulAgentSelf_basic(t *testing.T) {
resource.TestStep{ resource.TestStep{
Config: testAccDataConsulAgentSelfConfig, Config: testAccDataConsulAgentSelfConfig,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckDataSourceValue("data.consul_agent_self.read", "bootstrap", "false"), testAccCheckDataSourceValue("data.consul_agent_self.read", "acl_datacenter", "<all>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "datacenter", "dc1"), testAccCheckDataSourceValue("data.consul_agent_self.read", "acl_default_policy", "<all>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "acl_disabled_ttl", "<all>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "acl_down_policy", "<all>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "acl_enforce_0_8_semantics", "<all>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "acl_ttl", "<all>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "advertise_addr", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "bind_addr", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "bootstrap_expect", "<all>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "bootstrap_mode", "false"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "client_addr", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "datacenter", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "dev_mode", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "domain", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "enable_anonymous_signature", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "enable_coordinates", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "enable_debug", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "enable_remote_exec", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "enable_syslog", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "enable_ui", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "enable_update_check", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "id", "<any>"), testAccCheckDataSourceValue("data.consul_agent_self.read", "id", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "leave_on_int", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "leave_on_term", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "log_level", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "name", "<any>"), testAccCheckDataSourceValue("data.consul_agent_self.read", "name", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "server", "true"), testAccCheckDataSourceValue("data.consul_agent_self.read", "pid_file", "<all>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "rejoin_after_leave", "<any>"),
// testAccCheckDataSourceValue("data.consul_agent_self.read", "retry_join", "<all>"),
// testAccCheckDataSourceValue("data.consul_agent_self.read", "retry_join_wan", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "retry_max_attempts", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "retry_max_attempts_wan", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "serf_lan_bind_addr", "<all>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "serf_wan_bind_addr", "<all>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "server_mode", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "server_name", "<all>"),
// testAccCheckDataSourceValue("data.consul_agent_self.read", "start_join", "<all>"),
// testAccCheckDataSourceValue("data.consul_agent_self.read", "start_join_wan", "<all>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "syslog_facility", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "telemetry.enable_hostname", "<all>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "tls_ca_file", "<all>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "tls_cert_file", "<all>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "tls_key_file", "<all>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "tls_verify_incoming", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "tls_verify_outgoing", "<any>"),
testAccCheckDataSourceValue("data.consul_agent_self.read", "tls_verify_server_hostname", "<any>"),
), ),
}, },
}, },
@ -33,14 +74,15 @@ func testAccCheckDataSourceValue(n, attr, val string) resource.TestCheckFunc {
if !ok { if !ok {
return fmt.Errorf("Resource not found") return fmt.Errorf("Resource not found")
} }
out, ok := rn.Primary.Attributes[attr] out, found := rn.Primary.Attributes[attr]
if !ok { switch {
case !found:
return fmt.Errorf("Attribute '%s' not found: %#v", attr, rn.Primary.Attributes) return fmt.Errorf("Attribute '%s' not found: %#v", attr, rn.Primary.Attributes)
} case val == "<all>":
if val != "<any>" && out != val { // Value found, don't care what the payload is (including the zero value)
case val != "<any>" && out != val:
return fmt.Errorf("Attribute '%s' value '%s' != '%s'", attr, out, val) return fmt.Errorf("Attribute '%s' value '%s' != '%s'", attr, out, val)
} case val == "<any>" && out == "":
if val == "<any>" && out == "" {
return fmt.Errorf("Attribute '%s' value '%s'", attr, out) return fmt.Errorf("Attribute '%s' value '%s'", attr, out)
} }
return nil return nil

View File

@ -2,7 +2,6 @@ package consul
import ( import (
"fmt" "fmt"
"time"
consulapi "github.com/hashicorp/consul/api" consulapi "github.com/hashicorp/consul/api"
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
@ -115,44 +114,9 @@ func dataSourceConsulCatalogNodesRead(d *schema.ResourceData, meta interface{})
client := meta.(*consulapi.Client) client := meta.(*consulapi.Client)
// Parse out data source filters to populate Consul's query options // Parse out data source filters to populate Consul's query options
queryOpts, err := getQueryOpts(d, client)
dc, err := getDC(d, client)
if err != nil { if err != nil {
return err return errwrap.Wrapf("unable to get query options for fetching catalog nodes: {{err}}", err)
}
queryOpts := &consulapi.QueryOptions{
Datacenter: dc,
}
if v, ok := d.GetOk(allowStale); ok {
queryOpts.AllowStale = v.(bool)
}
if v, ok := d.GetOk(requireConsistent); ok {
queryOpts.RequireConsistent = v.(bool)
}
if v, ok := d.GetOk(nodeMeta); ok {
m := v.(map[string]interface{})
nodeMetaMap := make(map[string]string, len(nodeMeta))
for s, t := range m {
nodeMetaMap[s] = t.(string)
}
queryOpts.NodeMeta = nodeMetaMap
}
if v, ok := d.GetOk(token); ok {
queryOpts.Token = v.(string)
}
if v, ok := d.GetOk(waitIndex); ok {
queryOpts.WaitIndex = uint64(v.(int))
}
if v, ok := d.GetOk(waitTime); ok {
d, _ := time.ParseDuration(v.(string))
queryOpts.WaitTime = d
} }
nodes, meta, err := client.Catalog().Nodes(queryOpts) nodes, meta, err := client.Catalog().Nodes(queryOpts)
@ -199,9 +163,9 @@ func dataSourceConsulCatalogNodesRead(d *schema.ResourceData, meta interface{})
} }
const idKeyFmt = "catalog-nodes-%s" const idKeyFmt = "catalog-nodes-%s"
d.SetId(fmt.Sprintf(idKeyFmt, dc)) d.SetId(fmt.Sprintf(idKeyFmt, queryOpts.Datacenter))
d.Set("datacenter", dc) d.Set("datacenter", queryOpts.Datacenter)
if err := d.Set(nodesAttr, l); err != nil { if err := d.Set(nodesAttr, l); err != nil {
return errwrap.Wrapf("Unable to store nodes: {{err}}", err) return errwrap.Wrapf("Unable to store nodes: {{err}}", err)
} }

View File

@ -1,536 +1,51 @@
package consul package consul
import ( import (
"bytes" "time"
"fmt"
"sort"
"strconv"
"github.com/hashicorp/errwrap" consulapi "github.com/hashicorp/consul/api"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
) )
// apiAttr is the type used for constants representing well known keys within func getQueryOpts(d *schema.ResourceData, client *consulapi.Client) (*consulapi.QueryOptions, error) {
// the API that are transmitted back to a resource over an API. dc, err := getDC(d, client)
type apiAttr string if err != nil {
return nil, err
}
// schemaAttr is the type used for constants representing well known keys queryOpts := &consulapi.QueryOptions{
// within the schema for a resource. Datacenter: dc,
type schemaAttr string }
// sourceFlags represent the ways in which an attribute can be written. Some if v, ok := d.GetOk(allowStale); ok {
// sources are mutually exclusive, yet other flag combinations are composable. queryOpts.AllowStale = v.(bool)
type sourceFlags int }
// typeKey is the lookup mechanism for the generated schema. if v, ok := d.GetOk(requireConsistent); ok {
type typeKey int queryOpts.RequireConsistent = v.(bool)
}
const ( if v, ok := d.GetOk(nodeMeta); ok {
// sourceUserRequired indicates the parameter must be provided by the user in m := v.(map[string]interface{})
// their configuration. nodeMetaMap := make(map[string]string, len(nodeMeta))
sourceUserRequired sourceFlags = 1 << iota for s, t := range m {
nodeMetaMap[s] = t.(string)
// 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
// sourceLocalFilter indicates the parameter is only used as input to the
// resource or data source and not to be entered into the state file.
sourceLocalFilter
)
const (
// modifyState is a mask that selects all attribute sources that can modify
// the state (i.e. everything but filters used in data sources).
modifyState = sourceUserRequired | sourceUserOptional | sourceAPIResult
// computedAttrMask is a mask that selects source*'s that are Computed in the
// schema.
computedAttrMask = sourceAPIResult
// optionalAttrMask is a mask that selects source*'s that are Optional in the
// schema.
optionalAttrMask = sourceAPIResult | sourceLocalFilter
// requiredAttrMask is a mask that selects source*'s that are Required in the
// schema.
requiredAttrMask = sourceUserRequired
)
type typeEntry struct {
APIName apiAttr
APIAliases []apiAttr
Source sourceFlags
Default interface{}
Description string
SchemaName schemaAttr
Type schema.ValueType
ValidateFuncs []interface{}
SetMembers map[typeKey]*typeEntry
ListSchema 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, interface{}) (interface{}, bool)
// APIToState takes the value from APITest and writes it to the attrWriter
APIToState func(*typeEntry, interface{}, attrWriter) error
// ConfigRead, if it returns true, returned a value that will be passed to its
// ConfigUse handler.
ConfigRead func(*typeEntry, attrReader) (interface{}, bool)
// ConfigUse takes the value returned from ConfigRead as the second argument
// and a 3rd optional opaque context argument.
ConfigUse func(e *typeEntry, v interface{}, target interface{}) error
}
type typeHandlers struct {
APITest func(*typeEntry, 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 interface{}) (interface{}, bool) {
m := self.(map[string]interface{})
v, found := m[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))
} }
queryOpts.NodeMeta = nodeMetaMap
} }
return false, false if v, ok := d.GetOk(token); ok {
} queryOpts.Token = v.(string)
}
func apiTestFloat64(e *typeEntry, self interface{}) (interface{}, bool) {
m := self.(map[string]interface{}) if v, ok := d.GetOk(waitIndex); ok {
queryOpts.WaitIndex = uint64(v.(int))
v, found := m[string(e.APIName)] }
if found {
if f, ok := v.(float64); ok { if v, ok := d.GetOk(waitTime); ok {
return f, true d, _ := time.ParseDuration(v.(string))
} else { queryOpts.WaitTime = d
panic(fmt.Sprintf("PROVIDER BUG: %q fails float64 type assertion", e.SchemaName)) }
}
} return queryOpts, nil
return 0.0, false
}
func apiTestID(e *typeEntry, self interface{}) (interface{}, bool) {
m := self.(map[string]interface{})
v, _ := apiTestString(e, m)
// Unconditionally return true so that the call to the APIToState handler can
// return an error.
return v, true
}
func apiTestList(e *typeEntry, self interface{}) (interface{}, bool) {
m := self.(map[string]interface{})
names := append([]apiAttr{e.APIName}, e.APIAliases...)
const defaultListLen = 8
l := make([]interface{}, 0, defaultListLen)
var foundName bool
for _, name := range names {
v, found := m[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, selfRaw interface{}) (interface{}, bool) {
self := selfRaw.(map[string]interface{})
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, selfRaw interface{}) (interface{}, bool) {
self := selfRaw.(map[string]interface{})
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, selfRaw interface{}) (interface{}, bool) {
self := selfRaw.(map[string]interface{})
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
}
func (e *typeEntry) MustLookupTypeHandler() *typeHandlers {
h := &typeHandlers{
APITest: e.APITest,
APIToState: e.APIToState,
}
defaultHandler := e.LookupDefaultTypeHandler()
if h.APITest == nil {
h.APITest = defaultHandler.APITest
if h.APITest == nil {
panic(fmt.Sprint("PROVIDER BUG: %v missing APITest method", e.SchemaName))
}
}
if h.APIToState == nil {
h.APIToState = defaultHandler.APIToState
if h.APIToState == nil {
panic(fmt.Sprint("PROVIDER BUG: %v missing APIToState method", e.SchemaName))
}
}
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 typeEntryListToSchema(e *typeEntry) map[string]*schema.Schema {
return map[string]*schema.Schema{
string(e.SchemaName): e.ToSchema(),
}
}
func typeEntryMapToResource(in map[typeKey]*typeEntry) *schema.Resource {
return &schema.Resource{
Schema: typeEntryMapToSchema(in),
}
}
func typeEntryMapToSchema(in map[typeKey]*typeEntry) map[string]*schema.Schema {
out := make(map[string]*schema.Schema, len(in))
for _, e := range in {
out[string(e.SchemaName)] = e.ToSchema()
}
return out
}
func (e *typeEntry) Validate() {
if e.Source&sourceAPIResult != 0 && e.Type == schema.TypeSet {
panic(fmt.Sprintf("PROVIDER BUG: %s can not be computed and of type Set", e.SchemaName))
}
if e.Source&sourceLocalFilter != 0 {
if e.ConfigRead == nil {
panic(fmt.Sprintf("PROVIDER BUG: %s can not be configured as a local filter and be missing a config read handler", e.SchemaName))
}
if e.ConfigUse == nil {
panic(fmt.Sprintf("PROVIDER BUG: %s can not be configured as a local filter and be missing a config use handler", 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))
}
}
func (e *typeEntry) ToSchema() *schema.Schema {
e.Validate()
attr := &schema.Schema{
Computed: e.Source&computedAttrMask != 0,
Default: e.Default,
Description: e.Description,
Optional: e.Source&optionalAttrMask != 0,
Required: e.Source&requiredAttrMask != 0,
Type: e.Type,
// ValidateFunc: e.MakeValidationFunc(),
}
// Fixup the type: use the real type vs a surrogate type
switch e.Type {
case schema.TypeList:
if e.ListSchema == nil {
attr.Elem = &schema.Schema{
Type: schema.TypeString,
}
} else {
attr.Elem = typeEntryMapToResource(e.ListSchema)
}
case schema.TypeSet:
attr.Elem = &schema.Resource{
Schema: typeEntryMapToSchema(e.SetMembers),
}
}
return attr
}
func mapStringToMapInterface(in map[string]string) map[string]interface{} {
out := make(map[string]interface{}, len(in))
for k, v := range in {
out[k] = v
}
return out
} }

View File

@ -3,6 +3,7 @@ package consul
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"strconv"
"time" "time"
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
@ -15,6 +16,9 @@ type validatorInputs []interface{}
// validateDurationMin is the minimum duration to accept as input // validateDurationMin is the minimum duration to accept as input
type validateDurationMin string type validateDurationMin string
// validateIntMax is the maximum integer value to accept as input
type validateIntMax int
// validateIntMin is the minimum integer value to accept as input // validateIntMin is the minimum integer value to accept as input
type validateIntMin int type validateIntMin int
@ -35,6 +39,8 @@ func makeValidationFunc(name string, validators []interface{}) func(v interface{
switch u := v.(type) { switch u := v.(type) {
case validateDurationMin: case validateDurationMin:
fns = append(fns, validateDurationMinFactory(name, string(u))) fns = append(fns, validateDurationMinFactory(name, string(u)))
case validateIntMax:
fns = append(fns, validateIntMaxFactory(name, int(u)))
case validateIntMin: case validateIntMin:
fns = append(fns, validateIntMinFactory(name, int(u))) fns = append(fns, validateIntMinFactory(name, int(u)))
case validateRegexp: case validateRegexp:
@ -77,10 +83,50 @@ func validateDurationMinFactory(name, minDuration string) func(v interface{}, ke
} }
} }
func validateIntMaxFactory(name string, max int) func(v interface{}, key string) (warnings []string, errors []error) {
return func(v interface{}, key string) (warnings []string, errors []error) {
switch u := v.(type) {
case string:
i, err := strconv.ParseInt(u, 10, 64)
if err != nil {
errors = append(errors, errwrap.Wrapf(fmt.Sprintf("unable to convert %q to an integer: {{err}}", u), err))
break
}
if i > int64(max) {
errors = append(errors, fmt.Errorf("Invalid %s specified: %d more than the required maximum %d", name, v.(int), max))
}
case int:
if u > max {
errors = append(errors, fmt.Errorf("Invalid %s specified: %d more than the required maximum %d", name, v.(int), max))
}
default:
errors = append(errors, fmt.Errorf("Unsupported type in int max validation: %T", v))
}
return warnings, errors
}
}
func validateIntMinFactory(name string, min int) func(v interface{}, key string) (warnings []string, errors []error) { func validateIntMinFactory(name string, min int) func(v interface{}, key string) (warnings []string, errors []error) {
return func(v interface{}, key string) (warnings []string, errors []error) { return func(v interface{}, key string) (warnings []string, errors []error) {
if v.(int) < min { switch u := v.(type) {
errors = append(errors, fmt.Errorf("Invalid %s specified: %d less than the required minimum %d", name, v.(int), min)) case string:
i, err := strconv.ParseInt(u, 10, 64)
if err != nil {
errors = append(errors, errwrap.Wrapf(fmt.Sprintf("unable to convert %q to an integer: {{err}}", u), err))
break
}
if i < int64(min) {
errors = append(errors, fmt.Errorf("Invalid %s specified: %d less than the required minimum %d", name, v.(int), min))
}
case int:
if u < min {
errors = append(errors, fmt.Errorf("Invalid %s specified: %d less than the required minimum %d", name, v.(int), min))
}
default:
errors = append(errors, fmt.Errorf("Unsupported type in int min validation: %T", v))
} }
return warnings, errors return warnings, errors