From 0e7dab09e6831c117439ab34215add1d05c19bb3 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Mon, 27 Nov 2017 16:51:26 -0500 Subject: [PATCH] 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. --- config/module/get_test.go | 335 --------------------------------- config/module/module_test.go | 20 -- config/module/registry.go | 218 --------------------- config/module/registry_test.go | 194 ------------------- config/module/storage.go | 20 +- config/module/storage_test.go | 148 ++++++++++++++- 6 files changed, 153 insertions(+), 782 deletions(-) delete mode 100644 config/module/get_test.go delete mode 100644 config/module/registry.go delete mode 100644 config/module/registry_test.go diff --git a/config/module/get_test.go b/config/module/get_test.go deleted file mode 100644 index 0c6ff020f5..0000000000 --- a/config/module/get_test.go +++ /dev/null @@ -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) - } -} diff --git a/config/module/module_test.go b/config/module/module_test.go index 931f97bbbd..62e7ed2a75 100644 --- a/config/module/module_test.go +++ b/config/module/module_test.go @@ -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 -} diff --git a/config/module/registry.go b/config/module/registry.go deleted file mode 100644 index da67c5ab93..0000000000 --- a/config/module/registry.go +++ /dev/null @@ -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 -} diff --git a/config/module/registry_test.go b/config/module/registry_test.go deleted file mode 100644 index dab7444c2f..0000000000 --- a/config/module/registry_test.go +++ /dev/null @@ -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) - } -} diff --git a/config/module/storage.go b/config/module/storage.go index 121719765c..0660812b00 100644 --- a/config/module/storage.go +++ b/config/module/storage.go @@ -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 } diff --git a/config/module/storage_test.go b/config/module/storage_test.go index 6fa1212f8a..10811190e3 100644 --- a/config/module/storage_test.go +++ b/config/module/storage_test.go @@ -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) + } }