pulumi/pkg/resource/resource.go
2017-02-25 07:25:33 -08:00

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
}