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 } diff --git a/state/remote/remote_test.go b/state/remote/remote_test.go index 16afccdf19..db3c795c80 100644 --- a/state/remote/remote_test.go +++ b/state/remote/remote_test.go @@ -2,6 +2,8 @@ package remote import ( "bytes" + "io/ioutil" + "os" "testing" "github.com/hashicorp/terraform/state" @@ -46,8 +48,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 +61,75 @@ 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()) + + remoteStateFile, err := ioutil.TempFile("", "tf") + if err != nil { + t.Fatal(err) + } + remoteStateFile.Close() + os.Remove(remoteStateFile.Name()) + defer os.Remove(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()}, + } + + remoteClient := &FileClient{ + Path: remoteStateFile.Name(), + } + + cache := &state.CacheState{ + Cache: &state.LocalState{ + Path: localStateFile.Name(), + }, + Durable: &State{ + Client: remoteClient, + }, + } + + // 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) + } + + // This will persist the local state we just wrote to the remote state file + err = cache.PersistState() + if err != nil { + t.Fatal(err) + } + + // now compare the two state files just to be sure + localData, err := ioutil.ReadFile(localStateFile.Name()) + if err != nil { + t.Fatal(err) + } + + 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") + } +} 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))