pulumi/pkg/resource/plugin/rpc.go
joeduffy 35aa6b7559 Rename pulumi/lumi to pulumi/pulumi-fabric
We are renaming Lumi to Pulumi Fabric.  This change simply renames the
pulumi/lumi repo to pulumi/pulumi-fabric, without the CLI tools and other
changes that will follow soon afterwards.
2017-08-02 09:25:22 -07:00

294 lines
11 KiB
Go

// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package plugin
import (
"reflect"
"sort"
"github.com/golang/glog"
structpb "github.com/golang/protobuf/ptypes/struct"
"github.com/pulumi/pulumi-fabric/pkg/resource"
"github.com/pulumi/pulumi-fabric/pkg/util/contract"
)
// MarshalOptions controls the marshaling of RPC structures.
type MarshalOptions struct {
SkipNulls bool // true to skip nulls altogether in the resulting map.
DisallowUnknowns bool // true if we are disallowing unknown values (results in assertion failures).
}
const (
// UnknownBoolValue is a sentinel indicating that a bool property's value is not known, because it depends on
// a computation with values whose values themselves are not yet known (e.g., dependent upon an output property).
UnknownBoolValue = "1c4a061d-8072-4f0a-a4cb-0ff528b18fe7"
// UnknownNumberValue is a sentinel indicating that a number property's value is not known, because it depends on
// a computation with values whose values themselves are not yet known (e.g., dependent upon an output property).
UnknownNumberValue = "3eeb2bf0-c639-47a8-9e75-3b44932eb421"
// UnknownStringValue is a sentinel indicating that a string property's value is not known, because it depends on
// a computation with values whose values themselves are not yet known (e.g., dependent upon an output property).
UnknownStringValue = "04da6b54-80e4-46f7-96ec-b56ff0331ba9"
// UnknownArrayValue is a sentinel indicating that an array property's value is not known, because it depends on
// a computation with values whose values themselves are not yet known (e.g., dependent upon an output property).
UnknownArrayValue = "6a19a0b0-7e62-4c92-b797-7f8e31da9cc2"
// UnknownAssetValue is a sentinel indicating that an asset property's value is not known, because it depends on
// a computation with values whose values themselves are not yet known (e.g., dependent upon an output property).
UnknownAssetValue = "030794c1-ac77-496b-92df-f27374a8bd58"
// UnknownArchiveValue is a sentinel indicating that an archive property's value is not known, because it depends
// on a computation with values whose values themselves are not yet known (e.g., dependent upon an output property).
UnknownArchiveValue = "e48ece36-62e2-4504-bad9-02848725956a"
// UnknownObjectValue is a sentinel indicating that an archive property's value is not known, because it depends
// on a computation with values whose values themselves are not yet known (e.g., dependent upon an output property).
UnknownObjectValue = "dd056dcd-154b-4c76-9bd3-c8f88648b5ff"
)
// MarshalProperties marshals a resource's property map as a "JSON-like" protobuf structure.
func MarshalProperties(props resource.PropertyMap, opts MarshalOptions) *structpb.Struct {
fields := make(map[string]*structpb.Value)
for _, key := range props.StableKeys() {
v := props[key]
glog.V(9).Infof("Marshaling property for RPC: %v=%v", key, v)
if v.IsOutput() {
glog.V(9).Infof("Skipping output property %v", key)
} else if opts.SkipNulls && v.IsNull() {
glog.V(9).Infof("Skipping null property %v (as requested)", key)
} else {
fields[string(key)] = MarshalPropertyValue(v, opts)
}
}
return &structpb.Struct{
Fields: fields,
}
}
// MarshalPropertyValue marshals a single resource property value into its "JSON-like" value representation.
func MarshalPropertyValue(v resource.PropertyValue, opts MarshalOptions) *structpb.Value {
if v.IsNull() {
return MarshalNull(opts)
} else if v.IsBool() {
return &structpb.Value{
Kind: &structpb.Value_BoolValue{
BoolValue: v.BoolValue(),
},
}
} else if v.IsNumber() {
return &structpb.Value{
Kind: &structpb.Value_NumberValue{
NumberValue: v.NumberValue(),
},
}
} else if v.IsString() {
return MarshalString(v.StringValue(), opts)
} else if v.IsArray() {
var elems []*structpb.Value
for _, elem := range v.ArrayValue() {
elems = append(elems, MarshalPropertyValue(elem, opts))
}
return &structpb.Value{
Kind: &structpb.Value_ListValue{
ListValue: &structpb.ListValue{Values: elems},
},
}
} else if v.IsAsset() {
return MarshalAsset(v.AssetValue(), opts)
} else if v.IsArchive() {
return MarshalArchive(v.ArchiveValue(), opts)
} else if v.IsObject() {
obj := MarshalProperties(v.ObjectValue(), opts)
return MarshalStruct(obj, opts)
} else if v.IsComputed() {
return marshalUnknownProperty(v.ComputedValue().Element, opts)
} else if v.IsOutput() {
// Note that at the moment we don't differentiate between computed and output properties on the wire. As
// a result, they will show up as computed on the other end. This distinction isn't currently interesting.
return marshalUnknownProperty(v.OutputValue().Element, opts)
}
contract.Failf("Unrecognized property value: %v (type=%v)", v.V, reflect.TypeOf(v.V))
return nil
}
// marshalUnknownProperty marshals an unknown property in a way that lets us recover its type on the other end.
func marshalUnknownProperty(elem resource.PropertyValue, opts MarshalOptions) *structpb.Value {
contract.Assertf(!opts.DisallowUnknowns, "Unexpected unknown value when opts.DisallowUnknowns")
// Normal cases, these get sentinels.
if elem.IsBool() {
return MarshalString(UnknownBoolValue, opts)
} else if elem.IsNumber() {
return MarshalString(UnknownNumberValue, opts)
} else if elem.IsString() {
return MarshalString(UnknownStringValue, opts)
} else if elem.IsArray() {
return MarshalString(UnknownArrayValue, opts)
} else if elem.IsAsset() {
return MarshalString(UnknownAssetValue, opts)
} else if elem.IsArchive() {
return MarshalString(UnknownArchiveValue, opts)
} else if elem.IsObject() {
return MarshalString(UnknownObjectValue, opts)
}
// If for some reason we end up with a recursive computed/output, just keep digging.
if elem.IsComputed() {
return marshalUnknownProperty(elem.ComputedValue().Element, opts)
} else if elem.IsOutput() {
return marshalUnknownProperty(elem.OutputValue().Element, opts)
}
// Finally, if a null, we can guess its value! (the one and only...)
if elem.IsNull() {
return MarshalNull(opts)
}
contract.Failf("Unexpected output/computed property element: %v", elem)
return nil
}
// UnmarshalProperties unmarshals a "JSON-like" protobuf structure into a new resource property map.
func UnmarshalProperties(props *structpb.Struct, opts MarshalOptions) resource.PropertyMap {
result := make(resource.PropertyMap)
// First sort the keys so we enumerate them in order (in case errors happen, we want determinism).
var keys []string
if props != nil {
for k := range props.Fields {
keys = append(keys, k)
}
sort.Strings(keys)
}
// And now unmarshal every field it into the map.
for _, key := range keys {
pk := resource.PropertyKey(key)
v := UnmarshalPropertyValue(props.Fields[key], opts)
glog.V(9).Infof("Unmarshaling property for RPC: %v=%v", key, v)
if opts.SkipNulls && v.IsNull() {
glog.V(9).Infof("Skipping unmarshaling of %v (it is null)", key)
} else {
result[pk] = v
}
}
return result
}
// UnmarshalPropertyValue unmarshals a single "JSON-like" value into a new property value.
func UnmarshalPropertyValue(v *structpb.Value, opts MarshalOptions) resource.PropertyValue {
contract.Assert(v != nil)
switch v.Kind.(type) {
case *structpb.Value_NullValue:
return resource.NewNullProperty()
case *structpb.Value_BoolValue:
return resource.NewBoolProperty(v.GetBoolValue())
case *structpb.Value_NumberValue:
return resource.NewNumberProperty(v.GetNumberValue())
case *structpb.Value_StringValue:
// If it's a string, it could be an unknown property, or just a regular string.
s := v.GetStringValue()
if unk, isunk := unmarshalUnknownPropertyValue(s, opts); isunk {
return unk
}
return resource.NewStringProperty(s)
case *structpb.Value_ListValue:
// If there's already an array, prefer to swap elements within it.
var elems []resource.PropertyValue
lst := v.GetListValue()
for i, elem := range lst.GetValues() {
if i == len(elems) {
elems = append(elems, resource.PropertyValue{})
}
contract.Assert(len(elems) > i)
elems[i] = UnmarshalPropertyValue(elem, opts)
}
return resource.NewArrayProperty(elems)
case *structpb.Value_StructValue:
// Start by unmarshaling.
obj := UnmarshalProperties(v.GetStructValue(), opts)
// Before returning it as an object, check to see if it's a known recoverable type.
objmap := obj.Mappable()
if asset, isasset := resource.DeserializeAsset(objmap); isasset {
return resource.NewAssetProperty(asset)
} else if archive, isarchive := resource.DeserializeArchive(objmap); isarchive {
return resource.NewArchiveProperty(archive)
}
return resource.NewObjectProperty(obj)
default:
contract.Failf("Unrecognized structpb value kind: %v", reflect.TypeOf(v.Kind))
return resource.NewNullProperty()
}
}
func unmarshalUnknownPropertyValue(s string, opts MarshalOptions) (resource.PropertyValue, bool) {
var elem resource.PropertyValue
var unknown bool
switch s {
case UnknownBoolValue:
elem, unknown = resource.NewBoolProperty(false), true
case UnknownNumberValue:
elem, unknown = resource.NewNumberProperty(0), true
case UnknownStringValue:
elem, unknown = resource.NewStringProperty(""), true
case UnknownArrayValue:
elem, unknown = resource.NewArrayProperty([]resource.PropertyValue{}), true
case UnknownAssetValue:
elem, unknown = resource.NewAssetProperty(resource.Asset{}), true
case UnknownArchiveValue:
elem, unknown = resource.NewArchiveProperty(resource.Archive{}), true
case UnknownObjectValue:
elem, unknown = resource.NewObjectProperty(make(resource.PropertyMap)), true
}
if unknown {
comp := resource.Computed{Element: elem}
return resource.NewComputedProperty(comp), true
}
return resource.PropertyValue{}, false
}
// MarshalNull marshals a nil to its protobuf form.
func MarshalNull(opts MarshalOptions) *structpb.Value {
return &structpb.Value{
Kind: &structpb.Value_NullValue{
NullValue: structpb.NullValue_NULL_VALUE,
},
}
}
// MarshalString marshals a string to its protobuf form.
func MarshalString(s string, opts MarshalOptions) *structpb.Value {
return &structpb.Value{
Kind: &structpb.Value_StringValue{
StringValue: s,
},
}
}
// MarshalStruct marshals a struct for use in a protobuf field where a value is expected.
func MarshalStruct(obj *structpb.Struct, opts MarshalOptions) *structpb.Value {
return &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: obj,
},
}
}
// MarshalAsset marshals an asset into its wire form for resource provider plugins.
func MarshalAsset(v resource.Asset, opts MarshalOptions) *structpb.Value {
// To marshal an asset, we need to first serialize it, and then marshal that.
sera := v.Serialize()
serap := resource.NewPropertyMapFromMap(sera)
return MarshalPropertyValue(resource.NewObjectProperty(serap), opts)
}
// MarshalArchive marshals an archive into its wire form for resource provider plugins.
func MarshalArchive(v resource.Archive, opts MarshalOptions) *structpb.Value {
// To marshal an archive, we need to first serialize it, and then marshal that.
sera := v.Serialize()
serap := resource.NewPropertyMapFromMap(sera)
return MarshalPropertyValue(resource.NewObjectProperty(serap), opts)
}