From b3633b531a97e053eb08b4000705c99e14dc1b76 Mon Sep 17 00:00:00 2001 From: Ian Wahbe Date: Wed, 24 Nov 2021 16:07:57 -0800 Subject: [PATCH] Add support for adds and deletes in detailed diffs Supporting unit tests are included. --- sdk/go/common/resource/plugin/provider.go | 56 ++++++-- .../common/resource/plugin/provider_test.go | 126 ++++++++++++++++++ 2 files changed, 170 insertions(+), 12 deletions(-) create mode 100644 sdk/go/common/resource/plugin/provider_test.go diff --git a/sdk/go/common/resource/plugin/provider.go b/sdk/go/common/resource/plugin/provider.go index 8f3a7a15b..50efbe22f 100644 --- a/sdk/go/common/resource/plugin/provider.go +++ b/sdk/go/common/resource/plugin/provider.go @@ -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. @@ -218,41 +218,66 @@ type DiffResult struct { DeleteBeforeReplace bool // if true, this resource must be deleted before recreating it. } -// Computes the detailed diff of Updated keys. +// Computes the detailed diff of Updated, Added and Deleted keys. func NewDetailedDiffFromObjectDiff(diff *resource.ObjectDiff) map[string]PropertyDiff { - return objectDiffToDetailsDiff("", diff) + if diff == nil { + return map[string]PropertyDiff{} + } + return objectDiffToDetailedDiff("", diff) } -func objectDiffToDetailsDiff(prefix string, diff *resource.ObjectDiff) map[string]PropertyDiff { +func objectDiffToDetailedDiff(prefix string, diff *resource.ObjectDiff) map[string]PropertyDiff { ret := map[string]PropertyDiff{} - for k, vd := range diff.Updates { - var nestedPrefix string + + getPrefix := func(k resource.PropertyKey) string { if prefix == "" { - nestedPrefix = string(k) - } else { - nestedPrefix = fmt.Sprintf("%s.%s", prefix, string(k)) + 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(fmt.Sprintf("%s[%d]", prefix, i), vd) { + 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 objectDiffToDetailsDiff(prefix, vd.Object) { + for kk, pd := range objectDiffToDetailedDiff(prefix, vd.Object) { ret[kk] = pd } } else if vd.Array != nil { @@ -260,7 +285,14 @@ func valueDiffToDetailedDiff(prefix string, vd resource.ValueDiff) map[string]Pr ret[kk] = pd } } else { - ret[prefix] = PropertyDiff{Kind: DiffUpdate} + 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 } diff --git a/sdk/go/common/resource/plugin/provider_test.go b/sdk/go/common/resource/plugin/provider_test.go new file mode 100644 index 000000000..11918cb53 --- /dev/null +++ b/sdk/go/common/resource/plugin/provider_test.go @@ -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) + }) + } +}