diff --git a/builtin/providers/consul/data_source_consul_catalog_nodes.go b/builtin/providers/consul/data_source_consul_catalog_nodes.go new file mode 100644 index 000000000..8bb707c2d --- /dev/null +++ b/builtin/providers/consul/data_source_consul_catalog_nodes.go @@ -0,0 +1,388 @@ +package consul + +import ( + "fmt" + "time" + + consulapi "github.com/hashicorp/consul/api" + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform/helper/schema" +) + +// Top-level consul_catalog_nodes attributes +const ( + _CatalogNodes _TypeKey = iota + _CatalogNodesAllowStale + _CatalogNodesDatacenter + _CatalogNodesNear + _CatalogNodesRequireConsistent + _CatalogNodesToken + _CatalogNodesWaitIndex + _CatalogNodesWaitTime +) + +// node.* attributes +const ( + _CatalogNodeID _TypeKey = iota + _CatalogNodeName + _CatalogNodeAddress + _CatalogNodeTaggedAddresses + _CatalogNodeMeta +) + +// node.tagged_addresses.* attributes +const ( + _CatalogNodeTaggedAddressesLAN _TypeKey = iota + _CatalogNodeTaggedAddressesWAN +) + +var _CatalogNodeAttrs = map[_TypeKey]*_TypeEntry{ + _CatalogNodeID: { + APIName: "ID", + SchemaName: "id", + Source: _SourceAPIResult, + Type: schema.TypeString, + ValidateFuncs: []interface{}{ + _ValidateRegexp(`^[\S]+$`), + }, + APITest: func(e *_TypeEntry, v interface{}) (interface{}, bool) { + node := v.(*consulapi.Node) + + if id := node.ID; id != "" { + return id, true + } + + // Use the node name - confusingly stored in the Node attribute - if no ID + // is available. + if name := node.Node; name != "" { + return name, true + } + + return "", false + }, + }, + _CatalogNodeName: { + APIName: "Name", + SchemaName: "name", + Source: _SourceAPIResult, + Type: schema.TypeString, + ValidateFuncs: []interface{}{ + _ValidateRegexp(`^[\S]+$`), + }, + APITest: func(e *_TypeEntry, v interface{}) (interface{}, bool) { + node := v.(*consulapi.Node) + + if name := node.Node; name != "" { + return name, true + } + + return "", false + }, + }, + _CatalogNodeAddress: { + APIName: "Address", + SchemaName: "address", + Source: _SourceAPIResult, + Type: schema.TypeString, + APITest: func(e *_TypeEntry, v interface{}) (interface{}, bool) { + node := v.(*consulapi.Node) + + if addr := node.Address; addr != "" { + return addr, true + } + + return "", false + }, + }, + _CatalogNodeTaggedAddresses: { + APIName: "TaggedAddresses", + SchemaName: "tagged_addresses", + Source: _SourceAPIResult, + Type: schema.TypeMap, + SetMembers: map[_TypeKey]*_TypeEntry{ + _CatalogNodeTaggedAddressesLAN: { + APIName: "LAN", + SchemaName: "lan", + Source: _SourceAPIResult, + Type: schema.TypeString, + APITest: func(e *_TypeEntry, v interface{}) (interface{}, bool) { + m := v.(map[string]string) + + if addr, found := m[string(e.SchemaName)]; found { + return addr, true + } + + return nil, false + }, + }, + _CatalogNodeTaggedAddressesWAN: { + APIName: "WAN", + SchemaName: "wan", + Source: _SourceAPIResult, + Type: schema.TypeString, + APITest: func(e *_TypeEntry, v interface{}) (interface{}, bool) { + m := v.(map[string]string) + + if addr, found := m[string(e.SchemaName)]; found { + return addr, true + } + + return nil, false + }, + }, + }, + APITest: func(e *_TypeEntry, v interface{}) (interface{}, bool) { + node := v.(*consulapi.Node) + + if addrs := node.TaggedAddresses; len(addrs) > 0 { + return _MapStringToMapInterface(addrs), true + } + + return nil, false + }, + }, + _CatalogNodeMeta: { + APIName: "Meta", + SchemaName: "meta", + Source: _SourceAPIResult, + Type: schema.TypeMap, + APITest: func(e *_TypeEntry, v interface{}) (interface{}, bool) { + node := v.(*consulapi.Node) + + if meta := node.Meta; len(meta) > 0 { + return _MapStringToMapInterface(meta), true + } + + return nil, false + }, + }, +} + +var _CatalogNodesAttrs = map[_TypeKey]*_TypeEntry{ + _CatalogNodesAllowStale: { + SchemaName: "allow_stale", + Source: _SourceLocalFilter, + Type: schema.TypeBool, + Default: true, + ConfigRead: func(e *_TypeEntry, r _AttrReader) (interface{}, bool) { + b, ok := r.GetBoolOK(e.SchemaName) + if !ok { + return nil, false + } + + return b, true + }, + ConfigUse: func(e *_TypeEntry, v interface{}, target interface{}) error { + b := v.(bool) + queryOpts := target.(*consulapi.QueryOptions) + queryOpts.AllowStale = b + return nil + }, + }, + _CatalogNodesDatacenter: { + SchemaName: "datacenter", + Source: _SourceLocalFilter, + Type: schema.TypeString, + ConfigRead: func(e *_TypeEntry, r _AttrReader) (interface{}, bool) { + s, ok := r.GetStringOK(e.SchemaName) + if !ok { + return nil, false + } + + return s, true + }, + ConfigUse: func(e *_TypeEntry, v interface{}, target interface{}) error { + s := v.(string) + queryOpts := target.(*consulapi.QueryOptions) + queryOpts.Datacenter = s + return nil + }, + }, + _CatalogNodesNear: { + SchemaName: "near", + Source: _SourceLocalFilter, + Type: schema.TypeString, + ConfigRead: func(e *_TypeEntry, r _AttrReader) (interface{}, bool) { + s, ok := r.GetStringOK(e.SchemaName) + if !ok { + return nil, false + } + + return s, true + }, + ConfigUse: func(e *_TypeEntry, v interface{}, target interface{}) error { + s := v.(string) + queryOpts := target.(*consulapi.QueryOptions) + queryOpts.Near = s + return nil + }, + }, + _CatalogNodes: { + SchemaName: "nodes", + Source: _SourceAPIResult, + Type: schema.TypeList, + ListSchema: _CatalogNodeAttrs, + }, + _CatalogNodesRequireConsistent: { + SchemaName: "require_consistent", + Source: _SourceLocalFilter, + Type: schema.TypeBool, + Default: false, + ConfigRead: func(e *_TypeEntry, r _AttrReader) (interface{}, bool) { + b, ok := r.GetBoolOK(e.SchemaName) + if !ok { + return nil, false + } + + return b, true + }, + ConfigUse: func(e *_TypeEntry, v interface{}, target interface{}) error { + b := v.(bool) + queryOpts := target.(*consulapi.QueryOptions) + queryOpts.RequireConsistent = b + return nil + }, + }, + _CatalogNodesToken: { + SchemaName: "token", + Source: _SourceLocalFilter, + Type: schema.TypeString, + ConfigRead: func(e *_TypeEntry, r _AttrReader) (interface{}, bool) { + s, ok := r.GetStringOK(e.SchemaName) + if !ok { + return nil, false + } + + return s, true + }, + ConfigUse: func(e *_TypeEntry, v interface{}, target interface{}) error { + s := v.(string) + queryOpts := target.(*consulapi.QueryOptions) + queryOpts.Token = s + return nil + }, + }, + _CatalogNodesWaitIndex: { + SchemaName: "wait_index", + Source: _SourceLocalFilter, + Type: schema.TypeInt, + ValidateFuncs: []interface{}{ + _ValidateIntMin(0), + }, + ConfigRead: func(e *_TypeEntry, r _AttrReader) (interface{}, bool) { + i, ok := r.GetIntOK(e.SchemaName) + if !ok { + return nil, false + } + + return uint64(i), true + }, + ConfigUse: func(e *_TypeEntry, v interface{}, target interface{}) error { + i := v.(uint64) + queryOpts := target.(*consulapi.QueryOptions) + queryOpts.WaitIndex = i + return nil + }, + }, + _CatalogNodesWaitTime: { + SchemaName: "wait_time", + Source: _SourceLocalFilter, + Type: schema.TypeString, + ValidateFuncs: []interface{}{ + _ValidateDurationMin("0ns"), + }, + ConfigRead: func(e *_TypeEntry, r _AttrReader) (interface{}, bool) { + d, ok := r.GetDurationOK(e.SchemaName) + if !ok { + return nil, false + } + + return d, true + }, + ConfigUse: func(e *_TypeEntry, v interface{}, target interface{}) error { + d := v.(time.Duration) + queryOpts := target.(*consulapi.QueryOptions) + queryOpts.WaitTime = d + return nil + }, + }, +} + +func dataSourceConsulCatalogNodes() *schema.Resource { + return &schema.Resource{ + Read: dataSourceConsulCatalogNodesRead, + Schema: _TypeEntryMapToSchema(_CatalogNodesAttrs), + } +} + +func dataSourceConsulCatalogNodesRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*consulapi.Client) + + dc, err := getDC(d, client) + if err != nil { + return err + } + + queryOpts := &consulapi.QueryOptions{ + Datacenter: dc, + } + + cfgReader := _NewConfigReader(d) + + // Construct the query options + for _, e := range _CatalogNodesAttrs[_CatalogNodes].ListSchema { + // Only evaluate attributes that impact the state + if e.Source&_SourceLocalFilter == 0 { + continue + } + + if v, ok := e.ConfigRead(e, cfgReader); ok { + if err := e.ConfigUse(e, v, queryOpts); err != nil { + return errwrap.Wrapf(fmt.Sprintf("error writing %q's query option: {{err}}", e.SchemaName), err) + } + } + } + + nodes, meta, err := client.Catalog().Nodes(queryOpts) + if err != nil { + return err + } + + // TODO(sean@): It'd be nice if this data source had a way of filtering out + // irrelevant data so only the important bits are persisted in the state file. + // Something like an attribute mask or even a regexp of matching schema + // attributesknames would be sufficient in the most basic case. Food for + // thought. + + l := make([]interface{}, 0, len(nodes)) + + for _, node := range nodes { + mWriter := _NewMapWriter(make(map[string]interface{}, len(_CatalogNodeAttrs))) + + // /v1/catalog/nodes returns a list of node objects + for _, e := range _CatalogNodesAttrs[_CatalogNodes].ListSchema { + // Only evaluate attributes that impact the state + if e.Source&_ModifyState == 0 { + continue + } + + h := e.MustLookupTypeHandler() + + if v, ok := h.APITest(e, node); ok { + if err := h.APIToState(e, v, mWriter); err != nil { + return errwrap.Wrapf(fmt.Sprintf("error writing %q's data to state: {{err}}", e.SchemaName), err) + } + } + } + + l = append(l, mWriter.ToMap()) + } + + dataSourceWriter := _NewStateWriter(d) + dataSourceWriter.SetList(_CatalogNodesAttrs[_CatalogNodes].SchemaName, l) + dataSourceWriter.SetString(_CatalogNodesAttrs[_CatalogNodesDatacenter].SchemaName, dc) + const idKeyFmt = "catalog-nodes-%s" + dataSourceWriter.SetID(fmt.Sprintf(idKeyFmt, dc)) + + return nil +} diff --git a/builtin/providers/consul/data_source_consul_catalog_nodes_test.go b/builtin/providers/consul/data_source_consul_catalog_nodes_test.go new file mode 100644 index 000000000..ede6f4342 --- /dev/null +++ b/builtin/providers/consul/data_source_consul_catalog_nodes_test.go @@ -0,0 +1,36 @@ +package consul + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccDataConsulCatalogNodes_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataConsulCatalogNodesConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckDataSourceValue("data.consul_catalog_nodes.read", "nodes.#", "1"), + testAccCheckDataSourceValue("data.consul_catalog_nodes.read", "nodes.0.id", ""), + testAccCheckDataSourceValue("data.consul_catalog_nodes.read", "nodes.0.name", ""), + testAccCheckDataSourceValue("data.consul_catalog_nodes.read", "nodes.0.address", ""), + ), + }, + }, + }) +} + +const testAccDataConsulCatalogNodesConfig = ` +data "consul_catalog_nodes" "read" { + allow_stale = true + require_consistent = false + near = "_agent" + token = "" + wait_index = 0 + wait_time = "1m" +} +` diff --git a/builtin/providers/consul/resource_provider.go b/builtin/providers/consul/resource_provider.go index 4688f543c..749223c5d 100644 --- a/builtin/providers/consul/resource_provider.go +++ b/builtin/providers/consul/resource_provider.go @@ -64,8 +64,9 @@ func Provider() terraform.ResourceProvider { }, DataSourcesMap: map[string]*schema.Resource{ - "consul_agent_self": dataSourceConsulAgentSelf(), - "consul_keys": dataSourceConsulKeys(), + "consul_agent_self": dataSourceConsulAgentSelf(), + "consul_catalog_nodes": dataSourceConsulCatalogNodes(), + "consul_keys": dataSourceConsulKeys(), }, ResourcesMap: map[string]*schema.Resource{ diff --git a/builtin/providers/consul/utils.go b/builtin/providers/consul/utils.go index 4cb9533df..a23429b2b 100644 --- a/builtin/providers/consul/utils.go +++ b/builtin/providers/consul/utils.go @@ -6,6 +6,7 @@ import ( "regexp" "sort" "strconv" + "time" "github.com/hashicorp/errwrap" "github.com/hashicorp/terraform/helper/hashcode" @@ -31,6 +32,12 @@ type _TypeKey int // function objects that are dynamically constructed and executed. type _ValidatorInputs []interface{} +// _ValidateDurationMin is the minimum duration to accept as input +type _ValidateDurationMin string + +// _ValidateIntMin is the minimum integer value to accept as input +type _ValidateIntMin int + // _ValidateRegexp is a regexp pattern to use to validate schema input. type _ValidateRegexp string @@ -46,29 +53,61 @@ const ( // _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, map[string]interface{}) (interface{}, bool) + 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, map[string]interface{}) (interface{}, bool) + APITest func(*_TypeEntry, interface{}) (interface{}, bool) APIToState func(*_TypeEntry, interface{}, _AttrWriter) error } @@ -99,8 +138,10 @@ var _TypeHandlerLookupMap = map[schema.ValueType]*_TypeHandlers{ }, } -func _APITestBool(e *_TypeEntry, self map[string]interface{}) (interface{}, bool) { - v, found := self[string(e.APIName)] +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 @@ -112,8 +153,10 @@ func _APITestBool(e *_TypeEntry, self map[string]interface{}) (interface{}, bool return false, false } -func _APITestFloat64(e *_TypeEntry, self map[string]interface{}) (interface{}, bool) { - v, found := self[string(e.APIName)] +func _APITestFloat64(e *_TypeEntry, self interface{}) (interface{}, bool) { + m := self.(map[string]interface{}) + + v, found := m[string(e.APIName)] if found { if f, ok := v.(float64); ok { return f, true @@ -124,22 +167,26 @@ func _APITestFloat64(e *_TypeEntry, self map[string]interface{}) (interface{}, b return 0.0, false } -func _APITestID(e *_TypeEntry, self map[string]interface{}) (interface{}, bool) { - v, _ := _APITestString(e, self) +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 map[string]interface{}) (interface{}, bool) { +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 := self[string(name)] + v, found := m[string(name)] if found { foundName = true // TODO(sean@): should make a list writer that normalizes v.(type) to a @@ -162,7 +209,9 @@ func _APITestList(e *_TypeEntry, self map[string]interface{}) (interface{}, bool return []interface{}{}, false } -func _APITestMap(e *_TypeEntry, self map[string]interface{}) (interface{}, bool) { +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 { @@ -174,7 +223,9 @@ func _APITestMap(e *_TypeEntry, self map[string]interface{}) (interface{}, bool) return "", false } -func _APITestSet(e *_TypeEntry, self map[string]interface{}) (interface{}, bool) { +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 { @@ -186,7 +237,9 @@ func _APITestSet(e *_TypeEntry, self map[string]interface{}) (interface{}, bool) return "", false } -func _APITestString(e *_TypeEntry, self map[string]interface{}) (interface{}, bool) { +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 { @@ -361,6 +414,33 @@ func (e *_TypeEntry) LookupDefaultTypeHandler() *_TypeHandlers { 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 { @@ -384,43 +464,42 @@ func _StateSet(d *schema.ResourceData, attrName _SchemaAttr, v interface{}) erro 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 { - 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 + out[string(e.SchemaName)] = e.ToSchema() } return out } func (e *_TypeEntry) Validate() { - if e.Source&_SourceAPIResult == _SourceAPIResult && e.Type == schema.TypeSet { + 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)) } @@ -438,9 +517,13 @@ func (e *_TypeEntry) MakeValidationFunc() func(v interface{}, key string) (warni return nil } - fns := make([]func(v interface{}, key string) (warnings []string, errors []error), len(e.ValidateFuncs)) + fns := make([]func(v interface{}, key string) (warnings []string, errors []error), 0, len(e.ValidateFuncs)) for _, v := range e.ValidateFuncs { switch u := v.(type) { + case _ValidateDurationMin: + fns = append(fns, _ValidateDurationMinFactory(e, string(u))) + case _ValidateIntMin: + fns = append(fns, _ValidateIntMinFactory(e, int(u))) case _ValidateRegexp: fns = append(fns, _ValidateRegexpFactory(e, string(u))) } @@ -457,29 +540,73 @@ func (e *_TypeEntry) MakeValidationFunc() func(v interface{}, key string) (warni } } -// _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 +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(), } - 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))) + // 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 +} + +func _ValidateDurationMinFactory(e *_TypeEntry, minDuration string) func(v interface{}, key string) (warnings []string, errors []error) { + dMin, err := time.ParseDuration(minDuration) + if err != nil { + panic(fmt.Sprintf("PROVIDER BUG: duration %q not valid: %#v", minDuration, err)) } 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 - } + d, err := time.ParseDuration(v.(string)) + if err != nil { + errors = append(errors, errwrap.Wrapf(fmt.Sprintf("Invalid %s specified (%q): {{err}}", e.SchemaName), err)) } + + if d < dMin { + errors = append(errors, fmt.Errorf("Invalid %s specified: duration %q less than the required minimum %s", e.SchemaName, v.(string), dMin)) + } + + return warnings, errors + } +} + +func _ValidateIntMinFactory(e *_TypeEntry, min int) func(v interface{}, key string) (warnings []string, errors []error) { + return func(v interface{}, key string) (warnings []string, errors []error) { + if v.(int) < min { + errors = append(errors, fmt.Errorf("Invalid %s specified: %d less than the required minimum %d", e.SchemaName, v.(int), min)) + } + return warnings, errors } }