Refactor of differ to make code reuse easier (#33054)

* refactor of differ to make code reuse easier

* fix imports
This commit is contained in:
Liam Cervante
2023-04-21 09:51:55 +02:00
committed by GitHub
parent 324c82b077
commit 357012a2f3
27 changed files with 409 additions and 337 deletions

View File

@@ -1,6 +1,7 @@
package differ
import (
"github.com/hashicorp/terraform/internal/command/jsonformat/structured"
"github.com/zclconf/go-cty/cty"
ctyjson "github.com/zclconf/go-cty/cty/json"
@@ -9,42 +10,42 @@ import (
"github.com/hashicorp/terraform/internal/command/jsonprovider"
)
func (change Change) ComputeDiffForAttribute(attribute *jsonprovider.Attribute) computed.Diff {
func ComputeDiffForAttribute(change structured.Change, attribute *jsonprovider.Attribute) computed.Diff {
if attribute.AttributeNestedType != nil {
return change.computeDiffForNestedAttribute(attribute.AttributeNestedType)
return computeDiffForNestedAttribute(change, attribute.AttributeNestedType)
}
return change.ComputeDiffForType(unmarshalAttribute(attribute))
return ComputeDiffForType(change, unmarshalAttribute(attribute))
}
func (change Change) computeDiffForNestedAttribute(nested *jsonprovider.NestedType) computed.Diff {
if sensitive, ok := change.checkForSensitiveNestedAttribute(nested); ok {
func computeDiffForNestedAttribute(change structured.Change, nested *jsonprovider.NestedType) computed.Diff {
if sensitive, ok := checkForSensitiveNestedAttribute(change, nested); ok {
return sensitive
}
if computed, ok := change.checkForUnknownNestedAttribute(nested); ok {
if computed, ok := checkForUnknownNestedAttribute(change, nested); ok {
return computed
}
switch NestingMode(nested.NestingMode) {
case nestingModeSingle, nestingModeGroup:
return change.computeAttributeDiffAsNestedObject(nested.Attributes)
return computeAttributeDiffAsNestedObject(change, nested.Attributes)
case nestingModeMap:
return change.computeAttributeDiffAsNestedMap(nested.Attributes)
return computeAttributeDiffAsNestedMap(change, nested.Attributes)
case nestingModeList:
return change.computeAttributeDiffAsNestedList(nested.Attributes)
return computeAttributeDiffAsNestedList(change, nested.Attributes)
case nestingModeSet:
return change.computeAttributeDiffAsNestedSet(nested.Attributes)
return computeAttributeDiffAsNestedSet(change, nested.Attributes)
default:
panic("unrecognized nesting mode: " + nested.NestingMode)
}
}
func (change Change) ComputeDiffForType(ctype cty.Type) computed.Diff {
if sensitive, ok := change.checkForSensitiveType(ctype); ok {
func ComputeDiffForType(change structured.Change, ctype cty.Type) computed.Diff {
if sensitive, ok := checkForSensitiveType(change, ctype); ok {
return sensitive
}
if computed, ok := change.checkForUnknownType(ctype); ok {
if computed, ok := checkForUnknownType(change, ctype); ok {
return computed
}
@@ -56,19 +57,19 @@ func (change Change) ComputeDiffForType(ctype cty.Type) computed.Diff {
// function computeChangeForDynamicValues(), but external callers will
// only be in this situation when processing outputs so this function
// is named for their benefit.
return change.ComputeDiffForOutput()
return ComputeDiffForOutput(change)
case ctype.IsPrimitiveType():
return change.computeAttributeDiffAsPrimitive(ctype)
return computeAttributeDiffAsPrimitive(change, ctype)
case ctype.IsObjectType():
return change.computeAttributeDiffAsObject(ctype.AttributeTypes())
return computeAttributeDiffAsObject(change, ctype.AttributeTypes())
case ctype.IsMapType():
return change.computeAttributeDiffAsMap(ctype.ElementType())
return computeAttributeDiffAsMap(change, ctype.ElementType())
case ctype.IsListType():
return change.computeAttributeDiffAsList(ctype.ElementType())
return computeAttributeDiffAsList(change, ctype.ElementType())
case ctype.IsTupleType():
return change.computeAttributeDiffAsTuple(ctype.TupleElementTypes())
return computeAttributeDiffAsTuple(change, ctype.TupleElementTypes())
case ctype.IsSetType():
return change.computeAttributeDiffAsSet(ctype.ElementType())
return computeAttributeDiffAsSet(change, ctype.ElementType())
default:
panic("unrecognized type: " + ctype.FriendlyName())
}

View File

@@ -1,201 +0,0 @@
package attribute_path
import "encoding/json"
// Matcher provides an interface for stepping through changes following an
// attribute path.
//
// GetChildWithKey and GetChildWithIndex will check if any of the internal paths
// match the provided key or index, and return a new Matcher that will match
// that children or potentially it's children.
//
// The caller of the above functions is required to know whether the next value
// in the path is a list type or an object type and call the relevant function,
// otherwise these functions will crash/panic.
//
// The Matches function returns true if the paths you have traversed until now
// ends.
type Matcher interface {
// Matches returns true if we have reached the end of a path and found an
// exact match.
Matches() bool
// MatchesPartial returns true if the current attribute is part of a path
// but not necessarily at the end of the path.
MatchesPartial() bool
GetChildWithKey(key string) Matcher
GetChildWithIndex(index int) Matcher
}
// Parse accepts a json.RawMessage and outputs a formatted Matcher object.
//
// Parse expects the message to be a JSON array of JSON arrays containing
// strings and floats. This function happily accepts a null input representing
// none of the changes in this resource are causing a replacement. The propagate
// argument tells the matcher to propagate any matches to the matched attributes
// children.
//
// In general, this function is designed to accept messages that have been
// produced by the lossy cty.Paths conversion functions within the jsonplan
// package. There is nothing particularly special about that conversion process
// though, it just produces the nested JSON arrays described above.
func Parse(message json.RawMessage, propagate bool) Matcher {
matcher := &PathMatcher{
Propagate: propagate,
}
if message == nil {
return matcher
}
if err := json.Unmarshal(message, &matcher.Paths); err != nil {
panic("failed to unmarshal attribute paths: " + err.Error())
}
return matcher
}
// Empty returns an empty PathMatcher that will by default match nothing.
//
// We give direct access to the PathMatcher struct so a matcher can be built
// in parts with the Append and AppendSingle functions.
func Empty(propagate bool) *PathMatcher {
return &PathMatcher{
Propagate: propagate,
}
}
// Append accepts an existing PathMatcher and returns a new one that attaches
// all the paths from message with the existing paths.
//
// The new PathMatcher is created fresh, and the existing one is unchanged.
func Append(matcher *PathMatcher, message json.RawMessage) *PathMatcher {
var values [][]interface{}
if err := json.Unmarshal(message, &values); err != nil {
panic("failed to unmarshal attribute paths: " + err.Error())
}
return &PathMatcher{
Propagate: matcher.Propagate,
Paths: append(matcher.Paths, values...),
}
}
// AppendSingle accepts an existing PathMatcher and returns a new one that
// attaches the single path from message with the existing paths.
//
// The new PathMatcher is created fresh, and the existing one is unchanged.
func AppendSingle(matcher *PathMatcher, message json.RawMessage) *PathMatcher {
var values []interface{}
if err := json.Unmarshal(message, &values); err != nil {
panic("failed to unmarshal attribute paths: " + err.Error())
}
return &PathMatcher{
Propagate: matcher.Propagate,
Paths: append(matcher.Paths, values),
}
}
// PathMatcher contains a slice of paths that represent paths through the values
// to relevant/tracked attributes.
type PathMatcher struct {
// We represent our internal paths as a [][]interface{} as the cty.Paths
// conversion process is lossy. Since the type information is lost there
// is no (easy) way to reproduce the original cty.Paths object. Instead,
// we simply rely on the external callers to know the type information and
// call the correct GetChild function.
Paths [][]interface{}
// Propagate tells the matcher that it should propagate any matches it finds
// onto the children of that match.
Propagate bool
}
func (p *PathMatcher) Matches() bool {
for _, path := range p.Paths {
if len(path) == 0 {
return true
}
}
return false
}
func (p *PathMatcher) MatchesPartial() bool {
return len(p.Paths) > 0
}
func (p *PathMatcher) GetChildWithKey(key string) Matcher {
child := &PathMatcher{
Propagate: p.Propagate,
}
for _, path := range p.Paths {
if len(path) == 0 {
// This means that the current value matched, but not necessarily
// it's child.
if p.Propagate {
// If propagate is true, then our child match our matches
child.Paths = append(child.Paths, path)
}
// If not we would simply drop this path from our set of paths but
// either way we just continue.
continue
}
if path[0].(string) == key {
child.Paths = append(child.Paths, path[1:])
}
}
return child
}
func (p *PathMatcher) GetChildWithIndex(index int) Matcher {
child := &PathMatcher{
Propagate: p.Propagate,
}
for _, path := range p.Paths {
if len(path) == 0 {
// This means that the current value matched, but not necessarily
// it's child.
if p.Propagate {
// If propagate is true, then our child match our matches
child.Paths = append(child.Paths, path)
}
// If not we would simply drop this path from our set of paths but
// either way we just continue.
continue
}
if int(path[0].(float64)) == index {
child.Paths = append(child.Paths, path[1:])
}
}
return child
}
// AlwaysMatcher returns a matcher that will always match all paths.
func AlwaysMatcher() Matcher {
return &alwaysMatcher{}
}
type alwaysMatcher struct{}
func (a *alwaysMatcher) Matches() bool {
return true
}
func (a *alwaysMatcher) MatchesPartial() bool {
return true
}
func (a *alwaysMatcher) GetChildWithKey(_ string) Matcher {
return a
}
func (a *alwaysMatcher) GetChildWithIndex(_ int) Matcher {
return a
}

View File

@@ -1,253 +0,0 @@
package attribute_path
import "testing"
func TestPathMatcher_FollowsPath(t *testing.T) {
var matcher Matcher
matcher = &PathMatcher{
Paths: [][]interface{}{
{
float64(0),
"key",
float64(0),
},
},
}
if matcher.Matches() {
t.Errorf("should not have exact matched at base level")
}
if !matcher.MatchesPartial() {
t.Errorf("should have partial matched at base level")
}
matcher = matcher.GetChildWithIndex(0)
if matcher.Matches() {
t.Errorf("should not have exact matched at first level")
}
if !matcher.MatchesPartial() {
t.Errorf("should have partial matched at first level")
}
matcher = matcher.GetChildWithKey("key")
if matcher.Matches() {
t.Errorf("should not have exact matched at second level")
}
if !matcher.MatchesPartial() {
t.Errorf("should have partial matched at second level")
}
matcher = matcher.GetChildWithIndex(0)
if !matcher.Matches() {
t.Errorf("should have exact matched at leaf level")
}
if !matcher.MatchesPartial() {
t.Errorf("should have partial matched at leaf level")
}
}
func TestPathMatcher_Propagates(t *testing.T) {
var matcher Matcher
matcher = &PathMatcher{
Paths: [][]interface{}{
{
float64(0),
"key",
},
},
Propagate: true,
}
if matcher.Matches() {
t.Errorf("should not have exact matched at base level")
}
if !matcher.MatchesPartial() {
t.Errorf("should have partial matched at base level")
}
matcher = matcher.GetChildWithIndex(0)
if matcher.Matches() {
t.Errorf("should not have exact matched at first level")
}
if !matcher.MatchesPartial() {
t.Errorf("should have partial matched at first level")
}
matcher = matcher.GetChildWithKey("key")
if !matcher.Matches() {
t.Errorf("should have exact matched at second level")
}
if !matcher.MatchesPartial() {
t.Errorf("should have partial matched at second level")
}
matcher = matcher.GetChildWithIndex(0)
if !matcher.Matches() {
t.Errorf("should have exact matched at leaf level")
}
if !matcher.MatchesPartial() {
t.Errorf("should have partial matched at leaf level")
}
}
func TestPathMatcher_DoesNotPropagate(t *testing.T) {
var matcher Matcher
matcher = &PathMatcher{
Paths: [][]interface{}{
{
float64(0),
"key",
},
},
}
if matcher.Matches() {
t.Errorf("should not have exact matched at base level")
}
if !matcher.MatchesPartial() {
t.Errorf("should have partial matched at base level")
}
matcher = matcher.GetChildWithIndex(0)
if matcher.Matches() {
t.Errorf("should not have exact matched at first level")
}
if !matcher.MatchesPartial() {
t.Errorf("should have partial matched at first level")
}
matcher = matcher.GetChildWithKey("key")
if !matcher.Matches() {
t.Errorf("should have exact matched at second level")
}
if !matcher.MatchesPartial() {
t.Errorf("should have partial matched at second level")
}
matcher = matcher.GetChildWithIndex(0)
if matcher.Matches() {
t.Errorf("should not have exact matched at leaf level")
}
if matcher.MatchesPartial() {
t.Errorf("should not have partial matched at leaf level")
}
}
func TestPathMatcher_BreaksPath(t *testing.T) {
var matcher Matcher
matcher = &PathMatcher{
Paths: [][]interface{}{
{
float64(0),
"key",
float64(0),
},
},
}
if matcher.Matches() {
t.Errorf("should not have exact matched at base level")
}
if !matcher.MatchesPartial() {
t.Errorf("should have partial matched at base level")
}
matcher = matcher.GetChildWithIndex(0)
if matcher.Matches() {
t.Errorf("should not have exact matched at first level")
}
if !matcher.MatchesPartial() {
t.Errorf("should have partial matched at first level")
}
matcher = matcher.GetChildWithKey("invalid")
if matcher.Matches() {
t.Errorf("should not have exact matched at second level")
}
if matcher.MatchesPartial() {
t.Errorf("should not have partial matched at second level")
}
}
func TestPathMatcher_MultiplePaths(t *testing.T) {
var matcher Matcher
matcher = &PathMatcher{
Paths: [][]interface{}{
{
float64(0),
"key",
float64(0),
},
{
float64(0),
"key",
float64(1),
},
},
}
if matcher.Matches() {
t.Errorf("should not have exact matched at base level")
}
if !matcher.MatchesPartial() {
t.Errorf("should have partial matched at base level")
}
matcher = matcher.GetChildWithIndex(0)
if matcher.Matches() {
t.Errorf("should not have exact matched at first level")
}
if !matcher.MatchesPartial() {
t.Errorf("should have partial matched at first level")
}
matcher = matcher.GetChildWithKey("key")
if matcher.Matches() {
t.Errorf("should not have exact matched at second level")
}
if !matcher.MatchesPartial() {
t.Errorf("should have partial matched at second level")
}
validZero := matcher.GetChildWithIndex(0)
validOne := matcher.GetChildWithIndex(1)
invalid := matcher.GetChildWithIndex(2)
if !validZero.Matches() {
t.Errorf("should have exact matched at leaf level")
}
if !validZero.MatchesPartial() {
t.Errorf("should have partial matched at leaf level")
}
if !validOne.Matches() {
t.Errorf("should have exact matched at leaf level")
}
if !validOne.MatchesPartial() {
t.Errorf("should have partial matched at leaf level")
}
if invalid.Matches() {
t.Errorf("should not have exact matched at leaf level")
}
if invalid.MatchesPartial() {
t.Errorf("should not have partial matched at leaf level")
}
}

View File

@@ -4,26 +4,27 @@ import (
"github.com/hashicorp/terraform/internal/command/jsonformat/collections"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed/renderers"
"github.com/hashicorp/terraform/internal/command/jsonformat/structured"
"github.com/hashicorp/terraform/internal/command/jsonprovider"
"github.com/hashicorp/terraform/internal/plans"
)
func (change Change) ComputeDiffForBlock(block *jsonprovider.Block) computed.Diff {
if sensitive, ok := change.checkForSensitiveBlock(block); ok {
func ComputeDiffForBlock(change structured.Change, block *jsonprovider.Block) computed.Diff {
if sensitive, ok := checkForSensitiveBlock(change, block); ok {
return sensitive
}
if unknown, ok := change.checkForUnknownBlock(block); ok {
if unknown, ok := checkForUnknownBlock(change, block); ok {
return unknown
}
current := change.getDefaultActionForIteration()
current := change.GetDefaultActionForIteration()
blockValue := change.asMap()
blockValue := change.AsMap()
attributes := make(map[string]computed.Diff)
for key, attr := range block.Attributes {
childValue := blockValue.getChild(key)
childValue := blockValue.GetChild(key)
if !childValue.RelevantAttributes.MatchesPartial() {
// Mark non-relevant attributes as unchanged.
@@ -43,7 +44,7 @@ func (change Change) ComputeDiffForBlock(block *jsonprovider.Block) computed.Dif
childValue.BeforeExplicit = false
childValue.AfterExplicit = false
childChange := childValue.ComputeDiffForAttribute(attr)
childChange := ComputeDiffForAttribute(childValue, attr)
if childChange.Action == plans.NoOp && childValue.Before == nil && childValue.After == nil {
// Don't record nil values at all in blocks.
continue
@@ -64,20 +65,20 @@ func (change Change) ComputeDiffForBlock(block *jsonprovider.Block) computed.Dif
}
for key, blockType := range block.BlockTypes {
childValue := blockValue.getChild(key)
childValue := blockValue.GetChild(key)
if !childValue.RelevantAttributes.MatchesPartial() {
// Mark non-relevant attributes as unchanged.
childValue = childValue.AsNoOp()
}
beforeSensitive := childValue.isBeforeSensitive()
afterSensitive := childValue.isAfterSensitive()
beforeSensitive := childValue.IsBeforeSensitive()
afterSensitive := childValue.IsAfterSensitive()
forcesReplacement := childValue.ReplacePaths.Matches()
switch NestingMode(blockType.NestingMode) {
case nestingModeSet:
diffs, action := childValue.computeBlockDiffsAsSet(blockType.Block)
diffs, action := computeBlockDiffsAsSet(childValue, blockType.Block)
if action == plans.NoOp && childValue.Before == nil && childValue.After == nil {
// Don't record nil values in blocks.
continue
@@ -85,7 +86,7 @@ func (change Change) ComputeDiffForBlock(block *jsonprovider.Block) computed.Dif
blocks.AddAllSetBlock(key, diffs, forcesReplacement, beforeSensitive, afterSensitive)
current = collections.CompareActions(current, action)
case nestingModeList:
diffs, action := childValue.computeBlockDiffsAsList(blockType.Block)
diffs, action := computeBlockDiffsAsList(childValue, blockType.Block)
if action == plans.NoOp && childValue.Before == nil && childValue.After == nil {
// Don't record nil values in blocks.
continue
@@ -93,7 +94,7 @@ func (change Change) ComputeDiffForBlock(block *jsonprovider.Block) computed.Dif
blocks.AddAllListBlock(key, diffs, forcesReplacement, beforeSensitive, afterSensitive)
current = collections.CompareActions(current, action)
case nestingModeMap:
diffs, action := childValue.computeBlockDiffsAsMap(blockType.Block)
diffs, action := computeBlockDiffsAsMap(childValue, blockType.Block)
if action == plans.NoOp && childValue.Before == nil && childValue.After == nil {
// Don't record nil values in blocks.
continue
@@ -101,7 +102,7 @@ func (change Change) ComputeDiffForBlock(block *jsonprovider.Block) computed.Dif
blocks.AddAllMapBlocks(key, diffs, forcesReplacement, beforeSensitive, afterSensitive)
current = collections.CompareActions(current, action)
case nestingModeSingle, nestingModeGroup:
diff := childValue.ComputeDiffForBlock(blockType.Block)
diff := ComputeDiffForBlock(childValue, blockType.Block)
if diff.Action == plans.NoOp && childValue.Before == nil && childValue.After == nil {
// Don't record nil values in blocks.
continue

View File

@@ -1,244 +0,0 @@
package differ
import (
"encoding/json"
"reflect"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
"github.com/hashicorp/terraform/internal/command/jsonformat/differ/attribute_path"
"github.com/hashicorp/terraform/internal/command/jsonplan"
"github.com/hashicorp/terraform/internal/command/jsonstate"
viewsjson "github.com/hashicorp/terraform/internal/command/views/json"
"github.com/hashicorp/terraform/internal/plans"
)
// Change contains the unmarshalled generic interface{} types that are output by
// the JSON functions in the various json packages (such as jsonplan and
// jsonprovider).
//
// A Change can be converted into a computed.Diff, ready for rendering, with the
// ComputeDiffForAttribute, ComputeDiffForOutput, and ComputeDiffForBlock
// functions.
//
// The Before and After fields are actually go-cty values, but we cannot convert
// them directly because of the Terraform Cloud redacted endpoint. The redacted
// endpoint turns sensitive values into strings regardless of their types.
// Because of this, we cannot just do a direct conversion using the ctyjson
// package. We would have to iterate through the schema first, find the
// sensitive values and their mapped types, update the types inside the schema
// to strings, and then go back and do the overall conversion. This isn't
// including any of the more complicated parts around what happens if something
// was sensitive before and isn't sensitive after or vice versa. This would mean
// the type would need to change between the before and after value. It is in
// fact just easier to iterate through the values as generic JSON interfaces.
type Change struct {
// BeforeExplicit matches AfterExplicit except references the Before value.
BeforeExplicit bool
// AfterExplicit refers to whether the After value is explicit or
// implicit. It is explicit if it has been specified by the user, and
// implicit if it has been set as a consequence of other changes.
//
// For example, explicitly setting a value to null in a list should result
// in After being null and AfterExplicit being true. In comparison,
// removing an element from a list should also result in After being null
// and AfterExplicit being false. Without the explicit information our
// functions would not be able to tell the difference between these two
// cases.
AfterExplicit bool
// Before contains the value before the proposed change.
//
// The type of the value should be informed by the schema and cast
// appropriately when needed.
Before interface{}
// After contains the value after the proposed change.
//
// The type of the value should be informed by the schema and cast
// appropriately when needed.
After interface{}
// Unknown describes whether the After value is known or unknown at the time
// of the plan. In practice, this means the after value should be rendered
// simply as `(known after apply)`.
//
// The concrete value could be a boolean describing whether the entirety of
// the After value is unknown, or it could be a list or a map depending on
// the schema describing whether specific elements or attributes within the
// value are unknown.
Unknown interface{}
// BeforeSensitive matches Unknown, but references whether the Before value
// is sensitive.
BeforeSensitive interface{}
// AfterSensitive matches Unknown, but references whether the After value is
// sensitive.
AfterSensitive interface{}
// ReplacePaths contains a set of paths that point to attributes/elements
// that are causing the overall resource to be replaced rather than simply
// updated.
ReplacePaths attribute_path.Matcher
// RelevantAttributes contains a set of paths that point attributes/elements
// that we should display. Any element/attribute not matched by this Matcher
// should be skipped.
RelevantAttributes attribute_path.Matcher
}
// FromJsonChange unmarshals the raw []byte values in the jsonplan.Change
// structs into generic interface{} types that can be reasoned about.
func FromJsonChange(change jsonplan.Change, relevantAttributes attribute_path.Matcher) Change {
return Change{
Before: unmarshalGeneric(change.Before),
After: unmarshalGeneric(change.After),
Unknown: unmarshalGeneric(change.AfterUnknown),
BeforeSensitive: unmarshalGeneric(change.BeforeSensitive),
AfterSensitive: unmarshalGeneric(change.AfterSensitive),
ReplacePaths: attribute_path.Parse(change.ReplacePaths, false),
RelevantAttributes: relevantAttributes,
}
}
// FromJsonResource unmarshals the raw values in the jsonstate.Resource structs
// into generic interface{} types that can be reasoned about.
func FromJsonResource(resource jsonstate.Resource) Change {
return Change{
// We model resource formatting as NoOps.
Before: unwrapAttributeValues(resource.AttributeValues),
After: unwrapAttributeValues(resource.AttributeValues),
// We have some sensitive values, but we don't have any unknown values.
Unknown: false,
BeforeSensitive: unmarshalGeneric(resource.SensitiveValues),
AfterSensitive: unmarshalGeneric(resource.SensitiveValues),
// We don't display replacement data for resources, and all attributes
// are relevant.
ReplacePaths: attribute_path.Empty(false),
RelevantAttributes: attribute_path.AlwaysMatcher(),
}
}
// FromJsonOutput unmarshals the raw values in the jsonstate.Output structs into
// generic interface{} types that can be reasoned about.
func FromJsonOutput(output jsonstate.Output) Change {
return Change{
// We model resource formatting as NoOps.
Before: unmarshalGeneric(output.Value),
After: unmarshalGeneric(output.Value),
// We have some sensitive values, but we don't have any unknown values.
Unknown: false,
BeforeSensitive: output.Sensitive,
AfterSensitive: output.Sensitive,
// We don't display replacement data for resources, and all attributes
// are relevant.
ReplacePaths: attribute_path.Empty(false),
RelevantAttributes: attribute_path.AlwaysMatcher(),
}
}
// FromJsonViewsOutput unmarshals the raw values in the viewsjson.Output structs into
// generic interface{} types that can be reasoned about.
func FromJsonViewsOutput(output viewsjson.Output) Change {
return Change{
// We model resource formatting as NoOps.
Before: unmarshalGeneric(output.Value),
After: unmarshalGeneric(output.Value),
// We have some sensitive values, but we don't have any unknown values.
Unknown: false,
BeforeSensitive: output.Sensitive,
AfterSensitive: output.Sensitive,
// We don't display replacement data for resources, and all attributes
// are relevant.
ReplacePaths: attribute_path.Empty(false),
RelevantAttributes: attribute_path.AlwaysMatcher(),
}
}
func (change Change) asDiff(renderer computed.DiffRenderer) computed.Diff {
return computed.NewDiff(renderer, change.calculateChange(), change.ReplacePaths.Matches())
}
func (change Change) calculateChange() plans.Action {
if (change.Before == nil && !change.BeforeExplicit) && (change.After != nil || change.AfterExplicit) {
return plans.Create
}
if (change.After == nil && !change.AfterExplicit) && (change.Before != nil || change.BeforeExplicit) {
return plans.Delete
}
if reflect.DeepEqual(change.Before, change.After) && change.AfterExplicit == change.BeforeExplicit && change.isAfterSensitive() == change.isBeforeSensitive() {
return plans.NoOp
}
return plans.Update
}
// getDefaultActionForIteration is used to guess what the change could be for
// complex attributes (collections and objects) and blocks.
//
// You can't really tell the difference between a NoOp and an Update just by
// looking at the attribute itself as you need to inspect the children.
//
// This function returns a Delete or a Create action if the before or after
// values were null, and returns a NoOp for all other cases. It should be used
// in conjunction with compareActions to calculate the actual action based on
// the actions of the children.
func (change Change) getDefaultActionForIteration() plans.Action {
if change.Before == nil && change.After == nil {
return plans.NoOp
}
if change.Before == nil {
return plans.Create
}
if change.After == nil {
return plans.Delete
}
return plans.NoOp
}
// AsNoOp returns the current change as if it is a NoOp operation.
//
// Basically it replaces all the after values with the before values.
func (change Change) AsNoOp() Change {
return Change{
BeforeExplicit: change.BeforeExplicit,
AfterExplicit: change.BeforeExplicit,
Before: change.Before,
After: change.Before,
Unknown: false,
BeforeSensitive: change.BeforeSensitive,
AfterSensitive: change.BeforeSensitive,
ReplacePaths: change.ReplacePaths,
RelevantAttributes: change.RelevantAttributes,
}
}
func unmarshalGeneric(raw json.RawMessage) interface{} {
if raw == nil {
return nil
}
var out interface{}
if err := json.Unmarshal(raw, &out); err != nil {
panic("unrecognized json type: " + err.Error())
}
return out
}
func unwrapAttributeValues(values jsonstate.AttributeValues) map[string]interface{} {
out := make(map[string]interface{})
for key, value := range values {
out[key] = unmarshalGeneric(value)
}
return out
}

View File

@@ -1,83 +0,0 @@
package differ
import (
"github.com/hashicorp/terraform/internal/command/jsonformat/differ/attribute_path"
)
// ChangeMap is a Change that represents a Map or an Object type, and has
// converted the relevant interfaces into maps for easier access.
type ChangeMap struct {
// Before contains the value before the proposed change.
Before map[string]interface{}
// After contains the value after the proposed change.
After map[string]interface{}
// Unknown contains the unknown status of any elements/attributes of this
// map/object.
Unknown map[string]interface{}
// BeforeSensitive contains the before sensitive status of any
// elements/attributes of this map/object.
BeforeSensitive map[string]interface{}
// AfterSensitive contains the after sensitive status of any
// elements/attributes of this map/object.
AfterSensitive map[string]interface{}
// ReplacePaths matches the same attributes in Change exactly.
ReplacePaths attribute_path.Matcher
// RelevantAttributes matches the same attributes in Change exactly.
RelevantAttributes attribute_path.Matcher
}
func (change Change) asMap() ChangeMap {
return ChangeMap{
Before: genericToMap(change.Before),
After: genericToMap(change.After),
Unknown: genericToMap(change.Unknown),
BeforeSensitive: genericToMap(change.BeforeSensitive),
AfterSensitive: genericToMap(change.AfterSensitive),
ReplacePaths: change.ReplacePaths,
RelevantAttributes: change.RelevantAttributes,
}
}
func (m ChangeMap) getChild(key string) Change {
before, beforeExplicit := getFromGenericMap(m.Before, key)
after, afterExplicit := getFromGenericMap(m.After, key)
unknown, _ := getFromGenericMap(m.Unknown, key)
beforeSensitive, _ := getFromGenericMap(m.BeforeSensitive, key)
afterSensitive, _ := getFromGenericMap(m.AfterSensitive, key)
return Change{
BeforeExplicit: beforeExplicit,
AfterExplicit: afterExplicit,
Before: before,
After: after,
Unknown: unknown,
BeforeSensitive: beforeSensitive,
AfterSensitive: afterSensitive,
ReplacePaths: m.ReplacePaths.GetChildWithKey(key),
RelevantAttributes: m.RelevantAttributes.GetChildWithKey(key),
}
}
func getFromGenericMap(generic map[string]interface{}, key string) (interface{}, bool) {
if generic == nil {
return nil, false
}
if child, ok := generic[key]; ok {
return child, ok
}
return nil, false
}
func genericToMap(generic interface{}) map[string]interface{} {
if concrete, ok := generic.(map[string]interface{}); ok {
return concrete
}
return nil
}

View File

@@ -1,86 +0,0 @@
package differ
import (
"github.com/hashicorp/terraform/internal/command/jsonformat/differ/attribute_path"
)
// ChangeSlice is a Change that represents a Tuple, Set, or List type, and has
// converted the relevant interfaces into slices for easier access.
type ChangeSlice struct {
// Before contains the value before the proposed change.
Before []interface{}
// After contains the value after the proposed change.
After []interface{}
// Unknown contains the unknown status of any elements of this list/set.
Unknown []interface{}
// BeforeSensitive contains the before sensitive status of any elements of
//this list/set.
BeforeSensitive []interface{}
// AfterSensitive contains the after sensitive status of any elements of
//this list/set.
AfterSensitive []interface{}
// ReplacePaths matches the same attributes in Change exactly.
ReplacePaths attribute_path.Matcher
// RelevantAttributes matches the same attributes in Change exactly.
RelevantAttributes attribute_path.Matcher
}
func (change Change) asSlice() ChangeSlice {
return ChangeSlice{
Before: genericToSlice(change.Before),
After: genericToSlice(change.After),
Unknown: genericToSlice(change.Unknown),
BeforeSensitive: genericToSlice(change.BeforeSensitive),
AfterSensitive: genericToSlice(change.AfterSensitive),
ReplacePaths: change.ReplacePaths,
RelevantAttributes: change.RelevantAttributes,
}
}
func (s ChangeSlice) getChild(beforeIx, afterIx int) Change {
before, beforeExplicit := getFromGenericSlice(s.Before, beforeIx)
after, afterExplicit := getFromGenericSlice(s.After, afterIx)
unknown, _ := getFromGenericSlice(s.Unknown, afterIx)
beforeSensitive, _ := getFromGenericSlice(s.BeforeSensitive, beforeIx)
afterSensitive, _ := getFromGenericSlice(s.AfterSensitive, afterIx)
mostRelevantIx := beforeIx
if beforeIx < 0 || beforeIx >= len(s.Before) {
mostRelevantIx = afterIx
}
return Change{
BeforeExplicit: beforeExplicit,
AfterExplicit: afterExplicit,
Before: before,
After: after,
Unknown: unknown,
BeforeSensitive: beforeSensitive,
AfterSensitive: afterSensitive,
ReplacePaths: s.ReplacePaths.GetChildWithIndex(mostRelevantIx),
RelevantAttributes: s.RelevantAttributes.GetChildWithIndex(mostRelevantIx),
}
}
func getFromGenericSlice(generic []interface{}, ix int) (interface{}, bool) {
if generic == nil {
return nil, false
}
if ix < 0 || ix >= len(generic) {
return nil, false
}
return generic[ix], true
}
func genericToSlice(generic interface{}) []interface{} {
if concrete, ok := generic.([]interface{}); ok {
return concrete
}
return nil
}

View File

@@ -0,0 +1,12 @@
package differ
import (
"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
"github.com/hashicorp/terraform/internal/command/jsonformat/structured"
)
// asDiff is a helper function to abstract away some simple and common
// functionality when converting a renderer into a concrete diff.
func asDiff(change structured.Change, renderer computed.DiffRenderer) computed.Diff {
return computed.NewDiff(renderer, change.CalculateAction(), change.ReplacePaths.Matches())
}

View File

@@ -6,16 +6,17 @@ import (
"github.com/hashicorp/terraform/internal/command/jsonformat/collections"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed/renderers"
"github.com/hashicorp/terraform/internal/command/jsonformat/differ/attribute_path"
"github.com/hashicorp/terraform/internal/command/jsonformat/structured"
"github.com/hashicorp/terraform/internal/command/jsonformat/structured/attribute_path"
"github.com/hashicorp/terraform/internal/command/jsonprovider"
"github.com/hashicorp/terraform/internal/plans"
)
func (change Change) computeAttributeDiffAsList(elementType cty.Type) computed.Diff {
sliceValue := change.asSlice()
func computeAttributeDiffAsList(change structured.Change, elementType cty.Type) computed.Diff {
sliceValue := change.AsSlice()
processIndices := func(beforeIx, afterIx int) computed.Diff {
value := sliceValue.getChild(beforeIx, afterIx)
value := sliceValue.GetChild(beforeIx, afterIx)
// It's actually really difficult to render the diffs when some indices
// within a slice are relevant and others aren't. To make this simpler
@@ -37,7 +38,7 @@ func (change Change) computeAttributeDiffAsList(elementType cty.Type) computed.D
// after.
value.RelevantAttributes = attribute_path.AlwaysMatcher()
return value.ComputeDiffForType(elementType)
return ComputeDiffForType(value, elementType)
}
isObjType := func(_ interface{}) bool {
@@ -48,11 +49,11 @@ func (change Change) computeAttributeDiffAsList(elementType cty.Type) computed.D
return computed.NewDiff(renderers.List(elements), current, change.ReplacePaths.Matches())
}
func (change Change) computeAttributeDiffAsNestedList(attributes map[string]*jsonprovider.Attribute) computed.Diff {
func computeAttributeDiffAsNestedList(change structured.Change, attributes map[string]*jsonprovider.Attribute) computed.Diff {
var elements []computed.Diff
current := change.getDefaultActionForIteration()
change.processNestedList(func(value Change) {
element := value.computeDiffForNestedAttribute(&jsonprovider.NestedType{
current := change.GetDefaultActionForIteration()
processNestedList(change, func(value structured.Change) {
element := computeDiffForNestedAttribute(value, &jsonprovider.NestedType{
Attributes: attributes,
NestingMode: "single",
})
@@ -62,21 +63,21 @@ func (change Change) computeAttributeDiffAsNestedList(attributes map[string]*jso
return computed.NewDiff(renderers.NestedList(elements), current, change.ReplacePaths.Matches())
}
func (change Change) computeBlockDiffsAsList(block *jsonprovider.Block) ([]computed.Diff, plans.Action) {
func computeBlockDiffsAsList(change structured.Change, block *jsonprovider.Block) ([]computed.Diff, plans.Action) {
var elements []computed.Diff
current := change.getDefaultActionForIteration()
change.processNestedList(func(value Change) {
element := value.ComputeDiffForBlock(block)
current := change.GetDefaultActionForIteration()
processNestedList(change, func(value structured.Change) {
element := ComputeDiffForBlock(value, block)
elements = append(elements, element)
current = collections.CompareActions(current, element.Action)
})
return elements, current
}
func (change Change) processNestedList(process func(value Change)) {
sliceValue := change.asSlice()
func processNestedList(change structured.Change, process func(value structured.Change)) {
sliceValue := change.AsSlice()
for ix := 0; ix < len(sliceValue.Before) || ix < len(sliceValue.After); ix++ {
value := sliceValue.getChild(ix, ix)
value := sliceValue.GetChild(ix, ix)
if !value.RelevantAttributes.MatchesPartial() {
// Mark non-relevant attributes as unchanged.
value = value.AsNoOp()

View File

@@ -6,12 +6,13 @@ import (
"github.com/hashicorp/terraform/internal/command/jsonformat/collections"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed/renderers"
"github.com/hashicorp/terraform/internal/command/jsonformat/structured"
"github.com/hashicorp/terraform/internal/command/jsonprovider"
"github.com/hashicorp/terraform/internal/plans"
)
func (change Change) computeAttributeDiffAsMap(elementType cty.Type) computed.Diff {
mapValue := change.asMap()
func computeAttributeDiffAsMap(change structured.Change, elementType cty.Type) computed.Diff {
mapValue := change.AsMap()
// The jsonplan package will have stripped out unknowns from our after value
// so we're going to add them back in here.
@@ -45,25 +46,25 @@ func (change Change) computeAttributeDiffAsMap(elementType cty.Type) computed.Di
}
elements, current := collections.TransformMap(mapValue.Before, after, func(key string) computed.Diff {
value := mapValue.getChild(key)
value := mapValue.GetChild(key)
if !value.RelevantAttributes.MatchesPartial() {
// Mark non-relevant attributes as unchanged.
value = value.AsNoOp()
}
return value.ComputeDiffForType(elementType)
return ComputeDiffForType(value, elementType)
})
return computed.NewDiff(renderers.Map(elements), current, change.ReplacePaths.Matches())
}
func (change Change) computeAttributeDiffAsNestedMap(attributes map[string]*jsonprovider.Attribute) computed.Diff {
mapValue := change.asMap()
func computeAttributeDiffAsNestedMap(change structured.Change, attributes map[string]*jsonprovider.Attribute) computed.Diff {
mapValue := change.AsMap()
elements, current := collections.TransformMap(mapValue.Before, mapValue.After, func(key string) computed.Diff {
value := mapValue.getChild(key)
value := mapValue.GetChild(key)
if !value.RelevantAttributes.MatchesPartial() {
// Mark non-relevant attributes as unchanged.
value = value.AsNoOp()
}
return value.computeDiffForNestedAttribute(&jsonprovider.NestedType{
return computeDiffForNestedAttribute(value, &jsonprovider.NestedType{
Attributes: attributes,
NestingMode: "single",
})
@@ -71,14 +72,14 @@ func (change Change) computeAttributeDiffAsNestedMap(attributes map[string]*json
return computed.NewDiff(renderers.NestedMap(elements), current, change.ReplacePaths.Matches())
}
func (change Change) computeBlockDiffsAsMap(block *jsonprovider.Block) (map[string]computed.Diff, plans.Action) {
mapValue := change.asMap()
func computeBlockDiffsAsMap(change structured.Change, block *jsonprovider.Block) (map[string]computed.Diff, plans.Action) {
mapValue := change.AsMap()
return collections.TransformMap(mapValue.Before, mapValue.After, func(key string) computed.Diff {
value := mapValue.getChild(key)
value := mapValue.GetChild(key)
if !value.RelevantAttributes.MatchesPartial() {
// Mark non-relevant attributes as unchanged.
value = value.AsNoOp()
}
return value.ComputeDiffForBlock(block)
return ComputeDiffForBlock(value, block)
})
}

View File

@@ -6,20 +6,21 @@ import (
"github.com/hashicorp/terraform/internal/command/jsonformat/collections"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed/renderers"
"github.com/hashicorp/terraform/internal/command/jsonformat/structured"
"github.com/hashicorp/terraform/internal/command/jsonprovider"
"github.com/hashicorp/terraform/internal/plans"
)
func (change Change) computeAttributeDiffAsObject(attributes map[string]cty.Type) computed.Diff {
attributeDiffs, action := processObject(change, attributes, func(value Change, ctype cty.Type) computed.Diff {
return value.ComputeDiffForType(ctype)
func computeAttributeDiffAsObject(change structured.Change, attributes map[string]cty.Type) computed.Diff {
attributeDiffs, action := processObject(change, attributes, func(value structured.Change, ctype cty.Type) computed.Diff {
return ComputeDiffForType(value, ctype)
})
return computed.NewDiff(renderers.Object(attributeDiffs), action, change.ReplacePaths.Matches())
}
func (change Change) computeAttributeDiffAsNestedObject(attributes map[string]*jsonprovider.Attribute) computed.Diff {
attributeDiffs, action := processObject(change, attributes, func(value Change, attribute *jsonprovider.Attribute) computed.Diff {
return value.ComputeDiffForAttribute(attribute)
func computeAttributeDiffAsNestedObject(change structured.Change, attributes map[string]*jsonprovider.Attribute) computed.Diff {
attributeDiffs, action := processObject(change, attributes, func(value structured.Change, attribute *jsonprovider.Attribute) computed.Diff {
return ComputeDiffForAttribute(value, attribute)
})
return computed.NewDiff(renderers.NestedObject(attributeDiffs), action, change.ReplacePaths.Matches())
}
@@ -35,13 +36,13 @@ func (change Change) computeAttributeDiffAsNestedObject(attributes map[string]*j
// Also, as it generic we cannot make this function a method on Change as you
// can't create generic methods on structs. Instead, we make this a generic
// function that receives the value as an argument.
func processObject[T any](v Change, attributes map[string]T, computeDiff func(Change, T) computed.Diff) (map[string]computed.Diff, plans.Action) {
func processObject[T any](v structured.Change, attributes map[string]T, computeDiff func(structured.Change, T) computed.Diff) (map[string]computed.Diff, plans.Action) {
attributeDiffs := make(map[string]computed.Diff)
mapValue := v.asMap()
mapValue := v.AsMap()
currentAction := v.getDefaultActionForIteration()
currentAction := v.GetDefaultActionForIteration()
for key, attribute := range attributes {
attributeValue := mapValue.getChild(key)
attributeValue := mapValue.GetChild(key)
if !attributeValue.RelevantAttributes.MatchesPartial() {
// Mark non-relevant attributes as unchanged.

View File

@@ -5,14 +5,15 @@ import (
"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed/renderers"
"github.com/hashicorp/terraform/internal/command/jsonformat/structured"
)
func (change Change) ComputeDiffForOutput() computed.Diff {
if sensitive, ok := change.checkForSensitiveType(cty.DynamicPseudoType); ok {
func ComputeDiffForOutput(change structured.Change) computed.Diff {
if sensitive, ok := checkForSensitiveType(change, cty.DynamicPseudoType); ok {
return sensitive
}
if unknown, ok := change.checkForUnknownType(cty.DynamicPseudoType); ok {
if unknown, ok := checkForUnknownType(change, cty.DynamicPseudoType); ok {
return unknown
}

View File

@@ -4,10 +4,10 @@ import (
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed/renderers"
"github.com/hashicorp/terraform/internal/command/jsonformat/structured"
)
func (change Change) computeAttributeDiffAsPrimitive(ctype cty.Type) computed.Diff {
return change.asDiff(renderers.Primitive(change.Before, change.After, ctype))
func computeAttributeDiffAsPrimitive(change structured.Change, ctype cty.Type) computed.Diff {
return asDiff(change, renderers.Primitive(change.Before, change.After, ctype))
}

View File

@@ -5,33 +5,34 @@ import (
"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed/renderers"
"github.com/hashicorp/terraform/internal/command/jsonformat/structured"
"github.com/hashicorp/terraform/internal/command/jsonprovider"
"github.com/hashicorp/terraform/internal/plans"
)
type CreateSensitiveRenderer func(computed.Diff, bool, bool) computed.DiffRenderer
func (change Change) checkForSensitiveType(ctype cty.Type) (computed.Diff, bool) {
return change.checkForSensitive(renderers.Sensitive, func(value Change) computed.Diff {
return value.ComputeDiffForType(ctype)
func checkForSensitiveType(change structured.Change, ctype cty.Type) (computed.Diff, bool) {
return checkForSensitive(change, renderers.Sensitive, func(value structured.Change) computed.Diff {
return ComputeDiffForType(value, ctype)
})
}
func (change Change) checkForSensitiveNestedAttribute(attribute *jsonprovider.NestedType) (computed.Diff, bool) {
return change.checkForSensitive(renderers.Sensitive, func(value Change) computed.Diff {
return value.computeDiffForNestedAttribute(attribute)
func checkForSensitiveNestedAttribute(change structured.Change, attribute *jsonprovider.NestedType) (computed.Diff, bool) {
return checkForSensitive(change, renderers.Sensitive, func(value structured.Change) computed.Diff {
return computeDiffForNestedAttribute(value, attribute)
})
}
func (change Change) checkForSensitiveBlock(block *jsonprovider.Block) (computed.Diff, bool) {
return change.checkForSensitive(renderers.SensitiveBlock, func(value Change) computed.Diff {
return value.ComputeDiffForBlock(block)
func checkForSensitiveBlock(change structured.Change, block *jsonprovider.Block) (computed.Diff, bool) {
return checkForSensitive(change, renderers.SensitiveBlock, func(value structured.Change) computed.Diff {
return ComputeDiffForBlock(value, block)
})
}
func (change Change) checkForSensitive(create CreateSensitiveRenderer, computedDiff func(value Change) computed.Diff) (computed.Diff, bool) {
beforeSensitive := change.isBeforeSensitive()
afterSensitive := change.isAfterSensitive()
func checkForSensitive(change structured.Change, create CreateSensitiveRenderer, computedDiff func(value structured.Change) computed.Diff) (computed.Diff, bool) {
beforeSensitive := change.IsBeforeSensitive()
afterSensitive := change.IsAfterSensitive()
if !beforeSensitive && !afterSensitive {
return computed.Diff{}, false
@@ -44,7 +45,7 @@ func (change Change) checkForSensitive(create CreateSensitiveRenderer, computedD
// The change can choose what to do with this information, in most cases
// it will just be ignored in favour of printing `(sensitive value)`.
value := Change{
value := structured.Change{
BeforeExplicit: change.BeforeExplicit,
AfterExplicit: change.AfterExplicit,
Before: change.Before,
@@ -75,17 +76,3 @@ func (change Change) checkForSensitive(create CreateSensitiveRenderer, computedD
return computed.NewDiff(create(inner, beforeSensitive, afterSensitive), action, change.ReplacePaths.Matches()), true
}
func (change Change) isBeforeSensitive() bool {
if sensitive, ok := change.BeforeSensitive.(bool); ok {
return sensitive
}
return false
}
func (change Change) isAfterSensitive() bool {
if sensitive, ok := change.AfterSensitive.(bool); ok {
return sensitive
}
return false
}

View File

@@ -8,27 +8,28 @@ import (
"github.com/hashicorp/terraform/internal/command/jsonformat/collections"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed/renderers"
"github.com/hashicorp/terraform/internal/command/jsonformat/differ/attribute_path"
"github.com/hashicorp/terraform/internal/command/jsonformat/structured"
"github.com/hashicorp/terraform/internal/command/jsonformat/structured/attribute_path"
"github.com/hashicorp/terraform/internal/command/jsonprovider"
"github.com/hashicorp/terraform/internal/plans"
)
func (change Change) computeAttributeDiffAsSet(elementType cty.Type) computed.Diff {
func computeAttributeDiffAsSet(change structured.Change, elementType cty.Type) computed.Diff {
var elements []computed.Diff
current := change.getDefaultActionForIteration()
change.processSet(func(value Change) {
element := value.ComputeDiffForType(elementType)
current := change.GetDefaultActionForIteration()
processSet(change, func(value structured.Change) {
element := ComputeDiffForType(value, elementType)
elements = append(elements, element)
current = collections.CompareActions(current, element.Action)
})
return computed.NewDiff(renderers.Set(elements), current, change.ReplacePaths.Matches())
}
func (change Change) computeAttributeDiffAsNestedSet(attributes map[string]*jsonprovider.Attribute) computed.Diff {
func computeAttributeDiffAsNestedSet(change structured.Change, attributes map[string]*jsonprovider.Attribute) computed.Diff {
var elements []computed.Diff
current := change.getDefaultActionForIteration()
change.processSet(func(value Change) {
element := value.computeDiffForNestedAttribute(&jsonprovider.NestedType{
current := change.GetDefaultActionForIteration()
processSet(change, func(value structured.Change) {
element := computeDiffForNestedAttribute(value, &jsonprovider.NestedType{
Attributes: attributes,
NestingMode: "single",
})
@@ -38,19 +39,19 @@ func (change Change) computeAttributeDiffAsNestedSet(attributes map[string]*json
return computed.NewDiff(renderers.NestedSet(elements), current, change.ReplacePaths.Matches())
}
func (change Change) computeBlockDiffsAsSet(block *jsonprovider.Block) ([]computed.Diff, plans.Action) {
func computeBlockDiffsAsSet(change structured.Change, block *jsonprovider.Block) ([]computed.Diff, plans.Action) {
var elements []computed.Diff
current := change.getDefaultActionForIteration()
change.processSet(func(value Change) {
element := value.ComputeDiffForBlock(block)
current := change.GetDefaultActionForIteration()
processSet(change, func(value structured.Change) {
element := ComputeDiffForBlock(value, block)
elements = append(elements, element)
current = collections.CompareActions(current, element.Action)
})
return elements, current
}
func (change Change) processSet(process func(value Change)) {
sliceValue := change.asSlice()
func processSet(change structured.Change, process func(value structured.Change)) {
sliceValue := change.AsSlice()
foundInBefore := make(map[int]int)
foundInAfter := make(map[int]int)
@@ -67,8 +68,8 @@ func (change Change) processSet(process func(value Change)) {
continue
}
child := sliceValue.getChild(ix, jx)
if reflect.DeepEqual(child.Before, child.After) && child.isBeforeSensitive() == child.isAfterSensitive() && !child.isUnknown() {
child := sliceValue.GetChild(ix, jx)
if reflect.DeepEqual(child.Before, child.After) && child.IsBeforeSensitive() == child.IsAfterSensitive() && !child.IsUnknown() {
matched = true
foundInBefore[ix] = jx
foundInAfter[jx] = ix
@@ -80,7 +81,7 @@ func (change Change) processSet(process func(value Change)) {
}
}
clearRelevantStatus := func(change Change) Change {
clearRelevantStatus := func(change structured.Change) structured.Change {
// It's actually really difficult to render the diffs when some indices
// within a slice are relevant and others aren't. To make this simpler
// we just treat all children of a relevant list or set as also
@@ -112,11 +113,11 @@ func (change Change) processSet(process func(value Change)) {
for ix := 0; ix < len(sliceValue.Before); ix++ {
if jx := foundInBefore[ix]; jx >= 0 {
child := clearRelevantStatus(sliceValue.getChild(ix, jx))
child := clearRelevantStatus(sliceValue.GetChild(ix, jx))
process(child)
continue
}
child := clearRelevantStatus(sliceValue.getChild(ix, len(sliceValue.After)))
child := clearRelevantStatus(sliceValue.GetChild(ix, len(sliceValue.After)))
process(child)
}
@@ -125,7 +126,7 @@ func (change Change) processSet(process func(value Change)) {
// Then this value was handled in the previous for loop.
continue
}
child := clearRelevantStatus(sliceValue.getChild(len(sliceValue.Before), jx))
child := clearRelevantStatus(sliceValue.GetChild(len(sliceValue.Before), jx))
process(child)
}
}

View File

@@ -6,19 +6,20 @@ import (
"github.com/hashicorp/terraform/internal/command/jsonformat/collections"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed/renderers"
"github.com/hashicorp/terraform/internal/command/jsonformat/structured"
)
func (change Change) computeAttributeDiffAsTuple(elementTypes []cty.Type) computed.Diff {
func computeAttributeDiffAsTuple(change structured.Change, elementTypes []cty.Type) computed.Diff {
var elements []computed.Diff
current := change.getDefaultActionForIteration()
sliceValue := change.asSlice()
current := change.GetDefaultActionForIteration()
sliceValue := change.AsSlice()
for ix, elementType := range elementTypes {
childValue := sliceValue.getChild(ix, ix)
childValue := sliceValue.GetChild(ix, ix)
if !childValue.RelevantAttributes.MatchesPartial() {
// Mark non-relevant attributes as unchanged.
childValue = childValue.AsNoOp()
}
element := childValue.ComputeDiffForType(elementType)
element := ComputeDiffForType(childValue, elementType)
elements = append(elements, element)
current = collections.CompareActions(current, element.Action)
}

View File

@@ -4,18 +4,17 @@ import (
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed/renderers"
"github.com/hashicorp/terraform/internal/command/jsonformat/structured"
"github.com/hashicorp/terraform/internal/command/jsonprovider"
)
func (change Change) checkForUnknownType(ctype cty.Type) (computed.Diff, bool) {
return change.checkForUnknown(false, func(value Change) computed.Diff {
return value.ComputeDiffForType(ctype)
func checkForUnknownType(change structured.Change, ctype cty.Type) (computed.Diff, bool) {
return checkForUnknown(change, false, func(value structured.Change) computed.Diff {
return ComputeDiffForType(value, ctype)
})
}
func (change Change) checkForUnknownNestedAttribute(attribute *jsonprovider.NestedType) (computed.Diff, bool) {
func checkForUnknownNestedAttribute(change structured.Change, attribute *jsonprovider.NestedType) (computed.Diff, bool) {
// We want our child attributes to show up as computed instead of deleted.
// Let's populate that here.
@@ -24,12 +23,12 @@ func (change Change) checkForUnknownNestedAttribute(attribute *jsonprovider.Nest
childUnknown[key] = true
}
return change.checkForUnknown(childUnknown, func(value Change) computed.Diff {
return value.computeDiffForNestedAttribute(attribute)
return checkForUnknown(change, childUnknown, func(value structured.Change) computed.Diff {
return computeDiffForNestedAttribute(value, attribute)
})
}
func (change Change) checkForUnknownBlock(block *jsonprovider.Block) (computed.Diff, bool) {
func checkForUnknownBlock(change structured.Change, block *jsonprovider.Block) (computed.Diff, bool) {
// We want our child attributes to show up as computed instead of deleted.
// Let's populate that here.
@@ -38,13 +37,13 @@ func (change Change) checkForUnknownBlock(block *jsonprovider.Block) (computed.D
childUnknown[key] = true
}
return change.checkForUnknown(childUnknown, func(value Change) computed.Diff {
return value.ComputeDiffForBlock(block)
return checkForUnknown(change, childUnknown, func(value structured.Change) computed.Diff {
return ComputeDiffForBlock(value, block)
})
}
func (change Change) checkForUnknown(childUnknown interface{}, computeDiff func(value Change) computed.Diff) (computed.Diff, bool) {
unknown := change.isUnknown()
func checkForUnknown(change structured.Change, childUnknown interface{}, computeDiff func(value structured.Change) computed.Diff) (computed.Diff, bool) {
unknown := change.IsUnknown()
if !unknown {
return computed.Diff{}, false
@@ -56,26 +55,19 @@ func (change Change) checkForUnknown(childUnknown interface{}, computeDiff func(
change.AfterExplicit = true
if change.Before == nil {
return change.asDiff(renderers.Unknown(computed.Diff{})), true
return asDiff(change, renderers.Unknown(computed.Diff{})), true
}
// If we get here, then we have a before value. We're going to model a
// delete operation and our renderer later can render the overall change
// accurately.
beforeValue := Change{
beforeValue := structured.Change{
Before: change.Before,
BeforeSensitive: change.BeforeSensitive,
Unknown: childUnknown,
ReplacePaths: change.ReplacePaths,
RelevantAttributes: change.RelevantAttributes,
}
return change.asDiff(renderers.Unknown(computeDiff(beforeValue))), true
}
func (change Change) isUnknown() bool {
if unknown, ok := change.Unknown.(bool); ok {
return unknown
}
return false
return asDiff(change, renderers.Unknown(computeDiff(beforeValue))), true
}