plugin/discovery: removing deprecated functions

This commit is contained in:
Kristin Laemmert 2018-08-02 14:35:08 -07:00 committed by Martin Atkins
parent ce5e66e178
commit f83d5866fe
4 changed files with 375 additions and 305 deletions

View File

@ -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.
`

View File

@ -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",
}

View File

@ -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")
)

View File

@ -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)
})
}