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