mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Merge pull request #26187 from hashicorp/alisdair/concise-diff
command: Add experimental concise diff renderer
This commit is contained in:
commit
a18e1cb24f
@ -289,12 +289,9 @@ Terraform will perform the following actions:
|
|||||||
|
|
||||||
# test_instance.foo is tainted, so must be replaced
|
# test_instance.foo is tainted, so must be replaced
|
||||||
-/+ resource "test_instance" "foo" {
|
-/+ resource "test_instance" "foo" {
|
||||||
ami = "bar"
|
# (1 unchanged attribute hidden)
|
||||||
|
|
||||||
network_interface {
|
# (1 unchanged block hidden)
|
||||||
description = "Main network interface"
|
|
||||||
device_index = 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Plan: 1 to add, 0 to change, 1 to destroy.`
|
Plan: 1 to add, 0 to change, 1 to destroy.`
|
||||||
@ -468,12 +465,9 @@ Terraform will perform the following actions:
|
|||||||
|
|
||||||
# test_instance.foo is tainted, so must be replaced
|
# test_instance.foo is tainted, so must be replaced
|
||||||
+/- resource "test_instance" "foo" {
|
+/- resource "test_instance" "foo" {
|
||||||
ami = "bar"
|
# (1 unchanged attribute hidden)
|
||||||
|
|
||||||
network_interface {
|
# (1 unchanged block hidden)
|
||||||
description = "Main network interface"
|
|
||||||
device_index = 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Plan: 1 to add, 0 to change, 1 to destroy.`
|
Plan: 1 to add, 0 to change, 1 to destroy.`
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
|
"github.com/hashicorp/terraform/helper/experiment"
|
||||||
"github.com/hashicorp/terraform/plans"
|
"github.com/hashicorp/terraform/plans"
|
||||||
"github.com/hashicorp/terraform/plans/objchange"
|
"github.com/hashicorp/terraform/plans/objchange"
|
||||||
"github.com/hashicorp/terraform/states"
|
"github.com/hashicorp/terraform/states"
|
||||||
@ -98,6 +99,7 @@ func ResourceChange(
|
|||||||
color: color,
|
color: color,
|
||||||
action: change.Action,
|
action: change.Action,
|
||||||
requiredReplace: change.RequiredReplace,
|
requiredReplace: change.RequiredReplace,
|
||||||
|
concise: experiment.Enabled(experiment.X_concise_diff),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Most commonly-used resources have nested blocks that result in us
|
// Most commonly-used resources have nested blocks that result in us
|
||||||
@ -123,10 +125,10 @@ func ResourceChange(
|
|||||||
changeV.Change.Before = objchange.NormalizeObjectFromLegacySDK(changeV.Change.Before, schema)
|
changeV.Change.Before = objchange.NormalizeObjectFromLegacySDK(changeV.Change.Before, schema)
|
||||||
changeV.Change.After = objchange.NormalizeObjectFromLegacySDK(changeV.Change.After, schema)
|
changeV.Change.After = objchange.NormalizeObjectFromLegacySDK(changeV.Change.After, schema)
|
||||||
|
|
||||||
bodyWritten := p.writeBlockBodyDiff(schema, changeV.Before, changeV.After, 6, path)
|
result := p.writeBlockBodyDiff(schema, changeV.Before, changeV.After, 6, path)
|
||||||
if bodyWritten {
|
if result.bodyWritten {
|
||||||
buf.WriteString("\n")
|
p.buf.WriteString("\n")
|
||||||
buf.WriteString(strings.Repeat(" ", 4))
|
p.buf.WriteString(strings.Repeat(" ", 4))
|
||||||
}
|
}
|
||||||
buf.WriteString("}\n")
|
buf.WriteString("}\n")
|
||||||
|
|
||||||
@ -144,9 +146,10 @@ func OutputChanges(
|
|||||||
) string {
|
) string {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
p := blockBodyDiffPrinter{
|
p := blockBodyDiffPrinter{
|
||||||
buf: &buf,
|
buf: &buf,
|
||||||
color: color,
|
color: color,
|
||||||
action: plans.Update, // not actually used in this case, because we're not printing a containing block
|
action: plans.Update, // not actually used in this case, because we're not printing a containing block
|
||||||
|
concise: experiment.Enabled(experiment.X_concise_diff),
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're going to reuse the codepath we used for printing resource block
|
// We're going to reuse the codepath we used for printing resource block
|
||||||
@ -189,16 +192,24 @@ type blockBodyDiffPrinter struct {
|
|||||||
color *colorstring.Colorize
|
color *colorstring.Colorize
|
||||||
action plans.Action
|
action plans.Action
|
||||||
requiredReplace cty.PathSet
|
requiredReplace cty.PathSet
|
||||||
|
concise bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type blockBodyDiffResult struct {
|
||||||
|
bodyWritten bool
|
||||||
|
skippedAttributes int
|
||||||
|
skippedBlocks int
|
||||||
}
|
}
|
||||||
|
|
||||||
const forcesNewResourceCaption = " [red]# forces replacement[reset]"
|
const forcesNewResourceCaption = " [red]# forces replacement[reset]"
|
||||||
|
|
||||||
// writeBlockBodyDiff writes attribute or block differences
|
// writeBlockBodyDiff writes attribute or block differences
|
||||||
// and returns true if any differences were found and written
|
// and returns true if any differences were found and written
|
||||||
func (p *blockBodyDiffPrinter) writeBlockBodyDiff(schema *configschema.Block, old, new cty.Value, indent int, path cty.Path) bool {
|
func (p *blockBodyDiffPrinter) writeBlockBodyDiff(schema *configschema.Block, old, new cty.Value, indent int, path cty.Path) blockBodyDiffResult {
|
||||||
path = ctyEnsurePathCapacity(path, 1)
|
path = ctyEnsurePathCapacity(path, 1)
|
||||||
|
|
||||||
bodyWritten := false
|
result := blockBodyDiffResult{}
|
||||||
|
|
||||||
blankBeforeBlocks := false
|
blankBeforeBlocks := false
|
||||||
{
|
{
|
||||||
attrNames := make([]string, 0, len(schema.Attributes))
|
attrNames := make([]string, 0, len(schema.Attributes))
|
||||||
@ -229,8 +240,21 @@ func (p *blockBodyDiffPrinter) writeBlockBodyDiff(schema *configschema.Block, ol
|
|||||||
oldVal := ctyGetAttrMaybeNull(old, name)
|
oldVal := ctyGetAttrMaybeNull(old, name)
|
||||||
newVal := ctyGetAttrMaybeNull(new, name)
|
newVal := ctyGetAttrMaybeNull(new, name)
|
||||||
|
|
||||||
bodyWritten = true
|
result.bodyWritten = true
|
||||||
p.writeAttrDiff(name, attrS, oldVal, newVal, attrNameLen, indent, path)
|
skipped := p.writeAttrDiff(name, attrS, oldVal, newVal, attrNameLen, indent, path)
|
||||||
|
if skipped {
|
||||||
|
result.skippedAttributes++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.skippedAttributes > 0 {
|
||||||
|
noun := "attributes"
|
||||||
|
if result.skippedAttributes == 1 {
|
||||||
|
noun = "attribute"
|
||||||
|
}
|
||||||
|
p.buf.WriteString("\n")
|
||||||
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
||||||
|
p.buf.WriteString(p.color.Color(fmt.Sprintf("[dark_gray]# (%d unchanged %s hidden)[reset]", result.skippedAttributes, noun)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,21 +270,31 @@ func (p *blockBodyDiffPrinter) writeBlockBodyDiff(schema *configschema.Block, ol
|
|||||||
oldVal := ctyGetAttrMaybeNull(old, name)
|
oldVal := ctyGetAttrMaybeNull(old, name)
|
||||||
newVal := ctyGetAttrMaybeNull(new, name)
|
newVal := ctyGetAttrMaybeNull(new, name)
|
||||||
|
|
||||||
bodyWritten = true
|
result.bodyWritten = true
|
||||||
p.writeNestedBlockDiffs(name, blockS, oldVal, newVal, blankBeforeBlocks, indent, path)
|
skippedBlocks := p.writeNestedBlockDiffs(name, blockS, oldVal, newVal, blankBeforeBlocks, indent, path)
|
||||||
|
if skippedBlocks > 0 {
|
||||||
|
result.skippedBlocks += skippedBlocks
|
||||||
|
}
|
||||||
|
|
||||||
// Always include a blank for any subsequent block types.
|
// Always include a blank for any subsequent block types.
|
||||||
blankBeforeBlocks = true
|
blankBeforeBlocks = true
|
||||||
}
|
}
|
||||||
|
if result.skippedBlocks > 0 {
|
||||||
|
noun := "blocks"
|
||||||
|
if result.skippedBlocks == 1 {
|
||||||
|
noun = "block"
|
||||||
|
}
|
||||||
|
p.buf.WriteString("\n")
|
||||||
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
||||||
|
p.buf.WriteString(p.color.Color(fmt.Sprintf("[dark_gray]# (%d unchanged %s hidden)[reset]", result.skippedBlocks, noun)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bodyWritten
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *blockBodyDiffPrinter) writeAttrDiff(name string, attrS *configschema.Attribute, old, new cty.Value, nameLen, indent int, path cty.Path) {
|
func (p *blockBodyDiffPrinter) writeAttrDiff(name string, attrS *configschema.Attribute, old, new cty.Value, nameLen, indent int, path cty.Path) bool {
|
||||||
path = append(path, cty.GetAttrStep{Name: name})
|
path = append(path, cty.GetAttrStep{Name: name})
|
||||||
p.buf.WriteString("\n")
|
|
||||||
p.buf.WriteString(strings.Repeat(" ", indent))
|
|
||||||
showJustNew := false
|
showJustNew := false
|
||||||
var action plans.Action
|
var action plans.Action
|
||||||
switch {
|
switch {
|
||||||
@ -276,6 +310,12 @@ func (p *blockBodyDiffPrinter) writeAttrDiff(name string, attrS *configschema.At
|
|||||||
action = plans.Update
|
action = plans.Update
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if action == plans.NoOp && p.concise && !identifyingAttribute(name, attrS) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
p.buf.WriteString("\n")
|
||||||
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
||||||
p.writeActionSymbol(action)
|
p.writeActionSymbol(action)
|
||||||
|
|
||||||
p.buf.WriteString(p.color.Color("[bold]"))
|
p.buf.WriteString(p.color.Color("[bold]"))
|
||||||
@ -300,13 +340,16 @@ func (p *blockBodyDiffPrinter) writeAttrDiff(name string, attrS *configschema.At
|
|||||||
p.writeValueDiff(old, new, indent+2, path)
|
p.writeValueDiff(old, new, indent+2, path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *configschema.NestedBlock, old, new cty.Value, blankBefore bool, indent int, path cty.Path) {
|
func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *configschema.NestedBlock, old, new cty.Value, blankBefore bool, indent int, path cty.Path) int {
|
||||||
|
skippedBlocks := 0
|
||||||
path = append(path, cty.GetAttrStep{Name: name})
|
path = append(path, cty.GetAttrStep{Name: name})
|
||||||
if old.IsNull() && new.IsNull() {
|
if old.IsNull() && new.IsNull() {
|
||||||
// Nothing to do if both old and new is null
|
// Nothing to do if both old and new is null
|
||||||
return
|
return skippedBlocks
|
||||||
}
|
}
|
||||||
|
|
||||||
// Where old/new are collections representing a nesting mode other than
|
// Where old/new are collections representing a nesting mode other than
|
||||||
@ -335,7 +378,10 @@ func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *config
|
|||||||
if blankBefore {
|
if blankBefore {
|
||||||
p.buf.WriteRune('\n')
|
p.buf.WriteRune('\n')
|
||||||
}
|
}
|
||||||
p.writeNestedBlockDiff(name, nil, &blockS.Block, action, old, new, indent, path)
|
skipped := p.writeNestedBlockDiff(name, nil, &blockS.Block, action, old, new, indent, path)
|
||||||
|
if skipped {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
case configschema.NestingList:
|
case configschema.NestingList:
|
||||||
// For the sake of handling nested blocks, we'll treat a null list
|
// For the sake of handling nested blocks, we'll treat a null list
|
||||||
// the same as an empty list since the config language doesn't
|
// the same as an empty list since the config language doesn't
|
||||||
@ -377,19 +423,28 @@ func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *config
|
|||||||
if oldItem.RawEquals(newItem) {
|
if oldItem.RawEquals(newItem) {
|
||||||
action = plans.NoOp
|
action = plans.NoOp
|
||||||
}
|
}
|
||||||
p.writeNestedBlockDiff(name, nil, &blockS.Block, action, oldItem, newItem, indent, path)
|
skipped := p.writeNestedBlockDiff(name, nil, &blockS.Block, action, oldItem, newItem, indent, path)
|
||||||
|
if skipped {
|
||||||
|
skippedBlocks++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for i := commonLen; i < len(oldItems); i++ {
|
for i := commonLen; i < len(oldItems); i++ {
|
||||||
path := append(path, cty.IndexStep{Key: cty.NumberIntVal(int64(i))})
|
path := append(path, cty.IndexStep{Key: cty.NumberIntVal(int64(i))})
|
||||||
oldItem := oldItems[i]
|
oldItem := oldItems[i]
|
||||||
newItem := cty.NullVal(oldItem.Type())
|
newItem := cty.NullVal(oldItem.Type())
|
||||||
p.writeNestedBlockDiff(name, nil, &blockS.Block, plans.Delete, oldItem, newItem, indent, path)
|
skipped := p.writeNestedBlockDiff(name, nil, &blockS.Block, plans.Delete, oldItem, newItem, indent, path)
|
||||||
|
if skipped {
|
||||||
|
skippedBlocks++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for i := commonLen; i < len(newItems); i++ {
|
for i := commonLen; i < len(newItems); i++ {
|
||||||
path := append(path, cty.IndexStep{Key: cty.NumberIntVal(int64(i))})
|
path := append(path, cty.IndexStep{Key: cty.NumberIntVal(int64(i))})
|
||||||
newItem := newItems[i]
|
newItem := newItems[i]
|
||||||
oldItem := cty.NullVal(newItem.Type())
|
oldItem := cty.NullVal(newItem.Type())
|
||||||
p.writeNestedBlockDiff(name, nil, &blockS.Block, plans.Create, oldItem, newItem, indent, path)
|
skipped := p.writeNestedBlockDiff(name, nil, &blockS.Block, plans.Create, oldItem, newItem, indent, path)
|
||||||
|
if skipped {
|
||||||
|
skippedBlocks++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case configschema.NestingSet:
|
case configschema.NestingSet:
|
||||||
// For the sake of handling nested blocks, we'll treat a null set
|
// For the sake of handling nested blocks, we'll treat a null set
|
||||||
@ -403,7 +458,7 @@ func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *config
|
|||||||
|
|
||||||
if (len(oldItems) + len(newItems)) == 0 {
|
if (len(oldItems) + len(newItems)) == 0 {
|
||||||
// Nothing to do if both sets are empty
|
// Nothing to do if both sets are empty
|
||||||
return
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
allItems := make([]cty.Value, 0, len(oldItems)+len(newItems))
|
allItems := make([]cty.Value, 0, len(oldItems)+len(newItems))
|
||||||
@ -437,7 +492,10 @@ func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *config
|
|||||||
newValue = val
|
newValue = val
|
||||||
}
|
}
|
||||||
path := append(path, cty.IndexStep{Key: val})
|
path := append(path, cty.IndexStep{Key: val})
|
||||||
p.writeNestedBlockDiff(name, nil, &blockS.Block, action, oldValue, newValue, indent, path)
|
skipped := p.writeNestedBlockDiff(name, nil, &blockS.Block, action, oldValue, newValue, indent, path)
|
||||||
|
if skipped {
|
||||||
|
skippedBlocks++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case configschema.NestingMap:
|
case configschema.NestingMap:
|
||||||
@ -451,7 +509,7 @@ func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *config
|
|||||||
newItems := new.AsValueMap()
|
newItems := new.AsValueMap()
|
||||||
if (len(oldItems) + len(newItems)) == 0 {
|
if (len(oldItems) + len(newItems)) == 0 {
|
||||||
// Nothing to do if both maps are empty
|
// Nothing to do if both maps are empty
|
||||||
return
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
allKeys := make(map[string]bool)
|
allKeys := make(map[string]bool)
|
||||||
@ -489,12 +547,20 @@ func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *config
|
|||||||
}
|
}
|
||||||
|
|
||||||
path := append(path, cty.IndexStep{Key: cty.StringVal(k)})
|
path := append(path, cty.IndexStep{Key: cty.StringVal(k)})
|
||||||
p.writeNestedBlockDiff(name, &k, &blockS.Block, action, oldValue, newValue, indent, path)
|
skipped := p.writeNestedBlockDiff(name, &k, &blockS.Block, action, oldValue, newValue, indent, path)
|
||||||
|
if skipped {
|
||||||
|
skippedBlocks++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return skippedBlocks
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *blockBodyDiffPrinter) writeNestedBlockDiff(name string, label *string, blockS *configschema.Block, action plans.Action, old, new cty.Value, indent int, path cty.Path) {
|
func (p *blockBodyDiffPrinter) writeNestedBlockDiff(name string, label *string, blockS *configschema.Block, action plans.Action, old, new cty.Value, indent int, path cty.Path) bool {
|
||||||
|
if action == plans.NoOp && p.concise {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
p.buf.WriteString("\n")
|
p.buf.WriteString("\n")
|
||||||
p.buf.WriteString(strings.Repeat(" ", indent))
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
||||||
p.writeActionSymbol(action)
|
p.writeActionSymbol(action)
|
||||||
@ -509,12 +575,14 @@ func (p *blockBodyDiffPrinter) writeNestedBlockDiff(name string, label *string,
|
|||||||
p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
|
p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
|
||||||
}
|
}
|
||||||
|
|
||||||
bodyWritten := p.writeBlockBodyDiff(blockS, old, new, indent+4, path)
|
result := p.writeBlockBodyDiff(blockS, old, new, indent+4, path)
|
||||||
if bodyWritten {
|
if result.bodyWritten {
|
||||||
p.buf.WriteString("\n")
|
p.buf.WriteString("\n")
|
||||||
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
||||||
}
|
}
|
||||||
p.buf.WriteString("}")
|
p.buf.WriteString("}")
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *blockBodyDiffPrinter) writeValue(val cty.Value, action plans.Action, indent int) {
|
func (p *blockBodyDiffPrinter) writeValue(val cty.Value, action plans.Action, indent int) {
|
||||||
@ -819,11 +887,10 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa
|
|||||||
removed = cty.SetValEmpty(ty.ElementType())
|
removed = cty.SetValEmpty(ty.ElementType())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suppressedElements := 0
|
||||||
for it := all.ElementIterator(); it.Next(); {
|
for it := all.ElementIterator(); it.Next(); {
|
||||||
_, val := it.Element()
|
_, val := it.Element()
|
||||||
|
|
||||||
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
|
||||||
|
|
||||||
var action plans.Action
|
var action plans.Action
|
||||||
switch {
|
switch {
|
||||||
case !val.IsKnown():
|
case !val.IsKnown():
|
||||||
@ -836,11 +903,28 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa
|
|||||||
action = plans.NoOp
|
action = plans.NoOp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if action == plans.NoOp && p.concise {
|
||||||
|
suppressedElements++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
||||||
p.writeActionSymbol(action)
|
p.writeActionSymbol(action)
|
||||||
p.writeValue(val, action, indent+4)
|
p.writeValue(val, action, indent+4)
|
||||||
p.buf.WriteString(",\n")
|
p.buf.WriteString(",\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if suppressedElements > 0 {
|
||||||
|
p.writeActionSymbol(plans.NoOp)
|
||||||
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
||||||
|
noun := "elements"
|
||||||
|
if suppressedElements == 1 {
|
||||||
|
noun = "element"
|
||||||
|
}
|
||||||
|
p.buf.WriteString(p.color.Color(fmt.Sprintf("[dark_gray]# (%d unchanged %s hidden)[reset]", suppressedElements, noun)))
|
||||||
|
p.buf.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
p.buf.WriteString(strings.Repeat(" ", indent))
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
||||||
p.buf.WriteString("]")
|
p.buf.WriteString("]")
|
||||||
return
|
return
|
||||||
@ -852,7 +936,74 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa
|
|||||||
p.buf.WriteString("\n")
|
p.buf.WriteString("\n")
|
||||||
|
|
||||||
elemDiffs := ctySequenceDiff(old.AsValueSlice(), new.AsValueSlice())
|
elemDiffs := ctySequenceDiff(old.AsValueSlice(), new.AsValueSlice())
|
||||||
for _, elemDiff := range elemDiffs {
|
|
||||||
|
// Maintain a stack of suppressed lines in the diff for later
|
||||||
|
// display or elision
|
||||||
|
var suppressedElements []*plans.Change
|
||||||
|
var changeShown bool
|
||||||
|
|
||||||
|
for i := 0; i < len(elemDiffs); i++ {
|
||||||
|
// In concise mode, push any no-op diff elements onto the stack
|
||||||
|
if p.concise {
|
||||||
|
for i < len(elemDiffs) && elemDiffs[i].Action == plans.NoOp {
|
||||||
|
suppressedElements = append(suppressedElements, elemDiffs[i])
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have some suppressed elements on the stack…
|
||||||
|
if len(suppressedElements) > 0 {
|
||||||
|
// If we've just rendered a change, display the first
|
||||||
|
// element in the stack as context
|
||||||
|
if changeShown {
|
||||||
|
elemDiff := suppressedElements[0]
|
||||||
|
p.buf.WriteString(strings.Repeat(" ", indent+4))
|
||||||
|
p.writeValue(elemDiff.After, elemDiff.Action, indent+4)
|
||||||
|
p.buf.WriteString(",\n")
|
||||||
|
suppressedElements = suppressedElements[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
hidden := len(suppressedElements)
|
||||||
|
|
||||||
|
// If we're not yet at the end of the list, capture the
|
||||||
|
// last element on the stack as context for the upcoming
|
||||||
|
// change to be rendered
|
||||||
|
var nextContextDiff *plans.Change
|
||||||
|
if hidden > 0 && i < len(elemDiffs) {
|
||||||
|
hidden--
|
||||||
|
nextContextDiff = suppressedElements[hidden]
|
||||||
|
suppressedElements = suppressedElements[:hidden]
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are still hidden elements, show an elision
|
||||||
|
// statement counting them
|
||||||
|
if hidden > 0 {
|
||||||
|
p.writeActionSymbol(plans.NoOp)
|
||||||
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
||||||
|
noun := "elements"
|
||||||
|
if hidden == 1 {
|
||||||
|
noun = "element"
|
||||||
|
}
|
||||||
|
p.buf.WriteString(p.color.Color(fmt.Sprintf("[dark_gray]# (%d unchanged %s hidden)[reset]", hidden, noun)))
|
||||||
|
p.buf.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display the next context diff if it was captured above
|
||||||
|
if nextContextDiff != nil {
|
||||||
|
p.buf.WriteString(strings.Repeat(" ", indent+4))
|
||||||
|
p.writeValue(nextContextDiff.After, nextContextDiff.Action, indent+4)
|
||||||
|
p.buf.WriteString(",\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suppressed elements have now been handled so clear them again
|
||||||
|
suppressedElements = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if i >= len(elemDiffs) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
elemDiff := elemDiffs[i]
|
||||||
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
||||||
p.writeActionSymbol(elemDiff.Action)
|
p.writeActionSymbol(elemDiff.Action)
|
||||||
switch elemDiff.Action {
|
switch elemDiff.Action {
|
||||||
@ -869,10 +1020,12 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa
|
|||||||
}
|
}
|
||||||
|
|
||||||
p.buf.WriteString(",\n")
|
p.buf.WriteString(",\n")
|
||||||
|
changeShown = true
|
||||||
}
|
}
|
||||||
|
|
||||||
p.buf.WriteString(strings.Repeat(" ", indent))
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
||||||
p.buf.WriteString("]")
|
p.buf.WriteString("]")
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
case ty.IsMapType():
|
case ty.IsMapType():
|
||||||
@ -903,6 +1056,7 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa
|
|||||||
|
|
||||||
sort.Strings(allKeys)
|
sort.Strings(allKeys)
|
||||||
|
|
||||||
|
suppressedElements := 0
|
||||||
lastK := ""
|
lastK := ""
|
||||||
for i, k := range allKeys {
|
for i, k := range allKeys {
|
||||||
if i > 0 && lastK == k {
|
if i > 0 && lastK == k {
|
||||||
@ -910,7 +1064,6 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa
|
|||||||
}
|
}
|
||||||
lastK = k
|
lastK = k
|
||||||
|
|
||||||
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
|
||||||
kV := cty.StringVal(k)
|
kV := cty.StringVal(k)
|
||||||
var action plans.Action
|
var action plans.Action
|
||||||
if old.HasIndex(kV).False() {
|
if old.HasIndex(kV).False() {
|
||||||
@ -923,8 +1076,14 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa
|
|||||||
action = plans.Update
|
action = plans.Update
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if action == plans.NoOp && p.concise {
|
||||||
|
suppressedElements++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
path := append(path, cty.IndexStep{Key: kV})
|
path := append(path, cty.IndexStep{Key: kV})
|
||||||
|
|
||||||
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
||||||
p.writeActionSymbol(action)
|
p.writeActionSymbol(action)
|
||||||
p.writeValue(kV, action, indent+4)
|
p.writeValue(kV, action, indent+4)
|
||||||
p.buf.WriteString(strings.Repeat(" ", keyLen-len(k)))
|
p.buf.WriteString(strings.Repeat(" ", keyLen-len(k)))
|
||||||
@ -946,8 +1105,20 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa
|
|||||||
p.buf.WriteByte('\n')
|
p.buf.WriteByte('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if suppressedElements > 0 {
|
||||||
|
p.writeActionSymbol(plans.NoOp)
|
||||||
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
||||||
|
noun := "elements"
|
||||||
|
if suppressedElements == 1 {
|
||||||
|
noun = "element"
|
||||||
|
}
|
||||||
|
p.buf.WriteString(p.color.Color(fmt.Sprintf("[dark_gray]# (%d unchanged %s hidden)[reset]", suppressedElements, noun)))
|
||||||
|
p.buf.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
p.buf.WriteString(strings.Repeat(" ", indent))
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
||||||
p.buf.WriteString("}")
|
p.buf.WriteString("}")
|
||||||
|
|
||||||
return
|
return
|
||||||
case ty.IsObjectType():
|
case ty.IsObjectType():
|
||||||
p.buf.WriteString("{")
|
p.buf.WriteString("{")
|
||||||
@ -976,6 +1147,7 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa
|
|||||||
|
|
||||||
sort.Strings(allKeys)
|
sort.Strings(allKeys)
|
||||||
|
|
||||||
|
suppressedElements := 0
|
||||||
lastK := ""
|
lastK := ""
|
||||||
for i, k := range allKeys {
|
for i, k := range allKeys {
|
||||||
if i > 0 && lastK == k {
|
if i > 0 && lastK == k {
|
||||||
@ -983,7 +1155,6 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa
|
|||||||
}
|
}
|
||||||
lastK = k
|
lastK = k
|
||||||
|
|
||||||
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
|
||||||
kV := k
|
kV := k
|
||||||
var action plans.Action
|
var action plans.Action
|
||||||
if !old.Type().HasAttribute(kV) {
|
if !old.Type().HasAttribute(kV) {
|
||||||
@ -996,8 +1167,14 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa
|
|||||||
action = plans.Update
|
action = plans.Update
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if action == plans.NoOp && p.concise {
|
||||||
|
suppressedElements++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
path := append(path, cty.GetAttrStep{Name: kV})
|
path := append(path, cty.GetAttrStep{Name: kV})
|
||||||
|
|
||||||
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
||||||
p.writeActionSymbol(action)
|
p.writeActionSymbol(action)
|
||||||
p.buf.WriteString(k)
|
p.buf.WriteString(k)
|
||||||
p.buf.WriteString(strings.Repeat(" ", keyLen-len(k)))
|
p.buf.WriteString(strings.Repeat(" ", keyLen-len(k)))
|
||||||
@ -1020,6 +1197,17 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa
|
|||||||
p.buf.WriteString("\n")
|
p.buf.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if suppressedElements > 0 {
|
||||||
|
p.writeActionSymbol(plans.NoOp)
|
||||||
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
||||||
|
noun := "elements"
|
||||||
|
if suppressedElements == 1 {
|
||||||
|
noun = "element"
|
||||||
|
}
|
||||||
|
p.buf.WriteString(p.color.Color(fmt.Sprintf("[dark_gray]# (%d unchanged %s hidden)[reset]", suppressedElements, noun)))
|
||||||
|
p.buf.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
p.buf.WriteString(strings.Repeat(" ", indent))
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
||||||
p.buf.WriteString("}")
|
p.buf.WriteString("}")
|
||||||
|
|
||||||
@ -1266,3 +1454,11 @@ func DiffActionSymbol(action plans.Action) string {
|
|||||||
return " ?"
|
return " ?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extremely coarse heuristic for determining whether or not a given attribute
|
||||||
|
// name is important for identifying a resource. In the future, this may be
|
||||||
|
// replaced by a flag in the schema, but for now this is likely to be good
|
||||||
|
// enough.
|
||||||
|
func identifyingAttribute(name string, attrSchema *configschema.Attribute) bool {
|
||||||
|
return name == "id" || name == "tags" || name == "name"
|
||||||
|
}
|
||||||
|
@ -4,8 +4,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
|
"github.com/hashicorp/terraform/helper/experiment"
|
||||||
"github.com/hashicorp/terraform/plans"
|
"github.com/hashicorp/terraform/plans"
|
||||||
"github.com/mitchellh/colorstring"
|
"github.com/mitchellh/colorstring"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
@ -204,12 +206,19 @@ func TestResourceChange_primitiveTypes(t *testing.T) {
|
|||||||
Before: cty.ObjectVal(map[string]cty.Value{
|
Before: cty.ObjectVal(map[string]cty.Value{
|
||||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||||
"more_lines": cty.StringVal(`original
|
"more_lines": cty.StringVal(`original
|
||||||
|
long
|
||||||
|
multi-line
|
||||||
|
string
|
||||||
|
field
|
||||||
`),
|
`),
|
||||||
}),
|
}),
|
||||||
After: cty.ObjectVal(map[string]cty.Value{
|
After: cty.ObjectVal(map[string]cty.Value{
|
||||||
"id": cty.UnknownVal(cty.String),
|
"id": cty.UnknownVal(cty.String),
|
||||||
"more_lines": cty.StringVal(`original
|
"more_lines": cty.StringVal(`original
|
||||||
new line
|
extremely long
|
||||||
|
multi-line
|
||||||
|
string
|
||||||
|
field
|
||||||
`),
|
`),
|
||||||
}),
|
}),
|
||||||
Schema: &configschema.Block{
|
Schema: &configschema.Block{
|
||||||
@ -225,7 +234,11 @@ new line
|
|||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
~ more_lines = <<~EOT
|
~ more_lines = <<~EOT
|
||||||
original
|
original
|
||||||
+ new line
|
- long
|
||||||
|
+ extremely long
|
||||||
|
multi-line
|
||||||
|
string
|
||||||
|
field
|
||||||
EOT
|
EOT
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@ -344,6 +357,13 @@ new line
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "blah" -> (known after apply)
|
||||||
|
~ str = "before" -> "after"
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
~ id = "blah" -> (known after apply)
|
~ id = "blah" -> (known after apply)
|
||||||
password = (sensitive value)
|
password = (sensitive value)
|
||||||
@ -435,6 +455,65 @@ new line
|
|||||||
+ forced = "example" # forces replacement
|
+ forced = "example" # forces replacement
|
||||||
name = "name"
|
name = "name"
|
||||||
}
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"show all identifying attributes even if unchanged": {
|
||||||
|
Action: plans.Update,
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Before: cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||||
|
"ami": cty.StringVal("ami-BEFORE"),
|
||||||
|
"bar": cty.StringVal("bar"),
|
||||||
|
"foo": cty.StringVal("foo"),
|
||||||
|
"name": cty.StringVal("alice"),
|
||||||
|
"tags": cty.MapVal(map[string]cty.Value{
|
||||||
|
"name": cty.StringVal("bob"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
After: cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||||
|
"ami": cty.StringVal("ami-AFTER"),
|
||||||
|
"bar": cty.StringVal("bar"),
|
||||||
|
"foo": cty.StringVal("foo"),
|
||||||
|
"name": cty.StringVal("alice"),
|
||||||
|
"tags": cty.MapVal(map[string]cty.Value{
|
||||||
|
"name": cty.StringVal("bob"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
Schema: &configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
||||||
|
"ami": {Type: cty.String, Optional: true},
|
||||||
|
"bar": {Type: cty.String, Optional: true},
|
||||||
|
"foo": {Type: cty.String, Optional: true},
|
||||||
|
"name": {Type: cty.String, Optional: true},
|
||||||
|
"tags": {Type: cty.Map(cty.String), Optional: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RequiredReplace: cty.NewPathSet(),
|
||||||
|
Tainted: false,
|
||||||
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||||
|
id = "i-02ae66f368e8518a9"
|
||||||
|
name = "alice"
|
||||||
|
tags = {
|
||||||
|
"name" = "bob"
|
||||||
|
}
|
||||||
|
# (2 unchanged attributes hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||||
|
bar = "bar"
|
||||||
|
foo = "foo"
|
||||||
|
id = "i-02ae66f368e8518a9"
|
||||||
|
name = "alice"
|
||||||
|
tags = {
|
||||||
|
"name" = "bob"
|
||||||
|
}
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -489,7 +568,7 @@ func TestResourceChange_JSON(t *testing.T) {
|
|||||||
Mode: addrs.ManagedResourceMode,
|
Mode: addrs.ManagedResourceMode,
|
||||||
Before: cty.ObjectVal(map[string]cty.Value{
|
Before: cty.ObjectVal(map[string]cty.Value{
|
||||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||||
"json_field": cty.StringVal(`{"aaa": "value"}`),
|
"json_field": cty.StringVal(`{"aaa": "value","ccc": 5}`),
|
||||||
}),
|
}),
|
||||||
After: cty.ObjectVal(map[string]cty.Value{
|
After: cty.ObjectVal(map[string]cty.Value{
|
||||||
"id": cty.UnknownVal(cty.String),
|
"id": cty.UnknownVal(cty.String),
|
||||||
@ -504,12 +583,26 @@ func TestResourceChange_JSON(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ json_field = jsonencode(
|
||||||
|
~ {
|
||||||
|
+ bbb = "new_value"
|
||||||
|
- ccc = 5 -> null
|
||||||
|
# (1 unchanged element hidden)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
~ json_field = jsonencode(
|
~ json_field = jsonencode(
|
||||||
~ {
|
~ {
|
||||||
aaa = "value"
|
aaa = "value"
|
||||||
+ bbb = "new_value"
|
+ bbb = "new_value"
|
||||||
|
- ccc = 5 -> null
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -637,6 +730,17 @@ func TestResourceChange_JSON(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example must be replaced
|
ExpectedOutput: ` # test_instance.example must be replaced
|
||||||
|
-/+ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ json_field = jsonencode(
|
||||||
|
~ {
|
||||||
|
+ bbb = "new_value"
|
||||||
|
# (1 unchanged element hidden)
|
||||||
|
} # forces replacement
|
||||||
|
)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example must be replaced
|
||||||
-/+ resource "test_instance" "example" {
|
-/+ resource "test_instance" "example" {
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
~ json_field = jsonencode(
|
~ json_field = jsonencode(
|
||||||
@ -757,6 +861,18 @@ func TestResourceChange_JSON(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ json_field = jsonencode(
|
||||||
|
~ [
|
||||||
|
# (1 unchanged element hidden)
|
||||||
|
"second",
|
||||||
|
- "third",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
~ json_field = jsonencode(
|
~ json_field = jsonencode(
|
||||||
@ -789,6 +905,19 @@ func TestResourceChange_JSON(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ json_field = jsonencode(
|
||||||
|
~ [
|
||||||
|
# (1 unchanged element hidden)
|
||||||
|
"second",
|
||||||
|
+ "third",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
~ json_field = jsonencode(
|
~ json_field = jsonencode(
|
||||||
@ -825,8 +954,8 @@ func TestResourceChange_JSON(t *testing.T) {
|
|||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
~ json_field = jsonencode(
|
~ json_field = jsonencode(
|
||||||
~ {
|
~ {
|
||||||
first = "111"
|
|
||||||
+ second = "222"
|
+ second = "222"
|
||||||
|
# (1 unchanged element hidden)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1087,6 +1216,15 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
+ list_field = [
|
||||||
|
+ "new-element",
|
||||||
|
]
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -1121,6 +1259,15 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ list_field = [
|
||||||
|
+ "new-element",
|
||||||
|
]
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -1138,7 +1285,10 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
|||||||
"ami": cty.StringVal("ami-STATIC"),
|
"ami": cty.StringVal("ami-STATIC"),
|
||||||
"list_field": cty.ListVal([]cty.Value{
|
"list_field": cty.ListVal([]cty.Value{
|
||||||
cty.StringVal("aaaa"),
|
cty.StringVal("aaaa"),
|
||||||
cty.StringVal("cccc"),
|
cty.StringVal("bbbb"),
|
||||||
|
cty.StringVal("dddd"),
|
||||||
|
cty.StringVal("eeee"),
|
||||||
|
cty.StringVal("ffff"),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
After: cty.ObjectVal(map[string]cty.Value{
|
After: cty.ObjectVal(map[string]cty.Value{
|
||||||
@ -1148,6 +1298,9 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
|||||||
cty.StringVal("aaaa"),
|
cty.StringVal("aaaa"),
|
||||||
cty.StringVal("bbbb"),
|
cty.StringVal("bbbb"),
|
||||||
cty.StringVal("cccc"),
|
cty.StringVal("cccc"),
|
||||||
|
cty.StringVal("dddd"),
|
||||||
|
cty.StringVal("eeee"),
|
||||||
|
cty.StringVal("ffff"),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
Schema: &configschema.Block{
|
Schema: &configschema.Block{
|
||||||
@ -1160,13 +1313,29 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ list_field = [
|
||||||
|
# (1 unchanged element hidden)
|
||||||
|
"bbbb",
|
||||||
|
+ "cccc",
|
||||||
|
"dddd",
|
||||||
|
# (2 unchanged elements hidden)
|
||||||
|
]
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
~ list_field = [
|
~ list_field = [
|
||||||
"aaaa",
|
"aaaa",
|
||||||
+ "bbbb",
|
"bbbb",
|
||||||
"cccc",
|
+ "cccc",
|
||||||
|
"dddd",
|
||||||
|
"eeee",
|
||||||
|
"ffff",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@ -1203,6 +1372,17 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example must be replaced
|
ExpectedOutput: ` # test_instance.example must be replaced
|
||||||
|
-/+ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ list_field = [ # forces replacement
|
||||||
|
"aaaa",
|
||||||
|
+ "bbbb",
|
||||||
|
"cccc",
|
||||||
|
]
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example must be replaced
|
||||||
-/+ resource "test_instance" "example" {
|
-/+ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -1224,6 +1404,8 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
|||||||
cty.StringVal("aaaa"),
|
cty.StringVal("aaaa"),
|
||||||
cty.StringVal("bbbb"),
|
cty.StringVal("bbbb"),
|
||||||
cty.StringVal("cccc"),
|
cty.StringVal("cccc"),
|
||||||
|
cty.StringVal("dddd"),
|
||||||
|
cty.StringVal("eeee"),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
After: cty.ObjectVal(map[string]cty.Value{
|
After: cty.ObjectVal(map[string]cty.Value{
|
||||||
@ -1231,6 +1413,8 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
|||||||
"ami": cty.StringVal("ami-STATIC"),
|
"ami": cty.StringVal("ami-STATIC"),
|
||||||
"list_field": cty.ListVal([]cty.Value{
|
"list_field": cty.ListVal([]cty.Value{
|
||||||
cty.StringVal("bbbb"),
|
cty.StringVal("bbbb"),
|
||||||
|
cty.StringVal("dddd"),
|
||||||
|
cty.StringVal("eeee"),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
Schema: &configschema.Block{
|
Schema: &configschema.Block{
|
||||||
@ -1243,6 +1427,19 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ list_field = [
|
||||||
|
- "aaaa",
|
||||||
|
"bbbb",
|
||||||
|
- "cccc",
|
||||||
|
"dddd",
|
||||||
|
# (1 unchanged element hidden)
|
||||||
|
]
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -1250,6 +1447,8 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
|||||||
- "aaaa",
|
- "aaaa",
|
||||||
"bbbb",
|
"bbbb",
|
||||||
- "cccc",
|
- "cccc",
|
||||||
|
"dddd",
|
||||||
|
"eeee",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@ -1307,6 +1506,17 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ list_field = [
|
||||||
|
- "aaaa",
|
||||||
|
- "bbbb",
|
||||||
|
- "cccc",
|
||||||
|
]
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -1341,6 +1551,13 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
+ list_field = []
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -1379,6 +1596,18 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ list_field = [
|
||||||
|
"aaaa",
|
||||||
|
- "bbbb",
|
||||||
|
+ (known after apply),
|
||||||
|
"cccc",
|
||||||
|
]
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -1401,6 +1630,8 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
|||||||
cty.StringVal("aaaa"),
|
cty.StringVal("aaaa"),
|
||||||
cty.StringVal("bbbb"),
|
cty.StringVal("bbbb"),
|
||||||
cty.StringVal("cccc"),
|
cty.StringVal("cccc"),
|
||||||
|
cty.StringVal("dddd"),
|
||||||
|
cty.StringVal("eeee"),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
After: cty.ObjectVal(map[string]cty.Value{
|
After: cty.ObjectVal(map[string]cty.Value{
|
||||||
@ -1411,6 +1642,8 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
|||||||
cty.UnknownVal(cty.String),
|
cty.UnknownVal(cty.String),
|
||||||
cty.UnknownVal(cty.String),
|
cty.UnknownVal(cty.String),
|
||||||
cty.StringVal("cccc"),
|
cty.StringVal("cccc"),
|
||||||
|
cty.StringVal("dddd"),
|
||||||
|
cty.StringVal("eeee"),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
Schema: &configschema.Block{
|
Schema: &configschema.Block{
|
||||||
@ -1423,6 +1656,20 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ list_field = [
|
||||||
|
"aaaa",
|
||||||
|
- "bbbb",
|
||||||
|
+ (known after apply),
|
||||||
|
+ (known after apply),
|
||||||
|
"cccc",
|
||||||
|
# (2 unchanged elements hidden)
|
||||||
|
]
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -1432,6 +1679,72 @@ func TestResourceChange_primitiveList(t *testing.T) {
|
|||||||
+ (known after apply),
|
+ (known after apply),
|
||||||
+ (known after apply),
|
+ (known after apply),
|
||||||
"cccc",
|
"cccc",
|
||||||
|
"dddd",
|
||||||
|
"eeee",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
runTestCases(t, testCases)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResourceChange_primitiveTuple(t *testing.T) {
|
||||||
|
testCases := map[string]testCase{
|
||||||
|
"in-place update": {
|
||||||
|
Action: plans.Update,
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Before: cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||||
|
"tuple_field": cty.TupleVal([]cty.Value{
|
||||||
|
cty.StringVal("aaaa"),
|
||||||
|
cty.StringVal("bbbb"),
|
||||||
|
cty.StringVal("dddd"),
|
||||||
|
cty.StringVal("eeee"),
|
||||||
|
cty.StringVal("ffff"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
After: cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||||
|
"tuple_field": cty.TupleVal([]cty.Value{
|
||||||
|
cty.StringVal("aaaa"),
|
||||||
|
cty.StringVal("bbbb"),
|
||||||
|
cty.StringVal("cccc"),
|
||||||
|
cty.StringVal("eeee"),
|
||||||
|
cty.StringVal("ffff"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
Schema: &configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"id": {Type: cty.String, Required: true},
|
||||||
|
"tuple_field": {Type: cty.Tuple([]cty.Type{cty.String, cty.String, cty.String, cty.String, cty.String}), Optional: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RequiredReplace: cty.NewPathSet(),
|
||||||
|
Tainted: false,
|
||||||
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
id = "i-02ae66f368e8518a9"
|
||||||
|
~ tuple_field = [
|
||||||
|
# (1 unchanged element hidden)
|
||||||
|
"bbbb",
|
||||||
|
- "dddd",
|
||||||
|
+ "cccc",
|
||||||
|
"eeee",
|
||||||
|
# (1 unchanged element hidden)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
id = "i-02ae66f368e8518a9"
|
||||||
|
~ tuple_field = [
|
||||||
|
"aaaa",
|
||||||
|
"bbbb",
|
||||||
|
- "dddd",
|
||||||
|
+ "cccc",
|
||||||
|
"eeee",
|
||||||
|
"ffff",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@ -1467,6 +1780,15 @@ func TestResourceChange_primitiveSet(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
+ set_field = [
|
||||||
|
+ "new-element",
|
||||||
|
]
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -1501,6 +1823,15 @@ func TestResourceChange_primitiveSet(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ set_field = [
|
||||||
|
+ "new-element",
|
||||||
|
]
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -1540,6 +1871,16 @@ func TestResourceChange_primitiveSet(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ set_field = [
|
||||||
|
+ "bbbb",
|
||||||
|
# (2 unchanged elements hidden)
|
||||||
|
]
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -1583,6 +1924,16 @@ func TestResourceChange_primitiveSet(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example must be replaced
|
ExpectedOutput: ` # test_instance.example must be replaced
|
||||||
|
-/+ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ set_field = [ # forces replacement
|
||||||
|
+ "bbbb",
|
||||||
|
# (2 unchanged elements hidden)
|
||||||
|
]
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example must be replaced
|
||||||
-/+ resource "test_instance" "example" {
|
-/+ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -1623,6 +1974,17 @@ func TestResourceChange_primitiveSet(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ set_field = [
|
||||||
|
- "aaaa",
|
||||||
|
- "cccc",
|
||||||
|
# (1 unchanged element hidden)
|
||||||
|
]
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -1685,6 +2047,16 @@ func TestResourceChange_primitiveSet(t *testing.T) {
|
|||||||
},
|
},
|
||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ set_field = [
|
||||||
|
- "aaaa",
|
||||||
|
- "bbbb",
|
||||||
|
]
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -1718,6 +2090,13 @@ func TestResourceChange_primitiveSet(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
+ set_field = []
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -1751,6 +2130,16 @@ func TestResourceChange_primitiveSet(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ set_field = [
|
||||||
|
- "aaaa",
|
||||||
|
- "bbbb",
|
||||||
|
] -> (known after apply)
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -1790,6 +2179,17 @@ func TestResourceChange_primitiveSet(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ set_field = [
|
||||||
|
- "bbbb",
|
||||||
|
~ (known after apply),
|
||||||
|
# (1 unchanged element hidden)
|
||||||
|
]
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -1832,6 +2232,15 @@ func TestResourceChange_map(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
+ map_field = {
|
||||||
|
+ "new-key" = "new-element"
|
||||||
|
}
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -1866,6 +2275,15 @@ func TestResourceChange_map(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ map_field = {
|
||||||
|
+ "new-key" = "new-element"
|
||||||
|
}
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -1905,6 +2323,16 @@ func TestResourceChange_map(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ map_field = {
|
||||||
|
+ "b" = "bbbb"
|
||||||
|
# (2 unchanged elements hidden)
|
||||||
|
}
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -1948,6 +2376,16 @@ func TestResourceChange_map(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example must be replaced
|
ExpectedOutput: ` # test_instance.example must be replaced
|
||||||
|
-/+ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ map_field = { # forces replacement
|
||||||
|
+ "b" = "bbbb"
|
||||||
|
# (2 unchanged elements hidden)
|
||||||
|
}
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example must be replaced
|
||||||
-/+ resource "test_instance" "example" {
|
-/+ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -1988,6 +2426,17 @@ func TestResourceChange_map(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ map_field = {
|
||||||
|
- "a" = "aaaa" -> null
|
||||||
|
- "c" = "cccc" -> null
|
||||||
|
# (1 unchanged element hidden)
|
||||||
|
}
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -2056,6 +2505,16 @@ func TestResourceChange_map(t *testing.T) {
|
|||||||
RequiredReplace: cty.NewPathSet(),
|
RequiredReplace: cty.NewPathSet(),
|
||||||
Tainted: false,
|
Tainted: false,
|
||||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
|
~ map_field = {
|
||||||
|
~ "b" = "bbbb" -> (known after apply)
|
||||||
|
# (2 unchanged elements hidden)
|
||||||
|
}
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
~ resource "test_instance" "example" {
|
~ resource "test_instance" "example" {
|
||||||
ami = "ami-STATIC"
|
ami = "ami-STATIC"
|
||||||
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
~ id = "i-02ae66f368e8518a9" -> (known after apply)
|
||||||
@ -2121,6 +2580,14 @@ func TestResourceChange_nestedList(t *testing.T) {
|
|||||||
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||||
id = "i-02ae66f368e8518a9"
|
id = "i-02ae66f368e8518a9"
|
||||||
|
|
||||||
|
# (1 unchanged block hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||||
|
id = "i-02ae66f368e8518a9"
|
||||||
|
|
||||||
root_block_device {
|
root_block_device {
|
||||||
volume_type = "gp2"
|
volume_type = "gp2"
|
||||||
}
|
}
|
||||||
@ -2284,6 +2751,17 @@ func TestResourceChange_nestedList(t *testing.T) {
|
|||||||
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||||
id = "i-02ae66f368e8518a9"
|
id = "i-02ae66f368e8518a9"
|
||||||
|
|
||||||
|
~ root_block_device {
|
||||||
|
+ new_field = "new_value"
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||||
|
id = "i-02ae66f368e8518a9"
|
||||||
|
|
||||||
~ root_block_device {
|
~ root_block_device {
|
||||||
+ new_field = "new_value"
|
+ new_field = "new_value"
|
||||||
volume_type = "gp2"
|
volume_type = "gp2"
|
||||||
@ -2861,6 +3339,17 @@ func TestResourceChange_nestedMap(t *testing.T) {
|
|||||||
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||||
id = "i-02ae66f368e8518a9"
|
id = "i-02ae66f368e8518a9"
|
||||||
|
|
||||||
|
~ root_block_device "a" {
|
||||||
|
+ new_field = "new_value"
|
||||||
|
# (1 unchanged attribute hidden)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||||
|
id = "i-02ae66f368e8518a9"
|
||||||
|
|
||||||
~ root_block_device "a" {
|
~ root_block_device "a" {
|
||||||
+ new_field = "new_value"
|
+ new_field = "new_value"
|
||||||
volume_type = "gp2"
|
volume_type = "gp2"
|
||||||
@ -2927,6 +3416,18 @@ func TestResourceChange_nestedMap(t *testing.T) {
|
|||||||
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||||
id = "i-02ae66f368e8518a9"
|
id = "i-02ae66f368e8518a9"
|
||||||
|
|
||||||
|
+ root_block_device "b" {
|
||||||
|
+ new_field = "new_value"
|
||||||
|
+ volume_type = "gp2"
|
||||||
|
}
|
||||||
|
# (1 unchanged block hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example will be updated in-place
|
||||||
|
~ resource "test_instance" "example" {
|
||||||
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||||
|
id = "i-02ae66f368e8518a9"
|
||||||
|
|
||||||
root_block_device "a" {
|
root_block_device "a" {
|
||||||
volume_type = "gp2"
|
volume_type = "gp2"
|
||||||
}
|
}
|
||||||
@ -2994,6 +3495,17 @@ func TestResourceChange_nestedMap(t *testing.T) {
|
|||||||
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||||
id = "i-02ae66f368e8518a9"
|
id = "i-02ae66f368e8518a9"
|
||||||
|
|
||||||
|
~ root_block_device "a" { # forces replacement
|
||||||
|
~ volume_type = "gp2" -> "different"
|
||||||
|
}
|
||||||
|
# (1 unchanged block hidden)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
VerboseOutput: ` # test_instance.example must be replaced
|
||||||
|
-/+ resource "test_instance" "example" {
|
||||||
|
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||||
|
id = "i-02ae66f368e8518a9"
|
||||||
|
|
||||||
~ root_block_device "a" { # forces replacement
|
~ root_block_device "a" { # forces replacement
|
||||||
~ volume_type = "gp2" -> "different"
|
~ volume_type = "gp2" -> "different"
|
||||||
}
|
}
|
||||||
@ -3119,6 +3631,10 @@ type testCase struct {
|
|||||||
RequiredReplace cty.PathSet
|
RequiredReplace cty.PathSet
|
||||||
Tainted bool
|
Tainted bool
|
||||||
ExpectedOutput string
|
ExpectedOutput string
|
||||||
|
|
||||||
|
// This field and all associated values can be removed if the concise diff
|
||||||
|
// experiment succeeds.
|
||||||
|
VerboseOutput string
|
||||||
}
|
}
|
||||||
|
|
||||||
func runTestCases(t *testing.T, testCases map[string]testCase) {
|
func runTestCases(t *testing.T, testCases map[string]testCase) {
|
||||||
@ -3170,9 +3686,24 @@ func runTestCases(t *testing.T, testCases map[string]testCase) {
|
|||||||
RequiredReplace: tc.RequiredReplace,
|
RequiredReplace: tc.RequiredReplace,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
experiment.SetEnabled(experiment.X_concise_diff, true)
|
||||||
output := ResourceChange(change, tc.Tainted, tc.Schema, color)
|
output := ResourceChange(change, tc.Tainted, tc.Schema, color)
|
||||||
if output != tc.ExpectedOutput {
|
if output != tc.ExpectedOutput {
|
||||||
t.Fatalf("Unexpected diff.\ngot:\n%s\nwant:\n%s\n", output, tc.ExpectedOutput)
|
t.Errorf("Unexpected diff.\ngot:\n%s\nwant:\n%s\n", output, tc.ExpectedOutput)
|
||||||
|
t.Errorf("%s", cmp.Diff(output, tc.ExpectedOutput))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temporary coverage for verbose diff behaviour. All lines below
|
||||||
|
// in this function can be removed if the concise diff experiment
|
||||||
|
// succeeds.
|
||||||
|
if tc.VerboseOutput == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
experiment.SetEnabled(experiment.X_concise_diff, false)
|
||||||
|
output = ResourceChange(change, tc.Tainted, tc.Schema, color)
|
||||||
|
if output != tc.VerboseOutput {
|
||||||
|
t.Errorf("Unexpected diff.\ngot:\n%s\nwant:\n%s\n", output, tc.VerboseOutput)
|
||||||
|
t.Errorf("%s", cmp.Diff(output, tc.VerboseOutput))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -3243,11 +3774,11 @@ func TestOutputChanges(t *testing.T) {
|
|||||||
},
|
},
|
||||||
`
|
`
|
||||||
~ foo = [
|
~ foo = [
|
||||||
"alpha",
|
# (1 unchanged element hidden)
|
||||||
"beta",
|
"beta",
|
||||||
+ "gamma",
|
+ "gamma",
|
||||||
"delta",
|
"delta",
|
||||||
"epsilon",
|
# (1 unchanged element hidden)
|
||||||
]`,
|
]`,
|
||||||
},
|
},
|
||||||
"multiple outputs changed, one sensitive": {
|
"multiple outputs changed, one sensitive": {
|
||||||
@ -3280,6 +3811,7 @@ func TestOutputChanges(t *testing.T) {
|
|||||||
|
|
||||||
for name, tc := range testCases {
|
for name, tc := range testCases {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
|
experiment.SetEnabled(experiment.X_concise_diff, true)
|
||||||
output := OutputChanges(tc.changes, color)
|
output := OutputChanges(tc.changes, color)
|
||||||
if output != tc.output {
|
if output != tc.output {
|
||||||
t.Errorf("Unexpected diff.\ngot:\n%s\nwant:\n%s\n", output, tc.output)
|
t.Errorf("Unexpected diff.\ngot:\n%s\nwant:\n%s\n", output, tc.output)
|
||||||
|
@ -198,8 +198,8 @@ func formatStateModule(p blockBodyDiffPrinter, m *states.Module, schemas *terraf
|
|||||||
}
|
}
|
||||||
|
|
||||||
path := make(cty.Path, 0, 3)
|
path := make(cty.Path, 0, 3)
|
||||||
bodyWritten := p.writeBlockBodyDiff(schema, val.Value, val.Value, 2, path)
|
result := p.writeBlockBodyDiff(schema, val.Value, val.Value, 2, path)
|
||||||
if bodyWritten {
|
if result.bodyWritten {
|
||||||
p.buf.WriteString("\n")
|
p.buf.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +53,9 @@ var (
|
|||||||
// Shadow graph. This is already on by default. Disabling it will be
|
// Shadow graph. This is already on by default. Disabling it will be
|
||||||
// allowed for awhile in order for it to not block operations.
|
// allowed for awhile in order for it to not block operations.
|
||||||
X_shadow = newBasicID("shadow", "SHADOW", false)
|
X_shadow = newBasicID("shadow", "SHADOW", false)
|
||||||
|
|
||||||
|
// Concise plan diff output
|
||||||
|
X_concise_diff = newBasicID("concise_diff", "CONCISE_DIFF", true)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Global variables this package uses because we are a package
|
// Global variables this package uses because we are a package
|
||||||
@ -73,6 +76,7 @@ func init() {
|
|||||||
// The list of all experiments, update this when an experiment is added.
|
// The list of all experiments, update this when an experiment is added.
|
||||||
All = []ID{
|
All = []ID{
|
||||||
X_shadow,
|
X_shadow,
|
||||||
|
X_concise_diff,
|
||||||
x_force,
|
x_force,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user