github.com/jzbruno/terraform@v0.10.3-0.20180104230435-18975d727047/command/apply.go (about) 1 package command 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "os" 8 "sort" 9 "strings" 10 11 "github.com/hashicorp/terraform/tfdiags" 12 13 "github.com/hashicorp/go-getter" 14 "github.com/hashicorp/terraform/backend" 15 "github.com/hashicorp/terraform/config" 16 "github.com/hashicorp/terraform/config/module" 17 "github.com/hashicorp/terraform/terraform" 18 ) 19 20 // ApplyCommand is a Command implementation that applies a Terraform 21 // configuration and actually builds or changes infrastructure. 22 type ApplyCommand struct { 23 Meta 24 25 // If true, then this apply command will become the "destroy" 26 // command. It is just like apply but only processes a destroy. 27 Destroy bool 28 } 29 30 func (c *ApplyCommand) Run(args []string) int { 31 var destroyForce, refresh, autoApprove bool 32 args, err := c.Meta.process(args, true) 33 if err != nil { 34 return 1 35 } 36 37 cmdName := "apply" 38 if c.Destroy { 39 cmdName = "destroy" 40 } 41 42 cmdFlags := c.Meta.flagSet(cmdName) 43 if c.Destroy { 44 cmdFlags.BoolVar(&destroyForce, "force", false, "force") 45 } 46 cmdFlags.BoolVar(&refresh, "refresh", true, "refresh") 47 if !c.Destroy { 48 cmdFlags.BoolVar(&autoApprove, "auto-approve", false, "skip interactive approval of plan before applying") 49 } 50 cmdFlags.IntVar( 51 &c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism") 52 cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path") 53 cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path") 54 cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path") 55 cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") 56 cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") 57 cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } 58 if err := cmdFlags.Parse(args); err != nil { 59 return 1 60 } 61 62 // Get the args. The "maybeInit" flag tracks whether we may need to 63 // initialize the configuration from a remote path. This is true as long 64 // as we have an argument. 65 args = cmdFlags.Args() 66 maybeInit := len(args) == 1 67 configPath, err := ModulePath(args) 68 if err != nil { 69 c.Ui.Error(err.Error()) 70 return 1 71 } 72 73 // Check for user-supplied plugin path 74 if c.pluginPath, err = c.loadPluginPath(); err != nil { 75 c.Ui.Error(fmt.Sprintf("Error loading plugin path: %s", err)) 76 return 1 77 } 78 79 if !c.Destroy && maybeInit { 80 // We need the pwd for the getter operation below 81 pwd, err := os.Getwd() 82 if err != nil { 83 c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err)) 84 return 1 85 } 86 87 // Do a detect to determine if we need to do an init + apply. 88 if detected, err := getter.Detect(configPath, pwd, getter.Detectors); err != nil { 89 c.Ui.Error(fmt.Sprintf( 90 "Invalid path: %s", err)) 91 return 1 92 } else if !strings.HasPrefix(detected, "file") { 93 // If this isn't a file URL then we're doing an init + 94 // apply. 95 var init InitCommand 96 init.Meta = c.Meta 97 if code := init.Run([]string{detected}); code != 0 { 98 return code 99 } 100 101 // Change the config path to be the cwd 102 configPath = pwd 103 } 104 } 105 106 // Check if the path is a plan 107 plan, err := c.Plan(configPath) 108 if err != nil { 109 c.Ui.Error(err.Error()) 110 return 1 111 } 112 if c.Destroy && plan != nil { 113 c.Ui.Error(fmt.Sprintf( 114 "Destroy can't be called with a plan file.")) 115 return 1 116 } 117 if plan != nil { 118 // Reset the config path for backend loading 119 configPath = "" 120 } 121 122 var diags tfdiags.Diagnostics 123 124 // Load the module if we don't have one yet (not running from plan) 125 var mod *module.Tree 126 if plan == nil { 127 var modDiags tfdiags.Diagnostics 128 mod, modDiags = c.Module(configPath) 129 diags = diags.Append(modDiags) 130 if modDiags.HasErrors() { 131 c.showDiagnostics(diags) 132 return 1 133 } 134 } 135 136 var conf *config.Config 137 if mod != nil { 138 conf = mod.Config() 139 } 140 141 // Load the backend 142 b, err := c.Backend(&BackendOpts{ 143 Config: conf, 144 Plan: plan, 145 }) 146 if err != nil { 147 c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) 148 return 1 149 } 150 151 // Build the operation 152 opReq := c.Operation() 153 opReq.Destroy = c.Destroy 154 opReq.Module = mod 155 opReq.Plan = plan 156 opReq.PlanRefresh = refresh 157 opReq.Type = backend.OperationTypeApply 158 opReq.AutoApprove = autoApprove 159 opReq.DestroyForce = destroyForce 160 161 // Perform the operation 162 ctx, ctxCancel := context.WithCancel(context.Background()) 163 defer ctxCancel() 164 165 op, err := b.Operation(ctx, opReq) 166 if err != nil { 167 c.Ui.Error(fmt.Sprintf("Error starting operation: %s", err)) 168 return 1 169 } 170 171 // Wait for the operation to complete or an interrupt to occur 172 select { 173 case <-c.ShutdownCh: 174 // Cancel our context so we can start gracefully exiting 175 ctxCancel() 176 177 // Notify the user 178 c.Ui.Output(outputInterrupt) 179 180 // Still get the result, since there is still one 181 select { 182 case <-c.ShutdownCh: 183 c.Ui.Error( 184 "Two interrupts received. Exiting immediately. Note that data\n" + 185 "loss may have occurred.") 186 return 1 187 case <-op.Done(): 188 } 189 case <-op.Done(): 190 if err := op.Err; err != nil { 191 diags = diags.Append(err) 192 } 193 } 194 195 c.showDiagnostics(diags) 196 if diags.HasErrors() { 197 return 1 198 } 199 200 if !c.Destroy { 201 // Get the right module that we used. If we ran a plan, then use 202 // that module. 203 if plan != nil { 204 mod = plan.Module 205 } 206 207 if outputs := outputsAsString(op.State, terraform.RootModulePath, mod.Config().Outputs, true); outputs != "" { 208 c.Ui.Output(c.Colorize().Color(outputs)) 209 } 210 } 211 212 return 0 213 } 214 215 func (c *ApplyCommand) Help() string { 216 if c.Destroy { 217 return c.helpDestroy() 218 } 219 220 return c.helpApply() 221 } 222 223 func (c *ApplyCommand) Synopsis() string { 224 if c.Destroy { 225 return "Destroy Terraform-managed infrastructure" 226 } 227 228 return "Builds or changes infrastructure" 229 } 230 231 func (c *ApplyCommand) helpApply() string { 232 helpText := ` 233 Usage: terraform apply [options] [DIR-OR-PLAN] 234 235 Builds or changes infrastructure according to Terraform configuration 236 files in DIR. 237 238 By default, apply scans the current directory for the configuration 239 and applies the changes appropriately. However, a path to another 240 configuration or an execution plan can be provided. Execution plans can be 241 used to only execute a pre-determined set of actions. 242 243 Options: 244 245 -backup=path Path to backup the existing state file before 246 modifying. Defaults to the "-state-out" path with 247 ".backup" extension. Set to "-" to disable backup. 248 249 -lock=true Lock the state file when locking is supported. 250 251 -lock-timeout=0s Duration to retry a state lock. 252 253 -auto-approve Skip interactive approval of plan before applying. 254 255 -input=true Ask for input for variables if not directly set. 256 257 -no-color If specified, output won't contain any color. 258 259 -parallelism=n Limit the number of parallel resource operations. 260 Defaults to 10. 261 262 -refresh=true Update state prior to checking for differences. This 263 has no effect if a plan file is given to apply. 264 265 -state=path Path to read and save state (unless state-out 266 is specified). Defaults to "terraform.tfstate". 267 268 -state-out=path Path to write state to that is different than 269 "-state". This can be used to preserve the old 270 state. 271 272 -target=resource Resource to target. Operation will be limited to this 273 resource and its dependencies. This flag can be used 274 multiple times. 275 276 -var 'foo=bar' Set a variable in the Terraform configuration. This 277 flag can be set multiple times. 278 279 -var-file=foo Set variables in the Terraform configuration from 280 a file. If "terraform.tfvars" or any ".auto.tfvars" 281 files are present, they will be automatically loaded. 282 283 284 ` 285 return strings.TrimSpace(helpText) 286 } 287 288 func (c *ApplyCommand) helpDestroy() string { 289 helpText := ` 290 Usage: terraform destroy [options] [DIR] 291 292 Destroy Terraform-managed infrastructure. 293 294 Options: 295 296 -backup=path Path to backup the existing state file before 297 modifying. Defaults to the "-state-out" path with 298 ".backup" extension. Set to "-" to disable backup. 299 300 -force Don't ask for input for destroy confirmation. 301 302 -lock=true Lock the state file when locking is supported. 303 304 -lock-timeout=0s Duration to retry a state lock. 305 306 -no-color If specified, output won't contain any color. 307 308 -parallelism=n Limit the number of concurrent operations. 309 Defaults to 10. 310 311 -refresh=true Update state prior to checking for differences. This 312 has no effect if a plan file is given to apply. 313 314 -state=path Path to read and save state (unless state-out 315 is specified). Defaults to "terraform.tfstate". 316 317 -state-out=path Path to write state to that is different than 318 "-state". This can be used to preserve the old 319 state. 320 321 -target=resource Resource to target. Operation will be limited to this 322 resource and its dependencies. This flag can be used 323 multiple times. 324 325 -var 'foo=bar' Set a variable in the Terraform configuration. This 326 flag can be set multiple times. 327 328 -var-file=foo Set variables in the Terraform configuration from 329 a file. If "terraform.tfvars" or any ".auto.tfvars" 330 files are present, they will be automatically loaded. 331 332 333 ` 334 return strings.TrimSpace(helpText) 335 } 336 337 func outputsAsString(state *terraform.State, modPath []string, schema []*config.Output, includeHeader bool) string { 338 if state == nil { 339 return "" 340 } 341 342 ms := state.ModuleByPath(modPath) 343 if ms == nil { 344 return "" 345 } 346 347 outputs := ms.Outputs 348 outputBuf := new(bytes.Buffer) 349 if len(outputs) > 0 { 350 schemaMap := make(map[string]*config.Output) 351 if schema != nil { 352 for _, s := range schema { 353 schemaMap[s.Name] = s 354 } 355 } 356 357 if includeHeader { 358 outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n") 359 } 360 361 // Output the outputs in alphabetical order 362 keyLen := 0 363 ks := make([]string, 0, len(outputs)) 364 for key, _ := range outputs { 365 ks = append(ks, key) 366 if len(key) > keyLen { 367 keyLen = len(key) 368 } 369 } 370 sort.Strings(ks) 371 372 for _, k := range ks { 373 schema, ok := schemaMap[k] 374 if ok && schema.Sensitive { 375 outputBuf.WriteString(fmt.Sprintf("%s = <sensitive>\n", k)) 376 continue 377 } 378 379 v := outputs[k] 380 switch typedV := v.Value.(type) { 381 case string: 382 outputBuf.WriteString(fmt.Sprintf("%s = %s\n", k, typedV)) 383 case []interface{}: 384 outputBuf.WriteString(formatListOutput("", k, typedV)) 385 outputBuf.WriteString("\n") 386 case map[string]interface{}: 387 outputBuf.WriteString(formatMapOutput("", k, typedV)) 388 outputBuf.WriteString("\n") 389 } 390 } 391 } 392 393 return strings.TrimSpace(outputBuf.String()) 394 } 395 396 const outputInterrupt = `Interrupt received. 397 Please wait for Terraform to exit or data loss may occur. 398 Gracefully shutting down...`