mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-30 10:47:14 -06:00
cfb440ea60
Init should only _add_ values, not remove them. During graph execution, there are steps that expect that a state isn't being actively pruned out from under it. Namely: writing deposed states. Writing deposed states has no way to handle if a state changes underneath it because the only way to uniquely identify a deposed state is its index in the deposed array. When destroying deposed resources, we set the value to `<nil>`. If the array is pruned before the next deposed destroy, then the indexes have changed, and this can cause a crash. This PR does the following (with more details below): * `init()` no longer prunes. * `ReadState()` always prunes before returning. I can't think of a scenario where this is unsafe since generally we can always START from a pruned state, its just causing problems to prune mid-execution. * Exported State APIs updated to be robust against nil ModuleStates. Instead, I think we should adopt the following semantics for init/prune in our structures that support it (Diff, for example). By having consistent semantics around these functions, we can avoid this in the future and have set expectations working with them. * `init()` (in anything) will only ever be additive, and won't change ordering or existing values. It won't remove values. * `prune()` is destructive, expectedly. * Functions on a structure must not assume a pruned structure 100% of the time. They must be robust to handle nils. This is especially important because in many cases values such as `Modules` in state are exported so end users can simply modify them outside of the exported APIs. This PR may expose us to unknown crashes but I've tried to cover our cases in exposed APIs by checking for nil.
79 lines
2.0 KiB
Go
79 lines
2.0 KiB
Go
package terraform
|
|
|
|
import (
|
|
"github.com/hashicorp/terraform/config"
|
|
"github.com/hashicorp/terraform/config/module"
|
|
"github.com/hashicorp/terraform/dag"
|
|
)
|
|
|
|
// OrphanResourceTransformer is a GraphTransformer that adds resource
|
|
// orphans to the graph. A resource orphan is a resource that is
|
|
// represented in the state but not in the configuration.
|
|
//
|
|
// This only adds orphans that have no representation at all in the
|
|
// configuration.
|
|
type OrphanResourceTransformer struct {
|
|
Concrete ConcreteResourceNodeFunc
|
|
|
|
// State is the global state. We require the global state to
|
|
// properly find module orphans at our path.
|
|
State *State
|
|
|
|
// Module is the root module. We'll look up the proper configuration
|
|
// using the graph path.
|
|
Module *module.Tree
|
|
}
|
|
|
|
func (t *OrphanResourceTransformer) Transform(g *Graph) error {
|
|
if t.State == nil {
|
|
// If the entire state is nil, there can't be any orphans
|
|
return nil
|
|
}
|
|
|
|
// Go through the modules and for each module transform in order
|
|
// to add the orphan.
|
|
for _, ms := range t.State.Modules {
|
|
if err := t.transform(g, ms); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (t *OrphanResourceTransformer) transform(g *Graph, ms *ModuleState) error {
|
|
if ms == nil {
|
|
return nil
|
|
}
|
|
|
|
// Get the configuration for this path. The configuration might be
|
|
// nil if the module was removed from the configuration. This is okay,
|
|
// this just means that every resource is an orphan.
|
|
var c *config.Config
|
|
if m := t.Module.Child(ms.Path[1:]); m != nil {
|
|
c = m.Config()
|
|
}
|
|
|
|
// Go through the orphans and add them all to the state
|
|
for _, key := range ms.Orphans(c) {
|
|
// Build the abstract resource
|
|
addr, err := parseResourceAddressInternal(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
addr.Path = ms.Path[1:]
|
|
|
|
// Build the abstract node and the concrete one
|
|
abstract := &NodeAbstractResource{Addr: addr}
|
|
var node dag.Vertex = abstract
|
|
if f := t.Concrete; f != nil {
|
|
node = f(abstract)
|
|
}
|
|
|
|
// Add it to the graph
|
|
g.Add(node)
|
|
}
|
|
|
|
return nil
|
|
}
|