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