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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 409 additions and 337 deletions

View File

@ -9,7 +9,7 @@ import (
"github.com/hashicorp/terraform/internal/command/jsonformat/collections"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
"github.com/hashicorp/terraform/internal/command/jsonformat/differ/attribute_path"
"github.com/hashicorp/terraform/internal/command/jsonformat/structured/attribute_path"
"github.com/hashicorp/terraform/internal/plans"
)

View File

@ -3,7 +3,8 @@ package jsonformat
import (
"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
"github.com/hashicorp/terraform/internal/command/jsonformat/differ"
"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/jsonplan"
"github.com/hashicorp/terraform/internal/plans"
)
@ -42,22 +43,25 @@ func precomputeDiffs(plan Plan, mode plans.Mode) diffs {
}
schema := plan.getSchema(drift)
change := structured.FromJsonChange(drift.Change, relevantAttrs)
diffs.drift = append(diffs.drift, diff{
change: drift,
diff: differ.FromJsonChange(drift.Change, relevantAttrs).ComputeDiffForBlock(schema.Block),
diff: differ.ComputeDiffForBlock(change, schema.Block),
})
}
for _, change := range plan.ResourceChanges {
schema := plan.getSchema(change)
structuredChange := structured.FromJsonChange(change.Change, attribute_path.AlwaysMatcher())
diffs.changes = append(diffs.changes, diff{
change: change,
diff: differ.FromJsonChange(change.Change, attribute_path.AlwaysMatcher()).ComputeDiffForBlock(schema.Block),
diff: differ.ComputeDiffForBlock(structuredChange, schema.Block),
})
}
for key, output := range plan.OutputChanges {
diffs.outputs[key] = differ.FromJsonChange(output, attribute_path.AlwaysMatcher()).ComputeDiffForOutput()
change := structured.FromJsonChange(output, attribute_path.AlwaysMatcher())
diffs.outputs[key] = differ.ComputeDiffForOutput(change)
}
return diffs

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

@ -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

@ -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
}

View File

@ -7,7 +7,7 @@ import (
"github.com/hashicorp/terraform/internal/command/jsonformat/collections"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
"github.com/hashicorp/terraform/internal/command/jsonformat/differ/attribute_path"
"github.com/hashicorp/terraform/internal/command/jsonformat/structured/attribute_path"
"github.com/hashicorp/terraform/internal/plans"
)
@ -150,7 +150,6 @@ func (opts JsonOpts) processObject(before, after map[string]interface{}, relevan
// Mark non-relevant attributes as unchanged.
afterChild = beforeChild
afterExplicit = beforeExplicit
}
return opts.Transform(beforeChild, afterChild, beforeExplicit, afterExplicit, childRelevantAttributes)

View File

@ -11,7 +11,8 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/command/jsonformat/differ"
"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/jsonplan"
"github.com/hashicorp/terraform/internal/command/jsonprovider"
"github.com/hashicorp/terraform/internal/configs/configschema"
@ -6654,12 +6655,11 @@ func runTestCases(t *testing.T, testCases map[string]testCase) {
}
jsonschemas := jsonprovider.MarshalForRenderer(tfschemas)
change := structured.FromJsonChange(jsonchanges[0].Change, attribute_path.AlwaysMatcher())
renderer := Renderer{Colorize: color}
diff := diff{
change: jsonchanges[0],
diff: differ.
FromJsonChange(jsonchanges[0].Change, attribute_path.AlwaysMatcher()).
ComputeDiffForBlock(jsonschemas[jsonchanges[0].ProviderName].ResourceSchemas[jsonchanges[0].Type].Block),
diff: differ.ComputeDiffForBlock(change, jsonschemas[jsonchanges[0].ProviderName].ResourceSchemas[jsonchanges[0].Type].Block),
}
output, _ := renderHumanDiff(renderer, diff, proposedChange)
if diff := cmp.Diff(output, tc.ExpectedOutput); diff != "" {

View File

@ -8,6 +8,7 @@ import (
"github.com/hashicorp/terraform/internal/command/format"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
"github.com/hashicorp/terraform/internal/command/jsonformat/differ"
"github.com/hashicorp/terraform/internal/command/jsonformat/structured"
"github.com/hashicorp/terraform/internal/command/jsonplan"
"github.com/hashicorp/terraform/internal/command/jsonprovider"
"github.com/hashicorp/terraform/internal/command/jsonstate"
@ -112,7 +113,7 @@ func (r Renderer) RenderLog(log *JSONLog) error {
if len(log.Outputs) > 0 {
r.Streams.Println(r.Colorize.Color("[bold][green]Outputs:[reset]"))
for name, output := range log.Outputs {
change := differ.FromJsonViewsOutput(output)
change := structured.FromJsonViewsOutput(output)
ctype, err := ctyjson.UnmarshalType(output.Type)
if err != nil {
return err
@ -121,7 +122,7 @@ func (r Renderer) RenderLog(log *JSONLog) error {
opts := computed.NewRenderHumanOpts(r.Colorize)
opts.ShowUnchangedChildren = true
outputDiff := change.ComputeDiffForType(ctype)
outputDiff := differ.ComputeDiffForType(change, ctype)
outputStr := outputDiff.RenderHuman(0, opts)
msg := fmt.Sprintf("%s = %s", name, outputStr)

View File

@ -7,6 +7,7 @@ import (
"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
"github.com/hashicorp/terraform/internal/command/jsonformat/differ"
"github.com/hashicorp/terraform/internal/command/jsonformat/structured"
"github.com/hashicorp/terraform/internal/command/jsonprovider"
"github.com/hashicorp/terraform/internal/command/jsonstate"
)
@ -63,9 +64,11 @@ func (state State) renderHumanStateModule(renderer Renderer, module jsonstate.Mo
schema := state.GetSchema(resource)
switch resource.Mode {
case jsonstate.ManagedResourceMode:
renderer.Streams.Printf("resource %q %q %s", resource.Type, resource.Name, differ.FromJsonResource(resource).ComputeDiffForBlock(schema.Block).RenderHuman(0, opts))
change := structured.FromJsonResource(resource)
renderer.Streams.Printf("resource %q %q %s", resource.Type, resource.Name, differ.ComputeDiffForBlock(change, schema.Block).RenderHuman(0, opts))
case jsonstate.DataResourceMode:
renderer.Streams.Printf("data %q %q %s", resource.Type, resource.Name, differ.FromJsonResource(resource).ComputeDiffForBlock(schema.Block).RenderHuman(0, opts))
change := structured.FromJsonResource(resource)
renderer.Streams.Printf("data %q %q %s", resource.Type, resource.Name, differ.ComputeDiffForBlock(change, schema.Block).RenderHuman(0, opts))
default:
panic("found unrecognized resource mode: " + resource.Mode)
}
@ -91,13 +94,14 @@ func (state State) renderHumanStateOutputs(renderer Renderer, opts computed.Rend
for _, key := range keys {
output := state.RootModuleOutputs[key]
change := structured.FromJsonOutput(output)
ctype, err := ctyjson.UnmarshalType(output.Type)
if err != nil {
// We can actually do this without the type, so even if we fail
// to work out the type let's just render this anyway.
renderer.Streams.Printf("%s = %s\n", key, differ.FromJsonOutput(state.RootModuleOutputs[key]).ComputeDiffForOutput().RenderHuman(0, opts))
renderer.Streams.Printf("%s = %s\n", key, differ.ComputeDiffForOutput(change).RenderHuman(0, opts))
} else {
renderer.Streams.Printf("%s = %s\n", key, differ.FromJsonOutput(state.RootModuleOutputs[key]).ComputeDiffForType(ctype).RenderHuman(0, opts))
renderer.Streams.Printf("%s = %s\n", key, differ.ComputeDiffForType(change, ctype).RenderHuman(0, opts))
}
}
}

View File

@ -1,11 +1,10 @@
package differ
package structured
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/jsonformat/structured/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"
@ -163,11 +162,13 @@ func FromJsonViewsOutput(output viewsjson.Output) Change {
}
}
func (change Change) asDiff(renderer computed.DiffRenderer) computed.Diff {
return computed.NewDiff(renderer, change.calculateChange(), change.ReplacePaths.Matches())
}
// AsDiff
func (change Change) calculateChange() plans.Action {
// CalculateAction does a very simple analysis to make the best guess at the
// action this change describes. For complex types such as objects, maps, lists,
// or sets it is likely more efficient to work out the action directly instead
// of relying on this function.
func (change Change) CalculateAction() plans.Action {
if (change.Before == nil && !change.BeforeExplicit) && (change.After != nil || change.AfterExplicit) {
return plans.Create
}
@ -175,14 +176,14 @@ func (change Change) calculateChange() plans.Action {
return plans.Delete
}
if reflect.DeepEqual(change.Before, change.After) && change.AfterExplicit == change.BeforeExplicit && change.isAfterSensitive() == change.isBeforeSensitive() {
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
// 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
@ -192,7 +193,7 @@ func (change Change) calculateChange() plans.Action {
// 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 {
func (change Change) GetDefaultActionForIteration() plans.Action {
if change.Before == nil && change.After == nil {
return plans.NoOp
}

View File

@ -0,0 +1,6 @@
// Package structured contains the structured representation of the JSON changes
// returned by the jsonplan package.
//
// Placing these in a dedicated package allows for greater reuse across the
// various type of renderers.
package structured

View File

@ -1,7 +1,7 @@
package differ
package structured
import (
"github.com/hashicorp/terraform/internal/command/jsonformat/differ/attribute_path"
"github.com/hashicorp/terraform/internal/command/jsonformat/structured/attribute_path"
)
// ChangeMap is a Change that represents a Map or an Object type, and has
@ -32,7 +32,10 @@ type ChangeMap struct {
RelevantAttributes attribute_path.Matcher
}
func (change Change) asMap() ChangeMap {
// AsMap converts the Change into an object or map representation by converting
// the internal Before, After, Unknown, BeforeSensitive, and AfterSensitive
// data structures into generic maps.
func (change Change) AsMap() ChangeMap {
return ChangeMap{
Before: genericToMap(change.Before),
After: genericToMap(change.After),
@ -44,7 +47,9 @@ func (change Change) asMap() ChangeMap {
}
}
func (m ChangeMap) getChild(key string) Change {
// GetChild safely packages up a Change object for the given child, handling
// all the cases where the data might be null or a static boolean.
func (m ChangeMap) GetChild(key string) Change {
before, beforeExplicit := getFromGenericMap(m.Before, key)
after, afterExplicit := getFromGenericMap(m.After, key)
unknown, _ := getFromGenericMap(m.Unknown, key)
@ -64,6 +69,29 @@ func (m ChangeMap) getChild(key string) Change {
}
}
// Keys returns all the possible keys for this map. The keys for the map are
// potentially hidden and spread across multiple internal data structures and
// so this function conveniently packages them up.
func (m ChangeMap) Keys() []string {
var keys []string
for before := range m.Before {
keys = append(keys, before)
}
for after := range m.After {
keys = append(keys, after)
}
for unknown := range m.Unknown {
keys = append(keys, unknown)
}
for sensitive := range m.AfterSensitive {
keys = append(keys, sensitive)
}
for sensitive := range m.BeforeSensitive {
keys = append(keys, sensitive)
}
return keys
}
func getFromGenericMap(generic map[string]interface{}, key string) (interface{}, bool) {
if generic == nil {
return nil, false

View File

@ -0,0 +1,15 @@
package structured
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

@ -1,7 +1,7 @@
package differ
package structured
import (
"github.com/hashicorp/terraform/internal/command/jsonformat/differ/attribute_path"
"github.com/hashicorp/terraform/internal/command/jsonformat/structured/attribute_path"
)
// ChangeSlice is a Change that represents a Tuple, Set, or List type, and has
@ -31,7 +31,10 @@ type ChangeSlice struct {
RelevantAttributes attribute_path.Matcher
}
func (change Change) asSlice() ChangeSlice {
// AsSlice converts the Change into a slice representation by converting the
// internal Before, After, Unknown, BeforeSensitive, and AfterSensitive data
// structures into generic slices.
func (change Change) AsSlice() ChangeSlice {
return ChangeSlice{
Before: genericToSlice(change.Before),
After: genericToSlice(change.After),
@ -43,7 +46,9 @@ func (change Change) asSlice() ChangeSlice {
}
}
func (s ChangeSlice) getChild(beforeIx, afterIx int) Change {
// GetChild safely packages up a Change object for the given child, handling
// all the cases where the data might be null or a static boolean.
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)

View File

@ -0,0 +1,8 @@
package structured
func (change Change) IsUnknown() bool {
if unknown, ok := change.Unknown.(bool); ok {
return unknown
}
return false
}