github.com/richardbowden/terraform@v0.6.12-0.20160901200758-30ea22c25211/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  	return s.ZeroValue()
    42  }
    43  
    44  // addrToSchema finds the final element schema for the given address
    45  // and the given schema. It returns all the schemas that led to the final
    46  // schema. These are in order of the address (out to in).
    47  func addrToSchema(addr []string, schemaMap map[string]*Schema) []*Schema {
    48  	current := &Schema{
    49  		Type: typeObject,
    50  		Elem: schemaMap,
    51  	}
    52  
    53  	// If we aren't given an address, then the user is requesting the
    54  	// full object, so we return the special value which is the full object.
    55  	if len(addr) == 0 {
    56  		return []*Schema{current}
    57  	}
    58  
    59  	result := make([]*Schema, 0, len(addr))
    60  	for len(addr) > 0 {
    61  		k := addr[0]
    62  		addr = addr[1:]
    63  
    64  	REPEAT:
    65  		// We want to trim off the first "typeObject" since its not a
    66  		// real lookup that people do. i.e. []string{"foo"} in a structure
    67  		// isn't {typeObject, typeString}, its just a {typeString}.
    68  		if len(result) > 0 || current.Type != typeObject {
    69  			result = append(result, current)
    70  		}
    71  
    72  		switch t := current.Type; t {
    73  		case TypeBool, TypeInt, TypeFloat, TypeString:
    74  			if len(addr) > 0 {
    75  				return nil
    76  			}
    77  		case TypeList, TypeSet:
    78  			switch v := current.Elem.(type) {
    79  			case *Resource:
    80  				current = &Schema{
    81  					Type: typeObject,
    82  					Elem: v.Schema,
    83  				}
    84  			case *Schema:
    85  				current = v
    86  			default:
    87  				return nil
    88  			}
    89  
    90  			// If we only have one more thing and the next thing
    91  			// is a #, then we're accessing the index which is always
    92  			// an int.
    93  			if len(addr) > 0 && addr[0] == "#" {
    94  				current = &Schema{Type: TypeInt}
    95  				break
    96  			}
    97  		case TypeMap:
    98  			if len(addr) > 0 {
    99  				current = &Schema{Type: TypeString}
   100  			}
   101  		case typeObject:
   102  			// If we're already in the object, then we want to handle Sets
   103  			// and Lists specially. Basically, their next key is the lookup
   104  			// key (the set value or the list element). For these scenarios,
   105  			// we just want to skip it and move to the next element if there
   106  			// is one.
   107  			if len(result) > 0 {
   108  				lastType := result[len(result)-2].Type
   109  				if lastType == TypeSet || lastType == TypeList {
   110  					if len(addr) == 0 {
   111  						break
   112  					}
   113  
   114  					k = addr[0]
   115  					addr = addr[1:]
   116  				}
   117  			}
   118  
   119  			m := current.Elem.(map[string]*Schema)
   120  			val, ok := m[k]
   121  			if !ok {
   122  				return nil
   123  			}
   124  
   125  			current = val
   126  			goto REPEAT
   127  		}
   128  	}
   129  
   130  	return result
   131  }
   132  
   133  // readListField is a generic method for reading a list field out of a
   134  // a FieldReader. It does this based on the assumption that there is a key
   135  // "foo.#" for a list "foo" and that the indexes are "foo.0", "foo.1", etc.
   136  // after that point.
   137  func readListField(
   138  	r FieldReader, addr []string, schema *Schema) (FieldReadResult, error) {
   139  	addrPadded := make([]string, len(addr)+1)
   140  	copy(addrPadded, addr)
   141  	addrPadded[len(addrPadded)-1] = "#"
   142  
   143  	// Get the number of elements in the list
   144  	countResult, err := r.ReadField(addrPadded)
   145  	if err != nil {
   146  		return FieldReadResult{}, err
   147  	}
   148  	if !countResult.Exists {
   149  		// No count, means we have no list
   150  		countResult.Value = 0
   151  	}
   152  
   153  	// If we have an empty list, then return an empty list
   154  	if countResult.Computed || countResult.Value.(int) == 0 {
   155  		return FieldReadResult{
   156  			Value:    []interface{}{},
   157  			Exists:   countResult.Exists,
   158  			Computed: countResult.Computed,
   159  		}, nil
   160  	}
   161  
   162  	// Go through each count, and get the item value out of it
   163  	result := make([]interface{}, countResult.Value.(int))
   164  	for i, _ := range result {
   165  		is := strconv.FormatInt(int64(i), 10)
   166  		addrPadded[len(addrPadded)-1] = is
   167  		rawResult, err := r.ReadField(addrPadded)
   168  		if err != nil {
   169  			return FieldReadResult{}, err
   170  		}
   171  		if !rawResult.Exists {
   172  			// This should never happen, because by the time the data
   173  			// gets to the FieldReaders, all the defaults should be set by
   174  			// Schema.
   175  			rawResult.Value = nil
   176  		}
   177  
   178  		result[i] = rawResult.Value
   179  	}
   180  
   181  	return FieldReadResult{
   182  		Value:  result,
   183  		Exists: true,
   184  	}, nil
   185  }
   186  
   187  // readObjectField is a generic method for reading objects out of FieldReaders
   188  // based on the assumption that building an address of []string{k, FIELD}
   189  // will result in the proper field data.
   190  func readObjectField(
   191  	r FieldReader,
   192  	addr []string,
   193  	schema map[string]*Schema) (FieldReadResult, error) {
   194  	result := make(map[string]interface{})
   195  	exists := false
   196  	for field, s := range schema {
   197  		addrRead := make([]string, len(addr), len(addr)+1)
   198  		copy(addrRead, addr)
   199  		addrRead = append(addrRead, field)
   200  		rawResult, err := r.ReadField(addrRead)
   201  		if err != nil {
   202  			return FieldReadResult{}, err
   203  		}
   204  		if rawResult.Exists {
   205  			exists = true
   206  		}
   207  
   208  		result[field] = rawResult.ValueOrZero(s)
   209  	}
   210  
   211  	return FieldReadResult{
   212  		Value:  result,
   213  		Exists: exists,
   214  	}, nil
   215  }
   216  
   217  func stringToPrimitive(
   218  	value string, computed bool, schema *Schema) (interface{}, error) {
   219  	var returnVal interface{}
   220  	switch schema.Type {
   221  	case TypeBool:
   222  		if value == "" {
   223  			returnVal = false
   224  			break
   225  		}
   226  
   227  		v, err := strconv.ParseBool(value)
   228  		if err != nil {
   229  			return nil, err
   230  		}
   231  
   232  		returnVal = v
   233  	case TypeFloat:
   234  		if value == "" {
   235  			returnVal = 0.0
   236  			break
   237  		}
   238  		if computed {
   239  			break
   240  		}
   241  
   242  		v, err := strconv.ParseFloat(value, 64)
   243  		if err != nil {
   244  			return nil, err
   245  		}
   246  
   247  		returnVal = v
   248  	case TypeInt:
   249  		if value == "" {
   250  			returnVal = 0
   251  			break
   252  		}
   253  		if computed {
   254  			break
   255  		}
   256  
   257  		v, err := strconv.ParseInt(value, 0, 0)
   258  		if err != nil {
   259  			return nil, err
   260  		}
   261  
   262  		returnVal = int(v)
   263  	case TypeString:
   264  		returnVal = value
   265  	default:
   266  		panic(fmt.Sprintf("Unknown type: %s", schema.Type))
   267  	}
   268  
   269  	return returnVal, nil
   270  }