github.com/armen/terraform@v0.5.2-0.20150529052519-caa8117a08f1/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 Destroy: c.Destroy, 97 Path: configPath, 98 StatePath: c.Meta.statePath, 99 }) 100 if err != nil { 101 c.Ui.Error(err.Error()) 102 return 1 103 } 104 if c.Destroy && planned { 105 c.Ui.Error(fmt.Sprintf( 106 "Destroy can't be called with a plan file.")) 107 return 1 108 } 109 if !destroyForce && c.Destroy { 110 v, err := c.UIInput().Input(&terraform.InputOpts{ 111 Id: "destroy", 112 Query: "Do you really want to destroy?", 113 Description: "Terraform will delete all your managed infrastructure.\n" + 114 "There is no undo. Only 'yes' will be accepted to confirm.", 115 }) 116 if err != nil { 117 c.Ui.Error(fmt.Sprintf("Error asking for confirmation: %s", err)) 118 return 1 119 } 120 if v != "yes" { 121 c.Ui.Output("Destroy cancelled.") 122 return 1 123 } 124 } 125 if !planned { 126 if err := ctx.Input(c.InputMode()); err != nil { 127 c.Ui.Error(fmt.Sprintf("Error configuring: %s", err)) 128 return 1 129 } 130 } 131 if !validateContext(ctx, c.Ui) { 132 return 1 133 } 134 135 // Plan if we haven't already 136 if !planned { 137 if refresh { 138 if _, err := ctx.Refresh(); err != nil { 139 c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err)) 140 return 1 141 } 142 } 143 144 if _, err := ctx.Plan(); err != nil { 145 c.Ui.Error(fmt.Sprintf( 146 "Error creating plan: %s", err)) 147 return 1 148 } 149 } 150 151 // Setup the state hook for continous state updates 152 { 153 state, err := c.State() 154 if err != nil { 155 c.Ui.Error(fmt.Sprintf( 156 "Error reading state: %s", err)) 157 return 1 158 } 159 160 stateHook.State = state 161 } 162 163 // Start the apply in a goroutine so that we can be interrupted. 164 var state *terraform.State 165 var applyErr error 166 doneCh := make(chan struct{}) 167 go func() { 168 defer close(doneCh) 169 state, applyErr = ctx.Apply() 170 }() 171 172 // Wait for the apply to finish or for us to be interrupted so 173 // we can handle it properly. 174 err = nil 175 select { 176 case <-c.ShutdownCh: 177 c.Ui.Output("Interrupt received. Gracefully shutting down...") 178 179 // Stop execution 180 go ctx.Stop() 181 182 // Still get the result, since there is still one 183 select { 184 case <-c.ShutdownCh: 185 c.Ui.Error( 186 "Two interrupts received. Exiting immediately. Note that data\n" + 187 "loss may have occurred.") 188 return 1 189 case <-doneCh: 190 } 191 case <-doneCh: 192 } 193 194 // Persist the state 195 if state != nil { 196 if err := c.Meta.PersistState(state); err != nil { 197 c.Ui.Error(fmt.Sprintf("Failed to save state: %s", err)) 198 return 1 199 } 200 } 201 202 if applyErr != nil { 203 c.Ui.Error(fmt.Sprintf( 204 "Error applying plan:\n\n"+ 205 "%s\n\n"+ 206 "Terraform does not automatically rollback in the face of errors.\n"+ 207 "Instead, your Terraform state file has been partially updated with\n"+ 208 "any resources that successfully completed. Please address the error\n"+ 209 "above and apply again to incrementally change your infrastructure.", 210 applyErr)) 211 return 1 212 } 213 214 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 215 "[reset][bold][green]\n"+ 216 "Apply complete! Resources: %d added, %d changed, %d destroyed.", 217 countHook.Added, 218 countHook.Changed, 219 countHook.Removed))) 220 221 if countHook.Added > 0 || countHook.Changed > 0 { 222 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 223 "[reset]\n"+ 224 "The state of your infrastructure has been saved to the path\n"+ 225 "below. This state is required to modify and destroy your\n"+ 226 "infrastructure, so keep it safe. To inspect the complete state\n"+ 227 "use the `terraform show` command.\n\n"+ 228 "State path: %s", 229 c.Meta.StateOutPath()))) 230 } 231 232 // If we have outputs, then output those at the end. 233 var outputs map[string]string 234 if !c.Destroy && state != nil { 235 outputs = state.RootModule().Outputs 236 } 237 if len(outputs) > 0 { 238 outputBuf := new(bytes.Buffer) 239 outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n") 240 241 // Output the outputs in alphabetical order 242 keyLen := 0 243 keys := make([]string, 0, len(outputs)) 244 for key, _ := range outputs { 245 keys = append(keys, key) 246 if len(key) > keyLen { 247 keyLen = len(key) 248 } 249 } 250 sort.Strings(keys) 251 252 for _, k := range keys { 253 v := outputs[k] 254 255 outputBuf.WriteString(fmt.Sprintf( 256 " %s%s = %s\n", 257 k, 258 strings.Repeat(" ", keyLen-len(k)), 259 v)) 260 } 261 262 c.Ui.Output(c.Colorize().Color( 263 strings.TrimSpace(outputBuf.String()))) 264 } 265 266 return 0 267 } 268 269 func (c *ApplyCommand) Help() string { 270 if c.Destroy { 271 return c.helpDestroy() 272 } 273 274 return c.helpApply() 275 } 276 277 func (c *ApplyCommand) Synopsis() string { 278 if c.Destroy { 279 return "Destroy Terraform-managed infrastructure" 280 } 281 282 return "Builds or changes infrastructure" 283 } 284 285 func (c *ApplyCommand) helpApply() string { 286 helpText := ` 287 Usage: terraform apply [options] [DIR] 288 289 Builds or changes infrastructure according to Terraform configuration 290 files in DIR. 291 292 DIR can also be a SOURCE as given to the "init" command. In this case, 293 apply behaves as though "init" was called followed by "apply". This only 294 works for sources that aren't files, and only if the current working 295 directory is empty of Terraform files. This is a shortcut for getting 296 started. 297 298 Options: 299 300 -backup=path Path to backup the existing state file before 301 modifying. Defaults to the "-state-out" path with 302 ".backup" extension. Set to "-" to disable backup. 303 304 -input=true Ask for input for variables if not directly set. 305 306 -no-color If specified, output won't contain any color. 307 308 -refresh=true Update state prior to checking for differences. This 309 has no effect if a plan file is given to apply. 310 311 -state=path Path to read and save state (unless state-out 312 is specified). Defaults to "terraform.tfstate". 313 314 -state-out=path Path to write state to that is different than 315 "-state". This can be used to preserve the old 316 state. 317 318 -target=resource Resource to target. Operation will be limited to this 319 resource and its dependencies. This flag can be used 320 multiple times. 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 -target=resource Resource to target. Operation will be limited to this 361 resource and its dependencies. This flag can be used 362 multiple times. 363 364 -var 'foo=bar' Set a variable in the Terraform configuration. This 365 flag can be set multiple times. 366 367 -var-file=foo Set variables in the Terraform configuration from 368 a file. If "terraform.tfvars" is present, it will be 369 automatically loaded if this flag is not specified. 370 371 372 ` 373 return strings.TrimSpace(helpText) 374 }