Skip to content

Commit 529542d

Browse files
committed
Add -create-default-workspace flag to init command, for overriding behaviour when TF isn't in interactive mode
This required refactoring the signature of the initBackend method; pass in all init args instead of a subset
1 parent 9d027ca commit 529542d

File tree

5 files changed

+65
-33
lines changed

5 files changed

+65
-33
lines changed

internal/command/arguments/init.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ type Init struct {
7878
// TODO(SarahFrench/radeksimko): Remove this once the feature is no longer
7979
// experimental
8080
EnablePssExperiment bool
81+
82+
// CreateDefaultWorkspace indicates whether the default workspace should be created by
83+
// Terraform when initializing a state store for the first time.
84+
CreateDefaultWorkspace bool
8185
}
8286

8387
// ParseInit processes CLI arguments, returning an Init value and errors.
@@ -111,7 +115,7 @@ func ParseInit(args []string) (*Init, tfdiags.Diagnostics) {
111115
cmdFlags.BoolVar(&init.Json, "json", false, "json")
112116
cmdFlags.Var(&init.BackendConfig, "backend-config", "")
113117
cmdFlags.Var(&init.PluginPath, "plugin-dir", "plugin directory")
114-
118+
cmdFlags.BoolVar(&init.CreateDefaultWorkspace, "create-default-workspace", true, "when -input=false, use this flag to block creation of the default workspace")
115119
// Used for enabling experimental code that's invoked before configuration is parsed.
116120
cmdFlags.BoolVar(&init.EnablePssExperiment, "enable-pluggable-state-storage-experiment", false, "Enable the pluggable state storage experiment")
117121

@@ -139,6 +143,14 @@ func ParseInit(args []string) (*Init, tfdiags.Diagnostics) {
139143
))
140144
}
141145

146+
if init.InputEnabled && !init.CreateDefaultWorkspace {
147+
diags = diags.Append(tfdiags.Sourceless(
148+
tfdiags.Warning,
149+
"Invalid init options",
150+
"The flag -create-default-workspace=false is ignored when Terraform is configured to ask users for input. Instead, add -input=false or remove the -create-default-workspace flag",
151+
))
152+
}
153+
142154
init.Args = cmdFlags.Args()
143155

144156
backendFlagSet := FlagIsSet(cmdFlags, "backend")

internal/command/init.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func (c *InitCommand) Run(args []string) int {
5151
args = c.Meta.process(args)
5252
initArgs, initDiags := arguments.ParseInit(args)
5353

54-
view := views.NewInit(initArgs.ViewType, c.View)
54+
view := views.NewInit(viewType, c.View)
5555

5656
if initDiags.HasErrors() {
5757
diags = diags.Append(initDiags)
@@ -159,7 +159,11 @@ func (c *InitCommand) initCloud(ctx context.Context, root *configs.Module, extra
159159
return back, true, diags
160160
}
161161

162-
func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, extraConfig arguments.FlagNameValueSlice, viewType arguments.ViewType, locks *depsfile.Locks, view views.Init) (be backend.Backend, output bool, diags tfdiags.Diagnostics) {
162+
func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, initArgs *arguments.Init, locks *depsfile.Locks, view views.Init) (be backend.Backend, output bool, diags tfdiags.Diagnostics) {
163+
// Temporary vars to account for refactoring the method's parameters
164+
extraConfig := initArgs.BackendConfig
165+
viewType := initArgs.ViewType
166+
163167
ctx, span := tracer.Start(ctx, "initialize backend")
164168
_ = ctx // prevent staticcheck from complaining to avoid a maintenance hazard of having the wrong ctx in scope here
165169
defer span.End()
@@ -271,12 +275,13 @@ func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, ext
271275
}
272276

273277
opts = &BackendOpts{
274-
StateStoreConfig: root.StateStore,
275-
Locks: locks,
276-
ProviderFactory: factory,
277-
ConfigOverride: configOverride,
278-
Init: true,
279-
ViewType: viewType,
278+
StateStoreConfig: root.StateStore,
279+
Locks: locks,
280+
ProviderFactory: factory,
281+
CreateDefaultWorkspace: initArgs.CreateDefaultWorkspace,
282+
ConfigOverride: configOverride,
283+
Init: true,
284+
ViewType: viewType,
280285
}
281286

282287
case root.Backend != nil:

internal/command/init_run.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ func (c *InitCommand) run(initArgs *arguments.Init, view views.Init) int {
172172
back, backendOutput, backDiags = c.initCloud(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, view)
173173
case initArgs.Backend:
174174
var locks *depsfile.Locks // Empty locks- this value is unused when a `backend` is used (vs. a `state_store`)
175-
back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, locks, view)
175+
back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, initArgs, locks, view)
176176
default:
177177
// load the previously-stored backend config
178178
back, backDiags = c.Meta.backendFromState(ctx)

internal/command/init_run_experiment.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ func (c *InitCommand) runPssInit(initArgs *arguments.Init, view views.Init) int
208208
// This handles case when config contains either backend or state_store blocks.
209209
// This is valid as either can be implementations of backend.Backend, which is what we
210210
// obtain here.
211-
back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, configLocks, view)
211+
back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, initArgs, configLocks, view)
212212
default:
213213
// load the previously-stored backend config
214214
back, backDiags = c.Meta.backendFromState(ctx)

internal/command/meta_backend.go

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ type BackendOpts struct {
8989
// ViewType will set console output format for the
9090
// initialization operation (JSON or human-readable).
9191
ViewType arguments.ViewType
92+
93+
// CreateDefaultWorkspace signifies whether the operations backend should create
94+
// the default workspace or not
95+
CreateDefaultWorkspace bool
9296
}
9397

9498
// BackendWithRemoteTerraformVersion is a shared interface between the 'remote' and 'cloud' backends
@@ -1666,32 +1670,43 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, backendSMgr *cli
16661670
return nil, diags
16671671

16681672
case ws == backend.DefaultStateName:
1669-
// TODO: do we want to prompt for input here (m.Input()), or create automatically unless -readonly flag present?
1670-
// input := m.UIInput()
1671-
// desc := fmt.Sprintf("Terraform will create the %q workspace via %q.\n"+
1672-
// "Only 'yes' will be accepted to approve.", backend.DefaultStateName, c.Type)
1673-
// v, err := input.Input(context.Background(), &terraform.InputOpts{
1674-
// Id: "approve",
1675-
// Query: fmt.Sprintf("Workspace %q does not exit, would you like to create one?", backend.DefaultStateName),
1676-
// Description: desc,
1677-
// })
1678-
// if err != nil {
1679-
// diags = diags.Append(fmt.Errorf("Failed to confirm default workspace creation: %w", err))
1680-
// return nil, diags
1681-
// }
1682-
// if v != "yes" {
1683-
// diags = diags.Append(errors.New("Failed to create default workspace"))
1684-
// return nil, diags
1685-
// }
1686-
1687-
// TODO: Confirm if defaulting to creation on first use (rather than error) is a good idea
1688-
// Make the default workspace. All other workspaces are user-created via the workspace commands.
1689-
m.createDefaultWorkspace(c, b)
1673+
// Should we create the default state after prompting the user, or not?
1674+
if m.Input() {
1675+
// If input is enabled, we prompt the user before creating the default workspace.
1676+
input := m.UIInput()
1677+
desc := fmt.Sprintf("Terraform will create the %q workspace via state store %q.\n"+
1678+
"Only 'yes' will be accepted to approve.", backend.DefaultStateName, c.Type)
1679+
v, err := input.Input(context.Background(), &terraform.InputOpts{
1680+
Id: "approve",
1681+
Query: fmt.Sprintf("Workspace the %s workspace does not exit, would you like to create it?", backend.DefaultStateName),
1682+
Description: desc,
1683+
})
1684+
if err != nil {
1685+
diags = diags.Append(fmt.Errorf("Failed to confirm %s workspace creation: %w", backend.DefaultStateName, err))
1686+
return nil, diags
1687+
}
1688+
if v != "yes" {
1689+
diags = diags.Append(fmt.Errorf("Cancelled creation of the %s workspace", backend.DefaultStateName))
1690+
return nil, diags
1691+
}
1692+
m.createDefaultWorkspace(c, b)
1693+
} else {
1694+
// If input is disabled, we don't prompt before creating the default workspace.
1695+
// However this can be blocked with other flags present.
1696+
if opts.CreateDefaultWorkspace {
1697+
m.createDefaultWorkspace(c, b)
1698+
} else {
1699+
diags = diags.Append(&hcl.Diagnostic{
1700+
Severity: hcl.DiagWarning,
1701+
Summary: "The default workspace does not exist",
1702+
Detail: "Terraform has been configured to skip creation of the default workspace in the state store. This may cause issues in subsequent Terraform operations",
1703+
})
1704+
}
1705+
}
16901706
default:
16911707
diags = diags.Append(err)
16921708
return nil, diags
16931709
}
1694-
// TODO: handle if input is not enabled
16951710
}
16961711
}
16971712
if diags.HasErrors() {

0 commit comments

Comments
 (0)