github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/ns1/meta.go (about)

     1  package ns1
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  
     7  	"github.com/hashicorp/terraform/helper/schema"
     8  	"gopkg.in/ns1/ns1-go.v2/rest/model/data"
     9  )
    10  
    11  type TfSchemaBuilder func(*schema.Schema)
    12  
    13  func mtSimple(t schema.ValueType) TfSchemaBuilder {
    14  	return func(s *schema.Schema) {
    15  		s.Type = t
    16  	}
    17  }
    18  
    19  func mtStringEnum(se *StringEnum) TfSchemaBuilder {
    20  	return func(s *schema.Schema) {
    21  		s.Type = schema.TypeString
    22  		s.ValidateFunc = func(v interface{}, k string) ([]string, []error) {
    23  			_, err := se.Check(v.(string))
    24  			if err != nil {
    25  				return nil, []error{err}
    26  			}
    27  			return nil, nil
    28  		}
    29  	}
    30  }
    31  
    32  var mtInt TfSchemaBuilder = mtSimple(schema.TypeInt)
    33  var mtBool TfSchemaBuilder = mtSimple(schema.TypeBool)
    34  var mtString TfSchemaBuilder = mtSimple(schema.TypeString)
    35  var mtFloat64 TfSchemaBuilder = mtSimple(schema.TypeFloat)
    36  
    37  func mtList(elementSchemaBuilder TfSchemaBuilder) TfSchemaBuilder {
    38  	return func(s *schema.Schema) {
    39  		s.Type = schema.TypeList
    40  		elementSchema := &schema.Schema{}
    41  		elementSchemaBuilder(elementSchema)
    42  		s.Elem = elementSchema
    43  	}
    44  }
    45  
    46  var mtStringList TfSchemaBuilder = mtList(mtString)
    47  
    48  type MetaFieldSpec struct {
    49  	NameInDynamic string
    50  	NameInStruct  string
    51  	SchemaBuilder TfSchemaBuilder
    52  }
    53  
    54  type MetaField struct {
    55  	MetaFieldSpec
    56  	NameInDynamicForFeed string
    57  	StructIndex          int
    58  	StructGoType         reflect.Type
    59  }
    60  
    61  var georegionEnum *StringEnum = NewStringEnum([]string{
    62  	"US-WEST",
    63  	"US-EAST",
    64  	"US-CENTRAL",
    65  	"EUROPE",
    66  	"AFRICA",
    67  	"ASIAPAC",
    68  	"SOUTH-AMERICA",
    69  })
    70  
    71  func makeMetaFields() []MetaField {
    72  	var specs []MetaFieldSpec = []MetaFieldSpec{
    73  		{"up", "Up", mtBool},
    74  		{"connections", "Connections", mtInt},
    75  		{"requests", "Requests", mtInt},
    76  		{"loadavg", "LoadAvg", mtFloat64},
    77  		{"pulsar", "Pulsar", mtInt},
    78  		{"latitude", "Latitude", mtFloat64},
    79  		{"longitude", "Longitude", mtFloat64},
    80  		{"georegion", "Georegion", mtList(mtStringEnum(georegionEnum))},
    81  		{"country", "Country", mtStringList},
    82  		{"us_state", "USState", mtStringList},
    83  		{"ca_province", "CAProvince", mtStringList},
    84  		{"note", "Note", mtString},
    85  		{"ip_prefixes", "IPPrefixes", mtStringList},
    86  		{"asn", "ASN", mtList(mtInt)},
    87  		{"priority", "Priority", mtInt},
    88  		{"weight", "Weight", mtFloat64},
    89  		{"low_watermark", "LowWatermark", mtInt},
    90  		{"high_watermark", "HighWatermark", mtInt},
    91  	}
    92  
    93  	// Figure out the field indexes (in data.Meta) for all the fields.
    94  	// This way we can later lookup by index, which should be faster than by name.
    95  
    96  	rt := reflect.TypeOf(data.Meta{})
    97  	fields := make([]MetaField, len(specs))
    98  	for i, spec := range specs {
    99  		rf, present := rt.FieldByName(spec.NameInStruct)
   100  		if !present {
   101  			panic(fmt.Sprintf("Field %q not present", spec.NameInStruct))
   102  		}
   103  		if len(rf.Index) != 1 {
   104  			panic(fmt.Sprintf("Expecting a single index, got %#v", rf.Index))
   105  		}
   106  		index := rf.Index[0]
   107  		fields[i] = MetaField{
   108  			MetaFieldSpec:        spec,
   109  			StructIndex:          index,
   110  			NameInDynamicForFeed: spec.NameInDynamic + "_feed",
   111  			StructGoType:         rf.Type,
   112  		}
   113  	}
   114  
   115  	return fields
   116  }
   117  
   118  var metaFields []MetaField = makeMetaFields()
   119  
   120  func makeMetaSchema() *schema.Schema {
   121  	fields := make(map[string]*schema.Schema)
   122  
   123  	for _, f := range metaFields {
   124  		fieldSchema := &schema.Schema{
   125  			Optional: true,
   126  			ForceNew: true,
   127  			// TODO: Fields that arent in configuration shouldnt show up in resource data
   128  			// ConflictsWith: []string{f.NameInDynamicForFeed},
   129  		}
   130  		f.SchemaBuilder(fieldSchema)
   131  
   132  		fields[f.NameInDynamic] = fieldSchema
   133  
   134  		// Add an "_feed"-suffixed field for the {"feed":...} value.
   135  		fields[f.NameInDynamicForFeed] = &schema.Schema{
   136  			Optional: true,
   137  			ForceNew: true,
   138  			// TODO: Fields that arent in configuration shouldnt show up in resource data
   139  			// ConflictsWith: []string{f.NameInDynamic},
   140  			Type: schema.TypeString,
   141  		}
   142  	}
   143  
   144  	metaSchemaInner := &schema.Resource{
   145  		Schema: fields,
   146  	}
   147  
   148  	// Wrap it in a list because that seems to be the only way to have nested structs.
   149  	return &schema.Schema{
   150  		Type:     schema.TypeList,
   151  		Optional: true,
   152  		MaxItems: 1,
   153  		Elem:     metaSchemaInner,
   154  	}
   155  }
   156  
   157  var metaSchema *schema.Schema = makeMetaSchema()
   158  
   159  func metaStructToDynamic(m *data.Meta) interface{} {
   160  	d := make(map[string]interface{})
   161  	mr := reflect.ValueOf(m).Elem()
   162  	for _, f := range metaFields {
   163  		fr := mr.Field(f.StructIndex)
   164  		fv := fr.Interface()
   165  
   166  		if fv == nil {
   167  			continue
   168  		}
   169  
   170  		if mapVal, isMap := fv.(map[string]interface{}); isMap {
   171  			if len(mapVal) == 1 {
   172  				if feedVal, ok := mapVal["feed"]; ok {
   173  					if feedStr, ok := feedVal.(string); ok {
   174  						d[f.NameInDynamicForFeed] = feedStr
   175  						continue
   176  					}
   177  				}
   178  			}
   179  			panic(fmt.Sprintf("expecting feed dict, got %+v", mapVal))
   180  		}
   181  
   182  		d[f.NameInDynamic] = fv
   183  	}
   184  	return []interface{}{d}
   185  }
   186  
   187  func metaDynamicToStruct(m *data.Meta, raw interface{}) {
   188  	l := raw.([]interface{})
   189  	if len(l) > 1 {
   190  		panic(fmt.Sprintf("list too long %#v", l))
   191  	}
   192  	if len(l) == 0 {
   193  		return
   194  	}
   195  	if l[0] == nil {
   196  		return
   197  	}
   198  
   199  	d := l[0].(map[string]interface{})
   200  
   201  	mr := reflect.ValueOf(m).Elem()
   202  	for _, f := range metaFields {
   203  		val, present := d[f.NameInDynamic]
   204  		if present {
   205  			fr := mr.Field(f.StructIndex)
   206  			fr.Set(reflect.ValueOf(val))
   207  		}
   208  
   209  		feed, present := d[f.NameInDynamicForFeed]
   210  		if present && feed != "" {
   211  			if feed == nil {
   212  				panic("unexpected nil")
   213  			}
   214  			fr := mr.Field(f.StructIndex)
   215  			fr.Set(reflect.ValueOf(map[string]interface{}{"feed": feed.(string)}))
   216  		}
   217  	}
   218  }