You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
terraform/internal/command/arguments/init.go

252 lines
9.4 KiB

// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// Init represents the command-line arguments for the init command.
type Init struct {
// FromModule identifies the module to copy into the target directory before init.
FromModule string
// Lockfile specifies a dependency lockfile mode.
Lockfile string
// TestDirectory is the directory containing any test files that should be
// validated alongside the main configuration. Should be relative to the
// Path.
TestsDirectory string
// ViewType specifies which init format to use: human or JSON.
ViewType ViewType
// Backend specifies whether to disable backend or HCP Terraform initialization.
Backend bool
// Cloud specifies whether to disable backend or HCP Terraform initialization.
Cloud bool
// Get specifies whether to disable downloading modules for this configuration
Get bool
// ForceInitCopy specifies whether to suppress prompts about copying state data.
ForceInitCopy bool
// StateLock specifies whether hold a state lock during backend migration.
StateLock bool
// StateLockTimeout specifies the duration to wait for a state lock.
StateLockTimeout time.Duration
// Reconfigure specifies whether to disregard any existing configuration, preventing migration of any existing state
Reconfigure bool
// MigrateState specifies whether to attempt to copy existing state to the new backend
MigrateState bool
// Upgrade specifies whether to upgrade modules and plugins as part of their respective installation steps
Upgrade bool
// Json specifies whether to output in JSON format
Json bool
// IgnoreRemoteVersion specifies whether to ignore remote and local Terraform versions compatibility
IgnoreRemoteVersion bool
BackendConfig FlagNameValueSlice
Vars *Vars
// InputEnabled is used to disable interactive input for unspecified
// variable and backend config values. Default is true.
InputEnabled bool
TargetFlags []string
CompactWarnings bool
PluginPath FlagStringSlice
StateStoreProviderLockFile string
// The -enable-pluggable-state-storage-experiment flag is used in control flow logic in the init command.
// TODO(SarahFrench/radeksimko): Remove this once the feature is no longer
// experimental
EnablePssExperiment bool
}
// ParseInit processes CLI arguments, returning an Init value and errors.
// If errors are encountered, an Init value is still returned representing
// the best effort interpretation of the arguments.
func ParseInit(rawArgs []string, experimentsEnabled bool) (*Init, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
init := &Init{
Vars: &Vars{},
}
init.BackendConfig = NewFlagNameValueSlice("-backend-config")
cmdFlags := extendedFlagSet("init", nil, nil, init.Vars)
cmdFlags.Var((*FlagStringSlice)(&init.TargetFlags), "target", "resource to target")
cmdFlags.BoolVar(&init.InputEnabled, "input", true, "input")
cmdFlags.BoolVar(&init.CompactWarnings, "compact-warnings", false, "use compact warnings")
cmdFlags.BoolVar(&init.Backend, "backend", true, "")
cmdFlags.BoolVar(&init.Cloud, "cloud", true, "")
cmdFlags.StringVar(&init.FromModule, "from-module", "", "copy the source of the given module into the directory before init")
cmdFlags.BoolVar(&init.Get, "get", true, "")
cmdFlags.BoolVar(&init.ForceInitCopy, "force-copy", false, "suppress prompts about copying state data")
cmdFlags.BoolVar(&init.StateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&init.StateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.BoolVar(&init.Reconfigure, "reconfigure", false, "reconfigure")
cmdFlags.BoolVar(&init.MigrateState, "migrate-state", false, "migrate state")
cmdFlags.BoolVar(&init.Upgrade, "upgrade", false, "")
cmdFlags.StringVar(&init.Lockfile, "lockfile", "", "Set a dependency lockfile mode")
cmdFlags.BoolVar(&init.IgnoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible")
cmdFlags.StringVar(&init.TestsDirectory, "test-directory", "tests", "test-directory")
cmdFlags.BoolVar(&init.Json, "json", false, "json")
cmdFlags.Var(&init.BackendConfig, "backend-config", "")
cmdFlags.Var(&init.PluginPath, "plugin-dir", "plugin directory")
cmdFlags.StringVar(&init.StateStoreProviderLockFile, "state-provider-lock-file", "", "path to the dependency lock file used to establish trust in the provider used for state storage. This flag can only be supplied when input is disabled. Defaults to the working directory's .terraform.lock.hcl file.")
// 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")
if err := cmdFlags.Parse(rawArgs); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
if v := os.Getenv("TF_ENABLE_PLUGGABLE_STATE_STORAGE"); v != "" {
init.EnablePssExperiment = true
}
if !experimentsEnabled {
// If experiments aren't enabled then these flags should not be used.
if init.EnablePssExperiment {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Cannot use -enable-pluggable-state-storage-experiment flag without experiments enabled",
"Terraform cannot use the -enable-pluggable-state-storage-experiment flag (or TF_ENABLE_PLUGGABLE_STATE_STORAGE environment variable) unless experiments are enabled.",
))
}
if init.StateStoreProviderLockFile != "" {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Cannot use -state-provider-lock-file flag without experiments enabled",
"Terraform cannot use the -state-provider-lock-file flag unless experiments are enabled.",
))
}
}
if !init.EnablePssExperiment && init.StateStoreProviderLockFile != "" {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Cannot use -state-provider-lock-file flag unless the pluggable state storage experiment is enabled",
"Terraform cannot use the -state-provider-lock-file flag unless the pluggable state storage experiment is enabled. Add the -enable-pluggable-state-storage-experiment flag to your command.",
))
}
if init.StateStoreProviderLockFile != "" {
if init.InputEnabled {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Cannot use -state-provider-lock-file flag when input is enabled",
"The -state-provider-lock-file flag is only intended to be used when input is disabled. Either remove the flag or add -input=false to your command.",
))
}
if init.Upgrade {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"The -state-provider-lock-file and -upgrade options are mutually-exclusive",
"The -upgrade flag causes Terraform to ignore prior dependency locks, so it cannot be used in conjunction with the -state-provider-lock-file flag. Remove one flag and try again.",
))
}
// Validate that the path uses the expected file name: .terraform.lock.hcl
srcFilename := filepath.Base(init.StateStoreProviderLockFile)
if srcFilename != lockFileName {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid -state-provider-lock-file value",
fmt.Sprintf("Expected lock file name to be %s, got: %s", lockFileName, srcFilename),
))
}
// Validate that the file exists
if _, err := os.Stat(init.StateStoreProviderLockFile); os.IsNotExist(err) {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Lock file supplied by -state-provider-lock-file does not exist",
fmt.Sprintf("Terraform cannot find the dependency lock file at %s. Please ensure the file exists and the path is correct.", init.StateStoreProviderLockFile),
))
}
}
if init.MigrateState && init.Json {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"The -migrate-state and -json options are mutually-exclusive",
"Terraform cannot ask for interactive approval when -json is set. To use the -migrate-state option, disable the -json option.",
))
}
if init.MigrateState && init.Reconfigure {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid init options",
"The -migrate-state and -reconfigure options are mutually-exclusive.",
))
}
if init.Upgrade && init.Lockfile == "readonly" {
// This is appended as a Go error because this validation already existed this way
// and it's been moved earlier in the process, to the arguments package.
diags = diags.Append(fmt.Errorf("The -upgrade flag conflicts with -lockfile=readonly."))
}
args := cmdFlags.Args()
if len(args) != 0 {
// No positional arguments are expected.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"No positional arguments are expected",
"The init command does not expect any positional arguments. Did you mean to use -chdir?",
))
}
backendFlagSet := FlagIsSet(cmdFlags, "backend")
cloudFlagSet := FlagIsSet(cmdFlags, "cloud")
if backendFlagSet && cloudFlagSet {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid init options",
"The -backend and -cloud options are aliases of one another and mutually-exclusive in their use",
))
} else if backendFlagSet {
init.Cloud = init.Backend
} else if cloudFlagSet {
init.Backend = init.Cloud
}
switch {
case init.Json:
init.ViewType = ViewJSON
default:
init.ViewType = ViewHuman
}
return init, diags
}