feat: Add -safe-init flag to init command, including validation of use with experiments and mutually-exclusive flags

pss/discovery-block-download-via-download-event-callbacks
Sarah French 3 weeks ago
parent 8e6d141fdf
commit e57bd43e67

@ -83,6 +83,13 @@ type Init struct {
// CreateDefaultWorkspace indicates whether the default workspace should be created by
// Terraform when initializing a state store for the first time.
CreateDefaultWorkspace bool
// SafeInitWithPluggableStateStore indicates whether the user has opted into the process of downloading and approving
// a new provider binary to use for pluggable state storage.
// When false and `init` detects that a provider for PSS needs to be downloaded, `init` will return early and prompt the user to re-run with `-safe init`.
// When true and `init` detects that a provider for PSS needs to be downloaded then the user will experience a new UX.
// Details of the new UX depending on whether Terraform is being run in automation or not.
SafeInitWithPluggableStateStore bool
}
// ParseInit processes CLI arguments, returning an Init value and errors.
@ -117,6 +124,7 @@ func ParseInit(args []string, experimentsEnabled bool) (*Init, tfdiags.Diagnosti
cmdFlags.Var(&init.BackendConfig, "backend-config", "")
cmdFlags.Var(&init.PluginPath, "plugin-dir", "plugin directory")
cmdFlags.BoolVar(&init.CreateDefaultWorkspace, "create-default-workspace", true, "when -input=false, use this flag to block creation of the default workspace")
cmdFlags.BoolVar(&init.SafeInitWithPluggableStateStore, "safe-init", false, `Enable the "safe init" workflow when downloading a provider binary for use with pluggable state storage.`)
// Used for enabling experimental code that's invoked before configuration is parsed.
cmdFlags.BoolVar(&init.EnablePssExperiment, "enable-pluggable-state-storage-experiment", false, "Enable the pluggable state storage experiment")
@ -158,6 +166,15 @@ func ParseInit(args []string, experimentsEnabled bool) (*Init, tfdiags.Diagnosti
"Terraform cannot use the -create-default-workspace flag (or TF_SKIP_CREATE_DEFAULT_WORKSPACE environment variable) unless experiments are enabled.",
))
}
if init.SafeInitWithPluggableStateStore {
// Can only be set to false by using the flag
// and we cannot identify if -create-default-workspace=true is set explicitly.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Cannot use -safe-init flag without experiments enabled",
"Terraform cannot use the -safe-init flag unless experiments are enabled.",
))
}
} else {
// Errors using flags despite experiments being enabled.
if !init.CreateDefaultWorkspace && !init.EnablePssExperiment {
@ -167,6 +184,38 @@ func ParseInit(args []string, experimentsEnabled bool) (*Init, tfdiags.Diagnosti
"Terraform cannot use the -create-default-workspace=false flag (or TF_SKIP_CREATE_DEFAULT_WORKSPACE environment variable) unless you also supply the -enable-pluggable-state-storage-experiment flag (or set the TF_ENABLE_PLUGGABLE_STATE_STORAGE environment variable).",
))
}
if init.SafeInitWithPluggableStateStore && !init.EnablePssExperiment {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Cannot use -safe-init flag unless the pluggable state storage experiment is enabled",
"Terraform cannot use the -safe-init flag unless you also supply the -enable-pluggable-state-storage-experiment flag (or set the TF_ENABLE_PLUGGABLE_STATE_STORAGE environment variable).",
))
}
}
// Manage all flag interactions with -safe-init
if init.SafeInitWithPluggableStateStore {
if !init.Backend {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"The -safe-init and -backend=false options are mutually-exclusive",
"Terraform cannot use the -safe-init flag to influence backend initialization if backend initialization is due to be skipped.",
))
}
if len(init.PluginPath) > 0 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"The -safe-init and -plugin-dir options are mutually-exclusive",
"Providers sourced through -plugin-dir have already been vetted by the user, so -safe-init is unnecessary. Please re-run the command without the -safe-init flag.",
))
}
if init.Lockfile == "readonly" {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
`The -safe-init and -lockfile=readonly options are mutually-exclusive`,
"The -safe-init flag is intended to help when first downloading or upgrading a provider to use for state storage, and in those scenarios the lockfile cannot be treated as read-only.",
))
}
}
if init.MigrateState && init.Json {

@ -40,19 +40,22 @@ func TestParseInit_basicValid(t *testing.T) {
FlagName: "-backend-config",
Items: &flagNameValue,
},
Vars: &Vars{},
InputEnabled: true,
CompactWarnings: false,
TargetFlags: nil,
CreateDefaultWorkspace: true,
Vars: &Vars{},
InputEnabled: true,
CompactWarnings: false,
TargetFlags: nil,
CreateDefaultWorkspace: true,
SafeInitWithPluggableStateStore: false,
},
},
"setting multiple options": {
[]string{"-backend=false", "-force-copy=true",
[]string{
"-backend=false", "-force-copy=true",
"-from-module=./main-dir", "-json", "-get=false",
"-lock=false", "-lock-timeout=10s", "-reconfigure=true",
"-upgrade=true", "-lockfile=readonly", "-compact-warnings=true",
"-ignore-remote-version=true", "-test-directory=./test-dir"},
"-ignore-remote-version=true", "-test-directory=./test-dir",
},
&Init{
FromModule: "./main-dir",
Lockfile: "readonly",
@ -156,6 +159,21 @@ func TestParseInit_invalid(t *testing.T) {
wantErr: "The -migrate-state and -reconfigure options are mutually-exclusive.",
wantViewType: ViewHuman,
},
"with both -safe-init and -backend=false options set": {
args: []string{"-safe-init", "-backend=false"},
wantErr: "The -safe-init and -backend=false options are mutually-exclusive",
wantViewType: ViewHuman,
},
"with both -safe-init and -plugin-dir options set": {
args: []string{"-safe-init", "-plugin-dir=./my/path/to/dir"},
wantErr: "The -safe-init and -plugin-dir options are mutually-exclusive",
wantViewType: ViewHuman,
},
"with both -safe-init and -lockfile=readonly options set": {
args: []string{"-safe-init", `-lockfile=readonly`},
wantErr: "The -safe-init and -lockfile=readonly options are mutually-exclusive",
wantViewType: ViewHuman,
},
}
for name, tc := range testCases {
@ -218,6 +236,16 @@ func TestParseInit_experimentalFlags(t *testing.T) {
experimentsEnabled: true,
wantErr: "Cannot use -create-default-workspace=false flag unless the pluggable state storage experiment is enabled",
},
"error: -safe-init and experiments are disabled": {
args: []string{"-safe-init"},
experimentsEnabled: false,
wantErr: "Cannot use -safe-init flag without experiments enabled: Terraform cannot use the -safe-init flag unless experiments are enabled.",
},
"error: -safe-init used without -enable-pluggable-state-storage-experiment": {
args: []string{"-safe-init"},
experimentsEnabled: true,
wantErr: "Cannot use -safe-init flag unless the pluggable state storage experiment is enabled",
},
}
for name, tc := range testCases {

Loading…
Cancel
Save