github.com/hooklift/terraform@v0.11.0-beta1.0.20171117000744-6786c1361ffe/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 186 // If we're requesting backend configuration or looking for required 187 // plugins, load the backend 188 if flagBackend { 189 header = true 190 191 // Only output that we're initializing a backend if we have 192 // something in the config. We can be UNSETTING a backend as well 193 // in which case we choose not to show this. 194 if conf.Terraform != nil && conf.Terraform.Backend != nil { 195 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 196 "\n[reset][bold]Initializing the backend..."))) 197 } 198 199 opts := &BackendOpts{ 200 Config: conf, 201 ConfigExtra: flagConfigExtra, 202 Init: true, 203 } 204 if back, err = c.Backend(opts); err != nil { 205 c.Ui.Error(err.Error()) 206 return 1 207 } 208 } 209 } 210 211 if back == nil { 212 // If we didn't initialize a backend then we'll try to at least 213 // instantiate one. This might fail if it wasn't already initalized 214 // by a previous run, so we must still expect that "back" may be nil 215 // in code that follows. 216 back, err = c.Backend(nil) 217 if err != nil { 218 // This is fine. We'll proceed with no backend, then. 219 back = nil 220 } 221 } 222 223 var state *terraform.State 224 225 // If we have a functional backend (either just initialized or initialized 226 // on a previous run) we'll use the current state as a potential source 227 // of provider dependencies. 228 if back != nil { 229 sMgr, err := back.State(c.Workspace()) 230 if err != nil { 231 c.Ui.Error(fmt.Sprintf( 232 "Error loading state: %s", err)) 233 return 1 234 } 235 236 if err := sMgr.RefreshState(); err != nil { 237 c.Ui.Error(fmt.Sprintf( 238 "Error refreshing state: %s", err)) 239 return 1 240 } 241 242 state = sMgr.State() 243 } 244 245 if v := os.Getenv(ProviderSkipVerifyEnvVar); v != "" { 246 c.ignorePluginChecksum = true 247 } 248 249 // Now that we have loaded all modules, check the module tree for missing providers. 250 err = c.getProviders(path, state, flagUpgrade) 251 if err != nil { 252 // this function provides its own output 253 log.Printf("[ERROR] %s", err) 254 return 1 255 } 256 257 // If we outputted information, then we need to output a newline 258 // so that our success message is nicely spaced out from prior text. 259 if header { 260 c.Ui.Output("") 261 } 262 263 c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitSuccess))) 264 if !c.RunningInAutomation { 265 // If we're not running in an automation wrapper, give the user 266 // some more detailed next steps that are appropriate for interactive 267 // shell usage. 268 c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitSuccessCLI))) 269 } 270 271 return 0 272 } 273 274 // Load the complete module tree, and fetch any missing providers. 275 // This method outputs its own Ui. 276 func (c *InitCommand) getProviders(path string, state *terraform.State, upgrade bool) error { 277 mod, err := c.Module(path) 278 if err != nil { 279 c.Ui.Error(fmt.Sprintf("Error getting plugins: %s", err)) 280 return err 281 } 282 283 if err := mod.Validate(); err != nil { 284 c.Ui.Error(fmt.Sprintf("Error getting plugins: %s", err)) 285 return err 286 } 287 288 if err := terraform.CheckRequiredVersion(mod); err != nil { 289 c.Ui.Error(err.Error()) 290 return err 291 } 292 293 var available discovery.PluginMetaSet 294 if upgrade { 295 // If we're in upgrade mode, we ignore any auto-installed plugins 296 // in "available", causing us to reinstall and possibly upgrade them. 297 available = c.providerPluginManuallyInstalledSet() 298 } else { 299 available = c.providerPluginSet() 300 } 301 302 requirements := terraform.ModuleTreeDependencies(mod, state).AllPluginRequirements() 303 if len(requirements) == 0 { 304 // nothing to initialize 305 return nil 306 } 307 308 c.Ui.Output(c.Colorize().Color( 309 "\n[reset][bold]Initializing provider plugins...", 310 )) 311 312 missing := c.missingPlugins(available, requirements) 313 internal := c.internalProviders() 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 if _, isInternal := internal[provider]; isInternal { 324 // Ignore internal providers; they are not eligible for 325 // installation. 326 continue 327 } 328 329 _, err := c.providerInstaller.Get(provider, reqd.Versions) 330 331 if err != nil { 332 switch err { 333 case discovery.ErrorNoSuchProvider: 334 c.Ui.Error(fmt.Sprintf(errProviderNotFound, provider, DefaultPluginVendorDir)) 335 case discovery.ErrorNoSuitableVersion: 336 if reqd.Versions.Unconstrained() { 337 // This should never happen, but might crop up if we catch 338 // the releases server in a weird state where the provider's 339 // directory is present but does not yet contain any 340 // versions. We'll treat it like ErrorNoSuchProvider, then. 341 c.Ui.Error(fmt.Sprintf(errProviderNotFound, provider, DefaultPluginVendorDir)) 342 } else { 343 c.Ui.Error(fmt.Sprintf(errProviderVersionsUnsuitable, provider, reqd.Versions)) 344 } 345 case discovery.ErrorNoVersionCompatible: 346 // FIXME: This error message is sub-awesome because we don't 347 // have enough information here to tell the user which versions 348 // we considered and which versions might be compatible. 349 constraint := reqd.Versions.String() 350 if constraint == "" { 351 constraint = "(any version)" 352 } 353 c.Ui.Error(fmt.Sprintf(errProviderIncompatible, provider, constraint)) 354 default: 355 c.Ui.Error(fmt.Sprintf(errProviderInstallError, provider, err.Error(), DefaultPluginVendorDir)) 356 } 357 358 errs = multierror.Append(errs, err) 359 } 360 } 361 362 if errs != nil { 363 return errs 364 } 365 } else if len(missing) > 0 { 366 // we have missing providers, but aren't going to try and download them 367 var lines []string 368 for provider, reqd := range missing { 369 if reqd.Versions.Unconstrained() { 370 lines = append(lines, fmt.Sprintf("* %s (any version)\n", provider)) 371 } else { 372 lines = append(lines, fmt.Sprintf("* %s (%s)\n", provider, reqd.Versions)) 373 } 374 errs = multierror.Append(errs, fmt.Errorf("missing provider %q", provider)) 375 } 376 sort.Strings(lines) 377 c.Ui.Error(fmt.Sprintf(errMissingProvidersNoInstall, strings.Join(lines, ""), DefaultPluginVendorDir)) 378 return errs 379 } 380 381 // With all the providers downloaded, we'll generate our lock file 382 // that ensures the provider binaries remain unchanged until we init 383 // again. If anything changes, other commands that use providers will 384 // fail with an error instructing the user to re-run this command. 385 available = c.providerPluginSet() // re-discover to see newly-installed plugins 386 chosen := choosePlugins(available, internal, requirements) 387 digests := map[string][]byte{} 388 for name, meta := range chosen { 389 digest, err := meta.SHA256() 390 if err != nil { 391 c.Ui.Error(fmt.Sprintf("failed to read provider plugin %s: %s", meta.Path, err)) 392 return err 393 } 394 digests[name] = digest 395 if c.ignorePluginChecksum { 396 digests[name] = nil 397 } 398 } 399 err = c.providerPluginsLock().Write(digests) 400 if err != nil { 401 c.Ui.Error(fmt.Sprintf("failed to save provider manifest: %s", err)) 402 return err 403 } 404 405 { 406 // Purge any auto-installed plugins that aren't being used. 407 purged, err := c.providerInstaller.PurgeUnused(chosen) 408 if err != nil { 409 // Failure to purge old plugins is not a fatal error 410 c.Ui.Warn(fmt.Sprintf("failed to purge unused plugins: %s", err)) 411 } 412 if purged != nil { 413 for meta := range purged { 414 log.Printf("[DEBUG] Purged unused %s plugin %s", meta.Name, meta.Path) 415 } 416 } 417 } 418 419 // If any providers have "floating" versions (completely unconstrained) 420 // we'll suggest the user constrain with a pessimistic constraint to 421 // avoid implicitly adopting a later major release. 422 constraintSuggestions := make(map[string]discovery.ConstraintStr) 423 for name, meta := range chosen { 424 req := requirements[name] 425 if req == nil { 426 // should never happen, but we don't want to crash here, so we'll 427 // be cautious. 428 continue 429 } 430 431 if req.Versions.Unconstrained() && meta.Version != discovery.VersionZero { 432 // meta.Version.MustParse is safe here because our "chosen" metas 433 // were already filtered for validity of versions. 434 constraintSuggestions[name] = meta.Version.MustParse().MinorUpgradeConstraintStr() 435 } 436 } 437 if len(constraintSuggestions) != 0 { 438 names := make([]string, 0, len(constraintSuggestions)) 439 for name := range constraintSuggestions { 440 names = append(names, name) 441 } 442 sort.Strings(names) 443 444 c.Ui.Output(outputInitProvidersUnconstrained) 445 for _, name := range names { 446 c.Ui.Output(fmt.Sprintf("* provider.%s: version = %q", name, constraintSuggestions[name])) 447 } 448 } 449 450 return nil 451 } 452 453 func (c *InitCommand) AutocompleteArgs() complete.Predictor { 454 return complete.PredictDirs("") 455 } 456 457 func (c *InitCommand) AutocompleteFlags() complete.Flags { 458 return complete.Flags{ 459 "-backend": completePredictBoolean, 460 "-backend-config": complete.PredictFiles("*.tfvars"), // can also be key=value, but we can't "predict" that 461 "-force-copy": complete.PredictNothing, 462 "-from-module": completePredictModuleSource, 463 "-get": completePredictBoolean, 464 "-get-plugins": completePredictBoolean, 465 "-input": completePredictBoolean, 466 "-lock": completePredictBoolean, 467 "-lock-timeout": complete.PredictAnything, 468 "-no-color": complete.PredictNothing, 469 "-plugin-dir": complete.PredictDirs(""), 470 "-reconfigure": complete.PredictNothing, 471 "-upgrade": completePredictBoolean, 472 "-verify-plugins": completePredictBoolean, 473 } 474 } 475 476 func (c *InitCommand) Help() string { 477 helpText := ` 478 Usage: terraform init [options] [DIR] 479 480 Initialize a new or existing Terraform working directory by creating 481 initial files, loading any remote state, downloading modules, etc. 482 483 This is the first command that should be run for any new or existing 484 Terraform configuration per machine. This sets up all the local data 485 necessary to run Terraform that is typically not committed to version 486 control. 487 488 This command is always safe to run multiple times. Though subsequent runs 489 may give errors, this command will never delete your configuration or 490 state. Even so, if you have important information, please back it up prior 491 to running this command, just in case. 492 493 If no arguments are given, the configuration in this working directory 494 is initialized. 495 496 Options: 497 498 -backend=true Configure the backend for this configuration. 499 500 -backend-config=path This can be either a path to an HCL file with key/value 501 assignments (same format as terraform.tfvars) or a 502 'key=value' format. This is merged with what is in the 503 configuration file. This can be specified multiple 504 times. The backend type must be in the configuration 505 itself. 506 507 -force-copy Suppress prompts about copying state data. This is 508 equivalent to providing a "yes" to all confirmation 509 prompts. 510 511 -from-module=SOURCE Copy the contents of the given module into the target 512 directory before initialization. 513 514 -get=true Download any modules for this configuration. 515 516 -get-plugins=true Download any missing plugins for this configuration. 517 518 -input=true Ask for input if necessary. If false, will error if 519 input was required. 520 521 -lock=true Lock the state file when locking is supported. 522 523 -lock-timeout=0s Duration to retry a state lock. 524 525 -no-color If specified, output won't contain any color. 526 527 -plugin-dir Directory containing plugin binaries. This overrides all 528 default search paths for plugins, and prevents the 529 automatic installation of plugins. This flag can be used 530 multiple times. 531 532 -reconfigure Reconfigure the backend, ignoring any saved 533 configuration. 534 535 -upgrade=false If installing modules (-get) or plugins (-get-plugins), 536 ignore previously-downloaded objects and install the 537 latest version allowed within configured constraints. 538 539 -verify-plugins=true Verify the authenticity and integrity of automatically 540 downloaded plugins. 541 ` 542 return strings.TrimSpace(helpText) 543 } 544 545 func (c *InitCommand) Synopsis() string { 546 return "Initialize a Terraform working directory" 547 } 548 549 const errInitConfigError = ` 550 There are some problems with the configuration, described below. 551 552 The Terraform configuration must be valid before initialization so that 553 Terraform can determine which modules and providers need to be installed. 554 ` 555 556 const errInitCopyNotEmpty = ` 557 The working directory already contains files. The -from-module option requires 558 an empty directory into which a copy of the referenced module will be placed. 559 560 To initialize the configuration already in this working directory, omit the 561 -from-module option. 562 ` 563 564 const outputInitEmpty = ` 565 [reset][bold]Terraform initialized in an empty directory![reset] 566 567 The directory has no Terraform configuration files. You may begin working 568 with Terraform immediately by creating Terraform configuration files. 569 ` 570 571 const outputInitSuccess = ` 572 [reset][bold][green]Terraform has been successfully initialized![reset][green] 573 ` 574 575 const outputInitSuccessCLI = `[reset][green] 576 You may now begin working with Terraform. Try running "terraform plan" to see 577 any changes that are required for your infrastructure. All Terraform commands 578 should now work. 579 580 If you ever set or change modules or backend configuration for Terraform, 581 rerun this command to reinitialize your working directory. If you forget, other 582 commands will detect it and remind you to do so if necessary. 583 ` 584 585 const outputInitProvidersUnconstrained = ` 586 The following providers do not have any version constraints in configuration, 587 so the latest version was installed. 588 589 To prevent automatic upgrades to new major versions that may contain breaking 590 changes, it is recommended to add version = "..." constraints to the 591 corresponding provider blocks in configuration, with the constraint strings 592 suggested below. 593 ` 594 595 const errProviderNotFound = ` 596 [reset][bold][red]Provider %[1]q not available for installation.[reset][red] 597 598 A provider named %[1]q could not be found in the official repository. 599 600 This may result from mistyping the provider name, or the given provider may 601 be a third-party provider that cannot be installed automatically. 602 603 In the latter case, the plugin must be installed manually by locating and 604 downloading a suitable distribution package and placing the plugin's executable 605 file in the following directory: 606 %[2]s 607 608 Terraform detects necessary plugins by inspecting the configuration and state. 609 To view the provider versions requested by each module, run 610 "terraform providers". 611 ` 612 613 const errProviderVersionsUnsuitable = ` 614 [reset][bold][red]No provider %[1]q plugins meet the constraint %[2]q.[reset][red] 615 616 The version constraint is derived from the "version" argument within the 617 provider %[1]q block in configuration. Child modules may also apply 618 provider version constraints. To view the provider versions requested by each 619 module in the current configuration, run "terraform providers". 620 621 To proceed, the version constraints for this provider must be relaxed by 622 either adjusting or removing the "version" argument in the provider blocks 623 throughout the configuration. 624 ` 625 626 const errProviderIncompatible = ` 627 [reset][bold][red]No available provider %[1]q plugins are compatible with this Terraform version.[reset][red] 628 629 From time to time, new Terraform major releases can change the requirements for 630 plugins such that older plugins become incompatible. 631 632 Terraform checked all of the plugin versions matching the given constraint: 633 %[2]s 634 635 Unfortunately, none of the suitable versions are compatible with this version 636 of Terraform. If you have recently upgraded Terraform, it may be necessary to 637 move to a newer major release of this provider. Alternatively, if you are 638 attempting to upgrade the provider to a new major version you may need to 639 also upgrade Terraform to support the new version. 640 641 Consult the documentation for this provider for more information on 642 compatibility between provider versions and Terraform versions. 643 ` 644 645 const errProviderInstallError = ` 646 [reset][bold][red]Error installing provider %[1]q: %[2]s.[reset][red] 647 648 Terraform analyses the configuration and state and automatically downloads 649 plugins for the providers used. However, when attempting to download this 650 plugin an unexpected error occured. 651 652 This may be caused if for some reason Terraform is unable to reach the 653 plugin repository. The repository may be unreachable if access is blocked 654 by a firewall. 655 656 If automatic installation is not possible or desirable in your environment, 657 you may alternatively manually install plugins by downloading a suitable 658 distribution package and placing the plugin's executable file in the 659 following directory: 660 %[3]s 661 ` 662 663 const errMissingProvidersNoInstall = ` 664 [reset][bold][red]Missing required providers.[reset][red] 665 666 The following provider constraints are not met by the currently-installed 667 provider plugins: 668 669 %[1]s 670 Terraform can automatically download and install plugins to meet the given 671 constraints, but this step was skipped due to the use of -get-plugins=false 672 and/or -plugin-dir on the command line. 673 674 If automatic installation is not possible or desirable in your environment, 675 you may manually install plugins by downloading a suitable distribution package 676 and placing the plugin's executable file in one of the directories given in 677 by -plugin-dir on the command line, or in the following directory if custom 678 plugin directories are not set: 679 %[2]s 680 `