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:
parent
03ddb9134a
commit
ea4cb6a20e
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
`
|
||||||
|
|
Loading…
Reference in New Issue