97daa0a34a
First check for empty deps before checking to see if the gathered dependencies are equivalent to the specified dependencies map.
885 lines
25 KiB
Go
885 lines
25 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
|
|
}
|
|
|
|
// HasEquivalentDependencies returns true if the deps are equal to or a subset of the gathered nested dependencies.
|
|
func (ci constructInput) HasEquivalentDependencies() bool {
|
|
deps := urnSet{}
|
|
gatherDeps(ci.value, deps)
|
|
return deps.contains(ci.deps)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// If there aren't any dependencies or the dependencies are equivalent, we don't necessarily
|
|
// need to create a top-level output for the property.
|
|
if len(ci.deps) == 0 || ci.HasEquivalentDependencies() {
|
|
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
|
|
}
|
|
|
|
type callFunc func(ctx *Context, tok string, args map[string]interface{}) (Input, 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, 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,
|
|
}
|
|
}
|
|
|
|
return &pulumirpc.CallResponse{
|
|
Return: rpcProps,
|
|
ReturnDependencies: rpcPropertyDeps,
|
|
}, 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
|
|
}
|