2020-12-15 22:24:46 +00:00
|
|
|
package lifecycletest
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"reflect"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/blang/semver"
|
|
|
|
"github.com/stretchr/testify/assert"
|
2021-08-17 00:01:20 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2020-12-15 22:24:46 +00:00
|
|
|
|
2023-09-18 11:01:28 +00:00
|
|
|
"github.com/pulumi/pulumi/pkg/v3/display"
|
2023-11-21 15:16:13 +00:00
|
|
|
. "github.com/pulumi/pulumi/pkg/v3/engine" //nolint:revive
|
2021-03-17 13:20:05 +00:00
|
|
|
"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/tokens"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
2020-12-15 22:24:46 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
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) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2020-12-15 22:24:46 +00:00
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
|
2023-03-03 16:36:39 +00:00
|
|
|
preview bool,
|
|
|
|
) (resource.ID, resource.PropertyMap, resource.Status, error) {
|
2020-12-15 22:24:46 +00:00
|
|
|
return "created-id", news, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
ReadF: func(urn resource.URN, id resource.ID,
|
2023-03-03 16:36:39 +00:00
|
|
|
inputs, state resource.PropertyMap,
|
|
|
|
) (plugin.ReadResult, resource.Status, error) {
|
2020-12-15 22:24:46 +00:00
|
|
|
return plugin.ReadResult{Inputs: inputs, Outputs: state}, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2020-12-15 22:24:46 +00:00
|
|
|
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
|
|
|
|
})
|
|
|
|
})
|
2023-09-28 21:50:18 +00:00
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
2020-12-15 22:24:46 +00:00
|
|
|
|
|
|
|
p := &TestPlan{
|
2023-09-28 21:50:18 +00:00
|
|
|
Options: TestUpdateOptions{HostF: hostF},
|
2020-12-15 22:24:46 +00:00
|
|
|
Steps: MakeBasicLifecycleSteps(t, 4),
|
|
|
|
}
|
|
|
|
p.Run(t, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Inspired by transformations_test.go.
|
|
|
|
func TestSingleResourceDefaultProviderGolangTransformations(t *testing.T) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2020-12-15 22:24:46 +00:00
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
|
2023-03-03 16:36:39 +00:00
|
|
|
preview bool,
|
|
|
|
) (resource.ID, resource.PropertyMap, resource.Status, error) {
|
2020-12-15 22:24:46 +00:00
|
|
|
return "created-id", news, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
ReadF: func(urn resource.URN, id resource.ID,
|
2023-03-03 16:36:39 +00:00
|
|
|
inputs, state resource.PropertyMap,
|
|
|
|
) (plugin.ReadResult, resource.Status, error) {
|
2020-12-15 22:24:46 +00:00
|
|
|
return plugin.ReadResult{Inputs: inputs, Outputs: state}, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
newResource := func(ctx *pulumi.Context, name string, opts ...pulumi.ResourceOption) error {
|
|
|
|
var res testResource
|
|
|
|
return ctx.RegisterResource("pkgA:m:typA", name, &testResourceInputs{
|
|
|
|
Foo: pulumi.String("bar"),
|
|
|
|
}, &res, opts...)
|
|
|
|
}
|
|
|
|
|
|
|
|
newComponent := func(ctx *pulumi.Context, name string, opts ...pulumi.ResourceOption) error {
|
|
|
|
var res testResource
|
|
|
|
err := ctx.RegisterComponentResource("pkgA:m:typA", name, &res, opts...)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var resChild testResource
|
|
|
|
return ctx.RegisterResource("pkgA:m:typA", name+"Child", &testResourceInputs{
|
|
|
|
Foo: pulumi.String("bar"),
|
|
|
|
}, &resChild, pulumi.Parent(&res))
|
|
|
|
}
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2020-12-15 22:24:46 +00:00
|
|
|
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 {
|
|
|
|
// Scenario #1 - apply a transformation to a CustomResource
|
|
|
|
res1Transformation := func(args *pulumi.ResourceTransformationArgs) *pulumi.ResourceTransformationResult {
|
|
|
|
// TODO[pulumi/pulumi#3846] We should use a mergeOptions-style API here.
|
|
|
|
return &pulumi.ResourceTransformationResult{
|
|
|
|
Props: args.Props,
|
|
|
|
Opts: append(args.Opts, pulumi.AdditionalSecretOutputs([]string{"output"})),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.NoError(t, newResource(ctx, "res1",
|
|
|
|
pulumi.Transformations([]pulumi.ResourceTransformation{res1Transformation})))
|
|
|
|
|
|
|
|
// Scenario #2 - apply a transformation to a Component to transform its children
|
|
|
|
res2Transformation := func(args *pulumi.ResourceTransformationArgs) *pulumi.ResourceTransformationResult {
|
|
|
|
if args.Name == "res2Child" {
|
|
|
|
// TODO[pulumi/pulumi#3846] We should use a mergeOptions-style API here.
|
|
|
|
return &pulumi.ResourceTransformationResult{
|
|
|
|
Props: args.Props,
|
|
|
|
Opts: append(args.Opts, pulumi.AdditionalSecretOutputs([]string{"output", "output2"})),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
assert.NoError(t, newComponent(ctx, "res2",
|
|
|
|
pulumi.Transformations([]pulumi.ResourceTransformation{res2Transformation})))
|
|
|
|
|
|
|
|
// Scenario #3 - apply a transformation to the Stack to transform all (future) resources in the stack
|
|
|
|
res3Transformation := func(args *pulumi.ResourceTransformationArgs) *pulumi.ResourceTransformationResult {
|
|
|
|
// Props might be nil.
|
|
|
|
var props *testResourceInputs
|
|
|
|
if args.Props == nil {
|
|
|
|
props = &testResourceInputs{}
|
|
|
|
} else {
|
|
|
|
props = args.Props.(*testResourceInputs)
|
|
|
|
}
|
|
|
|
props.Foo = pulumi.String("baz")
|
|
|
|
|
|
|
|
return &pulumi.ResourceTransformationResult{
|
|
|
|
Props: props,
|
|
|
|
Opts: args.Opts,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.NoError(t, ctx.RegisterStackTransformation(res3Transformation))
|
|
|
|
assert.NoError(t, newResource(ctx, "res3"))
|
|
|
|
|
|
|
|
// Scenario #4 - transformations are applied in order of decreasing specificity
|
|
|
|
// 1. (not in this example) Child transformation
|
|
|
|
// 2. First parent transformation
|
|
|
|
// 3. Second parent transformation
|
|
|
|
// 4. Stack transformation
|
|
|
|
res4Transformation1 := func(args *pulumi.ResourceTransformationArgs) *pulumi.ResourceTransformationResult {
|
|
|
|
if args.Name == "res4Child" {
|
|
|
|
props := args.Props.(*testResourceInputs)
|
|
|
|
props.Foo = pulumi.String("baz1")
|
|
|
|
|
|
|
|
return &pulumi.ResourceTransformationResult{
|
|
|
|
Props: props,
|
|
|
|
Opts: args.Opts,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
res4Transformation2 := func(args *pulumi.ResourceTransformationArgs) *pulumi.ResourceTransformationResult {
|
|
|
|
if args.Name == "res4Child" {
|
|
|
|
props := args.Props.(*testResourceInputs)
|
|
|
|
props.Foo = pulumi.String("baz2")
|
|
|
|
|
|
|
|
return &pulumi.ResourceTransformationResult{
|
|
|
|
Props: props,
|
|
|
|
Opts: args.Opts,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
assert.NoError(t, newComponent(ctx, "res4",
|
|
|
|
pulumi.Transformations([]pulumi.ResourceTransformation{res4Transformation1, res4Transformation2})))
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
2020-12-15 22:24:46 +00:00
|
|
|
|
|
|
|
p := &TestPlan{
|
2023-09-28 21:50:18 +00:00
|
|
|
Options: TestUpdateOptions{HostF: hostF},
|
2020-12-15 22:24:46 +00:00
|
|
|
}
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
2023-10-11 14:44:09 +00:00
|
|
|
_ []Event, err error,
|
|
|
|
) error {
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2020-12-15 22:24:46 +00:00
|
|
|
foundRes1 := false
|
|
|
|
foundRes2 := false
|
|
|
|
foundRes2Child := false
|
|
|
|
foundRes3 := false
|
|
|
|
foundRes4Child := false
|
|
|
|
// foundRes5Child1 := false
|
[engine] Only record a resource's chosen alias. (#9288)
As we discovered when removing aliases from the state entirely, the
snapshotter needs to be alias-aware so that it can fix up references to
resources that were aliased. After a resource operation finishes, the
snapshotter needs to write out a new copy of the snapshot. However, at
the time we write the snapshot, there may be resources that have not yet
been registered that refer to the just-registered resources by a
different URN due to aliasing. Those references need to be fixed up
prior to writing the snapshot in order to preserve the snapshot's
integrity (in particular, the property that all URNs refer to resources
that exist in the snapshot).
For example, consider the following simple dependency graph: A <-- B.
When that graph is serialized, B will contain a reference to A in its
dependency list. Let the next run of the program produces the graph A'
<-- B where A' is aliased to A. After A' is registered, the snapshotter
needs to write a snapshot that contains its state, but B must also be
updated so it references A' instead of A, which will no longer be in the
snapshot.
These changes take advantage of the fact that although a resource can
provide multiple aliases, it can only ever resolve those aliases to a
single resource in the existing state. Therefore, at the time the
statefile is fixed up, each resource in the statefile could only have
been aliased to a single old resource, and it is sufficient to store
only the URN of the chosen resource rather than all possible aliases. In
addition to preserving the ability to fix up references to aliased
resources, retaining the chosen alias allows the history of a logical
resource to be followed across aliases.
2022-03-28 15:36:08 +00:00
|
|
|
|
|
|
|
snap, err := entries.Snap(target.Snapshot)
|
|
|
|
require.NoError(t, err)
|
|
|
|
for _, res := range snap.Resources {
|
2020-12-15 22:24:46 +00:00
|
|
|
// "res1" has a transformation which adds additionalSecretOutputs
|
|
|
|
if res.URN.Name() == "res1" {
|
|
|
|
foundRes1 = true
|
|
|
|
assert.Equal(t, res.Type, tokens.Type("pkgA:m:typA"))
|
|
|
|
assert.Contains(t, res.AdditionalSecretOutputs, resource.PropertyKey("output"))
|
|
|
|
}
|
|
|
|
// "res2" has a transformation which adds additionalSecretOutputs to it's "child"
|
|
|
|
if res.URN.Name() == "res2" {
|
|
|
|
foundRes2 = true
|
|
|
|
assert.Equal(t, res.Type, tokens.Type("pkgA:m:typA"))
|
|
|
|
assert.NotContains(t, res.AdditionalSecretOutputs, resource.PropertyKey("output"))
|
|
|
|
}
|
|
|
|
if res.URN.Name() == "res2Child" {
|
|
|
|
foundRes2Child = true
|
2023-11-20 08:59:00 +00:00
|
|
|
assert.Equal(t, res.Parent.Name(), "res2")
|
2020-12-15 22:24:46 +00:00
|
|
|
assert.Equal(t, res.Type, tokens.Type("pkgA:m:typA"))
|
|
|
|
assert.Contains(t, res.AdditionalSecretOutputs, resource.PropertyKey("output"))
|
|
|
|
assert.Contains(t, res.AdditionalSecretOutputs, resource.PropertyKey("output2"))
|
|
|
|
}
|
|
|
|
// "res3" is impacted by a global stack transformation which sets
|
|
|
|
// Foo to "baz"
|
|
|
|
if res.URN.Name() == "res3" {
|
|
|
|
foundRes3 = true
|
|
|
|
assert.Equal(t, "baz", res.Inputs["foo"].StringValue())
|
2022-03-24 19:08:18 +00:00
|
|
|
assert.Len(t, res.Aliases, 0)
|
2020-12-15 22:24:46 +00:00
|
|
|
}
|
|
|
|
// "res4" is impacted by two component parent transformations which set
|
|
|
|
// Foo to "baz1" and then "baz2" and also a global stack
|
|
|
|
// transformation which sets optionalDefault to "baz". The end
|
|
|
|
// result should be "baz".
|
|
|
|
if res.URN.Name() == "res4Child" {
|
|
|
|
foundRes4Child = true
|
2023-11-20 08:59:00 +00:00
|
|
|
assert.Equal(t, res.Parent.Name(), "res4")
|
2020-12-15 22:24:46 +00:00
|
|
|
assert.Equal(t, "baz", res.Inputs["foo"].StringValue())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.True(t, foundRes1)
|
|
|
|
assert.True(t, foundRes2)
|
|
|
|
assert.True(t, foundRes2Child)
|
|
|
|
assert.True(t, foundRes3)
|
|
|
|
assert.True(t, foundRes4Child)
|
2023-10-11 14:44:09 +00:00
|
|
|
return err
|
2020-12-15 22:24:46 +00:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
|
|
|
|
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) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2020-12-15 22:24:46 +00:00
|
|
|
var expectedIgnoreChanges []string
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
|
2023-03-03 16:36:39 +00:00
|
|
|
preview bool,
|
|
|
|
) (resource.ID, resource.PropertyMap, resource.Status, error) {
|
2020-12-15 22:24:46 +00:00
|
|
|
return "created-id", news, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
ReadF: func(urn resource.URN, id resource.ID,
|
2023-03-03 16:36:39 +00:00
|
|
|
inputs, state resource.PropertyMap,
|
|
|
|
) (plugin.ReadResult, resource.Status, error) {
|
2020-12-15 22:24:46 +00:00
|
|
|
return plugin.ReadResult{Inputs: inputs, Outputs: state}, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
DiffF: func(urn resource.URN, id resource.ID,
|
2023-05-29 15:41:36 +00:00
|
|
|
oldInputs, oldOutputs, newInputs resource.PropertyMap, ignoreChanges []string,
|
2023-03-03 16:36:39 +00:00
|
|
|
) (plugin.DiffResult, error) {
|
2020-12-15 22:24:46 +00:00
|
|
|
// just verify that the IgnoreChanges prop made it through
|
|
|
|
assert.Equal(t, expectedIgnoreChanges, ignoreChanges)
|
|
|
|
return plugin.DiffResult{}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
setupAndRunProgram := func(ignoreChanges []string) *deploy.Snapshot {
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2020-12-15 22:24:46 +00:00
|
|
|
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
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
2020-12-15 22:24:46 +00:00
|
|
|
p := &TestPlan{
|
2023-09-28 21:50:18 +00:00
|
|
|
Options: TestUpdateOptions{HostF: hostF},
|
2020-12-15 22:24:46 +00:00
|
|
|
Steps: []TestStep{
|
|
|
|
{
|
|
|
|
Op: Update,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
2023-10-11 14:44:09 +00:00
|
|
|
events []Event, err error,
|
|
|
|
) error {
|
2020-12-15 22:24:46 +00:00
|
|
|
for _, event := range events {
|
|
|
|
if event.Type == ResourcePreEvent {
|
|
|
|
payload := event.Payload().(ResourcePreEventPayload)
|
2022-06-27 14:08:06 +00:00
|
|
|
assert.Equal(t, []display.StepOp{deploy.OpCreate}, []display.StepOp{payload.Metadata.Op})
|
2020-12-15 22:24:46 +00:00
|
|
|
}
|
|
|
|
}
|
2023-10-11 14:44:09 +00:00
|
|
|
return err
|
2020-12-15 22:24:46 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
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) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2020-12-15 22:24:46 +00:00
|
|
|
p := &TestPlan{}
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
2023-05-29 15:41:36 +00:00
|
|
|
DiffConfigF: func(urn resource.URN, oldInputs, oldOutputs, newInputs resource.PropertyMap,
|
2023-03-03 16:36:39 +00:00
|
|
|
ignoreChanges []string,
|
|
|
|
) (plugin.DiffResult, error) {
|
2023-05-29 15:41:36 +00:00
|
|
|
if !oldOutputs["foo"].DeepEquals(newInputs["foo"]) {
|
2020-12-15 22:24:46 +00:00
|
|
|
return plugin.DiffResult{
|
|
|
|
ReplaceKeys: []resource.PropertyKey{"foo"},
|
|
|
|
DeleteBeforeReplace: true,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
return plugin.DiffResult{}, nil
|
|
|
|
},
|
|
|
|
DiffF: func(urn resource.URN, id resource.ID,
|
2023-05-29 15:41:36 +00:00
|
|
|
oldInputs, oldOutputs, newInputs resource.PropertyMap, ignoreChanges []string,
|
2023-03-03 16:36:39 +00:00
|
|
|
) (plugin.DiffResult, error) {
|
2023-05-29 15:41:36 +00:00
|
|
|
if !oldOutputs["foo"].DeepEquals(newInputs["foo"]) {
|
2020-12-15 22:24:46 +00:00
|
|
|
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"
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2020-12-15 22:24:46 +00:00
|
|
|
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
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
p.Options.HostF = deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
2020-12-15 22:24:46 +00:00
|
|
|
p.Steps = []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 = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
2023-10-11 14:44:09 +00:00
|
|
|
evts []Event, err error,
|
|
|
|
) error {
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
2020-12-15 22:24:46 +00:00
|
|
|
|
|
|
|
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))
|
|
|
|
|
2023-10-11 14:44:09 +00:00
|
|
|
return err
|
2020-12-15 22:24:46 +00:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
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 = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
2023-10-11 14:44:09 +00:00
|
|
|
evts []Event, err error,
|
|
|
|
) error {
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
2020-12-15 22:24:46 +00:00
|
|
|
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))
|
|
|
|
|
2023-10-11 14:44:09 +00:00
|
|
|
return err
|
2020-12-15 22:24:46 +00:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
p.Run(t, snap)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestReadResourceGolangLifecycle(t *testing.T) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2020-12-15 22:24:46 +00:00
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
ReadF: func(urn resource.URN, id resource.ID,
|
2023-03-03 16:36:39 +00:00
|
|
|
inputs, state resource.PropertyMap,
|
|
|
|
) (plugin.ReadResult, resource.Status, error) {
|
2020-12-15 22:24:46 +00:00
|
|
|
assert.Equal(t, resource.ID("someId"), id)
|
|
|
|
return plugin.ReadResult{Inputs: inputs, Outputs: state}, 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 {
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2020-12-15 22:24:46 +00:00
|
|
|
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
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
2020-12-15 22:24:46 +00:00
|
|
|
p := &TestPlan{
|
2023-09-28 21:50:18 +00:00
|
|
|
Options: TestUpdateOptions{HostF: hostF},
|
2020-12-15 22:24:46 +00:00
|
|
|
Steps: []TestStep{
|
|
|
|
{
|
|
|
|
Op: Update,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
2023-10-11 14:44:09 +00:00
|
|
|
evts []Event, err error,
|
|
|
|
) error {
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
2020-12-15 22:24:46 +00:00
|
|
|
|
|
|
|
AssertSameSteps(t, []StepSummary{
|
|
|
|
{Op: deploy.OpCreate, URN: stackURN},
|
|
|
|
{Op: deploy.OpCreate, URN: defaultProviderURN},
|
|
|
|
{Op: deploy.OpRead, URN: urnA},
|
|
|
|
}, SuccessfulSteps(entries))
|
|
|
|
|
2023-10-11 14:44:09 +00:00
|
|
|
return err
|
2020-12-15 22:24:46 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
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) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2020-12-15 22:24:46 +00:00
|
|
|
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(urn resource.URN, news resource.PropertyMap, timeout float64,
|
2023-03-03 16:36:39 +00:00
|
|
|
preview bool,
|
|
|
|
) (resource.ID, resource.PropertyMap, resource.Status, error) {
|
2020-12-15 22:24:46 +00:00
|
|
|
return "created-id", news, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
ReadF: func(urn resource.URN, id resource.ID,
|
2023-03-03 16:36:39 +00:00
|
|
|
inputs, state resource.PropertyMap,
|
|
|
|
) (plugin.ReadResult, resource.Status, error) {
|
2020-12-15 22:24:46 +00:00
|
|
|
return plugin.ReadResult{Inputs: inputs, Outputs: state}, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
v.InvokeF = func(tok tokens.ModuleMember,
|
2023-03-03 16:36:39 +00:00
|
|
|
inputs resource.PropertyMap,
|
|
|
|
) (resource.PropertyMap, []plugin.CheckFailure, error) {
|
2020-12-15 22:24:46 +00:00
|
|
|
assert.True(t, v.Config.DeepEquals(inputs))
|
|
|
|
return nil, nil, nil
|
|
|
|
}
|
|
|
|
return v, nil
|
|
|
|
}),
|
|
|
|
deploytest.NewProviderLoader("pkgB", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
v := &deploytest.Provider{
|
|
|
|
CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
|
2023-03-03 16:36:39 +00:00
|
|
|
preview bool,
|
|
|
|
) (resource.ID, resource.PropertyMap, resource.Status, error) {
|
2020-12-15 22:24:46 +00:00
|
|
|
return "created-id", news, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
ReadF: func(urn resource.URN, id resource.ID,
|
2023-03-03 16:36:39 +00:00
|
|
|
inputs, state resource.PropertyMap,
|
|
|
|
) (plugin.ReadResult, resource.Status, error) {
|
2020-12-15 22:24:46 +00:00
|
|
|
return plugin.ReadResult{Inputs: inputs, Outputs: state}, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
v.InvokeF = func(tok tokens.ModuleMember,
|
2023-03-03 16:36:39 +00:00
|
|
|
inputs resource.PropertyMap,
|
|
|
|
) (resource.PropertyMap, []plugin.CheckFailure, error) {
|
2020-12-15 22:24:46 +00:00
|
|
|
assert.True(t, v.Config.DeepEquals(inputs))
|
|
|
|
return nil, nil, nil
|
|
|
|
}
|
|
|
|
return v, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2020-12-15 22:24:46 +00:00
|
|
|
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
|
2022-07-12 16:39:07 +00:00
|
|
|
var rereadParent pulumi.CustomResourceState
|
|
|
|
err = ctx.ReadResource("pkgA:m:typA", "readResA", testID, nil, &rereadParent, pulumi.ProviderMap(parentProviders))
|
2020-12-15 22:24:46 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
// parent uses specified provider from map
|
2022-07-12 16:39:07 +00:00
|
|
|
parentResultProvider = rereadParent.GetProvider("pkgA:m:typA")
|
2020-12-15 22:24:46 +00:00
|
|
|
assert.Equal(t, &providerA, parentResultProvider)
|
|
|
|
|
|
|
|
// read a child resource
|
2022-07-12 16:39:07 +00:00
|
|
|
var rereadChild pulumi.CustomResourceState
|
|
|
|
err = ctx.ReadResource("pkgB:m:typB", "readResBChild", testID, nil, &rereadChild, pulumi.Parent(&parentResource))
|
2020-12-15 22:24:46 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
// child uses provider value from parent
|
2022-07-12 16:39:07 +00:00
|
|
|
childResultProvider = rereadChild.GetProvider("pkgB:m:typB")
|
2020-12-15 22:24:46 +00:00
|
|
|
assert.Equal(t, &providerB, childResultProvider)
|
|
|
|
|
|
|
|
// read a child with a provider specified
|
2022-07-12 16:39:07 +00:00
|
|
|
var rereadChildWithOverride pulumi.CustomResourceState
|
|
|
|
err = ctx.ReadResource("pkgB:m:typB", "readResBChildOverride", testID, nil, &rereadChildWithOverride,
|
2020-12-15 22:24:46 +00:00
|
|
|
pulumi.Parent(&parentResource), pulumi.Provider(&providerBOverride))
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
// child uses the specified provider, and not the provider from the parent
|
2022-07-12 16:39:07 +00:00
|
|
|
childWithOverrideProvider = rereadChildWithOverride.GetProvider("pkgB:m:typB")
|
2020-12-15 22:24:46 +00:00
|
|
|
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
|
|
|
|
})
|
|
|
|
})
|
2023-09-28 21:50:18 +00:00
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
2020-12-15 22:24:46 +00:00
|
|
|
|
|
|
|
p := &TestPlan{
|
2023-09-28 21:50:18 +00:00
|
|
|
Options: TestUpdateOptions{HostF: hostF},
|
2020-12-15 22:24:46 +00:00
|
|
|
Steps: []TestStep{{Op: Update}},
|
|
|
|
}
|
|
|
|
p.Run(t, nil)
|
|
|
|
}
|
2021-07-01 19:32:08 +00:00
|
|
|
|
|
|
|
// This test validates the wiring of the ReplaceOnChanges prop in the go SDK.
|
|
|
|
func TestReplaceOnChangesGolangLifecycle(t *testing.T) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2021-07-01 19:32:08 +00:00
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
|
2023-03-03 16:36:39 +00:00
|
|
|
preview bool,
|
|
|
|
) (resource.ID, resource.PropertyMap, resource.Status, error) {
|
2021-07-01 19:32:08 +00:00
|
|
|
return "created-id", news, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
2022-04-19 09:16:10 +00:00
|
|
|
resourceProperties := &testResourceInputs{
|
|
|
|
Foo: pulumi.String("bar"),
|
|
|
|
}
|
2021-07-01 19:32:08 +00:00
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2022-04-19 09:16:10 +00:00
|
|
|
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)
|
2021-07-01 19:32:08 +00:00
|
|
|
|
2022-04-19 09:16:10 +00:00
|
|
|
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
|
2021-07-01 19:32:08 +00:00
|
|
|
})
|
2022-04-19 09:16:10 +00:00
|
|
|
})
|
2021-07-01 19:32:08 +00:00
|
|
|
|
2022-06-27 14:08:06 +00:00
|
|
|
expectedOps := []display.StepOp{deploy.OpCreate}
|
2022-04-19 09:16:10 +00:00
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
2022-04-19 09:16:10 +00:00
|
|
|
p := &TestPlan{
|
2023-09-28 21:50:18 +00:00
|
|
|
Options: TestUpdateOptions{HostF: hostF},
|
2022-04-19 09:16:10 +00:00
|
|
|
Steps: []TestStep{
|
|
|
|
{
|
|
|
|
Op: Update,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
2023-10-11 14:44:09 +00:00
|
|
|
events []Event, err error,
|
|
|
|
) error {
|
2022-06-27 14:08:06 +00:00
|
|
|
collectedOps := make([]display.StepOp, 0)
|
2022-04-19 09:16:10 +00:00
|
|
|
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)
|
2021-07-01 19:32:08 +00:00
|
|
|
}
|
|
|
|
}
|
2022-04-19 09:16:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
assert.Equal(t, expectedOps, collectedOps)
|
|
|
|
|
2023-10-11 14:44:09 +00:00
|
|
|
return err
|
2021-07-01 19:32:08 +00:00
|
|
|
},
|
|
|
|
},
|
2022-04-19 09:16:10 +00:00
|
|
|
},
|
2021-07-01 19:32:08 +00:00
|
|
|
}
|
|
|
|
|
2022-04-19 09:16:10 +00:00
|
|
|
snap := p.Run(t, nil)
|
|
|
|
assert.NotNil(t, snap)
|
|
|
|
|
|
|
|
// Change the property Foo, should now replace
|
|
|
|
resourceProperties = &testResourceInputs{
|
|
|
|
Foo: pulumi.String("baz"),
|
|
|
|
}
|
2022-06-27 14:08:06 +00:00
|
|
|
expectedOps = []display.StepOp{deploy.OpCreateReplacement, deploy.OpReplace, deploy.OpDeleteReplaced}
|
2021-07-01 19:32:08 +00:00
|
|
|
|
2022-04-19 09:16:10 +00:00
|
|
|
snap = p.Run(t, snap)
|
|
|
|
assert.NotNil(t, snap)
|
2021-07-01 19:32:08 +00:00
|
|
|
}
|
2021-08-17 00:01:20 +00:00
|
|
|
|
|
|
|
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) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2021-08-17 00:01:20 +00:00
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
|
2023-03-03 16:36:39 +00:00
|
|
|
preview bool,
|
|
|
|
) (resource.ID, resource.PropertyMap, resource.Status, error) {
|
2021-08-17 00:01:20 +00:00
|
|
|
return "created-id", news, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
deploytest.NewProviderLoader("pkgB", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
ConstructF: func(monitor *deploytest.ResourceMonitor, typ, name string, parent resource.URN,
|
2023-07-25 08:03:46 +00:00
|
|
|
inputs resource.PropertyMap, info plugin.ConstructInfo, options plugin.ConstructOptions,
|
2023-03-03 16:36:39 +00:00
|
|
|
) (plugin.ConstructResult, error) {
|
2021-08-17 00:01:20 +00:00
|
|
|
_, ok := inputs["bar"]
|
|
|
|
assert.False(t, ok)
|
|
|
|
|
|
|
|
urn, _, _, err := monitor.RegisterResource("pkgB:index:component", "componentA", false)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
outs := resource.PropertyMap{}
|
|
|
|
|
|
|
|
err = monitor.RegisterResourceOutputs(urn, outs)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
return plugin.ConstructResult{
|
|
|
|
URN: urn,
|
|
|
|
Outputs: outs,
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2021-08-17 00:01:20 +00:00
|
|
|
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
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
2021-08-17 00:01:20 +00:00
|
|
|
|
|
|
|
p := &TestPlan{
|
2023-09-28 21:50:18 +00:00
|
|
|
Options: TestUpdateOptions{HostF: hostF},
|
2021-08-17 00:01:20 +00:00
|
|
|
Steps: []TestStep{{Op: Update}},
|
|
|
|
}
|
|
|
|
p.Run(t, nil)
|
|
|
|
}
|