This change, part of pulumi/lumi#90, overhauls quite a bit of the core resource, planning, environments, and related areas. The biggest amount of movement comes from the splitting of pkg/resource into multiple sub-packages. This results in: - pkg/resource: just the core resource data structures. - pkg/resource/deployment: all planning and deployment logic. - pkg/resource/environment: all environment, configuration, and serialized checkpoint structures and logic. - pkg/resource/plugin: all dynamically loaded analyzer and provider logic, including the actual loading and RPC mechanisms. This also splits the resource abstraction up. We now have: - resource.Resource: a shared interface. - resource.Object: a resource that is connected to a live object that will periodically observe mutations due to ongoing evaluation of computations. Snapshots of its state may be taken; however, this is purely a "pre-planning" abstraction. - resource.State: a snapshot of a resource's state that is frozen. In other words, it is no longer connected to a live object. This is what will store provider outputs (ID and properties), and is what may be serialized into a deployment record. The branch is in a half-baked state as of this change; more changes are to come...
221 lines
7.7 KiB
Go
221 lines
7.7 KiB
Go
// Licensed to Pulumi Corporation ("Pulumi") under one or more
|
|
// contributor license agreements. See the NOTICE file distributed with
|
|
// this work for additional information regarding copyright ownership.
|
|
// Pulumi licenses this file to You under the Apache License, Version 2.0
|
|
// (the "License"); you may not use this file except in compliance with
|
|
// the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package plugin
|
|
|
|
import (
|
|
"reflect"
|
|
"sort"
|
|
|
|
structpb "github.com/golang/protobuf/ptypes/struct"
|
|
|
|
"github.com/pulumi/lumi/pkg/resource"
|
|
"github.com/pulumi/lumi/pkg/util/contract"
|
|
)
|
|
|
|
// MarshalOptions controls the marshaling of RPC structures.
|
|
type MarshalOptions struct {
|
|
OldURNs bool // true to permit old URNs in the properties (e.g., for pre-update).
|
|
RawResources bool // true to marshal resources "as-is"; often used when ID mappings aren't known yet.
|
|
}
|
|
|
|
// MarshalPropertiesWithUnknowns marshals a resource's property map as a "JSON-like" protobuf structure. Any URNs are
|
|
// replaced with their resource IDs during marshaling; it is an error to marshal a URN for a resource without an ID. A
|
|
// map of any unknown properties encountered during marshaling (latent values) is returned on the side; these values are
|
|
// marshaled using the default value in the returned structure and so this map is essential for interpreting results.
|
|
func MarshalPropertiesWithUnknowns(
|
|
ctx *Context, props resource.PropertyMap, opts MarshalOptions) (*structpb.Struct, map[string]bool) {
|
|
var unk map[string]bool
|
|
result := &structpb.Struct{
|
|
Fields: make(map[string]*structpb.Value),
|
|
}
|
|
for _, key := range resource.StablePropertyKeys(props) {
|
|
if v := props[key]; !v.IsOutput() {
|
|
mv, known := MarshalPropertyValue(ctx, props[key], opts)
|
|
if known {
|
|
result.Fields[string(key)] = mv
|
|
} else {
|
|
if unk == nil {
|
|
unk = make(map[string]bool)
|
|
}
|
|
unk[string(key)] = true // remember that this property was unknown, tainting this whole object.
|
|
}
|
|
}
|
|
}
|
|
return result, unk
|
|
}
|
|
|
|
// MarshalProperties performs ordinary marshaling of a resource's properties but then validates afterwards that all
|
|
// fields were known (in other words, no latent properties were encountered).
|
|
func MarshalProperties(ctx *Context, props resource.PropertyMap, opts MarshalOptions) *structpb.Struct {
|
|
pstr, unks := MarshalPropertiesWithUnknowns(ctx, props, opts)
|
|
contract.Assertf(unks == nil, "Unexpected unknown properties during final marshaling")
|
|
return pstr
|
|
}
|
|
|
|
// MarshalPropertyValue marshals a single resource property value into its "JSON-like" value representation. The
|
|
// boolean return value indicates whether the value was known (true) or unknown (false).
|
|
func MarshalPropertyValue(ctx *Context, v resource.PropertyValue, opts MarshalOptions) (*structpb.Value, bool) {
|
|
if v.IsNull() {
|
|
return &structpb.Value{
|
|
Kind: &structpb.Value_NullValue{
|
|
NullValue: structpb.NullValue_NULL_VALUE,
|
|
},
|
|
}, true
|
|
} else if v.IsBool() {
|
|
return &structpb.Value{
|
|
Kind: &structpb.Value_BoolValue{
|
|
BoolValue: v.BoolValue(),
|
|
},
|
|
}, true
|
|
} else if v.IsNumber() {
|
|
return &structpb.Value{
|
|
Kind: &structpb.Value_NumberValue{
|
|
NumberValue: v.NumberValue(),
|
|
},
|
|
}, true
|
|
} else if v.IsString() {
|
|
return &structpb.Value{
|
|
Kind: &structpb.Value_StringValue{
|
|
StringValue: v.StringValue(),
|
|
},
|
|
}, true
|
|
} else if v.IsArray() {
|
|
outcome := true
|
|
var elems []*structpb.Value
|
|
for _, elem := range v.ArrayValue() {
|
|
elemv, known := MarshalPropertyValue(ctx, elem, opts)
|
|
outcome = outcome && known
|
|
elems = append(elems, elemv)
|
|
}
|
|
return &structpb.Value{
|
|
Kind: &structpb.Value_ListValue{
|
|
ListValue: &structpb.ListValue{Values: elems},
|
|
},
|
|
}, outcome
|
|
} else if v.IsObject() {
|
|
obj, unks := MarshalPropertiesWithUnknowns(ctx, v.ObjectValue(), opts)
|
|
return &structpb.Value{
|
|
Kind: &structpb.Value_StructValue{
|
|
StructValue: obj,
|
|
},
|
|
}, unks == nil
|
|
} else if v.IsComputed() {
|
|
e := v.ComputedValue().Element
|
|
contract.Assert(!e.IsComputed())
|
|
w, known := MarshalPropertyValue(ctx, e, opts)
|
|
contract.Assert(known)
|
|
return w, false
|
|
} else if v.IsOutput() {
|
|
e := v.OutputValue().Element
|
|
contract.Assert(!e.IsComputed())
|
|
w, known := MarshalPropertyValue(ctx, e, opts)
|
|
contract.Assert(known)
|
|
return w, false
|
|
}
|
|
|
|
contract.Failf("Unrecognized property value: %v (type=%v)", v.V, reflect.TypeOf(v.V))
|
|
return nil, true
|
|
}
|
|
|
|
// UnmarshalProperties unmarshals a "JSON-like" protobuf structure into a new resource property map.
|
|
func UnmarshalProperties(ctx *Context, props *structpb.Struct, opts MarshalOptions) resource.PropertyMap {
|
|
result := make(resource.PropertyMap)
|
|
if props != nil {
|
|
UnmarshalPropertiesInto(ctx, props, result, opts)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// UnmarshalPropertiesInto unmarshals a "JSON-like" protobuf structure into an existing resource property map.
|
|
func UnmarshalPropertiesInto(ctx *Context, props *structpb.Struct, t resource.PropertyMap, opts MarshalOptions) {
|
|
contract.Assert(props != nil)
|
|
|
|
// First sort the keys so we enumerate them in order (in case errors happen, we want determinism).
|
|
var keys []string
|
|
for k := range props.Fields {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
|
|
// And now unmarshal every field it into the map.
|
|
for _, k := range keys {
|
|
pk := resource.PropertyKey(k)
|
|
v := t[pk]
|
|
UnmarshalPropertyValueInto(ctx, props.Fields[k], &v, opts)
|
|
contract.Assert(!v.IsComputed())
|
|
t[pk] = v
|
|
}
|
|
}
|
|
|
|
// UnmarshalPropertyValue unmarshals a single "JSON-like" value into a new property value.
|
|
func UnmarshalPropertyValue(ctx *Context, v *structpb.Value, opts MarshalOptions) resource.PropertyValue {
|
|
var result resource.PropertyValue
|
|
UnmarshalPropertyValueInto(ctx, v, &result, opts)
|
|
return result
|
|
}
|
|
|
|
// UnmarshalPropertyValueInto unmarshals a single "JSON-like" value into an existing property value slot. The existing
|
|
// slot may be used to drive transformations, if necessary, such as recovering resource URNs on the receiver side.
|
|
func UnmarshalPropertyValueInto(ctx *Context, v *structpb.Value, t *resource.PropertyValue, opts MarshalOptions) {
|
|
contract.Assert(v != nil)
|
|
|
|
switch v.Kind.(type) {
|
|
case *structpb.Value_NullValue:
|
|
contract.Assert(t.CanNull())
|
|
*t = resource.NewNullProperty()
|
|
case *structpb.Value_BoolValue:
|
|
contract.Assert(t.CanBool())
|
|
*t = resource.NewBoolProperty(v.GetBoolValue())
|
|
case *structpb.Value_NumberValue:
|
|
contract.Assert(t.CanNumber())
|
|
*t = resource.NewNumberProperty(v.GetNumberValue())
|
|
case *structpb.Value_StringValue:
|
|
*t = resource.NewStringProperty(v.GetStringValue())
|
|
case *structpb.Value_ListValue:
|
|
contract.Assert(t.CanArray())
|
|
|
|
// If there's already an array, prefer to swap elements within it.
|
|
var elems []resource.PropertyValue
|
|
if t.IsArray() {
|
|
elems = t.ArrayValue()
|
|
}
|
|
|
|
lst := v.GetListValue()
|
|
for i, elem := range lst.GetValues() {
|
|
if i == len(elems) {
|
|
elems = append(elems, resource.PropertyValue{})
|
|
}
|
|
contract.Assert(len(elems) > i)
|
|
UnmarshalPropertyValueInto(ctx, elem, &elems[i], opts)
|
|
}
|
|
*t = resource.NewArrayProperty(elems)
|
|
case *structpb.Value_StructValue:
|
|
contract.Assert(t.CanObject())
|
|
|
|
// If there's already an object, prefer to swap existing properties.
|
|
var obj resource.PropertyMap
|
|
if t.IsObject() {
|
|
obj = t.ObjectValue()
|
|
} else {
|
|
obj = make(resource.PropertyMap)
|
|
*t = resource.NewObjectProperty(obj)
|
|
}
|
|
|
|
UnmarshalPropertiesInto(ctx, v.GetStructValue(), obj, opts)
|
|
default:
|
|
contract.Failf("Unrecognized structpb value kind: %v", reflect.TypeOf(v.Kind))
|
|
}
|
|
}
|