github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/plugin/discovery/get.go (about) 1 package discovery 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "log" 9 "net/http" 10 "os" 11 "path/filepath" 12 "runtime" 13 "strconv" 14 "strings" 15 16 "github.com/hashicorp/errwrap" 17 getter "github.com/hashicorp/go-getter" 18 multierror "github.com/hashicorp/go-multierror" 19 "github.com/hashicorp/terraform-svchost/disco" 20 "github.com/hashicorp/terraform/addrs" 21 "github.com/hashicorp/terraform/httpclient" 22 "github.com/hashicorp/terraform/registry" 23 "github.com/hashicorp/terraform/registry/regsrc" 24 "github.com/hashicorp/terraform/registry/response" 25 "github.com/hashicorp/terraform/tfdiags" 26 tfversion "github.com/hashicorp/terraform/version" 27 "github.com/mitchellh/cli" 28 ) 29 30 // Releases are located by querying the terraform registry. 31 32 const protocolVersionHeader = "x-terraform-protocol-version" 33 34 var httpClient *http.Client 35 36 var errVersionNotFound = errors.New("version not found") 37 38 func init() { 39 httpClient = httpclient.New() 40 41 httpGetter := &getter.HttpGetter{ 42 Client: httpClient, 43 Netrc: true, 44 } 45 46 getter.Getters["http"] = httpGetter 47 getter.Getters["https"] = httpGetter 48 } 49 50 // An Installer maintains a local cache of plugins by downloading plugins 51 // from an online repository. 52 type Installer interface { 53 Get(provider addrs.Provider, req Constraints) (PluginMeta, tfdiags.Diagnostics, error) 54 PurgeUnused(used map[string]PluginMeta) (removed PluginMetaSet, err error) 55 } 56 57 // ProviderInstaller is an Installer implementation that knows how to 58 // download Terraform providers from the official HashiCorp releases service 59 // into a local directory. The files downloaded are compliant with the 60 // naming scheme expected by FindPlugins, so the target directory of a 61 // provider installer can be used as one of several plugin discovery sources. 62 type ProviderInstaller struct { 63 Dir string 64 65 // Cache is used to access and update a local cache of plugins if non-nil. 66 // Can be nil to disable caching. 67 Cache PluginCache 68 69 PluginProtocolVersion uint 70 71 // OS and Arch specify the OS and architecture that should be used when 72 // installing plugins. These use the same labels as the runtime.GOOS and 73 // runtime.GOARCH variables respectively, and indeed the values of these 74 // are used as defaults if either of these is the empty string. 75 OS string 76 Arch string 77 78 // Skip checksum and signature verification 79 SkipVerify bool 80 81 Ui cli.Ui // Ui for output 82 83 // Services is a required *disco.Disco, which may have services and 84 // credentials pre-loaded. 85 Services *disco.Disco 86 87 // registry client 88 registry *registry.Client 89 } 90 91 // Get is part of an implementation of type Installer, and attempts to download 92 // and install a Terraform provider matching the given constraints. 93 // 94 // This method may return one of a number of sentinel errors from this 95 // package to indicate issues that are likely to be resolvable via user action: 96 // 97 // ErrorNoSuchProvider: no provider with the given name exists in the repository. 98 // ErrorNoSuitableVersion: the provider exists but no available version matches constraints. 99 // ErrorNoVersionCompatible: a plugin was found within the constraints but it is 100 // incompatible with the current Terraform version. 101 // 102 // These errors should be recognized and handled as special cases by the caller 103 // to present a suitable user-oriented error message. 104 // 105 // All other errors indicate an internal problem that is likely _not_ solvable 106 // through user action, or at least not within Terraform's scope. Error messages 107 // are produced under the assumption that if presented to the user they will 108 // be presented alongside context about what is being installed, and thus the 109 // error messages do not redundantly include such information. 110 func (i *ProviderInstaller) Get(provider addrs.Provider, req Constraints) (PluginMeta, tfdiags.Diagnostics, error) { 111 var diags tfdiags.Diagnostics 112 113 // a little bit of initialization. 114 if i.OS == "" { 115 i.OS = runtime.GOOS 116 } 117 if i.Arch == "" { 118 i.Arch = runtime.GOARCH 119 } 120 if i.registry == nil { 121 i.registry = registry.NewClient(i.Services, nil) 122 } 123 124 // get a full listing of versions for the requested provider 125 allVersions, err := i.listProviderVersions(provider) 126 127 // TODO: return multiple errors 128 if err != nil { 129 log.Printf("[DEBUG] %s", err) 130 if registry.IsServiceUnreachable(err) { 131 registryHost, err := i.hostname() 132 if err == nil && registryHost == regsrc.PublicRegistryHost.Raw { 133 return PluginMeta{}, diags, ErrorPublicRegistryUnreachable 134 } 135 return PluginMeta{}, diags, ErrorServiceUnreachable 136 } 137 if registry.IsServiceNotProvided(err) { 138 return PluginMeta{}, diags, err 139 } 140 return PluginMeta{}, diags, ErrorNoSuchProvider 141 } 142 143 // Add any warnings from the response to diags 144 for _, warning := range allVersions.Warnings { 145 hostname, err := i.hostname() 146 if err != nil { 147 return PluginMeta{}, diags, err 148 } 149 diag := tfdiags.SimpleWarning(fmt.Sprintf("%s: %s", hostname, warning)) 150 diags = diags.Append(diag) 151 } 152 153 if len(allVersions.Versions) == 0 { 154 return PluginMeta{}, diags, ErrorNoSuitableVersion 155 } 156 providerSource := allVersions.ID 157 158 // Filter the list of plugin versions to those which meet the version constraints 159 versions := allowedVersions(allVersions, req) 160 if len(versions) == 0 { 161 return PluginMeta{}, diags, ErrorNoSuitableVersion 162 } 163 164 // sort them newest to oldest. The newest version wins! 165 response.ProviderVersionCollection(versions).Sort() 166 167 // if the chosen provider version does not support the requested platform, 168 // filter the list of acceptable versions to those that support that platform 169 if err := i.checkPlatformCompatibility(versions[0]); err != nil { 170 versions = i.platformCompatibleVersions(versions) 171 if len(versions) == 0 { 172 return PluginMeta{}, diags, ErrorNoVersionCompatibleWithPlatform 173 } 174 } 175 176 // we now have a winning platform-compatible version 177 versionMeta := versions[0] 178 v := VersionStr(versionMeta.Version).MustParse() 179 180 // check protocol compatibility 181 if err := i.checkPluginProtocol(versionMeta); err != nil { 182 closestMatch, err := i.findClosestProtocolCompatibleVersion(allVersions.Versions) 183 if err != nil { 184 // No operation here if we can't find a version with compatible protocol 185 return PluginMeta{}, diags, err 186 } 187 188 // Prompt version suggestion to UI based on closest protocol match 189 var errMsg string 190 closestVersion := VersionStr(closestMatch.Version).MustParse() 191 if v.NewerThan(closestVersion) { 192 errMsg = providerProtocolTooNew 193 } else { 194 errMsg = providerProtocolTooOld 195 } 196 197 constraintStr := req.String() 198 if constraintStr == "" { 199 constraintStr = "(any version)" 200 } 201 202 return PluginMeta{}, diags, errwrap.Wrap(ErrorVersionIncompatible, fmt.Errorf(fmt.Sprintf( 203 errMsg, provider.LegacyString(), v.String(), tfversion.String(), 204 closestVersion.String(), closestVersion.MinorUpgradeConstraintStr(), constraintStr))) 205 } 206 207 downloadURLs, err := i.listProviderDownloadURLs(providerSource, versionMeta.Version) 208 if err != nil { 209 return PluginMeta{}, diags, err 210 } 211 providerURL := downloadURLs.DownloadURL 212 213 if !i.SkipVerify { 214 // Terraform verifies the integrity of a provider release before downloading 215 // the plugin binary. The digital signature (SHA256SUMS.sig) on the 216 // release distribution (SHA256SUMS) is verified with the public key of the 217 // publisher provided in the Terraform Registry response, ensuring that 218 // everything is as intended by the publisher. The checksum of the provider 219 // plugin is expected in the SHA256SUMS file and is double checked to match 220 // the checksum of the original published release to the Registry. This 221 // enforces immutability of releases between the Registry and the plugin's 222 // host location. Lastly, the integrity of the binary is verified upon 223 // download matches the Registry and signed checksum. 224 sha256, err := i.getProviderChecksum(downloadURLs) 225 if err != nil { 226 return PluginMeta{}, diags, err 227 } 228 229 // add the checksum parameter for go-getter to verify the download for us. 230 if sha256 != "" { 231 providerURL = providerURL + "?checksum=sha256:" + sha256 232 } 233 } 234 235 printedProviderName := fmt.Sprintf("%q (%s)", provider.LegacyString(), providerSource) 236 i.Ui.Info(fmt.Sprintf("- Downloading plugin for provider %s %s...", printedProviderName, versionMeta.Version)) 237 log.Printf("[DEBUG] getting provider %s version %q", printedProviderName, versionMeta.Version) 238 err = i.install(provider, v, providerURL) 239 if err != nil { 240 return PluginMeta{}, diags, err 241 } 242 243 // Find what we just installed 244 // (This is weird, because go-getter doesn't directly return 245 // information about what was extracted, and we just extracted 246 // the archive directly into a shared dir here.) 247 log.Printf("[DEBUG] looking for the %s %s plugin we just installed", provider.LegacyString(), versionMeta.Version) 248 metas := FindPlugins("provider", []string{i.Dir}) 249 log.Printf("[DEBUG] all plugins found %#v", metas) 250 metas, _ = metas.ValidateVersions() 251 metas = metas.WithName(provider.Type).WithVersion(v) 252 log.Printf("[DEBUG] filtered plugins %#v", metas) 253 if metas.Count() == 0 { 254 // This should never happen. Suggests that the release archive 255 // contains an executable file whose name doesn't match the 256 // expected convention. 257 return PluginMeta{}, diags, fmt.Errorf( 258 "failed to find installed plugin version %s; this is a bug in Terraform and should be reported", 259 versionMeta.Version, 260 ) 261 } 262 263 if metas.Count() > 1 { 264 // This should also never happen, and suggests that a 265 // particular version was re-released with a different 266 // executable filename. We consider releases as immutable, so 267 // this is an error. 268 return PluginMeta{}, diags, fmt.Errorf( 269 "multiple plugins installed for version %s; this is a bug in Terraform and should be reported", 270 versionMeta.Version, 271 ) 272 } 273 274 // By now we know we have exactly one meta, and so "Newest" will 275 // return that one. 276 return metas.Newest(), diags, nil 277 } 278 279 func (i *ProviderInstaller) install(provider addrs.Provider, version Version, url string) error { 280 if i.Cache != nil { 281 log.Printf("[DEBUG] looking for provider %s %s in plugin cache", provider.LegacyString(), version) 282 cached := i.Cache.CachedPluginPath("provider", provider.Type, version) 283 if cached == "" { 284 log.Printf("[DEBUG] %s %s not yet in cache, so downloading %s", provider.LegacyString(), version, url) 285 err := getter.Get(i.Cache.InstallDir(), url) 286 if err != nil { 287 return err 288 } 289 // should now be in cache 290 cached = i.Cache.CachedPluginPath("provider", provider.Type, version) 291 if cached == "" { 292 // should never happen if the getter is behaving properly 293 // and the plugins are packaged properly. 294 return fmt.Errorf("failed to find downloaded plugin in cache %s", i.Cache.InstallDir()) 295 } 296 } 297 298 // Link or copy the cached binary into our install dir so the 299 // normal resolution machinery can find it. 300 filename := filepath.Base(cached) 301 targetPath := filepath.Join(i.Dir, filename) 302 // check if the target dir exists, and create it if not 303 var err error 304 if _, StatErr := os.Stat(i.Dir); os.IsNotExist(StatErr) { 305 err = os.MkdirAll(i.Dir, 0700) 306 } 307 if err != nil { 308 return err 309 } 310 311 log.Printf("[DEBUG] installing %s %s to %s from local cache %s", provider.LegacyString(), version, targetPath, cached) 312 313 // Delete if we can. If there's nothing there already then no harm done. 314 // This is important because we can't create a link if there's 315 // already a file of the same name present. 316 // (any other error here we'll catch below when we try to write here) 317 os.Remove(targetPath) 318 319 // We don't attempt linking on Windows because links are not 320 // comprehensively supported by all tools/apps in Windows and 321 // so we choose to be conservative to avoid creating any 322 // weird issues for Windows users. 323 linkErr := errors.New("link not supported for Windows") // placeholder error, never actually returned 324 if runtime.GOOS != "windows" { 325 // Try hard linking first. Hard links are preferable because this 326 // creates a self-contained directory that doesn't depend on the 327 // cache after install. 328 linkErr = os.Link(cached, targetPath) 329 330 // If that failed, try a symlink. This _does_ depend on the cache 331 // after install, so the user must manage the cache more carefully 332 // in this case, but avoids creating redundant copies of the 333 // plugins on disk. 334 if linkErr != nil { 335 linkErr = os.Symlink(cached, targetPath) 336 } 337 } 338 339 // If we still have an error then we'll try a copy as a fallback. 340 // In this case either the OS is Windows or the target filesystem 341 // can't support symlinks. 342 if linkErr != nil { 343 srcFile, err := os.Open(cached) 344 if err != nil { 345 return fmt.Errorf("failed to open cached plugin %s: %s", cached, err) 346 } 347 defer srcFile.Close() 348 349 destFile, err := os.OpenFile(targetPath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, os.ModePerm) 350 if err != nil { 351 return fmt.Errorf("failed to create %s: %s", targetPath, err) 352 } 353 354 _, err = io.Copy(destFile, srcFile) 355 if err != nil { 356 destFile.Close() 357 return fmt.Errorf("failed to copy cached plugin from %s to %s: %s", cached, targetPath, err) 358 } 359 360 err = destFile.Close() 361 if err != nil { 362 return fmt.Errorf("error creating %s: %s", targetPath, err) 363 } 364 } 365 366 // One way or another, by the time we get here we should have either 367 // a link or a copy of the cached plugin within i.Dir, as expected. 368 } else { 369 log.Printf("[DEBUG] plugin cache is disabled, so downloading %s %s from %s", provider.LegacyString(), version, url) 370 err := getter.Get(i.Dir, url) 371 if err != nil { 372 return err 373 } 374 } 375 return nil 376 } 377 378 func (i *ProviderInstaller) PurgeUnused(used map[string]PluginMeta) (PluginMetaSet, error) { 379 purge := make(PluginMetaSet) 380 381 present := FindPlugins("provider", []string{i.Dir}) 382 for meta := range present { 383 chosen, ok := used[meta.Name] 384 if !ok { 385 purge.Add(meta) 386 } 387 if chosen.Path != meta.Path { 388 purge.Add(meta) 389 } 390 } 391 392 removed := make(PluginMetaSet) 393 var errs error 394 for meta := range purge { 395 path := meta.Path 396 err := os.Remove(path) 397 if err != nil { 398 errs = multierror.Append(errs, fmt.Errorf( 399 "failed to remove unused provider plugin %s: %s", 400 path, err, 401 )) 402 } else { 403 removed.Add(meta) 404 } 405 } 406 407 return removed, errs 408 } 409 410 func (i *ProviderInstaller) getProviderChecksum(resp *response.TerraformProviderPlatformLocation) (string, error) { 411 // Get SHA256SUMS file. 412 shasums, err := getFile(resp.ShasumsURL) 413 if err != nil { 414 log.Printf("[ERROR] error fetching checksums from %q: %s", resp.ShasumsURL, err) 415 return "", ErrorMissingChecksumVerification 416 } 417 418 // Get SHA256SUMS.sig file. 419 signature, err := getFile(resp.ShasumsSignatureURL) 420 if err != nil { 421 log.Printf("[ERROR] error fetching checksums signature from %q: %s", resp.ShasumsSignatureURL, err) 422 return "", ErrorSignatureVerification 423 } 424 425 // Verify the GPG signature returned from the Registry. 426 asciiArmor := resp.SigningKeys.GPGASCIIArmor() 427 signer, err := verifySig(shasums, signature, asciiArmor) 428 if err != nil { 429 log.Printf("[ERROR] error verifying signature: %s", err) 430 return "", ErrorSignatureVerification 431 } 432 433 // Also verify the GPG signature against the HashiCorp public key. This is 434 // a temporary additional check until a more robust key verification 435 // process is added in a future release. 436 _, err = verifySig(shasums, signature, HashicorpPublicKey) 437 if err != nil { 438 log.Printf("[ERROR] error verifying signature against HashiCorp public key: %s", err) 439 return "", ErrorSignatureVerification 440 } 441 442 // Display identity for GPG key which succeeded verifying the signature. 443 // This could also be used to display to the user with i.Ui.Info(). 444 identities := []string{} 445 for k := range signer.Identities { 446 identities = append(identities, k) 447 } 448 identity := strings.Join(identities, ", ") 449 log.Printf("[DEBUG] verified GPG signature with key from %s", identity) 450 451 // Extract checksum for this os/arch platform binary and verify against Registry 452 checksum := checksumForFile(shasums, resp.Filename) 453 if checksum == "" { 454 log.Printf("[ERROR] missing checksum for %s from source %s", resp.Filename, resp.ShasumsURL) 455 return "", ErrorMissingChecksumVerification 456 } else if checksum != resp.Shasum { 457 log.Printf("[ERROR] unexpected checksum for %s from source %q", resp.Filename, resp.ShasumsURL) 458 return "", ErrorChecksumVerification 459 } 460 461 return checksum, nil 462 } 463 464 func (i *ProviderInstaller) hostname() (string, error) { 465 provider := regsrc.NewTerraformProvider("", i.OS, i.Arch) 466 svchost, err := provider.SvcHost() 467 if err != nil { 468 return "", err 469 } 470 471 return svchost.ForDisplay(), nil 472 } 473 474 // list all versions available for the named provider 475 func (i *ProviderInstaller) listProviderVersions(provider addrs.Provider) (*response.TerraformProviderVersions, error) { 476 req := regsrc.NewTerraformProvider(provider.Type, i.OS, i.Arch) 477 versions, err := i.registry.TerraformProviderVersions(req) 478 return versions, err 479 } 480 481 func (i *ProviderInstaller) listProviderDownloadURLs(name, version string) (*response.TerraformProviderPlatformLocation, error) { 482 urls, err := i.registry.TerraformProviderLocation(regsrc.NewTerraformProvider(name, i.OS, i.Arch), version) 483 if urls == nil { 484 return nil, fmt.Errorf("No download urls found for provider %s", name) 485 } 486 return urls, err 487 } 488 489 // findClosestProtocolCompatibleVersion searches for the provider version with the closest protocol match. 490 // Prerelease versions are filtered. 491 func (i *ProviderInstaller) findClosestProtocolCompatibleVersion(versions []*response.TerraformProviderVersion) (*response.TerraformProviderVersion, error) { 492 // Loop through all the provider versions to find the earliest and latest 493 // versions that match the installer protocol to then select the closest of the two 494 var latest, earliest *response.TerraformProviderVersion 495 for _, version := range versions { 496 // Prereleases are filtered and will not be suggested 497 v, err := VersionStr(version.Version).Parse() 498 if err != nil || v.IsPrerelease() { 499 continue 500 } 501 502 if err := i.checkPluginProtocol(version); err == nil { 503 if earliest == nil { 504 // Found the first provider version with compatible protocol 505 earliest = version 506 } 507 // Update the latest protocol compatible version 508 latest = version 509 } 510 } 511 if earliest == nil { 512 // No compatible protocol was found for any version 513 return nil, ErrorNoVersionCompatible 514 } 515 516 // Convert protocols to comparable types 517 protoString := strconv.Itoa(int(i.PluginProtocolVersion)) 518 protocolVersion, err := VersionStr(protoString).Parse() 519 if err != nil { 520 return nil, fmt.Errorf("invalid plugin protocol version: %q", i.PluginProtocolVersion) 521 } 522 523 earliestVersionProtocol, err := VersionStr(earliest.Protocols[0]).Parse() 524 if err != nil { 525 return nil, err 526 } 527 528 // Compare installer protocol version with the first protocol listed of the earliest match 529 // [A, B] where A is assumed the earliest compatible major version of the protocol pair 530 if protocolVersion.NewerThan(earliestVersionProtocol) { 531 // Provider protocols are too old, the closest version is the earliest compatible version 532 return earliest, nil 533 } 534 535 // Provider protocols are too new, the closest version is the latest compatible version 536 return latest, nil 537 } 538 539 func (i *ProviderInstaller) checkPluginProtocol(versionMeta *response.TerraformProviderVersion) error { 540 // TODO: should this be a different error? We should probably differentiate between 541 // no compatible versions and no protocol versions listed at all 542 if len(versionMeta.Protocols) == 0 { 543 return fmt.Errorf("no plugin protocol versions listed") 544 } 545 546 protoString := strconv.Itoa(int(i.PluginProtocolVersion)) 547 protocolVersion, err := VersionStr(protoString).Parse() 548 if err != nil { 549 return fmt.Errorf("invalid plugin protocol version: %q", i.PluginProtocolVersion) 550 } 551 protocolConstraint, err := protocolVersion.MinorUpgradeConstraintStr().Parse() 552 if err != nil { 553 // This should not fail if the preceding function succeeded. 554 return fmt.Errorf("invalid plugin protocol version: %q", protocolVersion.String()) 555 } 556 557 for _, p := range versionMeta.Protocols { 558 proPro, err := VersionStr(p).Parse() 559 if err != nil { 560 // invalid protocol reported by the registry. Move along. 561 log.Printf("[WARN] invalid provider protocol version %q found in the registry", versionMeta.Version) 562 continue 563 } 564 // success! 565 if protocolConstraint.Allows(proPro) { 566 return nil 567 } 568 } 569 570 return ErrorNoVersionCompatible 571 } 572 573 // REVIEWER QUESTION (again): this ends up swallowing a bunch of errors from 574 // checkPluginProtocol. Do they need to be percolated up better, or would 575 // debug messages would suffice in these situations? 576 func (i *ProviderInstaller) findPlatformCompatibleVersion(versions []*response.TerraformProviderVersion) (*response.TerraformProviderVersion, error) { 577 for _, version := range versions { 578 if err := i.checkPlatformCompatibility(version); err == nil { 579 return version, nil 580 } 581 } 582 583 return nil, ErrorNoVersionCompatibleWithPlatform 584 } 585 586 // platformCompatibleVersions returns a list of provider versions that are 587 // compatible with the requested platform. 588 func (i *ProviderInstaller) platformCompatibleVersions(versions []*response.TerraformProviderVersion) []*response.TerraformProviderVersion { 589 var v []*response.TerraformProviderVersion 590 for _, version := range versions { 591 if err := i.checkPlatformCompatibility(version); err == nil { 592 v = append(v, version) 593 } 594 } 595 return v 596 } 597 598 func (i *ProviderInstaller) checkPlatformCompatibility(versionMeta *response.TerraformProviderVersion) error { 599 if len(versionMeta.Platforms) == 0 { 600 return fmt.Errorf("no supported provider platforms listed") 601 } 602 for _, p := range versionMeta.Platforms { 603 if p.Arch == i.Arch && p.OS == i.OS { 604 return nil 605 } 606 } 607 return fmt.Errorf("version %s does not support the requested platform %s_%s", versionMeta.Version, i.OS, i.Arch) 608 } 609 610 // take the list of available versions for a plugin, and filter out those that 611 // don't fit the constraints. 612 func allowedVersions(available *response.TerraformProviderVersions, required Constraints) []*response.TerraformProviderVersion { 613 var allowed []*response.TerraformProviderVersion 614 615 for _, v := range available.Versions { 616 version, err := VersionStr(v.Version).Parse() 617 if err != nil { 618 log.Printf("[WARN] invalid version found for %q: %s", available.ID, err) 619 continue 620 } 621 if required.Allows(version) { 622 allowed = append(allowed, v) 623 } 624 } 625 return allowed 626 } 627 628 func checksumForFile(sums []byte, name string) string { 629 for _, line := range strings.Split(string(sums), "\n") { 630 parts := strings.Fields(line) 631 if len(parts) > 1 && parts[1] == name { 632 return parts[0] 633 } 634 } 635 return "" 636 } 637 638 func getFile(url string) ([]byte, error) { 639 resp, err := httpClient.Get(url) 640 if err != nil { 641 return nil, err 642 } 643 defer resp.Body.Close() 644 645 if resp.StatusCode != http.StatusOK { 646 return nil, fmt.Errorf("%s", resp.Status) 647 } 648 649 data, err := ioutil.ReadAll(resp.Body) 650 if err != nil { 651 return data, err 652 } 653 return data, nil 654 } 655 656 // providerProtocolTooOld is a message sent to the CLI UI if the provider's 657 // supported protocol versions are too old for the user's version of terraform, 658 // but an older version of the provider is compatible. 659 const providerProtocolTooOld = ` 660 [reset][bold][red]Provider %q v%s is not compatible with Terraform %s.[reset][red] 661 662 Provider version %s is the earliest compatible version. Select it with 663 the following version constraint: 664 665 version = %q 666 667 Terraform checked all of the plugin versions matching the given constraint: 668 %s 669 670 Consult the documentation for this provider for more information on 671 compatibility between provider and Terraform versions. 672 ` 673 674 // providerProtocolTooNew is a message sent to the CLI UI if the provider's 675 // supported protocol versions are too new for the user's version of terraform, 676 // and the user could either upgrade terraform or choose an older version of the 677 // provider 678 const providerProtocolTooNew = ` 679 [reset][bold][red]Provider %q v%s is not compatible with Terraform %s.[reset][red] 680 681 Provider version %s is the latest compatible version. Select it with 682 the following constraint: 683 684 version = %q 685 686 Terraform checked all of the plugin versions matching the given constraint: 687 %s 688 689 Consult the documentation for this provider for more information on 690 compatibility between provider and Terraform versions. 691 692 Alternatively, upgrade to the latest version of Terraform for compatibility with newer provider releases. 693 `