diff --git a/remote/remote.go b/remote/remote.go index 447125693..a204a2f1a 100644 --- a/remote/remote.go +++ b/remote/remote.go @@ -178,6 +178,30 @@ func validConfig(conf *terraform.RemoteState) error { return nil } +// ReadLocalState is used to read and parse the local state file +func ReadLocalState() (*terraform.State, []byte, error) { + path, err := HiddenStatePath() + if err != nil { + return nil, nil, err + } + + // Open the existing file + raw, err := ioutil.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { + return nil, nil, nil + } + return nil, nil, fmt.Errorf("Failed to open state file '%s': %s", path, err) + } + + // Decode the state + state, err := terraform.ReadState(bytes.NewReader(raw)) + if err != nil { + return nil, nil, fmt.Errorf("Failed to read state file '%s': %v", path, err) + } + return state, raw, nil +} + // ValidateConfig is used to take a remote state configuration, // ensure the local directory exists and that the remote state // does not conflict with an existing state file. @@ -193,39 +217,28 @@ func ValidateConfig(conf *terraform.RemoteState) error { "Remote state setup failed: %s", err) } - // Get the path to the state file - path, err := HiddenStatePath() + // Check for local state + local, _, err := ReadLocalState() if err != nil { return err } - // Open the existing file - f, err := os.Open(path) - if err != nil { - if !os.IsNotExist(err) { - return fmt.Errorf("Failed to open state file '%s': %s", path, err) - } + // Nothing to check if no local state yet + if local == nil { return nil } - defer f.Close() - - // Decode the state - state, err := terraform.ReadState(f) - if err != nil { - return fmt.Errorf("Failed to read state file '%s': %v", path, err) - } // If the hidden state file has no remote info, something // is definitely wrong... - if state.Remote == nil { - return fmt.Errorf(`State file '%s' missing remote storage information. + if local.Remote == nil { + return fmt.Errorf(`Local state file missing remote storage information. This is likely a bug, please report it.`) } // Check if there is a conflict - if !state.Remote.Equals(conf) { + if !local.Remote.Equals(conf) { return fmt.Errorf( - "Conflicting definitions for remote storage in existing state file '%s'", path) + "Conflicting definitions for remote storage in existing state file") } return nil } @@ -258,28 +271,12 @@ func RefreshState(conf *terraform.RemoteState) (StateChangeResult, error) { } } - // Get the path to the state file - path, err := HiddenStatePath() + // Decode the state + localState, raw, err := ReadLocalState() if err != nil { return StateChangeNoop, err } - // Get the existing state file - raw, err := ioutil.ReadFile(path) - if err != nil && !os.IsNotExist(err) { - return StateChangeNoop, fmt.Errorf("Failed to read local state: %v", err) - } - - // Decode the state - var localState *terraform.State - if raw != nil { - localState, err = terraform.ReadState(bytes.NewReader(raw)) - if err != nil { - return StateChangeNoop, - fmt.Errorf("Failed to decode state file '%s': %v", path, err) - } - } - // We need to handle the matrix of cases in reconciling // the local and remote state. Primarily the concern is // around the Serial number which should grow monotonically. @@ -335,18 +332,12 @@ func RefreshState(conf *terraform.RemoteState) (StateChangeResult, error) { // can be 'forced' to override any conflict detection // on the server-side. func PushState(conf *terraform.RemoteState, force bool) (StateChangeResult, error) { - // Get the path to the state file - path, err := HiddenStatePath() + // Read the local state + _, raw, err := ReadLocalState() if err != nil { return StateChangeNoop, err } - // Get the existing state file - raw, err := ioutil.ReadFile(path) - if err != nil && !os.IsNotExist(err) { - return StateChangeNoop, fmt.Errorf("Failed to read local state: %v", err) - } - // Check if there is no local state if raw == nil { return StateChangeNoop, fmt.Errorf("No local state to push")