Merge pull request #10233 from hashicorp/jbardin/GH-10229

An empty module in state can panic
This commit is contained in:
James Bardin 2016-11-21 17:35:33 -05:00 committed by GitHub
commit 91378d0499
3 changed files with 82 additions and 36 deletions

View File

@ -1,6 +1,7 @@
package state package state
import ( import (
"fmt"
"os" "os"
"reflect" "reflect"
"testing" "testing"
@ -70,36 +71,42 @@ func TestCacheState_RefreshState(t *testing.T) {
&terraform.ModuleState{ &terraform.ModuleState{
Path: terraform.RootModulePath, Path: terraform.RootModulePath,
Resources: map[string]*terraform.ResourceState{ Resources: map[string]*terraform.ResourceState{
"foo.foo": &terraform.ResourceState{}, "foo.foo": &terraform.ResourceState{
Primary: &terraform.InstanceState{
ID: "ID",
},
},
}, },
}, },
}, },
expected: CacheRefreshLocalNewer, expected: CacheRefreshLocalNewer,
}, },
} { } {
cache := testLocalState(t) t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
durable := testLocalState(t) cache := testLocalState(t)
defer os.Remove(cache.Path) durable := testLocalState(t)
defer os.Remove(durable.Path) defer os.Remove(cache.Path)
defer os.Remove(durable.Path)
cs := &CacheState{ cs := &CacheState{
Cache: cache, Cache: cache,
Durable: durable, Durable: durable,
} }
state := cache.State() state := cache.State()
state.Modules = test.cacheModules state.Modules = test.cacheModules
if err := cs.WriteState(state); err != nil { if err := cs.WriteState(state); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if err := cs.RefreshState(); err != nil { if err := cs.RefreshState(); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if cs.RefreshResult() != test.expected { if cs.RefreshResult() != test.expected {
t.Fatalf("bad %d: %v", i, cs.RefreshResult()) t.Fatalf("bad %d: %v", i, cs.RefreshResult())
} }
})
} }
} }

View File

@ -644,6 +644,10 @@ func (s *State) init() {
} }
s.ensureHasLineage() s.ensureHasLineage()
// We can't trust that state read from a file doesn't have nil/empty
// modules
s.prune()
for _, mod := range s.Modules { for _, mod := range s.Modules {
mod.init() mod.init()
} }
@ -651,6 +655,7 @@ func (s *State) init() {
if s.Remote != nil { if s.Remote != nil {
s.Remote.init() s.Remote.init()
} }
} }
func (s *State) EnsureHasLineage() { func (s *State) EnsureHasLineage() {
@ -695,6 +700,18 @@ func (s *State) prune() {
if s == nil { if s == nil {
return return
} }
// Filter out empty modules.
// A module is always assumed to have a path, and it's length isn't always
// bounds checked later on. Modules may be "emptied" during destroy, but we
// never want to store those in the state.
for i := 0; i < len(s.Modules); i++ {
if s.Modules[i] == nil || len(s.Modules[i].Path) == 0 {
s.Modules = append(s.Modules[:i], s.Modules[i+1:]...)
i--
}
}
for _, mod := range s.Modules { for _, mod := range s.Modules {
mod.prune() mod.prune()
} }
@ -1074,11 +1091,12 @@ func (m *ModuleState) prune() {
defer m.Unlock() defer m.Unlock()
for k, v := range m.Resources { for k, v := range m.Resources {
v.prune() if v == nil || (v.Primary == nil || v.Primary.ID == "") && len(v.Deposed) == 0 {
if (v.Primary == nil || v.Primary.ID == "") && len(v.Deposed) == 0 {
delete(m.Resources, k) delete(m.Resources, k)
continue
} }
v.prune()
} }
for k, v := range m.Outputs { for k, v := range m.Outputs {
@ -1420,19 +1438,6 @@ func (s *ResourceState) init() {
if s.Deposed == nil { if s.Deposed == nil {
s.Deposed = make([]*InstanceState, 0) s.Deposed = make([]*InstanceState, 0)
} }
// clean out any possible nil values read in from the state file
end := len(s.Deposed) - 1
for i := 0; i <= end; i++ {
if s.Deposed[i] == nil {
s.Deposed[i], s.Deposed[end] = s.Deposed[end], s.Deposed[i]
end--
i--
} else {
s.Deposed[i].init()
}
}
s.Deposed = s.Deposed[:end+1]
} }
func (s *ResourceState) deepcopy() *ResourceState { func (s *ResourceState) deepcopy() *ResourceState {

View File

@ -1674,3 +1674,37 @@ func TestParseResourceStateKey(t *testing.T) {
} }
} }
} }
func TestStateModuleOrphans_empty(t *testing.T) {
state := &State{
Modules: []*ModuleState{
&ModuleState{
Path: RootModulePath,
},
&ModuleState{
Path: []string{RootModuleName, "foo", "bar"},
},
&ModuleState{
Path: []string{},
},
nil,
},
}
state.init()
// just calling this to check for panic
state.ModuleOrphans(RootModulePath, nil)
for _, mod := range state.Modules {
if mod == nil {
t.Fatal("found nil module")
}
if mod.Path == nil {
t.Fatal("found nil module path")
}
if len(mod.Path) == 0 {
t.Fatal("found empty module path")
}
}
}