[sdk/go] Marshal output values (#7958)

This change adds support for marshaling outputs as output values in the Go SDK.
This commit is contained in:
Justin Van Patten 2021-09-27 09:01:40 -07:00 committed by GitHub
parent 74dfa83de5
commit 9deb5ca0ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 865 additions and 146 deletions

View file

@ -1108,7 +1108,6 @@ func (p *provider) Construct(info ConstructInfo, typ tokens.Type, name tokens.QN
// To initially scope the use of this new feature, we only keep output values for
// Construct and Call (when the client accepts them).
KeepOutputValues: p.acceptOutputs,
DontSkipOutputs: true,
})
if err != nil {
return ConstructResult{}, err
@ -1363,7 +1362,6 @@ func (p *provider) Call(tok tokens.ModuleMember, args resource.PropertyMap, info
// To initially scope the use of this new feature, we only keep output values for
// Construct and Call (when the client accepts them).
KeepOutputValues: p.acceptOutputs,
DontSkipOutputs: true,
})
if err != nil {
return CallResult{}, err

View file

@ -40,7 +40,6 @@ type MarshalOptions struct {
KeepResources bool // true if we are keeping resoures (otherwise we return raw urn).
SkipInternalKeys bool // true to skip internal property keys (keys that start with "__") in the resulting map.
KeepOutputValues bool // true if we are keeping output values.
DontSkipOutputs bool // true to not skip outputs.
}
const (
@ -73,9 +72,7 @@ func MarshalProperties(props resource.PropertyMap, opts MarshalOptions) (*struct
for _, key := range props.StableKeys() {
v := props[key]
logging.V(9).Infof("Marshaling property for RPC[%s]: %s=%v", opts.Label, key, v)
if !opts.DontSkipOutputs && v.IsOutput() && !v.OutputValue().Known {
logging.V(9).Infof("Skipping output property for RPC[%s]: %v", opts.Label, key)
} else if opts.SkipNulls && v.IsNull() {
if opts.SkipNulls && v.IsNull() {
logging.V(9).Infof("Skipping null property for RPC[%s]: %s (as requested)", opts.Label, key)
} else if opts.SkipInternalKeys && resource.IsInternalPropertyKey(key) {
logging.V(9).Infof("Skipping internal property for RPC[%s]: %s (as requested)", opts.Label, key)

View file

@ -436,7 +436,7 @@ func TestMarshalProperties(t *testing.T) {
},
{
name: "empty (KeepOutputValues)",
opts: MarshalOptions{DontSkipOutputs: true, KeepOutputValues: true},
opts: MarshalOptions{KeepOutputValues: true},
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{}),
},
@ -458,7 +458,7 @@ func TestMarshalProperties(t *testing.T) {
},
{
name: "unknown (KeepOutputValues)",
opts: MarshalOptions{DontSkipOutputs: true, KeepOutputValues: true},
opts: MarshalOptions{KeepOutputValues: true},
props: resource.PropertyMap{
"foo": resource.MakeOutput(resource.NewStringProperty("")),
},
@ -480,7 +480,7 @@ func TestMarshalProperties(t *testing.T) {
},
{
name: "unknown with deps (KeepOutputValues)",
opts: MarshalOptions{DontSkipOutputs: true, KeepOutputValues: true},
opts: MarshalOptions{KeepOutputValues: true},
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty(""),
@ -515,7 +515,7 @@ func TestMarshalProperties(t *testing.T) {
},
{
name: "known (KeepOutputValues)",
opts: MarshalOptions{DontSkipOutputs: true, KeepOutputValues: true},
opts: MarshalOptions{KeepOutputValues: true},
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("hello"),
@ -543,7 +543,7 @@ func TestMarshalProperties(t *testing.T) {
},
{
name: "known with deps (KeepOutputValues)",
opts: MarshalOptions{DontSkipOutputs: true, KeepOutputValues: true},
opts: MarshalOptions{KeepOutputValues: true},
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("hello"),
@ -582,7 +582,7 @@ func TestMarshalProperties(t *testing.T) {
},
{
name: "secret (KeepOutputValues)",
opts: MarshalOptions{DontSkipOutputs: true, KeepOutputValues: true},
opts: MarshalOptions{KeepOutputValues: true},
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
@ -614,7 +614,7 @@ func TestMarshalProperties(t *testing.T) {
},
{
name: "secret with deps (KeepOutputValues)",
opts: MarshalOptions{DontSkipOutputs: true, KeepOutputValues: true},
opts: MarshalOptions{KeepOutputValues: true},
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
@ -657,7 +657,7 @@ func TestMarshalProperties(t *testing.T) {
},
{
name: "unknown secret (KeepOutputValues)",
opts: MarshalOptions{DontSkipOutputs: true, KeepOutputValues: true},
opts: MarshalOptions{KeepOutputValues: true},
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
@ -685,7 +685,7 @@ func TestMarshalProperties(t *testing.T) {
},
{
name: "unknown secret with deps (KeepOutputValues)",
opts: MarshalOptions{DontSkipOutputs: true, KeepOutputValues: true},
opts: MarshalOptions{KeepOutputValues: true},
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
@ -1695,48 +1695,6 @@ func TestMarshalPropertiesDontSkipOutputs(t *testing.T) {
"value": resource.NewOutputProperty(resource.Output{}),
}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{
"nested": {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{},
},
},
},
},
},
},
{
name: "Output (KeepUnknowns, KeepOutputValues)",
opts: MarshalOptions{KeepUnknowns: true, KeepOutputValues: true},
props: resource.PropertyMap{
"message": resource.NewOutputProperty(resource.Output{}),
"nested": resource.NewObjectProperty(resource.PropertyMap{
"value": resource.NewOutputProperty(resource.Output{}),
}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{
"nested": {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{},
},
},
},
},
},
},
{
name: "Output (KeepUnknowns, DontSkipOutputs)",
opts: MarshalOptions{KeepUnknowns: true, DontSkipOutputs: true},
props: resource.PropertyMap{
"message": resource.NewOutputProperty(resource.Output{}),
"nested": resource.NewObjectProperty(resource.PropertyMap{
"value": resource.NewOutputProperty(resource.Output{}),
}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{
"message": {
@ -1757,8 +1715,8 @@ func TestMarshalPropertiesDontSkipOutputs(t *testing.T) {
},
},
{
name: "Output (KeepUnknowns, KeepOutputValues, DontSkipOutputs)",
opts: MarshalOptions{KeepUnknowns: true, KeepOutputValues: true, DontSkipOutputs: true},
name: "Output (KeepUnknowns, KeepOutputValues)",
opts: MarshalOptions{KeepUnknowns: true, KeepOutputValues: true},
props: resource.PropertyMap{
"message": resource.NewOutputProperty(resource.Output{}),
"nested": resource.NewObjectProperty(resource.PropertyMap{

View file

@ -51,7 +51,8 @@ type Context struct {
engine pulumirpc.EngineClient
engineConn *grpc.ClientConn
keepResources bool // true if resources should be marshaled as strongly-typed references.
keepResources bool // true if resources should be marshaled as strongly-typed references.
keepOutputValues bool // true if outputs should be marshaled as strongly-type output values.
rpcs int // the number of outstanding RPC requests.
rpcsDone *sync.Cond // an event signaling completion of RPCs.
@ -104,26 +105,37 @@ func NewContext(ctx context.Context, info RunInfo) (*Context, error) {
engine = &mockEngine{}
}
var keepResources bool
if monitor != nil {
supportsFeatureResp, err := monitor.SupportsFeature(ctx, &pulumirpc.SupportsFeatureRequest{
Id: "resourceReferences",
})
if err != nil {
return nil, fmt.Errorf("checking monitor features: %w", err)
supportsFeature := func(id string) (bool, error) {
if monitor != nil {
resp, err := monitor.SupportsFeature(ctx, &pulumirpc.SupportsFeatureRequest{Id: id})
if err != nil {
return false, fmt.Errorf("checking monitor features: %w", err)
}
return resp.GetHasSupport(), nil
}
keepResources = supportsFeatureResp.GetHasSupport()
return false, nil
}
keepResources, err := supportsFeature("resourceReferences")
if err != nil {
return nil, err
}
keepOutputValues, err := supportsFeature("outputValues")
if err != nil {
return nil, err
}
context := &Context{
ctx: ctx,
info: info,
exports: make(map[string]Input),
monitorConn: monitorConn,
monitor: monitor,
engineConn: engineConn,
engine: engine,
keepResources: keepResources,
ctx: ctx,
info: info,
exports: make(map[string]Input),
monitorConn: monitorConn,
monitor: monitor,
engineConn: engineConn,
engine: engine,
keepResources: keepResources,
keepOutputValues: keepOutputValues,
}
context.rpcsDone = sync.NewCond(&context.rpcsLock)
context.Log = &logState{
@ -383,8 +395,9 @@ func (ctx *Context) Call(tok string, args Input, output Output, self Resource, o
rpcArgs, err := plugin.MarshalProperties(
resolvedArgs,
ctx.withKeepOrRejectUnknowns(plugin.MarshalOptions{
KeepSecrets: true,
KeepResources: ctx.keepResources,
KeepSecrets: true,
KeepResources: ctx.keepResources,
KeepOutputValues: ctx.keepOutputValues,
}))
if err != nil {
return nil, fmt.Errorf("marshaling args: %w", err)
@ -1175,6 +1188,9 @@ func (ctx *Context) prepareResourceInputs(res Resource, props Input, t string, o
ctx.withKeepOrRejectUnknowns(plugin.MarshalOptions{
KeepSecrets: true,
KeepResources: ctx.keepResources,
// To initially scope the use of this new feature, we only keep output values when
// remote is true (for multi-lang components).
KeepOutputValues: remote && ctx.keepOutputValues,
}))
if err != nil {
return nil, fmt.Errorf("marshaling properties: %w", err)

View file

@ -73,8 +73,14 @@ func (m *mockMonitor) newURN(parent, typ, name string) string {
func (m *mockMonitor) SupportsFeature(ctx context.Context, in *pulumirpc.SupportsFeatureRequest,
opts ...grpc.CallOption) (*pulumirpc.SupportsFeatureResponse, error) {
id := in.GetId()
// Support for "outputValues" is deliberately disabled for the mock monitor so
// instances of `Output` don't show up in `MockResourceArgs` Inputs.
hasSupport := id == "secrets" || id == "resourceReferences"
return &pulumirpc.SupportsFeatureResponse{
HasSupport: true,
HasSupport: hasSupport,
}, nil
}

View file

@ -1277,8 +1277,14 @@ func TestConstructResult(t *testing.T) {
resolvedProps, _, _, err := marshalInputs(state)
assert.NoError(t, err)
assert.Equal(t, resource.NewPropertyMapFromMap(map[string]interface{}{
"foo": "hi",
"someValue": "something",
}), resolvedProps)
assert.Equal(t, resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("hi"),
Known: true,
}),
"someValue": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("something"),
Known: true,
}),
}, resolvedProps)
}

View file

@ -214,23 +214,6 @@ const cannotAwaitFmt = "cannot marshal Output value of type %T; please use Apply
// marshalInput marshals an input value, returning its raw serializable value along with any dependencies.
func marshalInput(v interface{}, destType reflect.Type, await bool) (resource.PropertyValue, []Resource, error) {
val, deps, secret, err := marshalInputAndDetermineSecret(v, destType, await)
if err != nil {
return val, deps, err
}
if secret {
return resource.MakeSecret(val), deps, nil
}
return val, deps, nil
}
// marshalInputAndDetermineSecret marshals an input value with information about secret status
func marshalInputAndDetermineSecret(v interface{},
destType reflect.Type,
await bool) (resource.PropertyValue, []Resource, bool, error) {
secret := false
var deps []Resource
for {
valueType := reflect.TypeOf(v)
@ -239,10 +222,17 @@ func marshalInputAndDetermineSecret(v interface{},
if input, ok := v.(Input); ok {
if inputType := reflect.ValueOf(input); inputType.Kind() == reflect.Ptr && inputType.IsNil() {
// input type is a ptr type with a nil backing value
return resource.PropertyValue{}, nil, secret, nil
return resource.PropertyValue{}, nil, nil
}
valueType = input.ElementType()
// Handle cases where the destination is a ptr type whose element type is the same as the value type
// (e.g. destType is *FooBar and valueType is FooBar).
// This avoids calling the ToOutput method to convert the input to an output in this case.
if valueType != destType && destType.Kind() == reflect.Ptr && valueType == destType.Elem() {
destType = destType.Elem()
}
// If the element type of the input is not identical to the type of the destination and the destination is
// not the any type (i.e. interface{}), attempt to convert the input to an appropriately-typed output.
if valueType != destType && destType != anyType {
@ -253,51 +243,69 @@ func marshalInputAndDetermineSecret(v interface{},
err := fmt.Errorf(
"cannot marshal an input of type %T with element type %v as a value of type %v",
input, valueType, destType)
return resource.PropertyValue{}, nil, false, err
return resource.PropertyValue{}, nil, err
}
}
// If the input is an Output, await its value. The returned value is fully resolved.
if output, ok := input.(Output); ok {
if !await {
return resource.PropertyValue{}, nil, false, fmt.Errorf(cannotAwaitFmt, output)
return resource.PropertyValue{}, nil, fmt.Errorf(cannotAwaitFmt, output)
}
// Await the output.
ov, known, outputSecret, outputDeps, err := output.getState().await(context.TODO())
ov, known, secret, outputDeps, err := output.getState().await(context.TODO())
if err != nil {
return resource.PropertyValue{}, nil, false, err
}
secret = outputSecret
// If the value is unknown, return the appropriate sentinel.
if !known {
return resource.MakeComputed(resource.NewStringProperty("")), outputDeps, secret, nil
return resource.PropertyValue{}, nil, err
}
v, deps = ov, outputDeps
urnSet, err := expandDependencies(context.TODO(), outputDeps)
if err != nil {
return resource.PropertyValue{}, nil, err
}
urns := urnSet.sortedValues()
var dependencies []resource.URN
if len(urns) > 0 {
dependencies = make([]resource.URN, len(urns))
for i, urn := range urns {
dependencies[i] = resource.URN(urn)
}
}
out := resource.Output{
Known: known,
Secret: secret,
Dependencies: dependencies,
}
if known {
out.Element, _, err = marshalInput(ov, destType, await)
if err != nil {
return resource.PropertyValue{}, nil, err
}
}
return resource.NewOutputProperty(out), outputDeps, nil
}
}
// If v is nil, just return that.
if v == nil {
return resource.PropertyValue{}, nil, secret, nil
return resource.PropertyValue{}, nil, nil
}
// Look for some well known types.
switch v := v.(type) {
case *asset:
if v.invalid {
return resource.PropertyValue{}, nil, false, fmt.Errorf("invalid asset")
return resource.PropertyValue{}, nil, fmt.Errorf("invalid asset")
}
return resource.NewAssetProperty(&resource.Asset{
Path: v.Path(),
Text: v.Text(),
URI: v.URI(),
}), deps, secret, nil
}), deps, nil
case *archive:
if v.invalid {
return resource.PropertyValue{}, nil, false, fmt.Errorf("invalid archive")
return resource.PropertyValue{}, nil, fmt.Errorf("invalid archive")
}
var assets map[string]interface{}
@ -306,7 +314,7 @@ func marshalInputAndDetermineSecret(v interface{},
for k, a := range as {
aa, _, err := marshalInput(a, anyType, await)
if err != nil {
return resource.PropertyValue{}, nil, false, err
return resource.PropertyValue{}, nil, err
}
assets[k] = aa.V
}
@ -315,13 +323,13 @@ func marshalInputAndDetermineSecret(v interface{},
Assets: assets,
Path: v.Path(),
URI: v.URI(),
}), deps, secret, nil
}), deps, nil
case Resource:
deps = append(deps, v)
urn, known, secretURN, err := v.URN().awaitURN(context.Background())
if err != nil {
return resource.PropertyValue{}, nil, false, err
return resource.PropertyValue{}, nil, err
}
contract.Assert(known)
contract.Assert(!secretURN)
@ -329,14 +337,14 @@ func marshalInputAndDetermineSecret(v interface{},
if custom, ok := v.(CustomResource); ok {
id, _, secretID, err := custom.ID().awaitID(context.Background())
if err != nil {
return resource.PropertyValue{}, nil, false, err
return resource.PropertyValue{}, nil, err
}
contract.Assert(!secretID)
return resource.MakeCustomResourceReference(resource.URN(urn), resource.ID(id), ""), deps, secret, nil
return resource.MakeCustomResourceReference(resource.URN(urn), resource.ID(id), ""), deps, nil
}
return resource.MakeComponentResourceReference(resource.URN(urn), ""), deps, secret, nil
return resource.MakeComponentResourceReference(resource.URN(urn), ""), deps, nil
}
if destType.Kind() == reflect.Interface {
@ -361,17 +369,17 @@ func marshalInputAndDetermineSecret(v interface{},
switch rv.Type().Kind() {
case reflect.Bool:
return resource.NewBoolProperty(rv.Bool()), deps, secret, nil
return resource.NewBoolProperty(rv.Bool()), deps, nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return resource.NewNumberProperty(float64(rv.Int())), deps, secret, nil
return resource.NewNumberProperty(float64(rv.Int())), deps, nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return resource.NewNumberProperty(float64(rv.Uint())), deps, secret, nil
return resource.NewNumberProperty(float64(rv.Uint())), deps, nil
case reflect.Float32, reflect.Float64:
return resource.NewNumberProperty(rv.Float()), deps, secret, nil
return resource.NewNumberProperty(rv.Float()), deps, nil
case reflect.Ptr, reflect.Interface:
// Dereference non-nil pointers and interfaces.
if rv.IsNil() {
return resource.PropertyValue{}, deps, secret, nil
return resource.PropertyValue{}, deps, nil
}
if destType.Kind() == reflect.Ptr {
destType = destType.Elem()
@ -379,10 +387,10 @@ func marshalInputAndDetermineSecret(v interface{},
v = rv.Elem().Interface()
continue
case reflect.String:
return resource.NewStringProperty(rv.String()), deps, secret, nil
return resource.NewStringProperty(rv.String()), deps, nil
case reflect.Array, reflect.Slice:
if rv.IsNil() {
return resource.PropertyValue{}, deps, secret, nil
return resource.PropertyValue{}, deps, nil
}
destElem := destType.Elem()
@ -393,22 +401,22 @@ func marshalInputAndDetermineSecret(v interface{},
elem := rv.Index(i)
e, d, err := marshalInput(elem.Interface(), destElem, await)
if err != nil {
return resource.PropertyValue{}, nil, false, err
return resource.PropertyValue{}, nil, err
}
if !e.IsNull() {
arr = append(arr, e)
}
deps = append(deps, d...)
}
return resource.NewArrayProperty(arr), deps, secret, nil
return resource.NewArrayProperty(arr), deps, nil
case reflect.Map:
if rv.Type().Key().Kind() != reflect.String {
return resource.PropertyValue{}, nil, false,
return resource.PropertyValue{}, nil,
fmt.Errorf("expected map keys to be strings; got %v", rv.Type().Key())
}
if rv.IsNil() {
return resource.PropertyValue{}, deps, secret, nil
return resource.PropertyValue{}, deps, nil
}
destElem := destType.Elem()
@ -419,14 +427,14 @@ func marshalInputAndDetermineSecret(v interface{},
value := rv.MapIndex(key)
mv, d, err := marshalInput(value.Interface(), destElem, await)
if err != nil {
return resource.PropertyValue{}, nil, false, err
return resource.PropertyValue{}, nil, err
}
if !mv.IsNull() {
obj[resource.PropertyKey(key.String())] = mv
}
deps = append(deps, d...)
}
return resource.NewObjectProperty(obj), deps, secret, nil
return resource.NewObjectProperty(obj), deps, nil
case reflect.Struct:
obj := resource.PropertyMap{}
typ := rv.Type()
@ -440,7 +448,7 @@ func marshalInputAndDetermineSecret(v interface{},
fv, d, err := marshalInput(rv.Field(i).Interface(), destField.Type, await)
if err != nil {
return resource.PropertyValue{}, nil, false, err
return resource.PropertyValue{}, nil, err
}
if !fv.IsNull() {
@ -448,9 +456,9 @@ func marshalInputAndDetermineSecret(v interface{},
}
deps = append(deps, d...)
}
return resource.NewObjectProperty(obj), deps, secret, nil
return resource.NewObjectProperty(obj), deps, nil
}
return resource.PropertyValue{}, nil, false, fmt.Errorf("unrecognized input property type: %v (%T)", v, v)
return resource.PropertyValue{}, nil, fmt.Errorf("unrecognized input property type: %v (%T)", v, v)
}
}
@ -491,8 +499,17 @@ func unmarshalResourceReference(ctx *Context, ref resource.ResourceReference) (R
func unmarshalPropertyValue(ctx *Context, v resource.PropertyValue) (interface{}, bool, error) {
switch {
case v.IsComputed() || v.IsOutput():
case v.IsComputed():
return nil, false, nil
case v.IsOutput():
if !v.OutputValue().Known {
return nil, v.OutputValue().Secret, nil
}
ov, _, err := unmarshalPropertyValue(ctx, v.OutputValue().Element)
if err != nil {
return nil, false, err
}
return ov, v.OutputValue().Secret, nil
case v.IsSecret():
sv, _, err := unmarshalPropertyValue(ctx, v.SecretValue().Element)
if err != nil {
@ -573,7 +590,7 @@ func unmarshalOutput(ctx *Context, v resource.PropertyValue, dest reflect.Value)
contract.Assert(dest.CanSet())
// Check for nils and unknowns. The destination will be left with the zero value.
if v.IsNull() || v.IsComputed() || v.IsOutput() {
if v.IsNull() || v.IsComputed() || (v.IsOutput() && !v.OutputValue().Known) {
return false, nil
}

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// nolint: unused,deadcode
// nolint: unused,deadcode,lll
package pulumi
import (
@ -623,7 +623,7 @@ func (UntypedArgs) ElementType() reflect.Type {
return reflect.TypeOf((*map[string]interface{})(nil)).Elem()
}
func TestMapInputMarhsalling(t *testing.T) {
func TestMapInputMarshalling(t *testing.T) {
var theResource simpleCustomResource
out := newOutput(nil, reflect.TypeOf((*StringOutput)(nil)).Elem(), &theResource)
out.getState().resolve("outputty", true, false, nil)
@ -645,17 +645,22 @@ func TestMapInputMarhsalling(t *testing.T) {
})
cases := []struct {
inputs Input
depUrns []string
inputs Input
depUrns []string
expectOutputValue bool
}{
{inputs: inputs1, depUrns: []string{""}},
{inputs: inputs1, depUrns: []string{""}, expectOutputValue: true},
{inputs: inputs2, depUrns: nil},
}
for _, c := range cases {
resolved, _, depUrns, err := marshalInputs(c.inputs)
assert.NoError(t, err)
assert.Equal(t, "outputty", resolved["prop"].StringValue())
if c.expectOutputValue {
assert.Equal(t, "outputty", resolved["prop"].OutputValue().Element.StringValue())
} else {
assert.Equal(t, "outputty", resolved["prop"].StringValue())
}
assert.Equal(t, "foo", resolved["nested"].ObjectValue()["foo"].StringValue())
assert.Equal(t, 42.0, resolved["nested"].ObjectValue()["bar"].NumberValue())
assert.Equal(t, len(c.depUrns), len(depUrns))
@ -880,3 +885,655 @@ func TestDependsOnComponent(t *testing.T) {
_, deps = newResource("resI", DependsOn([]Resource{resG}))
assert.Equal(t, []string{"resG"}, deps)
}
func TestOutputValueMarshalling(t *testing.T) {
ctx, err := NewContext(context.Background(), RunInfo{})
assert.Nil(t, err)
values := []struct {
value interface{}
expected resource.PropertyValue
}{
{value: nil, expected: resource.NewNullProperty()},
{value: 0, expected: resource.NewNumberProperty(0)},
{value: 1, expected: resource.NewNumberProperty(1)},
{value: "", expected: resource.NewStringProperty("")},
{value: "hi", expected: resource.NewStringProperty("hi")},
{value: map[string]string{}, expected: resource.NewObjectProperty(resource.PropertyMap{})},
{value: []string{}, expected: resource.NewArrayProperty(nil)},
}
for _, value := range values {
for _, deps := range [][]resource.URN{nil, {"fakeURN1", "fakeURN2"}} {
for _, known := range []bool{true, false} {
for _, secret := range []bool{true, false} {
var resources []Resource
if len(deps) > 0 {
for _, dep := range deps {
resources = append(resources, ctx.newDependencyResource(URN(dep)))
}
}
out := ctx.newOutput(anyOutputType, resources...)
out.getState().resolve(value.value, known, secret, nil)
expected := resource.Output{
Known: known,
Secret: secret,
Dependencies: deps,
}
if known {
expected.Element = value.expected
}
name := fmt.Sprintf("value=%v, known=%v, secret=%v, deps=%v", value, known, secret, deps)
t.Run(name, func(t *testing.T) {
inputs := Map{"value": out}
expected := resource.PropertyMap{"value": resource.NewOutputProperty(expected)}
actual, _, _, err := marshalInputs(inputs)
assert.NoError(t, err)
assert.Equal(t, expected, actual)
})
}
}
}
}
}
type foo struct {
TemplateOptions *TemplateOptions `pulumi:"templateOptions"`
}
type fooArgs struct {
TemplateOptions TemplateOptionsPtrInput
}
func (fooArgs) ElementType() reflect.Type {
return reflect.TypeOf((*foo)(nil)).Elem()
}
type TemplateOptions struct {
Description *string `pulumi:"description"`
TagSpecifications []TemplateTagSpecification `pulumi:"tagSpecifications"`
}
type TemplateOptionsInput interface {
Input
ToTemplateOptionsOutput() TemplateOptionsOutput
ToTemplateOptionsOutputWithContext(context.Context) TemplateOptionsOutput
}
type TemplateOptionsArgs struct {
Description StringPtrInput
TagSpecifications TemplateTagSpecificationArrayInput
}
func (TemplateOptionsArgs) ElementType() reflect.Type {
return reflect.TypeOf((*TemplateOptions)(nil)).Elem()
}
func (i TemplateOptionsArgs) ToTemplateOptionsOutput() TemplateOptionsOutput {
return i.ToTemplateOptionsOutputWithContext(context.Background())
}
func (i TemplateOptionsArgs) ToTemplateOptionsOutputWithContext(ctx context.Context) TemplateOptionsOutput {
return ToOutputWithContext(ctx, i).(TemplateOptionsOutput)
}
func (i TemplateOptionsArgs) ToTemplateOptionsPtrOutput() TemplateOptionsPtrOutput {
return i.ToTemplateOptionsPtrOutputWithContext(context.Background())
}
func (i TemplateOptionsArgs) ToTemplateOptionsPtrOutputWithContext(ctx context.Context) TemplateOptionsPtrOutput {
return ToOutputWithContext(ctx, i).(TemplateOptionsOutput).ToTemplateOptionsPtrOutputWithContext(ctx)
}
type TemplateOptionsPtrInput interface {
Input
ToTemplateOptionsPtrOutput() TemplateOptionsPtrOutput
ToTemplateOptionsPtrOutputWithContext(context.Context) TemplateOptionsPtrOutput
}
type templateOptionsPtrType TemplateOptionsArgs
func TemplateOptionsPtr(v *TemplateOptionsArgs) TemplateOptionsPtrInput {
return (*templateOptionsPtrType)(v)
}
func (*templateOptionsPtrType) ElementType() reflect.Type {
return reflect.TypeOf((**TemplateOptions)(nil)).Elem()
}
func (i *templateOptionsPtrType) ToTemplateOptionsPtrOutput() TemplateOptionsPtrOutput {
return i.ToTemplateOptionsPtrOutputWithContext(context.Background())
}
func (i *templateOptionsPtrType) ToTemplateOptionsPtrOutputWithContext(ctx context.Context) TemplateOptionsPtrOutput {
return ToOutputWithContext(ctx, i).(TemplateOptionsPtrOutput)
}
type TemplateOptionsOutput struct{ *OutputState }
func (TemplateOptionsOutput) ElementType() reflect.Type {
return reflect.TypeOf((*TemplateOptions)(nil)).Elem()
}
func (o TemplateOptionsOutput) ToTemplateOptionsOutput() TemplateOptionsOutput {
return o
}
func (o TemplateOptionsOutput) ToTemplateOptionsOutputWithContext(ctx context.Context) TemplateOptionsOutput {
return o
}
func (o TemplateOptionsOutput) ToTemplateOptionsPtrOutput() TemplateOptionsPtrOutput {
return o.ToTemplateOptionsPtrOutputWithContext(context.Background())
}
func (o TemplateOptionsOutput) ToTemplateOptionsPtrOutputWithContext(ctx context.Context) TemplateOptionsPtrOutput {
return o.ApplyTWithContext(ctx, func(_ context.Context, v TemplateOptions) *TemplateOptions {
return &v
}).(TemplateOptionsPtrOutput)
}
type TemplateOptionsPtrOutput struct{ *OutputState }
func (TemplateOptionsPtrOutput) ElementType() reflect.Type {
return reflect.TypeOf((**TemplateOptions)(nil)).Elem()
}
func (o TemplateOptionsPtrOutput) ToTemplateOptionsPtrOutput() TemplateOptionsPtrOutput {
return o
}
func (o TemplateOptionsPtrOutput) ToTemplateOptionsPtrOutputWithContext(ctx context.Context) TemplateOptionsPtrOutput {
return o
}
type TemplateTagSpecification struct {
Name *string `pulumi:"name"`
Tags map[string]string `pulumi:"tags"`
}
type TemplateTagSpecificationInput interface {
Input
ToTemplateTagSpecificationOutput() TemplateTagSpecificationOutput
ToTemplateTagSpecificationOutputWithContext(context.Context) TemplateTagSpecificationOutput
}
type TemplateTagSpecificationArgs struct {
Name StringPtrInput `pulumi:"name"`
Tags StringMapInput `pulumi:"tags"`
}
func (TemplateTagSpecificationArgs) ElementType() reflect.Type {
return reflect.TypeOf((*TemplateTagSpecification)(nil)).Elem()
}
func (i TemplateTagSpecificationArgs) ToTemplateTagSpecificationOutput() TemplateTagSpecificationOutput {
return i.ToTemplateTagSpecificationOutputWithContext(context.Background())
}
func (i TemplateTagSpecificationArgs) ToTemplateTagSpecificationOutputWithContext(ctx context.Context) TemplateTagSpecificationOutput {
return ToOutputWithContext(ctx, i).(TemplateTagSpecificationOutput)
}
type TemplateTagSpecificationArrayInput interface {
Input
ToTemplateTagSpecificationArrayOutput() TemplateTagSpecificationArrayOutput
ToTemplateTagSpecificationArrayOutputWithContext(context.Context) TemplateTagSpecificationArrayOutput
}
type TemplateTagSpecificationArray []TemplateTagSpecificationInput
func (TemplateTagSpecificationArray) ElementType() reflect.Type {
return reflect.TypeOf((*[]TemplateTagSpecification)(nil)).Elem()
}
func (i TemplateTagSpecificationArray) ToTemplateTagSpecificationArrayOutput() TemplateTagSpecificationArrayOutput {
return i.ToTemplateTagSpecificationArrayOutputWithContext(context.Background())
}
func (i TemplateTagSpecificationArray) ToTemplateTagSpecificationArrayOutputWithContext(ctx context.Context) TemplateTagSpecificationArrayOutput {
return ToOutputWithContext(ctx, i).(TemplateTagSpecificationArrayOutput)
}
type TemplateTagSpecificationOutput struct{ *OutputState }
func (TemplateTagSpecificationOutput) ElementType() reflect.Type {
return reflect.TypeOf((*TemplateTagSpecification)(nil)).Elem()
}
func (o TemplateTagSpecificationOutput) ToTemplateTagSpecificationOutput() TemplateTagSpecificationOutput {
return o
}
func (o TemplateTagSpecificationOutput) ToTemplateTagSpecificationOutputWithContext(ctx context.Context) TemplateTagSpecificationOutput {
return o
}
type TemplateTagSpecificationArrayOutput struct{ *OutputState }
func (TemplateTagSpecificationArrayOutput) ElementType() reflect.Type {
return reflect.TypeOf((*[]TemplateTagSpecification)(nil)).Elem()
}
func (o TemplateTagSpecificationArrayOutput) ToTemplateTagSpecificationArrayOutput() TemplateTagSpecificationArrayOutput {
return o
}
func (o TemplateTagSpecificationArrayOutput) ToTemplateTagSpecificationArrayOutputWithContext(ctx context.Context) TemplateTagSpecificationArrayOutput {
return o
}
func TestOutputValueMarshallingNested(t *testing.T) {
ctx, err := NewContext(context.Background(), RunInfo{})
assert.Nil(t, err)
RegisterOutputType(TemplateOptionsOutput{})
RegisterOutputType(TemplateOptionsPtrOutput{})
RegisterOutputType(TemplateTagSpecificationOutput{})
RegisterOutputType(TemplateTagSpecificationArrayOutput{})
templateOptionsPtrOutputType := reflect.TypeOf((*TemplateOptionsPtrOutput)(nil)).Elem()
unknownTemplateOptionsPtrOutput := ctx.newOutput(templateOptionsPtrOutputType).(TemplateOptionsPtrOutput)
unknownTemplateOptionsPtrOutput.getState().resolve(nil, false /*known*/, false /*secret*/, nil)
unknownSecretTemplateOptionsPtrOutput := ctx.newOutput(templateOptionsPtrOutputType).(TemplateOptionsPtrOutput)
unknownSecretTemplateOptionsPtrOutput.getState().resolve(nil, false /*known*/, true /*secret*/, nil)
stringOutputType := reflect.TypeOf((*StringOutput)(nil)).Elem()
unknownStringOutput := ctx.newOutput(stringOutputType).(StringOutput)
unknownStringOutput.getState().resolve("", false /*known*/, false /*secret*/, nil)
tests := []struct {
name string
input Input
expected resource.PropertyValue
}{
{
name: "empty",
input: fooArgs{},
expected: resource.NewObjectProperty(resource.PropertyMap{}),
},
{
name: "options empty",
input: fooArgs{
TemplateOptions: TemplateOptionsArgs{},
},
expected: resource.NewObjectProperty(resource.PropertyMap{
"templateOptions": resource.NewObjectProperty(resource.PropertyMap{}),
}),
},
{
name: "options unknown",
input: fooArgs{
TemplateOptions: unknownTemplateOptionsPtrOutput,
},
expected: resource.NewObjectProperty(resource.PropertyMap{
"templateOptions": resource.NewOutputProperty(resource.Output{}),
}),
},
{
name: "options unknown secret",
input: fooArgs{
TemplateOptions: unknownSecretTemplateOptionsPtrOutput,
},
expected: resource.NewObjectProperty(resource.PropertyMap{
"templateOptions": resource.NewOutputProperty(resource.Output{
Secret: true,
}),
}),
},
{
name: "options plain known description",
input: fooArgs{
TemplateOptions: TemplateOptionsArgs{
Description: String("hello"),
},
},
expected: resource.NewObjectProperty(resource.PropertyMap{
"templateOptions": resource.NewObjectProperty(resource.PropertyMap{
"description": resource.NewStringProperty("hello"),
}),
}),
},
{
name: "options plain known secret description",
input: fooArgs{
TemplateOptions: TemplateOptionsArgs{
Description: ToSecret(String("hello")).(StringOutput),
},
},
expected: resource.NewObjectProperty(resource.PropertyMap{
"templateOptions": resource.NewObjectProperty(resource.PropertyMap{
"description": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("hello"),
Known: true,
Secret: true,
}),
}),
}),
},
{
name: "options output known secret description",
input: fooArgs{
TemplateOptions: TemplateOptionsArgs{
Description: ToSecret(String("hello")).(StringOutput),
}.ToTemplateOptionsOutput(),
},
expected: resource.NewObjectProperty(resource.PropertyMap{
"templateOptions": resource.NewOutputProperty(resource.Output{
Element: resource.NewObjectProperty(resource.PropertyMap{
"description": resource.NewStringProperty("hello"),
}),
Known: true,
Secret: true,
}),
}),
},
{
name: "options plain unknown description",
input: fooArgs{
TemplateOptions: TemplateOptionsArgs{
Description: unknownStringOutput,
},
},
expected: resource.NewObjectProperty(resource.PropertyMap{
"templateOptions": resource.NewObjectProperty(resource.PropertyMap{
"description": resource.NewOutputProperty(resource.Output{}),
}),
}),
},
{
name: "options tag specifications nested unknown",
input: fooArgs{
TemplateOptions: TemplateOptionsArgs{
TagSpecifications: TemplateTagSpecificationArray{
TemplateTagSpecificationArgs{
Name: String("hello"),
Tags: StringMap{
"first": String("second"),
"third": unknownStringOutput,
},
},
},
},
},
expected: resource.NewObjectProperty(resource.PropertyMap{
"templateOptions": resource.NewObjectProperty(resource.PropertyMap{
"tagSpecifications": resource.NewArrayProperty([]resource.PropertyValue{
resource.NewObjectProperty(resource.PropertyMap{
"name": resource.NewStringProperty("hello"),
"tags": resource.NewObjectProperty(resource.PropertyMap{
"first": resource.NewStringProperty("second"),
"third": resource.NewOutputProperty(resource.Output{}),
}),
}),
}),
}),
}),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
inputs := Map{"value": tt.input}
expected := resource.PropertyMap{"value": tt.expected}
actual, _, _, err := marshalInputs(inputs)
assert.NoError(t, err)
assert.Equal(t, expected, actual)
})
}
}
type rubberTreeArgs struct {
Size *TreeSize `pulumi:"size"`
}
type RubberTreeArgs struct {
Size TreeSizePtrInput
}
func (RubberTreeArgs) ElementType() reflect.Type {
return reflect.TypeOf((*rubberTreeArgs)(nil)).Elem()
}
type TreeSize string
const (
TreeSizeSmall = TreeSize("small")
TreeSizeMedium = TreeSize("medium")
TreeSizeLarge = TreeSize("large")
)
func (TreeSize) ElementType() reflect.Type {
return reflect.TypeOf((*TreeSize)(nil)).Elem()
}
func (e TreeSize) ToTreeSizeOutput() TreeSizeOutput {
return ToOutput(e).(TreeSizeOutput)
}
func (e TreeSize) ToTreeSizeOutputWithContext(ctx context.Context) TreeSizeOutput {
return ToOutputWithContext(ctx, e).(TreeSizeOutput)
}
func (e TreeSize) ToTreeSizePtrOutput() TreeSizePtrOutput {
return e.ToTreeSizePtrOutputWithContext(context.Background())
}
func (e TreeSize) ToTreeSizePtrOutputWithContext(ctx context.Context) TreeSizePtrOutput {
return e.ToTreeSizeOutputWithContext(ctx).ToTreeSizePtrOutputWithContext(ctx)
}
func (e TreeSize) ToStringOutput() StringOutput {
return ToOutput(String(e)).(StringOutput)
}
func (e TreeSize) ToStringOutputWithContext(ctx context.Context) StringOutput {
return ToOutputWithContext(ctx, String(e)).(StringOutput)
}
func (e TreeSize) ToStringPtrOutput() StringPtrOutput {
return String(e).ToStringPtrOutputWithContext(context.Background())
}
func (e TreeSize) ToStringPtrOutputWithContext(ctx context.Context) StringPtrOutput {
return String(e).ToStringOutputWithContext(ctx).ToStringPtrOutputWithContext(ctx)
}
type TreeSizeOutput struct{ *OutputState }
func (TreeSizeOutput) ElementType() reflect.Type {
return reflect.TypeOf((*TreeSize)(nil)).Elem()
}
func (o TreeSizeOutput) ToTreeSizeOutput() TreeSizeOutput {
return o
}
func (o TreeSizeOutput) ToTreeSizeOutputWithContext(ctx context.Context) TreeSizeOutput {
return o
}
func (o TreeSizeOutput) ToTreeSizePtrOutput() TreeSizePtrOutput {
return o.ToTreeSizePtrOutputWithContext(context.Background())
}
func (o TreeSizeOutput) ToTreeSizePtrOutputWithContext(ctx context.Context) TreeSizePtrOutput {
return o.ApplyTWithContext(ctx, func(_ context.Context, v TreeSize) *TreeSize {
return &v
}).(TreeSizePtrOutput)
}
func (o TreeSizeOutput) ToStringOutput() StringOutput {
return o.ToStringOutputWithContext(context.Background())
}
func (o TreeSizeOutput) ToStringOutputWithContext(ctx context.Context) StringOutput {
return o.ApplyTWithContext(ctx, func(_ context.Context, e TreeSize) string {
return string(e)
}).(StringOutput)
}
func (o TreeSizeOutput) ToStringPtrOutput() StringPtrOutput {
return o.ToStringPtrOutputWithContext(context.Background())
}
func (o TreeSizeOutput) ToStringPtrOutputWithContext(ctx context.Context) StringPtrOutput {
return o.ApplyTWithContext(ctx, func(_ context.Context, e TreeSize) *string {
v := string(e)
return &v
}).(StringPtrOutput)
}
type TreeSizePtrOutput struct{ *OutputState }
func (TreeSizePtrOutput) ElementType() reflect.Type {
return reflect.TypeOf((**TreeSize)(nil)).Elem()
}
func (o TreeSizePtrOutput) ToTreeSizePtrOutput() TreeSizePtrOutput {
return o
}
func (o TreeSizePtrOutput) ToTreeSizePtrOutputWithContext(ctx context.Context) TreeSizePtrOutput {
return o
}
func (o TreeSizePtrOutput) Elem() TreeSizeOutput {
return o.ApplyT(func(v *TreeSize) TreeSize {
if v != nil {
return *v
}
var ret TreeSize
return ret
}).(TreeSizeOutput)
}
func (o TreeSizePtrOutput) ToStringPtrOutput() StringPtrOutput {
return o.ToStringPtrOutputWithContext(context.Background())
}
func (o TreeSizePtrOutput) ToStringPtrOutputWithContext(ctx context.Context) StringPtrOutput {
return o.ApplyTWithContext(ctx, func(_ context.Context, e *TreeSize) *string {
if e == nil {
return nil
}
v := string(*e)
return &v
}).(StringPtrOutput)
}
type TreeSizeInput interface {
Input
ToTreeSizeOutput() TreeSizeOutput
ToTreeSizeOutputWithContext(context.Context) TreeSizeOutput
}
var treeSizePtrType = reflect.TypeOf((**TreeSize)(nil)).Elem()
type TreeSizePtrInput interface {
Input
ToTreeSizePtrOutput() TreeSizePtrOutput
ToTreeSizePtrOutputWithContext(context.Context) TreeSizePtrOutput
}
type treeSizePtr string
func TreeSizePtr(v string) TreeSizePtrInput {
return (*treeSizePtr)(&v)
}
func (*treeSizePtr) ElementType() reflect.Type {
return treeSizePtrType
}
func (in *treeSizePtr) ToTreeSizePtrOutput() TreeSizePtrOutput {
return ToOutput(in).(TreeSizePtrOutput)
}
func (in *treeSizePtr) ToTreeSizePtrOutputWithContext(ctx context.Context) TreeSizePtrOutput {
return ToOutputWithContext(ctx, in).(TreeSizePtrOutput)
}
type TreeSizeMapInput interface {
Input
ToTreeSizeMapOutput() TreeSizeMapOutput
ToTreeSizeMapOutputWithContext(context.Context) TreeSizeMapOutput
}
type TreeSizeMap map[string]TreeSize
func (TreeSizeMap) ElementType() reflect.Type {
return reflect.TypeOf((*map[string]TreeSize)(nil)).Elem()
}
func (i TreeSizeMap) ToTreeSizeMapOutput() TreeSizeMapOutput {
return i.ToTreeSizeMapOutputWithContext(context.Background())
}
func (i TreeSizeMap) ToTreeSizeMapOutputWithContext(ctx context.Context) TreeSizeMapOutput {
return ToOutputWithContext(ctx, i).(TreeSizeMapOutput)
}
type TreeSizeMapOutput struct{ *OutputState }
func (TreeSizeMapOutput) ElementType() reflect.Type {
return reflect.TypeOf((*map[string]TreeSize)(nil)).Elem()
}
func (o TreeSizeMapOutput) ToTreeSizeMapOutput() TreeSizeMapOutput {
return o
}
func (o TreeSizeMapOutput) ToTreeSizeMapOutputWithContext(ctx context.Context) TreeSizeMapOutput {
return o
}
func (o TreeSizeMapOutput) MapIndex(k StringInput) TreeSizeOutput {
return All(o, k).ApplyT(func(vs []interface{}) TreeSize {
return vs[0].(map[string]TreeSize)[vs[1].(string)]
}).(TreeSizeOutput)
}
func TestOutputValueMarshallingEnums(t *testing.T) {
_, err := NewContext(context.Background(), RunInfo{})
assert.Nil(t, err)
RegisterOutputType(TreeSizeOutput{})
RegisterOutputType(TreeSizePtrOutput{})
RegisterOutputType(TreeSizeMapOutput{})
tests := []struct {
name string
input Input
expected resource.PropertyValue
}{
{
name: "empty",
input: &RubberTreeArgs{
Size: TreeSize("medium"),
},
expected: resource.NewObjectProperty(resource.PropertyMap{
"size": resource.NewStringProperty("medium"),
}),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
inputs := Map{"value": tt.input}
expected := resource.PropertyMap{"value": tt.expected}
actual, _, _, err := marshalInputs(inputs)
assert.NoError(t, err)
assert.Equal(t, expected, actual)
})
}
}

View file

@ -5,6 +5,7 @@ import (
"context"
"fmt"
"reflect"
"strings"
"testing"
"github.com/blang/semver"
@ -388,6 +389,69 @@ func TestRegisterResourceWithResourceReferences(t *testing.T) {
assert.NoError(t, err)
}
type testMyRemoteComponentArgs struct {
Inprop string `pulumi:"inprop"`
}
type testMyRemoteComponentInputs struct {
Inprop StringInput
}
func (testMyRemoteComponentInputs) ElementType() reflect.Type {
return reflect.TypeOf((*testMyRemoteComponentArgs)(nil)).Elem()
}
type testMyRemoteComponent struct {
ResourceState
Outprop StringOutput `pulumi:"outprop"`
}
func TestRemoteComponent(t *testing.T) {
mocks := &testMonitor{
NewResourceF: func(args MockResourceArgs) (string, resource.PropertyMap, error) {
switch args.TypeToken {
case "pkg:index:Instance":
return "i-1234567890abcdef0", resource.PropertyMap{}, nil
case "pkg:index:MyRemoteComponent":
outprop := resource.NewStringProperty(fmt.Sprintf("output: %s", args.Inputs["inprop"].StringValue()))
return args.Name + "_id", resource.PropertyMap{
"inprop": args.Inputs["inprop"],
"outprop": outprop,
}, nil
default:
return "", nil, errors.Errorf("unknown resource %s", args.TypeToken)
}
},
}
err := RunErr(func(ctx *Context) error {
var instance testInstanceResource
err := ctx.RegisterResource("pkg:index:Instance", "instance", &testInstanceResourceInputs{}, &instance)
assert.NoError(t, err)
var myremotecomponent testMyRemoteComponent
err = ctx.RegisterRemoteComponentResource(
"pkg:index:MyRemoteComponent", "myremotecomponent", &testMyRemoteComponentInputs{
Inprop: Sprintf("hello: %v", instance.id),
}, &myremotecomponent)
assert.NoError(t, err)
val, known, secret, deps, err := await(myremotecomponent.Outprop)
assert.NoError(t, err)
stringVal, ok := val.(string)
assert.True(t, ok)
assert.True(t, strings.HasPrefix(stringVal, "output: hello: "))
assert.True(t, known)
assert.False(t, secret)
assert.Equal(t, []Resource{&myremotecomponent}, deps)
return nil
}, WithMocks("project", "stack", mocks))
assert.NoError(t, err)
}
func TestWaitOrphanedApply(t *testing.T) {
mocks := &testMonitor{
NewResourceF: func(args MockResourceArgs) (string, resource.PropertyMap, error) {