Merge pull request #28718 from hashicorp/jbardin/backend-migrate
Prevent automatic backend migration during `terraform init`
This commit is contained in:
commit
f5e0d13079
|
@ -45,6 +45,7 @@ func (c *InitCommand) Run(args []string) int {
|
||||||
cmdFlags.BoolVar(&flagGet, "get", true, "")
|
cmdFlags.BoolVar(&flagGet, "get", true, "")
|
||||||
cmdFlags.BoolVar(&c.forceInitCopy, "force-copy", false, "suppress prompts about copying state data")
|
cmdFlags.BoolVar(&c.forceInitCopy, "force-copy", false, "suppress prompts about copying state data")
|
||||||
cmdFlags.BoolVar(&c.reconfigure, "reconfigure", false, "reconfigure")
|
cmdFlags.BoolVar(&c.reconfigure, "reconfigure", false, "reconfigure")
|
||||||
|
cmdFlags.BoolVar(&c.migrateState, "migrate-state", false, "migrate state")
|
||||||
cmdFlags.BoolVar(&flagUpgrade, "upgrade", false, "")
|
cmdFlags.BoolVar(&flagUpgrade, "upgrade", false, "")
|
||||||
cmdFlags.Var(&flagPluginPath, "plugin-dir", "plugin directory")
|
cmdFlags.Var(&flagPluginPath, "plugin-dir", "plugin directory")
|
||||||
cmdFlags.StringVar(&flagLockfile, "lockfile", "", "Set a dependency lockfile mode")
|
cmdFlags.StringVar(&flagLockfile, "lockfile", "", "Set a dependency lockfile mode")
|
||||||
|
@ -53,6 +54,17 @@ func (c *InitCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.migrateState && c.reconfigure {
|
||||||
|
c.Ui.Error("The -migrate-state and -reconfigure options are mutually-exclusive")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copying the state only happens during backend migration, so setting
|
||||||
|
// -force-copy implies -migrate-state
|
||||||
|
if c.forceInitCopy {
|
||||||
|
c.migrateState = true
|
||||||
|
}
|
||||||
|
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
if len(flagPluginPath) > 0 {
|
if len(flagPluginPath) > 0 {
|
||||||
|
@ -926,6 +938,7 @@ func (c *InitCommand) AutocompleteFlags() complete.Flags {
|
||||||
"-no-color": complete.PredictNothing,
|
"-no-color": complete.PredictNothing,
|
||||||
"-plugin-dir": complete.PredictDirs(""),
|
"-plugin-dir": complete.PredictDirs(""),
|
||||||
"-reconfigure": complete.PredictNothing,
|
"-reconfigure": complete.PredictNothing,
|
||||||
|
"-migrate-state": complete.PredictNothing,
|
||||||
"-upgrade": completePredictBoolean,
|
"-upgrade": completePredictBoolean,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -980,6 +993,9 @@ Options:
|
||||||
-reconfigure Reconfigure the backend, ignoring any saved
|
-reconfigure Reconfigure the backend, ignoring any saved
|
||||||
configuration.
|
configuration.
|
||||||
|
|
||||||
|
-migrate-state Reconfigure the backend, and attempt to migrate any
|
||||||
|
existing state.
|
||||||
|
|
||||||
-upgrade=false If installing modules (-get) or plugins, ignore
|
-upgrade=false If installing modules (-get) or plugins, ignore
|
||||||
previously-downloaded objects and install the
|
previously-downloaded objects and install the
|
||||||
latest version allowed within configured constraints.
|
latest version allowed within configured constraints.
|
||||||
|
|
|
@ -412,7 +412,7 @@ func TestInit_backendConfigFile(t *testing.T) {
|
||||||
View: view,
|
View: view,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
args := []string{"-backend-config="}
|
args := []string{"-backend-config=", "-migrate-state"}
|
||||||
if code := c.Run(args); code != 0 {
|
if code := c.Run(args); code != 0 {
|
||||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||||
}
|
}
|
||||||
|
@ -555,7 +555,7 @@ func TestInit_backendConfigFileChange(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{"-backend-config", "input.config"}
|
args := []string{"-backend-config", "input.config", "-migrate-state"}
|
||||||
if code := c.Run(args); code != 0 {
|
if code := c.Run(args); code != 0 {
|
||||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||||
}
|
}
|
||||||
|
@ -644,7 +644,7 @@ func TestInit_backendConfigKVReInit(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// override the -backend-config options by settings
|
// override the -backend-config options by settings
|
||||||
args = []string{"-input=false", "-backend-config", ""}
|
args = []string{"-input=false", "-backend-config", "", "-migrate-state"}
|
||||||
if code := c.Run(args); code != 0 {
|
if code := c.Run(args); code != 0 {
|
||||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||||
}
|
}
|
||||||
|
@ -906,7 +906,7 @@ func TestInit_inputFalse(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
args = []string{"-input=false", "-backend-config=path=bar"}
|
args = []string{"-input=false", "-backend-config=path=bar", "-migrate-state"}
|
||||||
if code := c.Run(args); code == 0 {
|
if code := c.Run(args); code == 0 {
|
||||||
t.Fatal("init should have failed", ui.OutputWriter)
|
t.Fatal("init should have failed", ui.OutputWriter)
|
||||||
}
|
}
|
||||||
|
|
|
@ -204,6 +204,9 @@ type Meta struct {
|
||||||
//
|
//
|
||||||
// reconfigure forces init to ignore any stored configuration.
|
// reconfigure forces init to ignore any stored configuration.
|
||||||
//
|
//
|
||||||
|
// migrateState confirms the user wishes to migrate from the prior backend
|
||||||
|
// configuration to a new configuration.
|
||||||
|
//
|
||||||
// compactWarnings (-compact-warnings) selects a more compact presentation
|
// compactWarnings (-compact-warnings) selects a more compact presentation
|
||||||
// of warnings in the output when they are not accompanied by errors.
|
// of warnings in the output when they are not accompanied by errors.
|
||||||
statePath string
|
statePath string
|
||||||
|
@ -214,6 +217,7 @@ type Meta struct {
|
||||||
stateLockTimeout time.Duration
|
stateLockTimeout time.Duration
|
||||||
forceInitCopy bool
|
forceInitCopy bool
|
||||||
reconfigure bool
|
reconfigure bool
|
||||||
|
migrateState bool
|
||||||
compactWarnings bool
|
compactWarnings bool
|
||||||
|
|
||||||
// Used with the import command to allow import of state when no matching config exists.
|
// Used with the import command to allow import of state when no matching config exists.
|
||||||
|
|
|
@ -6,7 +6,6 @@ package command
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -514,12 +513,19 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
|
||||||
// We're unsetting a backend (moving from backend => local)
|
// We're unsetting a backend (moving from backend => local)
|
||||||
case c == nil && !s.Backend.Empty():
|
case c == nil && !s.Backend.Empty():
|
||||||
log.Printf("[TRACE] Meta.Backend: previously-initialized %q backend is no longer present in config", s.Backend.Type)
|
log.Printf("[TRACE] Meta.Backend: previously-initialized %q backend is no longer present in config", s.Backend.Type)
|
||||||
|
|
||||||
|
initReason := fmt.Sprintf("Unsetting the previously set backend %q", s.Backend.Type)
|
||||||
if !opts.Init {
|
if !opts.Init {
|
||||||
initReason := fmt.Sprintf(
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
"Unsetting the previously set backend %q",
|
tfdiags.Error,
|
||||||
s.Backend.Type)
|
"Backend initialization required, please run \"terraform init\"",
|
||||||
m.backendInitRequired(initReason)
|
fmt.Sprintf(strings.TrimSpace(errBackendInit), initReason),
|
||||||
diags = diags.Append(errBackendInitRequired)
|
))
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
if !m.migrateState {
|
||||||
|
diags = diags.Append(migrateOrReconfigDiag)
|
||||||
return nil, diags
|
return nil, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,11 +535,12 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
|
||||||
case c != nil && s.Backend.Empty():
|
case c != nil && s.Backend.Empty():
|
||||||
log.Printf("[TRACE] Meta.Backend: moving from default local state only to %q backend", c.Type)
|
log.Printf("[TRACE] Meta.Backend: moving from default local state only to %q backend", c.Type)
|
||||||
if !opts.Init {
|
if !opts.Init {
|
||||||
initReason := fmt.Sprintf(
|
initReason := fmt.Sprintf("Initial configuration of the requested backend %q", c.Type)
|
||||||
"Initial configuration of the requested backend %q",
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
c.Type)
|
tfdiags.Error,
|
||||||
m.backendInitRequired(initReason)
|
"Backend initialization required, please run \"terraform init\"",
|
||||||
diags = diags.Append(errBackendInitRequired)
|
fmt.Sprintf(strings.TrimSpace(errBackendInit), initReason),
|
||||||
|
))
|
||||||
return nil, diags
|
return nil, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -558,12 +565,22 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
|
||||||
}
|
}
|
||||||
log.Printf("[TRACE] Meta.Backend: backend configuration has changed (from type %q to type %q)", s.Backend.Type, c.Type)
|
log.Printf("[TRACE] Meta.Backend: backend configuration has changed (from type %q to type %q)", s.Backend.Type, c.Type)
|
||||||
|
|
||||||
|
initReason := fmt.Sprintf("Backend configuration changed for %q", c.Type)
|
||||||
|
if s.Backend.Type != c.Type {
|
||||||
|
initReason = fmt.Sprintf("Backend configuration changed from %q to %q", s.Backend.Type, c.Type)
|
||||||
|
}
|
||||||
|
|
||||||
if !opts.Init {
|
if !opts.Init {
|
||||||
initReason := fmt.Sprintf(
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
"Backend configuration changed for %q",
|
tfdiags.Error,
|
||||||
c.Type)
|
"Backend initialization required, please run \"terraform init\"",
|
||||||
m.backendInitRequired(initReason)
|
fmt.Sprintf(strings.TrimSpace(errBackendInit), initReason),
|
||||||
diags = diags.Append(errBackendInitRequired)
|
))
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
if !m.migrateState {
|
||||||
|
diags = diags.Append(migrateOrReconfigDiag)
|
||||||
return nil, diags
|
return nil, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1097,11 +1114,6 @@ func (m *Meta) backendInitFromConfig(c *configs.Backend) (backend.Backend, cty.V
|
||||||
return b, configVal, diags
|
return b, configVal, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Meta) backendInitRequired(reason string) {
|
|
||||||
m.Ui.Output(m.Colorize().Color(fmt.Sprintf(
|
|
||||||
"[reset]"+strings.TrimSpace(errBackendInit)+"\n", reason)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper method to ignore remote backend version conflicts. Only call this
|
// Helper method to ignore remote backend version conflicts. Only call this
|
||||||
// for commands which cannot accidentally upgrade remote state files.
|
// for commands which cannot accidentally upgrade remote state files.
|
||||||
func (m *Meta) ignoreRemoteBackendVersionConflict(b backend.Backend) {
|
func (m *Meta) ignoreRemoteBackendVersionConflict(b backend.Backend) {
|
||||||
|
@ -1138,11 +1150,6 @@ func (m *Meta) remoteBackendVersionCheck(b backend.Backend, workspace string) tf
|
||||||
// Output constants and initialization code
|
// Output constants and initialization code
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
|
|
||||||
// errBackendInitRequired is the final error message shown when reinit
|
|
||||||
// is required for some reason. The error message includes the reason.
|
|
||||||
var errBackendInitRequired = errors.New(
|
|
||||||
"Initialization required. Please see the error message above.")
|
|
||||||
|
|
||||||
const errBackendLocalRead = `
|
const errBackendLocalRead = `
|
||||||
Error reading local state: %s
|
Error reading local state: %s
|
||||||
|
|
||||||
|
@ -1205,8 +1212,7 @@ and try again.
|
||||||
`
|
`
|
||||||
|
|
||||||
const errBackendInit = `
|
const errBackendInit = `
|
||||||
[reset][bold][yellow]Backend reinitialization required. Please run "terraform init".[reset]
|
Reason: %s
|
||||||
[yellow]Reason: %s
|
|
||||||
|
|
||||||
The "backend" is the interface that Terraform uses to store state,
|
The "backend" is the interface that Terraform uses to store state,
|
||||||
perform operations, etc. If this message is showing up, it means that the
|
perform operations, etc. If this message is showing up, it means that the
|
||||||
|
@ -1214,8 +1220,9 @@ Terraform configuration you're using is using a custom configuration for
|
||||||
the Terraform backend.
|
the Terraform backend.
|
||||||
|
|
||||||
Changes to backend configurations require reinitialization. This allows
|
Changes to backend configurations require reinitialization. This allows
|
||||||
Terraform to set up the new configuration, copy existing state, etc. This is
|
Terraform to set up the new configuration, copy existing state, etc. Please run
|
||||||
only done during "terraform init". Please run that command now then try again.
|
"terraform init" with either the "-reconfigure" or "-migrate-state" flags to
|
||||||
|
use the current configuration.
|
||||||
|
|
||||||
If the change reason above is incorrect, please verify your configuration
|
If the change reason above is incorrect, please verify your configuration
|
||||||
hasn't changed and try again. At this point, no changes to your existing
|
hasn't changed and try again. At this point, no changes to your existing
|
||||||
|
@ -1254,3 +1261,10 @@ const successBackendSet = `
|
||||||
Successfully configured the backend %q! Terraform will automatically
|
Successfully configured the backend %q! Terraform will automatically
|
||||||
use this backend unless the backend configuration changes.
|
use this backend unless the backend configuration changes.
|
||||||
`
|
`
|
||||||
|
|
||||||
|
var migrateOrReconfigDiag = tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Backend configuration changed",
|
||||||
|
"A change in the backend configuration has been detected, which may require migrating existing state.\n\n"+
|
||||||
|
"If you wish to attempt automatic migration of the state, use \"terraform init -migrate-state\".\n"+
|
||||||
|
`If you wish to store the current configuration with no changes to the state, use "terraform init -reconfigure".`)
|
||||||
|
|
|
@ -314,6 +314,10 @@ func TestMetaBackend_configureNewWithState(t *testing.T) {
|
||||||
// Setup the meta
|
// Setup the meta
|
||||||
m := testMetaBackend(t, nil)
|
m := testMetaBackend(t, nil)
|
||||||
|
|
||||||
|
// This combination should not require the extra -migrate-state flag, since
|
||||||
|
// there is no existing backend config
|
||||||
|
m.migrateState = false
|
||||||
|
|
||||||
// Get the backend
|
// Get the backend
|
||||||
b, diags := m.Backend(&BackendOpts{Init: true})
|
b, diags := m.Backend(&BackendOpts{Init: true})
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
|
@ -1884,5 +1888,8 @@ func testMetaBackend(t *testing.T, args []string) *Meta {
|
||||||
t.Fatalf("unexpected error: %s", err)
|
t.Fatalf("unexpected error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// metaBackend tests are verifying migrate actions
|
||||||
|
m.migrateState = true
|
||||||
|
|
||||||
return &m
|
return &m
|
||||||
}
|
}
|
||||||
|
|
|
@ -400,7 +400,7 @@ func TestStateRm_needsInit(t *testing.T) {
|
||||||
t.Fatalf("expected error output, got:\n%s", ui.OutputWriter.String())
|
t.Fatalf("expected error output, got:\n%s", ui.OutputWriter.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(ui.ErrorWriter.String(), "Initialization") {
|
if !strings.Contains(ui.ErrorWriter.String(), "Backend initialization") {
|
||||||
t.Fatalf("expected initialization error, got:\n%s", ui.ErrorWriter.String())
|
t.Fatalf("expected initialization error, got:\n%s", ui.ErrorWriter.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,11 +79,17 @@ During init, the root configuration directory is consulted for
|
||||||
is initialized using the given configuration settings.
|
is initialized using the given configuration settings.
|
||||||
|
|
||||||
Re-running init with an already-initialized backend will update the working
|
Re-running init with an already-initialized backend will update the working
|
||||||
directory to use the new backend settings. Depending on what changed, this
|
directory to use the new backend settings. Either `-reconfigure` or
|
||||||
may result in interactive prompts to confirm migration of workspace states.
|
`-migrate-state` must be supplied to update the backend configuration.
|
||||||
The `-force-copy` option suppresses these prompts and answers "yes" to the
|
|
||||||
migration questions. The `-reconfigure` option disregards any existing
|
The `-migrate-state` option will attempt to copy existing state to the new
|
||||||
configuration, preventing migration of any existing state.
|
backend, and depending on what changed, may result in interactive prompts to
|
||||||
|
confirm migration of workspace states. The `-force-copy` option suppresses
|
||||||
|
these prompts and answers "yes" to the migration questions. This implies
|
||||||
|
`-migrate-state`.
|
||||||
|
|
||||||
|
The `-reconfigure` option disregards any existing configuration, preventing
|
||||||
|
migration of any existing state.
|
||||||
|
|
||||||
To skip backend configuration, use `-backend=false`. Note that some other init
|
To skip backend configuration, use `-backend=false`. Note that some other init
|
||||||
steps require an initialized backend, so it is recommended to use this flag only
|
steps require an initialized backend, so it is recommended to use this flag only
|
||||||
|
|
Loading…
Reference in New Issue