Compare commits
9 commits
master
...
iwahbe/848
Author | SHA1 | Date | |
---|---|---|---|
b3633b531a | |||
702e4ba6f5 | |||
eafc611b43 | |||
c7255f44d5 | |||
5ab42ebf08 | |||
1fc877ff0b | |||
a2b61cbe57 | |||
55dfb78ee3 | |||
b16ed8a16d |
|
@ -8,5 +8,9 @@
|
|||
|
||||
### Bug Fixes
|
||||
|
||||
- [cli/engine] - Accurately computes the fields changed when diffing with unhelpful providers. This
|
||||
allows the `replaceOnChanges` feature to be respected for all providers.
|
||||
[#8488](https://github.com/pulumi/pulumi/pull/8488)
|
||||
|
||||
- [codegen/go] - Respect default values in Pulumi object types.
|
||||
[#8411](https://github.com/pulumi/pulumi/pull/8400)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1132,10 +1132,17 @@ func diffResource(urn resource.URN, id resource.ID, oldInputs, oldOutputs,
|
|||
return diff, err
|
||||
}
|
||||
if diff.Changes == plugin.DiffUnknown {
|
||||
if oldInputs.DeepEquals(newInputs) {
|
||||
diff.Changes = plugin.DiffNone
|
||||
} else {
|
||||
new, res := processIgnoreChanges(newInputs, oldInputs, ignoreChanges)
|
||||
if res != nil {
|
||||
return plugin.DiffResult{}, err
|
||||
}
|
||||
tmp := oldInputs.Diff(new)
|
||||
if tmp.AnyChanges() {
|
||||
diff.Changes = plugin.DiffSome
|
||||
diff.ChangedKeys = tmp.ChangedKeys()
|
||||
diff.DetailedDiff = plugin.NewDetailedDiffFromObjectDiff(tmp)
|
||||
} else {
|
||||
diff.Changes = plugin.DiffNone
|
||||
}
|
||||
}
|
||||
return diff, nil
|
||||
|
|
|
@ -1,8 +1,23 @@
|
|||
// Copyright 2016-2021, Pulumi Corporation.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -194,3 +209,93 @@ func TestApplyReplaceOnChangesEmptyDetailedDiff(t *testing.T) {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEngineDiff(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
oldInputs, newInputs resource.PropertyMap
|
||||
ignoreChanges []string
|
||||
expected []resource.PropertyKey
|
||||
expectedChanges plugin.DiffChanges
|
||||
}{
|
||||
{
|
||||
name: "Empty diff",
|
||||
oldInputs: resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"val1": resource.NewPropertyValue(8),
|
||||
"val2": resource.NewPropertyValue("hello"),
|
||||
}),
|
||||
newInputs: resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"val1": resource.NewPropertyValue(8),
|
||||
"val2": resource.NewPropertyValue("hello"),
|
||||
}),
|
||||
expected: nil,
|
||||
expectedChanges: plugin.DiffNone,
|
||||
},
|
||||
{
|
||||
name: "All changes",
|
||||
oldInputs: resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"val0": resource.NewPropertyValue(3.14),
|
||||
}),
|
||||
newInputs: resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"val1": resource.NewNumberProperty(42),
|
||||
"val2": resource.NewPropertyValue("world"),
|
||||
}),
|
||||
expected: []resource.PropertyKey{"val0", "val1", "val2"},
|
||||
expectedChanges: plugin.DiffSome,
|
||||
},
|
||||
{
|
||||
name: "Some changes",
|
||||
oldInputs: resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"val1": resource.NewPropertyValue(42),
|
||||
}),
|
||||
newInputs: resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"val1": resource.NewNumberProperty(42),
|
||||
"val2": resource.NewPropertyValue("world"),
|
||||
}),
|
||||
expected: []resource.PropertyKey{"val2"},
|
||||
expectedChanges: plugin.DiffSome,
|
||||
},
|
||||
{
|
||||
name: "Ignore some changes",
|
||||
oldInputs: resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"val1": resource.NewPropertyValue("hello"),
|
||||
}),
|
||||
newInputs: resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"val2": resource.NewPropertyValue(8),
|
||||
}),
|
||||
|
||||
ignoreChanges: []string{"val1"},
|
||||
expected: []resource.PropertyKey{"val2"},
|
||||
expectedChanges: plugin.DiffSome,
|
||||
},
|
||||
{
|
||||
name: "Ignore all changes",
|
||||
oldInputs: resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"val1": resource.NewPropertyValue("hello"),
|
||||
}),
|
||||
newInputs: resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"val2": resource.NewPropertyValue(8),
|
||||
}),
|
||||
|
||||
ignoreChanges: []string{"val1", "val2"},
|
||||
expected: nil,
|
||||
expectedChanges: plugin.DiffNone,
|
||||
},
|
||||
}
|
||||
urn := resource.URN("urn:pulumi:dev::website-and-lambda::aws:s3/bucket:Bucket::my-bucket")
|
||||
id := resource.ID("someid")
|
||||
var oldOutputs resource.PropertyMap
|
||||
allowUnknowns := false
|
||||
provider := deploytest.Provider{}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
diff, err := diffResource(urn, id, c.oldInputs, oldOutputs, c.newInputs, &provider, allowUnknowns, c.ignoreChanges)
|
||||
t.Logf("diff.ChangedKeys = %v", diff.ChangedKeys)
|
||||
t.Logf("diff.StableKeys = %v", diff.StableKeys)
|
||||
t.Logf("diff.ReplaceKeys = %v", diff.ReplaceKeys)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, c.expectedChanges, diff.Changes)
|
||||
assert.EqualValues(t, c.expected, diff.ChangedKeys)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016-2018, Pulumi Corporation.
|
||||
// Copyright 2016-2021, Pulumi Corporation.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -16,6 +16,7 @@ package plugin
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
||||
|
@ -217,6 +218,85 @@ type DiffResult struct {
|
|||
DeleteBeforeReplace bool // if true, this resource must be deleted before recreating it.
|
||||
}
|
||||
|
||||
// Computes the detailed diff of Updated, Added and Deleted keys.
|
||||
func NewDetailedDiffFromObjectDiff(diff *resource.ObjectDiff) map[string]PropertyDiff {
|
||||
if diff == nil {
|
||||
return map[string]PropertyDiff{}
|
||||
}
|
||||
return objectDiffToDetailedDiff("", diff)
|
||||
}
|
||||
|
||||
func objectDiffToDetailedDiff(prefix string, diff *resource.ObjectDiff) map[string]PropertyDiff {
|
||||
ret := map[string]PropertyDiff{}
|
||||
|
||||
getPrefix := func(k resource.PropertyKey) string {
|
||||
if prefix == "" {
|
||||
return string(k)
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", prefix, string(k))
|
||||
}
|
||||
|
||||
for k, vd := range diff.Updates {
|
||||
nestedPrefix := getPrefix(k)
|
||||
for kk, pd := range valueDiffToDetailedDiff(nestedPrefix, vd) {
|
||||
ret[kk] = pd
|
||||
}
|
||||
}
|
||||
|
||||
for k := range diff.Adds {
|
||||
nestedPrefix := getPrefix(k)
|
||||
ret[nestedPrefix] = PropertyDiff{Kind: DiffAdd}
|
||||
}
|
||||
|
||||
for k := range diff.Deletes {
|
||||
nestedPrefix := getPrefix(k)
|
||||
ret[nestedPrefix] = PropertyDiff{Kind: DiffDelete}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func arrayDiffToDetailedDiff(prefix string, d *resource.ArrayDiff) map[string]PropertyDiff {
|
||||
nestedPrefix := func(i int) string { return fmt.Sprintf("%s[%d]", prefix, i) }
|
||||
ret := map[string]PropertyDiff{}
|
||||
for i, vd := range d.Updates {
|
||||
for kk, pd := range valueDiffToDetailedDiff(nestedPrefix(i), vd) {
|
||||
ret[kk] = pd
|
||||
}
|
||||
}
|
||||
for i := range d.Adds {
|
||||
ret[nestedPrefix(i)] = PropertyDiff{Kind: DiffAdd}
|
||||
}
|
||||
for i := range d.Deletes {
|
||||
ret[nestedPrefix(i)] = PropertyDiff{Kind: DiffDelete}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func valueDiffToDetailedDiff(prefix string, vd resource.ValueDiff) map[string]PropertyDiff {
|
||||
ret := map[string]PropertyDiff{}
|
||||
if vd.Object != nil {
|
||||
for kk, pd := range objectDiffToDetailedDiff(prefix, vd.Object) {
|
||||
ret[kk] = pd
|
||||
}
|
||||
} else if vd.Array != nil {
|
||||
for kk, pd := range arrayDiffToDetailedDiff(prefix, vd.Array) {
|
||||
ret[kk] = pd
|
||||
}
|
||||
} else {
|
||||
switch {
|
||||
case vd.Old.V == nil && vd.New.V != nil:
|
||||
ret[prefix] = PropertyDiff{Kind: DiffAdd}
|
||||
case vd.Old.V != nil && vd.New.V == nil:
|
||||
ret[prefix] = PropertyDiff{Kind: DiffDelete}
|
||||
default:
|
||||
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 {
|
||||
|
|
126
sdk/go/common/resource/plugin/provider_test.go
Normal file
126
sdk/go/common/resource/plugin/provider_test.go
Normal file
|
@ -0,0 +1,126 @@
|
|||
// Copyright 2016-2021, Pulumi Corporation.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewDetailedDiff(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
diff *resource.ObjectDiff
|
||||
expected map[string]PropertyDiff
|
||||
}{
|
||||
{
|
||||
name: "updates",
|
||||
diff: resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"a": 1,
|
||||
"b": map[string]interface{}{
|
||||
"c": 2,
|
||||
"d": 3,
|
||||
},
|
||||
}).Diff(resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"a": -1,
|
||||
"b": map[string]interface{}{
|
||||
"c": -2,
|
||||
"d": 3,
|
||||
},
|
||||
})),
|
||||
expected: map[string]PropertyDiff{
|
||||
"a": {
|
||||
Kind: DiffUpdate,
|
||||
},
|
||||
"b.c": {
|
||||
Kind: DiffUpdate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "adds and deletes",
|
||||
diff: resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"b": map[string]interface{}{
|
||||
"c": 2,
|
||||
"d": 3,
|
||||
},
|
||||
}).Diff(resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"a": 1,
|
||||
"b": map[string]interface{}{
|
||||
"d": 3,
|
||||
},
|
||||
})),
|
||||
expected: map[string]PropertyDiff{
|
||||
"a": {
|
||||
Kind: DiffAdd,
|
||||
},
|
||||
"b.c": {
|
||||
Kind: DiffDelete,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "arrays",
|
||||
diff: resource.NewPropertyMapFromMap(map[string]interface{}{
|
||||
"a": []interface{}{
|
||||
map[string]interface{}{
|
||||
"a": 1,
|
||||
"b": []interface{}{
|
||||
2,
|
||||
3,
|
||||
},
|
||||
},
|
||||
},
|
||||
}).Diff(resource.NewPropertyMapFromMap(
|
||||
map[string]interface{}{
|
||||
"a": []interface{}{
|
||||
map[string]interface{}{
|
||||
"a": -1,
|
||||
"b": []interface{}{
|
||||
2,
|
||||
},
|
||||
},
|
||||
4,
|
||||
},
|
||||
})),
|
||||
expected: map[string]PropertyDiff{
|
||||
"a[0].a": {
|
||||
Kind: DiffUpdate,
|
||||
},
|
||||
"a[0].b[1]": {
|
||||
Kind: DiffDelete,
|
||||
},
|
||||
"a[1]": {
|
||||
Kind: DiffAdd,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil diff",
|
||||
diff: nil,
|
||||
expected: map[string]PropertyDiff{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
actual := NewDetailedDiffFromObjectDiff(c.diff)
|
||||
assert.Equal(t, c.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -55,6 +55,12 @@ func (diff *ObjectDiff) Same(k PropertyKey) bool {
|
|||
return !diff.Changed(k)
|
||||
}
|
||||
|
||||
// Returns true if there are no changes (adds, deletes, updates) in the diff. Also returns true if
|
||||
// diff is nil. Otherwise returns false.
|
||||
func (diff *ObjectDiff) AnyChanges() bool {
|
||||
return diff != nil && len(diff.Adds)+len(diff.Deletes)+len(diff.Updates) > 0
|
||||
}
|
||||
|
||||
// Keys returns a stable snapshot of all keys known to this object, across adds, deletes, sames, and updates.
|
||||
func (diff *ObjectDiff) Keys() []PropertyKey {
|
||||
var ks []PropertyKey
|
||||
|
@ -74,6 +80,19 @@ func (diff *ObjectDiff) Keys() []PropertyKey {
|
|||
return ks
|
||||
}
|
||||
|
||||
// All keys where Changed(k) = true.
|
||||
func (diff *ObjectDiff) ChangedKeys() []PropertyKey {
|
||||
var ks []PropertyKey
|
||||
if diff != nil {
|
||||
for _, k := range diff.Keys() {
|
||||
if diff.Changed(k) {
|
||||
ks = append(ks, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ks
|
||||
}
|
||||
|
||||
// ValueDiff holds the results of diffing two property values.
|
||||
type ValueDiff struct {
|
||||
Old PropertyValue // the old value.
|
||||
|
|
Loading…
Reference in a new issue