pulumi/pkg/resource/properties.go
joeduffy 86bfe5961d Implement updates
This change is a first whack at implementing updates.

Creation and deletion plans are pretty straightforward; we just take
a single graph, topologically sort it, and perform the operations in
the right order.  For creation, this is in dependency order (things
that are depended upon must be created before dependents); for deletion,
this is in reverse-dependency order (things that depend on others must
be deleted before dependencies).  These are just special cases of the more
general idea of performing DAG operations in dependency order.

Updates must work in terms of this more general notion.  For example:

* It is an error to delete a resource while another refers to it; thus,
  resources are deleted after deleting dependents, or after updating
  dependent properties that reference the resource to new values.

* It is an error to depend on a create a resource before it is created;
  thus, resources must be created before dependents are created, and/or
  before updates to existing resource properties that would cause them
  to refer to the new resource.

Of course, all of this is tangled up in a graph of dependencies.  As a
result, we must create a DAG of the dependencies between creates, updates,
and deletes, and then topologically sort this DAG, in order to determine
the proper order of update operations.

To do this, we slightly generalize the existing graph infrastructure,
while also specializing two kinds of graphs; the existing one becomes a
heapstate.ObjectGraph, while this new one is resource.planGraph (internal).
2017-02-23 14:56:23 -08:00

410 lines
12 KiB
Go

// Copyright 2016 Marapongo, Inc. All rights reserved.
package resource
import (
"fmt"
"reflect"
"github.com/marapongo/mu/pkg/tokens"
)
// PropertyKey is the name of a property.
type PropertyKey tokens.Name
// PropertyMap is a simple map keyed by property name with "JSON-like" values.
type PropertyMap map[PropertyKey]PropertyValue
// PropertyValue is the value of a property, limited to a select few types (see below).
type PropertyValue struct {
V interface{}
}
type ReqError struct {
K PropertyKey
}
func IsReqError(err error) bool {
_, isreq := err.(*ReqError)
return isreq
}
func (err *ReqError) Error() string {
return fmt.Sprintf("required property '%v' is missing", err.K)
}
// BoolOrErr checks that the given property has the type bool, issuing an error if not; req indicates if required.
func (m PropertyMap) BoolOrErr(k PropertyKey, req bool) (*bool, error) {
if v, has := m[k]; has && !v.IsNull() {
if !v.IsBool() {
return nil, fmt.Errorf("property '%v' is not a bool (%v)", k, reflect.TypeOf(v.V))
}
b := v.BoolValue()
return &b, nil
} else if req {
return nil, &ReqError{k}
}
return nil, nil
}
// NumberOrErr checks that the given property has the type float64, issuing an error if not; req indicates if required.
func (m PropertyMap) NumberOrErr(k PropertyKey, req bool) (*float64, error) {
if v, has := m[k]; has && !v.IsNull() {
if !v.IsNumber() {
return nil, fmt.Errorf("property '%v' is not a number (%v)", k, reflect.TypeOf(v.V))
}
n := v.NumberValue()
return &n, nil
} else if req {
return nil, &ReqError{k}
}
return nil, nil
}
// StringOrErr checks that the given property has the type string, issuing an error if not; req indicates if required.
func (m PropertyMap) StringOrErr(k PropertyKey, req bool) (*string, error) {
if v, has := m[k]; has && !v.IsNull() {
if !v.IsString() {
return nil, fmt.Errorf("property '%v' is not a string (%v)", k, reflect.TypeOf(v.V))
}
s := v.StringValue()
return &s, nil
} else if req {
return nil, &ReqError{k}
}
return nil, nil
}
// ArrayOrErr checks that the given property has the type array, issuing an error if not; req indicates if required.
func (m PropertyMap) ArrayOrErr(k PropertyKey, req bool) (*[]PropertyValue, error) {
if v, has := m[k]; has && !v.IsNull() {
if !v.IsArray() {
return nil, fmt.Errorf("property '%v' is not an array (%v)", k, reflect.TypeOf(v.V))
}
a := v.ArrayValue()
return &a, nil
} else if req {
return nil, &ReqError{k}
}
return nil, nil
}
// ObjectArrayOrErr ensures a property is an array of objects, issuing an error if not; req indicates if required.
func (m PropertyMap) ObjectArrayOrErr(k PropertyKey, req bool) (*[]PropertyMap, error) {
if v, has := m[k]; has && !v.IsNull() {
if !v.IsArray() {
return nil, fmt.Errorf("property '%v' is not an array (%v)", k, reflect.TypeOf(v.V))
}
a := v.ArrayValue()
var objs []PropertyMap
for i, e := range a {
if e.IsObject() {
objs = append(objs, e.ObjectValue())
} else {
return nil, fmt.Errorf(
"property '%v' array element %v is not an object (%v)", k, i, reflect.TypeOf(e))
}
}
return &objs, nil
} else if req {
return nil, &ReqError{k}
}
return nil, nil
}
// StringArrayOrErr ensures a property is an array of strings, issuing an error if not; req indicates if required.
func (m PropertyMap) StringArrayOrErr(k PropertyKey, req bool) (*[]string, error) {
if v, has := m[k]; has && !v.IsNull() {
if !v.IsArray() {
return nil, fmt.Errorf("property '%v' is not an array (%v)", k, reflect.TypeOf(v.V))
}
a := v.ArrayValue()
var strs []string
for i, e := range a {
if e.IsString() {
strs = append(strs, e.StringValue())
} else {
return nil, fmt.Errorf(
"property '%v' array element %v is not a string (%v)", k, i, reflect.TypeOf(e))
}
}
return &strs, nil
} else if req {
return nil, &ReqError{k}
}
return nil, nil
}
// ObjectOrErr checks that the given property is an object, issuing an error if not; req indicates if required.
func (m PropertyMap) ObjectOrErr(k PropertyKey, req bool) (*PropertyMap, error) {
if v, has := m[k]; has && !v.IsNull() {
if !v.IsObject() {
return nil, fmt.Errorf("property '%v' is not an object (%v)", k, reflect.TypeOf(v.V))
}
o := v.ObjectValue()
return &o, nil
} else if req {
return nil, &ReqError{k}
}
return nil, nil
}
// ResourceOrErr checks that the given property is a resource, issuing an error if not; req indicates if required.
func (m PropertyMap) ResourceOrErr(k PropertyKey, req bool) (*Moniker, error) {
if v, has := m[k]; has && !v.IsNull() {
if !v.IsResource() {
return nil, fmt.Errorf("property '%v' is not an object (%v)", k, reflect.TypeOf(v.V))
}
m := v.ResourceValue()
return &m, nil
} else if req {
return nil, &ReqError{k}
}
return nil, nil
}
// ReqBoolOrErr checks that the given property exists and has the type bool.
func (m PropertyMap) ReqBoolOrErr(k PropertyKey) (bool, error) {
b, err := m.BoolOrErr(k, true)
if err != nil {
return false, err
}
return *b, nil
}
// ReqNumberOrErr checks that the given property exists and has the type float64.
func (m PropertyMap) ReqNumberOrErr(k PropertyKey) (float64, error) {
n, err := m.NumberOrErr(k, true)
if err != nil {
return 0, err
}
return *n, nil
}
// ReqStringOrErr checks that the given property exists and has the type string.
func (m PropertyMap) ReqStringOrErr(k PropertyKey) (string, error) {
s, err := m.StringOrErr(k, true)
if err != nil {
return "", err
}
return *s, nil
}
// ReqArrayOrErr checks that the given property exists and has the type array.
func (m PropertyMap) ReqArrayOrErr(k PropertyKey) ([]PropertyValue, error) {
a, err := m.ArrayOrErr(k, true)
if err != nil {
return nil, err
}
return *a, nil
}
// ReqObjectArrayOrErr checks that the given property exists and has the type array of objects.
func (m PropertyMap) ReqObjectArrayOrErr(k PropertyKey) ([]PropertyMap, error) {
a, err := m.ObjectArrayOrErr(k, true)
if err != nil {
return nil, err
}
return *a, nil
}
// ReqStringArrayOrErr checks that the given property exists and has the type array of objects.
func (m PropertyMap) ReqStringArrayOrErr(k PropertyKey) ([]string, error) {
a, err := m.StringArrayOrErr(k, true)
if err != nil {
return nil, err
}
return *a, nil
}
// ReqObjectOrErr checks that the given property exists and has the type object.
func (m PropertyMap) ReqObjectOrErr(k PropertyKey) (PropertyMap, error) {
o, err := m.ObjectOrErr(k, true)
if err != nil {
return nil, err
}
return *o, nil
}
// ReqResourceOrErr checks that the given property exists and has the type moniker.
func (m PropertyMap) ReqResourceOrErr(k PropertyKey) (Moniker, error) {
r, err := m.ResourceOrErr(k, true)
if err != nil {
return Moniker(""), err
}
return *r, nil
}
// OptBoolOrErr checks that the given property has the type bool, if it exists.
func (m PropertyMap) OptBoolOrErr(k PropertyKey) (*bool, error) {
return m.BoolOrErr(k, false)
}
// OptNumberOrErr checks that the given property has the type float64, if it exists.
func (m PropertyMap) OptNumberOrErr(k PropertyKey) (*float64, error) {
return m.NumberOrErr(k, false)
}
// OptStringOrErr checks that the given property has the type string, if it exists.
func (m PropertyMap) OptStringOrErr(k PropertyKey) (*string, error) {
return m.StringOrErr(k, false)
}
// OptArrayOrErr checks that the given property has the type array, if it exists.
func (m PropertyMap) OptArrayOrErr(k PropertyKey) (*[]PropertyValue, error) {
return m.ArrayOrErr(k, false)
}
// OptObjectArrayOrErr checks that the given property has the type array of objects, if it exists.
func (m PropertyMap) OptObjectArrayOrErr(k PropertyKey) (*[]PropertyMap, error) {
return m.ObjectArrayOrErr(k, false)
}
// OptStringArrayOrErr checks that the given property has the type array of objects, if it exists.
func (m PropertyMap) OptStringArrayOrErr(k PropertyKey) (*[]string, error) {
return m.StringArrayOrErr(k, false)
}
// OptObjectOrErr checks that the given property has the type object, if it exists.
func (m PropertyMap) OptObjectOrErr(k PropertyKey) (*PropertyMap, error) {
return m.ObjectOrErr(k, false)
}
// OptResourceOrErr checks that the given property has the type moniker, if it exists.
func (m PropertyMap) OptResourceOrErr(k PropertyKey) (*Moniker, error) {
return m.ResourceOrErr(k, false)
}
// AllResources finds all resource monikers, transitively throughout the property map, and returns them.
func (props PropertyMap) AllResources() map[Moniker]bool {
monikers := make(map[Moniker]bool)
for _, k := range StablePropertyKeys(props) {
for m, v := range props[k].AllResources() {
monikers[m] = v
}
}
return monikers
}
// DeepEquals returns true if this property map is deeply equal to the other property map; and false otherwise.
func (props PropertyMap) DeepEquals(other PropertyMap) bool {
seen := make(map[PropertyKey]bool)
// If any in props either doesn't exist, or is of a different value, return false.
for _, k := range StablePropertyKeys(props) {
if p, has := other[k]; has {
if !props[k].DeepEquals(p) {
return false
}
} else {
return false
}
seen[k] = true
}
// If the other map has properties that this map doesn't have, return false.
for _, k := range StablePropertyKeys(other) {
if !seen[k] {
return false
}
}
return true
}
func NewPropertyNull() PropertyValue { return PropertyValue{nil} }
func NewPropertyBool(v bool) PropertyValue { return PropertyValue{v} }
func NewPropertyNumber(v float64) PropertyValue { return PropertyValue{v} }
func NewPropertyString(v string) PropertyValue { return PropertyValue{v} }
func NewPropertyArray(v []PropertyValue) PropertyValue { return PropertyValue{v} }
func NewPropertyObject(v PropertyMap) PropertyValue { return PropertyValue{v} }
func NewPropertyResource(v Moniker) PropertyValue { return PropertyValue{v} }
func (v PropertyValue) BoolValue() bool { return v.V.(bool) }
func (v PropertyValue) NumberValue() float64 { return v.V.(float64) }
func (v PropertyValue) StringValue() string { return v.V.(string) }
func (v PropertyValue) ArrayValue() []PropertyValue { return v.V.([]PropertyValue) }
func (v PropertyValue) ObjectValue() PropertyMap { return v.V.(PropertyMap) }
func (v PropertyValue) ResourceValue() Moniker { return v.V.(Moniker) }
func (b PropertyValue) IsNull() bool {
return b.V == nil
}
func (b PropertyValue) IsBool() bool {
_, is := b.V.(bool)
return is
}
func (b PropertyValue) IsNumber() bool {
_, is := b.V.(float64)
return is
}
func (b PropertyValue) IsString() bool {
_, is := b.V.(string)
return is
}
func (b PropertyValue) IsArray() bool {
_, is := b.V.([]PropertyValue)
return is
}
func (b PropertyValue) IsObject() bool {
_, is := b.V.(PropertyMap)
return is
}
func (b PropertyValue) IsResource() bool {
_, is := b.V.(Moniker)
return is
}
// AllResources finds all resource monikers, transitively throughout the property value, and returns them.
func (v PropertyValue) AllResources() map[Moniker]bool {
monikers := make(map[Moniker]bool)
if v.IsResource() {
monikers[v.ResourceValue()] = true
} else if v.IsArray() {
for _, elem := range v.ArrayValue() {
for m, v := range elem.AllResources() {
monikers[m] = v
}
}
} else if v.IsObject() {
for m, v := range v.ObjectValue().AllResources() {
monikers[m] = v
}
}
return monikers
}
// DeepEquals returns true if this property map is deeply equal to the other property map; and false otherwise.
func (v PropertyValue) DeepEquals(other PropertyValue) bool {
// Arrays are equal if they are both of the same size and elements are deeply equal.
if v.IsArray() {
if !other.IsArray() {
return false
}
va := v.ArrayValue()
oa := other.ArrayValue()
if len(va) != len(oa) {
return false
}
for i, elem := range va {
if !elem.DeepEquals(oa[i]) {
return false
}
}
return true
}
// Object values are equal if their contents are deeply equal.
if v.IsObject() {
if !other.IsObject() {
return false
}
vo := v.ObjectValue()
oa := other.ObjectValue()
return vo.DeepEquals(oa)
}
// For all other cases, primitives are equal if their values are equal.
return v.V == other.V
}