Skip to content

Commit

Permalink
[cli] Show rich diffs for JSON/YAML objects/arrays (#9380)
Browse files Browse the repository at this point in the history
When displaying a diff between textual values, first try to decode the
old and new values as JSON and YAML. If both values decode successfully,
render the diff between the decoded values rather than the diff between
the text.

Fixes #5831.
  • Loading branch information
pgavlin committed Apr 13, 2022
1 parent dc3a4a0 commit f6c8492
Show file tree
Hide file tree
Showing 6 changed files with 770 additions and 1 deletion.
3 changes: 3 additions & 0 deletions CHANGELOG_PENDING.md
Expand Up @@ -15,6 +15,9 @@
- [cli] Display richer diffs for texutal property values.
[#9376](https://github.com/pulumi/pulumi/pull/9376)

- [cli] Display richer diffs for JSON/YAML property values.
[#9380](https://github.com/pulumi/pulumi/pull/9380)

### Bug Fixes

- [codegen/node] - Fix an issue with escaping deprecation messages.
Expand Down
60 changes: 60 additions & 0 deletions pkg/backend/display/object_diff.go
Expand Up @@ -16,6 +16,7 @@ package display

import (
"bytes"
"encoding/json"
"fmt"
"io"
"reflect"
Expand All @@ -24,6 +25,7 @@ import (
"strings"

"github.com/sergi/go-diff/diffmatchpatch"
"gopkg.in/yaml.v3"

"github.com/pulumi/pulumi/pkg/v3/engine"
"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
Expand Down Expand Up @@ -1042,6 +1044,10 @@ func escape(s string) string {
}

func (p *propertyPrinter) printTextDiff(old, new string) {
if p.printEncodedValueDiff(old, new) {
return
}

differ := diffmatchpatch.New()
differ.DiffTimeout = 0

Expand Down Expand Up @@ -1152,3 +1158,57 @@ func (p *propertyPrinter) printLineDiff(diffs []diffmatchpatch.Diff) {
}
}
}

func (p *propertyPrinter) printEncodedValueDiff(old, new string) bool {
oldValue, oldKind, ok := p.decodeValue(old)
if !ok {
return false
}

newValue, newKind, ok := p.decodeValue(new)
if !ok {
return false
}

if oldKind == newKind {
p.write("(%s) ", oldKind)
} else {
p.write("(%s => %s) ", oldKind, newKind)
}

diff := oldValue.Diff(newValue, resource.IsInternalPropertyKey)
if diff == nil {
p.withOp(deploy.OpSame).printPropertyValue(oldValue)
return true
}

p.printPropertyValueDiff(func(*propertyPrinter) {}, *diff)
return true
}

func (p *propertyPrinter) decodeValue(repr string) (resource.PropertyValue, string, bool) {
decode := func() (interface{}, string, bool) {
r := strings.NewReader(repr)

var object interface{}
if err := json.NewDecoder(r).Decode(&object); err == nil {
return object, "json", true
}

r.Reset(repr)
if err := yaml.NewDecoder(r).Decode(&object); err == nil {
return object, "yaml", true
}

return nil, "", false
}

object, kind, ok := decode()
if ok {
switch object.(type) {
case []interface{}, map[string]interface{}:
return resource.NewPropertyValue(object), kind, true
}
}
return resource.PropertyValue{}, "", false
}

0 comments on commit f6c8492

Please sign in to comment.