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