pulumi/sdk/go/pulumi/provider.go
Justin Van Patten ba39ed9ad4
Add tests that return failures from Call (#8424)
- [sdk/nodejs] - Allow returning failures from Call in the provider without setting result outputs.
- [sdk/go] - Allow specifying Call failures from the provider.
- Add tests that return failures from Call.
2021-11-16 08:58:46 -08:00

909 lines
26 KiB
Go

// 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 `To<Name>Output` 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,
}
}