github.com/kevholditch/terraform@v0.9.7-0.20170613192930-9706042ddd51/command/apply.go (about) 1 package command 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "os" 8 "sort" 9 "strings" 10 11 "github.com/hashicorp/go-getter" 12 "github.com/hashicorp/terraform/backend" 13 "github.com/hashicorp/terraform/config" 14 "github.com/hashicorp/terraform/config/module" 15 "github.com/hashicorp/terraform/terraform" 16 ) 17 18 // ApplyCommand is a Command implementation that applies a Terraform 19 // configuration and actually builds or changes infrastructure. 20 type ApplyCommand struct { 21 Meta 22 23 // If true, then this apply command will become the "destroy" 24 // command. It is just like apply but only processes a destroy. 25 Destroy bool 26 27 // When this channel is closed, the apply will be cancelled. 28 ShutdownCh <-chan struct{} 29 } 30 31 func (c *ApplyCommand) Run(args []string) int { 32 var destroyForce, refresh bool 33 args = c.Meta.process(args, true) 34 35 cmdName := "apply" 36 if c.Destroy { 37 cmdName = "destroy" 38 } 39 40 cmdFlags := c.Meta.flagSet(cmdName) 41 if c.Destroy { 42 cmdFlags.BoolVar(&destroyForce, "force", false, "force") 43 } 44 cmdFlags.BoolVar(&refresh, "refresh", true, "refresh") 45 cmdFlags.IntVar( 46 &c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism") 47 cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path") 48 cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path") 49 cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path") 50 cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") 51 cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") 52 cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } 53 if err := cmdFlags.Parse(args); err != nil { 54 return 1 55 } 56 57 // Get the args. The "maybeInit" flag tracks whether we may need to 58 // initialize the configuration from a remote path. This is true as long 59 // as we have an argument. 60 args = cmdFlags.Args() 61 maybeInit := len(args) == 1 62 configPath, err := ModulePath(args) 63 if err != nil { 64 c.Ui.Error(err.Error()) 65 return 1 66 } 67 68 if !c.Destroy && maybeInit { 69 // We need the pwd for the getter operation below 70 pwd, err := os.Getwd() 71 if err != nil { 72 c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err)) 73 return 1 74 } 75 76 // Do a detect to determine if we need to do an init + apply. 77 if detected, err := getter.Detect(configPath, pwd, getter.Detectors); 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 // Check if the path is a plan 96 plan, err := c.Plan(configPath) 97 if err != nil { 98 c.Ui.Error(err.Error()) 99 return 1 100 } 101 if c.Destroy && plan != nil { 102 c.Ui.Error(fmt.Sprintf( 103 "Destroy can't be called with a plan file.")) 104 return 1 105 } 106 if plan != nil { 107 // Reset the config path for backend loading 108 configPath = "" 109 } 110 111 // Load the module if we don't have one yet (not running from plan) 112 var mod *module.Tree 113 if plan == nil { 114 mod, err = c.Module(configPath) 115 if err != nil { 116 c.Ui.Error(fmt.Sprintf("Failed to load root config module: %s", err)) 117 return 1 118 } 119 } 120 121 /* 122 terraform.SetDebugInfo(DefaultDataDir) 123 124 // Check for the legacy graph 125 if experiment.Enabled(experiment.X_legacyGraph) { 126 c.Ui.Output(c.Colorize().Color( 127 "[reset][bold][yellow]" + 128 "Legacy graph enabled! This will use the graph from Terraform 0.7.x\n" + 129 "to execute this operation. This will be removed in the future so\n" + 130 "please report any issues causing you to use this to the Terraform\n" + 131 "project.\n\n")) 132 } 133 */ 134 135 var conf *config.Config 136 if mod != nil { 137 conf = mod.Config() 138 } 139 140 // Load the backend 141 b, err := c.Backend(&BackendOpts{ 142 Config: conf, 143 Plan: plan, 144 }) 145 if err != nil { 146 c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) 147 return 1 148 } 149 150 // If we're not forcing and we're destroying, verify with the 151 // user at this point. 152 if !destroyForce && c.Destroy { 153 // Default destroy message 154 desc := "Terraform will delete all your managed infrastructure.\n" + 155 "There is no undo. Only 'yes' will be accepted to confirm." 156 157 // If targets are specified, list those to user 158 if c.Meta.targets != nil { 159 var descBuffer bytes.Buffer 160 descBuffer.WriteString("Terraform will delete the following infrastructure:\n") 161 for _, target := range c.Meta.targets { 162 descBuffer.WriteString("\t") 163 descBuffer.WriteString(target) 164 descBuffer.WriteString("\n") 165 } 166 descBuffer.WriteString("There is no undo. Only 'yes' will be accepted to confirm") 167 desc = descBuffer.String() 168 } 169 170 v, err := c.UIInput().Input(&terraform.InputOpts{ 171 Id: "destroy", 172 Query: "Do you really want to destroy?", 173 Description: desc, 174 }) 175 if err != nil { 176 c.Ui.Error(fmt.Sprintf("Error asking for confirmation: %s", err)) 177 return 1 178 } 179 if v != "yes" { 180 c.Ui.Output("Destroy cancelled.") 181 return 1 182 } 183 } 184 185 // Build the operation 186 opReq := c.Operation() 187 opReq.Destroy = c.Destroy 188 opReq.Module = mod 189 opReq.Plan = plan 190 opReq.PlanRefresh = refresh 191 opReq.Type = backend.OperationTypeApply 192 193 // Perform the operation 194 ctx, ctxCancel := context.WithCancel(context.Background()) 195 defer ctxCancel() 196 op, err := b.Operation(ctx, opReq) 197 if err != nil { 198 c.Ui.Error(fmt.Sprintf("Error starting operation: %s", err)) 199 return 1 200 } 201 202 // Wait for the operation to complete or an interrupt to occur 203 select { 204 case <-c.ShutdownCh: 205 // Cancel our context so we can start gracefully exiting 206 ctxCancel() 207 208 // Notify the user 209 c.Ui.Output(outputInterrupt) 210 211 // Still get the result, since there is still one 212 select { 213 case <-c.ShutdownCh: 214 c.Ui.Error( 215 "Two interrupts received. Exiting immediately. Note that data\n" + 216 "loss may have occurred.") 217 return 1 218 case <-op.Done(): 219 } 220 case <-op.Done(): 221 if err := op.Err; err != nil { 222 c.Ui.Error(err.Error()) 223 return 1 224 } 225 } 226 227 if !c.Destroy { 228 // Get the right module that we used. If we ran a plan, then use 229 // that module. 230 if plan != nil { 231 mod = plan.Module 232 } 233 234 if outputs := outputsAsString(op.State, terraform.RootModulePath, mod.Config().Outputs, true); outputs != "" { 235 c.Ui.Output(c.Colorize().Color(outputs)) 236 } 237 } 238 239 return 0 240 } 241 242 func (c *ApplyCommand) Help() string { 243 if c.Destroy { 244 return c.helpDestroy() 245 } 246 247 return c.helpApply() 248 } 249 250 func (c *ApplyCommand) Synopsis() string { 251 if c.Destroy { 252 return "Destroy Terraform-managed infrastructure" 253 } 254 255 return "Builds or changes infrastructure" 256 } 257 258 func (c *ApplyCommand) helpApply() string { 259 helpText := ` 260 Usage: terraform apply [options] [DIR-OR-PLAN] 261 262 Builds or changes infrastructure according to Terraform configuration 263 files in DIR. 264 265 By default, apply scans the current directory for the configuration 266 and applies the changes appropriately. However, a path to another 267 configuration or an execution plan can be provided. Execution plans can be 268 used to only execute a pre-determined set of actions. 269 270 DIR can also be a SOURCE as given to the "init" command. In this case, 271 apply behaves as though "init" was called followed by "apply". This only 272 works for sources that aren't files, and only if the current working 273 directory is empty of Terraform files. This is a shortcut for getting 274 started. 275 276 Options: 277 278 -backup=path Path to backup the existing state file before 279 modifying. Defaults to the "-state-out" path with 280 ".backup" extension. Set to "-" to disable backup. 281 282 -lock=true Lock the state file when locking is supported. 283 284 -lock-timeout=0s Duration to retry a state lock. 285 286 -input=true Ask for input for variables if not directly set. 287 288 -no-color If specified, output won't contain any color. 289 290 -parallelism=n Limit the number of parallel resource operations. 291 Defaults to 10. 292 293 -refresh=true Update state prior to checking for differences. This 294 has no effect if a plan file is given to apply. 295 296 -state=path Path to read and save state (unless state-out 297 is specified). Defaults to "terraform.tfstate". 298 299 -state-out=path Path to write state to that is different than 300 "-state". This can be used to preserve the old 301 state. 302 303 -target=resource Resource to target. Operation will be limited to this 304 resource and its dependencies. This flag can be used 305 multiple times. 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 -lock=true Lock the state file when locking is supported. 334 335 -lock-timeout=0s Duration to retry a state lock. 336 337 -no-color If specified, output won't contain any color. 338 339 -parallelism=n Limit the number of concurrent operations. 340 Defaults to 10. 341 342 -refresh=true Update state prior to checking for differences. This 343 has no effect if a plan file is given to apply. 344 345 -state=path Path to read and save state (unless state-out 346 is specified). Defaults to "terraform.tfstate". 347 348 -state-out=path Path to write state to that is different than 349 "-state". This can be used to preserve the old 350 state. 351 352 -target=resource Resource to target. Operation will be limited to this 353 resource and its dependencies. This flag can be used 354 multiple times. 355 356 -var 'foo=bar' Set a variable in the Terraform configuration. This 357 flag can be set multiple times. 358 359 -var-file=foo Set variables in the Terraform configuration from 360 a file. If "terraform.tfvars" is present, it will be 361 automatically loaded if this flag is not specified. 362 363 364 ` 365 return strings.TrimSpace(helpText) 366 } 367 368 func outputsAsString(state *terraform.State, modPath []string, schema []*config.Output, includeHeader bool) string { 369 if state == nil { 370 return "" 371 } 372 373 ms := state.ModuleByPath(modPath) 374 if ms == nil { 375 return "" 376 } 377 378 outputs := ms.Outputs 379 outputBuf := new(bytes.Buffer) 380 if len(outputs) > 0 { 381 schemaMap := make(map[string]*config.Output) 382 if schema != nil { 383 for _, s := range schema { 384 schemaMap[s.Name] = s 385 } 386 } 387 388 if includeHeader { 389 outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n") 390 } 391 392 // Output the outputs in alphabetical order 393 keyLen := 0 394 ks := make([]string, 0, len(outputs)) 395 for key, _ := range outputs { 396 ks = append(ks, key) 397 if len(key) > keyLen { 398 keyLen = len(key) 399 } 400 } 401 sort.Strings(ks) 402 403 for _, k := range ks { 404 schema, ok := schemaMap[k] 405 if ok && schema.Sensitive { 406 outputBuf.WriteString(fmt.Sprintf("%s = <sensitive>\n", k)) 407 continue 408 } 409 410 v := outputs[k] 411 switch typedV := v.Value.(type) { 412 case string: 413 outputBuf.WriteString(fmt.Sprintf("%s = %s\n", k, typedV)) 414 case []interface{}: 415 outputBuf.WriteString(formatListOutput("", k, typedV)) 416 outputBuf.WriteString("\n") 417 case map[string]interface{}: 418 outputBuf.WriteString(formatMapOutput("", k, typedV)) 419 outputBuf.WriteString("\n") 420 } 421 } 422 } 423 424 return strings.TrimSpace(outputBuf.String()) 425 } 426 427 const outputInterrupt = `Interrupt received. 428 Please wait for Terraform to exit or data loss may occur. 429 Gracefully shutting down...`