helper/schema: MultiLevelFieldReader

This commit is contained in:
Mitchell Hashimoto 2015-01-03 10:32:24 -05:00
parent 0b1da37b20
commit 3ff859d734
2 changed files with 322 additions and 0 deletions

View File

@ -0,0 +1,67 @@
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, schema *Schema) (FieldReadResult, error) {
return r.ReadFieldMerge(address, schema, r.Levels[len(r.Levels)-1])
}
func (r *MultiLevelFieldReader) ReadFieldExact(
address []string, schema *Schema, 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, schema)
if err != nil {
return FieldReadResult{}, fmt.Errorf(
"Error reading level %s: %s", level, err)
}
return result, nil
}
func (r *MultiLevelFieldReader) ReadFieldMerge(
address []string, schema *Schema, level string) (FieldReadResult, error) {
var result FieldReadResult
for _, l := range r.Levels {
r, ok := r.Readers[l]
if !ok {
continue
}
out, err := r.ReadField(address, schema)
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
}

View File

@ -0,0 +1,255 @@
package schema
import (
"reflect"
"strconv"
"testing"
"github.com/hashicorp/terraform/terraform"
)
func TestMultiLevelFieldReaderReadFieldExact(t *testing.T) {
cases := map[string]struct {
Addr []string
Schema *Schema
Readers []FieldReader
Level string
Result FieldReadResult
}{
"specific": {
Addr: []string{"foo"},
Schema: &Schema{
Type: TypeString,
},
Readers: []FieldReader{
&MapFieldReader{
Map: map[string]string{
"foo": "bar",
},
},
&MapFieldReader{
Map: map[string]string{
"foo": "baz",
},
},
&MapFieldReader{
Map: 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.Schema, 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
Schema *Schema
Readers []FieldReader
Result FieldReadResult
}{
"stringInDiff": {
Addr: []string{"availability_zone"},
Schema: &Schema{
Type: TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
Readers: []FieldReader{
&DiffFieldReader{
Source: &MapFieldReader{
Map: 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"},
Schema: &Schema{
Type: TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
Readers: []FieldReader{
&MapFieldReader{
Map: map[string]string{
"availability_zone": "foo",
},
},
&DiffFieldReader{
Source: &MapFieldReader{
Map: 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"},
Schema: &Schema{
Type: TypeList,
Optional: true,
Computed: true,
Elem: &Schema{
Type: TypeMap,
},
},
Readers: []FieldReader{
&DiffFieldReader{
Source: &MapFieldReader{
Map: 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"},
Schema: &Schema{
Type: TypeString,
},
Readers: []FieldReader{
&MapFieldReader{
Map: map[string]string{
"foo": "bar",
},
},
&MapFieldReader{
Map: 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, tc.Schema, 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)
}
}
}