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