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  `