// Copyright 2016-2021, Pulumi Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package pulumi import ( "context" "errors" "fmt" "reflect" "sort" "strings" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go" "google.golang.org/grpc" ) type constructFunc func(ctx *Context, typ, name string, inputs map[string]interface{}, options ResourceOption) (URNInput, Input, error) // construct adapts the gRPC ConstructRequest/ConstructResponse to/from the Pulumi Go SDK programming model. func construct(ctx context.Context, req *pulumirpc.ConstructRequest, engineConn *grpc.ClientConn, constructF constructFunc) (*pulumirpc.ConstructResponse, error) { // Configure the RunInfo. runInfo := RunInfo{ Project: req.GetProject(), Stack: req.GetStack(), Config: req.GetConfig(), ConfigSecretKeys: req.GetConfigSecretKeys(), Parallel: int(req.GetParallel()), DryRun: req.GetDryRun(), MonitorAddr: req.GetMonitorEndpoint(), engineConn: engineConn, } pulumiCtx, err := NewContext(ctx, runInfo) if err != nil { return nil, fmt.Errorf("constructing run context: %w", err) } // Deserialize the inputs and apply appropriate dependencies. inputDependencies := req.GetInputDependencies() deserializedInputs, err := plugin.UnmarshalProperties( req.GetInputs(), plugin.MarshalOptions{ KeepSecrets: true, KeepResources: true, KeepUnknowns: req.GetDryRun(), KeepOutputValues: true, }, ) if err != nil { return nil, fmt.Errorf("unmarshaling inputs: %w", err) } inputs := make(map[string]interface{}, len(deserializedInputs)) for key, value := range deserializedInputs { k := string(key) var deps urnSet if inputDeps, ok := inputDependencies[k]; ok { deps = urnSet{} for _, depURN := range inputDeps.GetUrns() { deps.add(URN(depURN)) } } inputs[k] = &constructInput{ value: value, deps: deps, } } // Rebuild the resource options. aliases := make([]Alias, len(req.GetAliases())) for i, urn := range req.GetAliases() { aliases[i] = Alias{URN: URN(urn)} } dependencyURNs := urnSet{} for _, urn := range req.GetDependencies() { dependencyURNs.add(URN(urn)) } providers := make(map[string]ProviderResource, len(req.GetProviders())) for pkg, ref := range req.GetProviders() { resource, err := createProviderResource(pulumiCtx, ref) if err != nil { return nil, err } providers[pkg] = resource } var parent Resource if req.GetParent() != "" { parent = pulumiCtx.newDependencyResource(URN(req.GetParent())) } opts := resourceOption(func(ro *resourceOptions) { ro.Aliases = aliases ro.DependsOn = []func(ctx context.Context) (urnSet, error){ func(ctx context.Context) (urnSet, error) { return dependencyURNs, nil }, } ro.Protect = req.GetProtect() ro.Providers = providers ro.Parent = parent }) urn, state, err := constructF(pulumiCtx, req.GetType(), req.GetName(), inputs, opts) if err != nil { return nil, err } // Wait for async work to finish. if err = pulumiCtx.wait(); err != nil { return nil, err } rpcURN, _, _, err := urn.ToURNOutput().awaitURN(ctx) if err != nil { return nil, err } // Serialize all state properties, first by awaiting them, and then marshaling them to the requisite gRPC values. resolvedProps, propertyDeps, _, err := marshalInputs(state) if err != nil { return nil, fmt.Errorf("marshaling properties: %w", err) } // Marshal all properties for the RPC call. keepUnknowns := req.GetDryRun() rpcProps, err := plugin.MarshalProperties( resolvedProps, plugin.MarshalOptions{KeepSecrets: true, KeepUnknowns: keepUnknowns, KeepResources: pulumiCtx.keepResources}) if err != nil { return nil, fmt.Errorf("marshaling properties: %w", err) } // Convert the property dependencies map for RPC and remove duplicates. rpcPropertyDeps := make(map[string]*pulumirpc.ConstructResponse_PropertyDependencies) for k, deps := range propertyDeps { sort.Slice(deps, func(i, j int) bool { return deps[i] < deps[j] }) urns := make([]string, 0, len(deps)) for i, d := range deps { if i > 0 && urns[i-1] == string(d) { continue } urns = append(urns, string(d)) } rpcPropertyDeps[k] = &pulumirpc.ConstructResponse_PropertyDependencies{ Urns: urns, } } return &pulumirpc.ConstructResponse{ Urn: string(rpcURN), State: rpcProps, StateDependencies: rpcPropertyDeps, }, nil } // createProviderResource rehydrates the provider reference into a registered ProviderResource, // otherwise it returns an instance of DependencyProviderResource. func createProviderResource(ctx *Context, ref string) (ProviderResource, error) { // Parse the URN and ID out of the provider reference. lastSep := strings.LastIndex(ref, "::") if lastSep == -1 { return nil, fmt.Errorf("expected '::' in provider reference %s", ref) } urn := ref[0:lastSep] id := ref[lastSep+2:] // Unmarshal the provider resource as a resource reference so we get back // the intended provider type with its state, if it's been registered. resource, err := unmarshalResourceReference(ctx, resource.ResourceReference{ URN: resource.URN(urn), ID: resource.NewStringProperty(id), }) if err != nil { return nil, err } return resource.(ProviderResource), nil } type constructInput struct { value resource.PropertyValue deps urnSet } func (ci constructInput) Dependencies(ctx *Context) []Resource { if ci.deps == nil { return nil } urns := ci.deps.sortedValues() var result []Resource if len(urns) > 0 { result = make([]Resource, len(urns)) for i, urn := range urns { result[i] = ctx.newDependencyResource(urn) } } return result } // constructInputsMap returns the inputs as a Map. func constructInputsMap(ctx *Context, inputs map[string]interface{}) (Map, error) { result := make(Map, len(inputs)) for k, v := range inputs { ci := v.(*constructInput) known := !ci.value.ContainsUnknowns() value, secret, err := unmarshalPropertyValue(ctx, ci.value) if err != nil { return nil, fmt.Errorf("unmarshaling input %q: %w", k, err) } resultType := anyOutputType if ot, ok := concreteTypeToOutputType.Load(reflect.TypeOf(value)); ok { resultType = ot.(reflect.Type) } output := ctx.newOutput(resultType, ci.Dependencies(ctx)...) output.getState().resolve(value, known, secret, nil) result[k] = output } return result, nil } func gatherDeps(v resource.PropertyValue, deps urnSet) { switch { case v.IsSecret(): gatherDeps(v.SecretValue().Element, deps) case v.IsComputed(): gatherDeps(v.Input().Element, deps) case v.IsOutput(): for _, urn := range v.OutputValue().Dependencies { deps.add(URN(urn)) } gatherDeps(v.OutputValue().Element, deps) case v.IsResourceReference(): deps.add(URN(v.ResourceReferenceValue().URN)) case v.IsArray(): for _, e := range v.ArrayValue() { gatherDeps(e, deps) } case v.IsObject(): for _, e := range v.ObjectValue() { gatherDeps(e, deps) } } } func copyInputTo(ctx *Context, v resource.PropertyValue, dest reflect.Value) error { contract.Assert(dest.CanSet()) // Check for nils. The destination will be left with the zero value. if v.IsNull() { return nil } // Allocate storage as necessary. for dest.Kind() == reflect.Ptr { elem := reflect.New(dest.Type().Elem()) dest.Set(elem) dest = elem.Elem() } switch { case v.IsSecret(): known, element := true, v.SecretValue().Element // See if it's value is unknown. if element.IsComputed() { known, element = false, element.Input().Element } // Handle this as a secret output. return copyInputTo(ctx, resource.NewOutputProperty(resource.Output{ Element: element, Known: known, Secret: true, }), dest) case v.IsComputed(): // Handle this as an unknown output. return copyInputTo(ctx, resource.MakeOutput(v.Input().Element), dest) case v.IsOutput(): // If it's known, there aren't any dependencies, and it's not a secret, just copy the value. if v.OutputValue().Known && len(v.OutputValue().Dependencies) == 0 && !v.OutputValue().Secret { return copyInputTo(ctx, v.OutputValue().Element, dest) } if !dest.Type().Implements(outputType) && !dest.Type().Implements(inputType) { return fmt.Errorf("expected destination type to implement %v or %v, got %v", inputType, outputType, dest.Type()) } resourceDeps := make([]Resource, len(v.OutputValue().Dependencies)) for i, dep := range v.OutputValue().Dependencies { resourceDeps[i] = ctx.newDependencyResource(URN(dep)) } result, err := createOutput(ctx, dest.Type(), v.OutputValue().Element, v.OutputValue().Known, v.OutputValue().Secret, resourceDeps) if err != nil { return err } dest.Set(result) return nil } if dest.Type().Implements(outputType) { result, err := createOutput(ctx, dest.Type(), v, true /*known*/, false /*secret*/, nil) if err != nil { return err } dest.Set(result) return nil } if dest.Type().Implements(inputType) { // Try to determine the input type from the interface. if it, ok := inputInterfaceTypeToConcreteType.Load(dest.Type()); ok { inputType := it.(reflect.Type) for inputType.Kind() == reflect.Ptr { inputType = inputType.Elem() } switch inputType.Kind() { case reflect.Bool: if !v.IsBool() { return fmt.Errorf("expected a %v, got a %s", inputType, v.TypeString()) } dest.Set(reflect.ValueOf(Bool(v.BoolValue()))) return nil case reflect.Int: if !v.IsNumber() { return fmt.Errorf("expected an %v, got a %s", inputType, v.TypeString()) } dest.Set(reflect.ValueOf(Int(int64(v.NumberValue())))) return nil case reflect.Float64: if !v.IsNumber() { return fmt.Errorf("expected an %v, got a %s", inputType, v.TypeString()) } dest.Set(reflect.ValueOf(Float64(v.NumberValue()))) return nil case reflect.String: if !v.IsString() { return fmt.Errorf("expected a %v, got a %s", inputType, v.TypeString()) } dest.Set(reflect.ValueOf(String(v.StringValue()))) return nil case reflect.Slice: return copyToSlice(ctx, v, inputType, dest) case reflect.Map: return copyToMap(ctx, v, inputType, dest) case reflect.Interface: if !anyType.Implements(inputType) { return fmt.Errorf("cannot unmarshal into non-empty interface type %v", inputType) } result, _, err := unmarshalPropertyValue(ctx, v) if err != nil { return err } dest.Set(reflect.ValueOf(result)) return nil case reflect.Struct: return copyToStruct(ctx, v, inputType, dest) default: panic(fmt.Sprintf("%v", inputType.Kind())) } } if v.IsAsset() { if !assetType.AssignableTo(dest.Type()) { return fmt.Errorf("expected a %s, got an asset", dest.Type()) } asset, _, err := unmarshalPropertyValue(ctx, v) if err != nil { return err } dest.Set(reflect.ValueOf(asset)) return nil } if v.IsArchive() { if !archiveType.AssignableTo(dest.Type()) { return fmt.Errorf("expected a %s, got an archive", dest.Type()) } archive, _, err := unmarshalPropertyValue(ctx, v) if err != nil { return err } dest.Set(reflect.ValueOf(archive)) return nil } // For plain structs that implements the Input interface. if dest.Type().Kind() == reflect.Struct { return copyToStruct(ctx, v, dest.Type(), dest) } // Otherwise, create an output. result, err := createOutput(ctx, dest.Type(), v, true /*known*/, false /*secret*/, nil) if err != nil { return err } dest.Set(result) return nil } switch dest.Type().Kind() { case reflect.Map: return copyToMap(ctx, v, dest.Type(), dest) case reflect.Slice: return copyToSlice(ctx, v, dest.Type(), dest) case reflect.Struct: return copyToStruct(ctx, v, dest.Type(), dest) } _, err := unmarshalOutput(ctx, v, dest) if err != nil { return fmt.Errorf("unmarshaling value: %w", err) } return nil } func createOutput(ctx *Context, destType reflect.Type, v resource.PropertyValue, known, secret bool, deps []Resource) (reflect.Value, error) { outputType := getOutputType(destType) output := ctx.newOutput(outputType, deps...) outputValueDest := reflect.New(output.ElementType()).Elem() _, err := unmarshalOutput(ctx, v, outputValueDest) if err != nil { return reflect.Value{}, fmt.Errorf("unmarshaling value: %w", err) } output.getState().resolve(outputValueDest.Interface(), known, secret, nil) return reflect.ValueOf(output), nil } func getOutputType(typ reflect.Type) reflect.Type { if typ.Implements(outputType) { return typ } else if typ.Implements(inputType) && typ.Kind() == reflect.Interface { // Attempt to determine the output type by looking up the registered input type, // getting the input type's element type, and then looking up the registered output // type by the element type. if inputStructType, found := inputInterfaceTypeToConcreteType.Load(typ); found { input := reflect.New(inputStructType.(reflect.Type)).Elem().Interface().(Input) elementType := input.ElementType() if outputType, ok := concreteTypeToOutputType.Load(elementType); ok { return outputType.(reflect.Type) } } // Otherwise, look for a `ToOutput` method that returns an output. toOutputMethodName := "To" + strings.TrimSuffix(typ.Name(), "Input") + "Output" if toOutputMethod, found := typ.MethodByName(toOutputMethodName); found { mt := toOutputMethod.Type if mt.NumIn() == 0 && mt.NumOut() == 1 && mt.Out(0).Implements(outputType) { return mt.Out(0) } } } return anyOutputType } func copyToSlice(ctx *Context, v resource.PropertyValue, typ reflect.Type, dest reflect.Value) error { if !v.IsArray() { return fmt.Errorf("expected a %v, got a %s", typ, v.TypeString()) } arr := v.ArrayValue() slice := reflect.MakeSlice(typ, len(arr), len(arr)) for i, e := range arr { if err := copyInputTo(ctx, e, slice.Index(i)); err != nil { return err } } dest.Set(slice) return nil } func copyToMap(ctx *Context, v resource.PropertyValue, typ reflect.Type, dest reflect.Value) error { if !v.IsObject() { return fmt.Errorf("expected a %v, got a %s", typ, v.TypeString()) } keyType, elemType := typ.Key(), typ.Elem() if keyType.Kind() != reflect.String { return fmt.Errorf("map keys must be assignable from type string") } result := reflect.MakeMap(typ) for k, e := range v.ObjectValue() { if resource.IsInternalPropertyKey(k) { continue } elem := reflect.New(elemType).Elem() if err := copyInputTo(ctx, e, elem); err != nil { return err } key := reflect.New(keyType).Elem() key.SetString(string(k)) result.SetMapIndex(key, elem) } dest.Set(result) return nil } func copyToStruct(ctx *Context, v resource.PropertyValue, typ reflect.Type, dest reflect.Value) error { if !v.IsObject() { return fmt.Errorf("expected a %v, got a %s", typ, v.TypeString()) } result := reflect.New(typ).Elem() obj := v.ObjectValue() for i := 0; i < typ.NumField(); i++ { fieldV := result.Field(i) if !fieldV.CanSet() { continue } tag := typ.Field(i).Tag.Get("pulumi") if tag == "" { continue } e, ok := obj[resource.PropertyKey(tag)] if !ok { continue } if err := copyInputTo(ctx, e, fieldV); err != nil { return err } } dest.Set(result) return nil } // constructInputsCopyTo sets the inputs on the given args struct. func constructInputsCopyTo(ctx *Context, inputs map[string]interface{}, args interface{}) error { if args == nil { return errors.New("args must not be nil") } argsV := reflect.ValueOf(args) typ := argsV.Type() if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Struct { return errors.New("args must be a pointer to a struct") } argsV, typ = argsV.Elem(), typ.Elem() for k, v := range inputs { ci := v.(*constructInput) for i := 0; i < typ.NumField(); i++ { fieldV := argsV.Field(i) if !fieldV.CanSet() { continue } field := typ.Field(i) tag, has := field.Tag.Lookup("pulumi") if !has || tag != k { continue } // Find all nested dependencies. deps := urnSet{} gatherDeps(ci.value, deps) // If the top-level property dependencies are equal to (or a subset of) the gathered nested // dependencies, we don't necessarily need to create a top-level output for the property. if deps.contains(ci.deps) { if err := copyInputTo(ctx, ci.value, fieldV); err != nil { return fmt.Errorf("copying input %q: %w", k, err) } continue } handleField := func(typ reflect.Type, value resource.PropertyValue, deps []Resource) (reflect.Value, error) { resultType := getOutputType(typ) output := ctx.newOutput(resultType, deps...) dest := reflect.New(output.ElementType()).Elem() known := !ci.value.ContainsUnknowns() secret, err := unmarshalOutput(ctx, value, dest) if err != nil { return reflect.Value{}, err } output.getState().resolve(dest.Interface(), known, secret, nil) return reflect.ValueOf(output), nil } isOutputOrInputType := func(typ reflect.Type) bool { return typ.Implements(outputType) || typ.Implements(inputType) } if isOutputOrInputType(field.Type) { val, err := handleField(field.Type, ci.value, ci.Dependencies(ctx)) if err != nil { return err } fieldV.Set(val) continue } if field.Type.Kind() == reflect.Slice && isOutputOrInputType(field.Type.Elem()) { elemType := field.Type.Elem() length := len(ci.value.ArrayValue()) dest := reflect.MakeSlice(field.Type, length, length) for i := 0; i < length; i++ { val, err := handleField(elemType, ci.value.ArrayValue()[i], ci.Dependencies(ctx)) if err != nil { return err } dest.Index(i).Set(val) } fieldV.Set(dest) continue } if field.Type.Kind() == reflect.Map && isOutputOrInputType(field.Type.Elem()) { elemType := field.Type.Elem() length := len(ci.value.ObjectValue()) dest := reflect.MakeMapWithSize(field.Type, length) for k, v := range ci.value.ObjectValue() { key := reflect.ValueOf(string(k)) val, err := handleField(elemType, v, ci.Dependencies(ctx)) if err != nil { return err } dest.SetMapIndex(key, val) } fieldV.Set(dest) continue } if len(ci.deps) > 0 { return fmt.Errorf("copying input %q: %s.%s is typed as %v but must be a type that implements %v or "+ "%v for input with dependencies", k, typ, field.Name, field.Type, inputType, outputType) } dest := reflect.New(field.Type).Elem() secret, err := unmarshalOutput(ctx, ci.value, dest) if err != nil { return fmt.Errorf("copying input %q: unmarshaling value: %w", k, err) } if secret { return fmt.Errorf("copying input %q: %s.%s is typed as %v but must be a type that implements %v or "+ "%v for secret input", k, typ, field.Name, field.Type, inputType, outputType) } fieldV.Set(reflect.ValueOf(dest.Interface())) } } return nil } // newConstructResult converts a resource into its associated URN and state. func newConstructResult(resource ComponentResource) (URNInput, Input, error) { if resource == nil { return nil, nil, errors.New("resource must not be nil") } resourceV := reflect.ValueOf(resource) typ := resourceV.Type() if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Struct { return nil, nil, errors.New("resource must be a pointer to a struct") } resourceV, typ = resourceV.Elem(), typ.Elem() state := make(Map) for i := 0; i < typ.NumField(); i++ { fieldV := resourceV.Field(i) if !fieldV.CanInterface() { continue } field := typ.Field(i) tag, has := field.Tag.Lookup("pulumi") if !has { continue } val := fieldV.Interface() if v, ok := val.(Input); ok { state[tag] = v } else { state[tag] = ToOutput(val) } } return resource.URN(), state, nil } // callFailure indicates that a call to Call failed; it contains the property and reason for the failure. type callFailure struct { Property string Reason string } type callFunc func(ctx *Context, tok string, args map[string]interface{}) (Input, []interface{}, error) // call adapts the gRPC CallRequest/CallResponse to/from the Pulumi Go SDK programming model. func call(ctx context.Context, req *pulumirpc.CallRequest, engineConn *grpc.ClientConn, callF callFunc) (*pulumirpc.CallResponse, error) { // Configure the RunInfo. runInfo := RunInfo{ Project: req.GetProject(), Stack: req.GetStack(), Config: req.GetConfig(), Parallel: int(req.GetParallel()), DryRun: req.GetDryRun(), MonitorAddr: req.GetMonitorEndpoint(), engineConn: engineConn, } pulumiCtx, err := NewContext(ctx, runInfo) if err != nil { return nil, fmt.Errorf("constructing run context: %w", err) } // Deserialize the inputs and apply appropriate dependencies. argDependencies := req.GetArgDependencies() deserializedArgs, err := plugin.UnmarshalProperties( req.GetArgs(), plugin.MarshalOptions{ KeepSecrets: true, KeepResources: true, KeepUnknowns: req.GetDryRun(), KeepOutputValues: true, }, ) if err != nil { return nil, fmt.Errorf("unmarshaling inputs: %w", err) } args := make(map[string]interface{}, len(deserializedArgs)) for key, value := range deserializedArgs { k := string(key) var deps urnSet if inputDeps, ok := argDependencies[k]; ok { deps = urnSet{} for _, depURN := range inputDeps.GetUrns() { deps.add(URN(depURN)) } } args[k] = &constructInput{ value: value, deps: deps, } } result, failures, err := callF(pulumiCtx, req.GetTok(), args) if err != nil { return nil, err } // Wait for async work to finish. if err = pulumiCtx.wait(); err != nil { return nil, err } // Serialize all result properties, first by awaiting them, and then marshaling them to the requisite gRPC values. resolvedProps, propertyDeps, _, err := marshalInputs(result) if err != nil { return nil, fmt.Errorf("marshaling properties: %w", err) } // Marshal all properties for the RPC call. keepUnknowns := req.GetDryRun() rpcProps, err := plugin.MarshalProperties( resolvedProps, plugin.MarshalOptions{KeepSecrets: true, KeepUnknowns: keepUnknowns, KeepResources: pulumiCtx.keepResources}) if err != nil { return nil, fmt.Errorf("marshaling properties: %w", err) } // Convert the property dependencies map for RPC and remove duplicates. rpcPropertyDeps := make(map[string]*pulumirpc.CallResponse_ReturnDependencies) for k, deps := range propertyDeps { sort.Slice(deps, func(i, j int) bool { return deps[i] < deps[j] }) urns := make([]string, 0, len(deps)) for i, d := range deps { if i > 0 && urns[i-1] == string(d) { continue } urns = append(urns, string(d)) } rpcPropertyDeps[k] = &pulumirpc.CallResponse_ReturnDependencies{ Urns: urns, } } var rpcFailures []*pulumirpc.CheckFailure if len(failures) > 0 { rpcFailures = make([]*pulumirpc.CheckFailure, len(failures)) for i, v := range failures { failure := v.(callFailure) rpcFailures[i] = &pulumirpc.CheckFailure{ Property: failure.Property, Reason: failure.Reason, } } } return &pulumirpc.CallResponse{ Return: rpcProps, ReturnDependencies: rpcPropertyDeps, Failures: rpcFailures, }, nil } // callArgsCopyTo sets the args on the given args struct. If there is a `__self__` argument, it will be // returned, otherwise it will return nil. func callArgsCopyTo(ctx *Context, source map[string]interface{}, args interface{}) (Resource, error) { // Use the same implementation as construct. if err := constructInputsCopyTo(ctx, source, args); err != nil { return nil, err } // Retrieve the `__self__` arg. self, err := callArgsSelf(ctx, source) if err != nil { return nil, err } return self, nil } // callArgsSelf retrieves the `__self__` argument. If `__self__` is present the value is returned, // otherwise the returned value will be nil. func callArgsSelf(ctx *Context, source map[string]interface{}) (Resource, error) { v, ok := source["__self__"] if !ok { return nil, nil } ci := v.(*constructInput) if ci.value.ContainsUnknowns() { return nil, errors.New("__self__ is unknown") } value, secret, err := unmarshalPropertyValue(ctx, ci.value) if err != nil { return nil, fmt.Errorf("unmarshaling __self__: %w", err) } if secret { return nil, errors.New("__self__ is a secret") } return value.(Resource), nil } // newCallResult converts a result struct into an input Map that can be marshalled. func newCallResult(result interface{}) (Input, error) { if result == nil { return nil, errors.New("result must not be nil") } resultV := reflect.ValueOf(result) typ := resultV.Type() if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Struct { return nil, errors.New("result must be a pointer to a struct") } resultV, typ = resultV.Elem(), typ.Elem() ret := make(Map) for i := 0; i < typ.NumField(); i++ { fieldV := resultV.Field(i) if !fieldV.CanInterface() { continue } field := typ.Field(i) tag, has := field.Tag.Lookup("pulumi") if !has { continue } val := fieldV.Interface() if v, ok := val.(Input); ok { ret[tag] = v } else { ret[tag] = ToOutput(val) } } return ret, nil } // newCallFailure creates a call failure. func newCallFailure(property, reason string) interface{} { return callFailure{ Property: property, Reason: reason, } }