diff --git a/command/format/diff.go b/command/format/diff.go index b081be73a..4db9b09c1 100644 --- a/command/format/diff.go +++ b/command/format/diff.go @@ -9,6 +9,7 @@ import ( "github.com/mitchellh/colorstring" "github.com/zclconf/go-cty/cty" + ctyjson "github.com/zclconf/go-cty/cty/json" "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/configs/configschema" @@ -422,6 +423,24 @@ func (p *blockBodyDiffPrinter) writeValue(val cty.Value, action plans.Action, in case ty.IsPrimitiveType(): switch ty { case cty.String: + { + // Special behavior for JSON strings + src := []byte(val.AsString()) + ty, err := ctyjson.ImpliedType(src) + if err == nil { + jv, err := ctyjson.Unmarshal(src, ty) + if err == nil { + p.buf.WriteString("jsonencode(") + p.buf.WriteByte('\n') + p.buf.WriteString(strings.Repeat(" ", indent+4)) + p.writeValue(jv, action, indent+4) + p.buf.WriteByte('\n') + p.buf.WriteString(strings.Repeat(" ", indent)) + p.buf.WriteByte(')') + break // don't *also* do the normal behavior below + } + } + } fmt.Fprintf(p.buf, "%q", val.AsString()) case cty.Bool: if val.True() { @@ -516,12 +535,42 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa if old.IsKnown() && new.IsKnown() && !old.IsNull() && !new.IsNull() { switch { // TODO: list diffs using longest-common-subsequence matching algorithm + // TODO: object diffs that behave a bit like the map diffs, including if the two object types don't exactly match // TODO: multi-line string diffs showing lines added/removed using longest-common-subsequence case ty == cty.String: - // We only have special behavior for multi-line strings here + // We have special behavior for both multi-line strings in general + // and for strings that can parse as JSON. For the JSON handling + // to apply, both old and new must be valid JSON. + // For single-line strings that don't parse as JSON we just fall + // out of this switch block and do the default old -> new rendering. oldS := old.AsString() newS := new.AsString() + + { + // Special behavior for JSON strings + oldBytes := []byte(oldS) + newBytes := []byte(newS) + oldType, oldErr := ctyjson.ImpliedType(oldBytes) + newType, newErr := ctyjson.ImpliedType(newBytes) + if oldErr == nil && newErr == nil { + oldJV, oldErr := ctyjson.Unmarshal(oldBytes, oldType) + newJV, newErr := ctyjson.Unmarshal(newBytes, newType) + if oldErr == nil && newErr == nil { + if !oldJV.RawEquals(newJV) { // two JSON values may differ only in insignificant whitespace + p.buf.WriteString("jsonencode(") + p.buf.WriteByte('\n') + p.buf.WriteString(strings.Repeat(" ", indent+4)) + p.writeValueDiff(oldJV, newJV, indent+4, path) + p.buf.WriteByte('\n') + p.buf.WriteString(strings.Repeat(" ", indent)) + p.buf.WriteByte(')') + return + } + } + } + } + if strings.Index(oldS, "\n") < 0 && strings.Index(newS, "\n") < 0 { break }