Merge pull request #764 from hashicorp/f-schema-typed-readers
Refactor of helper/schema to prepare for better state formats
This commit is contained in:
commit
12d20a8d52
|
@ -0,0 +1,269 @@
|
||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FieldReaders are responsible for decoding fields out of data into
|
||||||
|
// the proper typed representation. ResourceData uses this to query data
|
||||||
|
// out of multiple sources: config, state, diffs, etc.
|
||||||
|
type FieldReader interface {
|
||||||
|
ReadField([]string) (FieldReadResult, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FieldReadResult encapsulates all the resulting data from reading
|
||||||
|
// a field.
|
||||||
|
type FieldReadResult struct {
|
||||||
|
// Value is the actual read value. NegValue is the _negative_ value
|
||||||
|
// or the items that should be removed (if they existed). NegValue
|
||||||
|
// doesn't make sense for primitives but is important for any
|
||||||
|
// container types such as maps, sets, lists.
|
||||||
|
Value interface{}
|
||||||
|
ValueProcessed interface{}
|
||||||
|
|
||||||
|
// Exists is true if the field was found in the data. False means
|
||||||
|
// it wasn't found if there was no error.
|
||||||
|
Exists bool
|
||||||
|
|
||||||
|
// Computed is true if the field was found but the value
|
||||||
|
// is computed.
|
||||||
|
Computed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValueOrZero returns the value of this result or the zero value of the
|
||||||
|
// schema type, ensuring a consistent non-nil return value.
|
||||||
|
func (r *FieldReadResult) ValueOrZero(s *Schema) interface{} {
|
||||||
|
if r.Value != nil {
|
||||||
|
return r.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
result := s.Type.Zero()
|
||||||
|
|
||||||
|
// The zero value of a set is nil, but we want it
|
||||||
|
// to actually be an empty set object...
|
||||||
|
if s.Type == TypeSet && result == nil {
|
||||||
|
result = &Set{F: s.Set}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// addrToSchema finds the final element schema for the given address
|
||||||
|
// and the given schema. It returns all the schemas that led to the final
|
||||||
|
// schema. These are in order of the address (out to in).
|
||||||
|
func addrToSchema(addr []string, schemaMap map[string]*Schema) []*Schema {
|
||||||
|
current := &Schema{
|
||||||
|
Type: typeObject,
|
||||||
|
Elem: schemaMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we aren't given an address, then the user is requesting the
|
||||||
|
// full object, so we return the special value which is the full object.
|
||||||
|
if len(addr) == 0 {
|
||||||
|
return []*Schema{current}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]*Schema, 0, len(addr))
|
||||||
|
for len(addr) > 0 {
|
||||||
|
k := addr[0]
|
||||||
|
addr = addr[1:]
|
||||||
|
|
||||||
|
REPEAT:
|
||||||
|
// We want to trim off the first "typeObject" since its not a
|
||||||
|
// real lookup that people do. i.e. []string{"foo"} in a structure
|
||||||
|
// isn't {typeObject, typeString}, its just a {typeString}.
|
||||||
|
if len(result) > 0 || current.Type != typeObject {
|
||||||
|
result = append(result, current)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t := current.Type; t {
|
||||||
|
case TypeBool:
|
||||||
|
fallthrough
|
||||||
|
case TypeInt:
|
||||||
|
fallthrough
|
||||||
|
case TypeString:
|
||||||
|
if len(addr) > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case TypeList:
|
||||||
|
fallthrough
|
||||||
|
case TypeSet:
|
||||||
|
switch v := current.Elem.(type) {
|
||||||
|
case *Resource:
|
||||||
|
current = &Schema{
|
||||||
|
Type: typeObject,
|
||||||
|
Elem: v.Schema,
|
||||||
|
}
|
||||||
|
case *Schema:
|
||||||
|
current = v
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we only have one more thing and the the next thing
|
||||||
|
// is a #, then we're accessing the index which is always
|
||||||
|
// an int.
|
||||||
|
if len(addr) > 0 && addr[0] == "#" {
|
||||||
|
current = &Schema{Type: TypeInt}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case TypeMap:
|
||||||
|
if len(addr) > 0 {
|
||||||
|
current = &Schema{Type: TypeString}
|
||||||
|
}
|
||||||
|
case typeObject:
|
||||||
|
// If we're already in the object, then we want to handle Sets
|
||||||
|
// and Lists specially. Basically, their next key is the lookup
|
||||||
|
// key (the set value or the list element). For these scenarios,
|
||||||
|
// we just want to skip it and move to the next element if there
|
||||||
|
// is one.
|
||||||
|
if len(result) > 0 {
|
||||||
|
lastType := result[len(result)-2].Type
|
||||||
|
if lastType == TypeSet || lastType == TypeList {
|
||||||
|
if len(addr) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
k = addr[0]
|
||||||
|
addr = addr[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m := current.Elem.(map[string]*Schema)
|
||||||
|
val, ok := m[k]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
current = val
|
||||||
|
goto REPEAT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// readListField is a generic method for reading a list field out of a
|
||||||
|
// a FieldReader. It does this based on the assumption that there is a key
|
||||||
|
// "foo.#" for a list "foo" and that the indexes are "foo.0", "foo.1", etc.
|
||||||
|
// after that point.
|
||||||
|
func readListField(
|
||||||
|
r FieldReader, addr []string, schema *Schema) (FieldReadResult, error) {
|
||||||
|
addrPadded := make([]string, len(addr)+1)
|
||||||
|
copy(addrPadded, addr)
|
||||||
|
addrPadded[len(addrPadded)-1] = "#"
|
||||||
|
|
||||||
|
// Get the number of elements in the list
|
||||||
|
countResult, err := r.ReadField(addrPadded)
|
||||||
|
if err != nil {
|
||||||
|
return FieldReadResult{}, err
|
||||||
|
}
|
||||||
|
if !countResult.Exists {
|
||||||
|
// No count, means we have no list
|
||||||
|
countResult.Value = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have an empty list, then return an empty list
|
||||||
|
if countResult.Computed || countResult.Value.(int) == 0 {
|
||||||
|
return FieldReadResult{
|
||||||
|
Value: []interface{}{},
|
||||||
|
Exists: countResult.Exists,
|
||||||
|
Computed: countResult.Computed,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through each count, and get the item value out of it
|
||||||
|
result := make([]interface{}, countResult.Value.(int))
|
||||||
|
for i, _ := range result {
|
||||||
|
is := strconv.FormatInt(int64(i), 10)
|
||||||
|
addrPadded[len(addrPadded)-1] = is
|
||||||
|
rawResult, err := r.ReadField(addrPadded)
|
||||||
|
if err != nil {
|
||||||
|
return FieldReadResult{}, err
|
||||||
|
}
|
||||||
|
if !rawResult.Exists {
|
||||||
|
// This should never happen, because by the time the data
|
||||||
|
// gets to the FieldReaders, all the defaults should be set by
|
||||||
|
// Schema.
|
||||||
|
rawResult.Value = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result[i] = rawResult.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
return FieldReadResult{
|
||||||
|
Value: result,
|
||||||
|
Exists: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readObjectField is a generic method for reading objects out of FieldReaders
|
||||||
|
// based on the assumption that building an address of []string{k, FIELD}
|
||||||
|
// will result in the proper field data.
|
||||||
|
func readObjectField(
|
||||||
|
r FieldReader,
|
||||||
|
addr []string,
|
||||||
|
schema map[string]*Schema) (FieldReadResult, error) {
|
||||||
|
result := make(map[string]interface{})
|
||||||
|
exists := false
|
||||||
|
for field, s := range schema {
|
||||||
|
addrRead := make([]string, len(addr), len(addr)+1)
|
||||||
|
copy(addrRead, addr)
|
||||||
|
addrRead = append(addrRead, field)
|
||||||
|
rawResult, err := r.ReadField(addrRead)
|
||||||
|
if err != nil {
|
||||||
|
return FieldReadResult{}, err
|
||||||
|
}
|
||||||
|
if rawResult.Exists {
|
||||||
|
exists = true
|
||||||
|
}
|
||||||
|
|
||||||
|
result[field] = rawResult.ValueOrZero(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return FieldReadResult{
|
||||||
|
Value: result,
|
||||||
|
Exists: exists,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringToPrimitive(
|
||||||
|
value string, computed bool, schema *Schema) (interface{}, error) {
|
||||||
|
var returnVal interface{}
|
||||||
|
switch schema.Type {
|
||||||
|
case TypeBool:
|
||||||
|
if value == "" {
|
||||||
|
returnVal = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := strconv.ParseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
returnVal = v
|
||||||
|
case TypeInt:
|
||||||
|
if value == "" {
|
||||||
|
returnVal = 0
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if computed {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := strconv.ParseInt(value, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
returnVal = int(v)
|
||||||
|
case TypeString:
|
||||||
|
returnVal = value
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("Unknown type: %#v", schema.Type))
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnVal, nil
|
||||||
|
}
|
|
@ -0,0 +1,236 @@
|
||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigFieldReader reads fields out of an untyped map[string]string to
|
||||||
|
// the best of its ability.
|
||||||
|
type ConfigFieldReader struct {
|
||||||
|
Config *terraform.ResourceConfig
|
||||||
|
Schema map[string]*Schema
|
||||||
|
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ConfigFieldReader) ReadField(address []string) (FieldReadResult, error) {
|
||||||
|
return r.readField(address, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ConfigFieldReader) readField(
|
||||||
|
address []string, nested bool) (FieldReadResult, error) {
|
||||||
|
schemaList := addrToSchema(address, r.Schema)
|
||||||
|
if len(schemaList) == 0 {
|
||||||
|
return FieldReadResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !nested {
|
||||||
|
// If we have a set anywhere in the address, then we need to
|
||||||
|
// read that set out in order and actually replace that part of
|
||||||
|
// the address with the real list index. i.e. set.50 might actually
|
||||||
|
// map to set.12 in the config, since it is in list order in the
|
||||||
|
// config, not indexed by set value.
|
||||||
|
for i, v := range schemaList {
|
||||||
|
// Sets are the only thing that cause this issue.
|
||||||
|
if v.Type != TypeSet {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're at the end of the list, then we don't have to worry
|
||||||
|
// about this because we're just requesting the whole set.
|
||||||
|
if i == len(schemaList)-1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're looking for the count, then ignore...
|
||||||
|
if address[i+1] == "#" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the code
|
||||||
|
code, err := strconv.ParseInt(address[i+1], 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return FieldReadResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the set so we can get the index map that tells us the
|
||||||
|
// mapping of the hash code to the list index
|
||||||
|
_, indexMap, err := r.readSet(address[:i+1], v)
|
||||||
|
if err != nil {
|
||||||
|
return FieldReadResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
index, ok := indexMap[int(code)]
|
||||||
|
if !ok {
|
||||||
|
return FieldReadResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
address[i+1] = strconv.FormatInt(int64(index), 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
k := strings.Join(address, ".")
|
||||||
|
schema := schemaList[len(schemaList)-1]
|
||||||
|
switch schema.Type {
|
||||||
|
case TypeBool:
|
||||||
|
fallthrough
|
||||||
|
case TypeInt:
|
||||||
|
fallthrough
|
||||||
|
case TypeString:
|
||||||
|
return r.readPrimitive(k, schema)
|
||||||
|
case TypeList:
|
||||||
|
return readListField(&nestedConfigFieldReader{r}, address, schema)
|
||||||
|
case TypeMap:
|
||||||
|
return r.readMap(k)
|
||||||
|
case TypeSet:
|
||||||
|
result, _, err := r.readSet(address, schema)
|
||||||
|
return result, err
|
||||||
|
case typeObject:
|
||||||
|
return readObjectField(
|
||||||
|
&nestedConfigFieldReader{r},
|
||||||
|
address, schema.Elem.(map[string]*Schema))
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("Unknown type: %#v", schema.Type))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ConfigFieldReader) readMap(k string) (FieldReadResult, error) {
|
||||||
|
mraw, ok := r.Config.Get(k)
|
||||||
|
if !ok {
|
||||||
|
return FieldReadResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[string]interface{})
|
||||||
|
switch m := mraw.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
for _, innerRaw := range m {
|
||||||
|
for k, v := range innerRaw.(map[string]interface{}) {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case []map[string]interface{}:
|
||||||
|
for _, innerRaw := range m {
|
||||||
|
for k, v := range innerRaw {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case map[string]interface{}:
|
||||||
|
result = m
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unknown type: %#v", mraw))
|
||||||
|
}
|
||||||
|
|
||||||
|
return FieldReadResult{
|
||||||
|
Value: result,
|
||||||
|
Exists: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ConfigFieldReader) readPrimitive(
|
||||||
|
k string, schema *Schema) (FieldReadResult, error) {
|
||||||
|
raw, ok := r.Config.Get(k)
|
||||||
|
if !ok {
|
||||||
|
return FieldReadResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result string
|
||||||
|
if err := mapstructure.WeakDecode(raw, &result); err != nil {
|
||||||
|
return FieldReadResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
computed := r.Config.IsComputed(k)
|
||||||
|
returnVal, err := stringToPrimitive(result, computed, schema)
|
||||||
|
if err != nil {
|
||||||
|
return FieldReadResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return FieldReadResult{
|
||||||
|
Value: returnVal,
|
||||||
|
Exists: true,
|
||||||
|
Computed: computed,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ConfigFieldReader) readSet(
|
||||||
|
address []string, schema *Schema) (FieldReadResult, map[int]int, error) {
|
||||||
|
indexMap := make(map[int]int)
|
||||||
|
// Create the set that will be our result
|
||||||
|
set := &Set{F: schema.Set}
|
||||||
|
|
||||||
|
raw, err := readListField(&nestedConfigFieldReader{r}, address, schema)
|
||||||
|
if err != nil {
|
||||||
|
return FieldReadResult{}, indexMap, err
|
||||||
|
}
|
||||||
|
if !raw.Exists {
|
||||||
|
return FieldReadResult{Value: set}, indexMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the list is computed, the set is necessarilly computed
|
||||||
|
if raw.Computed {
|
||||||
|
return FieldReadResult{
|
||||||
|
Value: set,
|
||||||
|
Exists: true,
|
||||||
|
Computed: raw.Computed,
|
||||||
|
}, indexMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build up the set from the list elements
|
||||||
|
for i, v := range raw.Value.([]interface{}) {
|
||||||
|
// Check if any of the keys in this item are computed
|
||||||
|
computed := r.hasComputedSubKeys(
|
||||||
|
fmt.Sprintf("%s.%d", strings.Join(address, "."), i), schema)
|
||||||
|
|
||||||
|
code := set.add(v)
|
||||||
|
indexMap[code] = i
|
||||||
|
if computed {
|
||||||
|
set.m[-code] = set.m[code]
|
||||||
|
delete(set.m, code)
|
||||||
|
code = -code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FieldReadResult{
|
||||||
|
Value: set,
|
||||||
|
Exists: true,
|
||||||
|
}, indexMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasComputedSubKeys walks through a schema and returns whether or not the
|
||||||
|
// given key contains any subkeys that are computed.
|
||||||
|
func (r *ConfigFieldReader) hasComputedSubKeys(key string, schema *Schema) bool {
|
||||||
|
prefix := key + "."
|
||||||
|
|
||||||
|
switch t := schema.Elem.(type) {
|
||||||
|
case *Resource:
|
||||||
|
for k, schema := range t.Schema {
|
||||||
|
if r.Config.IsComputed(prefix + k) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.hasComputedSubKeys(prefix+k, schema) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// nestedConfigFieldReader is a funny little thing that just wraps a
|
||||||
|
// ConfigFieldReader to call readField when ReadField is called so that
|
||||||
|
// we don't recalculate the set rewrites in the address, which leads to
|
||||||
|
// an infinite loop.
|
||||||
|
type nestedConfigFieldReader struct {
|
||||||
|
Reader *ConfigFieldReader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *nestedConfigFieldReader) ReadField(
|
||||||
|
address []string) (FieldReadResult, error) {
|
||||||
|
return r.Reader.readField(address, true)
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfigFieldReader_impl(t *testing.T) {
|
||||||
|
var _ FieldReader = new(ConfigFieldReader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigFieldReader(t *testing.T) {
|
||||||
|
testFieldReader(t, func(s map[string]*Schema) FieldReader {
|
||||||
|
return &ConfigFieldReader{
|
||||||
|
Schema: s,
|
||||||
|
|
||||||
|
Config: testConfig(t, map[string]interface{}{
|
||||||
|
"bool": true,
|
||||||
|
"int": 42,
|
||||||
|
"string": "string",
|
||||||
|
|
||||||
|
"list": []interface{}{"foo", "bar"},
|
||||||
|
|
||||||
|
"listInt": []interface{}{21, 42},
|
||||||
|
|
||||||
|
"map": map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
"bar": "baz",
|
||||||
|
},
|
||||||
|
|
||||||
|
"set": []interface{}{10, 50},
|
||||||
|
"setDeep": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"index": 10,
|
||||||
|
"value": "foo",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"index": 50,
|
||||||
|
"value": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testConfig(
|
||||||
|
t *testing.T, raw map[string]interface{}) *terraform.ResourceConfig {
|
||||||
|
rc, err := config.NewRawConfig(raw)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return terraform.NewResourceConfig(rc)
|
||||||
|
}
|
|
@ -0,0 +1,174 @@
|
||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DiffFieldReader reads fields out of a diff structures.
|
||||||
|
//
|
||||||
|
// It also requires access to a Reader that reads fields from the structure
|
||||||
|
// that the diff was derived from. This is usually the state. This is required
|
||||||
|
// because a diff on its own doesn't have complete data about full objects
|
||||||
|
// such as maps.
|
||||||
|
//
|
||||||
|
// The Source MUST be the data that the diff was derived from. If it isn't,
|
||||||
|
// the behavior of this struct is undefined.
|
||||||
|
//
|
||||||
|
// Reading fields from a DiffFieldReader is identical to reading from
|
||||||
|
// Source except the diff will be applied to the end result.
|
||||||
|
//
|
||||||
|
// The "Exists" field on the result will be set to true if the complete
|
||||||
|
// field exists whether its from the source, diff, or a combination of both.
|
||||||
|
// It cannot be determined whether a retrieved value is composed of
|
||||||
|
// diff elements.
|
||||||
|
type DiffFieldReader struct {
|
||||||
|
Diff *terraform.InstanceDiff
|
||||||
|
Source FieldReader
|
||||||
|
Schema map[string]*Schema
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DiffFieldReader) ReadField(address []string) (FieldReadResult, error) {
|
||||||
|
schemaList := addrToSchema(address, r.Schema)
|
||||||
|
if len(schemaList) == 0 {
|
||||||
|
return FieldReadResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
schema := schemaList[len(schemaList)-1]
|
||||||
|
switch schema.Type {
|
||||||
|
case TypeBool:
|
||||||
|
fallthrough
|
||||||
|
case TypeInt:
|
||||||
|
fallthrough
|
||||||
|
case TypeString:
|
||||||
|
return r.readPrimitive(address, schema)
|
||||||
|
case TypeList:
|
||||||
|
return readListField(r, address, schema)
|
||||||
|
case TypeMap:
|
||||||
|
return r.readMap(address, schema)
|
||||||
|
case TypeSet:
|
||||||
|
return r.readSet(address, schema)
|
||||||
|
case typeObject:
|
||||||
|
return readObjectField(r, address, schema.Elem.(map[string]*Schema))
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("Unknown type: %#v", schema.Type))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DiffFieldReader) readMap(
|
||||||
|
address []string, schema *Schema) (FieldReadResult, error) {
|
||||||
|
result := make(map[string]interface{})
|
||||||
|
resultSet := false
|
||||||
|
|
||||||
|
// First read the map from the underlying source
|
||||||
|
source, err := r.Source.ReadField(address)
|
||||||
|
if err != nil {
|
||||||
|
return FieldReadResult{}, err
|
||||||
|
}
|
||||||
|
if source.Exists {
|
||||||
|
result = source.Value.(map[string]interface{})
|
||||||
|
resultSet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, read all the elements we have in our diff, and apply
|
||||||
|
// the diff to our result.
|
||||||
|
prefix := strings.Join(address, ".") + "."
|
||||||
|
for k, v := range r.Diff.Attributes {
|
||||||
|
if !strings.HasPrefix(k, prefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
resultSet = true
|
||||||
|
|
||||||
|
k = k[len(prefix):]
|
||||||
|
if v.NewRemoved {
|
||||||
|
delete(result, k)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result[k] = v.New
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultVal interface{}
|
||||||
|
if resultSet {
|
||||||
|
resultVal = result
|
||||||
|
}
|
||||||
|
|
||||||
|
return FieldReadResult{
|
||||||
|
Value: resultVal,
|
||||||
|
Exists: resultSet,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DiffFieldReader) readPrimitive(
|
||||||
|
address []string, schema *Schema) (FieldReadResult, error) {
|
||||||
|
result, err := r.Source.ReadField(address)
|
||||||
|
if err != nil {
|
||||||
|
return FieldReadResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
attrD, ok := r.Diff.Attributes[strings.Join(address, ".")]
|
||||||
|
if !ok {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultVal string
|
||||||
|
if !attrD.NewComputed {
|
||||||
|
resultVal = attrD.New
|
||||||
|
if attrD.NewExtra != nil {
|
||||||
|
result.ValueProcessed = resultVal
|
||||||
|
if err := mapstructure.WeakDecode(attrD.NewExtra, &resultVal); err != nil {
|
||||||
|
return FieldReadResult{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Computed = attrD.NewComputed
|
||||||
|
result.Exists = true
|
||||||
|
result.Value, err = stringToPrimitive(resultVal, false, schema)
|
||||||
|
if err != nil {
|
||||||
|
return FieldReadResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DiffFieldReader) readSet(
|
||||||
|
address []string, schema *Schema) (FieldReadResult, error) {
|
||||||
|
// Create the set that will be our result
|
||||||
|
set := &Set{F: schema.Set}
|
||||||
|
|
||||||
|
// Go through the map and find all the set items
|
||||||
|
prefix := strings.Join(address, ".") + "."
|
||||||
|
for k, _ := range r.Diff.Attributes {
|
||||||
|
if !strings.HasPrefix(k, prefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(k, prefix+"#") {
|
||||||
|
// Ignore the count field
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the key, since it might be a sub-object like "idx.field"
|
||||||
|
parts := strings.Split(k[len(prefix):], ".")
|
||||||
|
idx := parts[0]
|
||||||
|
|
||||||
|
raw, err := r.ReadField(append(address, idx))
|
||||||
|
if err != nil {
|
||||||
|
return FieldReadResult{}, err
|
||||||
|
}
|
||||||
|
if !raw.Exists {
|
||||||
|
// This shouldn't happen because we just verified it does exist
|
||||||
|
panic("missing field in set: " + k + "." + idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
set.Add(raw.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return FieldReadResult{
|
||||||
|
Value: set,
|
||||||
|
Exists: set.Len() > 0,
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,286 @@
|
||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDiffFieldReader_impl(t *testing.T) {
|
||||||
|
var _ FieldReader = new(DiffFieldReader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiffFieldReader_extra(t *testing.T) {
|
||||||
|
schema := map[string]*Schema{
|
||||||
|
"stringComputed": &Schema{Type: TypeString},
|
||||||
|
|
||||||
|
"listMap": &Schema{
|
||||||
|
Type: TypeList,
|
||||||
|
Elem: &Schema{
|
||||||
|
Type: TypeMap,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"mapRemove": &Schema{Type: TypeMap},
|
||||||
|
|
||||||
|
"setChange": &Schema{
|
||||||
|
Type: TypeSet,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &Resource{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"index": &Schema{
|
||||||
|
Type: TypeInt,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"value": &Schema{
|
||||||
|
Type: TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Set: func(a interface{}) int {
|
||||||
|
m := a.(map[string]interface{})
|
||||||
|
return m["index"].(int)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &DiffFieldReader{
|
||||||
|
Schema: schema,
|
||||||
|
Diff: &terraform.InstanceDiff{
|
||||||
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||||
|
"stringComputed": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "foo",
|
||||||
|
New: "bar",
|
||||||
|
NewComputed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"listMap.0.bar": &terraform.ResourceAttrDiff{
|
||||||
|
NewRemoved: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"mapRemove.bar": &terraform.ResourceAttrDiff{
|
||||||
|
NewRemoved: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"setChange.10.value": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "50",
|
||||||
|
New: "80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Source: &MapFieldReader{
|
||||||
|
Schema: schema,
|
||||||
|
Map: BasicMapReader(map[string]string{
|
||||||
|
"listMap.#": "2",
|
||||||
|
"listMap.0.foo": "bar",
|
||||||
|
"listMap.0.bar": "baz",
|
||||||
|
"listMap.1.baz": "baz",
|
||||||
|
|
||||||
|
"mapRemove.foo": "bar",
|
||||||
|
"mapRemove.bar": "bar",
|
||||||
|
|
||||||
|
"setChange.#": "1",
|
||||||
|
"setChange.10.index": "10",
|
||||||
|
"setChange.10.value": "50",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := map[string]struct {
|
||||||
|
Addr []string
|
||||||
|
Result FieldReadResult
|
||||||
|
Err bool
|
||||||
|
}{
|
||||||
|
"stringComputed": {
|
||||||
|
[]string{"stringComputed"},
|
||||||
|
FieldReadResult{
|
||||||
|
Value: "",
|
||||||
|
Exists: true,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
"listMapRemoval": {
|
||||||
|
[]string{"listMap"},
|
||||||
|
FieldReadResult{
|
||||||
|
Value: []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"baz": "baz",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Exists: true,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
"mapRemove": {
|
||||||
|
[]string{"mapRemove"},
|
||||||
|
FieldReadResult{
|
||||||
|
Value: map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
Exists: true,
|
||||||
|
Computed: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
"setChange": {
|
||||||
|
[]string{"setChange"},
|
||||||
|
FieldReadResult{
|
||||||
|
Value: []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"index": 10,
|
||||||
|
"value": "80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Exists: true,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
out, err := r.ReadField(tc.Addr)
|
||||||
|
if (err != nil) != tc.Err {
|
||||||
|
t.Fatalf("%s: err: %s", name, err)
|
||||||
|
}
|
||||||
|
if s, ok := out.Value.(*Set); ok {
|
||||||
|
// If it is a set, convert to a list so its more easily checked.
|
||||||
|
out.Value = s.List()
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tc.Result, out) {
|
||||||
|
t.Fatalf("%s: bad: %#v", name, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiffFieldReader(t *testing.T) {
|
||||||
|
testFieldReader(t, func(s map[string]*Schema) FieldReader {
|
||||||
|
return &DiffFieldReader{
|
||||||
|
Schema: s,
|
||||||
|
Diff: &terraform.InstanceDiff{
|
||||||
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||||
|
"bool": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "true",
|
||||||
|
},
|
||||||
|
|
||||||
|
"int": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "42",
|
||||||
|
},
|
||||||
|
|
||||||
|
"string": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "string",
|
||||||
|
},
|
||||||
|
|
||||||
|
"stringComputed": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "foo",
|
||||||
|
New: "bar",
|
||||||
|
NewComputed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"list.#": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "0",
|
||||||
|
New: "2",
|
||||||
|
},
|
||||||
|
|
||||||
|
"list.0": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "foo",
|
||||||
|
},
|
||||||
|
|
||||||
|
"list.1": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "bar",
|
||||||
|
},
|
||||||
|
|
||||||
|
"listInt.#": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "0",
|
||||||
|
New: "2",
|
||||||
|
},
|
||||||
|
|
||||||
|
"listInt.0": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "21",
|
||||||
|
},
|
||||||
|
|
||||||
|
"listInt.1": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "42",
|
||||||
|
},
|
||||||
|
|
||||||
|
"map.foo": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "bar",
|
||||||
|
},
|
||||||
|
|
||||||
|
"map.bar": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "baz",
|
||||||
|
},
|
||||||
|
|
||||||
|
"set.#": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "0",
|
||||||
|
New: "2",
|
||||||
|
},
|
||||||
|
|
||||||
|
"set.10": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "10",
|
||||||
|
},
|
||||||
|
|
||||||
|
"set.50": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "50",
|
||||||
|
},
|
||||||
|
|
||||||
|
"setDeep.#": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "0",
|
||||||
|
New: "2",
|
||||||
|
},
|
||||||
|
|
||||||
|
"setDeep.10.index": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "10",
|
||||||
|
},
|
||||||
|
|
||||||
|
"setDeep.10.value": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "foo",
|
||||||
|
},
|
||||||
|
|
||||||
|
"setDeep.50.index": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "50",
|
||||||
|
},
|
||||||
|
|
||||||
|
"setDeep.50.value": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Source: &MapFieldReader{
|
||||||
|
Schema: s,
|
||||||
|
Map: BasicMapReader(map[string]string{
|
||||||
|
"listMap.#": "2",
|
||||||
|
"listMap.0.foo": "bar",
|
||||||
|
"listMap.0.bar": "baz",
|
||||||
|
"listMap.1.baz": "baz",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,227 @@
|
||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MapFieldReader reads fields out of an untyped map[string]string to
|
||||||
|
// the best of its ability.
|
||||||
|
type MapFieldReader struct {
|
||||||
|
Map MapReader
|
||||||
|
Schema map[string]*Schema
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MapFieldReader) ReadField(address []string) (FieldReadResult, error) {
|
||||||
|
k := strings.Join(address, ".")
|
||||||
|
schemaList := addrToSchema(address, r.Schema)
|
||||||
|
if len(schemaList) == 0 {
|
||||||
|
return FieldReadResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
schema := schemaList[len(schemaList)-1]
|
||||||
|
switch schema.Type {
|
||||||
|
case TypeBool:
|
||||||
|
fallthrough
|
||||||
|
case TypeInt:
|
||||||
|
fallthrough
|
||||||
|
case TypeString:
|
||||||
|
return r.readPrimitive(address, schema)
|
||||||
|
case TypeList:
|
||||||
|
return readListField(r, address, schema)
|
||||||
|
case TypeMap:
|
||||||
|
return r.readMap(k)
|
||||||
|
case TypeSet:
|
||||||
|
return r.readSet(address, schema)
|
||||||
|
case typeObject:
|
||||||
|
return readObjectField(r, address, schema.Elem.(map[string]*Schema))
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("Unknown type: %#v", schema.Type))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MapFieldReader) readMap(k string) (FieldReadResult, error) {
|
||||||
|
result := make(map[string]interface{})
|
||||||
|
resultSet := false
|
||||||
|
|
||||||
|
// If the name of the map field is directly in the map with an
|
||||||
|
// empty string, it means that the map is being deleted, so mark
|
||||||
|
// that is is set.
|
||||||
|
if v, ok := r.Map.Access(k); ok && v == "" {
|
||||||
|
resultSet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := k + "."
|
||||||
|
r.Map.Range(func(k, v string) bool {
|
||||||
|
if strings.HasPrefix(k, prefix) {
|
||||||
|
result[k[len(prefix):]] = v
|
||||||
|
resultSet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
var resultVal interface{}
|
||||||
|
if resultSet {
|
||||||
|
resultVal = result
|
||||||
|
}
|
||||||
|
|
||||||
|
return FieldReadResult{
|
||||||
|
Value: resultVal,
|
||||||
|
Exists: resultSet,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MapFieldReader) readPrimitive(
|
||||||
|
address []string, schema *Schema) (FieldReadResult, error) {
|
||||||
|
k := strings.Join(address, ".")
|
||||||
|
result, ok := r.Map.Access(k)
|
||||||
|
if !ok {
|
||||||
|
return FieldReadResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
returnVal, err := stringToPrimitive(result, false, schema)
|
||||||
|
if err != nil {
|
||||||
|
return FieldReadResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return FieldReadResult{
|
||||||
|
Value: returnVal,
|
||||||
|
Exists: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MapFieldReader) readSet(
|
||||||
|
address []string, schema *Schema) (FieldReadResult, error) {
|
||||||
|
// Get the number of elements in the list
|
||||||
|
countRaw, err := r.readPrimitive(
|
||||||
|
append(address, "#"), &Schema{Type: TypeInt})
|
||||||
|
if err != nil {
|
||||||
|
return FieldReadResult{}, err
|
||||||
|
}
|
||||||
|
if !countRaw.Exists {
|
||||||
|
// No count, means we have no list
|
||||||
|
countRaw.Value = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the set that will be our result
|
||||||
|
set := &Set{F: schema.Set}
|
||||||
|
|
||||||
|
// If we have an empty list, then return an empty list
|
||||||
|
if countRaw.Computed || countRaw.Value.(int) == 0 {
|
||||||
|
return FieldReadResult{
|
||||||
|
Value: set,
|
||||||
|
Exists: countRaw.Exists,
|
||||||
|
Computed: countRaw.Computed,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through the map and find all the set items
|
||||||
|
prefix := strings.Join(address, ".") + "."
|
||||||
|
countExpected := countRaw.Value.(int)
|
||||||
|
countActual := make(map[string]struct{})
|
||||||
|
completed := r.Map.Range(func(k, _ string) bool {
|
||||||
|
if !strings.HasPrefix(k, prefix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(k, prefix+"#") {
|
||||||
|
// Ignore the count field
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the key, since it might be a sub-object like "idx.field"
|
||||||
|
parts := strings.Split(k[len(prefix):], ".")
|
||||||
|
idx := parts[0]
|
||||||
|
|
||||||
|
var raw FieldReadResult
|
||||||
|
raw, err = r.ReadField(append(address, idx))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !raw.Exists {
|
||||||
|
// This shouldn't happen because we just verified it does exist
|
||||||
|
panic("missing field in set: " + k + "." + idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
set.Add(raw.Value)
|
||||||
|
|
||||||
|
// Due to the way multimap readers work, if we've seen the number
|
||||||
|
// of fields we expect, then exit so that we don't read later values.
|
||||||
|
// For example: the "set" map might have "ports.#", "ports.0", and
|
||||||
|
// "ports.1", but the "state" map might have those plus "ports.2".
|
||||||
|
// We don't want "ports.2"
|
||||||
|
countActual[idx] = struct{}{}
|
||||||
|
if len(countActual) >= countExpected {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
if !completed && err != nil {
|
||||||
|
return FieldReadResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return FieldReadResult{
|
||||||
|
Value: set,
|
||||||
|
Exists: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapReader is an interface that is given to MapFieldReader for accessing
|
||||||
|
// a "map". This can be used to have alternate implementations. For a basic
|
||||||
|
// map[string]string, use BasicMapReader.
|
||||||
|
type MapReader interface {
|
||||||
|
Access(string) (string, bool)
|
||||||
|
Range(func(string, string) bool) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasicMapReader implements MapReader for a single map.
|
||||||
|
type BasicMapReader map[string]string
|
||||||
|
|
||||||
|
func (r BasicMapReader) Access(k string) (string, bool) {
|
||||||
|
v, ok := r[k]
|
||||||
|
return v, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r BasicMapReader) Range(f func(string, string) bool) bool {
|
||||||
|
for k, v := range r {
|
||||||
|
if cont := f(k, v); !cont {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiMapReader reads over multiple maps, preferring keys that are
|
||||||
|
// founder earlier (lower number index) vs. later (higher number index)
|
||||||
|
type MultiMapReader []map[string]string
|
||||||
|
|
||||||
|
func (r MultiMapReader) Access(k string) (string, bool) {
|
||||||
|
for _, m := range r {
|
||||||
|
if v, ok := m[k]; ok {
|
||||||
|
return v, ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r MultiMapReader) Range(f func(string, string) bool) bool {
|
||||||
|
done := make(map[string]struct{})
|
||||||
|
for _, m := range r {
|
||||||
|
for k, v := range m {
|
||||||
|
if _, ok := done[k]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if cont := f(k, v); !cont {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
done[k] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMapFieldReader_impl(t *testing.T) {
|
||||||
|
var _ FieldReader = new(MapFieldReader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapFieldReader(t *testing.T) {
|
||||||
|
testFieldReader(t, func(s map[string]*Schema) FieldReader {
|
||||||
|
return &MapFieldReader{
|
||||||
|
Schema: s,
|
||||||
|
|
||||||
|
Map: BasicMapReader(map[string]string{
|
||||||
|
"bool": "true",
|
||||||
|
"int": "42",
|
||||||
|
"string": "string",
|
||||||
|
|
||||||
|
"list.#": "2",
|
||||||
|
"list.0": "foo",
|
||||||
|
"list.1": "bar",
|
||||||
|
|
||||||
|
"listInt.#": "2",
|
||||||
|
"listInt.0": "21",
|
||||||
|
"listInt.1": "42",
|
||||||
|
|
||||||
|
"map.foo": "bar",
|
||||||
|
"map.bar": "baz",
|
||||||
|
|
||||||
|
"set.#": "2",
|
||||||
|
"set.10": "10",
|
||||||
|
"set.50": "50",
|
||||||
|
|
||||||
|
"setDeep.#": "2",
|
||||||
|
"setDeep.10.index": "10",
|
||||||
|
"setDeep.10.value": "foo",
|
||||||
|
"setDeep.50.index": "50",
|
||||||
|
"setDeep.50.value": "bar",
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapFieldReader_extra(t *testing.T) {
|
||||||
|
r := &MapFieldReader{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"mapDel": &Schema{Type: TypeMap},
|
||||||
|
},
|
||||||
|
|
||||||
|
Map: BasicMapReader(map[string]string{
|
||||||
|
"mapDel": "",
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := map[string]struct {
|
||||||
|
Addr []string
|
||||||
|
Out interface{}
|
||||||
|
OutOk bool
|
||||||
|
OutComputed bool
|
||||||
|
OutErr bool
|
||||||
|
}{
|
||||||
|
"mapDel": {
|
||||||
|
[]string{"mapDel"},
|
||||||
|
map[string]interface{}{},
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
out, err := r.ReadField(tc.Addr)
|
||||||
|
if (err != nil) != tc.OutErr {
|
||||||
|
t.Fatalf("%s: err: %s", name, err)
|
||||||
|
}
|
||||||
|
if out.Computed != tc.OutComputed {
|
||||||
|
t.Fatalf("%s: err: %#v", name, out.Computed)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, ok := out.Value.(*Set); ok {
|
||||||
|
// If it is a set, convert to a list so its more easily checked.
|
||||||
|
out.Value = s.List()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(out.Value, tc.Out) {
|
||||||
|
t.Fatalf("%s: out: %#v", name, out.Value)
|
||||||
|
}
|
||||||
|
if out.Exists != tc.OutOk {
|
||||||
|
t.Fatalf("%s: outOk: %#v", name, out.Exists)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MultiLevelFieldReader reads from other field readers,
|
||||||
|
// merging their results along the way in a specific order. You can specify
|
||||||
|
// "levels" and name them in order to read only an exact level or up to
|
||||||
|
// a specific level.
|
||||||
|
//
|
||||||
|
// This is useful for saying things such as "read the field from the state
|
||||||
|
// and config and merge them" or "read the latest value of the field".
|
||||||
|
type MultiLevelFieldReader struct {
|
||||||
|
Readers map[string]FieldReader
|
||||||
|
Levels []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MultiLevelFieldReader) ReadField(address []string) (FieldReadResult, error) {
|
||||||
|
return r.ReadFieldMerge(address, r.Levels[len(r.Levels)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MultiLevelFieldReader) ReadFieldExact(
|
||||||
|
address []string, level string) (FieldReadResult, error) {
|
||||||
|
reader, ok := r.Readers[level]
|
||||||
|
if !ok {
|
||||||
|
return FieldReadResult{}, fmt.Errorf(
|
||||||
|
"Unknown reader level: %s", level)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := reader.ReadField(address)
|
||||||
|
if err != nil {
|
||||||
|
return FieldReadResult{}, fmt.Errorf(
|
||||||
|
"Error reading level %s: %s", level, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MultiLevelFieldReader) ReadFieldMerge(
|
||||||
|
address []string, level string) (FieldReadResult, error) {
|
||||||
|
var result FieldReadResult
|
||||||
|
for _, l := range r.Levels {
|
||||||
|
if r, ok := r.Readers[l]; ok {
|
||||||
|
out, err := r.ReadField(address)
|
||||||
|
if err != nil {
|
||||||
|
return FieldReadResult{}, fmt.Errorf(
|
||||||
|
"Error reading level %s: %s", l, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: computed
|
||||||
|
if out.Exists {
|
||||||
|
result = out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if l == level {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
|
@ -0,0 +1,270 @@
|
||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMultiLevelFieldReaderReadFieldExact(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
Addr []string
|
||||||
|
Readers []FieldReader
|
||||||
|
Level string
|
||||||
|
Result FieldReadResult
|
||||||
|
}{
|
||||||
|
"specific": {
|
||||||
|
Addr: []string{"foo"},
|
||||||
|
|
||||||
|
Readers: []FieldReader{
|
||||||
|
&MapFieldReader{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"foo": &Schema{Type: TypeString},
|
||||||
|
},
|
||||||
|
Map: BasicMapReader(map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
&MapFieldReader{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"foo": &Schema{Type: TypeString},
|
||||||
|
},
|
||||||
|
Map: BasicMapReader(map[string]string{
|
||||||
|
"foo": "baz",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
&MapFieldReader{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"foo": &Schema{Type: TypeString},
|
||||||
|
},
|
||||||
|
Map: BasicMapReader(map[string]string{}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Level: "1",
|
||||||
|
Result: FieldReadResult{
|
||||||
|
Value: "baz",
|
||||||
|
Exists: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
readers := make(map[string]FieldReader)
|
||||||
|
levels := make([]string, len(tc.Readers))
|
||||||
|
for i, r := range tc.Readers {
|
||||||
|
is := strconv.FormatInt(int64(i), 10)
|
||||||
|
readers[is] = r
|
||||||
|
levels[i] = is
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &MultiLevelFieldReader{
|
||||||
|
Readers: readers,
|
||||||
|
Levels: levels,
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := r.ReadFieldExact(tc.Addr, tc.Level)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: err: %s", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(tc.Result, out) {
|
||||||
|
t.Fatalf("%s: bad: %#v", name, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiLevelFieldReaderReadFieldMerge(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
Addr []string
|
||||||
|
Readers []FieldReader
|
||||||
|
Result FieldReadResult
|
||||||
|
}{
|
||||||
|
"stringInDiff": {
|
||||||
|
Addr: []string{"availability_zone"},
|
||||||
|
|
||||||
|
Readers: []FieldReader{
|
||||||
|
&DiffFieldReader{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"availability_zone": &Schema{Type: TypeString},
|
||||||
|
},
|
||||||
|
|
||||||
|
Source: &MapFieldReader{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"availability_zone": &Schema{Type: TypeString},
|
||||||
|
},
|
||||||
|
Map: BasicMapReader(map[string]string{
|
||||||
|
"availability_zone": "foo",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
Diff: &terraform.InstanceDiff{
|
||||||
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||||
|
"availability_zone": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "foo",
|
||||||
|
New: "bar",
|
||||||
|
RequiresNew: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Result: FieldReadResult{
|
||||||
|
Value: "bar",
|
||||||
|
Exists: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"lastLevelComputed": {
|
||||||
|
Addr: []string{"availability_zone"},
|
||||||
|
|
||||||
|
Readers: []FieldReader{
|
||||||
|
&MapFieldReader{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"availability_zone": &Schema{Type: TypeString},
|
||||||
|
},
|
||||||
|
|
||||||
|
Map: BasicMapReader(map[string]string{
|
||||||
|
"availability_zone": "foo",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
&DiffFieldReader{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"availability_zone": &Schema{Type: TypeString},
|
||||||
|
},
|
||||||
|
|
||||||
|
Source: &MapFieldReader{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"availability_zone": &Schema{Type: TypeString},
|
||||||
|
},
|
||||||
|
|
||||||
|
Map: BasicMapReader(map[string]string{
|
||||||
|
"availability_zone": "foo",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
Diff: &terraform.InstanceDiff{
|
||||||
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||||
|
"availability_zone": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "foo",
|
||||||
|
New: "bar",
|
||||||
|
NewComputed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Result: FieldReadResult{
|
||||||
|
Value: "",
|
||||||
|
Exists: true,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"list of maps with removal in diff": {
|
||||||
|
Addr: []string{"config_vars"},
|
||||||
|
|
||||||
|
Readers: []FieldReader{
|
||||||
|
&DiffFieldReader{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"config_vars": &Schema{
|
||||||
|
Type: TypeList,
|
||||||
|
Elem: &Schema{Type: TypeMap},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Source: &MapFieldReader{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"config_vars": &Schema{
|
||||||
|
Type: TypeList,
|
||||||
|
Elem: &Schema{Type: TypeMap},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Map: BasicMapReader(map[string]string{
|
||||||
|
"config_vars.#": "2",
|
||||||
|
"config_vars.0.foo": "bar",
|
||||||
|
"config_vars.0.bar": "bar",
|
||||||
|
"config_vars.1.bar": "baz",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
Diff: &terraform.InstanceDiff{
|
||||||
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||||
|
"config_vars.0.bar": &terraform.ResourceAttrDiff{
|
||||||
|
NewRemoved: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Result: FieldReadResult{
|
||||||
|
Value: []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"bar": "baz",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Exists: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"first level only": {
|
||||||
|
Addr: []string{"foo"},
|
||||||
|
|
||||||
|
Readers: []FieldReader{
|
||||||
|
&MapFieldReader{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"foo": &Schema{Type: TypeString},
|
||||||
|
},
|
||||||
|
Map: BasicMapReader(map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
&MapFieldReader{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"foo": &Schema{Type: TypeString},
|
||||||
|
},
|
||||||
|
Map: BasicMapReader(map[string]string{}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Result: FieldReadResult{
|
||||||
|
Value: "bar",
|
||||||
|
Exists: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
readers := make(map[string]FieldReader)
|
||||||
|
levels := make([]string, len(tc.Readers))
|
||||||
|
for i, r := range tc.Readers {
|
||||||
|
is := strconv.FormatInt(int64(i), 10)
|
||||||
|
readers[is] = r
|
||||||
|
levels[i] = is
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &MultiLevelFieldReader{
|
||||||
|
Readers: readers,
|
||||||
|
Levels: levels,
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := r.ReadFieldMerge(tc.Addr, levels[len(levels)-1])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: err: %s", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(tc.Result, out) {
|
||||||
|
t.Fatalf("%s: bad: %#v", name, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,390 @@
|
||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddrToSchema(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
Addr []string
|
||||||
|
Schema map[string]*Schema
|
||||||
|
Result []ValueType
|
||||||
|
}{
|
||||||
|
"full object": {
|
||||||
|
[]string{},
|
||||||
|
map[string]*Schema{
|
||||||
|
"list": &Schema{
|
||||||
|
Type: TypeList,
|
||||||
|
Elem: &Schema{Type: TypeInt},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]ValueType{typeObject},
|
||||||
|
},
|
||||||
|
|
||||||
|
"list": {
|
||||||
|
[]string{"list"},
|
||||||
|
map[string]*Schema{
|
||||||
|
"list": &Schema{
|
||||||
|
Type: TypeList,
|
||||||
|
Elem: &Schema{Type: TypeInt},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]ValueType{TypeList},
|
||||||
|
},
|
||||||
|
|
||||||
|
"list.#": {
|
||||||
|
[]string{"list", "#"},
|
||||||
|
map[string]*Schema{
|
||||||
|
"list": &Schema{
|
||||||
|
Type: TypeList,
|
||||||
|
Elem: &Schema{Type: TypeInt},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]ValueType{TypeList, TypeInt},
|
||||||
|
},
|
||||||
|
|
||||||
|
"list.0": {
|
||||||
|
[]string{"list", "0"},
|
||||||
|
map[string]*Schema{
|
||||||
|
"list": &Schema{
|
||||||
|
Type: TypeList,
|
||||||
|
Elem: &Schema{Type: TypeInt},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]ValueType{TypeList, TypeInt},
|
||||||
|
},
|
||||||
|
|
||||||
|
"list.0 with resource": {
|
||||||
|
[]string{"list", "0"},
|
||||||
|
map[string]*Schema{
|
||||||
|
"list": &Schema{
|
||||||
|
Type: TypeList,
|
||||||
|
Elem: &Resource{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"field": &Schema{Type: TypeString},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]ValueType{TypeList, typeObject},
|
||||||
|
},
|
||||||
|
|
||||||
|
"list.0.field": {
|
||||||
|
[]string{"list", "0", "field"},
|
||||||
|
map[string]*Schema{
|
||||||
|
"list": &Schema{
|
||||||
|
Type: TypeList,
|
||||||
|
Elem: &Resource{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"field": &Schema{Type: TypeString},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]ValueType{TypeList, typeObject, TypeString},
|
||||||
|
},
|
||||||
|
|
||||||
|
"set": {
|
||||||
|
[]string{"set"},
|
||||||
|
map[string]*Schema{
|
||||||
|
"set": &Schema{
|
||||||
|
Type: TypeSet,
|
||||||
|
Elem: &Schema{Type: TypeInt},
|
||||||
|
Set: func(a interface{}) int {
|
||||||
|
return a.(int)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]ValueType{TypeSet},
|
||||||
|
},
|
||||||
|
|
||||||
|
"set.#": {
|
||||||
|
[]string{"set", "#"},
|
||||||
|
map[string]*Schema{
|
||||||
|
"set": &Schema{
|
||||||
|
Type: TypeSet,
|
||||||
|
Elem: &Schema{Type: TypeInt},
|
||||||
|
Set: func(a interface{}) int {
|
||||||
|
return a.(int)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]ValueType{TypeSet, TypeInt},
|
||||||
|
},
|
||||||
|
|
||||||
|
"set.0": {
|
||||||
|
[]string{"set", "0"},
|
||||||
|
map[string]*Schema{
|
||||||
|
"set": &Schema{
|
||||||
|
Type: TypeSet,
|
||||||
|
Elem: &Schema{Type: TypeInt},
|
||||||
|
Set: func(a interface{}) int {
|
||||||
|
return a.(int)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]ValueType{TypeSet, TypeInt},
|
||||||
|
},
|
||||||
|
|
||||||
|
"set.0 with resource": {
|
||||||
|
[]string{"set", "0"},
|
||||||
|
map[string]*Schema{
|
||||||
|
"set": &Schema{
|
||||||
|
Type: TypeSet,
|
||||||
|
Elem: &Resource{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"field": &Schema{Type: TypeString},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]ValueType{TypeSet, typeObject},
|
||||||
|
},
|
||||||
|
|
||||||
|
"mapElem": {
|
||||||
|
[]string{"map", "foo"},
|
||||||
|
map[string]*Schema{
|
||||||
|
"map": &Schema{Type: TypeMap},
|
||||||
|
},
|
||||||
|
[]ValueType{TypeMap, TypeString},
|
||||||
|
},
|
||||||
|
|
||||||
|
"setDeep": {
|
||||||
|
[]string{"set", "50", "index"},
|
||||||
|
map[string]*Schema{
|
||||||
|
"set": &Schema{
|
||||||
|
Type: TypeSet,
|
||||||
|
Elem: &Resource{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"index": &Schema{Type: TypeInt},
|
||||||
|
"value": &Schema{Type: TypeString},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Set: func(a interface{}) int {
|
||||||
|
return a.(map[string]interface{})["index"].(int)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]ValueType{TypeSet, typeObject, TypeInt},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
result := addrToSchema(tc.Addr, tc.Schema)
|
||||||
|
types := make([]ValueType, len(result))
|
||||||
|
for i, v := range result {
|
||||||
|
types[i] = v.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(types, tc.Result) {
|
||||||
|
t.Fatalf("%s: %#v", name, types)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// testFieldReader is a helper that should be used to verify that
|
||||||
|
// a FieldReader behaves properly in all the common cases.
|
||||||
|
func testFieldReader(t *testing.T, f func(map[string]*Schema) FieldReader) {
|
||||||
|
schema := map[string]*Schema{
|
||||||
|
// Primitives
|
||||||
|
"bool": &Schema{Type: TypeBool},
|
||||||
|
"int": &Schema{Type: TypeInt},
|
||||||
|
"string": &Schema{Type: TypeString},
|
||||||
|
|
||||||
|
// Lists
|
||||||
|
"list": &Schema{
|
||||||
|
Type: TypeList,
|
||||||
|
Elem: &Schema{Type: TypeString},
|
||||||
|
},
|
||||||
|
"listInt": &Schema{
|
||||||
|
Type: TypeList,
|
||||||
|
Elem: &Schema{Type: TypeInt},
|
||||||
|
},
|
||||||
|
"listMap": &Schema{
|
||||||
|
Type: TypeList,
|
||||||
|
Elem: &Schema{
|
||||||
|
Type: TypeMap,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Maps
|
||||||
|
"map": &Schema{Type: TypeMap},
|
||||||
|
|
||||||
|
// Sets
|
||||||
|
"set": &Schema{
|
||||||
|
Type: TypeSet,
|
||||||
|
Elem: &Schema{Type: TypeInt},
|
||||||
|
Set: func(a interface{}) int {
|
||||||
|
return a.(int)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"setDeep": &Schema{
|
||||||
|
Type: TypeSet,
|
||||||
|
Elem: &Resource{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"index": &Schema{Type: TypeInt},
|
||||||
|
"value": &Schema{Type: TypeString},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Set: func(a interface{}) int {
|
||||||
|
return a.(map[string]interface{})["index"].(int)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"setEmpty": &Schema{
|
||||||
|
Type: TypeSet,
|
||||||
|
Elem: &Schema{Type: TypeInt},
|
||||||
|
Set: func(a interface{}) int {
|
||||||
|
return a.(int)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := map[string]struct {
|
||||||
|
Addr []string
|
||||||
|
Result FieldReadResult
|
||||||
|
Err bool
|
||||||
|
}{
|
||||||
|
"noexist": {
|
||||||
|
[]string{"boolNOPE"},
|
||||||
|
FieldReadResult{
|
||||||
|
Value: nil,
|
||||||
|
Exists: false,
|
||||||
|
Computed: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
"bool": {
|
||||||
|
[]string{"bool"},
|
||||||
|
FieldReadResult{
|
||||||
|
Value: true,
|
||||||
|
Exists: true,
|
||||||
|
Computed: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
"int": {
|
||||||
|
[]string{"int"},
|
||||||
|
FieldReadResult{
|
||||||
|
Value: 42,
|
||||||
|
Exists: true,
|
||||||
|
Computed: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
"string": {
|
||||||
|
[]string{"string"},
|
||||||
|
FieldReadResult{
|
||||||
|
Value: "string",
|
||||||
|
Exists: true,
|
||||||
|
Computed: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
"list": {
|
||||||
|
[]string{"list"},
|
||||||
|
FieldReadResult{
|
||||||
|
Value: []interface{}{
|
||||||
|
"foo",
|
||||||
|
"bar",
|
||||||
|
},
|
||||||
|
Exists: true,
|
||||||
|
Computed: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
"listInt": {
|
||||||
|
[]string{"listInt"},
|
||||||
|
FieldReadResult{
|
||||||
|
Value: []interface{}{
|
||||||
|
21,
|
||||||
|
42,
|
||||||
|
},
|
||||||
|
Exists: true,
|
||||||
|
Computed: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
"map": {
|
||||||
|
[]string{"map"},
|
||||||
|
FieldReadResult{
|
||||||
|
Value: map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
"bar": "baz",
|
||||||
|
},
|
||||||
|
Exists: true,
|
||||||
|
Computed: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
"mapelem": {
|
||||||
|
[]string{"map", "foo"},
|
||||||
|
FieldReadResult{
|
||||||
|
Value: "bar",
|
||||||
|
Exists: true,
|
||||||
|
Computed: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
"set": {
|
||||||
|
[]string{"set"},
|
||||||
|
FieldReadResult{
|
||||||
|
Value: []interface{}{10, 50},
|
||||||
|
Exists: true,
|
||||||
|
Computed: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
"setDeep": {
|
||||||
|
[]string{"setDeep"},
|
||||||
|
FieldReadResult{
|
||||||
|
Value: []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"index": 10,
|
||||||
|
"value": "foo",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"index": 50,
|
||||||
|
"value": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Exists: true,
|
||||||
|
Computed: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
"setEmpty": {
|
||||||
|
[]string{"setEmpty"},
|
||||||
|
FieldReadResult{
|
||||||
|
Value: []interface{}{},
|
||||||
|
Exists: false,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
r := f(schema)
|
||||||
|
out, err := r.ReadField(tc.Addr)
|
||||||
|
if (err != nil) != tc.Err {
|
||||||
|
t.Fatalf("%s: err: %s", name, err)
|
||||||
|
}
|
||||||
|
if s, ok := out.Value.(*Set); ok {
|
||||||
|
// If it is a set, convert to a list so its more easily checked.
|
||||||
|
out.Value = s.List()
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tc.Result, out) {
|
||||||
|
t.Fatalf("%s: bad: %#v", name, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package schema
|
||||||
|
|
||||||
|
// FieldWriters are responsible for writing fields by address into
|
||||||
|
// a proper typed representation. ResourceData uses this to write new data
|
||||||
|
// into existing sources.
|
||||||
|
type FieldWriter interface {
|
||||||
|
WriteField([]string, interface{}) error
|
||||||
|
}
|
|
@ -0,0 +1,303 @@
|
||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MapFieldWriter writes data into a single map[string]string structure.
|
||||||
|
type MapFieldWriter struct {
|
||||||
|
Schema map[string]*Schema
|
||||||
|
|
||||||
|
lock sync.Mutex
|
||||||
|
result map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map returns the underlying map that is being written to.
|
||||||
|
func (w *MapFieldWriter) Map() map[string]string {
|
||||||
|
w.lock.Lock()
|
||||||
|
defer w.lock.Unlock()
|
||||||
|
if w.result == nil {
|
||||||
|
w.result = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *MapFieldWriter) WriteField(addr []string, value interface{}) error {
|
||||||
|
w.lock.Lock()
|
||||||
|
defer w.lock.Unlock()
|
||||||
|
if w.result == nil {
|
||||||
|
w.result = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
schemaList := addrToSchema(addr, w.Schema)
|
||||||
|
if len(schemaList) == 0 {
|
||||||
|
return fmt.Errorf("Invalid address to set: %#v", addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're setting anything other than a list root or set root,
|
||||||
|
// then disallow it.
|
||||||
|
for _, schema := range schemaList[:len(schemaList)-1] {
|
||||||
|
if schema.Type == TypeList {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"%s: can only set full list",
|
||||||
|
strings.Join(addr, "."))
|
||||||
|
}
|
||||||
|
|
||||||
|
if schema.Type == TypeMap {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"%s: can only set full map",
|
||||||
|
strings.Join(addr, "."))
|
||||||
|
}
|
||||||
|
|
||||||
|
if schema.Type == TypeSet {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"%s: can only set full set",
|
||||||
|
strings.Join(addr, "."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.set(addr, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *MapFieldWriter) set(addr []string, value interface{}) error {
|
||||||
|
schemaList := addrToSchema(addr, w.Schema)
|
||||||
|
if len(schemaList) == 0 {
|
||||||
|
return fmt.Errorf("Invalid address to set: %#v", addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
schema := schemaList[len(schemaList)-1]
|
||||||
|
switch schema.Type {
|
||||||
|
case TypeBool:
|
||||||
|
fallthrough
|
||||||
|
case TypeInt:
|
||||||
|
fallthrough
|
||||||
|
case TypeString:
|
||||||
|
return w.setPrimitive(addr, value, schema)
|
||||||
|
case TypeList:
|
||||||
|
return w.setList(addr, value, schema)
|
||||||
|
case TypeMap:
|
||||||
|
return w.setMap(addr, value, schema)
|
||||||
|
case TypeSet:
|
||||||
|
return w.setSet(addr, value, schema)
|
||||||
|
case typeObject:
|
||||||
|
return w.setObject(addr, value, schema)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("Unknown type: %#v", schema.Type))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *MapFieldWriter) setList(
|
||||||
|
addr []string,
|
||||||
|
v interface{},
|
||||||
|
schema *Schema) error {
|
||||||
|
k := strings.Join(addr, ".")
|
||||||
|
setElement := func(idx string, value interface{}) error {
|
||||||
|
addrCopy := make([]string, len(addr), len(addr)+1)
|
||||||
|
copy(addrCopy, addr)
|
||||||
|
return w.set(append(addrCopy, idx), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
var vs []interface{}
|
||||||
|
if err := mapstructure.Decode(v, &vs); err != nil {
|
||||||
|
return fmt.Errorf("%s: %s", k, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the entire list.
|
||||||
|
var err error
|
||||||
|
for i, elem := range vs {
|
||||||
|
is := strconv.FormatInt(int64(i), 10)
|
||||||
|
err = setElement(is, elem)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
for i, _ := range vs {
|
||||||
|
is := strconv.FormatInt(int64(i), 10)
|
||||||
|
setElement(is, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.result[k+".#"] = strconv.FormatInt(int64(len(vs)), 10)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *MapFieldWriter) setMap(
|
||||||
|
addr []string,
|
||||||
|
value interface{},
|
||||||
|
schema *Schema) error {
|
||||||
|
k := strings.Join(addr, ".")
|
||||||
|
v := reflect.ValueOf(value)
|
||||||
|
vs := make(map[string]interface{})
|
||||||
|
|
||||||
|
if value != nil {
|
||||||
|
if v.Kind() != reflect.Map {
|
||||||
|
return fmt.Errorf("%s: must be a map", k)
|
||||||
|
}
|
||||||
|
if v.Type().Key().Kind() != reflect.String {
|
||||||
|
return fmt.Errorf("%s: keys must strings", k)
|
||||||
|
}
|
||||||
|
for _, mk := range v.MapKeys() {
|
||||||
|
mv := v.MapIndex(mk)
|
||||||
|
vs[mk.String()] = mv.Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(vs) == 0 {
|
||||||
|
// The empty string here means the map is removed.
|
||||||
|
w.result[k] = ""
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the pure key since we're setting the full map value
|
||||||
|
delete(w.result, k)
|
||||||
|
|
||||||
|
// Set each subkey
|
||||||
|
addrCopy := make([]string, len(addr), len(addr)+1)
|
||||||
|
copy(addrCopy, addr)
|
||||||
|
for subKey, v := range vs {
|
||||||
|
if err := w.set(append(addrCopy, subKey), v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *MapFieldWriter) setObject(
|
||||||
|
addr []string,
|
||||||
|
value interface{},
|
||||||
|
schema *Schema) error {
|
||||||
|
// Set the entire object. First decode into a proper structure
|
||||||
|
var v map[string]interface{}
|
||||||
|
if err := mapstructure.Decode(value, &v); err != nil {
|
||||||
|
return fmt.Errorf("%s: %s", strings.Join(addr, "."), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make space for additional elements in the address
|
||||||
|
addrCopy := make([]string, len(addr), len(addr)+1)
|
||||||
|
copy(addrCopy, addr)
|
||||||
|
|
||||||
|
// Set each element in turn
|
||||||
|
var err error
|
||||||
|
for k1, v1 := range v {
|
||||||
|
if err = w.set(append(addrCopy, k1), v1); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
for k1, _ := range v {
|
||||||
|
w.set(append(addrCopy, k1), nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *MapFieldWriter) setPrimitive(
|
||||||
|
addr []string,
|
||||||
|
v interface{},
|
||||||
|
schema *Schema) error {
|
||||||
|
k := strings.Join(addr, ".")
|
||||||
|
|
||||||
|
if v == nil {
|
||||||
|
delete(w.result, k)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var set string
|
||||||
|
switch schema.Type {
|
||||||
|
case TypeBool:
|
||||||
|
var b bool
|
||||||
|
if err := mapstructure.Decode(v, &b); err != nil {
|
||||||
|
return fmt.Errorf("%s: %s", k, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
set = strconv.FormatBool(b)
|
||||||
|
case TypeString:
|
||||||
|
if err := mapstructure.Decode(v, &set); err != nil {
|
||||||
|
return fmt.Errorf("%s: %s", k, err)
|
||||||
|
}
|
||||||
|
case TypeInt:
|
||||||
|
var n int
|
||||||
|
if err := mapstructure.Decode(v, &n); err != nil {
|
||||||
|
return fmt.Errorf("%s: %s", k, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
set = strconv.FormatInt(int64(n), 10)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Unknown type: %#v", schema.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.result[k] = set
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *MapFieldWriter) setSet(
|
||||||
|
addr []string,
|
||||||
|
value interface{},
|
||||||
|
schema *Schema) error {
|
||||||
|
addrCopy := make([]string, len(addr), len(addr)+1)
|
||||||
|
copy(addrCopy, addr)
|
||||||
|
|
||||||
|
// If it is a slice, then we have to turn it into a *Set so that
|
||||||
|
// we get the proper order back based on the hash code.
|
||||||
|
if v := reflect.ValueOf(value); v.Kind() == reflect.Slice {
|
||||||
|
// Build a temp *ResourceData to use for the conversion
|
||||||
|
tempSchema := *schema
|
||||||
|
tempSchema.Type = TypeList
|
||||||
|
tempSchemaMap := map[string]*Schema{addr[0]: &tempSchema}
|
||||||
|
tempW := &MapFieldWriter{Schema: tempSchemaMap}
|
||||||
|
|
||||||
|
// Set the entire list, this lets us get sane values out of it
|
||||||
|
if err := tempW.WriteField(addr, value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the set by going over the list items in order and
|
||||||
|
// hashing them into the set. The reason we go over the list and
|
||||||
|
// not the `value` directly is because this forces all types
|
||||||
|
// to become []interface{} (generic) instead of []string, which
|
||||||
|
// most hash functions are expecting.
|
||||||
|
s := &Set{F: schema.Set}
|
||||||
|
tempR := &MapFieldReader{
|
||||||
|
Map: BasicMapReader(tempW.Map()),
|
||||||
|
Schema: tempSchemaMap,
|
||||||
|
}
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
is := strconv.FormatInt(int64(i), 10)
|
||||||
|
result, err := tempR.ReadField(append(addrCopy, is))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !result.Exists {
|
||||||
|
panic("set item just set doesn't exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Add(result.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
value = s
|
||||||
|
}
|
||||||
|
|
||||||
|
k := strings.Join(addr, ".")
|
||||||
|
for code, elem := range value.(*Set).m {
|
||||||
|
codeStr := strconv.FormatInt(int64(code), 10)
|
||||||
|
if err := w.set(append(addrCopy, codeStr), elem); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.result[k+".#"] = strconv.Itoa(value.(*Set).Len())
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,199 @@
|
||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMapFieldWriter_impl(t *testing.T) {
|
||||||
|
var _ FieldWriter = new(MapFieldWriter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapFieldWriter(t *testing.T) {
|
||||||
|
schema := map[string]*Schema{
|
||||||
|
"bool": &Schema{Type: TypeBool},
|
||||||
|
"int": &Schema{Type: TypeInt},
|
||||||
|
"string": &Schema{Type: TypeString},
|
||||||
|
"list": &Schema{
|
||||||
|
Type: TypeList,
|
||||||
|
Elem: &Schema{Type: TypeString},
|
||||||
|
},
|
||||||
|
"listInt": &Schema{
|
||||||
|
Type: TypeList,
|
||||||
|
Elem: &Schema{Type: TypeInt},
|
||||||
|
},
|
||||||
|
"map": &Schema{Type: TypeMap},
|
||||||
|
"set": &Schema{
|
||||||
|
Type: TypeSet,
|
||||||
|
Elem: &Schema{Type: TypeInt},
|
||||||
|
Set: func(a interface{}) int {
|
||||||
|
return a.(int)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"setDeep": &Schema{
|
||||||
|
Type: TypeSet,
|
||||||
|
Elem: &Resource{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"index": &Schema{Type: TypeInt},
|
||||||
|
"value": &Schema{Type: TypeString},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Set: func(a interface{}) int {
|
||||||
|
return a.(map[string]interface{})["index"].(int)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := map[string]struct {
|
||||||
|
Addr []string
|
||||||
|
Value interface{}
|
||||||
|
Err bool
|
||||||
|
Out map[string]string
|
||||||
|
}{
|
||||||
|
"noexist": {
|
||||||
|
[]string{"noexist"},
|
||||||
|
42,
|
||||||
|
true,
|
||||||
|
map[string]string{},
|
||||||
|
},
|
||||||
|
|
||||||
|
"bool": {
|
||||||
|
[]string{"bool"},
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
map[string]string{
|
||||||
|
"bool": "false",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"int": {
|
||||||
|
[]string{"int"},
|
||||||
|
42,
|
||||||
|
false,
|
||||||
|
map[string]string{
|
||||||
|
"int": "42",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"string": {
|
||||||
|
[]string{"string"},
|
||||||
|
"42",
|
||||||
|
false,
|
||||||
|
map[string]string{
|
||||||
|
"string": "42",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"list of strings": {
|
||||||
|
[]string{"list"},
|
||||||
|
[]interface{}{"foo", "bar"},
|
||||||
|
false,
|
||||||
|
map[string]string{
|
||||||
|
"list.#": "2",
|
||||||
|
"list.0": "foo",
|
||||||
|
"list.1": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"list element": {
|
||||||
|
[]string{"list", "0"},
|
||||||
|
"string",
|
||||||
|
true,
|
||||||
|
map[string]string{},
|
||||||
|
},
|
||||||
|
|
||||||
|
"map": {
|
||||||
|
[]string{"map"},
|
||||||
|
map[string]interface{}{"foo": "bar"},
|
||||||
|
false,
|
||||||
|
map[string]string{
|
||||||
|
"map.foo": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"map delete": {
|
||||||
|
[]string{"map"},
|
||||||
|
nil,
|
||||||
|
false,
|
||||||
|
map[string]string{
|
||||||
|
"map": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"map element": {
|
||||||
|
[]string{"map", "foo"},
|
||||||
|
"bar",
|
||||||
|
true,
|
||||||
|
map[string]string{},
|
||||||
|
},
|
||||||
|
|
||||||
|
"set": {
|
||||||
|
[]string{"set"},
|
||||||
|
[]interface{}{1, 2, 5},
|
||||||
|
false,
|
||||||
|
map[string]string{
|
||||||
|
"set.#": "3",
|
||||||
|
"set.1": "1",
|
||||||
|
"set.2": "2",
|
||||||
|
"set.5": "5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"set resource": {
|
||||||
|
[]string{"setDeep"},
|
||||||
|
[]interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"index": 10,
|
||||||
|
"value": "foo",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"index": 50,
|
||||||
|
"value": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
map[string]string{
|
||||||
|
"setDeep.#": "2",
|
||||||
|
"setDeep.10.index": "10",
|
||||||
|
"setDeep.10.value": "foo",
|
||||||
|
"setDeep.50.index": "50",
|
||||||
|
"setDeep.50.value": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"set element": {
|
||||||
|
[]string{"set", "5"},
|
||||||
|
5,
|
||||||
|
true,
|
||||||
|
map[string]string{},
|
||||||
|
},
|
||||||
|
|
||||||
|
"full object": {
|
||||||
|
nil,
|
||||||
|
map[string]interface{}{
|
||||||
|
"string": "foo",
|
||||||
|
"list": []interface{}{"foo", "bar"},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
map[string]string{
|
||||||
|
"string": "foo",
|
||||||
|
"list.#": "2",
|
||||||
|
"list.0": "foo",
|
||||||
|
"list.1": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
w := &MapFieldWriter{Schema: schema}
|
||||||
|
err := w.WriteField(tc.Addr, tc.Value)
|
||||||
|
if (err != nil) != tc.Err {
|
||||||
|
t.Fatalf("%s: err: %s", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := w.Map()
|
||||||
|
if !reflect.DeepEqual(actual, tc.Out) {
|
||||||
|
t.Fatalf("%s: bad: %#v", name, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -592,6 +592,29 @@ func TestResourceDataGet(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// #19 Empty Set
|
||||||
|
{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"ports": &Schema{
|
||||||
|
Type: TypeSet,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
Elem: &Schema{Type: TypeInt},
|
||||||
|
Set: func(a interface{}) int {
|
||||||
|
return a.(int)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
State: nil,
|
||||||
|
|
||||||
|
Diff: nil,
|
||||||
|
|
||||||
|
Key: "ports",
|
||||||
|
|
||||||
|
Value: []interface{}{},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tc := range cases {
|
for i, tc := range cases {
|
||||||
|
@ -606,7 +629,7 @@ func TestResourceDataGet(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(v, tc.Value) {
|
if !reflect.DeepEqual(v, tc.Value) {
|
||||||
t.Fatalf("Bad: %d\n\n%#v", i, v)
|
t.Fatalf("Bad: %d\n\n%#v\n\nExpected: %#v", i, v, tc.Value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -975,8 +998,12 @@ func TestResourceDataSet(t *testing.T) {
|
||||||
Err bool
|
Err bool
|
||||||
GetKey string
|
GetKey string
|
||||||
GetValue interface{}
|
GetValue interface{}
|
||||||
|
|
||||||
|
// GetPreProcess can be set to munge the return value before being
|
||||||
|
// compared to GetValue
|
||||||
|
GetPreProcess func(interface{}) interface{}
|
||||||
}{
|
}{
|
||||||
// Basic good
|
// #0: Basic good
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"availability_zone": &Schema{
|
"availability_zone": &Schema{
|
||||||
|
@ -998,7 +1025,7 @@ func TestResourceDataSet(t *testing.T) {
|
||||||
GetValue: "foo",
|
GetValue: "foo",
|
||||||
},
|
},
|
||||||
|
|
||||||
// Basic int
|
// #1: Basic int
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"port": &Schema{
|
"port": &Schema{
|
||||||
|
@ -1020,7 +1047,7 @@ func TestResourceDataSet(t *testing.T) {
|
||||||
GetValue: 80,
|
GetValue: 80,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Basic bool
|
// #2: Basic bool
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"vpc": &Schema{
|
"vpc": &Schema{
|
||||||
|
@ -1040,6 +1067,7 @@ func TestResourceDataSet(t *testing.T) {
|
||||||
GetValue: true,
|
GetValue: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// #3
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"vpc": &Schema{
|
"vpc": &Schema{
|
||||||
|
@ -1059,7 +1087,7 @@ func TestResourceDataSet(t *testing.T) {
|
||||||
GetValue: false,
|
GetValue: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Invalid type
|
// #4: Invalid type
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"availability_zone": &Schema{
|
"availability_zone": &Schema{
|
||||||
|
@ -1082,35 +1110,7 @@ func TestResourceDataSet(t *testing.T) {
|
||||||
GetValue: "",
|
GetValue: "",
|
||||||
},
|
},
|
||||||
|
|
||||||
// List of primitives, set element
|
// #5: List of primitives, set list
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
|
||||||
"ports": &Schema{
|
|
||||||
Type: TypeList,
|
|
||||||
Computed: true,
|
|
||||||
Elem: &Schema{Type: TypeInt},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
State: &terraform.InstanceState{
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"ports.#": "3",
|
|
||||||
"ports.0": "1",
|
|
||||||
"ports.1": "2",
|
|
||||||
"ports.2": "5",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Diff: nil,
|
|
||||||
|
|
||||||
Key: "ports.1",
|
|
||||||
Value: 3,
|
|
||||||
|
|
||||||
GetKey: "ports",
|
|
||||||
GetValue: []interface{}{1, 3, 5},
|
|
||||||
},
|
|
||||||
|
|
||||||
// List of primitives, set list
|
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ports": &Schema{
|
"ports": &Schema{
|
||||||
|
@ -1131,7 +1131,7 @@ func TestResourceDataSet(t *testing.T) {
|
||||||
GetValue: []interface{}{1, 2, 5},
|
GetValue: []interface{}{1, 2, 5},
|
||||||
},
|
},
|
||||||
|
|
||||||
// List of primitives, set list with error
|
// #6: List of primitives, set list with error
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ports": &Schema{
|
"ports": &Schema{
|
||||||
|
@ -1153,140 +1153,7 @@ func TestResourceDataSet(t *testing.T) {
|
||||||
GetValue: []interface{}{},
|
GetValue: []interface{}{},
|
||||||
},
|
},
|
||||||
|
|
||||||
// List of resource, set element
|
// #7: Set a list of maps
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
|
||||||
"ingress": &Schema{
|
|
||||||
Type: TypeList,
|
|
||||||
Computed: true,
|
|
||||||
Elem: &Resource{
|
|
||||||
Schema: map[string]*Schema{
|
|
||||||
"from": &Schema{
|
|
||||||
Type: TypeInt,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
State: &terraform.InstanceState{
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"ingress.#": "2",
|
|
||||||
"ingress.0.from": "80",
|
|
||||||
"ingress.1.from": "8080",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Diff: nil,
|
|
||||||
|
|
||||||
Key: "ingress.1.from",
|
|
||||||
Value: 9000,
|
|
||||||
|
|
||||||
GetKey: "ingress",
|
|
||||||
GetValue: []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"from": 80,
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"from": 9000,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// List of resource, set full resource element
|
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
|
||||||
"ingress": &Schema{
|
|
||||||
Type: TypeList,
|
|
||||||
Computed: true,
|
|
||||||
Elem: &Resource{
|
|
||||||
Schema: map[string]*Schema{
|
|
||||||
"from": &Schema{
|
|
||||||
Type: TypeInt,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
State: &terraform.InstanceState{
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"ingress.#": "2",
|
|
||||||
"ingress.0.from": "80",
|
|
||||||
"ingress.1.from": "8080",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Diff: nil,
|
|
||||||
|
|
||||||
Key: "ingress.1",
|
|
||||||
Value: map[string]interface{}{
|
|
||||||
"from": 9000,
|
|
||||||
},
|
|
||||||
|
|
||||||
GetKey: "ingress",
|
|
||||||
GetValue: []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"from": 80,
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"from": 9000,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// List of resource, set full resource element, with error
|
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
|
||||||
"ingress": &Schema{
|
|
||||||
Type: TypeList,
|
|
||||||
Computed: true,
|
|
||||||
Elem: &Resource{
|
|
||||||
Schema: map[string]*Schema{
|
|
||||||
"from": &Schema{
|
|
||||||
Type: TypeInt,
|
|
||||||
},
|
|
||||||
"to": &Schema{
|
|
||||||
Type: TypeInt,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
State: &terraform.InstanceState{
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"ingress.#": "2",
|
|
||||||
"ingress.0.from": "80",
|
|
||||||
"ingress.0.to": "10",
|
|
||||||
"ingress.1.from": "8080",
|
|
||||||
"ingress.1.to": "8080",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
Diff: nil,
|
|
||||||
|
|
||||||
Key: "ingress.1",
|
|
||||||
Value: map[string]interface{}{
|
|
||||||
"from": 9000,
|
|
||||||
"to": "bar",
|
|
||||||
},
|
|
||||||
Err: true,
|
|
||||||
|
|
||||||
GetKey: "ingress",
|
|
||||||
GetValue: []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"from": 80,
|
|
||||||
"to": 10,
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"from": 8080,
|
|
||||||
"to": 8080,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Set a list of maps
|
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"config_vars": &Schema{
|
"config_vars": &Schema{
|
||||||
|
@ -1325,46 +1192,7 @@ func TestResourceDataSet(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Set a list of maps
|
// #8: Set, with list
|
||||||
{
|
|
||||||
Schema: map[string]*Schema{
|
|
||||||
"config_vars": &Schema{
|
|
||||||
Type: TypeList,
|
|
||||||
Optional: true,
|
|
||||||
Computed: true,
|
|
||||||
Elem: &Schema{
|
|
||||||
Type: TypeMap,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
State: nil,
|
|
||||||
|
|
||||||
Diff: nil,
|
|
||||||
|
|
||||||
Key: "config_vars",
|
|
||||||
Value: []interface{}{
|
|
||||||
map[string]string{
|
|
||||||
"foo": "bar",
|
|
||||||
},
|
|
||||||
map[string]string{
|
|
||||||
"bar": "baz",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Err: false,
|
|
||||||
|
|
||||||
GetKey: "config_vars",
|
|
||||||
GetValue: []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"foo": "bar",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"bar": "baz",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Set, with list
|
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ports": &Schema{
|
"ports": &Schema{
|
||||||
|
@ -1394,7 +1222,7 @@ func TestResourceDataSet(t *testing.T) {
|
||||||
GetValue: []interface{}{100, 125},
|
GetValue: []interface{}{100, 125},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Set, with Set
|
// #9: Set, with Set
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ports": &Schema{
|
"ports": &Schema{
|
||||||
|
@ -1429,7 +1257,7 @@ func TestResourceDataSet(t *testing.T) {
|
||||||
GetValue: []interface{}{1, 2},
|
GetValue: []interface{}{1, 2},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Set single item
|
// #10: Set single item
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ports": &Schema{
|
"ports": &Schema{
|
||||||
|
@ -1458,6 +1286,74 @@ func TestResourceDataSet(t *testing.T) {
|
||||||
GetKey: "ports",
|
GetKey: "ports",
|
||||||
GetValue: []interface{}{80, 100},
|
GetValue: []interface{}{80, 100},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// #11: Set with nested set
|
||||||
|
{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"ports": &Schema{
|
||||||
|
Type: TypeSet,
|
||||||
|
Elem: &Resource{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"port": &Schema{
|
||||||
|
Type: TypeInt,
|
||||||
|
},
|
||||||
|
|
||||||
|
"set": &Schema{
|
||||||
|
Type: TypeSet,
|
||||||
|
Elem: &Schema{Type: TypeInt},
|
||||||
|
Set: func(a interface{}) int {
|
||||||
|
return a.(int)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Set: func(a interface{}) int {
|
||||||
|
return a.(map[string]interface{})["port"].(int)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
State: nil,
|
||||||
|
|
||||||
|
Key: "ports",
|
||||||
|
Value: []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"port": 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
GetKey: "ports",
|
||||||
|
GetValue: []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"port": 80,
|
||||||
|
"set": []interface{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
GetPreProcess: func(v interface{}) interface{} {
|
||||||
|
if v == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
s, ok := v.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
for _, v := range s {
|
||||||
|
m, ok := v.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if m["set"] == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if s, ok := m["set"].(*Set); ok {
|
||||||
|
m["set"] = s.List()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tc := range cases {
|
for i, tc := range cases {
|
||||||
|
@ -1475,6 +1371,11 @@ func TestResourceDataSet(t *testing.T) {
|
||||||
if s, ok := v.(*Set); ok {
|
if s, ok := v.(*Set); ok {
|
||||||
v = s.List()
|
v = s.List()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tc.GetPreProcess != nil {
|
||||||
|
v = tc.GetPreProcess(v)
|
||||||
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(v, tc.GetValue) {
|
if !reflect.DeepEqual(v, tc.GetValue) {
|
||||||
t.Fatalf("Get Bad: %d\n\n%#v", i, v)
|
t.Fatalf("Get Bad: %d\n\n%#v", i, v)
|
||||||
}
|
}
|
||||||
|
@ -1490,7 +1391,7 @@ func TestResourceDataState(t *testing.T) {
|
||||||
Result *terraform.InstanceState
|
Result *terraform.InstanceState
|
||||||
Partial []string
|
Partial []string
|
||||||
}{
|
}{
|
||||||
// Basic primitive in diff
|
// #0 Basic primitive in diff
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"availability_zone": &Schema{
|
"availability_zone": &Schema{
|
||||||
|
@ -1520,7 +1421,7 @@ func TestResourceDataState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Basic primitive set override
|
// #1 Basic primitive set override
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"availability_zone": &Schema{
|
"availability_zone": &Schema{
|
||||||
|
@ -1554,6 +1455,7 @@ func TestResourceDataState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// #2
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"vpc": &Schema{
|
"vpc": &Schema{
|
||||||
|
@ -1577,7 +1479,7 @@ func TestResourceDataState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Basic primitive with StateFunc set
|
// #3 Basic primitive with StateFunc set
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"availability_zone": &Schema{
|
"availability_zone": &Schema{
|
||||||
|
@ -1607,7 +1509,7 @@ func TestResourceDataState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// List
|
// #4 List
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ports": &Schema{
|
"ports": &Schema{
|
||||||
|
@ -1646,7 +1548,7 @@ func TestResourceDataState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// List of resources
|
// #5 List of resources
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ingress": &Schema{
|
"ingress": &Schema{
|
||||||
|
@ -1696,7 +1598,7 @@ func TestResourceDataState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// List of maps
|
// #6 List of maps
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"config_vars": &Schema{
|
"config_vars": &Schema{
|
||||||
|
@ -1727,10 +1629,15 @@ func TestResourceDataState(t *testing.T) {
|
||||||
},
|
},
|
||||||
|
|
||||||
Set: map[string]interface{}{
|
Set: map[string]interface{}{
|
||||||
"config_vars.1": map[string]interface{}{
|
"config_vars": []map[string]interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
"baz": "bang",
|
"baz": "bang",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
|
||||||
Result: &terraform.InstanceState{
|
Result: &terraform.InstanceState{
|
||||||
Attributes: map[string]string{
|
Attributes: map[string]string{
|
||||||
|
@ -1741,7 +1648,7 @@ func TestResourceDataState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// List of maps with removal in diff
|
// #7 List of maps with removal in diff
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"config_vars": &Schema{
|
"config_vars": &Schema{
|
||||||
|
@ -1781,7 +1688,7 @@ func TestResourceDataState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Basic state with other keys
|
// #8 Basic state with other keys
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"availability_zone": &Schema{
|
"availability_zone": &Schema{
|
||||||
|
@ -1818,7 +1725,7 @@ func TestResourceDataState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Sets
|
// #9 Sets
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ports": &Schema{
|
"ports": &Schema{
|
||||||
|
@ -1853,6 +1760,7 @@ func TestResourceDataState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// #10
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ports": &Schema{
|
"ports": &Schema{
|
||||||
|
@ -1883,6 +1791,7 @@ func TestResourceDataState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// #11
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ports": &Schema{
|
"ports": &Schema{
|
||||||
|
@ -1944,7 +1853,9 @@ func TestResourceDataState(t *testing.T) {
|
||||||
"ports.10.order": "10",
|
"ports.10.order": "10",
|
||||||
"ports.10.a.#": "1",
|
"ports.10.a.#": "1",
|
||||||
"ports.10.a.0": "80",
|
"ports.10.a.0": "80",
|
||||||
|
"ports.10.b.#": "0",
|
||||||
"ports.20.order": "20",
|
"ports.20.order": "20",
|
||||||
|
"ports.20.a.#": "0",
|
||||||
"ports.20.b.#": "1",
|
"ports.20.b.#": "1",
|
||||||
"ports.20.b.0": "100",
|
"ports.20.b.0": "100",
|
||||||
},
|
},
|
||||||
|
@ -1955,7 +1866,7 @@ func TestResourceDataState(t *testing.T) {
|
||||||
* PARTIAL STATES
|
* PARTIAL STATES
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Basic primitive
|
// #12 Basic primitive
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"availability_zone": &Schema{
|
"availability_zone": &Schema{
|
||||||
|
@ -1981,11 +1892,13 @@ func TestResourceDataState(t *testing.T) {
|
||||||
Partial: []string{},
|
Partial: []string{},
|
||||||
|
|
||||||
Result: &terraform.InstanceState{
|
Result: &terraform.InstanceState{
|
||||||
Attributes: map[string]string{},
|
Attributes: map[string]string{
|
||||||
|
"availability_zone": "",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// List
|
// #13 List
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ports": &Schema{
|
"ports": &Schema{
|
||||||
|
@ -2025,6 +1938,7 @@ func TestResourceDataState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// #14
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ports": &Schema{
|
"ports": &Schema{
|
||||||
|
@ -2059,7 +1973,7 @@ func TestResourceDataState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// List of resources
|
// #15 List of resources
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ingress": &Schema{
|
"ingress": &Schema{
|
||||||
|
@ -2110,7 +2024,7 @@ func TestResourceDataState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// List of maps
|
// #16 List of maps
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"config_vars": &Schema{
|
"config_vars": &Schema{
|
||||||
|
@ -2141,15 +2055,21 @@ func TestResourceDataState(t *testing.T) {
|
||||||
},
|
},
|
||||||
|
|
||||||
Set: map[string]interface{}{
|
Set: map[string]interface{}{
|
||||||
"config_vars.1": map[string]interface{}{
|
"config_vars": []map[string]interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
"baz": "bang",
|
"baz": "bang",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
|
||||||
Partial: []string{},
|
Partial: []string{},
|
||||||
|
|
||||||
Result: &terraform.InstanceState{
|
Result: &terraform.InstanceState{
|
||||||
Attributes: map[string]string{
|
Attributes: map[string]string{
|
||||||
|
// TODO: broken, shouldn't bar be removed?
|
||||||
"config_vars.#": "2",
|
"config_vars.#": "2",
|
||||||
"config_vars.0.foo": "bar",
|
"config_vars.0.foo": "bar",
|
||||||
"config_vars.0.bar": "bar",
|
"config_vars.0.bar": "bar",
|
||||||
|
@ -2158,7 +2078,7 @@ func TestResourceDataState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Sets
|
// #17 Sets
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ports": &Schema{
|
"ports": &Schema{
|
||||||
|
@ -2201,6 +2121,7 @@ func TestResourceDataState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// #18
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ports": &Schema{
|
"ports": &Schema{
|
||||||
|
@ -2234,7 +2155,7 @@ func TestResourceDataState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Maps
|
// #19 Maps
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"tags": &Schema{
|
"tags": &Schema{
|
||||||
|
@ -2262,6 +2183,7 @@ func TestResourceDataState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// #20
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"tags": &Schema{
|
"tags": &Schema{
|
||||||
|
|
|
@ -33,8 +33,33 @@ const (
|
||||||
TypeList
|
TypeList
|
||||||
TypeMap
|
TypeMap
|
||||||
TypeSet
|
TypeSet
|
||||||
|
typeObject
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Zero returns the zero value for a type.
|
||||||
|
func (t ValueType) Zero() interface{} {
|
||||||
|
switch t {
|
||||||
|
case TypeInvalid:
|
||||||
|
return nil
|
||||||
|
case TypeBool:
|
||||||
|
return false
|
||||||
|
case TypeInt:
|
||||||
|
return 0
|
||||||
|
case TypeString:
|
||||||
|
return ""
|
||||||
|
case TypeList:
|
||||||
|
return []interface{}{}
|
||||||
|
case TypeMap:
|
||||||
|
return map[string]interface{}{}
|
||||||
|
case TypeSet:
|
||||||
|
return nil
|
||||||
|
case typeObject:
|
||||||
|
return map[string]interface{}{}
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unknown type %#v", t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Schema is used to describe the structure of a value.
|
// Schema is used to describe the structure of a value.
|
||||||
//
|
//
|
||||||
// Read the documentation of the struct elements for important details.
|
// Read the documentation of the struct elements for important details.
|
||||||
|
@ -201,7 +226,6 @@ func (m schemaMap) Diff(
|
||||||
schema: m,
|
schema: m,
|
||||||
state: s,
|
state: s,
|
||||||
config: c,
|
config: c,
|
||||||
diffing: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, schema := range m {
|
for k, schema := range m {
|
||||||
|
@ -220,8 +244,10 @@ func (m schemaMap) Diff(
|
||||||
result2 := new(terraform.InstanceDiff)
|
result2 := new(terraform.InstanceDiff)
|
||||||
result2.Attributes = make(map[string]*terraform.ResourceAttrDiff)
|
result2.Attributes = make(map[string]*terraform.ResourceAttrDiff)
|
||||||
|
|
||||||
// Reset the data to not contain state
|
// Reset the data to not contain state. We have to call init()
|
||||||
|
// again in order to reset the FieldReaders.
|
||||||
d.state = nil
|
d.state = nil
|
||||||
|
d.init()
|
||||||
|
|
||||||
// Perform the diff again
|
// Perform the diff again
|
||||||
for k, schema := range m {
|
for k, schema := range m {
|
||||||
|
@ -457,6 +483,9 @@ func (m schemaMap) diffList(
|
||||||
d *ResourceData,
|
d *ResourceData,
|
||||||
all bool) error {
|
all bool) error {
|
||||||
o, n, _, computedList := d.diffChange(k)
|
o, n, _, computedList := d.diffChange(k)
|
||||||
|
if computedList {
|
||||||
|
n = nil
|
||||||
|
}
|
||||||
nSet := n != nil
|
nSet := n != nil
|
||||||
|
|
||||||
// If we have an old value and no new value is set or will be
|
// If we have an old value and no new value is set or will be
|
||||||
|
@ -654,6 +683,9 @@ func (m schemaMap) diffSet(
|
||||||
d *ResourceData,
|
d *ResourceData,
|
||||||
all bool) error {
|
all bool) error {
|
||||||
o, n, _, computedSet := d.diffChange(k)
|
o, n, _, computedSet := d.diffChange(k)
|
||||||
|
if computedSet {
|
||||||
|
n = nil
|
||||||
|
}
|
||||||
nSet := n != nil
|
nSet := n != nil
|
||||||
|
|
||||||
// If we have an old value and no new value is set or will be
|
// If we have an old value and no new value is set or will be
|
||||||
|
|
|
@ -8,6 +8,27 @@ import (
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestValueType_Zero(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Type ValueType
|
||||||
|
Value interface{}
|
||||||
|
}{
|
||||||
|
{TypeBool, false},
|
||||||
|
{TypeInt, 0},
|
||||||
|
{TypeString, ""},
|
||||||
|
{TypeList, []interface{}{}},
|
||||||
|
{TypeMap, map[string]interface{}{}},
|
||||||
|
{TypeSet, nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
actual := tc.Type.Zero()
|
||||||
|
if !reflect.DeepEqual(actual, tc.Value) {
|
||||||
|
t.Fatalf("%d: %#v != %#v", i, actual, tc.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSchemaMap_Diff(t *testing.T) {
|
func TestSchemaMap_Diff(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
Schema map[string]*Schema
|
Schema map[string]*Schema
|
||||||
|
@ -844,7 +865,7 @@ func TestSchemaMap_Diff(t *testing.T) {
|
||||||
Diff: &terraform.InstanceDiff{
|
Diff: &terraform.InstanceDiff{
|
||||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||||
"ports.#": &terraform.ResourceAttrDiff{
|
"ports.#": &terraform.ResourceAttrDiff{
|
||||||
Old: "",
|
Old: "0",
|
||||||
New: "",
|
New: "",
|
||||||
NewComputed: true,
|
NewComputed: true,
|
||||||
},
|
},
|
||||||
|
@ -1664,7 +1685,7 @@ func TestSchemaMap_Diff(t *testing.T) {
|
||||||
New: "1",
|
New: "1",
|
||||||
},
|
},
|
||||||
"route.~1.gateway.#": &terraform.ResourceAttrDiff{
|
"route.~1.gateway.#": &terraform.ResourceAttrDiff{
|
||||||
Old: "",
|
Old: "0",
|
||||||
NewComputed: true,
|
NewComputed: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package schema
|
package schema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
@ -100,6 +101,10 @@ func (s *Set) Union(other *Set) *Set {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Set) GoString() string {
|
||||||
|
return fmt.Sprintf("*Set(%#v)", s.m)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Set) init() {
|
func (s *Set) init() {
|
||||||
s.m = make(map[int]interface{})
|
s.m = make(map[int]interface{})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue