github.com/inge4pres/terraform@v0.7.5-0.20160930053151-bd083f84f376/command/apply.go (about) 1 package command 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "sort" 8 "strings" 9 10 "github.com/hashicorp/go-getter" 11 "github.com/hashicorp/go-multierror" 12 "github.com/hashicorp/terraform/config" 13 "github.com/hashicorp/terraform/terraform" 14 ) 15 16 // ApplyCommand is a Command implementation that applies a Terraform 17 // configuration and actually builds or changes infrastructure. 18 type ApplyCommand struct { 19 Meta 20 21 // If true, then this apply command will become the "destroy" 22 // command. It is just like apply but only processes a destroy. 23 Destroy bool 24 25 // When this channel is closed, the apply will be cancelled. 26 ShutdownCh <-chan struct{} 27 } 28 29 func (c *ApplyCommand) Run(args []string) int { 30 var destroyForce, refresh bool 31 args = c.Meta.process(args, true) 32 33 cmdName := "apply" 34 if c.Destroy { 35 cmdName = "destroy" 36 } 37 38 cmdFlags := c.Meta.flagSet(cmdName) 39 if c.Destroy { 40 cmdFlags.BoolVar(&destroyForce, "force", false, "force") 41 } 42 cmdFlags.BoolVar(&refresh, "refresh", true, "refresh") 43 cmdFlags.IntVar( 44 &c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism") 45 cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") 46 cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path") 47 cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path") 48 cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } 49 if err := cmdFlags.Parse(args); err != nil { 50 return 1 51 } 52 53 pwd, err := os.Getwd() 54 if err != nil { 55 c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err)) 56 return 1 57 } 58 59 var configPath string 60 maybeInit := true 61 args = cmdFlags.Args() 62 if len(args) > 1 { 63 c.Ui.Error("The apply command expects at most one argument.") 64 cmdFlags.Usage() 65 return 1 66 } else if len(args) == 1 { 67 configPath = args[0] 68 } else { 69 configPath = pwd 70 maybeInit = false 71 } 72 73 // Prepare the extra hooks to count resources 74 countHook := new(CountHook) 75 stateHook := new(StateHook) 76 c.Meta.extraHooks = []terraform.Hook{countHook, stateHook} 77 78 if !c.Destroy && maybeInit { 79 // Do a detect to determine if we need to do an init + apply. 80 if detected, err := getter.Detect(configPath, pwd, getter.Detectors); err != nil { 81 c.Ui.Error(fmt.Sprintf( 82 "Invalid path: %s", err)) 83 return 1 84 } else if !strings.HasPrefix(detected, "file") { 85 // If this isn't a file URL then we're doing an init + 86 // apply. 87 var init InitCommand 88 init.Meta = c.Meta 89 if code := init.Run([]string{detected}); code != 0 { 90 return code 91 } 92 93 // Change the config path to be the cwd 94 configPath = pwd 95 } 96 } 97 98 // Build the context based on the arguments given 99 ctx, planned, err := c.Context(contextOpts{ 100 Destroy: c.Destroy, 101 Path: configPath, 102 StatePath: c.Meta.statePath, 103 Parallelism: c.Meta.parallelism, 104 }) 105 if err != nil { 106 c.Ui.Error(err.Error()) 107 return 1 108 } 109 if c.Destroy && planned { 110 c.Ui.Error(fmt.Sprintf( 111 "Destroy can't be called with a plan file.")) 112 return 1 113 } 114 if !destroyForce && c.Destroy { 115 // Default destroy message 116 desc := "Terraform will delete all your managed infrastructure.\n" + 117 "There is no undo. Only 'yes' will be accepted to confirm." 118 119 // If targets are specified, list those to user 120 if c.Meta.targets != nil { 121 var descBuffer bytes.Buffer 122 descBuffer.WriteString("Terraform will delete the following infrastructure:\n") 123 for _, target := range c.Meta.targets { 124 descBuffer.WriteString("\t") 125 descBuffer.WriteString(target) 126 descBuffer.WriteString("\n") 127 } 128 descBuffer.WriteString("There is no undo. Only 'yes' will be accepted to confirm") 129 desc = descBuffer.String() 130 } 131 132 v, err := c.UIInput().Input(&terraform.InputOpts{ 133 Id: "destroy", 134 Query: "Do you really want to destroy?", 135 Description: desc, 136 }) 137 if err != nil { 138 c.Ui.Error(fmt.Sprintf("Error asking for confirmation: %s", err)) 139 return 1 140 } 141 if v != "yes" { 142 c.Ui.Output("Destroy cancelled.") 143 return 1 144 } 145 } 146 if !planned { 147 if err := ctx.Input(c.InputMode()); err != nil { 148 c.Ui.Error(fmt.Sprintf("Error configuring: %s", err)) 149 return 1 150 } 151 } 152 if !validateContext(ctx, c.Ui) { 153 return 1 154 } 155 156 // Plan if we haven't already 157 if !planned { 158 if refresh { 159 if _, err := ctx.Refresh(); err != nil { 160 c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err)) 161 return 1 162 } 163 } 164 165 if _, err := ctx.Plan(); err != nil { 166 c.Ui.Error(fmt.Sprintf( 167 "Error creating plan: %s", err)) 168 return 1 169 } 170 } 171 172 // Setup the state hook for continuous state updates 173 { 174 state, err := c.State() 175 if err != nil { 176 c.Ui.Error(fmt.Sprintf( 177 "Error reading state: %s", err)) 178 return 1 179 } 180 181 stateHook.State = state 182 } 183 184 // Start the apply in a goroutine so that we can be interrupted. 185 var state *terraform.State 186 var applyErr error 187 doneCh := make(chan struct{}) 188 go func() { 189 defer close(doneCh) 190 state, applyErr = ctx.Apply() 191 }() 192 193 // Wait for the apply to finish or for us to be interrupted so 194 // we can handle it properly. 195 err = nil 196 select { 197 case <-c.ShutdownCh: 198 c.Ui.Output("Interrupt received. Gracefully shutting down...") 199 200 // Stop execution 201 go ctx.Stop() 202 203 // Still get the result, since there is still one 204 select { 205 case <-c.ShutdownCh: 206 c.Ui.Error( 207 "Two interrupts received. Exiting immediately. Note that data\n" + 208 "loss may have occurred.") 209 return 1 210 case <-doneCh: 211 } 212 case <-doneCh: 213 } 214 215 // Persist the state 216 if state != nil { 217 if err := c.Meta.PersistState(state); err != nil { 218 c.Ui.Error(fmt.Sprintf("Failed to save state: %s", err)) 219 return 1 220 } 221 } 222 223 if applyErr != nil { 224 c.Ui.Error(fmt.Sprintf( 225 "Error applying plan:\n\n"+ 226 "%s\n\n"+ 227 "Terraform does not automatically rollback in the face of errors.\n"+ 228 "Instead, your Terraform state file has been partially updated with\n"+ 229 "any resources that successfully completed. Please address the error\n"+ 230 "above and apply again to incrementally change your infrastructure.", 231 multierror.Flatten(applyErr))) 232 return 1 233 } 234 235 if c.Destroy { 236 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 237 "[reset][bold][green]\n"+ 238 "Destroy complete! Resources: %d destroyed.", 239 countHook.Removed))) 240 } else { 241 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 242 "[reset][bold][green]\n"+ 243 "Apply complete! Resources: %d added, %d changed, %d destroyed.", 244 countHook.Added, 245 countHook.Changed, 246 countHook.Removed))) 247 } 248 249 if countHook.Added > 0 || countHook.Changed > 0 { 250 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 251 "[reset]\n"+ 252 "The state of your infrastructure has been saved to the path\n"+ 253 "below. This state is required to modify and destroy your\n"+ 254 "infrastructure, so keep it safe. To inspect the complete state\n"+ 255 "use the `terraform show` command.\n\n"+ 256 "State path: %s", 257 c.Meta.StateOutPath()))) 258 } 259 260 if !c.Destroy { 261 if outputs := outputsAsString(state, terraform.RootModulePath, ctx.Module().Config().Outputs, true); outputs != "" { 262 c.Ui.Output(c.Colorize().Color(outputs)) 263 } 264 } 265 266 return 0 267 } 268 269 func (c *ApplyCommand) Help() string { 270 if c.Destroy { 271 return c.helpDestroy() 272 } 273 274 return c.helpApply() 275 } 276 277 func (c *ApplyCommand) Synopsis() string { 278 if c.Destroy { 279 return "Destroy Terraform-managed infrastructure" 280 } 281 282 return "Builds or changes infrastructure" 283 } 284 285 func (c *ApplyCommand) helpApply() string { 286 helpText := ` 287 Usage: terraform apply [options] [DIR-OR-PLAN] 288 289 Builds or changes infrastructure according to Terraform configuration 290 files in DIR. 291 292 By default, apply scans the current directory for the configuration 293 and applies the changes appropriately. However, a path to another 294 configuration or an execution plan can be provided. Execution plans can be 295 used to only execute a pre-determined set of actions. 296 297 DIR can also be a SOURCE as given to the "init" command. In this case, 298 apply behaves as though "init" was called followed by "apply". This only 299 works for sources that aren't files, and only if the current working 300 directory is empty of Terraform files. This is a shortcut for getting 301 started. 302 303 Options: 304 305 -backup=path Path to backup the existing state file before 306 modifying. Defaults to the "-state-out" path with 307 ".backup" extension. Set to "-" to disable backup. 308 309 -input=true Ask for input for variables if not directly set. 310 311 -no-color If specified, output won't contain any color. 312 313 -parallelism=n Limit the number of concurrent operations. 314 Defaults to 10. 315 316 -refresh=true Update state prior to checking for differences. This 317 has no effect if a plan file is given to apply. 318 319 -state=path Path to read and save state (unless state-out 320 is specified). Defaults to "terraform.tfstate". 321 322 -state-out=path Path to write state to that is different than 323 "-state". This can be used to preserve the old 324 state. 325 326 -target=resource Resource to target. Operation will be limited to this 327 resource and its dependencies. This flag can be used 328 multiple times. 329 330 -var 'foo=bar' Set a variable in the Terraform configuration. This 331 flag can be set multiple times. 332 333 -var-file=foo Set variables in the Terraform configuration from 334 a file. If "terraform.tfvars" is present, it will be 335 automatically loaded if this flag is not specified. 336 337 338 ` 339 return strings.TrimSpace(helpText) 340 } 341 342 func (c *ApplyCommand) helpDestroy() string { 343 helpText := ` 344 Usage: terraform destroy [options] [DIR] 345 346 Destroy Terraform-managed infrastructure. 347 348 Options: 349 350 -backup=path Path to backup the existing state file before 351 modifying. Defaults to the "-state-out" path with 352 ".backup" extension. Set to "-" to disable backup. 353 354 -force Don't ask for input for destroy confirmation. 355 356 -no-color If specified, output won't contain any color. 357 358 -parallelism=n Limit the number of concurrent operations. 359 Defaults to 10. 360 361 -refresh=true Update state prior to checking for differences. This 362 has no effect if a plan file is given to apply. 363 364 -state=path Path to read and save state (unless state-out 365 is specified). Defaults to "terraform.tfstate". 366 367 -state-out=path Path to write state to that is different than 368 "-state". This can be used to preserve the old 369 state. 370 371 -target=resource Resource to target. Operation will be limited to this 372 resource and its dependencies. This flag can be used 373 multiple times. 374 375 -var 'foo=bar' Set a variable in the Terraform configuration. This 376 flag can be set multiple times. 377 378 -var-file=foo Set variables in the Terraform configuration from 379 a file. If "terraform.tfvars" is present, it will be 380 automatically loaded if this flag is not specified. 381 382 383 ` 384 return strings.TrimSpace(helpText) 385 } 386 387 func outputsAsString(state *terraform.State, modPath []string, schema []*config.Output, includeHeader bool) string { 388 if state == nil { 389 return "" 390 } 391 392 outputs := state.ModuleByPath(modPath).Outputs 393 outputBuf := new(bytes.Buffer) 394 if len(outputs) > 0 { 395 schemaMap := make(map[string]*config.Output) 396 if schema != nil { 397 for _, s := range schema { 398 schemaMap[s.Name] = s 399 } 400 } 401 402 if includeHeader { 403 outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n") 404 } 405 406 // Output the outputs in alphabetical order 407 keyLen := 0 408 ks := make([]string, 0, len(outputs)) 409 for key, _ := range outputs { 410 ks = append(ks, key) 411 if len(key) > keyLen { 412 keyLen = len(key) 413 } 414 } 415 sort.Strings(ks) 416 417 for _, k := range ks { 418 schema, ok := schemaMap[k] 419 if ok && schema.Sensitive { 420 outputBuf.WriteString(fmt.Sprintf("%s = <sensitive>\n", k)) 421 continue 422 } 423 424 v := outputs[k] 425 switch typedV := v.Value.(type) { 426 case string: 427 outputBuf.WriteString(fmt.Sprintf("%s = %s\n", k, typedV)) 428 case []interface{}: 429 outputBuf.WriteString(formatListOutput("", k, typedV)) 430 outputBuf.WriteString("\n") 431 case map[string]interface{}: 432 outputBuf.WriteString(formatMapOutput("", k, typedV)) 433 outputBuf.WriteString("\n") 434 } 435 } 436 } 437 438 return strings.TrimSpace(outputBuf.String()) 439 }