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