helper/schema: use the field reader/writer for state

This commit is contained in:
Mitchell Hashimoto 2015-01-10 12:18:32 -08:00
parent f64b09a045
commit 3c1b55a75f
3 changed files with 48 additions and 189 deletions

View File

@ -1,9 +1,7 @@
package schema package schema
import ( import (
"fmt"
"reflect" "reflect"
"strconv"
"strings" "strings"
"sync" "sync"
@ -139,8 +137,9 @@ func (d *ResourceData) Set(key string, value interface{}) error {
return d.setWriter.WriteField(strings.Split(key, "."), value) return d.setWriter.WriteField(strings.Split(key, "."), value)
} }
// SetPartial adds the key prefix to the final state output while // SetPartial adds the key to the final state output while
// in partial state mode. // in partial state mode. The key must be a root key in the schema (i.e.
// it cannot be "list.0").
// //
// If partial state mode is disabled, then this has no effect. Additionally, // If partial state mode is disabled, then this has no effect. Additionally,
// whenever partial state mode is toggled, the partial data is cleared. // whenever partial state mode is toggled, the partial data is cleared.
@ -203,9 +202,46 @@ func (d *ResourceData) State() *terraform.InstanceState {
return nil return nil
} }
result.Attributes = d.stateObject("", d.schema) // In order to build the final state attributes, we read the full
// attribute set as a map[string]interface{}, write it to a MapFieldWriter,
// and then use that map.
rawMap := make(map[string]interface{})
for k, _ := range d.schema {
source := getSourceSet
if d.partial {
source = getSourceState
if _, ok := d.partialMap[k]; ok {
source = getSourceSet
}
}
raw := d.get(k, nil, nil, source)
rawMap[k] = raw.Value
if raw.ValueProcessed != nil {
rawMap[k] = raw.ValueProcessed
}
}
mapW := &MapFieldWriter{Schema: d.schema}
if err := mapW.WriteField(nil, rawMap); err != nil {
return nil
}
result.Attributes = mapW.Map()
result.Ephemeral.ConnInfo = d.ConnInfo() result.Ephemeral.ConnInfo = d.ConnInfo()
// TODO: This is hacky and we can remove this when we have a proper
// state writer. We should instead have a proper StateFieldWriter
// and use that.
for k, schema := range d.schema {
if schema.Type != TypeMap {
continue
}
if result.Attributes[k] == "" {
delete(result.Attributes, k)
}
}
if v := d.Id(); v != "" { if v := d.Id(); v != "" {
result.Attributes["id"] = d.Id() result.Attributes["id"] = d.Id()
} }
@ -361,186 +397,3 @@ func (d *ResourceData) get(
Schema: schema, Schema: schema,
} }
} }
func (d *ResourceData) stateList(
prefix string,
schema *Schema) map[string]string {
countRaw := d.get(prefix, []string{"#"}, schema, d.stateSource(prefix))
if !countRaw.Exists {
if schema.Computed {
// If it is computed, then it always _exists_ in the state,
// it is just empty.
countRaw.Exists = true
countRaw.Value = 0
} else {
return nil
}
}
count := countRaw.Value.(int)
result := make(map[string]string)
if count > 0 || schema.Computed {
result[prefix+".#"] = strconv.FormatInt(int64(count), 10)
}
for i := 0; i < count; i++ {
key := fmt.Sprintf("%s.%d", prefix, i)
var m map[string]string
switch t := schema.Elem.(type) {
case *Resource:
m = d.stateObject(key, t.Schema)
case *Schema:
m = d.stateSingle(key, t)
}
for k, v := range m {
result[k] = v
}
}
return result
}
func (d *ResourceData) stateMap(
prefix string,
schema *Schema) map[string]string {
v := d.get(prefix, nil, schema, d.stateSource(prefix))
if !v.Exists {
return nil
}
elemSchema := &Schema{Type: TypeString}
result := make(map[string]string)
for mk, _ := range v.Value.(map[string]interface{}) {
mp := fmt.Sprintf("%s.%s", prefix, mk)
for k, v := range d.stateSingle(mp, elemSchema) {
result[k] = v
}
}
return result
}
func (d *ResourceData) stateObject(
prefix string,
schema map[string]*Schema) map[string]string {
result := make(map[string]string)
for k, v := range schema {
key := k
if prefix != "" {
key = prefix + "." + key
}
for k1, v1 := range d.stateSingle(key, v) {
result[k1] = v1
}
}
return result
}
func (d *ResourceData) statePrimitive(
prefix string,
schema *Schema) map[string]string {
raw := d.getRaw(prefix, d.stateSource(prefix))
if !raw.Exists {
return nil
}
v := raw.Value
if raw.ValueProcessed != nil {
v = raw.ValueProcessed
}
var vs string
switch schema.Type {
case TypeBool:
vs = strconv.FormatBool(v.(bool))
case TypeString:
vs = v.(string)
case TypeInt:
vs = strconv.FormatInt(int64(v.(int)), 10)
default:
panic(fmt.Sprintf("Unknown type: %#v", schema.Type))
}
return map[string]string{
prefix: vs,
}
}
func (d *ResourceData) stateSet(
prefix string,
schema *Schema) map[string]string {
raw := d.get(prefix, nil, schema, d.stateSource(prefix))
if !raw.Exists {
if schema.Computed {
// If it is computed, then it always _exists_ in the state,
// it is just empty.
raw.Exists = true
raw.Value = new(Set)
} else {
return nil
}
}
set := raw.Value.(*Set)
result := make(map[string]string)
result[prefix+".#"] = strconv.Itoa(set.Len())
for _, idx := range set.listCode() {
key := fmt.Sprintf("%s.%d", prefix, idx)
var m map[string]string
switch t := schema.Elem.(type) {
case *Resource:
m = d.stateObject(key, t.Schema)
case *Schema:
m = d.stateSingle(key, t)
}
for k, v := range m {
result[k] = v
}
}
return result
}
func (d *ResourceData) stateSingle(
prefix string,
schema *Schema) map[string]string {
switch schema.Type {
case TypeList:
return d.stateList(prefix, schema)
case TypeMap:
return d.stateMap(prefix, schema)
case TypeSet:
return d.stateSet(prefix, schema)
case TypeBool:
fallthrough
case TypeInt:
fallthrough
case TypeString:
return d.statePrimitive(prefix, schema)
default:
panic(fmt.Sprintf("%s: unknown type %#v", prefix, schema.Type))
}
}
func (d *ResourceData) stateSource(prefix string) getSource {
// If we're not doing a partial apply, then get the set level
if !d.partial {
return getSourceSet | getSourceDiff
}
// Otherwise, only return getSourceSet if its in the partial map.
// Otherwise we use state level only.
for k, _ := range d.partialMap {
if strings.HasPrefix(prefix, k) {
return getSourceSet | getSourceDiff
}
}
return getSourceState
}

View File

@ -1853,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",
}, },
@ -1890,7 +1892,9 @@ 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": "",
},
}, },
}, },

View File

@ -53,6 +53,8 @@ func (t ValueType) Zero() interface{} {
return map[string]interface{}{} return map[string]interface{}{}
case TypeSet: case TypeSet:
return nil return nil
case typeObject:
return map[string]interface{}{}
default: default:
panic(fmt.Sprintf("unknown type %#v", t)) panic(fmt.Sprintf("unknown type %#v", t))
} }