package terraform import ( "log" "github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/dag" "github.com/hashicorp/terraform/states" ) // OrphanResourceInstanceTransformer is a GraphTransformer that adds orphaned // resource instances to the graph. An "orphan" is an instance that is present // in the state but belongs to a resource that is no longer present in the // configuration. // // This is not the transformer that deals with "count orphans" (instances that // are no longer covered by a resource's "count" or "for_each" setting); that's // handled instead by OrphanResourceCountTransformer. type OrphanResourceInstanceTransformer struct { Concrete ConcreteResourceInstanceNodeFunc // State is the global state. We require the global state to // properly find module orphans at our path. State *states.State // Config is the root node in the configuration tree. We'll look up // the appropriate note in this tree using the path in each node. Config *configs.Config } func (t *OrphanResourceInstanceTransformer) Transform(g *Graph) error { if t.State == nil { // If the entire state is nil, there can't be any orphans return nil } if t.Config == nil { // Should never happen: we can't be doing any Terraform operations // without at least an empty configuration. panic("OrphanResourceInstanceTransformer used without setting Config") } // Go through the modules and for each module transform in order // to add the orphan. for _, ms := range t.State.Modules { if err := t.transform(g, ms); err != nil { return err } } return nil } func (t *OrphanResourceInstanceTransformer) transform(g *Graph, ms *states.Module) error { if ms == nil { return nil } moduleAddr := ms.Addr // Get the configuration for this module. The configuration might be // nil if the module was removed from the configuration. This is okay, // this just means that every resource is an orphan. var m *configs.Module if c := t.Config.DescendentForInstance(moduleAddr); c != nil { m = c.Module } // An "orphan" is a resource that is in the state but not the configuration, // so we'll walk the state resources and try to correlate each of them // with a configuration block. Each orphan gets a node in the graph whose // type is decided by t.Concrete. // // We don't handle orphans related to changes in the "count" and "for_each" // pseudo-arguments here. They are handled by OrphanResourceCountTransformer. for _, rs := range ms.Resources { if m != nil { if r := m.ResourceByAddr(rs.Addr); r != nil { continue } } for key := range rs.Instances { addr := rs.Addr.Instance(key).Absolute(moduleAddr) abstract := NewNodeAbstractResourceInstance(addr) var node dag.Vertex = abstract if f := t.Concrete; f != nil { node = f(abstract) } log.Printf("[TRACE] OrphanResourceInstanceTransformer: adding single-instance orphan node for %s", addr) g.Add(node) } } return nil } // OrphanResourceTransformer is a GraphTransformer that adds orphaned // resources to the graph. An "orphan" is a resource that is present in // the state but no longer present in the config. // // This is separate to OrphanResourceInstanceTransformer in that it deals with // whole resources, rather than individual instances of resources. Orphan // resource nodes are only used during apply to clean up leftover empty // resource state skeletons, after all of the instances inside have been // removed. // // This transformer will also create edges in the graph to any pre-existing // node that creates or destroys the entire orphaned resource or any of its // instances, to ensure that the "orphan-ness" of a resource is always dealt // with after all other aspects of it. type OrphanResourceTransformer struct { Concrete ConcreteResourceNodeFunc // State is the global state. State *states.State // Config is the root node in the configuration tree. Config *configs.Config } func (t *OrphanResourceTransformer) Transform(g *Graph) error { if t.State == nil { // If the entire state is nil, there can't be any orphans return nil } if t.Config == nil { // Should never happen: we can't be doing any Terraform operations // without at least an empty configuration. panic("OrphanResourceTransformer used without setting Config") } // We'll first collect up the existing nodes for each resource so we can // create dependency edges for any new nodes we create. deps := map[string][]dag.Vertex{} for _, v := range g.Vertices() { switch tv := v.(type) { case GraphNodeResourceInstance: k := tv.ResourceInstanceAddr().ContainingResource().String() deps[k] = append(deps[k], v) case GraphNodeResource: k := tv.ResourceAddr().String() deps[k] = append(deps[k], v) case GraphNodeDestroyer: k := tv.DestroyAddr().ContainingResource().String() deps[k] = append(deps[k], v) } } for _, ms := range t.State.Modules { moduleAddr := ms.Addr mc := t.Config.DescendentForInstance(moduleAddr) // might be nil if whole module has been removed for _, rs := range ms.Resources { if mc != nil { if r := mc.Module.ResourceByAddr(rs.Addr); r != nil { // It's in the config, so nothing to do for this one. continue } } addr := rs.Addr.Absolute(moduleAddr) abstract := NewNodeAbstractResource(addr) var node dag.Vertex = abstract if f := t.Concrete; f != nil { node = f(abstract) } log.Printf("[TRACE] OrphanResourceTransformer: adding whole-resource orphan node for %s", addr) g.Add(node) for _, dn := range deps[addr.String()] { log.Printf("[TRACE] OrphanResourceTransformer: node %q depends on %q", dag.VertexName(node), dag.VertexName(dn)) g.Connect(dag.BasicEdge(node, dn)) } } } return nil }