From ff7382680dc0ec1aebcd9923576739f2dbb91f67 Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Fri, 23 Oct 2020 13:06:17 -0400 Subject: [PATCH] states: Extract version check logic from read Instead of always checking the Terraform version associated with a state file when reading it, we add a CheckTerraformVersion method and call it from locations where we care about enforcing this check. For this commit, the check has been retained at all call sites for states/statefile.Read, with these exceptions: - Unit tests, which shouldn't care about the state file version; - E2E test runner which should always be using valid state files; - terraform.ShimLegacyState, where the check is pointless as the state file was just created by the current Terraform version. --- backend/remote/backend_state.go | 3 +++ command/show.go | 3 +++ command/state_push.go | 4 ++++ command/workspace_new.go | 4 ++++ plans/planfile/reader.go | 6 +++++- states/remote/state.go | 3 +++ states/statefile/file.go | 14 ++++++++++++++ states/statefile/read.go | 9 --------- states/statemgr/filesystem.go | 8 ++++++++ 9 files changed, 44 insertions(+), 10 deletions(-) diff --git a/backend/remote/backend_state.go b/backend/remote/backend_state.go index 5bbba65e16..53a39c062e 100644 --- a/backend/remote/backend_state.go +++ b/backend/remote/backend_state.go @@ -64,6 +64,9 @@ func (r *remoteClient) Put(state []byte) error { if err != nil { return fmt.Errorf("Error reading state: %s", err) } + if err := stateFile.CheckTerraformVersion(); err != nil { + return fmt.Errorf("Incompatible statefile: %s", err) + } options := tfe.StateVersionCreateOptions{ Lineage: tfe.String(stateFile.Lineage), diff --git a/command/show.go b/command/show.go index 7defadf76a..87f4498987 100644 --- a/command/show.go +++ b/command/show.go @@ -245,6 +245,9 @@ func getStateFromPath(path string) (*statefile.File, error) { if err != nil { return nil, fmt.Errorf("Error reading %s as a statefile: %s", path, err) } + if err := stateFile.CheckTerraformVersion(); err != nil { + return nil, fmt.Errorf("Incompatible statefile %s: %s", path, err) + } return stateFile, nil } diff --git a/command/state_push.go b/command/state_push.go index 5271c477c4..447b10b64d 100644 --- a/command/state_push.go +++ b/command/state_push.go @@ -63,6 +63,10 @@ func (c *StatePushCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("Error reading source state %q: %s", args[0], err)) return 1 } + if err := srcStateFile.CheckTerraformVersion(); err != nil { + c.Ui.Error(fmt.Sprintf("Incompatible statefile %q: %s", args[0], err)) + return 1 + } // Load the backend b, backendDiags := c.Backend(nil) diff --git a/command/workspace_new.go b/command/workspace_new.go index 549beebae5..0bf63274f1 100644 --- a/command/workspace_new.go +++ b/command/workspace_new.go @@ -141,6 +141,10 @@ func (c *WorkspaceNewCommand) Run(args []string) int { c.Ui.Error(err.Error()) return 1 } + if err := stateFile.CheckTerraformVersion(); err != nil { + c.Ui.Error(err.Error()) + return 1 + } // save the existing state in the new Backend. err = stateMgr.WriteState(stateFile.State) diff --git a/plans/planfile/reader.go b/plans/planfile/reader.go index 579e285999..a6a34a430f 100644 --- a/plans/planfile/reader.go +++ b/plans/planfile/reader.go @@ -101,7 +101,11 @@ func (r *Reader) ReadStateFile() (*statefile.File, error) { if err != nil { return nil, fmt.Errorf("failed to extract state from plan file: %s", err) } - return statefile.Read(r) + stateFile, err := statefile.Read(r) + if err == nil { + err = stateFile.CheckTerraformVersion() + } + return stateFile, err } } return nil, statefile.ErrNoState diff --git a/states/remote/state.go b/states/remote/state.go index f4abd3fc82..23a192f81e 100644 --- a/states/remote/state.go +++ b/states/remote/state.go @@ -125,6 +125,9 @@ func (s *State) refreshState() error { if err != nil { return err } + if err := stateFile.CheckTerraformVersion(); err != nil { + return err + } s.lineage = stateFile.Lineage s.serial = stateFile.Serial diff --git a/states/statefile/file.go b/states/statefile/file.go index 6e20240199..95f52df69a 100644 --- a/states/statefile/file.go +++ b/states/statefile/file.go @@ -1,6 +1,8 @@ package statefile import ( + "fmt" + version "github.com/hashicorp/go-version" "github.com/hashicorp/terraform/states" @@ -60,3 +62,15 @@ func (f *File) DeepCopy() *File { State: f.State.DeepCopy(), } } + +func (f *File) CheckTerraformVersion() error { + if f.TerraformVersion != nil && f.TerraformVersion.GreaterThan(tfversion.SemVer) { + return fmt.Errorf( + "state snapshot was created by Terraform v%s, which is newer than current v%s; upgrade to Terraform v%s or greater to work with this state", + f.TerraformVersion, + tfversion.SemVer, + f.TerraformVersion, + ) + } + return nil +} diff --git a/states/statefile/read.go b/states/statefile/read.go index d691c0290d..8abd3be14d 100644 --- a/states/statefile/read.go +++ b/states/statefile/read.go @@ -62,15 +62,6 @@ func Read(r io.Reader) (*File, error) { panic("readState returned nil state with no errors") } - if state.TerraformVersion != nil && state.TerraformVersion.GreaterThan(tfversion.SemVer) { - return state, fmt.Errorf( - "state snapshot was created by Terraform v%s, which is newer than current v%s; upgrade to Terraform v%s or greater to work with this state", - state.TerraformVersion, - tfversion.SemVer, - state.TerraformVersion, - ) - } - return state, diags.Err() } diff --git a/states/statemgr/filesystem.go b/states/statemgr/filesystem.go index 138e57dae5..c50e6ae486 100644 --- a/states/statemgr/filesystem.go +++ b/states/statemgr/filesystem.go @@ -280,6 +280,10 @@ func (s *Filesystem) refreshState() error { return err } log.Printf("[TRACE] statemgr.Filesystem: snapshot file has nil snapshot, but that's okay") + } else { + if err := f.CheckTerraformVersion(); err != nil { + return err + } } s.file = f @@ -459,6 +463,10 @@ func (s *Filesystem) createStateFiles() error { } log.Printf("[TRACE] statemgr.Filesystem: no previously-stored snapshot exists") } else { + if err := s.backupFile.CheckTerraformVersion(); err != nil { + return err + } + log.Printf("[TRACE] statemgr.Filesystem: existing snapshot has lineage %q serial %d", s.backupFile.Lineage, s.backupFile.Serial) }