github.com/hs0210/hashicorp-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 }