mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-02-15 04:00:58 -05:00
* change -> diff, value -> change * also update readme# * structured plan renderer: refactor replace paths logic * goimports * goimports * address comments * fix compile error
115 lines
4.3 KiB
Go
115 lines
4.3 KiB
Go
package replace
|
|
|
|
import "encoding/json"
|
|
|
|
// ForcesReplacement encapsulates the ReplacePaths logic from the Terraform
|
|
// change object.
|
|
//
|
|
// It is possible for a change to a deeply nested attribute or block to result
|
|
// in an entire resource being replaced (deleted then recreated) instead of
|
|
// simply updated. In this case, we want to attach some additional context to
|
|
// say, this resource is being replaced because of these changes to its
|
|
// internal values.
|
|
//
|
|
// The ReplacePaths field is a slice of paths that point to the values causing
|
|
// the replace operation. It's a slice of paths because you can have multiple
|
|
// internal values causing a replacement.
|
|
//
|
|
// Each path is a slice of indices, where an index can be a string or an
|
|
// integer. We represent this a slice of generic interfaces: []interface{}. This
|
|
// is because we actually parse this field from JSON and have no way to easily
|
|
// represent a value that can be a string or an integer in Go. Luckily, this
|
|
// doesn't matter too much from an implementation point of view because we
|
|
// always know what type to expect as we know whether we are currently looking
|
|
// at a list type (which means an integer) or a map type (which means a string).
|
|
//
|
|
// The GetChildWithKey and GetChildWithIndex return additional but modified
|
|
// ForcesReplacement objects, where a path is simply dropped if the index
|
|
// doesn't match or included with the first entry removed if the index did
|
|
// match. These functions are called as the outside Change objects are being
|
|
// created for a complex change's children.
|
|
//
|
|
// The ForcesReplacement function actually tells you whether the current value
|
|
// is causing a replacement operation as one of the paths will be empty since
|
|
// we removed an entry every time the path matched, and the last entry will have
|
|
// been removed when the change was created.
|
|
type ForcesReplacement struct {
|
|
ReplacePaths [][]interface{}
|
|
}
|
|
|
|
// Parse accepts a json.RawMessage and outputs a formatted ForcesReplacement
|
|
// 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.
|
|
func Parse(message json.RawMessage) ForcesReplacement {
|
|
replace := ForcesReplacement{}
|
|
if message == nil {
|
|
return replace
|
|
}
|
|
|
|
if err := json.Unmarshal(message, &replace.ReplacePaths); err != nil {
|
|
panic("failed to unmarshal replace paths: " + err.Error())
|
|
}
|
|
|
|
return replace
|
|
}
|
|
|
|
// ForcesReplacement returns true if this ForcesReplacement object represents
|
|
// a change that is causing the entire resource to be replaced.
|
|
func (replace ForcesReplacement) ForcesReplacement() bool {
|
|
for _, path := range replace.ReplacePaths {
|
|
if len(path) == 0 {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GetChildWithKey steps through the paths in this ForcesReplacement and checks
|
|
// if any match the specified key.
|
|
//
|
|
// This function assumes the index will all be strings, so callers have to be
|
|
// sure they have navigated through previous paths accurately to this point or
|
|
// this function is liable to panic.
|
|
func (replace ForcesReplacement) GetChildWithKey(key string) ForcesReplacement {
|
|
child := ForcesReplacement{}
|
|
for _, path := range replace.ReplacePaths {
|
|
if len(path) == 0 {
|
|
// This means that the current value is causing a replacement but
|
|
// not its children, so we skip as we are returning the child's
|
|
// value.
|
|
continue
|
|
}
|
|
|
|
if path[0].(string) == key {
|
|
child.ReplacePaths = append(child.ReplacePaths, path[1:])
|
|
}
|
|
}
|
|
return child
|
|
}
|
|
|
|
// GetChildWithIndex steps through the paths in this ForcesReplacement and
|
|
// checks if any match the specified index.
|
|
//
|
|
// This function assumes the index will all be integers, so callers have to be
|
|
// sure they have navigated through previous paths accurately to this point or
|
|
// this function is liable to panic.
|
|
func (replace ForcesReplacement) GetChildWithIndex(index int) ForcesReplacement {
|
|
child := ForcesReplacement{}
|
|
for _, path := range replace.ReplacePaths {
|
|
if len(path) == 0 {
|
|
// This means that the current value is causing a replacement but
|
|
// not its children, so we skip as we are returning the child's
|
|
// value.
|
|
continue
|
|
}
|
|
|
|
if int(path[0].(float64)) == index {
|
|
child.ReplacePaths = append(child.ReplacePaths, path[1:])
|
|
}
|
|
}
|
|
return child
|
|
}
|