]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
107c1cdb ND |
4 | "log" |
5 | ||
6 | "github.com/hashicorp/terraform/configs" | |
bae9f6d2 | 7 | "github.com/hashicorp/terraform/dag" |
107c1cdb | 8 | "github.com/hashicorp/terraform/states" |
bae9f6d2 JC |
9 | ) |
10 | ||
107c1cdb ND |
11 | // OrphanResourceInstanceTransformer is a GraphTransformer that adds orphaned |
12 | // resource instances to the graph. An "orphan" is an instance that is present | |
13 | // in the state but belongs to a resource that is no longer present in the | |
bae9f6d2 | 14 | // configuration. |
107c1cdb ND |
15 | // |
16 | // This is not the transformer that deals with "count orphans" (instances that | |
17 | // are no longer covered by a resource's "count" or "for_each" setting); that's | |
18 | // handled instead by OrphanResourceCountTransformer. | |
19 | type OrphanResourceInstanceTransformer struct { | |
20 | Concrete ConcreteResourceInstanceNodeFunc | |
bae9f6d2 JC |
21 | |
22 | // State is the global state. We require the global state to | |
23 | // properly find module orphans at our path. | |
107c1cdb | 24 | State *states.State |
bae9f6d2 | 25 | |
107c1cdb ND |
26 | // Config is the root node in the configuration tree. We'll look up |
27 | // the appropriate note in this tree using the path in each node. | |
28 | Config *configs.Config | |
bae9f6d2 JC |
29 | } |
30 | ||
107c1cdb | 31 | func (t *OrphanResourceInstanceTransformer) Transform(g *Graph) error { |
bae9f6d2 JC |
32 | if t.State == nil { |
33 | // If the entire state is nil, there can't be any orphans | |
34 | return nil | |
35 | } | |
107c1cdb ND |
36 | if t.Config == nil { |
37 | // Should never happen: we can't be doing any Terraform operations | |
38 | // without at least an empty configuration. | |
39 | panic("OrphanResourceInstanceTransformer used without setting Config") | |
40 | } | |
bae9f6d2 JC |
41 | |
42 | // Go through the modules and for each module transform in order | |
43 | // to add the orphan. | |
44 | for _, ms := range t.State.Modules { | |
45 | if err := t.transform(g, ms); err != nil { | |
46 | return err | |
47 | } | |
48 | } | |
49 | ||
50 | return nil | |
51 | } | |
52 | ||
107c1cdb | 53 | func (t *OrphanResourceInstanceTransformer) transform(g *Graph, ms *states.Module) error { |
bae9f6d2 JC |
54 | if ms == nil { |
55 | return nil | |
56 | } | |
57 | ||
107c1cdb ND |
58 | moduleAddr := ms.Addr |
59 | ||
60 | // Get the configuration for this module. The configuration might be | |
bae9f6d2 JC |
61 | // nil if the module was removed from the configuration. This is okay, |
62 | // this just means that every resource is an orphan. | |
107c1cdb ND |
63 | var m *configs.Module |
64 | if c := t.Config.DescendentForInstance(moduleAddr); c != nil { | |
65 | m = c.Module | |
bae9f6d2 JC |
66 | } |
67 | ||
107c1cdb ND |
68 | // An "orphan" is a resource that is in the state but not the configuration, |
69 | // so we'll walk the state resources and try to correlate each of them | |
70 | // with a configuration block. Each orphan gets a node in the graph whose | |
71 | // type is decided by t.Concrete. | |
72 | // | |
73 | // We don't handle orphans related to changes in the "count" and "for_each" | |
74 | // pseudo-arguments here. They are handled by OrphanResourceCountTransformer. | |
75 | for _, rs := range ms.Resources { | |
76 | if m != nil { | |
77 | if r := m.ResourceByAddr(rs.Addr); r != nil { | |
78 | continue | |
79 | } | |
bae9f6d2 | 80 | } |
bae9f6d2 | 81 | |
107c1cdb ND |
82 | for key := range rs.Instances { |
83 | addr := rs.Addr.Instance(key).Absolute(moduleAddr) | |
84 | abstract := NewNodeAbstractResourceInstance(addr) | |
85 | var node dag.Vertex = abstract | |
86 | if f := t.Concrete; f != nil { | |
87 | node = f(abstract) | |
88 | } | |
89 | log.Printf("[TRACE] OrphanResourceInstanceTransformer: adding single-instance orphan node for %s", addr) | |
90 | g.Add(node) | |
bae9f6d2 | 91 | } |
107c1cdb ND |
92 | } |
93 | ||
94 | return nil | |
95 | } | |
96 | ||
97 | // OrphanResourceTransformer is a GraphTransformer that adds orphaned | |
98 | // resources to the graph. An "orphan" is a resource that is present in | |
99 | // the state but no longer present in the config. | |
100 | // | |
101 | // This is separate to OrphanResourceInstanceTransformer in that it deals with | |
102 | // whole resources, rather than individual instances of resources. Orphan | |
103 | // resource nodes are only used during apply to clean up leftover empty | |
104 | // resource state skeletons, after all of the instances inside have been | |
105 | // removed. | |
106 | // | |
107 | // This transformer will also create edges in the graph to any pre-existing | |
108 | // node that creates or destroys the entire orphaned resource or any of its | |
109 | // instances, to ensure that the "orphan-ness" of a resource is always dealt | |
110 | // with after all other aspects of it. | |
111 | type OrphanResourceTransformer struct { | |
112 | Concrete ConcreteResourceNodeFunc | |
113 | ||
114 | // State is the global state. | |
115 | State *states.State | |
bae9f6d2 | 116 | |
107c1cdb ND |
117 | // Config is the root node in the configuration tree. |
118 | Config *configs.Config | |
119 | } | |
120 | ||
121 | func (t *OrphanResourceTransformer) Transform(g *Graph) error { | |
122 | if t.State == nil { | |
123 | // If the entire state is nil, there can't be any orphans | |
124 | return nil | |
125 | } | |
126 | if t.Config == nil { | |
127 | // Should never happen: we can't be doing any Terraform operations | |
128 | // without at least an empty configuration. | |
129 | panic("OrphanResourceTransformer used without setting Config") | |
130 | } | |
131 | ||
132 | // We'll first collect up the existing nodes for each resource so we can | |
133 | // create dependency edges for any new nodes we create. | |
134 | deps := map[string][]dag.Vertex{} | |
135 | for _, v := range g.Vertices() { | |
136 | switch tv := v.(type) { | |
137 | case GraphNodeResourceInstance: | |
138 | k := tv.ResourceInstanceAddr().ContainingResource().String() | |
139 | deps[k] = append(deps[k], v) | |
140 | case GraphNodeResource: | |
141 | k := tv.ResourceAddr().String() | |
142 | deps[k] = append(deps[k], v) | |
143 | case GraphNodeDestroyer: | |
144 | k := tv.DestroyAddr().ContainingResource().String() | |
145 | deps[k] = append(deps[k], v) | |
146 | } | |
147 | } | |
148 | ||
149 | for _, ms := range t.State.Modules { | |
150 | moduleAddr := ms.Addr | |
151 | ||
152 | mc := t.Config.DescendentForInstance(moduleAddr) // might be nil if whole module has been removed | |
153 | ||
154 | for _, rs := range ms.Resources { | |
155 | if mc != nil { | |
156 | if r := mc.Module.ResourceByAddr(rs.Addr); r != nil { | |
157 | // It's in the config, so nothing to do for this one. | |
158 | continue | |
159 | } | |
160 | } | |
161 | ||
162 | addr := rs.Addr.Absolute(moduleAddr) | |
163 | abstract := NewNodeAbstractResource(addr) | |
164 | var node dag.Vertex = abstract | |
165 | if f := t.Concrete; f != nil { | |
166 | node = f(abstract) | |
167 | } | |
168 | log.Printf("[TRACE] OrphanResourceTransformer: adding whole-resource orphan node for %s", addr) | |
169 | g.Add(node) | |
170 | for _, dn := range deps[addr.String()] { | |
171 | log.Printf("[TRACE] OrphanResourceTransformer: node %q depends on %q", dag.VertexName(node), dag.VertexName(dn)) | |
172 | g.Connect(dag.BasicEdge(node, dn)) | |
173 | } | |
174 | } | |
bae9f6d2 JC |
175 | } |
176 | ||
177 | return nil | |
107c1cdb | 178 | |
bae9f6d2 | 179 | } |