mirror of https://github.com/pulumi/pulumi.git
778 lines
24 KiB
Go
778 lines
24 KiB
Go
// Copyright 2020-2024, Pulumi Corporation.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package lifecycletest
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/blang/semver"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/display"
|
|
. "github.com/pulumi/pulumi/pkg/v3/engine" //nolint:revive
|
|
lt "github.com/pulumi/pulumi/pkg/v3/engine/lifecycletest/framework"
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/deploy/deploytest"
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
|
)
|
|
|
|
type testResource struct {
|
|
pulumi.CustomResourceState
|
|
|
|
Foo pulumi.StringOutput `pulumi:"foo"`
|
|
}
|
|
|
|
type testResourceArgs struct {
|
|
Foo string `pulumi:"foo"`
|
|
Bar string `pulumi:"bar"`
|
|
Baz string `pulumi:"baz"`
|
|
Bang string `pulumi:"bang"`
|
|
}
|
|
|
|
type testResourceInputs struct {
|
|
Foo pulumi.StringInput
|
|
Bar pulumi.StringInput
|
|
Baz pulumi.StringInput
|
|
Bang pulumi.StringInput
|
|
}
|
|
|
|
func (*testResourceInputs) ElementType() reflect.Type {
|
|
return reflect.TypeOf((*testResourceArgs)(nil))
|
|
}
|
|
|
|
func TestSingleResourceDefaultProviderGolangLifecycle(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: "created-id",
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
ReadF: func(_ context.Context, req plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
Inputs: req.Inputs,
|
|
Outputs: req.State,
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
ctx, err := pulumi.NewContext(context.Background(), pulumi.RunInfo{
|
|
Project: info.Project,
|
|
Stack: info.Stack,
|
|
Parallel: info.Parallel,
|
|
DryRun: info.DryRun,
|
|
MonitorAddr: info.MonitorAddress,
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
return pulumi.RunWithContext(ctx, func(ctx *pulumi.Context) error {
|
|
var resA testResource
|
|
err := ctx.RegisterResource("pkgA:m:typA", "resA", &testResourceInputs{
|
|
Foo: pulumi.String("bar"),
|
|
}, &resA)
|
|
assert.NoError(t, err)
|
|
|
|
var resB testResource
|
|
err = ctx.RegisterResource("pkgA:m:typA", "resB", &testResourceInputs{
|
|
Baz: resA.Foo.ApplyT(func(v string) string {
|
|
return v + "bar"
|
|
}).(pulumi.StringOutput),
|
|
}, &resB)
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
})
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
// Skip display tests because different ordering makes the colouring different.
|
|
Options: lt.TestUpdateOptions{T: t, HostF: hostF, SkipDisplayTests: true},
|
|
Steps: lt.MakeBasicLifecycleSteps(t, 4),
|
|
}
|
|
p.Run(t, nil)
|
|
}
|
|
|
|
// This test validates the wiring of the IgnoreChanges prop in the go SDK.
|
|
// It doesn't attempt to validate underlying behavior.
|
|
func TestIgnoreChangesGolangLifecycle(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var expectedIgnoreChanges []string
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: "created-id",
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
ReadF: func(_ context.Context, req plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
Inputs: req.Inputs,
|
|
Outputs: req.State,
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
DiffF: func(
|
|
_ context.Context,
|
|
req plugin.DiffRequest,
|
|
) (plugin.DiffResult, error) {
|
|
// just verify that the IgnoreChanges prop made it through
|
|
assert.Equal(t, expectedIgnoreChanges, req.IgnoreChanges)
|
|
return plugin.DiffResult{}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
setupAndRunProgram := func(ignoreChanges []string) *deploy.Snapshot {
|
|
programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
ctx, err := pulumi.NewContext(context.Background(), pulumi.RunInfo{
|
|
Project: info.Project,
|
|
Stack: info.Stack,
|
|
Parallel: info.Parallel,
|
|
DryRun: info.DryRun,
|
|
MonitorAddr: info.MonitorAddress,
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
return pulumi.RunWithContext(ctx, func(ctx *pulumi.Context) error {
|
|
var res pulumi.CustomResourceState
|
|
err := ctx.RegisterResource("pkgA:m:typA", "resA", nil, &res, pulumi.IgnoreChanges(ignoreChanges))
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
})
|
|
})
|
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{T: t, HostF: hostF},
|
|
Steps: []lt.TestStep{
|
|
{
|
|
Op: Update,
|
|
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
|
events []Event, err error,
|
|
) error {
|
|
for _, event := range events {
|
|
if event.Type == ResourcePreEvent {
|
|
payload := event.Payload().(ResourcePreEventPayload)
|
|
assert.Equal(t, []display.StepOp{deploy.OpCreate}, []display.StepOp{payload.Metadata.Op})
|
|
}
|
|
}
|
|
return err
|
|
},
|
|
},
|
|
},
|
|
}
|
|
return p.Run(t, nil)
|
|
}
|
|
|
|
// ignore changes specified
|
|
ignoreChanges := []string{"b"}
|
|
setupAndRunProgram(ignoreChanges)
|
|
|
|
// ignore changes empty
|
|
ignoreChanges = []string{}
|
|
setupAndRunProgram(ignoreChanges)
|
|
}
|
|
|
|
func TestExplicitDeleteBeforeReplaceGoSDK(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
p := <.TestPlan{}
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
DiffConfigF: func(
|
|
_ context.Context,
|
|
req plugin.DiffConfigRequest,
|
|
) (plugin.DiffResult, error) {
|
|
if !req.OldOutputs["foo"].DeepEquals(req.NewInputs["foo"]) {
|
|
return plugin.DiffResult{
|
|
ReplaceKeys: []resource.PropertyKey{"foo"},
|
|
DeleteBeforeReplace: true,
|
|
}, nil
|
|
}
|
|
return plugin.DiffResult{}, nil
|
|
},
|
|
DiffF: func(
|
|
_ context.Context,
|
|
req plugin.DiffRequest,
|
|
) (plugin.DiffResult, error) {
|
|
if !req.OldOutputs["foo"].DeepEquals(req.NewInputs["foo"]) {
|
|
return plugin.DiffResult{ReplaceKeys: []resource.PropertyKey{"foo"}}, nil
|
|
}
|
|
return plugin.DiffResult{}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
inputsA := &testResourceInputs{Foo: pulumi.String("foo")}
|
|
|
|
dbrValue, dbrA := true, (*bool)(nil)
|
|
getDbr := func() bool {
|
|
if dbrA == nil {
|
|
return false
|
|
}
|
|
return *dbrA
|
|
}
|
|
|
|
var stackURN, provURN, urnA resource.URN = "urn:pulumi:test::test::pulumi:pulumi:Stack::test-test",
|
|
"urn:pulumi:test::test::pulumi:providers:pkgA::provA", "urn:pulumi:test::test::pkgA:m:typA::resA"
|
|
programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
ctx, err := pulumi.NewContext(context.Background(), pulumi.RunInfo{
|
|
Project: info.Project,
|
|
Stack: info.Stack,
|
|
Parallel: info.Parallel,
|
|
DryRun: info.DryRun,
|
|
MonitorAddr: info.MonitorAddress,
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
return pulumi.RunWithContext(ctx, func(ctx *pulumi.Context) error {
|
|
provider := &pulumi.ProviderResourceState{}
|
|
err := ctx.RegisterResource(string(providers.MakeProviderType("pkgA")), "provA", nil, provider)
|
|
assert.NoError(t, err)
|
|
|
|
var res pulumi.CustomResourceState
|
|
err = ctx.RegisterResource("pkgA:m:typA", "resA", inputsA, &res,
|
|
pulumi.Provider(provider), pulumi.DeleteBeforeReplace(getDbr()))
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
})
|
|
})
|
|
|
|
p.Options.HostF = deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
p.Options.T = t
|
|
p.Options.SkipDisplayTests = true
|
|
p.Steps = []lt.TestStep{{Op: Update}}
|
|
snap := p.Run(t, nil)
|
|
|
|
// Change the value of resA.A. Should create before replace
|
|
inputsA.Foo = pulumi.String("bar")
|
|
p.Steps = []lt.TestStep{{
|
|
Op: Update,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
|
evts []Event, err error,
|
|
) error {
|
|
assert.NoError(t, err)
|
|
|
|
AssertSameSteps(t, []StepSummary{
|
|
{Op: deploy.OpSame, URN: stackURN},
|
|
{Op: deploy.OpSame, URN: provURN},
|
|
{Op: deploy.OpCreateReplacement, URN: urnA},
|
|
{Op: deploy.OpReplace, URN: urnA},
|
|
{Op: deploy.OpDeleteReplaced, URN: urnA},
|
|
}, SuccessfulSteps(entries))
|
|
|
|
return err
|
|
},
|
|
}}
|
|
snap = p.Run(t, snap)
|
|
|
|
// Change the registration of resA such that it requires delete-before-replace and change the value of resA.A.
|
|
// replacement should be delete-before-replace.
|
|
dbrA, inputsA.Foo = &dbrValue, pulumi.String("baz")
|
|
p.Steps = []lt.TestStep{{
|
|
Op: Update,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
|
evts []Event, err error,
|
|
) error {
|
|
assert.NoError(t, err)
|
|
AssertSameSteps(t, []StepSummary{
|
|
{Op: deploy.OpSame, URN: stackURN},
|
|
{Op: deploy.OpSame, URN: provURN},
|
|
{Op: deploy.OpDeleteReplaced, URN: urnA},
|
|
{Op: deploy.OpReplace, URN: urnA},
|
|
{Op: deploy.OpCreateReplacement, URN: urnA},
|
|
}, SuccessfulSteps(entries))
|
|
|
|
return err
|
|
},
|
|
}}
|
|
p.Run(t, snap)
|
|
}
|
|
|
|
func TestReadResourceGolangLifecycle(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
ReadF: func(_ context.Context, req plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
assert.Equal(t, resource.ID("someId"), req.ID)
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
Inputs: req.Inputs,
|
|
Outputs: req.State,
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
var stackURN, defaultProviderURN, urnA resource.URN = "urn:pulumi:test::test::pulumi:pulumi:Stack::test-test",
|
|
"urn:pulumi:test::test::pulumi:providers:pkgA::default", "urn:pulumi:test::test::pkgA:m:typA::resA"
|
|
|
|
setupAndRunProgram := func() *deploy.Snapshot {
|
|
programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
ctx, err := pulumi.NewContext(context.Background(), pulumi.RunInfo{
|
|
Project: info.Project,
|
|
Stack: info.Stack,
|
|
Parallel: info.Parallel,
|
|
DryRun: info.DryRun,
|
|
MonitorAddr: info.MonitorAddress,
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
return pulumi.RunWithContext(ctx, func(ctx *pulumi.Context) error {
|
|
var res pulumi.CustomResourceState
|
|
err := ctx.ReadResource("pkgA:m:typA", "resA", pulumi.ID("someId"), nil, &res)
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
})
|
|
})
|
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{T: t, HostF: hostF},
|
|
Steps: []lt.TestStep{
|
|
{
|
|
Op: Update,
|
|
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
|
evts []Event, err error,
|
|
) error {
|
|
assert.NoError(t, err)
|
|
|
|
AssertSameSteps(t, []StepSummary{
|
|
{Op: deploy.OpCreate, URN: stackURN},
|
|
{Op: deploy.OpCreate, URN: defaultProviderURN},
|
|
{Op: deploy.OpRead, URN: urnA},
|
|
}, SuccessfulSteps(entries))
|
|
|
|
return err
|
|
},
|
|
},
|
|
},
|
|
}
|
|
return p.Run(t, nil)
|
|
}
|
|
|
|
setupAndRunProgram()
|
|
}
|
|
|
|
// ensures that RegisterResource, ReadResource (TODO https://github.com/pulumi/pulumi/issues/3562),
|
|
// and Invoke all respect the provider hierarchy
|
|
// most specific providers are used first 1. resource.provider, 2. resource.providers, 3. resource.parent.providers
|
|
func TestProviderInheritanceGolangLifecycle(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
type invokeArgs struct {
|
|
Bang string `pulumi:"bang"`
|
|
Bar string `pulumi:"bar"`
|
|
}
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
v := &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: "created-id",
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
ReadF: func(_ context.Context, req plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
Inputs: req.Inputs,
|
|
Outputs: req.State,
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}
|
|
v.InvokeF = func(_ context.Context, req plugin.InvokeRequest) (plugin.InvokeResponse, error) {
|
|
assert.True(t, v.Config.DeepEquals(req.Args))
|
|
return plugin.InvokeResponse{}, nil
|
|
}
|
|
return v, nil
|
|
}),
|
|
deploytest.NewProviderLoader("pkgB", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
v := &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: "created-id",
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
ReadF: func(_ context.Context, req plugin.ReadRequest) (plugin.ReadResponse, error) {
|
|
return plugin.ReadResponse{
|
|
ReadResult: plugin.ReadResult{
|
|
Inputs: req.Inputs,
|
|
Outputs: req.State,
|
|
},
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}
|
|
v.InvokeF = func(_ context.Context, req plugin.InvokeRequest) (plugin.InvokeResponse, error) {
|
|
assert.True(t, v.Config.DeepEquals(req.Args))
|
|
return plugin.InvokeResponse{}, nil
|
|
}
|
|
return v, nil
|
|
}),
|
|
}
|
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
ctx, err := pulumi.NewContext(context.Background(), pulumi.RunInfo{
|
|
Project: info.Project,
|
|
Stack: info.Stack,
|
|
Parallel: info.Parallel,
|
|
DryRun: info.DryRun,
|
|
MonitorAddr: info.MonitorAddress,
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
return pulumi.RunWithContext(ctx, func(ctx *pulumi.Context) error {
|
|
// register a couple of providers, pass in some props that we can use to indentify it during invoke
|
|
var providerA pulumi.ProviderResourceState
|
|
err := ctx.RegisterResource(string(providers.MakeProviderType("pkgA")), "prov1",
|
|
&testResourceInputs{
|
|
Foo: pulumi.String("1"),
|
|
}, &providerA)
|
|
assert.NoError(t, err)
|
|
var providerB pulumi.ProviderResourceState
|
|
err = ctx.RegisterResource(string(providers.MakeProviderType("pkgB")), "prov2",
|
|
&testResourceInputs{
|
|
Bar: pulumi.String("2"),
|
|
Bang: pulumi.String(""),
|
|
}, &providerB)
|
|
assert.NoError(t, err)
|
|
var providerBOverride pulumi.ProviderResourceState
|
|
err = ctx.RegisterResource(string(providers.MakeProviderType("pkgB")), "prov3",
|
|
&testResourceInputs{
|
|
Bar: pulumi.String(""),
|
|
Bang: pulumi.String("3"),
|
|
}, &providerBOverride)
|
|
assert.NoError(t, err)
|
|
parentProviders := make(map[string]pulumi.ProviderResource)
|
|
parentProviders["pkgA"] = &providerA
|
|
parentProviders["pkgB"] = &providerB
|
|
// create a parent resource that uses provider map
|
|
var parentResource pulumi.CustomResourceState
|
|
err = ctx.RegisterResource("pkgA:m:typA", "resA", nil, &parentResource, pulumi.ProviderMap(parentProviders))
|
|
assert.NoError(t, err)
|
|
// parent uses specified provider from map
|
|
parentResultProvider := parentResource.GetProvider("pkgA:m:typA")
|
|
assert.Equal(t, &providerA, parentResultProvider)
|
|
|
|
// create a child resource
|
|
var childResource pulumi.CustomResourceState
|
|
err = ctx.RegisterResource("pkgB:m:typB", "resBChild", nil, &childResource, pulumi.Parent(&parentResource))
|
|
assert.NoError(t, err)
|
|
|
|
// child uses provider value from parent
|
|
childResultProvider := childResource.GetProvider("pkgB:m:typB")
|
|
assert.Equal(t, &providerB, childResultProvider)
|
|
|
|
// create a child with a provider specified
|
|
var childWithOverride pulumi.CustomResourceState
|
|
err = ctx.RegisterResource("pkgB:m:typB", "resBChildOverride", nil, &childWithOverride,
|
|
pulumi.Parent(&parentResource), pulumi.Provider(&providerBOverride))
|
|
assert.NoError(t, err)
|
|
|
|
// child uses the specified provider, and not the provider from the parent
|
|
childWithOverrideProvider := childWithOverride.GetProvider("pkgB:m:typB")
|
|
assert.Equal(t, &providerBOverride, childWithOverrideProvider)
|
|
|
|
// pass in a fake ID
|
|
testID := pulumi.ID("testID")
|
|
|
|
// read a resource that uses provider map
|
|
var rereadParent pulumi.CustomResourceState
|
|
err = ctx.ReadResource("pkgA:m:typA", "readResA", testID, nil, &rereadParent, pulumi.ProviderMap(parentProviders))
|
|
assert.NoError(t, err)
|
|
// parent uses specified provider from map
|
|
parentResultProvider = rereadParent.GetProvider("pkgA:m:typA")
|
|
assert.Equal(t, &providerA, parentResultProvider)
|
|
|
|
// read a child resource
|
|
var rereadChild pulumi.CustomResourceState
|
|
err = ctx.ReadResource("pkgB:m:typB", "readResBChild", testID, nil, &rereadChild, pulumi.Parent(&parentResource))
|
|
assert.NoError(t, err)
|
|
|
|
// child uses provider value from parent
|
|
childResultProvider = rereadChild.GetProvider("pkgB:m:typB")
|
|
assert.Equal(t, &providerB, childResultProvider)
|
|
|
|
// read a child with a provider specified
|
|
var rereadChildWithOverride pulumi.CustomResourceState
|
|
err = ctx.ReadResource("pkgB:m:typB", "readResBChildOverride", testID, nil, &rereadChildWithOverride,
|
|
pulumi.Parent(&parentResource), pulumi.Provider(&providerBOverride))
|
|
assert.NoError(t, err)
|
|
|
|
// child uses the specified provider, and not the provider from the parent
|
|
childWithOverrideProvider = rereadChildWithOverride.GetProvider("pkgB:m:typB")
|
|
assert.Equal(t, &providerBOverride, childWithOverrideProvider)
|
|
|
|
// invoke with specific provider
|
|
var invokeResult struct{}
|
|
err = ctx.Invoke("pkgB:do:something", invokeArgs{
|
|
Bang: "3",
|
|
}, &invokeResult, pulumi.Provider(&providerBOverride))
|
|
assert.NoError(t, err)
|
|
|
|
// invoke with parent
|
|
err = ctx.Invoke("pkgB:do:something", invokeArgs{
|
|
Bar: "2",
|
|
}, &invokeResult, pulumi.Parent(&parentResource))
|
|
assert.NoError(t, err)
|
|
|
|
// invoke with parent and provider
|
|
err = ctx.Invoke("pkgB:do:something", invokeArgs{
|
|
Bang: "3",
|
|
}, &invokeResult, pulumi.Parent(&parentResource), pulumi.Provider(&providerBOverride))
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
})
|
|
})
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{T: t, HostF: hostF},
|
|
Steps: []lt.TestStep{{Op: Update}},
|
|
}
|
|
p.Run(t, nil)
|
|
}
|
|
|
|
// This test validates the wiring of the ReplaceOnChanges prop in the go SDK.
|
|
func TestReplaceOnChangesGolangLifecycle(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: "created-id",
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
resourceProperties := &testResourceInputs{
|
|
Foo: pulumi.String("bar"),
|
|
}
|
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
ctx, err := pulumi.NewContext(context.Background(), pulumi.RunInfo{
|
|
Project: info.Project,
|
|
Stack: info.Stack,
|
|
Parallel: info.Parallel,
|
|
DryRun: info.DryRun,
|
|
MonitorAddr: info.MonitorAddress,
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
return pulumi.RunWithContext(ctx, func(ctx *pulumi.Context) error {
|
|
var res pulumi.CustomResourceState
|
|
err := ctx.RegisterResource("pkgA:m:typA", "resA", resourceProperties, &res,
|
|
pulumi.ReplaceOnChanges([]string{"foo"}))
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
})
|
|
})
|
|
|
|
expectedOps := []display.StepOp{deploy.OpCreate}
|
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{T: t, HostF: hostF},
|
|
Steps: []lt.TestStep{
|
|
{
|
|
Op: Update,
|
|
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
|
events []Event, err error,
|
|
) error {
|
|
collectedOps := make([]display.StepOp, 0)
|
|
for _, event := range events {
|
|
if event.Type == ResourcePreEvent {
|
|
payload := event.Payload().(ResourcePreEventPayload)
|
|
if payload.Metadata.URN == "urn:pulumi:test::test::pkgA:m:typA::resA" {
|
|
collectedOps = append(collectedOps, payload.Metadata.Op)
|
|
}
|
|
}
|
|
}
|
|
|
|
assert.Equal(t, expectedOps, collectedOps)
|
|
|
|
return err
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
snap := p.Run(t, nil)
|
|
assert.NotNil(t, snap)
|
|
|
|
// Change the property Foo, should now replace
|
|
resourceProperties = &testResourceInputs{
|
|
Foo: pulumi.String("baz"),
|
|
}
|
|
expectedOps = []display.StepOp{deploy.OpCreateReplacement, deploy.OpReplace, deploy.OpDeleteReplaced}
|
|
|
|
snap = p.Run(t, snap)
|
|
assert.NotNil(t, snap)
|
|
}
|
|
|
|
type remoteComponentArgs struct {
|
|
Foo pulumi.URN `pulumi:"foo"`
|
|
Bar *string `pulumi:"bar"`
|
|
}
|
|
|
|
type remoteComponentInputs struct {
|
|
Foo pulumi.URNInput `pulumi:"foo"`
|
|
Bar pulumi.StringPtrInput `pulumi:"bar"`
|
|
}
|
|
|
|
func (*remoteComponentInputs) ElementType() reflect.Type {
|
|
return reflect.TypeOf((*remoteComponentArgs)(nil)).Elem()
|
|
}
|
|
|
|
type remoteComponent struct {
|
|
pulumi.ResourceState
|
|
|
|
Foo pulumi.StringOutput `pulumi:"foo"`
|
|
Baz pulumi.StringOutput `pulumi:"baz"`
|
|
}
|
|
|
|
func TestRemoteComponentGolang(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
CreateF: func(_ context.Context, req plugin.CreateRequest) (plugin.CreateResponse, error) {
|
|
return plugin.CreateResponse{
|
|
ID: "created-id",
|
|
Properties: req.Properties,
|
|
Status: resource.StatusOK,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
deploytest.NewProviderLoader("pkgB", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
return &deploytest.Provider{
|
|
ConstructF: func(
|
|
_ context.Context,
|
|
req plugin.ConstructRequest,
|
|
monitor *deploytest.ResourceMonitor,
|
|
) (plugin.ConstructResult, error) {
|
|
_, ok := req.Inputs["bar"]
|
|
assert.False(t, ok)
|
|
|
|
resp, err := monitor.RegisterResource("pkgB:index:component", "componentA", false)
|
|
require.NoError(t, err)
|
|
|
|
outs := resource.PropertyMap{}
|
|
|
|
err = monitor.RegisterResourceOutputs(resp.URN, outs)
|
|
require.NoError(t, err)
|
|
|
|
return plugin.ConstructResponse{
|
|
URN: resp.URN,
|
|
Outputs: outs,
|
|
}, nil
|
|
},
|
|
}, nil
|
|
}),
|
|
}
|
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
ctx, err := pulumi.NewContext(context.Background(), pulumi.RunInfo{
|
|
Project: info.Project,
|
|
Stack: info.Stack,
|
|
Parallel: info.Parallel,
|
|
DryRun: info.DryRun,
|
|
MonitorAddr: info.MonitorAddress,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
return pulumi.RunWithContext(ctx, func(ctx *pulumi.Context) error {
|
|
var resB pulumi.CustomResourceState
|
|
err := ctx.RegisterResource("pkgA:index:typA", "resA", pulumi.Map{}, &resB)
|
|
require.NoError(t, err)
|
|
|
|
inputs := remoteComponentInputs{
|
|
Foo: resB.URN(),
|
|
}
|
|
|
|
var res remoteComponent
|
|
err = ctx.RegisterRemoteComponentResource("pkgB:index:component", "componentA", &inputs, &res)
|
|
require.NoError(t, err)
|
|
|
|
return nil
|
|
})
|
|
})
|
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
p := <.TestPlan{
|
|
Options: lt.TestUpdateOptions{T: t, HostF: hostF},
|
|
Steps: []lt.TestStep{{Op: Update}},
|
|
}
|
|
p.Run(t, nil)
|
|
}
|