From 3622bfddd635b0d36efd6ec43e1250cd1d576620 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 6 Jul 2016 10:48:52 -0400 Subject: [PATCH 1/3] Revert #7464 and allow an empty state Revert back to using a nil state. The external usage of the state shoudl always check the Empty() method. --- state/remote/remote_test.go | 76 ++++++++++++++++++++++++++++++++++++- state/remote/state.go | 6 +-- 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/state/remote/remote_test.go b/state/remote/remote_test.go index 16afccdf19..04f8757477 100644 --- a/state/remote/remote_test.go +++ b/state/remote/remote_test.go @@ -2,6 +2,9 @@ package remote import ( "bytes" + "fmt" + "io/ioutil" + "os" "testing" "github.com/hashicorp/terraform/state" @@ -46,8 +49,8 @@ func TestRemoteClient_noPayload(t *testing.T) { s := &State{ Client: nilClient{}, } - if err := s.RefreshState(); err != ErrRemoteStateNotFound { - t.Fatal("expected ErrRemoteStateNotFound, got", err) + if err := s.RefreshState(); err != nil { + t.Fatal("error refreshing empty remote state") } } @@ -59,3 +62,72 @@ func (nilClient) Get() (*Payload, error) { return nil, nil } func (c nilClient) Put([]byte) error { return nil } func (c nilClient) Delete() error { return nil } + +// ensure that remote state can be properly initialized +func TestRemoteClient_stateInit(t *testing.T) { + localStateFile, err := ioutil.TempFile("", "tf") + if err != nil { + t.Fatal(err) + } + + // we need to remove the temp files so we recognize there's no local or + // remote state. + localStateFile.Close() + os.Remove(localStateFile.Name()) + //defer os.Remove(localStateFile.Name()) + fmt.Println("LOCAL:", localStateFile.Name()) + + local := &state.LocalState{ + Path: localStateFile.Name(), + } + if err := local.RefreshState(); err != nil { + t.Fatal(err) + } + localState := local.State() + + fmt.Println("localState.Empty():", localState.Empty()) + + remoteStateFile, err := ioutil.TempFile("", "tf") + if err != nil { + t.Fatal(err) + } + remoteStateFile.Close() + os.Remove(remoteStateFile.Name()) + //defer os.Remove(remoteStateFile.Name() + fmt.Println("LOCAL:", localStateFile.Name()) + fmt.Println("REMOTE:", remoteStateFile.Name()) + + remoteClient := &FileClient{ + Path: remoteStateFile.Name(), + } + + durable := &State{ + Client: remoteClient, + } + + cache := &state.CacheState{ + Cache: local, + Durable: durable, + } + + if err := cache.RefreshState(); err != nil { + t.Fatal(err) + } + + switch cache.RefreshResult() { + + // we should be "refreshing" the remote state to initialize it + case state.CacheRefreshLocalNewer: + // Write our local state out to the durable storage to start. + if err := cache.WriteState(localState); err != nil { + t.Fatal("Error preparing remote state:", err) + } + if err := cache.PersistState(); err != nil { + t.Fatal("Error preparing remote state:", err) + } + default: + + t.Fatal("unexpected refresh result:", cache.RefreshResult()) + } + +} diff --git a/state/remote/state.go b/state/remote/state.go index 5f45129d63..18427f3410 100644 --- a/state/remote/state.go +++ b/state/remote/state.go @@ -2,13 +2,10 @@ package remote import ( "bytes" - "errors" "github.com/hashicorp/terraform/terraform" ) -var ErrRemoteStateNotFound = errors.New("no remote state found") - // State implements the State interfaces in the state package to handle // reading and writing the remote state. This State on its own does no // local caching so every persist will go to the remote storage and local @@ -37,8 +34,9 @@ func (s *State) RefreshState() error { return err } + // no remote state is OK if payload == nil { - return ErrRemoteStateNotFound + return nil } state, err := terraform.ReadState(bytes.NewReader(payload.Data)) From 2c27dd41bf03e6f8b75e40cfc002be441d1010fb Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 7 Jul 2016 15:37:57 -0400 Subject: [PATCH 2/3] Fix panic when there is no remote state - Check for an empty state. If nothing is referenced from that state in the config, there's nothing to do here. --- builtin/providers/terraform/data_source_state.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/builtin/providers/terraform/data_source_state.go b/builtin/providers/terraform/data_source_state.go index d8c97ebd52..c87da01fba 100644 --- a/builtin/providers/terraform/data_source_state.go +++ b/builtin/providers/terraform/data_source_state.go @@ -55,7 +55,14 @@ func dataSourceRemoteStateRead(d *schema.ResourceData, meta interface{}) error { d.SetId(time.Now().UTC().String()) outputMap := make(map[string]interface{}) - for key, val := range state.State().RootModule().Outputs { + + remoteState := state.State() + if remoteState.Empty() { + log.Println("[DEBUG] empty remote state") + return nil + } + + for key, val := range remoteState.RootModule().Outputs { outputMap[key] = val.Value } From 74813821ec38d7c18b0efed9aad37c1b7cf1aadd Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 7 Jul 2016 16:18:00 -0400 Subject: [PATCH 3/3] Add remote state init test Verify that a remote state file is correctly initialized in the same manner as used by the `terraform remote config` --- state/remote/remote_test.go | 72 +++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/state/remote/remote_test.go b/state/remote/remote_test.go index 04f8757477..db3c795c80 100644 --- a/state/remote/remote_test.go +++ b/state/remote/remote_test.go @@ -2,7 +2,6 @@ package remote import ( "bytes" - "fmt" "io/ioutil" "os" "testing" @@ -74,18 +73,7 @@ func TestRemoteClient_stateInit(t *testing.T) { // remote state. localStateFile.Close() os.Remove(localStateFile.Name()) - //defer os.Remove(localStateFile.Name()) - fmt.Println("LOCAL:", localStateFile.Name()) - - local := &state.LocalState{ - Path: localStateFile.Name(), - } - if err := local.RefreshState(); err != nil { - t.Fatal(err) - } - localState := local.State() - - fmt.Println("localState.Empty():", localState.Empty()) + defer os.Remove(localStateFile.Name()) remoteStateFile, err := ioutil.TempFile("", "tf") if err != nil { @@ -93,41 +81,55 @@ func TestRemoteClient_stateInit(t *testing.T) { } remoteStateFile.Close() os.Remove(remoteStateFile.Name()) - //defer os.Remove(remoteStateFile.Name() - fmt.Println("LOCAL:", localStateFile.Name()) - fmt.Println("REMOTE:", remoteStateFile.Name()) + defer os.Remove(remoteStateFile.Name()) - remoteClient := &FileClient{ - Path: remoteStateFile.Name(), + // Now we need an empty state to initialize the state files. + newState := terraform.NewState() + newState.Remote = &terraform.RemoteState{ + Type: "_local", + Config: map[string]string{"path": remoteStateFile.Name()}, } - durable := &State{ - Client: remoteClient, + remoteClient := &FileClient{ + Path: remoteStateFile.Name(), } cache := &state.CacheState{ - Cache: local, - Durable: durable, + Cache: &state.LocalState{ + Path: localStateFile.Name(), + }, + Durable: &State{ + Client: remoteClient, + }, } - if err := cache.RefreshState(); err != nil { + // This will write the local state file, and set the state field in the CacheState + err = cache.WriteState(newState) + if err != nil { t.Fatal(err) } - switch cache.RefreshResult() { + // This will persist the local state we just wrote to the remote state file + err = cache.PersistState() + if err != nil { + t.Fatal(err) + } - // we should be "refreshing" the remote state to initialize it - case state.CacheRefreshLocalNewer: - // Write our local state out to the durable storage to start. - if err := cache.WriteState(localState); err != nil { - t.Fatal("Error preparing remote state:", err) - } - if err := cache.PersistState(); err != nil { - t.Fatal("Error preparing remote state:", err) - } - default: + // now compare the two state files just to be sure + localData, err := ioutil.ReadFile(localStateFile.Name()) + if err != nil { + t.Fatal(err) + } - t.Fatal("unexpected refresh result:", cache.RefreshResult()) + remoteData, err := ioutil.ReadFile(remoteStateFile.Name()) + if err != nil { + t.Fatal(err) } + if !bytes.Equal(localData, remoteData) { + t.Log("state files don't match") + t.Log("Local:\n", string(localData)) + t.Log("Remote:\n", string(remoteData)) + t.Fatal("failed to initialize remote state") + } }