func (i *ProviderInstaller) findClosestProtocolCompatibleVersion(versions []*response.TerraformProviderVersion) (*response.TerraformProviderVersion, error) {
	var latest, earliest *response.TerraformProviderVersion
	for _, version := range versions {
		v, err := VersionStr(version.Version).Parse()
		if err != nil || v.IsPrerelease() {
			continue
		}
		if err := i.checkPluginProtocol(version); err == nil {
			if earliest == nil {
				earliest = version
			}
			latest = version
		}
	}
	if earliest == nil {
		return nil, ErrorNoVersionCompatible
	}
	protoString := strconv.Itoa(int(i.PluginProtocolVersion))
	protocolVersion, err := VersionStr(protoString).Parse()
	if err != nil {
		return nil, fmt.Errorf("invalid plugin protocol version: %q", i.PluginProtocolVersion)
	}
	earliestVersionProtocol, err := VersionStr(earliest.Protocols[0]).Parse()
	if err != nil {
		return nil, err
	}
	if protocolVersion.NewerThan(earliestVersionProtocol) {
		return earliest, nil
	}
	return latest, nil
}
findClosestProtocolCompatibleVersion searches for the provider version with the closest protocol match.
Prerelease versions are filtered.
package discovery
import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"github.com/hashicorp/errwrap"
	getter "github.com/hashicorp/go-getter"
	multierror "github.com/hashicorp/go-multierror"
	"github.com/hashicorp/terraform-svchost/disco"
	"github.com/hashicorp/terraform/addrs"
	"github.com/hashicorp/terraform/httpclient"
	"github.com/hashicorp/terraform/registry"
	"github.com/hashicorp/terraform/registry/regsrc"
	"github.com/hashicorp/terraform/registry/response"
	"github.com/hashicorp/terraform/tfdiags"
	tfversion "github.com/hashicorp/terraform/version"
	"github.com/mitchellh/cli"
)
const protocolVersionHeader = "x-terraform-protocol-version"
var httpClient *http.Client
var errVersionNotFound = errors.New("version not found")
func init() {
	httpClient = httpclient.New()
	httpGetter := &getter.HttpGetter{
		Client: httpClient,
		Netrc:  true,
	}
	getter.Getters["http"] = httpGetter
	getter.Getters["https"] = httpGetter
}
type Installer interface {
	Get(provider addrs.Provider, req Constraints) (PluginMeta, tfdiags.Diagnostics, error)
	PurgeUnused(used map[string]PluginMeta) (removed PluginMetaSet, err error)
}
type ProviderInstaller struct {
	Dir string
	Cache PluginCache
	PluginProtocolVersion uint
	OS   string
	Arch string
	SkipVerify bool
	Ui cli.Ui  
	Services *disco.Disco
	registry *registry.Client
}
func (i *ProviderInstaller) Get(provider addrs.Provider, req Constraints) (PluginMeta, tfdiags.Diagnostics, error) {
	var diags tfdiags.Diagnostics
	if i.OS == "" {
		i.OS = runtime.GOOS
	}
	if i.Arch == "" {
		i.Arch = runtime.GOARCH
	}
	if i.registry == nil {
		i.registry = registry.NewClient(i.Services, nil)
	}
	allVersions, err := i.listProviderVersions(provider)
	if err != nil {
		log.Printf("[DEBUG] %s", err)
		if registry.IsServiceUnreachable(err) {
			registryHost, err := i.hostname()
			if err == nil && registryHost == regsrc.PublicRegistryHost.Raw {
				return PluginMeta{}, diags, ErrorPublicRegistryUnreachable
			}
			return PluginMeta{}, diags, ErrorServiceUnreachable
		}
		if registry.IsServiceNotProvided(err) {
			return PluginMeta{}, diags, err
		}
		return PluginMeta{}, diags, ErrorNoSuchProvider
	}
	for _, warning := range allVersions.Warnings {
		hostname, err := i.hostname()
		if err != nil {
			return PluginMeta{}, diags, err
		}
		diag := tfdiags.SimpleWarning(fmt.Sprintf("%s: %s", hostname, warning))
		diags = diags.Append(diag)
	}
	if len(allVersions.Versions) == 0 {
		return PluginMeta{}, diags, ErrorNoSuitableVersion
	}
	providerSource := allVersions.ID
	versions := allowedVersions(allVersions, req)
	if len(versions) == 0 {
		return PluginMeta{}, diags, ErrorNoSuitableVersion
	}
	response.ProviderVersionCollection(versions).Sort()
	if err := i.checkPlatformCompatibility(versions[0]); err != nil {
		versions = i.platformCompatibleVersions(versions)
		if len(versions) == 0 {
			return PluginMeta{}, diags, ErrorNoVersionCompatibleWithPlatform
		}
	}
	versionMeta := versions[0]
	v := VersionStr(versionMeta.Version).MustParse()
	if err := i.checkPluginProtocol(versionMeta); err != nil {
		closestMatch, err := i.findClosestProtocolCompatibleVersion(allVersions.Versions)
		if err != nil {
			return PluginMeta{}, diags, err
		}
		var errMsg string
		closestVersion := VersionStr(closestMatch.Version).MustParse()
		if v.NewerThan(closestVersion) {
			errMsg = providerProtocolTooNew
		} else {
			errMsg = providerProtocolTooOld
		}
		constraintStr := req.String()
		if constraintStr == "" {
			constraintStr = "(any version)"
		}
		return PluginMeta{}, diags, errwrap.Wrap(ErrorVersionIncompatible, fmt.Errorf(fmt.Sprintf(
			errMsg, provider.LegacyString(), v.String(), tfversion.String(),
			closestVersion.String(), closestVersion.MinorUpgradeConstraintStr(), constraintStr)))
	}
	downloadURLs, err := i.listProviderDownloadURLs(providerSource, versionMeta.Version)
	if err != nil {
		return PluginMeta{}, diags, err
	}
	providerURL := downloadURLs.DownloadURL
	if !i.SkipVerify {
		sha256, err := i.getProviderChecksum(downloadURLs)
		if err != nil {
			return PluginMeta{}, diags, err
		}
		if sha256 != "" {
			providerURL = providerURL + "?checksum=sha256:" + sha256
		}
	}
	printedProviderName := fmt.Sprintf("%q (%s)", provider.LegacyString(), providerSource)
	i.Ui.Info(fmt.Sprintf("- Downloading plugin for provider %s %s...", printedProviderName, versionMeta.Version))
	log.Printf("[DEBUG] getting provider %s version %q", printedProviderName, versionMeta.Version)
	err = i.install(provider, v, providerURL)
	if err != nil {
		return PluginMeta{}, diags, err
	}
	log.Printf("[DEBUG] looking for the %s %s plugin we just installed", provider.LegacyString(), versionMeta.Version)
	metas := FindPlugins("provider", []string{i.Dir})
	log.Printf("[DEBUG] all plugins found %#v", metas)
	metas, _ = metas.ValidateVersions()
	metas = metas.WithName(provider.Type).WithVersion(v)
	log.Printf("[DEBUG] filtered plugins %#v", metas)
	if metas.Count() == 0 {
		return PluginMeta{}, diags, fmt.Errorf(
			"failed to find installed plugin version %s; this is a bug in Terraform and should be reported",
			versionMeta.Version,
		)
	}
	if metas.Count() > 1 {
		return PluginMeta{}, diags, fmt.Errorf(
			"multiple plugins installed for version %s; this is a bug in Terraform and should be reported",
			versionMeta.Version,
		)
	}
	return metas.Newest(), diags, nil
}
func (i *ProviderInstaller) install(provider addrs.Provider, version Version, url string) error {
	if i.Cache != nil {
		log.Printf("[DEBUG] looking for provider %s %s in plugin cache", provider.LegacyString(), version)
		cached := i.Cache.CachedPluginPath("provider", provider.Type, version)
		if cached == "" {
			log.Printf("[DEBUG] %s %s not yet in cache, so downloading %s", provider.LegacyString(), version, url)
			err := getter.Get(i.Cache.InstallDir(), url)
			if err != nil {
				return err
			}
			cached = i.Cache.CachedPluginPath("provider", provider.Type, version)
			if cached == "" {
				return fmt.Errorf("failed to find downloaded plugin in cache %s", i.Cache.InstallDir())
			}
		}
		filename := filepath.Base(cached)
		targetPath := filepath.Join(i.Dir, filename)
		var err error
		if _, StatErr := os.Stat(i.Dir); os.IsNotExist(StatErr) {
			err = os.MkdirAll(i.Dir, 0700)
		}
		if err != nil {
			return err
		}
		log.Printf("[DEBUG] installing %s %s to %s from local cache %s", provider.LegacyString(), version, targetPath, cached)
		os.Remove(targetPath)
		linkErr := errors.New("link not supported for Windows")  
		if runtime.GOOS != "windows" {
			linkErr = os.Link(cached, targetPath)
			if linkErr != nil {
				linkErr = os.Symlink(cached, targetPath)
			}
		}
		if linkErr != nil {
			srcFile, err := os.Open(cached)
			if err != nil {
				return fmt.Errorf("failed to open cached plugin %s: %s", cached, err)
			}
			defer srcFile.Close()
			destFile, err := os.OpenFile(targetPath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, os.ModePerm)
			if err != nil {
				return fmt.Errorf("failed to create %s: %s", targetPath, err)
			}
			_, err = io.Copy(destFile, srcFile)
			if err != nil {
				destFile.Close()
				return fmt.Errorf("failed to copy cached plugin from %s to %s: %s", cached, targetPath, err)
			}
			err = destFile.Close()
			if err != nil {
				return fmt.Errorf("error creating %s: %s", targetPath, err)
			}
		}
	} else {
		log.Printf("[DEBUG] plugin cache is disabled, so downloading %s %s from %s", provider.LegacyString(), version, url)
		err := getter.Get(i.Dir, url)
		if err != nil {
			return err
		}
	}
	return nil
}
func (i *ProviderInstaller) PurgeUnused(used map[string]PluginMeta) (PluginMetaSet, error) {
	purge := make(PluginMetaSet)
	present := FindPlugins("provider", []string{i.Dir})
	for meta := range present {
		chosen, ok := used[meta.Name]
		if !ok {
			purge.Add(meta)
		}
		if chosen.Path != meta.Path {
			purge.Add(meta)
		}
	}
	removed := make(PluginMetaSet)
	var errs error
	for meta := range purge {
		path := meta.Path
		err := os.Remove(path)
		if err != nil {
			errs = multierror.Append(errs, fmt.Errorf(
				"failed to remove unused provider plugin %s: %s",
				path, err,
			))
		} else {
			removed.Add(meta)
		}
	}
	return removed, errs
}
func (i *ProviderInstaller) getProviderChecksum(resp *response.TerraformProviderPlatformLocation) (string, error) {
	shasums, err := getFile(resp.ShasumsURL)
	if err != nil {
		log.Printf("[ERROR] error fetching checksums from %q: %s", resp.ShasumsURL, err)
		return "", ErrorMissingChecksumVerification
	}
	signature, err := getFile(resp.ShasumsSignatureURL)
	if err != nil {
		log.Printf("[ERROR] error fetching checksums signature from %q: %s", resp.ShasumsSignatureURL, err)
		return "", ErrorSignatureVerification
	}
	asciiArmor := resp.SigningKeys.GPGASCIIArmor()
	signer, err := verifySig(shasums, signature, asciiArmor)
	if err != nil {
		log.Printf("[ERROR] error verifying signature: %s", err)
		return "", ErrorSignatureVerification
	}
	_, err = verifySig(shasums, signature, HashicorpPublicKey)
	if err != nil {
		log.Printf("[ERROR] error verifying signature against HashiCorp public key: %s", err)
		return "", ErrorSignatureVerification
	}
	identities := []string{}
	for k := range signer.Identities {
		identities = append(identities, k)
	}
	identity := strings.Join(identities, ", ")
	log.Printf("[DEBUG] verified GPG signature with key from %s", identity)
	checksum := checksumForFile(shasums, resp.Filename)
	if checksum == "" {
		log.Printf("[ERROR] missing checksum for %s from source %s", resp.Filename, resp.ShasumsURL)
		return "", ErrorMissingChecksumVerification
	} else if checksum != resp.Shasum {
		log.Printf("[ERROR] unexpected checksum for %s from source %q", resp.Filename, resp.ShasumsURL)
		return "", ErrorChecksumVerification
	}
	return checksum, nil
}
func (i *ProviderInstaller) hostname() (string, error) {
	provider := regsrc.NewTerraformProvider("", i.OS, i.Arch)
	svchost, err := provider.SvcHost()
	if err != nil {
		return "", err
	}
	return svchost.ForDisplay(), nil
}
func (i *ProviderInstaller) listProviderVersions(provider addrs.Provider) (*response.TerraformProviderVersions, error) {
	req := regsrc.NewTerraformProvider(provider.Type, i.OS, i.Arch)
	versions, err := i.registry.TerraformProviderVersions(req)
	return versions, err
}
func (i *ProviderInstaller) listProviderDownloadURLs(name, version string) (*response.TerraformProviderPlatformLocation, error) {
	urls, err := i.registry.TerraformProviderLocation(regsrc.NewTerraformProvider(name, i.OS, i.Arch), version)
	if urls == nil {
		return nil, fmt.Errorf("No download urls found for provider %s", name)
	}
	return urls, err
}