161 lines
6.2 KiB
Go
161 lines
6.2 KiB
Go
// Copyright 2016 Pulumi, Inc. All rights reserved.
|
|
|
|
package resource
|
|
|
|
import (
|
|
"reflect"
|
|
|
|
"github.com/golang/glog"
|
|
|
|
"github.com/pulumi/coconut/pkg/compiler/symbols"
|
|
"github.com/pulumi/coconut/pkg/compiler/types"
|
|
"github.com/pulumi/coconut/pkg/compiler/types/predef"
|
|
"github.com/pulumi/coconut/pkg/eval/heapstate"
|
|
"github.com/pulumi/coconut/pkg/eval/rt"
|
|
"github.com/pulumi/coconut/pkg/tokens"
|
|
"github.com/pulumi/coconut/pkg/util/contract"
|
|
)
|
|
|
|
// ID is a unique resource identifier; it is managed by the provider and is mostly opaque to Coconut.
|
|
type ID string
|
|
|
|
// Resource is an instance of a resource with an ID, type, and bag of state.
|
|
type Resource interface {
|
|
ID() ID // the resource's unique ID, assigned by the resource provider (or blank if uncreated).
|
|
Moniker() Moniker // the resource's object moniker, a human-friendly, unique name for the resource.
|
|
Type() tokens.Type // the resource's type.
|
|
Properties() PropertyMap // the resource's property map.
|
|
HasID() bool // returns true if the resource has been assigned an ID.
|
|
SetID(id ID) // assignes an ID to this resource, for those under creation.
|
|
HasMoniker() bool // returns true if the resource has been assigned moniker.
|
|
SetMoniker(m Moniker) // assignes a moniker to this resource, for those under creation.
|
|
}
|
|
|
|
// ResourceState is returned when an error has occurred during a resource provider operation. It indicates whether the
|
|
// operation could be rolled back cleanly (OK). If not, it means the resource was left in an indeterminate state.
|
|
type ResourceState int
|
|
|
|
const (
|
|
StateOK ResourceState = iota
|
|
StateUnknown
|
|
)
|
|
|
|
func IsResourceType(t symbols.Type) bool { return types.HasBaseName(t, predef.CocoStdlibResourceClass) }
|
|
func IsResourceVertex(v *heapstate.ObjectVertex) bool { return IsResourceType(v.Obj().Type()) }
|
|
|
|
type resource struct {
|
|
id ID // the resource's unique ID, assigned by the resource provider (or blank if uncreated).
|
|
moniker Moniker // the resource's object moniker, a human-friendly, unique name for the resource.
|
|
t tokens.Type // the resource's type.
|
|
properties PropertyMap // the resource's property map.
|
|
}
|
|
|
|
func (r *resource) ID() ID { return r.id }
|
|
func (r *resource) Moniker() Moniker { return r.moniker }
|
|
func (r *resource) Type() tokens.Type { return r.t }
|
|
func (r *resource) Properties() PropertyMap { return r.properties }
|
|
|
|
func (r *resource) HasID() bool { return (string(r.id) != "") }
|
|
func (r *resource) SetID(id ID) {
|
|
contract.Requiref(!r.HasID(), "id", "empty")
|
|
r.id = id
|
|
}
|
|
|
|
func (r *resource) HasMoniker() bool { return (string(r.moniker) != "") }
|
|
func (r *resource) SetMoniker(m Moniker) {
|
|
contract.Requiref(!r.HasMoniker(), "moniker", "empty")
|
|
r.moniker = m
|
|
}
|
|
|
|
// NewResource creates a new resource from the information provided.
|
|
func NewResource(id ID, moniker Moniker, t tokens.Type, properties PropertyMap) Resource {
|
|
return &resource{
|
|
id: id,
|
|
moniker: moniker,
|
|
t: t,
|
|
properties: properties,
|
|
}
|
|
}
|
|
|
|
// NewObjectResource creates a new resource object out of the runtime object provided. The context is used to resolve
|
|
// dependencies between resources and must contain all references that could be encountered.
|
|
func NewObjectResource(ctx *Context, obj *rt.Object) Resource {
|
|
t := obj.Type()
|
|
contract.Assert(IsResourceType(t))
|
|
|
|
// Extract the moniker. This must already exist.
|
|
m, hasm := ctx.ObjMks[obj]
|
|
contract.Assertf(!hasm, "Object already assigned a moniker '%v'; double allocation detected", m)
|
|
|
|
// Do a deep copy of the resource properties. This ensures property serializability.
|
|
props := cloneObject(ctx, obj)
|
|
|
|
// Finally allocate and return the resource object; note that ID is left blank until the provider assignes one.
|
|
return &resource{
|
|
t: t.TypeToken(),
|
|
properties: props,
|
|
}
|
|
}
|
|
|
|
// cloneObject creates a property map out of a runtime object. The result is fully serializable in the sense that it
|
|
// can be stored in a JSON or YAML file, serialized over an RPC interface, etc. In particular, any references to other
|
|
// resources are replaced with their moniker equivalents, which the runtime understands.
|
|
func cloneObject(ctx *Context, obj *rt.Object) PropertyMap {
|
|
contract.Assert(obj != nil)
|
|
src := obj.PropertyValues()
|
|
dest := make(PropertyMap)
|
|
for _, k := range rt.StablePropertyKeys(src) {
|
|
// TODO: detect cycles.
|
|
if v, ok := cloneObjectValue(ctx, src[k].Obj()); ok {
|
|
dest[PropertyKey(k)] = v
|
|
}
|
|
}
|
|
return dest
|
|
}
|
|
|
|
// cloneObjectValue creates a single property value out of a runtime object. It returns false if the property could not
|
|
// be stored in a property (e.g., it is a function or other unrecognized or unserializable runtime object).
|
|
func cloneObjectValue(ctx *Context, obj *rt.Object) (PropertyValue, bool) {
|
|
t := obj.Type()
|
|
if IsResourceType(t) {
|
|
// For resources, simply look up the moniker from the resource map.
|
|
m, hasm := ctx.ObjMks[obj]
|
|
contract.Assertf(hasm, "Missing object reference; possible out of order dependency walk")
|
|
return NewPropertyResource(m), true
|
|
}
|
|
|
|
switch t {
|
|
case types.Null:
|
|
return NewPropertyNull(), true
|
|
case types.Bool:
|
|
return NewPropertyBool(obj.BoolValue()), true
|
|
case types.Number:
|
|
return NewPropertyNumber(obj.NumberValue()), true
|
|
case types.String:
|
|
return NewPropertyString(obj.StringValue()), true
|
|
case types.Object, types.Dynamic:
|
|
obj := cloneObject(ctx, obj) // an object literal, clone it
|
|
return NewPropertyObject(obj), true
|
|
}
|
|
|
|
switch t.(type) {
|
|
case *symbols.ArrayType:
|
|
var result []PropertyValue
|
|
for _, e := range *obj.ArrayValue() {
|
|
if v, ok := cloneObjectValue(ctx, e.Obj()); ok {
|
|
result = append(result, v)
|
|
}
|
|
}
|
|
return NewPropertyArray(result), true
|
|
case *symbols.Class:
|
|
obj := cloneObject(ctx, obj) // a class, just deep clone it
|
|
return NewPropertyObject(obj), true
|
|
}
|
|
|
|
// TODO: handle symbols.MapType.
|
|
// TODO: it's unclear if we should do something more drastic here. There will always be unrecognized property
|
|
// kinds because objects contain things like constructors, methods, etc. But we may want to ratchet this a bit.
|
|
glog.V(5).Infof("Ignoring object value of type '%v': unrecognized kind %v", t, reflect.TypeOf(t))
|
|
return PropertyValue{}, false
|
|
}
|