helper/schema: can read and get the state of sets
This commit is contained in:
parent
a7e1154a0f
commit
56dde5c0c1
|
@ -173,11 +173,83 @@ func (d *ResourceData) get(
|
|||
return d.getList(k, parts, schema, source)
|
||||
case TypeMap:
|
||||
return d.getMap(k, parts, schema, source)
|
||||
default:
|
||||
case TypeSet:
|
||||
return d.getSet(k, parts, schema, source)
|
||||
case TypeBool:
|
||||
fallthrough
|
||||
case TypeInt:
|
||||
fallthrough
|
||||
case TypeString:
|
||||
return d.getPrimitive(k, parts, schema, source)
|
||||
default:
|
||||
panic(fmt.Sprintf("%s: unknown type %s", k, schema.Type))
|
||||
}
|
||||
}
|
||||
|
||||
func (d *ResourceData) getSet(
|
||||
k string,
|
||||
parts []string,
|
||||
schema *Schema,
|
||||
source getSource) interface{} {
|
||||
raw := d.getList(k, nil, schema, source)
|
||||
if raw == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
list := raw.([]interface{})
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This is a reverse map of hash code => index in config used to
|
||||
// resolve direct set item lookup for turning into state. Confused?
|
||||
// Read on...
|
||||
//
|
||||
// To create the state (the state* functions), a Get call is done
|
||||
// with a full key such as "ports.0". The index of a set ("0") doesn't
|
||||
// make a lot of sense, but we need to deterministically list out
|
||||
// elements of the set like this. Luckily, same sets have a deterministic
|
||||
// List() output, so we can use that to look things up.
|
||||
//
|
||||
// This mapping makes it so that we can look up the hash code of an
|
||||
// object back to its index in the REAL config.
|
||||
var indexMap map[int]int
|
||||
if len(parts) > 0 {
|
||||
indexMap = make(map[int]int)
|
||||
}
|
||||
|
||||
// Build the set from all the items using the given hash code
|
||||
s := &Set{F: schema.Set}
|
||||
for i, v := range list {
|
||||
code := s.add(v)
|
||||
if indexMap != nil {
|
||||
indexMap[code] = i
|
||||
}
|
||||
}
|
||||
|
||||
// If we're trying to get a specific element, then rewrite the
|
||||
// index to be just that, then jump direct to getList.
|
||||
if len(parts) > 0 {
|
||||
index := parts[0]
|
||||
indexInt, err := strconv.ParseInt(index, 0, 0)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
codes := s.listCode()
|
||||
if int(indexInt) >= len(codes) {
|
||||
return nil
|
||||
}
|
||||
code := codes[indexInt]
|
||||
realIndex := indexMap[code]
|
||||
|
||||
parts[0] = strconv.FormatInt(int64(realIndex), 10)
|
||||
return d.getList(k, parts, schema, source)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (d *ResourceData) getMap(
|
||||
k string,
|
||||
parts []string,
|
||||
|
@ -241,6 +313,11 @@ func (d *ResourceData) getMap(
|
|||
}
|
||||
}
|
||||
|
||||
// If we're requesting a specific element, return that
|
||||
if len(parts) > 0 {
|
||||
return result[parts[0]]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -657,7 +734,7 @@ func (d *ResourceData) stateObject(
|
|||
func (d *ResourceData) statePrimitive(
|
||||
prefix string,
|
||||
schema *Schema) map[string]string {
|
||||
v := d.getPrimitive(prefix, nil, schema, getSourceSet)
|
||||
v := d.Get(prefix)
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -679,6 +756,37 @@ func (d *ResourceData) statePrimitive(
|
|||
}
|
||||
}
|
||||
|
||||
func (d *ResourceData) stateSet(
|
||||
prefix string,
|
||||
schema *Schema) map[string]string {
|
||||
raw := d.get(prefix, nil, schema, getSourceSet)
|
||||
if raw == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
set := raw.(*Set)
|
||||
list := set.List()
|
||||
result := make(map[string]string)
|
||||
result[prefix+".#"] = strconv.FormatInt(int64(len(list)), 10)
|
||||
for i := 0; i < len(list); 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) stateSingle(
|
||||
prefix string,
|
||||
schema *Schema) map[string]string {
|
||||
|
@ -687,7 +795,15 @@ func (d *ResourceData) stateSingle(
|
|||
return d.stateList(prefix, schema)
|
||||
case TypeMap:
|
||||
return d.stateMap(prefix, schema)
|
||||
default:
|
||||
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 %s", prefix, schema.Type))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -444,6 +444,34 @@ func TestResourceDataGet(t *testing.T) {
|
|||
|
||||
Value: []interface{}{},
|
||||
},
|
||||
|
||||
// Sets
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeSet,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &Schema{Type: TypeInt},
|
||||
Set: func(a interface{}) int {
|
||||
return a.(int)
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
State: &terraform.ResourceState{
|
||||
Attributes: map[string]string{
|
||||
"ports.#": "1",
|
||||
"ports.0": "80",
|
||||
},
|
||||
},
|
||||
|
||||
Diff: nil,
|
||||
|
||||
Key: "ports",
|
||||
|
||||
Value: []interface{}{80},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
|
@ -453,6 +481,10 @@ func TestResourceDataGet(t *testing.T) {
|
|||
}
|
||||
|
||||
v := d.Get(tc.Key)
|
||||
if s, ok := v.(*Set); ok {
|
||||
v = s.List()
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(v, tc.Value) {
|
||||
t.Fatalf("Bad: %d\n\n%#v", i, v)
|
||||
}
|
||||
|
@ -1346,6 +1378,39 @@ func TestResourceDataState(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Sets
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeSet,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &Schema{Type: TypeInt},
|
||||
Set: func(a interface{}) int {
|
||||
return a.(int)
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
State: &terraform.ResourceState{
|
||||
Attributes: map[string]string{
|
||||
"ports.#": "2",
|
||||
"ports.0": "100",
|
||||
"ports.1": "80",
|
||||
},
|
||||
},
|
||||
|
||||
Diff: nil,
|
||||
|
||||
Result: &terraform.ResourceState{
|
||||
Attributes: map[string]string{
|
||||
"ports.#": "2",
|
||||
"ports.0": "80",
|
||||
"ports.1": "100",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
|
|
|
@ -20,6 +20,7 @@ const (
|
|||
TypeString
|
||||
TypeList
|
||||
TypeMap
|
||||
TypeSet
|
||||
)
|
||||
|
||||
// Schema is used to describe the structure of a value.
|
||||
|
@ -43,18 +44,19 @@ type Schema struct {
|
|||
Computed bool
|
||||
ForceNew bool
|
||||
|
||||
// The following fields are only set for a TypeList Type.
|
||||
// The following fields are only set for a TypeList or TypeSet Type.
|
||||
//
|
||||
// Elem must be either a *Schema or a *Resource only if the Type is
|
||||
// TypeList, and represents what the element type is. If it is *Schema,
|
||||
// the element type is just a simple value. If it is *Resource, the
|
||||
// element type is a complex structure, potentially with its own lifecycle.
|
||||
//
|
||||
// Order defines a function to be called to order the elements in the
|
||||
// list. See SchemaOrderFunc for more info. If Order is set, then any
|
||||
// access of this list will result in the ordered list.
|
||||
Elem interface{}
|
||||
Order SchemaOrderFunc
|
||||
|
||||
// The follow fields are only valid for a TypeSet type.
|
||||
//
|
||||
// Set defines a function to determine the unique ID of an item so that
|
||||
// a proper set can be built.
|
||||
Set SchemaSetFunc
|
||||
|
||||
// ComputedWhen is a set of queries on the configuration. Whenever any
|
||||
// of these things is changed, it will require a recompute (this requires
|
||||
|
@ -62,9 +64,9 @@ type Schema struct {
|
|||
ComputedWhen []string
|
||||
}
|
||||
|
||||
// SchemaOrderFunc is the function used to compare two elements in a list
|
||||
// for ordering. It should return a boolean true if a is less than b.
|
||||
type SchemaOrderFunc func(a, b interface{}) bool
|
||||
// SchemaSetFunc is a function that must return a unique ID for the given
|
||||
// element. This unique ID is used to store the element in a hash.
|
||||
type SchemaSetFunc func(a interface{}) int
|
||||
|
||||
func (s *Schema) finalizeDiff(
|
||||
d *terraform.ResourceAttrDiff) *terraform.ResourceAttrDiff {
|
||||
|
@ -181,6 +183,11 @@ func (m schemaMap) InternalValidate() error {
|
|||
return fmt.Errorf("%s: Elem must be set for lists", k)
|
||||
}
|
||||
|
||||
// TODO: test
|
||||
if v.Set != nil {
|
||||
return fmt.Errorf("%s: Set can only be set for TypeSet", k)
|
||||
}
|
||||
|
||||
switch t := v.Elem.(type) {
|
||||
case *Resource:
|
||||
if err := t.InternalValidate(); err != nil {
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
package schema
|
||||
|
||||
// listSort implements sort.Interface to sort a list of []interface according
|
||||
// to a schema.
|
||||
type listSort struct {
|
||||
List []interface{}
|
||||
Schema *Schema
|
||||
}
|
||||
|
||||
func (s *listSort) Len() int {
|
||||
return len(s.List)
|
||||
}
|
||||
|
||||
func (s *listSort) Less(i, j int) bool {
|
||||
return s.Schema.Order(s.List[i], s.List[j])
|
||||
}
|
||||
|
||||
func (s *listSort) Swap(i, j int) {
|
||||
s.List[i], s.List[j] = s.List[j], s.List[i]
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestListSort_impl(t *testing.T) {
|
||||
var _ sort.Interface = new(listSort)
|
||||
}
|
||||
|
||||
func TestListSort(t *testing.T) {
|
||||
s := &listSort{
|
||||
List: []interface{}{5, 2, 1, 3, 4},
|
||||
Schema: &Schema{
|
||||
Order: func(a, b interface{}) bool {
|
||||
return a.(int) < b.(int)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
sort.Sort(s)
|
||||
|
||||
expected := []interface{}{1, 2, 3, 4, 5}
|
||||
if !reflect.DeepEqual(s.List, expected) {
|
||||
t.Fatalf("bad: %#v", s.List)
|
||||
}
|
||||
}
|
|
@ -323,11 +323,11 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
{
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeList,
|
||||
Type: TypeSet,
|
||||
Required: true,
|
||||
Elem: &Schema{Type: TypeInt},
|
||||
Order: func(a, b interface{}) bool {
|
||||
return a.(int) < b.(int)
|
||||
Set: func(a interface{}) int {
|
||||
return a.(int)
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Set is a set data structure that is returned for elements of type
|
||||
// TypeSet.
|
||||
type Set struct {
|
||||
F SchemaSetFunc
|
||||
|
||||
m map[int]interface{}
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// Add adds an item to the set if it isn't already in the set.
|
||||
func (s *Set) Add(item interface{}) {
|
||||
s.add(item)
|
||||
}
|
||||
|
||||
// List returns the elements of this set in slice format.
|
||||
//
|
||||
// The order of the returned elements is deterministic. Given the same
|
||||
// set, the order of this will always be the same.
|
||||
func (s *Set) List() []interface{}{
|
||||
result := make([]interface{}, len(s.m))
|
||||
for i, k := range s.listCode() {
|
||||
result[i] = s.m[k]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *Set) init() {
|
||||
s.m = make(map[int]interface{})
|
||||
}
|
||||
|
||||
func (s *Set) add(item interface{}) int {
|
||||
s.once.Do(s.init)
|
||||
|
||||
code := s.F(item)
|
||||
if _, ok := s.m[code]; !ok {
|
||||
s.m[code] = item
|
||||
}
|
||||
|
||||
return code
|
||||
}
|
||||
|
||||
func (s *Set) listCode() []int{
|
||||
// Sort the hash codes so the order of the list is deterministic
|
||||
keys := make([]int, 0, len(s.m))
|
||||
for k, _ := range s.m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Sort(sort.IntSlice(keys))
|
||||
return keys
|
||||
}
|
Loading…
Reference in New Issue