| package command |
| |
| import ( |
| "fmt" |
| "strings" |
| |
| "github.com/hashicorp/terraform/internal/backend" |
| "github.com/hashicorp/terraform/internal/command/arguments" |
| "github.com/hashicorp/terraform/internal/command/views" |
| "github.com/hashicorp/terraform/internal/tfdiags" |
| ) |
| |
| // PlanCommand is a Command implementation that compares a Terraform |
| // configuration to an actual infrastructure and shows the differences. |
| type PlanCommand struct { |
| Meta |
| } |
| |
| func (c *PlanCommand) Run(rawArgs []string) int { |
| // Parse and apply global view arguments |
| common, rawArgs := arguments.ParseView(rawArgs) |
| c.View.Configure(common) |
| |
| // Propagate -no-color for legacy use of Ui. The remote backend and |
| // cloud package use this; it should be removed when/if they are |
| // migrated to views. |
| c.Meta.color = !common.NoColor |
| c.Meta.Color = c.Meta.color |
| |
| // Parse and validate flags |
| args, diags := arguments.ParsePlan(rawArgs) |
| |
| // Instantiate the view, even if there are flag errors, so that we render |
| // diagnostics according to the desired view |
| view := views.NewPlan(args.ViewType, c.View) |
| |
| if diags.HasErrors() { |
| view.Diagnostics(diags) |
| view.HelpPrompt() |
| return 1 |
| } |
| |
| // Check for user-supplied plugin path |
| var err error |
| if c.pluginPath, err = c.loadPluginPath(); err != nil { |
| diags = diags.Append(err) |
| view.Diagnostics(diags) |
| return 1 |
| } |
| |
| // FIXME: the -input flag value is needed to initialize the backend and the |
| // operation, but there is no clear path to pass this value down, so we |
| // continue to mutate the Meta object state for now. |
| c.Meta.input = args.InputEnabled |
| |
| // FIXME: the -parallelism flag is used to control the concurrency of |
| // Terraform operations. At the moment, this value is used both to |
| // initialize the backend via the ContextOpts field inside CLIOpts, and to |
| // set a largely unused field on the Operation request. Again, there is no |
| // clear path to pass this value down, so we continue to mutate the Meta |
| // object state for now. |
| c.Meta.parallelism = args.Operation.Parallelism |
| |
| diags = diags.Append(c.providerDevOverrideRuntimeWarnings()) |
| |
| // Prepare the backend with the backend-specific arguments |
| be, beDiags := c.PrepareBackend(args.State, args.ViewType) |
| diags = diags.Append(beDiags) |
| if diags.HasErrors() { |
| view.Diagnostics(diags) |
| return 1 |
| } |
| |
| // Build the operation request |
| opReq, opDiags := c.OperationRequest(be, view, args.ViewType, args.Operation, args.OutPath) |
| diags = diags.Append(opDiags) |
| if diags.HasErrors() { |
| view.Diagnostics(diags) |
| return 1 |
| } |
| |
| // Collect variable value and add them to the operation request |
| diags = diags.Append(c.GatherVariables(opReq, args.Vars)) |
| if diags.HasErrors() { |
| view.Diagnostics(diags) |
| return 1 |
| } |
| |
| // Before we delegate to the backend, we'll print any warning diagnostics |
| // we've accumulated here, since the backend will start fresh with its own |
| // diagnostics. |
| view.Diagnostics(diags) |
| diags = nil |
| |
| // Perform the operation |
| op, err := c.RunOperation(be, opReq) |
| if err != nil { |
| diags = diags.Append(err) |
| view.Diagnostics(diags) |
| return 1 |
| } |
| |
| if op.Result != backend.OperationSuccess { |
| return op.Result.ExitStatus() |
| } |
| if args.DetailedExitCode && !op.PlanEmpty { |
| return 2 |
| } |
| |
| return op.Result.ExitStatus() |
| } |
| |
| func (c *PlanCommand) PrepareBackend(args *arguments.State, viewType arguments.ViewType) (backend.Enhanced, tfdiags.Diagnostics) { |
| // FIXME: we need to apply the state arguments to the meta object here |
| // because they are later used when initializing the backend. Carving a |
| // path to pass these arguments to the functions that need them is |
| // difficult but would make their use easier to understand. |
| c.Meta.applyStateArguments(args) |
| |
| backendConfig, diags := c.loadBackendConfig(".") |
| if diags.HasErrors() { |
| return nil, diags |
| } |
| |
| // Load the backend |
| be, beDiags := c.Backend(&BackendOpts{ |
| Config: backendConfig, |
| ViewType: viewType, |
| }) |
| diags = diags.Append(beDiags) |
| if beDiags.HasErrors() { |
| return nil, diags |
| } |
| |
| return be, diags |
| } |
| |
| func (c *PlanCommand) OperationRequest( |
| be backend.Enhanced, |
| view views.Plan, |
| viewType arguments.ViewType, |
| args *arguments.Operation, |
| planOutPath string, |
| ) (*backend.Operation, tfdiags.Diagnostics) { |
| var diags tfdiags.Diagnostics |
| |
| // Build the operation |
| opReq := c.Operation(be, viewType) |
| opReq.ConfigDir = "." |
| opReq.PlanMode = args.PlanMode |
| opReq.Hooks = view.Hooks() |
| opReq.PlanRefresh = args.Refresh |
| opReq.PlanOutPath = planOutPath |
| opReq.Targets = args.Targets |
| opReq.ForceReplace = args.ForceReplace |
| opReq.Type = backend.OperationTypePlan |
| opReq.View = view.Operation() |
| |
| var err error |
| opReq.ConfigLoader, err = c.initConfigLoader() |
| if err != nil { |
| diags = diags.Append(fmt.Errorf("Failed to initialize config loader: %s", err)) |
| return nil, diags |
| } |
| |
| return opReq, diags |
| } |
| |
| func (c *PlanCommand) GatherVariables(opReq *backend.Operation, args *arguments.Vars) tfdiags.Diagnostics { |
| var diags tfdiags.Diagnostics |
| |
| // FIXME the arguments package currently trivially gathers variable related |
| // arguments in a heterogenous slice, in order to minimize the number of |
| // code paths gathering variables during the transition to this structure. |
| // Once all commands that gather variables have been converted to this |
| // structure, we could move the variable gathering code to the arguments |
| // package directly, removing this shim layer. |
| |
| varArgs := args.All() |
| items := make([]rawFlag, len(varArgs)) |
| for i := range varArgs { |
| items[i].Name = varArgs[i].Name |
| items[i].Value = varArgs[i].Value |
| } |
| c.Meta.variableArgs = rawFlags{items: &items} |
| opReq.Variables, diags = c.collectVariableValues() |
| |
| return diags |
| } |
| |
| func (c *PlanCommand) Help() string { |
| helpText := ` |
| Usage: terraform [global options] plan [options] |
| |
| Generates a speculative execution plan, showing what actions Terraform |
| would take to apply the current configuration. This command will not |
| actually perform the planned actions. |
| |
| You can optionally save the plan to a file, which you can then pass to |
| the "apply" command to perform exactly the actions described in the plan. |
| |
| Plan Customization Options: |
| |
| The following options customize how Terraform will produce its plan. You |
| can also use these options when you run "terraform apply" without passing |
| it a saved plan, in order to plan and apply in a single command. |
| |
| -destroy Select the "destroy" planning mode, which creates a plan |
| to destroy all objects currently managed by this |
| Terraform configuration instead of the usual behavior. |
| |
| -refresh-only Select the "refresh only" planning mode, which checks |
| whether remote objects still match the outcome of the |
| most recent Terraform apply but does not propose any |
| actions to undo any changes made outside of Terraform. |
| |
| -refresh=false Skip checking for external changes to remote objects |
| while creating the plan. This can potentially make |
| planning faster, but at the expense of possibly planning |
| against a stale record of the remote system state. |
| |
| -replace=resource Force replacement of a particular resource instance using |
| its resource address. If the plan would've normally |
| produced an update or no-op action for this instance, |
| Terraform will plan to replace it instead. You can use |
| this option multiple times to replace more than one object. |
| |
| -target=resource Limit the planning operation to only the given module, |
| resource, or resource instance and all of its |
| dependencies. You can use this option multiple times to |
| include more than one object. This is for exceptional |
| use only. |
| |
| -var 'foo=bar' Set a value for one of the input variables in the root |
| module of the configuration. Use this option more than |
| once to set more than one variable. |
| |
| -var-file=filename Load variable values from the given file, in addition |
| to the default files terraform.tfvars and *.auto.tfvars. |
| Use this option more than once to include more than one |
| variables file. |
| |
| Other Options: |
| |
| -compact-warnings If Terraform produces any warnings that are not |
| accompanied by errors, shows them in a more compact form |
| that includes only the summary messages. |
| |
| -detailed-exitcode Return detailed exit codes when the command exits. This |
| will change the meaning of exit codes to: |
| 0 - Succeeded, diff is empty (no changes) |
| 1 - Errored |
| 2 - Succeeded, there is a diff |
| |
| -input=true Ask for input for variables if not directly set. |
| |
| -lock=false Don't hold a state lock during the operation. This is |
| dangerous if others might concurrently run commands |
| against the same workspace. |
| |
| -lock-timeout=0s Duration to retry a state lock. |
| |
| -no-color If specified, output won't contain any color. |
| |
| -out=path Write a plan file to the given path. This can be used as |
| input to the "apply" command. |
| |
| -parallelism=n Limit the number of concurrent operations. Defaults to 10. |
| |
| -state=statefile A legacy option used for the local backend only. See the |
| local backend's documentation for more information. |
| ` |
| return strings.TrimSpace(helpText) |
| } |
| |
| func (c *PlanCommand) Synopsis() string { |
| return "Show changes required by the current configuration" |
| } |