From 1e7ae17793bac6e9b80f236c9019b4f8f54a6e84 Mon Sep 17 00:00:00 2001 From: Julie Qiu Date: Mon, 11 Feb 2019 17:00:07 -0500 Subject: [PATCH] discovery/internal/proxyclient: create client for module proxy The discovery/internal/proxyclient package is created to interact with a module proxy. See `go help goproxy` for proxy protocol details. The following structs are introduced: - proxyclient.Client - proxyclient.VersionInfo The following exported functions are implemented: - New(rawurl string) (*Client, error) - GetInfo(name, version string) (*VersionInfo, error) discovery/internal/testdata is moved to discovery/internal/fetch/testdata. Change-Id: I3b0fa8dcf95f8dbbc39519640adbf199c1e61efb Reviewed-on: https://team-review.git.corp.google.com/c/414334 Reviewed-by: Andrew Bonventre --- internal/proxyclient/proxy_client.go | 58 +++++++++++ internal/proxyclient/proxy_client_test.go | 97 ++++++++++++++++++ .../proxyclient/testdata/modproxy/main.go | 28 +++++ .../testdata/modproxy/proxy/my/module/@v/list | 0 .../modproxy/proxy/my/module/@v/v1.0.0.info | 0 .../modproxy/proxy/my/module/@v/v1.0.0.mod | 0 .../modproxy/proxy/my/module/@v/v1.0.0.zip | Bin .../modproxy/proxy/my/module/@v/v1.1.0.info | 0 .../modproxy/proxy/my/module/@v/v1.1.0.mod | 0 .../modproxy/proxy/my/module/@v/v1.1.0.zip | Bin .../modproxy/proxy/my/module/@v/v1.1.1.info | 0 .../modproxy/proxy/my/module/@v/v1.1.1.mod | 0 .../modproxy/proxy/my/module/@v/v1.1.1.zip | Bin .../modproxy/proxy/my/module/v2/@v/list | 0 .../proxy/my/module/v2/@v/v2.0.0.info | 0 .../modproxy/proxy/my/module/v2/@v/v2.0.0.mod | 0 .../modproxy/proxy/my/module/v2/@v/v2.0.0.zip | Bin internal/testdata/modproxy/main.go | 18 ---- 18 files changed, 183 insertions(+), 18 deletions(-) create mode 100644 internal/proxyclient/proxy_client.go create mode 100644 internal/proxyclient/proxy_client_test.go create mode 100644 internal/proxyclient/testdata/modproxy/main.go rename internal/{ => proxyclient}/testdata/modproxy/proxy/my/module/@v/list (100%) rename internal/{ => proxyclient}/testdata/modproxy/proxy/my/module/@v/v1.0.0.info (100%) rename internal/{ => proxyclient}/testdata/modproxy/proxy/my/module/@v/v1.0.0.mod (100%) rename internal/{ => proxyclient}/testdata/modproxy/proxy/my/module/@v/v1.0.0.zip (100%) rename internal/{ => proxyclient}/testdata/modproxy/proxy/my/module/@v/v1.1.0.info (100%) rename internal/{ => proxyclient}/testdata/modproxy/proxy/my/module/@v/v1.1.0.mod (100%) rename internal/{ => proxyclient}/testdata/modproxy/proxy/my/module/@v/v1.1.0.zip (100%) rename internal/{ => proxyclient}/testdata/modproxy/proxy/my/module/@v/v1.1.1.info (100%) rename internal/{ => proxyclient}/testdata/modproxy/proxy/my/module/@v/v1.1.1.mod (100%) rename internal/{ => proxyclient}/testdata/modproxy/proxy/my/module/@v/v1.1.1.zip (100%) rename internal/{ => proxyclient}/testdata/modproxy/proxy/my/module/v2/@v/list (100%) rename internal/{ => proxyclient}/testdata/modproxy/proxy/my/module/v2/@v/v2.0.0.info (100%) rename internal/{ => proxyclient}/testdata/modproxy/proxy/my/module/v2/@v/v2.0.0.mod (100%) rename internal/{ => proxyclient}/testdata/modproxy/proxy/my/module/v2/@v/v2.0.0.zip (100%) delete mode 100644 internal/testdata/modproxy/main.go diff --git a/internal/proxyclient/proxy_client.go b/internal/proxyclient/proxy_client.go new file mode 100644 index 00000000..30fcaae5 --- /dev/null +++ b/internal/proxyclient/proxy_client.go @@ -0,0 +1,58 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proxyclient + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + "time" +) + +// A Client is used by the fetch service to communicate with a module +// proxy. It handles all methods defined by go help goproxy. +// TODO(julieqiu): Implement GetList, GetMod, and GetZip. +type Client struct { + url string // URL of the module proxy web server +} + +// A VersionInfo contains metadata about a given version of a module. +type VersionInfo struct { + Version string + Time time.Time +} + +// cleanURL trims the rawurl of trailing slashes. +func cleanURL(rawurl string) string { + return strings.TrimRight(rawurl, "/"), nil +} + +// New constructs a *Client using the provided rawurl, which is expected to +// be an absolute URI that can be directly passed to http.Get. +func New(rawurl string) *Client { + return &Client{url: cleanURL(rawurl)}, nil +} + +// infoURL constructs a url for a GET request to $GOPROXY//@v/list. +func (c *Client) infoURL(name, version string) string { + return fmt.Sprintf("%s/%s/@v/%s.info", c.url, name, version) +} + +// GetInfo makes a GET request to $GOPROXY//@v/.info and +// transforms that data into a *VersionInfo. +func (c *Client) GetInfo(name, version string) (*VersionInfo, error) { + r, err := http.Get(c.infoURL(name, version)) + if err != nil { + return nil, err + } + defer r.Body.Close() + + var v VersionInfo + if err = json.NewDecoder(r.Body).Decode(&v); err != nil { + return nil, err + } + return &v, nil +} diff --git a/internal/proxyclient/proxy_client_test.go b/internal/proxyclient/proxy_client_test.go new file mode 100644 index 00000000..66c780b4 --- /dev/null +++ b/internal/proxyclient/proxy_client_test.go @@ -0,0 +1,97 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proxyclient + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" +) + +type testCase struct { + proxy *httptest.Server + client *Client +} + +func setupTestCase(t *testing.T) (func(t *testing.T), *testCase) { + proxy := httptest.NewServer(http.FileServer(http.Dir("testdata/modproxy/proxy"))) + tc := testCase{ + proxy: proxy, + client: New(proxy.URL), + } + + fn := func(t *testing.T) { + proxy.Close() + } + return fn, &tc +} + +func TestCleanURL(t *testing.T) { + for raw, expected := range map[string]string{ + "http://localhost:7000/index": "http://localhost:7000/index", + "http://host.com/": "http://host.com", + "http://host.com///": "http://host.com", + } { + got, err := cleanURL(raw) + if err != nil { + t.Errorf("cleanURL(%q) error: %v", raw, err) + } + if got != expected { + t.Errorf("cleanURL(%q) = %q, want %q", raw, got, expected) + } + } +} + +func TestCleanURLErrors(t *testing.T) { + for _, raw := range []string{ + "localhost:7000/index", + "host.com", + "", + } { + _, err := cleanURL(raw) + expectedErr := "is an invalid url" + if err == nil { + t.Errorf("cleanURL(%q) error = %v, want %q", raw, err, expectedErr) + } + if !strings.Contains(err.Error(), expectedErr) { + t.Errorf("cleanURL(%q) error = %v, want %q", raw, err, expectedErr) + } + } +} + +func TestGetInfo(t *testing.T) { + teardownTestCase, testCase := setupTestCase(t) + defer teardownTestCase(t) + + name := "my/module" + version := "v1.0.0" + info, err := testCase.proxyClient.GetInfo(name, version) + if err != nil { + t.Errorf("GetInfo(%q, %q) error: %v", name, version, err) + } + + if info.Version != version { + t.Errorf("VersionInfo.Version for GetInfo(%q, %q) = %q, want %q", name, version, info.Version, version) + } + + expectedTime := time.Date(2019, 1, 30, 0, 0, 0, 0, time.UTC) + if info.Time != expectedTime { + t.Errorf("VersionInfo.Time for GetInfo(%q, %q) = %v, want %v", name, version, info.Time, expectedTime) + } +} + +func TestGetInfoVersionDoesNotExist(t *testing.T) { + teardownTestCase, testCase := setupTestCase(t) + defer teardownTestCase(t) + + name := "my/module" + version := "v3.0.0" + info, _ := testCase.proxyClient.GetInfo(name, version) + if info != nil { + t.Errorf("GetInfo(%q, %q) = %v, want %v", name, version, info, nil) + } +} diff --git a/internal/proxyclient/testdata/modproxy/main.go b/internal/proxyclient/testdata/modproxy/main.go new file mode 100644 index 00000000..219fb0b7 --- /dev/null +++ b/internal/proxyclient/testdata/modproxy/main.go @@ -0,0 +1,28 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// modproxy runs a local module proxy for testing. It implements the Module +// proxy protocol described at `go help goproxy` by serving files stored at +// ./proxy. The following modules are supported by this proxy: +// my/module v1.0.0 +// my/module v1.1.0 +// my/module/2 v12.0.0 +package main + +import ( + "fmt" + "log" + "net/http" + "path/filepath" +) + +var proxyURL, _ = filepath.Abs("proxy") + +func main() { + http.Handle("/", http.FileServer(http.Dir(proxyURL))) + + addr := ":7000" + log.Println(fmt.Sprintf("Listening on http://localhost%s", addr)) + log.Fatal(http.ListenAndServe(addr, nil)) +} diff --git a/internal/testdata/modproxy/proxy/my/module/@v/list b/internal/proxyclient/testdata/modproxy/proxy/my/module/@v/list similarity index 100% rename from internal/testdata/modproxy/proxy/my/module/@v/list rename to internal/proxyclient/testdata/modproxy/proxy/my/module/@v/list diff --git a/internal/testdata/modproxy/proxy/my/module/@v/v1.0.0.info b/internal/proxyclient/testdata/modproxy/proxy/my/module/@v/v1.0.0.info similarity index 100% rename from internal/testdata/modproxy/proxy/my/module/@v/v1.0.0.info rename to internal/proxyclient/testdata/modproxy/proxy/my/module/@v/v1.0.0.info diff --git a/internal/testdata/modproxy/proxy/my/module/@v/v1.0.0.mod b/internal/proxyclient/testdata/modproxy/proxy/my/module/@v/v1.0.0.mod similarity index 100% rename from internal/testdata/modproxy/proxy/my/module/@v/v1.0.0.mod rename to internal/proxyclient/testdata/modproxy/proxy/my/module/@v/v1.0.0.mod diff --git a/internal/testdata/modproxy/proxy/my/module/@v/v1.0.0.zip b/internal/proxyclient/testdata/modproxy/proxy/my/module/@v/v1.0.0.zip similarity index 100% rename from internal/testdata/modproxy/proxy/my/module/@v/v1.0.0.zip rename to internal/proxyclient/testdata/modproxy/proxy/my/module/@v/v1.0.0.zip diff --git a/internal/testdata/modproxy/proxy/my/module/@v/v1.1.0.info b/internal/proxyclient/testdata/modproxy/proxy/my/module/@v/v1.1.0.info similarity index 100% rename from internal/testdata/modproxy/proxy/my/module/@v/v1.1.0.info rename to internal/proxyclient/testdata/modproxy/proxy/my/module/@v/v1.1.0.info diff --git a/internal/testdata/modproxy/proxy/my/module/@v/v1.1.0.mod b/internal/proxyclient/testdata/modproxy/proxy/my/module/@v/v1.1.0.mod similarity index 100% rename from internal/testdata/modproxy/proxy/my/module/@v/v1.1.0.mod rename to internal/proxyclient/testdata/modproxy/proxy/my/module/@v/v1.1.0.mod diff --git a/internal/testdata/modproxy/proxy/my/module/@v/v1.1.0.zip b/internal/proxyclient/testdata/modproxy/proxy/my/module/@v/v1.1.0.zip similarity index 100% rename from internal/testdata/modproxy/proxy/my/module/@v/v1.1.0.zip rename to internal/proxyclient/testdata/modproxy/proxy/my/module/@v/v1.1.0.zip diff --git a/internal/testdata/modproxy/proxy/my/module/@v/v1.1.1.info b/internal/proxyclient/testdata/modproxy/proxy/my/module/@v/v1.1.1.info similarity index 100% rename from internal/testdata/modproxy/proxy/my/module/@v/v1.1.1.info rename to internal/proxyclient/testdata/modproxy/proxy/my/module/@v/v1.1.1.info diff --git a/internal/testdata/modproxy/proxy/my/module/@v/v1.1.1.mod b/internal/proxyclient/testdata/modproxy/proxy/my/module/@v/v1.1.1.mod similarity index 100% rename from internal/testdata/modproxy/proxy/my/module/@v/v1.1.1.mod rename to internal/proxyclient/testdata/modproxy/proxy/my/module/@v/v1.1.1.mod diff --git a/internal/testdata/modproxy/proxy/my/module/@v/v1.1.1.zip b/internal/proxyclient/testdata/modproxy/proxy/my/module/@v/v1.1.1.zip similarity index 100% rename from internal/testdata/modproxy/proxy/my/module/@v/v1.1.1.zip rename to internal/proxyclient/testdata/modproxy/proxy/my/module/@v/v1.1.1.zip diff --git a/internal/testdata/modproxy/proxy/my/module/v2/@v/list b/internal/proxyclient/testdata/modproxy/proxy/my/module/v2/@v/list similarity index 100% rename from internal/testdata/modproxy/proxy/my/module/v2/@v/list rename to internal/proxyclient/testdata/modproxy/proxy/my/module/v2/@v/list diff --git a/internal/testdata/modproxy/proxy/my/module/v2/@v/v2.0.0.info b/internal/proxyclient/testdata/modproxy/proxy/my/module/v2/@v/v2.0.0.info similarity index 100% rename from internal/testdata/modproxy/proxy/my/module/v2/@v/v2.0.0.info rename to internal/proxyclient/testdata/modproxy/proxy/my/module/v2/@v/v2.0.0.info diff --git a/internal/testdata/modproxy/proxy/my/module/v2/@v/v2.0.0.mod b/internal/proxyclient/testdata/modproxy/proxy/my/module/v2/@v/v2.0.0.mod similarity index 100% rename from internal/testdata/modproxy/proxy/my/module/v2/@v/v2.0.0.mod rename to internal/proxyclient/testdata/modproxy/proxy/my/module/v2/@v/v2.0.0.mod diff --git a/internal/testdata/modproxy/proxy/my/module/v2/@v/v2.0.0.zip b/internal/proxyclient/testdata/modproxy/proxy/my/module/v2/@v/v2.0.0.zip similarity index 100% rename from internal/testdata/modproxy/proxy/my/module/v2/@v/v2.0.0.zip rename to internal/proxyclient/testdata/modproxy/proxy/my/module/v2/@v/v2.0.0.zip diff --git a/internal/testdata/modproxy/main.go b/internal/testdata/modproxy/main.go deleted file mode 100644 index 55273e13..00000000 --- a/internal/testdata/modproxy/main.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package main - -import ( - "log" - "net/http" -) - -const proxyURL = "./proxy" - -func main() { - http.Handle("/", http.FileServer(http.Dir(proxyURL))) - - log.Fatal(http.ListenAndServe(":8080", nil)) -}