mirror of https://github.com/pulumi/pulumi.git
1071 lines
32 KiB
Go
1071 lines
32 KiB
Go
// Copyright 2016-2018, 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 (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/blang/semver"
|
|
"golang.org/x/net/context"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
|
rarchive "github.com/pulumi/pulumi/sdk/v3/go/common/resource/archive"
|
|
rasset "github.com/pulumi/pulumi/sdk/v3/go/common/resource/asset"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/internal"
|
|
)
|
|
|
|
// addDependency adds a dependency on the given resource to the set of deps.
|
|
//
|
|
// The behavior of this method depends on whether or not the resource is a custom resource, a local component resource,
|
|
// a remote component resource, a dependency resource, or a rehydrated component resource:
|
|
//
|
|
// - Custom resources are added directly to the set, as they are "real" nodes in the dependency graph.
|
|
// - Local component resources act as aggregations of their descendents. Rather than adding the component resource
|
|
// itself, each child resource is added as a dependency.
|
|
// - Remote component resources are added directly to the set, as they naturally act as aggregations of their children
|
|
// with respect to dependencies: the construction of a remote component always waits on the construction of its
|
|
// children.
|
|
// - Dependency resources are added directly to the set.
|
|
// - Rehydrated component resources are added directly to the set.
|
|
//
|
|
// In other words, if we had:
|
|
//
|
|
// Comp1
|
|
// / | \
|
|
// Cust1 Comp2 Remote1
|
|
// / \ \
|
|
// Cust2 Cust3 Comp3
|
|
// / \
|
|
// Cust4 Cust5
|
|
//
|
|
// Then the transitively reachable resources of Comp1 will be [Cust1, Cust2, Cust3, Remote1].
|
|
// It will *not* include:
|
|
// * Cust4 because it is a child of a custom resource
|
|
// * Comp2 because it is a non-remote component resoruce
|
|
// * Comp3 and Cust5 because Comp3 is a child of a remote component resource
|
|
func addDependency(ctx context.Context, deps urnSet, res, from Resource) error {
|
|
if _, custom := res.(CustomResource); !custom {
|
|
// If `res` is the same as `from`, exit early to avoid depending on
|
|
// children that haven't been registered yet.
|
|
if res == from {
|
|
return nil
|
|
}
|
|
|
|
for _, child := range res.getChildren() {
|
|
if err := addDependency(ctx, deps, child, from); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// keepDependency() returns true for remote component resources, dependency resources,
|
|
// and rehydrated component resources.
|
|
if !res.keepDependency() {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
urn, _, _, err := res.URN().awaitURN(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
deps.add(urn)
|
|
return nil
|
|
}
|
|
|
|
// expandDependencies expands the given slice of Resources into a set of URNs.
|
|
func expandDependencies(ctx context.Context, deps []Resource) (urnSet, error) {
|
|
urns := urnSet{}
|
|
for _, r := range deps {
|
|
if err := addDependency(ctx, urns, r, nil /* from */); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return urns, nil
|
|
}
|
|
|
|
// marshalInputs turns resource property inputs into a map suitable for marshaling.
|
|
func marshalInputs(props Input) (resource.PropertyMap, map[string][]URN, []URN, error) {
|
|
deps := urnSet{}
|
|
pmap, pdeps := resource.PropertyMap{}, map[string][]URN{}
|
|
|
|
if props == nil {
|
|
return pmap, pdeps, nil, nil
|
|
}
|
|
|
|
marshalProperty := func(pname string, pv interface{}, pt reflect.Type) error {
|
|
// Get the underlying value, possibly waiting for an output to arrive.
|
|
v, resourceDeps, err := marshalInput(pv, pt, true)
|
|
if err != nil {
|
|
return fmt.Errorf("awaiting input property %q: %w", pname, err)
|
|
}
|
|
|
|
// Record all dependencies accumulated from reading this property.
|
|
allDeps, err := expandDependencies(context.TODO(), resourceDeps)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
deps.union(allDeps)
|
|
|
|
if !v.IsNull() || len(allDeps) > 0 {
|
|
pmap[resource.PropertyKey(pname)] = v
|
|
pdeps[pname] = allDeps.values()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
pv := reflect.ValueOf(props)
|
|
if pv.Kind() == reflect.Ptr {
|
|
if pv.IsNil() {
|
|
return pmap, pdeps, nil, nil
|
|
}
|
|
pv = pv.Elem()
|
|
}
|
|
pt := pv.Type()
|
|
|
|
rt := props.ElementType()
|
|
if rt.Kind() == reflect.Ptr {
|
|
rt = rt.Elem()
|
|
}
|
|
|
|
//nolint:exhaustive // We only need to handle the types we care about.
|
|
switch pt.Kind() {
|
|
case reflect.Struct:
|
|
contract.Assertf(rt.Kind() == reflect.Struct, "expected struct, got %v (%v)", rt, rt.Kind())
|
|
// We use the resolved type to decide how to convert inputs to outputs.
|
|
rt := props.ElementType()
|
|
if rt.Kind() == reflect.Ptr {
|
|
rt = rt.Elem()
|
|
}
|
|
getMappedField := internal.MapStructTypes(pt, rt)
|
|
// Now, marshal each field in the input.
|
|
numFields := pt.NumField()
|
|
for i := 0; i < numFields; i++ {
|
|
destField, _ := getMappedField(reflect.Value{}, i)
|
|
tag := destField.Tag.Get("pulumi")
|
|
tag = strings.Split(tag, ",")[0] // tagName,flag => tagName
|
|
if tag == "" {
|
|
continue
|
|
}
|
|
err := marshalProperty(tag, pv.Field(i).Interface(), destField.Type)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
}
|
|
case reflect.Map:
|
|
ktype := rt.Key()
|
|
contract.Assertf(ktype.Kind() == reflect.String,
|
|
"expected map with string keys, got %v (%v)", ktype, ktype.Kind())
|
|
for _, key := range pv.MapKeys() {
|
|
keyname := key.Interface().(string)
|
|
val := pv.MapIndex(key).Interface()
|
|
err := marshalProperty(keyname, val, rt.Elem())
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
}
|
|
default:
|
|
return nil, nil, nil, fmt.Errorf("cannot marshal Input that is not a struct or map, saw type %s", pt.String())
|
|
}
|
|
|
|
return pmap, pdeps, deps.values(), nil
|
|
}
|
|
|
|
// `gosec` thinks these are credentials, but they are not.
|
|
//
|
|
//nolint:gosec
|
|
const rpcTokenUnknownValue = "04da6b54-80e4-46f7-96ec-b56ff0331ba9"
|
|
|
|
const cannotAwaitFmt = "cannot marshal Output value of type %T; please use Apply to access the Output's value"
|
|
|
|
// marshalInput marshals an input value, returning its raw serializable value along with any dependencies.
|
|
func marshalInput(v interface{}, destType reflect.Type, await bool) (resource.PropertyValue, []Resource, error) {
|
|
return marshalInputImpl(v, destType, await, false /*skipInputCheck*/)
|
|
}
|
|
|
|
// marshalInputImpl marshals an input value, returning its raw serializable value along with any dependencies.
|
|
func marshalInputImpl(v interface{},
|
|
destType reflect.Type,
|
|
await,
|
|
skipInputCheck bool,
|
|
) (resource.PropertyValue, []Resource, error) {
|
|
var deps []Resource
|
|
for {
|
|
valueType := reflect.TypeOf(v)
|
|
|
|
// If this is an Input, make sure it is of the proper type and await it if it is an output/
|
|
if input, ok := v.(Input); !skipInputCheck && ok {
|
|
if inputType := reflect.ValueOf(input); inputType.Kind() == reflect.Ptr && inputType.IsNil() {
|
|
// input type is a ptr type with a nil backing value
|
|
return resource.PropertyValue{}, nil, nil
|
|
}
|
|
valueType = input.ElementType()
|
|
|
|
// Handle cases where the destination is a ptr type whose element type is the same as the value type
|
|
// (e.g. destType is *FooBar and valueType is FooBar).
|
|
// This avoids calling the ToOutput method to convert the input to an output in this case.
|
|
if valueType != destType && destType.Kind() == reflect.Ptr && valueType == destType.Elem() {
|
|
destType = destType.Elem()
|
|
}
|
|
|
|
// If the element type of the input is not identical to the type of the destination and the destination is
|
|
// not the any type (i.e. interface{}), attempt to convert the input to an appropriately-typed output.
|
|
if valueType != destType && destType != anyType {
|
|
if newOutput, ok := internal.CallToOutputMethod(context.TODO(), reflect.ValueOf(input), destType); ok {
|
|
// We were able to convert the input. Use the result as the new input value.
|
|
input, valueType = newOutput, destType
|
|
} else if !valueType.AssignableTo(destType) {
|
|
err := fmt.Errorf(
|
|
"cannot marshal an input of type %T with element type %v as a value of type %v",
|
|
input, valueType, destType)
|
|
return resource.PropertyValue{}, nil, err
|
|
}
|
|
}
|
|
|
|
// If the input is an Output, await its value. The returned value is fully resolved.
|
|
if output, ok := input.(Output); ok {
|
|
if !await {
|
|
return resource.PropertyValue{}, nil, fmt.Errorf(cannotAwaitFmt, output)
|
|
}
|
|
|
|
// Await the output.
|
|
ov, known, secret, outputDeps, err := awaitWithContext(context.TODO(), output)
|
|
if err != nil {
|
|
return resource.PropertyValue{}, nil, err
|
|
}
|
|
|
|
// Get the underlying value, if known.
|
|
var element resource.PropertyValue
|
|
if known {
|
|
element, _, err = marshalInputImpl(ov, destType, await, true /*skipInputCheck*/)
|
|
if err != nil {
|
|
return resource.PropertyValue{}, nil, err
|
|
}
|
|
|
|
// If it's known, not a secret, and has no deps, return the value itself.
|
|
if !secret && len(outputDeps) == 0 {
|
|
return element, nil, nil
|
|
}
|
|
}
|
|
|
|
// Expand dependencies.
|
|
urnSet, err := expandDependencies(context.TODO(), outputDeps)
|
|
if err != nil {
|
|
return resource.PropertyValue{}, nil, err
|
|
}
|
|
var dependencies []resource.URN
|
|
if len(urnSet) > 0 {
|
|
dependencies = make([]resource.URN, len(urnSet))
|
|
for i, urn := range urnSet.sortedValues() {
|
|
dependencies[i] = resource.URN(urn)
|
|
}
|
|
}
|
|
|
|
return resource.NewOutputProperty(resource.Output{
|
|
Element: element,
|
|
Known: known,
|
|
Secret: secret,
|
|
Dependencies: dependencies,
|
|
}), outputDeps, nil
|
|
}
|
|
}
|
|
|
|
// Set skipInputCheck to false, so that if we loop around we don't skip the input check.
|
|
skipInputCheck = false
|
|
|
|
// If v is nil, just return that.
|
|
if v == nil {
|
|
return resource.PropertyValue{}, nil, nil
|
|
} else if val := reflect.ValueOf(v); val.Kind() == reflect.Ptr && val.IsNil() {
|
|
// Here we round trip through a reflect.Value to catch fat pointers of the
|
|
// form
|
|
//
|
|
// <SomeType><nil value>
|
|
//
|
|
// This prevents calling methods on nil pointers when we cast to an interface
|
|
// (like `Resource`)
|
|
return resource.PropertyValue{}, nil, nil
|
|
}
|
|
|
|
// Look for some well known types.
|
|
switch v := v.(type) {
|
|
case *asset:
|
|
if v.invalid {
|
|
return resource.PropertyValue{}, nil, errors.New("invalid asset")
|
|
}
|
|
return resource.NewAssetProperty(&rasset.Asset{
|
|
Path: v.Path(),
|
|
Text: v.Text(),
|
|
URI: v.URI(),
|
|
}), deps, nil
|
|
case *archive:
|
|
if v.invalid {
|
|
return resource.PropertyValue{}, nil, errors.New("invalid 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, anyType, await)
|
|
if err != nil {
|
|
return resource.PropertyValue{}, nil, err
|
|
}
|
|
assets[k] = aa.V
|
|
}
|
|
}
|
|
return resource.NewArchiveProperty(&rarchive.Archive{
|
|
Assets: assets,
|
|
Path: v.Path(),
|
|
URI: v.URI(),
|
|
}), deps, nil
|
|
case Resource:
|
|
deps = append(deps, v)
|
|
|
|
urn, known, secretURN, err := v.URN().awaitURN(context.Background())
|
|
if err != nil {
|
|
return resource.PropertyValue{}, nil, err
|
|
}
|
|
contract.Assertf(known, "URN must be known")
|
|
contract.Assertf(!secretURN, "URN must not be secret")
|
|
|
|
if custom, ok := v.(CustomResource); ok {
|
|
id, _, secretID, err := custom.ID().awaitID(context.Background())
|
|
if err != nil {
|
|
return resource.PropertyValue{}, nil, err
|
|
}
|
|
contract.Assertf(!secretID, "CustomResource must not have a secret ID")
|
|
|
|
return resource.MakeCustomResourceReference(resource.URN(urn), resource.ID(id), ""), deps, nil
|
|
}
|
|
|
|
return resource.MakeComponentResourceReference(resource.URN(urn), ""), deps, nil
|
|
}
|
|
|
|
if destType.Kind() == reflect.Interface {
|
|
// This happens in the case of Any.
|
|
if valueType.Kind() == reflect.Interface {
|
|
valueType = reflect.TypeOf(v)
|
|
}
|
|
destType = valueType
|
|
}
|
|
|
|
rv := reflect.ValueOf(v)
|
|
|
|
if rv.Type().Kind() == reflect.Array || rv.Type().Kind() == reflect.Slice || rv.Type().Kind() == reflect.Map {
|
|
// Not assignable in prompt form because of the difference in input and output shapes.
|
|
//
|
|
// TODO(7434): update these checks once fixed.
|
|
} else {
|
|
contract.Assertf(valueType.AssignableTo(destType) || valueType.ConvertibleTo(destType),
|
|
"%v: cannot assign %v to %v", v, valueType, destType)
|
|
}
|
|
|
|
//nolint:exhaustive // We only need to handle the types we care about.
|
|
switch rv.Type().Kind() {
|
|
case reflect.Bool:
|
|
return resource.NewBoolProperty(rv.Bool()), deps, nil
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return resource.NewNumberProperty(float64(rv.Int())), deps, nil
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
return resource.NewNumberProperty(float64(rv.Uint())), deps, nil
|
|
case reflect.Float32, reflect.Float64:
|
|
return resource.NewNumberProperty(rv.Float()), deps, nil
|
|
case reflect.Ptr, reflect.Interface:
|
|
// Dereference non-nil pointers and interfaces.
|
|
if rv.IsNil() {
|
|
return resource.PropertyValue{}, deps, nil
|
|
}
|
|
if destType.Kind() == reflect.Ptr {
|
|
destType = destType.Elem()
|
|
}
|
|
v = rv.Elem().Interface()
|
|
continue
|
|
case reflect.String:
|
|
return resource.NewStringProperty(rv.String()), deps, nil
|
|
case reflect.Array, reflect.Slice:
|
|
if rv.IsNil() {
|
|
return resource.PropertyValue{}, deps, nil
|
|
}
|
|
|
|
destElem := destType.Elem()
|
|
|
|
// If an array or a slice, create a new array by recursing into elements.
|
|
arr := make([]resource.PropertyValue, 0, rv.Len())
|
|
for i := 0; i < rv.Len(); i++ {
|
|
elem := rv.Index(i)
|
|
e, d, err := marshalInput(elem.Interface(), destElem, await)
|
|
if err != nil {
|
|
return resource.PropertyValue{}, nil, err
|
|
}
|
|
if !e.IsNull() {
|
|
arr = append(arr, e)
|
|
}
|
|
deps = append(deps, d...)
|
|
}
|
|
return resource.NewArrayProperty(arr), deps, nil
|
|
case reflect.Map:
|
|
if rv.Type().Key().Kind() != reflect.String {
|
|
return resource.PropertyValue{}, nil,
|
|
fmt.Errorf("expected map keys to be strings; got %v", rv.Type().Key())
|
|
}
|
|
|
|
if rv.IsNil() {
|
|
return resource.PropertyValue{}, deps, nil
|
|
}
|
|
|
|
destElem := destType.Elem()
|
|
|
|
// For maps, only support string-based keys, and recurse into the values.
|
|
obj := resource.PropertyMap{}
|
|
for _, key := range rv.MapKeys() {
|
|
value := rv.MapIndex(key)
|
|
mv, d, err := marshalInput(value.Interface(), destElem, await)
|
|
if err != nil {
|
|
return resource.PropertyValue{}, nil, err
|
|
}
|
|
if !mv.IsNull() {
|
|
obj[resource.PropertyKey(key.String())] = mv
|
|
}
|
|
deps = append(deps, d...)
|
|
}
|
|
return resource.NewObjectProperty(obj), deps, nil
|
|
case reflect.Struct:
|
|
obj := resource.PropertyMap{}
|
|
typ := rv.Type()
|
|
getMappedField := internal.MapStructTypes(typ, destType)
|
|
for i := 0; i < typ.NumField(); i++ {
|
|
destField, _ := getMappedField(reflect.Value{}, i)
|
|
tag := destField.Tag.Get("pulumi")
|
|
tag = strings.Split(tag, ",")[0] // tagName,flag => tagName
|
|
if tag == "" {
|
|
continue
|
|
}
|
|
|
|
fv, d, err := marshalInput(rv.Field(i).Interface(), destField.Type, await)
|
|
if err != nil {
|
|
return resource.PropertyValue{}, nil, err
|
|
}
|
|
|
|
if !fv.IsNull() {
|
|
obj[resource.PropertyKey(tag)] = fv
|
|
}
|
|
deps = append(deps, d...)
|
|
}
|
|
return resource.NewObjectProperty(obj), deps, nil
|
|
}
|
|
return resource.PropertyValue{}, nil, fmt.Errorf("unrecognized input property type: %v (%T)", v, v)
|
|
}
|
|
}
|
|
|
|
func unmarshalResourceReference(ctx *Context, ref resource.ResourceReference) (Resource, error) {
|
|
version := nullVersion
|
|
if len(ref.PackageVersion) > 0 {
|
|
var err error
|
|
version, err = semver.ParseTolerant(ref.PackageVersion)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse provider version: %s", ref.PackageVersion)
|
|
}
|
|
}
|
|
|
|
resName := ref.URN.Name()
|
|
resType := ref.URN.Type()
|
|
|
|
isProvider := tokens.Token(resType).HasModuleMember() && resType.Module() == "pulumi:providers"
|
|
if isProvider {
|
|
pkgName := resType.Name().String()
|
|
if resourcePackageV, ok := resourcePackages.Load(pkgName, version); ok {
|
|
resourcePackage := resourcePackageV.(ResourcePackage)
|
|
return resourcePackage.ConstructProvider(ctx, resName, string(resType), string(ref.URN))
|
|
}
|
|
id, _ := ref.IDString()
|
|
return ctx.newDependencyProviderResource(URN(ref.URN), ID(id)), nil
|
|
}
|
|
|
|
modName := resType.Module().String()
|
|
if resourceModuleV, ok := resourceModules.Load(modName, version); ok {
|
|
resourceModule := resourceModuleV.(ResourceModule)
|
|
return resourceModule.Construct(ctx, resName, string(resType), string(ref.URN))
|
|
}
|
|
if id, hasID := ref.IDString(); hasID {
|
|
return ctx.newDependencyCustomResource(URN(ref.URN), ID(id)), nil
|
|
}
|
|
return ctx.newDependencyResource(URN(ref.URN)), nil
|
|
}
|
|
|
|
func unmarshalPropertyValue(ctx *Context, v resource.PropertyValue) (interface{}, bool, error) {
|
|
switch {
|
|
case v.IsComputed():
|
|
return nil, false, nil
|
|
case v.IsOutput():
|
|
if !v.OutputValue().Known {
|
|
return nil, v.OutputValue().Secret, nil
|
|
}
|
|
ov, _, err := unmarshalPropertyValue(ctx, v.OutputValue().Element)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
return ov, v.OutputValue().Secret, nil
|
|
case v.IsSecret():
|
|
sv, _, err := unmarshalPropertyValue(ctx, v.SecretValue().Element)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
return sv, true, nil
|
|
case v.IsArray():
|
|
arr := v.ArrayValue()
|
|
rv := make([]interface{}, len(arr))
|
|
secret := false
|
|
for i, e := range arr {
|
|
ev, esecret, err := unmarshalPropertyValue(ctx, e)
|
|
secret = secret || esecret
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
rv[i] = ev
|
|
}
|
|
return rv, secret, nil
|
|
case v.IsObject():
|
|
m := make(map[string]interface{})
|
|
secret := false
|
|
for k, e := range v.ObjectValue() {
|
|
ev, esecret, err := unmarshalPropertyValue(ctx, e)
|
|
secret = secret || esecret
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
m[string(k)] = ev
|
|
}
|
|
return m, secret, nil
|
|
case v.IsAsset():
|
|
asset := v.AssetValue()
|
|
switch {
|
|
case asset.IsPath():
|
|
return NewFileAsset(asset.Path), false, nil
|
|
case asset.IsText():
|
|
return NewStringAsset(asset.Text), false, nil
|
|
case asset.IsURI():
|
|
return NewRemoteAsset(asset.URI), false, nil
|
|
}
|
|
return nil, false, errors.New("expected asset to be one of File, String, or Remote; got none")
|
|
case v.IsArchive():
|
|
archive := v.ArchiveValue()
|
|
secret := false
|
|
switch {
|
|
case archive.IsAssets():
|
|
as := make(map[string]interface{})
|
|
for k, v := range archive.Assets {
|
|
a, asecret, err := unmarshalPropertyValue(ctx, resource.NewPropertyValue(v))
|
|
secret = secret || asecret
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
as[k] = a
|
|
}
|
|
return NewAssetArchive(as), secret, nil
|
|
case archive.IsPath():
|
|
return NewFileArchive(archive.Path), secret, nil
|
|
case archive.IsURI():
|
|
return NewRemoteArchive(archive.URI), secret, nil
|
|
}
|
|
return nil, false, errors.New("expected asset to be one of File, String, or Remote; got none")
|
|
case v.IsResourceReference():
|
|
resource, err := unmarshalResourceReference(ctx, v.ResourceReferenceValue())
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
return resource, false, nil
|
|
default:
|
|
return v.V, false, nil
|
|
}
|
|
}
|
|
|
|
// unmarshalPropertyMap is used to turn the values in a resource.PropertyMap into sensible runtime types. This tries to
|
|
// keep things as plain types where possible (e.g. a string property value will just be a `pulumi.String`, not an
|
|
// `OutputString`). It will use `pulumi.Output` for values that are either Computed (will always be a
|
|
// `pulumi.AnyOutput`), secret, or an output property value.
|
|
func unmarshalPropertyMap(ctx *Context, v resource.PropertyMap) (Map, error) {
|
|
if v == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
var unmarshal func(resource.PropertyValue) (Input, error)
|
|
unmarshal = func(v resource.PropertyValue) (Input, error) {
|
|
switch {
|
|
case v.IsNull():
|
|
return nil, nil
|
|
case v.IsBool():
|
|
return Bool(v.BoolValue()), nil
|
|
case v.IsNumber():
|
|
return Float64(v.NumberValue()), nil
|
|
case v.IsString():
|
|
return String(v.StringValue()), nil
|
|
case v.IsArray():
|
|
a := v.ArrayValue()
|
|
r := make(Array, len(a))
|
|
for i, v := range a {
|
|
uv, err := unmarshal(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r[i] = uv
|
|
}
|
|
return r, nil
|
|
case v.IsObject():
|
|
m := v.ObjectValue()
|
|
return unmarshalPropertyMap(ctx, m)
|
|
case v.IsAsset():
|
|
asset := v.AssetValue()
|
|
switch {
|
|
case asset.IsPath():
|
|
return NewFileAsset(asset.Path), nil
|
|
case asset.IsText():
|
|
return NewStringAsset(asset.Text), nil
|
|
case asset.IsURI():
|
|
return NewRemoteAsset(asset.URI), nil
|
|
}
|
|
return nil, errors.New("expected asset to be one of File, String, or Remote; got none")
|
|
case v.IsArchive():
|
|
archive := v.ArchiveValue()
|
|
secret := false
|
|
switch {
|
|
case archive.IsAssets():
|
|
as := make(map[string]interface{})
|
|
for k, v := range archive.Assets {
|
|
a, asecret, err := unmarshalPropertyValue(ctx, resource.NewPropertyValue(v))
|
|
secret = secret || asecret
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
as[k] = a
|
|
}
|
|
return NewAssetArchive(as), nil
|
|
case archive.IsPath():
|
|
return NewFileArchive(archive.Path), nil
|
|
case archive.IsURI():
|
|
return NewRemoteArchive(archive.URI), nil
|
|
}
|
|
return nil, errors.New("expected archive to be one of Assets, File, or Remote; got none")
|
|
case v.IsResourceReference():
|
|
resRef := v.ResourceReferenceValue()
|
|
res := ctx.newDependencyResource(URN(resRef.URN))
|
|
|
|
output := ctx.newOutput(reflect.TypeOf((*ResourceOutput)(nil)).Elem())
|
|
internal.ResolveOutput(output, res, true, false, nil /* deps */)
|
|
return output, nil
|
|
|
|
case v.IsComputed():
|
|
typ := reflect.TypeOf((*any)(nil)).Elem()
|
|
typ = getOutputType(typ)
|
|
output := ctx.newOutput(typ)
|
|
internal.ResolveOutput(output, nil, false, false, nil /* deps */)
|
|
return output, nil
|
|
case v.IsSecret():
|
|
element, err := unmarshal(v.SecretValue().Element)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ToSecret(element), nil
|
|
case v.IsOutput():
|
|
deps := make([]internal.Resource, len(v.OutputValue().Dependencies))
|
|
for i, dep := range v.OutputValue().Dependencies {
|
|
deps[i] = ctx.newDependencyResource(URN(dep))
|
|
}
|
|
|
|
known := v.OutputValue().Known
|
|
secret := v.OutputValue().Secret
|
|
|
|
// If the output is known, we can unmarshal it directly else it's nil
|
|
typ := anyOutputType
|
|
var element interface{}
|
|
if v.OutputValue().Known {
|
|
var err error
|
|
element, err = unmarshal(v.OutputValue().Element)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Return an output of the type of the inner value, except for nil which should type as Output[any].
|
|
if element != nil {
|
|
// element will be an Input/Output type like pulumi.String or pulumi.AnyOutput. We want
|
|
// the inner value to assign to the output below. If the inner value is an output itself
|
|
// this collapses it to a single output value, this probably isn't ideal but nested
|
|
// outputs are really hard to support wihout generics.
|
|
o := ToOutput(element)
|
|
if o != nil {
|
|
typ = reflect.TypeOf(o)
|
|
|
|
innerValue, innerKnown, innerSecret, innerDeps, err := awaitWithContext(ctx.Context(), o)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
element = innerValue
|
|
known = known && innerKnown
|
|
secret = secret || innerSecret
|
|
for _, dep := range innerDeps {
|
|
deps = append(deps, dep)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
output := ctx.newOutput(typ)
|
|
internal.ResolveOutput(output, element, known, secret, deps)
|
|
return output, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("unknown property value %v", v)
|
|
}
|
|
|
|
m := make(Map)
|
|
for k, v := range v {
|
|
uv, err := unmarshal(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
m[string(k)] = uv
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
// unmarshalOutput unmarshals a single output variable into its runtime representation.
|
|
// returning a bool that indicates secretness
|
|
func unmarshalOutput(ctx *Context, v resource.PropertyValue, dest reflect.Value) (bool, error) {
|
|
contract.Requiref(dest.CanSet(), "dest", "value must be settable")
|
|
|
|
// Check for nils and unknowns. The destination will be left with the zero value.
|
|
if v.IsNull() || v.IsComputed() || (v.IsOutput() && !v.OutputValue().Known) {
|
|
return false, nil
|
|
}
|
|
|
|
// Allocate storage as necessary.
|
|
for dest.Kind() == reflect.Ptr {
|
|
elem := reflect.New(dest.Type().Elem())
|
|
dest.Set(elem)
|
|
dest = elem.Elem()
|
|
}
|
|
|
|
// In the case of assets and archives, turn these into real asset and archive structures.
|
|
switch {
|
|
case v.IsAsset():
|
|
if !assetType.AssignableTo(dest.Type()) {
|
|
return false, fmt.Errorf("expected a %s, got an asset", dest.Type())
|
|
}
|
|
|
|
asset, secret, err := unmarshalPropertyValue(ctx, v)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
dest.Set(reflect.ValueOf(asset))
|
|
return secret, nil
|
|
case v.IsArchive():
|
|
if !archiveType.AssignableTo(dest.Type()) {
|
|
return false, fmt.Errorf("expected a %s, got an archive", dest.Type())
|
|
}
|
|
|
|
archive, secret, err := unmarshalPropertyValue(ctx, v)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
dest.Set(reflect.ValueOf(archive))
|
|
return secret, nil
|
|
case v.IsSecret():
|
|
if _, err := unmarshalOutput(ctx, v.SecretValue().Element, dest); err != nil {
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
case v.IsResourceReference():
|
|
res, secret, err := unmarshalPropertyValue(ctx, v)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
resV := reflect.ValueOf(res).Elem()
|
|
if !resV.Type().AssignableTo(dest.Type()) {
|
|
return false, fmt.Errorf("expected a %s, got a resource of type %s", dest.Type(), resV.Type())
|
|
}
|
|
dest.Set(resV)
|
|
return secret, nil
|
|
case v.IsOutput():
|
|
if _, err := unmarshalOutput(ctx, v.OutputValue().Element, dest); err != nil {
|
|
return false, err
|
|
}
|
|
return v.OutputValue().Secret, nil
|
|
}
|
|
|
|
// Unmarshal based on the desired type.
|
|
//nolint:exhaustive // We only need to handle a few types here.
|
|
switch dest.Kind() {
|
|
case reflect.Bool:
|
|
if !v.IsBool() {
|
|
return false, fmt.Errorf("expected a %v, got a %s", dest.Type(), v.TypeString())
|
|
}
|
|
dest.SetBool(v.BoolValue())
|
|
return false, nil
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
if !v.IsNumber() {
|
|
return false, fmt.Errorf("expected an %v, got a %s", dest.Type(), v.TypeString())
|
|
}
|
|
dest.SetInt(int64(v.NumberValue()))
|
|
return false, nil
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
if !v.IsNumber() {
|
|
return false, fmt.Errorf("expected an %v, got a %s", dest.Type(), v.TypeString())
|
|
}
|
|
dest.SetUint(uint64(v.NumberValue()))
|
|
return false, nil
|
|
case reflect.Float32, reflect.Float64:
|
|
if !v.IsNumber() {
|
|
return false, fmt.Errorf("expected an %v, got a %s", dest.Type(), v.TypeString())
|
|
}
|
|
dest.SetFloat(v.NumberValue())
|
|
return false, nil
|
|
case reflect.String:
|
|
switch {
|
|
case v.IsString():
|
|
dest.SetString(v.StringValue())
|
|
case v.IsResourceReference():
|
|
ref := v.ResourceReferenceValue()
|
|
if id, hasID := ref.IDString(); hasID {
|
|
dest.SetString(id)
|
|
} else {
|
|
dest.SetString(string(ref.URN))
|
|
}
|
|
default:
|
|
return false, fmt.Errorf("expected a %v, got a %s", dest.Type(), v.TypeString())
|
|
}
|
|
return false, nil
|
|
case reflect.Slice:
|
|
if !v.IsArray() {
|
|
return false, fmt.Errorf("expected a %v, got a %s", dest.Type(), v.TypeString())
|
|
}
|
|
arr := v.ArrayValue()
|
|
slice := reflect.MakeSlice(dest.Type(), len(arr), len(arr))
|
|
secret := false
|
|
for i, e := range arr {
|
|
isecret, err := unmarshalOutput(ctx, e, slice.Index(i))
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
secret = secret || isecret
|
|
}
|
|
dest.Set(slice)
|
|
return secret, nil
|
|
case reflect.Map:
|
|
if !v.IsObject() {
|
|
return false, fmt.Errorf("expected a %v, got a %s", dest.Type(), v.TypeString())
|
|
}
|
|
|
|
keyType, elemType := dest.Type().Key(), dest.Type().Elem()
|
|
if keyType.Kind() != reflect.String {
|
|
return false, errors.New("map keys must be assignable from type string")
|
|
}
|
|
|
|
result := reflect.MakeMap(dest.Type())
|
|
secret := false
|
|
for k, e := range v.ObjectValue() {
|
|
if resource.IsInternalPropertyKey(k) {
|
|
continue
|
|
}
|
|
elem := reflect.New(elemType).Elem()
|
|
esecret, err := unmarshalOutput(ctx, e, elem)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
secret = secret || esecret
|
|
|
|
key := reflect.New(keyType).Elem()
|
|
key.SetString(string(k))
|
|
|
|
result.SetMapIndex(key, elem)
|
|
}
|
|
dest.Set(result)
|
|
return secret, nil
|
|
case reflect.Interface:
|
|
// Tolerate invalid asset or archive values.
|
|
typ := dest.Type()
|
|
switch typ {
|
|
case assetType:
|
|
_, secret, err := unmarshalPropertyValue(ctx, v)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
asset := &asset{invalid: true}
|
|
dest.Set(reflect.ValueOf(asset))
|
|
return secret, nil
|
|
case archiveType:
|
|
_, secret, err := unmarshalPropertyValue(ctx, v)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
archive := &archive{invalid: true}
|
|
dest.Set(reflect.ValueOf(archive))
|
|
return secret, nil
|
|
}
|
|
|
|
if !anyType.Implements(typ) {
|
|
return false, fmt.Errorf("cannot unmarshal into non-empty interface type %v", dest.Type())
|
|
}
|
|
|
|
// If we're unmarshaling into the empty interface type, use the property type as the type of the result.
|
|
result, secret, err := unmarshalPropertyValue(ctx, v)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
dest.Set(reflect.ValueOf(result))
|
|
return secret, nil
|
|
case reflect.Struct:
|
|
typ := dest.Type()
|
|
if !v.IsObject() {
|
|
return false, fmt.Errorf("expected a %v, got a %s", dest.Type(), v.TypeString())
|
|
}
|
|
|
|
obj := v.ObjectValue()
|
|
secret := false
|
|
for i := 0; i < typ.NumField(); i++ {
|
|
fieldV := dest.Field(i)
|
|
if !fieldV.CanSet() {
|
|
continue
|
|
}
|
|
|
|
tag := typ.Field(i).Tag.Get("pulumi")
|
|
tag = strings.Split(tag, ",")[0] // tagName,flag => tagName
|
|
if tag == "" {
|
|
continue
|
|
}
|
|
|
|
e, ok := obj[resource.PropertyKey(tag)]
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
osecret, err := unmarshalOutput(ctx, e, fieldV)
|
|
secret = secret || osecret
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
}
|
|
return secret, nil
|
|
default:
|
|
return false, fmt.Errorf("cannot unmarshal into type %v", dest.Type())
|
|
}
|
|
}
|
|
|
|
type Versioned interface {
|
|
Version() semver.Version
|
|
}
|
|
|
|
type versionedMap struct {
|
|
sync.RWMutex
|
|
versions map[string][]Versioned
|
|
}
|
|
|
|
// nullVersion represents the wildcard version (match any version).
|
|
var nullVersion semver.Version
|
|
|
|
func (vm *versionedMap) Load(key string, version semver.Version) (Versioned, bool) {
|
|
vm.RLock()
|
|
defer vm.RUnlock()
|
|
|
|
wildcard := version.EQ(nullVersion)
|
|
|
|
var bestVersion Versioned
|
|
for _, v := range vm.versions[key] {
|
|
// Unless we are matching a wildcard version, constrain search to matching major version.
|
|
if !wildcard && v.Version().Major != version.Major {
|
|
continue
|
|
}
|
|
|
|
// If we find an exact match, return that.
|
|
if v.Version().EQ(version) {
|
|
return v, true
|
|
}
|
|
|
|
if bestVersion == nil {
|
|
bestVersion = v
|
|
continue
|
|
}
|
|
if v.Version().GTE(bestVersion.Version()) {
|
|
bestVersion = v
|
|
}
|
|
}
|
|
|
|
return bestVersion, bestVersion != nil
|
|
}
|
|
|
|
func (vm *versionedMap) Store(key string, value Versioned) error {
|
|
vm.Lock()
|
|
defer vm.Unlock()
|
|
|
|
hasVersion := func(versions []Versioned, version semver.Version) bool {
|
|
for _, v := range versions {
|
|
if v.Version().EQ(value.Version()) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
if _, exists := vm.versions[key]; exists && hasVersion(vm.versions[key], value.Version()) {
|
|
return fmt.Errorf("existing registration for %v: %s", key, value.Version())
|
|
}
|
|
|
|
vm.versions[key] = append(vm.versions[key], value)
|
|
|
|
return nil
|
|
}
|
|
|
|
type ResourcePackage interface {
|
|
Versioned
|
|
ConstructProvider(ctx *Context, name, typ, urn string) (ProviderResource, error)
|
|
}
|
|
|
|
type ResourceModule interface {
|
|
Versioned
|
|
Construct(ctx *Context, name, typ, urn string) (Resource, error)
|
|
}
|
|
|
|
var (
|
|
resourcePackages versionedMap
|
|
resourceModules versionedMap
|
|
)
|
|
|
|
// RegisterResourcePackage register a resource package with the Pulumi runtime.
|
|
func RegisterResourcePackage(pkg string, resourcePackage ResourcePackage) {
|
|
if err := resourcePackages.Store(pkg, resourcePackage); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func moduleKey(pkg, mod string) string {
|
|
return fmt.Sprintf("%s:%s", pkg, mod)
|
|
}
|
|
|
|
// RegisterResourceModule register a resource module with the Pulumi runtime.
|
|
func RegisterResourceModule(pkg, mod string, module ResourceModule) {
|
|
key := moduleKey(pkg, mod)
|
|
if err := resourceModules.Store(key, module); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func init() {
|
|
resourcePackages = versionedMap{versions: make(map[string][]Versioned)}
|
|
resourceModules = versionedMap{versions: make(map[string][]Versioned)}
|
|
}
|