github.com/candidpartners/terraform@v0.9.5-0.20171005231213-29f5f88820f6/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 // Load the backend 136 b, err := c.Backend(&BackendOpts{ 137 ConfigPath: configPath, 138 Plan: plan, 139 }) 140 if err != nil { 141 c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) 142 return 1 143 } 144 145 // If we're not forcing and we're destroying, verify with the 146 // user at this point. 147 if !destroyForce && c.Destroy { 148 // Default destroy message 149 desc := "Terraform will delete all your managed infrastructure.\n" + 150 "There is no undo. Only 'yes' will be accepted to confirm." 151 152 // If targets are specified, list those to user 153 if c.Meta.targets != nil { 154 var descBuffer bytes.Buffer 155 descBuffer.WriteString("Terraform will delete the following infrastructure:\n") 156 for _, target := range c.Meta.targets { 157 descBuffer.WriteString("\t") 158 descBuffer.WriteString(target) 159 descBuffer.WriteString("\n") 160 } 161 descBuffer.WriteString("There is no undo. Only 'yes' will be accepted to confirm") 162 desc = descBuffer.String() 163 } 164 165 v, err := c.UIInput().Input(&terraform.InputOpts{ 166 Id: "destroy", 167 Query: "Do you really want to destroy?", 168 Description: desc, 169 }) 170 if err != nil { 171 c.Ui.Error(fmt.Sprintf("Error asking for confirmation: %s", err)) 172 return 1 173 } 174 if v != "yes" { 175 c.Ui.Output("Destroy cancelled.") 176 return 1 177 } 178 } 179 180 // Build the operation 181 opReq := c.Operation() 182 opReq.Destroy = c.Destroy 183 opReq.Module = mod 184 opReq.Plan = plan 185 opReq.PlanRefresh = refresh 186 opReq.Type = backend.OperationTypeApply 187 188 // Perform the operation 189 ctx, ctxCancel := context.WithCancel(context.Background()) 190 defer ctxCancel() 191 op, err := b.Operation(ctx, opReq) 192 if err != nil { 193 c.Ui.Error(fmt.Sprintf("Error starting operation: %s", err)) 194 return 1 195 } 196 197 // Wait for the operation to complete or an interrupt to occur 198 select { 199 case <-c.ShutdownCh: 200 // Cancel our context so we can start gracefully exiting 201 ctxCancel() 202 203 // Notify the user 204 c.Ui.Output(outputInterrupt) 205 206 // Still get the result, since there is still one 207 select { 208 case <-c.ShutdownCh: 209 c.Ui.Error( 210 "Two interrupts received. Exiting immediately. Note that data\n" + 211 "loss may have occurred.") 212 return 1 213 case <-op.Done(): 214 } 215 case <-op.Done(): 216 if err := op.Err; err != nil { 217 c.Ui.Error(err.Error()) 218 return 1 219 } 220 } 221 222 if !c.Destroy { 223 // Get the right module that we used. If we ran a plan, then use 224 // that module. 225 if plan != nil { 226 mod = plan.Module 227 } 228 229 if outputs := outputsAsString(op.State, terraform.RootModulePath, mod.Config().Outputs, true); outputs != "" { 230 c.Ui.Output(c.Colorize().Color(outputs)) 231 } 232 } 233 234 return 0 235 } 236 237 func (c *ApplyCommand) Help() string { 238 if c.Destroy { 239 return c.helpDestroy() 240 } 241 242 return c.helpApply() 243 } 244 245 func (c *ApplyCommand) Synopsis() string { 246 if c.Destroy { 247 return "Destroy Terraform-managed infrastructure" 248 } 249 250 return "Builds or changes infrastructure" 251 } 252 253 func (c *ApplyCommand) helpApply() string { 254 helpText := ` 255 Usage: terraform apply [options] [DIR-OR-PLAN] 256 257 Builds or changes infrastructure according to Terraform configuration 258 files in DIR. 259 260 By default, apply scans the current directory for the configuration 261 and applies the changes appropriately. However, a path to another 262 configuration or an execution plan can be provided. Execution plans can be 263 used to only execute a pre-determined set of actions. 264 265 DIR can also be a SOURCE as given to the "init" command. In this case, 266 apply behaves as though "init" was called followed by "apply". This only 267 works for sources that aren't files, and only if the current working 268 directory is empty of Terraform files. This is a shortcut for getting 269 started. 270 271 Options: 272 273 -backup=path Path to backup the existing state file before 274 modifying. Defaults to the "-state-out" path with 275 ".backup" extension. Set to "-" to disable backup. 276 277 -lock=true Lock the state file when locking is supported. 278 279 -lock-timeout=0s Duration to retry a state lock. 280 281 -input=true Ask for input for variables if not directly set. 282 283 -no-color If specified, output won't contain any color. 284 285 -parallelism=n Limit the number of parallel resource operations. 286 Defaults to 10. 287 288 -refresh=true Update state prior to checking for differences. This 289 has no effect if a plan file is given to apply. 290 291 -state=path Path to read and save state (unless state-out 292 is specified). Defaults to "terraform.tfstate". 293 294 -state-out=path Path to write state to that is different than 295 "-state". This can be used to preserve the old 296 state. 297 298 -target=resource Resource to target. Operation will be limited to this 299 resource and its dependencies. This flag can be used 300 multiple times. 301 302 -var 'foo=bar' Set a variable in the Terraform configuration. This 303 flag can be set multiple times. 304 305 -var-file=foo Set variables in the Terraform configuration from 306 a file. If "terraform.tfvars" is present, it will be 307 automatically loaded if this flag is not specified. 308 309 310 ` 311 return strings.TrimSpace(helpText) 312 } 313 314 func (c *ApplyCommand) helpDestroy() string { 315 helpText := ` 316 Usage: terraform destroy [options] [DIR] 317 318 Destroy Terraform-managed infrastructure. 319 320 Options: 321 322 -backup=path Path to backup the existing state file before 323 modifying. Defaults to the "-state-out" path with 324 ".backup" extension. Set to "-" to disable backup. 325 326 -force Don't ask for input for destroy confirmation. 327 328 -lock=true Lock the state file when locking is supported. 329 330 -lock-timeout=0s Duration to retry a state lock. 331 332 -no-color If specified, output won't contain any color. 333 334 -parallelism=n Limit the number of concurrent operations. 335 Defaults to 10. 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 -target=resource Resource to target. Operation will be limited to this 348 resource and its dependencies. This flag can be used 349 multiple times. 350 351 -var 'foo=bar' Set a variable in the Terraform configuration. This 352 flag can be set multiple times. 353 354 -var-file=foo Set variables in the Terraform configuration from 355 a file. If "terraform.tfvars" is present, it will be 356 automatically loaded if this flag is not specified. 357 358 359 ` 360 return strings.TrimSpace(helpText) 361 } 362 363 func outputsAsString(state *terraform.State, modPath []string, schema []*config.Output, includeHeader bool) string { 364 if state == nil { 365 return "" 366 } 367 368 ms := state.ModuleByPath(modPath) 369 if ms == nil { 370 return "" 371 } 372 373 outputs := ms.Outputs 374 outputBuf := new(bytes.Buffer) 375 if len(outputs) > 0 { 376 schemaMap := make(map[string]*config.Output) 377 if schema != nil { 378 for _, s := range schema { 379 schemaMap[s.Name] = s 380 } 381 } 382 383 if includeHeader { 384 outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n") 385 } 386 387 // Output the outputs in alphabetical order 388 keyLen := 0 389 ks := make([]string, 0, len(outputs)) 390 for key, _ := range outputs { 391 ks = append(ks, key) 392 if len(key) > keyLen { 393 keyLen = len(key) 394 } 395 } 396 sort.Strings(ks) 397 398 for _, k := range ks { 399 schema, ok := schemaMap[k] 400 if ok && schema.Sensitive { 401 outputBuf.WriteString(fmt.Sprintf("%s = <sensitive>\n", k)) 402 continue 403 } 404 405 v := outputs[k] 406 switch typedV := v.Value.(type) { 407 case string: 408 outputBuf.WriteString(fmt.Sprintf("%s = %s\n", k, typedV)) 409 case []interface{}: 410 outputBuf.WriteString(formatListOutput("", k, typedV)) 411 outputBuf.WriteString("\n") 412 case map[string]interface{}: 413 outputBuf.WriteString(formatMapOutput("", k, typedV)) 414 outputBuf.WriteString("\n") 415 } 416 } 417 } 418 419 return strings.TrimSpace(outputBuf.String()) 420 } 421 422 const outputInterrupt = `Interrupt received. 423 Please wait for Terraform to exit or data loss may occur. 424 Gracefully shutting down...`