github.com/paultyng/terraform@v0.6.11-0.20180227224804-66ff8f8bed40/command/init.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "log" 6 "os" 7 "sort" 8 "strings" 9 10 "github.com/posener/complete" 11 12 multierror "github.com/hashicorp/go-multierror" 13 "github.com/hashicorp/terraform/backend" 14 "github.com/hashicorp/terraform/config" 15 "github.com/hashicorp/terraform/config/module" 16 "github.com/hashicorp/terraform/helper/variables" 17 "github.com/hashicorp/terraform/plugin" 18 "github.com/hashicorp/terraform/plugin/discovery" 19 "github.com/hashicorp/terraform/terraform" 20 ) 21 22 // InitCommand is a Command implementation that takes a Terraform 23 // module and clones it to the working directory. 24 type InitCommand struct { 25 Meta 26 27 // getPlugins is for the -get-plugins flag 28 getPlugins bool 29 30 // providerInstaller is used to download and install providers that 31 // aren't found locally. This uses a discovery.ProviderInstaller instance 32 // by default, but it can be overridden here as a way to mock fetching 33 // providers for tests. 34 providerInstaller discovery.Installer 35 } 36 37 func (c *InitCommand) Run(args []string) int { 38 var flagFromModule string 39 var flagBackend, flagGet, flagUpgrade bool 40 var flagConfigExtra map[string]interface{} 41 var flagPluginPath FlagStringSlice 42 var flagVerifyPlugins bool 43 44 args, err := c.Meta.process(args, false) 45 if err != nil { 46 return 1 47 } 48 cmdFlags := c.flagSet("init") 49 cmdFlags.BoolVar(&flagBackend, "backend", true, "") 50 cmdFlags.Var((*variables.FlagAny)(&flagConfigExtra), "backend-config", "") 51 cmdFlags.StringVar(&flagFromModule, "from-module", "", "copy the source of the given module into the directory before init") 52 cmdFlags.BoolVar(&flagGet, "get", true, "") 53 cmdFlags.BoolVar(&c.getPlugins, "get-plugins", true, "") 54 cmdFlags.BoolVar(&c.forceInitCopy, "force-copy", false, "suppress prompts about copying state data") 55 cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") 56 cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") 57 cmdFlags.BoolVar(&c.reconfigure, "reconfigure", false, "reconfigure") 58 cmdFlags.BoolVar(&flagUpgrade, "upgrade", false, "") 59 cmdFlags.Var(&flagPluginPath, "plugin-dir", "plugin directory") 60 cmdFlags.BoolVar(&flagVerifyPlugins, "verify-plugins", true, "verify plugins") 61 62 cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } 63 if err := cmdFlags.Parse(args); err != nil { 64 return 1 65 } 66 67 if len(flagPluginPath) > 0 { 68 c.pluginPath = flagPluginPath 69 c.getPlugins = false 70 } 71 72 // set providerInstaller if we don't have a test version already 73 if c.providerInstaller == nil { 74 c.providerInstaller = &discovery.ProviderInstaller{ 75 Dir: c.pluginDir(), 76 Cache: c.pluginCache(), 77 PluginProtocolVersion: plugin.Handshake.ProtocolVersion, 78 SkipVerify: !flagVerifyPlugins, 79 Ui: c.Ui, 80 } 81 } 82 83 // Validate the arg count 84 args = cmdFlags.Args() 85 if len(args) > 1 { 86 c.Ui.Error("The init command expects at most one argument.\n") 87 cmdFlags.Usage() 88 return 1 89 } 90 91 if err := c.storePluginPath(c.pluginPath); err != nil { 92 c.Ui.Error(fmt.Sprintf("Error saving -plugin-path values: %s", err)) 93 return 1 94 } 95 96 // Get our pwd. We don't always need it but always getting it is easier 97 // than the logic to determine if it is or isn't needed. 98 pwd, err := os.Getwd() 99 if err != nil { 100 c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err)) 101 return 1 102 } 103 104 // If an argument is provided then it overrides our working directory. 105 path := pwd 106 if len(args) == 1 { 107 path = args[0] 108 } 109 110 // This will track whether we outputted anything so that we know whether 111 // to output a newline before the success message 112 var header bool 113 114 if flagFromModule != "" { 115 src := flagFromModule 116 117 empty, err := config.IsEmptyDir(path) 118 if err != nil { 119 c.Ui.Error(fmt.Sprintf("Error validating destination directory: %s", err)) 120 return 1 121 } 122 if !empty { 123 c.Ui.Error(strings.TrimSpace(errInitCopyNotEmpty)) 124 return 1 125 } 126 127 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 128 "[reset][bold]Copying configuration[reset] from %q...", src, 129 ))) 130 header = true 131 132 s := module.NewStorage("", c.Services, c.Credentials) 133 if err := s.GetModule(path, src); err != nil { 134 c.Ui.Error(fmt.Sprintf("Error copying source module: %s", err)) 135 return 1 136 } 137 } 138 139 // If our directory is empty, then we're done. We can't get or setup 140 // the backend with an empty directory. 141 if empty, err := config.IsEmptyDir(path); err != nil { 142 c.Ui.Error(fmt.Sprintf( 143 "Error checking configuration: %s", err)) 144 return 1 145 } else if empty { 146 c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitEmpty))) 147 return 0 148 } 149 150 var back backend.Backend 151 152 // If we're performing a get or loading the backend, then we perform 153 // some extra tasks. 154 if flagGet || flagBackend { 155 conf, err := c.Config(path) 156 if err != nil { 157 // Since this may be the user's first ever interaction with Terraform, 158 // we'll provide some additional context in this case. 159 c.Ui.Error(strings.TrimSpace(errInitConfigError)) 160 c.showDiagnostics(err) 161 return 1 162 } 163 164 // If we requested downloading modules and have modules in the config 165 if flagGet && len(conf.Modules) > 0 { 166 header = true 167 168 getMode := module.GetModeGet 169 if flagUpgrade { 170 getMode = module.GetModeUpdate 171 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 172 "[reset][bold]Upgrading modules..."))) 173 } else { 174 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 175 "[reset][bold]Initializing modules..."))) 176 } 177 178 if err := getModules(&c.Meta, path, getMode); err != nil { 179 c.Ui.Error(fmt.Sprintf( 180 "Error downloading modules: %s", err)) 181 return 1 182 } 183 } 184 185 // If we're requesting backend configuration or looking for required 186 // plugins, load the backend 187 if flagBackend { 188 header = true 189 190 // Only output that we're initializing a backend if we have 191 // something in the config. We can be UNSETTING a backend as well 192 // in which case we choose not to show this. 193 if conf.Terraform != nil && conf.Terraform.Backend != nil { 194 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 195 "\n[reset][bold]Initializing the backend..."))) 196 } 197 198 opts := &BackendOpts{ 199 Config: conf, 200 ConfigExtra: flagConfigExtra, 201 Init: true, 202 } 203 if back, err = c.Backend(opts); err != nil { 204 c.Ui.Error(err.Error()) 205 return 1 206 } 207 } 208 } 209 210 if back == nil { 211 // If we didn't initialize a backend then we'll try to at least 212 // instantiate one. This might fail if it wasn't already initalized 213 // by a previous run, so we must still expect that "back" may be nil 214 // in code that follows. 215 back, err = c.Backend(nil) 216 if err != nil { 217 // This is fine. We'll proceed with no backend, then. 218 back = nil 219 } 220 } 221 222 var state *terraform.State 223 224 // If we have a functional backend (either just initialized or initialized 225 // on a previous run) we'll use the current state as a potential source 226 // of provider dependencies. 227 if back != nil { 228 sMgr, err := back.State(c.Workspace()) 229 if err != nil { 230 c.Ui.Error(fmt.Sprintf( 231 "Error loading state: %s", err)) 232 return 1 233 } 234 235 if err := sMgr.RefreshState(); err != nil { 236 c.Ui.Error(fmt.Sprintf( 237 "Error refreshing state: %s", err)) 238 return 1 239 } 240 241 state = sMgr.State() 242 } 243 244 if v := os.Getenv(ProviderSkipVerifyEnvVar); v != "" { 245 c.ignorePluginChecksum = true 246 } 247 248 // Now that we have loaded all modules, check the module tree for missing providers. 249 err = c.getProviders(path, state, flagUpgrade) 250 if err != nil { 251 // this function provides its own output 252 log.Printf("[ERROR] %s", err) 253 return 1 254 } 255 256 // If we outputted information, then we need to output a newline 257 // so that our success message is nicely spaced out from prior text. 258 if header { 259 c.Ui.Output("") 260 } 261 262 c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitSuccess))) 263 if !c.RunningInAutomation { 264 // If we're not running in an automation wrapper, give the user 265 // some more detailed next steps that are appropriate for interactive 266 // shell usage. 267 c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitSuccessCLI))) 268 } 269 270 return 0 271 } 272 273 // Load the complete module tree, and fetch any missing providers. 274 // This method outputs its own Ui. 275 func (c *InitCommand) getProviders(path string, state *terraform.State, upgrade bool) error { 276 mod, diags := c.Module(path) 277 if diags.HasErrors() { 278 c.showDiagnostics(diags) 279 return diags.Err() 280 } 281 282 if err := terraform.CheckStateVersion(state); err != nil { 283 diags = diags.Append(err) 284 c.showDiagnostics(diags) 285 return err 286 } 287 288 if err := terraform.CheckRequiredVersion(mod); err != nil { 289 diags = diags.Append(err) 290 c.showDiagnostics(diags) 291 return err 292 } 293 294 var available discovery.PluginMetaSet 295 if upgrade { 296 // If we're in upgrade mode, we ignore any auto-installed plugins 297 // in "available", causing us to reinstall and possibly upgrade them. 298 available = c.providerPluginManuallyInstalledSet() 299 } else { 300 available = c.providerPluginSet() 301 } 302 303 requirements := terraform.ModuleTreeDependencies(mod, state).AllPluginRequirements() 304 if len(requirements) == 0 { 305 // nothing to initialize 306 return nil 307 } 308 309 c.Ui.Output(c.Colorize().Color( 310 "\n[reset][bold]Initializing provider plugins...", 311 )) 312 313 missing := c.missingPlugins(available, requirements) 314 315 var errs error 316 if c.getPlugins { 317 if len(missing) > 0 { 318 c.Ui.Output(fmt.Sprintf("- Checking for available provider plugins on %s...", 319 discovery.GetReleaseHost())) 320 } 321 322 for provider, reqd := range missing { 323 _, err := c.providerInstaller.Get(provider, reqd.Versions) 324 325 if err != nil { 326 switch err { 327 case discovery.ErrorNoSuchProvider: 328 c.Ui.Error(fmt.Sprintf(errProviderNotFound, provider, DefaultPluginVendorDir)) 329 case discovery.ErrorNoSuitableVersion: 330 if reqd.Versions.Unconstrained() { 331 // This should never happen, but might crop up if we catch 332 // the releases server in a weird state where the provider's 333 // directory is present but does not yet contain any 334 // versions. We'll treat it like ErrorNoSuchProvider, then. 335 c.Ui.Error(fmt.Sprintf(errProviderNotFound, provider, DefaultPluginVendorDir)) 336 } else { 337 c.Ui.Error(fmt.Sprintf(errProviderVersionsUnsuitable, provider, reqd.Versions)) 338 } 339 case discovery.ErrorNoVersionCompatible: 340 // FIXME: This error message is sub-awesome because we don't 341 // have enough information here to tell the user which versions 342 // we considered and which versions might be compatible. 343 constraint := reqd.Versions.String() 344 if constraint == "" { 345 constraint = "(any version)" 346 } 347 c.Ui.Error(fmt.Sprintf(errProviderIncompatible, provider, constraint)) 348 default: 349 c.Ui.Error(fmt.Sprintf(errProviderInstallError, provider, err.Error(), DefaultPluginVendorDir)) 350 } 351 352 errs = multierror.Append(errs, err) 353 } 354 } 355 356 if errs != nil { 357 return errs 358 } 359 } else if len(missing) > 0 { 360 // we have missing providers, but aren't going to try and download them 361 var lines []string 362 for provider, reqd := range missing { 363 if reqd.Versions.Unconstrained() { 364 lines = append(lines, fmt.Sprintf("* %s (any version)\n", provider)) 365 } else { 366 lines = append(lines, fmt.Sprintf("* %s (%s)\n", provider, reqd.Versions)) 367 } 368 errs = multierror.Append(errs, fmt.Errorf("missing provider %q", provider)) 369 } 370 sort.Strings(lines) 371 c.Ui.Error(fmt.Sprintf(errMissingProvidersNoInstall, strings.Join(lines, ""), DefaultPluginVendorDir)) 372 return errs 373 } 374 375 // With all the providers downloaded, we'll generate our lock file 376 // that ensures the provider binaries remain unchanged until we init 377 // again. If anything changes, other commands that use providers will 378 // fail with an error instructing the user to re-run this command. 379 available = c.providerPluginSet() // re-discover to see newly-installed plugins 380 381 // internal providers were already filtered out, since we don't need to get them. 382 chosen := choosePlugins(available, nil, requirements) 383 384 digests := map[string][]byte{} 385 for name, meta := range chosen { 386 digest, err := meta.SHA256() 387 if err != nil { 388 c.Ui.Error(fmt.Sprintf("failed to read provider plugin %s: %s", meta.Path, err)) 389 return err 390 } 391 digests[name] = digest 392 if c.ignorePluginChecksum { 393 digests[name] = nil 394 } 395 } 396 err := c.providerPluginsLock().Write(digests) 397 if err != nil { 398 c.Ui.Error(fmt.Sprintf("failed to save provider manifest: %s", err)) 399 return err 400 } 401 402 { 403 // Purge any auto-installed plugins that aren't being used. 404 purged, err := c.providerInstaller.PurgeUnused(chosen) 405 if err != nil { 406 // Failure to purge old plugins is not a fatal error 407 c.Ui.Warn(fmt.Sprintf("failed to purge unused plugins: %s", err)) 408 } 409 if purged != nil { 410 for meta := range purged { 411 log.Printf("[DEBUG] Purged unused %s plugin %s", meta.Name, meta.Path) 412 } 413 } 414 } 415 416 // If any providers have "floating" versions (completely unconstrained) 417 // we'll suggest the user constrain with a pessimistic constraint to 418 // avoid implicitly adopting a later major release. 419 constraintSuggestions := make(map[string]discovery.ConstraintStr) 420 for name, meta := range chosen { 421 req := requirements[name] 422 if req == nil { 423 // should never happen, but we don't want to crash here, so we'll 424 // be cautious. 425 continue 426 } 427 428 if req.Versions.Unconstrained() && meta.Version != discovery.VersionZero { 429 // meta.Version.MustParse is safe here because our "chosen" metas 430 // were already filtered for validity of versions. 431 constraintSuggestions[name] = meta.Version.MustParse().MinorUpgradeConstraintStr() 432 } 433 } 434 if len(constraintSuggestions) != 0 { 435 names := make([]string, 0, len(constraintSuggestions)) 436 for name := range constraintSuggestions { 437 names = append(names, name) 438 } 439 sort.Strings(names) 440 441 c.Ui.Output(outputInitProvidersUnconstrained) 442 for _, name := range names { 443 c.Ui.Output(fmt.Sprintf("* provider.%s: version = %q", name, constraintSuggestions[name])) 444 } 445 } 446 447 return nil 448 } 449 450 func (c *InitCommand) AutocompleteArgs() complete.Predictor { 451 return complete.PredictDirs("") 452 } 453 454 func (c *InitCommand) AutocompleteFlags() complete.Flags { 455 return complete.Flags{ 456 "-backend": completePredictBoolean, 457 "-backend-config": complete.PredictFiles("*.tfvars"), // can also be key=value, but we can't "predict" that 458 "-force-copy": complete.PredictNothing, 459 "-from-module": completePredictModuleSource, 460 "-get": completePredictBoolean, 461 "-get-plugins": completePredictBoolean, 462 "-input": completePredictBoolean, 463 "-lock": completePredictBoolean, 464 "-lock-timeout": complete.PredictAnything, 465 "-no-color": complete.PredictNothing, 466 "-plugin-dir": complete.PredictDirs(""), 467 "-reconfigure": complete.PredictNothing, 468 "-upgrade": completePredictBoolean, 469 "-verify-plugins": completePredictBoolean, 470 } 471 } 472 473 func (c *InitCommand) Help() string { 474 helpText := ` 475 Usage: terraform init [options] [DIR] 476 477 Initialize a new or existing Terraform working directory by creating 478 initial files, loading any remote state, downloading modules, etc. 479 480 This is the first command that should be run for any new or existing 481 Terraform configuration per machine. This sets up all the local data 482 necessary to run Terraform that is typically not committed to version 483 control. 484 485 This command is always safe to run multiple times. Though subsequent runs 486 may give errors, this command will never delete your configuration or 487 state. Even so, if you have important information, please back it up prior 488 to running this command, just in case. 489 490 If no arguments are given, the configuration in this working directory 491 is initialized. 492 493 Options: 494 495 -backend=true Configure the backend for this configuration. 496 497 -backend-config=path This can be either a path to an HCL file with key/value 498 assignments (same format as terraform.tfvars) or a 499 'key=value' format. This is merged with what is in the 500 configuration file. This can be specified multiple 501 times. The backend type must be in the configuration 502 itself. 503 504 -force-copy Suppress prompts about copying state data. This is 505 equivalent to providing a "yes" to all confirmation 506 prompts. 507 508 -from-module=SOURCE Copy the contents of the given module into the target 509 directory before initialization. 510 511 -get=true Download any modules for this configuration. 512 513 -get-plugins=true Download any missing plugins for this configuration. 514 515 -input=true Ask for input if necessary. If false, will error if 516 input was required. 517 518 -lock=true Lock the state file when locking is supported. 519 520 -lock-timeout=0s Duration to retry a state lock. 521 522 -no-color If specified, output won't contain any color. 523 524 -plugin-dir Directory containing plugin binaries. This overrides all 525 default search paths for plugins, and prevents the 526 automatic installation of plugins. This flag can be used 527 multiple times. 528 529 -reconfigure Reconfigure the backend, ignoring any saved 530 configuration. 531 532 -upgrade=false If installing modules (-get) or plugins (-get-plugins), 533 ignore previously-downloaded objects and install the 534 latest version allowed within configured constraints. 535 536 -verify-plugins=true Verify the authenticity and integrity of automatically 537 downloaded plugins. 538 ` 539 return strings.TrimSpace(helpText) 540 } 541 542 func (c *InitCommand) Synopsis() string { 543 return "Initialize a Terraform working directory" 544 } 545 546 const errInitConfigError = ` 547 There are some problems with the configuration, described below. 548 549 The Terraform configuration must be valid before initialization so that 550 Terraform can determine which modules and providers need to be installed. 551 ` 552 553 const errInitCopyNotEmpty = ` 554 The working directory already contains files. The -from-module option requires 555 an empty directory into which a copy of the referenced module will be placed. 556 557 To initialize the configuration already in this working directory, omit the 558 -from-module option. 559 ` 560 561 const outputInitEmpty = ` 562 [reset][bold]Terraform initialized in an empty directory![reset] 563 564 The directory has no Terraform configuration files. You may begin working 565 with Terraform immediately by creating Terraform configuration files. 566 ` 567 568 const outputInitSuccess = ` 569 [reset][bold][green]Terraform has been successfully initialized![reset][green] 570 ` 571 572 const outputInitSuccessCLI = `[reset][green] 573 You may now begin working with Terraform. Try running "terraform plan" to see 574 any changes that are required for your infrastructure. All Terraform commands 575 should now work. 576 577 If you ever set or change modules or backend configuration for Terraform, 578 rerun this command to reinitialize your working directory. If you forget, other 579 commands will detect it and remind you to do so if necessary. 580 ` 581 582 const outputInitProvidersUnconstrained = ` 583 The following providers do not have any version constraints in configuration, 584 so the latest version was installed. 585 586 To prevent automatic upgrades to new major versions that may contain breaking 587 changes, it is recommended to add version = "..." constraints to the 588 corresponding provider blocks in configuration, with the constraint strings 589 suggested below. 590 ` 591 592 const errProviderNotFound = ` 593 [reset][bold][red]Provider %[1]q not available for installation.[reset][red] 594 595 A provider named %[1]q could not be found in the official repository. 596 597 This may result from mistyping the provider name, or the given provider may 598 be a third-party provider that cannot be installed automatically. 599 600 In the latter case, the plugin must be installed manually by locating and 601 downloading a suitable distribution package and placing the plugin's executable 602 file in the following directory: 603 %[2]s 604 605 Terraform detects necessary plugins by inspecting the configuration and state. 606 To view the provider versions requested by each module, run 607 "terraform providers". 608 ` 609 610 const errProviderVersionsUnsuitable = ` 611 [reset][bold][red]No provider %[1]q plugins meet the constraint %[2]q.[reset][red] 612 613 The version constraint is derived from the "version" argument within the 614 provider %[1]q block in configuration. Child modules may also apply 615 provider version constraints. To view the provider versions requested by each 616 module in the current configuration, run "terraform providers". 617 618 To proceed, the version constraints for this provider must be relaxed by 619 either adjusting or removing the "version" argument in the provider blocks 620 throughout the configuration. 621 ` 622 623 const errProviderIncompatible = ` 624 [reset][bold][red]No available provider %[1]q plugins are compatible with this Terraform version.[reset][red] 625 626 From time to time, new Terraform major releases can change the requirements for 627 plugins such that older plugins become incompatible. 628 629 Terraform checked all of the plugin versions matching the given constraint: 630 %[2]s 631 632 Unfortunately, none of the suitable versions are compatible with this version 633 of Terraform. If you have recently upgraded Terraform, it may be necessary to 634 move to a newer major release of this provider. Alternatively, if you are 635 attempting to upgrade the provider to a new major version you may need to 636 also upgrade Terraform to support the new version. 637 638 Consult the documentation for this provider for more information on 639 compatibility between provider versions and Terraform versions. 640 ` 641 642 const errProviderInstallError = ` 643 [reset][bold][red]Error installing provider %[1]q: %[2]s.[reset][red] 644 645 Terraform analyses the configuration and state and automatically downloads 646 plugins for the providers used. However, when attempting to download this 647 plugin an unexpected error occured. 648 649 This may be caused if for some reason Terraform is unable to reach the 650 plugin repository. The repository may be unreachable if access is blocked 651 by a firewall. 652 653 If automatic installation is not possible or desirable in your environment, 654 you may alternatively manually install plugins by downloading a suitable 655 distribution package and placing the plugin's executable file in the 656 following directory: 657 %[3]s 658 ` 659 660 const errMissingProvidersNoInstall = ` 661 [reset][bold][red]Missing required providers.[reset][red] 662 663 The following provider constraints are not met by the currently-installed 664 provider plugins: 665 666 %[1]s 667 Terraform can automatically download and install plugins to meet the given 668 constraints, but this step was skipped due to the use of -get-plugins=false 669 and/or -plugin-dir on the command line. 670 671 If automatic installation is not possible or desirable in your environment, 672 you may manually install plugins by downloading a suitable distribution package 673 and placing the plugin's executable file in one of the directories given in 674 by -plugin-dir on the command line, or in the following directory if custom 675 plugin directories are not set: 676 %[2]s 677 `