From efe78b2910c5b1f2292f0a16d990b2ad92352feb Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 1 Sep 2020 15:45:12 -0700 Subject: [PATCH] main: new global option -chdir This new option is intended to address the previous inconsistencies where some older subcommands supported partially changing the target directory (where Terraform would use the new directory inconsistently) where newer commands did not support that override at all. Instead, now Terraform will accept a -chdir command at the start of the command line (before the subcommand) and will interpret it as a request to direct all actions that would normally be taken in the current working directory into the target directory instead. This is similar to options offered by some other similar tools, such as the -C option in "make". The new option is only accepted at the start of the command line (before the subcommand) as a way to reflect that it is a global command (not specific to a particular subcommand) and that it takes effect _before_ executing the subcommand. This also means it'll be forced to appear before any other command-specific arguments that take file paths, which hopefully communicates that those other arguments are interpreted relative to the overridden path. As a measure of pragmatism for existing uses, the path.cwd object in the Terraform language will continue to return the _original_ working directory (ignoring -chdir), in case that is important in some exceptional workflows. The path.root object gives the root module directory, which will always match the overriden working directory unless the user simultaneously uses one of the legacy directory override arguments, which is not a pattern we intend to support in the long run. As a first step down the deprecation path, this commit adjusts the documentation to de-emphasize the inconsistent old command line arguments, including specific guidance on what to use instead for the main three workflow commands, but all of those options remain supported in the same way as they were before. In a later commit we'll make those arguments produce a visible deprecation warning in Terraform's output, and then in an even later commit we'll remove them entirely so that -chdir is the single supported way to run Terraform from a directory other than the one containing the root module configuration. --- command/e2etest/primary_test.go | 88 +++++++++++++++++++ .../testdata/chdir-option/subdir/main.tf | 7 ++ command/meta.go | 14 ++- commands.go | 4 +- e2e/e2e.go | 8 +- help.go | 9 +- main.go | 88 +++++++++++++++++-- terraform/context.go | 13 +++ terraform/evaluate.go | 25 +++++- website/docs/commands/apply.html.markdown | 34 +++++-- website/docs/commands/console.html.markdown | 5 +- website/docs/commands/destroy.html.markdown | 4 +- .../docs/commands/force-unlock.html.markdown | 2 +- website/docs/commands/get.html.markdown | 16 ++-- website/docs/commands/graph.html.markdown | 6 +- website/docs/commands/index.html.markdown | 54 ++++++++++-- website/docs/commands/init.html.markdown | 34 +++++-- website/docs/commands/plan.html.markdown | 29 +++++- website/docs/commands/providers.html.markdown | 36 ++------ website/docs/commands/refresh.html.markdown | 7 +- website/docs/commands/show.html.markdown | 7 +- website/docs/commands/validate.html.markdown | 9 +- 22 files changed, 397 insertions(+), 102 deletions(-) create mode 100644 command/e2etest/testdata/chdir-option/subdir/main.tf diff --git a/command/e2etest/primary_test.go b/command/e2etest/primary_test.go index 1cf39395f..46a7c33bf 100644 --- a/command/e2etest/primary_test.go +++ b/command/e2etest/primary_test.go @@ -9,6 +9,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/hashicorp/terraform/e2e" + "github.com/zclconf/go-cty/cty" ) // The tests in this file are for the "primary workflow", which includes @@ -126,3 +127,90 @@ func TestPrimarySeparatePlan(t *testing.T) { } } + +func TestPrimaryChdirOption(t *testing.T) { + t.Parallel() + + // This test case does not include any provider dependencies, so it's + // safe to run it even when network access is disallowed. + + fixturePath := filepath.Join("testdata", "chdir-option") + tf := e2e.NewBinary(terraformBin, fixturePath) + defer tf.Close() + + //// INIT + stdout, stderr, err := tf.Run("-chdir=subdir", "init") + if err != nil { + t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr) + } + + //// PLAN + stdout, stderr, err = tf.Run("-chdir=subdir", "plan", "-out=tfplan") + if err != nil { + t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr) + } + + if !strings.Contains(stdout, "0 to add, 0 to change, 0 to destroy") { + t.Errorf("incorrect plan tally; want 0 to add:\n%s", stdout) + } + + if !strings.Contains(stdout, "This plan was saved to: tfplan") { + t.Errorf("missing \"This plan was saved to...\" message in plan output\n%s", stdout) + } + if !strings.Contains(stdout, "terraform apply \"tfplan\"") { + t.Errorf("missing next-step instruction in plan output\n%s", stdout) + } + + // The saved plan is in the subdirectory because -chdir switched there + plan, err := tf.Plan("subdir/tfplan") + if err != nil { + t.Fatalf("failed to read plan file: %s", err) + } + + diffResources := plan.Changes.Resources + if len(diffResources) != 0 { + t.Errorf("incorrect diff in plan; want no resource changes, but have:\n%s", spew.Sdump(diffResources)) + } + + //// APPLY + stdout, stderr, err = tf.Run("-chdir=subdir", "apply", "tfplan") + if err != nil { + t.Fatalf("unexpected apply error: %s\nstderr:\n%s", err, stderr) + } + + if !strings.Contains(stdout, "Resources: 0 added, 0 changed, 0 destroyed") { + t.Errorf("incorrect apply tally; want 0 added:\n%s", stdout) + } + + // The state file is in subdir because -chdir changed the current working directory. + state, err := tf.StateFromFile("subdir/terraform.tfstate") + if err != nil { + t.Fatalf("failed to read state file: %s", err) + } + + gotOutput := state.RootModule().OutputValues["cwd"] + wantOutputValue := cty.StringVal(tf.Path()) // path.cwd returns the original path, because path.root is how we get the overridden path + if gotOutput == nil || !wantOutputValue.RawEquals(gotOutput.Value) { + t.Errorf("incorrect value for cwd output\ngot: %#v\nwant Value: %#v", gotOutput, wantOutputValue) + } + + gotOutput = state.RootModule().OutputValues["root"] + wantOutputValue = cty.StringVal(tf.Path("subdir")) // path.root is a relative path, but the text fixture uses abspath on it. + if gotOutput == nil || !wantOutputValue.RawEquals(gotOutput.Value) { + t.Errorf("incorrect value for root output\ngot: %#v\nwant Value: %#v", gotOutput, wantOutputValue) + } + + if len(state.RootModule().Resources) != 0 { + t.Errorf("unexpected resources in state") + } + + //// DESTROY + stdout, stderr, err = tf.Run("-chdir=subdir", "destroy", "-auto-approve") + if err != nil { + t.Fatalf("unexpected destroy error: %s\nstderr:\n%s", err, stderr) + } + + if !strings.Contains(stdout, "Resources: 0 destroyed") { + t.Errorf("incorrect destroy tally; want 0 destroyed:\n%s", stdout) + } +} diff --git a/command/e2etest/testdata/chdir-option/subdir/main.tf b/command/e2etest/testdata/chdir-option/subdir/main.tf new file mode 100644 index 000000000..eddb20049 --- /dev/null +++ b/command/e2etest/testdata/chdir-option/subdir/main.tf @@ -0,0 +1,7 @@ +output "cwd" { + value = path.cwd +} + +output "root" { + value = abspath(path.root) +} diff --git a/command/meta.go b/command/meta.go index 48bb3c4ae..ad830fcf2 100644 --- a/command/meta.go +++ b/command/meta.go @@ -39,6 +39,17 @@ type Meta struct { // command with a Meta field. These are expected to be set externally // (not from within the command itself). + // OriginalWorkingDir, if set, is the actual working directory where + // Terraform was run from. This might not be the _actual_ current working + // directory, because users can add the -chdir=... option to the beginning + // of their command line to ask Terraform to switch. + // + // Most things should just use the current working directory in order to + // respect the user's override, but we retain this for exceptional + // situations where we need to refer back to the original working directory + // for some reason. + OriginalWorkingDir string + Color bool // True if output should be colored GlobalPluginDirs []string // Additional paths to search for plugins PluginOverrides *PluginOverrides // legacy overrides from .terraformrc file @@ -384,7 +395,8 @@ func (m *Meta) contextOpts() (*terraform.ContextOpts, error) { } opts.Meta = &terraform.ContextMeta{ - Env: workspace, + Env: workspace, + OriginalWorkingDir: m.OriginalWorkingDir, } return &opts, nil diff --git a/commands.go b/commands.go index d02fbeb3d..529730872 100644 --- a/commands.go +++ b/commands.go @@ -40,7 +40,7 @@ const ( OutputPrefix = "o:" ) -func initCommands(config *cliconfig.Config, services *disco.Disco, providerSrc getproviders.Source, unmanagedProviders map[addrs.Provider]*plugin.ReattachConfig) { +func initCommands(originalWorkingDir string, config *cliconfig.Config, services *disco.Disco, providerSrc getproviders.Source, unmanagedProviders map[addrs.Provider]*plugin.ReattachConfig) { var inAutomation bool if v := os.Getenv(runningInAutomationEnvName); v != "" { inAutomation = true @@ -64,6 +64,8 @@ func initCommands(config *cliconfig.Config, services *disco.Disco, providerSrc g dataDir := os.Getenv("TF_DATA_DIR") meta := command.Meta{ + OriginalWorkingDir: originalWorkingDir, + Color: true, GlobalPluginDirs: globalPluginDirs(), PluginOverrides: &PluginOverrides, diff --git a/e2e/e2e.go b/e2e/e2e.go index 615344cbc..56655911c 100644 --- a/e2e/e2e.go +++ b/e2e/e2e.go @@ -180,7 +180,13 @@ func (b *binary) FileExists(path ...string) bool { // LocalState is a helper for easily reading the local backend's state file // terraform.tfstate from the working directory. func (b *binary) LocalState() (*states.State, error) { - f, err := b.OpenFile("terraform.tfstate") + return b.StateFromFile("terraform.tfstate") +} + +// StateFromFile is a helper for easily reading a state snapshot from a file +// on disk relative to the working directory. +func (b *binary) StateFromFile(filename string) (*states.State, error) { + f, err := b.OpenFile(filename) if err != nil { return nil, err } diff --git a/help.go b/help.go index 588a621db..e020edea9 100644 --- a/help.go +++ b/help.go @@ -32,7 +32,7 @@ func helpFunc(commands map[string]cli.CommandFactory) string { // website/source/docs/commands/index.html.markdown; if you // change this then consider updating that to match. helpText := fmt.Sprintf(` -Usage: terraform [-version] [-help] [args] +Usage: terraform [global options] [args] The available commands for execution are listed below. The most common, useful commands are shown first, followed by @@ -44,6 +44,13 @@ Common commands: %s All other commands: %s + +Global options (use these before the subcommand, if any): + -chdir=DIR Switch to a different working directory before executing + the given subcommand. + -help Show this help output, or the help for a specified + subcommand. + -version An alias for the "version" subcommand. `, listCommands(porcelain, maxKeyLen), listCommands(plumbing, maxKeyLen)) return strings.TrimSpace(helpText) diff --git a/main.go b/main.go index abdd253f6..4165764a3 100644 --- a/main.go +++ b/main.go @@ -129,6 +129,11 @@ func wrappedMain() int { log.Printf("[INFO] Go runtime version: %s", runtime.Version()) log.Printf("[INFO] CLI args: %#v", os.Args) + // NOTE: We're intentionally calling LoadConfig _before_ handling a possible + // -chdir=... option on the command line, so that a possible relative + // path in the TERRAFORM_CONFIG_FILE environment variable (though probably + // ill-advised) will be resolved relative to the true working directory, + // not the overridden one. config, diags := cliconfig.LoadConfig() if len(diags) > 0 { @@ -203,9 +208,40 @@ func wrappedMain() int { // Initialize the backends. backendInit.Init(services) + // Get the command line args. + binName := filepath.Base(os.Args[0]) + args := os.Args[1:] + + originalWd, err := os.Getwd() + if err != nil { + // It would be very strange to end up here + Ui.Error(fmt.Sprintf("Failed to determine current working directory: %s", err)) + return 1 + } + + // The arguments can begin with a -chdir option to ask Terraform to switch + // to a different working directory for the rest of its work. If that + // option is present then extractChdirOption returns a trimmed args with that option removed. + overrideWd, args, err := extractChdirOption(args) + if err != nil { + Ui.Error(fmt.Sprintf("Invalid -chdir option: %s", err)) + return 1 + } + if overrideWd != "" { + os.Chdir(overrideWd) + if err != nil { + Ui.Error(fmt.Sprintf("Error handling -chdir option: %s", err)) + return 1 + } + } + // In tests, Commands may already be set to provide mock commands if Commands == nil { - initCommands(config, services, providerSrc, unmanagedProviders) + // Commands get to hold on to the original working directory here, + // in case they need to refer back to it for any special reason, though + // they should primarily be working with the override working directory + // that we've now switched to above. + initCommands(originalWd, config, services, providerSrc, unmanagedProviders) } // Run checkpoint @@ -214,10 +250,6 @@ func wrappedMain() int { // Make sure we clean up any managed plugins at the end of this defer plugin.CleanupClients() - // Get the command line args. - binName := filepath.Base(os.Args[0]) - args := os.Args[1:] - // Build the CLI so far, we do this so we can query the subcommand. cliRunner := &cli.CLI{ Args: args, @@ -433,3 +465,49 @@ func parseReattachProviders(in string) (map[addrs.Provider]*plugin.ReattachConfi } return unmanagedProviders, nil } + +func extractChdirOption(args []string) (string, []string, error) { + if len(args) == 0 { + return "", args, nil + } + + const argName = "-chdir" + const argPrefix = argName + "=" + var argValue string + var argPos int + + for i, arg := range args { + if !strings.HasPrefix(arg, "-") { + // Because the chdir option is a subcommand-agnostic one, we require + // it to appear before any subcommand argument, so if we find a + // non-option before we find -chdir then we are finished. + break + } + if arg == argName || arg == argPrefix { + return "", args, fmt.Errorf("must include an equals sign followed by a directory path, like -chdir=example") + } + if strings.HasPrefix(arg, argPrefix) { + argPos = i + argValue = arg[len(argPrefix):] + } + } + + // When we fall out here, we'll have populated argValue with a non-empty + // string if the -chdir=... option was present and valid, or left it + // empty if it wasn't present. + if argValue == "" { + return "", args, nil + } + + // If we did find the option then we'll need to produce a new args that + // doesn't include it anymore. + if argPos == 0 { + // Easy case: we can just slice off the front + return argValue, args[1:], nil + } + // Otherwise we need to construct a new array and copy to it. + newArgs := make([]string, len(args)-1) + copy(newArgs, args[:argPos]) + copy(newArgs[argPos:], args[argPos+1:]) + return argValue, newArgs, nil +} diff --git a/terraform/context.go b/terraform/context.go index 61594771d..cc2e5c7b7 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -74,6 +74,19 @@ type ContextOpts struct { // initializer. type ContextMeta struct { Env string // Env is the state environment + + // OriginalWorkingDir is the working directory where the Terraform CLI + // was run from, which may no longer actually be the current working + // directory if the user included the -chdir=... option. + // + // If this string is empty then the original working directory is the same + // as the current working directory. + // + // In most cases we should respect the user's override by ignoring this + // path and just using the current working directory, but this is here + // for some exceptional cases where the original working directory is + // needed. + OriginalWorkingDir string } // Context represents all the context that Terraform needs in order to diff --git a/terraform/evaluate.go b/terraform/evaluate.go index da6cf427f..3e77ca9b8 100644 --- a/terraform/evaluate.go +++ b/terraform/evaluate.go @@ -553,7 +553,29 @@ func (d *evaluationStateData) GetPathAttr(addr addrs.PathAttr, rng tfdiags.Sourc switch addr.Name { case "cwd": - wd, err := os.Getwd() + var err error + var wd string + if d.Evaluator.Meta != nil { + // Meta is always non-nil in the normal case, but some test cases + // are not so realistic. + wd = d.Evaluator.Meta.OriginalWorkingDir + } + if wd == "" { + wd, err = os.Getwd() + if err != nil { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: `Failed to get working directory`, + Detail: fmt.Sprintf(`The value for path.cwd cannot be determined due to a system error: %s`, err), + Subject: rng.ToHCL().Ptr(), + }) + return cty.DynamicVal, diags + } + } + // The current working directory should always be absolute, whether we + // just looked it up or whether we were relying on ContextMeta's + // (possibly non-normalized) path. + wd, err = filepath.Abs(wd) if err != nil { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, @@ -563,6 +585,7 @@ func (d *evaluationStateData) GetPathAttr(addr addrs.PathAttr, rng tfdiags.Sourc }) return cty.DynamicVal, diags } + return cty.StringVal(filepath.ToSlash(wd)), diags case "module": diff --git a/website/docs/commands/apply.html.markdown b/website/docs/commands/apply.html.markdown index ee6649c59..621f9155c 100644 --- a/website/docs/commands/apply.html.markdown +++ b/website/docs/commands/apply.html.markdown @@ -16,16 +16,15 @@ set of actions generated by a `terraform plan` execution plan. ## Usage -Usage: `terraform apply [options] [dir-or-plan]` +Usage: `terraform apply [options] [plan]` By default, `apply` scans the current directory for the configuration -and applies the changes appropriately. However, a path to another configuration -or an execution plan can be provided. Explicit execution plan files can be -used to split plan and apply into separate steps within -[automation systems](https://learn.hashicorp.com/terraform/development/running-terraform-in-automation). +and applies the changes appropriately. However, you can optionally give the +path to a saved plan file that was previously created with +[`terraform plan`](plan.html). -If no explicit plan file is given on the command line, `terraform apply` will -create a new plan automatically and prompt for approval to apply it. If the +If you don't give a plan file on the command line, `terraform apply` will +create a new plan automatically and then prompt for approval to apply it. If the created plan does not include any changes to resources or to root module output values then `terraform apply` will exit immediately, without prompting. @@ -83,3 +82,24 @@ The command-line flags are all optional. The list of available flags are: first and the `.auto.tfvars` files after in alphabetical order. Any files specified by `-var-file` override any values set automatically from files in the working directory. This flag can be used multiple times. + +## Passing a Different Configuration Directory + +Terraform v0.13 and earlier also accepted a directory path in place of the +plan file argument to `terraform apply`, in which case Terraform would use +that directory as the root module instead of the current working directory. + +That usage is still supported in Terraform v0.14, but is now deprecated and we +plan to remove it in Terraform v0.15. If your workflow relies on overriding +the root module directory, use +[the `-chdir` global option](./#switching-working-directory-with--chdir) +instead, which works across all commands and makes Terraform consistently look +in the given directory for all files it would normaly read or write in the +current working directory. + +If your previous use of this legacy pattern was also relying on Terraform +writing the `.terraform` subdirectory into the current working directory even +though the root module directory was overridden, use +[the `TF_DATA_DIR` environment variable](environment-variables.html#TF_DATA_DIR) +to direct Terraform to write the `.terraform` directory to a location other +than the current working directory. diff --git a/website/docs/commands/console.html.markdown b/website/docs/commands/console.html.markdown index 47712650a..511f85164 100644 --- a/website/docs/commands/console.html.markdown +++ b/website/docs/commands/console.html.markdown @@ -14,7 +14,7 @@ evaluating [expressions](/docs/configuration/expressions.html). ## Usage -Usage: `terraform console [options] [dir]` +Usage: `terraform console [options]` This command provides an interactive command-line console for evaluating and experimenting with [expressions](/docs/configuration/expressions.html). @@ -26,9 +26,6 @@ If the current state is empty or has not yet been created, the console can be used to experiment with the expression syntax and [built-in functions](/docs/configuration/functions.html). -The `dir` argument specifies the directory of the root module to use. -If a path is not specified, the current working directory is used. - The supported options are: * `-state=path` - Path to a local state file. Expressions will be evaluated diff --git a/website/docs/commands/destroy.html.markdown b/website/docs/commands/destroy.html.markdown index d7079725f..f07c357a4 100644 --- a/website/docs/commands/destroy.html.markdown +++ b/website/docs/commands/destroy.html.markdown @@ -13,12 +13,12 @@ infrastructure. ## Usage -Usage: `terraform destroy [options] [dir]` +Usage: `terraform destroy [options]` Infrastructure managed by Terraform will be destroyed. This will ask for confirmation before destroying. -This command accepts all the arguments and flags that the [apply +This command accepts all the arguments and options that the [apply command](/docs/commands/apply.html) accepts, with the exception of a plan file argument. diff --git a/website/docs/commands/force-unlock.html.markdown b/website/docs/commands/force-unlock.html.markdown index 53c9ca0d0..506b1762c 100644 --- a/website/docs/commands/force-unlock.html.markdown +++ b/website/docs/commands/force-unlock.html.markdown @@ -17,7 +17,7 @@ process. ## Usage -Usage: terraform force-unlock LOCK_ID [DIR] +Usage: terraform force-unlock LOCK_ID Manually unlock the state for the defined configuration. diff --git a/website/docs/commands/get.html.markdown b/website/docs/commands/get.html.markdown index a7eb32ad6..e497e6867 100644 --- a/website/docs/commands/get.html.markdown +++ b/website/docs/commands/get.html.markdown @@ -13,19 +13,13 @@ The `terraform get` command is used to download and update ## Usage -Usage: `terraform get [options] [dir]` +Usage: `terraform get [options]` -The modules are downloaded into a local `.terraform` folder. This -folder should not be committed to version control. The `.terraform` -folder is created relative to your current working directory -regardless of the `dir` argument given to this command. +The modules are downloaded into a `.terraform` subdirectory of the current +working directory. Don't commit this directory to your version control +repository. -If a module is already downloaded and the `-update` flag is _not_ set, -Terraform will do nothing. As a result, it is safe (and fast) to run this -command multiple times. - -The command-line flags are all optional. The list of available flags are: +The `get` command supports the following option: * `-update` - If specified, modules that are already downloaded will be checked for updates and the updates will be downloaded if present. -* `dir` - Sets the path of the [root module](/docs/modules/index.html#definitions). diff --git a/website/docs/commands/graph.html.markdown b/website/docs/commands/graph.html.markdown index 56cb3fb5e..04c286b03 100644 --- a/website/docs/commands/graph.html.markdown +++ b/website/docs/commands/graph.html.markdown @@ -16,10 +16,10 @@ The output is in the DOT format, which can be used by ## Usage -Usage: `terraform graph [options] [DIR]` +Usage: `terraform graph [options]` -Outputs the visual dependency graph of Terraform resources according to -configuration files in DIR (or the current directory if omitted). +Outputs the visual dependency graph of Terraform resources represented by the +configuration in the current working directory. The graph is outputted in DOT format. The typical program that can read this format is GraphViz, but many web services are also available diff --git a/website/docs/commands/index.html.markdown b/website/docs/commands/index.html.markdown index 8a1d10d95..9ec75951d 100644 --- a/website/docs/commands/index.html.markdown +++ b/website/docs/commands/index.html.markdown @@ -22,8 +22,7 @@ most likely expect. To view a list of the available commands at any time, just run terraform with no arguments: ```text -$ terraform -Usage: terraform [-version] [-help] [args] +Usage: terraform [global options] [args] The available commands for execution are listed below. The most common, useful commands are shown first, followed by @@ -41,6 +40,8 @@ Common commands: graph Create a visual graph of Terraform resources import Import existing infrastructure into Terraform init Initialize a Terraform working directory + login Obtain and save credentials for a remote host + logout Remove locally-stored credentials for a remote host output Read an output from a state file plan Generate and show an execution plan providers Prints a tree of the providers used in the configuration @@ -53,18 +54,24 @@ Common commands: workspace Workspace management All other commands: - 0.12upgrade Rewrites pre-0.12 module source code for v0.12 debug Debug output management (experimental) force-unlock Manually unlock the terraform state - push Obsolete command for Terraform Enterprise legacy (v1) state Advanced state management + + +Global options (use these before the subcommand, if any): + -chdir=DIR Switch to a different working directory before executing + the given subcommand. + -help Show this help output, or the help for a specified + subcommand. + -version An alias for the "version" subcommand. ``` -To get help for any specific command, pass the -h flag to the relevant subcommand. For example, -to see help about the graph subcommand: +To get help for any specific command, use the -help option to the relevant +subcommand. For example, to see help about the graph subcommand: ```text -$ terraform graph -h +$ terraform graph -help Usage: terraform graph [options] PATH Outputs the visual graph of Terraform resources. If the path given is @@ -77,6 +84,39 @@ Usage: terraform graph [options] PATH to read this format. ``` +## Switching working directory with `-chdir` + +The usual way to run Terraform is to first switch to the directory containing +the `.tf` files for your root module (for example, using the `cd` command), so +that Terraform will find those files automatically without any extra arguments. + +In some cases though — particularly when wrapping Terraform in automation +scripts — it can be convenient to run Terraform from a different directory than +the root module directory. To allow that, Terraform supports a global option +`-chdir=...` which you can include before the name of the subcommand you intend +to run: + +``` +terraform -chdir=environments/production apply +``` + +The `chdir` option instructs Terraform to change its working directory to the +given directory before running the given subcommand. This means that any files +that Terraform would normally read or write in the current working directory +will be read or written in the given directory instead. + +There are two exceptions where Terraform will use the original working directory +even when you specify `-chdir=...`: + +* Settings in the [CLI Configuration](cli-config.html) are not for a specific + subcommand and Terraform processes them before acting on the `-chdir` + option. + +* In case you need to use files from the original working directory as part + of your configuration, a reference to `path.cwd` in the configuration will + produce the original working directory instead of the overridden working + directory. Use `path.root` to get the root module directory. + ## Shell Tab-completion If you use either `bash` or `zsh` as your command shell, Terraform can provide diff --git a/website/docs/commands/init.html.markdown b/website/docs/commands/init.html.markdown index 3d2e5485f..705167a7d 100644 --- a/website/docs/commands/init.html.markdown +++ b/website/docs/commands/init.html.markdown @@ -17,23 +17,18 @@ from version control. It is safe to run this command multiple times. ## Usage -Usage: `terraform init [options] [DIR]` +Usage: `terraform init [options]` This command performs several different initialization steps in order to -prepare a working directory for use. More details on these are in the -sections below, but in most cases it is not necessary to worry about these -individual steps. +prepare the current working directory for use with Terraform. More details on +these are in the sections below, but in most cases it is not necessary to worry +about these individual steps. This command is always safe to run multiple times, to bring the working directory up to date with changes in the configuration. Though subsequent runs may give errors, this command will never delete your existing configuration or state. -If no arguments are given, the configuration in the current working directory -is initialized. It is recommended to run Terraform with the current working -directory set to the root directory of the configuration, and omit the `DIR` -argument. - ## General Options The following options apply to all of (or several of) the initialization steps: @@ -166,3 +161,24 @@ There are some special concerns when running `init` in such an environment, including optionally making plugins available locally to avoid repeated re-installation. For more information, see [`Running Terraform in Automation`](https://learn.hashicorp.com/terraform/development/running-terraform-in-automation). + +## Passing a Different Configuration Directory + +Terraform v0.13 and earlier also accepted a directory path in place of the +plan file argument to `terraform apply`, in which case Terraform would use +that directory as the root module instead of the current working directory. + +That usage is still supported in Terraform v0.14, but is now deprecated and we +plan to remove it in Terraform v0.15. If your workflow relies on overriding +the root module directory, use +[the `-chdir` global option](./#switching-working-directory-with--chdir) +instead, which works across all commands and makes Terraform consistently look +in the given directory for all files it would normaly read or write in the +current working directory. + +If your previous use of this legacy pattern was also relying on Terraform +writing the `.terraform` subdirectory into the current working directory even +though the root module directory was overridden, use +[the `TF_DATA_DIR` environment variable](environment-variables.html#TF_DATA_DIR) +to direct Terraform to write the `.terraform` directory to a location other +than the current working directory. diff --git a/website/docs/commands/plan.html.markdown b/website/docs/commands/plan.html.markdown index add83ddb6..bb30073a9 100644 --- a/website/docs/commands/plan.html.markdown +++ b/website/docs/commands/plan.html.markdown @@ -30,12 +30,12 @@ If Terraform detects no changes to resource or to root module output values, ## Usage -Usage: `terraform plan [options] [dir]` +Usage: `terraform plan [options]` -By default, `plan` requires no flags and looks in the current directory -for the configuration and state file to refresh. +The `plan` subcommand looks in the current working directory for the root module +configuration. -The command-line flags are all optional. The list of available flags are: +The available options are: * `-compact-warnings` - If Terraform produces any warnings that are not accompanied by errors, show them in a more compact form that includes only @@ -132,3 +132,24 @@ or keep it at rest for an extended period of time. Future versions of Terraform will make plan files more secure. + +## Passing a Different Configuration Directory + +Terraform v0.13 and earlier accepted an additional positional argument giving +a directory path, in which case Terraform would use that directory as the root +module instead of the current working directory. + +That usage is still supported in Terraform v0.14, but is now deprecated and we +plan to remove it in Terraform v0.15. If your workflow relies on overriding +the root module directory, use +[the `-chdir` global option](./#switching-working-directory-with--chdir) +instead, which works across all commands and makes Terraform consistently look +in the given directory for all files it would normaly read or write in the +current working directory. + +If your previous use of this legacy pattern was also relying on Terraform +writing the `.terraform` subdirectory into the current working directory even +though the root module directory was overridden, use +[the `TF_DATA_DIR` environment variable](environment-variables.html#TF_DATA_DIR) +to direct Terraform to write the `.terraform` directory to a location other +than the current working directory. diff --git a/website/docs/commands/providers.html.markdown b/website/docs/commands/providers.html.markdown index d1b34cb7f..d30c21303 100644 --- a/website/docs/commands/providers.html.markdown +++ b/website/docs/commands/providers.html.markdown @@ -9,36 +9,14 @@ description: |- # Command: providers -The `terraform providers` command prints information about the providers -used in the current configuration. +The `terraform providers` command shows information about the +[provider requirements](/docs/configuration/provider-requirements.html) of the +configuration in the current working directory, as an aid to understanding +where each requirement was detected from. -Provider dependencies are created in several different ways: - -* Explicit use of a `terraform.required_providers` block in configuration, - optionally including a version constraint. - -* Explicit use of a `provider` block in configuration, optionally including - a version constraint. - -* Use of any resource belonging to a particular provider in a `resource` or - `data` block in configuration. - -* Existence of any resource instance belonging to a particular provider in - the current _state_. For example, if a particular resource is removed - from configuration, it continues to create a dependency on its provider - until its instances have been destroyed. - -This command gives an overview of all of the current dependencies, as an aid -to understanding why a particular provider is needed. - -This command is a nested subcommand, meaning that it has further subcommands. -These subcommands are listed to the left. +This command also has several subcommands with different purposes, which +are listed in the navigation bar. ## Usage -Usage: `terraform providers [config-path]` - -Pass an explicit configuration path to override the default of using the -current working directory. - -Please refer to the subcommands to the left for additional usages. +Usage: `terraform providers` diff --git a/website/docs/commands/refresh.html.markdown b/website/docs/commands/refresh.html.markdown index 4700bf3e9..6506cc96a 100644 --- a/website/docs/commands/refresh.html.markdown +++ b/website/docs/commands/refresh.html.markdown @@ -19,12 +19,9 @@ plan or apply. ## Usage -Usage: `terraform refresh [options] [dir]` +Usage: `terraform refresh [options]` -By default, `refresh` requires no flags and looks in the current directory -for the configuration and state file to refresh. - -The command-line flags are all optional. The list of available flags are: +The `terraform refresh` command accepts the following options: * `-backup=path` - Path to the backup file. Defaults to `-state-out` with the ".backup" extension. Disabled by setting to "-". diff --git a/website/docs/commands/show.html.markdown b/website/docs/commands/show.html.markdown index c5ac12f25..bb0cd7c41 100644 --- a/website/docs/commands/show.html.markdown +++ b/website/docs/commands/show.html.markdown @@ -32,12 +32,13 @@ The output format is covered in detail in [JSON Output Format](/docs/internals/j ## Usage -Usage: `terraform show [options] [path]` +Usage: `terraform show [options] [file]` You may use `show` with a path to either a Terraform state file or plan -file. If no path is specified, the current state will be shown. +file. If you don't specify a file path, Terraform will show the latest state +snapshot. -The command-line flags are all optional. The list of available flags are: +This command accepts the following options: * `-no-color` - Disables output with coloring diff --git a/website/docs/commands/validate.html.markdown b/website/docs/commands/validate.html.markdown index 08bea2397..344b094be 100644 --- a/website/docs/commands/validate.html.markdown +++ b/website/docs/commands/validate.html.markdown @@ -29,20 +29,15 @@ validation without accessing any configured remote backend, use: $ terraform init -backend=false ``` -If dir is not specified, then the current directory will be used. - To verify configuration in the context of a particular run (a particular target workspace, input variable values, etc), use the `terraform plan` command instead, which includes an implied validation check. ## Usage -Usage: `terraform validate [options] [dir]` +Usage: `terraform validate [options]` -By default, `validate` requires no flags and looks in the current directory -for the configurations. - -The command-line flags are all optional. The available flags are: +This command accepts the following options: - `-json` - Produce output in a machine-readable JSON format, suitable for use in text editor integrations and other automated systems. Always disables