github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/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 "strings" 12 13 svchost "github.com/hashicorp/terraform-svchost" 14 "github.com/hashicorp/terraform-svchost/auth" 15 "github.com/hashicorp/terraform-svchost/disco" 16 "github.com/hashicorp/terraform/internal/httpclient" 17 "github.com/hashicorp/terraform/internal/registry/regsrc" 18 "github.com/hashicorp/terraform/internal/registry/response" 19 tfversion "github.com/hashicorp/terraform/version" 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 "providers.v1": fmt.Sprintf("%s/v1/providers", s.URL), 30 } 31 d := disco.NewWithCredentialsSource(credsSrc) 32 d.SetUserAgent(httpclient.TerraformUserAgent(tfversion.String())) 33 34 d.ForceHostServices(svchost.Hostname("registry.terraform.io"), services) 35 d.ForceHostServices(svchost.Hostname("localhost"), services) 36 d.ForceHostServices(svchost.Hostname("localhost.localdomain"), services) 37 d.ForceHostServices(svchost.Hostname("example.com"), services) 38 return d 39 } 40 41 // Map of module names and location of test modules. 42 // Only one version for now, as we only lookup latest from the registry. 43 type testMod struct { 44 location string 45 version string 46 } 47 48 // Map of provider names and location of test providers. 49 // Only one version for now, as we only lookup latest from the registry. 50 type testProvider struct { 51 version string 52 url string 53 } 54 55 const ( 56 testCred = "test-auth-token" 57 ) 58 59 var ( 60 regHost = svchost.Hostname(regsrc.PublicRegistryHost.Normalized()) 61 credsSrc = auth.StaticCredentialsSource(map[svchost.Hostname]map[string]interface{}{ 62 regHost: {"token": testCred}, 63 }) 64 ) 65 66 // All the locationes from the mockRegistry start with a file:// scheme. If 67 // the the location string here doesn't have a scheme, the mockRegistry will 68 // find the absolute path and return a complete URL. 69 var testMods = map[string][]testMod{ 70 "registry/foo/bar": {{ 71 location: "file:///download/registry/foo/bar/0.2.3//*?archive=tar.gz", 72 version: "0.2.3", 73 }}, 74 "registry/foo/baz": {{ 75 location: "file:///download/registry/foo/baz/1.10.0//*?archive=tar.gz", 76 version: "1.10.0", 77 }}, 78 "registry/local/sub": {{ 79 location: "testdata/registry-tar-subdir/foo.tgz//*?archive=tar.gz", 80 version: "0.1.2", 81 }}, 82 "exists-in-registry/identifier/provider": {{ 83 location: "file:///registry/exists", 84 version: "0.2.0", 85 }}, 86 "relative/foo/bar": {{ // There is an exception for the "relative/" prefix in the test registry server 87 location: "/relative-path", 88 version: "0.2.0", 89 }}, 90 "test-versions/name/provider": { 91 {version: "2.2.0"}, 92 {version: "2.1.1"}, 93 {version: "1.2.2"}, 94 {version: "1.2.1"}, 95 }, 96 "private/name/provider": { 97 {version: "1.0.0"}, 98 }, 99 } 100 101 var testProviders = map[string][]testProvider{ 102 "-/foo": { 103 { 104 version: "0.2.3", 105 url: "https://releases.hashicorp.com/terraform-provider-foo/0.2.3/terraform-provider-foo.zip", 106 }, 107 {version: "0.3.0"}, 108 }, 109 "-/bar": { 110 { 111 version: "0.1.1", 112 url: "https://releases.hashicorp.com/terraform-provider-bar/0.1.1/terraform-provider-bar.zip", 113 }, 114 {version: "0.1.2"}, 115 }, 116 } 117 118 func providerAlias(provider string) string { 119 re := regexp.MustCompile("^-/") 120 if re.MatchString(provider) { 121 return re.ReplaceAllString(provider, "terraform-providers/") 122 } 123 return provider 124 } 125 126 func init() { 127 // Add provider aliases 128 for provider, info := range testProviders { 129 alias := providerAlias(provider) 130 testProviders[alias] = info 131 } 132 } 133 134 func mockRegHandler() http.Handler { 135 mux := http.NewServeMux() 136 137 moduleDownload := func(w http.ResponseWriter, r *http.Request) { 138 p := strings.TrimLeft(r.URL.Path, "/") 139 // handle download request 140 re := regexp.MustCompile(`^([-a-z]+/\w+/\w+).*/download$`) 141 // download lookup 142 matches := re.FindStringSubmatch(p) 143 if len(matches) != 2 { 144 w.WriteHeader(http.StatusBadRequest) 145 return 146 } 147 148 // check for auth 149 if strings.Contains(matches[0], "private/") { 150 if !strings.Contains(r.Header.Get("Authorization"), testCred) { 151 http.Error(w, "", http.StatusForbidden) 152 return 153 } 154 } 155 156 versions, ok := testMods[matches[1]] 157 if !ok { 158 http.NotFound(w, r) 159 return 160 } 161 mod := versions[0] 162 163 location := mod.location 164 if !strings.HasPrefix(matches[0], "relative/") && !strings.HasPrefix(location, "file:///") { 165 // we can't use filepath.Abs because it will clean `//` 166 wd, _ := os.Getwd() 167 location = fmt.Sprintf("file://%s/%s", wd, location) 168 } 169 170 w.Header().Set("X-Terraform-Get", location) 171 w.WriteHeader(http.StatusNoContent) 172 // no body 173 } 174 175 moduleVersions := func(w http.ResponseWriter, r *http.Request) { 176 p := strings.TrimLeft(r.URL.Path, "/") 177 re := regexp.MustCompile(`^([-a-z]+/\w+/\w+)/versions$`) 178 matches := re.FindStringSubmatch(p) 179 if len(matches) != 2 { 180 w.WriteHeader(http.StatusBadRequest) 181 return 182 } 183 184 // check for auth 185 if strings.Contains(matches[1], "private/") { 186 if !strings.Contains(r.Header.Get("Authorization"), testCred) { 187 http.Error(w, "", http.StatusForbidden) 188 } 189 } 190 191 name := matches[1] 192 versions, ok := testMods[name] 193 if !ok { 194 http.NotFound(w, r) 195 return 196 } 197 198 // only adding the single requested module for now 199 // this is the minimal that any regisry is epected to support 200 mpvs := &response.ModuleProviderVersions{ 201 Source: name, 202 } 203 204 for _, v := range versions { 205 mv := &response.ModuleVersion{ 206 Version: v.version, 207 } 208 mpvs.Versions = append(mpvs.Versions, mv) 209 } 210 211 resp := response.ModuleVersions{ 212 Modules: []*response.ModuleProviderVersions{mpvs}, 213 } 214 215 js, err := json.Marshal(resp) 216 if err != nil { 217 http.Error(w, err.Error(), http.StatusInternalServerError) 218 return 219 } 220 w.Header().Set("Content-Type", "application/json") 221 w.Write(js) 222 } 223 224 mux.Handle("/v1/modules/", 225 http.StripPrefix("/v1/modules/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 226 if strings.HasSuffix(r.URL.Path, "/download") { 227 moduleDownload(w, r) 228 return 229 } 230 231 if strings.HasSuffix(r.URL.Path, "/versions") { 232 moduleVersions(w, r) 233 return 234 } 235 236 http.NotFound(w, r) 237 })), 238 ) 239 240 mux.HandleFunc("/.well-known/terraform.json", func(w http.ResponseWriter, r *http.Request) { 241 w.Header().Set("Content-Type", "application/json") 242 io.WriteString(w, `{"modules.v1":"http://localhost/v1/modules/", "providers.v1":"http://localhost/v1/providers/"}`) 243 }) 244 return mux 245 } 246 247 // Registry returns an httptest server that mocks out some registry functionality. 248 func Registry() *httptest.Server { 249 return httptest.NewServer(mockRegHandler()) 250 } 251 252 // RegistryRetryableErrorsServer returns an httptest server that mocks out the 253 // registry API to return 502 errors. 254 func RegistryRetryableErrorsServer() *httptest.Server { 255 mux := http.NewServeMux() 256 mux.HandleFunc("/v1/modules/", func(w http.ResponseWriter, r *http.Request) { 257 http.Error(w, "mocked server error", http.StatusBadGateway) 258 }) 259 mux.HandleFunc("/v1/providers/", func(w http.ResponseWriter, r *http.Request) { 260 http.Error(w, "mocked server error", http.StatusBadGateway) 261 }) 262 return httptest.NewServer(mux) 263 }