2020-12-15 23:24:46 +01:00
|
|
|
package lifecycletest
|
|
|
|
|
|
|
|
import (
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/blang/semver"
|
|
|
|
combinations "github.com/mxschmitt/golang-combinations"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
|
2021-03-17 14:20:05 +01:00
|
|
|
. "github.com/pulumi/pulumi/pkg/v3/engine"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/deploy/deploytest"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
|
2021-07-28 04:17:54 +02:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
|
2021-03-17 14:20:05 +01:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/result"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
|
2020-12-15 23:24:46 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestDestroyTarget(t *testing.T) {
|
|
|
|
// Try refreshing a stack with combinations of the above resources as target to destroy.
|
|
|
|
subsets := combinations.All(complexTestDependencyGraphNames)
|
|
|
|
|
|
|
|
for _, subset := range subsets {
|
|
|
|
// limit to up to 3 resources to destroy. This keeps the test running time under
|
|
|
|
// control as it only generates a few hundred combinations instead of several thousand.
|
|
|
|
if len(subset) <= 3 {
|
|
|
|
destroySpecificTargets(t, subset, true, /*targetDependents*/
|
|
|
|
func(urns []resource.URN, deleted map[resource.URN]bool) {})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
destroySpecificTargets(
|
|
|
|
t, []string{"A"}, true, /*targetDependents*/
|
|
|
|
func(urns []resource.URN, deleted map[resource.URN]bool) {
|
|
|
|
// when deleting 'A' we expect A, B, C, E, F, and K to be deleted
|
|
|
|
names := complexTestDependencyGraphNames
|
|
|
|
assert.Equal(t, map[resource.URN]bool{
|
|
|
|
pickURN(t, urns, names, "A"): true,
|
|
|
|
pickURN(t, urns, names, "B"): true,
|
|
|
|
pickURN(t, urns, names, "C"): true,
|
|
|
|
pickURN(t, urns, names, "E"): true,
|
|
|
|
pickURN(t, urns, names, "F"): true,
|
|
|
|
pickURN(t, urns, names, "K"): true,
|
|
|
|
}, deleted)
|
|
|
|
})
|
|
|
|
|
|
|
|
destroySpecificTargets(
|
|
|
|
t, []string{"A"}, false, /*targetDependents*/
|
|
|
|
func(urns []resource.URN, deleted map[resource.URN]bool) {})
|
|
|
|
}
|
|
|
|
|
|
|
|
func destroySpecificTargets(
|
|
|
|
t *testing.T, targets []string, targetDependents bool,
|
|
|
|
validate func(urns []resource.URN, deleted map[resource.URN]bool)) {
|
|
|
|
|
|
|
|
// A
|
|
|
|
// _________|_________
|
|
|
|
// B C D
|
|
|
|
// ___|___ ___|___
|
|
|
|
// E F G H I J
|
|
|
|
// |__|
|
|
|
|
// K L
|
|
|
|
|
|
|
|
p := &TestPlan{}
|
|
|
|
|
|
|
|
urns, old, program := generateComplexTestDependencyGraph(t, p)
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffConfigF: func(urn resource.URN, olds, news resource.PropertyMap,
|
|
|
|
ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
if !olds["A"].DeepEquals(news["A"]) {
|
|
|
|
return plugin.DiffResult{
|
|
|
|
ReplaceKeys: []resource.PropertyKey{"A"},
|
|
|
|
DeleteBeforeReplace: true,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
return plugin.DiffResult{}, nil
|
|
|
|
},
|
|
|
|
DiffF: func(urn resource.URN, id resource.ID,
|
|
|
|
olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
|
|
|
|
if !olds["A"].DeepEquals(news["A"]) {
|
|
|
|
return plugin.DiffResult{ReplaceKeys: []resource.PropertyKey{"A"}}, nil
|
|
|
|
}
|
|
|
|
return plugin.DiffResult{}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Options.Host = deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
p.Options.TargetDependents = targetDependents
|
|
|
|
|
|
|
|
destroyTargets := []resource.URN{}
|
|
|
|
for _, target := range targets {
|
|
|
|
destroyTargets = append(destroyTargets, pickURN(t, urns, complexTestDependencyGraphNames, target))
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Options.DestroyTargets = destroyTargets
|
|
|
|
t.Logf("Destroying targets: %v", destroyTargets)
|
|
|
|
|
|
|
|
// If we're not forcing the targets to be destroyed, then expect to get a failure here as
|
|
|
|
// we'll have downstream resources to delete that weren't specified explicitly.
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Destroy,
|
|
|
|
ExpectFailure: !targetDependents,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
|
|
|
assert.True(t, len(entries) > 0)
|
|
|
|
|
|
|
|
deleted := make(map[resource.URN]bool)
|
|
|
|
for _, entry := range entries {
|
|
|
|
assert.Equal(t, deploy.OpDelete, entry.Step.Op())
|
|
|
|
deleted[entry.Step.URN()] = true
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, target := range p.Options.DestroyTargets {
|
|
|
|
assert.Contains(t, deleted, target)
|
|
|
|
}
|
|
|
|
|
|
|
|
validate(urns, deleted)
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
|
|
|
|
p.Run(t, old)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestUpdateTarget(t *testing.T) {
|
|
|
|
// Try refreshing a stack with combinations of the above resources as target to destroy.
|
|
|
|
subsets := combinations.All(complexTestDependencyGraphNames)
|
|
|
|
|
|
|
|
for _, subset := range subsets {
|
|
|
|
// limit to up to 3 resources to destroy. This keeps the test running time under
|
|
|
|
// control as it only generates a few hundred combinations instead of several thousand.
|
|
|
|
if len(subset) <= 3 {
|
|
|
|
updateSpecificTargets(t, subset)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
updateSpecificTargets(t, []string{"A"})
|
|
|
|
|
|
|
|
// Also update a target that doesn't exist to make sure we don't crash or otherwise go off the rails.
|
|
|
|
updateInvalidTarget(t)
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateSpecificTargets(t *testing.T, targets []string) {
|
|
|
|
// A
|
|
|
|
// _________|_________
|
|
|
|
// B C D
|
|
|
|
// ___|___ ___|___
|
|
|
|
// E F G H I J
|
|
|
|
// |__|
|
|
|
|
// K L
|
|
|
|
|
|
|
|
p := &TestPlan{}
|
|
|
|
|
|
|
|
urns, old, program := generateComplexTestDependencyGraph(t, p)
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap,
|
|
|
|
ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
|
|
|
|
// all resources will change.
|
|
|
|
return plugin.DiffResult{
|
|
|
|
Changes: plugin.DiffSome,
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
|
|
|
|
UpdateF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap, timeout float64,
|
|
|
|
ignoreChanges []string, preview bool) (resource.PropertyMap, resource.Status, error) {
|
|
|
|
|
|
|
|
outputs := olds.Copy()
|
|
|
|
|
|
|
|
outputs["output_prop"] = resource.NewPropertyValue(42)
|
|
|
|
return outputs, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Options.Host = deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
|
|
|
|
updateTargets := []resource.URN{}
|
|
|
|
for _, target := range targets {
|
|
|
|
updateTargets = append(updateTargets,
|
|
|
|
pickURN(t, urns, complexTestDependencyGraphNames, target))
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Options.UpdateTargets = updateTargets
|
|
|
|
t.Logf("Updating targets: %v", updateTargets)
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: false,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
|
|
|
assert.True(t, len(entries) > 0)
|
|
|
|
|
|
|
|
updated := make(map[resource.URN]bool)
|
|
|
|
sames := make(map[resource.URN]bool)
|
|
|
|
for _, entry := range entries {
|
|
|
|
if entry.Step.Op() == deploy.OpUpdate {
|
|
|
|
updated[entry.Step.URN()] = true
|
|
|
|
} else if entry.Step.Op() == deploy.OpSame {
|
|
|
|
sames[entry.Step.URN()] = true
|
|
|
|
} else {
|
|
|
|
assert.FailNowf(t, "", "Got a step that wasn't a same/update: %v", entry.Step.Op())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, target := range p.Options.UpdateTargets {
|
|
|
|
assert.Contains(t, updated, target)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, target := range p.Options.UpdateTargets {
|
|
|
|
assert.NotContains(t, sames, target)
|
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
p.Run(t, old)
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateInvalidTarget(t *testing.T) {
|
|
|
|
p := &TestPlan{}
|
|
|
|
|
|
|
|
_, old, program := generateComplexTestDependencyGraph(t, p)
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap,
|
|
|
|
ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
|
|
|
|
// all resources will change.
|
|
|
|
return plugin.DiffResult{
|
|
|
|
Changes: plugin.DiffSome,
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
|
|
|
|
UpdateF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap, timeout float64,
|
|
|
|
ignoreChanges []string, preview bool) (resource.PropertyMap, resource.Status, error) {
|
|
|
|
|
|
|
|
outputs := olds.Copy()
|
|
|
|
|
|
|
|
outputs["output_prop"] = resource.NewPropertyValue(42)
|
|
|
|
return outputs, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Options.Host = deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
|
|
|
|
p.Options.UpdateTargets = []resource.URN{"foo"}
|
|
|
|
t.Logf("Updating invalid targets: %v", p.Options.UpdateTargets)
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: true,
|
|
|
|
}}
|
|
|
|
|
|
|
|
p.Run(t, old)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCreateDuringTargetedUpdate_CreateMentionedAsTarget(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
program1 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host1 := deploytest.NewPluginHost(nil, nil, program1, loaders...)
|
|
|
|
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{Host: host1},
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{Op: Update}}
|
|
|
|
snap1 := p.Run(t, nil)
|
|
|
|
|
|
|
|
// Now, create a resource resB. This shouldn't be a problem since resB isn't referenced by anything.
|
|
|
|
program2 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resB", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host2 := deploytest.NewPluginHost(nil, nil, program2, loaders...)
|
|
|
|
|
|
|
|
resA := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
resB := p.NewURN("pkgA:m:typA", "resB", "")
|
|
|
|
p.Options.Host = host2
|
|
|
|
p.Options.UpdateTargets = []resource.URN{resA, resB}
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: false,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
|
|
|
assert.True(t, len(entries) > 0)
|
|
|
|
|
|
|
|
for _, entry := range entries {
|
|
|
|
if entry.Step.URN() == resA {
|
|
|
|
assert.Equal(t, deploy.OpSame, entry.Step.Op())
|
|
|
|
} else if entry.Step.URN() == resB {
|
|
|
|
assert.Equal(t, deploy.OpCreate, entry.Step.Op())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
p.Run(t, snap1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCreateDuringTargetedUpdate_UntargetedCreateNotReferenced(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
program1 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host1 := deploytest.NewPluginHost(nil, nil, program1, loaders...)
|
|
|
|
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{Host: host1},
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{Op: Update}}
|
|
|
|
snap1 := p.Run(t, nil)
|
|
|
|
|
|
|
|
// Now, create a resource resB. This shouldn't be a problem since resB isn't referenced by anything.
|
|
|
|
program2 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resB", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host2 := deploytest.NewPluginHost(nil, nil, program2, loaders...)
|
|
|
|
|
|
|
|
resA := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
|
|
|
|
p.Options.Host = host2
|
|
|
|
p.Options.UpdateTargets = []resource.URN{resA}
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: false,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
|
|
|
assert.True(t, len(entries) > 0)
|
|
|
|
|
|
|
|
for _, entry := range entries {
|
|
|
|
// everything should be a same op here.
|
|
|
|
assert.Equal(t, deploy.OpSame, entry.Step.Op())
|
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
p.Run(t, snap1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCreateDuringTargetedUpdate_UntargetedCreateReferencedByTarget(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
program1 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host1 := deploytest.NewPluginHost(nil, nil, program1, loaders...)
|
|
|
|
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{Host: host1},
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{Op: Update}}
|
|
|
|
p.Run(t, nil)
|
|
|
|
|
|
|
|
resA := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
resB := p.NewURN("pkgA:m:typA", "resB", "")
|
|
|
|
|
|
|
|
// Now, create a resource resB. But reference it from A. This will cause a dependency we can't
|
|
|
|
// satisfy.
|
|
|
|
program2 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resB", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true,
|
|
|
|
deploytest.ResourceOptions{
|
|
|
|
Dependencies: []resource.URN{resB},
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host2 := deploytest.NewPluginHost(nil, nil, program2, loaders...)
|
|
|
|
|
|
|
|
p.Options.Host = host2
|
|
|
|
p.Options.UpdateTargets = []resource.URN{resA}
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: true,
|
|
|
|
}}
|
|
|
|
p.Run(t, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCreateDuringTargetedUpdate_UntargetedCreateReferencedByUntargetedCreate(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
program1 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host1 := deploytest.NewPluginHost(nil, nil, program1, loaders...)
|
|
|
|
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{Host: host1},
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{Op: Update}}
|
|
|
|
snap1 := p.Run(t, nil)
|
|
|
|
|
|
|
|
resA := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
resB := p.NewURN("pkgA:m:typA", "resB", "")
|
|
|
|
|
|
|
|
// Now, create a resource resB. But reference it from A. This will cause a dependency we can't
|
|
|
|
// satisfy.
|
|
|
|
program2 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resB", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resC", true,
|
|
|
|
deploytest.ResourceOptions{
|
|
|
|
Dependencies: []resource.URN{resB},
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host2 := deploytest.NewPluginHost(nil, nil, program2, loaders...)
|
|
|
|
|
|
|
|
p.Options.Host = host2
|
|
|
|
p.Options.UpdateTargets = []resource.URN{resA}
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: false,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
|
|
|
assert.True(t, len(entries) > 0)
|
|
|
|
|
|
|
|
for _, entry := range entries {
|
|
|
|
assert.Equal(t, deploy.OpSame, entry.Step.Op())
|
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
p.Run(t, snap1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestReplaceSpecificTargets(t *testing.T) {
|
|
|
|
// A
|
|
|
|
// _________|_________
|
|
|
|
// B C D
|
|
|
|
// ___|___ ___|___
|
|
|
|
// E F G H I J
|
|
|
|
// |__|
|
|
|
|
// K L
|
|
|
|
|
|
|
|
p := &TestPlan{}
|
|
|
|
|
|
|
|
urns, old, program := generateComplexTestDependencyGraph(t, p)
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap,
|
|
|
|
ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
|
|
|
|
// No resources will change.
|
|
|
|
return plugin.DiffResult{Changes: plugin.DiffNone}, nil
|
|
|
|
},
|
|
|
|
|
|
|
|
CreateF: func(urn resource.URN, news resource.PropertyMap, timeout float64,
|
|
|
|
preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
|
|
|
|
|
|
|
|
return "created-id", news, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Options.Host = deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
|
|
|
|
getURN := func(name string) resource.URN {
|
|
|
|
return pickURN(t, urns, complexTestDependencyGraphNames, name)
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Options.ReplaceTargets = []resource.URN{
|
|
|
|
getURN("F"),
|
|
|
|
getURN("B"),
|
|
|
|
getURN("G"),
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: false,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
|
|
|
assert.True(t, len(entries) > 0)
|
|
|
|
|
|
|
|
replaced := make(map[resource.URN]bool)
|
|
|
|
sames := make(map[resource.URN]bool)
|
|
|
|
for _, entry := range entries {
|
|
|
|
if entry.Step.Op() == deploy.OpReplace {
|
|
|
|
replaced[entry.Step.URN()] = true
|
|
|
|
} else if entry.Step.Op() == deploy.OpSame {
|
|
|
|
sames[entry.Step.URN()] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, target := range p.Options.ReplaceTargets {
|
|
|
|
assert.Contains(t, replaced, target)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, target := range p.Options.ReplaceTargets {
|
|
|
|
assert.NotContains(t, sames, target)
|
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
|
|
|
|
p.Run(t, old)
|
|
|
|
}
|
2021-07-28 04:17:54 +02:00
|
|
|
|
|
|
|
var componentBasedTestDependencyGraphNames = []string{"A", "B", "C", "D", "E", "F", "G", "H",
|
|
|
|
"I", "J", "K", "L", "M", "N"}
|
|
|
|
|
|
|
|
func generateParentedTestDependencyGraph(t *testing.T, p *TestPlan) (
|
|
|
|
// Parent-child graph
|
|
|
|
// A B
|
|
|
|
// __|__ ____|____
|
|
|
|
// D I E F
|
|
|
|
// __|__ __|__ __|__
|
|
|
|
// G H J K L M
|
|
|
|
//
|
|
|
|
// A has children D, I
|
|
|
|
// D has children G, H
|
|
|
|
// B has children E, F
|
|
|
|
// E has children J, K
|
|
|
|
// F has children L, M
|
|
|
|
//
|
|
|
|
// Dependency graph
|
|
|
|
// G H
|
|
|
|
// | __|__
|
|
|
|
// I K N
|
|
|
|
//
|
|
|
|
// I depends on G
|
|
|
|
// K depends on H
|
|
|
|
// N depends on H
|
|
|
|
|
|
|
|
[]resource.URN, *deploy.Snapshot, plugin.LanguageRuntime) {
|
|
|
|
resTypeComponent := tokens.Type("pkgA:index:Component")
|
|
|
|
resTypeResource := tokens.Type("pkgA:index:Resource")
|
|
|
|
|
|
|
|
names := componentBasedTestDependencyGraphNames
|
|
|
|
|
|
|
|
urnA := p.NewURN(resTypeComponent, names[0], "")
|
|
|
|
urnB := p.NewURN(resTypeComponent, names[1], "")
|
|
|
|
urnC := p.NewURN(resTypeResource, names[2], "")
|
|
|
|
urnD := p.NewURN(resTypeComponent, names[3], urnA)
|
|
|
|
urnE := p.NewURN(resTypeComponent, names[4], urnB)
|
|
|
|
urnF := p.NewURN(resTypeComponent, names[5], urnB)
|
|
|
|
urnG := p.NewURN(resTypeResource, names[6], urnD)
|
|
|
|
urnH := p.NewURN(resTypeResource, names[7], urnD)
|
|
|
|
urnI := p.NewURN(resTypeResource, names[8], urnA)
|
|
|
|
urnJ := p.NewURN(resTypeResource, names[9], urnE)
|
|
|
|
urnK := p.NewURN(resTypeResource, names[10], urnE)
|
|
|
|
urnL := p.NewURN(resTypeResource, names[11], urnF)
|
|
|
|
urnM := p.NewURN(resTypeResource, names[12], urnF)
|
|
|
|
urnN := p.NewURN(resTypeResource, names[13], "")
|
|
|
|
|
|
|
|
urns := []resource.URN{urnA, urnB, urnC, urnD, urnE, urnF, urnG, urnH, urnI, urnJ, urnK, urnL, urnM, urnN}
|
|
|
|
|
|
|
|
newResource := func(urn, parent resource.URN, id resource.ID,
|
|
|
|
dependencies []resource.URN, propertyDeps propertyDependencies) *resource.State {
|
|
|
|
return newResource(urn, parent, id, "", dependencies, propertyDeps,
|
|
|
|
nil, urn.Type() != resTypeComponent)
|
|
|
|
}
|
|
|
|
|
|
|
|
old := &deploy.Snapshot{
|
|
|
|
Resources: []*resource.State{
|
|
|
|
newResource(urnA, "", "0", nil, nil),
|
|
|
|
newResource(urnB, "", "1", nil, nil),
|
|
|
|
newResource(urnC, "", "2", nil, nil),
|
|
|
|
newResource(urnD, urnA, "3", nil, nil),
|
|
|
|
newResource(urnE, urnB, "4", nil, nil),
|
|
|
|
newResource(urnF, urnB, "5", nil, nil),
|
|
|
|
newResource(urnG, urnD, "6", nil, nil),
|
|
|
|
newResource(urnH, urnD, "7", nil, nil),
|
|
|
|
newResource(urnI, urnA, "8", []resource.URN{urnG},
|
|
|
|
propertyDependencies{"A": []resource.URN{urnG}}),
|
|
|
|
newResource(urnJ, urnE, "9", nil, nil),
|
|
|
|
newResource(urnK, urnE, "10", []resource.URN{urnH},
|
|
|
|
propertyDependencies{"A": []resource.URN{urnH}}),
|
|
|
|
newResource(urnL, urnF, "11", nil, nil),
|
|
|
|
newResource(urnM, urnF, "12", nil, nil),
|
|
|
|
newResource(urnN, "", "13", []resource.URN{urnH},
|
|
|
|
propertyDependencies{"A": []resource.URN{urnH}}),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
program := deploytest.NewLanguageRuntime(
|
|
|
|
func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
register := func(urn, parent resource.URN) resource.ID {
|
|
|
|
_, id, _, err := monitor.RegisterResource(
|
|
|
|
urn.Type(),
|
|
|
|
string(urn.Name()),
|
|
|
|
urn.Type() != resTypeComponent,
|
|
|
|
deploytest.ResourceOptions{
|
|
|
|
Inputs: nil,
|
|
|
|
Parent: parent,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
return id
|
|
|
|
}
|
|
|
|
|
|
|
|
register(urnA, "")
|
|
|
|
register(urnB, "")
|
|
|
|
register(urnC, "")
|
|
|
|
register(urnD, urnA)
|
|
|
|
register(urnE, urnB)
|
|
|
|
register(urnF, urnB)
|
|
|
|
register(urnG, urnD)
|
|
|
|
register(urnH, urnD)
|
|
|
|
register(urnI, urnA)
|
|
|
|
register(urnJ, urnE)
|
|
|
|
register(urnK, urnE)
|
|
|
|
register(urnL, urnF)
|
|
|
|
register(urnM, urnF)
|
|
|
|
register(urnN, "")
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
return urns, old, program
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestDestroyTargetWithChildren(t *testing.T) {
|
|
|
|
// when deleting 'A' with targetDependents specified we expect A, D, G, H, I, K and N to be deleted.
|
|
|
|
destroySpecificTargetsWithChildren(
|
|
|
|
t, []string{"A"}, true, /*targetDependents*/
|
|
|
|
func(urns []resource.URN, deleted map[resource.URN]bool) {
|
|
|
|
names := componentBasedTestDependencyGraphNames
|
|
|
|
assert.Equal(t, map[resource.URN]bool{
|
|
|
|
pickURN(t, urns, names, "A"): true,
|
|
|
|
pickURN(t, urns, names, "D"): true,
|
|
|
|
pickURN(t, urns, names, "G"): true,
|
|
|
|
pickURN(t, urns, names, "H"): true,
|
|
|
|
pickURN(t, urns, names, "I"): true,
|
|
|
|
pickURN(t, urns, names, "K"): true,
|
|
|
|
pickURN(t, urns, names, "N"): true,
|
|
|
|
}, deleted)
|
|
|
|
})
|
|
|
|
|
|
|
|
// when deleting 'A' with targetDependents not specified, we expect an error.
|
|
|
|
destroySpecificTargetsWithChildren(
|
|
|
|
t, []string{"A"}, false, /*targetDependents*/
|
|
|
|
func(urns []resource.URN, deleted map[resource.URN]bool) {})
|
|
|
|
|
|
|
|
// when deleting 'B' we expect B, E, F, J, K, L, M to be deleted.
|
|
|
|
destroySpecificTargetsWithChildren(
|
|
|
|
t, []string{"B"}, false, /*targetDependents*/
|
|
|
|
func(urns []resource.URN, deleted map[resource.URN]bool) {
|
|
|
|
names := componentBasedTestDependencyGraphNames
|
|
|
|
assert.Equal(t, map[resource.URN]bool{
|
|
|
|
pickURN(t, urns, names, "B"): true,
|
|
|
|
pickURN(t, urns, names, "E"): true,
|
|
|
|
pickURN(t, urns, names, "F"): true,
|
|
|
|
pickURN(t, urns, names, "J"): true,
|
|
|
|
pickURN(t, urns, names, "K"): true,
|
|
|
|
pickURN(t, urns, names, "L"): true,
|
|
|
|
pickURN(t, urns, names, "M"): true,
|
|
|
|
}, deleted)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func destroySpecificTargetsWithChildren(
|
|
|
|
t *testing.T, targets []string, targetDependents bool,
|
|
|
|
validate func(urns []resource.URN, deleted map[resource.URN]bool)) {
|
|
|
|
|
|
|
|
p := &TestPlan{}
|
|
|
|
|
|
|
|
urns, old, program := generateParentedTestDependencyGraph(t, p)
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffConfigF: func(urn resource.URN, olds, news resource.PropertyMap,
|
|
|
|
ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
if !olds["A"].DeepEquals(news["A"]) {
|
|
|
|
return plugin.DiffResult{
|
|
|
|
ReplaceKeys: []resource.PropertyKey{"A"},
|
|
|
|
DeleteBeforeReplace: true,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
return plugin.DiffResult{}, nil
|
|
|
|
},
|
|
|
|
DiffF: func(urn resource.URN, id resource.ID,
|
|
|
|
olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
|
|
|
|
if !olds["A"].DeepEquals(news["A"]) {
|
|
|
|
return plugin.DiffResult{ReplaceKeys: []resource.PropertyKey{"A"}}, nil
|
|
|
|
}
|
|
|
|
return plugin.DiffResult{}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Options.Host = deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
p.Options.TargetDependents = targetDependents
|
|
|
|
|
|
|
|
destroyTargets := []resource.URN{}
|
|
|
|
for _, target := range targets {
|
|
|
|
destroyTargets = append(destroyTargets, pickURN(t, urns, componentBasedTestDependencyGraphNames, target))
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Options.DestroyTargets = destroyTargets
|
|
|
|
t.Logf("Destroying targets: %v", destroyTargets)
|
|
|
|
|
|
|
|
// If we're not forcing the targets to be destroyed, then expect to get a failure here as
|
|
|
|
// we'll have downstream resources to delete that weren't specified explicitly.
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Destroy,
|
|
|
|
ExpectFailure: !targetDependents,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
|
|
|
assert.True(t, len(entries) > 0)
|
|
|
|
|
|
|
|
deleted := make(map[resource.URN]bool)
|
|
|
|
for _, entry := range entries {
|
|
|
|
assert.Equal(t, deploy.OpDelete, entry.Step.Op())
|
|
|
|
deleted[entry.Step.URN()] = true
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, target := range p.Options.DestroyTargets {
|
|
|
|
assert.Contains(t, deleted, target)
|
|
|
|
}
|
|
|
|
|
|
|
|
validate(urns, deleted)
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
|
|
|
|
p.Run(t, old)
|
|
|
|
}
|
|
|
|
|
|
|
|
func newResource(urn, parent resource.URN, id resource.ID, provider string, dependencies []resource.URN,
|
|
|
|
propertyDeps propertyDependencies, outputs resource.PropertyMap, custom bool) *resource.State {
|
|
|
|
|
|
|
|
inputs := resource.PropertyMap{}
|
|
|
|
for k := range propertyDeps {
|
|
|
|
inputs[k] = resource.NewStringProperty("foo")
|
|
|
|
}
|
|
|
|
|
|
|
|
return &resource.State{
|
|
|
|
Type: urn.Type(),
|
|
|
|
URN: urn,
|
|
|
|
Custom: custom,
|
|
|
|
Delete: false,
|
|
|
|
ID: id,
|
|
|
|
Inputs: inputs,
|
|
|
|
Outputs: outputs,
|
|
|
|
Dependencies: dependencies,
|
|
|
|
PropertyDependencies: propertyDeps,
|
|
|
|
Provider: provider,
|
|
|
|
Parent: parent,
|
|
|
|
}
|
|
|
|
}
|