github.com/jameswoolfenden/terraform@v0.11.12-beta1/registry/test/mock_registry.go (about)

     1  package test
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"os"
    10  	"regexp"
    11  	"sort"
    12  	"strings"
    13  
    14  	version "github.com/hashicorp/go-version"
    15  	"github.com/hashicorp/terraform/registry/regsrc"
    16  	"github.com/hashicorp/terraform/registry/response"
    17  	"github.com/hashicorp/terraform/svchost"
    18  	"github.com/hashicorp/terraform/svchost/auth"
    19  	"github.com/hashicorp/terraform/svchost/disco"
    20  )
    21  
    22  // Disco return a *disco.Disco mapping registry.terraform.io, localhost,
    23  // localhost.localdomain, and example.com to the test server.
    24  func Disco(s *httptest.Server) *disco.Disco {
    25  	services := map[string]interface{}{
    26  		// Note that both with and without trailing slashes are supported behaviours
    27  		// TODO: add specific tests to enumerate both possibilities.
    28  		"modules.v1": fmt.Sprintf("%s/v1/modules", s.URL),
    29  	}
    30  	d := disco.NewWithCredentialsSource(credsSrc)
    31  
    32  	d.ForceHostServices(svchost.Hostname("registry.terraform.io"), services)
    33  	d.ForceHostServices(svchost.Hostname("localhost"), services)
    34  	d.ForceHostServices(svchost.Hostname("localhost.localdomain"), services)
    35  	d.ForceHostServices(svchost.Hostname("example.com"), services)
    36  	return d
    37  }
    38  
    39  // Map of module names and location of test modules.
    40  // Only one version for now, as we only lookup latest from the registry.
    41  type testMod struct {
    42  	location string
    43  	version  string
    44  }
    45  
    46  const (
    47  	testCred = "test-auth-token"
    48  )
    49  
    50  var (
    51  	regHost  = svchost.Hostname(regsrc.PublicRegistryHost.Normalized())
    52  	credsSrc = auth.StaticCredentialsSource(map[svchost.Hostname]map[string]interface{}{
    53  		regHost: {"token": testCred},
    54  	})
    55  )
    56  
    57  // All the locationes from the mockRegistry start with a file:// scheme. If
    58  // the the location string here doesn't have a scheme, the mockRegistry will
    59  // find the absolute path and return a complete URL.
    60  var testMods = map[string][]testMod{
    61  	"registry/foo/bar": {{
    62  		location: "file:///download/registry/foo/bar/0.2.3//*?archive=tar.gz",
    63  		version:  "0.2.3",
    64  	}},
    65  	"registry/foo/baz": {{
    66  		location: "file:///download/registry/foo/baz/1.10.0//*?archive=tar.gz",
    67  		version:  "1.10.0",
    68  	}},
    69  	"registry/local/sub": {{
    70  		location: "test-fixtures/registry-tar-subdir/foo.tgz//*?archive=tar.gz",
    71  		version:  "0.1.2",
    72  	}},
    73  	"exists-in-registry/identifier/provider": {{
    74  		location: "file:///registry/exists",
    75  		version:  "0.2.0",
    76  	}},
    77  	"relative/foo/bar": {{ // There is an exception for the "relative/" prefix in the test registry server
    78  		location: "/relative-path",
    79  		version:  "0.2.0",
    80  	}},
    81  	"test-versions/name/provider": {
    82  		{version: "2.2.0"},
    83  		{version: "2.1.1"},
    84  		{version: "1.2.2"},
    85  		{version: "1.2.1"},
    86  	},
    87  	"private/name/provider": {
    88  		{version: "1.0.0"},
    89  	},
    90  }
    91  
    92  func latestVersion(versions []string) string {
    93  	var col version.Collection
    94  	for _, v := range versions {
    95  		ver, err := version.NewVersion(v)
    96  		if err != nil {
    97  			panic(err)
    98  		}
    99  		col = append(col, ver)
   100  	}
   101  
   102  	sort.Sort(col)
   103  	return col[len(col)-1].String()
   104  }
   105  
   106  func mockRegHandler() http.Handler {
   107  	mux := http.NewServeMux()
   108  
   109  	download := func(w http.ResponseWriter, r *http.Request) {
   110  		p := strings.TrimLeft(r.URL.Path, "/")
   111  		// handle download request
   112  		re := regexp.MustCompile(`^([-a-z]+/\w+/\w+).*/download$`)
   113  		// download lookup
   114  		matches := re.FindStringSubmatch(p)
   115  		if len(matches) != 2 {
   116  			w.WriteHeader(http.StatusBadRequest)
   117  			return
   118  		}
   119  
   120  		// check for auth
   121  		if strings.Contains(matches[0], "private/") {
   122  			if !strings.Contains(r.Header.Get("Authorization"), testCred) {
   123  				http.Error(w, "", http.StatusForbidden)
   124  				return
   125  			}
   126  		}
   127  
   128  		versions, ok := testMods[matches[1]]
   129  		if !ok {
   130  			http.NotFound(w, r)
   131  			return
   132  		}
   133  		mod := versions[0]
   134  
   135  		location := mod.location
   136  		if !strings.HasPrefix(matches[0], "relative/") && !strings.HasPrefix(location, "file:///") {
   137  			// we can't use filepath.Abs because it will clean `//`
   138  			wd, _ := os.Getwd()
   139  			location = fmt.Sprintf("file://%s/%s", wd, location)
   140  		}
   141  
   142  		w.Header().Set("X-Terraform-Get", location)
   143  		w.WriteHeader(http.StatusNoContent)
   144  		// no body
   145  		return
   146  	}
   147  
   148  	versions := func(w http.ResponseWriter, r *http.Request) {
   149  		p := strings.TrimLeft(r.URL.Path, "/")
   150  		re := regexp.MustCompile(`^([-a-z]+/\w+/\w+)/versions$`)
   151  		matches := re.FindStringSubmatch(p)
   152  		if len(matches) != 2 {
   153  			w.WriteHeader(http.StatusBadRequest)
   154  			return
   155  		}
   156  
   157  		// check for auth
   158  		if strings.Contains(matches[1], "private/") {
   159  			if !strings.Contains(r.Header.Get("Authorization"), testCred) {
   160  				http.Error(w, "", http.StatusForbidden)
   161  			}
   162  		}
   163  
   164  		name := matches[1]
   165  		versions, ok := testMods[name]
   166  		if !ok {
   167  			http.NotFound(w, r)
   168  			return
   169  		}
   170  
   171  		// only adding the single requested module for now
   172  		// this is the minimal that any regisry is epected to support
   173  		mpvs := &response.ModuleProviderVersions{
   174  			Source: name,
   175  		}
   176  
   177  		for _, v := range versions {
   178  			mv := &response.ModuleVersion{
   179  				Version: v.version,
   180  			}
   181  			mpvs.Versions = append(mpvs.Versions, mv)
   182  		}
   183  
   184  		resp := response.ModuleVersions{
   185  			Modules: []*response.ModuleProviderVersions{mpvs},
   186  		}
   187  
   188  		js, err := json.Marshal(resp)
   189  		if err != nil {
   190  			http.Error(w, err.Error(), http.StatusInternalServerError)
   191  			return
   192  		}
   193  		w.Header().Set("Content-Type", "application/json")
   194  		w.Write(js)
   195  	}
   196  
   197  	mux.Handle("/v1/modules/",
   198  		http.StripPrefix("/v1/modules/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   199  			if strings.HasSuffix(r.URL.Path, "/download") {
   200  				download(w, r)
   201  				return
   202  			}
   203  
   204  			if strings.HasSuffix(r.URL.Path, "/versions") {
   205  				versions(w, r)
   206  				return
   207  			}
   208  
   209  			http.NotFound(w, r)
   210  		})),
   211  	)
   212  
   213  	mux.HandleFunc("/.well-known/terraform.json", func(w http.ResponseWriter, r *http.Request) {
   214  		w.Header().Set("Content-Type", "application/json")
   215  		io.WriteString(w, `{"modules.v1":"http://localhost/v1/modules/"}`)
   216  	})
   217  	return mux
   218  }
   219  
   220  // NewRegistry return an httptest server that mocks out some registry functionality.
   221  func Registry() *httptest.Server {
   222  	return httptest.NewServer(mockRegHandler())
   223  }