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