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(&c.forceInitCopy, "force-copy", false, "suppress prompts about copying state data")
|
||||
cmdFlags.BoolVar(&c.reconfigure, "reconfigure", false, "reconfigure")
|
||||
cmdFlags.BoolVar(&c.migrateState, "migrate-state", false, "migrate state")
|
||||
cmdFlags.BoolVar(&flagUpgrade, "upgrade", false, "")
|
||||
cmdFlags.Var(&flagPluginPath, "plugin-dir", "plugin directory")
|
||||
cmdFlags.StringVar(&flagLockfile, "lockfile", "", "Set a dependency lockfile mode")
|
||||
|
@ -53,6 +54,17 @@ func (c *InitCommand) Run(args []string) int {
|
|||
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
|
||||
|
||||
if len(flagPluginPath) > 0 {
|
||||
|
@ -926,6 +938,7 @@ func (c *InitCommand) AutocompleteFlags() complete.Flags {
|
|||
"-no-color": complete.PredictNothing,
|
||||
"-plugin-dir": complete.PredictDirs(""),
|
||||
"-reconfigure": complete.PredictNothing,
|
||||
"-migrate-state": complete.PredictNothing,
|
||||
"-upgrade": completePredictBoolean,
|
||||
}
|
||||
}
|
||||
|
@ -980,6 +993,9 @@ Options:
|
|||
-reconfigure Reconfigure the backend, ignoring any saved
|
||||
configuration.
|
||||
|
||||
-migrate-state Reconfigure the backend, and attempt to migrate any
|
||||
existing state.
|
||||
|
||||
-upgrade=false If installing modules (-get) or plugins, ignore
|
||||
previously-downloaded objects and install the
|
||||
latest version allowed within configured constraints.
|
||||
|
|
|
@ -412,7 +412,7 @@ func TestInit_backendConfigFile(t *testing.T) {
|
|||
View: view,
|
||||
},
|
||||
}
|
||||
args := []string{"-backend-config="}
|
||||
args := []string{"-backend-config=", "-migrate-state"}
|
||||
if code := c.Run(args); code != 0 {
|
||||
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 {
|
||||
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
|
||||
args = []string{"-input=false", "-backend-config", ""}
|
||||
args = []string{"-input=false", "-backend-config", "", "-migrate-state"}
|
||||
if code := c.Run(args); code != 0 {
|
||||
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 {
|
||||
t.Fatal("init should have failed", ui.OutputWriter)
|
||||
}
|
||||
|
|
|
@ -204,6 +204,9 @@ type Meta struct {
|
|||
//
|
||||
// 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
|
||||
// of warnings in the output when they are not accompanied by errors.
|
||||
statePath string
|
||||
|
@ -214,6 +217,7 @@ type Meta struct {
|
|||
stateLockTimeout time.Duration
|
||||
forceInitCopy bool
|
||||
reconfigure bool
|
||||
migrateState bool
|
||||
compactWarnings bool
|
||||
|
||||
// Used with the import command to allow import of state when no matching config exists.
|
||||
|
|
|
@ -6,7 +6,6 @@ package command
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"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)
|
||||
case c == nil && !s.Backend.Empty():
|
||||
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 {
|
||||
initReason := fmt.Sprintf(
|
||||
"Unsetting the previously set backend %q",
|
||||
s.Backend.Type)
|
||||
m.backendInitRequired(initReason)
|
||||
diags = diags.Append(errBackendInitRequired)
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Backend initialization required, please run \"terraform init\"",
|
||||
fmt.Sprintf(strings.TrimSpace(errBackendInit), initReason),
|
||||
))
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
if !m.migrateState {
|
||||
diags = diags.Append(migrateOrReconfigDiag)
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
|
@ -529,11 +535,12 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
|
|||
case c != nil && s.Backend.Empty():
|
||||
log.Printf("[TRACE] Meta.Backend: moving from default local state only to %q backend", c.Type)
|
||||
if !opts.Init {
|
||||
initReason := fmt.Sprintf(
|
||||
"Initial configuration of the requested backend %q",
|
||||
c.Type)
|
||||
m.backendInitRequired(initReason)
|
||||
diags = diags.Append(errBackendInitRequired)
|
||||
initReason := fmt.Sprintf("Initial configuration of the requested backend %q", c.Type)
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Backend initialization required, please run \"terraform init\"",
|
||||
fmt.Sprintf(strings.TrimSpace(errBackendInit), initReason),
|
||||
))
|
||||
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)
|
||||
|
||||
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 {
|
||||
initReason := fmt.Sprintf(
|
||||
"Backend configuration changed for %q",
|
||||
c.Type)
|
||||
m.backendInitRequired(initReason)
|
||||
diags = diags.Append(errBackendInitRequired)
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Backend initialization required, please run \"terraform init\"",
|
||||
fmt.Sprintf(strings.TrimSpace(errBackendInit), initReason),
|
||||
))
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
if !m.migrateState {
|
||||
diags = diags.Append(migrateOrReconfigDiag)
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
|
@ -1097,11 +1114,6 @@ func (m *Meta) backendInitFromConfig(c *configs.Backend) (backend.Backend, cty.V
|
|||
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
|
||||
// for commands which cannot accidentally upgrade remote state files.
|
||||
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
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
// 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 = `
|
||||
Error reading local state: %s
|
||||
|
||||
|
@ -1205,8 +1212,7 @@ and try again.
|
|||
`
|
||||
|
||||
const errBackendInit = `
|
||||
[reset][bold][yellow]Backend reinitialization required. Please run "terraform init".[reset]
|
||||
[yellow]Reason: %s
|
||||
Reason: %s
|
||||
|
||||
The "backend" is the interface that Terraform uses to store state,
|
||||
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.
|
||||
|
||||
Changes to backend configurations require reinitialization. This allows
|
||||
Terraform to set up the new configuration, copy existing state, etc. This is
|
||||
only done during "terraform init". Please run that command now then try again.
|
||||
Terraform to set up the new configuration, copy existing state, etc. Please run
|
||||
"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
|
||||
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
|
||||
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
|
||||
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
|
||||
b, diags := m.Backend(&BackendOpts{Init: true})
|
||||
if diags.HasErrors() {
|
||||
|
@ -1884,5 +1888,8 @@ func testMetaBackend(t *testing.T, args []string) *Meta {
|
|||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
// metaBackend tests are verifying migrate actions
|
||||
m.migrateState = true
|
||||
|
||||
return &m
|
||||
}
|
||||
|
|
|
@ -400,7 +400,7 @@ func TestStateRm_needsInit(t *testing.T) {
|
|||
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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,11 +79,17 @@ During init, the root configuration directory is consulted for
|
|||
is initialized using the given configuration settings.
|
||||
|
||||
Re-running init with an already-initialized backend will update the working
|
||||
directory to use the new backend settings. Depending on what changed, this
|
||||
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. The `-reconfigure` option disregards any existing
|
||||
configuration, preventing migration of any existing state.
|
||||
directory to use the new backend settings. Either `-reconfigure` or
|
||||
`-migrate-state` must be supplied to update the backend configuration.
|
||||
|
||||
The `-migrate-state` option will attempt to copy existing state to the new
|
||||
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
|
||||
steps require an initialized backend, so it is recommended to use this flag only
|
||||
|
|
Loading…
Reference in New Issue