internal: Fix LookupLegacyProvider

When looking up the namespace for a legacy provider source, we need to
use the /v1/providers/-/{name}/versions endpoint. For non-HashiCorp
providers, the /v1/providers/-/{name} endpoint returns a 404.

This commit updates the LegacyProviderDefaultNamespace method and the
mock registry servers accordingly.
This commit is contained in:
Alisdair McDiarmid 2020-05-08 11:40:55 -04:00
parent f82700bc56
commit 62b0cbed12
4 changed files with 70 additions and 31 deletions

View File

@ -1,6 +1,7 @@
package command
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
@ -361,21 +362,22 @@ func fakeRegistryHandler(resp http.ResponseWriter, req *http.Request) {
pathParts := strings.Split(path, "/")[3:]
if len(pathParts) != 2 {
if len(pathParts) != 3 {
resp.WriteHeader(404)
resp.Write([]byte(`unrecognized path scheme`))
return
}
if pathParts[0] != "-" {
if pathParts[0] != "-" || pathParts[2] != "versions" {
resp.WriteHeader(404)
resp.Write([]byte(`this registry only supports legacy namespace lookup requests`))
}
if namespace, ok := legacyProviderNamespaces[pathParts[1]]; ok {
name := pathParts[1]
if namespace, ok := legacyProviderNamespaces[name]; ok {
resp.Header().Set("Content-Type", "application/json")
resp.WriteHeader(200)
resp.Write([]byte(`{"namespace":"` + namespace + `"}`))
resp.Write([]byte(fmt.Sprintf(`{"id":"%s/%s"}`, namespace, name)))
} else {
resp.WriteHeader(404)
resp.Write([]byte(`provider not found`))

View File

@ -1,6 +1,7 @@
package getproviders
import (
"strings"
"testing"
"github.com/hashicorp/terraform/addrs"
@ -27,3 +28,37 @@ func TestLookupLegacyProvider(t *testing.T) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, want)
}
}
func TestLookupLegacyProvider_invalidResponse(t *testing.T) {
source, _, close := testRegistrySource(t)
defer close()
got, err := LookupLegacyProvider(
addrs.NewLegacyProvider("invalid"),
source,
)
if !got.IsZero() {
t.Errorf("got non-zero addr\ngot: %#v\nwant: %#v", got, nil)
}
wantErr := "Error parsing provider ID from Registry: Invalid provider source string"
if gotErr := err.Error(); !strings.Contains(gotErr, wantErr) {
t.Fatalf("unexpected error: got %q, want %q", gotErr, wantErr)
}
}
func TestLookupLegacyProvider_unexpectedTypeChange(t *testing.T) {
source, _, close := testRegistrySource(t)
defer close()
got, err := LookupLegacyProvider(
addrs.NewLegacyProvider("changetype"),
source,
)
if !got.IsZero() {
t.Errorf("got non-zero addr\ngot: %#v\nwant: %#v", got, nil)
}
wantErr := `Registry returned provider with type "newtype", expected "changetype"`
if gotErr := err.Error(); gotErr != wantErr {
t.Fatalf("unexpected error: got %q, want %q", gotErr, wantErr)
}
}

View File

@ -292,7 +292,7 @@ func (c *registryClient) PackageMeta(provider addrs.Provider, version Version, t
return ret, nil
}
// LegacyProviderCanonicalAddress returns the raw address strings produced by
// LegacyProviderDefaultNamespace returns the raw address strings produced by
// the registry when asked about the given unqualified provider type name.
// The returned namespace string is taken verbatim from the registry's response.
//
@ -300,7 +300,7 @@ func (c *registryClient) PackageMeta(provider addrs.Provider, version Version, t
// in older configurations. New configurations should be written so as not to
// depend on it.
func (c *registryClient) LegacyProviderDefaultNamespace(typeName string) (string, error) {
endpointPath, err := url.Parse(path.Join("-", typeName))
endpointPath, err := url.Parse(path.Join("-", typeName, "versions"))
if err != nil {
// Should never happen because we're constructing this from
// already-validated components.
@ -338,7 +338,7 @@ func (c *registryClient) LegacyProviderDefaultNamespace(typeName string) (string
}
type ResponseBody struct {
Namespace string
Id string
}
var body ResponseBody
@ -347,7 +347,16 @@ func (c *registryClient) LegacyProviderDefaultNamespace(typeName string) (string
return "", c.errQueryFailed(placeholderProviderAddr, err)
}
return body.Namespace, nil
provider, diags := addrs.ParseProviderSourceString(body.Id)
if diags.HasErrors() {
return "", fmt.Errorf("Error parsing provider ID from Registry: %s", diags.Err())
}
if provider.Type != typeName {
return "", fmt.Errorf("Registry returned provider with type %q, expected %q", provider.Type, typeName)
}
return provider.Namespace, nil
}
func (c *registryClient) addHeadersToRequest(req *http.Request) {

View File

@ -114,34 +114,12 @@ func fakeRegistryHandler(resp http.ResponseWriter, req *http.Request) {
}
pathParts := strings.Split(path, "/")[3:]
if len(pathParts) < 2 {
resp.WriteHeader(404)
resp.Write([]byte(`unexpected number of path parts`))
return
}
log.Printf("[TRACE] fake provider registry request for %#v", pathParts)
if len(pathParts) == 2 {
switch pathParts[0] + "/" + pathParts[1] {
case "-/legacy":
// NOTE: This legacy lookup endpoint is specific to
// registry.terraform.io and not expected to work on any other
// registry host.
resp.Header().Set("Content-Type", "application/json")
resp.WriteHeader(200)
resp.Write([]byte(`{"namespace":"legacycorp"}`))
default:
resp.WriteHeader(404)
resp.Write([]byte(`unknown namespace or provider type for direct lookup`))
}
}
if len(pathParts) < 3 {
resp.WriteHeader(404)
resp.Write([]byte(`unexpected number of path parts`))
return
}
log.Printf("[TRACE] fake provider registry request for %#v", pathParts)
if pathParts[2] == "versions" {
if len(pathParts) != 3 {
@ -162,6 +140,21 @@ func fakeRegistryHandler(resp http.ResponseWriter, req *http.Request) {
resp.Header().Set("Content-Type", "application/json")
resp.WriteHeader(200)
resp.Write([]byte(`{"versions":[]}`))
case "-/legacy":
resp.Header().Set("Content-Type", "application/json")
resp.WriteHeader(200)
// This response is used for testing LookupLegacyProvider
resp.Write([]byte(`{"id":"legacycorp/legacy"}`))
case "-/changetype":
resp.Header().Set("Content-Type", "application/json")
resp.WriteHeader(200)
// This (unrealistic) response is used for error handling code coverage
resp.Write([]byte(`{"id":"legacycorp/newtype"}`))
case "-/invalid":
resp.Header().Set("Content-Type", "application/json")
resp.WriteHeader(200)
// This (unrealistic) response is used for error handling code coverage
resp.Write([]byte(`{"id":"some/invalid/id/string"}`))
default:
resp.WriteHeader(404)
resp.Write([]byte(`unknown namespace or provider type`))