2018-08-29 21:12:18 +02:00
|
|
|
package format
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
2020-05-27 01:59:06 +02:00
|
|
|
"log"
|
2018-08-29 21:12:18 +02:00
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/mitchellh/colorstring"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
2018-08-30 04:18:44 +02:00
|
|
|
ctyjson "github.com/zclconf/go-cty/cty/json"
|
2018-08-29 21:12:18 +02:00
|
|
|
|
2021-05-17 21:00:50 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
2021-05-17 21:17:09 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
2021-06-25 20:13:57 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/lang/marks"
|
2021-05-17 21:33:17 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/plans"
|
|
|
|
"github.com/hashicorp/terraform/internal/plans/objchange"
|
2021-05-17 21:43:35 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/states"
|
2018-08-29 21:12:18 +02:00
|
|
|
)
|
|
|
|
|
2021-09-13 23:30:16 +02:00
|
|
|
// DiffLanguage controls the description of the resource change reasons.
|
|
|
|
type DiffLanguage rune
|
|
|
|
|
|
|
|
//go:generate go run golang.org/x/tools/cmd/stringer -type=DiffLanguage diff.go
|
|
|
|
|
|
|
|
const (
|
|
|
|
// DiffLanguageProposedChange indicates that the change is one which is
|
|
|
|
// planned to be applied.
|
|
|
|
DiffLanguageProposedChange DiffLanguage = 'P'
|
|
|
|
|
|
|
|
// DiffLanguageDetectedDrift indicates that the change is detected drift
|
|
|
|
// from the configuration.
|
|
|
|
DiffLanguageDetectedDrift DiffLanguage = 'D'
|
|
|
|
)
|
|
|
|
|
2018-08-29 21:12:18 +02:00
|
|
|
// ResourceChange returns a string representation of a change to a particular
|
|
|
|
// resource, for inclusion in user-facing plan output.
|
|
|
|
//
|
|
|
|
// The resource schema must be provided along with the change so that the
|
|
|
|
// formatted change can reflect the configuration structure for the associated
|
|
|
|
// resource.
|
|
|
|
//
|
|
|
|
// If "color" is non-nil, it will be used to color the result. Otherwise,
|
|
|
|
// no color codes will be included.
|
|
|
|
func ResourceChange(
|
|
|
|
change *plans.ResourceInstanceChangeSrc,
|
|
|
|
schema *configschema.Block,
|
|
|
|
color *colorstring.Colorize,
|
2021-09-13 23:30:16 +02:00
|
|
|
language DiffLanguage,
|
2018-08-29 21:12:18 +02:00
|
|
|
) string {
|
|
|
|
addr := change.Addr
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
|
|
|
if color == nil {
|
|
|
|
color = &colorstring.Colorize{
|
|
|
|
Colors: colorstring.DefaultColors,
|
|
|
|
Disable: true,
|
|
|
|
Reset: false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-30 02:25:13 +02:00
|
|
|
dispAddr := addr.String()
|
|
|
|
if change.DeposedKey != states.NotDeposed {
|
|
|
|
dispAddr = fmt.Sprintf("%s (deposed object %s)", dispAddr, change.DeposedKey)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch change.Action {
|
|
|
|
case plans.Create:
|
2021-09-15 22:53:58 +02:00
|
|
|
buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] will be created"), dispAddr))
|
2018-08-30 02:25:13 +02:00
|
|
|
case plans.Read:
|
2021-09-15 22:53:58 +02:00
|
|
|
buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] will be read during apply\n # (config refers to values not yet known)"), dispAddr))
|
2018-08-30 02:25:13 +02:00
|
|
|
case plans.Update:
|
2021-09-13 23:30:16 +02:00
|
|
|
switch language {
|
|
|
|
case DiffLanguageProposedChange:
|
2021-09-15 22:53:58 +02:00
|
|
|
buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] will be updated in-place"), dispAddr))
|
2021-09-13 23:30:16 +02:00
|
|
|
case DiffLanguageDetectedDrift:
|
2021-09-15 22:53:58 +02:00
|
|
|
buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] has changed"), dispAddr))
|
2021-09-13 23:30:16 +02:00
|
|
|
default:
|
2021-09-15 22:53:58 +02:00
|
|
|
buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] update (unknown reason %s)"), dispAddr, language))
|
2021-09-13 23:30:16 +02:00
|
|
|
}
|
2018-09-22 02:08:52 +02:00
|
|
|
case plans.CreateThenDelete, plans.DeleteThenCreate:
|
2021-04-28 21:02:34 +02:00
|
|
|
switch change.ActionReason {
|
|
|
|
case plans.ResourceInstanceReplaceBecauseTainted:
|
2021-09-15 22:53:58 +02:00
|
|
|
buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] is tainted, so must be [bold][red]replaced"), dispAddr))
|
2021-04-30 23:46:22 +02:00
|
|
|
case plans.ResourceInstanceReplaceByRequest:
|
2021-09-15 22:53:58 +02:00
|
|
|
buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] will be [bold][red]replaced[reset], as requested"), dispAddr))
|
2021-04-28 21:02:34 +02:00
|
|
|
default:
|
2021-09-15 22:53:58 +02:00
|
|
|
buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] must be [bold][red]replaced"), dispAddr))
|
2019-03-06 01:18:55 +01:00
|
|
|
}
|
2018-08-30 02:25:13 +02:00
|
|
|
case plans.Delete:
|
2021-09-13 23:30:16 +02:00
|
|
|
switch language {
|
|
|
|
case DiffLanguageProposedChange:
|
2021-09-15 22:53:58 +02:00
|
|
|
buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] will be [bold][red]destroyed"), dispAddr))
|
2021-09-13 23:30:16 +02:00
|
|
|
case DiffLanguageDetectedDrift:
|
2021-09-15 22:53:58 +02:00
|
|
|
buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] has been deleted"), dispAddr))
|
2021-09-13 23:30:16 +02:00
|
|
|
default:
|
2021-09-15 22:53:58 +02:00
|
|
|
buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] delete (unknown reason %s)"), dispAddr, language))
|
2021-09-13 23:30:16 +02:00
|
|
|
}
|
2021-09-23 03:23:35 +02:00
|
|
|
// We can sometimes give some additional detail about why we're
|
|
|
|
// proposing to delete. We show this as additional notes, rather than
|
|
|
|
// as additional wording in the main action statement, in an attempt
|
|
|
|
// to make the "will be destroyed" message prominent and consistent
|
|
|
|
// in all cases, for easier scanning of this often-risky action.
|
|
|
|
switch change.ActionReason {
|
|
|
|
case plans.ResourceInstanceDeleteBecauseNoResourceConfig:
|
|
|
|
buf.WriteString(fmt.Sprintf("\n # (because %s is not in configuration)", addr.Resource.Resource))
|
|
|
|
case plans.ResourceInstanceDeleteBecauseNoModule:
|
|
|
|
buf.WriteString(fmt.Sprintf("\n # (because %s is not in configuration)", addr.Module))
|
|
|
|
case plans.ResourceInstanceDeleteBecauseWrongRepetition:
|
|
|
|
// We have some different variations of this one
|
|
|
|
switch addr.Resource.Key.(type) {
|
|
|
|
case nil:
|
|
|
|
buf.WriteString("\n # (because resource uses count or for_each)")
|
|
|
|
case addrs.IntKey:
|
|
|
|
buf.WriteString("\n # (because resource does not use count)")
|
|
|
|
case addrs.StringKey:
|
|
|
|
buf.WriteString("\n # (because resource does not use for_each)")
|
|
|
|
}
|
|
|
|
case plans.ResourceInstanceDeleteBecauseCountIndex:
|
|
|
|
buf.WriteString(fmt.Sprintf("\n # (because index %s is out of range for count)", addr.Resource.Key))
|
|
|
|
case plans.ResourceInstanceDeleteBecauseEachKey:
|
|
|
|
buf.WriteString(fmt.Sprintf("\n # (because key %s is not in for_each map)", addr.Resource.Key))
|
|
|
|
}
|
2021-05-13 00:40:55 +02:00
|
|
|
if change.DeposedKey != states.NotDeposed {
|
|
|
|
// Some extra context about this unusual situation.
|
2021-08-31 23:33:26 +02:00
|
|
|
buf.WriteString(color.Color("\n # (left over from a partially-failed replacement of this instance)"))
|
2021-05-13 00:40:55 +02:00
|
|
|
}
|
2021-09-03 18:10:16 +02:00
|
|
|
case plans.NoOp:
|
|
|
|
if change.Moved() {
|
2021-09-15 22:53:58 +02:00
|
|
|
buf.WriteString(fmt.Sprintf(color.Color("[bold] # %s[reset] has moved to [bold]%s[reset]"), change.PrevRunAddr.String(), dispAddr))
|
2021-09-03 18:10:16 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
fallthrough
|
2018-08-30 02:25:13 +02:00
|
|
|
default:
|
|
|
|
// should never happen, since the above is exhaustive
|
|
|
|
buf.WriteString(fmt.Sprintf("%s has an action the plan renderer doesn't support (this is a bug)", dispAddr))
|
|
|
|
}
|
|
|
|
buf.WriteString(color.Color("[reset]\n"))
|
2018-08-29 21:12:18 +02:00
|
|
|
|
2021-09-03 18:10:16 +02:00
|
|
|
if change.Moved() && change.Action != plans.NoOp {
|
2021-09-23 03:23:35 +02:00
|
|
|
buf.WriteString(fmt.Sprintf(color.Color(" # [reset](moved from %s)\n"), change.PrevRunAddr.String()))
|
2021-09-03 18:10:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if change.Moved() && change.Action == plans.NoOp {
|
|
|
|
buf.WriteString(" ")
|
|
|
|
} else {
|
|
|
|
buf.WriteString(color.Color(DiffActionSymbol(change.Action)) + " ")
|
|
|
|
}
|
2018-08-29 21:12:18 +02:00
|
|
|
|
|
|
|
switch addr.Resource.Resource.Mode {
|
|
|
|
case addrs.ManagedResourceMode:
|
2018-08-30 02:25:13 +02:00
|
|
|
buf.WriteString(fmt.Sprintf(
|
|
|
|
"resource %q %q",
|
2018-08-29 21:12:18 +02:00
|
|
|
addr.Resource.Resource.Type,
|
|
|
|
addr.Resource.Resource.Name,
|
2018-08-30 02:25:13 +02:00
|
|
|
))
|
2018-08-29 21:12:18 +02:00
|
|
|
case addrs.DataResourceMode:
|
2018-08-30 02:25:13 +02:00
|
|
|
buf.WriteString(fmt.Sprintf(
|
|
|
|
"data %q %q ",
|
2018-08-29 21:12:18 +02:00
|
|
|
addr.Resource.Resource.Type,
|
|
|
|
addr.Resource.Resource.Name,
|
2018-08-30 02:25:13 +02:00
|
|
|
))
|
2018-08-29 21:12:18 +02:00
|
|
|
default:
|
|
|
|
// should never happen, since the above is exhaustive
|
|
|
|
buf.WriteString(addr.String())
|
|
|
|
}
|
|
|
|
|
2019-01-14 17:07:04 +01:00
|
|
|
buf.WriteString(" {")
|
2018-08-29 21:12:18 +02:00
|
|
|
|
|
|
|
p := blockBodyDiffPrinter{
|
|
|
|
buf: &buf,
|
|
|
|
color: color,
|
|
|
|
action: change.Action,
|
|
|
|
requiredReplace: change.RequiredReplace,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Most commonly-used resources have nested blocks that result in us
|
|
|
|
// going at least three traversals deep while we recurse here, so we'll
|
|
|
|
// start with that much capacity and then grow as needed for deeper
|
|
|
|
// structures.
|
|
|
|
path := make(cty.Path, 0, 3)
|
|
|
|
|
|
|
|
changeV, err := change.Decode(schema.ImpliedType())
|
|
|
|
if err != nil {
|
|
|
|
// Should never happen in here, since we've already been through
|
|
|
|
// loads of layers of encode/decode of the planned changes before now.
|
|
|
|
panic(fmt.Sprintf("failed to decode plan for %s while rendering diff: %s", addr, err))
|
|
|
|
}
|
|
|
|
|
2019-03-08 23:32:41 +01:00
|
|
|
// We currently have an opt-out that permits the legacy SDK to return values
|
|
|
|
// that defy our usual conventions around handling of nesting blocks. To
|
|
|
|
// avoid the rendering code from needing to handle all of these, we'll
|
|
|
|
// normalize first.
|
|
|
|
// (Ideally we'd do this as part of the SDK opt-out implementation in core,
|
|
|
|
// but we've added it here for now to reduce risk of unexpected impacts
|
|
|
|
// on other code in core.)
|
|
|
|
changeV.Change.Before = objchange.NormalizeObjectFromLegacySDK(changeV.Change.Before, schema)
|
|
|
|
changeV.Change.After = objchange.NormalizeObjectFromLegacySDK(changeV.Change.After, schema)
|
|
|
|
|
2020-09-10 17:06:40 +02:00
|
|
|
result := p.writeBlockBodyDiff(schema, changeV.Before, changeV.After, 6, path)
|
|
|
|
if result.bodyWritten {
|
2020-08-07 17:59:06 +02:00
|
|
|
buf.WriteString("\n")
|
|
|
|
buf.WriteString(strings.Repeat(" ", 4))
|
2021-05-06 02:35:25 +02:00
|
|
|
}
|
|
|
|
buf.WriteString("}\n")
|
|
|
|
|
|
|
|
return buf.String()
|
|
|
|
}
|
|
|
|
|
2020-05-27 01:59:06 +02:00
|
|
|
// OutputChanges returns a string representation of a set of changes to output
|
|
|
|
// values for inclusion in user-facing plan output.
|
|
|
|
//
|
|
|
|
// If "color" is non-nil, it will be used to color the result. Otherwise,
|
|
|
|
// no color codes will be included.
|
|
|
|
func OutputChanges(
|
|
|
|
changes []*plans.OutputChangeSrc,
|
|
|
|
color *colorstring.Colorize,
|
|
|
|
) string {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
p := blockBodyDiffPrinter{
|
2020-12-02 21:42:41 +01:00
|
|
|
buf: &buf,
|
|
|
|
color: color,
|
|
|
|
action: plans.Update, // not actually used in this case, because we're not printing a containing block
|
2020-05-27 01:59:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// We're going to reuse the codepath we used for printing resource block
|
|
|
|
// diffs, by pretending that the set of defined outputs are the attributes
|
|
|
|
// of some resource. It's a little forced to do this, but it gives us all
|
|
|
|
// the same formatting heuristics as we normally use for resource
|
|
|
|
// attributes.
|
|
|
|
oldVals := make(map[string]cty.Value, len(changes))
|
|
|
|
newVals := make(map[string]cty.Value, len(changes))
|
|
|
|
synthSchema := &configschema.Block{
|
|
|
|
Attributes: make(map[string]*configschema.Attribute, len(changes)),
|
|
|
|
}
|
|
|
|
for _, changeSrc := range changes {
|
|
|
|
name := changeSrc.Addr.OutputValue.Name
|
|
|
|
change, err := changeSrc.Decode()
|
|
|
|
if err != nil {
|
|
|
|
// It'd be weird to get a decoding error here because that would
|
|
|
|
// suggest that Terraform itself just produced an invalid plan, and
|
|
|
|
// we don't have any good way to ignore it in this codepath, so
|
|
|
|
// we'll just log it and ignore it.
|
|
|
|
log.Printf("[ERROR] format.OutputChanges: Failed to decode planned change for output %q: %s", name, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
synthSchema.Attributes[name] = &configschema.Attribute{
|
|
|
|
Type: cty.DynamicPseudoType, // output types are decided dynamically based on the given value
|
|
|
|
Optional: true,
|
|
|
|
Sensitive: change.Sensitive,
|
|
|
|
}
|
|
|
|
oldVals[name] = change.Before
|
|
|
|
newVals[name] = change.After
|
|
|
|
}
|
|
|
|
|
|
|
|
p.writeBlockBodyDiff(synthSchema, cty.ObjectVal(oldVals), cty.ObjectVal(newVals), 2, nil)
|
|
|
|
|
|
|
|
return buf.String()
|
|
|
|
}
|
|
|
|
|
2018-08-29 21:12:18 +02:00
|
|
|
type blockBodyDiffPrinter struct {
|
|
|
|
buf *bytes.Buffer
|
|
|
|
color *colorstring.Colorize
|
|
|
|
action plans.Action
|
|
|
|
requiredReplace cty.PathSet
|
2020-12-02 21:42:41 +01:00
|
|
|
// verbose is set to true when using the "diff" printer to format state
|
|
|
|
verbose bool
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type blockBodyDiffResult struct {
|
|
|
|
bodyWritten bool
|
|
|
|
skippedAttributes int
|
|
|
|
skippedBlocks int
|
2018-08-29 21:12:18 +02:00
|
|
|
}
|
|
|
|
|
2018-08-30 02:25:13 +02:00
|
|
|
const forcesNewResourceCaption = " [red]# forces replacement[reset]"
|
2018-08-29 21:12:18 +02:00
|
|
|
|
2019-01-14 17:07:04 +01:00
|
|
|
// writeBlockBodyDiff writes attribute or block differences
|
|
|
|
// and returns true if any differences were found and written
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
func (p *blockBodyDiffPrinter) writeBlockBodyDiff(schema *configschema.Block, old, new cty.Value, indent int, path cty.Path) blockBodyDiffResult {
|
2018-08-29 21:12:18 +02:00
|
|
|
path = ctyEnsurePathCapacity(path, 1)
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
result := blockBodyDiffResult{}
|
|
|
|
|
2021-03-15 14:31:23 +01:00
|
|
|
// write the attributes diff
|
|
|
|
blankBeforeBlocks := p.writeAttrsDiff(schema.Attributes, old, new, indent, path, &result)
|
2021-05-04 16:23:50 +02:00
|
|
|
p.writeSkippedAttr(result.skippedAttributes, indent+2)
|
2018-08-29 21:12:18 +02:00
|
|
|
|
|
|
|
{
|
|
|
|
blockTypeNames := make([]string, 0, len(schema.BlockTypes))
|
|
|
|
for name := range schema.BlockTypes {
|
|
|
|
blockTypeNames = append(blockTypeNames, name)
|
|
|
|
}
|
|
|
|
sort.Strings(blockTypeNames)
|
|
|
|
|
|
|
|
for _, name := range blockTypeNames {
|
|
|
|
blockS := schema.BlockTypes[name]
|
|
|
|
oldVal := ctyGetAttrMaybeNull(old, name)
|
|
|
|
newVal := ctyGetAttrMaybeNull(new, name)
|
|
|
|
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
result.bodyWritten = true
|
|
|
|
skippedBlocks := p.writeNestedBlockDiffs(name, blockS, oldVal, newVal, blankBeforeBlocks, indent, path)
|
|
|
|
if skippedBlocks > 0 {
|
|
|
|
result.skippedBlocks += skippedBlocks
|
|
|
|
}
|
2018-08-29 21:12:18 +02:00
|
|
|
|
|
|
|
// Always include a blank for any subsequent block types.
|
|
|
|
blankBeforeBlocks = true
|
|
|
|
}
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
if result.skippedBlocks > 0 {
|
|
|
|
noun := "blocks"
|
|
|
|
if result.skippedBlocks == 1 {
|
|
|
|
noun = "block"
|
|
|
|
}
|
|
|
|
p.buf.WriteString("\n")
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
2021-09-15 22:53:58 +02:00
|
|
|
p.buf.WriteString(fmt.Sprintf(p.color.Color("[dark_gray]# (%d unchanged %s hidden)[reset]"), result.skippedBlocks, noun))
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
}
|
2018-08-29 21:12:18 +02:00
|
|
|
}
|
2019-01-14 17:07:04 +01:00
|
|
|
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
return result
|
2018-08-29 21:12:18 +02:00
|
|
|
}
|
|
|
|
|
2021-03-15 14:31:23 +01:00
|
|
|
func (p *blockBodyDiffPrinter) writeAttrsDiff(
|
|
|
|
attrsS map[string]*configschema.Attribute,
|
|
|
|
old, new cty.Value,
|
|
|
|
indent int,
|
|
|
|
path cty.Path,
|
|
|
|
result *blockBodyDiffResult) bool {
|
|
|
|
|
|
|
|
blankBeforeBlocks := false
|
|
|
|
|
|
|
|
attrNames := make([]string, 0, len(attrsS))
|
|
|
|
attrNameLen := 0
|
|
|
|
for name := range attrsS {
|
|
|
|
oldVal := ctyGetAttrMaybeNull(old, name)
|
|
|
|
newVal := ctyGetAttrMaybeNull(new, name)
|
|
|
|
if oldVal.IsNull() && newVal.IsNull() {
|
|
|
|
// Skip attributes where both old and new values are null
|
|
|
|
// (we do this early here so that we'll do our value alignment
|
|
|
|
// based on the longest attribute name that has a change, rather
|
|
|
|
// than the longest attribute name in the full set.)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
attrNames = append(attrNames, name)
|
|
|
|
if len(name) > attrNameLen {
|
|
|
|
attrNameLen = len(name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sort.Strings(attrNames)
|
|
|
|
if len(attrNames) > 0 {
|
|
|
|
blankBeforeBlocks = true
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, name := range attrNames {
|
|
|
|
attrS := attrsS[name]
|
|
|
|
oldVal := ctyGetAttrMaybeNull(old, name)
|
|
|
|
newVal := ctyGetAttrMaybeNull(new, name)
|
|
|
|
|
|
|
|
result.bodyWritten = true
|
|
|
|
skipped := p.writeAttrDiff(name, attrS, oldVal, newVal, attrNameLen, indent, path)
|
|
|
|
if skipped {
|
|
|
|
result.skippedAttributes++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return blankBeforeBlocks
|
|
|
|
}
|
|
|
|
|
2020-09-10 22:45:31 +02:00
|
|
|
// getPlanActionAndShow returns the action value
|
|
|
|
// and a boolean for showJustNew. In this function we
|
|
|
|
// modify the old and new values to remove any possible marks
|
|
|
|
func getPlanActionAndShow(old cty.Value, new cty.Value) (plans.Action, bool) {
|
2018-08-29 21:12:18 +02:00
|
|
|
var action plans.Action
|
2020-09-10 22:45:31 +02:00
|
|
|
showJustNew := false
|
2018-08-29 21:12:18 +02:00
|
|
|
switch {
|
|
|
|
case old.IsNull():
|
|
|
|
action = plans.Create
|
|
|
|
showJustNew = true
|
|
|
|
case new.IsNull():
|
|
|
|
action = plans.Delete
|
|
|
|
case ctyEqualWithUnknown(old, new):
|
|
|
|
action = plans.NoOp
|
|
|
|
showJustNew = true
|
|
|
|
default:
|
|
|
|
action = plans.Update
|
|
|
|
}
|
2020-09-10 22:45:31 +02:00
|
|
|
return action, showJustNew
|
|
|
|
}
|
|
|
|
|
|
|
|
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})
|
|
|
|
action, showJustNew := getPlanActionAndShow(old, new)
|
2018-08-29 21:12:18 +02:00
|
|
|
|
2020-12-02 21:42:41 +01:00
|
|
|
if action == plans.NoOp && !p.verbose && !identifyingAttribute(name, attrS) {
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-03-15 14:31:23 +01:00
|
|
|
if attrS.NestedType != nil {
|
2021-08-18 19:17:03 +02:00
|
|
|
p.writeNestedAttrDiff(name, attrS.NestedType, old, new, nameLen, indent, path, action, showJustNew)
|
|
|
|
return false
|
2021-02-18 14:48:52 +01:00
|
|
|
}
|
|
|
|
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
p.buf.WriteString("\n")
|
2020-09-23 22:28:55 +02:00
|
|
|
|
2020-09-29 18:59:30 +02:00
|
|
|
p.writeSensitivityWarning(old, new, indent, action, false)
|
2020-09-23 22:28:55 +02:00
|
|
|
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
2018-08-29 21:12:18 +02:00
|
|
|
p.writeActionSymbol(action)
|
|
|
|
|
|
|
|
p.buf.WriteString(p.color.Color("[bold]"))
|
|
|
|
p.buf.WriteString(name)
|
|
|
|
p.buf.WriteString(p.color.Color("[reset]"))
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", nameLen-len(name)))
|
|
|
|
p.buf.WriteString(" = ")
|
|
|
|
|
2021-03-15 14:31:23 +01:00
|
|
|
if attrS.Sensitive {
|
2018-08-29 21:12:18 +02:00
|
|
|
p.buf.WriteString("(sensitive value)")
|
2021-05-03 12:48:10 +02:00
|
|
|
if p.pathForcesNewResource(path) {
|
|
|
|
p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
|
|
|
|
}
|
2018-08-29 21:12:18 +02:00
|
|
|
} else {
|
|
|
|
switch {
|
|
|
|
case showJustNew:
|
|
|
|
p.writeValue(new, action, indent+2)
|
2019-03-29 22:52:13 +01:00
|
|
|
if p.pathForcesNewResource(path) {
|
|
|
|
p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
|
|
|
|
}
|
2018-08-29 21:12:18 +02:00
|
|
|
default:
|
|
|
|
// We show new even if it is null to emphasize the fact
|
|
|
|
// that it is being unset, since otherwise it is easy to
|
|
|
|
// misunderstand that the value is still set to the old value.
|
|
|
|
p.writeValueDiff(old, new, indent+2, path)
|
|
|
|
}
|
|
|
|
}
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
|
|
|
|
return false
|
2018-08-29 21:12:18 +02:00
|
|
|
}
|
|
|
|
|
2021-03-15 14:31:23 +01:00
|
|
|
// writeNestedAttrDiff is responsible for formatting Attributes with NestedTypes
|
|
|
|
// in the diff.
|
|
|
|
func (p *blockBodyDiffPrinter) writeNestedAttrDiff(
|
|
|
|
name string, objS *configschema.Object, old, new cty.Value,
|
|
|
|
nameLen, indent int, path cty.Path, action plans.Action, showJustNew bool) {
|
|
|
|
|
|
|
|
p.buf.WriteString("\n")
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
|
|
|
p.writeActionSymbol(action)
|
|
|
|
|
|
|
|
p.buf.WriteString(p.color.Color("[bold]"))
|
|
|
|
p.buf.WriteString(name)
|
|
|
|
p.buf.WriteString(p.color.Color("[reset]"))
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", nameLen-len(name)))
|
|
|
|
|
|
|
|
result := &blockBodyDiffResult{}
|
|
|
|
switch objS.Nesting {
|
|
|
|
case configschema.NestingSingle:
|
|
|
|
p.buf.WriteString(" = {")
|
|
|
|
if action != plans.NoOp && (p.pathForcesNewResource(path) || p.pathForcesNewResource(path[:len(path)-1])) {
|
|
|
|
p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
|
|
|
|
}
|
|
|
|
p.writeAttrsDiff(objS.Attributes, old, new, indent+2, path, result)
|
2021-05-04 16:23:50 +02:00
|
|
|
p.writeSkippedAttr(result.skippedAttributes, indent+4)
|
2021-03-15 14:31:23 +01:00
|
|
|
p.buf.WriteString("\n")
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
|
|
|
p.buf.WriteString("}")
|
|
|
|
|
|
|
|
case configschema.NestingList:
|
|
|
|
p.buf.WriteString(" = [")
|
|
|
|
if action != plans.NoOp && (p.pathForcesNewResource(path) || p.pathForcesNewResource(path[:len(path)-1])) {
|
|
|
|
p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
|
|
|
|
}
|
|
|
|
p.buf.WriteString("\n")
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+4))
|
|
|
|
p.writeActionSymbol(action)
|
|
|
|
p.buf.WriteString("{")
|
|
|
|
|
|
|
|
oldItems := ctyCollectionValues(old)
|
|
|
|
newItems := ctyCollectionValues(new)
|
|
|
|
// Here we intentionally preserve the index-based correspondance
|
|
|
|
// between old and new, rather than trying to detect insertions
|
|
|
|
// and removals in the list, because this more accurately reflects
|
|
|
|
// how Terraform Core and providers will understand the change,
|
|
|
|
// particularly when the nested block contains computed attributes
|
|
|
|
// that will themselves maintain correspondance by index.
|
|
|
|
|
|
|
|
// commonLen is number of elements that exist in both lists, which
|
|
|
|
// will be presented as updates (~). Any additional items in one
|
|
|
|
// of the lists will be presented as either creates (+) or deletes (-)
|
|
|
|
// depending on which list they belong to.
|
|
|
|
var commonLen int
|
2021-05-04 16:23:50 +02:00
|
|
|
// unchanged is the number of unchanged elements
|
|
|
|
var unchanged int
|
|
|
|
|
2021-03-15 14:31:23 +01:00
|
|
|
switch {
|
|
|
|
case len(oldItems) < len(newItems):
|
|
|
|
commonLen = len(oldItems)
|
|
|
|
default:
|
|
|
|
commonLen = len(newItems)
|
|
|
|
}
|
|
|
|
for i := 0; i < commonLen; i++ {
|
|
|
|
path := append(path, cty.IndexStep{Key: cty.NumberIntVal(int64(i))})
|
|
|
|
oldItem := oldItems[i]
|
|
|
|
newItem := newItems[i]
|
|
|
|
if oldItem.RawEquals(newItem) {
|
|
|
|
action = plans.NoOp
|
2021-05-04 16:23:50 +02:00
|
|
|
unchanged++
|
|
|
|
}
|
|
|
|
if action != plans.NoOp {
|
|
|
|
p.writeAttrsDiff(objS.Attributes, oldItem, newItem, indent+6, path, result)
|
|
|
|
p.writeSkippedAttr(result.skippedAttributes, indent+8)
|
|
|
|
p.buf.WriteString("\n")
|
2021-03-15 14:31:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for i := commonLen; i < len(oldItems); i++ {
|
|
|
|
path := append(path, cty.IndexStep{Key: cty.NumberIntVal(int64(i))})
|
|
|
|
oldItem := oldItems[i]
|
|
|
|
newItem := cty.NullVal(oldItem.Type())
|
|
|
|
p.writeAttrsDiff(objS.Attributes, oldItem, newItem, indent+6, path, result)
|
2021-05-04 16:23:50 +02:00
|
|
|
p.buf.WriteString("\n")
|
2021-03-15 14:31:23 +01:00
|
|
|
}
|
|
|
|
for i := commonLen; i < len(newItems); i++ {
|
|
|
|
path := append(path, cty.IndexStep{Key: cty.NumberIntVal(int64(i))})
|
|
|
|
newItem := newItems[i]
|
|
|
|
oldItem := cty.NullVal(newItem.Type())
|
|
|
|
p.writeAttrsDiff(objS.Attributes, oldItem, newItem, indent+6, path, result)
|
2021-05-04 16:23:50 +02:00
|
|
|
p.buf.WriteString("\n")
|
2021-03-15 14:31:23 +01:00
|
|
|
}
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+4))
|
|
|
|
p.buf.WriteString("},\n")
|
2021-05-04 16:23:50 +02:00
|
|
|
p.writeSkippedElems(unchanged, indent+4)
|
2021-03-15 14:31:23 +01:00
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
|
|
|
p.buf.WriteString("]")
|
|
|
|
|
2021-08-18 19:17:03 +02:00
|
|
|
if !new.IsKnown() {
|
|
|
|
p.buf.WriteString(" -> (known after apply)")
|
|
|
|
}
|
|
|
|
|
2021-03-15 14:31:23 +01:00
|
|
|
case configschema.NestingSet:
|
|
|
|
oldItems := ctyCollectionValues(old)
|
|
|
|
newItems := ctyCollectionValues(new)
|
|
|
|
|
2021-08-18 19:17:03 +02:00
|
|
|
var all cty.Value
|
|
|
|
if len(oldItems)+len(newItems) > 0 {
|
|
|
|
allItems := make([]cty.Value, 0, len(oldItems)+len(newItems))
|
|
|
|
allItems = append(allItems, oldItems...)
|
|
|
|
allItems = append(allItems, newItems...)
|
2021-08-17 00:13:55 +02:00
|
|
|
|
2021-08-18 19:17:03 +02:00
|
|
|
all = cty.SetVal(allItems)
|
|
|
|
} else {
|
|
|
|
all = cty.SetValEmpty(old.Type().ElementType())
|
|
|
|
}
|
2021-03-15 14:31:23 +01:00
|
|
|
|
|
|
|
p.buf.WriteString(" = [")
|
|
|
|
|
|
|
|
for it := all.ElementIterator(); it.Next(); {
|
|
|
|
_, val := it.Element()
|
|
|
|
var action plans.Action
|
|
|
|
var oldValue, newValue cty.Value
|
|
|
|
switch {
|
|
|
|
case !val.IsKnown():
|
|
|
|
action = plans.Update
|
|
|
|
newValue = val
|
2021-08-18 19:17:03 +02:00
|
|
|
case !new.IsKnown():
|
|
|
|
action = plans.Delete
|
|
|
|
// the value must have come from the old set
|
|
|
|
oldValue = val
|
|
|
|
// Mark the new val as null, but the entire set will be
|
|
|
|
// displayed as "(unknown after apply)"
|
|
|
|
newValue = cty.NullVal(val.Type())
|
2021-08-17 00:25:16 +02:00
|
|
|
case old.IsNull() || !old.HasElement(val).True():
|
2021-03-15 14:31:23 +01:00
|
|
|
action = plans.Create
|
|
|
|
oldValue = cty.NullVal(val.Type())
|
|
|
|
newValue = val
|
2021-08-17 00:25:16 +02:00
|
|
|
case new.IsNull() || !new.HasElement(val).True():
|
2021-03-15 14:31:23 +01:00
|
|
|
action = plans.Delete
|
|
|
|
oldValue = val
|
|
|
|
newValue = cty.NullVal(val.Type())
|
|
|
|
default:
|
|
|
|
action = plans.NoOp
|
|
|
|
oldValue = val
|
|
|
|
newValue = val
|
|
|
|
}
|
|
|
|
|
|
|
|
p.buf.WriteString("\n")
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+4))
|
|
|
|
p.writeActionSymbol(action)
|
|
|
|
p.buf.WriteString("{")
|
|
|
|
|
|
|
|
if action != plans.NoOp && (p.pathForcesNewResource(path) || p.pathForcesNewResource(path[:len(path)-1])) {
|
|
|
|
p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
|
|
|
|
}
|
|
|
|
|
|
|
|
path := append(path, cty.IndexStep{Key: val})
|
|
|
|
p.writeAttrsDiff(objS.Attributes, oldValue, newValue, indent+6, path, result)
|
|
|
|
|
|
|
|
p.buf.WriteString("\n")
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+4))
|
|
|
|
p.buf.WriteString("},")
|
|
|
|
}
|
|
|
|
p.buf.WriteString("\n")
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
|
|
|
p.buf.WriteString("]")
|
|
|
|
|
2021-08-18 19:17:03 +02:00
|
|
|
if !new.IsKnown() {
|
|
|
|
p.buf.WriteString(" -> (known after apply)")
|
|
|
|
}
|
|
|
|
|
2021-03-15 14:31:23 +01:00
|
|
|
case configschema.NestingMap:
|
2021-07-21 14:51:35 +02:00
|
|
|
// For the sake of handling nested blocks, we'll treat a null map
|
|
|
|
// the same as an empty map since the config language doesn't
|
|
|
|
// distinguish these anyway.
|
|
|
|
old = ctyNullBlockMapAsEmpty(old)
|
|
|
|
new = ctyNullBlockMapAsEmpty(new)
|
|
|
|
|
2021-03-15 14:31:23 +01:00
|
|
|
oldItems := old.AsValueMap()
|
2021-08-18 19:17:03 +02:00
|
|
|
|
|
|
|
newItems := map[string]cty.Value{}
|
|
|
|
|
|
|
|
if new.IsKnown() {
|
|
|
|
newItems = new.AsValueMap()
|
|
|
|
}
|
2021-03-15 14:31:23 +01:00
|
|
|
|
|
|
|
allKeys := make(map[string]bool)
|
|
|
|
for k := range oldItems {
|
|
|
|
allKeys[k] = true
|
|
|
|
}
|
|
|
|
for k := range newItems {
|
|
|
|
allKeys[k] = true
|
|
|
|
}
|
|
|
|
allKeysOrder := make([]string, 0, len(allKeys))
|
|
|
|
for k := range allKeys {
|
|
|
|
allKeysOrder = append(allKeysOrder, k)
|
|
|
|
}
|
|
|
|
sort.Strings(allKeysOrder)
|
|
|
|
|
2021-05-04 16:23:50 +02:00
|
|
|
p.buf.WriteString(" = {\n")
|
2021-03-15 14:31:23 +01:00
|
|
|
|
2021-05-04 16:23:50 +02:00
|
|
|
// unchanged tracks the number of unchanged elements
|
|
|
|
unchanged := 0
|
2021-03-15 14:31:23 +01:00
|
|
|
for _, k := range allKeysOrder {
|
|
|
|
var action plans.Action
|
|
|
|
oldValue := oldItems[k]
|
2021-08-18 19:17:03 +02:00
|
|
|
|
2021-03-15 14:31:23 +01:00
|
|
|
newValue := newItems[k]
|
|
|
|
switch {
|
|
|
|
case oldValue == cty.NilVal:
|
|
|
|
oldValue = cty.NullVal(newValue.Type())
|
|
|
|
action = plans.Create
|
|
|
|
case newValue == cty.NilVal:
|
|
|
|
newValue = cty.NullVal(oldValue.Type())
|
|
|
|
action = plans.Delete
|
|
|
|
case !newValue.RawEquals(oldValue):
|
|
|
|
action = plans.Update
|
|
|
|
default:
|
|
|
|
action = plans.NoOp
|
2021-05-04 16:23:50 +02:00
|
|
|
unchanged++
|
2021-03-15 14:31:23 +01:00
|
|
|
}
|
|
|
|
|
2021-05-04 16:23:50 +02:00
|
|
|
if action != plans.NoOp {
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+4))
|
|
|
|
p.writeActionSymbol(action)
|
|
|
|
fmt.Fprintf(p.buf, "%q = {", k)
|
|
|
|
if p.pathForcesNewResource(path) || p.pathForcesNewResource(path[:len(path)-1]) {
|
|
|
|
p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
|
|
|
|
}
|
2021-03-15 14:31:23 +01:00
|
|
|
|
2021-05-04 16:23:50 +02:00
|
|
|
path := append(path, cty.IndexStep{Key: cty.StringVal(k)})
|
|
|
|
p.writeAttrsDiff(objS.Attributes, oldValue, newValue, indent+6, path, result)
|
|
|
|
p.writeSkippedAttr(result.skippedAttributes, indent+8)
|
|
|
|
p.buf.WriteString("\n")
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+4))
|
|
|
|
p.buf.WriteString("},\n")
|
2021-03-15 14:31:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-04 16:23:50 +02:00
|
|
|
p.writeSkippedElems(unchanged, indent+4)
|
2021-03-15 14:31:23 +01:00
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
|
|
|
p.buf.WriteString("}")
|
2021-08-18 19:17:03 +02:00
|
|
|
if !new.IsKnown() {
|
|
|
|
p.buf.WriteString(" -> (known after apply)")
|
|
|
|
}
|
2021-03-15 14:31:23 +01:00
|
|
|
}
|
2021-02-18 14:48:52 +01:00
|
|
|
}
|
|
|
|
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *configschema.NestedBlock, old, new cty.Value, blankBefore bool, indent int, path cty.Path) int {
|
|
|
|
skippedBlocks := 0
|
2018-08-29 21:12:18 +02:00
|
|
|
path = append(path, cty.GetAttrStep{Name: name})
|
|
|
|
if old.IsNull() && new.IsNull() {
|
|
|
|
// Nothing to do if both old and new is null
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
return skippedBlocks
|
2018-08-29 21:12:18 +02:00
|
|
|
}
|
|
|
|
|
2020-09-29 18:59:30 +02:00
|
|
|
// If either the old or the new value is marked,
|
|
|
|
// Display a special diff because it is irrelevant
|
|
|
|
// to list all obfuscated attributes as (sensitive)
|
2021-06-25 20:13:57 +02:00
|
|
|
if old.HasMark(marks.Sensitive) || new.HasMark(marks.Sensitive) {
|
2020-10-07 16:50:54 +02:00
|
|
|
p.writeSensitiveNestedBlockDiff(name, old, new, indent, blankBefore, path)
|
2020-09-29 18:59:30 +02:00
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2018-08-29 21:12:18 +02:00
|
|
|
// Where old/new are collections representing a nesting mode other than
|
|
|
|
// NestingSingle, we assume the collection value can never be unknown
|
|
|
|
// since we always produce the container for the nested objects, even if
|
|
|
|
// the objects within are computed.
|
|
|
|
|
|
|
|
switch blockS.Nesting {
|
2019-04-09 00:32:53 +02:00
|
|
|
case configschema.NestingSingle, configschema.NestingGroup:
|
2018-08-29 21:12:18 +02:00
|
|
|
var action plans.Action
|
2019-04-09 00:32:53 +02:00
|
|
|
eqV := new.Equals(old)
|
2018-08-29 21:12:18 +02:00
|
|
|
switch {
|
|
|
|
case old.IsNull():
|
|
|
|
action = plans.Create
|
|
|
|
case new.IsNull():
|
|
|
|
action = plans.Delete
|
2019-04-09 00:32:53 +02:00
|
|
|
case !new.IsWhollyKnown() || !old.IsWhollyKnown():
|
2018-08-29 21:12:18 +02:00
|
|
|
// "old" should actually always be known due to our contract
|
|
|
|
// that old values must never be unknown, but we'll allow it
|
|
|
|
// anyway to be robust.
|
|
|
|
action = plans.Update
|
2019-04-09 00:32:53 +02:00
|
|
|
case !eqV.IsKnown() || !eqV.True():
|
2018-08-29 21:12:18 +02:00
|
|
|
action = plans.Update
|
|
|
|
}
|
|
|
|
|
|
|
|
if blankBefore {
|
|
|
|
p.buf.WriteRune('\n')
|
|
|
|
}
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
skipped := p.writeNestedBlockDiff(name, nil, &blockS.Block, action, old, new, indent, path)
|
|
|
|
if skipped {
|
|
|
|
return 1
|
|
|
|
}
|
2018-08-29 21:12:18 +02:00
|
|
|
case configschema.NestingList:
|
|
|
|
// 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
|
|
|
|
// distinguish these anyway.
|
2019-03-09 21:27:39 +01:00
|
|
|
old = ctyNullBlockListAsEmpty(old)
|
|
|
|
new = ctyNullBlockListAsEmpty(new)
|
2018-08-29 21:12:18 +02:00
|
|
|
|
|
|
|
oldItems := ctyCollectionValues(old)
|
|
|
|
newItems := ctyCollectionValues(new)
|
|
|
|
|
|
|
|
// Here we intentionally preserve the index-based correspondance
|
|
|
|
// between old and new, rather than trying to detect insertions
|
|
|
|
// and removals in the list, because this more accurately reflects
|
|
|
|
// how Terraform Core and providers will understand the change,
|
|
|
|
// particularly when the nested block contains computed attributes
|
|
|
|
// that will themselves maintain correspondance by index.
|
|
|
|
|
|
|
|
// commonLen is number of elements that exist in both lists, which
|
|
|
|
// will be presented as updates (~). Any additional items in one
|
|
|
|
// of the lists will be presented as either creates (+) or deletes (-)
|
|
|
|
// depending on which list they belong to.
|
|
|
|
var commonLen int
|
|
|
|
switch {
|
|
|
|
case len(oldItems) < len(newItems):
|
|
|
|
commonLen = len(oldItems)
|
|
|
|
default:
|
|
|
|
commonLen = len(newItems)
|
|
|
|
}
|
|
|
|
|
|
|
|
if blankBefore && (len(oldItems) > 0 || len(newItems) > 0) {
|
|
|
|
p.buf.WriteRune('\n')
|
|
|
|
}
|
2018-12-11 13:56:11 +01:00
|
|
|
|
2018-08-29 21:12:18 +02:00
|
|
|
for i := 0; i < commonLen; i++ {
|
|
|
|
path := append(path, cty.IndexStep{Key: cty.NumberIntVal(int64(i))})
|
|
|
|
oldItem := oldItems[i]
|
|
|
|
newItem := newItems[i]
|
2018-12-11 13:56:11 +01:00
|
|
|
action := plans.Update
|
|
|
|
if oldItem.RawEquals(newItem) {
|
|
|
|
action = plans.NoOp
|
|
|
|
}
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
skipped := p.writeNestedBlockDiff(name, nil, &blockS.Block, action, oldItem, newItem, indent, path)
|
|
|
|
if skipped {
|
|
|
|
skippedBlocks++
|
|
|
|
}
|
2018-08-29 21:12:18 +02:00
|
|
|
}
|
|
|
|
for i := commonLen; i < len(oldItems); i++ {
|
|
|
|
path := append(path, cty.IndexStep{Key: cty.NumberIntVal(int64(i))})
|
|
|
|
oldItem := oldItems[i]
|
|
|
|
newItem := cty.NullVal(oldItem.Type())
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
skipped := p.writeNestedBlockDiff(name, nil, &blockS.Block, plans.Delete, oldItem, newItem, indent, path)
|
|
|
|
if skipped {
|
|
|
|
skippedBlocks++
|
|
|
|
}
|
2018-08-29 21:12:18 +02:00
|
|
|
}
|
|
|
|
for i := commonLen; i < len(newItems); i++ {
|
|
|
|
path := append(path, cty.IndexStep{Key: cty.NumberIntVal(int64(i))})
|
|
|
|
newItem := newItems[i]
|
|
|
|
oldItem := cty.NullVal(newItem.Type())
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
skipped := p.writeNestedBlockDiff(name, nil, &blockS.Block, plans.Create, oldItem, newItem, indent, path)
|
|
|
|
if skipped {
|
|
|
|
skippedBlocks++
|
|
|
|
}
|
2018-08-29 21:12:18 +02:00
|
|
|
}
|
|
|
|
case configschema.NestingSet:
|
|
|
|
// For the sake of handling nested blocks, we'll treat a null set
|
|
|
|
// the same as an empty set since the config language doesn't
|
|
|
|
// distinguish these anyway.
|
2019-03-09 21:27:39 +01:00
|
|
|
old = ctyNullBlockSetAsEmpty(old)
|
|
|
|
new = ctyNullBlockSetAsEmpty(new)
|
2018-08-29 21:12:18 +02:00
|
|
|
|
|
|
|
oldItems := ctyCollectionValues(old)
|
|
|
|
newItems := ctyCollectionValues(new)
|
|
|
|
|
|
|
|
if (len(oldItems) + len(newItems)) == 0 {
|
|
|
|
// Nothing to do if both sets are empty
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
return 0
|
2018-08-29 21:12:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
allItems := make([]cty.Value, 0, len(oldItems)+len(newItems))
|
|
|
|
allItems = append(allItems, oldItems...)
|
|
|
|
allItems = append(allItems, newItems...)
|
|
|
|
all := cty.SetVal(allItems)
|
|
|
|
|
|
|
|
if blankBefore {
|
|
|
|
p.buf.WriteRune('\n')
|
|
|
|
}
|
|
|
|
|
|
|
|
for it := all.ElementIterator(); it.Next(); {
|
|
|
|
_, val := it.Element()
|
|
|
|
var action plans.Action
|
|
|
|
var oldValue, newValue cty.Value
|
|
|
|
switch {
|
2019-01-21 16:02:59 +01:00
|
|
|
case !val.IsKnown():
|
|
|
|
action = plans.Update
|
|
|
|
newValue = val
|
2018-08-29 21:12:18 +02:00
|
|
|
case !old.HasElement(val).True():
|
|
|
|
action = plans.Create
|
|
|
|
oldValue = cty.NullVal(val.Type())
|
|
|
|
newValue = val
|
|
|
|
case !new.HasElement(val).True():
|
|
|
|
action = plans.Delete
|
|
|
|
oldValue = val
|
|
|
|
newValue = cty.NullVal(val.Type())
|
|
|
|
default:
|
|
|
|
action = plans.NoOp
|
|
|
|
oldValue = val
|
|
|
|
newValue = val
|
|
|
|
}
|
|
|
|
path := append(path, cty.IndexStep{Key: val})
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
skipped := p.writeNestedBlockDiff(name, nil, &blockS.Block, action, oldValue, newValue, indent, path)
|
|
|
|
if skipped {
|
|
|
|
skippedBlocks++
|
|
|
|
}
|
2018-08-29 21:12:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
case configschema.NestingMap:
|
2019-03-09 21:30:23 +01:00
|
|
|
// For the sake of handling nested blocks, we'll treat a null map
|
|
|
|
// the same as an empty map since the config language doesn't
|
|
|
|
// distinguish these anyway.
|
|
|
|
old = ctyNullBlockMapAsEmpty(old)
|
|
|
|
new = ctyNullBlockMapAsEmpty(new)
|
|
|
|
|
|
|
|
oldItems := old.AsValueMap()
|
|
|
|
newItems := new.AsValueMap()
|
|
|
|
if (len(oldItems) + len(newItems)) == 0 {
|
|
|
|
// Nothing to do if both maps are empty
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
return 0
|
2019-03-09 21:30:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
allKeys := make(map[string]bool)
|
|
|
|
for k := range oldItems {
|
|
|
|
allKeys[k] = true
|
|
|
|
}
|
|
|
|
for k := range newItems {
|
|
|
|
allKeys[k] = true
|
|
|
|
}
|
|
|
|
allKeysOrder := make([]string, 0, len(allKeys))
|
|
|
|
for k := range allKeys {
|
|
|
|
allKeysOrder = append(allKeysOrder, k)
|
|
|
|
}
|
|
|
|
sort.Strings(allKeysOrder)
|
|
|
|
|
|
|
|
if blankBefore {
|
|
|
|
p.buf.WriteRune('\n')
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, k := range allKeysOrder {
|
|
|
|
var action plans.Action
|
|
|
|
oldValue := oldItems[k]
|
|
|
|
newValue := newItems[k]
|
|
|
|
switch {
|
|
|
|
case oldValue == cty.NilVal:
|
|
|
|
oldValue = cty.NullVal(newValue.Type())
|
|
|
|
action = plans.Create
|
|
|
|
case newValue == cty.NilVal:
|
|
|
|
newValue = cty.NullVal(oldValue.Type())
|
|
|
|
action = plans.Delete
|
|
|
|
case !newValue.RawEquals(oldValue):
|
|
|
|
action = plans.Update
|
|
|
|
default:
|
|
|
|
action = plans.NoOp
|
|
|
|
}
|
|
|
|
|
|
|
|
path := append(path, cty.IndexStep{Key: cty.StringVal(k)})
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
skipped := p.writeNestedBlockDiff(name, &k, &blockS.Block, action, oldValue, newValue, indent, path)
|
|
|
|
if skipped {
|
|
|
|
skippedBlocks++
|
|
|
|
}
|
2019-03-09 21:30:23 +01:00
|
|
|
}
|
2018-08-29 21:12:18 +02:00
|
|
|
}
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
return skippedBlocks
|
2018-08-29 21:12:18 +02:00
|
|
|
}
|
|
|
|
|
2020-10-07 16:50:54 +02:00
|
|
|
func (p *blockBodyDiffPrinter) writeSensitiveNestedBlockDiff(name string, old, new cty.Value, indent int, blankBefore bool, path cty.Path) {
|
2020-10-02 20:56:50 +02:00
|
|
|
var action plans.Action
|
|
|
|
switch {
|
|
|
|
case old.IsNull():
|
|
|
|
action = plans.Create
|
|
|
|
case new.IsNull():
|
|
|
|
action = plans.Delete
|
|
|
|
case !new.IsWhollyKnown() || !old.IsWhollyKnown():
|
|
|
|
// "old" should actually always be known due to our contract
|
|
|
|
// that old values must never be unknown, but we'll allow it
|
|
|
|
// anyway to be robust.
|
|
|
|
action = plans.Update
|
2020-10-13 19:55:16 +02:00
|
|
|
case !ctyEqualValueAndMarks(old, new):
|
2020-10-02 20:56:50 +02:00
|
|
|
action = plans.Update
|
|
|
|
}
|
|
|
|
|
|
|
|
if blankBefore {
|
|
|
|
p.buf.WriteRune('\n')
|
|
|
|
}
|
|
|
|
|
|
|
|
// New line before warning printing
|
|
|
|
p.buf.WriteRune('\n')
|
|
|
|
p.writeSensitivityWarning(old, new, indent, action, true)
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
|
|
|
p.writeActionSymbol(action)
|
|
|
|
fmt.Fprintf(p.buf, "%s {", name)
|
2020-10-07 16:50:54 +02:00
|
|
|
if action != plans.NoOp && p.pathForcesNewResource(path) {
|
|
|
|
p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
|
|
|
|
}
|
2020-10-02 20:56:50 +02:00
|
|
|
p.buf.WriteRune('\n')
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+4))
|
|
|
|
p.buf.WriteString("# At least one attribute in this block is (or was) sensitive,\n")
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+4))
|
|
|
|
p.buf.WriteString("# so its contents will not be displayed.")
|
|
|
|
p.buf.WriteRune('\n')
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
|
|
|
p.buf.WriteString("}")
|
|
|
|
}
|
|
|
|
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
func (p *blockBodyDiffPrinter) writeNestedBlockDiff(name string, label *string, blockS *configschema.Block, action plans.Action, old, new cty.Value, indent int, path cty.Path) bool {
|
2020-12-02 21:42:41 +01:00
|
|
|
if action == plans.NoOp && !p.verbose {
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2019-01-14 17:07:04 +01:00
|
|
|
p.buf.WriteString("\n")
|
2018-08-29 21:12:18 +02:00
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
|
|
|
p.writeActionSymbol(action)
|
|
|
|
|
|
|
|
if label != nil {
|
2018-09-30 18:37:52 +02:00
|
|
|
fmt.Fprintf(p.buf, "%s %q {", name, *label)
|
2018-08-29 21:12:18 +02:00
|
|
|
} else {
|
|
|
|
fmt.Fprintf(p.buf, "%s {", name)
|
|
|
|
}
|
|
|
|
|
|
|
|
if action != plans.NoOp && (p.pathForcesNewResource(path) || p.pathForcesNewResource(path[:len(path)-1])) {
|
|
|
|
p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
|
|
|
|
}
|
|
|
|
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
result := p.writeBlockBodyDiff(blockS, old, new, indent+4, path)
|
|
|
|
if result.bodyWritten {
|
2019-01-14 17:07:04 +01:00
|
|
|
p.buf.WriteString("\n")
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
|
|
|
}
|
|
|
|
p.buf.WriteString("}")
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
|
|
|
|
return false
|
2018-08-29 21:12:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *blockBodyDiffPrinter) writeValue(val cty.Value, action plans.Action, indent int) {
|
2020-08-07 17:59:06 +02:00
|
|
|
// Could check specifically for the sensitivity marker
|
2021-06-25 20:13:57 +02:00
|
|
|
if val.HasMark(marks.Sensitive) {
|
2020-08-07 17:59:06 +02:00
|
|
|
p.buf.WriteString("(sensitive)")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-08-29 21:12:18 +02:00
|
|
|
if !val.IsKnown() {
|
|
|
|
p.buf.WriteString("(known after apply)")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if val.IsNull() {
|
2019-01-11 20:27:09 +01:00
|
|
|
p.buf.WriteString(p.color.Color("[dark_gray]null[reset]"))
|
2018-08-29 21:12:18 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ty := val.Type()
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case ty.IsPrimitiveType():
|
|
|
|
switch ty {
|
|
|
|
case cty.String:
|
2018-08-30 04:18:44 +02:00
|
|
|
{
|
2018-08-30 04:29:54 +02:00
|
|
|
// Special behavior for JSON strings containing array or object
|
2018-08-30 04:18:44 +02:00
|
|
|
src := []byte(val.AsString())
|
|
|
|
ty, err := ctyjson.ImpliedType(src)
|
2019-03-14 22:20:42 +01:00
|
|
|
// check for the special case of "null", which decodes to nil,
|
|
|
|
// and just allow it to be printed out directly
|
2019-11-15 16:25:49 +01:00
|
|
|
if err == nil && !ty.IsPrimitiveType() && strings.TrimSpace(val.AsString()) != "null" {
|
2018-08-30 04:18:44 +02:00
|
|
|
jv, err := ctyjson.Unmarshal(src, ty)
|
|
|
|
if err == nil {
|
|
|
|
p.buf.WriteString("jsonencode(")
|
2019-01-14 16:29:36 +01:00
|
|
|
if jv.LengthInt() == 0 {
|
|
|
|
p.writeValue(jv, action, 0)
|
|
|
|
} else {
|
|
|
|
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))
|
|
|
|
}
|
2018-08-30 04:18:44 +02:00
|
|
|
p.buf.WriteByte(')')
|
|
|
|
break // don't *also* do the normal behavior below
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-11-07 00:59:54 +01:00
|
|
|
|
|
|
|
if strings.Contains(val.AsString(), "\n") {
|
|
|
|
// It's a multi-line string, so we want to use the multi-line
|
|
|
|
// rendering so it'll be readable. Rather than re-implement
|
|
|
|
// that here, we'll just re-use the multi-line string diff
|
|
|
|
// printer with no changes, which ends up producing the
|
|
|
|
// result we want here.
|
|
|
|
// The path argument is nil because we don't track path
|
|
|
|
// information into strings and we know that a string can't
|
|
|
|
// have any indices or attributes that might need to be marked
|
|
|
|
// as (requires replacement), which is what that argument is for.
|
|
|
|
p.writeValueDiff(val, val, indent, nil)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2018-08-29 21:12:18 +02:00
|
|
|
fmt.Fprintf(p.buf, "%q", val.AsString())
|
|
|
|
case cty.Bool:
|
|
|
|
if val.True() {
|
|
|
|
p.buf.WriteString("true")
|
|
|
|
} else {
|
|
|
|
p.buf.WriteString("false")
|
|
|
|
}
|
|
|
|
case cty.Number:
|
|
|
|
bf := val.AsBigFloat()
|
|
|
|
p.buf.WriteString(bf.Text('f', -1))
|
|
|
|
default:
|
|
|
|
// should never happen, since the above is exhaustive
|
|
|
|
fmt.Fprintf(p.buf, "%#v", val)
|
|
|
|
}
|
|
|
|
case ty.IsListType() || ty.IsSetType() || ty.IsTupleType():
|
2019-01-14 16:34:41 +01:00
|
|
|
p.buf.WriteString("[")
|
2018-08-29 21:12:18 +02:00
|
|
|
|
|
|
|
it := val.ElementIterator()
|
|
|
|
for it.Next() {
|
|
|
|
_, val := it.Element()
|
2019-01-14 16:34:41 +01:00
|
|
|
|
|
|
|
p.buf.WriteString("\n")
|
2018-08-29 21:12:18 +02:00
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
|
|
|
p.writeActionSymbol(action)
|
|
|
|
p.writeValue(val, action, indent+4)
|
2019-01-14 16:34:41 +01:00
|
|
|
p.buf.WriteString(",")
|
2018-08-29 21:12:18 +02:00
|
|
|
}
|
|
|
|
|
2019-01-14 16:34:41 +01:00
|
|
|
if val.LengthInt() > 0 {
|
|
|
|
p.buf.WriteString("\n")
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
|
|
|
}
|
2018-08-29 21:12:18 +02:00
|
|
|
p.buf.WriteString("]")
|
|
|
|
case ty.IsMapType():
|
2019-01-14 16:15:01 +01:00
|
|
|
p.buf.WriteString("{")
|
2018-08-29 21:12:18 +02:00
|
|
|
|
2018-08-30 03:09:34 +02:00
|
|
|
keyLen := 0
|
|
|
|
for it := val.ElementIterator(); it.Next(); {
|
|
|
|
key, _ := it.Element()
|
|
|
|
if keyStr := key.AsString(); len(keyStr) > keyLen {
|
|
|
|
keyLen = len(keyStr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for it := val.ElementIterator(); it.Next(); {
|
2018-08-29 21:12:18 +02:00
|
|
|
key, val := it.Element()
|
2019-01-14 16:15:01 +01:00
|
|
|
|
|
|
|
p.buf.WriteString("\n")
|
2018-08-29 21:12:18 +02:00
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
|
|
|
p.writeActionSymbol(action)
|
|
|
|
p.writeValue(key, action, indent+4)
|
2018-08-30 03:09:34 +02:00
|
|
|
p.buf.WriteString(strings.Repeat(" ", keyLen-len(key.AsString())))
|
2018-08-29 21:12:18 +02:00
|
|
|
p.buf.WriteString(" = ")
|
|
|
|
p.writeValue(val, action, indent+4)
|
|
|
|
}
|
|
|
|
|
2019-01-14 16:15:01 +01:00
|
|
|
if val.LengthInt() > 0 {
|
|
|
|
p.buf.WriteString("\n")
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
|
|
|
}
|
2018-08-29 21:12:18 +02:00
|
|
|
p.buf.WriteString("}")
|
|
|
|
case ty.IsObjectType():
|
2019-01-14 16:29:36 +01:00
|
|
|
p.buf.WriteString("{")
|
2018-08-29 21:12:18 +02:00
|
|
|
|
|
|
|
atys := ty.AttributeTypes()
|
|
|
|
attrNames := make([]string, 0, len(atys))
|
|
|
|
nameLen := 0
|
|
|
|
for attrName := range atys {
|
|
|
|
attrNames = append(attrNames, attrName)
|
|
|
|
if len(attrName) > nameLen {
|
|
|
|
nameLen = len(attrName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sort.Strings(attrNames)
|
|
|
|
|
|
|
|
for _, attrName := range attrNames {
|
|
|
|
val := val.GetAttr(attrName)
|
2019-01-14 16:29:36 +01:00
|
|
|
|
|
|
|
p.buf.WriteString("\n")
|
2018-08-29 21:12:18 +02:00
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
|
|
|
p.writeActionSymbol(action)
|
|
|
|
p.buf.WriteString(attrName)
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", nameLen-len(attrName)))
|
|
|
|
p.buf.WriteString(" = ")
|
|
|
|
p.writeValue(val, action, indent+4)
|
|
|
|
}
|
|
|
|
|
2019-01-14 16:29:36 +01:00
|
|
|
if len(attrNames) > 0 {
|
|
|
|
p.buf.WriteString("\n")
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
|
|
|
}
|
2018-08-29 21:12:18 +02:00
|
|
|
p.buf.WriteString("}")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, path cty.Path) {
|
|
|
|
ty := old.Type()
|
2019-01-23 14:13:48 +01:00
|
|
|
typesEqual := ctyTypesEqual(ty, new.Type())
|
2018-08-29 21:12:18 +02:00
|
|
|
|
|
|
|
// We have some specialized diff implementations for certain complex
|
|
|
|
// values where it's useful to see a visualization of the diff of
|
|
|
|
// the nested elements rather than just showing the entire old and
|
|
|
|
// new values verbatim.
|
|
|
|
// However, these specialized implementations can apply only if both
|
|
|
|
// values are known and non-null.
|
2019-01-23 14:13:48 +01:00
|
|
|
if old.IsKnown() && new.IsKnown() && !old.IsNull() && !new.IsNull() && typesEqual {
|
2021-06-25 20:13:57 +02:00
|
|
|
if old.HasMark(marks.Sensitive) || new.HasMark(marks.Sensitive) {
|
2020-10-01 20:34:44 +02:00
|
|
|
p.buf.WriteString("(sensitive)")
|
2020-10-06 19:05:09 +02:00
|
|
|
if p.pathForcesNewResource(path) {
|
|
|
|
p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
|
|
|
|
}
|
2020-10-01 20:34:44 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-08-29 21:12:18 +02:00
|
|
|
switch {
|
|
|
|
case ty == cty.String:
|
2018-08-30 04:18:44 +02:00
|
|
|
// 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.
|
2018-08-29 21:12:18 +02:00
|
|
|
oldS := old.AsString()
|
|
|
|
newS := new.AsString()
|
2018-08-30 04:18:44 +02:00
|
|
|
|
|
|
|
{
|
2018-08-30 04:29:54 +02:00
|
|
|
// Special behavior for JSON strings containing object or
|
|
|
|
// list values.
|
2018-08-30 04:18:44 +02:00
|
|
|
oldBytes := []byte(oldS)
|
|
|
|
newBytes := []byte(newS)
|
|
|
|
oldType, oldErr := ctyjson.ImpliedType(oldBytes)
|
|
|
|
newType, newErr := ctyjson.ImpliedType(newBytes)
|
2018-08-30 04:29:54 +02:00
|
|
|
if oldErr == nil && newErr == nil && !(oldType.IsPrimitiveType() && newType.IsPrimitiveType()) {
|
2018-08-30 04:18:44 +02:00
|
|
|
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')
|
2018-08-30 21:25:31 +02:00
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
|
|
|
p.writeActionSymbol(plans.Update)
|
2018-08-30 04:18:44 +02:00
|
|
|
p.writeValueDiff(oldJV, newJV, indent+4, path)
|
|
|
|
p.buf.WriteByte('\n')
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
|
|
|
p.buf.WriteByte(')')
|
2018-08-30 21:25:31 +02:00
|
|
|
} else {
|
2020-09-23 22:28:55 +02:00
|
|
|
// if they differ only in insignificant whitespace
|
2018-08-30 21:25:31 +02:00
|
|
|
// then we'll note that but still expand out the
|
|
|
|
// effective value.
|
|
|
|
if p.pathForcesNewResource(path) {
|
|
|
|
p.buf.WriteString(p.color.Color("jsonencode( [red]# whitespace changes force replacement[reset]"))
|
|
|
|
} else {
|
|
|
|
p.buf.WriteString(p.color.Color("jsonencode( [dim]# whitespace changes[reset]"))
|
|
|
|
}
|
|
|
|
p.buf.WriteByte('\n')
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+4))
|
|
|
|
p.writeValue(oldJV, plans.NoOp, indent+4)
|
|
|
|
p.buf.WriteByte('\n')
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
|
|
|
p.buf.WriteByte(')')
|
2018-08-30 04:18:44 +02:00
|
|
|
}
|
2018-08-30 21:25:31 +02:00
|
|
|
return
|
2018-08-30 04:18:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-01 18:42:37 +01:00
|
|
|
if !strings.Contains(oldS, "\n") && !strings.Contains(newS, "\n") {
|
2018-08-29 21:12:18 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2020-08-02 17:33:35 +02:00
|
|
|
p.buf.WriteString("<<-EOT")
|
2018-08-29 21:12:18 +02:00
|
|
|
if p.pathForcesNewResource(path) {
|
|
|
|
p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
|
|
|
|
}
|
|
|
|
p.buf.WriteString("\n")
|
|
|
|
|
|
|
|
var oldLines, newLines []cty.Value
|
|
|
|
{
|
|
|
|
r := strings.NewReader(oldS)
|
|
|
|
sc := bufio.NewScanner(r)
|
|
|
|
for sc.Scan() {
|
|
|
|
oldLines = append(oldLines, cty.StringVal(sc.Text()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
{
|
|
|
|
r := strings.NewReader(newS)
|
|
|
|
sc := bufio.NewScanner(r)
|
|
|
|
for sc.Scan() {
|
|
|
|
newLines = append(newLines, cty.StringVal(sc.Text()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-11 16:42:06 +01:00
|
|
|
// Optimization for strings which are exactly equal: just print
|
|
|
|
// directly without calculating the sequence diff. This makes a
|
|
|
|
// significant difference when this code path is reached via a
|
|
|
|
// writeValue call with a large multi-line string.
|
|
|
|
if oldS == newS {
|
|
|
|
for _, line := range newLines {
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+4))
|
|
|
|
p.buf.WriteString(line.AsString())
|
|
|
|
p.buf.WriteString("\n")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
diffLines := ctySequenceDiff(oldLines, newLines)
|
|
|
|
for _, diffLine := range diffLines {
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
|
|
|
p.writeActionSymbol(diffLine.Action)
|
|
|
|
|
|
|
|
switch diffLine.Action {
|
|
|
|
case plans.NoOp, plans.Delete:
|
|
|
|
p.buf.WriteString(diffLine.Before.AsString())
|
|
|
|
case plans.Create:
|
|
|
|
p.buf.WriteString(diffLine.After.AsString())
|
|
|
|
default:
|
|
|
|
// Should never happen since the above covers all
|
|
|
|
// actions that ctySequenceDiff can return for strings
|
|
|
|
p.buf.WriteString(diffLine.After.AsString())
|
2019-01-23 12:15:46 +01:00
|
|
|
|
2021-02-11 16:42:06 +01:00
|
|
|
}
|
|
|
|
p.buf.WriteString("\n")
|
2018-08-29 21:12:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent)) // +4 here because there's no symbol
|
|
|
|
p.buf.WriteString("EOT")
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
case ty.IsSetType():
|
|
|
|
p.buf.WriteString("[")
|
|
|
|
if p.pathForcesNewResource(path) {
|
|
|
|
p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
|
|
|
|
}
|
|
|
|
p.buf.WriteString("\n")
|
|
|
|
|
|
|
|
var addedVals, removedVals, allVals []cty.Value
|
|
|
|
for it := old.ElementIterator(); it.Next(); {
|
|
|
|
_, val := it.Element()
|
|
|
|
allVals = append(allVals, val)
|
|
|
|
if new.HasElement(val).False() {
|
|
|
|
removedVals = append(removedVals, val)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for it := new.ElementIterator(); it.Next(); {
|
|
|
|
_, val := it.Element()
|
|
|
|
allVals = append(allVals, val)
|
2019-01-21 16:02:59 +01:00
|
|
|
if val.IsKnown() && old.HasElement(val).False() {
|
2018-08-29 21:12:18 +02:00
|
|
|
addedVals = append(addedVals, val)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var all, added, removed cty.Value
|
|
|
|
if len(allVals) > 0 {
|
|
|
|
all = cty.SetVal(allVals)
|
|
|
|
} else {
|
|
|
|
all = cty.SetValEmpty(ty.ElementType())
|
|
|
|
}
|
|
|
|
if len(addedVals) > 0 {
|
|
|
|
added = cty.SetVal(addedVals)
|
|
|
|
} else {
|
|
|
|
added = cty.SetValEmpty(ty.ElementType())
|
|
|
|
}
|
|
|
|
if len(removedVals) > 0 {
|
|
|
|
removed = cty.SetVal(removedVals)
|
|
|
|
} else {
|
|
|
|
removed = cty.SetValEmpty(ty.ElementType())
|
|
|
|
}
|
|
|
|
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
suppressedElements := 0
|
2018-08-29 21:12:18 +02:00
|
|
|
for it := all.ElementIterator(); it.Next(); {
|
|
|
|
_, val := it.Element()
|
|
|
|
|
|
|
|
var action plans.Action
|
|
|
|
switch {
|
2019-01-21 16:02:59 +01:00
|
|
|
case !val.IsKnown():
|
|
|
|
action = plans.Update
|
2018-08-29 21:12:18 +02:00
|
|
|
case added.HasElement(val).True():
|
|
|
|
action = plans.Create
|
|
|
|
case removed.HasElement(val).True():
|
|
|
|
action = plans.Delete
|
|
|
|
default:
|
|
|
|
action = plans.NoOp
|
|
|
|
}
|
|
|
|
|
2020-12-02 21:42:41 +01:00
|
|
|
if action == plans.NoOp && !p.verbose {
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
suppressedElements++
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
2018-08-29 21:12:18 +02:00
|
|
|
p.writeActionSymbol(action)
|
|
|
|
p.writeValue(val, action, indent+4)
|
|
|
|
p.buf.WriteString(",\n")
|
|
|
|
}
|
|
|
|
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
if suppressedElements > 0 {
|
|
|
|
p.writeActionSymbol(plans.NoOp)
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
|
|
|
noun := "elements"
|
|
|
|
if suppressedElements == 1 {
|
|
|
|
noun = "element"
|
|
|
|
}
|
2021-09-15 22:53:58 +02:00
|
|
|
p.buf.WriteString(fmt.Sprintf(p.color.Color("[dark_gray]# (%d unchanged %s hidden)[reset]"), suppressedElements, noun))
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
p.buf.WriteString("\n")
|
|
|
|
}
|
|
|
|
|
2018-08-29 21:12:18 +02:00
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
|
|
|
p.buf.WriteString("]")
|
|
|
|
return
|
2018-08-30 21:25:59 +02:00
|
|
|
case ty.IsListType() || ty.IsTupleType():
|
|
|
|
p.buf.WriteString("[")
|
|
|
|
if p.pathForcesNewResource(path) {
|
|
|
|
p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
|
|
|
|
}
|
|
|
|
p.buf.WriteString("\n")
|
|
|
|
|
2019-01-23 12:15:46 +01:00
|
|
|
elemDiffs := ctySequenceDiff(old.AsValueSlice(), new.AsValueSlice())
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
|
|
|
|
// 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++ {
|
2020-12-02 21:42:41 +01:00
|
|
|
if !p.verbose {
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
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]
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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"
|
|
|
|
}
|
2021-09-15 22:53:58 +02:00
|
|
|
p.buf.WriteString(fmt.Sprintf(p.color.Color("[dark_gray]# (%d unchanged %s hidden)[reset]"), hidden, noun))
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
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]
|
2019-01-23 12:15:46 +01:00
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
|
|
|
p.writeActionSymbol(elemDiff.Action)
|
|
|
|
switch elemDiff.Action {
|
|
|
|
case plans.NoOp, plans.Delete:
|
|
|
|
p.writeValue(elemDiff.Before, elemDiff.Action, indent+4)
|
|
|
|
case plans.Update:
|
|
|
|
p.writeValueDiff(elemDiff.Before, elemDiff.After, indent+4, path)
|
|
|
|
case plans.Create:
|
|
|
|
p.writeValue(elemDiff.After, elemDiff.Action, indent+4)
|
|
|
|
default:
|
|
|
|
// Should never happen since the above covers all
|
|
|
|
// actions that ctySequenceDiff can return.
|
|
|
|
p.writeValue(elemDiff.After, elemDiff.Action, indent+4)
|
2019-01-15 16:37:03 +01:00
|
|
|
}
|
2019-01-23 12:15:46 +01:00
|
|
|
|
|
|
|
p.buf.WriteString(",\n")
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
changeShown = true
|
2018-08-30 21:25:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
|
|
|
p.buf.WriteString("]")
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
|
2018-08-30 21:25:59 +02:00
|
|
|
return
|
|
|
|
|
2018-08-30 03:09:34 +02:00
|
|
|
case ty.IsMapType():
|
|
|
|
p.buf.WriteString("{")
|
|
|
|
if p.pathForcesNewResource(path) {
|
|
|
|
p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
|
|
|
|
}
|
|
|
|
p.buf.WriteString("\n")
|
|
|
|
|
|
|
|
var allKeys []string
|
|
|
|
keyLen := 0
|
|
|
|
for it := old.ElementIterator(); it.Next(); {
|
|
|
|
k, _ := it.Element()
|
|
|
|
keyStr := k.AsString()
|
|
|
|
allKeys = append(allKeys, keyStr)
|
|
|
|
if len(keyStr) > keyLen {
|
|
|
|
keyLen = len(keyStr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for it := new.ElementIterator(); it.Next(); {
|
|
|
|
k, _ := it.Element()
|
|
|
|
keyStr := k.AsString()
|
|
|
|
allKeys = append(allKeys, keyStr)
|
|
|
|
if len(keyStr) > keyLen {
|
|
|
|
keyLen = len(keyStr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Strings(allKeys)
|
|
|
|
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
suppressedElements := 0
|
2018-08-30 03:09:34 +02:00
|
|
|
lastK := ""
|
|
|
|
for i, k := range allKeys {
|
|
|
|
if i > 0 && lastK == k {
|
|
|
|
continue // skip duplicates (list is sorted)
|
|
|
|
}
|
|
|
|
lastK = k
|
|
|
|
|
|
|
|
kV := cty.StringVal(k)
|
|
|
|
var action plans.Action
|
|
|
|
if old.HasIndex(kV).False() {
|
|
|
|
action = plans.Create
|
|
|
|
} else if new.HasIndex(kV).False() {
|
|
|
|
action = plans.Delete
|
2020-09-24 22:29:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if old.HasIndex(kV).True() && new.HasIndex(kV).True() {
|
2020-10-13 19:55:16 +02:00
|
|
|
if ctyEqualValueAndMarks(old.Index(kV), new.Index(kV)) {
|
2020-09-24 22:29:30 +02:00
|
|
|
action = plans.NoOp
|
|
|
|
} else {
|
|
|
|
action = plans.Update
|
|
|
|
}
|
2018-08-30 03:09:34 +02:00
|
|
|
}
|
|
|
|
|
2020-12-02 21:42:41 +01:00
|
|
|
if action == plans.NoOp && !p.verbose {
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
suppressedElements++
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-08-30 03:09:34 +02:00
|
|
|
path := append(path, cty.IndexStep{Key: kV})
|
|
|
|
|
2020-09-23 22:28:55 +02:00
|
|
|
oldV := old.Index(kV)
|
|
|
|
newV := new.Index(kV)
|
2020-09-29 18:59:30 +02:00
|
|
|
p.writeSensitivityWarning(oldV, newV, indent+2, action, false)
|
2020-09-23 22:28:55 +02:00
|
|
|
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
2018-08-30 03:09:34 +02:00
|
|
|
p.writeActionSymbol(action)
|
|
|
|
p.writeValue(kV, action, indent+4)
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", keyLen-len(k)))
|
|
|
|
p.buf.WriteString(" = ")
|
|
|
|
switch action {
|
|
|
|
case plans.Create, plans.NoOp:
|
|
|
|
v := new.Index(kV)
|
2021-06-25 20:13:57 +02:00
|
|
|
if v.HasMark(marks.Sensitive) {
|
2020-09-23 22:28:55 +02:00
|
|
|
p.buf.WriteString("(sensitive)")
|
|
|
|
} else {
|
|
|
|
p.writeValue(v, action, indent+4)
|
|
|
|
}
|
2018-08-30 03:09:34 +02:00
|
|
|
case plans.Delete:
|
|
|
|
oldV := old.Index(kV)
|
|
|
|
newV := cty.NullVal(oldV.Type())
|
2018-08-30 03:54:56 +02:00
|
|
|
p.writeValueDiff(oldV, newV, indent+4, path)
|
2018-08-30 03:09:34 +02:00
|
|
|
default:
|
2021-06-25 20:13:57 +02:00
|
|
|
if oldV.HasMark(marks.Sensitive) || newV.HasMark(marks.Sensitive) {
|
2020-09-23 22:28:55 +02:00
|
|
|
p.buf.WriteString("(sensitive)")
|
|
|
|
} else {
|
|
|
|
p.writeValueDiff(oldV, newV, indent+4, path)
|
|
|
|
}
|
2018-08-30 03:09:34 +02:00
|
|
|
}
|
|
|
|
|
2018-08-30 04:20:24 +02:00
|
|
|
p.buf.WriteByte('\n')
|
2018-08-30 03:09:34 +02:00
|
|
|
}
|
|
|
|
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
if suppressedElements > 0 {
|
|
|
|
p.writeActionSymbol(plans.NoOp)
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
|
|
|
noun := "elements"
|
|
|
|
if suppressedElements == 1 {
|
|
|
|
noun = "element"
|
|
|
|
}
|
2021-09-15 22:53:58 +02:00
|
|
|
p.buf.WriteString(fmt.Sprintf(p.color.Color("[dark_gray]# (%d unchanged %s hidden)[reset]"), suppressedElements, noun))
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
p.buf.WriteString("\n")
|
|
|
|
}
|
|
|
|
|
2018-08-30 03:09:34 +02:00
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
|
|
|
p.buf.WriteString("}")
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
|
2018-08-30 03:09:34 +02:00
|
|
|
return
|
2019-01-15 16:37:03 +01:00
|
|
|
case ty.IsObjectType():
|
|
|
|
p.buf.WriteString("{")
|
|
|
|
p.buf.WriteString("\n")
|
|
|
|
|
|
|
|
forcesNewResource := p.pathForcesNewResource(path)
|
|
|
|
|
|
|
|
var allKeys []string
|
|
|
|
keyLen := 0
|
|
|
|
for it := old.ElementIterator(); it.Next(); {
|
|
|
|
k, _ := it.Element()
|
|
|
|
keyStr := k.AsString()
|
|
|
|
allKeys = append(allKeys, keyStr)
|
|
|
|
if len(keyStr) > keyLen {
|
|
|
|
keyLen = len(keyStr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for it := new.ElementIterator(); it.Next(); {
|
|
|
|
k, _ := it.Element()
|
|
|
|
keyStr := k.AsString()
|
|
|
|
allKeys = append(allKeys, keyStr)
|
|
|
|
if len(keyStr) > keyLen {
|
|
|
|
keyLen = len(keyStr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Strings(allKeys)
|
|
|
|
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
suppressedElements := 0
|
2019-01-15 16:37:03 +01:00
|
|
|
lastK := ""
|
|
|
|
for i, k := range allKeys {
|
|
|
|
if i > 0 && lastK == k {
|
|
|
|
continue // skip duplicates (list is sorted)
|
|
|
|
}
|
|
|
|
lastK = k
|
|
|
|
|
|
|
|
kV := k
|
|
|
|
var action plans.Action
|
|
|
|
if !old.Type().HasAttribute(kV) {
|
|
|
|
action = plans.Create
|
|
|
|
} else if !new.Type().HasAttribute(kV) {
|
|
|
|
action = plans.Delete
|
2020-10-13 19:55:16 +02:00
|
|
|
} else if ctyEqualValueAndMarks(old.GetAttr(kV), new.GetAttr(kV)) {
|
2019-01-15 16:37:03 +01:00
|
|
|
action = plans.NoOp
|
|
|
|
} else {
|
|
|
|
action = plans.Update
|
|
|
|
}
|
|
|
|
|
2020-12-02 21:42:41 +01:00
|
|
|
if action == plans.NoOp && !p.verbose {
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
suppressedElements++
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-01-15 16:37:03 +01:00
|
|
|
path := append(path, cty.GetAttrStep{Name: kV})
|
|
|
|
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
2019-01-15 16:37:03 +01:00
|
|
|
p.writeActionSymbol(action)
|
|
|
|
p.buf.WriteString(k)
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", keyLen-len(k)))
|
|
|
|
p.buf.WriteString(" = ")
|
|
|
|
|
|
|
|
switch action {
|
|
|
|
case plans.Create, plans.NoOp:
|
|
|
|
v := new.GetAttr(kV)
|
|
|
|
p.writeValue(v, action, indent+4)
|
|
|
|
case plans.Delete:
|
|
|
|
oldV := old.GetAttr(kV)
|
|
|
|
newV := cty.NullVal(oldV.Type())
|
|
|
|
p.writeValueDiff(oldV, newV, indent+4, path)
|
|
|
|
default:
|
|
|
|
oldV := old.GetAttr(kV)
|
|
|
|
newV := new.GetAttr(kV)
|
|
|
|
p.writeValueDiff(oldV, newV, indent+4, path)
|
|
|
|
}
|
|
|
|
|
|
|
|
p.buf.WriteString("\n")
|
|
|
|
}
|
|
|
|
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
if suppressedElements > 0 {
|
|
|
|
p.writeActionSymbol(plans.NoOp)
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
|
|
|
noun := "elements"
|
|
|
|
if suppressedElements == 1 {
|
|
|
|
noun = "element"
|
|
|
|
}
|
2021-09-15 22:53:58 +02:00
|
|
|
p.buf.WriteString(fmt.Sprintf(p.color.Color("[dark_gray]# (%d unchanged %s hidden)[reset]"), suppressedElements, noun))
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
p.buf.WriteString("\n")
|
|
|
|
}
|
|
|
|
|
2019-01-15 16:37:03 +01:00
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
|
|
|
p.buf.WriteString("}")
|
|
|
|
|
|
|
|
if forcesNewResource {
|
|
|
|
p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
|
|
|
|
}
|
|
|
|
return
|
2018-08-29 21:12:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-23 12:15:46 +01:00
|
|
|
// In all other cases, we just show the new and old values as-is
|
|
|
|
p.writeValue(old, plans.Delete, indent)
|
|
|
|
if new.IsNull() {
|
|
|
|
p.buf.WriteString(p.color.Color(" [dark_gray]->[reset] "))
|
|
|
|
} else {
|
|
|
|
p.buf.WriteString(p.color.Color(" [yellow]->[reset] "))
|
2019-01-11 20:27:09 +01:00
|
|
|
}
|
2019-01-23 12:15:46 +01:00
|
|
|
|
2018-08-29 21:12:18 +02:00
|
|
|
p.writeValue(new, plans.Create, indent)
|
|
|
|
if p.pathForcesNewResource(path) {
|
|
|
|
p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// writeActionSymbol writes a symbol to represent the given action, followed
|
|
|
|
// by a space.
|
|
|
|
//
|
|
|
|
// It only supports the actions that can be represented with a single character:
|
|
|
|
// Create, Delete, Update and NoAction.
|
|
|
|
func (p *blockBodyDiffPrinter) writeActionSymbol(action plans.Action) {
|
|
|
|
switch action {
|
|
|
|
case plans.Create:
|
|
|
|
p.buf.WriteString(p.color.Color("[green]+[reset] "))
|
|
|
|
case plans.Delete:
|
|
|
|
p.buf.WriteString(p.color.Color("[red]-[reset] "))
|
|
|
|
case plans.Update:
|
|
|
|
p.buf.WriteString(p.color.Color("[yellow]~[reset] "))
|
|
|
|
case plans.NoOp:
|
|
|
|
p.buf.WriteString(" ")
|
|
|
|
default:
|
|
|
|
// Should never happen
|
|
|
|
p.buf.WriteString(p.color.Color("? "))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-29 18:59:30 +02:00
|
|
|
func (p *blockBodyDiffPrinter) writeSensitivityWarning(old, new cty.Value, indent int, action plans.Action, isBlock bool) {
|
2020-09-24 19:49:34 +02:00
|
|
|
// Dont' show this warning for create or delete
|
|
|
|
if action == plans.Create || action == plans.Delete {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-09-29 18:59:30 +02:00
|
|
|
// Customize the warning based on if it is an attribute or block
|
|
|
|
diffType := "attribute value"
|
|
|
|
if isBlock {
|
|
|
|
diffType = "block"
|
|
|
|
}
|
|
|
|
|
2021-05-18 16:24:31 +02:00
|
|
|
// If only attribute sensitivity is changing, clarify that the value is unchanged
|
|
|
|
var valueUnchangedSuffix string
|
|
|
|
if !isBlock {
|
|
|
|
oldUnmarked, _ := old.UnmarkDeep()
|
|
|
|
newUnmarked, _ := new.UnmarkDeep()
|
|
|
|
if oldUnmarked.RawEquals(newUnmarked) {
|
|
|
|
valueUnchangedSuffix = " The value is unchanged."
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-25 20:13:57 +02:00
|
|
|
if new.HasMark(marks.Sensitive) && !old.HasMark(marks.Sensitive) {
|
2020-09-23 22:28:55 +02:00
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
2021-09-15 22:53:58 +02:00
|
|
|
p.buf.WriteString(fmt.Sprintf(p.color.Color("# [yellow]Warning:[reset] this %s will be marked as sensitive and will not\n"), diffType))
|
2020-09-23 22:28:55 +02:00
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
2021-05-18 16:24:31 +02:00
|
|
|
p.buf.WriteString(fmt.Sprintf("# display in UI output after applying this change.%s\n", valueUnchangedSuffix))
|
2020-09-23 22:28:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Note if changing this attribute will change its sensitivity
|
2021-06-25 20:13:57 +02:00
|
|
|
if old.HasMark(marks.Sensitive) && !new.HasMark(marks.Sensitive) {
|
2020-09-23 22:28:55 +02:00
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
2021-09-15 22:53:58 +02:00
|
|
|
p.buf.WriteString(fmt.Sprintf(p.color.Color("# [yellow]Warning:[reset] this %s will no longer be marked as sensitive\n"), diffType))
|
2020-09-23 22:28:55 +02:00
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
2021-05-18 16:24:31 +02:00
|
|
|
p.buf.WriteString(fmt.Sprintf("# after applying this change.%s\n", valueUnchangedSuffix))
|
2020-09-23 22:28:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-29 21:12:18 +02:00
|
|
|
func (p *blockBodyDiffPrinter) pathForcesNewResource(path cty.Path) bool {
|
2019-12-05 22:00:19 +01:00
|
|
|
if !p.action.IsReplace() || p.requiredReplace.Empty() {
|
|
|
|
// "requiredReplace" only applies when the instance is being replaced,
|
|
|
|
// and we should only inspect that set if it is not empty
|
2018-08-29 21:12:18 +02:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
return p.requiredReplace.Has(path)
|
|
|
|
}
|
|
|
|
|
2019-01-13 23:51:05 +01:00
|
|
|
func ctyEmptyString(value cty.Value) bool {
|
|
|
|
if !value.IsNull() && value.IsKnown() {
|
|
|
|
valueType := value.Type()
|
|
|
|
if valueType == cty.String && value.AsString() == "" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2018-08-29 21:12:18 +02:00
|
|
|
func ctyGetAttrMaybeNull(val cty.Value, name string) cty.Value {
|
2019-01-13 23:51:05 +01:00
|
|
|
attrType := val.Type().AttributeType(name)
|
|
|
|
|
2018-08-29 21:12:18 +02:00
|
|
|
if val.IsNull() {
|
2019-01-13 23:51:05 +01:00
|
|
|
return cty.NullVal(attrType)
|
|
|
|
}
|
|
|
|
|
|
|
|
// We treat "" as null here
|
|
|
|
// as existing SDK doesn't support null yet.
|
|
|
|
// This allows us to avoid spurious diffs
|
|
|
|
// until we introduce null to the SDK.
|
|
|
|
attrValue := val.GetAttr(name)
|
2020-08-07 17:59:06 +02:00
|
|
|
// If the value is marked, the ctyEmptyString function will fail
|
|
|
|
if !val.ContainsMarked() && ctyEmptyString(attrValue) {
|
2019-01-13 23:51:05 +01:00
|
|
|
return cty.NullVal(attrType)
|
2018-08-29 21:12:18 +02:00
|
|
|
}
|
|
|
|
|
2019-01-13 23:51:05 +01:00
|
|
|
return attrValue
|
2018-08-29 21:12:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func ctyCollectionValues(val cty.Value) []cty.Value {
|
2018-10-19 22:51:15 +02:00
|
|
|
if !val.IsKnown() || val.IsNull() {
|
2018-10-19 01:21:32 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-08-29 21:12:18 +02:00
|
|
|
ret := make([]cty.Value, 0, val.LengthInt())
|
|
|
|
for it := val.ElementIterator(); it.Next(); {
|
|
|
|
_, value := it.Element()
|
2020-09-29 18:59:30 +02:00
|
|
|
ret = append(ret, value)
|
2018-08-29 21:12:18 +02:00
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2019-01-23 12:15:46 +01:00
|
|
|
// ctySequenceDiff returns differences between given sequences of cty.Value(s)
|
|
|
|
// in the form of Create, Delete, or Update actions (for objects).
|
|
|
|
func ctySequenceDiff(old, new []cty.Value) []*plans.Change {
|
2018-08-29 21:12:18 +02:00
|
|
|
var ret []*plans.Change
|
|
|
|
lcs := objchange.LongestCommonSubsequence(old, new)
|
|
|
|
var oldI, newI, lcsI int
|
|
|
|
for oldI < len(old) || newI < len(new) || lcsI < len(lcs) {
|
2021-07-09 19:14:20 +02:00
|
|
|
// We first process items in the old and new sequences which are not
|
|
|
|
// equal to the current common sequence item. Old items are marked as
|
|
|
|
// deletions, and new items are marked as additions.
|
|
|
|
//
|
|
|
|
// There is an exception for deleted & created object items, which we
|
|
|
|
// try to render as updates where that makes sense.
|
2018-08-29 21:12:18 +02:00
|
|
|
for oldI < len(old) && (lcsI >= len(lcs) || !old[oldI].RawEquals(lcs[lcsI])) {
|
2021-07-09 19:14:20 +02:00
|
|
|
// Render this as an object update if all of these are true:
|
|
|
|
//
|
|
|
|
// - the current old item is an object;
|
|
|
|
// - there's a current new item which is also an object;
|
|
|
|
// - either there are no common items left, or the current new item
|
|
|
|
// doesn't equal the current common item.
|
|
|
|
//
|
|
|
|
// Why do we need the the last clause? If we have current items in all
|
|
|
|
// three sequences, and the current new item is equal to a common item,
|
|
|
|
// then we should just need to advance the old item list and we'll
|
|
|
|
// eventually find a common item matching both old and new.
|
|
|
|
//
|
|
|
|
// This combination of conditions allows us to render an object update
|
|
|
|
// diff instead of a combination of delete old & create new.
|
2019-11-09 01:05:23 +01:00
|
|
|
isObjectDiff := old[oldI].Type().IsObjectType() && newI < len(new) && new[newI].Type().IsObjectType() && (lcsI >= len(lcs) || !new[newI].RawEquals(lcs[lcsI]))
|
|
|
|
if isObjectDiff {
|
2019-01-23 12:15:46 +01:00
|
|
|
ret = append(ret, &plans.Change{
|
|
|
|
Action: plans.Update,
|
|
|
|
Before: old[oldI],
|
|
|
|
After: new[newI],
|
|
|
|
})
|
|
|
|
oldI++
|
|
|
|
newI++ // we also consume the next "new" in this case
|
|
|
|
continue
|
2018-08-29 21:12:18 +02:00
|
|
|
}
|
|
|
|
|
2021-07-09 19:14:20 +02:00
|
|
|
// Otherwise, this item is not part of the common sequence, so
|
|
|
|
// render as a deletion.
|
2018-08-29 21:12:18 +02:00
|
|
|
ret = append(ret, &plans.Change{
|
|
|
|
Action: plans.Delete,
|
|
|
|
Before: old[oldI],
|
|
|
|
After: cty.NullVal(old[oldI].Type()),
|
|
|
|
})
|
|
|
|
oldI++
|
|
|
|
}
|
|
|
|
for newI < len(new) && (lcsI >= len(lcs) || !new[newI].RawEquals(lcs[lcsI])) {
|
|
|
|
ret = append(ret, &plans.Change{
|
|
|
|
Action: plans.Create,
|
|
|
|
Before: cty.NullVal(new[newI].Type()),
|
|
|
|
After: new[newI],
|
|
|
|
})
|
|
|
|
newI++
|
|
|
|
}
|
2021-07-09 19:14:20 +02:00
|
|
|
|
|
|
|
// When we've exhausted the old & new sequences of items which are not
|
|
|
|
// in the common subsequence, we render a common item and continue.
|
2018-08-29 21:12:18 +02:00
|
|
|
if lcsI < len(lcs) {
|
|
|
|
ret = append(ret, &plans.Change{
|
|
|
|
Action: plans.NoOp,
|
2019-04-27 17:23:37 +02:00
|
|
|
Before: lcs[lcsI],
|
|
|
|
After: lcs[lcsI],
|
2018-08-29 21:12:18 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
// All of our indexes advance together now, since the line
|
|
|
|
// is common to all three sequences.
|
|
|
|
lcsI++
|
|
|
|
oldI++
|
|
|
|
newI++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2020-10-13 19:55:16 +02:00
|
|
|
// ctyEqualValueAndMarks checks equality of two possibly-marked values,
|
|
|
|
// considering partially-unknown values and equal values with different marks
|
|
|
|
// as inequal
|
2018-08-29 21:12:18 +02:00
|
|
|
func ctyEqualWithUnknown(old, new cty.Value) bool {
|
2019-01-21 16:02:59 +01:00
|
|
|
if !old.IsWhollyKnown() || !new.IsWhollyKnown() {
|
2018-08-29 21:12:18 +02:00
|
|
|
return false
|
|
|
|
}
|
2020-10-13 19:55:16 +02:00
|
|
|
return ctyEqualValueAndMarks(old, new)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ctyEqualValueAndMarks checks equality of two possibly-marked values,
|
|
|
|
// considering equal values with different marks as inequal
|
|
|
|
func ctyEqualValueAndMarks(old, new cty.Value) bool {
|
|
|
|
oldUnmarked, oldMarks := old.UnmarkDeep()
|
|
|
|
newUnmarked, newMarks := new.UnmarkDeep()
|
|
|
|
sameValue := oldUnmarked.Equals(newUnmarked)
|
|
|
|
return sameValue.IsKnown() && sameValue.True() && oldMarks.Equal(newMarks)
|
2018-08-29 21:12:18 +02:00
|
|
|
}
|
|
|
|
|
2019-01-23 14:13:48 +01:00
|
|
|
// ctyTypesEqual checks equality of two types more loosely
|
|
|
|
// by avoiding checks of object/tuple elements
|
|
|
|
// as we render differences on element-by-element basis anyway
|
|
|
|
func ctyTypesEqual(oldT, newT cty.Type) bool {
|
|
|
|
if oldT.IsObjectType() && newT.IsObjectType() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if oldT.IsTupleType() && newT.IsTupleType() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return oldT.Equals(newT)
|
|
|
|
}
|
|
|
|
|
2018-08-29 21:12:18 +02:00
|
|
|
func ctyEnsurePathCapacity(path cty.Path, minExtra int) cty.Path {
|
|
|
|
if cap(path)-len(path) >= minExtra {
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
newCap := cap(path) * 2
|
|
|
|
if newCap < (len(path) + minExtra) {
|
|
|
|
newCap = len(path) + minExtra
|
|
|
|
}
|
|
|
|
newPath := make(cty.Path, len(path), newCap)
|
|
|
|
copy(newPath, path)
|
|
|
|
return newPath
|
|
|
|
}
|
2019-03-09 21:27:39 +01:00
|
|
|
|
|
|
|
// ctyNullBlockListAsEmpty either returns the given value verbatim if it is non-nil
|
|
|
|
// or returns an empty value of a suitable type to serve as a placeholder for it.
|
|
|
|
//
|
|
|
|
// In particular, this function handles the special situation where a "list" is
|
|
|
|
// actually represented as a tuple type where nested blocks contain
|
|
|
|
// dynamically-typed values.
|
|
|
|
func ctyNullBlockListAsEmpty(in cty.Value) cty.Value {
|
|
|
|
if !in.IsNull() {
|
|
|
|
return in
|
|
|
|
}
|
|
|
|
if ty := in.Type(); ty.IsListType() {
|
|
|
|
return cty.ListValEmpty(ty.ElementType())
|
|
|
|
}
|
|
|
|
return cty.EmptyTupleVal // must need a tuple, then
|
|
|
|
}
|
|
|
|
|
|
|
|
// ctyNullBlockMapAsEmpty either returns the given value verbatim if it is non-nil
|
|
|
|
// or returns an empty value of a suitable type to serve as a placeholder for it.
|
|
|
|
//
|
|
|
|
// In particular, this function handles the special situation where a "map" is
|
|
|
|
// actually represented as an object type where nested blocks contain
|
|
|
|
// dynamically-typed values.
|
|
|
|
func ctyNullBlockMapAsEmpty(in cty.Value) cty.Value {
|
|
|
|
if !in.IsNull() {
|
|
|
|
return in
|
|
|
|
}
|
|
|
|
if ty := in.Type(); ty.IsMapType() {
|
|
|
|
return cty.MapValEmpty(ty.ElementType())
|
|
|
|
}
|
|
|
|
return cty.EmptyObjectVal // must need an object, then
|
|
|
|
}
|
|
|
|
|
|
|
|
// ctyNullBlockSetAsEmpty either returns the given value verbatim if it is non-nil
|
|
|
|
// or returns an empty value of a suitable type to serve as a placeholder for it.
|
|
|
|
func ctyNullBlockSetAsEmpty(in cty.Value) cty.Value {
|
|
|
|
if !in.IsNull() {
|
|
|
|
return in
|
|
|
|
}
|
|
|
|
// Dynamically-typed attributes are not supported inside blocks backed by
|
|
|
|
// sets, so our result here is always a set.
|
|
|
|
return cty.SetValEmpty(in.Type().ElementType())
|
|
|
|
}
|
2019-11-06 02:22:49 +01:00
|
|
|
|
|
|
|
// DiffActionSymbol returns a string that, once passed through a
|
|
|
|
// colorstring.Colorize, will produce a result that can be written
|
|
|
|
// to a terminal to produce a symbol made of three printable
|
|
|
|
// characters, possibly interspersed with VT100 color codes.
|
|
|
|
func DiffActionSymbol(action plans.Action) string {
|
|
|
|
switch action {
|
|
|
|
case plans.DeleteThenCreate:
|
|
|
|
return "[red]-[reset]/[green]+[reset]"
|
|
|
|
case plans.CreateThenDelete:
|
|
|
|
return "[green]+[reset]/[red]-[reset]"
|
|
|
|
case plans.Create:
|
|
|
|
return " [green]+[reset]"
|
|
|
|
case plans.Delete:
|
|
|
|
return " [red]-[reset]"
|
|
|
|
case plans.Read:
|
|
|
|
return " [cyan]<=[reset]"
|
|
|
|
case plans.Update:
|
|
|
|
return " [yellow]~[reset]"
|
|
|
|
default:
|
|
|
|
return " ?"
|
|
|
|
}
|
|
|
|
}
|
command: Add experimental concise diff renderer
When rendering a diff between current state and projected state, we only
show resources and outputs which have changes. However, we show a full
structural diff for these values, which includes all attributes and
blocks for a changed resource or output. The result can be a very long
diff, which makes it difficult to verify what the changed fields are.
This commit adds an experimental concise diff renderer, which suppresses
most unchanged fields, only displaying the most relevant changes and
some identifying context. This means:
- Always show all identifying attributes, initially defined as `id`,
`name`, and `tags`, even if unchanged;
- Only show changed, added, or removed primitive values: `string`,
`number`, or `bool`;
- Only show added or removed elements in unordered collections and
structural types: `map`, `set`, and `object`;
- Show added or removed elements with any surrounding unchanged elements
for sequence types: `list` and `tuple`;
- Only show added or removed nested blocks, or blocks with changed
attributes.
If any attributes, collection elements, or blocks are hidden, a count
is kept and displayed at the end of the parent scope. This ensures that
it is clear that the diff is only displaying a subset of the resource.
The experiment is currently enabled by default, but can be disabled by
setting the TF_X_CONCISE_DIFF environment variable to 0.
2020-08-19 22:47:56 +02:00
|
|
|
|
|
|
|
// 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"
|
|
|
|
}
|
2021-05-04 16:23:50 +02:00
|
|
|
|
|
|
|
func (p *blockBodyDiffPrinter) writeSkippedAttr(skipped, indent int) {
|
|
|
|
if skipped > 0 {
|
|
|
|
noun := "attributes"
|
|
|
|
if skipped == 1 {
|
|
|
|
noun = "attribute"
|
|
|
|
}
|
|
|
|
p.buf.WriteString("\n")
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
2021-09-15 22:53:58 +02:00
|
|
|
p.buf.WriteString(fmt.Sprintf(p.color.Color("[dark_gray]# (%d unchanged %s hidden)[reset]"), skipped, noun))
|
2021-05-04 16:23:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *blockBodyDiffPrinter) writeSkippedElems(skipped, indent int) {
|
|
|
|
if skipped > 0 {
|
|
|
|
noun := "elements"
|
|
|
|
if skipped == 1 {
|
|
|
|
noun = "element"
|
|
|
|
}
|
|
|
|
p.buf.WriteString(strings.Repeat(" ", indent))
|
2021-09-15 22:53:58 +02:00
|
|
|
p.buf.WriteString(fmt.Sprintf(p.color.Color("[dark_gray]# (%d unchanged %s hidden)[reset]"), skipped, noun))
|
2021-05-04 16:23:50 +02:00
|
|
|
p.buf.WriteString("\n")
|
|
|
|
}
|
|
|
|
}
|