github.com/kevinklinger/open_terraform@v1.3.6/noninternal/command/plan.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/kevinklinger/open_terraform/noninternal/backend"
     8  	"github.com/kevinklinger/open_terraform/noninternal/command/arguments"
     9  	"github.com/kevinklinger/open_terraform/noninternal/command/views"
    10  	"github.com/kevinklinger/open_terraform/noninternal/tfdiags"
    11  )
    12  
    13  // PlanCommand is a Command implementation that compares a Terraform
    14  // configuration to an actual infrastructure and shows the differences.
    15  type PlanCommand struct {
    16  	Meta
    17  }
    18  
    19  func (c *PlanCommand) Run(rawArgs []string) int {
    20  	// Parse and apply global view arguments
    21  	common, rawArgs := arguments.ParseView(rawArgs)
    22  	c.View.Configure(common)
    23  
    24  	// Propagate -no-color for legacy use of Ui.  The remote backend and
    25  	// cloud package use this; it should be removed when/if they are
    26  	// migrated to views.
    27  	c.Meta.color = !common.NoColor
    28  	c.Meta.Color = c.Meta.color
    29  
    30  	// Parse and validate flags
    31  	args, diags := arguments.ParsePlan(rawArgs)
    32  
    33  	// Instantiate the view, even if there are flag errors, so that we render
    34  	// diagnostics according to the desired view
    35  	view := views.NewPlan(args.ViewType, c.View)
    36  
    37  	if diags.HasErrors() {
    38  		view.Diagnostics(diags)
    39  		view.HelpPrompt()
    40  		return 1
    41  	}
    42  
    43  	// Check for user-supplied plugin path
    44  	var err error
    45  	if c.pluginPath, err = c.loadPluginPath(); err != nil {
    46  		diags = diags.Append(err)
    47  		view.Diagnostics(diags)
    48  		return 1
    49  	}
    50  
    51  	// FIXME: the -input flag value is needed to initialize the backend and the
    52  	// operation, but there is no clear path to pass this value down, so we
    53  	// continue to mutate the Meta object state for now.
    54  	c.Meta.input = args.InputEnabled
    55  
    56  	// FIXME: the -parallelism flag is used to control the concurrency of
    57  	// Terraform operations. At the moment, this value is used both to
    58  	// initialize the backend via the ContextOpts field inside CLIOpts, and to
    59  	// set a largely unused field on the Operation request. Again, there is no
    60  	// clear path to pass this value down, so we continue to mutate the Meta
    61  	// object state for now.
    62  	c.Meta.parallelism = args.Operation.Parallelism
    63  
    64  	diags = diags.Append(c.providerDevOverrideRuntimeWarnings())
    65  
    66  	// Prepare the backend with the backend-specific arguments
    67  	be, beDiags := c.PrepareBackend(args.State)
    68  	diags = diags.Append(beDiags)
    69  	if diags.HasErrors() {
    70  		view.Diagnostics(diags)
    71  		return 1
    72  	}
    73  
    74  	// Build the operation request
    75  	opReq, opDiags := c.OperationRequest(be, view, args.Operation, args.OutPath)
    76  	diags = diags.Append(opDiags)
    77  	if diags.HasErrors() {
    78  		view.Diagnostics(diags)
    79  		return 1
    80  	}
    81  
    82  	// Collect variable value and add them to the operation request
    83  	diags = diags.Append(c.GatherVariables(opReq, args.Vars))
    84  	if diags.HasErrors() {
    85  		view.Diagnostics(diags)
    86  		return 1
    87  	}
    88  
    89  	// Before we delegate to the backend, we'll print any warning diagnostics
    90  	// we've accumulated here, since the backend will start fresh with its own
    91  	// diagnostics.
    92  	view.Diagnostics(diags)
    93  	diags = nil
    94  
    95  	// Perform the operation
    96  	op, err := c.RunOperation(be, opReq)
    97  	if err != nil {
    98  		diags = diags.Append(err)
    99  		view.Diagnostics(diags)
   100  		return 1
   101  	}
   102  
   103  	if op.Result != backend.OperationSuccess {
   104  		return op.Result.ExitStatus()
   105  	}
   106  	if args.DetailedExitCode && !op.PlanEmpty {
   107  		return 2
   108  	}
   109  
   110  	return op.Result.ExitStatus()
   111  }
   112  
   113  func (c *PlanCommand) PrepareBackend(args *arguments.State) (backend.Enhanced, tfdiags.Diagnostics) {
   114  	// FIXME: we need to apply the state arguments to the meta object here
   115  	// because they are later used when initializing the backend. Carving a
   116  	// path to pass these arguments to the functions that need them is
   117  	// difficult but would make their use easier to understand.
   118  	c.Meta.applyStateArguments(args)
   119  
   120  	backendConfig, diags := c.loadBackendConfig(".")
   121  	if diags.HasErrors() {
   122  		return nil, diags
   123  	}
   124  
   125  	// Load the backend
   126  	be, beDiags := c.Backend(&BackendOpts{
   127  		Config: backendConfig,
   128  	})
   129  	diags = diags.Append(beDiags)
   130  	if beDiags.HasErrors() {
   131  		return nil, diags
   132  	}
   133  
   134  	return be, diags
   135  }
   136  
   137  func (c *PlanCommand) OperationRequest(
   138  	be backend.Enhanced,
   139  	view views.Plan,
   140  	args *arguments.Operation,
   141  	planOutPath string,
   142  ) (*backend.Operation, tfdiags.Diagnostics) {
   143  	var diags tfdiags.Diagnostics
   144  
   145  	// Build the operation
   146  	opReq := c.Operation(be)
   147  	opReq.ConfigDir = "."
   148  	opReq.PlanMode = args.PlanMode
   149  	opReq.Hooks = view.Hooks()
   150  	opReq.PlanRefresh = args.Refresh
   151  	opReq.PlanOutPath = planOutPath
   152  	opReq.Targets = args.Targets
   153  	opReq.ForceReplace = args.ForceReplace
   154  	opReq.Type = backend.OperationTypePlan
   155  	opReq.View = view.Operation()
   156  
   157  	var err error
   158  	opReq.ConfigLoader, err = c.initConfigLoader()
   159  	if err != nil {
   160  		diags = diags.Append(fmt.Errorf("Failed to initialize config loader: %s", err))
   161  		return nil, diags
   162  	}
   163  
   164  	return opReq, diags
   165  }
   166  
   167  func (c *PlanCommand) GatherVariables(opReq *backend.Operation, args *arguments.Vars) tfdiags.Diagnostics {
   168  	var diags tfdiags.Diagnostics
   169  
   170  	// FIXME the arguments package currently trivially gathers variable related
   171  	// arguments in a heterogenous slice, in order to minimize the number of
   172  	// code paths gathering variables during the transition to this structure.
   173  	// Once all commands that gather variables have been converted to this
   174  	// structure, we could move the variable gathering code to the arguments
   175  	// package directly, removing this shim layer.
   176  
   177  	varArgs := args.All()
   178  	items := make([]rawFlag, len(varArgs))
   179  	for i := range varArgs {
   180  		items[i].Name = varArgs[i].Name
   181  		items[i].Value = varArgs[i].Value
   182  	}
   183  	c.Meta.variableArgs = rawFlags{items: &items}
   184  	opReq.Variables, diags = c.collectVariableValues()
   185  
   186  	return diags
   187  }
   188  
   189  func (c *PlanCommand) Help() string {
   190  	helpText := `
   191  Usage: terraform [global options] plan [options]
   192  
   193    Generates a speculative execution plan, showing what actions Terraform
   194    would take to apply the current configuration. This command will not
   195    actually perform the planned actions.
   196  
   197    You can optionally save the plan to a file, which you can then pass to
   198    the "apply" command to perform exactly the actions described in the plan.
   199  
   200  Plan Customization Options:
   201  
   202    The following options customize how Terraform will produce its plan. You
   203    can also use these options when you run "terraform apply" without passing
   204    it a saved plan, in order to plan and apply in a single command.
   205  
   206    -destroy            Select the "destroy" planning mode, which creates a plan
   207                        to destroy all objects currently managed by this
   208                        Terraform configuration instead of the usual behavior.
   209  
   210    -refresh-only       Select the "refresh only" planning mode, which checks
   211                        whether remote objects still match the outcome of the
   212                        most recent Terraform apply but does not propose any
   213                        actions to undo any changes made outside of Terraform.
   214  
   215    -refresh=false      Skip checking for external changes to remote objects
   216                        while creating the plan. This can potentially make
   217                        planning faster, but at the expense of possibly planning
   218                        against a stale record of the remote system state.
   219  
   220    -replace=resource   Force replacement of a particular resource instance using
   221                        its resource address. If the plan would've normally
   222                        produced an update or no-op action for this instance,
   223                        Terraform will plan to replace it instead. You can use
   224                        this option multiple times to replace more than one object.
   225  
   226    -target=resource    Limit the planning operation to only the given module,
   227                        resource, or resource instance and all of its
   228                        dependencies. You can use this option multiple times to
   229                        include more than one object. This is for exceptional
   230                        use only.
   231  
   232    -var 'foo=bar'      Set a value for one of the input variables in the root
   233                        module of the configuration. Use this option more than
   234                        once to set more than one variable.
   235  
   236    -var-file=filename  Load variable values from the given file, in addition
   237                        to the default files terraform.tfvars and *.auto.tfvars.
   238                        Use this option more than once to include more than one
   239                        variables file.
   240  
   241  Other Options:
   242  
   243    -compact-warnings   If Terraform produces any warnings that are not
   244                        accompanied by errors, shows them in a more compact form
   245                        that includes only the summary messages.
   246  
   247    -detailed-exitcode  Return detailed exit codes when the command exits. This
   248                        will change the meaning of exit codes to:
   249                        0 - Succeeded, diff is empty (no changes)
   250                        1 - Errored
   251                        2 - Succeeded, there is a diff
   252  
   253    -input=true         Ask for input for variables if not directly set.
   254  
   255    -lock=false         Don't hold a state lock during the operation. This is
   256                        dangerous if others might concurrently run commands
   257                        against the same workspace.
   258  
   259    -lock-timeout=0s    Duration to retry a state lock.
   260  
   261    -no-color           If specified, output won't contain any color.
   262  
   263    -out=path           Write a plan file to the given path. This can be used as
   264                        input to the "apply" command.
   265  
   266    -parallelism=n      Limit the number of concurrent operations. Defaults to 10.
   267  
   268    -state=statefile    A legacy option used for the local backend only. See the
   269                        local backend's documentation for more information.
   270  `
   271  	return strings.TrimSpace(helpText)
   272  }
   273  
   274  func (c *PlanCommand) Synopsis() string {
   275  	return "Show changes required by the current configuration"
   276  }