Add a test for a provider update + resource deletion.
This commit is contained in:
parent
99ab10dac3
commit
6ed3d1974b
|
@ -41,6 +41,9 @@ func Destroy(u UpdateInfo, ctx *Context, opts UpdateOptions, dryRun bool) (Resou
|
||||||
}
|
}
|
||||||
defer emitter.Close()
|
defer emitter.Close()
|
||||||
|
|
||||||
|
logging.V(7).Infof("*** Starting Destroy(preview=%v) ***", dryRun)
|
||||||
|
defer logging.V(7).Infof("*** Destroy(preview=%v) complete ***", dryRun)
|
||||||
|
|
||||||
return update(ctx, info, deploymentOptions{
|
return update(ctx, info, deploymentOptions{
|
||||||
UpdateOptions: opts,
|
UpdateOptions: opts,
|
||||||
SourceFunc: newDestroySource,
|
SourceFunc: newDestroySource,
|
||||||
|
|
|
@ -2,10 +2,13 @@
|
||||||
package lifecycletest
|
package lifecycletest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/blang/semver"
|
"github.com/blang/semver"
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
. "github.com/pulumi/pulumi/pkg/v3/engine"
|
. "github.com/pulumi/pulumi/pkg/v3/engine"
|
||||||
"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
|
"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
|
||||||
|
@ -338,27 +341,155 @@ func TestSingleResourceExplicitProviderReplace(t *testing.T) {
|
||||||
p.Run(t, snap)
|
p.Run(t, snap)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSingleResourceExplicitProviderAliasReplace verifies that providers respect aliases,
|
type configurableProvider struct {
|
||||||
// and propagate replaces as a result of an aliased provider diff.
|
id string
|
||||||
func TestSingleResourceExplicitProviderAliasReplace(t *testing.T) {
|
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,
|
||||||
|
preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *configurableProvider) delete(urn resource.URN, id resource.ID, olds resource.PropertyMap,
|
||||||
|
timeout float64) (resource.Status, error) {
|
||||||
|
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) {
|
||||||
|
var creates, deletes sync.Map
|
||||||
|
|
||||||
loaders := []*deploytest.ProviderLoader{
|
loaders := []*deploytest.ProviderLoader{
|
||||||
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
||||||
|
configurable := &configurableProvider{
|
||||||
|
creates: &creates,
|
||||||
|
deletes: &deletes,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &deploytest.Provider{
|
||||||
|
DiffConfigF: func(urn resource.URN, olds, news resource.PropertyMap,
|
||||||
|
ignoreChanges []string) (plugin.DiffResult, error) {
|
||||||
|
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
|
||||||
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
||||||
|
provURN, provID, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), providerName, true,
|
||||||
|
deploytest.ResourceOptions{
|
||||||
|
Inputs: providerInputs,
|
||||||
|
Aliases: aliases,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
if provID == "" {
|
||||||
|
provID = providers.UnknownID
|
||||||
|
}
|
||||||
|
|
||||||
|
provRef, err := providers.NewReference(provURN, provID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
if registerResource {
|
||||||
|
_, resourceID, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
||||||
|
Provider: provRef.String(),
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
||||||
|
|
||||||
|
p := &TestPlan{
|
||||||
|
Options: UpdateOptions{Host: host},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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}}
|
||||||
|
snap = 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) {
|
||||||
|
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{
|
return &deploytest.Provider{
|
||||||
DiffConfigF: func(urn resource.URN, olds, news resource.PropertyMap,
|
DiffConfigF: func(urn resource.URN, olds, news resource.PropertyMap,
|
||||||
ignoreChanges []string) (plugin.DiffResult, error) {
|
ignoreChanges []string) (plugin.DiffResult, error) {
|
||||||
// Always require replacement.
|
|
||||||
keys := []resource.PropertyKey{}
|
keys := []resource.PropertyKey{}
|
||||||
for k := range news {
|
for k := range news {
|
||||||
keys = append(keys, k)
|
keys = append(keys, k)
|
||||||
}
|
}
|
||||||
return plugin.DiffResult{ReplaceKeys: keys}, nil
|
return plugin.DiffResult{ReplaceKeys: keys}, nil
|
||||||
},
|
},
|
||||||
|
ConfigureF: configurable.configure,
|
||||||
|
CreateF: configurable.create,
|
||||||
|
DeleteF: configurable.delete,
|
||||||
}, nil
|
}, nil
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
providerInputs := resource.PropertyMap{
|
providerInputs := resource.PropertyMap{
|
||||||
resource.PropertyKey("foo"): resource.NewStringProperty("bar"),
|
resource.PropertyKey("id"): resource.NewStringProperty("first"),
|
||||||
}
|
}
|
||||||
providerName := "provA"
|
providerName := "provA"
|
||||||
aliases := []resource.URN{}
|
aliases := []resource.URN{}
|
||||||
|
@ -419,7 +550,7 @@ func TestSingleResourceExplicitProviderAliasReplace(t *testing.T) {
|
||||||
snap = p.Run(t, snap)
|
snap = p.Run(t, snap)
|
||||||
|
|
||||||
// Change the config and run an update maintaining the alias. We expect everything to require replacement.
|
// Change the config and run an update maintaining the alias. We expect everything to require replacement.
|
||||||
providerInputs[resource.PropertyKey("foo")] = resource.NewStringProperty("baz")
|
providerInputs[resource.PropertyKey("id")] = resource.NewStringProperty("second")
|
||||||
p.Steps = []TestStep{{
|
p.Steps = []TestStep{{
|
||||||
Op: Update,
|
Op: Update,
|
||||||
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
||||||
|
@ -428,10 +559,23 @@ func TestSingleResourceExplicitProviderAliasReplace(t *testing.T) {
|
||||||
provURN := p.NewProviderURN("pkgA", providerName, "")
|
provURN := p.NewProviderURN("pkgA", providerName, "")
|
||||||
resURN := p.NewURN("pkgA:m:typA", "resA", "")
|
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.
|
// Look for replace steps on the provider and the resource.
|
||||||
replacedProvider, replacedResource := false, false
|
replacedProvider, replacedResource := false, false
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
op := entry.Step.Op()
|
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 {
|
if entry.Kind != JournalEntrySuccess || op != deploy.OpDeleteReplaced {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -448,6 +592,18 @@ func TestSingleResourceExplicitProviderAliasReplace(t *testing.T) {
|
||||||
assert.True(t, replacedProvider)
|
assert.True(t, replacedProvider)
|
||||||
assert.True(t, replacedResource)
|
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)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -44,6 +44,9 @@ func Refresh(u UpdateInfo, ctx *Context, opts UpdateOptions, dryRun bool) (Resou
|
||||||
// Force opts.Refresh to true.
|
// Force opts.Refresh to true.
|
||||||
opts.Refresh = true
|
opts.Refresh = true
|
||||||
|
|
||||||
|
logging.V(7).Infof("*** Starting Refresh(preview=%v) ***", dryRun)
|
||||||
|
defer logging.V(7).Infof("*** Refresh(preview=%v) complete ***", dryRun)
|
||||||
|
|
||||||
return update(ctx, info, deploymentOptions{
|
return update(ctx, info, deploymentOptions{
|
||||||
UpdateOptions: opts,
|
UpdateOptions: opts,
|
||||||
SourceFunc: newRefreshSource,
|
SourceFunc: newRefreshSource,
|
||||||
|
|
|
@ -179,6 +179,9 @@ func Update(u UpdateInfo, ctx *Context, opts UpdateOptions, dryRun bool) (Resour
|
||||||
}
|
}
|
||||||
defer emitter.Close()
|
defer emitter.Close()
|
||||||
|
|
||||||
|
logging.V(7).Infof("*** Starting Update(preview=%v) ***", dryRun)
|
||||||
|
defer logging.V(7).Infof("*** Update(preview=%v) complete ***", dryRun)
|
||||||
|
|
||||||
return update(ctx, info, deploymentOptions{
|
return update(ctx, info, deploymentOptions{
|
||||||
UpdateOptions: opts,
|
UpdateOptions: opts,
|
||||||
SourceFunc: newUpdateSource,
|
SourceFunc: newUpdateSource,
|
||||||
|
|
|
@ -308,6 +308,8 @@ func (r *Registry) Diff(urn resource.URN, id resource.ID, olds, news resource.Pr
|
||||||
contract.IgnoreError(closeErr)
|
contract.IgnoreError(closeErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logging.V(7).Infof("%s: executed (%#v, %#v)", label, diff.Changes, diff.ReplaceKeys)
|
||||||
|
|
||||||
return diff, nil
|
return diff, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue