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