terraform: don't put the ResourceState in Resource

This commit is contained in:
Mitchell Hashimoto 2014-09-21 22:08:21 -07:00
parent 82e92e7024
commit 73e2a43427
6 changed files with 122 additions and 115 deletions

View File

@ -126,15 +126,14 @@ func (c *Context) Apply() (*State, error) {
} }
c.state.init() c.state.init()
// Initialize the state with all the resources
graphInitState(c.state, g)
// Walk // Walk
log.Printf("[INFO] Apply walk starting") log.Printf("[INFO] Apply walk starting")
err = g.Walk(c.applyWalkFn()) err = g.Walk(c.applyWalkFn())
log.Printf("[INFO] Apply walk complete") 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 // Prune the state so that we have as clean a state as possible
c.state.prune() c.state.prune()
@ -209,6 +208,9 @@ func (c *Context) Plan(opts *PlanOpts) (*Plan, error) {
c.state = old c.state = old
}() }()
// Initialize the state with all the resources
graphInitState(c.state, g)
walkFn = c.planWalkFn(p) walkFn = c.planWalkFn(p)
} }
@ -244,6 +246,12 @@ func (c *Context) Refresh() (*State, error) {
return c.state, err 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()) err = g.Walk(c.refreshWalkFn())
// Prune the state // Prune the state
@ -529,8 +537,11 @@ func (c *Context) applyWalkFn() depgraph.WalkFunc {
return nil return nil
} }
// Ensure the state is initialized is := r.State
r.State.init() if is == nil {
is = new(InstanceState)
}
is.init()
if !diff.Destroy { if !diff.Destroy {
// Since we need the configuration, interpolate the variables // Since we need the configuration, interpolate the variables
@ -538,7 +549,7 @@ func (c *Context) applyWalkFn() depgraph.WalkFunc {
return err 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 { if err != nil {
return err return err
} }
@ -578,12 +589,12 @@ func (c *Context) applyWalkFn() depgraph.WalkFunc {
} }
for _, h := range c.hooks { 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! // With the completed diff, apply!
log.Printf("[DEBUG] %s: Executing Apply", r.Id) 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 var errs []error
if applyerr != nil { if applyerr != nil {
@ -611,25 +622,9 @@ func (c *Context) applyWalkFn() depgraph.WalkFunc {
} }
} }
if r.Flags&FlagTainted != 0 { // Set the result state
// Update the tainted resource. r.State = is
r.State.Tainted[r.TaintedIndex] = is c.persistState(r)
} 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()
// Invoke any provisioners we have defined. This is only done // Invoke any provisioners we have defined. This is only done
// if the resource was created, as updates or deletes do not // 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 // Additionally, we need to be careful to not run this if there
// was an error during the provider apply. // was an error during the provider apply.
tainted := false 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 { 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 { if err := c.applyProvisioners(r, is); err != nil {
@ -649,27 +644,21 @@ func (c *Context) applyWalkFn() depgraph.WalkFunc {
} }
for _, h := range c.hooks { for _, h := range c.hooks {
handleHook(h.PostProvisionResource(r.Id, r.State.Primary)) handleHook(h.PostProvisionResource(r.Id, is))
} }
} }
c.sl.Lock() // If we're tainted then we need to update some flags
if tainted { if tainted && r.Flags&FlagTainted == 0 {
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 {
r.Flags &^= FlagPrimary r.Flags &^= FlagPrimary
r.Flags &^= FlagHasTainted r.Flags &^= FlagHasTainted
r.Flags |= FlagTainted r.Flags |= FlagTainted
r.TaintedIndex = -1
c.persistState(r)
} }
for _, h := range c.hooks { 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 // Determine the new state and update variables
@ -766,7 +755,7 @@ func (c *Context) planWalkFn(result *Plan) depgraph.WalkFunc {
var diff *InstanceDiff var diff *InstanceDiff
is := r.State.Primary is := r.State
for _, h := range c.hooks { for _, h := range c.hooks {
handleHook(h.PreDiff(r.Id, is)) 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 // Get a diff from the newest state
log.Printf("[DEBUG] %s: Executing diff", r.Id) log.Printf("[DEBUG] %s: Executing diff", r.Id)
var err error 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. // If we're tainted, we pretend to create a new thing.
state = new(ResourceState) diffIs = new(InstanceState)
state.Type = r.State.Type
} }
state.init() diffIs.init()
diff, err = r.Provider.Diff(r.Info, state.Primary, r.Config)
diff, err = r.Provider.Diff(r.Info, diffIs, r.Config)
if err != nil { if err != nil {
return err return err
} }
@ -846,23 +836,9 @@ func (c *Context) planWalkFn(result *Plan) depgraph.WalkFunc {
is = is.MergeDiff(diff) is = is.MergeDiff(diff)
} }
// TODO(mitchellh): do we really need this? I had to do this because // Set it so that it can be updated
// the pointer of r.State is shared with the original c.state r.State = is
// which is now result.State, so modifying this was actually c.persistState(r)
// 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
return nil return nil
} }
@ -883,7 +859,7 @@ func (c *Context) planDestroyWalkFn(result *Plan) depgraph.WalkFunc {
} }
r := rn.Resource 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) log.Printf("[DEBUG] %s: Making for destroy", r.Id)
l.Lock() l.Lock()
@ -899,10 +875,7 @@ func (c *Context) planDestroyWalkFn(result *Plan) depgraph.WalkFunc {
func (c *Context) refreshWalkFn() depgraph.WalkFunc { func (c *Context) refreshWalkFn() depgraph.WalkFunc {
cb := func(r *Resource) error { cb := func(r *Resource) error {
is := r.State.Primary is := r.State
if r.Flags&FlagTainted != 0 {
is = r.State.Tainted[r.TaintedIndex]
}
if is == nil || is.ID == "" { if is == nil || is.ID == "" {
log.Printf("[DEBUG] %s: Not refreshing, ID is empty", r.Id) log.Printf("[DEBUG] %s: Not refreshing, ID is empty", r.Id)
@ -922,17 +895,9 @@ func (c *Context) refreshWalkFn() depgraph.WalkFunc {
is.init() is.init()
} }
if r.Flags&FlagTainted != 0 { // Set the updated state
r.State.Tainted[r.TaintedIndex] = is r.State = is
} else { c.persistState(r)
r.State.Primary = is
}
// TODO: Handle other modules
c.sl.Lock()
mod := c.state.RootModule()
mod.Resources[r.Id] = r.State
c.sl.Unlock()
for _, h := range c.hooks { for _, h := range c.hooks {
handleHook(h.PostRefresh(r.Id, is)) handleHook(h.PostRefresh(r.Id, is))
@ -1111,3 +1076,43 @@ func (c *Context) genericWalkFn(cb genericWalkFunc) depgraph.WalkFunc {
return nil 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()
}

View File

@ -172,24 +172,28 @@ func Graph(opts *GraphOpts) (*depgraph.Graph, error) {
return g, nil return g, nil
} }
// EncodeDependencies is used to walk the graph and encode the // graphInitState is used to initialize a State with a ResourceState
// logical dependency information into the resource state. This // for every resource.
// allows the dependency tree to be recovered from the state file //
// such that orphaned resources will still be destroyed in the // This method is very important to call because it will properly setup
// proper order. // the ResourceState dependency information with data from the graph. This
func EncodeDependencies(g *depgraph.Graph) { // 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 { for _, n := range g.Nouns {
// Ignore any non-resource nodes // Ignore any non-resource nodes
rn, ok := n.Meta.(*GraphNodeResource) rn, ok := n.Meta.(*GraphNodeResource)
if !ok { if !ok {
continue continue
} }
r := rn.Resource
// Skip only if the resource has state rs := mod.Resources[r.Id]
rs := rn.Resource if rs == nil {
state := rs.State rs = new(ResourceState)
if state == nil { rs.init()
continue mod.Resources[r.Id] = rs
} }
// Update the dependencies // Update the dependencies
@ -197,7 +201,7 @@ func EncodeDependencies(g *depgraph.Graph) {
for _, dep := range n.Deps { for _, dep := range n.Deps {
switch target := dep.Target.Meta.(type) { switch target := dep.Target.Meta.(type) {
case *GraphNodeResource: case *GraphNodeResource:
if target.Resource.Id == rs.Id { if target.Resource.Id == r.Id {
continue continue
} }
inject = append(inject, target.Resource.Id) inject = append(inject, target.Resource.Id)
@ -212,7 +216,7 @@ func EncodeDependencies(g *depgraph.Graph) {
} }
// Update the dependencies // Update the dependencies
state.Dependencies = inject rs.Dependencies = inject
} }
} }
@ -275,7 +279,7 @@ func graphAddConfigResources(
Resource: &Resource{ Resource: &Resource{
Id: name, Id: name,
Info: &InstanceInfo{Type: r.Type}, Info: &InstanceInfo{Type: r.Type},
State: state, State: state.Primary,
Config: NewResourceConfig(r.RawConfig), Config: NewResourceConfig(r.RawConfig),
Flags: flags, Flags: flags,
}, },
@ -604,7 +608,7 @@ func graphAddOrphans(g *depgraph.Graph, c *config.Config, s *State) {
Resource: &Resource{ Resource: &Resource{
Id: k, Id: k,
Info: &InstanceInfo{Type: rs.Type}, Info: &InstanceInfo{Type: rs.Type},
State: rs, State: rs.Primary,
Config: NewResourceConfig(nil), Config: NewResourceConfig(nil),
Flags: FlagOrphan, Flags: FlagOrphan,
}, },
@ -625,8 +629,8 @@ func graphAddOrphans(g *depgraph.Graph, c *config.Config, s *State) {
rn := n.Meta.(*GraphNodeResource) rn := n.Meta.(*GraphNodeResource)
// If we have no dependencies, then just continue // If we have no dependencies, then just continue
deps := rn.Resource.State.Dependencies rs := mod.Resources[n.Name]
if len(deps) == 0 { if len(rs.Dependencies) == 0 {
continue continue
} }
@ -641,7 +645,7 @@ func graphAddOrphans(g *depgraph.Graph, c *config.Config, s *State) {
continue continue
} }
for _, depName := range rn.Resource.State.Dependencies { for _, depName := range rs.Dependencies {
if rn2.Resource.Id != depName { if rn2.Resource.Id != depName {
continue 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) name := fmt.Sprintf("%s (tainted #%d)", k, i+1)
// Add each of the tainted resources to the graph, and encode // Add each of the tainted resources to the graph, and encode
@ -813,7 +817,7 @@ func graphAddTainted(g *depgraph.Graph, s *State) {
Resource: &Resource{ Resource: &Resource{
Id: k, Id: k,
Info: &InstanceInfo{Type: rs.Type}, Info: &InstanceInfo{Type: rs.Type},
State: rs, State: is,
Config: NewResourceConfig(nil), Config: NewResourceConfig(nil),
Diff: &InstanceDiff{Destroy: true}, Diff: &InstanceDiff{Destroy: true},
Flags: FlagTainted, Flags: FlagTainted,

View File

@ -79,7 +79,7 @@ func graphDotAddResources(buf *bytes.Buffer, g *depgraph.Graph) {
// green = create. Destroy is in the next section. // green = create. Destroy is in the next section.
var color, fillColor string var color, fillColor string
if rn.Resource.Diff != nil && !rn.Resource.Diff.Empty() { 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" color = "#FFFF00"
fillColor = "#FFFF94" fillColor = "#FFFF94"
} else { } else {

View File

@ -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") config := testConfig(t, "graph-basic")
state := &State{ state := &State{
Modules: []*ModuleState{ Modules: []*ModuleState{
@ -546,7 +546,7 @@ func TestEncodeDependencies(t *testing.T) {
} }
// This should encode the dependency information into the state // This should encode the dependency information into the state
EncodeDependencies(g) graphInitState(state, g)
root := state.RootModule() root := state.RootModule()
web := root.Resources["aws_instance.web"] 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") config := testConfig(t, "graph-count")
state := &State{ state := &State{
Modules: []*ModuleState{ Modules: []*ModuleState{
@ -590,7 +590,7 @@ func TestEncodeDependencies_Count(t *testing.T) {
} }
// This should encode the dependency information into the state // This should encode the dependency information into the state
EncodeDependencies(g) graphInitState(state, g)
root := state.RootModule() root := state.RootModule()
web := root.Resources["aws_instance.web.0"] web := root.Resources["aws_instance.web.0"]

View File

@ -31,7 +31,7 @@ type Resource struct {
Config *ResourceConfig Config *ResourceConfig
Diff *InstanceDiff Diff *InstanceDiff
Provider ResourceProvider Provider ResourceProvider
State *ResourceState State *InstanceState
Provisioners []*ResourceProvisionerConfig Provisioners []*ResourceProvisionerConfig
Flags ResourceFlag Flags ResourceFlag
TaintedIndex int TaintedIndex int
@ -56,7 +56,7 @@ func (r *Resource) Vars() map[string]string {
} }
vars := make(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 vars[fmt.Sprintf("%s.%s", r.Id, ak)] = av
} }

View File

@ -16,13 +16,11 @@ func TestResource_Vars(t *testing.T) {
r = &Resource{ r = &Resource{
Id: "key", Id: "key",
State: &ResourceState{ State: &InstanceState{
Primary: &InstanceState{
Attributes: map[string]string{ Attributes: map[string]string{
"foo": "bar", "foo": "bar",
}, },
}, },
},
} }
expected := map[string]string{ expected := map[string]string{