Parameterize TestReplaceOnChanges with engine diff
This commit also adds DetailedDiff to the engine diff.
This commit is contained in:
parent
eafc611b43
commit
702e4ba6f5
|
@ -944,156 +944,147 @@ func TestSingleResourceIgnoreChanges(t *testing.T) {
|
|||
}, []string{"a", "b"}, []deploy.StepOp{deploy.OpUpdate})
|
||||
}
|
||||
|
||||
func objectDiffToDetailedDiff(prefix string, d *resource.ObjectDiff) map[string]plugin.PropertyDiff {
|
||||
ret := map[string]plugin.PropertyDiff{}
|
||||
for k, vd := range d.Updates {
|
||||
var nestedPrefix string
|
||||
if prefix == "" {
|
||||
nestedPrefix = string(k)
|
||||
} else {
|
||||
nestedPrefix = fmt.Sprintf("%s.%s", prefix, string(k))
|
||||
}
|
||||
for kk, pd := range valueDiffToDetailedDiff(nestedPrefix, vd) {
|
||||
ret[kk] = pd
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
type DiffFunc = func(urn resource.URN, id resource.ID,
|
||||
olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error)
|
||||
|
||||
func arrayDiffToDetailedDiff(prefix string, d *resource.ArrayDiff) map[string]plugin.PropertyDiff {
|
||||
ret := map[string]plugin.PropertyDiff{}
|
||||
for i, vd := range d.Updates {
|
||||
for kk, pd := range valueDiffToDetailedDiff(fmt.Sprintf("%s[%d]", prefix, i), vd) {
|
||||
ret[kk] = pd
|
||||
func replaceOnChangesTest(t *testing.T, name string, diffFunc DiffFunc) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
loaders := []*deploytest.ProviderLoader{
|
||||
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
||||
return &deploytest.Provider{
|
||||
DiffF: diffFunc,
|
||||
UpdateF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap, timeout float64,
|
||||
ignoreChanges []string, preview bool) (resource.PropertyMap, resource.Status, error) {
|
||||
return news, resource.StatusOK, nil
|
||||
},
|
||||
CreateF: func(urn resource.URN, inputs resource.PropertyMap, timeout float64,
|
||||
preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
|
||||
return resource.ID("id123"), inputs, resource.StatusOK, nil
|
||||
},
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func valueDiffToDetailedDiff(prefix string, vd resource.ValueDiff) map[string]plugin.PropertyDiff {
|
||||
ret := map[string]plugin.PropertyDiff{}
|
||||
if vd.Object != nil {
|
||||
for kk, pd := range objectDiffToDetailedDiff(prefix, vd.Object) {
|
||||
ret[kk] = pd
|
||||
updateProgramWithProps := func(snap *deploy.Snapshot, props resource.PropertyMap, replaceOnChanges []string,
|
||||
allowedOps []deploy.StepOp) *deploy.Snapshot {
|
||||
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
||||
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
||||
Inputs: props,
|
||||
ReplaceOnChanges: replaceOnChanges,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
return nil
|
||||
})
|
||||
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
||||
p := &TestPlan{
|
||||
Options: UpdateOptions{Host: host},
|
||||
Steps: []TestStep{
|
||||
{
|
||||
Op: Update,
|
||||
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
||||
events []Event, res result.Result) result.Result {
|
||||
for _, event := range events {
|
||||
if event.Type == ResourcePreEvent {
|
||||
payload := event.Payload().(ResourcePreEventPayload)
|
||||
assert.Subset(t, allowedOps, []deploy.StepOp{payload.Metadata.Op})
|
||||
}
|
||||
}
|
||||
return res
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return p.Run(t, snap)
|
||||
}
|
||||
} else if vd.Array != nil {
|
||||
for kk, pd := range arrayDiffToDetailedDiff(prefix, vd.Array) {
|
||||
ret[kk] = pd
|
||||
|
||||
snap := updateProgramWithProps(nil, resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"a": 1,
|
||||
"b": map[string]interface{}{
|
||||
"c": "foo",
|
||||
},
|
||||
}), []string{"a", "b.c"}, []deploy.StepOp{deploy.OpCreate})
|
||||
|
||||
// Ensure that a change to a replaceOnChange property results in an OpReplace
|
||||
snap = updateProgramWithProps(snap, resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"a": 2,
|
||||
"b": map[string]interface{}{
|
||||
"c": "foo",
|
||||
},
|
||||
}), []string{"a"}, []deploy.StepOp{deploy.OpReplace, deploy.OpCreateReplacement, deploy.OpDeleteReplaced})
|
||||
|
||||
// Ensure that a change to a nested replaceOnChange property results in an OpReplace
|
||||
snap = updateProgramWithProps(snap, resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"a": 2,
|
||||
"b": map[string]interface{}{
|
||||
"c": "bar",
|
||||
},
|
||||
}), []string{"b.c"}, []deploy.StepOp{deploy.OpReplace, deploy.OpCreateReplacement, deploy.OpDeleteReplaced})
|
||||
|
||||
// Ensure that a change to any property of a "*" replaceOnChange results in an OpReplace
|
||||
snap = updateProgramWithProps(snap, resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"a": 3,
|
||||
"b": map[string]interface{}{
|
||||
"c": "baz",
|
||||
},
|
||||
}), []string{"*"}, []deploy.StepOp{deploy.OpReplace, deploy.OpCreateReplacement, deploy.OpDeleteReplaced})
|
||||
|
||||
// Ensure that a change to an non-replaceOnChange property results in an OpUpdate
|
||||
snap = updateProgramWithProps(snap, resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"a": 4,
|
||||
"b": map[string]interface{}{
|
||||
"c": "qux",
|
||||
},
|
||||
}), nil, []deploy.StepOp{deploy.OpUpdate})
|
||||
|
||||
// We ensure that we are listing to the engine diff function only when the provider function
|
||||
// is nil. We do this by adding some weirdness to the provider diff function.
|
||||
allowed := []deploy.StepOp{deploy.OpCreateReplacement, deploy.OpReplace, deploy.OpDeleteReplaced}
|
||||
if diffFunc != nil {
|
||||
allowed = []deploy.StepOp{deploy.OpSame}
|
||||
}
|
||||
} else {
|
||||
ret[prefix] = plugin.PropertyDiff{Kind: plugin.DiffUpdate}
|
||||
}
|
||||
return ret
|
||||
snap = updateProgramWithProps(snap, resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"a": 42, // 42 is a special value in the "provider" diff function.
|
||||
"b": map[string]interface{}{
|
||||
"c": "qux",
|
||||
},
|
||||
}), []string{"a"}, allowed)
|
||||
|
||||
_ = snap
|
||||
})
|
||||
}
|
||||
|
||||
func TestReplaceOnChanges(t *testing.T) {
|
||||
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) {
|
||||
|
||||
diff := olds.Diff(news)
|
||||
if diff == nil {
|
||||
return plugin.DiffResult{Changes: plugin.DiffNone}, nil
|
||||
}
|
||||
detailedDiff := objectDiffToDetailedDiff("", diff)
|
||||
var changedKeys []resource.PropertyKey
|
||||
for _, k := range diff.Keys() {
|
||||
if diff.Changed(k) {
|
||||
changedKeys = append(changedKeys, k)
|
||||
}
|
||||
}
|
||||
return plugin.DiffResult{
|
||||
Changes: plugin.DiffSome,
|
||||
ChangedKeys: changedKeys,
|
||||
DetailedDiff: detailedDiff,
|
||||
}, nil
|
||||
},
|
||||
UpdateF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap, timeout float64,
|
||||
ignoreChanges []string, preview bool) (resource.PropertyMap, resource.Status, error) {
|
||||
return news, resource.StatusOK, nil
|
||||
},
|
||||
CreateF: func(urn resource.URN, inputs resource.PropertyMap, timeout float64,
|
||||
preview bool) (resource.ID, resource.PropertyMap, resource.Status, error) {
|
||||
return resource.ID("id123"), inputs, resource.StatusOK, nil
|
||||
},
|
||||
// We simulate a provider that has it's own diff function.
|
||||
replaceOnChangesTest(t, "provider diff",
|
||||
func(urn resource.URN, id resource.ID,
|
||||
olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error) {
|
||||
|
||||
// To establish a observable difference between the provider and engine diff function,
|
||||
// we treat 42 as an OpSame. We use this to check that the right diff function is being
|
||||
// used.
|
||||
for k, v := range news {
|
||||
if v == resource.NewNumberProperty(42) {
|
||||
news[k] = olds[k]
|
||||
}
|
||||
}
|
||||
diff := olds.Diff(news)
|
||||
if diff == nil {
|
||||
return plugin.DiffResult{Changes: plugin.DiffNone}, nil
|
||||
}
|
||||
detailedDiff := plugin.NewDetailedDiffFromObjectDiff(diff)
|
||||
changedKeys := diff.ChangedKeys()
|
||||
|
||||
return plugin.DiffResult{
|
||||
Changes: plugin.DiffSome,
|
||||
ChangedKeys: changedKeys,
|
||||
DetailedDiff: detailedDiff,
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
|
||||
updateProgramWithProps := func(snap *deploy.Snapshot, props resource.PropertyMap, replaceOnChanges []string,
|
||||
allowedOps []deploy.StepOp) *deploy.Snapshot {
|
||||
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
||||
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
||||
Inputs: props,
|
||||
ReplaceOnChanges: replaceOnChanges,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
return nil
|
||||
})
|
||||
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
||||
p := &TestPlan{
|
||||
Options: UpdateOptions{Host: host},
|
||||
Steps: []TestStep{
|
||||
{
|
||||
Op: Update,
|
||||
Validate: func(project workspace.Project, target deploy.Target, entries JournalEntries,
|
||||
events []Event, res result.Result) result.Result {
|
||||
for _, event := range events {
|
||||
if event.Type == ResourcePreEvent {
|
||||
payload := event.Payload().(ResourcePreEventPayload)
|
||||
assert.Subset(t, allowedOps, []deploy.StepOp{payload.Metadata.Op})
|
||||
}
|
||||
}
|
||||
return res
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return p.Run(t, snap)
|
||||
}
|
||||
|
||||
snap := updateProgramWithProps(nil, resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"a": 1,
|
||||
"b": map[string]interface{}{
|
||||
"c": "foo",
|
||||
},
|
||||
}), []string{"a", "b.c"}, []deploy.StepOp{deploy.OpCreate})
|
||||
|
||||
// Ensure that a change to a replaceOnChange property results in an OpReplace
|
||||
snap = updateProgramWithProps(snap, resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"a": 2,
|
||||
"b": map[string]interface{}{
|
||||
"c": "foo",
|
||||
},
|
||||
}), []string{"a"}, []deploy.StepOp{deploy.OpReplace, deploy.OpCreateReplacement, deploy.OpDeleteReplaced})
|
||||
|
||||
// Ensure that a change to a nested replaceOnChange property results in an OpReplace
|
||||
snap = updateProgramWithProps(snap, resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"a": 2,
|
||||
"b": map[string]interface{}{
|
||||
"c": "bar",
|
||||
},
|
||||
}), []string{"b.c"}, []deploy.StepOp{deploy.OpReplace, deploy.OpCreateReplacement, deploy.OpDeleteReplaced})
|
||||
|
||||
// Ensure that a change to any property of a "*" replaceOnChange results in an OpReplace
|
||||
snap = updateProgramWithProps(snap, resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"a": 3,
|
||||
"b": map[string]interface{}{
|
||||
"c": "baz",
|
||||
},
|
||||
}), []string{"*"}, []deploy.StepOp{deploy.OpReplace, deploy.OpCreateReplacement, deploy.OpDeleteReplaced})
|
||||
|
||||
// Ensure that a change to an non-replaceOnChange property results in an OpUpdate
|
||||
snap = updateProgramWithProps(snap, resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"a": 4,
|
||||
"b": map[string]interface{}{
|
||||
"c": "qux",
|
||||
},
|
||||
}), nil, []deploy.StepOp{deploy.OpUpdate})
|
||||
|
||||
_ = snap
|
||||
// We simulate a provider that does not have it's own diff function. This tests the engines diff
|
||||
// function instead.
|
||||
replaceOnChangesTest(t, "engine diff", nil)
|
||||
}
|
||||
|
||||
// Resource is an abstract representation of a resource graph
|
||||
|
|
|
@ -1140,6 +1140,7 @@ func diffResource(urn resource.URN, id resource.ID, oldInputs, oldOutputs,
|
|||
if tmp.AnyChanges() {
|
||||
diff.Changes = plugin.DiffSome
|
||||
diff.ChangedKeys = tmp.ChangedKeys()
|
||||
diff.DetailedDiff = plugin.NewDetailedDiffFromObjectDiff(tmp)
|
||||
} else {
|
||||
diff.Changes = plugin.DiffNone
|
||||
}
|
||||
|
|
|
@ -210,7 +210,7 @@ func TestApplyReplaceOnChangesEmptyDetailedDiff(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
func TestEngineDiffResource(t *testing.T) {
|
||||
func TestEngineDiff(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
oldInputs, newInputs resource.PropertyMap
|
||||
|
|
|
@ -16,6 +16,7 @@ package plugin
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
||||
|
@ -217,6 +218,53 @@ type DiffResult struct {
|
|||
DeleteBeforeReplace bool // if true, this resource must be deleted before recreating it.
|
||||
}
|
||||
|
||||
// Computes the detailed diff of Updated keys.
|
||||
func NewDetailedDiffFromObjectDiff(diff *resource.ObjectDiff) map[string]PropertyDiff {
|
||||
return objectDiffToDetailsDiff("", diff)
|
||||
}
|
||||
|
||||
func objectDiffToDetailsDiff(prefix string, diff *resource.ObjectDiff) map[string]PropertyDiff {
|
||||
ret := map[string]PropertyDiff{}
|
||||
for k, vd := range diff.Updates {
|
||||
var nestedPrefix string
|
||||
if prefix == "" {
|
||||
nestedPrefix = string(k)
|
||||
} else {
|
||||
nestedPrefix = fmt.Sprintf("%s.%s", prefix, string(k))
|
||||
}
|
||||
for kk, pd := range valueDiffToDetailedDiff(nestedPrefix, vd) {
|
||||
ret[kk] = pd
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func arrayDiffToDetailedDiff(prefix string, d *resource.ArrayDiff) map[string]PropertyDiff {
|
||||
ret := map[string]PropertyDiff{}
|
||||
for i, vd := range d.Updates {
|
||||
for kk, pd := range valueDiffToDetailedDiff(fmt.Sprintf("%s[%d]", prefix, i), vd) {
|
||||
ret[kk] = pd
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func valueDiffToDetailedDiff(prefix string, vd resource.ValueDiff) map[string]PropertyDiff {
|
||||
ret := map[string]PropertyDiff{}
|
||||
if vd.Object != nil {
|
||||
for kk, pd := range objectDiffToDetailsDiff(prefix, vd.Object) {
|
||||
ret[kk] = pd
|
||||
}
|
||||
} else if vd.Array != nil {
|
||||
for kk, pd := range arrayDiffToDetailedDiff(prefix, vd.Array) {
|
||||
ret[kk] = pd
|
||||
}
|
||||
} else {
|
||||
ret[prefix] = PropertyDiff{Kind: DiffUpdate}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Replace returns true if this diff represents a replacement.
|
||||
func (r DiffResult) Replace() bool {
|
||||
for _, v := range r.DetailedDiff {
|
||||
|
|
Loading…
Reference in a new issue