From a7f61a59b00909a67800245f4292526ee806361b Mon Sep 17 00:00:00 2001 From: Pat Gavlin Date: Tue, 12 Nov 2019 14:20:06 -0800 Subject: [PATCH] Reimplement Output for Go. (#3496) - Use a mutex + condition variable instead of a channel for synchronizaiton in order to allow multiple calls to resolve/reject - Properly handle outputs that are resolved to other outputs, especially if those outputs are not of exactly type Output - Remove the Value() methods that allowed prompt access to output values - Add variants of `Apply` that take a context parameter - Ensure that resource outputs properly incorporate their resource as a dependency - Make `Output` a plain struct. Uninitialized outputs will be treated as resolved and unknown. This makes conversions between output types more ergonomic. Contributes to #3492. --- CHANGELOG.md | 3 + pkg/engine/lifecycle_test.go | 51 ++ sdk/go/pulumi/context.go | 186 +++---- sdk/go/pulumi/properties.go | 809 +++++++++++++++---------------- sdk/go/pulumi/properties_test.go | 233 +++++---- sdk/go/pulumi/resource.go | 4 +- sdk/go/pulumi/rpc.go | 226 +++++---- sdk/go/pulumi/rpc_test.go | 85 +++- sdk/go/pulumi/run.go | 4 +- 9 files changed, 834 insertions(+), 767 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1695b1e5d..c4236a8cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ CHANGELOG calculated by the provider. [#3327](https://github.com/pulumi/pulumi/pull/3327) +- Refactor the Output API in the Go SDK. + [#3496](https://github.com/pulumi/pulumi/pull/3496) + ## 1.5.1 (2019-11-06) - Include the .NET language provider in the Windows SDK. diff --git a/pkg/engine/lifecycle_test.go b/pkg/engine/lifecycle_test.go index ad75eecc0..ee7682e93 100644 --- a/pkg/engine/lifecycle_test.go +++ b/pkg/engine/lifecycle_test.go @@ -46,6 +46,7 @@ import ( "github.com/pulumi/pulumi/pkg/util/result" "github.com/pulumi/pulumi/pkg/util/rpcutil/rpcerror" "github.com/pulumi/pulumi/pkg/workspace" + "github.com/pulumi/pulumi/sdk/go/pulumi" combinations "github.com/mxschmitt/golang-combinations" ) @@ -4895,3 +4896,53 @@ func TestPreviewInputPropagation(t *testing.T) { _, res = TestOp(Update).Run(project, p.GetTarget(snap), p.Options, preview, p.BackendClient, nil) assert.Nil(t, res) } + +func TestSingleResourceDefaultProviderGolangLifecycle(t *testing.T) { + loaders := []*deploytest.ProviderLoader{ + deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) { + return &deploytest.Provider{ + CreateF: func(urn resource.URN, + news resource.PropertyMap, timeout float64) (resource.ID, resource.PropertyMap, resource.Status, error) { + + return "created-id", news, resource.StatusOK, nil + }, + ReadF: func(urn resource.URN, id resource.ID, + inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) { + return plugin.ReadResult{Inputs: inputs, Outputs: state}, resource.StatusOK, nil + }, + }, nil + }), + } + + program := deploytest.NewLanguageRuntime(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error { + ctx, err := pulumi.NewContext(context.Background(), pulumi.RunInfo{ + Project: info.Project, + Stack: info.Stack, + Parallel: info.Parallel, + DryRun: info.DryRun, + MonitorAddr: info.MonitorAddress, + }) + assert.NoError(t, err) + + return pulumi.RunWithContext(ctx, func(ctx *pulumi.Context) error { + res, err := ctx.RegisterResource("pkgA:m:typA", "resA", true, map[string]interface{}{ + "foo": "bar", + }) + assert.NoError(t, err) + + _, err = ctx.RegisterResource("pkgA:m:typA", "resB", true, map[string]interface{}{ + "baz": res.State["foo"], + }) + assert.NoError(t, err) + + return nil + }) + }) + host := deploytest.NewPluginHost(nil, nil, program, loaders...) + + p := &TestPlan{ + Options: UpdateOptions{host: host}, + Steps: MakeBasicLifecycleSteps(t, 4), + } + p.Run(t, nil) +} diff --git a/sdk/go/pulumi/context.go b/sdk/go/pulumi/context.go index 1580a9889..d4919b6ef 100644 --- a/sdk/go/pulumi/context.go +++ b/sdk/go/pulumi/context.go @@ -19,7 +19,7 @@ import ( "sync" structpb "github.com/golang/protobuf/ptypes/struct" - "github.com/hashicorp/go-multierror" + multierror "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "golang.org/x/net/context" "google.golang.org/grpc" @@ -137,7 +137,7 @@ func (ctx *Context) Invoke(tok string, args map[string]interface{}, opts ...Invo // Serialize arguments, first by awaiting them, and then marshaling them to the requisite gRPC values. // TODO[pulumi/pulumi#1483]: feels like we should be propagating dependencies to the outputs, instead of ignoring. - rpcArgs, _, _, err := marshalInputs(args) + rpcArgs, _, _, err := marshalInputs(args, false) if err != nil { return nil, errors.Wrap(err, "marshaling arguments") } @@ -195,7 +195,7 @@ func (ctx *Context) ReadResource( } // Create resolvers for the resource's outputs. - outputs := makeResourceOutputs(true, props) + res := makeResourceState(true, props) // Kick off the resource read operation. This will happen asynchronously and resolve the above properties. go func() { @@ -204,7 +204,7 @@ func (ctx *Context) ReadResource( var state *structpb.Struct var err error defer func() { - outputs.resolve(ctx.DryRun(), err, props, urn, resID, state) + res.resolve(ctx.DryRun(), err, props, urn, resID, state) ctx.endRPC() }() @@ -233,15 +233,7 @@ func (ctx *Context) ReadResource( } }() - outs := make(map[string]*Output) - for k, s := range outputs.state { - outs[k] = s.out - } - return &ResourceState{ - urn: (*URNOutput)(outputs.urn.out), - id: (*IDOutput)(outputs.id.out), - State: outs, - }, nil + return res, nil } // RegisterResource creates and registers a new resource object. t is the fully qualified type token and name is @@ -261,7 +253,7 @@ func (ctx *Context) RegisterResource( } // Create resolvers for the resource's outputs. - outputs := makeResourceOutputs(custom, props) + res := makeResourceState(custom, props) // Kick off the resource registration. If we are actually performing a deployment, the resulting properties // will be resolved asynchronously as the RPC operation completes. If we're just planning, values won't resolve. @@ -271,7 +263,7 @@ func (ctx *Context) RegisterResource( var state *structpb.Struct var err error defer func() { - outputs.resolve(ctx.DryRun(), err, props, urn, resID, state) + res.resolve(ctx.DryRun(), err, props, urn, resID, state) ctx.endRPC() }() @@ -307,101 +299,89 @@ func (ctx *Context) RegisterResource( } }() - var id *IDOutput - if outputs.id != nil { - id = (*IDOutput)(outputs.id.out) - } - outs := make(map[string]*Output) - for k, s := range outputs.state { - outs[k] = s.out - } - return &ResourceState{ - urn: (*URNOutput)(outputs.urn.out), - id: id, - State: outs, - }, nil + return res, nil } -// resourceOutputs captures the outputs and resolvers for a resource operation. -type resourceOutputs struct { - urn *resourceOutput - id *resourceOutput - state map[string]*resourceOutput +// ResourceState contains the results of a resource registration operation. +type ResourceState struct { + // urn will resolve to the resource's URN after registration has completed. + urn URNOutput + // id will resolve to the resource's ID after registration, provided this is for a custom resource. + id IDOutput + // State contains the full set of expected output properties and will resolve after completion. + State Outputs } -// makeResourceOutputs creates a set of resolvers that we'll use to finalize state, for URNs, IDs, and output +// URN will resolve to the resource's URN after registration has completed. +func (state *ResourceState) URN() URNOutput { + return state.urn +} + +// ID will resolve to the resource's ID after registration, provided this is for a custom resource. +func (state *ResourceState) ID() IDOutput { + return state.id +} + +// makeResourceState creates a set of resolvers that we'll use to finalize state, for URNs, IDs, and output // properties. -func makeResourceOutputs(custom bool, props map[string]interface{}) *resourceOutputs { - outURN, resolveURN, rejectURN := NewOutput(nil) - urn := &resourceOutput{out: outURN, resolve: resolveURN, reject: rejectURN} +func makeResourceState(custom bool, props map[string]interface{}) *ResourceState { + state := &ResourceState{} + + state.urn = URNOutput(newOutput(state)) - var id *resourceOutput if custom { - outID, resolveID, rejectID := NewOutput(nil) - id = &resourceOutput{out: outID, resolve: resolveID, reject: rejectID} + state.id = IDOutput(newOutput(state)) } - state := make(map[string]*resourceOutput) + state.State = make(map[string]Output) for key := range props { - outState, resolveState, rejectState := NewOutput(nil) - state[key] = &resourceOutput{ - out: outState, - resolve: resolveState, - reject: rejectState, - } + state.State[key] = newOutput(state) } - return &resourceOutputs{ - urn: urn, - id: id, - state: state, - } + return state } // resolve resolves the resource outputs using the given error and/or values. -func (outputs *resourceOutputs) resolve(dryrun bool, err error, inputs map[string]interface{}, urn, id string, +func (state *ResourceState) resolve(dryrun bool, err error, inputs map[string]interface{}, urn, id string, result *structpb.Struct) { - var outprops map[string]interface{} if err == nil { outprops, err = unmarshalOutputs(result) } if err != nil { // If there was an error, we must reject everything: URN, ID, and state properties. - outputs.urn.reject(err) - if outputs.id != nil { - outputs.id.reject(err) + state.urn.s.reject(err) + if state.id.s != nil { + state.id.s.reject(err) } - for _, s := range outputs.state { - s.reject(err) - } - } else { - // Resolve the URN and ID. - outputs.urn.resolve(URN(urn), true) - if outputs.id != nil { - if id == "" && dryrun { - outputs.id.resolve("", false) - } else { - outputs.id.resolve(ID(id), true) - } + for _, o := range state.State { + o.s.reject(err) } + return + } - // During previews, it's possible that nils will be returned due to unknown values. This function - // determines the known-ed-ness of a given value below. - isKnown := func(v interface{}) bool { - return !dryrun || v != nil - } + // Resolve the URN and ID. + state.urn.s.resolve(URN(urn), true) + if state.id.s != nil { + known := id != "" || !dryrun + state.id.s.resolve(ID(id), known) + } - // Now resolve all output properties. - for k, s := range outputs.state { - v, has := outprops[k] - if !has && !dryrun { - // If we did not receive a value for a particular property, resolve it to the corresponding input - // if any exists. - v = inputs[k] - } - s.resolve(v, isKnown(v)) + // During previews, it's possible that nils will be returned due to unknown values. This function + // determines the known-ness of a given value below. + isKnown := func(v interface{}) bool { + return !dryrun || v != nil + } + + // Now resolve all output properties. + for k, o := range state.State { + v, has := outprops[k] + if !has && !dryrun { + // If we did not receive a value for a particular property, resolve it to the corresponding input + // if any exists. + v = inputs[k] } + o.s.resolve(v, isKnown(v)) } } @@ -430,7 +410,8 @@ func (ctx *Context) prepareResourceInputs(props map[string]interface{}, opts ... timeouts := ctx.getTimeouts(opts...) // Serialize all properties, first by awaiting them, and then marshaling them to the requisite gRPC values. - rpcProps, propertyDeps, rpcDeps, err := marshalInputs(props) + keepUnknowns := ctx.DryRun() + rpcProps, propertyDeps, rpcDeps, err := marshalInputs(props, keepUnknowns) if err != nil { return nil, errors.Wrap(err, "marshaling properties") } @@ -477,12 +458,6 @@ func (ctx *Context) prepareResourceInputs(props map[string]interface{}, opts ... }, nil } -type resourceOutput struct { - out *Output - resolve func(interface{}, bool) - reject func(error) -} - func (ctx *Context) getTimeouts(opts ...ResourceOpt) *pulumirpc.RegisterResourceRequest_CustomTimeouts { var timeouts pulumirpc.RegisterResourceRequest_CustomTimeouts for _, opt := range opts { @@ -530,7 +505,7 @@ func (ctx *Context) getOpts(opts ...ResourceOpt) (URN, []URN, bool, string, bool if parent == nil { parentURN = ctx.stackR } else { - urn, err := parent.URN().Value() + urn, _, err := parent.URN().await(context.TODO()) if err != nil { return "", nil, false, "", false, "", err } @@ -541,7 +516,7 @@ func (ctx *Context) getOpts(opts ...ResourceOpt) (URN, []URN, bool, string, bool if deps != nil { depURNs = make([]URN, len(deps)) for i, r := range deps { - urn, err := r.URN().Value() + urn, _, err := r.URN().await(context.TODO()) if err != nil { return "", nil, false, "", false, "", err } @@ -562,11 +537,11 @@ func (ctx *Context) getOpts(opts ...ResourceOpt) (URN, []URN, bool, string, bool } func (ctx *Context) resolveProviderReference(provider ProviderResource) (string, error) { - urn, err := provider.URN().Value() + urn, _, err := provider.URN().await(context.TODO()) if err != nil { return "", err } - id, known, err := provider.ID().Value() + id, known, err := provider.ID().await(context.TODO()) if err != nil { return "", err } @@ -621,26 +596,6 @@ func (ctx *Context) waitForRPCs() { ctx.rpcs = noMoreRPCs } -// ResourceState contains the results of a resource registration operation. -type ResourceState struct { - // urn will resolve to the resource's URN after registration has completed. - urn *URNOutput - // id will resolve to the resource's ID after registration, provided this is for a custom resource. - id *IDOutput - // State contains the full set of expected output properties and will resolve after completion. - State Outputs -} - -// URN will resolve to the resource's URN after registration has completed. -func (s *ResourceState) URN() *URNOutput { - return s.urn -} - -// ID will resolve to the resource's ID after registration, provided this is for a custom resource. -func (s *ResourceState) ID() *IDOutput { - return s.id -} - var _ Resource = (*ResourceState)(nil) var _ CustomResource = (*ResourceState)(nil) var _ ComponentResource = (*ResourceState)(nil) @@ -648,7 +603,8 @@ var _ ProviderResource = (*ResourceState)(nil) // RegisterResourceOutputs completes the resource registration, attaching an optional set of computed outputs. func (ctx *Context) RegisterResourceOutputs(urn URN, outs map[string]interface{}) error { - outsMarshalled, _, _, err := marshalInputs(outs) + keepUnknowns := ctx.DryRun() + outsMarshalled, _, _, err := marshalInputs(outs, keepUnknowns) if err != nil { return errors.Wrap(err, "marshaling outputs") } diff --git a/sdk/go/pulumi/properties.go b/sdk/go/pulumi/properties.go index 6400052b4..ad91644e2 100644 --- a/sdk/go/pulumi/properties.go +++ b/sdk/go/pulumi/properties.go @@ -12,16 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. +// nolint: lll package pulumi import ( + "context" "reflect" + "sync" - "github.com/spf13/cast" - + "github.com/pkg/errors" "github.com/pulumi/pulumi/sdk/go/pulumi/asset" ) +const ( + outputPending = iota + outputResolved + outputRejected +) + // Output helps encode the relationship between resources in a Pulumi application. Specifically an output property // holds onto a value and the resource it came from. An output value can then be provided when constructing new // resources, allowing that new resource to know both the value as well as the resource the value came from. This @@ -32,605 +40,558 @@ type Output struct { // outputState is a heap-allocated block of state for each output property, in case of aliasing. type outputState struct { - sync chan *valueOrError // the channel for outputs whose values are not yet known. - voe *valueOrError // the value or error, after the channel has been rendezvoused with. - deps []Resource // the dependencies associated with this output property. + mutex sync.Mutex + cond *sync.Cond + + state uint32 // one of output{Pending,Resolved,Rejected} + + value interface{} // the value of this output if it is resolved. + err error // the error associated with this output if it is rejected. + known bool // true if this output's value is known. + + deps []Resource // the dependencies associated with this output property. } -// valueOrError is a discriminated union between a value (possibly nil) or an error. -type valueOrError struct { - value interface{} // a value, if the output resolved to a value. - err error // an error, if the producer yielded an error instead of a value. - known bool // true if this value is known, versus just being a placeholder during previews. +func (o *outputState) dependencies() []Resource { + if o == nil { + return nil + } + return o.deps +} + +func (o *outputState) fulfill(value interface{}, known bool, err error) { + if o == nil { + return + } + + o.mutex.Lock() + defer func() { + o.mutex.Unlock() + o.cond.Broadcast() + }() + + if o.state != outputPending { + return + } + + if err != nil { + o.state, o.err, o.known = outputRejected, err, true + } else { + o.state, o.value, o.known = outputResolved, value, known + } +} + +func (o *outputState) resolve(value interface{}, known bool) { + o.fulfill(value, known, nil) +} + +func (o *outputState) reject(err error) { + o.fulfill(nil, true, err) +} + +func (o *outputState) await(ctx context.Context) (interface{}, bool, error) { + for { + if o == nil { + // If the state is nil, treat its value as resolved and unknown. + return nil, false, nil + } + + o.mutex.Lock() + for o.state == outputPending { + if ctx.Err() != nil { + return nil, true, ctx.Err() + } + o.cond.Wait() + } + o.mutex.Unlock() + + if !o.known || o.err != nil { + return nil, o.known, o.err + } + + ov, ok := isOutput(o.value) + if !ok { + return o.value, true, nil + } + o = ov.s + } +} + +func newOutput(deps ...Resource) Output { + out := Output{ + s: &outputState{ + deps: deps, + }, + } + out.s.cond = sync.NewCond(&out.s.mutex) + + return out +} + +var outputType = reflect.TypeOf(Output{}) + +func isOutput(v interface{}) (Output, bool) { + if v != nil { + rv := reflect.ValueOf(v) + if rv.Type().ConvertibleTo(outputType) { + return rv.Convert(outputType).Interface().(Output), true + } + } + return Output{}, false } // NewOutput returns an output value that can be used to rendezvous with the production of a value or error. The // function returns the output itself, plus two functions: one for resolving a value, and another for rejecting with an -// error; exactly one function must be called. This acts like a promise. -func NewOutput(deps []Resource) (*Output, func(interface{}, bool), func(error)) { - out := &Output{ - s: &outputState{ - sync: make(chan *valueOrError, 1), - deps: deps, - }, +// error; exactly one function must be called. This acts like a promise. +func NewOutput() (Output, func(interface{}), func(error)) { + out := newOutput() + + resolve := func(v interface{}) { + out.s.resolve(v, true) } - return out, out.resolve, out.reject -} - -// resolve will resolve the output. It is not exported, because we want to control the capabilities tightly, such -// that anybody who happens to have an Output is not allowed to resolve it; only those who created it can. -func (out *Output) resolve(v interface{}, known bool) { - // If v is another output, chain this rather than resolving to an output directly. - if other, isOut := v.(*Output); known && isOut { - go func() { - real, otherKnown, err := other.Value() - if err != nil { - out.reject(err) - } else { - out.resolve(real, otherKnown) - } - }() - } else { - out.s.sync <- &valueOrError{value: v, known: known} + reject := func(err error) { + out.s.reject(err) } + + return out, resolve, reject } -// reject will reject the output. It is not exported, because we want to control the capabilities tightly, such -// that anybody who happens to have an Output is not allowed to reject it; only those who created it can. -func (out *Output) reject(err error) { - out.s.sync <- &valueOrError{err: err} +// ApplyWithContext transforms the data of the output property using the applier func. The result remains an output +// property, and accumulates all implicated dependencies, so that resources can be properly tracked using a DAG. +// This function does not block awaiting the value; instead, it spawns a Goroutine that will await its availability. +func (out Output) Apply(applier func(v interface{}) (interface{}, error)) Output { + return out.ApplyWithContext(context.Background(), func(ctx context.Context, v interface{}) (interface{}, error) { + return applier(v) + }) } -// Apply transforms the data of the output property using the applier func. The result remains an output property, -// and accumulates all implicated dependencies, so that resources can be properly tracked using a DAG. This function -// does not block awaiting the value; instead, it spawns a Goroutine that will await its availability. -func (out *Output) Apply(applier func(v interface{}) (interface{}, error)) *Output { - result, resolve, reject := NewOutput(out.Deps()) +// ApplyWithContext transforms the data of the output property using the applier func. The result remains an output +// property, and accumulates all implicated dependencies, so that resources can be properly tracked using a DAG. +// This function does not block awaiting the value; instead, it spawns a Goroutine that will await its availability. +// The provided context can be used to reject the output as canceled. +func (out Output) ApplyWithContext(ctx context.Context, + applier func(ctx context.Context, v interface{}) (interface{}, error)) Output { + + result := newOutput(out.s.deps...) go func() { - for { - v, known, err := out.Value() - if err != nil { - reject(err) - break - } else { - if known { - // If we have a known value, run the applier to transform it. - u, err := applier(v) - if err != nil { - reject(err) - break - } else { - // Now that we've transformed the value, it's possible we have another output. If so, pluck it - // out and go around to await it until we hit a real value. Note that we are not capturing the - // resources of this inner output, intentionally, as the output returned should be related to - // this output already. - if newout, ok := v.(*Output); ok { - out = newout - } else { - resolve(u, true) - break - } - } - } else { - // If the value isn't known, skip the apply function. - resolve(nil, false) - break - } - } + v, known, err := out.s.await(ctx) + if err != nil || !known { + result.s.fulfill(nil, known, err) + return } + + // If we have a known value, run the applier to transform it. + u, err := applier(ctx, v) + if err != nil { + result.s.reject(err) + return + } + + // Fulfill the result. + result.s.fulfill(u, true, nil) }() return result } -// Deps returns the dependencies for this output property. -func (out *Output) Deps() []Resource { return out.s.deps } - -// Value retrieves the underlying value for this output property. -func (out *Output) Value() (interface{}, bool, error) { - // If neither error nor value are available, first await the channel. Only one Goroutine will make it through this - // and is responsible for closing the channel, to signal to other awaiters that it's safe to read the values. - if out.s.voe == nil { - if voe := <-out.s.sync; voe != nil { - out.s.voe = voe // first time through, publish the value. - close(out.s.sync) // and close the channel to signal to others that the memozied value is available. - } - } - return out.s.voe.value, out.s.voe.known, out.s.voe.err -} - -// Archive retrives the underlying value for this output property as an archive. -func (out *Output) Archive() (asset.Archive, bool, error) { - v, known, err := out.Value() - if err != nil || !known { - return nil, known, err - } - return v.(asset.Archive), true, nil -} - -// Array retrives the underlying value for this output property as an array. -func (out *Output) Array() ([]interface{}, bool, error) { - v, known, err := out.Value() - if err != nil || !known { - return nil, known, err - } - return cast.ToSlice(v), true, nil -} - -// Asset retrives the underlying value for this output property as an asset. -func (out *Output) Asset() (asset.Asset, bool, error) { - v, known, err := out.Value() - if err != nil || !known { - return nil, known, err - } - return v.(asset.Asset), true, nil -} - -// Bool retrives the underlying value for this output property as a bool. -func (out *Output) Bool() (bool, bool, error) { - v, known, err := out.Value() - if err != nil || !known { - return false, known, err - } - return cast.ToBool(v), true, nil -} - -// Map retrives the underlying value for this output property as a map. -func (out *Output) Map() (map[string]interface{}, bool, error) { - v, known, err := out.Value() - if err != nil || !known { - return nil, known, err - } - return cast.ToStringMap(v), true, nil -} - -// Float32 retrives the underlying value for this output property as a float32. -func (out *Output) Float32() (float32, bool, error) { - v, known, err := out.Value() - if err != nil || !known { - return 0, known, err - } - return cast.ToFloat32(v), true, nil -} - -// Float64 retrives the underlying value for this output property as a float64. -func (out *Output) Float64() (float64, bool, error) { - v, known, err := out.Value() - if err != nil || !known { - return 0, known, err - } - return cast.ToFloat64(v), true, nil -} - -// ID retrives the underlying value for this output property as an ID. -func (out *Output) ID() (ID, bool, error) { - v, known, err := out.Value() - if err != nil || !known { - return "", known, err - } - return ID(toString(v)), true, nil -} - -// Int retrives the underlying value for this output property as a int. -func (out *Output) Int() (int, bool, error) { - v, known, err := out.Value() - if err != nil || !known { - return 0, known, err - } - return cast.ToInt(v), true, nil -} - -// Int8 retrives the underlying value for this output property as a int8. -func (out *Output) Int8() (int8, bool, error) { - v, known, err := out.Value() - if err != nil || !known { - return 0, known, err - } - return cast.ToInt8(v), true, nil -} - -// Int16 retrives the underlying value for this output property as a int16. -func (out *Output) Int16() (int16, bool, error) { - v, known, err := out.Value() - if err != nil || !known { - return 0, known, err - } - return cast.ToInt16(v), true, nil -} - -// Int32 retrives the underlying value for this output property as a int32. -func (out *Output) Int32() (int32, bool, error) { - v, known, err := out.Value() - if err != nil || !known { - return 0, known, err - } - return cast.ToInt32(v), true, nil -} - -// Int64 retrives the underlying value for this output property as a int64. -func (out *Output) Int64() (int64, bool, error) { - v, known, err := out.Value() - if err != nil || !known { - return 0, known, err - } - return cast.ToInt64(v), true, nil -} - -// String retrives the underlying value for this output property as a string. -func (out *Output) String() (string, bool, error) { - v, known, err := out.Value() - if err != nil || !known { - return "", known, err - } - return toString(v), true, nil -} - -// Uint retrives the underlying value for this output property as a uint. -func (out *Output) Uint() (uint, bool, error) { - v, known, err := out.Value() - if err != nil || !known { - return 0, known, err - } - return cast.ToUint(v), true, nil -} - -// Uint8 retrives the underlying value for this output property as a uint8. -func (out *Output) Uint8() (uint8, bool, error) { - v, known, err := out.Value() - if err != nil || !known { - return 0, known, err - } - return cast.ToUint8(v), true, nil -} - -// Uint16 retrives the underlying value for this output property as a uint16. -func (out *Output) Uint16() (uint16, bool, error) { - v, known, err := out.Value() - if err != nil || !known { - return 0, known, err - } - return cast.ToUint16(v), true, nil -} - -// Uint32 retrives the underlying value for this output property as a uint32. -func (out *Output) Uint32() (uint32, bool, error) { - v, known, err := out.Value() - if err != nil || !known { - return 0, known, err - } - return cast.ToUint32(v), true, nil -} - -// Uint64 retrives the underlying value for this output property as a uint64. -func (out *Output) Uint64() (uint64, bool, error) { - v, known, err := out.Value() - if err != nil || !known { - return 0, known, err - } - return cast.ToUint64(v), true, nil -} - -// URN retrives the underlying value for this output property as a URN. -func (out *Output) URN() (URN, error) { - v, known, err := out.Value() - if err != nil || !known { - return "", err - } - return URN(toString(v)), nil -} - // Outputs is a map of property name to value, one for each resource output property. -type Outputs map[string]*Output +type Outputs map[string]Output // ArchiveOutput is an Output that is typed to return archive values. type ArchiveOutput Output -// Value returns the underlying archive value. -func (out *ArchiveOutput) Value() (asset.Archive, bool, error) { - return (*Output)(out).Archive() -} +var archiveType = reflect.TypeOf((*asset.Archive)(nil)).Elem() // Apply applies a transformation to the archive value when it is available. -func (out *ArchiveOutput) Apply(applier func(asset.Archive) (interface{}, error)) *Output { - return (*Output)(out).Apply(func(v interface{}) (interface{}, error) { - return applier(v.(asset.Archive)) +func (out ArchiveOutput) Apply(applier func(asset.Archive) (interface{}, error)) Output { + return out.ApplyWithContext(context.Background(), func(_ context.Context, v asset.Archive) (interface{}, error) { + return applier(v) + }) +} + +// ApplyWithContext applies a transformation to the archive value when it is available. +func (out ArchiveOutput) ApplyWithContext(ctx context.Context, applier func(context.Context, asset.Archive) (interface{}, error)) Output { + return Output(out).ApplyWithContext(ctx, func(ctx context.Context, v interface{}) (interface{}, error) { + return applier(ctx, convert(v, archiveType).(asset.Archive)) }) } // ArrayOutput is an Output that is typed to return arrays of values. type ArrayOutput Output -// Value returns the underlying array value. -func (out *ArrayOutput) Value() ([]interface{}, bool, error) { - return (*Output)(out).Array() +var arrayType = reflect.TypeOf((*[]interface{})(nil)).Elem() + +// Apply applies a transformation to the archive value when it is available. +func (out ArrayOutput) Apply(applier func([]interface{}) (interface{}, error)) Output { + return out.ApplyWithContext(context.Background(), func(_ context.Context, v []interface{}) (interface{}, error) { + return applier(v) + }) } -// Apply applies a transformation to the array value when it is available. -func (out *ArrayOutput) Apply(applier func([]interface{}) (interface{}, error)) *Output { - return (*Output)(out).Apply(func(v interface{}) (interface{}, error) { - return applier(cast.ToSlice(v)) +// ApplyWithContext applies a transformation to the archive value when it is available. +func (out ArrayOutput) ApplyWithContext(ctx context.Context, applier func(context.Context, []interface{}) (interface{}, error)) Output { + return Output(out).ApplyWithContext(ctx, func(ctx context.Context, v interface{}) (interface{}, error) { + return applier(ctx, convert(v, arrayType).([]interface{})) }) } // AssetOutput is an Output that is typed to return asset values. type AssetOutput Output -// Value returns the underlying asset value. -func (out *AssetOutput) Value() (asset.Asset, bool, error) { - return (*Output)(out).Asset() +var assetType = reflect.TypeOf((*asset.Asset)(nil)).Elem() + +// Apply applies a transformation to the archive value when it is available. +func (out AssetOutput) Apply(applier func(asset.Asset) (interface{}, error)) Output { + return out.ApplyWithContext(context.Background(), func(_ context.Context, v asset.Asset) (interface{}, error) { + return applier(v) + }) } -// Apply applies a transformation to the asset value when it is available. -func (out *AssetOutput) Apply(applier func(asset.Asset) (interface{}, error)) *Output { - return (*Output)(out).Apply(func(v interface{}) (interface{}, error) { - return applier(v.(asset.Asset)) +// ApplyWithContext applies a transformation to the archive value when it is available. +func (out AssetOutput) ApplyWithContext(ctx context.Context, applier func(context.Context, asset.Asset) (interface{}, error)) Output { + return Output(out).ApplyWithContext(ctx, func(ctx context.Context, v interface{}) (interface{}, error) { + return applier(ctx, convert(v, assetType).(asset.Asset)) }) } // BoolOutput is an Output that is typed to return bool values. type BoolOutput Output -// Value returns the underlying bool value. -func (out *BoolOutput) Value() (bool, bool, error) { - return (*Output)(out).Bool() +var boolType = reflect.TypeOf(false) + +// Apply applies a transformation to the archive value when it is available. +func (out BoolOutput) Apply(applier func(bool) (interface{}, error)) Output { + return out.ApplyWithContext(context.Background(), func(_ context.Context, v bool) (interface{}, error) { + return applier(v) + }) } -// Apply applies a transformation to the bool value when it is available. -func (out *BoolOutput) Apply(applier func(bool) (interface{}, error)) *Output { - return (*Output)(out).Apply(func(v interface{}) (interface{}, error) { - return applier(v.(bool)) +// ApplyWithContext applies a transformation to the archive value when it is available. +func (out BoolOutput) ApplyWithContext(ctx context.Context, applier func(context.Context, bool) (interface{}, error)) Output { + return Output(out).ApplyWithContext(ctx, func(ctx context.Context, v interface{}) (interface{}, error) { + return applier(ctx, convert(v, boolType).(bool)) }) } // Float32Output is an Output that is typed to return float32 values. type Float32Output Output -// Value returns the underlying number value. -func (out *Float32Output) Value() (float32, bool, error) { - return (*Output)(out).Float32() +var float32Type = reflect.TypeOf(float32(0)) + +// Apply applies a transformation to the archive value when it is available. +func (out Float32Output) Apply(applier func(float32) (interface{}, error)) Output { + return out.ApplyWithContext(context.Background(), func(_ context.Context, v float32) (interface{}, error) { + return applier(v) + }) } -// Apply applies a transformation to the float32 value when it is available. -func (out *Float32Output) Apply(applier func(float32) (interface{}, error)) *Output { - return (*Output)(out).Apply(func(v interface{}) (interface{}, error) { - return applier(cast.ToFloat32(v)) +// ApplyWithContext applies a transformation to the archive value when it is available. +func (out Float32Output) ApplyWithContext(ctx context.Context, applier func(context.Context, float32) (interface{}, error)) Output { + return Output(out).ApplyWithContext(ctx, func(ctx context.Context, v interface{}) (interface{}, error) { + return applier(ctx, convert(v, float32Type).(float32)) }) } // Float64Output is an Output that is typed to return float64 values. type Float64Output Output -// Value returns the underlying number value. -func (out *Float64Output) Value() (float64, bool, error) { - return (*Output)(out).Float64() +var float64Type = reflect.TypeOf(float64(0)) + +// Apply applies a transformation to the archive value when it is available. +func (out Float64Output) Apply(applier func(float64) (interface{}, error)) Output { + return out.ApplyWithContext(context.Background(), func(_ context.Context, v float64) (interface{}, error) { + return applier(v) + }) } -// Apply applies a transformation to the float64 value when it is available. -func (out *Float64Output) Apply(applier func(float64) (interface{}, error)) *Output { - return (*Output)(out).Apply(func(v interface{}) (interface{}, error) { - return applier(cast.ToFloat64(v)) +// ApplyWithContext applies a transformation to the archive value when it is available. +func (out Float64Output) ApplyWithContext(ctx context.Context, applier func(context.Context, float64) (interface{}, error)) Output { + return Output(out).ApplyWithContext(ctx, func(ctx context.Context, v interface{}) (interface{}, error) { + return applier(ctx, convert(v, float64Type).(float64)) }) } // IDOutput is an Output that is typed to return ID values. type IDOutput Output -// Value returns the underlying number value. -func (out *IDOutput) Value() (ID, bool, error) { - return (*Output)(out).ID() +var stringType = reflect.TypeOf("") + +func (out IDOutput) await(ctx context.Context) (ID, bool, error) { + id, known, err := out.s.await(ctx) + if !known || err != nil { + return "", known, err + } + return ID(convert(id, stringType).(string)), true, nil } -// Apply applies a transformation to the ID value when it is available. -func (out *IDOutput) Apply(applier func(ID) (interface{}, error)) *Output { - return (*Output)(out).Apply(func(v interface{}) (interface{}, error) { - return applier(ID(toString(v))) +// Apply applies a transformation to the archive value when it is available. +func (out IDOutput) Apply(applier func(ID) (interface{}, error)) Output { + return out.ApplyWithContext(context.Background(), func(_ context.Context, v ID) (interface{}, error) { + return applier(v) + }) +} + +// ApplyWithContext applies a transformation to the archive value when it is available. +func (out IDOutput) ApplyWithContext(ctx context.Context, applier func(context.Context, ID) (interface{}, error)) Output { + return Output(out).ApplyWithContext(ctx, func(ctx context.Context, v interface{}) (interface{}, error) { + return applier(ctx, ID(convert(v, stringType).(string))) }) } // IntOutput is an Output that is typed to return int values. type IntOutput Output -// Value returns the underlying number value. -func (out *IntOutput) Value() (int, bool, error) { - return (*Output)(out).Int() +var intType = reflect.TypeOf(int(0)) + +// Apply applies a transformation to the archive value when it is available. +func (out IntOutput) Apply(applier func(int) (interface{}, error)) Output { + return out.ApplyWithContext(context.Background(), func(_ context.Context, v int) (interface{}, error) { + return applier(v) + }) } -// Apply applies a transformation to the int value when it is available. -func (out *IntOutput) Apply(applier func(int) (interface{}, error)) *Output { - return (*Output)(out).Apply(func(v interface{}) (interface{}, error) { - return applier(cast.ToInt(v)) +// ApplyWithContext applies a transformation to the archive value when it is available. +func (out IntOutput) ApplyWithContext(ctx context.Context, applier func(context.Context, int) (interface{}, error)) Output { + return Output(out).ApplyWithContext(ctx, func(ctx context.Context, v interface{}) (interface{}, error) { + return applier(ctx, convert(v, intType).(int)) }) } // Int8Output is an Output that is typed to return int8 values. type Int8Output Output -// Value returns the underlying number value. -func (out *Int8Output) Value() (int8, bool, error) { - return (*Output)(out).Int8() +var int8Type = reflect.TypeOf(int8(0)) + +// Apply applies a transformation to the archive value when it is available. +func (out Int8Output) Apply(applier func(int8) (interface{}, error)) Output { + return out.ApplyWithContext(context.Background(), func(_ context.Context, v int8) (interface{}, error) { + return applier(v) + }) } -// Apply applies a transformation to the int8 value when it is available. -func (out *Int8Output) Apply(applier func(int8) (interface{}, error)) *Output { - return (*Output)(out).Apply(func(v interface{}) (interface{}, error) { - return applier(cast.ToInt8(v)) +// ApplyWithContext applies a transformation to the archive value when it is available. +func (out Int8Output) ApplyWithContext(ctx context.Context, applier func(context.Context, int8) (interface{}, error)) Output { + return Output(out).ApplyWithContext(ctx, func(ctx context.Context, v interface{}) (interface{}, error) { + return applier(ctx, convert(v, int8Type).(int8)) }) } // Int16Output is an Output that is typed to return int16 values. type Int16Output Output -// Value returns the underlying number value. -func (out *Int16Output) Value() (int16, bool, error) { - return (*Output)(out).Int16() +var int16Type = reflect.TypeOf(int16(0)) + +// Apply applies a transformation to the archive value when it is available. +func (out Int16Output) Apply(applier func(int16) (interface{}, error)) Output { + return out.ApplyWithContext(context.Background(), func(_ context.Context, v int16) (interface{}, error) { + return applier(v) + }) } -// Apply applies a transformation to the int16 value when it is available. -func (out *Int16Output) Apply(applier func(int16) (interface{}, error)) *Output { - return (*Output)(out).Apply(func(v interface{}) (interface{}, error) { - return applier(cast.ToInt16(v)) +// ApplyWithContext applies a transformation to the archive value when it is available. +func (out Int16Output) ApplyWithContext(ctx context.Context, applier func(context.Context, int16) (interface{}, error)) Output { + return Output(out).ApplyWithContext(ctx, func(ctx context.Context, v interface{}) (interface{}, error) { + return applier(ctx, convert(v, int16Type).(int16)) }) } // Int32Output is an Output that is typed to return int32 values. type Int32Output Output -// Value returns the underlying number value. -func (out *Int32Output) Value() (int32, bool, error) { - return (*Output)(out).Int32() +var int32Type = reflect.TypeOf(int32(0)) + +// Apply applies a transformation to the archive value when it is available. +func (out Int32Output) Apply(applier func(int32) (interface{}, error)) Output { + return out.ApplyWithContext(context.Background(), func(_ context.Context, v int32) (interface{}, error) { + return applier(v) + }) } -// Apply applies a transformation to the int32 value when it is available. -func (out *Int32Output) Apply(applier func(int32) (interface{}, error)) *Output { - return (*Output)(out).Apply(func(v interface{}) (interface{}, error) { - return applier(cast.ToInt32(v)) +// ApplyWithContext applies a transformation to the archive value when it is available. +func (out Int32Output) ApplyWithContext(ctx context.Context, applier func(context.Context, int32) (interface{}, error)) Output { + return Output(out).ApplyWithContext(ctx, func(ctx context.Context, v interface{}) (interface{}, error) { + return applier(ctx, convert(v, int32Type).(int32)) }) } // Int64Output is an Output that is typed to return int64 values. type Int64Output Output -// Value returns the underlying number value. -func (out *Int64Output) Value() (int64, bool, error) { return (*Output)(out).Int64() } +var int64Type = reflect.TypeOf(int64(0)) -// Apply applies a transformation to the int64 value when it is available. -func (out *Int64Output) Apply(applier func(int64) (interface{}, error)) *Output { - return (*Output)(out).Apply(func(v interface{}) (interface{}, error) { - return applier(cast.ToInt64(v)) +// Apply applies a transformation to the archive value when it is available. +func (out Int64Output) Apply(applier func(int64) (interface{}, error)) Output { + return out.ApplyWithContext(context.Background(), func(_ context.Context, v int64) (interface{}, error) { + return applier(v) + }) +} + +// ApplyWithContext applies a transformation to the archive value when it is available. +func (out Int64Output) ApplyWithContext(ctx context.Context, applier func(context.Context, int64) (interface{}, error)) Output { + return Output(out).ApplyWithContext(ctx, func(ctx context.Context, v interface{}) (interface{}, error) { + return applier(ctx, convert(v, int64Type).(int64)) }) } // MapOutput is an Output that is typed to return map values. type MapOutput Output -// Value returns the underlying number value. -func (out *MapOutput) Value() (map[string]interface{}, bool, error) { - return (*Output)(out).Map() -} +var mapType = reflect.TypeOf(map[string]interface{}{}) // Apply applies a transformation to the number value when it is available. -func (out *MapOutput) Apply(applier func(map[string]interface{}) (interface{}, error)) *Output { - return (*Output)(out).Apply(func(v interface{}) (interface{}, error) { - return applier(cast.ToStringMap(v)) +func (out MapOutput) Apply(applier func(map[string]interface{}) (interface{}, error)) Output { + return out.ApplyWithContext(context.Background(), func(_ context.Context, v map[string]interface{}) (interface{}, error) { + return applier(v) + }) +} + +// ApplyWithContext applies a transformation to the number value when it is available. +func (out MapOutput) ApplyWithContext(ctx context.Context, applier func(context.Context, map[string]interface{}) (interface{}, error)) Output { + return Output(out).ApplyWithContext(ctx, func(ctx context.Context, v interface{}) (interface{}, error) { + return applier(ctx, convert(v, mapType).(map[string]interface{})) }) } // StringOutput is an Output that is typed to return number values. type StringOutput Output -// Value returns the underlying number value. -func (out *StringOutput) Value() (string, bool, error) { - return (*Output)(out).String() +// Apply applies a transformation to the archive value when it is available. +func (out StringOutput) Apply(applier func(string) (interface{}, error)) Output { + return out.ApplyWithContext(context.Background(), func(_ context.Context, v string) (interface{}, error) { + return applier(v) + }) } -// Apply applies a transformation to the number value when it is available. -func (out *StringOutput) Apply(applier func(string) (interface{}, error)) *Output { - return (*Output)(out).Apply(func(v interface{}) (interface{}, error) { - return applier(toString(v)) +// ApplyWithContext applies a transformation to the archive value when it is available. +func (out StringOutput) ApplyWithContext(ctx context.Context, applier func(context.Context, string) (interface{}, error)) Output { + return Output(out).ApplyWithContext(ctx, func(ctx context.Context, v interface{}) (interface{}, error) { + return applier(ctx, convert(v, stringType).(string)) }) } // UintOutput is an Output that is typed to return uint values. type UintOutput Output -// Value returns the underlying number value. -func (out *UintOutput) Value() (uint, bool, error) { - return (*Output)(out).Uint() +var uintType = reflect.TypeOf(uint(0)) + +// Apply applies a transformation to the archive value when it is available. +func (out UintOutput) Apply(applier func(uint) (interface{}, error)) Output { + return out.ApplyWithContext(context.Background(), func(_ context.Context, v uint) (interface{}, error) { + return applier(v) + }) } -// Apply applies a transformation to the uint value when it is available. -func (out *UintOutput) Apply(applier func(uint) (interface{}, error)) *Output { - return (*Output)(out).Apply(func(v interface{}) (interface{}, error) { - return applier(cast.ToUint(v)) +// ApplyWithContext applies a transformation to the archive value when it is available. +func (out UintOutput) ApplyWithContext(ctx context.Context, applier func(context.Context, uint) (interface{}, error)) Output { + return Output(out).ApplyWithContext(ctx, func(ctx context.Context, v interface{}) (interface{}, error) { + return applier(ctx, convert(v, uintType).(uint)) }) } // Uint8Output is an Output that is typed to return uint8 values. type Uint8Output Output -// Value returns the underlying number value. -func (out *Uint8Output) Value() (uint8, bool, error) { - return (*Output)(out).Uint8() +var uint8Type = reflect.TypeOf(uint8(0)) + +// Apply applies a transformation to the archive value when it is available. +func (out Uint8Output) Apply(applier func(uint8) (interface{}, error)) Output { + return out.ApplyWithContext(context.Background(), func(_ context.Context, v uint8) (interface{}, error) { + return applier(v) + }) } -// Apply applies a transformation to the uint8 value when it is available. -func (out *Uint8Output) Apply(applier func(uint8) (interface{}, error)) *Output { - return (*Output)(out).Apply(func(v interface{}) (interface{}, error) { - return applier(cast.ToUint8(v)) +// ApplyWithContext applies a transformation to the archive value when it is available. +func (out Uint8Output) ApplyWithContext(ctx context.Context, applier func(context.Context, uint8) (interface{}, error)) Output { + return Output(out).ApplyWithContext(ctx, func(ctx context.Context, v interface{}) (interface{}, error) { + return applier(ctx, convert(v, uint8Type).(uint8)) }) } // Uint16Output is an Output that is typed to return uint16 values. type Uint16Output Output -// Value returns the underlying number value. -func (out *Uint16Output) Value() (uint16, bool, error) { - return (*Output)(out).Uint16() +var uint16Type = reflect.TypeOf(uint16(0)) + +// Apply applies a transformation to the archive value when it is available. +func (out Uint16Output) Apply(applier func(uint16) (interface{}, error)) Output { + return out.ApplyWithContext(context.Background(), func(_ context.Context, v uint16) (interface{}, error) { + return applier(v) + }) } -// Apply applies a transformation to the uint16 value when it is available. -func (out *Uint16Output) Apply(applier func(uint16) (interface{}, error)) *Output { - return (*Output)(out).Apply(func(v interface{}) (interface{}, error) { - return applier(cast.ToUint16(v)) +// ApplyWithContext applies a transformation to the archive value when it is available. +func (out Uint16Output) ApplyWithContext(ctx context.Context, applier func(context.Context, uint16) (interface{}, error)) Output { + return Output(out).ApplyWithContext(ctx, func(ctx context.Context, v interface{}) (interface{}, error) { + return applier(ctx, convert(v, uint16Type).(uint16)) }) } // Uint32Output is an Output that is typed to return uint32 values. type Uint32Output Output -// Value returns the underlying number value. -func (out *Uint32Output) Value() (uint32, bool, error) { - return (*Output)(out).Uint32() +var uint32Type = reflect.TypeOf(uint32(0)) + +// Apply applies a transformation to the archive value when it is available. +func (out Uint32Output) Apply(applier func(uint32) (interface{}, error)) Output { + return out.ApplyWithContext(context.Background(), func(_ context.Context, v uint32) (interface{}, error) { + return applier(v) + }) } -// Apply applies a transformation to the uint32 value when it is available. -func (out *Uint32Output) Apply(applier func(uint32) (interface{}, error)) *Output { - return (*Output)(out).Apply(func(v interface{}) (interface{}, error) { - return applier(cast.ToUint32(v)) +// ApplyWithContext applies a transformation to the archive value when it is available. +func (out Uint32Output) ApplyWithContext(ctx context.Context, applier func(context.Context, uint32) (interface{}, error)) Output { + return Output(out).ApplyWithContext(ctx, func(ctx context.Context, v interface{}) (interface{}, error) { + return applier(ctx, convert(v, uint32Type).(uint32)) }) } // Uint64Output is an Output that is typed to return uint64 values. type Uint64Output Output -// Value returns the underlying number value. -func (out *Uint64Output) Value() (uint64, bool, error) { - return (*Output)(out).Uint64() +var uint64Type = reflect.TypeOf(uint64(0)) + +// Apply applies a transformation to the archive value when it is available. +func (out Uint64Output) Apply(applier func(uint64) (interface{}, error)) Output { + return out.ApplyWithContext(context.Background(), func(_ context.Context, v uint64) (interface{}, error) { + return applier(v) + }) } -// Apply applies a transformation to the uint64 value when it is available. -func (out *Uint64Output) Apply(applier func(uint64) (interface{}, error)) *Output { - return (*Output)(out).Apply(func(v interface{}) (interface{}, error) { - return applier(cast.ToUint64(v)) +// ApplyWithContext applies a transformation to the archive value when it is available. +func (out Uint64Output) ApplyWithContext(ctx context.Context, applier func(context.Context, uint64) (interface{}, error)) Output { + return Output(out).ApplyWithContext(ctx, func(ctx context.Context, v interface{}) (interface{}, error) { + return applier(ctx, convert(v, uint64Type).(uint64)) }) } // URNOutput is an Output that is typed to return URN values. type URNOutput Output -// Value returns the underlying number value. -func (out *URNOutput) Value() (URN, error) { - return (*Output)(out).URN() +func (out URNOutput) await(ctx context.Context) (URN, bool, error) { + urn, known, err := out.s.await(ctx) + if !known || err != nil { + return "", known, err + } + return URN(convert(urn, stringType).(string)), true, nil } -// Apply applies a transformation to the URN value when it is available. -func (out *URNOutput) Apply(applier func(URN) (interface{}, error)) *Output { - return (*Output)(out).Apply(func(v interface{}) (interface{}, error) { - return applier(URN(toString(v))) +// Apply applies a transformation to the archive value when it is available. +func (out URNOutput) Apply(applier func(URN) (interface{}, error)) Output { + return out.ApplyWithContext(context.Background(), func(_ context.Context, v URN) (interface{}, error) { + return applier(v) }) } -// toString attempts to convert v to a string. -func toString(v interface{}) string { - if s := cast.ToString(v); s != "" { - return s - } - - // See if this can convert through reflection (e.g., for type aliases). - st := reflect.TypeOf("") - sv := reflect.ValueOf(v) - if sv.Type().ConvertibleTo(st) { - return sv.Convert(st).Interface().(string) - } - - return "" +// ApplyWithContext applies a transformation to the archive value when it is available. +func (out URNOutput) ApplyWithContext(ctx context.Context, applier func(context.Context, URN) (interface{}, error)) Output { + return Output(out).ApplyWithContext(ctx, func(ctx context.Context, v interface{}) (interface{}, error) { + return applier(ctx, URN(convert(v, stringType).(string))) + }) +} + +func convert(v interface{}, to reflect.Type) interface{} { + rv := reflect.ValueOf(v) + if !rv.Type().ConvertibleTo(to) { + panic(errors.Errorf("cannot convert output value of type %s to %s", rv.Type(), to)) + } + return rv.Convert(to).Interface() } diff --git a/sdk/go/pulumi/properties_test.go b/sdk/go/pulumi/properties_test.go index a2af8b46f..68ef0de9e 100644 --- a/sdk/go/pulumi/properties_test.go +++ b/sdk/go/pulumi/properties_test.go @@ -15,178 +15,148 @@ package pulumi import ( + "context" "testing" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) +func assertApplied(t *testing.T, o Output) { + _, known, err := o.s.await(context.Background()) + assert.True(t, known) + assert.Nil(t, err) +} + func TestBasicOutputs(t *testing.T) { // Just test basic resolve and reject functionality. { - out, resolve, _ := NewOutput(nil) + out, resolve, _ := NewOutput() go func() { - resolve(42, true) + resolve(42) }() - v, known, err := out.Value() + v, known, err := out.s.await(context.Background()) assert.Nil(t, err) assert.True(t, known) assert.NotNil(t, v) assert.Equal(t, 42, v.(int)) } { - out, _, reject := NewOutput(nil) + out, _, reject := NewOutput() go func() { reject(errors.New("boom")) }() - v, _, err := out.Value() + v, _, err := out.s.await(context.Background()) assert.NotNil(t, err) assert.Nil(t, v) } } func TestArrayOutputs(t *testing.T) { - out, resolve, _ := NewOutput(nil) + out, resolve, _ := NewOutput() go func() { - resolve([]interface{}{nil, 0, "x"}, true) + resolve([]interface{}{nil, 0, "x"}) }() { - v, known, err := out.Array() - assert.Nil(t, err) - assert.True(t, known) - assert.NotNil(t, v) - if assert.Equal(t, 3, len(v)) { - assert.Equal(t, nil, v[0]) - assert.Equal(t, 0, v[1]) - assert.Equal(t, "x", v[2]) - } - } - { - arr := (*ArrayOutput)(out) - v, _, err := arr.Value() - assert.Nil(t, err) - assert.NotNil(t, v) - if assert.Equal(t, 3, len(v)) { - assert.Equal(t, nil, v[0]) - assert.Equal(t, 0, v[1]) - assert.Equal(t, "x", v[2]) - } + arr := ArrayOutput(out) + assertApplied(t, arr.Apply(func(arr []interface{}) (interface{}, error) { + assert.NotNil(t, arr) + if assert.Equal(t, 3, len(arr)) { + assert.Equal(t, nil, arr[0]) + assert.Equal(t, 0, arr[1]) + assert.Equal(t, "x", arr[2]) + } + return nil, nil + })) } } func TestBoolOutputs(t *testing.T) { - out, resolve, _ := NewOutput(nil) + out, resolve, _ := NewOutput() go func() { - resolve(true, true) + resolve(true) }() { - v, known, err := out.Bool() - assert.Nil(t, err) - assert.True(t, known) - assert.True(t, v) - } - { - b := (*BoolOutput)(out) - v, known, err := b.Value() - assert.Nil(t, err) - assert.True(t, known) - assert.True(t, v) + b := BoolOutput(out) + assertApplied(t, b.Apply(func(v bool) (interface{}, error) { + assert.True(t, v) + return nil, nil + })) } } func TestMapOutputs(t *testing.T) { - out, resolve, _ := NewOutput(nil) + out, resolve, _ := NewOutput() go func() { resolve(map[string]interface{}{ "x": 1, "y": false, "z": "abc", - }, true) + }) }() { - v, known, err := out.Map() - assert.Nil(t, err) - assert.True(t, known) - assert.NotNil(t, v) - assert.Equal(t, 1, v["x"]) - assert.Equal(t, false, v["y"]) - assert.Equal(t, "abc", v["z"]) - } - { - b := (*MapOutput)(out) - v, known, err := b.Value() - assert.Nil(t, err) - assert.True(t, known) - assert.NotNil(t, v) - assert.Equal(t, 1, v["x"]) - assert.Equal(t, false, v["y"]) - assert.Equal(t, "abc", v["z"]) + b := MapOutput(out) + assertApplied(t, b.Apply(func(v map[string]interface{}) (interface{}, error) { + assert.NotNil(t, v) + assert.Equal(t, 1, v["x"]) + assert.Equal(t, false, v["y"]) + assert.Equal(t, "abc", v["z"]) + return nil, nil + })) } } func TestNumberOutputs(t *testing.T) { - out, resolve, _ := NewOutput(nil) + out, resolve, _ := NewOutput() go func() { - resolve(42.345, true) + resolve(42.345) }() { - v, known, err := out.Float64() - assert.Nil(t, err) - assert.True(t, known) - assert.Equal(t, 42.345, v) - } - { - b := (*Float64Output)(out) - v, known, err := b.Value() - assert.Nil(t, err) - assert.True(t, known) - assert.Equal(t, 42.345, v) + b := Float64Output(out) + assertApplied(t, b.Apply(func(v float64) (interface{}, error) { + assert.Equal(t, 42.345, v) + return nil, nil + })) } } func TestStringOutputs(t *testing.T) { - out, resolve, _ := NewOutput(nil) + out, resolve, _ := NewOutput() go func() { - resolve("a stringy output", true) + resolve("a stringy output") }() { - v, known, err := out.String() - assert.Nil(t, err) - assert.True(t, known) - assert.Equal(t, "a stringy output", v) - } - { - b := (*StringOutput)(out) - v, known, err := b.Value() - assert.Nil(t, err) - assert.True(t, known) - assert.Equal(t, "a stringy output", v) + b := StringOutput(out) + assertApplied(t, b.Apply(func(v string) (interface{}, error) { + assert.Equal(t, "a stringy output", v) + return nil, nil + })) } } func TestResolveOutputToOutput(t *testing.T) { // Test that resolving an output to an output yields the value, not the output. { - out, resolve, _ := NewOutput(nil) + out, resolve, _ := NewOutput() go func() { - other, resolveOther, _ := NewOutput(nil) - resolve(other, true) - go func() { resolveOther(99, true) }() + other, resolveOther, _ := NewOutput() + resolve(other) + go func() { resolveOther(99) }() }() - v, known, err := out.Value() - assert.Nil(t, err) - assert.True(t, known) - assert.Equal(t, v, 99) + assertApplied(t, out.Apply(func(v interface{}) (interface{}, error) { + assert.Equal(t, v, 99) + return nil, nil + })) } // Similarly, test that resolving an output to a rejected output yields an error. { - out, resolve, _ := NewOutput(nil) + out, resolve, _ := NewOutput() go func() { - other, _, rejectOther := NewOutput(nil) - resolve(other, true) + other, _, rejectOther := NewOutput() + resolve(other) go func() { rejectOther(errors.New("boom")) }() }() - v, _, err := out.Value() + v, _, err := out.s.await(context.Background()) assert.NotNil(t, err) assert.Nil(t, v) } @@ -195,81 +165,104 @@ func TestResolveOutputToOutput(t *testing.T) { func TestOutputApply(t *testing.T) { // Test that resolved outputs lead to applies being run. { - out, resolve, _ := NewOutput(nil) - go func() { resolve(42, true) }() + out, resolve, _ := NewOutput() + go func() { resolve(42) }() var ranApp bool - b := (*IntOutput)(out) + b := IntOutput(out) app := b.Apply(func(v int) (interface{}, error) { ranApp = true return v + 1, nil }) - v, known, err := app.Value() + v, known, err := app.s.await(context.Background()) assert.True(t, ranApp) assert.Nil(t, err) assert.True(t, known) assert.Equal(t, v, 43) } - // Test that resolved, but known outputs, skip the running of applies. + // Test that resolved, but unknown outputs, skip the running of applies. { - out, resolve, _ := NewOutput(nil) - go func() { resolve(42, false) }() + out := newOutput() + go func() { out.s.fulfill(42, false, nil) }() var ranApp bool - b := (*IntOutput)(out) + b := IntOutput(out) app := b.Apply(func(v int) (interface{}, error) { ranApp = true return v + 1, nil }) - _, known, err := app.Value() + _, known, err := app.s.await(context.Background()) assert.False(t, ranApp) assert.Nil(t, err) assert.False(t, known) } // Test that rejected outputs do not run the apply, and instead flow the error. { - out, _, reject := NewOutput(nil) + out, _, reject := NewOutput() go func() { reject(errors.New("boom")) }() var ranApp bool - b := (*IntOutput)(out) + b := IntOutput(out) app := b.Apply(func(v int) (interface{}, error) { ranApp = true return v + 1, nil }) - v, _, err := app.Value() + v, _, err := app.s.await(context.Background()) assert.False(t, ranApp) assert.NotNil(t, err) assert.Nil(t, v) } // Test that an an apply that returns an output returns the resolution of that output, not the output itself. { - out, resolve, _ := NewOutput(nil) - go func() { resolve(42, true) }() + out, resolve, _ := NewOutput() + go func() { resolve(42) }() var ranApp bool - b := (*IntOutput)(out) + b := IntOutput(out) app := b.Apply(func(v int) (interface{}, error) { - other, resolveOther, _ := NewOutput(nil) - go func() { resolveOther(v+1, true) }() + other, resolveOther, _ := NewOutput() + go func() { resolveOther(v + 1) }() ranApp = true return other, nil }) - v, known, err := app.Value() + v, known, err := app.s.await(context.Background()) assert.True(t, ranApp) assert.Nil(t, err) assert.True(t, known) assert.Equal(t, v, 43) + + app = b.Apply(func(v int) (interface{}, error) { + other, resolveOther, _ := NewOutput() + go func() { resolveOther(v + 2) }() + ranApp = true + return IntOutput(other), nil + }) + v, known, err = app.s.await(context.Background()) + assert.True(t, ranApp) + assert.Nil(t, err) + assert.True(t, known) + assert.Equal(t, v, 44) } // Test that an an apply that reject an output returns the rejection of that output, not the output itself. { - out, resolve, _ := NewOutput(nil) - go func() { resolve(42, true) }() + out, resolve, _ := NewOutput() + go func() { resolve(42) }() var ranApp bool - b := (*IntOutput)(out) + b := IntOutput(out) app := b.Apply(func(v int) (interface{}, error) { - other, _, rejectOther := NewOutput(nil) + other, _, rejectOther := NewOutput() go func() { rejectOther(errors.New("boom")) }() ranApp = true return other, nil }) - v, _, err := app.Value() + v, _, err := app.s.await(context.Background()) + assert.True(t, ranApp) + assert.NotNil(t, err) + assert.Nil(t, v) + + app = b.Apply(func(v int) (interface{}, error) { + other, _, rejectOther := NewOutput() + go func() { rejectOther(errors.New("boom")) }() + ranApp = true + return IntOutput(other), nil + }) + v, _, err = app.s.await(context.Background()) assert.True(t, ranApp) assert.NotNil(t, err) assert.Nil(t, v) diff --git a/sdk/go/pulumi/resource.go b/sdk/go/pulumi/resource.go index 6a5756494..17b3ab74a 100644 --- a/sdk/go/pulumi/resource.go +++ b/sdk/go/pulumi/resource.go @@ -24,7 +24,7 @@ type ( // Resource represents a cloud resource managed by Pulumi. type Resource interface { // URN is this resource's stable logical URN used to distinctly address it before, during, and after deployments. - URN() *URNOutput + URN() URNOutput } // CustomResource is a cloud resource whose create, read, update, and delete (CRUD) operations are managed by performing @@ -34,7 +34,7 @@ type CustomResource interface { Resource // ID is the provider-assigned unique identifier for this managed resource. It is set during deployments, // but might be missing ("") during planning phases. - ID() *IDOutput + ID() IDOutput } // ComponentResource is a resource that aggregates one or more other child resources into a higher level abstraction. diff --git a/sdk/go/pulumi/rpc.go b/sdk/go/pulumi/rpc.go index aa55d0673..c9b992fa9 100644 --- a/sdk/go/pulumi/rpc.go +++ b/sdk/go/pulumi/rpc.go @@ -20,6 +20,7 @@ import ( structpb "github.com/golang/protobuf/ptypes/struct" "github.com/pkg/errors" "github.com/spf13/cast" + "golang.org/x/net/context" "github.com/pulumi/pulumi/pkg/resource" "github.com/pulumi/pulumi/pkg/resource/plugin" @@ -27,7 +28,9 @@ import ( ) // marshalInputs turns resource property inputs into a gRPC struct suitable for marshaling. -func marshalInputs(props map[string]interface{}) (*structpb.Struct, map[string][]URN, []URN, error) { +func marshalInputs(props map[string]interface{}, + keepUnknowns bool) (*structpb.Struct, map[string][]URN, []URN, error) { + var depURNs []URN pmap, pdeps := make(map[string]interface{}), make(map[string][]URN) for key := range props { @@ -42,7 +45,7 @@ func marshalInputs(props map[string]interface{}) (*structpb.Struct, map[string][ // Record all dependencies accumulated from reading this property. deps := make([]URN, 0, len(resourceDeps)) for _, dep := range resourceDeps { - depURN, err := dep.URN().Value() + depURN, _, err := dep.URN().await(context.TODO()) if err != nil { return nil, nil, nil, err } @@ -56,7 +59,7 @@ func marshalInputs(props map[string]interface{}) (*structpb.Struct, map[string][ // Marshal all properties for the RPC call. m, err := plugin.MarshalProperties( resource.NewPropertyMapFromMap(pmap), - plugin.MarshalOptions{KeepUnknowns: true}, + plugin.MarshalOptions{KeepUnknowns: keepUnknowns}, ) return m, pdeps, depURNs, err } @@ -73,114 +76,135 @@ const ( // marshalInput marshals an input value, returning its raw serializable value along with any dependencies. func marshalInput(v interface{}) (interface{}, []Resource, error) { - // If nil, just return that. - if v == nil { - return nil, nil, nil - } + for { + // If v is nil, just return that. + if v == nil { + return nil, nil, nil + } - // Next, look for some well known types. - switch t := v.(type) { - case bool, int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64, float32, float64, string: - return t, nil, nil - case asset.Asset: - return map[string]interface{}{ - rpcTokenSpecialSigKey: rpcTokenSpecialAssetSig, - "path": t.Path(), - "text": t.Text(), - "uri": t.URI(), - }, nil, nil - case asset.Archive: - var assets map[string]interface{} - if as := t.Assets(); as != nil { - assets = make(map[string]interface{}) - for k, a := range as { - aa, _, err := marshalInput(a) + // If this is an Output, recurse. + if out, ok := isOutput(v); ok { + return marshalInputOutput(out) + } + + // Next, look for some well known types. + switch v := v.(type) { + case asset.Asset: + return map[string]interface{}{ + rpcTokenSpecialSigKey: rpcTokenSpecialAssetSig, + "path": v.Path(), + "text": v.Text(), + "uri": v.URI(), + }, nil, nil + case asset.Archive: + var assets map[string]interface{} + if as := v.Assets(); as != nil { + assets = make(map[string]interface{}) + for k, a := range as { + aa, _, err := marshalInput(a) + if err != nil { + return nil, nil, err + } + assets[k] = aa + } + } + + return map[string]interface{}{ + rpcTokenSpecialSigKey: rpcTokenSpecialAssetSig, + "assets": assets, + "path": v.Path(), + "uri": v.URI(), + }, nil, nil + case CustomResource: + // Resources aren't serializable; instead, serialize a reference to ID, tracking as a dependency. + e, d, err := marshalInput(v.ID()) + if err != nil { + return nil, nil, err + } + return e, append([]Resource{v}, d...), nil + } + + rv := reflect.ValueOf(v) + switch rv.Type().Kind() { + case reflect.Bool: + return rv.Bool(), nil, nil + case reflect.Int: + return int(rv.Int()), nil, nil + case reflect.Int8: + return int8(rv.Int()), nil, nil + case reflect.Int16: + return int16(rv.Int()), nil, nil + case reflect.Int32: + return int32(rv.Int()), nil, nil + case reflect.Int64: + return rv.Int(), nil, nil + case reflect.Uint: + return uint(rv.Uint()), nil, nil + case reflect.Uint8: + return uint8(rv.Uint()), nil, nil + case reflect.Uint16: + return uint16(rv.Uint()), nil, nil + case reflect.Uint32: + return uint32(rv.Uint()), nil, nil + case reflect.Uint64: + return rv.Uint(), nil, nil + case reflect.Float32: + return float32(rv.Float()), nil, nil + case reflect.Float64: + return rv.Float(), nil, nil + case reflect.Ptr, reflect.Interface: + // Dereference non-nil pointers and interfaces. + if rv.IsNil() { + return nil, nil, nil + } + rv = rv.Elem() + case reflect.Array, reflect.Slice: + // If an array or a slice, create a new array by recursing into elements. + var arr []interface{} + var deps []Resource + for i := 0; i < rv.Len(); i++ { + elem := rv.Index(i) + e, d, err := marshalInput(elem.Interface()) if err != nil { return nil, nil, err } - assets[k] = aa + arr = append(arr, e) + deps = append(deps, d...) } - } + return arr, deps, nil + case reflect.Map: + // For maps, only support string-based keys, and recurse into the values. + obj := make(map[string]interface{}) + var deps []Resource + for _, key := range rv.MapKeys() { + k, ok := key.Interface().(string) + if !ok { + return nil, nil, + errors.Errorf("expected map keys to be strings; got %v", reflect.TypeOf(key.Interface())) + } + value := rv.MapIndex(key) + mv, d, err := marshalInput(value.Interface()) + if err != nil { + return nil, nil, err + } - return map[string]interface{}{ - rpcTokenSpecialSigKey: rpcTokenSpecialAssetSig, - "assets": assets, - "path": t.Path(), - "uri": t.URI(), - }, nil, nil - case Output: - return marshalInputOutput(&t) - case *Output: - return marshalInputOutput(t) - case CustomResource: - // Resources aren't serializable; instead, serialize a reference to ID, tracking as a dependency.a - e, d, err := marshalInput(t.ID()) - if err != nil { - return nil, nil, err + obj[k] = mv + deps = append(deps, d...) + } + return obj, deps, nil + case reflect.String: + return rv.String(), nil, nil + default: + return nil, nil, errors.Errorf("unrecognized input property type: %v (%T)", v, v) } - return e, append([]Resource{t}, d...), nil + v = rv.Interface() } - // Finally, handle the usual primitives (numbers, strings, arrays, maps, ...) - rv := reflect.ValueOf(v) - switch rk := rv.Type().Kind(); rk { - case reflect.Array, reflect.Slice: - // If an array or a slice, create a new array by recursing into elements. - var arr []interface{} - var deps []Resource - for i := 0; i < rv.Len(); i++ { - elem := rv.Index(i) - e, d, err := marshalInput(elem.Interface()) - if err != nil { - return nil, nil, err - } - arr = append(arr, e) - deps = append(deps, d...) - } - return arr, deps, nil - case reflect.Map: - // For maps, only support string-based keys, and recurse into the values. - obj := make(map[string]interface{}) - var deps []Resource - for _, key := range rv.MapKeys() { - k, ok := key.Interface().(string) - if !ok { - return nil, nil, - errors.Errorf("expected map keys to be strings; got %v", reflect.TypeOf(key.Interface())) - } - value := rv.MapIndex(key) - mv, d, err := marshalInput(value.Interface()) - if err != nil { - return nil, nil, err - } - - obj[k] = mv - deps = append(deps, d...) - } - return obj, deps, nil - case reflect.Ptr: - // See if this is an alias for *Output. If so, convert to an *Output, and recurse. - ot := reflect.TypeOf(&Output{}) - if rv.Type().ConvertibleTo(ot) { - oo := rv.Convert(ot) - return marshalInput(oo.Interface()) - } - - // For all other pointers, recurse into the underlying value. - if rv.IsNil() { - return nil, nil, nil - } - return marshalInput(rv.Elem().Interface()) - case reflect.String: - return marshalInput(rv.String()) - } - - return nil, nil, errors.Errorf("unrecognized input property type: %v (%v)", v, reflect.TypeOf(v)) } -func marshalInputOutput(out *Output) (interface{}, []Resource, error) { +func marshalInputOutput(out Output) (interface{}, []Resource, error) { // Await the value and return its raw value. - ov, known, err := out.Value() + ov, known, err := out.s.await(context.TODO()) if err != nil { return nil, nil, err } @@ -191,11 +215,11 @@ func marshalInputOutput(out *Output) (interface{}, []Resource, error) { if merr != nil { return nil, nil, merr } - return e, append(out.Deps(), d...), nil + return e, append(out.s.dependencies(), d...), nil } // Otherwise, simply return the unknown value sentinel. - return rpcTokenUnknownValue, out.Deps(), nil + return rpcTokenUnknownValue, out.s.dependencies(), nil } // unmarshalOutputs unmarshals all the outputs into a simple map. diff --git a/sdk/go/pulumi/rpc_test.go b/sdk/go/pulumi/rpc_test.go index bdc107a31..e983e0b7f 100644 --- a/sdk/go/pulumi/rpc_test.go +++ b/sdk/go/pulumi/rpc_test.go @@ -25,8 +25,11 @@ import ( // TestMarshalRoundtrip ensures that marshaling a complex structure to and from its on-the-wire gRPC format succeeds. func TestMarshalRoundtrip(t *testing.T) { // Create interesting inputs. - out, resolve, _ := NewOutput(nil) - resolve("outputty", true) + out, resolve, _ := NewOutput() + resolve("outputty") + out2 := newOutput() + out2.s.fulfill(nil, false, nil) + out3 := Output{} input := map[string]interface{}{ "s": "a string", "a": true, @@ -47,10 +50,13 @@ func TestMarshalRoundtrip(t *testing.T) { "y": 999.9, "z": false, }, + "g": out2, + "h": URN("foo"), + "i": out3, } // Marshal those inputs. - m, pdeps, deps, err := marshalInputs(input) + m, pdeps, deps, err := marshalInputs(input, true) if !assert.Nil(t, err) { assert.Equal(t, len(input), len(pdeps)) assert.Equal(t, 0, len(deps)) @@ -83,9 +89,82 @@ func TestMarshalRoundtrip(t *testing.T) { assert.Equal(t, "y", am["x"]) assert.Equal(t, 999.9, am["y"]) assert.Equal(t, false, am["z"]) + assert.Equal(t, rpcTokenUnknownValue, res["g"]) + assert.Equal(t, "foo", res["h"]) + assert.Equal(t, rpcTokenUnknownValue, res["i"]) } } } + + // Marshal those inputs without unknowns. + m, pdeps, deps, err = marshalInputs(input, false) + if !assert.Nil(t, err) { + assert.Equal(t, len(input), len(pdeps)) + assert.Equal(t, 0, len(deps)) + + // Now just unmarshal and ensure the resulting map matches. + res, err := unmarshalOutputs(m) + if !assert.Nil(t, err) { + if !assert.NotNil(t, res) { + assert.Equal(t, "a string", res["s"]) + assert.Equal(t, true, res["a"]) + assert.Equal(t, 42, res["b"]) + assert.Equal(t, "put a lime in the coconut", res["cStringAsset"].(asset.Asset).Text()) + assert.Equal(t, "foo.txt", res["cFileAsset"].(asset.Asset).Path()) + assert.Equal(t, "https://pulumi.com/fake/asset.txt", res["cRemoteAsset"].(asset.Asset).URI()) + ar := res["dAssetArchive"].(asset.Archive).Assets() + assert.Equal(t, 2, len(ar)) + assert.Equal(t, "bar.txt", ar["subAsset"].(asset.Asset).Path()) + assert.Equal(t, "bar.zip", ar["subrchive"].(asset.Archive).Path()) + assert.Equal(t, "foo.zip", res["dFileArchive"].(asset.Archive).Path()) + assert.Equal(t, "https://pulumi.com/fake/archive.zip", res["dRemoteArchive"].(asset.Archive).URI()) + assert.Equal(t, "outputty", res["e"]) + aa := res["fArray"].([]interface{}) + assert.Equal(t, 4, len(aa)) + assert.Equal(t, 0, aa[0]) + assert.Equal(t, 1.3, aa[1]) + assert.Equal(t, "x", aa[2]) + assert.Equal(t, false, aa[3]) + am := res["fMap"].(map[string]interface{}) + assert.Equal(t, 3, len(am)) + assert.Equal(t, "y", am["x"]) + assert.Equal(t, 999.9, am["y"]) + assert.Equal(t, false, am["z"]) + assert.Equal(t, nil, res["g"]) + assert.Equal(t, "foo", res["h"]) + assert.Equal(t, nil, res["i"]) + } + } + } +} + +func TestResourceState(t *testing.T) { + state := makeResourceState(true, map[string]interface{}{"baz": nil}) + + s, _, _, _ := marshalInputs(map[string]interface{}{"baz": "qux"}, true) + state.resolve(false, nil, nil, "foo", "bar", s) + + input := map[string]interface{}{ + "urn": state.urn, + "id": state.id, + "baz": state.State["baz"], + } + m, pdeps, deps, err := marshalInputs(input, true) + assert.Nil(t, err) + assert.Equal(t, map[string][]URN{ + "urn": {"foo"}, + "id": {"foo"}, + "baz": {"foo"}, + }, pdeps) + assert.Equal(t, []URN{"foo", "foo", "foo"}, deps) + + res, err := unmarshalOutputs(m) + assert.Nil(t, err) + assert.Equal(t, map[string]interface{}{ + "urn": "foo", + "id": "bar", + "baz": "qux", + }, res) } func TestUnmarshalUnsupportedSecret(t *testing.T) { diff --git a/sdk/go/pulumi/run.go b/sdk/go/pulumi/run.go index b8e1d4d85..87c61a0d3 100644 --- a/sdk/go/pulumi/run.go +++ b/sdk/go/pulumi/run.go @@ -20,7 +20,7 @@ import ( "os" "strconv" - "github.com/hashicorp/go-multierror" + multierror "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "golang.org/x/net/context" @@ -77,7 +77,7 @@ func RunWithContext(ctx *Context, body RunFunc) error { if err != nil { return err } - ctx.stackR, err = reg.URN().Value() + ctx.stackR, _, err = reg.URN().await(context.TODO()) if err != nil { return err }