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 }