Add a datasource for catalog nodes.

This commit is contained in:
Sean Chittenden 2017-02-09 09:01:38 -08:00
parent db2f217f25
commit ac1294633a
No known key found for this signature in database
GPG Key ID: 4EBC9DC16C2E5E16
4 changed files with 609 additions and 57 deletions

View File

@ -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
}

View File

@ -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", "<any>"),
testAccCheckDataSourceValue("data.consul_catalog_nodes.read", "nodes.0.name", "<any>"),
testAccCheckDataSourceValue("data.consul_catalog_nodes.read", "nodes.0.address", "<any>"),
),
},
},
})
}
const testAccDataConsulCatalogNodesConfig = `
data "consul_catalog_nodes" "read" {
allow_stale = true
require_consistent = false
near = "_agent"
token = ""
wait_index = 0
wait_time = "1m"
}
`

View File

@ -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{

View File

@ -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
}
}