mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Merge pull request #211 from hashicorp/f-resource-sets
helper/schema: TypeSet - Sets!
This commit is contained in:
commit
6530a0ba2e
@ -153,13 +153,14 @@ func (d *ResourceData) getChange(
|
|||||||
key string,
|
key string,
|
||||||
oldLevel getSource,
|
oldLevel getSource,
|
||||||
newLevel getSource) (interface{}, interface{}) {
|
newLevel getSource) (interface{}, interface{}) {
|
||||||
var parts []string
|
var parts, parts2 []string
|
||||||
if key != "" {
|
if key != "" {
|
||||||
parts = strings.Split(key, ".")
|
parts = strings.Split(key, ".")
|
||||||
|
parts2 = strings.Split(key, ".")
|
||||||
}
|
}
|
||||||
|
|
||||||
o := d.getObject("", parts, d.schema, oldLevel)
|
o := d.getObject("", parts, d.schema, oldLevel)
|
||||||
n := d.getObject("", parts, d.schema, newLevel)
|
n := d.getObject("", parts2, d.schema, newLevel)
|
||||||
return o, n
|
return o, n
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,11 +174,91 @@ func (d *ResourceData) get(
|
|||||||
return d.getList(k, parts, schema, source)
|
return d.getList(k, parts, schema, source)
|
||||||
case TypeMap:
|
case TypeMap:
|
||||||
return d.getMap(k, parts, schema, source)
|
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)
|
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{} {
|
||||||
|
s := &Set{F: schema.Set}
|
||||||
|
raw := d.getList(k, nil, schema, source)
|
||||||
|
if raw == nil {
|
||||||
|
if len(parts) > 0 {
|
||||||
|
return d.getList(k, parts, schema, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
list := raw.([]interface{})
|
||||||
|
if len(list) == 0 {
|
||||||
|
if len(parts) > 0 {
|
||||||
|
return d.getList(k, parts, schema, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
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(
|
func (d *ResourceData) getMap(
|
||||||
k string,
|
k string,
|
||||||
parts []string,
|
parts []string,
|
||||||
@ -241,6 +322,11 @@ func (d *ResourceData) getMap(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we're requesting a specific element, return that
|
||||||
|
if len(parts) > 0 {
|
||||||
|
return result[parts[0]]
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,11 +422,13 @@ func (d *ResourceData) getPrimitive(
|
|||||||
if err := mapstructure.WeakDecode(v, &result); err != nil {
|
if err := mapstructure.WeakDecode(v, &result); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resultSet = true
|
||||||
} else {
|
} else {
|
||||||
result = ""
|
result = ""
|
||||||
|
resultSet = false
|
||||||
}
|
}
|
||||||
|
|
||||||
resultSet = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.diff != nil && source >= getSourceDiff {
|
if d.diff != nil && source >= getSourceDiff {
|
||||||
@ -403,8 +491,16 @@ func (d *ResourceData) set(
|
|||||||
return d.setList(k, parts, schema, value)
|
return d.setList(k, parts, schema, value)
|
||||||
case TypeMap:
|
case TypeMap:
|
||||||
return d.setMapValue(k, parts, schema, value)
|
return d.setMapValue(k, parts, schema, value)
|
||||||
default:
|
case TypeSet:
|
||||||
|
return d.setSet(k, parts, schema, value)
|
||||||
|
case TypeBool:
|
||||||
|
fallthrough
|
||||||
|
case TypeInt:
|
||||||
|
fallthrough
|
||||||
|
case TypeString:
|
||||||
return d.setPrimitive(k, schema, value)
|
return d.setPrimitive(k, schema, value)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("%s: unknown type %s", k, schema.Type))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -584,6 +680,22 @@ func (d *ResourceData) setPrimitive(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *ResourceData) setSet(
|
||||||
|
k string,
|
||||||
|
parts []string,
|
||||||
|
schema *Schema,
|
||||||
|
value interface{}) error {
|
||||||
|
if len(parts) > 0 {
|
||||||
|
return fmt.Errorf("%s: can only set the full set, not elements", k)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, ok := value.(*Set); ok {
|
||||||
|
value = s.List()
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.setList(k, nil, schema, value)
|
||||||
|
}
|
||||||
|
|
||||||
func (d *ResourceData) stateList(
|
func (d *ResourceData) stateList(
|
||||||
prefix string,
|
prefix string,
|
||||||
schema *Schema) map[string]string {
|
schema *Schema) map[string]string {
|
||||||
@ -657,7 +769,7 @@ func (d *ResourceData) stateObject(
|
|||||||
func (d *ResourceData) statePrimitive(
|
func (d *ResourceData) statePrimitive(
|
||||||
prefix string,
|
prefix string,
|
||||||
schema *Schema) map[string]string {
|
schema *Schema) map[string]string {
|
||||||
v := d.getPrimitive(prefix, nil, schema, getSourceSet)
|
v := d.Get(prefix)
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -679,6 +791,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(
|
func (d *ResourceData) stateSingle(
|
||||||
prefix string,
|
prefix string,
|
||||||
schema *Schema) map[string]string {
|
schema *Schema) map[string]string {
|
||||||
@ -687,7 +830,15 @@ func (d *ResourceData) stateSingle(
|
|||||||
return d.stateList(prefix, schema)
|
return d.stateList(prefix, schema)
|
||||||
case TypeMap:
|
case TypeMap:
|
||||||
return d.stateMap(prefix, schema)
|
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)
|
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{}{},
|
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 {
|
for i, tc := range cases {
|
||||||
@ -453,6 +481,10 @@ func TestResourceDataGet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
v := d.Get(tc.Key)
|
v := d.Get(tc.Key)
|
||||||
|
if s, ok := v.(*Set); ok {
|
||||||
|
v = s.List()
|
||||||
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(v, tc.Value) {
|
if !reflect.DeepEqual(v, tc.Value) {
|
||||||
t.Fatalf("Bad: %d\n\n%#v", i, v)
|
t.Fatalf("Bad: %d\n\n%#v", i, v)
|
||||||
}
|
}
|
||||||
@ -1023,6 +1055,101 @@ func TestResourceDataSet(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Set, with list
|
||||||
|
{
|
||||||
|
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.#": "3",
|
||||||
|
"ports.0": "100",
|
||||||
|
"ports.1": "80",
|
||||||
|
"ports.2": "80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Key: "ports",
|
||||||
|
Value: []interface{}{100, 125, 125},
|
||||||
|
|
||||||
|
GetKey: "ports",
|
||||||
|
GetValue: []interface{}{100, 125},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Set, with Set
|
||||||
|
{
|
||||||
|
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.#": "3",
|
||||||
|
"ports.0": "100",
|
||||||
|
"ports.1": "80",
|
||||||
|
"ports.2": "80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Key: "ports",
|
||||||
|
Value: &Set{
|
||||||
|
m: map[int]interface{}{
|
||||||
|
1: 1,
|
||||||
|
2: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
GetKey: "ports",
|
||||||
|
GetValue: []interface{}{1, 2},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Set single item
|
||||||
|
{
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Key: "ports.0",
|
||||||
|
Value: 256,
|
||||||
|
Err: true,
|
||||||
|
|
||||||
|
GetKey: "ports",
|
||||||
|
GetValue: []interface{}{80, 100},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tc := range cases {
|
for i, tc := range cases {
|
||||||
@ -1037,6 +1164,9 @@ func TestResourceDataSet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
v := d.Get(tc.GetKey)
|
v := d.Get(tc.GetKey)
|
||||||
|
if s, ok := v.(*Set); ok {
|
||||||
|
v = s.List()
|
||||||
|
}
|
||||||
if !reflect.DeepEqual(v, tc.GetValue) {
|
if !reflect.DeepEqual(v, tc.GetValue) {
|
||||||
t.Fatalf("Get Bad: %d\n\n%#v", i, v)
|
t.Fatalf("Get Bad: %d\n\n%#v", i, v)
|
||||||
}
|
}
|
||||||
@ -1346,6 +1476,40 @@ 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.#": "3",
|
||||||
|
"ports.0": "100",
|
||||||
|
"ports.1": "80",
|
||||||
|
"ports.2": "80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Diff: nil,
|
||||||
|
|
||||||
|
Result: &terraform.ResourceState{
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"ports.#": "2",
|
||||||
|
"ports.0": "80",
|
||||||
|
"ports.1": "100",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tc := range cases {
|
for i, tc := range cases {
|
||||||
|
@ -20,6 +20,7 @@ const (
|
|||||||
TypeString
|
TypeString
|
||||||
TypeList
|
TypeList
|
||||||
TypeMap
|
TypeMap
|
||||||
|
TypeSet
|
||||||
)
|
)
|
||||||
|
|
||||||
// Schema is used to describe the structure of a value.
|
// Schema is used to describe the structure of a value.
|
||||||
@ -43,18 +44,19 @@ type Schema struct {
|
|||||||
Computed bool
|
Computed bool
|
||||||
ForceNew 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
|
// 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,
|
// 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
|
// the element type is just a simple value. If it is *Resource, the
|
||||||
// element type is a complex structure, potentially with its own lifecycle.
|
// element type is a complex structure, potentially with its own lifecycle.
|
||||||
|
Elem interface{}
|
||||||
|
|
||||||
|
// The follow fields are only valid for a TypeSet type.
|
||||||
//
|
//
|
||||||
// Order defines a function to be called to order the elements in the
|
// Set defines a function to determine the unique ID of an item so that
|
||||||
// list. See SchemaOrderFunc for more info. If Order is set, then any
|
// a proper set can be built.
|
||||||
// access of this list will result in the ordered list.
|
Set SchemaSetFunc
|
||||||
Elem interface{}
|
|
||||||
Order SchemaOrderFunc
|
|
||||||
|
|
||||||
// ComputedWhen is a set of queries on the configuration. Whenever any
|
// ComputedWhen is a set of queries on the configuration. Whenever any
|
||||||
// of these things is changed, it will require a recompute (this requires
|
// of these things is changed, it will require a recompute (this requires
|
||||||
@ -62,9 +64,9 @@ type Schema struct {
|
|||||||
ComputedWhen []string
|
ComputedWhen []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// SchemaOrderFunc is the function used to compare two elements in a list
|
// SchemaSetFunc is a function that must return a unique ID for the given
|
||||||
// for ordering. It should return a boolean true if a is less than b.
|
// element. This unique ID is used to store the element in a hash.
|
||||||
type SchemaOrderFunc func(a, b interface{}) bool
|
type SchemaSetFunc func(a interface{}) int
|
||||||
|
|
||||||
func (s *Schema) finalizeDiff(
|
func (s *Schema) finalizeDiff(
|
||||||
d *terraform.ResourceAttrDiff) *terraform.ResourceAttrDiff {
|
d *terraform.ResourceAttrDiff) *terraform.ResourceAttrDiff {
|
||||||
@ -176,11 +178,17 @@ func (m schemaMap) InternalValidate() error {
|
|||||||
return fmt.Errorf("%s: ComputedWhen can only be set with Computed", k)
|
return fmt.Errorf("%s: ComputedWhen can only be set with Computed", k)
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.Type == TypeList {
|
if v.Type == TypeList || v.Type == TypeSet {
|
||||||
if v.Elem == nil {
|
if v.Elem == nil {
|
||||||
return fmt.Errorf("%s: Elem must be set for lists", k)
|
return fmt.Errorf("%s: Elem must be set for lists", k)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v.Type == TypeList && v.Set != nil {
|
||||||
|
return fmt.Errorf("%s: Set can only be set for TypeSet", k)
|
||||||
|
} else if v.Type == TypeSet && v.Set == nil {
|
||||||
|
return fmt.Errorf("%s: Set must be set", k)
|
||||||
|
}
|
||||||
|
|
||||||
switch t := v.Elem.(type) {
|
switch t := v.Elem.(type) {
|
||||||
case *Resource:
|
case *Resource:
|
||||||
if err := t.InternalValidate(); err != nil {
|
if err := t.InternalValidate(); err != nil {
|
||||||
@ -216,6 +224,8 @@ func (m schemaMap) diff(
|
|||||||
err = m.diffList(k, schema, diff, d)
|
err = m.diffList(k, schema, diff, d)
|
||||||
case TypeMap:
|
case TypeMap:
|
||||||
err = m.diffMap(k, schema, diff, d)
|
err = m.diffMap(k, schema, diff, d)
|
||||||
|
case TypeSet:
|
||||||
|
err = m.diffSet(k, schema, diff, d)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("%s: unknown type %s", k, schema.Type)
|
err = fmt.Errorf("%s: unknown type %s", k, schema.Type)
|
||||||
}
|
}
|
||||||
@ -229,6 +239,12 @@ func (m schemaMap) diffList(
|
|||||||
diff *terraform.ResourceDiff,
|
diff *terraform.ResourceDiff,
|
||||||
d *ResourceData) error {
|
d *ResourceData) error {
|
||||||
o, n, _ := d.diffChange(k)
|
o, n, _ := d.diffChange(k)
|
||||||
|
if s, ok := o.(*Set); ok {
|
||||||
|
o = s.List()
|
||||||
|
}
|
||||||
|
if s, ok := n.(*Set); ok {
|
||||||
|
n = s.List()
|
||||||
|
}
|
||||||
os := o.([]interface{})
|
os := o.([]interface{})
|
||||||
vs := n.([]interface{})
|
vs := n.([]interface{})
|
||||||
|
|
||||||
@ -341,6 +357,14 @@ func (m schemaMap) diffMap(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m schemaMap) diffSet(
|
||||||
|
k string,
|
||||||
|
schema *Schema,
|
||||||
|
diff *terraform.ResourceDiff,
|
||||||
|
d *ResourceData) error {
|
||||||
|
return m.diffList(k, schema, diff, d)
|
||||||
|
}
|
||||||
|
|
||||||
func (m schemaMap) diffString(
|
func (m schemaMap) diffString(
|
||||||
k string,
|
k string,
|
||||||
schema *Schema,
|
schema *Schema,
|
||||||
@ -363,9 +387,15 @@ func (m schemaMap) diffString(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removed := false
|
||||||
|
if o != nil && n == nil {
|
||||||
|
removed = true
|
||||||
|
}
|
||||||
|
|
||||||
diff.Attributes[k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{
|
diff.Attributes[k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{
|
||||||
Old: os,
|
Old: os,
|
||||||
New: ns,
|
New: ns,
|
||||||
|
NewRemoved: removed,
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return 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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -310,7 +310,7 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||||||
Diff: &terraform.ResourceDiff{
|
Diff: &terraform.ResourceDiff{
|
||||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||||
"ports.#": &terraform.ResourceAttrDiff{
|
"ports.#": &terraform.ResourceAttrDiff{
|
||||||
Old: "",
|
Old: "",
|
||||||
NewComputed: true,
|
NewComputed: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -320,14 +320,17 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
* Set
|
||||||
|
*/
|
||||||
|
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ports": &Schema{
|
"ports": &Schema{
|
||||||
Type: TypeList,
|
Type: TypeSet,
|
||||||
Required: true,
|
Required: true,
|
||||||
Elem: &Schema{Type: TypeInt},
|
Elem: &Schema{Type: TypeInt},
|
||||||
Order: func(a, b interface{}) bool {
|
Set: func(a interface{}) int {
|
||||||
return a.(int) < b.(int)
|
return a.(int)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -361,7 +364,88 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||||||
|
|
||||||
Err: false,
|
Err: false,
|
||||||
},
|
},
|
||||||
*/
|
|
||||||
|
{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"ports": &Schema{
|
||||||
|
Type: TypeSet,
|
||||||
|
Required: true,
|
||||||
|
Elem: &Schema{Type: TypeInt},
|
||||||
|
Set: func(a interface{}) int {
|
||||||
|
return a.(int)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
State: &terraform.ResourceState{
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"ports.#": "2",
|
||||||
|
"ports.0": "2",
|
||||||
|
"ports.1": "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"ports": []interface{}{5, 2, 1},
|
||||||
|
},
|
||||||
|
|
||||||
|
Diff: &terraform.ResourceDiff{
|
||||||
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||||
|
"ports.#": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "2",
|
||||||
|
New: "3",
|
||||||
|
},
|
||||||
|
"ports.2": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: "5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Err: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"ports": &Schema{
|
||||||
|
Type: TypeSet,
|
||||||
|
Required: true,
|
||||||
|
Elem: &Schema{Type: TypeInt},
|
||||||
|
Set: func(a interface{}) int {
|
||||||
|
return a.(int)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
State: &terraform.ResourceState{
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"ports.#": "2",
|
||||||
|
"ports.0": "2",
|
||||||
|
"ports.1": "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Config: map[string]interface{}{},
|
||||||
|
|
||||||
|
Diff: &terraform.ResourceDiff{
|
||||||
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||||
|
"ports.#": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "2",
|
||||||
|
New: "0",
|
||||||
|
},
|
||||||
|
"ports.0": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "1",
|
||||||
|
NewRemoved: true,
|
||||||
|
},
|
||||||
|
"ports.1": &terraform.ResourceAttrDiff{
|
||||||
|
Old: "2",
|
||||||
|
NewRemoved: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Err: false,
|
||||||
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* List of structure decode
|
* List of structure decode
|
||||||
@ -700,6 +784,29 @@ func TestSchemaMap_InternalValidate(t *testing.T) {
|
|||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// List element with Set set
|
||||||
|
{
|
||||||
|
map[string]*Schema{
|
||||||
|
"foo": &Schema{
|
||||||
|
Type: TypeList,
|
||||||
|
Elem: &Schema{Type: TypeInt},
|
||||||
|
Set: func(interface{}) int { return 0 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Set element with no Set set
|
||||||
|
{
|
||||||
|
map[string]*Schema{
|
||||||
|
"foo": &Schema{
|
||||||
|
Type: TypeSet,
|
||||||
|
Elem: &Schema{Type: TypeInt},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
// Required but computed
|
// Required but computed
|
||||||
{
|
{
|
||||||
map[string]*Schema{
|
map[string]*Schema{
|
||||||
|
119
helper/schema/set.go
Normal file
119
helper/schema/set.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains checks if the set has the given item.
|
||||||
|
func (s *Set) Contains(item interface{}) bool {
|
||||||
|
_, ok := s.m[s.F(item)]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the amount of items in the set.
|
||||||
|
func (s *Set) Len() int {
|
||||||
|
return len(s.m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Differences performs a set difference of the two sets, returning
|
||||||
|
// a new third set that has only the elements unique to this set.
|
||||||
|
func (s *Set) Difference(other *Set) *Set {
|
||||||
|
result := &Set{F: s.F}
|
||||||
|
result.init()
|
||||||
|
|
||||||
|
for k, v := range s.m {
|
||||||
|
if _, ok := other.m[k]; !ok {
|
||||||
|
result.m[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intersection performs the set intersection of the two sets
|
||||||
|
// and returns a new third set.
|
||||||
|
func (s *Set) Intersection(other *Set) *Set {
|
||||||
|
result := &Set{F: s.F}
|
||||||
|
result.init()
|
||||||
|
|
||||||
|
for k, v := range s.m {
|
||||||
|
if _, ok := other.m[k]; ok {
|
||||||
|
result.m[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Union performs the set union of the two sets and returns a new third
|
||||||
|
// set.
|
||||||
|
func (s *Set) Union(other *Set) *Set {
|
||||||
|
result := &Set{F: s.F}
|
||||||
|
result.init()
|
||||||
|
|
||||||
|
for k, v := range s.m {
|
||||||
|
result.m[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range other.m {
|
||||||
|
result.m[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
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) index(item interface{}) int {
|
||||||
|
return sort.SearchInts(s.listCode(), s.F(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
86
helper/schema/set_test.go
Normal file
86
helper/schema/set_test.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSetAdd(t *testing.T) {
|
||||||
|
s := &Set{F: testSetInt}
|
||||||
|
s.Add(1)
|
||||||
|
s.Add(5)
|
||||||
|
s.Add(25)
|
||||||
|
|
||||||
|
expected := []interface{}{1, 5, 25}
|
||||||
|
actual := s.List()
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("bad: %#v", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetContains(t *testing.T) {
|
||||||
|
s := &Set{F: testSetInt}
|
||||||
|
s.Add(5)
|
||||||
|
|
||||||
|
if s.Contains(2) {
|
||||||
|
t.Fatal("should not contain")
|
||||||
|
}
|
||||||
|
if !s.Contains(5) {
|
||||||
|
t.Fatal("should contain")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetDifference(t *testing.T) {
|
||||||
|
s1 := &Set{F: testSetInt}
|
||||||
|
s2:= &Set{F: testSetInt}
|
||||||
|
|
||||||
|
s1.Add(1)
|
||||||
|
s1.Add(5)
|
||||||
|
|
||||||
|
s2.Add(5)
|
||||||
|
s2.Add(25)
|
||||||
|
|
||||||
|
expected := []interface{}{1}
|
||||||
|
actual := s1.Difference(s2).List()
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("bad: %#v", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetIntersection(t *testing.T) {
|
||||||
|
s1 := &Set{F: testSetInt}
|
||||||
|
s2:= &Set{F: testSetInt}
|
||||||
|
|
||||||
|
s1.Add(1)
|
||||||
|
s1.Add(5)
|
||||||
|
|
||||||
|
s2.Add(5)
|
||||||
|
s2.Add(25)
|
||||||
|
|
||||||
|
expected := []interface{}{5}
|
||||||
|
actual := s1.Intersection(s2).List()
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("bad: %#v", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetUnion(t *testing.T) {
|
||||||
|
s1 := &Set{F: testSetInt}
|
||||||
|
s2:= &Set{F: testSetInt}
|
||||||
|
|
||||||
|
s1.Add(1)
|
||||||
|
s1.Add(5)
|
||||||
|
|
||||||
|
s2.Add(5)
|
||||||
|
s2.Add(25)
|
||||||
|
|
||||||
|
expected := []interface{}{1, 5, 25}
|
||||||
|
actual := s1.Union(s2).List()
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("bad: %#v", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSetInt(v interface{}) int {
|
||||||
|
return v.(int)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user