mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-03 20:50:59 -05:00
Changes between empty strings and `null` were hidden in the CLI output, because the SDK could not reliably detect the difference and may return either value depending on the situation. This legacy behavior can be confusing for authors of new provider which can correctly handle `null`, and it would be preferable to be able to render those changes in the CLI. While we don't have enough information to detect when the legacy behavior is required, we can detect a number of cases where it's certain that we are not dealing with a legacy schema and should output the full diff.
169 lines
4.9 KiB
Go
169 lines
4.9 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package structured
|
|
|
|
import (
|
|
"github.com/hashicorp/terraform/internal/command/jsonformat/structured/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
|
|
|
|
// this reflects the parent NonLegacyValue, so that any behavior is
|
|
// automatically inherited into child changes.
|
|
nonLegacySchema bool
|
|
}
|
|
|
|
// 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),
|
|
Unknown: genericToMap(change.Unknown),
|
|
BeforeSensitive: genericToMap(change.BeforeSensitive),
|
|
AfterSensitive: genericToMap(change.AfterSensitive),
|
|
ReplacePaths: change.ReplacePaths,
|
|
RelevantAttributes: change.RelevantAttributes,
|
|
nonLegacySchema: change.NonLegacySchema,
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
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),
|
|
NonLegacySchema: m.nonLegacySchema,
|
|
}
|
|
}
|
|
|
|
// ExplicitKeys returns the keys in the Before and After, as opposed to AllKeys
|
|
// which also includes keys from the additional meta structures (like the
|
|
// sensitive and unknown values).
|
|
//
|
|
// This function is useful for processing nested attributes and repeated blocks
|
|
// where the unknown and sensitive structs contain information about the actual
|
|
// attributes, while the before and after structs hold the actual nested values.
|
|
func (m ChangeMap) ExplicitKeys() []string {
|
|
keys := make(map[string]bool)
|
|
for before := range m.Before {
|
|
if _, ok := keys[before]; ok {
|
|
continue
|
|
}
|
|
keys[before] = true
|
|
}
|
|
for after := range m.After {
|
|
if _, ok := keys[after]; ok {
|
|
continue
|
|
}
|
|
keys[after] = true
|
|
}
|
|
|
|
var dedupedKeys []string
|
|
for key := range keys {
|
|
dedupedKeys = append(dedupedKeys, key)
|
|
}
|
|
return dedupedKeys
|
|
}
|
|
|
|
// AllKeys 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) AllKeys() []string {
|
|
keys := make(map[string]bool)
|
|
for before := range m.Before {
|
|
if _, ok := keys[before]; ok {
|
|
continue
|
|
}
|
|
keys[before] = true
|
|
}
|
|
for after := range m.After {
|
|
if _, ok := keys[after]; ok {
|
|
continue
|
|
}
|
|
keys[after] = true
|
|
}
|
|
for unknown := range m.Unknown {
|
|
if _, ok := keys[unknown]; ok {
|
|
continue
|
|
}
|
|
keys[unknown] = true
|
|
}
|
|
for sensitive := range m.AfterSensitive {
|
|
if _, ok := keys[sensitive]; ok {
|
|
continue
|
|
}
|
|
keys[sensitive] = true
|
|
}
|
|
for sensitive := range m.BeforeSensitive {
|
|
if _, ok := keys[sensitive]; ok {
|
|
continue
|
|
}
|
|
keys[sensitive] = true
|
|
}
|
|
|
|
var dedupedKeys []string
|
|
for key := range keys {
|
|
dedupedKeys = append(dedupedKeys, key)
|
|
}
|
|
return dedupedKeys
|
|
}
|
|
|
|
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
|
|
}
|