terraform/terraform/transform_orphan.go

267 lines
6.3 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)
}
// Go over each resource orphan and add it to the graph.
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]
resourceVertexes[i] = g.Add(&graphNodeOrphanResource{
ResourceName: k,
ResourceType: rs.Type,
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 {
moduleVertexes[i] = g.Add(&graphNodeOrphanModule{
Path: path,
dependentOn: t.State.ModuleByPath(path).Dependencies,
})
}
// 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 {
ResourceName string
ResourceType string
dependentOn []string
}
func (n *graphNodeOrphanResource) DependableName() []string {
return []string{n.dependableName()}
}
func (n *graphNodeOrphanResource) DependentOn() []string {
return n.dependentOn
}
func (n *graphNodeOrphanResource) Name() string {
return fmt.Sprintf("%s (orphan)", n.ResourceName)
}
func (n *graphNodeOrphanResource) ProvidedBy() []string {
return []string{resourceProvider(n.ResourceName)}
}
// GraphNodeEvalable impl.
func (n *graphNodeOrphanResource) EvalTree() EvalNode {
var state *InstanceState
seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
// Build instance info
info := &InstanceInfo{Id: n.ResourceName, Type: n.ResourceType}
seq.Nodes = append(seq.Nodes, &EvalInstanceInfo{Info: info})
// Refresh the resource
seq.Nodes = append(seq.Nodes, &EvalOpFilter{
Ops: []walkOperation{walkRefresh},
Node: &EvalSequence{
Nodes: []EvalNode{
&EvalReadState{
Name: n.ResourceName,
Output: &state,
},
&EvalRefresh{
Info: info,
Provider: &EvalGetProvider{Name: n.ProvidedBy()[0]},
State: &state,
Output: &state,
},
&EvalWriteState{
Name: n.ResourceName,
ResourceType: n.ResourceType,
Dependencies: n.DependentOn(),
State: &state,
},
},
},
})
// Diff the resource
var diff *InstanceDiff
seq.Nodes = append(seq.Nodes, &EvalOpFilter{
Ops: []walkOperation{walkPlan, walkPlanDestroy},
Node: &EvalSequence{
Nodes: []EvalNode{
&EvalDiffDestroy{
Info: info,
State: &EvalReadState{Name: n.ResourceName},
Output: &diff,
},
&EvalWriteDiff{
Name: n.ResourceName,
Diff: &diff,
},
},
},
})
// Apply
var provider ResourceProvider
seq.Nodes = append(seq.Nodes, &EvalOpFilter{
Ops: []walkOperation{walkApply},
Node: &EvalSequence{
Nodes: []EvalNode{
&EvalReadDiff{
Name: n.ResourceName,
Diff: &diff,
},
&EvalGetProvider{
Name: n.ProvidedBy()[0],
Output: &provider,
},
&EvalReadState{
Name: n.ResourceName,
Output: &state,
},
&EvalApply{
Info: info,
State: &state,
Diff: &diff,
Provider: &provider,
Output: &state,
},
&EvalWriteState{
Name: n.ResourceName,
ResourceType: n.ResourceType,
Dependencies: n.DependentOn(),
State: &state,
},
},
},
})
return seq
}
func (n *graphNodeOrphanResource) dependableName() string {
return n.ResourceName
}