github.com/vic3lord/terraform@v0.8.0-rc1.0.20170626102919-16c6dd2cb372/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 // Check for user-supplied plugin path 69 if c.pluginPath, err = c.loadPluginPath(); err != nil { 70 c.Ui.Error(fmt.Sprintf("Error loading plugin path: %s", err)) 71 return 1 72 } 73 74 if !c.Destroy && maybeInit { 75 // We need the pwd for the getter operation below 76 pwd, err := os.Getwd() 77 if err != nil { 78 c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err)) 79 return 1 80 } 81 82 // Do a detect to determine if we need to do an init + apply. 83 if detected, err := getter.Detect(configPath, pwd, getter.Detectors); err != nil { 84 c.Ui.Error(fmt.Sprintf( 85 "Invalid path: %s", err)) 86 return 1 87 } else if !strings.HasPrefix(detected, "file") { 88 // If this isn't a file URL then we're doing an init + 89 // apply. 90 var init InitCommand 91 init.Meta = c.Meta 92 if code := init.Run([]string{detected}); code != 0 { 93 return code 94 } 95 96 // Change the config path to be the cwd 97 configPath = pwd 98 } 99 } 100 101 // Check if the path is a plan 102 plan, err := c.Plan(configPath) 103 if err != nil { 104 c.Ui.Error(err.Error()) 105 return 1 106 } 107 if c.Destroy && plan != nil { 108 c.Ui.Error(fmt.Sprintf( 109 "Destroy can't be called with a plan file.")) 110 return 1 111 } 112 if plan != nil { 113 // Reset the config path for backend loading 114 configPath = "" 115 } 116 117 // Load the module if we don't have one yet (not running from plan) 118 var mod *module.Tree 119 if plan == nil { 120 mod, err = c.Module(configPath) 121 if err != nil { 122 c.Ui.Error(fmt.Sprintf("Failed to load root config module: %s", err)) 123 return 1 124 } 125 } 126 127 /* 128 terraform.SetDebugInfo(DefaultDataDir) 129 130 // Check for the legacy graph 131 if experiment.Enabled(experiment.X_legacyGraph) { 132 c.Ui.Output(c.Colorize().Color( 133 "[reset][bold][yellow]" + 134 "Legacy graph enabled! This will use the graph from Terraform 0.7.x\n" + 135 "to execute this operation. This will be removed in the future so\n" + 136 "please report any issues causing you to use this to the Terraform\n" + 137 "project.\n\n")) 138 } 139 */ 140 141 var conf *config.Config 142 if mod != nil { 143 conf = mod.Config() 144 } 145 146 // Load the backend 147 b, err := c.Backend(&BackendOpts{ 148 Config: conf, 149 Plan: plan, 150 }) 151 if err != nil { 152 c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) 153 return 1 154 } 155 156 // If we're not forcing and we're destroying, verify with the 157 // user at this point. 158 if !destroyForce && c.Destroy { 159 // Default destroy message 160 desc := "Terraform will delete all your managed infrastructure.\n" + 161 "There is no undo. Only 'yes' will be accepted to confirm." 162 163 // If targets are specified, list those to user 164 if c.Meta.targets != nil { 165 var descBuffer bytes.Buffer 166 descBuffer.WriteString("Terraform will delete the following infrastructure:\n") 167 for _, target := range c.Meta.targets { 168 descBuffer.WriteString("\t") 169 descBuffer.WriteString(target) 170 descBuffer.WriteString("\n") 171 } 172 descBuffer.WriteString("There is no undo. Only 'yes' will be accepted to confirm") 173 desc = descBuffer.String() 174 } 175 176 v, err := c.UIInput().Input(&terraform.InputOpts{ 177 Id: "destroy", 178 Query: "Do you really want to destroy?", 179 Description: desc, 180 }) 181 if err != nil { 182 c.Ui.Error(fmt.Sprintf("Error asking for confirmation: %s", err)) 183 return 1 184 } 185 if v != "yes" { 186 c.Ui.Output("Destroy cancelled.") 187 return 1 188 } 189 } 190 191 // Build the operation 192 opReq := c.Operation() 193 opReq.Destroy = c.Destroy 194 opReq.Module = mod 195 opReq.Plan = plan 196 opReq.PlanRefresh = refresh 197 opReq.Type = backend.OperationTypeApply 198 199 // Perform the operation 200 ctx, ctxCancel := context.WithCancel(context.Background()) 201 defer ctxCancel() 202 op, err := b.Operation(ctx, opReq) 203 if err != nil { 204 c.Ui.Error(fmt.Sprintf("Error starting operation: %s", err)) 205 return 1 206 } 207 208 // Wait for the operation to complete or an interrupt to occur 209 select { 210 case <-c.ShutdownCh: 211 // Cancel our context so we can start gracefully exiting 212 ctxCancel() 213 214 // Notify the user 215 c.Ui.Output(outputInterrupt) 216 217 // Still get the result, since there is still one 218 select { 219 case <-c.ShutdownCh: 220 c.Ui.Error( 221 "Two interrupts received. Exiting immediately. Note that data\n" + 222 "loss may have occurred.") 223 return 1 224 case <-op.Done(): 225 } 226 case <-op.Done(): 227 if err := op.Err; err != nil { 228 c.Ui.Error(err.Error()) 229 return 1 230 } 231 } 232 233 if !c.Destroy { 234 // Get the right module that we used. If we ran a plan, then use 235 // that module. 236 if plan != nil { 237 mod = plan.Module 238 } 239 240 if outputs := outputsAsString(op.State, terraform.RootModulePath, mod.Config().Outputs, true); outputs != "" { 241 c.Ui.Output(c.Colorize().Color(outputs)) 242 } 243 } 244 245 return 0 246 } 247 248 func (c *ApplyCommand) Help() string { 249 if c.Destroy { 250 return c.helpDestroy() 251 } 252 253 return c.helpApply() 254 } 255 256 func (c *ApplyCommand) Synopsis() string { 257 if c.Destroy { 258 return "Destroy Terraform-managed infrastructure" 259 } 260 261 return "Builds or changes infrastructure" 262 } 263 264 func (c *ApplyCommand) helpApply() string { 265 helpText := ` 266 Usage: terraform apply [options] [DIR-OR-PLAN] 267 268 Builds or changes infrastructure according to Terraform configuration 269 files in DIR. 270 271 By default, apply scans the current directory for the configuration 272 and applies the changes appropriately. However, a path to another 273 configuration or an execution plan can be provided. Execution plans can be 274 used to only execute a pre-determined set of actions. 275 276 DIR can also be a SOURCE as given to the "init" command. In this case, 277 apply behaves as though "init" was called followed by "apply". This only 278 works for sources that aren't files, and only if the current working 279 directory is empty of Terraform files. This is a shortcut for getting 280 started. 281 282 Options: 283 284 -backup=path Path to backup the existing state file before 285 modifying. Defaults to the "-state-out" path with 286 ".backup" extension. Set to "-" to disable backup. 287 288 -lock=true Lock the state file when locking is supported. 289 290 -lock-timeout=0s Duration to retry a state lock. 291 292 -input=true Ask for input for variables if not directly set. 293 294 -no-color If specified, output won't contain any color. 295 296 -parallelism=n Limit the number of parallel resource operations. 297 Defaults to 10. 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 -target=resource Resource to target. Operation will be limited to this 310 resource and its dependencies. This flag can be used 311 multiple times. 312 313 -var 'foo=bar' Set a variable in the Terraform configuration. This 314 flag can be set multiple times. 315 316 -var-file=foo Set variables in the Terraform configuration from 317 a file. If "terraform.tfvars" is present, it will be 318 automatically loaded if this flag is not specified. 319 320 321 ` 322 return strings.TrimSpace(helpText) 323 } 324 325 func (c *ApplyCommand) helpDestroy() string { 326 helpText := ` 327 Usage: terraform destroy [options] [DIR] 328 329 Destroy Terraform-managed infrastructure. 330 331 Options: 332 333 -backup=path Path to backup the existing state file before 334 modifying. Defaults to the "-state-out" path with 335 ".backup" extension. Set to "-" to disable backup. 336 337 -force Don't ask for input for destroy confirmation. 338 339 -lock=true Lock the state file when locking is supported. 340 341 -lock-timeout=0s Duration to retry a state lock. 342 343 -no-color If specified, output won't contain any color. 344 345 -parallelism=n Limit the number of concurrent operations. 346 Defaults to 10. 347 348 -refresh=true Update state prior to checking for differences. This 349 has no effect if a plan file is given to apply. 350 351 -state=path Path to read and save state (unless state-out 352 is specified). Defaults to "terraform.tfstate". 353 354 -state-out=path Path to write state to that is different than 355 "-state". This can be used to preserve the old 356 state. 357 358 -target=resource Resource to target. Operation will be limited to this 359 resource and its dependencies. This flag can be used 360 multiple times. 361 362 -var 'foo=bar' Set a variable in the Terraform configuration. This 363 flag can be set multiple times. 364 365 -var-file=foo Set variables in the Terraform configuration from 366 a file. If "terraform.tfvars" is present, it will be 367 automatically loaded if this flag is not specified. 368 369 370 ` 371 return strings.TrimSpace(helpText) 372 } 373 374 func outputsAsString(state *terraform.State, modPath []string, schema []*config.Output, includeHeader bool) string { 375 if state == nil { 376 return "" 377 } 378 379 ms := state.ModuleByPath(modPath) 380 if ms == nil { 381 return "" 382 } 383 384 outputs := ms.Outputs 385 outputBuf := new(bytes.Buffer) 386 if len(outputs) > 0 { 387 schemaMap := make(map[string]*config.Output) 388 if schema != nil { 389 for _, s := range schema { 390 schemaMap[s.Name] = s 391 } 392 } 393 394 if includeHeader { 395 outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n") 396 } 397 398 // Output the outputs in alphabetical order 399 keyLen := 0 400 ks := make([]string, 0, len(outputs)) 401 for key, _ := range outputs { 402 ks = append(ks, key) 403 if len(key) > keyLen { 404 keyLen = len(key) 405 } 406 } 407 sort.Strings(ks) 408 409 for _, k := range ks { 410 schema, ok := schemaMap[k] 411 if ok && schema.Sensitive { 412 outputBuf.WriteString(fmt.Sprintf("%s = <sensitive>\n", k)) 413 continue 414 } 415 416 v := outputs[k] 417 switch typedV := v.Value.(type) { 418 case string: 419 outputBuf.WriteString(fmt.Sprintf("%s = %s\n", k, typedV)) 420 case []interface{}: 421 outputBuf.WriteString(formatListOutput("", k, typedV)) 422 outputBuf.WriteString("\n") 423 case map[string]interface{}: 424 outputBuf.WriteString(formatMapOutput("", k, typedV)) 425 outputBuf.WriteString("\n") 426 } 427 } 428 } 429 430 return strings.TrimSpace(outputBuf.String()) 431 } 432 433 const outputInterrupt = `Interrupt received. 434 Please wait for Terraform to exit or data loss may occur. 435 Gracefully shutting down...`