2020-12-15 22:24:46 +00:00
|
|
|
package lifecycletest
|
|
|
|
|
|
|
|
import (
|
2023-10-11 14:44:09 +00:00
|
|
|
"fmt"
|
2021-07-28 19:12:53 +00:00
|
|
|
"sync"
|
2020-12-15 22:24:46 +00:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/blang/semver"
|
2021-07-28 19:12:53 +00:00
|
|
|
"github.com/gofrs/uuid"
|
2020-12-15 22:24:46 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
2021-07-28 19:12:53 +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/config"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
|
2023-03-13 16:01:20 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
|
2021-03-17 13:20:05 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
|
2020-12-15 22:24:46 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestSingleResourceDefaultProviderLifecycle(t *testing.T) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
Reuse provider instances where possible (#14127)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/13987.
This reworks the registry to better track provider instances such that
we can reuse unconfigured instances between Creates, Updates, and Sames.
When we allocate a provider instance in the registry for a Check call we
save it with the special id "unconfigured". This value should never make
its way back to program SDKs, it's purely an internal value for the
engine.
When we do a Create, Update or Same we look to see if there's an
unconfigured provider to use and if so configures that one, else it
starts up a fresh one. (N.B. Update we can assume there will always be
an unconfigured one from the Check call before).
This has also fixed registry Create to use the ID `UnknownID` rather
than `""`, have added some contract assertions to check that and fixed
up some test fallout because of that (the tests had been getting away
with leaving ID blank before).
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-10-12 20:46:01 +00:00
|
|
|
startupCount := 0
|
2020-12-15 22:24:46 +00:00
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
Reuse provider instances where possible (#14127)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/13987.
This reworks the registry to better track provider instances such that
we can reuse unconfigured instances between Creates, Updates, and Sames.
When we allocate a provider instance in the registry for a Check call we
save it with the special id "unconfigured". This value should never make
its way back to program SDKs, it's purely an internal value for the
engine.
When we do a Create, Update or Same we look to see if there's an
unconfigured provider to use and if so configures that one, else it
starts up a fresh one. (N.B. Update we can assume there will always be
an unconfigured one from the Check call before).
This has also fixed registry Create to use the ID `UnknownID` rather
than `""`, have added some contract assertions to check that and fixed
up some test fallout because of that (the tests had been getting away
with leaving ID blank before).
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-10-12 20:46:01 +00:00
|
|
|
startupCount++
|
2020-12-15 22:24:46 +00:00
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
2020-12-15 22:24:46 +00:00
|
|
|
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, 2),
|
|
|
|
}
|
|
|
|
p.Run(t, nil)
|
Reuse provider instances where possible (#14127)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
Fixes https://github.com/pulumi/pulumi/issues/13987.
This reworks the registry to better track provider instances such that
we can reuse unconfigured instances between Creates, Updates, and Sames.
When we allocate a provider instance in the registry for a Check call we
save it with the special id "unconfigured". This value should never make
its way back to program SDKs, it's purely an internal value for the
engine.
When we do a Create, Update or Same we look to see if there's an
unconfigured provider to use and if so configures that one, else it
starts up a fresh one. (N.B. Update we can assume there will always be
an unconfigured one from the Check call before).
This has also fixed registry Create to use the ID `UnknownID` rather
than `""`, have added some contract assertions to check that and fixed
up some test fallout because of that (the tests had been getting away
with leaving ID blank before).
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2023-10-12 20:46:01 +00:00
|
|
|
|
|
|
|
// We should have started the provider 10 times, twice for each of the steps in the basic lifecycle (one preview,
|
|
|
|
// one up), but zero for the last refresh step where the provider is not needed.
|
|
|
|
assert.Equal(t, 10, startupCount)
|
2020-12-15 22:24:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestSingleResourceExplicitProviderLifecycle(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{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
provURN, provID, _, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true)
|
2020-12-15 22:24:46 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
if provID == "" {
|
|
|
|
provID = providers.UnknownID
|
|
|
|
}
|
|
|
|
|
|
|
|
provRef, err := providers.NewReference(provURN, provID)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
2020-12-15 22:24:46 +00:00
|
|
|
Provider: provRef.String(),
|
|
|
|
})
|
|
|
|
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, 2),
|
|
|
|
}
|
|
|
|
p.Run(t, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSingleResourceDefaultProviderUpgrade(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{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
2020-12-15 22:24:46 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
provURN := p.NewProviderURN("pkgA", "default", "")
|
|
|
|
resURN := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
|
|
|
|
// Create an old snapshot with an existing copy of the single resource and no providers.
|
|
|
|
old := &deploy.Snapshot{
|
|
|
|
Resources: []*resource.State{{
|
|
|
|
Type: resURN.Type(),
|
|
|
|
URN: resURN,
|
|
|
|
Custom: true,
|
|
|
|
ID: "0",
|
|
|
|
Inputs: resource.PropertyMap{},
|
|
|
|
Outputs: resource.PropertyMap{},
|
|
|
|
}},
|
|
|
|
}
|
|
|
|
|
|
|
|
isRefresh := false
|
|
|
|
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
|
|
|
// Should see only sames: the default provider should be injected into the old state before the update
|
|
|
|
// runs.
|
|
|
|
for _, entry := range entries {
|
|
|
|
switch urn := entry.Step.URN(); urn {
|
|
|
|
case provURN, resURN:
|
|
|
|
expect := deploy.OpSame
|
|
|
|
if isRefresh {
|
|
|
|
expect = deploy.OpRefresh
|
|
|
|
}
|
|
|
|
assert.Equal(t, expect, entry.Step.Op())
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
}
|
[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)
|
|
|
|
assert.Len(t, snap.Resources, 2)
|
2023-10-11 14:44:09 +00:00
|
|
|
return err
|
2020-12-15 22:24:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Run a single update step using the base snapshot.
|
|
|
|
p.Steps = []TestStep{{Op: Update, Validate: validate}}
|
|
|
|
p.Run(t, old)
|
|
|
|
|
|
|
|
// Run a single refresh step using the base snapshot.
|
|
|
|
isRefresh = true
|
|
|
|
p.Steps = []TestStep{{Op: Refresh, Validate: validate}}
|
|
|
|
p.Run(t, old)
|
|
|
|
|
|
|
|
// Run a single destroy step using the base snapshot.
|
|
|
|
isRefresh = false
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Destroy,
|
|
|
|
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
|
|
|
// Should see two deletes: the default provider should be injected into the old state before the update
|
|
|
|
// runs.
|
|
|
|
deleted := make(map[resource.URN]bool)
|
|
|
|
for _, entry := range entries {
|
|
|
|
switch urn := entry.Step.URN(); urn {
|
|
|
|
case provURN, resURN:
|
|
|
|
deleted[urn] = true
|
|
|
|
assert.Equal(t, deploy.OpDelete, entry.Step.Op())
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.Len(t, deleted, 2)
|
[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)
|
|
|
|
assert.Len(t, snap.Resources, 0)
|
2023-10-11 14:44:09 +00:00
|
|
|
return err
|
2020-12-15 22:24:46 +00:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
p.Run(t, old)
|
|
|
|
|
|
|
|
// Run a partial lifecycle using the base snapshot, skipping the initial update step.
|
|
|
|
p.Steps = MakeBasicLifecycleSteps(t, 2)[1:]
|
|
|
|
p.Run(t, old)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSingleResourceDefaultProviderReplace(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{
|
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) {
|
2020-12-15 22:24:46 +00:00
|
|
|
// Always require replacement.
|
|
|
|
keys := []resource.PropertyKey{}
|
2023-05-29 15:41:36 +00:00
|
|
|
for k := range newInputs {
|
2020-12-15 22:24:46 +00:00
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
return plugin.DiffResult{ReplaceKeys: keys}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
2020-12-15 22:24:46 +00:00
|
|
|
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
|
|
|
Config: config.Map{
|
|
|
|
config.MustMakeKey("pkgA", "foo"): config.NewValue("bar"),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build a basic lifecycle.
|
|
|
|
steps := MakeBasicLifecycleSteps(t, 2)
|
|
|
|
|
|
|
|
// Run the lifecycle through its no-op update+refresh.
|
|
|
|
p.Steps = steps[:4]
|
|
|
|
snap := p.Run(t, nil)
|
|
|
|
|
|
|
|
// Change the config and run an update. We expect everything to require replacement.
|
|
|
|
p.Config[config.MustMakeKey("pkgA", "foo")] = config.NewValue("baz")
|
|
|
|
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 {
|
2020-12-15 22:24:46 +00:00
|
|
|
provURN := p.NewProviderURN("pkgA", "default", "")
|
|
|
|
resURN := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
|
|
|
|
// Look for replace steps on the provider and the resource.
|
|
|
|
replacedProvider, replacedResource := false, false
|
|
|
|
for _, entry := range entries {
|
|
|
|
if entry.Kind != JournalEntrySuccess || entry.Step.Op() != deploy.OpDeleteReplaced {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
switch urn := entry.Step.URN(); urn {
|
|
|
|
case provURN:
|
|
|
|
replacedProvider = true
|
|
|
|
case resURN:
|
|
|
|
replacedResource = true
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.True(t, replacedProvider)
|
|
|
|
assert.True(t, replacedResource)
|
|
|
|
|
2023-10-11 14:44:09 +00:00
|
|
|
return err
|
2020-12-15 22:24:46 +00:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
|
|
|
|
snap = p.Run(t, snap)
|
|
|
|
|
|
|
|
// Resume the lifecycle with another no-op update.
|
|
|
|
p.Steps = steps[2:]
|
|
|
|
p.Run(t, snap)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSingleResourceExplicitProviderReplace(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{
|
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) {
|
2020-12-15 22:24:46 +00:00
|
|
|
// Always require replacement.
|
|
|
|
keys := []resource.PropertyKey{}
|
2023-05-29 15:41:36 +00:00
|
|
|
for k := range newInputs {
|
2020-12-15 22:24:46 +00:00
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
return plugin.DiffResult{ReplaceKeys: keys}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
providerInputs := resource.PropertyMap{
|
|
|
|
resource.PropertyKey("foo"): resource.NewStringProperty("bar"),
|
|
|
|
}
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
provURN, provID, _, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true,
|
2020-12-15 22:24:46 +00:00
|
|
|
deploytest.ResourceOptions{Inputs: providerInputs})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
if provID == "" {
|
|
|
|
provID = providers.UnknownID
|
|
|
|
}
|
|
|
|
|
|
|
|
provRef, err := providers.NewReference(provURN, provID)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
2020-12-15 22:24:46 +00:00
|
|
|
Provider: provRef.String(),
|
|
|
|
})
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
// Build a basic lifecycle.
|
|
|
|
steps := MakeBasicLifecycleSteps(t, 2)
|
|
|
|
|
|
|
|
// Run the lifecycle through its no-op update+refresh.
|
|
|
|
p.Steps = steps[:4]
|
|
|
|
snap := p.Run(t, nil)
|
|
|
|
|
|
|
|
// Change the config and run an update. We expect everything to require replacement.
|
|
|
|
providerInputs[resource.PropertyKey("foo")] = resource.NewStringProperty("baz")
|
|
|
|
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 {
|
2020-12-15 22:24:46 +00:00
|
|
|
provURN := p.NewProviderURN("pkgA", "provA", "")
|
|
|
|
resURN := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
|
|
|
|
// Look for replace steps on the provider and the resource.
|
|
|
|
replacedProvider, replacedResource := false, false
|
|
|
|
for _, entry := range entries {
|
|
|
|
if entry.Kind != JournalEntrySuccess || entry.Step.Op() != deploy.OpDeleteReplaced {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
switch urn := entry.Step.URN(); urn {
|
|
|
|
case provURN:
|
|
|
|
replacedProvider = true
|
|
|
|
case resURN:
|
|
|
|
replacedResource = true
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.True(t, replacedProvider)
|
|
|
|
assert.True(t, replacedResource)
|
|
|
|
|
2023-10-11 14:44:09 +00:00
|
|
|
return err
|
2020-12-15 22:24:46 +00:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
snap = p.Run(t, snap)
|
|
|
|
|
|
|
|
// Resume the lifecycle with another no-op update.
|
|
|
|
p.Steps = steps[2:]
|
|
|
|
p.Run(t, snap)
|
|
|
|
}
|
|
|
|
|
2021-07-28 19:12:53 +00:00
|
|
|
type configurableProvider struct {
|
|
|
|
id string
|
|
|
|
replace bool
|
|
|
|
creates *sync.Map
|
|
|
|
deletes *sync.Map
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *configurableProvider) configure(news resource.PropertyMap) error {
|
|
|
|
p.id = news["id"].StringValue()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *configurableProvider) create(urn resource.URN, inputs resource.PropertyMap, timeout float64,
|
2023-03-03 16:36:39 +00:00
|
|
|
preview bool,
|
|
|
|
) (resource.ID, resource.PropertyMap, resource.Status, error) {
|
2021-07-28 19:12:53 +00:00
|
|
|
uid, err := uuid.NewV4()
|
|
|
|
if err != nil {
|
|
|
|
return "", nil, resource.StatusUnknown, err
|
|
|
|
}
|
|
|
|
id := resource.ID(uid.String())
|
|
|
|
|
|
|
|
p.creates.Store(id, p.id)
|
|
|
|
return id, inputs, resource.StatusOK, nil
|
|
|
|
}
|
|
|
|
|
2023-10-13 14:12:26 +00:00
|
|
|
func (p *configurableProvider) delete(urn resource.URN, id resource.ID, oldInputs, oldOutputs resource.PropertyMap,
|
2023-03-03 16:36:39 +00:00
|
|
|
timeout float64,
|
|
|
|
) (resource.Status, error) {
|
2021-07-28 19:12:53 +00:00
|
|
|
p.deletes.Store(id, p.id)
|
|
|
|
return resource.StatusOK, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestSingleResourceExplicitProviderAliasUpdateDelete verifies that providers respect aliases during updates, and
|
|
|
|
// that the correct instance of an explicit provider is used to delete a removed resource.
|
|
|
|
func TestSingleResourceExplicitProviderAliasUpdateDelete(t *testing.T) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2021-07-28 19:12:53 +00:00
|
|
|
var creates, deletes sync.Map
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
configurable := &configurableProvider{
|
|
|
|
creates: &creates,
|
|
|
|
deletes: &deletes,
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2021-07-28 19:12:53 +00:00
|
|
|
return plugin.DiffResult{}, nil
|
|
|
|
},
|
|
|
|
ConfigureF: configurable.configure,
|
|
|
|
CreateF: configurable.create,
|
|
|
|
DeleteF: configurable.delete,
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
providerInputs := resource.PropertyMap{
|
|
|
|
resource.PropertyKey("id"): resource.NewStringProperty("first"),
|
|
|
|
}
|
|
|
|
providerName := "provA"
|
|
|
|
aliases := []resource.URN{}
|
|
|
|
registerResource := true
|
|
|
|
var resourceID resource.ID
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
provURN, provID, _, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), providerName, true,
|
2021-07-28 19:12:53 +00:00
|
|
|
deploytest.ResourceOptions{
|
2022-09-22 17:13:55 +00:00
|
|
|
Inputs: providerInputs,
|
2022-09-21 19:42:24 +00:00
|
|
|
AliasURNs: aliases,
|
2021-07-28 19:12:53 +00:00
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
if provID == "" {
|
|
|
|
provID = providers.UnknownID
|
|
|
|
}
|
|
|
|
|
|
|
|
provRef, err := providers.NewReference(provURN, provID)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
if registerResource {
|
2024-02-08 13:01:47 +00:00
|
|
|
_, resourceID, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
2021-07-28 19:12:53 +00:00
|
|
|
Provider: provRef.String(),
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
2023-09-28 21:50:18 +00:00
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
2021-07-28 19:12:53 +00:00
|
|
|
|
|
|
|
p := &TestPlan{
|
2023-09-28 21:50:18 +00:00
|
|
|
Options: TestUpdateOptions{HostF: hostF},
|
2021-07-28 19:12:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Build a basic lifecycle.
|
|
|
|
steps := MakeBasicLifecycleSteps(t, 2)
|
|
|
|
|
|
|
|
// Run the lifecycle through its initial update+refresh.
|
|
|
|
p.Steps = steps[:4]
|
|
|
|
snap := p.Run(t, nil)
|
|
|
|
|
|
|
|
// Add a provider alias to the original URN.
|
|
|
|
aliases = []resource.URN{
|
|
|
|
p.NewProviderURN("pkgA", "provA", ""),
|
|
|
|
}
|
|
|
|
// Change the provider name and configuration and remove the resource. This will cause an Update for the provider
|
|
|
|
// and a Delete for the resource. The updated provider instance should be used to perform the delete.
|
|
|
|
providerName = "provB"
|
|
|
|
providerInputs[resource.PropertyKey("id")] = resource.NewStringProperty("second")
|
|
|
|
registerResource = false
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{Op: Update}}
|
|
|
|
_ = p.Run(t, snap)
|
|
|
|
|
|
|
|
// Check the identity of the provider that performed the delete.
|
|
|
|
deleterID, ok := deletes.Load(resourceID)
|
|
|
|
require.True(t, ok)
|
|
|
|
assert.Equal(t, "second", deleterID)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestSingleResourceExplicitProviderAliasReplace verifies that providers respect aliases,
|
|
|
|
// and propagate replaces as a result of an aliased provider diff.
|
|
|
|
func TestSingleResourceExplicitProviderAliasReplace(t *testing.T) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2021-07-28 19:12:53 +00:00
|
|
|
var creates, deletes sync.Map
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
configurable := &configurableProvider{
|
|
|
|
replace: true,
|
|
|
|
creates: &creates,
|
|
|
|
deletes: &deletes,
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2021-07-28 19:12:53 +00:00
|
|
|
keys := []resource.PropertyKey{}
|
2023-05-29 15:41:36 +00:00
|
|
|
for k := range newInputs {
|
2021-07-28 19:12:53 +00:00
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
return plugin.DiffResult{ReplaceKeys: keys}, nil
|
|
|
|
},
|
|
|
|
ConfigureF: configurable.configure,
|
|
|
|
CreateF: configurable.create,
|
|
|
|
DeleteF: configurable.delete,
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
providerInputs := resource.PropertyMap{
|
|
|
|
resource.PropertyKey("id"): resource.NewStringProperty("first"),
|
|
|
|
}
|
|
|
|
providerName := "provA"
|
|
|
|
aliases := []resource.URN{}
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
provURN, provID, _, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), providerName, true,
|
2021-07-28 19:12:53 +00:00
|
|
|
deploytest.ResourceOptions{
|
2022-09-22 17:13:55 +00:00
|
|
|
Inputs: providerInputs,
|
2022-09-21 19:42:24 +00:00
|
|
|
AliasURNs: aliases,
|
2021-07-28 19:12:53 +00:00
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
if provID == "" {
|
|
|
|
provID = providers.UnknownID
|
|
|
|
}
|
|
|
|
|
|
|
|
provRef, err := providers.NewReference(provURN, provID)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
2021-07-28 19:12:53 +00:00
|
|
|
Provider: provRef.String(),
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
2023-09-28 21:50:18 +00:00
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
2021-07-28 19:12:53 +00:00
|
|
|
|
|
|
|
p := &TestPlan{
|
2023-09-28 21:50:18 +00:00
|
|
|
Options: TestUpdateOptions{HostF: hostF},
|
2021-07-28 19:12:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Build a basic lifecycle.
|
|
|
|
steps := MakeBasicLifecycleSteps(t, 2)
|
|
|
|
|
|
|
|
// Run the lifecycle through its no-op update+refresh.
|
|
|
|
p.Steps = steps[:4]
|
|
|
|
snap := p.Run(t, nil)
|
|
|
|
|
|
|
|
// add a provider alias to the original URN
|
|
|
|
aliases = []resource.URN{
|
|
|
|
p.NewProviderURN("pkgA", "provA", ""),
|
|
|
|
}
|
|
|
|
// change the provider name
|
|
|
|
providerName = "provB"
|
|
|
|
// run an update expecting no-op respecting the aliases.
|
|
|
|
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 {
|
2021-07-28 19:12:53 +00:00
|
|
|
for _, entry := range entries {
|
|
|
|
if entry.Step.Op() != deploy.OpSame {
|
|
|
|
t.Fatalf("update should contain no changes: %v", entry.Step.URN())
|
|
|
|
}
|
|
|
|
}
|
2023-10-11 14:44:09 +00:00
|
|
|
return err
|
2021-07-28 19:12:53 +00:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
snap = p.Run(t, snap)
|
|
|
|
|
|
|
|
// Change the config and run an update maintaining the alias. We expect everything to require replacement.
|
|
|
|
providerInputs[resource.PropertyKey("id")] = resource.NewStringProperty("second")
|
|
|
|
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 {
|
2021-07-28 19:12:53 +00:00
|
|
|
provURN := p.NewProviderURN("pkgA", providerName, "")
|
|
|
|
resURN := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
|
|
|
|
// Find the delete and create IDs for the resource.
|
|
|
|
var createdID, deletedID resource.ID
|
|
|
|
|
|
|
|
// Look for replace steps on the provider and the resource.
|
|
|
|
replacedProvider, replacedResource := false, false
|
|
|
|
for _, entry := range entries {
|
|
|
|
op := entry.Step.Op()
|
|
|
|
|
|
|
|
if entry.Step.URN() == resURN {
|
|
|
|
switch op {
|
|
|
|
case deploy.OpCreateReplacement:
|
|
|
|
createdID = entry.Step.New().ID
|
|
|
|
case deploy.OpDeleteReplaced:
|
|
|
|
deletedID = entry.Step.Old().ID
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if entry.Kind != JournalEntrySuccess || op != deploy.OpDeleteReplaced {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
switch urn := entry.Step.URN(); urn {
|
|
|
|
case provURN:
|
|
|
|
replacedProvider = true
|
|
|
|
case resURN:
|
|
|
|
replacedResource = true
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.True(t, replacedProvider)
|
|
|
|
assert.True(t, replacedResource)
|
|
|
|
|
|
|
|
// Check the identities of the providers that performed the create and delete.
|
|
|
|
//
|
|
|
|
// For a replacement, the newly-created provider should be used to create the new resource, and the original
|
|
|
|
// provider should be used to delete the old resource.
|
|
|
|
creatorID, ok := creates.Load(createdID)
|
|
|
|
require.True(t, ok)
|
|
|
|
assert.Equal(t, "second", creatorID)
|
|
|
|
|
|
|
|
deleterID, ok := deletes.Load(deletedID)
|
|
|
|
require.True(t, ok)
|
|
|
|
assert.Equal(t, "first", deleterID)
|
|
|
|
|
2023-10-11 14:44:09 +00:00
|
|
|
return err
|
2021-07-28 19:12:53 +00:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
snap = p.Run(t, snap)
|
|
|
|
|
|
|
|
// Resume the lifecycle with another no-op update.
|
|
|
|
p.Steps = steps[2:]
|
|
|
|
p.Run(t, snap)
|
|
|
|
}
|
|
|
|
|
2020-12-15 22:24:46 +00:00
|
|
|
func TestSingleResourceExplicitProviderDeleteBeforeReplace(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{
|
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) {
|
2020-12-15 22:24:46 +00:00
|
|
|
// Always require replacement.
|
|
|
|
keys := []resource.PropertyKey{}
|
2023-05-29 15:41:36 +00:00
|
|
|
for k := range newInputs {
|
2020-12-15 22:24:46 +00:00
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
return plugin.DiffResult{ReplaceKeys: keys, DeleteBeforeReplace: true}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
providerInputs := resource.PropertyMap{
|
|
|
|
resource.PropertyKey("foo"): resource.NewStringProperty("bar"),
|
|
|
|
}
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
provURN, provID, _, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true,
|
2020-12-15 22:24:46 +00:00
|
|
|
deploytest.ResourceOptions{Inputs: providerInputs})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
if provID == "" {
|
|
|
|
provID = providers.UnknownID
|
|
|
|
}
|
|
|
|
|
|
|
|
provRef, err := providers.NewReference(provURN, provID)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
2020-12-15 22:24:46 +00:00
|
|
|
Provider: provRef.String(),
|
|
|
|
})
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
// Build a basic lifecycle.
|
|
|
|
steps := MakeBasicLifecycleSteps(t, 2)
|
|
|
|
|
|
|
|
// Run the lifecycle through its no-op update+refresh.
|
|
|
|
p.Steps = steps[:4]
|
|
|
|
snap := p.Run(t, nil)
|
|
|
|
|
|
|
|
// Change the config and run an update. We expect everything to require replacement.
|
|
|
|
providerInputs[resource.PropertyKey("foo")] = resource.NewStringProperty("baz")
|
|
|
|
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 {
|
2020-12-15 22:24:46 +00:00
|
|
|
provURN := p.NewProviderURN("pkgA", "provA", "")
|
|
|
|
resURN := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
|
|
|
|
// Look for replace steps on the provider and the resource.
|
|
|
|
createdProvider, createdResource := false, false
|
|
|
|
deletedProvider, deletedResource := false, false
|
|
|
|
for _, entry := range entries {
|
|
|
|
if entry.Kind != JournalEntrySuccess {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
switch urn := entry.Step.URN(); urn {
|
|
|
|
case provURN:
|
|
|
|
if entry.Step.Op() == deploy.OpDeleteReplaced {
|
|
|
|
assert.False(t, createdProvider)
|
|
|
|
assert.False(t, createdResource)
|
|
|
|
assert.True(t, deletedResource)
|
|
|
|
deletedProvider = true
|
|
|
|
} else if entry.Step.Op() == deploy.OpCreateReplacement {
|
|
|
|
assert.True(t, deletedProvider)
|
|
|
|
assert.True(t, deletedResource)
|
|
|
|
assert.False(t, createdResource)
|
|
|
|
createdProvider = true
|
|
|
|
}
|
|
|
|
case resURN:
|
|
|
|
if entry.Step.Op() == deploy.OpDeleteReplaced {
|
|
|
|
assert.False(t, deletedProvider)
|
|
|
|
assert.False(t, deletedResource)
|
|
|
|
deletedResource = true
|
|
|
|
} else if entry.Step.Op() == deploy.OpCreateReplacement {
|
|
|
|
assert.True(t, deletedProvider)
|
|
|
|
assert.True(t, deletedResource)
|
|
|
|
assert.True(t, createdProvider)
|
|
|
|
createdResource = true
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.True(t, deletedProvider)
|
|
|
|
assert.True(t, deletedResource)
|
|
|
|
|
2023-10-11 14:44:09 +00:00
|
|
|
return err
|
2020-12-15 22:24:46 +00:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
snap = p.Run(t, snap)
|
|
|
|
|
|
|
|
// Resume the lifecycle with another no-op update.
|
|
|
|
p.Steps = steps[2:]
|
|
|
|
p.Run(t, snap)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestDefaultProviderDiff tests that the engine can gracefully recover whenever a resource's default provider changes
|
|
|
|
// and there is no diff in the provider's inputs.
|
|
|
|
func TestDefaultProviderDiff(t *testing.T) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2020-12-15 22:24:46 +00:00
|
|
|
const resName, resBName = "resA", "resB"
|
2023-04-12 09:35:20 +00:00
|
|
|
expect1710 := true
|
2020-12-15 22:24:46 +00:00
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("0.17.10"), func() (plugin.Provider, error) {
|
2023-04-12 09:35:20 +00:00
|
|
|
// If we don't expect to load this assert if called
|
|
|
|
if !expect1710 {
|
|
|
|
assert.Fail(t, "unexpected call to 0.17.10 provider")
|
|
|
|
}
|
2020-12-15 22:24:46 +00:00
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("0.17.11"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("0.17.12"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
2022-06-27 14:08:06 +00:00
|
|
|
runProgram := func(base *deploy.Snapshot, versionA, versionB string, expectedStep display.StepOp) *deploy.Snapshot {
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", resName, true, deploytest.ResourceOptions{
|
2020-12-15 22:24:46 +00:00
|
|
|
Version: versionA,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", resBName, true, deploytest.ResourceOptions{
|
2020-12-15 22:24:46 +00:00
|
|
|
Version: versionB,
|
|
|
|
})
|
|
|
|
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 _, entry := range entries {
|
|
|
|
if entry.Kind != JournalEntrySuccess {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-11-20 08:59:00 +00:00
|
|
|
switch entry.Step.URN().Name() {
|
2020-12-15 22:24:46 +00:00
|
|
|
case resName, resBName:
|
|
|
|
assert.Equal(t, expectedStep, entry.Step.Op())
|
|
|
|
}
|
|
|
|
}
|
2023-10-11 14:44:09 +00:00
|
|
|
return err
|
2020-12-15 22:24:46 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return p.Run(t, base)
|
|
|
|
}
|
|
|
|
|
|
|
|
// This test simulates the upgrade scenario of old-style default providers to new-style versioned default providers.
|
|
|
|
//
|
|
|
|
// The first update creates a stack using a language host that does not report a version to the engine. As a result,
|
|
|
|
// the engine makes up a default provider for "pkgA" and calls it "default". It then creates the two resources that
|
|
|
|
// we are creating and associates them with the default provider.
|
|
|
|
snap := runProgram(nil, "", "", deploy.OpCreate)
|
|
|
|
for _, res := range snap.Resources {
|
|
|
|
switch {
|
|
|
|
case providers.IsDefaultProvider(res.URN):
|
2023-11-20 08:59:00 +00:00
|
|
|
assert.Equal(t, "default", res.URN.Name())
|
|
|
|
case res.URN.Name() == resName || res.URN.Name() == resBName:
|
2020-12-15 22:24:46 +00:00
|
|
|
provRef, err := providers.ParseReference(res.Provider)
|
|
|
|
assert.NoError(t, err)
|
2023-11-20 08:59:00 +00:00
|
|
|
assert.Equal(t, "default", provRef.URN().Name())
|
2020-12-15 22:24:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The second update switches to a language host that does report a version to the engine. As a result, the engine
|
|
|
|
// uses this version to make a new provider, with a different URN, and uses that provider to operate on resA and
|
|
|
|
// resB.
|
|
|
|
//
|
|
|
|
// Despite switching out the provider, the engine should still generate a Same step for resA. It is vital that the
|
|
|
|
// engine gracefully react to changes in the default provider in this manner. See pulumi/pulumi#2753 for what
|
|
|
|
// happens when it doesn't.
|
|
|
|
snap = runProgram(snap, "0.17.10", "0.17.10", deploy.OpSame)
|
|
|
|
for _, res := range snap.Resources {
|
|
|
|
switch {
|
|
|
|
case providers.IsDefaultProvider(res.URN):
|
2023-11-20 08:59:00 +00:00
|
|
|
assert.Equal(t, "default_0_17_10", res.URN.Name())
|
|
|
|
case res.URN.Name() == resName || res.URN.Name() == resBName:
|
2020-12-15 22:24:46 +00:00
|
|
|
provRef, err := providers.ParseReference(res.Provider)
|
|
|
|
assert.NoError(t, err)
|
2023-11-20 08:59:00 +00:00
|
|
|
assert.Equal(t, "default_0_17_10", provRef.URN().Name())
|
2020-12-15 22:24:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The third update changes the version that the language host reports to the engine. This simulates a scenario in
|
|
|
|
// which a user updates their SDK to a new version of a provider package. In order to simulate side-by-side
|
|
|
|
// packages with different versions, this update requests distinct package versions for resA and resB.
|
2023-04-12 09:35:20 +00:00
|
|
|
expect1710 = false
|
2020-12-15 22:24:46 +00:00
|
|
|
snap = runProgram(snap, "0.17.11", "0.17.12", deploy.OpSame)
|
|
|
|
for _, res := range snap.Resources {
|
|
|
|
switch {
|
|
|
|
case providers.IsDefaultProvider(res.URN):
|
2023-11-20 08:59:00 +00:00
|
|
|
assert.True(t, res.URN.Name() == "default_0_17_11" || res.URN.Name() == "default_0_17_12")
|
|
|
|
case res.URN.Name() == resName:
|
2020-12-15 22:24:46 +00:00
|
|
|
provRef, err := providers.ParseReference(res.Provider)
|
|
|
|
assert.NoError(t, err)
|
2023-11-20 08:59:00 +00:00
|
|
|
assert.Equal(t, "default_0_17_11", provRef.URN().Name())
|
|
|
|
case res.URN.Name() == resBName:
|
2020-12-15 22:24:46 +00:00
|
|
|
provRef, err := providers.ParseReference(res.Provider)
|
|
|
|
assert.NoError(t, err)
|
2023-11-20 08:59:00 +00:00
|
|
|
assert.Equal(t, "default_0_17_12", provRef.URN().Name())
|
2020-12-15 22:24:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestDefaultProviderDiffReplacement tests that, when replacing a default provider for a resource, the engine will
|
|
|
|
// replace the resource if DiffConfig on the new provider returns a diff for the provider's new state.
|
|
|
|
func TestDefaultProviderDiffReplacement(t *testing.T) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2020-12-15 22:24:46 +00:00
|
|
|
const resName, resBName = "resA", "resB"
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("0.17.10"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
// This implementation of DiffConfig always requests replacement.
|
2023-05-29 15:41:36 +00:00
|
|
|
DiffConfigF: func(_ resource.URN, oldInputs, oldOutputs, newInputs resource.PropertyMap,
|
2023-03-03 16:36:39 +00:00
|
|
|
ignoreChanges []string,
|
|
|
|
) (plugin.DiffResult, error) {
|
2020-12-15 22:24:46 +00:00
|
|
|
keys := []resource.PropertyKey{}
|
2023-05-29 15:41:36 +00:00
|
|
|
for k := range newInputs {
|
2020-12-15 22:24:46 +00:00
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
return plugin.DiffResult{
|
|
|
|
Changes: plugin.DiffSome,
|
|
|
|
ReplaceKeys: keys,
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("0.17.11"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
2022-06-27 14:08:06 +00:00
|
|
|
runProgram := func(base *deploy.Snapshot, versionA, versionB string,
|
2023-03-03 16:36:39 +00:00
|
|
|
expectedSteps ...display.StepOp,
|
|
|
|
) *deploy.Snapshot {
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", resName, true, deploytest.ResourceOptions{
|
2020-12-15 22:24:46 +00:00
|
|
|
Version: versionA,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", resBName, true, deploytest.ResourceOptions{
|
2020-12-15 22:24:46 +00:00
|
|
|
Version: versionB,
|
|
|
|
})
|
|
|
|
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 _, entry := range entries {
|
|
|
|
if entry.Kind != JournalEntrySuccess {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-11-20 08:59:00 +00:00
|
|
|
switch entry.Step.URN().Name() {
|
2020-12-15 22:24:46 +00:00
|
|
|
case resName:
|
2022-06-27 14:08:06 +00:00
|
|
|
assert.Subset(t, expectedSteps, []display.StepOp{entry.Step.Op()})
|
2020-12-15 22:24:46 +00:00
|
|
|
case resBName:
|
|
|
|
assert.Subset(t,
|
2022-06-27 14:08:06 +00:00
|
|
|
[]display.StepOp{deploy.OpCreate, deploy.OpSame}, []display.StepOp{entry.Step.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, base)
|
|
|
|
}
|
|
|
|
|
|
|
|
// This test simulates the upgrade scenario of default providers, except that the requested upgrade results in the
|
|
|
|
// provider getting replaced. Because of this, the engine should decide to replace resA. It should not decide to
|
|
|
|
// replace resB, as its change does not require replacement.
|
|
|
|
snap := runProgram(nil, "", "", deploy.OpCreate)
|
|
|
|
for _, res := range snap.Resources {
|
|
|
|
switch {
|
|
|
|
case providers.IsDefaultProvider(res.URN):
|
2023-11-20 08:59:00 +00:00
|
|
|
assert.Equal(t, "default", res.URN.Name())
|
|
|
|
case res.URN.Name() == resName || res.URN.Name() == resBName:
|
2020-12-15 22:24:46 +00:00
|
|
|
provRef, err := providers.ParseReference(res.Provider)
|
|
|
|
assert.NoError(t, err)
|
2023-11-20 08:59:00 +00:00
|
|
|
assert.Equal(t, "default", provRef.URN().Name())
|
2020-12-15 22:24:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Upon update, now that the language host is sending a version, DiffConfig reports that there's a diff between the
|
|
|
|
// old and new provider and so we must replace resA.
|
|
|
|
snap = runProgram(snap, "0.17.10", "0.17.11", deploy.OpCreateReplacement, deploy.OpReplace, deploy.OpDeleteReplaced)
|
|
|
|
for _, res := range snap.Resources {
|
|
|
|
switch {
|
|
|
|
case providers.IsDefaultProvider(res.URN):
|
2023-11-20 08:59:00 +00:00
|
|
|
assert.True(t, res.URN.Name() == "default_0_17_10" || res.URN.Name() == "default_0_17_11")
|
|
|
|
case res.URN.Name() == resName:
|
2020-12-15 22:24:46 +00:00
|
|
|
provRef, err := providers.ParseReference(res.Provider)
|
|
|
|
assert.NoError(t, err)
|
2023-11-20 08:59:00 +00:00
|
|
|
assert.Equal(t, "default_0_17_10", provRef.URN().Name())
|
|
|
|
case res.URN.Name() == resBName:
|
2020-12-15 22:24:46 +00:00
|
|
|
provRef, err := providers.ParseReference(res.Provider)
|
|
|
|
assert.NoError(t, err)
|
2023-11-20 08:59:00 +00:00
|
|
|
assert.Equal(t, "default_0_17_11", provRef.URN().Name())
|
2020-12-15 22:24:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-01-05 23:57:11 +00:00
|
|
|
|
|
|
|
func TestProviderVersionDefault(t *testing.T) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2021-01-05 23:57:11 +00:00
|
|
|
version := ""
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
version = "1.0.0"
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.5.0"), func() (plugin.Provider, error) {
|
|
|
|
version = "1.5.0"
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
provURN, provID, _, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true)
|
2021-01-05 23:57:11 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
if provID == "" {
|
|
|
|
provID = providers.UnknownID
|
|
|
|
}
|
|
|
|
|
|
|
|
provRef, err := providers.NewReference(provURN, provID)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
2021-01-05 23:57:11 +00:00
|
|
|
Provider: provRef.String(),
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
2023-09-28 21:50:18 +00:00
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
2021-01-05 23:57:11 +00:00
|
|
|
|
|
|
|
p := &TestPlan{
|
2023-09-28 21:50:18 +00:00
|
|
|
Options: TestUpdateOptions{HostF: hostF},
|
2021-01-05 23:57:11 +00:00
|
|
|
Steps: MakeBasicLifecycleSteps(t, 2),
|
|
|
|
}
|
|
|
|
p.Run(t, nil)
|
|
|
|
|
|
|
|
assert.Equal(t, "1.5.0", version)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestProviderVersionOption(t *testing.T) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2021-01-05 23:57:11 +00:00
|
|
|
version := ""
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
version = "1.0.0"
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.5.0"), func() (plugin.Provider, error) {
|
|
|
|
version = "1.5.0"
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
provURN, provID, _, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true,
|
2021-01-05 23:57:11 +00:00
|
|
|
deploytest.ResourceOptions{
|
|
|
|
Version: "1.0.0",
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
if provID == "" {
|
|
|
|
provID = providers.UnknownID
|
|
|
|
}
|
|
|
|
|
|
|
|
provRef, err := providers.NewReference(provURN, provID)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
2021-01-05 23:57:11 +00:00
|
|
|
Provider: provRef.String(),
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
2023-09-28 21:50:18 +00:00
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
2021-01-05 23:57:11 +00:00
|
|
|
|
|
|
|
p := &TestPlan{
|
2023-09-28 21:50:18 +00:00
|
|
|
Options: TestUpdateOptions{HostF: hostF},
|
2021-01-05 23:57:11 +00:00
|
|
|
Steps: MakeBasicLifecycleSteps(t, 2),
|
|
|
|
}
|
|
|
|
p.Run(t, nil)
|
|
|
|
|
|
|
|
assert.Equal(t, "1.0.0", version)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestProviderVersionInput(t *testing.T) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2021-01-05 23:57:11 +00:00
|
|
|
version := ""
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
version = "1.0.0"
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.5.0"), func() (plugin.Provider, error) {
|
|
|
|
version = "1.5.0"
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
provURN, provID, _, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true,
|
2021-01-05 23:57:11 +00:00
|
|
|
deploytest.ResourceOptions{
|
|
|
|
Inputs: resource.PropertyMap{
|
|
|
|
"version": resource.NewStringProperty("1.0.0"),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
if provID == "" {
|
|
|
|
provID = providers.UnknownID
|
|
|
|
}
|
|
|
|
|
|
|
|
provRef, err := providers.NewReference(provURN, provID)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
2021-01-05 23:57:11 +00:00
|
|
|
Provider: provRef.String(),
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
2023-09-28 21:50:18 +00:00
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
2021-01-05 23:57:11 +00:00
|
|
|
|
|
|
|
p := &TestPlan{
|
2023-09-28 21:50:18 +00:00
|
|
|
Options: TestUpdateOptions{HostF: hostF},
|
2021-01-05 23:57:11 +00:00
|
|
|
Steps: MakeBasicLifecycleSteps(t, 2),
|
|
|
|
}
|
|
|
|
p.Run(t, nil)
|
|
|
|
|
|
|
|
assert.Equal(t, "1.0.0", version)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestProviderVersionInputAndOption(t *testing.T) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2021-01-05 23:57:11 +00:00
|
|
|
version := ""
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
version = "1.0.0"
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.5.0"), func() (plugin.Provider, error) {
|
|
|
|
version = "1.5.0"
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
provURN, provID, _, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true,
|
2021-01-05 23:57:11 +00:00
|
|
|
deploytest.ResourceOptions{
|
|
|
|
Inputs: resource.PropertyMap{
|
|
|
|
"version": resource.NewStringProperty("1.5.0"),
|
|
|
|
},
|
|
|
|
Version: "1.0.0",
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
if provID == "" {
|
|
|
|
provID = providers.UnknownID
|
|
|
|
}
|
|
|
|
|
|
|
|
provRef, err := providers.NewReference(provURN, provID)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
2021-01-05 23:57:11 +00:00
|
|
|
Provider: provRef.String(),
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
2023-09-28 21:50:18 +00:00
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
2021-01-05 23:57:11 +00:00
|
|
|
|
|
|
|
p := &TestPlan{
|
2023-09-28 21:50:18 +00:00
|
|
|
Options: TestUpdateOptions{HostF: hostF},
|
2021-01-05 23:57:11 +00:00
|
|
|
Steps: MakeBasicLifecycleSteps(t, 2),
|
|
|
|
}
|
|
|
|
p.Run(t, nil)
|
|
|
|
|
|
|
|
assert.Equal(t, "1.0.0", version)
|
|
|
|
}
|
2021-12-17 22:52:01 +00:00
|
|
|
|
|
|
|
func TestPluginDownloadURLPassthrough(t *testing.T) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2021-12-17 22:52:01 +00:00
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
pkgAPluginDownloadURL := "get.pulumi.com/${VERSION}"
|
|
|
|
pkgAType := providers.MakeProviderType("pkgA")
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
provURN, provID, _, _, err := monitor.RegisterResource(pkgAType, "provA", true, deploytest.ResourceOptions{
|
2021-12-17 22:52:01 +00:00
|
|
|
PluginDownloadURL: pkgAPluginDownloadURL,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
if provID == "" {
|
|
|
|
provID = providers.UnknownID
|
|
|
|
}
|
|
|
|
|
|
|
|
provRef, err := providers.NewReference(provURN, provID)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
2021-12-17 22:52:01 +00:00
|
|
|
Provider: provRef.String(),
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
2023-09-28 21:50:18 +00:00
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
2021-12-17 22:52:01 +00:00
|
|
|
|
|
|
|
steps := MakeBasicLifecycleSteps(t, 2)
|
|
|
|
steps[0].ValidateAnd(func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
2023-10-11 14:44:09 +00:00
|
|
|
_ []Event, err error,
|
|
|
|
) error {
|
2021-12-17 22:52:01 +00:00
|
|
|
for _, e := range entries {
|
|
|
|
r := e.Step.New()
|
|
|
|
if r.Type == pkgAType && r.Inputs["pluginDownloadURL"].StringValue() != pkgAPluginDownloadURL {
|
2023-10-11 14:44:09 +00:00
|
|
|
return fmt.Errorf("Found unexpected value %v", r.Inputs["pluginDownloadURL"])
|
2021-12-17 22:52:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
p := &TestPlan{
|
2023-09-28 21:50:18 +00:00
|
|
|
Options: TestUpdateOptions{HostF: hostF},
|
2021-12-17 22:52:01 +00:00
|
|
|
Steps: steps,
|
|
|
|
}
|
|
|
|
p.Run(t, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that creating a resource with pluginDownloadURL set will instantiate a default provider with
|
|
|
|
// pluginDownloadURL set.
|
|
|
|
func TestPluginDownloadURLDefaultProvider(t *testing.T) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2021-12-17 22:52:01 +00:00
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
url := "get.pulumi.com"
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err := monitor.RegisterResource("pkgA::Foo", "foo", true, deploytest.ResourceOptions{
|
2021-12-17 22:52:01 +00:00
|
|
|
PluginDownloadURL: url,
|
|
|
|
})
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
|
|
|
|
snapshot := (&TestPlan{
|
2023-09-28 21:50:18 +00:00
|
|
|
Options: TestUpdateOptions{HostF: deploytest.NewPluginHostF(nil, nil, programF, loaders...)},
|
2021-12-17 22:52:01 +00:00
|
|
|
// The first step is the update. We don't want the full lifecycle because we want to see the
|
|
|
|
// created resources.
|
|
|
|
Steps: MakeBasicLifecycleSteps(t, 2)[:1],
|
|
|
|
}).Run(t, nil)
|
|
|
|
|
|
|
|
foundDefaultProvider := false
|
|
|
|
for _, r := range snapshot.Resources {
|
|
|
|
if providers.IsDefaultProvider(r.URN) {
|
|
|
|
actualURL, err := providers.GetProviderDownloadURL(r.Inputs)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, url, actualURL)
|
|
|
|
foundDefaultProvider = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.Truef(t, foundDefaultProvider, "Found resources: %#v", snapshot.Resources)
|
|
|
|
}
|
2022-01-26 17:08:36 +00:00
|
|
|
|
|
|
|
func TestMultipleResourceDenyDefaultProviderLifecycle(t *testing.T) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2022-01-26 17:08:36 +00:00
|
|
|
cases := []struct {
|
|
|
|
name string
|
|
|
|
f deploytest.ProgramFunc
|
|
|
|
disabled string
|
|
|
|
expectFail bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "default-blocked",
|
|
|
|
f: func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
2022-01-26 17:08:36 +00:00
|
|
|
assert.NoError(t, err)
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err = monitor.RegisterResource("pkgB:m:typB", "resB", true)
|
2022-01-26 17:08:36 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
disabled: `["pkgA"]`,
|
|
|
|
expectFail: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "explicit-not-blocked",
|
|
|
|
f: func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
provURN, provID, _, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true)
|
2022-01-26 17:08:36 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
provRef, err := providers.NewReference(provURN, provID)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
2022-01-26 17:08:36 +00:00
|
|
|
Provider: provRef.String(),
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err = monitor.RegisterResource("pkgB:m:typB", "resB", true)
|
2022-01-26 17:08:36 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
disabled: `["pkgA"]`,
|
|
|
|
expectFail: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "wildcard",
|
|
|
|
f: func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
2022-01-26 17:08:36 +00:00
|
|
|
assert.NoError(t, err)
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err = monitor.RegisterResource("pkgB:m:typB", "resB", true)
|
2022-01-26 17:08:36 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
disabled: `["*"]`,
|
|
|
|
expectFail: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range cases {
|
2022-03-04 08:17:41 +00:00
|
|
|
tt := tt
|
2022-01-26 17:08:36 +00:00
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
2022-03-04 08:17:41 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2022-01-26 17:08:36 +00:00
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
deploytest.NewProviderLoader("pkgB", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(tt.f)
|
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
2022-01-26 17:08:36 +00:00
|
|
|
|
|
|
|
c := config.Map{}
|
|
|
|
k := config.MustMakeKey("pulumi", "disable-default-providers")
|
|
|
|
c[k] = config.NewValue(tt.disabled)
|
|
|
|
|
|
|
|
expectedCreated := 4
|
|
|
|
if tt.expectFail {
|
|
|
|
expectedCreated = 0
|
|
|
|
}
|
|
|
|
update := MakeBasicLifecycleSteps(t, expectedCreated)[:1]
|
|
|
|
update[0].ExpectFailure = tt.expectFail
|
|
|
|
p := &TestPlan{
|
2023-09-28 21:50:18 +00:00
|
|
|
Options: TestUpdateOptions{HostF: hostF},
|
2022-01-26 17:08:36 +00:00
|
|
|
Steps: update,
|
|
|
|
Config: c,
|
|
|
|
}
|
|
|
|
p.Run(t, nil)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2022-06-15 20:03:11 +00:00
|
|
|
|
|
|
|
func TestProviderVersionAssignment(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
prog := func(opts ...deploytest.ResourceOptions) deploytest.ProgramFunc {
|
|
|
|
return func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err := monitor.RegisterResource("pkgA:r:typA", "resA", true, opts...)
|
2022-06-15 20:03:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err = monitor.RegisterResource("pulumi:providers:pkgA", "provA", true, opts...)
|
2022-06-15 20:03:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cases := []struct {
|
|
|
|
name string
|
2022-08-26 14:51:14 +00:00
|
|
|
plugins []workspace.PluginSpec
|
2022-06-15 20:03:11 +00:00
|
|
|
snapshot *deploy.Snapshot
|
|
|
|
validate func(t *testing.T, r *resource.State)
|
|
|
|
versions []string
|
|
|
|
prog deploytest.ProgramFunc
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "empty",
|
|
|
|
versions: []string{"1.0.0"},
|
|
|
|
validate: func(*testing.T, *resource.State) {},
|
|
|
|
prog: prog(),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "default-version",
|
|
|
|
versions: []string{"1.0.0", "1.1.0"},
|
2022-08-26 14:51:14 +00:00
|
|
|
plugins: []workspace.PluginSpec{{
|
2022-06-15 20:03:11 +00:00
|
|
|
Name: "pkgA",
|
|
|
|
Version: &semver.Version{Major: 1, Minor: 1},
|
|
|
|
PluginDownloadURL: "example.com/default",
|
|
|
|
Kind: workspace.ResourcePlugin,
|
|
|
|
}},
|
|
|
|
validate: func(t *testing.T, r *resource.State) {
|
|
|
|
if providers.IsProviderType(r.Type) && !providers.IsDefaultProvider(r.URN) {
|
|
|
|
assert.Equal(t, r.Inputs["version"].StringValue(), "1.1.0")
|
|
|
|
assert.Equal(t, r.Inputs["pluginDownloadURL"].StringValue(), "example.com/default")
|
|
|
|
}
|
|
|
|
},
|
|
|
|
prog: prog(),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "specified-provider",
|
|
|
|
versions: []string{"1.0.0", "1.1.0"},
|
2022-08-26 14:51:14 +00:00
|
|
|
plugins: []workspace.PluginSpec{{
|
2022-06-15 20:03:11 +00:00
|
|
|
Name: "pkgA",
|
|
|
|
Version: &semver.Version{Major: 1, Minor: 1},
|
|
|
|
Kind: workspace.ResourcePlugin,
|
|
|
|
}},
|
|
|
|
validate: func(t *testing.T, r *resource.State) {
|
|
|
|
if providers.IsProviderType(r.Type) && !providers.IsDefaultProvider(r.URN) {
|
|
|
|
_, hasVersion := r.Inputs["version"]
|
|
|
|
assert.False(t, hasVersion)
|
|
|
|
assert.Equal(t, r.Inputs["pluginDownloadURL"].StringValue(), "example.com/download")
|
|
|
|
}
|
|
|
|
},
|
|
|
|
prog: prog(deploytest.ResourceOptions{PluginDownloadURL: "example.com/download"}),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "higher-in-snapshot",
|
|
|
|
versions: []string{"1.3.0", "1.1.0"},
|
|
|
|
prog: prog(),
|
2022-08-26 14:51:14 +00:00
|
|
|
plugins: []workspace.PluginSpec{{
|
2022-06-15 20:03:11 +00:00
|
|
|
Name: "pkgA",
|
|
|
|
Version: &semver.Version{Major: 1, Minor: 1},
|
|
|
|
Kind: workspace.ResourcePlugin,
|
|
|
|
}},
|
|
|
|
snapshot: &deploy.Snapshot{
|
|
|
|
Resources: []*resource.State{
|
|
|
|
{
|
|
|
|
Type: "providers:pulumi:pkgA",
|
|
|
|
URN: "this:is:a:urn::ofaei",
|
|
|
|
Inputs: map[resource.PropertyKey]resource.PropertyValue{
|
|
|
|
"version": resource.NewPropertyValue("1.3.0"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
validate: func(t *testing.T, r *resource.State) {
|
|
|
|
if providers.IsProviderType(r.Type) && !providers.IsDefaultProvider(r.URN) {
|
|
|
|
assert.Equal(t, r.Inputs["version"].StringValue(), "1.1.0")
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, c := range cases {
|
|
|
|
c := c
|
|
|
|
t.Run(c.name, func(t *testing.T) {
|
|
|
|
t.Parallel()
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(c.prog, c.plugins...)
|
2022-06-15 20:03:11 +00:00
|
|
|
loaders := []*deploytest.ProviderLoader{}
|
|
|
|
for _, v := range c.versions {
|
|
|
|
loaders = append(loaders,
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse(v), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}))
|
|
|
|
}
|
2023-09-28 21:50:18 +00:00
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
2022-06-15 20:03:11 +00:00
|
|
|
|
|
|
|
update := []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 {
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-15 20:03:11 +00:00
|
|
|
snap, err := entries.Snap(target.Snapshot)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Len(t, snap.Resources, 3)
|
|
|
|
for _, r := range snap.Resources {
|
|
|
|
c.validate(t, r)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}}}
|
|
|
|
|
|
|
|
p := &TestPlan{
|
2023-09-28 21:50:18 +00:00
|
|
|
Options: TestUpdateOptions{HostF: hostF},
|
2022-06-15 20:03:11 +00:00
|
|
|
Steps: update,
|
|
|
|
}
|
|
|
|
p.Run(t, &deploy.Snapshot{})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2023-03-13 16:01:20 +00:00
|
|
|
|
|
|
|
// TestDeletedWithOptionInheritance checks that a resource that sets its parent to another resource inherits
|
|
|
|
// that resource's DeletedWith option.
|
|
|
|
func TestDeletedWithOptionInheritance(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
2023-07-18 15:10:23 +00:00
|
|
|
expectedUrn := resource.CreateURN("expect-this", "pkg:index:type", "", "project", "stack")
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
parentUrn, _, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
2023-07-18 15:10:23 +00:00
|
|
|
DeletedWith: expectedUrn,
|
2023-03-13 16:01:20 +00:00
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
|
2023-03-13 16:01:20 +00:00
|
|
|
Parent: parentUrn,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffF: func(
|
2023-05-29 15:41:36 +00:00
|
|
|
urn resource.URN, id resource.ID, oldInputs, oldOutputs, newInputs resource.PropertyMap, ignoreChanges []string,
|
2023-03-13 16:01:20 +00:00
|
|
|
) (plugin.DiffResult, error) {
|
|
|
|
return plugin.DiffResult{}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
2023-03-13 16:01:20 +00:00
|
|
|
|
|
|
|
p := &TestPlan{
|
2023-09-28 21:50:18 +00:00
|
|
|
Options: TestUpdateOptions{HostF: hostF},
|
2023-03-13 16:01:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
project := p.GetProject()
|
2023-10-11 14:44:09 +00:00
|
|
|
snap, err := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
|
2023-03-13 16:01:20 +00:00
|
|
|
for _, res := range snap.Resources[1:] {
|
2023-07-18 15:10:23 +00:00
|
|
|
assert.Equal(t, expectedUrn, res.DeletedWith)
|
2023-03-13 16:01:20 +00:00
|
|
|
}
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
2023-03-13 16:01:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TestDeletedWithOptionInheritanceMLC checks that an MLC's DeletedWith option is propagated to resources that
|
|
|
|
// set an MLC as its parent. MLC's are remote and at the time of writing their RegisterResource call asks the
|
|
|
|
// resource monitor to ask the constructor to call the necessary RegisterResource calls on the program's behalf.
|
|
|
|
func TestDeletedWithOptionInheritanceMLC(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
2023-07-18 15:10:23 +00:00
|
|
|
expectedUrn := resource.CreateURN("expect-this", "pkg:index:type", "", "project", "stack")
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
parentUrn, _, _, _, err := monitor.RegisterResource("pkgA:m:typComponent", "resA", false, deploytest.ResourceOptions{
|
2023-03-13 16:01:20 +00:00
|
|
|
Remote: true,
|
2023-07-18 15:10:23 +00:00
|
|
|
DeletedWith: expectedUrn,
|
2023-03-13 16:01:20 +00:00
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
|
2023-03-13 16:01:20 +00:00
|
|
|
Parent: parentUrn,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffF: func(
|
2023-05-29 15:41:36 +00:00
|
|
|
urn resource.URN, id resource.ID, oldInputs, oldOutputs, newInputs resource.PropertyMap, ignoreChanges []string,
|
2023-03-13 16:01:20 +00:00
|
|
|
) (plugin.DiffResult, error) {
|
|
|
|
return plugin.DiffResult{}, nil
|
|
|
|
},
|
|
|
|
ConstructF: func(monitor *deploytest.ResourceMonitor, typ, name string,
|
2023-07-25 08:03:46 +00:00
|
|
|
parent resource.URN, inputs resource.PropertyMap,
|
|
|
|
info plugin.ConstructInfo, options plugin.ConstructOptions,
|
2023-03-13 16:01:20 +00:00
|
|
|
) (plugin.ConstructResult, error) {
|
|
|
|
require.Equal(t, "resA", name)
|
|
|
|
require.Equal(t, "pkgA:m:typComponent", typ)
|
|
|
|
|
2024-02-08 13:01:47 +00:00
|
|
|
urn, _, _, _, err := monitor.RegisterResource(tokens.Type(typ), name, false, deploytest.ResourceOptions{
|
2023-03-13 16:01:20 +00:00
|
|
|
DeletedWith: options.DeletedWith,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resC", true, deploytest.ResourceOptions{
|
2023-03-13 16:01:20 +00:00
|
|
|
Parent: urn,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
return plugin.ConstructResult{
|
|
|
|
URN: urn,
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
2023-09-28 21:50:18 +00:00
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
2023-03-13 16:01:20 +00:00
|
|
|
|
|
|
|
p := &TestPlan{
|
2023-09-28 21:50:18 +00:00
|
|
|
Options: TestUpdateOptions{HostF: hostF},
|
2023-03-13 16:01:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
project := p.GetProject()
|
2023-10-11 14:44:09 +00:00
|
|
|
snap, err := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
|
2023-03-13 16:01:20 +00:00
|
|
|
for _, res := range snap.Resources[1:] {
|
2023-07-18 15:10:23 +00:00
|
|
|
assert.Equal(t, expectedUrn, res.DeletedWith)
|
2023-03-13 16:01:20 +00:00
|
|
|
}
|
2023-10-13 09:46:07 +00:00
|
|
|
assert.NoError(t, err)
|
2023-03-13 16:01:20 +00:00
|
|
|
}
|
2024-01-30 16:45:10 +00:00
|
|
|
|
|
|
|
// TestComponentProvidersInheritance is to test that the `providers` map is propagated to child resources. The rules
|
|
|
|
// around providers inheritances are _weird_. They are only used for remote construct calls, but they propagate through
|
|
|
|
// any "component parent", not custom resource parents. This is probably just badly spec'd behavior from the first
|
|
|
|
// release that we're now stuck with.
|
|
|
|
func TestComponentProvidersInheritance(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
programF := deploytest.NewLanguageRuntimeF(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2024-02-08 13:01:47 +00:00
|
|
|
provURN, provID, _, _, err := monitor.RegisterResource("pulumi:providers:pkg", "provA", true)
|
2024-01-30 16:45:10 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
if provID == "" {
|
|
|
|
provID = providers.UnknownID
|
|
|
|
}
|
|
|
|
|
|
|
|
provRef, err := providers.NewReference(provURN, provID)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2024-02-08 13:01:47 +00:00
|
|
|
aURN, _, _, _, err := monitor.RegisterResource("my_component", "resA", false, deploytest.ResourceOptions{
|
2024-01-30 16:45:10 +00:00
|
|
|
Providers: map[string]string{"pkgA": provRef.String()},
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
// resB _should_ see the explicit provider in it's construct options because it's parent is a component with
|
|
|
|
// providers set.
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err = monitor.RegisterResource("pkg:index:component", "resB", false, deploytest.ResourceOptions{
|
2024-01-30 16:45:10 +00:00
|
|
|
Remote: true,
|
|
|
|
Parent: aURN,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2024-02-08 13:01:47 +00:00
|
|
|
cURN, _, _, _, err := monitor.RegisterResource("pkg:index:type", "resC", true, deploytest.ResourceOptions{
|
2024-01-30 16:45:10 +00:00
|
|
|
Providers: map[string]string{"pkgA": provRef.String()},
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
// resD _should NOT_ see the explicit provider in it's construct options because it's parent is a custom.
|
2024-02-08 13:01:47 +00:00
|
|
|
_, _, _, _, err = monitor.RegisterResource("pkg:index:component", "resD", false, deploytest.ResourceOptions{
|
2024-01-30 16:45:10 +00:00
|
|
|
Remote: true,
|
|
|
|
Parent: cURN,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkg", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffF: func(
|
|
|
|
urn resource.URN, id resource.ID, oldInputs, oldOutputs, newInputs resource.PropertyMap, ignoreChanges []string,
|
|
|
|
) (plugin.DiffResult, error) {
|
|
|
|
return plugin.DiffResult{}, nil
|
|
|
|
},
|
|
|
|
ConstructF: func(monitor *deploytest.ResourceMonitor, typ, name string,
|
|
|
|
parent resource.URN, inputs resource.PropertyMap,
|
|
|
|
info plugin.ConstructInfo, options plugin.ConstructOptions,
|
|
|
|
) (plugin.ConstructResult, error) {
|
|
|
|
assert.Equal(t, "pkg:index:component", typ)
|
|
|
|
|
|
|
|
if name == "resB" {
|
|
|
|
assert.Contains(t, options.Providers["pkgA"], "urn:pulumi:test::test::pulumi:providers:pkg::provA::")
|
|
|
|
} else {
|
|
|
|
assert.Equal(t, "resD", name)
|
|
|
|
assert.NotContains(t, options.Providers, "pkgA")
|
|
|
|
}
|
|
|
|
|
2024-02-08 13:01:47 +00:00
|
|
|
urn, _, _, _, err := monitor.RegisterResource(tokens.Type(typ), name, false, deploytest.ResourceOptions{})
|
2024-01-30 16:45:10 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return plugin.ConstructResult{
|
|
|
|
URN: urn,
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
hostF := deploytest.NewPluginHostF(nil, nil, programF, loaders...)
|
|
|
|
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: TestUpdateOptions{HostF: hostF},
|
|
|
|
}
|
|
|
|
|
|
|
|
project := p.GetProject()
|
|
|
|
_, err := TestOp(Update).Run(project, p.GetTarget(t, nil), p.Options, false, p.BackendClient, nil)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
}
|