check state version during init

The init command needs to parse the state to resolve providers, but
changes to the state format can cause that to fail with difficult to
understand errors. Check the terraform version during init and provide
the same error that would be returned by plan or apply.
This commit is contained in:
James Bardin 2017-12-14 15:46:43 -05:00
parent 03ddb9134a
commit ea4cb6a20e
4 changed files with 64 additions and 10 deletions

View File

@ -180,7 +180,6 @@ func (c *InitCommand) Run(args []string) int {
"Error downloading modules: %s", err)) "Error downloading modules: %s", err))
return 1 return 1
} }
} }
// If we're requesting backend configuration or looking for required // 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() 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 { if err := terraform.CheckRequiredVersion(mod); err != nil {
diags = diags.Append(err) diags = diags.Append(err)
c.showDiagnostics(diags) c.showDiagnostics(diags)

View File

@ -11,8 +11,11 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/hashicorp/terraform/backend/local"
"github.com/hashicorp/terraform/helper/copy" "github.com/hashicorp/terraform/helper/copy"
"github.com/hashicorp/terraform/plugin/discovery" "github.com/hashicorp/terraform/plugin/discovery"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
@ -543,12 +546,12 @@ func TestInit_getProvider(t *testing.T) {
defer os.RemoveAll(td) defer os.RemoveAll(td)
defer testChdir(t, td)() defer testChdir(t, td)()
overrides := metaOverridesForProvider(testProvider())
ui := new(cli.MockUi) ui := new(cli.MockUi)
m := Meta{ m := Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: overrides,
Ui: ui, Ui: ui,
} }
installer := &mockProviderInstaller{ installer := &mockProviderInstaller{
Providers: map[string][]string{ Providers: map[string][]string{
// looking for an exact version // looking for an exact version
@ -591,6 +594,36 @@ func TestInit_getProvider(t *testing.T) {
if _, err := os.Stat(betweenPath); os.IsNotExist(err) { if _, err := os.Stat(betweenPath); os.IsNotExist(err) {
t.Fatal("provider 'between' not downloaded") 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 // make sure we can locate providers in various paths

View File

@ -145,13 +145,8 @@ func NewContext(opts *ContextOpts) (*Context, error) {
// If our state is from the future, then error. Callers can avoid // If our state is from the future, then error. Callers can avoid
// this error by explicitly setting `StateFutureAllowed`. // this error by explicitly setting `StateFutureAllowed`.
if !opts.StateFutureAllowed && state.FromFutureTerraform() { if err := CheckStateVersion(state); err != nil && !opts.StateFutureAllowed {
return nil, fmt.Errorf( return nil, err
"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)
} }
// Explicitly reset our state version to our current version so that // Explicitly reset our state version to our current version so that

View File

@ -2174,6 +2174,19 @@ func (s moduleStateSort) Swap(i, j int) {
s[i], s[j] = s[j], s[i] 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 = ` const stateValidateErrMultiModule = `
Multiple modules with the same path: %s 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 to behave in unexpected and error prone ways and is invalid. Please back up
and modify your state file manually to resolve this. 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.
`