github.com/ndarilek/terraform@v0.3.8-0.20150320140257-d3135c1b2bac/helper/schema/field_reader.go (about)

     1  package schema
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  )
     7  
     8  // FieldReaders are responsible for decoding fields out of data into
     9  // the proper typed representation. ResourceData uses this to query data
    10  // out of multiple sources: config, state, diffs, etc.
    11  type FieldReader interface {
    12  	ReadField([]string) (FieldReadResult, error)
    13  }
    14  
    15  // FieldReadResult encapsulates all the resulting data from reading
    16  // a field.
    17  type FieldReadResult struct {
    18  	// Value is the actual read value. NegValue is the _negative_ value
    19  	// or the items that should be removed (if they existed). NegValue
    20  	// doesn't make sense for primitives but is important for any
    21  	// container types such as maps, sets, lists.
    22  	Value          interface{}
    23  	ValueProcessed interface{}
    24  
    25  	// Exists is true if the field was found in the data. False means
    26  	// it wasn't found if there was no error.
    27  	Exists bool
    28  
    29  	// Computed is true if the field was found but the value
    30  	// is computed.
    31  	Computed bool
    32  }
    33  
    34  // ValueOrZero returns the value of this result or the zero value of the
    35  // schema type, ensuring a consistent non-nil return value.
    36  func (r *FieldReadResult) ValueOrZero(s *Schema) interface{} {
    37  	if r.Value != nil {
    38  		return r.Value
    39  	}
    40  
    41  	result := s.Type.Zero()
    42  
    43  	// The zero value of a set is nil, but we want it
    44  	// to actually be an empty set object...
    45  	if set, ok := result.(*Set); ok && set.F == nil {
    46  		set.F = s.Set
    47  	}
    48  
    49  	return result
    50  }
    51  
    52  // addrToSchema finds the final element schema for the given address
    53  // and the given schema. It returns all the schemas that led to the final
    54  // schema. These are in order of the address (out to in).
    55  func addrToSchema(addr []string, schemaMap map[string]*Schema) []*Schema {
    56  	current := &Schema{
    57  		Type: typeObject,
    58  		Elem: schemaMap,
    59  	}
    60  
    61  	// If we aren't given an address, then the user is requesting the
    62  	// full object, so we return the special value which is the full object.
    63  	if len(addr) == 0 {
    64  		return []*Schema{current}
    65  	}
    66  
    67  	result := make([]*Schema, 0, len(addr))
    68  	for len(addr) > 0 {
    69  		k := addr[0]
    70  		addr = addr[1:]
    71  
    72  	REPEAT:
    73  		// We want to trim off the first "typeObject" since its not a
    74  		// real lookup that people do. i.e. []string{"foo"} in a structure
    75  		// isn't {typeObject, typeString}, its just a {typeString}.
    76  		if len(result) > 0 || current.Type != typeObject {
    77  			result = append(result, current)
    78  		}
    79  
    80  		switch t := current.Type; t {
    81  		case TypeBool:
    82  			fallthrough
    83  		case TypeInt:
    84  			fallthrough
    85  		case TypeFloat:
    86  			fallthrough
    87  		case TypeString:
    88  			if len(addr) > 0 {
    89  				return nil
    90  			}
    91  		case TypeList:
    92  			fallthrough
    93  		case TypeSet:
    94  			switch v := current.Elem.(type) {
    95  			case *Resource:
    96  				current = &Schema{
    97  					Type: typeObject,
    98  					Elem: v.Schema,
    99  				}
   100  			case *Schema:
   101  				current = v
   102  			default:
   103  				return nil
   104  			}
   105  
   106  			// If we only have one more thing and the the next thing
   107  			// is a #, then we're accessing the index which is always
   108  			// an int.
   109  			if len(addr) > 0 && addr[0] == "#" {
   110  				current = &Schema{Type: TypeInt}
   111  				break
   112  			}
   113  		case TypeMap:
   114  			if len(addr) > 0 {
   115  				current = &Schema{Type: TypeString}
   116  			}
   117  		case typeObject:
   118  			// If we're already in the object, then we want to handle Sets
   119  			// and Lists specially. Basically, their next key is the lookup
   120  			// key (the set value or the list element). For these scenarios,
   121  			// we just want to skip it and move to the next element if there
   122  			// is one.
   123  			if len(result) > 0 {
   124  				lastType := result[len(result)-2].Type
   125  				if lastType == TypeSet || lastType == TypeList {
   126  					if len(addr) == 0 {
   127  						break
   128  					}
   129  
   130  					k = addr[0]
   131  					addr = addr[1:]
   132  				}
   133  			}
   134  
   135  			m := current.Elem.(map[string]*Schema)
   136  			val, ok := m[k]
   137  			if !ok {
   138  				return nil
   139  			}
   140  
   141  			current = val
   142  			goto REPEAT
   143  		}
   144  	}
   145  
   146  	return result
   147  }
   148  
   149  // readListField is a generic method for reading a list field out of a
   150  // a FieldReader. It does this based on the assumption that there is a key
   151  // "foo.#" for a list "foo" and that the indexes are "foo.0", "foo.1", etc.
   152  // after that point.
   153  func readListField(
   154  	r FieldReader, addr []string, schema *Schema) (FieldReadResult, error) {
   155  	addrPadded := make([]string, len(addr)+1)
   156  	copy(addrPadded, addr)
   157  	addrPadded[len(addrPadded)-1] = "#"
   158  
   159  	// Get the number of elements in the list
   160  	countResult, err := r.ReadField(addrPadded)
   161  	if err != nil {
   162  		return FieldReadResult{}, err
   163  	}
   164  	if !countResult.Exists {
   165  		// No count, means we have no list
   166  		countResult.Value = 0
   167  	}
   168  
   169  	// If we have an empty list, then return an empty list
   170  	if countResult.Computed || countResult.Value.(int) == 0 {
   171  		return FieldReadResult{
   172  			Value:    []interface{}{},
   173  			Exists:   countResult.Exists,
   174  			Computed: countResult.Computed,
   175  		}, nil
   176  	}
   177  
   178  	// Go through each count, and get the item value out of it
   179  	result := make([]interface{}, countResult.Value.(int))
   180  	for i, _ := range result {
   181  		is := strconv.FormatInt(int64(i), 10)
   182  		addrPadded[len(addrPadded)-1] = is
   183  		rawResult, err := r.ReadField(addrPadded)
   184  		if err != nil {
   185  			return FieldReadResult{}, err
   186  		}
   187  		if !rawResult.Exists {
   188  			// This should never happen, because by the time the data
   189  			// gets to the FieldReaders, all the defaults should be set by
   190  			// Schema.
   191  			rawResult.Value = nil
   192  		}
   193  
   194  		result[i] = rawResult.Value
   195  	}
   196  
   197  	return FieldReadResult{
   198  		Value:  result,
   199  		Exists: true,
   200  	}, nil
   201  }
   202  
   203  // readObjectField is a generic method for reading objects out of FieldReaders
   204  // based on the assumption that building an address of []string{k, FIELD}
   205  // will result in the proper field data.
   206  func readObjectField(
   207  	r FieldReader,
   208  	addr []string,
   209  	schema map[string]*Schema) (FieldReadResult, error) {
   210  	result := make(map[string]interface{})
   211  	exists := false
   212  	for field, s := range schema {
   213  		addrRead := make([]string, len(addr), len(addr)+1)
   214  		copy(addrRead, addr)
   215  		addrRead = append(addrRead, field)
   216  		rawResult, err := r.ReadField(addrRead)
   217  		if err != nil {
   218  			return FieldReadResult{}, err
   219  		}
   220  		if rawResult.Exists {
   221  			exists = true
   222  		}
   223  
   224  		result[field] = rawResult.ValueOrZero(s)
   225  	}
   226  
   227  	return FieldReadResult{
   228  		Value:  result,
   229  		Exists: exists,
   230  	}, nil
   231  }
   232  
   233  func stringToPrimitive(
   234  	value string, computed bool, schema *Schema) (interface{}, error) {
   235  	var returnVal interface{}
   236  	switch schema.Type {
   237  	case TypeBool:
   238  		if value == "" {
   239  			returnVal = false
   240  			break
   241  		}
   242  
   243  		v, err := strconv.ParseBool(value)
   244  		if err != nil {
   245  			return nil, err
   246  		}
   247  
   248  		returnVal = v
   249  	case TypeFloat:
   250  		if value == "" {
   251  			returnVal = 0.0
   252  			break
   253  		}
   254  		if computed {
   255  			break
   256  		}
   257  
   258  		v, err := strconv.ParseFloat(value, 64)
   259  		if err != nil {
   260  			return nil, err
   261  		}
   262  
   263  		returnVal = v
   264  	case TypeInt:
   265  		if value == "" {
   266  			returnVal = 0
   267  			break
   268  		}
   269  		if computed {
   270  			break
   271  		}
   272  
   273  		v, err := strconv.ParseInt(value, 0, 0)
   274  		if err != nil {
   275  			return nil, err
   276  		}
   277  
   278  		returnVal = int(v)
   279  	case TypeString:
   280  		returnVal = value
   281  	default:
   282  		panic(fmt.Sprintf("Unknown type: %s", schema.Type))
   283  	}
   284  
   285  	return returnVal, nil
   286  }