github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/arguments/apply.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package arguments
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/terramate-io/tf/plans"
    10  	"github.com/terramate-io/tf/tfdiags"
    11  )
    12  
    13  // Apply represents the command-line arguments for the apply command.
    14  type Apply struct {
    15  	// State, Operation, and Vars are the common extended flags
    16  	State     *State
    17  	Operation *Operation
    18  	Vars      *Vars
    19  
    20  	// AutoApprove skips the manual verification step for the apply operation.
    21  	AutoApprove bool
    22  
    23  	// InputEnabled is used to disable interactive input for unspecified
    24  	// variable and backend config values. Default is true.
    25  	InputEnabled bool
    26  
    27  	// PlanPath contains an optional path to a stored plan file
    28  	PlanPath string
    29  
    30  	// ViewType specifies which output format to use
    31  	ViewType ViewType
    32  }
    33  
    34  // ParseApply processes CLI arguments, returning an Apply value and errors.
    35  // If errors are encountered, an Apply value is still returned representing
    36  // the best effort interpretation of the arguments.
    37  func ParseApply(args []string) (*Apply, tfdiags.Diagnostics) {
    38  	var diags tfdiags.Diagnostics
    39  	apply := &Apply{
    40  		State:     &State{},
    41  		Operation: &Operation{},
    42  		Vars:      &Vars{},
    43  	}
    44  
    45  	cmdFlags := extendedFlagSet("apply", apply.State, apply.Operation, apply.Vars)
    46  	cmdFlags.BoolVar(&apply.AutoApprove, "auto-approve", false, "auto-approve")
    47  	cmdFlags.BoolVar(&apply.InputEnabled, "input", true, "input")
    48  
    49  	var json bool
    50  	cmdFlags.BoolVar(&json, "json", false, "json")
    51  
    52  	if err := cmdFlags.Parse(args); err != nil {
    53  		diags = diags.Append(tfdiags.Sourceless(
    54  			tfdiags.Error,
    55  			"Failed to parse command-line flags",
    56  			err.Error(),
    57  		))
    58  	}
    59  
    60  	args = cmdFlags.Args()
    61  	if len(args) > 0 {
    62  		apply.PlanPath = args[0]
    63  		args = args[1:]
    64  	}
    65  
    66  	if len(args) > 0 {
    67  		diags = diags.Append(tfdiags.Sourceless(
    68  			tfdiags.Error,
    69  			"Too many command line arguments",
    70  			"Expected at most one positional argument.",
    71  		))
    72  	}
    73  
    74  	// JSON view currently does not support input, so we disable it here.
    75  	if json {
    76  		apply.InputEnabled = false
    77  	}
    78  
    79  	// JSON view cannot confirm apply, so we require either a plan file or
    80  	// auto-approve to be specified. We intentionally fail here rather than
    81  	// override auto-approve, which would be dangerous.
    82  	if json && apply.PlanPath == "" && !apply.AutoApprove {
    83  		diags = diags.Append(tfdiags.Sourceless(
    84  			tfdiags.Error,
    85  			"Plan file or auto-approve required",
    86  			"Terraform cannot ask for interactive approval when -json is set. You can either apply a saved plan file, or enable the -auto-approve option.",
    87  		))
    88  	}
    89  
    90  	diags = diags.Append(apply.Operation.Parse())
    91  
    92  	switch {
    93  	case json:
    94  		apply.ViewType = ViewJSON
    95  	default:
    96  		apply.ViewType = ViewHuman
    97  	}
    98  
    99  	return apply, diags
   100  }
   101  
   102  // ParseApplyDestroy is a special case of ParseApply that deals with the
   103  // "terraform destroy" command, which is effectively an alias for
   104  // "terraform apply -destroy".
   105  func ParseApplyDestroy(args []string) (*Apply, tfdiags.Diagnostics) {
   106  	apply, diags := ParseApply(args)
   107  
   108  	// So far ParseApply was using the command line options like -destroy
   109  	// and -refresh-only to determine the plan mode. For "terraform destroy"
   110  	// we expect neither of those arguments to be set, and so the plan mode
   111  	// should currently be set to NormalMode, which we'll replace with
   112  	// DestroyMode here. If it's already set to something else then that
   113  	// suggests incorrect usage.
   114  	switch apply.Operation.PlanMode {
   115  	case plans.NormalMode:
   116  		// This indicates that the user didn't specify any mode options at
   117  		// all, which is correct, although we know from the command that
   118  		// they actually intended to use DestroyMode here.
   119  		apply.Operation.PlanMode = plans.DestroyMode
   120  	case plans.DestroyMode:
   121  		diags = diags.Append(tfdiags.Sourceless(
   122  			tfdiags.Error,
   123  			"Invalid mode option",
   124  			"The -destroy option is not valid for \"terraform destroy\", because this command always runs in destroy mode.",
   125  		))
   126  	case plans.RefreshOnlyMode:
   127  		diags = diags.Append(tfdiags.Sourceless(
   128  			tfdiags.Error,
   129  			"Invalid mode option",
   130  			"The -refresh-only option is not valid for \"terraform destroy\".",
   131  		))
   132  	default:
   133  		// This is a non-ideal error message for if we forget to handle a
   134  		// newly-handled plan mode in Operation.Parse. Ideally they should all
   135  		// have cases above so we can produce better error messages.
   136  		diags = diags.Append(tfdiags.Sourceless(
   137  			tfdiags.Error,
   138  			"Invalid mode option",
   139  			fmt.Sprintf("The \"terraform destroy\" command doesn't support %s.", apply.Operation.PlanMode),
   140  		))
   141  	}
   142  
   143  	// NOTE: It's also invalid to have apply.PlanPath set in this codepath,
   144  	// but we don't check that in here because we'll return a different error
   145  	// message depending on whether the given path seems to refer to a saved
   146  	// plan file or to a configuration directory. The apply command
   147  	// implementation itself therefore handles this situation.
   148  
   149  	return apply, diags
   150  }