Merge pull request #16773 from hashicorp/jbardin/registry
Registry client refactor
This commit is contained in:
commit
120709d0d3
|
@ -1,16 +1,13 @@
|
||||||
package module
|
package module
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http/httptest"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
"github.com/hashicorp/terraform/svchost"
|
|
||||||
"github.com/hashicorp/terraform/svchost/disco"
|
"github.com/hashicorp/terraform/svchost/disco"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -49,20 +46,3 @@ func testStorage(t *testing.T, d *disco.Disco) *Storage {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
return NewStorage(tempDir(t), d, nil)
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
getter "github.com/hashicorp/go-getter"
|
getter "github.com/hashicorp/go-getter"
|
||||||
|
"github.com/hashicorp/terraform/registry"
|
||||||
"github.com/hashicorp/terraform/registry/regsrc"
|
"github.com/hashicorp/terraform/registry/regsrc"
|
||||||
"github.com/hashicorp/terraform/svchost/auth"
|
"github.com/hashicorp/terraform/svchost/auth"
|
||||||
"github.com/hashicorp/terraform/svchost/disco"
|
"github.com/hashicorp/terraform/svchost/disco"
|
||||||
|
@ -73,20 +74,17 @@ type Storage struct {
|
||||||
Ui cli.Ui
|
Ui cli.Ui
|
||||||
// Mode is the GetMode that will be used for various operations.
|
// Mode is the GetMode that will be used for various operations.
|
||||||
Mode GetMode
|
Mode GetMode
|
||||||
|
|
||||||
|
registry *registry.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStorage(dir string, services *disco.Disco, creds auth.CredentialsSource) *Storage {
|
func NewStorage(dir string, services *disco.Disco, creds auth.CredentialsSource) *Storage {
|
||||||
s := &Storage{
|
regClient := registry.NewClient(services, creds, nil)
|
||||||
StorageDir: dir,
|
|
||||||
Services: services,
|
|
||||||
Creds: creds,
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure this isn't nil
|
return &Storage{
|
||||||
if s.Services == nil {
|
StorageDir: dir,
|
||||||
s.Services = disco.NewDisco()
|
registry: regClient,
|
||||||
}
|
}
|
||||||
return s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadManifest returns the moduleManifest file from the parent directory.
|
// 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
|
// we need to lookup available versions
|
||||||
// Only on Get if it's not found, on unconditionally on Update
|
// Only on Get if it's not found, on unconditionally on Update
|
||||||
if (s.Mode == GetModeGet && !found) || (s.Mode == GetModeUpdate) {
|
if (s.Mode == GetModeGet && !found) || (s.Mode == GetModeUpdate) {
|
||||||
resp, err := s.lookupModuleVersions(mod)
|
resp, err := s.registry.Versions(mod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return rec, err
|
return rec, err
|
||||||
}
|
}
|
||||||
|
@ -338,7 +336,7 @@ func (s Storage) findRegistryModule(mSource, constraint string) (moduleRecord, e
|
||||||
|
|
||||||
rec.Version = match.Version
|
rec.Version = match.Version
|
||||||
|
|
||||||
rec.url, err = s.lookupModuleLocation(mod, rec.Version)
|
rec.url, err = s.registry.Location(mod, rec.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return rec, err
|
return rec, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,20 @@ package module
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/registry/regsrc"
|
||||||
|
"github.com/hashicorp/terraform/registry/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetModule(t *testing.T) {
|
func TestGetModule(t *testing.T) {
|
||||||
server := mockRegistry()
|
server := test.Registry()
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
disco := testDisco(server)
|
disco := test.Disco(server)
|
||||||
|
|
||||||
td, err := ioutil.TempDir("", "tf")
|
td, err := ioutil.TempDir("", "tf")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -19,7 +24,7 @@ func TestGetModule(t *testing.T) {
|
||||||
defer os.RemoveAll(td)
|
defer os.RemoveAll(td)
|
||||||
storage := NewStorage(td, disco, nil)
|
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.
|
// relative to our cwd.
|
||||||
err = storage.GetModule(filepath.Join(td, "foo"), "registry/local/sub")
|
err = storage.GetModule(filepath.Join(td, "foo"), "registry/local/sub")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -45,5 +50,140 @@ func TestGetModule(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package module
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -12,75 +12,75 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
cleanhttp "github.com/hashicorp/go-cleanhttp"
|
cleanhttp "github.com/hashicorp/go-cleanhttp"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/registry/regsrc"
|
"github.com/hashicorp/terraform/registry/regsrc"
|
||||||
"github.com/hashicorp/terraform/registry/response"
|
"github.com/hashicorp/terraform/registry/response"
|
||||||
"github.com/hashicorp/terraform/svchost"
|
"github.com/hashicorp/terraform/svchost"
|
||||||
|
"github.com/hashicorp/terraform/svchost/auth"
|
||||||
|
"github.com/hashicorp/terraform/svchost/disco"
|
||||||
"github.com/hashicorp/terraform/version"
|
"github.com/hashicorp/terraform/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultRegistry = "registry.terraform.io"
|
|
||||||
registryServiceID = "registry.v1"
|
|
||||||
xTerraformGet = "X-Terraform-Get"
|
xTerraformGet = "X-Terraform-Get"
|
||||||
xTerraformVersion = "X-Terraform-Version"
|
xTerraformVersion = "X-Terraform-Version"
|
||||||
requestTimeout = 10 * time.Second
|
requestTimeout = 10 * time.Second
|
||||||
serviceID = "modules.v1"
|
serviceID = "modules.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var tfVersion = version.String()
|
||||||
httpClient *http.Client
|
|
||||||
tfVersion = version.String()
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
// Client provides methods to query Terraform Registries.
|
||||||
httpClient = cleanhttp.DefaultPooledClient()
|
type Client struct {
|
||||||
httpClient.Timeout = requestTimeout
|
// this is the client to be used for all requests.
|
||||||
|
client *http.Client
|
||||||
|
|
||||||
|
// services is a required *disco.Disco, which may have services and
|
||||||
|
// credentials pre-loaded.
|
||||||
|
services *disco.Disco
|
||||||
|
|
||||||
|
// Creds optionally provides credentials for communicating with service
|
||||||
|
// providers.
|
||||||
|
creds auth.CredentialsSource
|
||||||
}
|
}
|
||||||
|
|
||||||
type errModuleNotFound string
|
func NewClient(services *disco.Disco, creds auth.CredentialsSource, client *http.Client) *Client {
|
||||||
|
if services == nil {
|
||||||
func (e errModuleNotFound) Error() string {
|
services = disco.NewDisco()
|
||||||
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, "/") {
|
services.SetCredentialsSource(creds)
|
||||||
regURL.Path += "/"
|
|
||||||
|
if client == nil {
|
||||||
|
client = cleanhttp.DefaultPooledClient()
|
||||||
|
client.Timeout = requestTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
return regURL
|
services.Transport = client.Transport.(*http.Transport)
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Storage) addRequestCreds(host svchost.Hostname, req *http.Request) {
|
return &Client{
|
||||||
if s.Creds == nil {
|
client: client,
|
||||||
return
|
services: services,
|
||||||
}
|
creds: creds,
|
||||||
|
|
||||||
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.
|
// Discover qeuries the host, and returns the url for the registry.
|
||||||
func (s *Storage) lookupModuleVersions(module *regsrc.Module) (*response.ModuleVersions, error) {
|
func (c *Client) Discover(host svchost.Hostname) *url.URL {
|
||||||
|
service := c.services.DiscoverServiceURL(host, serviceID)
|
||||||
|
if !strings.HasSuffix(service.Path, "/") {
|
||||||
|
service.Path += "/"
|
||||||
|
}
|
||||||
|
return service
|
||||||
|
}
|
||||||
|
|
||||||
|
// Versions queries the registry for a module, and returns the available versions.
|
||||||
|
func (c *Client) Versions(module *regsrc.Module) (*response.ModuleVersions, error) {
|
||||||
host, err := module.SvcHost()
|
host, err := module.SvcHost()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
service := s.discoverRegURL(host)
|
service := c.Discover(host)
|
||||||
if service == nil {
|
if service == nil {
|
||||||
return nil, fmt.Errorf("host %s does not provide Terraform modules", host)
|
return nil, fmt.Errorf("host %s does not provide Terraform modules", host)
|
||||||
}
|
}
|
||||||
|
@ -99,10 +99,10 @@ func (s *Storage) lookupModuleVersions(module *regsrc.Module) (*response.ModuleV
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.addRequestCreds(host, req)
|
c.addRequestCreds(host, req)
|
||||||
req.Header.Set(xTerraformVersion, tfVersion)
|
req.Header.Set(xTerraformVersion, tfVersion)
|
||||||
|
|
||||||
resp, err := httpClient.Do(req)
|
resp, err := c.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -112,7 +112,7 @@ func (s *Storage) lookupModuleVersions(module *regsrc.Module) (*response.ModuleV
|
||||||
case http.StatusOK:
|
case http.StatusOK:
|
||||||
// OK
|
// OK
|
||||||
case http.StatusNotFound:
|
case http.StatusNotFound:
|
||||||
return nil, errModuleNotFound(module.String())
|
return nil, fmt.Errorf("module %q not found", module.String())
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("error looking up module versions: %s", resp.Status)
|
return nil, fmt.Errorf("error looking up module versions: %s", resp.Status)
|
||||||
}
|
}
|
||||||
|
@ -133,14 +133,31 @@ func (s *Storage) lookupModuleVersions(module *regsrc.Module) (*response.ModuleV
|
||||||
return &versions, nil
|
return &versions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// lookup the location of a specific module version in the registry
|
func (c *Client) addRequestCreds(host svchost.Hostname, req *http.Request) {
|
||||||
func (s *Storage) lookupModuleLocation(module *regsrc.Module, version string) (string, error) {
|
if c.creds == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
creds, err := c.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location find the download location for a specific version module.
|
||||||
|
// This returns a string, because the final location may contain special go-getter syntax.
|
||||||
|
func (c *Client) Location(module *regsrc.Module, version string) (string, error) {
|
||||||
host, err := module.SvcHost()
|
host, err := module.SvcHost()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
service := s.discoverRegURL(host)
|
service := c.Discover(host)
|
||||||
if service == nil {
|
if service == nil {
|
||||||
return "", fmt.Errorf("host %s does not provide Terraform modules", host.ForDisplay())
|
return "", fmt.Errorf("host %s does not provide Terraform modules", host.ForDisplay())
|
||||||
}
|
}
|
||||||
|
@ -163,10 +180,10 @@ func (s *Storage) lookupModuleLocation(module *regsrc.Module, version string) (s
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.addRequestCreds(host, req)
|
c.addRequestCreds(host, req)
|
||||||
req.Header.Set(xTerraformVersion, tfVersion)
|
req.Header.Set(xTerraformVersion, tfVersion)
|
||||||
|
|
||||||
resp, err := httpClient.Do(req)
|
resp, err := c.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package module
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
@ -7,16 +7,15 @@ import (
|
||||||
|
|
||||||
version "github.com/hashicorp/go-version"
|
version "github.com/hashicorp/go-version"
|
||||||
"github.com/hashicorp/terraform/registry/regsrc"
|
"github.com/hashicorp/terraform/registry/regsrc"
|
||||||
"github.com/hashicorp/terraform/svchost"
|
"github.com/hashicorp/terraform/registry/test"
|
||||||
"github.com/hashicorp/terraform/svchost/auth"
|
|
||||||
"github.com/hashicorp/terraform/svchost/disco"
|
"github.com/hashicorp/terraform/svchost/disco"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLookupModuleVersions(t *testing.T) {
|
func TestLookupModuleVersions(t *testing.T) {
|
||||||
server := mockRegistry()
|
server := test.Registry()
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
regDisco := testDisco(server)
|
client := NewClient(test.Disco(server), nil, nil)
|
||||||
|
|
||||||
// test with and without a hostname
|
// test with and without a hostname
|
||||||
for _, src := range []string{
|
for _, src := range []string{
|
||||||
|
@ -28,8 +27,7 @@ func TestLookupModuleVersions(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s := &Storage{Services: regDisco}
|
resp, err := client.Versions(modsrc)
|
||||||
resp, err := s.lookupModuleVersions(modsrc)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -58,11 +56,10 @@ func TestLookupModuleVersions(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegistryAuth(t *testing.T) {
|
func TestRegistryAuth(t *testing.T) {
|
||||||
server := mockRegistry()
|
server := test.Registry()
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
regDisco := testDisco(server)
|
client := NewClient(test.Disco(server), nil, nil)
|
||||||
storage := testStorage(t, regDisco)
|
|
||||||
|
|
||||||
src := "private/name/provider"
|
src := "private/name/provider"
|
||||||
mod, err := regsrc.ParseModuleSource(src)
|
mod, err := regsrc.ParseModuleSource(src)
|
||||||
|
@ -71,36 +68,32 @@ func TestRegistryAuth(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// both should fail without auth
|
// both should fail without auth
|
||||||
_, err = storage.lookupModuleVersions(mod)
|
_, err = client.Versions(mod)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error")
|
t.Fatal("expected error")
|
||||||
}
|
}
|
||||||
_, err = storage.lookupModuleLocation(mod, "1.0.0")
|
_, err = client.Location(mod, "1.0.0")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error")
|
t.Fatal("expected error")
|
||||||
}
|
}
|
||||||
|
|
||||||
storage.Creds = auth.StaticCredentialsSource(map[svchost.Hostname]map[string]interface{}{
|
client = NewClient(test.Disco(server), test.Credentials, nil)
|
||||||
svchost.Hostname(defaultRegistry): {"token": testCredentials},
|
|
||||||
})
|
|
||||||
|
|
||||||
_, err = storage.lookupModuleVersions(mod)
|
_, err = client.Versions(mod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, err = storage.lookupModuleLocation(mod, "1.0.0")
|
_, err = client.Location(mod, "1.0.0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLookupModuleLocationRelative(t *testing.T) {
|
func TestLookupModuleLocationRelative(t *testing.T) {
|
||||||
server := mockRegistry()
|
server := test.Registry()
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
regDisco := testDisco(server)
|
client := NewClient(test.Disco(server), nil, nil)
|
||||||
storage := testStorage(t, regDisco)
|
|
||||||
|
|
||||||
src := "relative/foo/bar"
|
src := "relative/foo/bar"
|
||||||
mod, err := regsrc.ParseModuleSource(src)
|
mod, err := regsrc.ParseModuleSource(src)
|
||||||
|
@ -108,7 +101,7 @@ func TestLookupModuleLocationRelative(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
got, err := storage.lookupModuleLocation(mod, "0.2.0")
|
got, err := client.Location(mod, "0.2.0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -117,7 +110,6 @@ func TestLookupModuleLocationRelative(t *testing.T) {
|
||||||
if got != want {
|
if got != want {
|
||||||
t.Errorf("wrong location %s; want %s", got, want)
|
t.Errorf("wrong location %s; want %s", got, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccLookupModuleVersions(t *testing.T) {
|
func TestAccLookupModuleVersions(t *testing.T) {
|
||||||
|
@ -129,17 +121,15 @@ func TestAccLookupModuleVersions(t *testing.T) {
|
||||||
// test with and without a hostname
|
// test with and without a hostname
|
||||||
for _, src := range []string{
|
for _, src := range []string{
|
||||||
"terraform-aws-modules/vpc/aws",
|
"terraform-aws-modules/vpc/aws",
|
||||||
defaultRegistry + "/terraform-aws-modules/vpc/aws",
|
regsrc.PublicRegistryHost.String() + "/terraform-aws-modules/vpc/aws",
|
||||||
} {
|
} {
|
||||||
modsrc, err := regsrc.ParseModuleSource(src)
|
modsrc, err := regsrc.ParseModuleSource(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s := &Storage{
|
s := NewClient(regDisco, nil, nil)
|
||||||
Services: regDisco,
|
resp, err := s.Versions(modsrc)
|
||||||
}
|
|
||||||
resp, err := s.lookupModuleVersions(modsrc)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -169,11 +159,10 @@ func TestAccLookupModuleVersions(t *testing.T) {
|
||||||
|
|
||||||
// the error should reference the config source exatly, not the discovered path.
|
// the error should reference the config source exatly, not the discovered path.
|
||||||
func TestLookupLookupModuleError(t *testing.T) {
|
func TestLookupLookupModuleError(t *testing.T) {
|
||||||
server := mockRegistry()
|
server := test.Registry()
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
regDisco := testDisco(server)
|
client := NewClient(test.Disco(server), nil, nil)
|
||||||
storage := testStorage(t, regDisco)
|
|
||||||
|
|
||||||
// this should not be found in teh registry
|
// this should not be found in teh registry
|
||||||
src := "bad/local/path"
|
src := "bad/local/path"
|
||||||
|
@ -182,7 +171,7 @@ func TestLookupLookupModuleError(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = storage.lookupModuleLocation(mod, "0.2.0")
|
_, err = client.Location(mod, "0.2.0")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error")
|
t.Fatal("expected error")
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package module
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -6,18 +6,36 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
|
||||||
|
|
||||||
version "github.com/hashicorp/go-version"
|
version "github.com/hashicorp/go-version"
|
||||||
"github.com/hashicorp/terraform/registry/regsrc"
|
"github.com/hashicorp/terraform/registry/regsrc"
|
||||||
"github.com/hashicorp/terraform/registry/response"
|
"github.com/hashicorp/terraform/registry/response"
|
||||||
|
"github.com/hashicorp/terraform/svchost"
|
||||||
|
"github.com/hashicorp/terraform/svchost/auth"
|
||||||
|
"github.com/hashicorp/terraform/svchost/disco"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
// Map of module names and location of test modules.
|
// Map of module names and location of test modules.
|
||||||
// Only one version for now, as we only lookup latest from the registry.
|
// Only one version for now, as we only lookup latest from the registry.
|
||||||
type testMod struct {
|
type testMod struct {
|
||||||
|
@ -26,7 +44,14 @@ type testMod struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
testCredentials = "test-auth-token"
|
testCred = "test-auth-token"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
regHost = svchost.Hostname(regsrc.PublicRegistryHost.Normalized())
|
||||||
|
Credentials = auth.StaticCredentialsSource(map[svchost.Hostname]map[string]interface{}{
|
||||||
|
regHost: {"token": testCred},
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
// All the locationes from the mockRegistry start with a file:// scheme. If
|
// All the locationes from the mockRegistry start with a file:// scheme. If
|
||||||
|
@ -94,8 +119,9 @@ func mockRegHandler() http.Handler {
|
||||||
|
|
||||||
// check for auth
|
// check for auth
|
||||||
if strings.Contains(matches[0], "private/") {
|
if strings.Contains(matches[0], "private/") {
|
||||||
if !strings.Contains(r.Header.Get("Authorization"), testCredentials) {
|
if !strings.Contains(r.Header.Get("Authorization"), testCred) {
|
||||||
http.Error(w, "", http.StatusForbidden)
|
http.Error(w, "", http.StatusForbidden)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,7 +156,7 @@ func mockRegHandler() http.Handler {
|
||||||
|
|
||||||
// check for auth
|
// check for auth
|
||||||
if strings.Contains(matches[1], "private/") {
|
if strings.Contains(matches[1], "private/") {
|
||||||
if !strings.Contains(r.Header.Get("Authorization"), testCredentials) {
|
if !strings.Contains(r.Header.Get("Authorization"), testCred) {
|
||||||
http.Error(w, "", http.StatusForbidden)
|
http.Error(w, "", http.StatusForbidden)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -191,145 +217,7 @@ func mockRegHandler() http.Handler {
|
||||||
return mux
|
return mux
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just enough like a registry to exercise our code.
|
// NewRegistry return an httptest server that mocks out some registry functionality.
|
||||||
// Returns the location of the latest version
|
func Registry() *httptest.Server {
|
||||||
func mockRegistry() *httptest.Server {
|
return httptest.NewServer(mockRegHandler())
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue