diff --git a/terraform/graph_builder_apply.go b/terraform/graph_builder_apply.go index f5e7e7b85..fe415c49f 100644 --- a/terraform/graph_builder_apply.go +++ b/terraform/graph_builder_apply.go @@ -54,6 +54,9 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer { State: b.State, }, + // Attach the state + &AttachStateTransformer{State: b.State}, + // Create all the providers &MissingProviderTransformer{Providers: b.Providers, Factory: providerFactory}, &ProviderTransformer{}, diff --git a/terraform/node_resource_apply.go b/terraform/node_resource_apply.go index 0503f0a7b..c9c987474 100644 --- a/terraform/node_resource_apply.go +++ b/terraform/node_resource_apply.go @@ -86,6 +86,16 @@ func (n *NodeApplyableResource) ProvisionedBy() []string { return result } +// GraphNodeAttachResourceState +func (n *NodeApplyableResource) ResourceAddr() *ResourceAddress { + return n.Addr +} + +// GraphNodeAttachResourceState +func (n *NodeApplyableResource) AttachResourceState(s *ResourceState) { + n.ResourceState = s +} + // GraphNodeEvalable func (n *NodeApplyableResource) EvalTree() EvalNode { // stateId is the ID to put into the state diff --git a/terraform/node_resource_destroy.go b/terraform/node_resource_destroy.go index 5eaa80c27..9955a7c4b 100644 --- a/terraform/node_resource_destroy.go +++ b/terraform/node_resource_destroy.go @@ -2,42 +2,27 @@ package terraform import ( "fmt" - - "github.com/hashicorp/terraform/config" ) // NodeDestroyResource represents a resource that is to be destroyed. -type NodeApplyableResource struct { - Addr *ResourceAddress // Addr is the address for this resource +type NodeDestroyResource struct { + Addr *ResourceAddress // Addr is the address for this resource + ResourceState *ResourceState // State is the resource state for this resource } -func (n *NodeApplyableResource) Name() string { +func (n *NodeDestroyResource) Name() string { return n.Addr.String() } // GraphNodeSubPath -func (n *NodeApplyableResource) Path() []string { +func (n *NodeDestroyResource) Path() []string { return n.Addr.Path } -// GraphNodeReferenceable -func (n *NodeApplyableResource) ReferenceableName() []string { - if n.Config == nil { - return nil - } - - return []string{n.Config.Id()} -} - // GraphNodeProviderConsumer -func (n *NodeApplyableResource) ProvidedBy() []string { - // If we have a config we prefer that above all else - if n.Config != nil { - return []string{resourceProvider(n.Config.Type, n.Config.Provider)} - } - +func (n *NodeDestroyResource) ProvidedBy() []string { // If we have state, then we will use the provider from there - if n.ResourceState != nil { + if n.ResourceState != nil && n.ResourceState.Provider != "" { return []string{n.ResourceState.Provider} } @@ -45,7 +30,124 @@ func (n *NodeApplyableResource) ProvidedBy() []string { return []string{resourceProvider(n.Addr.Type, "")} } -// GraphNodeEvalable -func (n *NodeApplyableResource) EvalTree() EvalNode { - return nil +// GraphNodeAttachResourceState +func (n *NodeDestroyResource) ResourceAddr() *ResourceAddress { + return n.Addr +} + +// GraphNodeAttachResourceState +func (n *NodeDestroyResource) AttachResourceState(s *ResourceState) { + n.ResourceState = s +} + +// GraphNodeEvalable +func (n *NodeDestroyResource) EvalTree() EvalNode { + // stateId is the ID to put into the state + stateId := n.Addr.stateId() + if n.Addr.Index > -1 { + stateId = fmt.Sprintf("%s.%d", stateId, n.Addr.Index) + } + + // Build the instance info. More of this will be populated during eval + info := &InstanceInfo{ + Id: stateId, + Type: n.Addr.Type, + } + + // Get our state + rs := n.ResourceState + if rs == nil { + rs = &ResourceState{} + } + rs.Provider = n.ProvidedBy()[0] + + var diffApply *InstanceDiff + var provider ResourceProvider + var state *InstanceState + var err error + return &EvalOpFilter{ + Ops: []walkOperation{walkApply, walkDestroy}, + Node: &EvalSequence{ + Nodes: []EvalNode{ + // Get the saved diff for apply + &EvalReadDiff{ + Name: stateId, + Diff: &diffApply, + }, + + // Filter the diff so we only get the destroy + &EvalFilterDiff{ + Diff: &diffApply, + Output: &diffApply, + Destroy: true, + }, + + // If we're not destroying, then compare diffs + &EvalIf{ + If: func(ctx EvalContext) (bool, error) { + if diffApply != nil && diffApply.GetDestroy() { + return true, nil + } + + return true, EvalEarlyExitError{} + }, + Then: EvalNoop{}, + }, + + // Load the instance info so we have the module path set + &EvalInstanceInfo{Info: info}, + + &EvalGetProvider{ + Name: n.ProvidedBy()[0], + Output: &provider, + }, + &EvalReadState{ + Name: stateId, + Output: &state, + }, + &EvalRequireState{ + State: &state, + }, + // Make sure we handle data sources properly. + &EvalIf{ + If: func(ctx EvalContext) (bool, error) { + /* TODO: data source + if n.Resource.Mode == config.DataResourceMode { + return true, nil + } + */ + + return false, nil + }, + + Then: &EvalReadDataApply{ + Info: info, + Diff: &diffApply, + Provider: &provider, + Output: &state, + }, + Else: &EvalApply{ + Info: info, + State: &state, + Diff: &diffApply, + Provider: &provider, + Output: &state, + Error: &err, + }, + }, + &EvalWriteState{ + Name: stateId, + ResourceType: n.Addr.Type, + Provider: rs.Provider, + Dependencies: rs.Dependencies, + State: &state, + }, + &EvalApplyPost{ + Info: info, + State: &state, + Error: &err, + }, + }, + }, + } } diff --git a/terraform/transform_attach_state.go b/terraform/transform_attach_state.go new file mode 100644 index 000000000..68a8f3b07 --- /dev/null +++ b/terraform/transform_attach_state.go @@ -0,0 +1,68 @@ +package terraform + +import ( + "log" + + "github.com/hashicorp/terraform/dag" +) + +// GraphNodeAttachResourceState is an interface that can be implemented +// to request that a ResourceState is attached to the node. +type GraphNodeAttachResourceState interface { + // The address to the resource for the state + ResourceAddr() *ResourceAddress + + // Sets the state + AttachResourceState(*ResourceState) +} + +// AttachStateTransformer goes through the graph and attaches +// state to nodes that implement the interfaces above. +type AttachStateTransformer struct { + State *State // State is the root state +} + +func (t *AttachStateTransformer) Transform(g *Graph) error { + // If no state, then nothing to do + if t.State == nil { + log.Printf("[DEBUG] Not attaching any state: state is nil") + return nil + } + + filter := &StateFilter{State: t.State} + for _, v := range g.Vertices() { + // Only care about nodes requesting we're adding state + an, ok := v.(GraphNodeAttachResourceState) + if !ok { + continue + } + addr := an.ResourceAddr() + + // Get the module state + results, err := filter.Filter(addr.String()) + if err != nil { + return err + } + + // Attach the first resource state we get + found := false + for _, result := range results { + if rs, ok := result.Value.(*ResourceState); ok { + log.Printf( + "[DEBUG] Attaching resource state to %q: %s", + dag.VertexName(v), rs) + an.AttachResourceState(rs) + found = true + break + } + } + + if !found { + log.Printf( + "[DEBUG] Resource state not foudn for %q: %s", + dag.VertexName(v), addr) + } + } + + return nil +} diff --git a/terraform/transform_diff.go b/terraform/transform_diff.go index cae5d7528..21fd0bbc6 100644 --- a/terraform/transform_diff.go +++ b/terraform/transform_diff.go @@ -41,16 +41,6 @@ func (t *DiffTransformer) Transform(g *Graph) error { for name, inst := range m.Resources { log.Printf("[TRACE] DiffTransformer: Resource %q: %#v", name, inst) - // TODO: destroy - if inst.Destroy { - } - - // If this diff has no attribute changes, then we have - // nothing to do and therefore won't add it to the graph. - if len(inst.Attributes) == 0 { - continue - } - // We have changes! This is a create or update operation. // First grab the address so we have a unique way to // reference this resource. @@ -64,10 +54,18 @@ func (t *DiffTransformer) Transform(g *Graph) error { // the address. Remove "root" from it. addr.Path = m.Path[1:] - // Add the resource to the graph - nodes = append(nodes, &NodeApplyableResource{ - Addr: addr, - }) + // If we're destroying, add the destroy node + if inst.Destroy { + g.Add(&NodeDestroyResource{Addr: addr}) + } + + // If we have changes, then add the applyable version + if len(inst.Attributes) > 0 { + // Add the resource to the graph + nodes = append(nodes, &NodeApplyableResource{ + Addr: addr, + }) + } } } @@ -97,28 +95,6 @@ func (t *DiffTransformer) Transform(g *Graph) error { break } } - - // Grab the state at this path - if ms := t.State.ModuleByPath(normalizeModulePath(n.Addr.Path)); ms != nil { - for name, rs := range ms.Resources { - // Parse the name for comparison - addr, err := parseResourceAddressInternal(name) - if err != nil { - panic(fmt.Sprintf( - "Error parsing internal name, this is a bug: %q", name)) - } - addr.Path = n.Addr.Path - - // If this is not the same resource, then continue - if !addr.Equals(n.Addr) { - continue - } - - // Same resource! - n.ResourceState = rs - break - } - } } // Add all the nodes to the graph