github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/init.go (about) 1 package command 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "reflect" 8 "sort" 9 "strings" 10 11 "github.com/hashicorp/hcl/v2" 12 svchost "github.com/hashicorp/terraform-svchost" 13 "github.com/posener/complete" 14 "github.com/zclconf/go-cty/cty" 15 16 "github.com/hashicorp/terraform/internal/addrs" 17 "github.com/hashicorp/terraform/internal/backend" 18 backendInit "github.com/hashicorp/terraform/internal/backend/init" 19 "github.com/hashicorp/terraform/internal/cloud" 20 "github.com/hashicorp/terraform/internal/command/arguments" 21 "github.com/hashicorp/terraform/internal/configs" 22 "github.com/hashicorp/terraform/internal/configs/configschema" 23 "github.com/hashicorp/terraform/internal/getproviders" 24 "github.com/hashicorp/terraform/internal/providercache" 25 "github.com/hashicorp/terraform/internal/states" 26 "github.com/hashicorp/terraform/internal/terraform" 27 "github.com/hashicorp/terraform/internal/tfdiags" 28 tfversion "github.com/hashicorp/terraform/version" 29 ) 30 31 // InitCommand is a Command implementation that takes a Terraform 32 // module and clones it to the working directory. 33 type InitCommand struct { 34 Meta 35 } 36 37 func (c *InitCommand) Run(args []string) int { 38 var flagFromModule, flagLockfile string 39 var flagBackend, flagCloud, flagGet, flagUpgrade bool 40 var flagPluginPath FlagStringSlice 41 flagConfigExtra := newRawFlags("-backend-config") 42 43 args = c.Meta.process(args) 44 cmdFlags := c.Meta.extendedFlagSet("init") 45 cmdFlags.BoolVar(&flagBackend, "backend", true, "") 46 cmdFlags.BoolVar(&flagCloud, "cloud", true, "") 47 cmdFlags.Var(flagConfigExtra, "backend-config", "") 48 cmdFlags.StringVar(&flagFromModule, "from-module", "", "copy the source of the given module into the directory before init") 49 cmdFlags.BoolVar(&flagGet, "get", true, "") 50 cmdFlags.BoolVar(&c.forceInitCopy, "force-copy", false, "suppress prompts about copying state data") 51 cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") 52 cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") 53 cmdFlags.BoolVar(&c.reconfigure, "reconfigure", false, "reconfigure") 54 cmdFlags.BoolVar(&c.migrateState, "migrate-state", false, "migrate state") 55 cmdFlags.BoolVar(&flagUpgrade, "upgrade", false, "") 56 cmdFlags.Var(&flagPluginPath, "plugin-dir", "plugin directory") 57 cmdFlags.StringVar(&flagLockfile, "lockfile", "", "Set a dependency lockfile mode") 58 cmdFlags.BoolVar(&c.Meta.ignoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible") 59 cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } 60 if err := cmdFlags.Parse(args); err != nil { 61 return 1 62 } 63 64 backendFlagSet := arguments.FlagIsSet(cmdFlags, "backend") 65 cloudFlagSet := arguments.FlagIsSet(cmdFlags, "cloud") 66 67 switch { 68 case backendFlagSet && cloudFlagSet: 69 c.Ui.Error("The -backend and -cloud options are aliases of one another and mutually-exclusive in their use") 70 return 1 71 case backendFlagSet: 72 flagCloud = flagBackend 73 case cloudFlagSet: 74 flagBackend = flagCloud 75 } 76 77 if c.migrateState && c.reconfigure { 78 c.Ui.Error("The -migrate-state and -reconfigure options are mutually-exclusive") 79 return 1 80 } 81 82 // Copying the state only happens during backend migration, so setting 83 // -force-copy implies -migrate-state 84 if c.forceInitCopy { 85 c.migrateState = true 86 } 87 88 var diags tfdiags.Diagnostics 89 90 if len(flagPluginPath) > 0 { 91 c.pluginPath = flagPluginPath 92 } 93 94 // Validate the arg count and get the working directory 95 args = cmdFlags.Args() 96 path, err := ModulePath(args) 97 if err != nil { 98 c.Ui.Error(err.Error()) 99 return 1 100 } 101 102 if err := c.storePluginPath(c.pluginPath); err != nil { 103 c.Ui.Error(fmt.Sprintf("Error saving -plugin-path values: %s", err)) 104 return 1 105 } 106 107 // This will track whether we outputted anything so that we know whether 108 // to output a newline before the success message 109 var header bool 110 111 if flagFromModule != "" { 112 src := flagFromModule 113 114 empty, err := configs.IsEmptyDir(path) 115 if err != nil { 116 c.Ui.Error(fmt.Sprintf("Error validating destination directory: %s", err)) 117 return 1 118 } 119 if !empty { 120 c.Ui.Error(strings.TrimSpace(errInitCopyNotEmpty)) 121 return 1 122 } 123 124 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 125 "[reset][bold]Copying configuration[reset] from %q...", src, 126 ))) 127 header = true 128 129 hooks := uiModuleInstallHooks{ 130 Ui: c.Ui, 131 ShowLocalPaths: false, // since they are in a weird location for init 132 } 133 134 initDirFromModuleAbort, initDirFromModuleDiags := c.initDirFromModule(path, src, hooks) 135 diags = diags.Append(initDirFromModuleDiags) 136 if initDirFromModuleAbort || initDirFromModuleDiags.HasErrors() { 137 c.showDiagnostics(diags) 138 return 1 139 } 140 141 c.Ui.Output("") 142 } 143 144 // If our directory is empty, then we're done. We can't get or set up 145 // the backend with an empty directory. 146 empty, err := configs.IsEmptyDir(path) 147 if err != nil { 148 diags = diags.Append(fmt.Errorf("Error checking configuration: %s", err)) 149 c.showDiagnostics(diags) 150 return 1 151 } 152 if empty { 153 c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitEmpty))) 154 return 0 155 } 156 157 // Load just the root module to begin backend and module initialization 158 rootModEarly, earlyConfDiags := c.loadSingleModule(path) 159 160 // There may be parsing errors in config loading but these will be shown later _after_ 161 // checking for core version requirement errors. Not meeting the version requirement should 162 // be the first error displayed if that is an issue, but other operations are required 163 // before being able to check core version requirements. 164 if rootModEarly == nil { 165 c.Ui.Error(c.Colorize().Color(strings.TrimSpace(errInitConfigError))) 166 diags = diags.Append(earlyConfDiags) 167 c.showDiagnostics(diags) 168 169 return 1 170 } 171 172 var back backend.Backend 173 174 // There may be config errors or backend init errors but these will be shown later _after_ 175 // checking for core version requirement errors. 176 var backDiags tfdiags.Diagnostics 177 var backendOutput bool 178 179 switch { 180 case flagCloud && rootModEarly.CloudConfig != nil: 181 back, backendOutput, backDiags = c.initCloud(rootModEarly, flagConfigExtra) 182 case flagBackend: 183 back, backendOutput, backDiags = c.initBackend(rootModEarly, flagConfigExtra) 184 default: 185 // load the previously-stored backend config 186 back, backDiags = c.Meta.backendFromState() 187 } 188 if backendOutput { 189 header = true 190 } 191 192 var state *states.State 193 194 // If we have a functional backend (either just initialized or initialized 195 // on a previous run) we'll use the current state as a potential source 196 // of provider dependencies. 197 if back != nil { 198 c.ignoreRemoteVersionConflict(back) 199 workspace, err := c.Workspace() 200 if err != nil { 201 c.Ui.Error(fmt.Sprintf("Error selecting workspace: %s", err)) 202 return 1 203 } 204 sMgr, err := back.StateMgr(workspace) 205 if err != nil { 206 c.Ui.Error(fmt.Sprintf("Error loading state: %s", err)) 207 return 1 208 } 209 210 if err := sMgr.RefreshState(); err != nil { 211 c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err)) 212 return 1 213 } 214 215 state = sMgr.State() 216 } 217 218 if flagGet { 219 modsOutput, modsAbort, modsDiags := c.getModules(path, rootModEarly, flagUpgrade) 220 diags = diags.Append(modsDiags) 221 if modsAbort || modsDiags.HasErrors() { 222 c.showDiagnostics(diags) 223 return 1 224 } 225 if modsOutput { 226 header = true 227 } 228 } 229 230 // With all of the modules (hopefully) installed, we can now try to load the 231 // whole configuration tree. 232 config, confDiags := c.loadConfig(path) 233 // configDiags will be handled after the version constraint check, since an 234 // incorrect version of terraform may be producing errors for configuration 235 // constructs added in later versions. 236 237 // Before we go further, we'll check to make sure none of the modules in 238 // the configuration declare that they don't support this Terraform 239 // version, so we can produce a version-related error message rather than 240 // potentially-confusing downstream errors. 241 versionDiags := terraform.CheckCoreVersionRequirements(config) 242 if versionDiags.HasErrors() { 243 c.showDiagnostics(versionDiags) 244 return 1 245 } 246 247 // If we pass the core version check, we want to show any errors from initializing the backend next, 248 // which will include syntax errors from loading the configuration. However, there's a special case 249 // where we are unable to load the backend from configuration or state _and_ the configuration has 250 // errors. In that case, we want to show a slightly friendlier error message for newcomers. 251 showBackendDiags := back != nil || rootModEarly.Backend != nil || rootModEarly.CloudConfig != nil 252 if showBackendDiags { 253 diags = diags.Append(backDiags) 254 if backDiags.HasErrors() { 255 c.showDiagnostics(diags) 256 return 1 257 } 258 } else { 259 diags = diags.Append(earlyConfDiags) 260 if earlyConfDiags.HasErrors() { 261 c.Ui.Error(strings.TrimSpace(errInitConfigError)) 262 c.showDiagnostics(diags) 263 return 1 264 } 265 } 266 267 // If everything is ok with the core version check and backend initialization, 268 // show other errors from loading the full configuration tree. 269 diags = diags.Append(confDiags) 270 if confDiags.HasErrors() { 271 c.Ui.Error(strings.TrimSpace(errInitConfigError)) 272 c.showDiagnostics(diags) 273 return 1 274 } 275 276 // Now that we have loaded all modules, check the module tree for missing providers. 277 providersOutput, providersAbort, providerDiags := c.getProviders(config, state, flagUpgrade, flagPluginPath, flagLockfile) 278 diags = diags.Append(providerDiags) 279 if providersAbort || providerDiags.HasErrors() { 280 c.showDiagnostics(diags) 281 return 1 282 } 283 if providersOutput { 284 header = true 285 } 286 287 // If we outputted information, then we need to output a newline 288 // so that our success message is nicely spaced out from prior text. 289 if header { 290 c.Ui.Output("") 291 } 292 293 // If we accumulated any warnings along the way that weren't accompanied 294 // by errors then we'll output them here so that the success message is 295 // still the final thing shown. 296 c.showDiagnostics(diags) 297 _, cloud := back.(*cloud.Cloud) 298 output := outputInitSuccess 299 if cloud { 300 output = outputInitSuccessCloud 301 } 302 303 c.Ui.Output(c.Colorize().Color(strings.TrimSpace(output))) 304 305 if !c.RunningInAutomation { 306 // If we're not running in an automation wrapper, give the user 307 // some more detailed next steps that are appropriate for interactive 308 // shell usage. 309 output = outputInitSuccessCLI 310 if cloud { 311 output = outputInitSuccessCLICloud 312 } 313 c.Ui.Output(c.Colorize().Color(strings.TrimSpace(output))) 314 } 315 return 0 316 } 317 318 func (c *InitCommand) getModules(path string, earlyRoot *configs.Module, upgrade bool) (output bool, abort bool, diags tfdiags.Diagnostics) { 319 if len(earlyRoot.ModuleCalls) == 0 { 320 // Nothing to do 321 return false, false, nil 322 } 323 324 if upgrade { 325 c.Ui.Output(c.Colorize().Color("[reset][bold]Upgrading modules...")) 326 } else { 327 c.Ui.Output(c.Colorize().Color("[reset][bold]Initializing modules...")) 328 } 329 330 hooks := uiModuleInstallHooks{ 331 Ui: c.Ui, 332 ShowLocalPaths: true, 333 } 334 335 installAbort, installDiags := c.installModules(path, upgrade, hooks) 336 diags = diags.Append(installDiags) 337 338 // At this point, installModules may have generated error diags or been 339 // aborted by SIGINT. In any case we continue and the manifest as best 340 // we can. 341 342 // Since module installer has modified the module manifest on disk, we need 343 // to refresh the cache of it in the loader. 344 if c.configLoader != nil { 345 if err := c.configLoader.RefreshModules(); err != nil { 346 // Should never happen 347 diags = diags.Append(tfdiags.Sourceless( 348 tfdiags.Error, 349 "Failed to read module manifest", 350 fmt.Sprintf("After installing modules, Terraform could not re-read the manifest of installed modules. This is a bug in Terraform. %s.", err), 351 )) 352 } 353 } 354 355 return true, installAbort, diags 356 } 357 358 func (c *InitCommand) initCloud(root *configs.Module, extraConfig rawFlags) (be backend.Backend, output bool, diags tfdiags.Diagnostics) { 359 c.Ui.Output(c.Colorize().Color("\n[reset][bold]Initializing Terraform Cloud...")) 360 361 if len(extraConfig.AllItems()) != 0 { 362 diags = diags.Append(tfdiags.Sourceless( 363 tfdiags.Error, 364 "Invalid command-line option", 365 "The -backend-config=... command line option is only for state backends, and is not applicable to Terraform Cloud-based configurations.\n\nTo change the set of workspaces associated with this configuration, edit the Cloud configuration block in the root module.", 366 )) 367 return nil, true, diags 368 } 369 370 backendConfig := root.CloudConfig.ToBackendConfig() 371 372 opts := &BackendOpts{ 373 Config: &backendConfig, 374 Init: true, 375 } 376 377 back, backDiags := c.Backend(opts) 378 diags = diags.Append(backDiags) 379 return back, true, diags 380 } 381 382 func (c *InitCommand) initBackend(root *configs.Module, extraConfig rawFlags) (be backend.Backend, output bool, diags tfdiags.Diagnostics) { 383 c.Ui.Output(c.Colorize().Color("\n[reset][bold]Initializing the backend...")) 384 385 var backendConfig *configs.Backend 386 var backendConfigOverride hcl.Body 387 if root.Backend != nil { 388 backendType := root.Backend.Type 389 if backendType == "cloud" { 390 diags = diags.Append(&hcl.Diagnostic{ 391 Severity: hcl.DiagError, 392 Summary: "Unsupported backend type", 393 Detail: fmt.Sprintf("There is no explicit backend type named %q. To configure Terraform Cloud, declare a 'cloud' block instead.", backendType), 394 Subject: &root.Backend.TypeRange, 395 }) 396 return nil, true, diags 397 } 398 399 bf := backendInit.Backend(backendType) 400 if bf == nil { 401 detail := fmt.Sprintf("There is no backend type named %q.", backendType) 402 if msg, removed := backendInit.RemovedBackends[backendType]; removed { 403 detail = msg 404 } 405 406 diags = diags.Append(&hcl.Diagnostic{ 407 Severity: hcl.DiagError, 408 Summary: "Unsupported backend type", 409 Detail: detail, 410 Subject: &root.Backend.TypeRange, 411 }) 412 return nil, true, diags 413 } 414 415 b := bf() 416 backendSchema := b.ConfigSchema() 417 backendConfig = root.Backend 418 419 var overrideDiags tfdiags.Diagnostics 420 backendConfigOverride, overrideDiags = c.backendConfigOverrideBody(extraConfig, backendSchema) 421 diags = diags.Append(overrideDiags) 422 if overrideDiags.HasErrors() { 423 return nil, true, diags 424 } 425 } else { 426 // If the user supplied a -backend-config on the CLI but no backend 427 // block was found in the configuration, it's likely - but not 428 // necessarily - a mistake. Return a warning. 429 if !extraConfig.Empty() { 430 diags = diags.Append(tfdiags.Sourceless( 431 tfdiags.Warning, 432 "Missing backend configuration", 433 `-backend-config was used without a "backend" block in the configuration. 434 435 If you intended to override the default local backend configuration, 436 no action is required, but you may add an explicit backend block to your 437 configuration to clear this warning: 438 439 terraform { 440 backend "local" {} 441 } 442 443 However, if you intended to override a defined backend, please verify that 444 the backend configuration is present and valid. 445 `, 446 )) 447 } 448 } 449 450 opts := &BackendOpts{ 451 Config: backendConfig, 452 ConfigOverride: backendConfigOverride, 453 Init: true, 454 } 455 456 back, backDiags := c.Backend(opts) 457 diags = diags.Append(backDiags) 458 return back, true, diags 459 } 460 461 // Load the complete module tree, and fetch any missing providers. 462 // This method outputs its own Ui. 463 func (c *InitCommand) getProviders(config *configs.Config, state *states.State, upgrade bool, pluginDirs []string, flagLockfile string) (output, abort bool, diags tfdiags.Diagnostics) { 464 // Dev overrides cause the result of "terraform init" to be irrelevant for 465 // any overridden providers, so we'll warn about it to avoid later 466 // confusion when Terraform ends up using a different provider than the 467 // lock file called for. 468 diags = diags.Append(c.providerDevOverrideInitWarnings()) 469 470 // First we'll collect all the provider dependencies we can see in the 471 // configuration and the state. 472 reqs, hclDiags := config.ProviderRequirements() 473 diags = diags.Append(hclDiags) 474 if hclDiags.HasErrors() { 475 return false, true, diags 476 } 477 if state != nil { 478 stateReqs := state.ProviderRequirements() 479 reqs = reqs.Merge(stateReqs) 480 } 481 482 for providerAddr := range reqs { 483 if providerAddr.IsLegacy() { 484 diags = diags.Append(tfdiags.Sourceless( 485 tfdiags.Error, 486 "Invalid legacy provider address", 487 fmt.Sprintf( 488 "This configuration or its associated state refers to the unqualified provider %q.\n\nYou must complete the Terraform 0.13 upgrade process before upgrading to later versions.", 489 providerAddr.Type, 490 ), 491 )) 492 } 493 } 494 495 previousLocks, moreDiags := c.lockedDependencies() 496 diags = diags.Append(moreDiags) 497 498 if diags.HasErrors() { 499 return false, true, diags 500 } 501 502 var inst *providercache.Installer 503 if len(pluginDirs) == 0 { 504 // By default we use a source that looks for providers in all of the 505 // standard locations, possibly customized by the user in CLI config. 506 inst = c.providerInstaller() 507 } else { 508 // If the user passes at least one -plugin-dir then that circumvents 509 // the usual sources and forces Terraform to consult only the given 510 // directories. Anything not available in one of those directories 511 // is not available for installation. 512 source := c.providerCustomLocalDirectorySource(pluginDirs) 513 inst = c.providerInstallerCustomSource(source) 514 515 // The default (or configured) search paths are logged earlier, in provider_source.go 516 // Log that those are being overridden by the `-plugin-dir` command line options 517 log.Println("[DEBUG] init: overriding provider plugin search paths") 518 log.Printf("[DEBUG] will search for provider plugins in %s", pluginDirs) 519 } 520 521 // Installation can be aborted by interruption signals 522 ctx, done := c.InterruptibleContext() 523 defer done() 524 525 // We want to print out a nice warning if we don't manage to pull 526 // checksums for all our providers. This is tracked via callbacks 527 // and incomplete providers are stored here for later analysis. 528 var incompleteProviders []string 529 530 // Because we're currently just streaming a series of events sequentially 531 // into the terminal, we're showing only a subset of the events to keep 532 // things relatively concise. Later it'd be nice to have a progress UI 533 // where statuses update in-place, but we can't do that as long as we 534 // are shimming our vt100 output to the legacy console API on Windows. 535 evts := &providercache.InstallerEvents{ 536 PendingProviders: func(reqs map[addrs.Provider]getproviders.VersionConstraints) { 537 c.Ui.Output(c.Colorize().Color( 538 "\n[reset][bold]Initializing provider plugins...", 539 )) 540 }, 541 ProviderAlreadyInstalled: func(provider addrs.Provider, selectedVersion getproviders.Version) { 542 c.Ui.Info(fmt.Sprintf("- Using previously-installed %s v%s", provider.ForDisplay(), selectedVersion)) 543 }, 544 BuiltInProviderAvailable: func(provider addrs.Provider) { 545 c.Ui.Info(fmt.Sprintf("- %s is built in to Terraform", provider.ForDisplay())) 546 }, 547 BuiltInProviderFailure: func(provider addrs.Provider, err error) { 548 diags = diags.Append(tfdiags.Sourceless( 549 tfdiags.Error, 550 "Invalid dependency on built-in provider", 551 fmt.Sprintf("Cannot use %s: %s.", provider.ForDisplay(), err), 552 )) 553 }, 554 QueryPackagesBegin: func(provider addrs.Provider, versionConstraints getproviders.VersionConstraints, locked bool) { 555 if locked { 556 c.Ui.Info(fmt.Sprintf("- Reusing previous version of %s from the dependency lock file", provider.ForDisplay())) 557 } else { 558 if len(versionConstraints) > 0 { 559 c.Ui.Info(fmt.Sprintf("- Finding %s versions matching %q...", provider.ForDisplay(), getproviders.VersionConstraintsString(versionConstraints))) 560 } else { 561 c.Ui.Info(fmt.Sprintf("- Finding latest version of %s...", provider.ForDisplay())) 562 } 563 } 564 }, 565 LinkFromCacheBegin: func(provider addrs.Provider, version getproviders.Version, cacheRoot string) { 566 c.Ui.Info(fmt.Sprintf("- Using %s v%s from the shared cache directory", provider.ForDisplay(), version)) 567 }, 568 FetchPackageBegin: func(provider addrs.Provider, version getproviders.Version, location getproviders.PackageLocation) { 569 c.Ui.Info(fmt.Sprintf("- Installing %s v%s...", provider.ForDisplay(), version)) 570 }, 571 QueryPackagesFailure: func(provider addrs.Provider, err error) { 572 switch errorTy := err.(type) { 573 case getproviders.ErrProviderNotFound: 574 sources := errorTy.Sources 575 displaySources := make([]string, len(sources)) 576 for i, source := range sources { 577 displaySources[i] = fmt.Sprintf(" - %s", source) 578 } 579 diags = diags.Append(tfdiags.Sourceless( 580 tfdiags.Error, 581 "Failed to query available provider packages", 582 fmt.Sprintf("Could not retrieve the list of available versions for provider %s: %s\n\n%s", 583 provider.ForDisplay(), err, strings.Join(displaySources, "\n"), 584 ), 585 )) 586 case getproviders.ErrRegistryProviderNotKnown: 587 // We might be able to suggest an alternative provider to use 588 // instead of this one. 589 suggestion := fmt.Sprintf("\n\nAll modules should specify their required_providers so that external consumers will get the correct providers when using a module. To see which modules are currently depending on %s, run the following command:\n terraform providers", provider.ForDisplay()) 590 alternative := getproviders.MissingProviderSuggestion(ctx, provider, inst.ProviderSource(), reqs) 591 if alternative != provider { 592 suggestion = fmt.Sprintf( 593 "\n\nDid you intend to use %s? If so, you must specify that source address in each module which requires that provider. To see which modules are currently depending on %s, run the following command:\n terraform providers", 594 alternative.ForDisplay(), provider.ForDisplay(), 595 ) 596 } 597 598 diags = diags.Append(tfdiags.Sourceless( 599 tfdiags.Error, 600 "Failed to query available provider packages", 601 fmt.Sprintf("Could not retrieve the list of available versions for provider %s: %s%s", 602 provider.ForDisplay(), err, suggestion, 603 ), 604 )) 605 case getproviders.ErrHostNoProviders: 606 switch { 607 case errorTy.Hostname == svchost.Hostname("github.com") && !errorTy.HasOtherVersion: 608 // If a user copies the URL of a GitHub repository into 609 // the source argument and removes the schema to make it 610 // provider-address-shaped then that's one way we can end up 611 // here. We'll use a specialized error message in anticipation 612 // of that mistake. We only do this if github.com isn't a 613 // provider registry, to allow for the (admittedly currently 614 // rather unlikely) possibility that github.com starts being 615 // a real Terraform provider registry in the future. 616 diags = diags.Append(tfdiags.Sourceless( 617 tfdiags.Error, 618 "Invalid provider registry host", 619 fmt.Sprintf("The given source address %q specifies a GitHub repository rather than a Terraform provider. Refer to the documentation of the provider to find the correct source address to use.", 620 provider.String(), 621 ), 622 )) 623 624 case errorTy.HasOtherVersion: 625 diags = diags.Append(tfdiags.Sourceless( 626 tfdiags.Error, 627 "Invalid provider registry host", 628 fmt.Sprintf("The host %q given in in provider source address %q does not offer a Terraform provider registry that is compatible with this Terraform version, but it may be compatible with a different Terraform version.", 629 errorTy.Hostname, provider.String(), 630 ), 631 )) 632 633 default: 634 diags = diags.Append(tfdiags.Sourceless( 635 tfdiags.Error, 636 "Invalid provider registry host", 637 fmt.Sprintf("The host %q given in in provider source address %q does not offer a Terraform provider registry.", 638 errorTy.Hostname, provider.String(), 639 ), 640 )) 641 } 642 643 case getproviders.ErrRequestCanceled: 644 // We don't attribute cancellation to any particular operation, 645 // but rather just emit a single general message about it at 646 // the end, by checking ctx.Err(). 647 648 default: 649 diags = diags.Append(tfdiags.Sourceless( 650 tfdiags.Error, 651 "Failed to query available provider packages", 652 fmt.Sprintf("Could not retrieve the list of available versions for provider %s: %s", 653 provider.ForDisplay(), err, 654 ), 655 )) 656 } 657 658 }, 659 QueryPackagesWarning: func(provider addrs.Provider, warnings []string) { 660 displayWarnings := make([]string, len(warnings)) 661 for i, warning := range warnings { 662 displayWarnings[i] = fmt.Sprintf("- %s", warning) 663 } 664 665 diags = diags.Append(tfdiags.Sourceless( 666 tfdiags.Warning, 667 "Additional provider information from registry", 668 fmt.Sprintf("The remote registry returned warnings for %s:\n%s", 669 provider.String(), 670 strings.Join(displayWarnings, "\n"), 671 ), 672 )) 673 }, 674 LinkFromCacheFailure: func(provider addrs.Provider, version getproviders.Version, err error) { 675 diags = diags.Append(tfdiags.Sourceless( 676 tfdiags.Error, 677 "Failed to install provider from shared cache", 678 fmt.Sprintf("Error while importing %s v%s from the shared cache directory: %s.", provider.ForDisplay(), version, err), 679 )) 680 }, 681 FetchPackageFailure: func(provider addrs.Provider, version getproviders.Version, err error) { 682 const summaryIncompatible = "Incompatible provider version" 683 switch err := err.(type) { 684 case getproviders.ErrProtocolNotSupported: 685 closestAvailable := err.Suggestion 686 switch { 687 case closestAvailable == getproviders.UnspecifiedVersion: 688 diags = diags.Append(tfdiags.Sourceless( 689 tfdiags.Error, 690 summaryIncompatible, 691 fmt.Sprintf(errProviderVersionIncompatible, provider.String()), 692 )) 693 case version.GreaterThan(closestAvailable): 694 diags = diags.Append(tfdiags.Sourceless( 695 tfdiags.Error, 696 summaryIncompatible, 697 fmt.Sprintf(providerProtocolTooNew, provider.ForDisplay(), 698 version, tfversion.String(), closestAvailable, closestAvailable, 699 getproviders.VersionConstraintsString(reqs[provider]), 700 ), 701 )) 702 default: // version is less than closestAvailable 703 diags = diags.Append(tfdiags.Sourceless( 704 tfdiags.Error, 705 summaryIncompatible, 706 fmt.Sprintf(providerProtocolTooOld, provider.ForDisplay(), 707 version, tfversion.String(), closestAvailable, closestAvailable, 708 getproviders.VersionConstraintsString(reqs[provider]), 709 ), 710 )) 711 } 712 case getproviders.ErrPlatformNotSupported: 713 switch { 714 case err.MirrorURL != nil: 715 // If we're installing from a mirror then it may just be 716 // the mirror lacking the package, rather than it being 717 // unavailable from upstream. 718 diags = diags.Append(tfdiags.Sourceless( 719 tfdiags.Error, 720 summaryIncompatible, 721 fmt.Sprintf( 722 "Your chosen provider mirror at %s does not have a %s v%s package available for your current platform, %s.\n\nProvider releases are separate from Terraform CLI releases, so this provider might not support your current platform. Alternatively, the mirror itself might have only a subset of the plugin packages available in the origin registry, at %s.", 723 err.MirrorURL, err.Provider, err.Version, err.Platform, 724 err.Provider.Hostname, 725 ), 726 )) 727 default: 728 diags = diags.Append(tfdiags.Sourceless( 729 tfdiags.Error, 730 summaryIncompatible, 731 fmt.Sprintf( 732 "Provider %s v%s does not have a package available for your current platform, %s.\n\nProvider releases are separate from Terraform CLI releases, so not all providers are available for all platforms. Other versions of this provider may have different platforms supported.", 733 err.Provider, err.Version, err.Platform, 734 ), 735 )) 736 } 737 738 case getproviders.ErrRequestCanceled: 739 // We don't attribute cancellation to any particular operation, 740 // but rather just emit a single general message about it at 741 // the end, by checking ctx.Err(). 742 743 default: 744 // We can potentially end up in here under cancellation too, 745 // in spite of our getproviders.ErrRequestCanceled case above, 746 // because not all of the outgoing requests we do under the 747 // "fetch package" banner are source metadata requests. 748 // In that case we will emit a redundant error here about 749 // the request being cancelled, but we'll still detect it 750 // as a cancellation after the installer returns and do the 751 // normal cancellation handling. 752 753 diags = diags.Append(tfdiags.Sourceless( 754 tfdiags.Error, 755 "Failed to install provider", 756 fmt.Sprintf("Error while installing %s v%s: %s", provider.ForDisplay(), version, err), 757 )) 758 } 759 }, 760 FetchPackageSuccess: func(provider addrs.Provider, version getproviders.Version, localDir string, authResult *getproviders.PackageAuthenticationResult) { 761 var keyID string 762 if authResult != nil && authResult.ThirdPartySigned() { 763 keyID = authResult.KeyID 764 } 765 if keyID != "" { 766 keyID = c.Colorize().Color(fmt.Sprintf(", key ID [reset][bold]%s[reset]", keyID)) 767 } 768 769 c.Ui.Info(fmt.Sprintf("- Installed %s v%s (%s%s)", provider.ForDisplay(), version, authResult, keyID)) 770 }, 771 ProvidersLockUpdated: func(provider addrs.Provider, version getproviders.Version, localHashes []getproviders.Hash, signedHashes []getproviders.Hash, priorHashes []getproviders.Hash) { 772 // We're going to use this opportunity to track if we have any 773 // "incomplete" installs of providers. An incomplete install is 774 // when we are only going to write the local hashes into our lock 775 // file which means a `terraform init` command will fail in future 776 // when used on machines of a different architecture. 777 // 778 // We want to print a warning about this. 779 780 if len(signedHashes) > 0 { 781 // If we have any signedHashes hashes then we don't worry - as 782 // we know we retrieved all available hashes for this version 783 // anyway. 784 return 785 } 786 787 // If local hashes and prior hashes are exactly the same then 788 // it means we didn't record any signed hashes previously, and 789 // we know we're not adding any extra in now (because we already 790 // checked the signedHashes), so that's a problem. 791 // 792 // In the actual check here, if we have any priorHashes and those 793 // hashes are not the same as the local hashes then we're going to 794 // accept that this provider has been configured correctly. 795 if len(priorHashes) > 0 && !reflect.DeepEqual(localHashes, priorHashes) { 796 return 797 } 798 799 // Now, either signedHashes is empty, or priorHashes is exactly the 800 // same as our localHashes which means we never retrieved the 801 // signedHashes previously. 802 // 803 // Either way, this is bad. Let's complain/warn. 804 incompleteProviders = append(incompleteProviders, provider.ForDisplay()) 805 }, 806 ProvidersFetched: func(authResults map[addrs.Provider]*getproviders.PackageAuthenticationResult) { 807 thirdPartySigned := false 808 for _, authResult := range authResults { 809 if authResult.ThirdPartySigned() { 810 thirdPartySigned = true 811 break 812 } 813 } 814 if thirdPartySigned { 815 c.Ui.Info(fmt.Sprintf("\nPartner and community providers are signed by their developers.\n" + 816 "If you'd like to know more about provider signing, you can read about it here:\n" + 817 "https://www.terraform.io/docs/cli/plugins/signing.html")) 818 } 819 }, 820 } 821 ctx = evts.OnContext(ctx) 822 823 mode := providercache.InstallNewProvidersOnly 824 if upgrade { 825 if flagLockfile == "readonly" { 826 c.Ui.Error("The -upgrade flag conflicts with -lockfile=readonly.") 827 return true, true, diags 828 } 829 830 mode = providercache.InstallUpgrades 831 } 832 newLocks, err := inst.EnsureProviderVersions(ctx, previousLocks, reqs, mode) 833 if ctx.Err() == context.Canceled { 834 c.showDiagnostics(diags) 835 c.Ui.Error("Provider installation was canceled by an interrupt signal.") 836 return true, true, diags 837 } 838 if err != nil { 839 // The errors captured in "err" should be redundant with what we 840 // received via the InstallerEvents callbacks above, so we'll 841 // just return those as long as we have some. 842 if !diags.HasErrors() { 843 diags = diags.Append(err) 844 } 845 846 return true, true, diags 847 } 848 849 // If the provider dependencies have changed since the last run then we'll 850 // say a little about that in case the reader wasn't expecting a change. 851 // (When we later integrate module dependencies into the lock file we'll 852 // probably want to refactor this so that we produce one lock-file related 853 // message for all changes together, but this is here for now just because 854 // it's the smallest change relative to what came before it, which was 855 // a hidden JSON file specifically for tracking providers.) 856 if !newLocks.Equal(previousLocks) { 857 // if readonly mode 858 if flagLockfile == "readonly" { 859 // check if required provider dependences change 860 if !newLocks.EqualProviderAddress(previousLocks) { 861 diags = diags.Append(tfdiags.Sourceless( 862 tfdiags.Error, 863 `Provider dependency changes detected`, 864 `Changes to the required provider dependencies were detected, but the lock file is read-only. To use and record these requirements, run "terraform init" without the "-lockfile=readonly" flag.`, 865 )) 866 return true, true, diags 867 } 868 869 // suppress updating the file to record any new information it learned, 870 // such as a hash using a new scheme. 871 diags = diags.Append(tfdiags.Sourceless( 872 tfdiags.Warning, 873 `Provider lock file not updated`, 874 `Changes to the provider selections were detected, but not saved in the .terraform.lock.hcl file. To record these selections, run "terraform init" without the "-lockfile=readonly" flag.`, 875 )) 876 return true, false, diags 877 } 878 879 // Jump in here and add a warning if any of the providers are incomplete. 880 if len(incompleteProviders) > 0 { 881 // We don't really care about the order here, we just want the 882 // output to be deterministic. 883 sort.Slice(incompleteProviders, func(i, j int) bool { 884 return incompleteProviders[i] < incompleteProviders[j] 885 }) 886 diags = diags.Append(tfdiags.Sourceless( 887 tfdiags.Warning, 888 incompleteLockFileInformationHeader, 889 fmt.Sprintf( 890 incompleteLockFileInformationBody, 891 strings.Join(incompleteProviders, "\n - "), 892 getproviders.CurrentPlatform.String()))) 893 } 894 895 if previousLocks.Empty() { 896 // A change from empty to non-empty is special because it suggests 897 // we're running "terraform init" for the first time against a 898 // new configuration. In that case we'll take the opportunity to 899 // say a little about what the dependency lock file is, for new 900 // users or those who are upgrading from a previous Terraform 901 // version that didn't have dependency lock files. 902 c.Ui.Output(c.Colorize().Color(` 903 Terraform has created a lock file [bold].terraform.lock.hcl[reset] to record the provider 904 selections it made above. Include this file in your version control repository 905 so that Terraform can guarantee to make the same selections by default when 906 you run "terraform init" in the future.`)) 907 } else { 908 c.Ui.Output(c.Colorize().Color(` 909 Terraform has made some changes to the provider dependency selections recorded 910 in the .terraform.lock.hcl file. Review those changes and commit them to your 911 version control system if they represent changes you intended to make.`)) 912 } 913 914 moreDiags = c.replaceLockedDependencies(newLocks) 915 diags = diags.Append(moreDiags) 916 } 917 918 return true, false, diags 919 } 920 921 // backendConfigOverrideBody interprets the raw values of -backend-config 922 // arguments into a hcl Body that should override the backend settings given 923 // in the configuration. 924 // 925 // If the result is nil then no override needs to be provided. 926 // 927 // If the returned diagnostics contains errors then the returned body may be 928 // incomplete or invalid. 929 func (c *InitCommand) backendConfigOverrideBody(flags rawFlags, schema *configschema.Block) (hcl.Body, tfdiags.Diagnostics) { 930 items := flags.AllItems() 931 if len(items) == 0 { 932 return nil, nil 933 } 934 935 var ret hcl.Body 936 var diags tfdiags.Diagnostics 937 synthVals := make(map[string]cty.Value) 938 939 mergeBody := func(newBody hcl.Body) { 940 if ret == nil { 941 ret = newBody 942 } else { 943 ret = configs.MergeBodies(ret, newBody) 944 } 945 } 946 flushVals := func() { 947 if len(synthVals) == 0 { 948 return 949 } 950 newBody := configs.SynthBody("-backend-config=...", synthVals) 951 mergeBody(newBody) 952 synthVals = make(map[string]cty.Value) 953 } 954 955 if len(items) == 1 && items[0].Value == "" { 956 // Explicitly remove all -backend-config options. 957 // We do this by setting an empty but non-nil ConfigOverrides. 958 return configs.SynthBody("-backend-config=''", synthVals), diags 959 } 960 961 for _, item := range items { 962 eq := strings.Index(item.Value, "=") 963 964 if eq == -1 { 965 // The value is interpreted as a filename. 966 newBody, fileDiags := c.loadHCLFile(item.Value) 967 diags = diags.Append(fileDiags) 968 if fileDiags.HasErrors() { 969 continue 970 } 971 // Generate an HCL body schema for the backend block. 972 var bodySchema hcl.BodySchema 973 for name := range schema.Attributes { 974 // We intentionally ignore the `Required` attribute here 975 // because backend config override files can be partial. The 976 // goal is to make sure we're not loading a file with 977 // extraneous attributes or blocks. 978 bodySchema.Attributes = append(bodySchema.Attributes, hcl.AttributeSchema{ 979 Name: name, 980 }) 981 } 982 for name, block := range schema.BlockTypes { 983 var labelNames []string 984 if block.Nesting == configschema.NestingMap { 985 labelNames = append(labelNames, "key") 986 } 987 bodySchema.Blocks = append(bodySchema.Blocks, hcl.BlockHeaderSchema{ 988 Type: name, 989 LabelNames: labelNames, 990 }) 991 } 992 // Verify that the file body matches the expected backend schema. 993 _, schemaDiags := newBody.Content(&bodySchema) 994 diags = diags.Append(schemaDiags) 995 if schemaDiags.HasErrors() { 996 continue 997 } 998 flushVals() // deal with any accumulated individual values first 999 mergeBody(newBody) 1000 } else { 1001 name := item.Value[:eq] 1002 rawValue := item.Value[eq+1:] 1003 attrS := schema.Attributes[name] 1004 if attrS == nil { 1005 diags = diags.Append(tfdiags.Sourceless( 1006 tfdiags.Error, 1007 "Invalid backend configuration argument", 1008 fmt.Sprintf("The backend configuration argument %q given on the command line is not expected for the selected backend type.", name), 1009 )) 1010 continue 1011 } 1012 value, valueDiags := configValueFromCLI(item.String(), rawValue, attrS.Type) 1013 diags = diags.Append(valueDiags) 1014 if valueDiags.HasErrors() { 1015 continue 1016 } 1017 synthVals[name] = value 1018 } 1019 } 1020 1021 flushVals() 1022 1023 return ret, diags 1024 } 1025 1026 func (c *InitCommand) AutocompleteArgs() complete.Predictor { 1027 return complete.PredictDirs("") 1028 } 1029 1030 func (c *InitCommand) AutocompleteFlags() complete.Flags { 1031 return complete.Flags{ 1032 "-backend": completePredictBoolean, 1033 "-cloud": completePredictBoolean, 1034 "-backend-config": complete.PredictFiles("*.tfvars"), // can also be key=value, but we can't "predict" that 1035 "-force-copy": complete.PredictNothing, 1036 "-from-module": completePredictModuleSource, 1037 "-get": completePredictBoolean, 1038 "-input": completePredictBoolean, 1039 "-lock": completePredictBoolean, 1040 "-lock-timeout": complete.PredictAnything, 1041 "-no-color": complete.PredictNothing, 1042 "-plugin-dir": complete.PredictDirs(""), 1043 "-reconfigure": complete.PredictNothing, 1044 "-migrate-state": complete.PredictNothing, 1045 "-upgrade": completePredictBoolean, 1046 } 1047 } 1048 1049 func (c *InitCommand) Help() string { 1050 helpText := ` 1051 Usage: terraform [global options] init [options] 1052 1053 Initialize a new or existing Terraform working directory by creating 1054 initial files, loading any remote state, downloading modules, etc. 1055 1056 This is the first command that should be run for any new or existing 1057 Terraform configuration per machine. This sets up all the local data 1058 necessary to run Terraform that is typically not committed to version 1059 control. 1060 1061 This command is always safe to run multiple times. Though subsequent runs 1062 may give errors, this command will never delete your configuration or 1063 state. Even so, if you have important information, please back it up prior 1064 to running this command, just in case. 1065 1066 Options: 1067 1068 -backend=false Disable backend or Terraform Cloud initialization 1069 for this configuration and use what was previously 1070 initialized instead. 1071 1072 aliases: -cloud=false 1073 1074 -backend-config=path Configuration to be merged with what is in the 1075 configuration file's 'backend' block. This can be 1076 either a path to an HCL file with key/value 1077 assignments (same format as terraform.tfvars) or a 1078 'key=value' format, and can be specified multiple 1079 times. The backend type must be in the configuration 1080 itself. 1081 1082 -force-copy Suppress prompts about copying state data when 1083 initializating a new state backend. This is 1084 equivalent to providing a "yes" to all confirmation 1085 prompts. 1086 1087 -from-module=SOURCE Copy the contents of the given module into the target 1088 directory before initialization. 1089 1090 -get=false Disable downloading modules for this configuration. 1091 1092 -input=false Disable interactive prompts. Note that some actions may 1093 require interactive prompts and will error if input is 1094 disabled. 1095 1096 -lock=false Don't hold a state lock during backend migration. 1097 This is dangerous if others might concurrently run 1098 commands against the same workspace. 1099 1100 -lock-timeout=0s Duration to retry a state lock. 1101 1102 -no-color If specified, output won't contain any color. 1103 1104 -plugin-dir Directory containing plugin binaries. This overrides all 1105 default search paths for plugins, and prevents the 1106 automatic installation of plugins. This flag can be used 1107 multiple times. 1108 1109 -reconfigure Reconfigure a backend, ignoring any saved 1110 configuration. 1111 1112 -migrate-state Reconfigure a backend, and attempt to migrate any 1113 existing state. 1114 1115 -upgrade Install the latest module and provider versions 1116 allowed within configured constraints, overriding the 1117 default behavior of selecting exactly the version 1118 recorded in the dependency lockfile. 1119 1120 -lockfile=MODE Set a dependency lockfile mode. 1121 Currently only "readonly" is valid. 1122 1123 -ignore-remote-version A rare option used for Terraform Cloud and the remote backend 1124 only. Set this to ignore checking that the local and remote 1125 Terraform versions use compatible state representations, making 1126 an operation proceed even when there is a potential mismatch. 1127 See the documentation on configuring Terraform with 1128 Terraform Cloud for more information. 1129 1130 ` 1131 return strings.TrimSpace(helpText) 1132 } 1133 1134 func (c *InitCommand) Synopsis() string { 1135 return "Prepare your working directory for other commands" 1136 } 1137 1138 const errInitConfigError = ` 1139 [reset]There are some problems with the configuration, described below. 1140 1141 The Terraform configuration must be valid before initialization so that 1142 Terraform can determine which modules and providers need to be installed. 1143 ` 1144 1145 const errInitCopyNotEmpty = ` 1146 The working directory already contains files. The -from-module option requires 1147 an empty directory into which a copy of the referenced module will be placed. 1148 1149 To initialize the configuration already in this working directory, omit the 1150 -from-module option. 1151 ` 1152 1153 const outputInitEmpty = ` 1154 [reset][bold]Terraform initialized in an empty directory![reset] 1155 1156 The directory has no Terraform configuration files. You may begin working 1157 with Terraform immediately by creating Terraform configuration files. 1158 ` 1159 1160 const outputInitSuccess = ` 1161 [reset][bold][green]Terraform has been successfully initialized![reset][green] 1162 ` 1163 1164 const outputInitSuccessCloud = ` 1165 [reset][bold][green]Terraform Cloud has been successfully initialized![reset][green] 1166 ` 1167 1168 const outputInitSuccessCLI = `[reset][green] 1169 You may now begin working with Terraform. Try running "terraform plan" to see 1170 any changes that are required for your infrastructure. All Terraform commands 1171 should now work. 1172 1173 If you ever set or change modules or backend configuration for Terraform, 1174 rerun this command to reinitialize your working directory. If you forget, other 1175 commands will detect it and remind you to do so if necessary. 1176 ` 1177 1178 const outputInitSuccessCLICloud = `[reset][green] 1179 You may now begin working with Terraform Cloud. Try running "terraform plan" to 1180 see any changes that are required for your infrastructure. 1181 1182 If you ever set or change modules or Terraform Settings, run "terraform init" 1183 again to reinitialize your working directory. 1184 ` 1185 1186 // providerProtocolTooOld is a message sent to the CLI UI if the provider's 1187 // supported protocol versions are too old for the user's version of terraform, 1188 // but a newer version of the provider is compatible. 1189 const providerProtocolTooOld = `Provider %q v%s is not compatible with Terraform %s. 1190 Provider version %s is the latest compatible version. Select it with the following version constraint: 1191 version = %q 1192 1193 Terraform checked all of the plugin versions matching the given constraint: 1194 %s 1195 1196 Consult the documentation for this provider for more information on compatibility between provider and Terraform versions. 1197 ` 1198 1199 // providerProtocolTooNew is a message sent to the CLI UI if the provider's 1200 // supported protocol versions are too new for the user's version of terraform, 1201 // and the user could either upgrade terraform or choose an older version of the 1202 // provider. 1203 const providerProtocolTooNew = `Provider %q v%s is not compatible with Terraform %s. 1204 You need to downgrade to v%s or earlier. Select it with the following constraint: 1205 version = %q 1206 1207 Terraform checked all of the plugin versions matching the given constraint: 1208 %s 1209 1210 Consult the documentation for this provider for more information on compatibility between provider and Terraform versions. 1211 Alternatively, upgrade to the latest version of Terraform for compatibility with newer provider releases. 1212 ` 1213 1214 // No version of the provider is compatible. 1215 const errProviderVersionIncompatible = `No compatible versions of provider %s were found.` 1216 1217 // incompleteLockFileInformationHeader is the summary displayed to users when 1218 // the lock file has only recorded local hashes. 1219 const incompleteLockFileInformationHeader = `Incomplete lock file information for providers` 1220 1221 // incompleteLockFileInformationBody is the body of text displayed to users when 1222 // the lock file has only recorded local hashes. 1223 const incompleteLockFileInformationBody = `Due to your customized provider installation methods, Terraform was forced to calculate lock file checksums locally for the following providers: 1224 - %s 1225 1226 The current .terraform.lock.hcl file only includes checksums for %s, so Terraform running on another platform will fail to install these providers. 1227 1228 To calculate additional checksums for another platform, run: 1229 terraform providers lock -platform=linux_amd64 1230 (where linux_amd64 is the platform to generate)`