Respect provider aliases (#7166)
This commit is contained in:
parent
5b2fdb27d3
commit
f4efb7564b
|
@ -1,3 +1,6 @@
|
|||
### Improvements
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [cli] - Respect provider aliases
|
||||
[#7166](https://github.com/pulumi/pulumi/pull/7166)
|
||||
|
|
|
@ -41,6 +41,9 @@ func Destroy(u UpdateInfo, ctx *Context, opts UpdateOptions, dryRun bool) (Resou
|
|||
}
|
||||
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{
|
||||
UpdateOptions: opts,
|
||||
SourceFunc: newDestroySource,
|
||||
|
|
|
@ -2,10 +2,13 @@
|
|||
package lifecycletest
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
. "github.com/pulumi/pulumi/pkg/v3/engine"
|
||||
"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
|
||||
|
@ -338,6 +341,279 @@ func TestSingleResourceExplicitProviderReplace(t *testing.T) {
|
|||
p.Run(t, snap)
|
||||
}
|
||||
|
||||
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,
|
||||
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{
|
||||
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}}
|
||||
_ = 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{
|
||||
DiffConfigF: func(urn resource.URN, olds, news resource.PropertyMap,
|
||||
ignoreChanges []string) (plugin.DiffResult, error) {
|
||||
keys := []resource.PropertyKey{}
|
||||
for k := range news {
|
||||
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{}
|
||||
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)
|
||||
|
||||
_, _, _, 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 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,
|
||||
_ []Event, res result.Result) result.Result {
|
||||
for _, entry := range entries {
|
||||
if entry.Step.Op() != deploy.OpSame {
|
||||
t.Fatalf("update should contain no changes: %v", entry.Step.URN())
|
||||
}
|
||||
}
|
||||
return res
|
||||
},
|
||||
}}
|
||||
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,
|
||||
_ []Event, res result.Result) result.Result {
|
||||
|
||||
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)
|
||||
|
||||
return res
|
||||
},
|
||||
}}
|
||||
snap = p.Run(t, snap)
|
||||
|
||||
// Resume the lifecycle with another no-op update.
|
||||
p.Steps = steps[2:]
|
||||
p.Run(t, snap)
|
||||
}
|
||||
|
||||
func TestSingleResourceExplicitProviderDeleteBeforeReplace(t *testing.T) {
|
||||
loaders := []*deploytest.ProviderLoader{
|
||||
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
||||
|
|
|
@ -44,6 +44,9 @@ func Refresh(u UpdateInfo, ctx *Context, opts UpdateOptions, dryRun bool) (Resou
|
|||
// Force opts.Refresh to 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{
|
||||
UpdateOptions: opts,
|
||||
SourceFunc: newRefreshSource,
|
||||
|
|
|
@ -179,6 +179,9 @@ func Update(u UpdateInfo, ctx *Context, opts UpdateOptions, dryRun bool) (Resour
|
|||
}
|
||||
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{
|
||||
UpdateOptions: opts,
|
||||
SourceFunc: newUpdateSource,
|
||||
|
|
|
@ -360,6 +360,10 @@ func (d *Deployment) Prev() *Snapshot { return d.prev }
|
|||
func (d *Deployment) Olds() map[resource.URN]*resource.State { return d.olds }
|
||||
func (d *Deployment) Source() Source { return d.source }
|
||||
|
||||
func (d *Deployment) SameProvider(ref providers.Reference) {
|
||||
d.providers.Same(ref)
|
||||
}
|
||||
|
||||
func (d *Deployment) GetProvider(ref providers.Reference) (plugin.Provider, bool) {
|
||||
return d.providers.GetProvider(ref)
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@ type Registry struct {
|
|||
isPreview bool
|
||||
providers map[Reference]plugin.Provider
|
||||
builtins plugin.Provider
|
||||
aliases map[resource.URN]resource.URN
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
|
@ -91,6 +92,7 @@ func NewRegistry(host plugin.Host, prev []*resource.State, isPreview bool,
|
|||
isPreview: isPreview,
|
||||
providers: make(map[Reference]plugin.Provider),
|
||||
builtins: builtins,
|
||||
aliases: make(map[resource.URN]resource.URN),
|
||||
}
|
||||
|
||||
for _, res := range prev {
|
||||
|
@ -156,6 +158,10 @@ func (r *Registry) setProvider(ref Reference, provider plugin.Provider) {
|
|||
logging.V(7).Infof("setProvider(%v)", ref)
|
||||
|
||||
r.providers[ref] = provider
|
||||
|
||||
if alias, ok := r.aliases[ref.URN()]; ok {
|
||||
r.providers[mustNewReference(alias, ref.ID())] = provider
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Registry) deleteProvider(ref Reference) (plugin.Provider, bool) {
|
||||
|
@ -254,6 +260,14 @@ func (r *Registry) Check(urn resource.URN, olds, news resource.PropertyMap,
|
|||
return inputs, nil, nil
|
||||
}
|
||||
|
||||
// RegisterAliases informs the registry that the new provider object with the given URN is aliased to the given list
|
||||
// of URNs.
|
||||
func (r *Registry) RegisterAlias(providerURN, alias resource.URN) {
|
||||
if providerURN != alias {
|
||||
r.aliases[providerURN] = alias
|
||||
}
|
||||
}
|
||||
|
||||
// Diff diffs the configuration of the indicated provider. The provider corresponding to the given URN must have
|
||||
// previously been loaded by a call to Check.
|
||||
func (r *Registry) Diff(urn resource.URN, id resource.ID, olds, news resource.PropertyMap,
|
||||
|
@ -299,9 +313,27 @@ func (r *Registry) Diff(urn resource.URN, id resource.ID, olds, news resource.Pr
|
|||
contract.IgnoreError(closeErr)
|
||||
}
|
||||
|
||||
logging.V(7).Infof("%s: executed (%#v, %#v)", label, diff.Changes, diff.ReplaceKeys)
|
||||
|
||||
return diff, nil
|
||||
}
|
||||
|
||||
// Same executes as part of the "Same" step for a provider that has not changed. It exists solely to allow the registry
|
||||
// to point aliases for a provider to the proper object.
|
||||
func (r *Registry) Same(ref Reference) {
|
||||
r.m.RLock()
|
||||
defer r.m.RUnlock()
|
||||
|
||||
logging.V(7).Infof("Same(%v)", ref)
|
||||
|
||||
// If this provider is aliased to a different old URN, make sure that it is present under both the old reference and
|
||||
// the new reference.
|
||||
if alias, ok := r.aliases[ref.URN()]; ok {
|
||||
aliasRef := mustNewReference(alias, ref.ID())
|
||||
r.providers[ref] = r.providers[aliasRef]
|
||||
}
|
||||
}
|
||||
|
||||
// Create coonfigures the provider with the given URN using the indicated configuration, assigns it an ID, and
|
||||
// registers it under the assigned (URN, ID).
|
||||
//
|
||||
|
|
|
@ -121,9 +121,22 @@ func (s *SameStep) Res() *resource.State { return s.new }
|
|||
func (s *SameStep) Logical() bool { return true }
|
||||
|
||||
func (s *SameStep) Apply(preview bool) (resource.Status, StepCompleteFunc, error) {
|
||||
// Retain the ID, and outputs:
|
||||
// Retain the ID and outputs
|
||||
s.new.ID = s.old.ID
|
||||
s.new.Outputs = s.old.Outputs
|
||||
|
||||
// If the resource is a provider, ensure that it is present in the registry under the appropriate URNs.
|
||||
if providers.IsProviderType(s.new.Type) {
|
||||
ref, err := providers.NewReference(s.new.URN, s.new.ID)
|
||||
if err != nil {
|
||||
return resource.StatusOK, nil, errors.Errorf(
|
||||
"bad provider reference '%v' for resource %v: %v", s.Provider(), s.URN(), err)
|
||||
}
|
||||
if s.Deployment() != nil {
|
||||
s.Deployment().SameProvider(ref)
|
||||
}
|
||||
}
|
||||
|
||||
complete := func() { s.reg.Done(&RegisterResult{State: s.new}) }
|
||||
return resource.StatusOK, complete, nil
|
||||
}
|
||||
|
|
|
@ -228,6 +228,9 @@ func (sg *stepGenerator) generateSteps(event RegisterResourceEvent) ([]Step, res
|
|||
sg.deployment.Diag().Errorf(diag.GetDuplicateResourceAliasError(urn), urnOrAlias, urn, previousAliasURN)
|
||||
}
|
||||
sg.aliased[urnOrAlias] = urn
|
||||
|
||||
// register the alias with the provider registry
|
||||
sg.deployment.providers.RegisterAlias(urn, urnOrAlias)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue