plugin/discovery: removing deprecated functions
This commit is contained in:
parent
ce5e66e178
commit
f83d5866fe
|
@ -10,12 +10,9 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
|
||||
getter "github.com/hashicorp/go-getter"
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
|
||||
|
@ -27,19 +24,10 @@ import (
|
|||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
// Releases are located by parsing the html listing from releases.hashicorp.com.
|
||||
//
|
||||
// The URL for releases follows the pattern:
|
||||
// https://releases.hashicorp.com/terraform-provider-name/<x.y.z>/terraform-provider-name_<x.y.z>_<os>_<arch>.<ext>
|
||||
//
|
||||
// The plugin protocol version will be saved with the release and returned in
|
||||
// the header X-TERRAFORM_PROTOCOL_VERSION.
|
||||
// Releases are located by querying the terraform registry.
|
||||
|
||||
const protocolVersionHeader = "x-terraform-protocol-version"
|
||||
|
||||
//var releaseHost = "https://releases.hashicorp.com"
|
||||
var releaseHost = "https://tf-registry-staging.herokuapp.com"
|
||||
|
||||
var httpClient *http.Client
|
||||
|
||||
var errVersionNotFound = errors.New("version not found")
|
||||
|
@ -117,7 +105,7 @@ type ProviderInstaller struct {
|
|||
// be presented alongside context about what is being installed, and thus the
|
||||
// error messages do not redundantly include such information.
|
||||
func (i *ProviderInstaller) Get(provider string, req Constraints) (PluginMeta, error) {
|
||||
// a little bit of initialization
|
||||
// a little bit of initialization.
|
||||
if i.OS == "" {
|
||||
i.OS = runtime.GOOS
|
||||
}
|
||||
|
@ -125,14 +113,15 @@ func (i *ProviderInstaller) Get(provider string, req Constraints) (PluginMeta, e
|
|||
i.Arch = runtime.GOARCH
|
||||
}
|
||||
if i.registry == nil {
|
||||
i.registry = registry.NewClient(i.Services, nil, nil)
|
||||
i.registry = registry.NewClient(i.Services, nil)
|
||||
}
|
||||
|
||||
// get a full listing of versions for the requested provider
|
||||
allVersions, err := i.listProviderVersions(provider)
|
||||
|
||||
// TODO: return multiple errors
|
||||
if err != nil {
|
||||
return PluginMeta{}, err
|
||||
return PluginMeta{}, ErrorNoSuchProvider
|
||||
}
|
||||
if len(allVersions.Versions) == 0 {
|
||||
return PluginMeta{}, ErrorNoSuitableVersion
|
||||
|
@ -144,71 +133,39 @@ func (i *ProviderInstaller) Get(provider string, req Constraints) (PluginMeta, e
|
|||
return PluginMeta{}, ErrorNoSuitableVersion
|
||||
}
|
||||
|
||||
// sort them newest to oldest
|
||||
sort.Sort(response.Collection(versions))
|
||||
// the winning version is the newest
|
||||
// sort them newest to oldest. The newest version wins!
|
||||
response.Collection(versions).Sort()
|
||||
versionMeta := versions[0]
|
||||
// get a Version from the version string
|
||||
// we already know this will not error from the preceding functions
|
||||
v, _ := VersionStr(versionMeta.Version).Parse()
|
||||
v := VersionStr(versionMeta.Version).MustParse()
|
||||
|
||||
// Ensure that our installation directory exists
|
||||
err = os.MkdirAll(i.Dir, os.ModePerm)
|
||||
if err != nil {
|
||||
return PluginMeta{}, fmt.Errorf("failed to create plugin dir %s: %s", i.Dir, err)
|
||||
// check platform compatibility
|
||||
if err := i.checkPlatformCompatibility(versionMeta); err != nil {
|
||||
// filter the list of versions to those that support the requested OS_ARCH
|
||||
// reset the "current" versionMeta
|
||||
// versionMeta = filteredVersions[0]
|
||||
return PluginMeta{}, ErrorNoVersionCompatible
|
||||
}
|
||||
|
||||
// check plugin protocol compatibility
|
||||
// We only validate the most recent version that meets the version constraints.
|
||||
// see RFC TF-055: Provider Protocol Versioning for more information
|
||||
protoString := strconv.Itoa(int(i.PluginProtocolVersion))
|
||||
protocolVersion, err := VersionStr(protoString).Parse()
|
||||
if err != nil {
|
||||
return PluginMeta{}, fmt.Errorf("invalid plugin protocol version: %q", i.PluginProtocolVersion)
|
||||
}
|
||||
protocolConstraint, err := protocolVersion.MinorUpgradeConstraintStr().Parse()
|
||||
if err != nil {
|
||||
// This should not fail if the preceding function succeeded.
|
||||
return PluginMeta{}, fmt.Errorf("invalid plugin protocol version: %q", protocolVersion.String())
|
||||
}
|
||||
|
||||
for _, p := range versionMeta.Protocols {
|
||||
proPro, err := VersionStr(p).Parse()
|
||||
if err != nil {
|
||||
// invalid protocol reported by the registry. Move along.
|
||||
log.Printf("[WARN] invalid provider protocol version %q found in the registry", provider, versionMeta.Version)
|
||||
continue
|
||||
}
|
||||
if !protocolConstraint.Allows(proPro) {
|
||||
// TODO: get most recent compatible plugin and return a handy-dandy string for the user
|
||||
// latest, err := getNewestCompatiblePlugin
|
||||
// i.Ui.output|info): "the latest version of plugin BLAH which supports protocol BLAH is BLAH"
|
||||
// Add this to your provider block:
|
||||
// version = ~BLAH
|
||||
// and if none is found, return ErrorNoVersionCompatible
|
||||
return PluginMeta{}, fmt.Errorf("The latest version of plugin %q does not support plugin protocol version %q", provider, protocolVersion)
|
||||
}
|
||||
}
|
||||
|
||||
var downloadURLs *response.TerraformProviderPlatformLocation
|
||||
// check plugin platform compatibility
|
||||
for _, p := range versionMeta.Platforms {
|
||||
if p.Arch == i.Arch && p.OS == i.OS {
|
||||
downloadURLs, err = i.listProviderDownloadURLs(provider, versionMeta.Version)
|
||||
if err != nil {
|
||||
return PluginMeta{}, fmt.Errorf("Problem getting ")
|
||||
// check protocol compatibility
|
||||
if err := i.checkPluginProtocol(versionMeta); err != nil {
|
||||
closestMatch, err := i.findProtocolCompatibleVersion(versions)
|
||||
if err == nil {
|
||||
if err := i.checkPlatformCompatibility(closestMatch); err != nil {
|
||||
// This is where we give up instead of leap-frogging every version to check protocol & platform
|
||||
return PluginMeta{}, ErrorNoSuitableVersion
|
||||
}
|
||||
break
|
||||
// This is a placeholder message.
|
||||
i.Ui.Error(fmt.Sprintf("the most recent version of %s to match your platform is %s", provider, closestMatch))
|
||||
return PluginMeta{}, ErrorNoVersionCompatible
|
||||
}
|
||||
// TODO: return the most recent compatible versions
|
||||
// return PluginMeta{}, ErrorNoVersionCompatibleWithPlatform
|
||||
return PluginMeta{}, fmt.Errorf("The latest version of plugin %q does not support the requested platform %s %s", provider, i.OS, i.Arch)
|
||||
return PluginMeta{}, ErrorNoVersionCompatibleWithPlatform
|
||||
}
|
||||
|
||||
downloadURLs, err := i.listProviderDownloadURLs(provider, versionMeta.Version)
|
||||
providerURL := downloadURLs.DownloadURL
|
||||
|
||||
if !i.SkipVerify {
|
||||
sha256, err := i.getProviderChecksum(provider, downloadURLs)
|
||||
sha256, err := i.getProviderChecksum(downloadURLs)
|
||||
if err != nil {
|
||||
return PluginMeta{}, err
|
||||
}
|
||||
|
@ -283,7 +240,7 @@ func (i *ProviderInstaller) install(provider string, version Version, url string
|
|||
}
|
||||
|
||||
// Link or copy the cached binary into our install dir so the
|
||||
// normal resolution machinery can find it.
|
||||
// normal resolution machinery can find it.
|
||||
filename := filepath.Base(cached)
|
||||
targetPath := filepath.Join(i.Dir, filename)
|
||||
|
||||
|
@ -351,7 +308,6 @@ func (i *ProviderInstaller) install(provider string, version Version, url string
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -387,44 +343,7 @@ func (i *ProviderInstaller) PurgeUnused(used map[string]PluginMeta) (PluginMetaS
|
|||
return removed, errs
|
||||
}
|
||||
|
||||
// Plugins are referred to by the short name, but all URLs and files will use
|
||||
// the full name prefixed with terraform-<plugin_type>-
|
||||
func (i *ProviderInstaller) providerName(name string) string {
|
||||
return "terraform-provider-" + name
|
||||
}
|
||||
|
||||
func (i *ProviderInstaller) providerFileName(name, version string) string {
|
||||
os := i.OS
|
||||
arch := i.Arch
|
||||
if os == "" {
|
||||
os = runtime.GOOS
|
||||
}
|
||||
if arch == "" {
|
||||
arch = runtime.GOARCH
|
||||
}
|
||||
return fmt.Sprintf("%s_%s_%s_%s.zip", i.providerName(name), version, os, arch)
|
||||
}
|
||||
|
||||
// providerVersionsURL returns the path to the released versions directory for the provider:
|
||||
// https://releases.hashicorp.com/terraform-provider-name/
|
||||
func (i *ProviderInstaller) providerVersionsURL(name string) string {
|
||||
return releaseHost + "/" + i.providerName(name) + "/"
|
||||
}
|
||||
|
||||
// providerURL returns the full path to the provider file, using the current OS
|
||||
// and ARCH:
|
||||
// .../terraform-provider-name_<x.y.z>/terraform-provider-name_<x.y.z>_<os>_<arch>.<ext>
|
||||
func (i *ProviderInstaller) providerURL(name, version string) string {
|
||||
return fmt.Sprintf("%s%s/%s", i.providerVersionsURL(name), version, i.providerFileName(name, version))
|
||||
}
|
||||
|
||||
func (i *ProviderInstaller) providerChecksumURL(name, version string) string {
|
||||
fileName := fmt.Sprintf("%s_%s_SHA256SUMS", i.providerName(name), version)
|
||||
u := fmt.Sprintf("%s%s/%s", i.providerVersionsURL(name), version, fileName)
|
||||
return u
|
||||
}
|
||||
|
||||
func (i *ProviderInstaller) getProviderChecksum(name string, urls *response.TerraformProviderPlatformLocation) (string, error) {
|
||||
func (i *ProviderInstaller) getProviderChecksum(urls *response.TerraformProviderPlatformLocation) (string, error) {
|
||||
checksums, err := getPluginSHA256SUMs(urls.ShasumsURL, urls.ShasumsSignatureURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -440,6 +359,86 @@ func (i *ProviderInstaller) listProviderVersions(name string) (*response.Terrafo
|
|||
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
|
||||
}
|
||||
|
||||
// REVIEWER QUESTION: this ends up swallowing a bunch of errors from
|
||||
// checkPluginProtocol. Do they need to be percolated up better, or would
|
||||
// debug messages would suffice in these situations?
|
||||
func (i *ProviderInstaller) findProtocolCompatibleVersion(versions []*response.TerraformProviderVersion) (*response.TerraformProviderVersion, error) {
|
||||
for _, version := range versions {
|
||||
if err := i.checkPluginProtocol(version); err == nil {
|
||||
return version, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrorNoVersionCompatible
|
||||
}
|
||||
|
||||
func (i *ProviderInstaller) checkPluginProtocol(versionMeta *response.TerraformProviderVersion) error {
|
||||
// TODO: should this be a different error? We should probably differentiate between
|
||||
// no compatible versions and no protocol versions listed at all
|
||||
// No protocols at all!
|
||||
if len(versionMeta.Protocols) == 0 {
|
||||
return fmt.Errorf("no plugin protocol versions listed")
|
||||
}
|
||||
|
||||
protoString := strconv.Itoa(int(i.PluginProtocolVersion))
|
||||
protocolVersion, err := VersionStr(protoString).Parse()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid plugin protocol version: %q", i.PluginProtocolVersion)
|
||||
}
|
||||
protocolConstraint, err := protocolVersion.MinorUpgradeConstraintStr().Parse()
|
||||
if err != nil {
|
||||
// This should not fail if the preceding function succeeded.
|
||||
return fmt.Errorf("invalid plugin protocol version: %q", protocolVersion.String())
|
||||
}
|
||||
|
||||
for _, p := range versionMeta.Protocols {
|
||||
proPro, err := VersionStr(p).Parse()
|
||||
if err != nil {
|
||||
// invalid protocol reported by the registry. Move along.
|
||||
log.Printf("[WARN] invalid provider protocol version %q found in the registry", versionMeta.Version)
|
||||
continue
|
||||
}
|
||||
// success!
|
||||
if protocolConstraint.Allows(proPro) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return ErrorNoVersionCompatible
|
||||
}
|
||||
|
||||
// REVIEWER QUESTION (again): this ends up swallowing a bunch of errors from
|
||||
// checkPluginProtocol. Do they need to be percolated up better, or would
|
||||
// debug messages would suffice in these situations?
|
||||
func (i *ProviderInstaller) findPlatformCompatibleVersion(versions []*response.TerraformProviderVersion) (*response.TerraformProviderVersion, error) {
|
||||
for _, version := range versions {
|
||||
if err := i.checkPlatformCompatibility(version); err == nil {
|
||||
return version, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrorNoVersionCompatibleWithPlatform
|
||||
}
|
||||
|
||||
func (i *ProviderInstaller) checkPlatformCompatibility(versionMeta *response.TerraformProviderVersion) error {
|
||||
if len(versionMeta.Platforms) == 0 {
|
||||
return fmt.Errorf("no supported provider platforms listed")
|
||||
}
|
||||
for _, p := range versionMeta.Platforms {
|
||||
if p.Arch == i.Arch && p.OS == i.OS {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("version %s does not support the requested platform %s_%s", versionMeta.Version, i.OS, i.Arch)
|
||||
}
|
||||
|
||||
// take the list of available versions for a plugin, and filter out those that
|
||||
// don't fit the constraints.
|
||||
func allowedVersions(available *response.TerraformProviderVersions, required Constraints) []*response.TerraformProviderVersion {
|
||||
|
@ -455,83 +454,9 @@ func allowedVersions(available *response.TerraformProviderVersions, required Con
|
|||
allowed = append(allowed, v)
|
||||
}
|
||||
}
|
||||
|
||||
return allowed
|
||||
}
|
||||
|
||||
// return a list of the plugin versions at the given URL
|
||||
func listPluginVersions(url string) ([]Version, error) {
|
||||
resp, err := httpClient.Get(url)
|
||||
if err != nil {
|
||||
// http library produces a verbose error message that includes the
|
||||
// URL being accessed, etc.
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
log.Printf("[ERROR] failed to fetch plugin versions from %s\n%s\n%s", url, resp.Status, body)
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusNotFound, http.StatusForbidden:
|
||||
// These are treated as indicative of the given name not being
|
||||
// a valid provider name at all.
|
||||
return nil, ErrorNoSuchProvider
|
||||
|
||||
default:
|
||||
// All other errors are assumed to be operational problems.
|
||||
return nil, fmt.Errorf("error accessing %s: %s", url, resp.Status)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
body, err := html.Parse(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
names := []string{}
|
||||
|
||||
// all we need to do is list links on the directory listing page that look like plugins
|
||||
var f func(*html.Node)
|
||||
f = func(n *html.Node) {
|
||||
if n.Type == html.ElementNode && n.Data == "a" {
|
||||
c := n.FirstChild
|
||||
if c != nil && c.Type == html.TextNode && strings.HasPrefix(c.Data, "terraform-") {
|
||||
names = append(names, c.Data)
|
||||
return
|
||||
}
|
||||
}
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
f(c)
|
||||
}
|
||||
}
|
||||
f(body)
|
||||
|
||||
return versionsFromNames(names), nil
|
||||
}
|
||||
|
||||
// parse the list of directory names into a sorted list of available versions
|
||||
func versionsFromNames(names []string) []Version {
|
||||
var versions []Version
|
||||
for _, name := range names {
|
||||
parts := strings.SplitN(name, "_", 2)
|
||||
if len(parts) == 2 && parts[1] != "" {
|
||||
v, err := VersionStr(parts[1]).Parse()
|
||||
if err != nil {
|
||||
// filter invalid versions scraped from the page
|
||||
log.Printf("[WARN] invalid version found for %q: %s", name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
versions = append(versions, v)
|
||||
}
|
||||
}
|
||||
|
||||
return versions
|
||||
}
|
||||
|
||||
func checksumForFile(sums []byte, name string) string {
|
||||
for _, line := range strings.Split(string(sums), "\n") {
|
||||
parts := strings.Fields(line)
|
||||
|
@ -579,11 +504,27 @@ func getFile(url string) ([]byte, error) {
|
|||
return data, nil
|
||||
}
|
||||
|
||||
func GetReleaseHost() string {
|
||||
return releaseHost
|
||||
}
|
||||
// ProviderProtocolTooOld is a message sent to the CLI UI if the provider's
|
||||
// supported protocol versions are too old for the user's version of terraform,
|
||||
// but an older version of the provider is compatible.
|
||||
const providerProtocolTooOld = `Provider %q v%s is not compatible with Terraform %s.
|
||||
|
||||
func (i *ProviderInstaller) listProviderDownloadURLs(name, version string) (*response.TerraformProviderPlatformLocation, error) {
|
||||
urls, err := i.registry.TerraformProviderLocation(regsrc.NewTerraformProvider(name, i.OS, i.Arch), version)
|
||||
return urls, err
|
||||
}
|
||||
Provider version %s is the earliest compatible version.
|
||||
Select it with the following version constraint:
|
||||
|
||||
version = %q
|
||||
`
|
||||
|
||||
// ProviderProtocolTooNew is a message sent to the CLI UI if the provider's
|
||||
// supported protocol versions are too new for the user's version of terraform,
|
||||
// and the user could either upgrade terraform or choose an older version of the
|
||||
// provider
|
||||
const providerProtocolTooNew = `Provider %q v%s is not compatible with Terraform %s.
|
||||
|
||||
Provider version v%s is the latest compatible version. Select
|
||||
it with the following constraint:
|
||||
|
||||
version = %q
|
||||
|
||||
Alternatively, upgrade to the latest version of Terraform for compatibility with newer provider releases.
|
||||
`
|
||||
|
|
|
@ -2,30 +2,80 @@ package discovery
|
|||
|
||||
import (
|
||||
"archive/zip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/registry"
|
||||
"github.com/hashicorp/terraform/registry/response"
|
||||
"github.com/hashicorp/terraform/svchost"
|
||||
"github.com/hashicorp/terraform/svchost/disco"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
const testProviderFile = "test provider binary"
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
server := testReleaseServer()
|
||||
l, err := net.Listen("tcp", "127.0.0.1:8080")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// NewUnstartedServer creates a listener. Close that listener and replace
|
||||
// with the one we created.
|
||||
server.Listener.Close()
|
||||
server.Listener = l
|
||||
server.Start()
|
||||
defer server.Close()
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
// return the directory listing for the "test" provider
|
||||
func testListingHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(versionList))
|
||||
parts := strings.Split(r.URL.Path, "/")
|
||||
if len(parts) != 6 {
|
||||
http.Error(w, "not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
provider := parts[4]
|
||||
if provider == "test" {
|
||||
js, err := json.Marshal(versionList)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Write(js)
|
||||
}
|
||||
http.Error(w, ErrorNoSuchProvider.Error(), http.StatusNotFound)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// return the download URLs for the "test" provider
|
||||
func testDownloadHandler(w http.ResponseWriter, r *http.Request) {
|
||||
js, err := json.Marshal(downloadURLs)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Write(js)
|
||||
}
|
||||
|
||||
func testChecksumHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// this exact plugin has a signnature and checksum file
|
||||
// this exact plugin has a signature and checksum file
|
||||
if r.URL.Path == "/terraform-provider-template/0.1.0/terraform-provider-template_0.1.0_SHA256SUMS" {
|
||||
http.ServeFile(w, r, "testdata/terraform-provider-template_0.1.0_SHA256SUMS")
|
||||
return
|
||||
|
@ -51,32 +101,25 @@ func testChecksumHandler(w http.ResponseWriter, r *http.Request) {
|
|||
// returns a 200 for a valid provider url, using the patch number for the
|
||||
// plugin protocol version.
|
||||
func testHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/terraform-provider-test/" {
|
||||
if strings.HasSuffix(r.URL.Path, "/versions") {
|
||||
testListingHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(r.URL.Path, "/download") {
|
||||
testDownloadHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.Split(r.URL.Path, "/")
|
||||
if len(parts) != 4 {
|
||||
if len(parts) != 7 {
|
||||
http.Error(w, "not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
filename := parts[3]
|
||||
|
||||
reg := regexp.MustCompile(`(terraform-provider-test)_(\d).(\d).(\d)_([^_]+)_([^._]+).zip`)
|
||||
|
||||
fileParts := reg.FindStringSubmatch(filename)
|
||||
if len(fileParts) != 7 {
|
||||
http.Error(w, "invalid provider: "+filename, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set(protocolVersionHeader, fileParts[4])
|
||||
|
||||
// write a dummy file
|
||||
z := zip.NewWriter(w)
|
||||
fn := fmt.Sprintf("%s_v%s.%s.%s_x%s", fileParts[1], fileParts[2], fileParts[3], fileParts[4], fileParts[4])
|
||||
fn := fmt.Sprintf("%s_v%s", parts[4], parts[5])
|
||||
f, err := z.Create(fn)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -87,33 +130,42 @@ func testHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func testReleaseServer() *httptest.Server {
|
||||
handler := http.NewServeMux()
|
||||
handler.HandleFunc("/terraform-provider-test/", testHandler)
|
||||
handler.HandleFunc("/v1/providers/terraform-providers/", testHandler)
|
||||
handler.HandleFunc("/terraform-provider-template/", testChecksumHandler)
|
||||
handler.HandleFunc("/terraform-provider-badsig/", testChecksumHandler)
|
||||
handler.HandleFunc("/.well-known/terraform.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
io.WriteString(w, `{"modules.v1":"http://localhost/v1/modules/", "providers.v1":"http://localhost/v1/providers/"}`)
|
||||
})
|
||||
|
||||
return httptest.NewServer(handler)
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
server := testReleaseServer()
|
||||
releaseHost = server.URL
|
||||
|
||||
os.Exit(m.Run())
|
||||
return httptest.NewUnstartedServer(handler)
|
||||
}
|
||||
|
||||
func TestVersionListing(t *testing.T) {
|
||||
i := &ProviderInstaller{}
|
||||
versions, err := i.listProviderVersions("test")
|
||||
server := testReleaseServer()
|
||||
server.Start()
|
||||
defer server.Close()
|
||||
|
||||
i := newProviderInstaller(server)
|
||||
|
||||
allVersions, err := i.listProviderVersions("test")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Versions(versions).Sort()
|
||||
var versions []*response.TerraformProviderVersion
|
||||
|
||||
expected := []string{
|
||||
"1.2.4",
|
||||
"1.2.3",
|
||||
"1.2.1",
|
||||
for _, v := range allVersions.Versions {
|
||||
versions = append(versions, v)
|
||||
}
|
||||
|
||||
response.Collection(versions).Sort()
|
||||
|
||||
expected := []*response.TerraformProviderVersion{
|
||||
{Version: "1.2.4"},
|
||||
{Version: "1.2.3"},
|
||||
{Version: "1.2.1"},
|
||||
}
|
||||
|
||||
if len(versions) != len(expected) {
|
||||
|
@ -121,24 +173,60 @@ func TestVersionListing(t *testing.T) {
|
|||
}
|
||||
|
||||
for i, v := range versions {
|
||||
if v.String() != expected[i] {
|
||||
if v.Version != expected[i].Version {
|
||||
t.Fatalf("incorrect version: %q, expected %q", v, expected[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckProtocolVersions(t *testing.T) {
|
||||
i := &ProviderInstaller{}
|
||||
if checkPlugin(i.providerURL("test", VersionStr("1.2.3").MustParse().String()), 4) {
|
||||
t.Fatal("protocol version 4 is not compatible")
|
||||
tests := []struct {
|
||||
VersionMeta *response.TerraformProviderVersion
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
&response.TerraformProviderVersion{
|
||||
Protocols: []string{"1", "2"},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
&response.TerraformProviderVersion{
|
||||
Protocols: []string{"4"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
&response.TerraformProviderVersion{
|
||||
Protocols: []string{"4.2"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
if !checkPlugin(i.providerURL("test", VersionStr("1.2.3").MustParse().String()), 3) {
|
||||
t.Fatal("protocol version 3 should be compatible")
|
||||
server := testReleaseServer()
|
||||
server.Start()
|
||||
defer server.Close()
|
||||
i := newProviderInstaller(server)
|
||||
|
||||
for _, test := range tests {
|
||||
err := i.checkPluginProtocol(test.VersionMeta)
|
||||
if test.Err {
|
||||
if err == nil {
|
||||
t.Fatal("succeeded; want error")
|
||||
}
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderInstallerGet(t *testing.T) {
|
||||
server := testReleaseServer()
|
||||
server.Start()
|
||||
defer server.Close()
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", "tf-plugin")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -152,17 +240,20 @@ func TestProviderInstallerGet(t *testing.T) {
|
|||
PluginProtocolVersion: 5,
|
||||
SkipVerify: true,
|
||||
Ui: cli.NewMockUi(),
|
||||
registry: registry.NewClient(Disco(server), nil),
|
||||
}
|
||||
_, err = i.Get("test", AllVersions)
|
||||
if err != ErrorNoVersionCompatible {
|
||||
|
||||
if err != ErrorNoVersionCompatibleWithPlatform {
|
||||
t.Fatal("want error for incompatible version")
|
||||
}
|
||||
|
||||
i = &ProviderInstaller{
|
||||
Dir: tmpDir,
|
||||
PluginProtocolVersion: 3,
|
||||
PluginProtocolVersion: 4,
|
||||
SkipVerify: true,
|
||||
Ui: cli.NewMockUi(),
|
||||
registry: registry.NewClient(Disco(server), nil),
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -184,12 +275,12 @@ func TestProviderInstallerGet(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// we should have version 1.2.3
|
||||
dest := filepath.Join(tmpDir, "terraform-provider-test_v1.2.3_x3")
|
||||
// we should have version 1.2.4
|
||||
dest := filepath.Join(tmpDir, "terraform-provider-test_v1.2.4")
|
||||
|
||||
wantMeta := PluginMeta{
|
||||
Name: "test",
|
||||
Version: VersionStr("1.2.3"),
|
||||
Version: VersionStr("1.2.4"),
|
||||
Path: dest,
|
||||
}
|
||||
if !reflect.DeepEqual(gotMeta, wantMeta) {
|
||||
|
@ -209,6 +300,9 @@ func TestProviderInstallerGet(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestProviderInstallerPurgeUnused(t *testing.T) {
|
||||
server := testReleaseServer()
|
||||
defer server.Close()
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", "tf-plugin")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -235,6 +329,7 @@ func TestProviderInstallerPurgeUnused(t *testing.T) {
|
|||
PluginProtocolVersion: 3,
|
||||
SkipVerify: true,
|
||||
Ui: cli.NewMockUi(),
|
||||
registry: registry.NewClient(Disco(server), nil),
|
||||
}
|
||||
purged, err := i.PurgeUnused(map[string]PluginMeta{
|
||||
"test": PluginMeta{
|
||||
|
@ -272,66 +367,102 @@ func TestProviderInstallerPurgeUnused(t *testing.T) {
|
|||
|
||||
// Test fetching a provider's checksum file while verifying its signature.
|
||||
func TestProviderChecksum(t *testing.T) {
|
||||
i := &ProviderInstaller{}
|
||||
|
||||
// we only need the checksum, as getter is doing the actual file comparison.
|
||||
sha256sum, err := i.getProviderChecksum("template", "0.1.0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
tests := []struct {
|
||||
URLs *response.TerraformProviderPlatformLocation
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
&response.TerraformProviderPlatformLocation{
|
||||
ShasumsURL: "http://127.0.0.1:8080/terraform-provider-template/0.1.0/terraform-provider-template_0.1.0_SHA256SUMS",
|
||||
ShasumsSignatureURL: "http://127.0.0.1:8080/terraform-provider-template/0.1.0/terraform-provider-template_0.1.0_SHA256SUMS.sig",
|
||||
Filename: "terraform-provider-template_0.1.0_darwin_amd64.zip",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
&response.TerraformProviderPlatformLocation{
|
||||
ShasumsURL: "http://127.0.0.1:8080/terraform-provider-badsig/0.1.0/terraform-provider-badsig_0.1.0_SHA256SUMS",
|
||||
ShasumsSignatureURL: "http://127.0.0.1:8080/terraform-provider-badsig/0.1.0/terraform-provider-badsig_0.1.0_SHA256SUMS.sig",
|
||||
Filename: "terraform-provider-template_0.1.0_darwin_amd64.zip",
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
// get the expected checksum for our os/arch
|
||||
sumData, err := ioutil.ReadFile("testdata/terraform-provider-template_0.1.0_SHA256SUMS")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
i := ProviderInstaller{}
|
||||
|
||||
expected := checksumForFile(sumData, i.providerFileName("template", "0.1.0"))
|
||||
for _, test := range tests {
|
||||
sha256sum, err := i.getProviderChecksum(test.URLs)
|
||||
if test.Err {
|
||||
if err == nil {
|
||||
t.Fatal("succeeded; want error")
|
||||
}
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if sha256sum != expected {
|
||||
t.Fatalf("expected: %s\ngot %s\n", sha256sum, expected)
|
||||
// get the expected checksum for our os/arch
|
||||
sumData, err := ioutil.ReadFile("testdata/terraform-provider-template_0.1.0_SHA256SUMS")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := checksumForFile(sumData, test.URLs.Filename)
|
||||
|
||||
if sha256sum != expected {
|
||||
t.Fatalf("expected: %s\ngot %s\n", sha256sum, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test fetching a provider's checksum file witha bad signature
|
||||
func TestProviderChecksumBadSignature(t *testing.T) {
|
||||
i := &ProviderInstaller{}
|
||||
|
||||
// we only need the checksum, as getter is doing the actual file comparison.
|
||||
sha256sum, err := i.getProviderChecksum("badsig", "0.1.0")
|
||||
if err == nil {
|
||||
t.Fatal("expcted error")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "signature") {
|
||||
t.Fatal("expected signature error, got:", err)
|
||||
}
|
||||
|
||||
if sha256sum != "" {
|
||||
t.Fatal("expected no checksum, got:", sha256sum)
|
||||
// newProviderInstaller returns a minimally-initialized ProviderInstaller
|
||||
func newProviderInstaller(s *httptest.Server) ProviderInstaller {
|
||||
return ProviderInstaller{
|
||||
registry: registry.NewClient(Disco(s), nil),
|
||||
OS: runtime.GOOS,
|
||||
Arch: runtime.GOARCH,
|
||||
}
|
||||
}
|
||||
|
||||
const versionList = `<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="../">../</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/terraform-provider-test/1.2.3/">terraform-provider-test_1.2.3</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/terraform-provider-test/1.2.1/">terraform-provider-test_1.2.1</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/terraform-provider-test/1.2.4/">terraform-provider-test_1.2.4</a>
|
||||
</li>
|
||||
</ul>
|
||||
<footer>
|
||||
Proudly fronted by <a href="https://fastly.com/?utm_source=hashicorp" target="_TOP">Fastly</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
// Disco return a *disco.Disco mapping registry.terraform.io, localhost,
|
||||
// localhost.localdomain, and example.com to the test server.
|
||||
func Disco(s *httptest.Server) *disco.Disco {
|
||||
services := map[string]interface{}{
|
||||
// Note that both with and without trailing slashes are supported behaviours
|
||||
"modules.v1": fmt.Sprintf("%s/v1/modules", s.URL),
|
||||
"providers.v1": fmt.Sprintf("%s/v1/providers", s.URL),
|
||||
}
|
||||
d := disco.New()
|
||||
|
||||
d.ForceHostServices(svchost.Hostname("registry.terraform.io"), services)
|
||||
d.ForceHostServices(svchost.Hostname("localhost"), services)
|
||||
d.ForceHostServices(svchost.Hostname("localhost.localdomain"), services)
|
||||
d.ForceHostServices(svchost.Hostname("example.com"), services)
|
||||
return d
|
||||
}
|
||||
|
||||
var versionList = response.TerraformProvider{
|
||||
ID: "test",
|
||||
Versions: []*response.TerraformProviderVersion{
|
||||
{Version: "1.2.1"},
|
||||
{Version: "1.2.3"},
|
||||
{
|
||||
Version: "1.2.4",
|
||||
Protocols: []string{"4"},
|
||||
Platforms: []*response.TerraformProviderPlatform{
|
||||
{
|
||||
OS: "darwin",
|
||||
Arch: "amd64",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var downloadURLs = response.TerraformProviderPlatformLocation{
|
||||
ShasumsURL: "https://registry.terraform.io/terraform-provider-template/1.2.4/terraform-provider-test_1.2.4_SHA256SUMS",
|
||||
ShasumsSignatureURL: "https://registry.terraform.io/terraform-provider-template/1.2.4/terraform-provider-test_1.2.4_SHA256SUMS.sig",
|
||||
Filename: "terraform-provider-template_1.2.4_darwin_amd64.zip",
|
||||
DownloadURL: "http://127.0.0.1:8080/v1/providers/terraform-providers/terraform-provider-test/1.2.4/terraform-provider-test_1.2.4_darwin_amd64.zip",
|
||||
}
|
||||
|
|
|
@ -4,5 +4,5 @@ package regsrc
|
|||
|
||||
var (
|
||||
// PublicRegistryHost is a FriendlyHost that represents the public registry.
|
||||
PublicRegistryHost = NewFriendlyHost("tf-registry-staging.herokuapp.com")
|
||||
PublicRegistryHost = NewFriendlyHost("registry.terraform.io")
|
||||
)
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
package response
|
||||
|
||||
import version "github.com/hashicorp/go-version"
|
||||
import (
|
||||
"sort"
|
||||
|
||||
version "github.com/hashicorp/go-version"
|
||||
)
|
||||
|
||||
// TerraformProvider is the response structure for all required information for
|
||||
// Terraform to choose a download URL. It must include all versions and all
|
||||
|
@ -48,21 +52,15 @@ type TerraformProviderPlatformLocation struct {
|
|||
ShasumsSignatureURL string `json:"shasums_signature_url"`
|
||||
}
|
||||
|
||||
// Collection type implements the sort.Sort interface so that
|
||||
// an array of TerraformProviderVersion can be sorted.
|
||||
// Collection type for TerraformProviderVersion
|
||||
type Collection []*TerraformProviderVersion
|
||||
|
||||
func (v Collection) Len() int {
|
||||
return len(v)
|
||||
}
|
||||
// Sort sorts versions from newest to oldest.
|
||||
func (v Collection) Sort() {
|
||||
sort.Slice(v, func(i, j int) bool {
|
||||
versionA, _ := version.NewVersion(v[i].Version)
|
||||
versionB, _ := version.NewVersion(v[j].Version)
|
||||
|
||||
func (v Collection) Less(i, j int) bool {
|
||||
versionA, _ := version.NewVersion(v[i].Version)
|
||||
versionB, _ := version.NewVersion(v[j].Version)
|
||||
|
||||
return versionA.LessThan(versionB)
|
||||
}
|
||||
|
||||
func (v Collection) Swap(i, j int) {
|
||||
v[i], v[j] = v[j], v[i]
|
||||
return versionA.GreaterThan(versionB)
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue