Support remote components in Go (#5558)
This commit is contained in:
parent
5cef84f036
commit
855f14c053
|
@ -2,7 +2,9 @@ CHANGELOG
|
|||
=========
|
||||
|
||||
## HEAD (Unreleased)
|
||||
_(none)_
|
||||
|
||||
- Add internal scaffolding for using cross-language components from Go.
|
||||
[#5558](https://github.com/pulumi/pulumi/pull/5558)
|
||||
|
||||
## 2.12.1 (2020-10-23)
|
||||
|
||||
|
|
|
@ -343,7 +343,7 @@ func (ctx *Context) ReadResource(
|
|||
var state *structpb.Struct
|
||||
var err error
|
||||
defer func() {
|
||||
res.resolve(ctx.DryRun(), err, inputs, urn, resID, state)
|
||||
res.resolve(ctx.DryRun(), err, inputs, urn, resID, state, nil)
|
||||
ctx.endRPC(err)
|
||||
}()
|
||||
|
||||
|
@ -410,6 +410,12 @@ func (ctx *Context) ReadResource(
|
|||
//
|
||||
func (ctx *Context) RegisterResource(
|
||||
t, name string, props Input, resource Resource, opts ...ResourceOption) error {
|
||||
|
||||
return ctx.registerResource(t, name, props, resource, false /*remote*/, opts...)
|
||||
}
|
||||
|
||||
func (ctx *Context) registerResource(
|
||||
t, name string, props Input, resource Resource, remote bool, opts ...ResourceOption) error {
|
||||
if t == "" {
|
||||
return errors.New("resource type argument cannot be empty")
|
||||
} else if name == "" {
|
||||
|
@ -469,9 +475,10 @@ func (ctx *Context) RegisterResource(
|
|||
var urn, resID string
|
||||
var inputs *resourceInputs
|
||||
var state *structpb.Struct
|
||||
deps := make(map[string][]Resource)
|
||||
var err error
|
||||
defer func() {
|
||||
res.resolve(ctx.DryRun(), err, inputs, urn, resID, state)
|
||||
res.resolve(ctx.DryRun(), err, inputs, urn, resID, state, deps)
|
||||
ctx.endRPC(err)
|
||||
}()
|
||||
|
||||
|
@ -500,6 +507,7 @@ func (ctx *Context) RegisterResource(
|
|||
AcceptSecrets: true,
|
||||
AdditionalSecretOutputs: inputs.additionalSecretOutputs,
|
||||
Version: inputs.version,
|
||||
Remote: remote,
|
||||
})
|
||||
if err != nil {
|
||||
logging.V(9).Infof("RegisterResource(%s, %s): error: %v", t, name, err)
|
||||
|
@ -509,6 +517,13 @@ func (ctx *Context) RegisterResource(
|
|||
if resp != nil {
|
||||
urn, resID = resp.Urn, resp.Id
|
||||
state = resp.Object
|
||||
for key, propertyDependencies := range resp.GetPropertyDependencies() {
|
||||
var resources []Resource
|
||||
for _, urn := range propertyDependencies.GetUrns() {
|
||||
resources = append(resources, &ResourceState{urn: URNInput(URN(urn)).ToURNOutput()})
|
||||
}
|
||||
deps[key] = resources
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -518,7 +533,13 @@ func (ctx *Context) RegisterResource(
|
|||
func (ctx *Context) RegisterComponentResource(
|
||||
t, name string, resource ComponentResource, opts ...ResourceOption) error {
|
||||
|
||||
return ctx.RegisterResource(t, name, nil, resource, opts...)
|
||||
return ctx.RegisterResource(t, name, nil /*props*/, resource, opts...)
|
||||
}
|
||||
|
||||
func (ctx *Context) RegisterRemoteComponentResource(
|
||||
t, name string, props Input, resource ComponentResource, opts ...ResourceOption) error {
|
||||
|
||||
return ctx.registerResource(t, name, props, resource, true /*remote*/, opts...)
|
||||
}
|
||||
|
||||
// resourceState contains the results of a resource registration operation.
|
||||
|
@ -720,7 +741,7 @@ func makeResourceState(t, name string, resourceV Resource, providers map[string]
|
|||
|
||||
// resolve resolves the resource outputs using the given error and/or values.
|
||||
func (state *resourceState) resolve(dryrun bool, err error, inputs *resourceInputs, urn, id string,
|
||||
result *structpb.Struct) {
|
||||
result *structpb.Struct, deps map[string][]Resource) {
|
||||
|
||||
var inprops resource.PropertyMap
|
||||
if inputs != nil {
|
||||
|
@ -784,7 +805,7 @@ func (state *resourceState) resolve(dryrun bool, err error, inputs *resourceInpu
|
|||
if err != nil {
|
||||
output.reject(err)
|
||||
} else {
|
||||
output.resolve(dest.Interface(), known, secret)
|
||||
output.resolve(dest.Interface(), known, secret, deps[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,17 +15,19 @@ func testPrintf(t *testing.T, ins ...interface{}) {
|
|||
// Fprintf
|
||||
buf := &bytes.Buffer{}
|
||||
out := Output(Fprintf(buf, f, ins...))
|
||||
_, known, secret, err := await(out)
|
||||
_, known, secret, deps, err := await(out)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Nil(t, deps)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, buf.String())
|
||||
|
||||
// Sprintf
|
||||
out = Sprintf(f, ins...)
|
||||
v, known, secret, err := await(out)
|
||||
v, known, secret, deps, err := await(out)
|
||||
assert.False(t, secret)
|
||||
assert.True(t, known)
|
||||
assert.Nil(t, deps)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, v)
|
||||
}
|
||||
|
|
|
@ -211,7 +211,7 @@ func marshalInputAndDetermineSecret(v interface{},
|
|||
}
|
||||
|
||||
// Await the output.
|
||||
ov, known, outputSecret, err := output.await(context.TODO())
|
||||
ov, known, outputSecret, outputDeps, err := output.await(context.TODO())
|
||||
if err != nil {
|
||||
return resource.PropertyValue{}, nil, false, err
|
||||
}
|
||||
|
@ -219,10 +219,10 @@ func marshalInputAndDetermineSecret(v interface{},
|
|||
|
||||
// If the value is unknown, return the appropriate sentinel.
|
||||
if !known {
|
||||
return resource.MakeComputed(resource.NewStringProperty("")), output.dependencies(), secret, nil
|
||||
return resource.MakeComputed(resource.NewStringProperty("")), outputDeps, secret, nil
|
||||
}
|
||||
|
||||
v, deps = ov, output.dependencies()
|
||||
v, deps = ov, outputDeps
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ func TestMarshalRoundtrip(t *testing.T) {
|
|||
out, resolve, _ := NewOutput()
|
||||
resolve("outputty")
|
||||
out2 := newOutputState(reflect.TypeOf(""))
|
||||
out2.fulfill(nil, false, false, nil)
|
||||
out2.fulfill(nil, false, false, nil, nil)
|
||||
inputs := testInputs{
|
||||
S: String("a string"),
|
||||
A: Bool(true),
|
||||
|
@ -296,7 +296,7 @@ func TestResourceState(t *testing.T) {
|
|||
resolved,
|
||||
plugin.MarshalOptions{KeepUnknowns: true})
|
||||
assert.NoError(t, err)
|
||||
state.resolve(false, nil, nil, "foo", "bar", s)
|
||||
state.resolve(false, nil, nil, "foo", "bar", s, nil)
|
||||
|
||||
input := &testResourceInputs{
|
||||
URN: theResource.URN(),
|
||||
|
@ -419,7 +419,7 @@ func TestMarshalRoundtripNestedSecret(t *testing.T) {
|
|||
out, resolve, _ := NewOutput()
|
||||
resolve("outputty")
|
||||
out2 := newOutputState(reflect.TypeOf(""))
|
||||
out2.fulfill(nil, false, true, nil)
|
||||
out2.fulfill(nil, false, true, nil, nil)
|
||||
inputs := testInputs{
|
||||
S: String("a string"),
|
||||
A: Bool(true),
|
||||
|
@ -506,7 +506,7 @@ func (UntypedArgs) ElementType() reflect.Type {
|
|||
func TestMapInputMarhsalling(t *testing.T) {
|
||||
var theResource simpleResource
|
||||
out := newOutput(reflect.TypeOf((*StringOutput)(nil)).Elem(), &theResource)
|
||||
out.resolve("outputty", true, false)
|
||||
out.resolve("outputty", true, false, nil)
|
||||
|
||||
inputs1 := Map(map[string]Input{
|
||||
"prop": out,
|
||||
|
|
|
@ -101,22 +101,25 @@ func TestRegisterResource(t *testing.T) {
|
|||
}, &res)
|
||||
assert.NoError(t, err)
|
||||
|
||||
id, known, secret, err := await(res.ID())
|
||||
id, known, secret, deps, err := await(res.ID())
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Equal(t, []Resource{&res}, deps)
|
||||
assert.Equal(t, ID("someID"), id)
|
||||
|
||||
urn, known, secret, err := await(res.URN())
|
||||
urn, known, secret, deps, err := await(res.URN())
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Equal(t, []Resource{&res}, deps)
|
||||
assert.NotEqual(t, "", urn)
|
||||
|
||||
foo, known, secret, err := await(res.Foo)
|
||||
foo, known, secret, deps, err := await(res.Foo)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Equal(t, []Resource{&res}, deps)
|
||||
assert.Equal(t, "qux", foo)
|
||||
|
||||
// Test map marshaling.
|
||||
|
@ -129,22 +132,25 @@ func TestRegisterResource(t *testing.T) {
|
|||
}, &res2)
|
||||
assert.NoError(t, err)
|
||||
|
||||
id, known, secret, err = await(res2.ID())
|
||||
id, known, secret, deps, err = await(res2.ID())
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Equal(t, []Resource{&res2}, deps)
|
||||
assert.Equal(t, ID("someID"), id)
|
||||
|
||||
urn, known, secret, err = await(res2.URN())
|
||||
urn, known, secret, deps, err = await(res2.URN())
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Equal(t, []Resource{&res2}, deps)
|
||||
assert.NotEqual(t, "", urn)
|
||||
|
||||
outputs, known, secret, err := await(res2.Outputs)
|
||||
outputs, known, secret, deps, err := await(res2.Outputs)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Equal(t, []Resource{&res2}, deps)
|
||||
assert.Equal(t, map[string]interface{}{"foo": "qux"}, outputs)
|
||||
|
||||
return nil
|
||||
|
@ -176,22 +182,25 @@ func TestReadResource(t *testing.T) {
|
|||
}, &res)
|
||||
assert.NoError(t, err)
|
||||
|
||||
id, known, secret, err := await(res.ID())
|
||||
id, known, secret, deps, err := await(res.ID())
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Equal(t, []Resource{&res}, deps)
|
||||
assert.Equal(t, ID("someID"), id)
|
||||
|
||||
urn, known, secret, err := await(res.URN())
|
||||
urn, known, secret, deps, err := await(res.URN())
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Equal(t, []Resource{&res}, deps)
|
||||
assert.NotEqual(t, "", urn)
|
||||
|
||||
foo, known, secret, err := await(res.Foo)
|
||||
foo, known, secret, deps, err := await(res.Foo)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Equal(t, []Resource{&res}, deps)
|
||||
assert.Equal(t, "qux", foo)
|
||||
|
||||
return nil
|
||||
|
|
|
@ -36,21 +36,21 @@ func TestStackReference(t *testing.T) {
|
|||
resName = "stack"
|
||||
ref0, err := NewStackReference(ctx, resName, nil)
|
||||
assert.NoError(t, err)
|
||||
_, _, _, err = await(ref0.ID())
|
||||
_, _, _, _, err = await(ref0.ID())
|
||||
assert.NoError(t, err)
|
||||
resName = "stack2"
|
||||
ref1, err := NewStackReference(ctx, resName, &StackReferenceArgs{Name: String("stack")})
|
||||
assert.NoError(t, err)
|
||||
outs0, _, _, err := await(ref0.Outputs)
|
||||
outs0, _, _, _, err := await(ref0.Outputs)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, outputs, outs0)
|
||||
zed0, _, _, err := await(ref0.GetOutput(String("zed")))
|
||||
zed0, _, _, _, err := await(ref0.GetOutput(String("zed")))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, outputs["zed"], zed0)
|
||||
outs1, _, _, err := await(ref1.Outputs)
|
||||
outs1, _, _, _, err := await(ref1.Outputs)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, outputs, outs1)
|
||||
zed1, _, _, err := await(ref1.GetOutput(String("zed")))
|
||||
zed1, _, _, _, err := await(ref1.GetOutput(String("zed")))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, outputs["zed"], zed1)
|
||||
return nil
|
||||
|
|
|
@ -30,13 +30,13 @@ func TestOutputApply(t *testing.T) {
|
|||
// Test that resolved outputs lead to applies being run.
|
||||
{
|
||||
out := newIntOutput()
|
||||
go func() { out.resolve(42, true, false) }()
|
||||
go func() { out.resolve(42, true, false, nil) }()
|
||||
var ranApp bool
|
||||
app := out.ApplyT(func(v int) (interface{}, error) {
|
||||
ranApp = true
|
||||
return v + 1, nil
|
||||
})
|
||||
v, known, _, err := await(app)
|
||||
v, known, _, _, err := await(app)
|
||||
assert.True(t, ranApp)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, known)
|
||||
|
@ -45,13 +45,13 @@ func TestOutputApply(t *testing.T) {
|
|||
// Test that resolved, but unknown outputs, skip the running of applies.
|
||||
{
|
||||
out := newIntOutput()
|
||||
go func() { out.resolve(42, false, false) }()
|
||||
go func() { out.resolve(42, false, false, nil) }()
|
||||
var ranApp bool
|
||||
app := out.ApplyT(func(v int) (interface{}, error) {
|
||||
ranApp = true
|
||||
return v + 1, nil
|
||||
})
|
||||
_, known, _, err := await(app)
|
||||
_, known, _, _, err := await(app)
|
||||
assert.False(t, ranApp)
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, known)
|
||||
|
@ -65,7 +65,7 @@ func TestOutputApply(t *testing.T) {
|
|||
ranApp = true
|
||||
return v + 1, nil
|
||||
})
|
||||
v, _, _, err := await(app)
|
||||
v, _, _, _, err := await(app)
|
||||
assert.False(t, ranApp)
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, v)
|
||||
|
@ -73,7 +73,7 @@ func TestOutputApply(t *testing.T) {
|
|||
// Test that an an apply that returns an output returns the resolution of that output, not the output itself.
|
||||
{
|
||||
out := newIntOutput()
|
||||
go func() { out.resolve(42, true, false) }()
|
||||
go func() { out.resolve(42, true, false, nil) }()
|
||||
var ranApp bool
|
||||
app := out.ApplyT(func(v int) (interface{}, error) {
|
||||
other, resolveOther, _ := NewOutput()
|
||||
|
@ -81,7 +81,7 @@ func TestOutputApply(t *testing.T) {
|
|||
ranApp = true
|
||||
return other, nil
|
||||
})
|
||||
v, known, _, err := await(app)
|
||||
v, known, _, _, err := await(app)
|
||||
assert.True(t, ranApp)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, known)
|
||||
|
@ -93,7 +93,7 @@ func TestOutputApply(t *testing.T) {
|
|||
ranApp = true
|
||||
return other, nil
|
||||
})
|
||||
v, known, _, err = await(app)
|
||||
v, known, _, _, err = await(app)
|
||||
assert.True(t, ranApp)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, known)
|
||||
|
@ -102,7 +102,7 @@ func TestOutputApply(t *testing.T) {
|
|||
// Test that an an apply that reject an output returns the rejection of that output, not the output itself.
|
||||
{
|
||||
out := newIntOutput()
|
||||
go func() { out.resolve(42, true, false) }()
|
||||
go func() { out.resolve(42, true, false, nil) }()
|
||||
var ranApp bool
|
||||
app := out.ApplyT(func(v int) (interface{}, error) {
|
||||
other, _, rejectOther := NewOutput()
|
||||
|
@ -110,7 +110,7 @@ func TestOutputApply(t *testing.T) {
|
|||
ranApp = true
|
||||
return other, nil
|
||||
})
|
||||
v, _, _, err := await(app)
|
||||
v, _, _, _, err := await(app)
|
||||
assert.True(t, ranApp)
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, v)
|
||||
|
@ -121,7 +121,7 @@ func TestOutputApply(t *testing.T) {
|
|||
ranApp = true
|
||||
return other, nil
|
||||
})
|
||||
v, _, _, err = await(app)
|
||||
v, _, _, _, err = await(app)
|
||||
assert.True(t, ranApp)
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, v)
|
||||
|
@ -129,17 +129,17 @@ func TestOutputApply(t *testing.T) {
|
|||
// Test builtin applies.
|
||||
{
|
||||
out := newIntOutput()
|
||||
go func() { out.resolve(42, true, false) }()
|
||||
go func() { out.resolve(42, true, false, nil) }()
|
||||
|
||||
{{range .Builtins}}
|
||||
t.Run("Apply{{.Name}}", func(t *testing.T) {
|
||||
o2 := out.Apply{{.Name}}(func(v int) {{.ElementType}} { return *new({{.ElementType}}) })
|
||||
_, known, _, err := await(o2)
|
||||
_, known, _, _, err := await(o2)
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
|
||||
o2 = out.Apply{{.Name}}WithContext(context.Background(), func(_ context.Context, v int) {{.ElementType}} { return *new({{.ElementType}}) })
|
||||
_, known, _, err = await(o2)
|
||||
_, known, _, _, err = await(o2)
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
@ -148,7 +148,7 @@ func TestOutputApply(t *testing.T) {
|
|||
// Test that applies return appropriate concrete implementations of Output based on the callback type
|
||||
{
|
||||
out := newIntOutput()
|
||||
go func() { out.resolve(42, true, false) }()
|
||||
go func() { out.resolve(42, true, false, nil) }()
|
||||
|
||||
{{range .Builtins}}
|
||||
t.Run("ApplyT::{{.Name}}Output", func(t *testing.T) {
|
||||
|
@ -165,10 +165,10 @@ func TestOutputApply(t *testing.T) {
|
|||
}
|
||||
|
||||
out := newIntOutput()
|
||||
go func() { out.resolve(42, true, false) }()
|
||||
go func() { out.resolve(42, true, false, nil) }()
|
||||
|
||||
out2 := StringOutput{newOutputState(reflect.TypeOf(""))}
|
||||
go func() { out2.resolve("hello", true, false) }()
|
||||
go func() { out2.resolve("hello", true, false, nil) }()
|
||||
|
||||
res := out.
|
||||
ApplyT(func(v int) myStructType {
|
||||
|
@ -230,7 +230,7 @@ func TestOutputApply(t *testing.T) {
|
|||
_, ok := res.(StringArrayOutput)
|
||||
assert.True(t, ok)
|
||||
|
||||
v, known, _, err := await(res)
|
||||
v, known, _, _, err := await(res)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, known)
|
||||
assert.Equal(t, []string{"qux", "zed"}, v)
|
||||
|
@ -238,7 +238,7 @@ func TestOutputApply(t *testing.T) {
|
|||
_, ok = res2.(StringArrayOutput)
|
||||
assert.True(t, ok)
|
||||
|
||||
v, known, _, err = await(res2)
|
||||
v, known, _, _, err = await(res2)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, known)
|
||||
assert.Equal(t, []string{"foo", "bar"}, v)
|
||||
|
@ -246,7 +246,7 @@ func TestOutputApply(t *testing.T) {
|
|||
_, ok = res3.(StringOutput)
|
||||
assert.True(t, ok)
|
||||
|
||||
v, known, _, err = await(res3)
|
||||
v, known, _, _, err = await(res3)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, known)
|
||||
assert.Equal(t, "foo,bar,qux,zed", v)
|
||||
|
@ -254,12 +254,12 @@ func TestOutputApply(t *testing.T) {
|
|||
_, ok = res4.(AnyOutput)
|
||||
assert.True(t, ok)
|
||||
|
||||
v, known, _, err = await(res4)
|
||||
v, known, _, _, err = await(res4)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, known)
|
||||
assert.Equal(t, &myStructType{foo: 42, bar: "hello"}, v)
|
||||
|
||||
v, known, _, err = await(res5)
|
||||
v, known, _, _, err = await(res5)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, known)
|
||||
assert.Equal(t, "foo,bar,qux,zed;42;hello", v)
|
||||
|
@ -273,7 +273,7 @@ func TestToOutput{{.Name}}(t *testing.T) {
|
|||
_, ok := out.({{.Name}}Input)
|
||||
assert.True(t, ok)
|
||||
|
||||
_, known, _, err := await(out)
|
||||
_, known, _, _, err := await(out)
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -281,7 +281,7 @@ func TestToOutput{{.Name}}(t *testing.T) {
|
|||
_, ok = out.({{.Name}}Input)
|
||||
assert.True(t, ok)
|
||||
|
||||
_, known, _, err = await(out)
|
||||
_, known, _, _, err = await(out)
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@ -294,25 +294,25 @@ func TestTo{{.Name}}Output(t *testing.T) {
|
|||
|
||||
out := in.To{{.Name}}Output()
|
||||
|
||||
_, known, _, err := await(out)
|
||||
_, known, _, _, err := await(out)
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
|
||||
out = out.To{{.Name}}Output()
|
||||
|
||||
_, known, _, err = await(out)
|
||||
_, known, _, _, err = await(out)
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
|
||||
out = in.To{{.Name}}OutputWithContext(context.Background())
|
||||
|
||||
_, known, _, err = await(out)
|
||||
_, known, _, _, err = await(out)
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
|
||||
out = out.To{{.Name}}OutputWithContext(context.Background())
|
||||
|
||||
_, known, _, err = await(out)
|
||||
_, known, _, _, err = await(out)
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@ -322,56 +322,56 @@ func TestTo{{.Name}}Output(t *testing.T) {
|
|||
func TestBuiltinConversions(t *testing.T) {
|
||||
archiveIn := NewFileArchive("foo.zip")
|
||||
assetOrArchiveOut := archiveIn.ToAssetOrArchiveOutput()
|
||||
archiveV, known, _, err := await(assetOrArchiveOut)
|
||||
archiveV, known, _, _, err := await(assetOrArchiveOut)
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, archiveIn, archiveV)
|
||||
|
||||
archiveOut := archiveIn.ToArchiveOutput()
|
||||
assetOrArchiveOut = archiveOut.ToAssetOrArchiveOutput()
|
||||
archiveV, known, _, err = await(assetOrArchiveOut)
|
||||
archiveV, known, _, _, err = await(assetOrArchiveOut)
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, archiveIn, archiveV)
|
||||
|
||||
assetIn := NewFileAsset("foo.zip")
|
||||
assetOrArchiveOut = assetIn.ToAssetOrArchiveOutput()
|
||||
assetV, known, _, err := await(assetOrArchiveOut)
|
||||
assetV, known, _, _, err := await(assetOrArchiveOut)
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, assetIn, assetV)
|
||||
|
||||
assetOut := assetIn.ToAssetOutput()
|
||||
assetOrArchiveOut = assetOut.ToAssetOrArchiveOutput()
|
||||
assetV, known, _, err = await(assetOrArchiveOut)
|
||||
assetV, known, _, _, err = await(assetOrArchiveOut)
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, assetIn, assetV)
|
||||
|
||||
idIn := ID("foo")
|
||||
stringOut := idIn.ToStringOutput()
|
||||
stringV, known, _, err := await(stringOut)
|
||||
stringV, known, _, _, err := await(stringOut)
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, string(idIn), stringV)
|
||||
|
||||
idOut := idIn.ToIDOutput()
|
||||
stringOut = idOut.ToStringOutput()
|
||||
stringV, known, _, err = await(stringOut)
|
||||
stringV, known, _, _, err = await(stringOut)
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, string(idIn), stringV)
|
||||
|
||||
urnIn := URN("foo")
|
||||
stringOut = urnIn.ToStringOutput()
|
||||
stringV, known, _, err = await(stringOut)
|
||||
stringV, known, _, _, err = await(stringOut)
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, string(urnIn), stringV)
|
||||
|
||||
urnOut := urnIn.ToURNOutput()
|
||||
stringOut = urnOut.ToStringOutput()
|
||||
stringV, known, _, err = await(stringOut)
|
||||
stringV, known, _, _, err = await(stringOut)
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, string(urnIn), stringV)
|
||||
|
@ -383,11 +383,11 @@ func TestBuiltinConversions(t *testing.T) {
|
|||
func Test{{.Name}}Elem(t *testing.T) {
|
||||
out := ({{.Example}}).To{{.Name}}Output()
|
||||
|
||||
av, known, _, err := await(out)
|
||||
av, known, _, _, err := await(out)
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
|
||||
iv, known, _, err := await(out.Elem())
|
||||
iv, known, _, _, err := await(out.Elem())
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -403,11 +403,11 @@ func Test{{.Name}}Elem(t *testing.T) {
|
|||
func Test{{.Name}}Index(t *testing.T) {
|
||||
out := ({{.Example}}).To{{.Name}}Output()
|
||||
|
||||
av, known, _, err := await(out)
|
||||
av, known, _, _, err := await(out)
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
|
||||
iv, known, _, err := await(out.Index(Int(0)))
|
||||
iv, known, _, _, err := await(out.Index(Int(0)))
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -422,11 +422,11 @@ func Test{{.Name}}Index(t *testing.T) {
|
|||
func Test{{.Name}}Index(t *testing.T) {
|
||||
out := ({{.Example}}).To{{.Name}}Output()
|
||||
|
||||
av, known, _, err := await(out)
|
||||
av, known, _, _, err := await(out)
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
|
||||
iv, known, _, err := await(out.MapIndex(String("baz")))
|
||||
iv, known, _, _, err := await(out.MapIndex(String("baz")))
|
||||
assert.True(t, known)
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
|
|
@ -39,12 +39,12 @@ type Output interface {
|
|||
|
||||
getState() *OutputState
|
||||
dependencies() []Resource
|
||||
fulfillValue(value reflect.Value, known, secret bool, err error)
|
||||
resolveValue(value reflect.Value, known, secret bool)
|
||||
fulfill(value interface{}, known, secret bool, err error)
|
||||
resolve(value interface{}, known, secret bool)
|
||||
fulfillValue(value reflect.Value, known, secret bool, deps []Resource, err error)
|
||||
resolveValue(value reflect.Value, known, secret bool, deps []Resource)
|
||||
fulfill(value interface{}, known, secret bool, deps []Resource, err error)
|
||||
resolve(value interface{}, known, secret bool, deps []Resource)
|
||||
reject(err error)
|
||||
await(ctx context.Context) (interface{}, bool, bool, error)
|
||||
await(ctx context.Context) (interface{}, bool, bool, []Resource, error)
|
||||
isSecret() bool
|
||||
}
|
||||
|
||||
|
@ -99,11 +99,11 @@ func (o *OutputState) dependencies() []Resource {
|
|||
return o.deps
|
||||
}
|
||||
|
||||
func (o *OutputState) fulfill(value interface{}, known, secret bool, err error) {
|
||||
o.fulfillValue(reflect.ValueOf(value), known, secret, err)
|
||||
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, err error) {
|
||||
func (o *OutputState) fulfillValue(value reflect.Value, known, secret bool, deps []Resource, err error) {
|
||||
if o == nil {
|
||||
return
|
||||
}
|
||||
|
@ -125,39 +125,57 @@ func (o *OutputState) fulfillValue(value reflect.Value, known, secret bool, err
|
|||
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) {
|
||||
o.fulfill(value, known, secret, nil)
|
||||
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) {
|
||||
o.fulfillValue(value, known, secret, 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, err)
|
||||
o.fulfill(nil, true, false, nil, err)
|
||||
}
|
||||
|
||||
func (o *OutputState) await(ctx context.Context) (interface{}, bool, bool, error) {
|
||||
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
|
||||
return nil, false, false, nil, nil
|
||||
}
|
||||
|
||||
o.mutex.Lock()
|
||||
for o.state == outputPending {
|
||||
if ctx.Err() != nil {
|
||||
return nil, true, false, ctx.Err()
|
||||
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.err
|
||||
return nil, o.known, o.secret, o.deps, o.err
|
||||
}
|
||||
|
||||
// If the result is an Output, await it in turn.
|
||||
|
@ -166,7 +184,7 @@ func (o *OutputState) await(ctx context.Context) (interface{}, bool, bool, error
|
|||
// the element type of the outer output. We should reconsider this.
|
||||
ov, ok := o.value.(Output)
|
||||
if !ok {
|
||||
return o.value, true, o.secret, nil
|
||||
return o.value, true, o.secret, o.deps, nil
|
||||
}
|
||||
o = ov.getState()
|
||||
}
|
||||
|
@ -223,7 +241,7 @@ func NewOutput() (Output, func(interface{}), func(error)) {
|
|||
out := newOutputState(anyType)
|
||||
|
||||
resolve := func(v interface{}) {
|
||||
out.resolve(v, true, false)
|
||||
out.resolve(v, true, false, nil)
|
||||
}
|
||||
reject := func(err error) {
|
||||
out.reject(err)
|
||||
|
@ -378,9 +396,9 @@ func (o *OutputState) ApplyTWithContext(ctx context.Context, applier interface{}
|
|||
|
||||
result := newOutput(resultType, o.dependencies()...)
|
||||
go func() {
|
||||
v, known, secret, err := o.await(ctx)
|
||||
v, known, secret, deps, err := o.await(ctx)
|
||||
if err != nil || !known {
|
||||
result.fulfill(nil, known, secret, err)
|
||||
result.fulfill(nil, known, secret, deps, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -396,7 +414,7 @@ func (o *OutputState) ApplyTWithContext(ctx context.Context, applier interface{}
|
|||
}
|
||||
|
||||
// Fulfill the result.
|
||||
result.fulfillValue(results[0], true, secret, nil)
|
||||
result.fulfillValue(results[0], true, secret, deps, nil)
|
||||
}()
|
||||
return result
|
||||
}
|
||||
|
@ -530,11 +548,11 @@ func callToOutputMethod(ctx context.Context, input reflect.Value, resolvedType r
|
|||
return toOutputMethod.Call([]reflect.Value{reflect.ValueOf(ctx)})[0].Interface().(Output), true
|
||||
}
|
||||
|
||||
func awaitInputs(ctx context.Context, v, resolved reflect.Value) (bool, bool, error) {
|
||||
func awaitInputs(ctx context.Context, v, resolved reflect.Value) (bool, bool, []Resource, error) {
|
||||
contract.Assert(v.IsValid())
|
||||
|
||||
if !resolved.CanSet() {
|
||||
return true, false, nil
|
||||
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
|
||||
|
@ -544,7 +562,7 @@ func awaitInputs(ctx context.Context, v, resolved reflect.Value) (bool, bool, er
|
|||
input, isNonNil := v.Interface().(Input)
|
||||
if !isNonNil {
|
||||
// A nil input is already fully-resolved.
|
||||
return true, false, nil
|
||||
return true, false, nil, nil
|
||||
}
|
||||
|
||||
valueType = input.ElementType()
|
||||
|
@ -570,9 +588,9 @@ func awaitInputs(ctx context.Context, v, resolved reflect.Value) (bool, bool, er
|
|||
|
||||
// If the input is an Output, await its value. The returned value is fully resolved.
|
||||
if output, ok := input.(Output); ok {
|
||||
e, known, secret, err := output.await(ctx)
|
||||
e, known, secret, deps, err := output.await(ctx)
|
||||
if err != nil || !known {
|
||||
return known, secret, err
|
||||
return known, secret, deps, err
|
||||
}
|
||||
if !assignInput {
|
||||
val := reflect.ValueOf(e)
|
||||
|
@ -583,13 +601,13 @@ func awaitInputs(ctx context.Context, v, resolved reflect.Value) (bool, bool, er
|
|||
} else {
|
||||
resolved.Set(reflect.ValueOf(input))
|
||||
}
|
||||
return true, secret, nil
|
||||
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
|
||||
return true, false, nil, nil
|
||||
}
|
||||
|
||||
v, isInput = reflect.ValueOf(input), true
|
||||
|
@ -626,7 +644,7 @@ func awaitInputs(ctx context.Context, v, resolved reflect.Value) (bool, bool, er
|
|||
resolved = reflect.New(valueType).Elem()
|
||||
}
|
||||
|
||||
known, secret, err := true, false, error(nil)
|
||||
known, secret, deps, err := true, false, make([]Resource, 0), error(nil)
|
||||
switch v.Kind() {
|
||||
case reflect.Interface:
|
||||
if !v.IsNil() {
|
||||
|
@ -643,9 +661,10 @@ func awaitInputs(ctx context.Context, v, resolved reflect.Value) (bool, bool, er
|
|||
numFields := typ.NumField()
|
||||
for i := 0; i < numFields; i++ {
|
||||
_, field := getMappedField(resolved, i)
|
||||
fknown, fsecret, ferr := awaitInputs(ctx, v.Field(i), field)
|
||||
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
|
||||
}
|
||||
|
@ -653,9 +672,10 @@ func awaitInputs(ctx context.Context, v, resolved reflect.Value) (bool, bool, er
|
|||
case reflect.Array:
|
||||
l := v.Len()
|
||||
for i := 0; i < l; i++ {
|
||||
eknown, esecret, eerr := awaitInputs(ctx, v.Index(i), resolved.Index(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
|
||||
}
|
||||
|
@ -664,9 +684,10 @@ func awaitInputs(ctx context.Context, v, resolved reflect.Value) (bool, bool, er
|
|||
l := v.Len()
|
||||
resolved.Set(reflect.MakeSlice(resolved.Type(), l, l))
|
||||
for i := 0; i < l; i++ {
|
||||
eknown, esecret, eerr := awaitInputs(ctx, v.Index(i), resolved.Index(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
|
||||
}
|
||||
|
@ -677,13 +698,13 @@ func awaitInputs(ctx context.Context, v, resolved reflect.Value) (bool, bool, er
|
|||
iter := v.MapRange()
|
||||
for iter.Next() {
|
||||
kv := reflect.New(resolvedKeyType).Elem()
|
||||
kknown, ksecret, kerr := awaitInputs(ctx, iter.Key(), kv)
|
||||
kknown, ksecret, kdeps, kerr := awaitInputs(ctx, iter.Key(), kv)
|
||||
if err == nil {
|
||||
err = kerr
|
||||
}
|
||||
|
||||
vv := reflect.New(resolvedValueType).Elem()
|
||||
vknown, vsecret, verr := awaitInputs(ctx, iter.Value(), vv)
|
||||
vknown, vsecret, vdeps, verr := awaitInputs(ctx, iter.Value(), vv)
|
||||
if err == nil {
|
||||
err = verr
|
||||
}
|
||||
|
@ -694,6 +715,7 @@ func awaitInputs(ctx context.Context, v, resolved reflect.Value) (bool, bool, er
|
|||
|
||||
known = known && kknown && vknown
|
||||
secret = secret || ksecret || vsecret
|
||||
deps = append(append(deps, kdeps...), vdeps...)
|
||||
}
|
||||
default:
|
||||
if isInput {
|
||||
|
@ -701,7 +723,7 @@ func awaitInputs(ctx context.Context, v, resolved reflect.Value) (bool, bool, er
|
|||
}
|
||||
resolved.Set(v)
|
||||
}
|
||||
return known, secret, err
|
||||
return known, secret, deps, err
|
||||
}
|
||||
|
||||
// ToOutput returns an Output that will resolve when all Inputs contained in the given value have resolved.
|
||||
|
@ -729,20 +751,20 @@ func toOutputWithContext(ctx context.Context, v interface{}, forceSecret bool) O
|
|||
result := newOutput(resultType, gatherDependencies(v)...)
|
||||
go func() {
|
||||
if v == nil {
|
||||
result.fulfill(nil, true, false, nil)
|
||||
result.fulfill(nil, true, false, nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
element := reflect.New(resolvedType).Elem()
|
||||
|
||||
known, secret, err := awaitInputs(ctx, reflect.ValueOf(v), element)
|
||||
known, secret, deps, err := awaitInputs(ctx, reflect.ValueOf(v), element)
|
||||
secret = secret || forceSecret
|
||||
if err != nil || !known {
|
||||
result.fulfill(nil, known, secret, err)
|
||||
result.fulfill(nil, known, secret, deps, err)
|
||||
return
|
||||
}
|
||||
|
||||
result.resolveValue(element, true, secret)
|
||||
result.resolveValue(element, true, secret, deps)
|
||||
}()
|
||||
return result
|
||||
}
|
||||
|
@ -822,12 +844,12 @@ func AnyWithContext(ctx context.Context, v interface{}) AnyOutput {
|
|||
out := newOutput(anyOutputType, gatherDependencies(v)...)
|
||||
go func() {
|
||||
if v == nil {
|
||||
out.fulfill(nil, true, false, nil)
|
||||
out.fulfill(nil, true, false, nil, nil)
|
||||
return
|
||||
}
|
||||
var result interface{}
|
||||
known, secret, err := awaitInputs(ctx, reflect.ValueOf(v), reflect.ValueOf(&result).Elem())
|
||||
out.fulfill(result, known, secret, err)
|
||||
known, secret, deps, err := awaitInputs(ctx, reflect.ValueOf(v), reflect.ValueOf(&result).Elem())
|
||||
out.fulfill(result, known, secret, deps, err)
|
||||
}()
|
||||
return out.(AnyOutput)
|
||||
}
|
||||
|
@ -855,7 +877,7 @@ func (o IDOutput) ToStringPtrOutputWithContext(ctx context.Context) StringPtrOut
|
|||
}
|
||||
|
||||
func (o IDOutput) awaitID(ctx context.Context) (ID, bool, bool, error) {
|
||||
id, known, secret, err := o.await(ctx)
|
||||
id, known, secret, _, err := o.await(ctx)
|
||||
if !known || err != nil {
|
||||
return "", known, false, err
|
||||
}
|
||||
|
@ -879,7 +901,7 @@ func (o URNOutput) ToStringPtrOutputWithContext(ctx context.Context) StringPtrOu
|
|||
}
|
||||
|
||||
func (o URNOutput) awaitURN(ctx context.Context) (URN, bool, bool, error) {
|
||||
id, known, secret, err := o.await(ctx)
|
||||
id, known, secret, _, err := o.await(ctx)
|
||||
if !known || err != nil {
|
||||
return "", known, secret, err
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -25,12 +25,12 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func await(out Output) (interface{}, bool, bool, error) {
|
||||
func await(out Output) (interface{}, bool, bool, []Resource, error) {
|
||||
return out.await(context.Background())
|
||||
}
|
||||
|
||||
func assertApplied(t *testing.T, out Output) {
|
||||
_, known, _, err := await(out)
|
||||
_, known, _, _, err := await(out)
|
||||
assert.True(t, known)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
@ -46,10 +46,11 @@ func TestBasicOutputs(t *testing.T) {
|
|||
go func() {
|
||||
resolve(42)
|
||||
}()
|
||||
v, known, secret, err := await(out)
|
||||
v, known, secret, deps, err := await(out)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Nil(t, deps)
|
||||
assert.NotNil(t, v)
|
||||
assert.Equal(t, 42, v.(int))
|
||||
}
|
||||
|
@ -58,7 +59,7 @@ func TestBasicOutputs(t *testing.T) {
|
|||
go func() {
|
||||
reject(errors.New("boom"))
|
||||
}()
|
||||
v, _, _, err := await(out)
|
||||
v, _, _, _, err := await(out)
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, v)
|
||||
}
|
||||
|
@ -67,7 +68,7 @@ func TestBasicOutputs(t *testing.T) {
|
|||
func TestArrayOutputs(t *testing.T) {
|
||||
out := ArrayOutput{newOutputState(reflect.TypeOf([]interface{}{}))}
|
||||
go func() {
|
||||
out.resolve([]interface{}{nil, 0, "x"}, true, false)
|
||||
out.resolve([]interface{}{nil, 0, "x"}, true, false, nil)
|
||||
}()
|
||||
{
|
||||
assertApplied(t, out.ApplyT(func(arr []interface{}) (interface{}, error) {
|
||||
|
@ -85,7 +86,7 @@ func TestArrayOutputs(t *testing.T) {
|
|||
func TestBoolOutputs(t *testing.T) {
|
||||
out := BoolOutput{newOutputState(reflect.TypeOf(false))}
|
||||
go func() {
|
||||
out.resolve(true, true, false)
|
||||
out.resolve(true, true, false, nil)
|
||||
}()
|
||||
{
|
||||
assertApplied(t, out.ApplyT(func(v bool) (interface{}, error) {
|
||||
|
@ -102,7 +103,7 @@ func TestMapOutputs(t *testing.T) {
|
|||
"x": 1,
|
||||
"y": false,
|
||||
"z": "abc",
|
||||
}, true, false)
|
||||
}, true, false, nil)
|
||||
}()
|
||||
{
|
||||
assertApplied(t, out.ApplyT(func(v map[string]interface{}) (interface{}, error) {
|
||||
|
@ -118,7 +119,7 @@ func TestMapOutputs(t *testing.T) {
|
|||
func TestNumberOutputs(t *testing.T) {
|
||||
out := Float64Output{newOutputState(reflect.TypeOf(float64(0)))}
|
||||
go func() {
|
||||
out.resolve(42.345, true, false)
|
||||
out.resolve(42.345, true, false, nil)
|
||||
}()
|
||||
{
|
||||
assertApplied(t, out.ApplyT(func(v float64) (interface{}, error) {
|
||||
|
@ -131,7 +132,7 @@ func TestNumberOutputs(t *testing.T) {
|
|||
func TestStringOutputs(t *testing.T) {
|
||||
out := StringOutput{newOutputState(reflect.TypeOf(""))}
|
||||
go func() {
|
||||
out.resolve("a stringy output", true, false)
|
||||
out.resolve("a stringy output", true, false, nil)
|
||||
}()
|
||||
{
|
||||
assertApplied(t, out.ApplyT(func(v string) (interface{}, error) {
|
||||
|
@ -163,7 +164,7 @@ func TestResolveOutputToOutput(t *testing.T) {
|
|||
resolve(other)
|
||||
go func() { rejectOther(errors.New("boom")) }()
|
||||
}()
|
||||
v, _, _, err := await(out)
|
||||
v, _, _, _, err := await(out)
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, v)
|
||||
}
|
||||
|
@ -175,9 +176,10 @@ func TestToOutputStruct(t *testing.T) {
|
|||
_, ok := out.(nestedTypeOutput)
|
||||
assert.True(t, ok)
|
||||
|
||||
v, known, secret, err := await(out)
|
||||
v, known, secret, deps, err := await(out)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Nil(t, deps)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, nestedType{Foo: "bar", Bar: 42}, v)
|
||||
|
||||
|
@ -185,9 +187,10 @@ func TestToOutputStruct(t *testing.T) {
|
|||
_, ok = out.(nestedTypeOutput)
|
||||
assert.True(t, ok)
|
||||
|
||||
v, known, secret, err = await(out)
|
||||
v, known, secret, deps, err = await(out)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Nil(t, deps)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, nestedType{Foo: "bar", Bar: 42}, v)
|
||||
|
@ -196,9 +199,10 @@ func TestToOutputStruct(t *testing.T) {
|
|||
_, ok = out.(nestedTypeOutput)
|
||||
assert.True(t, ok)
|
||||
|
||||
v, known, secret, err = await(out)
|
||||
v, known, secret, deps, err = await(out)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Nil(t, deps)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, nestedType{Foo: "bar", Bar: 42}, v)
|
||||
}
|
||||
|
@ -236,9 +240,10 @@ func TestToOutputConvert(t *testing.T) {
|
|||
_, ok := out.(nestedTypeOutput)
|
||||
assert.True(t, ok)
|
||||
|
||||
v, known, secret, err := await(out)
|
||||
v, known, secret, deps, err := await(out)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Nil(t, deps)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, nestedType{Foo: "bar", Bar: 1}, v)
|
||||
}
|
||||
|
@ -259,9 +264,10 @@ func TestToOutputAny(t *testing.T) {
|
|||
_, ok := out.(AnyOutput)
|
||||
assert.True(t, ok)
|
||||
|
||||
v, known, secret, err := await(out)
|
||||
v, known, secret, deps, err := await(out)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Nil(t, deps)
|
||||
assert.NoError(t, err)
|
||||
|
||||
argsV := v.(*args)
|
||||
|
@ -284,6 +290,73 @@ func TestToOutputAny(t *testing.T) {
|
|||
assert.Equal(t, true, bo.value)
|
||||
}
|
||||
|
||||
func TestToOutputAnyDeps(t *testing.T) {
|
||||
type args struct {
|
||||
S StringInput
|
||||
I IntInput
|
||||
A Input
|
||||
R Resource
|
||||
}
|
||||
|
||||
stringDep1, stringDep2 := &ResourceState{}, &ResourceState{}
|
||||
stringOut := StringOutput{newOutputState(reflect.TypeOf(""), stringDep1)}
|
||||
go func() {
|
||||
stringOut.resolve("a stringy output", true, false, []Resource{stringDep2})
|
||||
}()
|
||||
|
||||
intDep1, intDep2 := &ResourceState{}, &ResourceState{}
|
||||
intOut := IntOutput{newOutputState(reflect.TypeOf(0), intDep1)}
|
||||
go func() {
|
||||
intOut.resolve(42, true, false, []Resource{intDep2})
|
||||
}()
|
||||
|
||||
boolDep1, boolDep2 := &ResourceState{}, &ResourceState{}
|
||||
boolOut := BoolOutput{newOutputState(reflect.TypeOf(true), boolDep1)}
|
||||
go func() {
|
||||
boolOut.resolve(true, true, false, []Resource{boolDep2})
|
||||
}()
|
||||
|
||||
res := &ResourceState{}
|
||||
|
||||
out := ToOutput(&args{
|
||||
S: stringOut,
|
||||
I: intOut,
|
||||
A: Map{"world": boolOut},
|
||||
R: res,
|
||||
})
|
||||
_, ok := out.(AnyOutput)
|
||||
assert.True(t, ok)
|
||||
|
||||
v, known, secret, deps, err := await(out)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.ElementsMatch(t, []Resource{stringDep1, stringDep2, intDep1, intDep2, boolDep1, boolDep2, res}, deps)
|
||||
assert.NoError(t, err)
|
||||
|
||||
argsV := v.(*args)
|
||||
|
||||
so, ok := argsV.S.(StringOutput)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, uint32(outputResolved), so.state)
|
||||
assert.Equal(t, "a stringy output", so.value)
|
||||
assert.ElementsMatch(t, []Resource{stringDep1, stringDep2}, so.deps)
|
||||
|
||||
io, ok := argsV.I.(IntOutput)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, uint32(outputResolved), io.state)
|
||||
assert.Equal(t, 42, io.value)
|
||||
assert.ElementsMatch(t, []Resource{intDep1, intDep2}, io.deps)
|
||||
|
||||
ai, ok := argsV.A.(Map)
|
||||
assert.True(t, ok)
|
||||
|
||||
bo, ok := ai["world"].(BoolOutput)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, uint32(outputResolved), bo.getState().state)
|
||||
assert.Equal(t, true, bo.value)
|
||||
assert.ElementsMatch(t, []Resource{boolDep1, boolDep2}, bo.deps)
|
||||
}
|
||||
|
||||
type args struct {
|
||||
S string
|
||||
I int
|
||||
|
@ -310,9 +383,10 @@ func TestToOutputInputAny(t *testing.T) {
|
|||
_, ok := out.(AnyOutput)
|
||||
assert.True(t, ok)
|
||||
|
||||
v, known, secret, err := await(out)
|
||||
v, known, secret, deps, err := await(out)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Nil(t, deps)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, &args{
|
||||
|
@ -407,41 +481,76 @@ func TestSecretApply(t *testing.T) {
|
|||
|
||||
func TestNil(t *testing.T) {
|
||||
ao := Any(nil)
|
||||
v, known, secret, err := await(ao)
|
||||
v, known, secret, deps, err := await(ao)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Nil(t, deps)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, nil, v)
|
||||
|
||||
o := ToOutput(nil)
|
||||
v, known, secret, err = await(o)
|
||||
v, known, secret, deps, err = await(o)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Nil(t, deps)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, nil, v)
|
||||
|
||||
o = ToOutput(ao)
|
||||
v, known, secret, err = await(o)
|
||||
v, known, secret, deps, err = await(o)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Nil(t, deps)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, nil, v)
|
||||
|
||||
ao = ToOutput("").ApplyT(func(v string) interface{} {
|
||||
return nil
|
||||
}).(AnyOutput)
|
||||
v, known, secret, err = await(ao)
|
||||
v, known, secret, deps, err = await(ao)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Nil(t, deps)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, nil, v)
|
||||
|
||||
bo := ao.ApplyBool(func(x interface{}) bool {
|
||||
return x == nil
|
||||
})
|
||||
v, known, secret, err = await(bo)
|
||||
v, known, secret, deps, err = await(bo)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.Nil(t, deps)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, v)
|
||||
}
|
||||
|
||||
// Test that dependencies flow through all/apply.
|
||||
func TestDeps(t *testing.T) {
|
||||
stringDep1, stringDep2 := &ResourceState{}, &ResourceState{}
|
||||
stringOut := StringOutput{newOutputState(reflect.TypeOf(""), stringDep1)}
|
||||
assert.ElementsMatch(t, []Resource{stringDep1}, stringOut.deps)
|
||||
go func() {
|
||||
stringOut.resolve("hello", true, false, []Resource{stringDep2})
|
||||
}()
|
||||
|
||||
boolDep1, boolDep2 := &ResourceState{}, &ResourceState{}
|
||||
boolOut := BoolOutput{newOutputState(reflect.TypeOf(true), boolDep1)}
|
||||
assert.ElementsMatch(t, []Resource{boolDep1}, boolOut.deps)
|
||||
go func() {
|
||||
boolOut.resolve(true, true, false, []Resource{boolDep2})
|
||||
}()
|
||||
|
||||
a := All(stringOut, boolOut).ApplyT(func(args []interface{}) (string, error) {
|
||||
s := args[0].(string)
|
||||
b := args[1].(bool)
|
||||
return fmt.Sprintf("%s: %v", s, b), nil
|
||||
})
|
||||
|
||||
v, known, secret, deps, err := await(a)
|
||||
assert.Equal(t, "hello: true", v)
|
||||
assert.True(t, known)
|
||||
assert.False(t, secret)
|
||||
assert.ElementsMatch(t, []Resource{stringDep1, stringDep2, boolDep1, boolDep2}, deps)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
3
tests/integration/construct_component/go/Pulumi.yaml
Normal file
3
tests/integration/construct_component/go/Pulumi.yaml
Normal file
|
@ -0,0 +1,3 @@
|
|||
name: construct_component_go
|
||||
description: A program that constructs remote component resources.
|
||||
runtime: go
|
58
tests/integration/construct_component/go/main.go
Normal file
58
tests/integration/construct_component/go/main.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
// Copyright 2016-2020, Pulumi Corporation. All rights reserved.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/pulumi/pulumi/sdk/v2/go/pulumi"
|
||||
)
|
||||
|
||||
type componentArgs struct {
|
||||
Echo interface{} `pulumi:"echo"`
|
||||
}
|
||||
|
||||
type ComponentArgs struct {
|
||||
Echo pulumi.Input
|
||||
}
|
||||
|
||||
func (ComponentArgs) ElementType() reflect.Type {
|
||||
return reflect.TypeOf((*componentArgs)(nil)).Elem()
|
||||
}
|
||||
|
||||
type Component struct {
|
||||
pulumi.ResourceState
|
||||
|
||||
Echo pulumi.AnyOutput `pulumi:"echo"`
|
||||
ChildID pulumi.StringOutput `pulumi:"childId"`
|
||||
}
|
||||
|
||||
func NewComponent(
|
||||
ctx *pulumi.Context, name string, args *ComponentArgs, opts ...pulumi.ResourceOption) (*Component, error) {
|
||||
|
||||
var resource Component
|
||||
err := ctx.RegisterRemoteComponentResource("testcomponent:index:Component", name, args, &resource, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &resource, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
pulumi.Run(func(ctx *pulumi.Context) error {
|
||||
componentA, err := NewComponent(ctx, "a", &ComponentArgs{Echo: pulumi.Int(42)})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = NewComponent(ctx, "b", &ComponentArgs{Echo: componentA.Echo})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = NewComponent(ctx, "C", &ComponentArgs{Echo: componentA.ChildID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
|
@ -10,6 +10,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/pulumi/pulumi/pkg/v2/testing/integration"
|
||||
"github.com/pulumi/pulumi/sdk/v2/go/common/resource"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestEmptyGo simply tests that we can build and run an empty Go project.
|
||||
|
@ -116,3 +118,59 @@ func TestLargeResourceGo(t *testing.T) {
|
|||
Dir: filepath.Join("large_resource", "go"),
|
||||
})
|
||||
}
|
||||
|
||||
// Test remote component construction in Go.
|
||||
func TestConstructGo(t *testing.T) {
|
||||
pathEnv, err := testComponentPathEnv()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build test component PATH: %v", err)
|
||||
}
|
||||
|
||||
// TODO[pulumi/pulumi#5455]: Dynamic providers fail to load when used from multi-lang components.
|
||||
// Until we've addressed this, set PULUMI_TEST_YARN_LINK_PULUMI, which tells the integration test
|
||||
// module to run `yarn install && yarn link @pulumi/pulumi` in the Go program's directory, allowing
|
||||
// the Node.js dynamic provider plugin to load.
|
||||
// When the underlying issue has been fixed, the use of this environment variable inside the integration
|
||||
// test module should be removed.
|
||||
const testYarnLinkPulumiEnv = "PULUMI_TEST_YARN_LINK_PULUMI=true"
|
||||
|
||||
var opts *integration.ProgramTestOptions
|
||||
opts = &integration.ProgramTestOptions{
|
||||
Env: []string{pathEnv, testYarnLinkPulumiEnv},
|
||||
Dir: filepath.Join("construct_component", "go"),
|
||||
Dependencies: []string{
|
||||
"github.com/pulumi/pulumi/sdk/v2",
|
||||
},
|
||||
Quick: true,
|
||||
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
|
||||
assert.NotNil(t, stackInfo.Deployment)
|
||||
if assert.Equal(t, 9, len(stackInfo.Deployment.Resources)) {
|
||||
stackRes := stackInfo.Deployment.Resources[0]
|
||||
assert.NotNil(t, stackRes)
|
||||
assert.Equal(t, resource.RootStackType, stackRes.Type)
|
||||
assert.Equal(t, "", string(stackRes.Parent))
|
||||
|
||||
// Check that dependencies flow correctly between the originating program and the remote component
|
||||
// plugin.
|
||||
urns := make(map[string]resource.URN)
|
||||
for _, res := range stackInfo.Deployment.Resources[1:] {
|
||||
assert.NotNil(t, res)
|
||||
|
||||
urns[string(res.URN.Name())] = res.URN
|
||||
switch res.URN.Name() {
|
||||
case "child-a":
|
||||
for _, deps := range res.PropertyDependencies {
|
||||
assert.Empty(t, deps)
|
||||
}
|
||||
case "child-b":
|
||||
assert.Equal(t, []resource.URN{urns["a"]}, res.PropertyDependencies["echo"])
|
||||
case "child-c":
|
||||
assert.ElementsMatch(t, []resource.URN{urns["child-a"], urns["a"]},
|
||||
res.PropertyDependencies["echo"])
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
integration.ProgramTest(t, opts)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue