helper/schema: FieldWriter, replace Set
This commit is contained in:
parent
e9a4aaaca7
commit
03c6453a72
|
@ -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,184 @@
|
||||||
|
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{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ResourceData is used to query and set the attributes of a resource.
|
// ResourceData is used to query and set the attributes of a resource.
|
||||||
|
@ -29,7 +28,7 @@ type ResourceData struct {
|
||||||
|
|
||||||
// Don't set
|
// Don't set
|
||||||
multiReader *MultiLevelFieldReader
|
multiReader *MultiLevelFieldReader
|
||||||
setMap map[string]string
|
setWriter *MapFieldWriter
|
||||||
newState *terraform.InstanceState
|
newState *terraform.InstanceState
|
||||||
partial bool
|
partial bool
|
||||||
partialMap map[string]struct{}
|
partialMap map[string]struct{}
|
||||||
|
@ -156,8 +155,7 @@ func (d *ResourceData) Partial(on bool) {
|
||||||
// will be returned.
|
// will be returned.
|
||||||
func (d *ResourceData) Set(key string, value interface{}) error {
|
func (d *ResourceData) Set(key string, value interface{}) error {
|
||||||
d.once.Do(d.init)
|
d.once.Do(d.init)
|
||||||
parts := strings.Split(key, ".")
|
return d.setWriter.WriteField(strings.Split(key, "."), value)
|
||||||
return d.setObject("", parts, d.schema, value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPartial adds the key prefix to the final state output while
|
// SetPartial adds the key prefix to the final state output while
|
||||||
|
@ -243,7 +241,7 @@ func (d *ResourceData) init() {
|
||||||
d.newState = ©State
|
d.newState = ©State
|
||||||
|
|
||||||
// Initialize the map for storing set data
|
// Initialize the map for storing set data
|
||||||
d.setMap = make(map[string]string)
|
d.setWriter = &MapFieldWriter{Schema: d.schema}
|
||||||
|
|
||||||
// Initialize the reader for getting data from the
|
// Initialize the reader for getting data from the
|
||||||
// underlying sources (config, diff, etc.)
|
// underlying sources (config, diff, etc.)
|
||||||
|
@ -274,7 +272,7 @@ func (d *ResourceData) init() {
|
||||||
}
|
}
|
||||||
readers["set"] = &MapFieldReader{
|
readers["set"] = &MapFieldReader{
|
||||||
Schema: d.schema,
|
Schema: d.schema,
|
||||||
Map: BasicMapReader(d.setMap),
|
Map: BasicMapReader(d.setWriter.Map()),
|
||||||
}
|
}
|
||||||
d.multiReader = &MultiLevelFieldReader{
|
d.multiReader = &MultiLevelFieldReader{
|
||||||
Levels: []string{
|
Levels: []string{
|
||||||
|
@ -383,277 +381,6 @@ func (d *ResourceData) get(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ResourceData) set(
|
|
||||||
k string,
|
|
||||||
parts []string,
|
|
||||||
schema *Schema,
|
|
||||||
value interface{}) error {
|
|
||||||
switch schema.Type {
|
|
||||||
case TypeList:
|
|
||||||
return d.setList(k, parts, schema, value)
|
|
||||||
case TypeMap:
|
|
||||||
return d.setMapValue(k, parts, schema, value)
|
|
||||||
case TypeSet:
|
|
||||||
return d.setSet(k, parts, schema, value)
|
|
||||||
case TypeBool:
|
|
||||||
fallthrough
|
|
||||||
case TypeInt:
|
|
||||||
fallthrough
|
|
||||||
case TypeString:
|
|
||||||
return d.setPrimitive(k, schema, value)
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("%s: unknown type %#v", k, schema.Type))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ResourceData) setList(
|
|
||||||
k string,
|
|
||||||
parts []string,
|
|
||||||
schema *Schema,
|
|
||||||
value interface{}) error {
|
|
||||||
if len(parts) > 0 {
|
|
||||||
return fmt.Errorf("%s: can only set the full list, not elements", k)
|
|
||||||
}
|
|
||||||
|
|
||||||
setElement := func(k string, idx string, value interface{}) error {
|
|
||||||
if idx == "#" {
|
|
||||||
return fmt.Errorf("%s: can't set count of list", k)
|
|
||||||
}
|
|
||||||
|
|
||||||
key := fmt.Sprintf("%s.%s", k, idx)
|
|
||||||
switch t := schema.Elem.(type) {
|
|
||||||
case *Resource:
|
|
||||||
return d.setObject(key, nil, t.Schema, value)
|
|
||||||
case *Schema:
|
|
||||||
return d.set(key, nil, t, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var vs []interface{}
|
|
||||||
if err := mapstructure.Decode(value, &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(k, is, elem)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
for i, _ := range vs {
|
|
||||||
is := strconv.FormatInt(int64(i), 10)
|
|
||||||
setElement(k, is, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.setMap[k+".#"] = strconv.FormatInt(int64(len(vs)), 10)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ResourceData) setMapValue(
|
|
||||||
k string,
|
|
||||||
parts []string,
|
|
||||||
schema *Schema,
|
|
||||||
value interface{}) error {
|
|
||||||
elemSchema := &Schema{Type: TypeString}
|
|
||||||
if len(parts) > 0 {
|
|
||||||
return fmt.Errorf("%s: full map must be set, no a single element", k)
|
|
||||||
}
|
|
||||||
|
|
||||||
v := reflect.ValueOf(value)
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
vs := make(map[string]interface{})
|
|
||||||
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.
|
|
||||||
d.setMap[k] = ""
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(d.setMap, k)
|
|
||||||
for subKey, v := range vs {
|
|
||||||
err := d.set(fmt.Sprintf("%s.%s", k, subKey), nil, elemSchema, v)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ResourceData) setObject(
|
|
||||||
k string,
|
|
||||||
parts []string,
|
|
||||||
schema map[string]*Schema,
|
|
||||||
value interface{}) error {
|
|
||||||
if len(parts) > 0 {
|
|
||||||
// We're setting a specific key in an object
|
|
||||||
key := parts[0]
|
|
||||||
parts = parts[1:]
|
|
||||||
|
|
||||||
s, ok := schema[key]
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("%s (internal): unknown key to set: %s", k, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if k != "" {
|
|
||||||
// If we're not at the root, then we need to append
|
|
||||||
// the key to get the full key path.
|
|
||||||
key = fmt.Sprintf("%s.%s", k, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.set(key, parts, s, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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", k, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set each element in turn
|
|
||||||
var err error
|
|
||||||
for k1, v1 := range v {
|
|
||||||
err = d.setObject(k, []string{k1}, schema, v1)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
for k1, _ := range v {
|
|
||||||
d.setObject(k, []string{k1}, schema, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ResourceData) setPrimitive(
|
|
||||||
k string,
|
|
||||||
schema *Schema,
|
|
||||||
v interface{}) error {
|
|
||||||
if v == nil {
|
|
||||||
delete(d.setMap, 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
d.setMap[k] = set
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ResourceData) setSet(
|
|
||||||
k string,
|
|
||||||
parts []string,
|
|
||||||
schema *Schema,
|
|
||||||
value interface{}) error {
|
|
||||||
if len(parts) > 0 {
|
|
||||||
return fmt.Errorf("%s: can only set the full set, not elements", k)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
tempD := &ResourceData{
|
|
||||||
setMap: make(map[string]string),
|
|
||||||
schema: map[string]*Schema{k: schema},
|
|
||||||
}
|
|
||||||
tempD.once.Do(tempD.init)
|
|
||||||
|
|
||||||
// Set the entire list, this lets us get sane values out of it
|
|
||||||
if err := tempD.setList(k, nil, schema, 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}
|
|
||||||
source := getSourceSet | getSourceExact
|
|
||||||
for i := 0; i < v.Len(); i++ {
|
|
||||||
is := strconv.FormatInt(int64(i), 10)
|
|
||||||
result := tempD.get(k, []string{is}, schema, source)
|
|
||||||
if !result.Exists {
|
|
||||||
panic("just set item doesn't exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Add(result.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
value = s
|
|
||||||
}
|
|
||||||
|
|
||||||
switch t := schema.Elem.(type) {
|
|
||||||
case *Resource:
|
|
||||||
for code, elem := range value.(*Set).m {
|
|
||||||
for field, _ := range t.Schema {
|
|
||||||
subK := fmt.Sprintf("%s.%d", k, code)
|
|
||||||
value := elem.(map[string]interface{})[field]
|
|
||||||
err := d.setObject(subK, []string{field}, t.Schema, value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *Schema:
|
|
||||||
for code, elem := range value.(*Set).m {
|
|
||||||
subK := fmt.Sprintf("%s.%d", k, code)
|
|
||||||
err := d.set(subK, nil, t, elem)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("%s: unknown element type (internal)", k)
|
|
||||||
}
|
|
||||||
|
|
||||||
d.setMap[k+".#"] = strconv.Itoa(value.(*Set).Len())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ResourceData) stateList(
|
func (d *ResourceData) stateList(
|
||||||
prefix string,
|
prefix string,
|
||||||
schema *Schema) map[string]string {
|
schema *Schema) map[string]string {
|
||||||
|
|
Loading…
Reference in New Issue