Skip to content

Commit 5001deb

Browse files
PSS: Ensure experimental backend codepath is isolated + gated correctly (#37868)
* PSS: Ensure experimental backend codepath is isolated + gated correctly * Update init_run.go Co-authored-by: Sarah French <[email protected]> --------- Co-authored-by: Sarah French <[email protected]>
1 parent 39eb8c7 commit 5001deb

File tree

3 files changed

+228
-132
lines changed

3 files changed

+228
-132
lines changed

internal/command/init.go

Lines changed: 19 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ import (
77
"context"
88
"fmt"
99
"log"
10-
"maps"
1110
"reflect"
12-
"slices"
1311
"sort"
1412
"strings"
1513

@@ -28,7 +26,6 @@ import (
2826
"github.com/hashicorp/terraform/internal/configs"
2927
"github.com/hashicorp/terraform/internal/configs/configschema"
3028
"github.com/hashicorp/terraform/internal/depsfile"
31-
"github.com/hashicorp/terraform/internal/didyoumean"
3229
"github.com/hashicorp/terraform/internal/getproviders"
3330
"github.com/hashicorp/terraform/internal/getproviders/providerreqs"
3431
"github.com/hashicorp/terraform/internal/providercache"
@@ -155,104 +152,16 @@ func (c *InitCommand) initCloud(ctx context.Context, root *configs.Module, extra
155152
return back, true, diags
156153
}
157154

158-
func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, initArgs *arguments.Init, configLocks *depsfile.Locks, view views.Init) (be backend.Backend, output bool, diags tfdiags.Diagnostics) {
155+
func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, extraConfig arguments.FlagNameValueSlice, viewType arguments.ViewType, view views.Init) (be backend.Backend, output bool, diags tfdiags.Diagnostics) {
159156
ctx, span := tracer.Start(ctx, "initialize backend")
160157
_ = ctx // prevent staticcheck from complaining to avoid a maintenance hazard of having the wrong ctx in scope here
161158
defer span.End()
162159

163-
if root.StateStore != nil {
164-
view.Output(views.InitializingStateStoreMessage)
165-
} else {
166-
view.Output(views.InitializingBackendMessage)
167-
}
168-
169-
var opts *BackendOpts
170-
switch {
171-
case root.StateStore != nil && root.Backend != nil:
172-
// We expect validation during config parsing to prevent mutually exclusive backend and state_store blocks,
173-
// but checking here just in case.
174-
diags = diags.Append(&hcl.Diagnostic{
175-
Severity: hcl.DiagError,
176-
Summary: "Conflicting backend and state_store configurations present during init",
177-
Detail: fmt.Sprintf("When initializing the backend there was configuration data present for both backend %q and state store %q. This is a bug in Terraform and should be reported.",
178-
root.Backend.Type,
179-
root.StateStore.Type,
180-
),
181-
Subject: &root.Backend.TypeRange,
182-
})
183-
return nil, true, diags
184-
case root.StateStore != nil:
185-
// state_store config present
186-
factory, fDiags := c.Meta.GetStateStoreProviderFactory(root.StateStore, configLocks)
187-
diags = diags.Append(fDiags)
188-
if fDiags.HasErrors() {
189-
return nil, true, diags
190-
}
191-
192-
// If overrides supplied by -backend-config CLI flag, process them
193-
var configOverride hcl.Body
194-
if !initArgs.BackendConfig.Empty() {
195-
// We need to launch an instance of the provider to get the config of the state store for processing any overrides.
196-
provider, err := factory()
197-
defer provider.Close() // Stop the child process once we're done with it here.
198-
if err != nil {
199-
diags = diags.Append(fmt.Errorf("error when obtaining provider instance during state store initialization: %w", err))
200-
return nil, true, diags
201-
}
202-
203-
resp := provider.GetProviderSchema()
204-
205-
if len(resp.StateStores) == 0 {
206-
diags = diags.Append(&hcl.Diagnostic{
207-
Severity: hcl.DiagError,
208-
Summary: "Provider does not support pluggable state storage",
209-
Detail: fmt.Sprintf("There are no state stores implemented by provider %s (%q)",
210-
root.StateStore.Provider.Name,
211-
root.StateStore.ProviderAddr),
212-
Subject: &root.StateStore.DeclRange,
213-
})
214-
return nil, true, diags
215-
}
216-
217-
stateStoreSchema, exists := resp.StateStores[root.StateStore.Type]
218-
if !exists {
219-
suggestions := slices.Sorted(maps.Keys(resp.StateStores))
220-
suggestion := didyoumean.NameSuggestion(root.StateStore.Type, suggestions)
221-
if suggestion != "" {
222-
suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
223-
}
224-
diags = diags.Append(&hcl.Diagnostic{
225-
Severity: hcl.DiagError,
226-
Summary: "State store not implemented by the provider",
227-
Detail: fmt.Sprintf("State store %q is not implemented by provider %s (%q)%s",
228-
root.StateStore.Type, root.StateStore.Provider.Name,
229-
root.StateStore.ProviderAddr, suggestion),
230-
Subject: &root.StateStore.DeclRange,
231-
})
232-
return nil, true, diags
233-
}
160+
view.Output(views.InitializingBackendMessage)
234161

235-
// Handle any overrides supplied via -backend-config CLI flags
236-
var overrideDiags tfdiags.Diagnostics
237-
configOverride, overrideDiags = c.backendConfigOverrideBody(initArgs.BackendConfig, stateStoreSchema.Body)
238-
diags = diags.Append(overrideDiags)
239-
if overrideDiags.HasErrors() {
240-
return nil, true, diags
241-
}
242-
}
243-
244-
opts = &BackendOpts{
245-
StateStoreConfig: root.StateStore,
246-
Locks: configLocks,
247-
ProviderFactory: factory,
248-
CreateDefaultWorkspace: initArgs.CreateDefaultWorkspace,
249-
ConfigOverride: configOverride,
250-
Init: true,
251-
ViewType: initArgs.ViewType,
252-
}
253-
254-
case root.Backend != nil:
255-
// backend config present
162+
var backendConfig *configs.Backend
163+
var backendConfigOverride hcl.Body
164+
if root.Backend != nil {
256165
backendType := root.Backend.Type
257166
if backendType == "cloud" {
258167
diags = diags.Append(&hcl.Diagnostic{
@@ -282,33 +191,19 @@ func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, ini
282191

283192
b := bf()
284193
backendSchema := b.ConfigSchema()
285-
backendConfig := root.Backend
286-
287-
// If overrides supplied by -backend-config CLI flag, process them
288-
var configOverride hcl.Body
289-
if !initArgs.BackendConfig.Empty() {
290-
var overrideDiags tfdiags.Diagnostics
291-
configOverride, overrideDiags = c.backendConfigOverrideBody(initArgs.BackendConfig, backendSchema)
292-
diags = diags.Append(overrideDiags)
293-
if overrideDiags.HasErrors() {
294-
return nil, true, diags
295-
}
296-
}
194+
backendConfig = root.Backend
297195

298-
opts = &BackendOpts{
299-
BackendConfig: backendConfig,
300-
ConfigOverride: configOverride,
301-
Init: true,
302-
ViewType: initArgs.ViewType,
196+
var overrideDiags tfdiags.Diagnostics
197+
backendConfigOverride, overrideDiags = c.backendConfigOverrideBody(extraConfig, backendSchema)
198+
diags = diags.Append(overrideDiags)
199+
if overrideDiags.HasErrors() {
200+
return nil, true, diags
303201
}
304-
305-
default:
306-
// No config; defaults to local state storage
307-
202+
} else {
308203
// If the user supplied a -backend-config on the CLI but no backend
309204
// block was found in the configuration, it's likely - but not
310205
// necessarily - a mistake. Return a warning.
311-
if !initArgs.BackendConfig.Empty() {
206+
if !extraConfig.Empty() {
312207
diags = diags.Append(tfdiags.Sourceless(
313208
tfdiags.Warning,
314209
"Missing backend configuration",
@@ -326,13 +221,14 @@ However, if you intended to override a defined backend, please verify that
326221
the backend configuration is present and valid.
327222
`,
328223
))
329-
330224
}
225+
}
331226

332-
opts = &BackendOpts{
333-
Init: true,
334-
ViewType: initArgs.ViewType,
335-
}
227+
opts := &BackendOpts{
228+
BackendConfig: backendConfig,
229+
ConfigOverride: backendConfigOverride,
230+
Init: true,
231+
ViewType: viewType,
336232
}
337233

338234
back, backDiags := c.Backend(opts)

internal/command/init_run.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"github.com/hashicorp/terraform/internal/command/arguments"
1515
"github.com/hashicorp/terraform/internal/command/views"
1616
"github.com/hashicorp/terraform/internal/configs"
17-
"github.com/hashicorp/terraform/internal/depsfile"
1817
"github.com/hashicorp/terraform/internal/states"
1918
"github.com/hashicorp/terraform/internal/terraform"
2019
"github.com/hashicorp/terraform/internal/tfdiags"
@@ -143,16 +142,28 @@ func (c *InitCommand) run(initArgs *arguments.Init, view views.Init) int {
143142

144143
return 1
145144
}
146-
if !c.Meta.AllowExperimentalFeatures && rootModEarly.StateStore != nil {
145+
if !(c.Meta.AllowExperimentalFeatures && initArgs.EnablePssExperiment) && rootModEarly.StateStore != nil {
147146
// TODO(SarahFrench/radeksimko) - remove when this feature isn't experimental.
148147
// This approach for making the feature experimental is required
149148
// to let us assert the feature is gated behind an experiment in tests.
150149
// See https://github.com/hashicorp/terraform/pull/37350#issuecomment-3168555619
150+
151+
detail := "Pluggable state store is an experiment which requires"
152+
if !c.Meta.AllowExperimentalFeatures {
153+
detail += " an experimental build of terraform"
154+
}
155+
if !initArgs.EnablePssExperiment {
156+
if !c.Meta.AllowExperimentalFeatures {
157+
detail += " and"
158+
}
159+
detail += " -enable-pluggable-state-storage-experiment flag"
160+
}
161+
151162
diags = diags.Append(earlyConfDiags)
152163
diags = diags.Append(&hcl.Diagnostic{
153164
Severity: hcl.DiagError,
154-
Summary: "Unsupported block type",
155-
Detail: "Blocks of type \"state_store\" are not expected here.",
165+
Summary: "Pluggable state store experiment not supported",
166+
Detail: detail,
156167
Subject: &rootModEarly.StateStore.TypeRange,
157168
})
158169
view.Diagnostics(diags)
@@ -171,10 +182,7 @@ func (c *InitCommand) run(initArgs *arguments.Init, view views.Init) int {
171182
case initArgs.Cloud && rootModEarly.CloudConfig != nil:
172183
back, backendOutput, backDiags = c.initCloud(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, view)
173184
case initArgs.Backend:
174-
// initBackend has new parameters that aren't relevant to the original (unpluggable) version of the init command logic here.
175-
// So for this version of the init command, we pass in empty locks intentionally.
176-
emptyLocks := depsfile.NewLocks()
177-
back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, initArgs, emptyLocks, view)
185+
back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, view)
178186
default:
179187
// load the previously-stored backend config
180188
back, backDiags = c.Meta.backendFromState(ctx)

0 commit comments

Comments
 (0)