Use the new registry.Client
The registry code has been moved into the new registry package. Remove the duplicated code and use the new registry and registry/test packages.
This commit is contained in:
parent
91bd72f22b
commit
0e7dab09e6
|
@ -1,335 +0,0 @@
|
|||
package module
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
version "github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/terraform/registry/regsrc"
|
||||
"github.com/hashicorp/terraform/registry/response"
|
||||
)
|
||||
|
||||
// Map of module names and location of test modules.
|
||||
// Only one version for now, as we only lookup latest from the registry.
|
||||
type testMod struct {
|
||||
location string
|
||||
version string
|
||||
}
|
||||
|
||||
const (
|
||||
testCredentials = "test-auth-token"
|
||||
)
|
||||
|
||||
// All the locationes from the mockRegistry start with a file:// scheme. If
|
||||
// the the location string here doesn't have a scheme, the mockRegistry will
|
||||
// find the absolute path and return a complete URL.
|
||||
var testMods = map[string][]testMod{
|
||||
"registry/foo/bar": {{
|
||||
location: "file:///download/registry/foo/bar/0.2.3//*?archive=tar.gz",
|
||||
version: "0.2.3",
|
||||
}},
|
||||
"registry/foo/baz": {{
|
||||
location: "file:///download/registry/foo/baz/1.10.0//*?archive=tar.gz",
|
||||
version: "1.10.0",
|
||||
}},
|
||||
"registry/local/sub": {{
|
||||
location: "test-fixtures/registry-tar-subdir/foo.tgz//*?archive=tar.gz",
|
||||
version: "0.1.2",
|
||||
}},
|
||||
"exists-in-registry/identifier/provider": {{
|
||||
location: "file:///registry/exists",
|
||||
version: "0.2.0",
|
||||
}},
|
||||
"relative/foo/bar": {{ // There is an exception for the "relative/" prefix in the test registry server
|
||||
location: "/relative-path",
|
||||
version: "0.2.0",
|
||||
}},
|
||||
"test-versions/name/provider": {
|
||||
{version: "2.2.0"},
|
||||
{version: "2.1.1"},
|
||||
{version: "1.2.2"},
|
||||
{version: "1.2.1"},
|
||||
},
|
||||
"private/name/provider": {
|
||||
{version: "1.0.0"},
|
||||
},
|
||||
}
|
||||
|
||||
func latestVersion(versions []string) string {
|
||||
var col version.Collection
|
||||
for _, v := range versions {
|
||||
ver, err := version.NewVersion(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
col = append(col, ver)
|
||||
}
|
||||
|
||||
sort.Sort(col)
|
||||
return col[len(col)-1].String()
|
||||
}
|
||||
|
||||
func mockRegHandler() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
download := func(w http.ResponseWriter, r *http.Request) {
|
||||
p := strings.TrimLeft(r.URL.Path, "/")
|
||||
// handle download request
|
||||
re := regexp.MustCompile(`^([-a-z]+/\w+/\w+).*/download$`)
|
||||
// download lookup
|
||||
matches := re.FindStringSubmatch(p)
|
||||
if len(matches) != 2 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// check for auth
|
||||
if strings.Contains(matches[0], "private/") {
|
||||
if !strings.Contains(r.Header.Get("Authorization"), testCredentials) {
|
||||
http.Error(w, "", http.StatusForbidden)
|
||||
}
|
||||
}
|
||||
|
||||
versions, ok := testMods[matches[1]]
|
||||
if !ok {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
mod := versions[0]
|
||||
|
||||
location := mod.location
|
||||
if !strings.HasPrefix(matches[0], "relative/") && !strings.HasPrefix(location, "file:///") {
|
||||
// we can't use filepath.Abs because it will clean `//`
|
||||
wd, _ := os.Getwd()
|
||||
location = fmt.Sprintf("file://%s/%s", wd, location)
|
||||
}
|
||||
|
||||
w.Header().Set("X-Terraform-Get", location)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
// no body
|
||||
return
|
||||
}
|
||||
|
||||
versions := func(w http.ResponseWriter, r *http.Request) {
|
||||
p := strings.TrimLeft(r.URL.Path, "/")
|
||||
re := regexp.MustCompile(`^([-a-z]+/\w+/\w+)/versions$`)
|
||||
matches := re.FindStringSubmatch(p)
|
||||
if len(matches) != 2 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// check for auth
|
||||
if strings.Contains(matches[1], "private/") {
|
||||
if !strings.Contains(r.Header.Get("Authorization"), testCredentials) {
|
||||
http.Error(w, "", http.StatusForbidden)
|
||||
}
|
||||
}
|
||||
|
||||
name := matches[1]
|
||||
versions, ok := testMods[name]
|
||||
if !ok {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// only adding the single requested module for now
|
||||
// this is the minimal that any regisry is epected to support
|
||||
mpvs := &response.ModuleProviderVersions{
|
||||
Source: name,
|
||||
}
|
||||
|
||||
for _, v := range versions {
|
||||
mv := &response.ModuleVersion{
|
||||
Version: v.version,
|
||||
}
|
||||
mpvs.Versions = append(mpvs.Versions, mv)
|
||||
}
|
||||
|
||||
resp := response.ModuleVersions{
|
||||
Modules: []*response.ModuleProviderVersions{mpvs},
|
||||
}
|
||||
|
||||
js, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(js)
|
||||
}
|
||||
|
||||
mux.Handle("/v1/modules/",
|
||||
http.StripPrefix("/v1/modules/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasSuffix(r.URL.Path, "/download") {
|
||||
download(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasSuffix(r.URL.Path, "/versions") {
|
||||
versions(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
http.NotFound(w, r)
|
||||
})),
|
||||
)
|
||||
|
||||
mux.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/"}`)
|
||||
})
|
||||
return mux
|
||||
}
|
||||
|
||||
// Just enough like a registry to exercise our code.
|
||||
// Returns the location of the latest version
|
||||
func mockRegistry() *httptest.Server {
|
||||
server := httptest.NewServer(mockRegHandler())
|
||||
return server
|
||||
}
|
||||
|
||||
// GitHub archives always contain the module source in a single subdirectory,
|
||||
// so the registry will return a path with with a `//*` suffix. We need to make
|
||||
// sure this doesn't intefere with our internal handling of `//` subdir.
|
||||
func TestRegistryGitHubArchive(t *testing.T) {
|
||||
server := mockRegistry()
|
||||
defer server.Close()
|
||||
|
||||
disco := testDisco(server)
|
||||
storage := testStorage(t, disco)
|
||||
|
||||
tree := NewTree("", testConfig(t, "registry-tar-subdir"))
|
||||
|
||||
storage.Mode = GetModeGet
|
||||
if err := tree.Load(storage); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !tree.Loaded() {
|
||||
t.Fatal("should be loaded")
|
||||
}
|
||||
|
||||
storage.Mode = GetModeNone
|
||||
if err := tree.Load(storage); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// stop the registry server, and make sure that we don't need to call out again
|
||||
server.Close()
|
||||
tree = NewTree("", testConfig(t, "registry-tar-subdir"))
|
||||
|
||||
storage.Mode = GetModeGet
|
||||
if err := tree.Load(storage); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !tree.Loaded() {
|
||||
t.Fatal("should be loaded")
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(tree.String())
|
||||
expected := strings.TrimSpace(treeLoadSubdirStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("got: \n\n%s\nexpected: \n\n%s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that the //subdir notation can be used with registry modules
|
||||
func TestRegisryModuleSubdir(t *testing.T) {
|
||||
server := mockRegistry()
|
||||
defer server.Close()
|
||||
|
||||
disco := testDisco(server)
|
||||
storage := testStorage(t, disco)
|
||||
tree := NewTree("", testConfig(t, "registry-subdir"))
|
||||
|
||||
storage.Mode = GetModeGet
|
||||
if err := tree.Load(storage); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !tree.Loaded() {
|
||||
t.Fatal("should be loaded")
|
||||
}
|
||||
|
||||
storage.Mode = GetModeNone
|
||||
if err := tree.Load(storage); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(tree.String())
|
||||
expected := strings.TrimSpace(treeLoadRegistrySubdirStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("got: \n\n%s\nexpected: \n\n%s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccRegistryDiscover(t *testing.T) {
|
||||
if os.Getenv("TF_ACC") == "" {
|
||||
t.Skip("skipping ACC test")
|
||||
}
|
||||
|
||||
// simply check that we get a valid github URL for this from the registry
|
||||
module, err := regsrc.ParseModuleSource("hashicorp/consul/aws")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s := NewStorage("/tmp", nil, nil)
|
||||
loc, err := s.lookupModuleLocation(module, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
u, err := url.Parse(loc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(u.Host, "github.com") {
|
||||
t.Fatalf("expected host 'github.com', got: %q", u.Host)
|
||||
}
|
||||
|
||||
if !strings.Contains(u.String(), "consul") {
|
||||
t.Fatalf("url doesn't contain 'consul': %s", u.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccRegistryLoad(t *testing.T) {
|
||||
if os.Getenv("TF_ACC") == "" {
|
||||
t.Skip("skipping ACC test")
|
||||
}
|
||||
|
||||
storage := testStorage(t, nil)
|
||||
tree := NewTree("", testConfig(t, "registry-load"))
|
||||
|
||||
storage.Mode = GetModeGet
|
||||
if err := tree.Load(storage); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !tree.Loaded() {
|
||||
t.Fatal("should be loaded")
|
||||
}
|
||||
|
||||
storage.Mode = GetModeNone
|
||||
if err := tree.Load(storage); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// TODO expand this further by fetching some metadata from the registry
|
||||
actual := strings.TrimSpace(tree.String())
|
||||
if !strings.Contains(actual, "(path: vault)") {
|
||||
t.Fatal("missing vault module, got:\n", actual)
|
||||
}
|
||||
}
|
|
@ -1,16 +1,13 @@
|
|||
package module
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/svchost"
|
||||
"github.com/hashicorp/terraform/svchost/disco"
|
||||
)
|
||||
|
||||
|
@ -49,20 +46,3 @@ func testStorage(t *testing.T, d *disco.Disco) *Storage {
|
|||
t.Helper()
|
||||
return NewStorage(tempDir(t), d, nil)
|
||||
}
|
||||
|
||||
// test discovery maps registry.terraform.io, localhost, localhost.localdomain,
|
||||
// and example.com to the test server.
|
||||
func testDisco(s *httptest.Server) *disco.Disco {
|
||||
services := map[string]interface{}{
|
||||
// Note that both with and without trailing slashes are supported behaviours
|
||||
// TODO: add specific tests to enumerate both possibilities.
|
||||
"modules.v1": fmt.Sprintf("%s/v1/modules", s.URL),
|
||||
}
|
||||
d := disco.NewDisco()
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -1,218 +0,0 @@
|
|||
package module
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
cleanhttp "github.com/hashicorp/go-cleanhttp"
|
||||
|
||||
"github.com/hashicorp/terraform/registry/regsrc"
|
||||
"github.com/hashicorp/terraform/registry/response"
|
||||
"github.com/hashicorp/terraform/svchost"
|
||||
"github.com/hashicorp/terraform/version"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultRegistry = "registry.terraform.io"
|
||||
registryServiceID = "registry.v1"
|
||||
xTerraformGet = "X-Terraform-Get"
|
||||
xTerraformVersion = "X-Terraform-Version"
|
||||
requestTimeout = 10 * time.Second
|
||||
serviceID = "modules.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
httpClient *http.Client
|
||||
tfVersion = version.String()
|
||||
)
|
||||
|
||||
func init() {
|
||||
httpClient = cleanhttp.DefaultPooledClient()
|
||||
httpClient.Timeout = requestTimeout
|
||||
}
|
||||
|
||||
type errModuleNotFound string
|
||||
|
||||
func (e errModuleNotFound) Error() string {
|
||||
return `module "` + string(e) + `" not found`
|
||||
}
|
||||
|
||||
func (s *Storage) discoverRegURL(host svchost.Hostname) *url.URL {
|
||||
regURL := s.Services.DiscoverServiceURL(host, serviceID)
|
||||
if regURL == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(regURL.Path, "/") {
|
||||
regURL.Path += "/"
|
||||
}
|
||||
|
||||
return regURL
|
||||
}
|
||||
|
||||
func (s *Storage) addRequestCreds(host svchost.Hostname, req *http.Request) {
|
||||
if s.Creds == nil {
|
||||
return
|
||||
}
|
||||
|
||||
creds, err := s.Creds.ForHost(host)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Failed to get credentials for %s: %s (ignoring)", host, err)
|
||||
return
|
||||
}
|
||||
|
||||
if creds != nil {
|
||||
creds.PrepareRequest(req)
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup module versions in the registry.
|
||||
func (s *Storage) lookupModuleVersions(module *regsrc.Module) (*response.ModuleVersions, error) {
|
||||
host, err := module.SvcHost()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
service := s.discoverRegURL(host)
|
||||
if service == nil {
|
||||
return nil, fmt.Errorf("host %s does not provide Terraform modules", host)
|
||||
}
|
||||
|
||||
p, err := url.Parse(path.Join(module.Module(), "versions"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
service = service.ResolveReference(p)
|
||||
|
||||
log.Printf("[DEBUG] fetching module versions from %q", service)
|
||||
|
||||
req, err := http.NewRequest("GET", service.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.addRequestCreds(host, req)
|
||||
req.Header.Set(xTerraformVersion, tfVersion)
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
// OK
|
||||
case http.StatusNotFound:
|
||||
return nil, errModuleNotFound(module.String())
|
||||
default:
|
||||
return nil, fmt.Errorf("error looking up module versions: %s", resp.Status)
|
||||
}
|
||||
|
||||
var versions response.ModuleVersions
|
||||
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
if err := dec.Decode(&versions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, mod := range versions.Modules {
|
||||
for _, v := range mod.Versions {
|
||||
log.Printf("[DEBUG] found available version %q for %s", v.Version, mod.Source)
|
||||
}
|
||||
}
|
||||
|
||||
return &versions, nil
|
||||
}
|
||||
|
||||
// lookup the location of a specific module version in the registry
|
||||
func (s *Storage) lookupModuleLocation(module *regsrc.Module, version string) (string, error) {
|
||||
host, err := module.SvcHost()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
service := s.discoverRegURL(host)
|
||||
if service == nil {
|
||||
return "", fmt.Errorf("host %s does not provide Terraform modules", host.ForDisplay())
|
||||
}
|
||||
|
||||
var p *url.URL
|
||||
if version == "" {
|
||||
p, err = url.Parse(path.Join(module.Module(), "download"))
|
||||
} else {
|
||||
p, err = url.Parse(path.Join(module.Module(), version, "download"))
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
download := service.ResolveReference(p)
|
||||
|
||||
log.Printf("[DEBUG] looking up module location from %q", download)
|
||||
|
||||
req, err := http.NewRequest("GET", download.String(), nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
s.addRequestCreds(host, req)
|
||||
req.Header.Set(xTerraformVersion, tfVersion)
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// there should be no body, but save it for logging
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error reading response body from registry: %s", err)
|
||||
}
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK, http.StatusNoContent:
|
||||
// OK
|
||||
case http.StatusNotFound:
|
||||
return "", fmt.Errorf("module %q version %q not found", module, version)
|
||||
default:
|
||||
// anything else is an error:
|
||||
return "", fmt.Errorf("error getting download location for %q: %s resp:%s", module, resp.Status, body)
|
||||
}
|
||||
|
||||
// the download location is in the X-Terraform-Get header
|
||||
location := resp.Header.Get(xTerraformGet)
|
||||
if location == "" {
|
||||
return "", fmt.Errorf("failed to get download URL for %q: %s resp:%s", module, resp.Status, body)
|
||||
}
|
||||
|
||||
// If location looks like it's trying to be a relative URL, treat it as
|
||||
// one.
|
||||
//
|
||||
// We don't do this for just _any_ location, since the X-Terraform-Get
|
||||
// header is a go-getter location rather than a URL, and so not all
|
||||
// possible values will parse reasonably as URLs.)
|
||||
//
|
||||
// When used in conjunction with go-getter we normally require this header
|
||||
// to be an absolute URL, but we are more liberal here because third-party
|
||||
// registry implementations may not "know" their own absolute URLs if
|
||||
// e.g. they are running behind a reverse proxy frontend, or such.
|
||||
if strings.HasPrefix(location, "/") || strings.HasPrefix(location, "./") || strings.HasPrefix(location, "../") {
|
||||
locationURL, err := url.Parse(location)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid relative URL for %q: %s", module, err)
|
||||
}
|
||||
locationURL = download.ResolveReference(locationURL)
|
||||
location = locationURL.String()
|
||||
}
|
||||
|
||||
return location, nil
|
||||
}
|
|
@ -1,194 +0,0 @@
|
|||
package module
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
version "github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/terraform/registry/regsrc"
|
||||
"github.com/hashicorp/terraform/svchost"
|
||||
"github.com/hashicorp/terraform/svchost/auth"
|
||||
"github.com/hashicorp/terraform/svchost/disco"
|
||||
)
|
||||
|
||||
func TestLookupModuleVersions(t *testing.T) {
|
||||
server := mockRegistry()
|
||||
defer server.Close()
|
||||
|
||||
regDisco := testDisco(server)
|
||||
|
||||
// test with and without a hostname
|
||||
for _, src := range []string{
|
||||
"example.com/test-versions/name/provider",
|
||||
"test-versions/name/provider",
|
||||
} {
|
||||
modsrc, err := regsrc.ParseModuleSource(src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s := &Storage{Services: regDisco}
|
||||
resp, err := s.lookupModuleVersions(modsrc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(resp.Modules) != 1 {
|
||||
t.Fatal("expected 1 module, got", len(resp.Modules))
|
||||
}
|
||||
|
||||
mod := resp.Modules[0]
|
||||
name := "test-versions/name/provider"
|
||||
if mod.Source != name {
|
||||
t.Fatalf("expected module name %q, got %q", name, mod.Source)
|
||||
}
|
||||
|
||||
if len(mod.Versions) != 4 {
|
||||
t.Fatal("expected 4 versions, got", len(mod.Versions))
|
||||
}
|
||||
|
||||
for _, v := range mod.Versions {
|
||||
_, err := version.NewVersion(v.Version)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid version %q: %s", v.Version, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistryAuth(t *testing.T) {
|
||||
server := mockRegistry()
|
||||
defer server.Close()
|
||||
|
||||
regDisco := testDisco(server)
|
||||
storage := testStorage(t, regDisco)
|
||||
|
||||
src := "private/name/provider"
|
||||
mod, err := regsrc.ParseModuleSource(src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// both should fail without auth
|
||||
_, err = storage.lookupModuleVersions(mod)
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
_, err = storage.lookupModuleLocation(mod, "1.0.0")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
|
||||
storage.Creds = auth.StaticCredentialsSource(map[svchost.Hostname]map[string]interface{}{
|
||||
svchost.Hostname(defaultRegistry): {"token": testCredentials},
|
||||
})
|
||||
|
||||
_, err = storage.lookupModuleVersions(mod)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = storage.lookupModuleLocation(mod, "1.0.0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestLookupModuleLocationRelative(t *testing.T) {
|
||||
server := mockRegistry()
|
||||
defer server.Close()
|
||||
|
||||
regDisco := testDisco(server)
|
||||
storage := testStorage(t, regDisco)
|
||||
|
||||
src := "relative/foo/bar"
|
||||
mod, err := regsrc.ParseModuleSource(src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, err := storage.lookupModuleLocation(mod, "0.2.0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want := server.URL + "/relative-path"
|
||||
if got != want {
|
||||
t.Errorf("wrong location %s; want %s", got, want)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAccLookupModuleVersions(t *testing.T) {
|
||||
if os.Getenv("TF_ACC") == "" {
|
||||
t.Skip()
|
||||
}
|
||||
regDisco := disco.NewDisco()
|
||||
|
||||
// test with and without a hostname
|
||||
for _, src := range []string{
|
||||
"terraform-aws-modules/vpc/aws",
|
||||
defaultRegistry + "/terraform-aws-modules/vpc/aws",
|
||||
} {
|
||||
modsrc, err := regsrc.ParseModuleSource(src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s := &Storage{
|
||||
Services: regDisco,
|
||||
}
|
||||
resp, err := s.lookupModuleVersions(modsrc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(resp.Modules) != 1 {
|
||||
t.Fatal("expected 1 module, got", len(resp.Modules))
|
||||
}
|
||||
|
||||
mod := resp.Modules[0]
|
||||
name := "terraform-aws-modules/vpc/aws"
|
||||
if mod.Source != name {
|
||||
t.Fatalf("expected module name %q, got %q", name, mod.Source)
|
||||
}
|
||||
|
||||
if len(mod.Versions) == 0 {
|
||||
t.Fatal("expected multiple versions, got 0")
|
||||
}
|
||||
|
||||
for _, v := range mod.Versions {
|
||||
_, err := version.NewVersion(v.Version)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid version %q: %s", v.Version, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the error should reference the config source exatly, not the discovered path.
|
||||
func TestLookupLookupModuleError(t *testing.T) {
|
||||
server := mockRegistry()
|
||||
defer server.Close()
|
||||
|
||||
regDisco := testDisco(server)
|
||||
storage := testStorage(t, regDisco)
|
||||
|
||||
// this should not be found in teh registry
|
||||
src := "bad/local/path"
|
||||
mod, err := regsrc.ParseModuleSource(src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = storage.lookupModuleLocation(mod, "0.2.0")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
|
||||
// check for the exact quoted string to ensure we didn't prepend a hostname.
|
||||
if !strings.Contains(err.Error(), `"bad/local/path"`) {
|
||||
t.Fatal("error should not include the hostname. got:", err)
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
getter "github.com/hashicorp/go-getter"
|
||||
"github.com/hashicorp/terraform/registry"
|
||||
"github.com/hashicorp/terraform/registry/regsrc"
|
||||
"github.com/hashicorp/terraform/svchost/auth"
|
||||
"github.com/hashicorp/terraform/svchost/disco"
|
||||
|
@ -73,20 +74,17 @@ type Storage struct {
|
|||
Ui cli.Ui
|
||||
// Mode is the GetMode that will be used for various operations.
|
||||
Mode GetMode
|
||||
|
||||
registry *registry.Client
|
||||
}
|
||||
|
||||
func NewStorage(dir string, services *disco.Disco, creds auth.CredentialsSource) *Storage {
|
||||
s := &Storage{
|
||||
StorageDir: dir,
|
||||
Services: services,
|
||||
Creds: creds,
|
||||
}
|
||||
regClient := registry.NewClient(services, creds, nil)
|
||||
|
||||
// make sure this isn't nil
|
||||
if s.Services == nil {
|
||||
s.Services = disco.NewDisco()
|
||||
return &Storage{
|
||||
StorageDir: dir,
|
||||
registry: regClient,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// loadManifest returns the moduleManifest file from the parent directory.
|
||||
|
@ -318,7 +316,7 @@ func (s Storage) findRegistryModule(mSource, constraint string) (moduleRecord, e
|
|||
// we need to lookup available versions
|
||||
// Only on Get if it's not found, on unconditionally on Update
|
||||
if (s.Mode == GetModeGet && !found) || (s.Mode == GetModeUpdate) {
|
||||
resp, err := s.lookupModuleVersions(mod)
|
||||
resp, err := s.registry.Versions(mod)
|
||||
if err != nil {
|
||||
return rec, err
|
||||
}
|
||||
|
@ -338,7 +336,7 @@ func (s Storage) findRegistryModule(mSource, constraint string) (moduleRecord, e
|
|||
|
||||
rec.Version = match.Version
|
||||
|
||||
rec.url, err = s.lookupModuleLocation(mod, rec.Version)
|
||||
rec.url, err = s.registry.Location(mod, rec.Version)
|
||||
if err != nil {
|
||||
return rec, err
|
||||
}
|
||||
|
|
|
@ -2,15 +2,20 @@ package module
|
|||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/registry/regsrc"
|
||||
"github.com/hashicorp/terraform/registry/test"
|
||||
)
|
||||
|
||||
func TestGetModule(t *testing.T) {
|
||||
server := mockRegistry()
|
||||
server := test.Registry()
|
||||
defer server.Close()
|
||||
disco := testDisco(server)
|
||||
disco := test.Disco(server)
|
||||
|
||||
td, err := ioutil.TempDir("", "tf")
|
||||
if err != nil {
|
||||
|
@ -19,7 +24,7 @@ func TestGetModule(t *testing.T) {
|
|||
defer os.RemoveAll(td)
|
||||
storage := NewStorage(td, disco, nil)
|
||||
|
||||
// this module exists in a test fixture, and is known by the mockRegistry
|
||||
// this module exists in a test fixture, and is known by the test.Registry
|
||||
// relative to our cwd.
|
||||
err = storage.GetModule(filepath.Join(td, "foo"), "registry/local/sub")
|
||||
if err != nil {
|
||||
|
@ -45,5 +50,140 @@ func TestGetModule(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// GitHub archives always contain the module source in a single subdirectory,
|
||||
// so the registry will return a path with with a `//*` suffix. We need to make
|
||||
// sure this doesn't intefere with our internal handling of `//` subdir.
|
||||
func TestRegistryGitHubArchive(t *testing.T) {
|
||||
server := test.Registry()
|
||||
defer server.Close()
|
||||
|
||||
disco := test.Disco(server)
|
||||
storage := testStorage(t, disco)
|
||||
|
||||
tree := NewTree("", testConfig(t, "registry-tar-subdir"))
|
||||
|
||||
storage.Mode = GetModeGet
|
||||
if err := tree.Load(storage); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !tree.Loaded() {
|
||||
t.Fatal("should be loaded")
|
||||
}
|
||||
|
||||
storage.Mode = GetModeNone
|
||||
if err := tree.Load(storage); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// stop the registry server, and make sure that we don't need to call out again
|
||||
server.Close()
|
||||
tree = NewTree("", testConfig(t, "registry-tar-subdir"))
|
||||
|
||||
storage.Mode = GetModeGet
|
||||
if err := tree.Load(storage); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !tree.Loaded() {
|
||||
t.Fatal("should be loaded")
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(tree.String())
|
||||
expected := strings.TrimSpace(treeLoadSubdirStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("got: \n\n%s\nexpected: \n\n%s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that the //subdir notation can be used with registry modules
|
||||
func TestRegisryModuleSubdir(t *testing.T) {
|
||||
server := test.Registry()
|
||||
defer server.Close()
|
||||
|
||||
disco := test.Disco(server)
|
||||
storage := testStorage(t, disco)
|
||||
tree := NewTree("", testConfig(t, "registry-subdir"))
|
||||
|
||||
storage.Mode = GetModeGet
|
||||
if err := tree.Load(storage); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !tree.Loaded() {
|
||||
t.Fatal("should be loaded")
|
||||
}
|
||||
|
||||
storage.Mode = GetModeNone
|
||||
if err := tree.Load(storage); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(tree.String())
|
||||
expected := strings.TrimSpace(treeLoadRegistrySubdirStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("got: \n\n%s\nexpected: \n\n%s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccRegistryDiscover(t *testing.T) {
|
||||
if os.Getenv("TF_ACC") == "" {
|
||||
t.Skip("skipping ACC test")
|
||||
}
|
||||
|
||||
// simply check that we get a valid github URL for this from the registry
|
||||
module, err := regsrc.ParseModuleSource("hashicorp/consul/aws")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s := NewStorage("/tmp", nil, nil)
|
||||
loc, err := s.registry.Location(module, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
u, err := url.Parse(loc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(u.Host, "github.com") {
|
||||
t.Fatalf("expected host 'github.com', got: %q", u.Host)
|
||||
}
|
||||
|
||||
if !strings.Contains(u.String(), "consul") {
|
||||
t.Fatalf("url doesn't contain 'consul': %s", u.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccRegistryLoad(t *testing.T) {
|
||||
if os.Getenv("TF_ACC") == "" {
|
||||
t.Skip("skipping ACC test")
|
||||
}
|
||||
|
||||
storage := testStorage(t, nil)
|
||||
tree := NewTree("", testConfig(t, "registry-load"))
|
||||
|
||||
storage.Mode = GetModeGet
|
||||
if err := tree.Load(storage); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !tree.Loaded() {
|
||||
t.Fatal("should be loaded")
|
||||
}
|
||||
|
||||
storage.Mode = GetModeNone
|
||||
if err := tree.Load(storage); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// TODO expand this further by fetching some metadata from the registry
|
||||
actual := strings.TrimSpace(tree.String())
|
||||
if !strings.Contains(actual, "(path: vault)") {
|
||||
t.Fatal("missing vault module, got:\n", actual)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue