mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 18:01:01 -06:00
8560f50cbc
This means it’s shown correctly in a plan and takes into account any actions that are dependant on the tainted resource and, vice verse, any actions that the tainted resource depends on. So this changes the behaviour from saying this resource is tainted so just forget about it and make sure it gets deleted in the background, to saying I want that resource to be recreated (taking into account the existing resource and it’s place in the graph).
419 lines
9.9 KiB
Go
419 lines
9.9 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/terraform/config"
|
|
"github.com/hashicorp/terraform/config/module"
|
|
"github.com/hashicorp/terraform/dag"
|
|
)
|
|
|
|
// GraphNodeStateRepresentative is an interface that can be implemented by
|
|
// a node to say that it is representing a resource in the state.
|
|
type GraphNodeStateRepresentative interface {
|
|
StateId() []string
|
|
}
|
|
|
|
// OrphanTransformer is a GraphTransformer that adds orphans to the
|
|
// graph. This transformer adds both resource and module orphans.
|
|
type OrphanTransformer struct {
|
|
// 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
|
|
|
|
// View, if non-nil will set a view on the module state.
|
|
View string
|
|
}
|
|
|
|
func (t *OrphanTransformer) Transform(g *Graph) error {
|
|
if t.State == nil {
|
|
// If the entire state is nil, there can't be any orphans
|
|
return nil
|
|
}
|
|
|
|
// Build up all our state representatives
|
|
resourceRep := make(map[string]struct{})
|
|
for _, v := range g.Vertices() {
|
|
if sr, ok := v.(GraphNodeStateRepresentative); ok {
|
|
for _, k := range sr.StateId() {
|
|
resourceRep[k] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
var config *config.Config
|
|
if t.Module != nil {
|
|
if module := t.Module.Child(g.Path[1:]); module != nil {
|
|
config = module.Config()
|
|
}
|
|
}
|
|
|
|
var resourceVertexes []dag.Vertex
|
|
if state := t.State.ModuleByPath(g.Path); state != nil {
|
|
// If we have state, then we can have orphan resources
|
|
|
|
// If we have a view, get the view
|
|
if t.View != "" {
|
|
state = state.View(t.View)
|
|
}
|
|
|
|
resourceOrphans := state.Orphans(config)
|
|
|
|
resourceVertexes = make([]dag.Vertex, len(resourceOrphans))
|
|
for i, k := range resourceOrphans {
|
|
// If this orphan is represented by some other node somehow,
|
|
// then ignore it.
|
|
if _, ok := resourceRep[k]; ok {
|
|
continue
|
|
}
|
|
|
|
rs := state.Resources[k]
|
|
|
|
rsk, err := ParseResourceStateKey(k)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resourceVertexes[i] = g.Add(&graphNodeOrphanResource{
|
|
Path: g.Path,
|
|
ResourceKey: rsk,
|
|
Provider: rs.Provider,
|
|
dependentOn: rs.Dependencies,
|
|
})
|
|
}
|
|
}
|
|
|
|
// Go over each module orphan and add it to the graph. We store the
|
|
// vertexes and states outside so that we can connect dependencies later.
|
|
moduleOrphans := t.State.ModuleOrphans(g.Path, config)
|
|
moduleVertexes := make([]dag.Vertex, len(moduleOrphans))
|
|
for i, path := range moduleOrphans {
|
|
var deps []string
|
|
if s := t.State.ModuleByPath(path); s != nil {
|
|
deps = s.Dependencies
|
|
}
|
|
|
|
moduleVertexes[i] = g.Add(&graphNodeOrphanModule{
|
|
Path: path,
|
|
dependentOn: deps,
|
|
})
|
|
}
|
|
|
|
// Now do the dependencies. We do this _after_ adding all the orphan
|
|
// nodes above because there are cases in which the orphans themselves
|
|
// depend on other orphans.
|
|
|
|
// Resource dependencies
|
|
for _, v := range resourceVertexes {
|
|
g.ConnectDependent(v)
|
|
}
|
|
|
|
// Module dependencies
|
|
for _, v := range moduleVertexes {
|
|
g.ConnectDependent(v)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// graphNodeOrphanModule is the graph vertex representing an orphan resource..
|
|
type graphNodeOrphanModule struct {
|
|
Path []string
|
|
|
|
dependentOn []string
|
|
}
|
|
|
|
func (n *graphNodeOrphanModule) DependableName() []string {
|
|
return []string{n.dependableName()}
|
|
}
|
|
|
|
func (n *graphNodeOrphanModule) DependentOn() []string {
|
|
return n.dependentOn
|
|
}
|
|
|
|
func (n *graphNodeOrphanModule) Name() string {
|
|
return fmt.Sprintf("%s (orphan)", n.dependableName())
|
|
}
|
|
|
|
func (n *graphNodeOrphanModule) dependableName() string {
|
|
return fmt.Sprintf("module.%s", n.Path[len(n.Path)-1])
|
|
}
|
|
|
|
// GraphNodeExpandable
|
|
func (n *graphNodeOrphanModule) Expand(b GraphBuilder) (GraphNodeSubgraph, error) {
|
|
g, err := b.Build(n.Path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &GraphNodeBasicSubgraph{
|
|
NameValue: n.Name(),
|
|
Graph: g,
|
|
}, nil
|
|
}
|
|
|
|
// graphNodeOrphanResource is the graph vertex representing an orphan resource..
|
|
type graphNodeOrphanResource struct {
|
|
Path []string
|
|
ResourceKey *ResourceStateKey
|
|
Provider string
|
|
|
|
dependentOn []string
|
|
}
|
|
|
|
func (n *graphNodeOrphanResource) ConfigType() GraphNodeConfigType {
|
|
return GraphNodeConfigTypeResource
|
|
}
|
|
|
|
func (n *graphNodeOrphanResource) ResourceAddress() *ResourceAddress {
|
|
return &ResourceAddress{
|
|
Index: n.ResourceKey.Index,
|
|
InstanceType: TypePrimary,
|
|
Name: n.ResourceKey.Name,
|
|
Path: n.Path[1:],
|
|
Type: n.ResourceKey.Type,
|
|
Mode: n.ResourceKey.Mode,
|
|
}
|
|
}
|
|
|
|
func (n *graphNodeOrphanResource) DependableName() []string {
|
|
return []string{n.dependableName()}
|
|
}
|
|
|
|
func (n *graphNodeOrphanResource) DependentOn() []string {
|
|
return n.dependentOn
|
|
}
|
|
|
|
func (n *graphNodeOrphanResource) Flatten(p []string) (dag.Vertex, error) {
|
|
return &graphNodeOrphanResourceFlat{
|
|
graphNodeOrphanResource: n,
|
|
PathValue: p,
|
|
}, nil
|
|
}
|
|
|
|
func (n *graphNodeOrphanResource) Name() string {
|
|
return fmt.Sprintf("%s (orphan)", n.ResourceKey)
|
|
}
|
|
|
|
func (n *graphNodeOrphanResource) ProvidedBy() []string {
|
|
return []string{resourceProvider(n.ResourceKey.Type, n.Provider)}
|
|
}
|
|
|
|
// GraphNodeEvalable impl.
|
|
func (n *graphNodeOrphanResource) EvalTree() EvalNode {
|
|
|
|
seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
|
|
|
|
// Build instance info
|
|
info := &InstanceInfo{Id: n.ResourceKey.String(), Type: n.ResourceKey.Type}
|
|
seq.Nodes = append(seq.Nodes, &EvalInstanceInfo{Info: info})
|
|
|
|
// Each resource mode has its own lifecycle
|
|
switch n.ResourceKey.Mode {
|
|
case config.ManagedResourceMode:
|
|
seq.Nodes = append(
|
|
seq.Nodes,
|
|
n.managedResourceEvalNodes(info)...,
|
|
)
|
|
case config.DataResourceMode:
|
|
seq.Nodes = append(
|
|
seq.Nodes,
|
|
n.dataResourceEvalNodes(info)...,
|
|
)
|
|
default:
|
|
panic(fmt.Errorf("unsupported resource mode %s", n.ResourceKey.Mode))
|
|
}
|
|
|
|
return seq
|
|
}
|
|
|
|
func (n *graphNodeOrphanResource) managedResourceEvalNodes(info *InstanceInfo) []EvalNode {
|
|
var provider ResourceProvider
|
|
var state *InstanceState
|
|
|
|
nodes := make([]EvalNode, 0, 3)
|
|
|
|
// Refresh the resource
|
|
nodes = append(nodes, &EvalOpFilter{
|
|
Ops: []walkOperation{walkRefresh},
|
|
Node: &EvalSequence{
|
|
Nodes: []EvalNode{
|
|
&EvalGetProvider{
|
|
Name: n.ProvidedBy()[0],
|
|
Output: &provider,
|
|
},
|
|
&EvalReadState{
|
|
Name: n.ResourceKey.String(),
|
|
Output: &state,
|
|
},
|
|
&EvalRefresh{
|
|
Info: info,
|
|
Provider: &provider,
|
|
State: &state,
|
|
Output: &state,
|
|
},
|
|
&EvalWriteState{
|
|
Name: n.ResourceKey.String(),
|
|
ResourceType: n.ResourceKey.Type,
|
|
Provider: n.Provider,
|
|
Dependencies: n.DependentOn(),
|
|
State: &state,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
// Diff the resource
|
|
var diff *InstanceDiff
|
|
nodes = append(nodes, &EvalOpFilter{
|
|
Ops: []walkOperation{walkPlan, walkPlanDestroy},
|
|
Node: &EvalSequence{
|
|
Nodes: []EvalNode{
|
|
&EvalReadState{
|
|
Name: n.ResourceKey.String(),
|
|
Output: &state,
|
|
},
|
|
&EvalDiffDestroy{
|
|
Info: info,
|
|
State: &state,
|
|
Output: &diff,
|
|
},
|
|
&EvalWriteDiff{
|
|
Name: n.ResourceKey.String(),
|
|
Diff: &diff,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
// Apply
|
|
var err error
|
|
nodes = append(nodes, &EvalOpFilter{
|
|
Ops: []walkOperation{walkApply, walkDestroy},
|
|
Node: &EvalSequence{
|
|
Nodes: []EvalNode{
|
|
&EvalReadDiff{
|
|
Name: n.ResourceKey.String(),
|
|
Diff: &diff,
|
|
},
|
|
&EvalGetProvider{
|
|
Name: n.ProvidedBy()[0],
|
|
Output: &provider,
|
|
},
|
|
&EvalReadState{
|
|
Name: n.ResourceKey.String(),
|
|
Output: &state,
|
|
},
|
|
&EvalApply{
|
|
Info: info,
|
|
State: &state,
|
|
Diff: &diff,
|
|
Provider: &provider,
|
|
Output: &state,
|
|
Error: &err,
|
|
},
|
|
&EvalWriteState{
|
|
Name: n.ResourceKey.String(),
|
|
ResourceType: n.ResourceKey.Type,
|
|
Provider: n.Provider,
|
|
Dependencies: n.DependentOn(),
|
|
State: &state,
|
|
},
|
|
&EvalApplyPost{
|
|
Info: info,
|
|
State: &state,
|
|
Error: &err,
|
|
},
|
|
&EvalUpdateStateHook{},
|
|
},
|
|
},
|
|
})
|
|
|
|
return nodes
|
|
}
|
|
|
|
func (n *graphNodeOrphanResource) dataResourceEvalNodes(info *InstanceInfo) []EvalNode {
|
|
nodes := make([]EvalNode, 0, 3)
|
|
|
|
// This will remain nil, since we don't retain states for orphaned
|
|
// data resources.
|
|
var state *InstanceState
|
|
|
|
// On both refresh and apply we just drop our state altogether,
|
|
// since the config resource validation pass will have proven that the
|
|
// resources remaining in the configuration don't need it.
|
|
nodes = append(nodes, &EvalOpFilter{
|
|
Ops: []walkOperation{walkRefresh, walkApply},
|
|
Node: &EvalSequence{
|
|
Nodes: []EvalNode{
|
|
&EvalWriteState{
|
|
Name: n.ResourceKey.String(),
|
|
ResourceType: n.ResourceKey.Type,
|
|
Provider: n.Provider,
|
|
Dependencies: n.DependentOn(),
|
|
State: &state, // state is nil
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
return nodes
|
|
}
|
|
|
|
func (n *graphNodeOrphanResource) dependableName() string {
|
|
return n.ResourceKey.String()
|
|
}
|
|
|
|
// GraphNodeDestroyable impl.
|
|
func (n *graphNodeOrphanResource) DestroyNode() GraphNodeDestroy {
|
|
return n
|
|
}
|
|
|
|
// GraphNodeDestroy impl.
|
|
func (n *graphNodeOrphanResource) CreateBeforeDestroy() bool {
|
|
return false
|
|
}
|
|
|
|
func (n *graphNodeOrphanResource) CreateNode() dag.Vertex {
|
|
return n
|
|
}
|
|
|
|
// Same as graphNodeOrphanResource, but for flattening
|
|
type graphNodeOrphanResourceFlat struct {
|
|
*graphNodeOrphanResource
|
|
|
|
PathValue []string
|
|
}
|
|
|
|
func (n *graphNodeOrphanResourceFlat) Name() string {
|
|
return fmt.Sprintf(
|
|
"%s.%s", modulePrefixStr(n.PathValue), n.graphNodeOrphanResource.Name())
|
|
}
|
|
|
|
func (n *graphNodeOrphanResourceFlat) Path() []string {
|
|
return n.PathValue
|
|
}
|
|
|
|
// GraphNodeDestroyable impl.
|
|
func (n *graphNodeOrphanResourceFlat) DestroyNode() GraphNodeDestroy {
|
|
return n
|
|
}
|
|
|
|
// GraphNodeDestroy impl.
|
|
func (n *graphNodeOrphanResourceFlat) CreateBeforeDestroy() bool {
|
|
return false
|
|
}
|
|
|
|
func (n *graphNodeOrphanResourceFlat) CreateNode() dag.Vertex {
|
|
return n
|
|
}
|
|
|
|
func (n *graphNodeOrphanResourceFlat) ProvidedBy() []string {
|
|
return modulePrefixList(
|
|
n.graphNodeOrphanResource.ProvidedBy(),
|
|
modulePrefixStr(n.PathValue))
|
|
}
|