github.com/rliebz/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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.Usage = func() { c.Ui.Error(c.Help()) } 52 if err := cmdFlags.Parse(args); err != nil { 53 return 1 54 } 55 56 // Get the args. The "maybeInit" flag tracks whether we may need to 57 // initialize the configuration from a remote path. This is true as long 58 // as we have an argument. 59 args = cmdFlags.Args() 60 maybeInit := len(args) == 1 61 configPath, err := ModulePath(args) 62 if err != nil { 63 c.Ui.Error(err.Error()) 64 return 1 65 } 66 67 if !c.Destroy && maybeInit { 68 // We need the pwd for the getter operation below 69 pwd, err := os.Getwd() 70 if err != nil { 71 c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err)) 72 return 1 73 } 74 75 // Do a detect to determine if we need to do an init + apply. 76 if detected, err := getter.Detect(configPath, pwd, getter.Detectors); err != nil { 77 c.Ui.Error(fmt.Sprintf( 78 "Invalid path: %s", err)) 79 return 1 80 } else if !strings.HasPrefix(detected, "file") { 81 // If this isn't a file URL then we're doing an init + 82 // apply. 83 var init InitCommand 84 init.Meta = c.Meta 85 if code := init.Run([]string{detected}); code != 0 { 86 return code 87 } 88 89 // Change the config path to be the cwd 90 configPath = pwd 91 } 92 } 93 94 // Check if the path is a plan 95 plan, err := c.Plan(configPath) 96 if err != nil { 97 c.Ui.Error(err.Error()) 98 return 1 99 } 100 if c.Destroy && plan != nil { 101 c.Ui.Error(fmt.Sprintf( 102 "Destroy can't be called with a plan file.")) 103 return 1 104 } 105 if plan != nil { 106 // Reset the config path for backend loading 107 configPath = "" 108 } 109 110 // Load the module if we don't have one yet (not running from plan) 111 var mod *module.Tree 112 if plan == nil { 113 mod, err = c.Module(configPath) 114 if err != nil { 115 c.Ui.Error(fmt.Sprintf("Failed to load root config module: %s", err)) 116 return 1 117 } 118 } 119 120 /* 121 terraform.SetDebugInfo(DefaultDataDir) 122 123 // Check for the legacy graph 124 if experiment.Enabled(experiment.X_legacyGraph) { 125 c.Ui.Output(c.Colorize().Color( 126 "[reset][bold][yellow]" + 127 "Legacy graph enabled! This will use the graph from Terraform 0.7.x\n" + 128 "to execute this operation. This will be removed in the future so\n" + 129 "please report any issues causing you to use this to the Terraform\n" + 130 "project.\n\n")) 131 } 132 */ 133 134 // Load the backend 135 b, err := c.Backend(&BackendOpts{ 136 ConfigPath: configPath, 137 Plan: plan, 138 }) 139 if err != nil { 140 c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) 141 return 1 142 } 143 144 // If we're not forcing and we're destroying, verify with the 145 // user at this point. 146 if !destroyForce && c.Destroy { 147 // Default destroy message 148 desc := "Terraform will delete all your managed infrastructure.\n" + 149 "There is no undo. Only 'yes' will be accepted to confirm." 150 151 // If targets are specified, list those to user 152 if c.Meta.targets != nil { 153 var descBuffer bytes.Buffer 154 descBuffer.WriteString("Terraform will delete the following infrastructure:\n") 155 for _, target := range c.Meta.targets { 156 descBuffer.WriteString("\t") 157 descBuffer.WriteString(target) 158 descBuffer.WriteString("\n") 159 } 160 descBuffer.WriteString("There is no undo. Only 'yes' will be accepted to confirm") 161 desc = descBuffer.String() 162 } 163 164 v, err := c.UIInput().Input(&terraform.InputOpts{ 165 Id: "destroy", 166 Query: "Do you really want to destroy?", 167 Description: desc, 168 }) 169 if err != nil { 170 c.Ui.Error(fmt.Sprintf("Error asking for confirmation: %s", err)) 171 return 1 172 } 173 if v != "yes" { 174 c.Ui.Output("Destroy cancelled.") 175 return 1 176 } 177 } 178 179 // Build the operation 180 opReq := c.Operation() 181 opReq.Destroy = c.Destroy 182 opReq.Module = mod 183 opReq.Plan = plan 184 opReq.PlanRefresh = refresh 185 opReq.Type = backend.OperationTypeApply 186 opReq.LockState = c.Meta.stateLock 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("Interrupt received. Gracefully shutting down...") 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 -input=true Ask for input for variables if not directly set. 280 281 -no-color If specified, output won't contain any color. 282 283 -parallelism=n Limit the number of parallel resource operations. 284 Defaults to 10. 285 286 -refresh=true Update state prior to checking for differences. This 287 has no effect if a plan file is given to apply. 288 289 -state=path Path to read and save state (unless state-out 290 is specified). Defaults to "terraform.tfstate". 291 292 -state-out=path Path to write state to that is different than 293 "-state". This can be used to preserve the old 294 state. 295 296 -target=resource Resource to target. Operation will be limited to this 297 resource and its dependencies. This flag can be used 298 multiple times. 299 300 -var 'foo=bar' Set a variable in the Terraform configuration. This 301 flag can be set multiple times. 302 303 -var-file=foo Set variables in the Terraform configuration from 304 a file. If "terraform.tfvars" is present, it will be 305 automatically loaded if this flag is not specified. 306 307 308 ` 309 return strings.TrimSpace(helpText) 310 } 311 312 func (c *ApplyCommand) helpDestroy() string { 313 helpText := ` 314 Usage: terraform destroy [options] [DIR] 315 316 Destroy Terraform-managed infrastructure. 317 318 Options: 319 320 -backup=path Path to backup the existing state file before 321 modifying. Defaults to the "-state-out" path with 322 ".backup" extension. Set to "-" to disable backup. 323 324 -force Don't ask for input for destroy confirmation. 325 326 -lock=true Lock the state file when locking is supported. 327 328 -no-color If specified, output won't contain any color. 329 330 -parallelism=n Limit the number of concurrent operations. 331 Defaults to 10. 332 333 -refresh=true Update state prior to checking for differences. This 334 has no effect if a plan file is given to apply. 335 336 -state=path Path to read and save state (unless state-out 337 is specified). Defaults to "terraform.tfstate". 338 339 -state-out=path Path to write state to that is different than 340 "-state". This can be used to preserve the old 341 state. 342 343 -target=resource Resource to target. Operation will be limited to this 344 resource and its dependencies. This flag can be used 345 multiple times. 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 } 358 359 func outputsAsString(state *terraform.State, modPath []string, schema []*config.Output, includeHeader bool) string { 360 if state == nil { 361 return "" 362 } 363 364 ms := state.ModuleByPath(modPath) 365 if ms == nil { 366 return "" 367 } 368 369 outputs := ms.Outputs 370 outputBuf := new(bytes.Buffer) 371 if len(outputs) > 0 { 372 schemaMap := make(map[string]*config.Output) 373 if schema != nil { 374 for _, s := range schema { 375 schemaMap[s.Name] = s 376 } 377 } 378 379 if includeHeader { 380 outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n") 381 } 382 383 // Output the outputs in alphabetical order 384 keyLen := 0 385 ks := make([]string, 0, len(outputs)) 386 for key, _ := range outputs { 387 ks = append(ks, key) 388 if len(key) > keyLen { 389 keyLen = len(key) 390 } 391 } 392 sort.Strings(ks) 393 394 for _, k := range ks { 395 schema, ok := schemaMap[k] 396 if ok && schema.Sensitive { 397 outputBuf.WriteString(fmt.Sprintf("%s = <sensitive>\n", k)) 398 continue 399 } 400 401 v := outputs[k] 402 switch typedV := v.Value.(type) { 403 case string: 404 outputBuf.WriteString(fmt.Sprintf("%s = %s\n", k, typedV)) 405 case []interface{}: 406 outputBuf.WriteString(formatListOutput("", k, typedV)) 407 outputBuf.WriteString("\n") 408 case map[string]interface{}: 409 outputBuf.WriteString(formatMapOutput("", k, typedV)) 410 outputBuf.WriteString("\n") 411 } 412 } 413 } 414 415 return strings.TrimSpace(outputBuf.String()) 416 }