Merge pull request #7320 from dtolnay/conflict

core: Allow refresh of local state with no resources
This commit is contained in:
James Nugent 2016-10-14 11:00:46 -05:00 committed by GitHub
commit afe2d7b65b
4 changed files with 135 additions and 1 deletions

View File

@ -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

View File

@ -4,6 +4,8 @@ import (
"os"
"reflect"
"testing"
"github.com/hashicorp/terraform/terraform"
)
func TestCacheState(t *testing.T) {
@ -50,6 +52,57 @@ 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{
Path: terraform.RootModulePath,
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)

View File

@ -262,6 +262,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 {

View File

@ -1235,6 +1235,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