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