diff --git a/command/init.go b/command/init.go index 66b1c7028a..9bfd8ef08d 100644 --- a/command/init.go +++ b/command/init.go @@ -180,7 +180,6 @@ func (c *InitCommand) Run(args []string) int { "Error downloading modules: %s", err)) return 1 } - } // If we're requesting backend configuration or looking for required @@ -280,6 +279,12 @@ func (c *InitCommand) getProviders(path string, state *terraform.State, upgrade return diags.Err() } + if err := terraform.CheckStateVersion(state); err != nil { + diags = diags.Append(err) + c.showDiagnostics(diags) + return err + } + if err := terraform.CheckRequiredVersion(mod); err != nil { diags = diags.Append(err) c.showDiagnostics(diags) diff --git a/command/init_test.go b/command/init_test.go index 05ad340938..f12363092d 100644 --- a/command/init_test.go +++ b/command/init_test.go @@ -11,8 +11,11 @@ import ( "strings" "testing" + "github.com/hashicorp/terraform/backend/local" "github.com/hashicorp/terraform/helper/copy" "github.com/hashicorp/terraform/plugin/discovery" + "github.com/hashicorp/terraform/state" + "github.com/hashicorp/terraform/terraform" "github.com/mitchellh/cli" ) @@ -543,12 +546,12 @@ func TestInit_getProvider(t *testing.T) { defer os.RemoveAll(td) defer testChdir(t, td)() + overrides := metaOverridesForProvider(testProvider()) ui := new(cli.MockUi) m := Meta{ - testingOverrides: metaOverridesForProvider(testProvider()), + testingOverrides: overrides, Ui: ui, } - installer := &mockProviderInstaller{ Providers: map[string][]string{ // looking for an exact version @@ -591,6 +594,36 @@ func TestInit_getProvider(t *testing.T) { if _, err := os.Stat(betweenPath); os.IsNotExist(err) { t.Fatal("provider 'between' not downloaded") } + + t.Run("future-state", func(t *testing.T) { + // getting providers should fail if a state from a newer version of + // terraform exists, since InitCommand.getProviders needs to inspect that + // state. + s := terraform.NewState() + s.TFVersion = "100.1.0" + local := &state.LocalState{ + Path: local.DefaultStateFilename, + } + if err := local.WriteState(s); err != nil { + t.Fatal(err) + } + + ui := new(cli.MockUi) + m.Ui = ui + c := &InitCommand{ + Meta: m, + providerInstaller: installer, + } + + if code := c.Run(nil); code == 0 { + t.Fatal("expected error, got:", ui.OutputWriter) + } + + errMsg := ui.ErrorWriter.String() + if !strings.Contains(errMsg, "future Terraform version") { + t.Fatal("unexpected error:", errMsg) + } + }) } // make sure we can locate providers in various paths diff --git a/terraform/context.go b/terraform/context.go index cede4f817d..53a12314a0 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -145,13 +145,8 @@ func NewContext(opts *ContextOpts) (*Context, error) { // If our state is from the future, then error. Callers can avoid // this error by explicitly setting `StateFutureAllowed`. - if !opts.StateFutureAllowed && state.FromFutureTerraform() { - return nil, fmt.Errorf( - "Terraform doesn't allow running any operations against a state\n"+ - "that was written by a future Terraform version. The state is\n"+ - "reporting it is written by Terraform '%s'.\n\n"+ - "Please run at least that version of Terraform to continue.", - state.TFVersion) + if err := CheckStateVersion(state); err != nil && !opts.StateFutureAllowed { + return nil, err } // Explicitly reset our state version to our current version so that diff --git a/terraform/state.go b/terraform/state.go index 5bc2f8a04f..6a363485e5 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -2174,6 +2174,19 @@ func (s moduleStateSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +// StateCompatible returns an error if the state is not compatible with the +// current version of terraform. +func CheckStateVersion(state *State) error { + if state == nil { + return nil + } + + if state.FromFutureTerraform() { + return fmt.Errorf(stateInvalidTerraformVersionErr, state.TFVersion) + } + return nil +} + const stateValidateErrMultiModule = ` Multiple modules with the same path: %s @@ -2182,3 +2195,11 @@ in your state file that point to the same module. This will cause Terraform to behave in unexpected and error prone ways and is invalid. Please back up and modify your state file manually to resolve this. ` + +const stateInvalidTerraformVersionErr = ` +Terraform doesn't allow running any operations against a state +that was written by a future Terraform version. The state is +reporting it is written by Terraform '%s' + +Please run at least that version of Terraform to continue. +`