pulumi/sdk/go/pulumi/types.go
Justin Van Patten 1a4f36e97b
[sdk/go] Add RegisterInputType and register built-in types (#7928)
This change adds a `RegisterInputType` function (similar to the existing `RegisterOutputType`) that is used to register an input interface's type and its associated input type, and adds registrations for the built-in input types.

This will be used when copying inputs to an args struct for multi-lang components. When a field is typed as the input interface (e.g. `StringMapInput`) and doesn't need to be an `Output`, we can use this to lookup the non-Output type that implements the interface (e.g. `StringMap`) so it can be instantiated.

A subsequent change will update the Go SDK codegen to produce input type registrations for a provider's input types.
2021-09-15 21:12:49 -07:00

1155 lines
35 KiB
Go

// Copyright 2016-2020, 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.
// nolint: lll, interfacer
package pulumi
import (
"context"
"errors"
"fmt"
"reflect"
"sync"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
)
// Output helps encode the relationship between resources in a Pulumi application. Specifically an output property
// holds onto a value and the resource it came from. An output value can then be provided when constructing new
// resources, allowing that new resource to know both the value as well as the resource the value came from. This
// allows for a precise "dependency graph" to be created, which properly tracks the relationship between resources.
type Output interface {
ElementType() reflect.Type
ApplyT(applier interface{}) Output
ApplyTWithContext(ctx context.Context, applier interface{}) Output
getState() *OutputState
}
var outputType = reflect.TypeOf((*Output)(nil)).Elem()
var inputType = reflect.TypeOf((*Input)(nil)).Elem()
var concreteTypeToOutputType sync.Map // map[reflect.Type]reflect.Type
// RegisterOutputType registers an Output type with the Pulumi runtime. If a value of this type's concrete type is
// returned by an Apply, the Apply will return the specific Output type.
func RegisterOutputType(output Output) {
elementType := output.ElementType()
existing, hasExisting := concreteTypeToOutputType.LoadOrStore(elementType, reflect.TypeOf(output))
if hasExisting {
panic(fmt.Errorf("an output type for %v is already registered: %v", elementType, existing))
}
}
var inputInterfaceTypeToConcreteType sync.Map // map[reflect.Type]reflect.Type
// RegisterInputType registers an Input type with the Pulumi runtime. This allows the input type to be instantiated
// for a given input interface.
func RegisterInputType(interfaceType reflect.Type, input Input) {
if interfaceType.Kind() != reflect.Interface {
panic(fmt.Errorf("expected %v to be an interface", interfaceType))
}
if !interfaceType.Implements(inputType) {
panic(fmt.Errorf("expected %v to implement %v", interfaceType, inputType))
}
concreteType := reflect.TypeOf(input)
if !concreteType.Implements(interfaceType) {
panic(fmt.Errorf("expected %v to implement interface %v", concreteType, interfaceType))
}
existing, hasExisting := inputInterfaceTypeToConcreteType.LoadOrStore(interfaceType, concreteType)
if hasExisting {
panic(fmt.Errorf("an input type for %v is already registered: %v", interfaceType, existing))
}
}
type workGroups []*workGroup
func (wgs workGroups) add() {
for _, g := range wgs {
g.Add(1)
}
}
func (wgs workGroups) done() {
for _, g := range wgs {
g.Done()
}
}
const (
outputPending = iota
outputResolved
outputRejected
)
// OutputState holds the internal details of an Output and implements the Apply and ApplyWithContext methods.
type OutputState struct {
mutex sync.Mutex
cond *sync.Cond
join *workGroup // the wait group associated with this output, if any.
state uint32 // one of output{Pending,Resolved,Rejected}
value interface{} // the value of this output if it is resolved.
err error // the error associated with this output if it is rejected.
known bool // true if this output's value is known.
secret bool // true if this output's value is secret
element reflect.Type // the element type of this output.
deps []Resource // the dependencies associated with this output property.
}
func getOutputState(v reflect.Value) (*OutputState, bool) {
if !v.IsValid() || !v.CanInterface() {
return nil, false
}
out, ok := v.Interface().(Output)
if !ok {
return nil, false
}
return out.getState(), true
}
func (o *OutputState) elementType() reflect.Type {
if o == nil {
return anyType
}
return o.element
}
func (o *OutputState) dependencies() []Resource {
if o == nil {
return nil
}
return o.deps
}
func (o *OutputState) fulfill(value interface{}, known, secret bool, deps []Resource, err error) {
o.fulfillValue(reflect.ValueOf(value), known, secret, deps, err)
}
func (o *OutputState) fulfillValue(value reflect.Value, known, secret bool, deps []Resource, err error) {
if o == nil {
return
}
o.mutex.Lock()
defer func() {
o.mutex.Unlock()
o.cond.Broadcast()
}()
if o.state != outputPending {
return
}
// If there is a wait group associated with this output--which should be the case in all outputs created
// by a Context or a combinator that was passed any non-prompt value--ensure that we decrement its count
// before this function returns. This allows Contexts to remain alive until all outstanding asynchronous
// work that may reference that context has completed.
//
// Code that creates an output must take care to bump the count for any relevant waitgroups prior to
// creating asynchronous work associated with that output. For combinators, this means digging through
// inputs, collecting all wait groups, and calling Add (see toOutputTWithContext for an example). For
// code that creates outputs directly, this is as simple as passing the wait group for the associated
// context to newOutput.
//
// User code should use combinators or Context.NewOutput to ensure that all asynchronous work is
// associated with a Context.
if o.join != nil {
// If this output is being resolved to another output O' with a different wait group, ensure that we
// don't decrement the current output's wait group until O' completes.
if other, ok := getOutputState(value); ok && other.join != o.join {
go func() {
//nolint:errcheck
other.await(context.Background())
o.join.Done()
}()
} else {
defer o.join.Done()
}
}
if err != nil {
o.state, o.err, o.known, o.secret = outputRejected, err, true, secret
} else {
if value.IsValid() {
reflect.ValueOf(&o.value).Elem().Set(value)
}
o.state, o.known, o.secret = outputResolved, known, secret
// If needed, merge the up-front provided dependencies with fulfilled dependencies, pruning duplicates.
if len(deps) == 0 {
// We didn't get any new dependencies, so no need to merge.
return
}
depSet := make(map[Resource]struct{})
for _, d := range o.deps {
depSet[d] = struct{}{}
}
for _, d := range deps {
depSet[d] = struct{}{}
}
mergedDeps := make([]Resource, 0, len(depSet))
for d := range depSet {
mergedDeps = append(mergedDeps, d)
}
o.deps = mergedDeps
}
}
func (o *OutputState) resolve(value interface{}, known, secret bool, deps []Resource) {
o.fulfill(value, known, secret, deps, nil)
}
func (o *OutputState) resolveValue(value reflect.Value, known, secret bool, deps []Resource) {
o.fulfillValue(value, known, secret, deps, nil)
}
func (o *OutputState) reject(err error) {
o.fulfill(nil, true, false, nil, err)
}
func (o *OutputState) await(ctx context.Context) (interface{}, bool, bool, []Resource, error) {
for {
if o == nil {
// If the state is nil, treat its value as resolved and unknown.
return nil, false, false, nil, nil
}
o.mutex.Lock()
for o.state == outputPending {
if ctx.Err() != nil {
return nil, true, false, nil, ctx.Err()
}
o.cond.Wait()
}
o.mutex.Unlock()
if !o.known || o.err != nil {
return nil, o.known, o.secret, o.deps, o.err
}
// If the result is an Output, await it in turn.
//
// NOTE: this isn't exactly type safe! The element type of the inner output really needs to be assignable to
// the element type of the outer output. We should reconsider this.
ov, ok := o.value.(Output)
if !ok {
return o.value, true, o.secret, o.deps, nil
}
o = ov.getState()
}
}
func (o *OutputState) getState() *OutputState {
return o
}
func newOutputState(join *workGroup, elementType reflect.Type, deps ...Resource) *OutputState {
if join != nil {
join.Add(1)
}
out := &OutputState{
join: join,
element: elementType,
deps: deps,
}
out.cond = sync.NewCond(&out.mutex)
return out
}
var outputStateType = reflect.TypeOf((*OutputState)(nil))
var outputTypeToOutputState sync.Map // map[reflect.Type]int
func newOutput(wg *workGroup, typ reflect.Type, deps ...Resource) Output {
contract.Assert(typ.Implements(outputType))
// All values that implement Output must embed a field of type `*OutputState` by virtue of the unexported
// `isOutput` method. If we yet haven't recorded the index of this field for the ouptut type `typ`, find and
// record it.
outputFieldV, ok := outputTypeToOutputState.Load(typ)
if !ok {
outputField := -1
for i := 0; i < typ.NumField(); i++ {
f := typ.Field(i)
if f.Anonymous && f.Type == outputStateType {
outputField = i
break
}
}
contract.Assert(outputField != -1)
outputTypeToOutputState.Store(typ, outputField)
outputFieldV = outputField
}
// Create the new output.
output := reflect.New(typ).Elem()
state := newOutputState(wg, output.Interface().(Output).ElementType(), deps...)
output.Field(outputFieldV.(int)).Set(reflect.ValueOf(state))
return output.Interface().(Output)
}
func newAnyOutput(wg *workGroup) (Output, func(interface{}), func(error)) {
out := newOutputState(wg, anyType)
resolve := func(v interface{}) {
out.resolve(v, true, false, nil)
}
reject := func(err error) {
out.getState().reject(err)
}
return AnyOutput{out}, resolve, reject
}
// NewOutput returns an output value that can be used to rendezvous with the production of a value or error. The
// function returns the output itself, plus two functions: one for resolving a value, and another for rejecting with an
// error; exactly one function must be called. This acts like a promise.
//
// Deprecated: use Context.NewOutput instead.
func NewOutput() (Output, func(interface{}), func(error)) {
return newAnyOutput(nil)
}
var contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
var errorType = reflect.TypeOf((*error)(nil)).Elem()
func makeContextful(fn interface{}, elementType reflect.Type) interface{} {
fv := reflect.ValueOf(fn)
if fv.Kind() != reflect.Func {
panic(errors.New("applier must be a function"))
}
ft := fv.Type()
if ft.NumIn() != 1 || !elementType.AssignableTo(ft.In(0)) {
panic(fmt.Errorf("applier must have 1 input parameter assignable from %v", elementType))
}
var outs []reflect.Type
switch ft.NumOut() {
case 1:
// Okay
outs = []reflect.Type{ft.Out(0)}
case 2:
// Second out parameter must be of type error
if !ft.Out(1).AssignableTo(errorType) {
panic(errors.New("applier's second return type must be assignable to error"))
}
outs = []reflect.Type{ft.Out(0), ft.Out(1)}
default:
panic(errors.New("applier must return exactly one or two values"))
}
ins := []reflect.Type{contextType, ft.In(0)}
contextfulType := reflect.FuncOf(ins, outs, ft.IsVariadic())
contextfulFunc := reflect.MakeFunc(contextfulType, func(args []reflect.Value) []reflect.Value {
// Slice off the context argument and call the applier.
return fv.Call(args[1:])
})
return contextfulFunc.Interface()
}
func checkApplier(fn interface{}, elementType reflect.Type) reflect.Value {
fv := reflect.ValueOf(fn)
if fv.Kind() != reflect.Func {
panic(errors.New("applier must be a function"))
}
ft := fv.Type()
if ft.NumIn() != 2 || !contextType.AssignableTo(ft.In(0)) || !elementType.AssignableTo(ft.In(1)) {
panic(fmt.Errorf("applier's input parameters must be assignable from %v and %v", contextType, elementType))
}
switch ft.NumOut() {
case 1:
// Okay
case 2:
// Second out parameter must be of type error
if !ft.Out(1).AssignableTo(errorType) {
panic(errors.New("applier's second return type must be assignable to error"))
}
default:
panic(errors.New("applier must return exactly one or two values"))
}
// Okay
return fv
}
// ApplyT transforms the data of the output property using the applier func. The result remains an output
// property, and accumulates all implicated dependencies, so that resources can be properly tracked using a DAG.
// This function does not block awaiting the value; instead, it spawns a Goroutine that will await its availability.
//
// The applier function must have one of the following signatures:
//
// func (v U) T
// func (v U) (T, error)
//
// U must be assignable from the ElementType of the Output. If T is a type that has a registered Output type, the
// result of ApplyT will be of the registered Output type, and can be used in an appropriate type assertion:
//
// stringOutput := pulumi.String("hello").ToStringOutput()
// intOutput := stringOutput.ApplyT(func(v string) int {
// return len(v)
// }).(pulumi.IntOutput)
//
// Otherwise, the result will be of type AnyOutput:
//
// stringOutput := pulumi.String("hello").ToStringOutput()
// intOutput := stringOutput.ApplyT(func(v string) []rune {
// return []rune(v)
// }).(pulumi.AnyOutput)
//
func (o *OutputState) ApplyT(applier interface{}) Output {
return o.ApplyTWithContext(context.Background(), makeContextful(applier, o.elementType()))
}
var anyOutputType = reflect.TypeOf((*AnyOutput)(nil)).Elem()
// ApplyTWithContext transforms the data of the output property using the applier func. The result remains an output
// property, and accumulates all implicated dependencies, so that resources can be properly tracked using a DAG.
// This function does not block awaiting the value; instead, it spawns a Goroutine that will await its availability.
// The provided context can be used to reject the output as canceled.
//
// The applier function must have one of the following signatures:
//
// func (ctx context.Context, v U) T
// func (ctx context.Context, v U) (T, error)
//
// U must be assignable from the ElementType of the Output. If T is a type that has a registered Output type, the
// result of ApplyT will be of the registered Output type, and can be used in an appropriate type assertion:
//
// stringOutput := pulumi.String("hello").ToStringOutput()
// intOutput := stringOutput.ApplyTWithContext(func(_ context.Context, v string) int {
// return len(v)
// }).(pulumi.IntOutput)
//
// Otherwise, the result will be of type AnyOutput:
//
// stringOutput := pulumi.String("hello").ToStringOutput()
// intOutput := stringOutput.ApplyT(func(_ context.Context, v string) []rune {
// return []rune(v)
// }).(pulumi.AnyOutput)
//
func (o *OutputState) ApplyTWithContext(ctx context.Context, applier interface{}) Output {
fn := checkApplier(applier, o.elementType())
resultType := anyOutputType
if ot, ok := concreteTypeToOutputType.Load(fn.Type().Out(0)); ok {
resultType = ot.(reflect.Type)
}
result := newOutput(o.join, resultType, o.dependencies()...)
go func() {
v, known, secret, deps, err := o.getState().await(ctx)
if err != nil || !known {
result.getState().fulfill(nil, known, secret, deps, err)
return
}
// If we have a known value, run the applier to transform it.
val := reflect.ValueOf(v)
if !val.IsValid() {
val = reflect.Zero(o.elementType())
}
results := fn.Call([]reflect.Value{reflect.ValueOf(ctx), val})
if len(results) == 2 && !results[1].IsNil() {
result.getState().reject(results[1].Interface().(error))
return
}
// Fulfill the result.
result.getState().fulfillValue(results[0], true, secret, deps, nil)
}()
return result
}
// IsSecret returns a bool representing the secretness of the Output
func IsSecret(o Output) bool {
return o.getState().secret
}
// Unsecret will unwrap a secret output as a new output with a resolved value and no secretness
func Unsecret(input Output) Output {
return UnsecretWithContext(context.Background(), input)
}
// UnsecretWithContext will unwrap a secret output as a new output with a resolved value and no secretness
func UnsecretWithContext(ctx context.Context, input Output) Output {
var x bool
o := toOutputWithContext(ctx, input.getState().join, input, &x)
// set immediate secretness ahead of resolution/fulfillment
o.getState().secret = false
return o
}
// ToSecret wraps the input in an Output marked as secret
// that will resolve when all Inputs contained in the given value have resolved.
func ToSecret(input interface{}) Output {
return ToSecretWithContext(context.Background(), input)
}
// ToSecretWithContext wraps the input in an Output marked as secret
// that will resolve when all Inputs contained in the given value have resolved.
func ToSecretWithContext(ctx context.Context, input interface{}) Output {
x := true
o := toOutputWithContext(ctx, nil, input, &x)
// set immediate secretness ahead of resolution/fufillment
o.getState().secret = true
return o
}
// All returns an ArrayOutput that will resolve when all of the provided inputs will resolve. Each element of the
// array will contain the resolved value of the corresponding output. The output will be rejected if any of the inputs
// is rejected.
func All(inputs ...interface{}) ArrayOutput {
return AllWithContext(context.Background(), inputs...)
}
// AllWithContext returns an ArrayOutput that will resolve when all of the provided inputs will resolve. Each
// element of the array will contain the resolved value of the corresponding output. The output will be rejected if any
// of the inputs is rejected.
func AllWithContext(ctx context.Context, inputs ...interface{}) ArrayOutput {
return ToOutputWithContext(ctx, inputs).(ArrayOutput)
}
func gatherDependencies(v interface{}) ([]Resource, workGroups) {
if v == nil {
return nil, nil
}
depSet := make(map[Resource]struct{})
joinSet := make(map[*workGroup]struct{})
gatherDependencySet(reflect.ValueOf(v), depSet, joinSet)
var joins workGroups
if len(joinSet) > 0 {
joins = make([]*workGroup, 0, len(joinSet))
for j := range joinSet {
joins = append(joins, j)
}
}
var deps []Resource
if len(depSet) > 0 {
deps = make([]Resource, 0, len(depSet))
for d := range depSet {
deps = append(deps, d)
}
}
return deps, joins
}
var resourceType = reflect.TypeOf((*Resource)(nil)).Elem()
func gatherDependencySet(v reflect.Value, deps map[Resource]struct{}, joins map[*workGroup]struct{}) {
for {
// Check for an Output that we can pull dependencies off of.
if v.Type().Implements(outputType) && v.CanInterface() {
output := v.Convert(outputType).Interface().(Output)
if join := output.getState().join; join != nil {
joins[join] = struct{}{}
}
for _, d := range output.getState().dependencies() {
deps[d] = struct{}{}
}
return
}
// Check for an actual Resource.
if v.Type().Implements(resourceType) {
if v.CanInterface() {
resource := v.Convert(resourceType).Interface().(Resource)
deps[resource] = struct{}{}
}
return
}
switch v.Kind() {
case reflect.Interface, reflect.Ptr:
if v.IsNil() {
return
}
v = v.Elem()
continue
case reflect.Struct:
numFields := v.Type().NumField()
for i := 0; i < numFields; i++ {
gatherDependencySet(v.Field(i), deps, joins)
}
case reflect.Array, reflect.Slice:
l := v.Len()
for i := 0; i < l; i++ {
gatherDependencySet(v.Index(i), deps, joins)
}
case reflect.Map:
iter := v.MapRange()
for iter.Next() {
gatherDependencySet(iter.Key(), deps, joins)
gatherDependencySet(iter.Value(), deps, joins)
}
}
return
}
}
func checkToOutputMethod(m reflect.Value, outputType reflect.Type) bool {
if !m.IsValid() {
return false
}
mt := m.Type()
if mt.NumIn() != 1 || mt.In(0) != contextType {
return false
}
return mt.NumOut() == 1 && mt.Out(0) == outputType
}
func callToOutputMethod(ctx context.Context, input reflect.Value, resolvedType reflect.Type) (Output, bool) {
ot, ok := concreteTypeToOutputType.Load(resolvedType)
if !ok {
return nil, false
}
outputType := ot.(reflect.Type)
toOutputMethodName := "To" + outputType.Name() + "WithContext"
toOutputMethod := input.MethodByName(toOutputMethodName)
if !checkToOutputMethod(toOutputMethod, outputType) {
return nil, false
}
return toOutputMethod.Call([]reflect.Value{reflect.ValueOf(ctx)})[0].Interface().(Output), true
}
func awaitInputs(ctx context.Context, v, resolved reflect.Value) (bool, bool, []Resource, error) {
contract.Assert(v.IsValid())
if !resolved.CanSet() {
return true, false, nil, nil
}
// If the value is an Input with of a different element type, turn it into an Output of the appropriate type and
// await it.
valueType, isInput := v.Type(), false
if v.CanInterface() && valueType.Implements(inputType) {
input, ok := v.Interface().(Input)
if !ok {
// A non-input type is already fully-resolved.
return true, false, nil, nil
}
if val := reflect.ValueOf(input); val.Kind() == reflect.Ptr && val.IsNil() {
// A nil input is already fully-resolved.
return true, false, nil, nil
}
valueType = input.ElementType()
assignInput := false
// 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 the appropriately-typed output.
if valueType != resolved.Type() && resolved.Type() != anyType {
if newOutput, ok := callToOutputMethod(ctx, reflect.ValueOf(input), resolved.Type()); ok {
// We were able to convert the input. Use the result as the new input value.
input = newOutput
} else if !valueType.AssignableTo(resolved.Type()) {
// If the value type is not assignable to the destination, see if we can assign the input value itself
// to the destination.
if !v.Type().AssignableTo(resolved.Type()) {
panic(fmt.Errorf("cannot convert an input of type %T to a value of type %v",
input, resolved.Type()))
} else {
assignInput = true
}
}
}
// If the input is an Output, await its value. The returned value is fully resolved.
if output, ok := input.(Output); ok {
e, known, secret, deps, err := output.getState().await(ctx)
if err != nil || !known {
return known, secret, deps, err
}
if !assignInput {
val := reflect.ValueOf(e)
if !val.IsValid() {
val = reflect.Zero(output.ElementType())
}
resolved.Set(val)
} else {
resolved.Set(reflect.ValueOf(input))
}
return true, secret, deps, nil
}
// Check for types that are already fully-resolved.
if v, ok := getResolvedValue(input); ok {
resolved.Set(v)
return true, false, nil, nil
}
v, isInput = reflect.ValueOf(input), true
// We require that the kind of an `Input`'s `ElementType` agrees with the kind of the `Input`'s underlying value.
// This requirement is trivially (and unintentionally) violated by `*T` if `*T` does not define `ElementType`,
// but `T` does (https://golang.org/ref/spec#Method_sets).
// In this case, dereference the pointer to get at its actual value.
if v.Kind() == reflect.Ptr && valueType.Kind() != reflect.Ptr {
v = v.Elem()
contract.Assert(v.Interface().(Input).ElementType() == valueType)
}
// If we are assigning the input value itself, update the value type.
if assignInput {
valueType = v.Type()
} else {
// Handle pointer inputs.
if v.Kind() == reflect.Ptr {
v, valueType = v.Elem(), valueType.Elem()
resolved.Set(reflect.New(resolved.Type().Elem()))
resolved = resolved.Elem()
}
}
}
contract.Assertf(valueType.AssignableTo(resolved.Type()), "%s not assignable to %s", valueType.String(), resolved.Type().String())
// If the resolved type is an interface, make an appropriate destination from the value's type.
if resolved.Kind() == reflect.Interface {
iface := resolved
defer func() { iface.Set(resolved) }()
resolved = reflect.New(valueType).Elem()
}
known, secret, deps, err := true, false, make([]Resource, 0), error(nil)
switch v.Kind() {
case reflect.Interface:
if !v.IsNil() {
return awaitInputs(ctx, v.Elem(), resolved)
}
case reflect.Ptr:
if !v.IsNil() {
resolved.Set(reflect.New(resolved.Type().Elem()))
return awaitInputs(ctx, v.Elem(), resolved.Elem())
}
case reflect.Struct:
typ := v.Type()
getMappedField := mapStructTypes(typ, resolved.Type())
numFields := typ.NumField()
for i := 0; i < numFields; i++ {
_, field := getMappedField(resolved, i)
fknown, fsecret, fdeps, ferr := awaitInputs(ctx, v.Field(i), field)
known = known && fknown
secret = secret || fsecret
deps = append(deps, fdeps...)
if err == nil {
err = ferr
}
}
case reflect.Array:
l := v.Len()
for i := 0; i < l; i++ {
eknown, esecret, edeps, eerr := awaitInputs(ctx, v.Index(i), resolved.Index(i))
known = known && eknown
secret = secret || esecret
deps = append(deps, edeps...)
if err == nil {
err = eerr
}
}
case reflect.Slice:
l := v.Len()
resolved.Set(reflect.MakeSlice(resolved.Type(), l, l))
for i := 0; i < l; i++ {
eknown, esecret, edeps, eerr := awaitInputs(ctx, v.Index(i), resolved.Index(i))
known = known && eknown
secret = secret || esecret
deps = append(deps, edeps...)
if err == nil {
err = eerr
}
}
case reflect.Map:
resolved.Set(reflect.MakeMap(resolved.Type()))
resolvedKeyType, resolvedValueType := resolved.Type().Key(), resolved.Type().Elem()
iter := v.MapRange()
for iter.Next() {
kv := reflect.New(resolvedKeyType).Elem()
kknown, ksecret, kdeps, kerr := awaitInputs(ctx, iter.Key(), kv)
if err == nil {
err = kerr
}
vv := reflect.New(resolvedValueType).Elem()
vknown, vsecret, vdeps, verr := awaitInputs(ctx, iter.Value(), vv)
if err == nil {
err = verr
}
if kerr == nil && verr == nil && kknown && vknown {
resolved.SetMapIndex(kv, vv)
}
known = known && kknown && vknown
secret = secret || ksecret || vsecret
deps = append(append(deps, kdeps...), vdeps...)
}
default:
if isInput {
v = v.Convert(valueType)
}
resolved.Set(v)
}
return known, secret, deps, err
}
func toOutputTWithContext(ctx context.Context, join *workGroup, outputType reflect.Type, v interface{}, result reflect.Value, forceSecretVal *bool) Output {
deps, joins := gatherDependencies(v)
done := joins.done
if join == nil {
switch len(joins) {
case 0:
// OK
case 1:
join, joins, done = joins[0], nil, func() {}
default:
join = &workGroup{}
done = func() {
join.Wait()
joins.done()
}
}
}
joins.add()
output := newOutput(join, outputType, deps...)
go func() {
defer done()
if v == nil {
output.getState().fulfill(nil, true, false, nil, nil)
return
}
known, secret, deps, err := awaitInputs(ctx, reflect.ValueOf(v), result)
if forceSecretVal != nil {
secret = *forceSecretVal
}
if err != nil || !known {
output.getState().fulfill(nil, known, secret, deps, err)
return
}
output.getState().resolveValue(result, true, secret, deps)
}()
return output
}
// ToOutput returns an Output that will resolve when all Inputs contained in the given value have resolved.
func ToOutput(v interface{}) Output {
return ToOutputWithContext(context.Background(), v)
}
// ToOutputWithContext returns an Output that will resolve when all Outputs contained in the given value have
// resolved.
func ToOutputWithContext(ctx context.Context, v interface{}) Output {
return toOutputWithContext(ctx, nil, v, nil)
}
func toOutputWithContext(ctx context.Context, join *workGroup, v interface{}, forceSecretVal *bool) Output {
resultType := reflect.TypeOf(v)
if input, ok := v.(Input); ok {
resultType = input.ElementType()
}
var result reflect.Value
if v != nil {
result = reflect.New(resultType).Elem()
}
outputType := anyOutputType
if ot, ok := concreteTypeToOutputType.Load(resultType); ok {
outputType = ot.(reflect.Type)
}
return toOutputTWithContext(ctx, join, outputType, v, result, forceSecretVal)
}
// Input is the type of a generic input value for a Pulumi resource. This type is used in conjunction with Output
// to provide polymorphism over strongly-typed input values.
//
// The intended pattern for nested Pulumi value types is to define an input interface and a plain, input, and output
// variant of the value type that implement the input interface.
//
// For example, given a nested Pulumi value type with the following shape:
//
// type Nested struct {
// Foo int
// Bar string
// }
//
// We would define the following:
//
// var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
//
// type NestedInput interface {
// pulumi.Input
//
// ToNestedOutput() NestedOutput
// ToNestedOutputWithContext(context.Context) NestedOutput
// }
//
// type Nested struct {
// Foo int `pulumi:"foo"`
// Bar string `pulumi:"bar"`
// }
//
// type NestedInputValue struct {
// Foo pulumi.IntInput `pulumi:"foo"`
// Bar pulumi.StringInput `pulumi:"bar"`
// }
//
// func (NestedInputValue) ElementType() reflect.Type {
// return nestedType
// }
//
// func (v NestedInputValue) ToNestedOutput() NestedOutput {
// return pulumi.ToOutput(v).(NestedOutput)
// }
//
// func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
// return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
// }
//
// type NestedOutput struct { *pulumi.OutputState }
//
// func (NestedOutput) ElementType() reflect.Type {
// return nestedType
// }
//
// func (o NestedOutput) ToNestedOutput() NestedOutput {
// return o
// }
//
// func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
// return o
// }
//
type Input interface {
ElementType() reflect.Type
}
var anyType = reflect.TypeOf((*interface{})(nil)).Elem()
func Any(v interface{}) AnyOutput {
return AnyWithContext(context.Background(), v)
}
func AnyWithContext(ctx context.Context, v interface{}) AnyOutput {
return anyWithContext(ctx, nil, v)
}
func anyWithContext(ctx context.Context, join *workGroup, v interface{}) AnyOutput {
var result interface{}
return toOutputTWithContext(ctx, join, anyOutputType, v, reflect.ValueOf(&result).Elem(), nil).(AnyOutput)
}
type AnyOutput struct{ *OutputState }
func (AnyOutput) ElementType() reflect.Type {
return anyType
}
func (in ID) ToStringPtrOutput() StringPtrOutput {
return in.ToStringPtrOutputWithContext(context.Background())
}
func (in ID) ToStringPtrOutputWithContext(ctx context.Context) StringPtrOutput {
return in.ToStringOutputWithContext(ctx).ToStringPtrOutputWithContext(ctx)
}
func (o IDOutput) ToStringPtrOutput() StringPtrOutput {
return o.ToStringPtrOutputWithContext(context.Background())
}
func (o IDOutput) ToStringPtrOutputWithContext(ctx context.Context) StringPtrOutput {
return o.ToStringOutputWithContext(ctx).ToStringPtrOutputWithContext(ctx)
}
func (o IDOutput) awaitID(ctx context.Context) (ID, bool, bool, error) {
id, known, secret, _, err := o.await(ctx)
if !known || err != nil {
return "", known, false, err
}
return ID(convert(id, stringType).(string)), true, secret, nil
}
func (in URN) ToStringPtrOutput() StringPtrOutput {
return in.ToStringPtrOutputWithContext(context.Background())
}
func (in URN) ToStringPtrOutputWithContext(ctx context.Context) StringPtrOutput {
return in.ToStringOutputWithContext(ctx).ToStringPtrOutputWithContext(ctx)
}
func (o URNOutput) ToStringPtrOutput() StringPtrOutput {
return o.ToStringPtrOutputWithContext(context.Background())
}
func (o URNOutput) ToStringPtrOutputWithContext(ctx context.Context) StringPtrOutput {
return o.ToStringOutputWithContext(ctx).ToStringPtrOutputWithContext(ctx)
}
func (o URNOutput) awaitURN(ctx context.Context) (URN, bool, bool, error) {
id, known, secret, _, err := o.await(ctx)
if !known || err != nil {
return "", known, secret, err
}
return URN(convert(id, stringType).(string)), true, secret, nil
}
func convert(v interface{}, to reflect.Type) interface{} {
rv := reflect.ValueOf(v)
if !rv.Type().ConvertibleTo(to) {
panic(fmt.Errorf("cannot convert output value of type %s to %s", rv.Type(), to))
}
return rv.Convert(to).Interface()
}
// TODO: ResourceOutput and the init() should probably be code generated.
// ResourceOutput is an Output that returns Resource values.
type ResourceOutput struct{ *OutputState }
// ElementType returns the element type of this Output (Resource).
func (ResourceOutput) ElementType() reflect.Type {
return reflect.TypeOf((*Resource)(nil)).Elem()
}
func (o ResourceOutput) ToResourceOutput() ResourceOutput {
return o
}
func (o ResourceOutput) ToResourceOutputWithContext(ctx context.Context) ResourceOutput {
return o
}
// An Input type carrying Resource values.
//
// Unfortunately `Resource` values do not implement `ResourceInput` in
// the current version. Use `NewResourceInput` instead.
type ResourceInput interface {
Input
ToResourceOutput() ResourceOutput
ToResourceOutputWithContext(context.Context) ResourceOutput
}
func NewResourceInput(resource Resource) ResourceInput {
return NewResourceOutput(resource)
}
func NewResourceOutput(resource Resource) ResourceOutput {
return Int(0).ToIntOutput().ApplyT(func(int) Resource { return resource }).(ResourceOutput)
}
var _ ResourceInput = &ResourceOutput{}
var resourceArrayType = reflect.TypeOf((*[]Resource)(nil)).Elem()
// ResourceArrayInput is an input type that accepts ResourceArray and ResourceArrayOutput values.
type ResourceArrayInput interface {
Input
ToResourceArrayOutput() ResourceArrayOutput
ToResourceArrayOutputWithContext(ctx context.Context) ResourceArrayOutput
}
// ResourceArray is an input type for []ResourceInput values.
type ResourceArray []ResourceInput
// ElementType returns the element type of this Input ([]Resource).
func (ResourceArray) ElementType() reflect.Type {
return resourceArrayType
}
func (in ResourceArray) ToResourceArrayOutput() ResourceArrayOutput {
return ToOutput(in).(ResourceArrayOutput)
}
func (in ResourceArray) ToResourceArrayOutputWithContext(ctx context.Context) ResourceArrayOutput {
return ToOutputWithContext(ctx, in).(ResourceArrayOutput)
}
// ResourceArrayOutput is an Output that returns []Resource values.
type ResourceArrayOutput struct{ *OutputState }
// ElementType returns the element type of this Output ([]Resource).
func (ResourceArrayOutput) ElementType() reflect.Type {
return resourceArrayType
}
func (o ResourceArrayOutput) ToResourceArrayOutput() ResourceArrayOutput {
return o
}
func (o ResourceArrayOutput) ToResourceArrayOutputWithContext(ctx context.Context) ResourceArrayOutput {
return o
}
// Index looks up the i'th element of the array if it is in bounds or returns the zero value of the appropriate
// type if the index is out of bounds.
func (o ResourceArrayOutput) Index(i IntInput) ResourceOutput {
return All(o, i).ApplyT(func(vs []interface{}) Resource {
arr := vs[0].([]Resource)
idx := vs[1].(int)
var ret Resource
if idx >= 0 && idx < len(arr) {
ret = arr[idx]
}
return ret
}).(ResourceOutput)
}
func ToResourceArray(in []Resource) ResourceArray {
return NewResourceArray(in...)
}
func NewResourceArray(in ...Resource) ResourceArray {
a := make(ResourceArray, len(in))
for i, v := range in {
a[i] = NewResourceInput(v)
}
return a
}
func ToResourceArrayOutput(in []ResourceOutput) ResourceArrayOutput {
return NewResourceArrayOutput(in...)
}
func NewResourceArrayOutput(in ...ResourceOutput) ResourceArrayOutput {
a := make(ResourceArray, len(in))
for i, v := range in {
a[i] = v
}
return a.ToResourceArrayOutput()
}
func init() {
RegisterInputType(reflect.TypeOf((*ResourceArrayInput)(nil)).Elem(), ResourceArray{})
RegisterOutputType(ResourceOutput{})
RegisterOutputType(ResourceArrayOutput{})
}