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 }