Implement unknown outputs

This commit implements unknown outputs in the same style as our Node.js
language provider.  That is to say, during previews, it's possible that
certain outputs will not have known values.  In those cases, we want to
flow sufficient information through the resolution of values, so that we
may skip applies.  We also return this fact from the direct accessors.
This commit is contained in:
joeduffy 2018-06-09 16:59:53 -07:00
parent 5a71ab9d12
commit 20af051caf
5 changed files with 264 additions and 184 deletions

View file

@ -162,14 +162,14 @@ func (ctx *Context) RegisterResource(
urn, resolveURN, rejectURN := NewOutput(nil) urn, resolveURN, rejectURN := NewOutput(nil)
var id *Output var id *Output
var resolveID func(interface{}) var resolveID func(interface{}, bool)
var rejectID func(error) var rejectID func(error)
if custom { if custom {
id, resolveID, rejectID = NewOutput(nil) id, resolveID, rejectID = NewOutput(nil)
} }
state := make(map[string]*Output) state := make(map[string]*Output)
resolveState := make(map[string]func(interface{})) resolveState := make(map[string]func(interface{}, bool))
rejectState := make(map[string]func(error)) rejectState := make(map[string]func(error))
for _, key := range keys { for _, key := range keys {
state[key], resolveState[key], rejectState[key] = NewOutput(nil) state[key], resolveState[key], rejectState[key] = NewOutput(nil)
@ -208,16 +208,18 @@ func (ctx *Context) RegisterResource(
} }
} else { } else {
glog.V(9).Infof("RegisterResource(%s, %s): success: %s %s %d", t, name, resp.Urn, resp.Id, len(outprops)) glog.V(9).Infof("RegisterResource(%s, %s): success: %s %s %d", t, name, resp.Urn, resp.Id, len(outprops))
resolveURN(URN(resp.Urn)) resolveURN(URN(resp.Urn), true)
if resolveID != nil { if resolveID != nil {
resolveID(ID(resp.Id)) resolveID(ID(resp.Id), true)
} }
for _, key := range keys { for _, key := range keys {
out, err := unmarshalOutput(outprops[key]) out, err := unmarshalOutput(outprops[key])
if err != nil { if err != nil {
rejectState[key](err) rejectState[key](err)
} else { } else {
resolveState[key](out) // During previews, it's possible that nils will be returned due to unknown values.
known := !ctx.DryRun() || out != nil
resolveState[key](out, known)
} }
} }
} }

View file

@ -34,12 +34,13 @@ type Output struct {
type valueOrError struct { type valueOrError struct {
value interface{} // a value, if the output resolved to a value. value interface{} // a value, if the output resolved to a value.
err error // an error, if the producer yielded an error instead of a value. err error // an error, if the producer yielded an error instead of a value.
known bool // true if this value is known, versus just being a placeholder during previews.
} }
// NewOutput returns an output value that can be used to rendezvous with the production of a value or error. The // 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 // 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. // error; exactly one function must be called. This acts like a promise.
func NewOutput(deps []Resource) (*Output, func(interface{}), func(error)) { func NewOutput(deps []Resource) (*Output, func(interface{}, bool), func(error)) {
out := &Output{ out := &Output{
sync: make(chan *valueOrError, 1), sync: make(chan *valueOrError, 1),
deps: deps, deps: deps,
@ -49,19 +50,19 @@ func NewOutput(deps []Resource) (*Output, func(interface{}), func(error)) {
// resolve will resolve the output. It is not exported, because we want to control the capabilities tightly, such // resolve will resolve the output. It is not exported, because we want to control the capabilities tightly, such
// that anybody who happens to have an Output is not allowed to resolve it; only those who created it can. // that anybody who happens to have an Output is not allowed to resolve it; only those who created it can.
func (out *Output) resolve(v interface{}) { func (out *Output) resolve(v interface{}, known bool) {
// If v is another output, chain this rather than resolving to an output directly. // If v is another output, chain this rather than resolving to an output directly.
if other, isOut := v.(*Output); isOut { if other, isOut := v.(*Output); known && isOut {
go func() { go func() {
real, err := other.Value() real, otherKnown, err := other.Value()
if err != nil { if err != nil {
out.reject(err) out.reject(err)
} else { } else {
out.resolve(real) out.resolve(real, otherKnown)
} }
}() }()
} else { } else {
out.sync <- &valueOrError{value: v} out.sync <- &valueOrError{value: v, known: known}
} }
} }
@ -78,27 +79,33 @@ func (out *Output) Apply(applier func(v interface{}) (interface{}, error)) *Outp
result, resolve, reject := NewOutput(out.Deps()) result, resolve, reject := NewOutput(out.Deps())
go func() { go func() {
for { for {
v, err := out.Value() v, known, err := out.Value()
if err != nil { if err != nil {
reject(err) reject(err)
break break
} else { } else {
// If we have a value, run the applier to transform it. if known {
u, err := applier(v) // If we have a known value, run the applier to transform it.
if err != nil { u, err := applier(v)
reject(err) if err != nil {
break reject(err)
} else {
// Now that we've transformed the value, it's possible we have another output. If so, pluck it
// out and go around to await it until we hit a real value. Note that we are not capturing the
// resources of this inner output, intentionally, as the output returned should be related to
// this output already.
if newout, ok := v.(*Output); ok {
out = newout
} else {
resolve(u)
break break
} else {
// Now that we've transformed the value, it's possible we have another output. If so, pluck it
// out and go around to await it until we hit a real value. Note that we are not capturing the
// resources of this inner output, intentionally, as the output returned should be related to
// this output already.
if newout, ok := v.(*Output); ok {
out = newout
} else {
resolve(u, true)
break
}
} }
} else {
// If the value isn't known, skip the apply function.
resolve(nil, false)
break
} }
} }
} }
@ -107,12 +114,10 @@ func (out *Output) Apply(applier func(v interface{}) (interface{}, error)) *Outp
} }
// Deps returns the dependencies for this output property. // Deps returns the dependencies for this output property.
func (out *Output) Deps() []Resource { func (out *Output) Deps() []Resource { return out.deps }
return out.deps
}
// Value retrieves the underlying value for this output property. // Value retrieves the underlying value for this output property.
func (out *Output) Value() (interface{}, error) { func (out *Output) Value() (interface{}, bool, error) {
// If neither error nor value are available, first await the channel. Only one Goroutine will make it through this // If neither error nor value are available, first await the channel. Only one Goroutine will make it through this
// and is responsible for closing the channel, to signal to other awaiters that it's safe to read the values. // and is responsible for closing the channel, to signal to other awaiters that it's safe to read the values.
if out.voe == nil { if out.voe == nil {
@ -121,184 +126,184 @@ func (out *Output) Value() (interface{}, error) {
close(out.sync) close(out.sync)
} }
} }
return out.voe.value, out.voe.err return out.voe.value, out.voe.known, out.voe.err
} }
// Archive retrives the underlying value for this output property as an archive. // Archive retrives the underlying value for this output property as an archive.
func (out *Output) Archive() (asset.Archive, error) { func (out *Output) Archive() (asset.Archive, bool, error) {
v, err := out.Value() v, known, err := out.Value()
if err != nil { if err != nil || !known {
return nil, err return nil, known, err
} }
return v.(asset.Archive), nil return v.(asset.Archive), true, nil
} }
// Array retrives the underlying value for this output property as an array. // Array retrives the underlying value for this output property as an array.
func (out *Output) Array() ([]interface{}, error) { func (out *Output) Array() ([]interface{}, bool, error) {
v, err := out.Value() v, known, err := out.Value()
if err != nil { if err != nil || !known {
return nil, err return nil, known, err
} }
return cast.ToSlice(v), nil return cast.ToSlice(v), true, nil
} }
// Asset retrives the underlying value for this output property as an asset. // Asset retrives the underlying value for this output property as an asset.
func (out *Output) Asset() (asset.Asset, error) { func (out *Output) Asset() (asset.Asset, bool, error) {
v, err := out.Value() v, known, err := out.Value()
if err != nil { if err != nil || !known {
return nil, err return nil, known, err
} }
return v.(asset.Asset), nil return v.(asset.Asset), true, nil
} }
// Bool retrives the underlying value for this output property as a bool. // Bool retrives the underlying value for this output property as a bool.
func (out *Output) Bool() (bool, error) { func (out *Output) Bool() (bool, bool, error) {
v, err := out.Value() v, known, err := out.Value()
if err != nil { if err != nil || !known {
return false, err return false, known, err
} }
return cast.ToBool(v), nil return cast.ToBool(v), true, nil
} }
// Map retrives the underlying value for this output property as a map. // Map retrives the underlying value for this output property as a map.
func (out *Output) Map() (map[string]interface{}, error) { func (out *Output) Map() (map[string]interface{}, bool, error) {
v, err := out.Value() v, known, err := out.Value()
if err != nil { if err != nil || !known {
return nil, err return nil, known, err
} }
return cast.ToStringMap(v), nil return cast.ToStringMap(v), true, nil
} }
// Float32 retrives the underlying value for this output property as a float32. // Float32 retrives the underlying value for this output property as a float32.
func (out *Output) Float32() (float32, error) { func (out *Output) Float32() (float32, bool, error) {
v, err := out.Value() v, known, err := out.Value()
if err != nil { if err != nil || !known {
return 0, err return 0, known, err
} }
return cast.ToFloat32(v), nil return cast.ToFloat32(v), true, nil
} }
// Float64 retrives the underlying value for this output property as a float64. // Float64 retrives the underlying value for this output property as a float64.
func (out *Output) Float64() (float64, error) { func (out *Output) Float64() (float64, bool, error) {
v, err := out.Value() v, known, err := out.Value()
if err != nil { if err != nil || !known {
return 0, err return 0, known, err
} }
return cast.ToFloat64(v), nil return cast.ToFloat64(v), true, nil
} }
// ID retrives the underlying value for this output property as an ID. // ID retrives the underlying value for this output property as an ID.
func (out *Output) ID() (ID, error) { func (out *Output) ID() (ID, bool, error) {
v, err := out.Value() v, known, err := out.Value()
if err != nil { if err != nil || !known {
return "", err return "", known, err
} }
return ID(cast.ToString(v)), nil return ID(cast.ToString(v)), true, nil
} }
// Int retrives the underlying value for this output property as a int. // Int retrives the underlying value for this output property as a int.
func (out *Output) Int() (int, error) { func (out *Output) Int() (int, bool, error) {
v, err := out.Value() v, known, err := out.Value()
if err != nil { if err != nil || !known {
return 0, err return 0, known, err
} }
return cast.ToInt(v), nil return cast.ToInt(v), true, nil
} }
// Int8 retrives the underlying value for this output property as a int8. // Int8 retrives the underlying value for this output property as a int8.
func (out *Output) Int8() (int8, error) { func (out *Output) Int8() (int8, bool, error) {
v, err := out.Value() v, known, err := out.Value()
if err != nil { if err != nil || !known {
return 0, err return 0, known, err
} }
return cast.ToInt8(v), nil return cast.ToInt8(v), true, nil
} }
// Int16 retrives the underlying value for this output property as a int16. // Int16 retrives the underlying value for this output property as a int16.
func (out *Output) Int16() (int16, error) { func (out *Output) Int16() (int16, bool, error) {
v, err := out.Value() v, known, err := out.Value()
if err != nil { if err != nil || !known {
return 0, err return 0, known, err
} }
return cast.ToInt16(v), nil return cast.ToInt16(v), true, nil
} }
// Int32 retrives the underlying value for this output property as a int32. // Int32 retrives the underlying value for this output property as a int32.
func (out *Output) Int32() (int32, error) { func (out *Output) Int32() (int32, bool, error) {
v, err := out.Value() v, known, err := out.Value()
if err != nil { if err != nil || !known {
return 0, err return 0, known, err
} }
return cast.ToInt32(v), nil return cast.ToInt32(v), true, nil
} }
// Int64 retrives the underlying value for this output property as a int64. // Int64 retrives the underlying value for this output property as a int64.
func (out *Output) Int64() (int64, error) { func (out *Output) Int64() (int64, bool, error) {
v, err := out.Value() v, known, err := out.Value()
if err != nil { if err != nil || !known {
return 0, err return 0, known, err
} }
return cast.ToInt64(v), nil return cast.ToInt64(v), true, nil
} }
// String retrives the underlying value for this output property as a string. // String retrives the underlying value for this output property as a string.
func (out *Output) String() (string, error) { func (out *Output) String() (string, bool, error) {
v, err := out.Value() v, known, err := out.Value()
if err != nil { if err != nil || !known {
return "", err return "", known, err
} }
return cast.ToString(v), nil return cast.ToString(v), true, nil
} }
// Uint retrives the underlying value for this output property as a uint. // Uint retrives the underlying value for this output property as a uint.
func (out *Output) Uint() (uint, error) { func (out *Output) Uint() (uint, bool, error) {
v, err := out.Value() v, known, err := out.Value()
if err != nil { if err != nil || !known {
return 0, err return 0, known, err
} }
return cast.ToUint(v), nil return cast.ToUint(v), true, nil
} }
// Uint8 retrives the underlying value for this output property as a uint8. // Uint8 retrives the underlying value for this output property as a uint8.
func (out *Output) Uint8() (uint8, error) { func (out *Output) Uint8() (uint8, bool, error) {
v, err := out.Value() v, known, err := out.Value()
if err != nil { if err != nil || !known {
return 0, err return 0, known, err
} }
return cast.ToUint8(v), nil return cast.ToUint8(v), true, nil
} }
// Uint16 retrives the underlying value for this output property as a uint16. // Uint16 retrives the underlying value for this output property as a uint16.
func (out *Output) Uint16() (uint16, error) { func (out *Output) Uint16() (uint16, bool, error) {
v, err := out.Value() v, known, err := out.Value()
if err != nil { if err != nil || !known {
return 0, err return 0, known, err
} }
return cast.ToUint16(v), nil return cast.ToUint16(v), true, nil
} }
// Uint32 retrives the underlying value for this output property as a uint32. // Uint32 retrives the underlying value for this output property as a uint32.
func (out *Output) Uint32() (uint32, error) { func (out *Output) Uint32() (uint32, bool, error) {
v, err := out.Value() v, known, err := out.Value()
if err != nil { if err != nil || !known {
return 0, err return 0, known, err
} }
return cast.ToUint32(v), nil return cast.ToUint32(v), true, nil
} }
// Uint64 retrives the underlying value for this output property as a uint64. // Uint64 retrives the underlying value for this output property as a uint64.
func (out *Output) Uint64() (uint64, error) { func (out *Output) Uint64() (uint64, bool, error) {
v, err := out.Value() v, known, err := out.Value()
if err != nil { if err != nil || !known {
return 0, err return 0, known, err
} }
return cast.ToUint64(v), nil return cast.ToUint64(v), true, nil
} }
// URN retrives the underlying value for this output property as a URN. // URN retrives the underlying value for this output property as a URN.
func (out *Output) URN() (URN, error) { func (out *Output) URN() (URN, error) {
v, err := out.Value() v, known, err := out.Value()
if err != nil { if err != nil || !known {
return "", err return "", err
} }
return URN(cast.ToString(v)), nil return URN(cast.ToString(v)), nil
@ -311,7 +316,9 @@ type Outputs map[string]*Output
type ArchiveOutput Output type ArchiveOutput Output
// Value returns the underlying archive value. // Value returns the underlying archive value.
func (out *ArchiveOutput) Value() (asset.Archive, error) { return (*Output)(out).Archive() } func (out *ArchiveOutput) Value() (asset.Archive, bool, error) {
return (*Output)(out).Archive()
}
// Apply applies a transformation to the archive value when it is available. // Apply applies a transformation to the archive value when it is available.
func (out *ArchiveOutput) Apply(applier func(asset.Archive) (interface{}, error)) *Output { func (out *ArchiveOutput) Apply(applier func(asset.Archive) (interface{}, error)) *Output {
@ -324,7 +331,9 @@ func (out *ArchiveOutput) Apply(applier func(asset.Archive) (interface{}, error)
type ArrayOutput Output type ArrayOutput Output
// Value returns the underlying array value. // Value returns the underlying array value.
func (out *ArrayOutput) Value() ([]interface{}, error) { return (*Output)(out).Array() } func (out *ArrayOutput) Value() ([]interface{}, bool, error) {
return (*Output)(out).Array()
}
// Apply applies a transformation to the array value when it is available. // Apply applies a transformation to the array value when it is available.
func (out *ArrayOutput) Apply(applier func([]interface{}) (interface{}, error)) *Output { func (out *ArrayOutput) Apply(applier func([]interface{}) (interface{}, error)) *Output {
@ -337,7 +346,9 @@ func (out *ArrayOutput) Apply(applier func([]interface{}) (interface{}, error))
type AssetOutput Output type AssetOutput Output
// Value returns the underlying asset value. // Value returns the underlying asset value.
func (out *AssetOutput) Value() (asset.Asset, error) { return (*Output)(out).Asset() } func (out *AssetOutput) Value() (asset.Asset, bool, error) {
return (*Output)(out).Asset()
}
// Apply applies a transformation to the asset value when it is available. // Apply applies a transformation to the asset value when it is available.
func (out *AssetOutput) Apply(applier func(asset.Asset) (interface{}, error)) *Output { func (out *AssetOutput) Apply(applier func(asset.Asset) (interface{}, error)) *Output {
@ -350,7 +361,9 @@ func (out *AssetOutput) Apply(applier func(asset.Asset) (interface{}, error)) *O
type BoolOutput Output type BoolOutput Output
// Value returns the underlying bool value. // Value returns the underlying bool value.
func (out *BoolOutput) Value() (bool, error) { return (*Output)(out).Bool() } func (out *BoolOutput) Value() (bool, bool, error) {
return (*Output)(out).Bool()
}
// Apply applies a transformation to the bool value when it is available. // Apply applies a transformation to the bool value when it is available.
func (out *BoolOutput) Apply(applier func(bool) (interface{}, error)) *Output { func (out *BoolOutput) Apply(applier func(bool) (interface{}, error)) *Output {
@ -363,7 +376,9 @@ func (out *BoolOutput) Apply(applier func(bool) (interface{}, error)) *Output {
type Float32Output Output type Float32Output Output
// Value returns the underlying number value. // Value returns the underlying number value.
func (out *Float32Output) Value() (float32, error) { return (*Output)(out).Float32() } func (out *Float32Output) Value() (float32, bool, error) {
return (*Output)(out).Float32()
}
// Apply applies a transformation to the float32 value when it is available. // Apply applies a transformation to the float32 value when it is available.
func (out *Float32Output) Apply(applier func(float32) (interface{}, error)) *Output { func (out *Float32Output) Apply(applier func(float32) (interface{}, error)) *Output {
@ -376,7 +391,9 @@ func (out *Float32Output) Apply(applier func(float32) (interface{}, error)) *Out
type Float64Output Output type Float64Output Output
// Value returns the underlying number value. // Value returns the underlying number value.
func (out *Float64Output) Value() (float64, error) { return (*Output)(out).Float64() } func (out *Float64Output) Value() (float64, bool, error) {
return (*Output)(out).Float64()
}
// Apply applies a transformation to the float64 value when it is available. // Apply applies a transformation to the float64 value when it is available.
func (out *Float64Output) Apply(applier func(float64) (interface{}, error)) *Output { func (out *Float64Output) Apply(applier func(float64) (interface{}, error)) *Output {
@ -389,7 +406,9 @@ func (out *Float64Output) Apply(applier func(float64) (interface{}, error)) *Out
type IntOutput Output type IntOutput Output
// Value returns the underlying number value. // Value returns the underlying number value.
func (out *IntOutput) Value() (int, error) { return (*Output)(out).Int() } func (out *IntOutput) Value() (int, bool, error) {
return (*Output)(out).Int()
}
// Apply applies a transformation to the int value when it is available. // Apply applies a transformation to the int value when it is available.
func (out *IntOutput) Apply(applier func(int) (interface{}, error)) *Output { func (out *IntOutput) Apply(applier func(int) (interface{}, error)) *Output {
@ -402,7 +421,9 @@ func (out *IntOutput) Apply(applier func(int) (interface{}, error)) *Output {
type Int8Output Output type Int8Output Output
// Value returns the underlying number value. // Value returns the underlying number value.
func (out *Int8Output) Value() (int8, error) { return (*Output)(out).Int8() } func (out *Int8Output) Value() (int8, bool, error) {
return (*Output)(out).Int8()
}
// Apply applies a transformation to the int8 value when it is available. // Apply applies a transformation to the int8 value when it is available.
func (out *Int8Output) Apply(applier func(int8) (interface{}, error)) *Output { func (out *Int8Output) Apply(applier func(int8) (interface{}, error)) *Output {
@ -415,7 +436,9 @@ func (out *Int8Output) Apply(applier func(int8) (interface{}, error)) *Output {
type Int16Output Output type Int16Output Output
// Value returns the underlying number value. // Value returns the underlying number value.
func (out *Int16Output) Value() (int16, error) { return (*Output)(out).Int16() } func (out *Int16Output) Value() (int16, bool, error) {
return (*Output)(out).Int16()
}
// Apply applies a transformation to the int16 value when it is available. // Apply applies a transformation to the int16 value when it is available.
func (out *Int16Output) Apply(applier func(int16) (interface{}, error)) *Output { func (out *Int16Output) Apply(applier func(int16) (interface{}, error)) *Output {
@ -428,7 +451,9 @@ func (out *Int16Output) Apply(applier func(int16) (interface{}, error)) *Output
type Int32Output Output type Int32Output Output
// Value returns the underlying number value. // Value returns the underlying number value.
func (out *Int32Output) Value() (int32, error) { return (*Output)(out).Int32() } func (out *Int32Output) Value() (int32, bool, error) {
return (*Output)(out).Int32()
}
// Apply applies a transformation to the int32 value when it is available. // Apply applies a transformation to the int32 value when it is available.
func (out *Int32Output) Apply(applier func(int32) (interface{}, error)) *Output { func (out *Int32Output) Apply(applier func(int32) (interface{}, error)) *Output {
@ -441,7 +466,7 @@ func (out *Int32Output) Apply(applier func(int32) (interface{}, error)) *Output
type Int64Output Output type Int64Output Output
// Value returns the underlying number value. // Value returns the underlying number value.
func (out *Int64Output) Value() (int64, error) { return (*Output)(out).Int64() } func (out *Int64Output) Value() (int64, bool, error) { return (*Output)(out).Int64() }
// Apply applies a transformation to the int64 value when it is available. // Apply applies a transformation to the int64 value when it is available.
func (out *Int64Output) Apply(applier func(int64) (interface{}, error)) *Output { func (out *Int64Output) Apply(applier func(int64) (interface{}, error)) *Output {
@ -454,7 +479,9 @@ func (out *Int64Output) Apply(applier func(int64) (interface{}, error)) *Output
type MapOutput Output type MapOutput Output
// Value returns the underlying number value. // Value returns the underlying number value.
func (out *MapOutput) Value() (map[string]interface{}, error) { return (*Output)(out).Map() } func (out *MapOutput) Value() (map[string]interface{}, bool, error) {
return (*Output)(out).Map()
}
// Apply applies a transformation to the number value when it is available. // Apply applies a transformation to the number value when it is available.
func (out *MapOutput) Apply(applier func(map[string]interface{}) (interface{}, error)) *Output { func (out *MapOutput) Apply(applier func(map[string]interface{}) (interface{}, error)) *Output {
@ -467,7 +494,9 @@ func (out *MapOutput) Apply(applier func(map[string]interface{}) (interface{}, e
type StringOutput Output type StringOutput Output
// Value returns the underlying number value. // Value returns the underlying number value.
func (out *StringOutput) Value() (string, error) { return (*Output)(out).String() } func (out *StringOutput) Value() (string, bool, error) {
return (*Output)(out).String()
}
// Apply applies a transformation to the number value when it is available. // Apply applies a transformation to the number value when it is available.
func (out *StringOutput) Apply(applier func(string) (interface{}, error)) *Output { func (out *StringOutput) Apply(applier func(string) (interface{}, error)) *Output {
@ -480,7 +509,9 @@ func (out *StringOutput) Apply(applier func(string) (interface{}, error)) *Outpu
type UintOutput Output type UintOutput Output
// Value returns the underlying number value. // Value returns the underlying number value.
func (out *UintOutput) Value() (uint, error) { return (*Output)(out).Uint() } func (out *UintOutput) Value() (uint, bool, error) {
return (*Output)(out).Uint()
}
// Apply applies a transformation to the uint value when it is available. // Apply applies a transformation to the uint value when it is available.
func (out *UintOutput) Apply(applier func(uint) (interface{}, error)) *Output { func (out *UintOutput) Apply(applier func(uint) (interface{}, error)) *Output {
@ -493,7 +524,9 @@ func (out *UintOutput) Apply(applier func(uint) (interface{}, error)) *Output {
type Uint8Output Output type Uint8Output Output
// Value returns the underlying number value. // Value returns the underlying number value.
func (out *Uint8Output) Value() (uint8, error) { return (*Output)(out).Uint8() } func (out *Uint8Output) Value() (uint8, bool, error) {
return (*Output)(out).Uint8()
}
// Apply applies a transformation to the uint8 value when it is available. // Apply applies a transformation to the uint8 value when it is available.
func (out *Uint8Output) Apply(applier func(uint8) (interface{}, error)) *Output { func (out *Uint8Output) Apply(applier func(uint8) (interface{}, error)) *Output {
@ -506,7 +539,9 @@ func (out *Uint8Output) Apply(applier func(uint8) (interface{}, error)) *Output
type Uint16Output Output type Uint16Output Output
// Value returns the underlying number value. // Value returns the underlying number value.
func (out *Uint16Output) Value() (uint16, error) { return (*Output)(out).Uint16() } func (out *Uint16Output) Value() (uint16, bool, error) {
return (*Output)(out).Uint16()
}
// Apply applies a transformation to the uint16 value when it is available. // Apply applies a transformation to the uint16 value when it is available.
func (out *Uint16Output) Apply(applier func(uint16) (interface{}, error)) *Output { func (out *Uint16Output) Apply(applier func(uint16) (interface{}, error)) *Output {
@ -519,7 +554,9 @@ func (out *Uint16Output) Apply(applier func(uint16) (interface{}, error)) *Outpu
type Uint32Output Output type Uint32Output Output
// Value returns the underlying number value. // Value returns the underlying number value.
func (out *Uint32Output) Value() (uint32, error) { return (*Output)(out).Uint32() } func (out *Uint32Output) Value() (uint32, bool, error) {
return (*Output)(out).Uint32()
}
// Apply applies a transformation to the uint32 value when it is available. // Apply applies a transformation to the uint32 value when it is available.
func (out *Uint32Output) Apply(applier func(uint32) (interface{}, error)) *Output { func (out *Uint32Output) Apply(applier func(uint32) (interface{}, error)) *Output {
@ -532,7 +569,9 @@ func (out *Uint32Output) Apply(applier func(uint32) (interface{}, error)) *Outpu
type Uint64Output Output type Uint64Output Output
// Value returns the underlying number value. // Value returns the underlying number value.
func (out *Uint64Output) Value() (uint64, error) { return (*Output)(out).Uint64() } func (out *Uint64Output) Value() (uint64, bool, error) {
return (*Output)(out).Uint64()
}
// Apply applies a transformation to the uint64 value when it is available. // Apply applies a transformation to the uint64 value when it is available.
func (out *Uint64Output) Apply(applier func(uint64) (interface{}, error)) *Output { func (out *Uint64Output) Apply(applier func(uint64) (interface{}, error)) *Output {

View file

@ -26,10 +26,11 @@ func TestBasicOutputs(t *testing.T) {
{ {
out, resolve, _ := NewOutput(nil) out, resolve, _ := NewOutput(nil)
go func() { go func() {
resolve(42) resolve(42, true)
}() }()
v, err := out.Value() v, known, err := out.Value()
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, known)
assert.NotNil(t, v) assert.NotNil(t, v)
assert.Equal(t, 42, v.(int)) assert.Equal(t, 42, v.(int))
} }
@ -38,7 +39,7 @@ func TestBasicOutputs(t *testing.T) {
go func() { go func() {
reject(errors.New("boom")) reject(errors.New("boom"))
}() }()
v, err := out.Value() v, _, err := out.Value()
assert.NotNil(t, err) assert.NotNil(t, err)
assert.Nil(t, v) assert.Nil(t, v)
} }
@ -47,11 +48,12 @@ func TestBasicOutputs(t *testing.T) {
func TestArrayOutputs(t *testing.T) { func TestArrayOutputs(t *testing.T) {
out, resolve, _ := NewOutput(nil) out, resolve, _ := NewOutput(nil)
go func() { go func() {
resolve([]interface{}{nil, 0, "x"}) resolve([]interface{}{nil, 0, "x"}, true)
}() }()
{ {
v, err := out.Array() v, known, err := out.Array()
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, known)
assert.NotNil(t, v) assert.NotNil(t, v)
if assert.Equal(t, 3, len(v)) { if assert.Equal(t, 3, len(v)) {
assert.Equal(t, nil, v[0]) assert.Equal(t, nil, v[0])
@ -61,7 +63,7 @@ func TestArrayOutputs(t *testing.T) {
} }
{ {
arr := (*ArrayOutput)(out) arr := (*ArrayOutput)(out)
v, err := arr.Value() v, _, err := arr.Value()
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, v) assert.NotNil(t, v)
if assert.Equal(t, 3, len(v)) { if assert.Equal(t, 3, len(v)) {
@ -75,17 +77,19 @@ func TestArrayOutputs(t *testing.T) {
func TestBoolOutputs(t *testing.T) { func TestBoolOutputs(t *testing.T) {
out, resolve, _ := NewOutput(nil) out, resolve, _ := NewOutput(nil)
go func() { go func() {
resolve(true) resolve(true, true)
}() }()
{ {
v, err := out.Bool() v, known, err := out.Bool()
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, known)
assert.True(t, v) assert.True(t, v)
} }
{ {
b := (*BoolOutput)(out) b := (*BoolOutput)(out)
v, err := b.Value() v, known, err := b.Value()
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, known)
assert.True(t, v) assert.True(t, v)
} }
} }
@ -97,11 +101,12 @@ func TestMapOutputs(t *testing.T) {
"x": 1, "x": 1,
"y": false, "y": false,
"z": "abc", "z": "abc",
}) }, true)
}() }()
{ {
v, err := out.Map() v, known, err := out.Map()
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, known)
assert.NotNil(t, v) assert.NotNil(t, v)
assert.Equal(t, 1, v["x"]) assert.Equal(t, 1, v["x"])
assert.Equal(t, false, v["y"]) assert.Equal(t, false, v["y"])
@ -109,8 +114,9 @@ func TestMapOutputs(t *testing.T) {
} }
{ {
b := (*MapOutput)(out) b := (*MapOutput)(out)
v, err := b.Value() v, known, err := b.Value()
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, known)
assert.NotNil(t, v) assert.NotNil(t, v)
assert.Equal(t, 1, v["x"]) assert.Equal(t, 1, v["x"])
assert.Equal(t, false, v["y"]) assert.Equal(t, false, v["y"])
@ -121,17 +127,19 @@ func TestMapOutputs(t *testing.T) {
func TestNumberOutputs(t *testing.T) { func TestNumberOutputs(t *testing.T) {
out, resolve, _ := NewOutput(nil) out, resolve, _ := NewOutput(nil)
go func() { go func() {
resolve(42.345) resolve(42.345, true)
}() }()
{ {
v, err := out.Float64() v, known, err := out.Float64()
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, known)
assert.Equal(t, 42.345, v) assert.Equal(t, 42.345, v)
} }
{ {
b := (*Float64Output)(out) b := (*Float64Output)(out)
v, err := b.Value() v, known, err := b.Value()
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, known)
assert.Equal(t, 42.345, v) assert.Equal(t, 42.345, v)
} }
} }
@ -139,17 +147,19 @@ func TestNumberOutputs(t *testing.T) {
func TestStringOutputs(t *testing.T) { func TestStringOutputs(t *testing.T) {
out, resolve, _ := NewOutput(nil) out, resolve, _ := NewOutput(nil)
go func() { go func() {
resolve("a stringy output") resolve("a stringy output", true)
}() }()
{ {
v, err := out.String() v, known, err := out.String()
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, known)
assert.Equal(t, "a stringy output", v) assert.Equal(t, "a stringy output", v)
} }
{ {
b := (*StringOutput)(out) b := (*StringOutput)(out)
v, err := b.Value() v, known, err := b.Value()
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, known)
assert.Equal(t, "a stringy output", v) assert.Equal(t, "a stringy output", v)
} }
} }
@ -160,11 +170,12 @@ func TestResolveOutputToOutput(t *testing.T) {
out, resolve, _ := NewOutput(nil) out, resolve, _ := NewOutput(nil)
go func() { go func() {
other, resolveOther, _ := NewOutput(nil) other, resolveOther, _ := NewOutput(nil)
resolve(other) resolve(other, true)
go func() { resolveOther(99) }() go func() { resolveOther(99, true) }()
}() }()
v, err := out.Value() v, known, err := out.Value()
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, known)
assert.Equal(t, v, 99) assert.Equal(t, v, 99)
} }
// Similarly, test that resolving an output to a rejected output yields an error. // Similarly, test that resolving an output to a rejected output yields an error.
@ -172,10 +183,10 @@ func TestResolveOutputToOutput(t *testing.T) {
out, resolve, _ := NewOutput(nil) out, resolve, _ := NewOutput(nil)
go func() { go func() {
other, _, rejectOther := NewOutput(nil) other, _, rejectOther := NewOutput(nil)
resolve(other) resolve(other, true)
go func() { rejectOther(errors.New("boom")) }() go func() { rejectOther(errors.New("boom")) }()
}() }()
v, err := out.Value() v, _, err := out.Value()
assert.NotNil(t, err) assert.NotNil(t, err)
assert.Nil(t, v) assert.Nil(t, v)
} }
@ -185,18 +196,34 @@ func TestOutputApply(t *testing.T) {
// Test that resolved outputs lead to applies being run. // Test that resolved outputs lead to applies being run.
{ {
out, resolve, _ := NewOutput(nil) out, resolve, _ := NewOutput(nil)
go func() { resolve(42) }() go func() { resolve(42, true) }()
var ranApp bool var ranApp bool
b := (*IntOutput)(out) b := (*IntOutput)(out)
app := b.Apply(func(v int) (interface{}, error) { app := b.Apply(func(v int) (interface{}, error) {
ranApp = true ranApp = true
return v + 1, nil return v + 1, nil
}) })
v, err := app.Value() v, known, err := app.Value()
assert.True(t, ranApp) assert.True(t, ranApp)
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, known)
assert.Equal(t, v, 43) assert.Equal(t, v, 43)
} }
// Test that resolved, but known outputs, skip the running of applies.
{
out, resolve, _ := NewOutput(nil)
go func() { resolve(42, false) }()
var ranApp bool
b := (*IntOutput)(out)
app := b.Apply(func(v int) (interface{}, error) {
ranApp = true
return v + 1, nil
})
_, known, err := app.Value()
assert.False(t, ranApp)
assert.Nil(t, err)
assert.False(t, known)
}
// Test that rejected outputs do not run the apply, and instead flow the error. // Test that rejected outputs do not run the apply, and instead flow the error.
{ {
out, _, reject := NewOutput(nil) out, _, reject := NewOutput(nil)
@ -207,7 +234,7 @@ func TestOutputApply(t *testing.T) {
ranApp = true ranApp = true
return v + 1, nil return v + 1, nil
}) })
v, err := app.Value() v, _, err := app.Value()
assert.False(t, ranApp) assert.False(t, ranApp)
assert.NotNil(t, err) assert.NotNil(t, err)
assert.Nil(t, v) assert.Nil(t, v)
@ -215,24 +242,25 @@ func TestOutputApply(t *testing.T) {
// Test that an an apply that returns an output returns the resolution of that output, not the output itself. // Test that an an apply that returns an output returns the resolution of that output, not the output itself.
{ {
out, resolve, _ := NewOutput(nil) out, resolve, _ := NewOutput(nil)
go func() { resolve(42) }() go func() { resolve(42, true) }()
var ranApp bool var ranApp bool
b := (*IntOutput)(out) b := (*IntOutput)(out)
app := b.Apply(func(v int) (interface{}, error) { app := b.Apply(func(v int) (interface{}, error) {
other, resolveOther, _ := NewOutput(nil) other, resolveOther, _ := NewOutput(nil)
go func() { resolveOther(v + 1) }() go func() { resolveOther(v+1, true) }()
ranApp = true ranApp = true
return other, nil return other, nil
}) })
v, err := app.Value() v, known, err := app.Value()
assert.True(t, ranApp) assert.True(t, ranApp)
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, known)
assert.Equal(t, v, 43) assert.Equal(t, v, 43)
} }
// Test that an an apply that reject an output returns the rejection of that output, not the output itself. // Test that an an apply that reject an output returns the rejection of that output, not the output itself.
{ {
out, resolve, _ := NewOutput(nil) out, resolve, _ := NewOutput(nil)
go func() { resolve(42) }() go func() { resolve(42, true) }()
var ranApp bool var ranApp bool
b := (*IntOutput)(out) b := (*IntOutput)(out)
app := b.Apply(func(v int) (interface{}, error) { app := b.Apply(func(v int) (interface{}, error) {
@ -241,7 +269,7 @@ func TestOutputApply(t *testing.T) {
ranApp = true ranApp = true
return other, nil return other, nil
}) })
v, err := app.Value() v, _, err := app.Value()
assert.True(t, ranApp) assert.True(t, ranApp)
assert.NotNil(t, err) assert.NotNil(t, err)
assert.Nil(t, v) assert.Nil(t, v)

View file

@ -107,16 +107,22 @@ func marshalInput(v interface{}) (interface{}, []Resource, error) {
}, nil, nil }, nil, nil
case *Output: case *Output:
// Await the value and return its raw value. // Await the value and return its raw value.
ov, err := t.Value() ov, known, err := t.Value()
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
// TODO: unknownValue
e, d, err := marshalInput(ov) if known {
if err != nil { // If the value is known, marshal it.
return nil, nil, err e, d, merr := marshalInput(ov)
if merr != nil {
return nil, nil, merr
}
return e, append(t.Deps(), d...), nil
} else {
// Otherwise, simply return the unknown value sentinel.
return rpcTokenUnknownValue, t.Deps(), nil
} }
return e, append(t.Deps(), d...), err
case CustomResource: case CustomResource:
// Resources aren't serializable; instead, serialize a reference to ID, tracking as a dependency.a // Resources aren't serializable; instead, serialize a reference to ID, tracking as a dependency.a
e, d, err := marshalInput(t.ID()) e, d, err := marshalInput(t.ID())
@ -196,6 +202,11 @@ func unmarshalOutputs(outs *structpb.Struct) (map[string]interface{}, error) {
// unmarshalOutput unmarshals a single output variable into its runtime representation. For the most part, this just // unmarshalOutput unmarshals a single output variable into its runtime representation. For the most part, this just
// returns the raw value. In a small number of cases, we need to change a type. // returns the raw value. In a small number of cases, we need to change a type.
func unmarshalOutput(v interface{}) (interface{}, error) { func unmarshalOutput(v interface{}) (interface{}, error) {
// Check for nils and unknowns.
if v == nil || v == rpcTokenUnknownValue {
return nil, nil
}
// In the case of assets and archives, turn these into real asset and archive structures. // In the case of assets and archives, turn these into real asset and archive structures.
if m, ok := v.(map[string]interface{}); ok { if m, ok := v.(map[string]interface{}); ok {
if m[rpcTokenSpecialSigKey] == rpcTokenSpecialAssetSig { if m[rpcTokenSpecialSigKey] == rpcTokenSpecialAssetSig {

View file

@ -26,7 +26,7 @@ import (
func TestMarshalRoundtrip(t *testing.T) { func TestMarshalRoundtrip(t *testing.T) {
// Create interesting inputs. // Create interesting inputs.
out, resolve, _ := NewOutput(nil) out, resolve, _ := NewOutput(nil)
resolve("outputty") resolve("outputty", true)
input := map[string]interface{}{ input := map[string]interface{}{
"s": "a string", "s": "a string",
"a": true, "a": true,