diff --git a/state/cache.go b/state/cache.go index e58e1ee2d..b82bb65f3 100644 --- a/state/cache.go +++ b/state/cache.go @@ -64,8 +64,13 @@ func (s *CacheState) RefreshState() error { // Cache is newer than remote. Not a big deal, user can just // persist to get correct state. s.refreshResult = CacheRefreshLocalNewer - case cached == nil && durable != nil: + case !cached.HasResources() && durable != nil: // Cache should be updated since the remote is set but cache isn't + // + // If local is empty then we'll treat it as missing so that + // it can be overwritten by an already-existing remote. This + // allows the user to activate remote state for the first time + // against an already-existing remote state. s.refreshResult = CacheRefreshUpdateLocal case durable.Serial < cached.Serial: // Cache is newer than remote. Not a big deal, user can just diff --git a/state/cache_test.go b/state/cache_test.go index c99aeb826..57a00499f 100644 --- a/state/cache_test.go +++ b/state/cache_test.go @@ -4,6 +4,8 @@ import ( "os" "reflect" "testing" + + "github.com/hashicorp/terraform/terraform" ) func TestCacheState(t *testing.T) { @@ -50,6 +52,56 @@ func TestCacheState_persistDurable(t *testing.T) { } } +func TestCacheState_RefreshState(t *testing.T) { + for i, test := range []struct { + cacheModules []*terraform.ModuleState + expected CacheRefreshResult + }{ + { + cacheModules: nil, + expected: CacheRefreshUpdateLocal, + }, + { + cacheModules: []*terraform.ModuleState{}, + expected: CacheRefreshUpdateLocal, + }, + { + cacheModules: []*terraform.ModuleState{ + &terraform.ModuleState{ + Resources: map[string]*terraform.ResourceState{ + "foo.foo": &terraform.ResourceState{}, + }, + }, + }, + expected: CacheRefreshLocalNewer, + }, + } { + cache := testLocalState(t) + durable := testLocalState(t) + defer os.Remove(cache.Path) + defer os.Remove(durable.Path) + + cs := &CacheState{ + Cache: cache, + Durable: durable, + } + + state := cache.State() + state.Modules = test.cacheModules + if err := cs.WriteState(state); err != nil { + t.Fatalf("err: %s", err) + } + + if err := cs.RefreshState(); err != nil { + t.Fatalf("err: %s", err) + } + + if cs.RefreshResult() != test.expected { + t.Fatalf("bad %d: %v", i, cs.RefreshResult()) + } + } +} + func TestCacheState_impl(t *testing.T) { var _ StateReader = new(CacheState) var _ StateWriter = new(CacheState) diff --git a/terraform/state.go b/terraform/state.go index 80691fd82..77e1957fb 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -220,6 +220,24 @@ func (s *State) Empty() bool { return len(s.Modules) == 0 } +// HasResources returns true if the state contains any resources. +// +// This is similar to !s.Empty, but returns true also in the case where the +// state has modules but all of them are devoid of resources. +func (s *State) HasResources() bool { + if s.Empty() { + return false + } + + for _, mod := range s.Modules { + if len(mod.Resources) > 0 { + return true + } + } + + return false +} + // IsRemote returns true if State represents a state that exists and is // remote. func (s *State) IsRemote() bool { diff --git a/terraform/state_test.go b/terraform/state_test.go index 5a74dbb12..c0afd853f 100644 --- a/terraform/state_test.go +++ b/terraform/state_test.go @@ -1136,6 +1136,64 @@ func TestStateEmpty(t *testing.T) { } } +func TestStateHasResources(t *testing.T) { + cases := []struct { + In *State + Result bool + }{ + { + nil, + false, + }, + { + &State{}, + false, + }, + { + &State{ + Remote: &RemoteState{Type: "foo"}, + }, + false, + }, + { + &State{ + Modules: []*ModuleState{ + &ModuleState{}, + }, + }, + false, + }, + { + &State{ + Modules: []*ModuleState{ + &ModuleState{}, + &ModuleState{}, + }, + }, + false, + }, + { + &State{ + Modules: []*ModuleState{ + &ModuleState{}, + &ModuleState{ + Resources: map[string]*ResourceState{ + "foo.foo": &ResourceState{}, + }, + }, + }, + }, + true, + }, + } + + for i, tc := range cases { + if tc.In.HasResources() != tc.Result { + t.Fatalf("bad %d %#v:\n\n%#v", i, tc.Result, tc.In) + } + } +} + func TestStateFromFutureTerraform(t *testing.T) { cases := []struct { In string