diff --git a/terraform/context.go b/terraform/context.go index cd4b74fcd..813a4c5ca 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -126,15 +126,14 @@ func (c *Context) Apply() (*State, error) { } c.state.init() + // Initialize the state with all the resources + graphInitState(c.state, g) + // Walk log.Printf("[INFO] Apply walk starting") err = g.Walk(c.applyWalkFn()) log.Printf("[INFO] Apply walk complete") - // Encode the dependencies, this pushes the logical dependencies - // into the state so that we can recover it later. - EncodeDependencies(g) - // Prune the state so that we have as clean a state as possible c.state.prune() @@ -209,6 +208,9 @@ func (c *Context) Plan(opts *PlanOpts) (*Plan, error) { c.state = old }() + // Initialize the state with all the resources + graphInitState(c.state, g) + walkFn = c.planWalkFn(p) } @@ -244,6 +246,12 @@ func (c *Context) Refresh() (*State, error) { return c.state, err } + if c.state != nil { + // Initialize the state with all the resources + graphInitState(c.state, g) + } + + // Walk the graph err = g.Walk(c.refreshWalkFn()) // Prune the state @@ -529,8 +537,11 @@ func (c *Context) applyWalkFn() depgraph.WalkFunc { return nil } - // Ensure the state is initialized - r.State.init() + is := r.State + if is == nil { + is = new(InstanceState) + } + is.init() if !diff.Destroy { // Since we need the configuration, interpolate the variables @@ -538,7 +549,7 @@ func (c *Context) applyWalkFn() depgraph.WalkFunc { return err } - diff, err = r.Provider.Diff(r.Info, r.State.Primary, r.Config) + diff, err = r.Provider.Diff(r.Info, is, r.Config) if err != nil { return err } @@ -578,12 +589,12 @@ func (c *Context) applyWalkFn() depgraph.WalkFunc { } for _, h := range c.hooks { - handleHook(h.PreApply(r.Id, r.State.Primary, diff)) + handleHook(h.PreApply(r.Id, is, diff)) } // With the completed diff, apply! log.Printf("[DEBUG] %s: Executing Apply", r.Id) - is, applyerr := r.Provider.Apply(r.Info, r.State.Primary, diff) + is, applyerr := r.Provider.Apply(r.Info, is, diff) var errs []error if applyerr != nil { @@ -611,25 +622,9 @@ func (c *Context) applyWalkFn() depgraph.WalkFunc { } } - if r.Flags&FlagTainted != 0 { - // Update the tainted resource. - r.State.Tainted[r.TaintedIndex] = is - } else { - // Update the primary resource - r.State.Primary = is - } - - // Update the resulting diff - c.sl.Lock() - - // TODO: Get other modules - mod := c.state.RootModule() - if is.ID == "" && len(r.State.Tainted) == 0 { - delete(mod.Resources, r.Id) - } else { - mod.Resources[r.Id] = r.State - } - c.sl.Unlock() + // Set the result state + r.State = is + c.persistState(r) // Invoke any provisioners we have defined. This is only done // if the resource was created, as updates or deletes do not @@ -638,9 +633,9 @@ func (c *Context) applyWalkFn() depgraph.WalkFunc { // Additionally, we need to be careful to not run this if there // was an error during the provider apply. tainted := false - if applyerr == nil && r.State.Primary.ID != "" && len(r.Provisioners) > 0 { + if applyerr == nil && is.ID != "" && len(r.Provisioners) > 0 { for _, h := range c.hooks { - handleHook(h.PreProvisionResource(r.Id, r.State.Primary)) + handleHook(h.PreProvisionResource(r.Id, is)) } if err := c.applyProvisioners(r, is); err != nil { @@ -649,27 +644,21 @@ func (c *Context) applyWalkFn() depgraph.WalkFunc { } for _, h := range c.hooks { - handleHook(h.PostProvisionResource(r.Id, r.State.Primary)) + handleHook(h.PostProvisionResource(r.Id, is)) } } - c.sl.Lock() - if tainted { - log.Printf("[DEBUG] %s: Marking as tainted", r.Id) - r.State.Tainted = append(r.State.Tainted, r.State.Primary) - r.State.Primary = nil - } - c.sl.Unlock() - - // Update the state for the resource itself - if tainted { + // If we're tainted then we need to update some flags + if tainted && r.Flags&FlagTainted == 0 { r.Flags &^= FlagPrimary r.Flags &^= FlagHasTainted r.Flags |= FlagTainted + r.TaintedIndex = -1 + c.persistState(r) } for _, h := range c.hooks { - handleHook(h.PostApply(r.Id, r.State.Primary, applyerr)) + handleHook(h.PostApply(r.Id, is, applyerr)) } // Determine the new state and update variables @@ -766,7 +755,7 @@ func (c *Context) planWalkFn(result *Plan) depgraph.WalkFunc { var diff *InstanceDiff - is := r.State.Primary + is := r.State for _, h := range c.hooks { handleHook(h.PreDiff(r.Id, is)) @@ -786,14 +775,15 @@ func (c *Context) planWalkFn(result *Plan) depgraph.WalkFunc { // Get a diff from the newest state log.Printf("[DEBUG] %s: Executing diff", r.Id) var err error - state := r.State - if r.Flags&FlagHasTainted != 0 { + + diffIs := is + if diffIs == nil || r.Flags&FlagHasTainted != 0 { // If we're tainted, we pretend to create a new thing. - state = new(ResourceState) - state.Type = r.State.Type + diffIs = new(InstanceState) } - state.init() - diff, err = r.Provider.Diff(r.Info, state.Primary, r.Config) + diffIs.init() + + diff, err = r.Provider.Diff(r.Info, diffIs, r.Config) if err != nil { return err } @@ -846,23 +836,9 @@ func (c *Context) planWalkFn(result *Plan) depgraph.WalkFunc { is = is.MergeDiff(diff) } - // TODO(mitchellh): do we really need this? I had to do this because - // the pointer of r.State is shared with the original c.state - // which is now result.State, so modifying this was actually - // modifying the plan state, which is not what we want. I *think* - // this will be solved if we move to having InstanceState on - // type Resource rather than ResourceState, so I'm just going to - // keep this in here for now to get tests to work. - state := r.State.deepcopy() - state.Primary = is - - // Update our internal state so that variable computation works - c.sl.Lock() - defer c.sl.Unlock() - - // TODO: Handle other modules - mod := c.state.RootModule() - mod.Resources[r.Id] = state + // Set it so that it can be updated + r.State = is + c.persistState(r) return nil } @@ -883,7 +859,7 @@ func (c *Context) planDestroyWalkFn(result *Plan) depgraph.WalkFunc { } r := rn.Resource - if r.State.Primary != nil && r.State.Primary.ID != "" { + if r.State != nil && r.State.ID != "" { log.Printf("[DEBUG] %s: Making for destroy", r.Id) l.Lock() @@ -899,10 +875,7 @@ func (c *Context) planDestroyWalkFn(result *Plan) depgraph.WalkFunc { func (c *Context) refreshWalkFn() depgraph.WalkFunc { cb := func(r *Resource) error { - is := r.State.Primary - if r.Flags&FlagTainted != 0 { - is = r.State.Tainted[r.TaintedIndex] - } + is := r.State if is == nil || is.ID == "" { log.Printf("[DEBUG] %s: Not refreshing, ID is empty", r.Id) @@ -922,17 +895,9 @@ func (c *Context) refreshWalkFn() depgraph.WalkFunc { is.init() } - if r.Flags&FlagTainted != 0 { - r.State.Tainted[r.TaintedIndex] = is - } else { - r.State.Primary = is - } - - // TODO: Handle other modules - c.sl.Lock() - mod := c.state.RootModule() - mod.Resources[r.Id] = r.State - c.sl.Unlock() + // Set the updated state + r.State = is + c.persistState(r) for _, h := range c.hooks { handleHook(h.PostRefresh(r.Id, is)) @@ -1111,3 +1076,43 @@ func (c *Context) genericWalkFn(cb genericWalkFunc) depgraph.WalkFunc { return nil } } + +func (c *Context) persistState(r *Resource) { + // Acquire a state lock around this whole thing since we're updating that + c.sl.Lock() + defer c.sl.Unlock() + + // If we have no state, then we don't persist. + if c.state == nil { + return + } + + // Get the state for this resource. The resource state should always + // exist because we call graphInitState before anything that could + // potentially call this. + module := c.state.RootModule() + rs := module.Resources[r.Id] + if rs == nil { + panic(fmt.Sprintf("nil ResourceState for ID: %s", r.Id)) + } + + // Assign the instance state to the proper location + if r.Flags&FlagTainted != 0 { + if r.TaintedIndex >= 0 { + // Tainted with a pre-existing index, just update that spot + rs.Tainted[r.TaintedIndex] = r.State + } else { + // Newly tainted, so append it to the list, update the + // index, and remove the primary. + rs.Tainted = append(rs.Tainted, r.State) + rs.Primary = nil + r.TaintedIndex = len(rs.Tainted) - 1 + } + } else { + // The primary instance, so just set it directly + rs.Primary = r.State + } + + // Do a pruning so that empty resources are not saved + rs.prune() +} diff --git a/terraform/graph.go b/terraform/graph.go index 8911a5c9b..4fcb5f1dc 100644 --- a/terraform/graph.go +++ b/terraform/graph.go @@ -172,24 +172,28 @@ func Graph(opts *GraphOpts) (*depgraph.Graph, error) { return g, nil } -// EncodeDependencies is used to walk the graph and encode the -// logical dependency information into the resource state. This -// allows the dependency tree to be recovered from the state file -// such that orphaned resources will still be destroyed in the -// proper order. -func EncodeDependencies(g *depgraph.Graph) { +// graphInitState is used to initialize a State with a ResourceState +// for every resource. +// +// This method is very important to call because it will properly setup +// the ResourceState dependency information with data from the graph. This +// allows orphaned resources to be destroyed in the proper order. +func graphInitState(s *State, g *depgraph.Graph) { + // TODO: other modules + mod := s.RootModule() + for _, n := range g.Nouns { // Ignore any non-resource nodes rn, ok := n.Meta.(*GraphNodeResource) if !ok { continue } - - // Skip only if the resource has state - rs := rn.Resource - state := rs.State - if state == nil { - continue + r := rn.Resource + rs := mod.Resources[r.Id] + if rs == nil { + rs = new(ResourceState) + rs.init() + mod.Resources[r.Id] = rs } // Update the dependencies @@ -197,7 +201,7 @@ func EncodeDependencies(g *depgraph.Graph) { for _, dep := range n.Deps { switch target := dep.Target.Meta.(type) { case *GraphNodeResource: - if target.Resource.Id == rs.Id { + if target.Resource.Id == r.Id { continue } inject = append(inject, target.Resource.Id) @@ -212,7 +216,7 @@ func EncodeDependencies(g *depgraph.Graph) { } // Update the dependencies - state.Dependencies = inject + rs.Dependencies = inject } } @@ -275,7 +279,7 @@ func graphAddConfigResources( Resource: &Resource{ Id: name, Info: &InstanceInfo{Type: r.Type}, - State: state, + State: state.Primary, Config: NewResourceConfig(r.RawConfig), Flags: flags, }, @@ -604,7 +608,7 @@ func graphAddOrphans(g *depgraph.Graph, c *config.Config, s *State) { Resource: &Resource{ Id: k, Info: &InstanceInfo{Type: rs.Type}, - State: rs, + State: rs.Primary, Config: NewResourceConfig(nil), Flags: FlagOrphan, }, @@ -625,8 +629,8 @@ func graphAddOrphans(g *depgraph.Graph, c *config.Config, s *State) { rn := n.Meta.(*GraphNodeResource) // If we have no dependencies, then just continue - deps := rn.Resource.State.Dependencies - if len(deps) == 0 { + rs := mod.Resources[n.Name] + if len(rs.Dependencies) == 0 { continue } @@ -641,7 +645,7 @@ func graphAddOrphans(g *depgraph.Graph, c *config.Config, s *State) { continue } - for _, depName := range rn.Resource.State.Dependencies { + for _, depName := range rs.Dependencies { if rn2.Resource.Id != depName { continue } @@ -800,7 +804,7 @@ func graphAddTainted(g *depgraph.Graph, s *State) { } } - for i, _ := range rs.Tainted { + for i, is := range rs.Tainted { name := fmt.Sprintf("%s (tainted #%d)", k, i+1) // Add each of the tainted resources to the graph, and encode @@ -813,7 +817,7 @@ func graphAddTainted(g *depgraph.Graph, s *State) { Resource: &Resource{ Id: k, Info: &InstanceInfo{Type: rs.Type}, - State: rs, + State: is, Config: NewResourceConfig(nil), Diff: &InstanceDiff{Destroy: true}, Flags: FlagTainted, diff --git a/terraform/graph_dot.go b/terraform/graph_dot.go index b7db917d0..7b0d0d325 100644 --- a/terraform/graph_dot.go +++ b/terraform/graph_dot.go @@ -79,7 +79,7 @@ func graphDotAddResources(buf *bytes.Buffer, g *depgraph.Graph) { // green = create. Destroy is in the next section. var color, fillColor string if rn.Resource.Diff != nil && !rn.Resource.Diff.Empty() { - if rn.Resource.State != nil && rn.Resource.State.Primary.ID != "" { + if rn.Resource.State != nil && rn.Resource.State.ID != "" { color = "#FFFF00" fillColor = "#FFFF94" } else { diff --git a/terraform/graph_test.go b/terraform/graph_test.go index 8501b7c3a..a9170f6f9 100644 --- a/terraform/graph_test.go +++ b/terraform/graph_test.go @@ -516,7 +516,7 @@ func TestGraphAddDiff_destroy_counts(t *testing.T) { } } -func TestEncodeDependencies(t *testing.T) { +func TestGraphInitState(t *testing.T) { config := testConfig(t, "graph-basic") state := &State{ Modules: []*ModuleState{ @@ -546,7 +546,7 @@ func TestEncodeDependencies(t *testing.T) { } // This should encode the dependency information into the state - EncodeDependencies(g) + graphInitState(state, g) root := state.RootModule() web := root.Resources["aws_instance.web"] @@ -560,7 +560,7 @@ func TestEncodeDependencies(t *testing.T) { } } -func TestEncodeDependencies_Count(t *testing.T) { +func TestGraphInitState_Count(t *testing.T) { config := testConfig(t, "graph-count") state := &State{ Modules: []*ModuleState{ @@ -590,7 +590,7 @@ func TestEncodeDependencies_Count(t *testing.T) { } // This should encode the dependency information into the state - EncodeDependencies(g) + graphInitState(state, g) root := state.RootModule() web := root.Resources["aws_instance.web.0"] diff --git a/terraform/resource.go b/terraform/resource.go index 2962e3491..7923409ae 100644 --- a/terraform/resource.go +++ b/terraform/resource.go @@ -31,7 +31,7 @@ type Resource struct { Config *ResourceConfig Diff *InstanceDiff Provider ResourceProvider - State *ResourceState + State *InstanceState Provisioners []*ResourceProvisionerConfig Flags ResourceFlag TaintedIndex int @@ -56,7 +56,7 @@ func (r *Resource) Vars() map[string]string { } vars := make(map[string]string) - for ak, av := range r.State.Primary.Attributes { + for ak, av := range r.State.Attributes { vars[fmt.Sprintf("%s.%s", r.Id, ak)] = av } diff --git a/terraform/resource_test.go b/terraform/resource_test.go index b8afb4e5d..19ed56355 100644 --- a/terraform/resource_test.go +++ b/terraform/resource_test.go @@ -16,11 +16,9 @@ func TestResource_Vars(t *testing.T) { r = &Resource{ Id: "key", - State: &ResourceState{ - Primary: &InstanceState{ - Attributes: map[string]string{ - "foo": "bar", - }, + State: &InstanceState{ + Attributes: map[string]string{ + "foo": "bar", }, }, }