opentofu/terraform/transform_orphan.go
2017-01-20 20:36:53 -08:00

438 lines
10 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 {
// Resource is resource configuration. This is only non-nil when
// expanding a resource that is in the configuration. It can't be
// dependend on.
Resource *config.Resource
// 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,
Resource: t.Resource,
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
Resource *config.Resource
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}
info.uniqueExtra = "destroy"
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,
},
&EvalCheckPreventDestroy{
Resource: n.Resource,
ResourceId: n.ResourceKey.String(),
Diff: &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,
},
&EvalApplyPre{
Info: info,
State: &state,
Diff: &diff,
},
&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))
}