From 3c1b55a75fb892ea7255236387acb52478e68a59 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 10 Jan 2015 12:18:32 -0800 Subject: [PATCH] helper/schema: use the field reader/writer for state --- helper/schema/resource_data.go | 229 +++++----------------------- helper/schema/resource_data_test.go | 6 +- helper/schema/schema.go | 2 + 3 files changed, 48 insertions(+), 189 deletions(-) diff --git a/helper/schema/resource_data.go b/helper/schema/resource_data.go index 10f9e8a77..bdabd684f 100644 --- a/helper/schema/resource_data.go +++ b/helper/schema/resource_data.go @@ -1,9 +1,7 @@ package schema import ( - "fmt" "reflect" - "strconv" "strings" "sync" @@ -139,8 +137,9 @@ func (d *ResourceData) Set(key string, value interface{}) error { return d.setWriter.WriteField(strings.Split(key, "."), value) } -// SetPartial adds the key prefix to the final state output while -// in partial state mode. +// SetPartial adds the key to the final state output while +// 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, // whenever partial state mode is toggled, the partial data is cleared. @@ -203,9 +202,46 @@ func (d *ResourceData) State() *terraform.InstanceState { 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() + // 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 != "" { result.Attributes["id"] = d.Id() } @@ -361,186 +397,3 @@ func (d *ResourceData) get( 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 -} diff --git a/helper/schema/resource_data_test.go b/helper/schema/resource_data_test.go index a76218e39..6b9426680 100644 --- a/helper/schema/resource_data_test.go +++ b/helper/schema/resource_data_test.go @@ -1853,7 +1853,9 @@ func TestResourceDataState(t *testing.T) { "ports.10.order": "10", "ports.10.a.#": "1", "ports.10.a.0": "80", + "ports.10.b.#": "0", "ports.20.order": "20", + "ports.20.a.#": "0", "ports.20.b.#": "1", "ports.20.b.0": "100", }, @@ -1890,7 +1892,9 @@ func TestResourceDataState(t *testing.T) { Partial: []string{}, Result: &terraform.InstanceState{ - Attributes: map[string]string{}, + Attributes: map[string]string{ + "availability_zone": "", + }, }, }, diff --git a/helper/schema/schema.go b/helper/schema/schema.go index 218de7851..85bc9207e 100644 --- a/helper/schema/schema.go +++ b/helper/schema/schema.go @@ -53,6 +53,8 @@ func (t ValueType) Zero() interface{} { return map[string]interface{}{} case TypeSet: return nil + case typeObject: + return map[string]interface{}{} default: panic(fmt.Sprintf("unknown type %#v", t)) }