github.com/rmenn/terraform@v0.3.8-0.20150225065417-fc84b3a78802/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/terraform/config/module" 11 "github.com/hashicorp/terraform/terraform" 12 ) 13 14 // ApplyCommand is a Command implementation that applies a Terraform 15 // configuration and actually builds or changes infrastructure. 16 type ApplyCommand struct { 17 Meta 18 19 // If true, then this apply command will become the "destroy" 20 // command. It is just like apply but only processes a destroy. 21 Destroy bool 22 23 // When this channel is closed, the apply will be cancelled. 24 ShutdownCh <-chan struct{} 25 } 26 27 func (c *ApplyCommand) Run(args []string) int { 28 var destroyForce, refresh bool 29 args = c.Meta.process(args, true) 30 31 cmdName := "apply" 32 if c.Destroy { 33 cmdName = "destroy" 34 } 35 36 cmdFlags := c.Meta.flagSet(cmdName) 37 if c.Destroy { 38 cmdFlags.BoolVar(&destroyForce, "force", false, "force") 39 } 40 cmdFlags.BoolVar(&refresh, "refresh", true, "refresh") 41 cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") 42 cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path") 43 cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path") 44 cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } 45 if err := cmdFlags.Parse(args); err != nil { 46 return 1 47 } 48 49 pwd, err := os.Getwd() 50 if err != nil { 51 c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err)) 52 return 1 53 } 54 55 var configPath string 56 maybeInit := true 57 args = cmdFlags.Args() 58 if len(args) > 1 { 59 c.Ui.Error("The apply command expects at most one argument.") 60 cmdFlags.Usage() 61 return 1 62 } else if len(args) == 1 { 63 configPath = args[0] 64 } else { 65 configPath = pwd 66 maybeInit = false 67 } 68 69 // Prepare the extra hooks to count resources 70 countHook := new(CountHook) 71 stateHook := new(StateHook) 72 c.Meta.extraHooks = []terraform.Hook{countHook, stateHook} 73 74 if !c.Destroy && maybeInit { 75 // Do a detect to determine if we need to do an init + apply. 76 if detected, err := module.Detect(configPath, pwd); err != nil { 77 c.Ui.Error(fmt.Sprintf( 78 "Invalid path: %s", err)) 79 return 1 80 } else if !strings.HasPrefix(detected, "file") { 81 // If this isn't a file URL then we're doing an init + 82 // apply. 83 var init InitCommand 84 init.Meta = c.Meta 85 if code := init.Run([]string{detected}); code != 0 { 86 return code 87 } 88 89 // Change the config path to be the cwd 90 configPath = pwd 91 } 92 } 93 94 // Build the context based on the arguments given 95 ctx, planned, err := c.Context(contextOpts{ 96 Path: configPath, 97 StatePath: c.Meta.statePath, 98 }) 99 if err != nil { 100 c.Ui.Error(err.Error()) 101 return 1 102 } 103 if c.Destroy && planned { 104 c.Ui.Error(fmt.Sprintf( 105 "Destroy can't be called with a plan file.")) 106 return 1 107 } 108 if !destroyForce && c.Destroy { 109 v, err := c.UIInput().Input(&terraform.InputOpts{ 110 Id: "destroy", 111 Query: "Do you really want to destroy?", 112 Description: "Terraform will delete all your managed infrastructure.\n" + 113 "There is no undo. Only 'yes' will be accepted to confirm.", 114 }) 115 if err != nil { 116 c.Ui.Error(fmt.Sprintf("Error asking for confirmation: %s", err)) 117 return 1 118 } 119 if v != "yes" { 120 c.Ui.Output("Destroy cancelled.") 121 return 1 122 } 123 } 124 if !planned { 125 if err := ctx.Input(c.InputMode()); err != nil { 126 c.Ui.Error(fmt.Sprintf("Error configuring: %s", err)) 127 return 1 128 } 129 } 130 if !validateContext(ctx, c.Ui) { 131 return 1 132 } 133 134 // Plan if we haven't already 135 if !planned { 136 if refresh { 137 if _, err := ctx.Refresh(); err != nil { 138 c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err)) 139 return 1 140 } 141 } 142 143 var opts terraform.PlanOpts 144 if c.Destroy { 145 opts.Destroy = true 146 } 147 148 if _, err := ctx.Plan(&opts); 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 continous 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 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 we have outputs, then output those at the end. 237 var outputs map[string]string 238 if !c.Destroy && state != nil { 239 outputs = state.RootModule().Outputs 240 } 241 if len(outputs) > 0 { 242 outputBuf := new(bytes.Buffer) 243 outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n") 244 245 // Output the outputs in alphabetical order 246 keyLen := 0 247 keys := make([]string, 0, len(outputs)) 248 for key, _ := range outputs { 249 keys = append(keys, key) 250 if len(key) > keyLen { 251 keyLen = len(key) 252 } 253 } 254 sort.Strings(keys) 255 256 for _, k := range keys { 257 v := outputs[k] 258 259 outputBuf.WriteString(fmt.Sprintf( 260 " %s%s = %s\n", 261 k, 262 strings.Repeat(" ", keyLen-len(k)), 263 v)) 264 } 265 266 c.Ui.Output(c.Colorize().Color( 267 strings.TrimSpace(outputBuf.String()))) 268 } 269 270 return 0 271 } 272 273 func (c *ApplyCommand) Help() string { 274 if c.Destroy { 275 return c.helpDestroy() 276 } 277 278 return c.helpApply() 279 } 280 281 func (c *ApplyCommand) Synopsis() string { 282 if c.Destroy { 283 return "Destroy Terraform-managed infrastructure" 284 } 285 286 return "Builds or changes infrastructure" 287 } 288 289 func (c *ApplyCommand) helpApply() string { 290 helpText := ` 291 Usage: terraform apply [options] [DIR] 292 293 Builds or changes infrastructure according to Terraform configuration 294 files in DIR. 295 296 DIR can also be a SOURCE as given to the "init" command. In this case, 297 apply behaves as though "init" was called followed by "apply". This only 298 works for sources that aren't files, and only if the current working 299 directory is empty of Terraform files. This is a shortcut for getting 300 started. 301 302 Options: 303 304 -backup=path Path to backup the existing state file before 305 modifying. Defaults to the "-state-out" path with 306 ".backup" extension. Set to "-" to disable backup. 307 308 -input=true Ask for input for variables if not directly set. 309 310 -no-color If specified, output won't contain any color. 311 312 -refresh=true Update state prior to checking for differences. This 313 has no effect if a plan file is given to apply. 314 315 -state=path Path to read and save state (unless state-out 316 is specified). Defaults to "terraform.tfstate". 317 318 -state-out=path Path to write state to that is different than 319 "-state". This can be used to preserve the old 320 state. 321 322 -var 'foo=bar' Set a variable in the Terraform configuration. This 323 flag can be set multiple times. 324 325 -var-file=foo Set variables in the Terraform configuration from 326 a file. If "terraform.tfvars" is present, it will be 327 automatically loaded if this flag is not specified. 328 329 330 ` 331 return strings.TrimSpace(helpText) 332 } 333 334 func (c *ApplyCommand) helpDestroy() string { 335 helpText := ` 336 Usage: terraform destroy [options] [DIR] 337 338 Destroy Terraform-managed infrastructure. 339 340 Options: 341 342 -backup=path Path to backup the existing state file before 343 modifying. Defaults to the "-state-out" path with 344 ".backup" extension. Set to "-" to disable backup. 345 346 -force Don't ask for input for destroy confirmation. 347 348 -no-color If specified, output won't contain any color. 349 350 -refresh=true Update state prior to checking for differences. This 351 has no effect if a plan file is given to apply. 352 353 -state=path Path to read and save state (unless state-out 354 is specified). Defaults to "terraform.tfstate". 355 356 -state-out=path Path to write state to that is different than 357 "-state". This can be used to preserve the old 358 state. 359 360 -var 'foo=bar' Set a variable in the Terraform configuration. This 361 flag can be set multiple times. 362 363 -var-file=foo Set variables in the Terraform configuration from 364 a file. If "terraform.tfvars" is present, it will be 365 automatically loaded if this flag is not specified. 366 367 368 ` 369 return strings.TrimSpace(helpText) 370 }