github.com/hobbeswalsh/terraform@v0.3.7-0.20150619183303-ad17cf55a0fa/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 we have outputs, then output those at the end. 234 var outputs map[string]string 235 if !c.Destroy && state != nil { 236 outputs = state.RootModule().Outputs 237 } 238 if len(outputs) > 0 { 239 outputBuf := new(bytes.Buffer) 240 outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n") 241 242 // Output the outputs in alphabetical order 243 keyLen := 0 244 keys := make([]string, 0, len(outputs)) 245 for key, _ := range outputs { 246 keys = append(keys, key) 247 if len(key) > keyLen { 248 keyLen = len(key) 249 } 250 } 251 sort.Strings(keys) 252 253 for _, k := range keys { 254 v := outputs[k] 255 256 outputBuf.WriteString(fmt.Sprintf( 257 " %s%s = %s\n", 258 k, 259 strings.Repeat(" ", keyLen-len(k)), 260 v)) 261 } 262 263 c.Ui.Output(c.Colorize().Color( 264 strings.TrimSpace(outputBuf.String()))) 265 } 266 267 return 0 268 } 269 270 func (c *ApplyCommand) Help() string { 271 if c.Destroy { 272 return c.helpDestroy() 273 } 274 275 return c.helpApply() 276 } 277 278 func (c *ApplyCommand) Synopsis() string { 279 if c.Destroy { 280 return "Destroy Terraform-managed infrastructure" 281 } 282 283 return "Builds or changes infrastructure" 284 } 285 286 func (c *ApplyCommand) helpApply() string { 287 helpText := ` 288 Usage: terraform apply [options] [DIR] 289 290 Builds or changes infrastructure according to Terraform configuration 291 files in DIR. 292 293 DIR can also be a SOURCE as given to the "init" command. In this case, 294 apply behaves as though "init" was called followed by "apply". This only 295 works for sources that aren't files, and only if the current working 296 directory is empty of Terraform files. This is a shortcut for getting 297 started. 298 299 Options: 300 301 -backup=path Path to backup the existing state file before 302 modifying. Defaults to the "-state-out" path with 303 ".backup" extension. Set to "-" to disable backup. 304 305 -input=true Ask for input for variables if not directly set. 306 307 -no-color If specified, output won't contain any color. 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 -refresh=true Update state prior to checking for differences. This 352 has no effect if a plan file is given to apply. 353 354 -state=path Path to read and save state (unless state-out 355 is specified). Defaults to "terraform.tfstate". 356 357 -state-out=path Path to write state to that is different than 358 "-state". This can be used to preserve the old 359 state. 360 361 -target=resource Resource to target. Operation will be limited to this 362 resource and its dependencies. This flag can be used 363 multiple times. 364 365 -var 'foo=bar' Set a variable in the Terraform configuration. This 366 flag can be set multiple times. 367 368 -var-file=foo Set variables in the Terraform configuration from 369 a file. If "terraform.tfvars" is present, it will be 370 automatically loaded if this flag is not specified. 371 372 373 ` 374 return strings.TrimSpace(helpText) 375 }