2017-02-25 16:25:33 +01:00
|
|
|
// Copyright 2016 Pulumi, Inc. All rights reserved.
|
2017-02-20 20:18:47 +01:00
|
|
|
|
|
|
|
package resource
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
|
|
|
|
2017-02-25 16:25:33 +01:00
|
|
|
"github.com/pulumi/coconut/pkg/tokens"
|
2017-03-12 22:13:44 +01:00
|
|
|
"github.com/pulumi/coconut/pkg/util/contract"
|
|
|
|
"github.com/pulumi/coconut/pkg/util/mapper"
|
2017-02-20 20:18:47 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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) {
|
2017-02-20 22:27:31 +01:00
|
|
|
if v, has := m[k]; has && !v.IsNull() {
|
2017-02-20 20:18:47 +01:00
|
|
|
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) {
|
2017-02-20 22:27:31 +01:00
|
|
|
if v, has := m[k]; has && !v.IsNull() {
|
2017-02-20 20:18:47 +01:00
|
|
|
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) {
|
2017-02-20 22:27:31 +01:00
|
|
|
if v, has := m[k]; has && !v.IsNull() {
|
2017-02-20 20:18:47 +01:00
|
|
|
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) {
|
2017-02-20 22:27:31 +01:00
|
|
|
if v, has := m[k]; has && !v.IsNull() {
|
2017-02-20 20:18:47 +01:00
|
|
|
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) {
|
2017-02-20 22:27:31 +01:00
|
|
|
if v, has := m[k]; has && !v.IsNull() {
|
2017-02-20 20:18:47 +01:00
|
|
|
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) {
|
2017-02-20 22:27:31 +01:00
|
|
|
if v, has := m[k]; has && !v.IsNull() {
|
2017-02-20 20:18:47 +01:00
|
|
|
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) {
|
2017-02-20 22:27:31 +01:00
|
|
|
if v, has := m[k]; has && !v.IsNull() {
|
2017-02-20 20:18:47 +01:00
|
|
|
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.
|
2017-03-03 02:10:10 +01:00
|
|
|
func (m PropertyMap) ResourceOrErr(k PropertyKey, req bool) (*URN, error) {
|
2017-02-20 22:27:31 +01:00
|
|
|
if v, has := m[k]; has && !v.IsNull() {
|
2017-02-20 20:18:47 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-03-03 02:10:10 +01:00
|
|
|
// ReqResourceOrErr checks that the given property exists and has the type URN.
|
|
|
|
func (m PropertyMap) ReqResourceOrErr(k PropertyKey) (URN, error) {
|
2017-02-20 20:18:47 +01:00
|
|
|
r, err := m.ResourceOrErr(k, true)
|
|
|
|
if err != nil {
|
2017-03-03 02:10:10 +01:00
|
|
|
return URN(""), err
|
2017-02-20 20:18:47 +01:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2017-03-03 02:10:10 +01:00
|
|
|
// OptResourceOrErr checks that the given property has the type URN, if it exists.
|
|
|
|
func (m PropertyMap) OptResourceOrErr(k PropertyKey) (*URN, error) {
|
2017-02-20 20:18:47 +01:00
|
|
|
return m.ResourceOrErr(k, false)
|
|
|
|
}
|
|
|
|
|
2017-03-12 22:13:44 +01:00
|
|
|
// Mappable returns a mapper-compatible object map, suitable for deserialization into structures.
|
|
|
|
func (props PropertyMap) Mappable() mapper.Object {
|
|
|
|
obj := make(mapper.Object)
|
|
|
|
for _, k := range StablePropertyKeys(props) {
|
|
|
|
obj[string(k)] = props[k].Mappable()
|
|
|
|
}
|
|
|
|
return obj
|
|
|
|
}
|
|
|
|
|
2017-03-03 02:10:10 +01:00
|
|
|
// AllResources finds all resource URNs, transitively throughout the property map, and returns them.
|
|
|
|
func (props PropertyMap) AllResources() map[URN]bool {
|
|
|
|
URNs := make(map[URN]bool)
|
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 23:56:23 +01:00
|
|
|
for _, k := range StablePropertyKeys(props) {
|
|
|
|
for m, v := range props[k].AllResources() {
|
2017-03-03 02:10:10 +01:00
|
|
|
URNs[m] = v
|
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 23:56:23 +01:00
|
|
|
}
|
|
|
|
}
|
2017-03-03 02:10:10 +01:00
|
|
|
return URNs
|
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 23:56:23 +01:00
|
|
|
}
|
|
|
|
|
Support replacement IDs
This change introduces a new RPC function to the provider interface;
in pseudo-code:
UpdateImpact(id ID, t Type, olds PropertyMap, news PropertyMap)
(bool, PropertyMap, error)
Essentially, during the planning phase, we will consult each provider
about the nature of a proposed update. This update includes a set of
old properties and the new ones and, if the resource provider will need
to replace the property as a result of the update, it will return true;
in general, the PropertyMap will eventually contain a list of all
properties that will be modified as a result of the operation (see below).
The planning phase reacts to this by propagating the change to dependent
resources, so that they know that the ID will change (and so that they
can recalculate their own state accordingly, possibly leading to a ripple
effect). This ensures the overall DAG / schedule is ordered correctly.
This change is most of pulumi/coconut#105. The only missing piece
is to generalize replacing the "ID" property with replacing arbitrary
properties; there are hooks in here for this, but until pulumi/coconut#90
is addressed, it doesn't make sense to make much progress on this.
2017-03-01 18:08:53 +01:00
|
|
|
// ReplaceResources finds all resources and lets an updater function update them if necessary. This is often used
|
2017-03-03 02:10:10 +01:00
|
|
|
// during a "replacement"-style updated, to replace all URNs of a certain value with another.
|
|
|
|
func (props PropertyMap) ReplaceResources(updater func(URN) URN) PropertyMap {
|
Support replacement IDs
This change introduces a new RPC function to the provider interface;
in pseudo-code:
UpdateImpact(id ID, t Type, olds PropertyMap, news PropertyMap)
(bool, PropertyMap, error)
Essentially, during the planning phase, we will consult each provider
about the nature of a proposed update. This update includes a set of
old properties and the new ones and, if the resource provider will need
to replace the property as a result of the update, it will return true;
in general, the PropertyMap will eventually contain a list of all
properties that will be modified as a result of the operation (see below).
The planning phase reacts to this by propagating the change to dependent
resources, so that they know that the ID will change (and so that they
can recalculate their own state accordingly, possibly leading to a ripple
effect). This ensures the overall DAG / schedule is ordered correctly.
This change is most of pulumi/coconut#105. The only missing piece
is to generalize replacing the "ID" property with replacing arbitrary
properties; there are hooks in here for this, but until pulumi/coconut#90
is addressed, it doesn't make sense to make much progress on this.
2017-03-01 18:08:53 +01:00
|
|
|
result := make(PropertyMap)
|
|
|
|
for _, k := range StablePropertyKeys(props) {
|
|
|
|
result[k] = props[k].ReplaceResources(updater)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2017-02-20 20:18:47 +01:00
|
|
|
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} }
|
2017-03-03 02:10:10 +01:00
|
|
|
func NewPropertyResource(v URN) PropertyValue { return PropertyValue{v} }
|
2017-02-20 20:18:47 +01:00
|
|
|
|
|
|
|
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) }
|
2017-03-03 02:10:10 +01:00
|
|
|
func (v PropertyValue) ResourceValue() URN { return v.V.(URN) }
|
2017-02-20 20:18:47 +01:00
|
|
|
|
|
|
|
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 {
|
2017-03-03 02:10:10 +01:00
|
|
|
_, is := b.V.(URN)
|
2017-02-20 20:18:47 +01:00
|
|
|
return is
|
|
|
|
}
|
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 23:56:23 +01:00
|
|
|
|
2017-03-12 22:13:44 +01:00
|
|
|
// Mappable returns a mapper-compatible value, suitable for deserialization into structures.
|
|
|
|
func (v PropertyValue) Mappable() mapper.Value {
|
|
|
|
if v.IsNull() {
|
|
|
|
return nil
|
|
|
|
} else if v.IsBool() {
|
|
|
|
return v.BoolValue()
|
|
|
|
} else if v.IsNumber() {
|
|
|
|
return v.NumberValue()
|
|
|
|
} else if v.IsString() {
|
|
|
|
return v.StringValue()
|
|
|
|
} else if v.IsArray() {
|
|
|
|
var arr []mapper.Value
|
|
|
|
for _, e := range v.ArrayValue() {
|
|
|
|
arr = append(arr, e.Mappable())
|
|
|
|
}
|
|
|
|
return arr
|
|
|
|
} else {
|
|
|
|
contract.Assert(v.IsObject())
|
|
|
|
return v.ObjectValue().Mappable()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-03 02:10:10 +01:00
|
|
|
// AllResources finds all resource URNs, transitively throughout the property value, and returns them.
|
|
|
|
func (v PropertyValue) AllResources() map[URN]bool {
|
|
|
|
URNs := make(map[URN]bool)
|
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 23:56:23 +01:00
|
|
|
if v.IsResource() {
|
2017-03-03 02:10:10 +01:00
|
|
|
URNs[v.ResourceValue()] = true
|
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 23:56:23 +01:00
|
|
|
} else if v.IsArray() {
|
|
|
|
for _, elem := range v.ArrayValue() {
|
|
|
|
for m, v := range elem.AllResources() {
|
2017-03-03 02:10:10 +01:00
|
|
|
URNs[m] = v
|
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 23:56:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if v.IsObject() {
|
|
|
|
for m, v := range v.ObjectValue().AllResources() {
|
2017-03-03 02:10:10 +01:00
|
|
|
URNs[m] = v
|
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 23:56:23 +01:00
|
|
|
}
|
|
|
|
}
|
2017-03-03 02:10:10 +01:00
|
|
|
return URNs
|
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 23:56:23 +01:00
|
|
|
}
|
Support replacement IDs
This change introduces a new RPC function to the provider interface;
in pseudo-code:
UpdateImpact(id ID, t Type, olds PropertyMap, news PropertyMap)
(bool, PropertyMap, error)
Essentially, during the planning phase, we will consult each provider
about the nature of a proposed update. This update includes a set of
old properties and the new ones and, if the resource provider will need
to replace the property as a result of the update, it will return true;
in general, the PropertyMap will eventually contain a list of all
properties that will be modified as a result of the operation (see below).
The planning phase reacts to this by propagating the change to dependent
resources, so that they know that the ID will change (and so that they
can recalculate their own state accordingly, possibly leading to a ripple
effect). This ensures the overall DAG / schedule is ordered correctly.
This change is most of pulumi/coconut#105. The only missing piece
is to generalize replacing the "ID" property with replacing arbitrary
properties; there are hooks in here for this, but until pulumi/coconut#90
is addressed, it doesn't make sense to make much progress on this.
2017-03-01 18:08:53 +01:00
|
|
|
|
|
|
|
// ReplaceResources finds all resources and lets an updater function update them if necessary. This is often used
|
2017-03-03 02:10:10 +01:00
|
|
|
// during a "replacement"-style updated, to replace all URNs of a certain value with another.
|
|
|
|
func (v PropertyValue) ReplaceResources(updater func(URN) URN) PropertyValue {
|
Support replacement IDs
This change introduces a new RPC function to the provider interface;
in pseudo-code:
UpdateImpact(id ID, t Type, olds PropertyMap, news PropertyMap)
(bool, PropertyMap, error)
Essentially, during the planning phase, we will consult each provider
about the nature of a proposed update. This update includes a set of
old properties and the new ones and, if the resource provider will need
to replace the property as a result of the update, it will return true;
in general, the PropertyMap will eventually contain a list of all
properties that will be modified as a result of the operation (see below).
The planning phase reacts to this by propagating the change to dependent
resources, so that they know that the ID will change (and so that they
can recalculate their own state accordingly, possibly leading to a ripple
effect). This ensures the overall DAG / schedule is ordered correctly.
This change is most of pulumi/coconut#105. The only missing piece
is to generalize replacing the "ID" property with replacing arbitrary
properties; there are hooks in here for this, but until pulumi/coconut#90
is addressed, it doesn't make sense to make much progress on this.
2017-03-01 18:08:53 +01:00
|
|
|
if v.IsResource() {
|
|
|
|
m := v.ResourceValue()
|
|
|
|
return NewPropertyResource(updater(m))
|
|
|
|
} else if v.IsArray() {
|
|
|
|
arr := v.ArrayValue()
|
|
|
|
elems := make([]PropertyValue, len(arr))
|
|
|
|
for i, elem := range arr {
|
|
|
|
elems[i] = elem.ReplaceResources(updater)
|
|
|
|
}
|
2017-03-01 22:34:29 +01:00
|
|
|
return NewPropertyArray(elems)
|
Support replacement IDs
This change introduces a new RPC function to the provider interface;
in pseudo-code:
UpdateImpact(id ID, t Type, olds PropertyMap, news PropertyMap)
(bool, PropertyMap, error)
Essentially, during the planning phase, we will consult each provider
about the nature of a proposed update. This update includes a set of
old properties and the new ones and, if the resource provider will need
to replace the property as a result of the update, it will return true;
in general, the PropertyMap will eventually contain a list of all
properties that will be modified as a result of the operation (see below).
The planning phase reacts to this by propagating the change to dependent
resources, so that they know that the ID will change (and so that they
can recalculate their own state accordingly, possibly leading to a ripple
effect). This ensures the overall DAG / schedule is ordered correctly.
This change is most of pulumi/coconut#105. The only missing piece
is to generalize replacing the "ID" property with replacing arbitrary
properties; there are hooks in here for this, but until pulumi/coconut#90
is addressed, it doesn't make sense to make much progress on this.
2017-03-01 18:08:53 +01:00
|
|
|
} else if v.IsObject() {
|
|
|
|
rep := v.ObjectValue().ReplaceResources(updater)
|
|
|
|
return NewPropertyObject(rep)
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|