PSS: Allow use of pluggable state stores with `-backend=false` during `init` commands (#38066)

pull/38085/head
Sarah French 1 month ago committed by GitHub
parent b926263c03
commit 44e5f86375
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -199,7 +199,6 @@ func TestInit_two_step_provider_download(t *testing.T) {
for tn, tc := range cases {
t.Run(tn, func(t *testing.T) {
// Create a temporary working directory no tf configuration but has state
td := t.TempDir()
testCopyDir(t, testFixturePath(tc.workDirPath), td)
@ -1578,7 +1577,6 @@ prompts.
t.Errorf("wrong error output\n%s", diff)
}
})
}
// make sure inputFalse stops execution on migrate
@ -3858,6 +3856,67 @@ func TestInit_stateStore_configChanges(t *testing.T) {
}
})
t.Run("the -backend=false flag makes Terraform ignore config and use only the the backend state file during initialization", func(t *testing.T) {
// Create a temporary working directory with state store configuration
// that doesn't match the backend state file
td := t.TempDir()
testCopyDir(t, testFixturePath("state-store-changed/store-config"), td)
t.Chdir(td)
mockProvider := mockPluggableStateStorageProvider()
// The previous init implied by this test scenario would have created this.
mockProvider.GetStatesResponse = &providers.GetStatesResponse{States: []string{"default"}}
mockProvider.MockStates = map[string]interface{}{"default": []byte(`{"version": 4,"terraform_version":"1.15.0","serial": 1,"lineage": "","outputs": {},"resources": [],"checks":[]}`)}
mockProviderAddress := addrs.NewDefaultProvider("test")
providerSource, close := newMockProviderSource(t, map[string][]string{
"hashicorp/test": {"1.2.3"}, // Matches provider version in backend state file fixture
})
defer close()
ui := new(cli.MockUi)
view, done := testView(t)
meta := Meta{
Ui: ui,
View: view,
AllowExperimentalFeatures: true,
testingOverrides: &testingOverrides{
Providers: map[addrs.Provider]providers.Factory{
mockProviderAddress: providers.FactoryFixed(mockProvider),
},
},
ProviderSource: providerSource,
}
c := &InitCommand{
Meta: meta,
}
args := []string{
"-enable-pluggable-state-storage-experiment=true",
"-backend=false",
}
code := c.Run(args)
testOutput := done(t)
if code != 0 {
t.Fatalf("expected code 0 exit code, got %d, output: \n%s", code, testOutput.All())
}
// Check output
output := testOutput.All()
expectedOutput := "Terraform has been successfully initialized!"
if !strings.Contains(output, expectedOutput) {
t.Fatalf("expected output to include %q, but got':\n %s", expectedOutput, output)
}
// When -backend=false the backend/state store isn't initialized, so we don't expect this
// output if the flag has the expected effect on Terraform.
unexpectedOutput := "Initializing the state store..."
if strings.Contains(output, unexpectedOutput) {
t.Fatalf("output included %q, which is unexpected if -backend=false is behaving correctly':\n %s", unexpectedOutput, output)
}
})
t.Run("handling changed state store config is currently unimplemented", func(t *testing.T) {
// Create a temporary working directory with state store configuration
// that doesn't match the backend state file
@ -3905,7 +3964,6 @@ func TestInit_stateStore_configChanges(t *testing.T) {
if !strings.Contains(output, expectedMsg) {
t.Fatalf("expected output to include %q, but got':\n %s", expectedMsg, output)
}
})
t.Run("handling changed state store provider config is currently unimplemented", func(t *testing.T) {
@ -4504,7 +4562,7 @@ func TestInit_stateStore_to_backend(t *testing.T) {
t.Fatal(err)
}
expectedOutputs := map[string]*states.OutputValue{
"test": &states.OutputValue{
"test": {
Addr: addrs.AbsOutputValue{
OutputValue: addrs.OutputValue{
Name: "test",

@ -1166,7 +1166,7 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
cloudMode := cloud.DetectConfigChangeType(s.Backend, backendConfig, false)
if !opts.Init {
//user ran another cmd that is not init but they are required to initialize because of a potential relevant change to their backend configuration
// user ran another cmd that is not init but they are required to initialize because of a potential relevant change to their backend configuration
initDiag := m.determineInitReason(s.Backend.Type, backendConfig.Type, cloudMode)
diags = diags.Append(initDiag)
return nil, diags
@ -1293,60 +1293,75 @@ func (m *Meta) backendFromState(_ context.Context) (backend.Backend, tfdiags.Dia
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(errBackendSavedUnknown{s.Backend.Type})
return nil, diags
}
b := f()
// Depending on the contents of the backend state file,
// prepare a backend.Backend in the appropriate way.
var b backend.Backend
switch {
case !s.StateStore.Empty():
// state_store
log.Printf("[TRACE] Meta.Backend: working directory was previously initialized for %q state store", s.StateStore.Type)
var ssDiags tfdiags.Diagnostics
b, ssDiags = m.savedStateStore(sMgr) // Relies on the state manager's internal state being refreshed above.
diags = diags.Append(ssDiags)
if ssDiags.HasErrors() {
return nil, diags
}
case !s.Backend.Empty():
// backend or cloud
if s.Backend.Type == "" {
return backendLocal.New(), diags
}
f := backendInit.Backend(s.Backend.Type)
if f == nil {
diags = diags.Append(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
}
// 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
}
// 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
}
configDiags := b.Configure(newVal)
diags = diags.Append(configDiags)
if configDiags.HasErrors() {
return nil, diags
}
// If the result of loading the backend is an enhanced backend,
// then set up enhanced backend service aliases.
if enhanced, ok := b.(backendrun.OperationsBackend); ok {
log.Printf("[TRACE] Meta.BackendForPlan: backend %T supports operations", b)
// If the result of loading the backend is an enhanced backend,
// then set up enhanced backend service aliases.
if enhanced, ok := b.(backendrun.OperationsBackend); ok {
log.Printf("[TRACE] Meta.BackendForPlan: backend %T supports operations", b)
if err := m.setupEnhancedBackendAliases(enhanced); err != nil {
diags = diags.Append(err)
return nil, diags
if err := m.setupEnhancedBackendAliases(enhanced); err != nil {
diags = diags.Append(err)
return nil, diags
}
}
log.Printf("[TRACE] Meta.Backend: working directory was previously initialized for %q backend", s.Backend.Type)
default:
// s.StateStore and s.Backend are empty, so return a local backend
log.Printf("[TRACE] Meta.Backend: working directory was previously initialized but has no backend (is using legacy remote state?)")
b = backendLocal.New()
}
return b, diags
@ -1371,8 +1386,8 @@ func (m *Meta) backendFromState(_ context.Context) (backend.Backend, tfdiags.Dia
// Unconfiguring a backend (moving from backend => local).
func (m *Meta) backend_c_r_S(
c *configs.Backend, cHash int, sMgr *clistate.LocalState, output bool, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) {
c *configs.Backend, cHash int, sMgr *clistate.LocalState, output bool, opts *BackendOpts,
) (backend.Backend, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
vt := arguments.ViewJSON

Loading…
Cancel
Save