Merge pull request #28718 from hashicorp/jbardin/backend-migrate

Prevent automatic backend migration during `terraform init`
This commit is contained in:
James Bardin 2021-05-17 14:42:35 -04:00 committed by GitHub
commit f5e0d13079
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 87 additions and 40 deletions

View File

@ -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.

View File

@ -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)
}

View File

@ -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.

View File

@ -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".`)

View File

@ -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
}

View File

@ -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())
}
}

View File

@ -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