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