421 lines
9.9 KiB
Go
421 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}
|
|
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,
|
|
},
|
|
&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))
|
|
}
|