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