command: use backend config from state when backend=false is used. (#23802)
* command: use backend config from state when backend=false is used. When a user runs `terraform init --backend=false`, terraform should inspect the state for a previously-configured backend, and use that backend, ignoring any backend config in the current configuration. If no backend is configured or there is no state, return a local backend. Fixes #16593
This commit is contained in:
parent
5d3ca8aaf1
commit
4d8fde3d6f
|
@ -295,6 +295,15 @@ func (c *InitCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
back = be
|
back = be
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// load the previously-stored backend config
|
||||||
|
be, backendDiags := c.Meta.backendFromState()
|
||||||
|
diags = diags.Append(backendDiags)
|
||||||
|
if backendDiags.HasErrors() {
|
||||||
|
c.showDiagnostics(diags)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
back = be
|
||||||
}
|
}
|
||||||
|
|
||||||
if back == nil {
|
if back == nil {
|
||||||
|
|
|
@ -562,6 +562,75 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// backendFromState returns the initialized (not configured) backend directly
|
||||||
|
// from the state. This should be used only when a user runs `terraform init
|
||||||
|
// -backend=false`. This function returns a local backend if there is no state
|
||||||
|
// or no backend configured.
|
||||||
|
func (m *Meta) backendFromState() (backend.Backend, tfdiags.Diagnostics) {
|
||||||
|
var diags tfdiags.Diagnostics
|
||||||
|
// Get the path to where we store a local cache of backend configuration
|
||||||
|
// if we're using a remote backend. This may not yet exist which means
|
||||||
|
// we haven't used a non-local backend before. That is okay.
|
||||||
|
statePath := filepath.Join(m.DataDir(), DefaultStateFilename)
|
||||||
|
sMgr := &state.LocalState{Path: statePath}
|
||||||
|
if err := sMgr.RefreshState(); err != nil {
|
||||||
|
diags = diags.Append(fmt.Errorf("Failed to load state: %s", err))
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
s := sMgr.State()
|
||||||
|
if s == nil {
|
||||||
|
// no state, so return a local backend
|
||||||
|
log.Printf("[TRACE] Meta.Backend: backend has not previously been initialized in this working directory")
|
||||||
|
return backendLocal.New(), diags
|
||||||
|
}
|
||||||
|
if s.Backend == nil {
|
||||||
|
// s.Backend is nil, so return a local backend
|
||||||
|
log.Printf("[TRACE] Meta.Backend: working directory was previously initialized but has no backend (is using legacy remote state?)")
|
||||||
|
return backendLocal.New(), diags
|
||||||
|
}
|
||||||
|
log.Printf("[TRACE] Meta.Backend: working directory was previously initialized for %q backend", s.Backend.Type)
|
||||||
|
|
||||||
|
//backend init function
|
||||||
|
if s.Backend.Type == "" {
|
||||||
|
return backendLocal.New(), diags
|
||||||
|
}
|
||||||
|
f := backendInit.Backend(s.Backend.Type)
|
||||||
|
if f == nil {
|
||||||
|
diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), s.Backend.Type))
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
b := f()
|
||||||
|
|
||||||
|
// The configuration saved in the working directory state file is used
|
||||||
|
// in this case, since it will contain any additional values that
|
||||||
|
// were provided via -backend-config arguments on terraform init.
|
||||||
|
schema := b.ConfigSchema()
|
||||||
|
configVal, err := s.Backend.Config(schema)
|
||||||
|
if err != nil {
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Failed to decode current backend config",
|
||||||
|
fmt.Sprintf("The backend configuration created by the most recent run of \"terraform init\" could not be decoded: %s. The configuration may have been initialized by an earlier version that used an incompatible configuration structure. Run \"terraform init -reconfigure\" to force re-initialization of the backend.", err),
|
||||||
|
))
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the config and then configure the backend
|
||||||
|
newVal, validDiags := b.PrepareConfig(configVal)
|
||||||
|
diags = diags.Append(validDiags)
|
||||||
|
if validDiags.HasErrors() {
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
configDiags := b.Configure(newVal)
|
||||||
|
diags = diags.Append(configDiags)
|
||||||
|
if configDiags.HasErrors() {
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, diags
|
||||||
|
}
|
||||||
|
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
// Backend Config Scenarios
|
// Backend Config Scenarios
|
||||||
//
|
//
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
|
|
||||||
backendInit "github.com/hashicorp/terraform/backend/init"
|
backendInit "github.com/hashicorp/terraform/backend/init"
|
||||||
backendLocal "github.com/hashicorp/terraform/backend/local"
|
backendLocal "github.com/hashicorp/terraform/backend/local"
|
||||||
|
backendInmem "github.com/hashicorp/terraform/backend/remote-state/inmem"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Test empty directory with no config/state creates a local state.
|
// Test empty directory with no config/state creates a local state.
|
||||||
|
@ -1771,7 +1772,7 @@ func TestMetaBackend_configureWithExtra(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// when confniguring a default local state, don't delete local state
|
// when configuring a default local state, don't delete local state
|
||||||
func TestMetaBackend_localDoesNotDeleteLocal(t *testing.T) {
|
func TestMetaBackend_localDoesNotDeleteLocal(t *testing.T) {
|
||||||
// Create a temporary working directory that is empty
|
// Create a temporary working directory that is empty
|
||||||
td := tempDir(t)
|
td := tempDir(t)
|
||||||
|
@ -1860,6 +1861,30 @@ func TestMetaBackend_configToExtra(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// no config; return inmem backend stored in state
|
||||||
|
func TestBackendFromState(t *testing.T) {
|
||||||
|
td := tempDir(t)
|
||||||
|
copy.CopyDir(testFixturePath("backend-from-state"), td)
|
||||||
|
defer os.RemoveAll(td)
|
||||||
|
defer testChdir(t, td)()
|
||||||
|
|
||||||
|
// Setup the meta
|
||||||
|
m := testMetaBackend(t, nil)
|
||||||
|
// terraform caches a small "state" file that stores the backend config.
|
||||||
|
// This test must override m.dataDir so it loads the "terraform.tfstate" file in the
|
||||||
|
// test directory as the backend config cache
|
||||||
|
m.OverrideDataDir = td
|
||||||
|
|
||||||
|
stateBackend, diags := m.backendFromState()
|
||||||
|
if diags.HasErrors() {
|
||||||
|
t.Fatal(diags.Err())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := stateBackend.(*backendInmem.Backend); !ok {
|
||||||
|
t.Fatal("did not get expected inmem backend")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func testMetaBackend(t *testing.T, args []string) *Meta {
|
func testMetaBackend(t *testing.T, args []string) *Meta {
|
||||||
var m Meta
|
var m Meta
|
||||||
m.Ui = new(cli.MockUi)
|
m.Ui = new(cli.MockUi)
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"terraform_version": "0.12.0",
|
||||||
|
"serial": 7,
|
||||||
|
"lineage": "configured",
|
||||||
|
"backend": {
|
||||||
|
"type": "inmem",
|
||||||
|
"config": {}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue