Introduce a first class Resource PropertyValue

Wraps a URN, but as a placeholder for a resource that should be re-hydrated on demarshaling.
This commit is contained in:
Luke Hoban 2019-12-28 20:38:43 -08:00 committed by Pat Gavlin
parent 51adc500de
commit ddc222d236
6 changed files with 106 additions and 15 deletions

View file

@ -6,7 +6,7 @@ TODO:
- [ ] Support `CustomResource`s as inputs
- [ ] Support `provider`/`providers` opts (ProviderResource hydration)
- [ ] Support `parent` opts (hydration of arbitrary Resources, even if no proxy exists?)
- [ ] First class `Resource` on RPC (instead of string matching `urn:pulumi` )
- [x] First class `Resource` on RPC (instead of string matching `urn:pulumi` )
- [ ] Real multi-lang: replace `remote.construct` with an engine invoke (or RPC) that spwans
language host, loads the requested module, evals the requested constructor, returns back the URN.
- [ ] Support client runtime in Python (and .NET and Go)

View file

@ -370,6 +370,10 @@ func filterPropertyMap(propertyMap resource.PropertyMap, debug bool) resource.Pr
}
case resource.Secret:
return "[secret]"
case resource.Resource:
return resource.Resource{
Urn: filterPropertyValue(t.Urn),
}
case resource.Computed:
return resource.Computed{
Element: filterPropertyValue(t.Element),

View file

@ -543,6 +543,17 @@ func DeserializePropertyValue(v interface{}, dec config.Decrypter,
cachingCrypter.insert(prop.SecretValue(), plaintext, ciphertext)
}
return prop, nil
case resource.ResourceSig:
urn, ok := objmap["urn"].(string)
if !ok {
return resource.PropertyValue{}, errors.New("malformed resource value: missing urn")
}
ev, err := DeserializePropertyValue(urn, dec)
if err != nil {
return resource.PropertyValue{}, err
}
return ev, nil
default:
return resource.PropertyValue{}, errors.Errorf("unrecognized signature '%v' in property map", sig)
}

View file

@ -36,6 +36,7 @@ type MarshalOptions struct {
ComputeAssetHashes bool // true if we are computing missing asset hashes on the fly.
KeepSecrets bool // true if we are keeping secrets (otherwise we replace them with their underlying value).
RejectAssets bool // true if we should return errors on Asset and Archive values.
KeepResources bool // true if we are keeping resources (otherwise we replace them with their URN)
SkipInternalKeys bool // true to skip internal property keys (keys that start with "__") in the resulting map.
}
@ -161,6 +162,15 @@ func MarshalPropertyValue(v resource.PropertyValue, opts MarshalOptions) (*struc
"value": v.SecretValue().Element,
})
return MarshalPropertyValue(secret, opts)
} else if v.IsResource() {
if !opts.KeepResources {
return MarshalPropertyValue(v.ResourceValue().Urn, opts)
}
res := resource.NewObjectProperty(resource.PropertyMap{
resource.SigKey: resource.NewStringProperty(resource.ResourceSig),
"urn": v.ResourceValue().Urn,
})
return MarshalPropertyValue(res, opts)
}
contract.Failf("Unrecognized property value in RPC[%s]: %v (type=%v)", opts.Label, v.V, reflect.TypeOf(v.V))
@ -345,6 +355,16 @@ func UnmarshalPropertyValue(v *structpb.Value, opts MarshalOptions) (*resource.P
}
s := resource.MakeSecret(value)
return &s, nil
case resource.ResourceSig:
urn, ok := obj["urn"]
if !ok {
return nil, errors.New("malformed RPC resource: missing urn")
}
if !opts.KeepResources {
return &urn, nil
}
r := resource.NewResourceProperty(resource.Resource{Urn: urn})
return &r, nil
default:
return nil, errors.Errorf("unrecognized signature '%v' in property map", sig)
}

View file

@ -96,6 +96,10 @@ type Secret struct {
Element PropertyValue
}
type Resource struct {
Urn PropertyValue
}
type ReqError struct {
K PropertyKey
}
@ -189,6 +193,7 @@ func NewObjectProperty(v PropertyMap) PropertyValue { return PropertyValue{v}
func NewComputedProperty(v Computed) PropertyValue { return PropertyValue{v} }
func NewOutputProperty(v Output) PropertyValue { return PropertyValue{v} }
func NewSecretProperty(v *Secret) PropertyValue { return PropertyValue{v} }
func NewResourceProperty(v Resource) PropertyValue { return PropertyValue{v} }
func MakeComputed(v PropertyValue) PropertyValue {
return NewComputedProperty(Computed{Element: v})
@ -202,6 +207,10 @@ func MakeSecret(v PropertyValue) PropertyValue {
return NewSecretProperty(&Secret{Element: v})
}
func MakeResource(v PropertyValue) PropertyValue {
return NewResourceProperty(Resource{Urn: v})
}
// NewPropertyValue turns a value into a property value, provided it is of a legal "JSON-like" kind.
func NewPropertyValue(v interface{}) PropertyValue {
return NewPropertyValueRepl(v, nil, nil)
@ -255,6 +264,8 @@ func NewPropertyValueRepl(v interface{},
return NewOutputProperty(t)
case *Secret:
return NewSecretProperty(t)
case Resource:
return NewResourceProperty(t)
}
// Next, see if it's an array, slice, pointer or struct, and handle each accordingly.
@ -382,6 +393,9 @@ func (v PropertyValue) OutputValue() Output { return v.V.(Output) }
// SecretValue fetches the underlying secret value (panicking if it isn't a secret).
func (v PropertyValue) SecretValue() *Secret { return v.V.(*Secret) }
// ResourceValue fetches the underlying resource value (panicking if it isn't a resource).
func (v PropertyValue) ResourceValue() Resource { return v.V.(Resource) }
// IsNull returns true if the underlying value is a null.
func (v PropertyValue) IsNull() bool {
return v.V == nil
@ -447,6 +461,12 @@ func (v PropertyValue) IsSecret() bool {
return is
}
// IsResource returns true if the underlying value is a resource value.
func (v PropertyValue) IsResource() bool {
_, is := v.V.(Resource)
return is
}
// TypeString returns a type representation of the property value's holder type.
func (v PropertyValue) TypeString() string {
if v.IsNull() {
@ -471,6 +491,8 @@ func (v PropertyValue) TypeString() string {
return "output<" + v.OutputValue().Element.TypeString() + ">"
} else if v.IsSecret() {
return "secret<" + v.SecretValue().Element.TypeString() + ">"
} else if v.IsResource() {
return "resource"
}
contract.Failf("Unrecognized PropertyValue type")
return ""
@ -514,6 +536,8 @@ func (v PropertyValue) MapRepl(replk func(string) (string, bool),
return v.OutputValue()
} else if v.IsSecret() {
return v.SecretValue()
} else if v.IsResource() {
return v.ResourceValue()
}
contract.Assertf(v.IsObject(), "v is not Object '%v' instead", v.TypeString())
return v.ObjectValue().MapRepl(replk, replv)
@ -550,6 +574,9 @@ func HasSig(obj PropertyMap, match string) bool {
// SecretSig is the unique secret signature.
const SecretSig = "1b47061264138c4ac30d75fd1eb44270"
// ResourceSig is the unique resource signature.
const ResourceSig = "5cf8f73096256a8f31e491e813e4eb8e"
// IsInternalPropertyKey returns true if the given property key is an internal key that should not be displayed to
// users.
func IsInternalPropertyKey(key PropertyKey) bool {

View file

@ -179,18 +179,22 @@ export function resolveProperties(
const isSecret = isRpcSecret(value);
value = unwrapRpcSecret(value);
// If this value is a URN, create a proxy wrapper around it.
if (typeof value === "string" && value.startsWith("urn:pulumi:")) {
const urnParts = value.split("::");
const qualifiedType = urnParts[2];
const type = qualifiedType.split("$").pop()!;
const proxyConstructor = proxyConstructors.get(type);
if (proxyConstructor) {
const urnName = urnParts[3];
value = new proxyConstructor(urnName, {}, { urn: value });
}
log.debug(`Saw valid URN ${value} during deserialization, but no proxy constructor is registered for type ${type}.`);
}
// TODO: This was replaced with first-class `Resource` reference in RPC results, which can
// always be deserialized without guessing about string values. In case we find a problem
// with that, leaving this here for now.
//
// // If this value is a URN, create a proxy wrapper around it.
// if (typeof value === "string" && value.startsWith("urn:pulumi:")) {
// const urnParts = value.split("::");
// const qualifiedType = urnParts[2];
// const type = qualifiedType.split("$").pop()!;
// const proxyConstructor = proxyConstructors.get(type);
// if (proxyConstructor) {
// const urnName = urnParts[3];
// value = new proxyConstructor(urnName, {}, { urn: value });
// }
// log.debug(`Saw valid URN ${value} during deserialization, but no proxy constructor is registered for type ${type}.`);
// }
try {
// If the value the engine handed back is or contains an unknown value, the resolver will mark its value as
@ -235,6 +239,10 @@ export const specialArchiveSig = "0def7320c3a5731c473e5ecbe6d01bc7";
* specialSecretSig is a randomly assigned hash used to identify secrets in maps. See pkg/resource/properties.go.
*/
export const specialSecretSig = "1b47061264138c4ac30d75fd1eb44270";
/**
* specialResourceSig is a randomly assigned hash used to identify resources in maps. See pkg/resource/properties.go.
*/
export const specialResourceSig = "5cf8f73096256a8f31e491e813e4eb8e";
/**
* serializeProperty serializes properties deeply. This understands how to wait on any unresolved promises, as
@ -328,7 +336,11 @@ export async function serializeProperty(ctx: string, prop: Input<any>, dependent
}
dependentResources.add(prop);
return serializeProperty(`${ctx}.urn`, prop.urn, dependentResources);
const urn = await serializeProperty(`${ctx}.urn`, prop.urn, dependentResources);
return {
[specialSigKey]: specialResourceSig,
urn: urn,
};
}
if (ComponentResource.isInstance(prop)) {
@ -350,7 +362,11 @@ export async function serializeProperty(ctx: string, prop: Input<any>, dependent
log.debug(`Serialize property [${ctx}]: component resource urn`);
}
return serializeProperty(`${ctx}.urn`, prop.urn, dependentResources);
const urn = await serializeProperty(`${ctx}.urn`, prop.urn, dependentResources);
return {
[specialSigKey]: specialResourceSig,
urn: urn,
};
}
if (prop instanceof Array) {
@ -481,6 +497,19 @@ export function deserializeProperty(prop: any): any {
[specialSigKey]: specialSecretSig,
value: deserializeProperty(prop["value"]),
};
case specialResourceSig:
const urn = prop["urn"];
// If this value is a URN, create a proxy wrapper around it.
const urnParts = urn.split("::");
const qualifiedType = urnParts[2];
const type = qualifiedType.split("$").pop()!;
const proxyConstructor = proxyConstructors.get(type);
if (proxyConstructor) {
const urnName = urnParts[3];
return new proxyConstructor(urnName, {}, { urn });
}
log.debug(`Saw valid URN ${urn} during deserialization, but no proxy constructor is registered for type ${type}.`);
return urn;
default:
throw new Error(`Unrecognized signature '${sig}' when unmarshaling resource property`);
}