github.com/trawler/terraform@v0.10.8-0.20171106022149-4b1c7a1d9b48/config/module/get_test.go (about)

     1  package module
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"net/url"
    10  	"os"
    11  	"regexp"
    12  	"sort"
    13  	"strings"
    14  	"testing"
    15  
    16  	version "github.com/hashicorp/go-version"
    17  	"github.com/hashicorp/terraform/registry/regsrc"
    18  	"github.com/hashicorp/terraform/registry/response"
    19  )
    20  
    21  // Map of module names and location of test modules.
    22  // Only one version for now, as we only lookup latest from the registry.
    23  type testMod struct {
    24  	location string
    25  	version  string
    26  }
    27  
    28  const (
    29  	testCredentials = "test-auth-token"
    30  )
    31  
    32  // All the locationes from the mockRegistry start with a file:// scheme. If
    33  // the the location string here doesn't have a scheme, the mockRegistry will
    34  // find the absolute path and return a complete URL.
    35  var testMods = map[string][]testMod{
    36  	"registry/foo/bar": {{
    37  		location: "file:///download/registry/foo/bar/0.2.3//*?archive=tar.gz",
    38  		version:  "0.2.3",
    39  	}},
    40  	"registry/foo/baz": {{
    41  		location: "file:///download/registry/foo/baz/1.10.0//*?archive=tar.gz",
    42  		version:  "1.10.0",
    43  	}},
    44  	"registry/local/sub": {{
    45  		location: "test-fixtures/registry-tar-subdir/foo.tgz//*?archive=tar.gz",
    46  		version:  "0.1.2",
    47  	}},
    48  	"exists-in-registry/identifier/provider": {{
    49  		location: "file:///registry/exists",
    50  		version:  "0.2.0",
    51  	}},
    52  	"relative/foo/bar": {{ // There is an exception for the "relative/" prefix in the test registry server
    53  		location: "/relative-path",
    54  		version:  "0.2.0",
    55  	}},
    56  	"test-versions/name/provider": {
    57  		{version: "2.2.0"},
    58  		{version: "2.1.1"},
    59  		{version: "1.2.2"},
    60  		{version: "1.2.1"},
    61  	},
    62  	"private/name/provider": {
    63  		{version: "1.0.0"},
    64  	},
    65  }
    66  
    67  func latestVersion(versions []string) string {
    68  	var col version.Collection
    69  	for _, v := range versions {
    70  		ver, err := version.NewVersion(v)
    71  		if err != nil {
    72  			panic(err)
    73  		}
    74  		col = append(col, ver)
    75  	}
    76  
    77  	sort.Sort(col)
    78  	return col[len(col)-1].String()
    79  }
    80  
    81  func mockRegHandler() http.Handler {
    82  	mux := http.NewServeMux()
    83  
    84  	download := func(w http.ResponseWriter, r *http.Request) {
    85  		p := strings.TrimLeft(r.URL.Path, "/")
    86  		// handle download request
    87  		re := regexp.MustCompile(`^([-a-z]+/\w+/\w+).*/download$`)
    88  		// download lookup
    89  		matches := re.FindStringSubmatch(p)
    90  		if len(matches) != 2 {
    91  			w.WriteHeader(http.StatusBadRequest)
    92  			return
    93  		}
    94  
    95  		// check for auth
    96  		if strings.Contains(matches[0], "private/") {
    97  			if !strings.Contains(r.Header.Get("Authorization"), testCredentials) {
    98  				http.Error(w, "", http.StatusForbidden)
    99  			}
   100  		}
   101  
   102  		versions, ok := testMods[matches[1]]
   103  		if !ok {
   104  			http.NotFound(w, r)
   105  			return
   106  		}
   107  		mod := versions[0]
   108  
   109  		location := mod.location
   110  		if !strings.HasPrefix(matches[0], "relative/") && !strings.HasPrefix(location, "file:///") {
   111  			// we can't use filepath.Abs because it will clean `//`
   112  			wd, _ := os.Getwd()
   113  			location = fmt.Sprintf("file://%s/%s", wd, location)
   114  		}
   115  
   116  		w.Header().Set("X-Terraform-Get", location)
   117  		w.WriteHeader(http.StatusNoContent)
   118  		// no body
   119  		return
   120  	}
   121  
   122  	versions := func(w http.ResponseWriter, r *http.Request) {
   123  		p := strings.TrimLeft(r.URL.Path, "/")
   124  		re := regexp.MustCompile(`^([-a-z]+/\w+/\w+)/versions$`)
   125  		matches := re.FindStringSubmatch(p)
   126  		if len(matches) != 2 {
   127  			w.WriteHeader(http.StatusBadRequest)
   128  			return
   129  		}
   130  
   131  		// check for auth
   132  		if strings.Contains(matches[1], "private/") {
   133  			if !strings.Contains(r.Header.Get("Authorization"), testCredentials) {
   134  				http.Error(w, "", http.StatusForbidden)
   135  			}
   136  		}
   137  
   138  		name := matches[1]
   139  		versions, ok := testMods[name]
   140  		if !ok {
   141  			http.NotFound(w, r)
   142  			return
   143  		}
   144  
   145  		// only adding the single requested module for now
   146  		// this is the minimal that any regisry is epected to support
   147  		mpvs := &response.ModuleProviderVersions{
   148  			Source: name,
   149  		}
   150  
   151  		for _, v := range versions {
   152  			mv := &response.ModuleVersion{
   153  				Version: v.version,
   154  			}
   155  			mpvs.Versions = append(mpvs.Versions, mv)
   156  		}
   157  
   158  		resp := response.ModuleVersions{
   159  			Modules: []*response.ModuleProviderVersions{mpvs},
   160  		}
   161  
   162  		js, err := json.Marshal(resp)
   163  		if err != nil {
   164  			http.Error(w, err.Error(), http.StatusInternalServerError)
   165  			return
   166  		}
   167  		w.Header().Set("Content-Type", "application/json")
   168  		w.Write(js)
   169  	}
   170  
   171  	mux.Handle("/v1/modules/",
   172  		http.StripPrefix("/v1/modules/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   173  			if strings.HasSuffix(r.URL.Path, "/download") {
   174  				download(w, r)
   175  				return
   176  			}
   177  
   178  			if strings.HasSuffix(r.URL.Path, "/versions") {
   179  				versions(w, r)
   180  				return
   181  			}
   182  
   183  			http.NotFound(w, r)
   184  		})),
   185  	)
   186  
   187  	mux.HandleFunc("/.well-known/terraform.json", func(w http.ResponseWriter, r *http.Request) {
   188  		w.Header().Set("Content-Type", "application/json")
   189  		io.WriteString(w, `{"modules.v1":"http://localhost/v1/modules/"}`)
   190  	})
   191  	return mux
   192  }
   193  
   194  // Just enough like a registry to exercise our code.
   195  // Returns the location of the latest version
   196  func mockRegistry() *httptest.Server {
   197  	server := httptest.NewServer(mockRegHandler())
   198  	return server
   199  }
   200  
   201  // GitHub archives always contain the module source in a single subdirectory,
   202  // so the registry will return a path with with a `//*` suffix. We need to make
   203  // sure this doesn't intefere with our internal handling of `//` subdir.
   204  func TestRegistryGitHubArchive(t *testing.T) {
   205  	server := mockRegistry()
   206  	defer server.Close()
   207  
   208  	disco := testDisco(server)
   209  	storage := testStorage(t, disco)
   210  
   211  	tree := NewTree("", testConfig(t, "registry-tar-subdir"))
   212  
   213  	storage.Mode = GetModeGet
   214  	if err := tree.Load(storage); err != nil {
   215  		t.Fatalf("err: %s", err)
   216  	}
   217  
   218  	if !tree.Loaded() {
   219  		t.Fatal("should be loaded")
   220  	}
   221  
   222  	storage.Mode = GetModeNone
   223  	if err := tree.Load(storage); err != nil {
   224  		t.Fatalf("err: %s", err)
   225  	}
   226  
   227  	// stop the registry server, and make sure that we don't need to call out again
   228  	server.Close()
   229  	tree = NewTree("", testConfig(t, "registry-tar-subdir"))
   230  
   231  	storage.Mode = GetModeGet
   232  	if err := tree.Load(storage); err != nil {
   233  		t.Fatalf("err: %s", err)
   234  	}
   235  
   236  	if !tree.Loaded() {
   237  		t.Fatal("should be loaded")
   238  	}
   239  
   240  	actual := strings.TrimSpace(tree.String())
   241  	expected := strings.TrimSpace(treeLoadSubdirStr)
   242  	if actual != expected {
   243  		t.Fatalf("got: \n\n%s\nexpected: \n\n%s", actual, expected)
   244  	}
   245  }
   246  
   247  // Test that the //subdir notation can be used with registry modules
   248  func TestRegisryModuleSubdir(t *testing.T) {
   249  	server := mockRegistry()
   250  	defer server.Close()
   251  
   252  	disco := testDisco(server)
   253  	storage := testStorage(t, disco)
   254  	tree := NewTree("", testConfig(t, "registry-subdir"))
   255  
   256  	storage.Mode = GetModeGet
   257  	if err := tree.Load(storage); err != nil {
   258  		t.Fatalf("err: %s", err)
   259  	}
   260  
   261  	if !tree.Loaded() {
   262  		t.Fatal("should be loaded")
   263  	}
   264  
   265  	storage.Mode = GetModeNone
   266  	if err := tree.Load(storage); err != nil {
   267  		t.Fatalf("err: %s", err)
   268  	}
   269  
   270  	actual := strings.TrimSpace(tree.String())
   271  	expected := strings.TrimSpace(treeLoadRegistrySubdirStr)
   272  	if actual != expected {
   273  		t.Fatalf("got: \n\n%s\nexpected: \n\n%s", actual, expected)
   274  	}
   275  }
   276  
   277  func TestAccRegistryDiscover(t *testing.T) {
   278  	if os.Getenv("TF_ACC") == "" {
   279  		t.Skip("skipping ACC test")
   280  	}
   281  
   282  	// simply check that we get a valid github URL for this from the registry
   283  	module, err := regsrc.ParseModuleSource("hashicorp/consul/aws")
   284  	if err != nil {
   285  		t.Fatal(err)
   286  	}
   287  
   288  	s := NewStorage("/tmp", nil, nil)
   289  	loc, err := s.lookupModuleLocation(module, "")
   290  	if err != nil {
   291  		t.Fatal(err)
   292  	}
   293  
   294  	u, err := url.Parse(loc)
   295  	if err != nil {
   296  		t.Fatal(err)
   297  	}
   298  
   299  	if !strings.HasSuffix(u.Host, "github.com") {
   300  		t.Fatalf("expected host 'github.com', got: %q", u.Host)
   301  	}
   302  
   303  	if !strings.Contains(u.String(), "consul") {
   304  		t.Fatalf("url doesn't contain 'consul': %s", u.String())
   305  	}
   306  }
   307  
   308  func TestAccRegistryLoad(t *testing.T) {
   309  	if os.Getenv("TF_ACC") == "" {
   310  		t.Skip("skipping ACC test")
   311  	}
   312  
   313  	storage := testStorage(t, nil)
   314  	tree := NewTree("", testConfig(t, "registry-load"))
   315  
   316  	storage.Mode = GetModeGet
   317  	if err := tree.Load(storage); err != nil {
   318  		t.Fatalf("err: %s", err)
   319  	}
   320  
   321  	if !tree.Loaded() {
   322  		t.Fatal("should be loaded")
   323  	}
   324  
   325  	storage.Mode = GetModeNone
   326  	if err := tree.Load(storage); err != nil {
   327  		t.Fatalf("err: %s", err)
   328  	}
   329  
   330  	// TODO expand this further by fetching some metadata from the registry
   331  	actual := strings.TrimSpace(tree.String())
   332  	if !strings.Contains(actual, "(path: vault)") {
   333  		t.Fatal("missing vault module, got:\n", actual)
   334  	}
   335  }