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