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