github.com/boyvanduuren/terraform@v0.7.0-rc2.0.20160805175930-de822d909c40/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 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 236 "[reset][bold][green]\n"+ 237 "Apply complete! Resources: %d added, %d changed, %d destroyed.", 238 countHook.Added, 239 countHook.Changed, 240 countHook.Removed))) 241 242 if countHook.Added > 0 || countHook.Changed > 0 { 243 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 244 "[reset]\n"+ 245 "The state of your infrastructure has been saved to the path\n"+ 246 "below. This state is required to modify and destroy your\n"+ 247 "infrastructure, so keep it safe. To inspect the complete state\n"+ 248 "use the `terraform show` command.\n\n"+ 249 "State path: %s", 250 c.Meta.StateOutPath()))) 251 } 252 253 if !c.Destroy { 254 if outputs := outputsAsString(state, terraform.RootModulePath, ctx.Module().Config().Outputs, true); outputs != "" { 255 c.Ui.Output(c.Colorize().Color(outputs)) 256 } 257 } 258 259 return 0 260 } 261 262 func (c *ApplyCommand) Help() string { 263 if c.Destroy { 264 return c.helpDestroy() 265 } 266 267 return c.helpApply() 268 } 269 270 func (c *ApplyCommand) Synopsis() string { 271 if c.Destroy { 272 return "Destroy Terraform-managed infrastructure" 273 } 274 275 return "Builds or changes infrastructure" 276 } 277 278 func (c *ApplyCommand) helpApply() string { 279 helpText := ` 280 Usage: terraform apply [options] [DIR-OR-PLAN] 281 282 Builds or changes infrastructure according to Terraform configuration 283 files in DIR. 284 285 By default, apply scans the current directory for the configuration 286 and applies the changes appropriately. However, a path to another 287 configuration or an execution plan can be provided. Execution plans can be 288 used to only execute a pre-determined set of actions. 289 290 DIR can also be a SOURCE as given to the "init" command. In this case, 291 apply behaves as though "init" was called followed by "apply". This only 292 works for sources that aren't files, and only if the current working 293 directory is empty of Terraform files. This is a shortcut for getting 294 started. 295 296 Options: 297 298 -backup=path Path to backup the existing state file before 299 modifying. Defaults to the "-state-out" path with 300 ".backup" extension. Set to "-" to disable backup. 301 302 -input=true Ask for input for variables if not directly set. 303 304 -no-color If specified, output won't contain any color. 305 306 -parallelism=n Limit the number of concurrent operations. 307 Defaults to 10. 308 309 -refresh=true Update state prior to checking for differences. This 310 has no effect if a plan file is given to apply. 311 312 -state=path Path to read and save state (unless state-out 313 is specified). Defaults to "terraform.tfstate". 314 315 -state-out=path Path to write state to that is different than 316 "-state". This can be used to preserve the old 317 state. 318 319 -target=resource Resource to target. Operation will be limited to this 320 resource and its dependencies. This flag can be used 321 multiple times. 322 323 -var 'foo=bar' Set a variable in the Terraform configuration. This 324 flag can be set multiple times. 325 326 -var-file=foo Set variables in the Terraform configuration from 327 a file. If "terraform.tfvars" is present, it will be 328 automatically loaded if this flag is not specified. 329 330 331 ` 332 return strings.TrimSpace(helpText) 333 } 334 335 func (c *ApplyCommand) helpDestroy() string { 336 helpText := ` 337 Usage: terraform destroy [options] [DIR] 338 339 Destroy Terraform-managed infrastructure. 340 341 Options: 342 343 -backup=path Path to backup the existing state file before 344 modifying. Defaults to the "-state-out" path with 345 ".backup" extension. Set to "-" to disable backup. 346 347 -force Don't ask for input for destroy confirmation. 348 349 -no-color If specified, output won't contain any color. 350 351 -parallelism=n Limit the number of concurrent operations. 352 Defaults to 10. 353 354 -refresh=true Update state prior to checking for differences. This 355 has no effect if a plan file is given to apply. 356 357 -state=path Path to read and save state (unless state-out 358 is specified). Defaults to "terraform.tfstate". 359 360 -state-out=path Path to write state to that is different than 361 "-state". This can be used to preserve the old 362 state. 363 364 -target=resource Resource to target. Operation will be limited to this 365 resource and its dependencies. This flag can be used 366 multiple times. 367 368 -var 'foo=bar' Set a variable in the Terraform configuration. This 369 flag can be set multiple times. 370 371 -var-file=foo Set variables in the Terraform configuration from 372 a file. If "terraform.tfvars" is present, it will be 373 automatically loaded if this flag is not specified. 374 375 376 ` 377 return strings.TrimSpace(helpText) 378 } 379 380 func outputsAsString(state *terraform.State, modPath []string, schema []*config.Output, includeHeader bool) string { 381 if state == nil { 382 return "" 383 } 384 385 outputs := state.ModuleByPath(modPath).Outputs 386 outputBuf := new(bytes.Buffer) 387 if len(outputs) > 0 { 388 schemaMap := make(map[string]*config.Output) 389 if schema != nil { 390 for _, s := range schema { 391 schemaMap[s.Name] = s 392 } 393 } 394 395 if includeHeader { 396 outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n") 397 } 398 399 // Output the outputs in alphabetical order 400 keyLen := 0 401 ks := make([]string, 0, len(outputs)) 402 for key, _ := range outputs { 403 ks = append(ks, key) 404 if len(key) > keyLen { 405 keyLen = len(key) 406 } 407 } 408 sort.Strings(ks) 409 410 for _, k := range ks { 411 schema, ok := schemaMap[k] 412 if ok && schema.Sensitive { 413 outputBuf.WriteString(fmt.Sprintf("%s = <sensitive>\n", k)) 414 continue 415 } 416 417 v := outputs[k] 418 switch typedV := v.Value.(type) { 419 case string: 420 outputBuf.WriteString(fmt.Sprintf("%s = %s\n", k, typedV)) 421 case []interface{}: 422 outputBuf.WriteString(formatListOutput("", k, typedV)) 423 outputBuf.WriteString("\n") 424 case map[string]interface{}: 425 outputBuf.WriteString(formatMapOutput("", k, typedV)) 426 outputBuf.WriteString("\n") 427 } 428 } 429 } 430 431 return strings.TrimSpace(outputBuf.String()) 432 }