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 }