helper/schema: use the field reader/writer for state
This commit is contained in:
parent
f64b09a045
commit
3c1b55a75f
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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": "",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue