internal/modules: add package
Package modules is added to assist with fetching modules at version via
the proxy and writing them to disk.
Change-Id: I90f734c0f38af8925dd3f6bd6a03e586f49c03bf
Reviewed-on: https://go-review.googlesource.com/c/pkgsite-metrics/+/464638
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Auto-Submit: Julie Qiu <julieqiu@google.com>
Run-TryBot: Julie Qiu <julieqiu@google.com>
Reviewed-by: Julie Qiu <julieqiu@google.com>
Run-TryBot: Julie Qiu <julie@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/internal/derrors/derrors.go b/internal/derrors/derrors.go
index 9dcb42c..f9514b7 100644
--- a/internal/derrors/derrors.go
+++ b/internal/derrors/derrors.go
@@ -35,6 +35,10 @@
// ProxyError is used to capture non-actionable server errors returned from the proxy.
ProxyError = errors.New("proxy error")
+
+ // ScanModuleOSError is used to capture issues with writing the module zip
+ // to disk during the scan setup process. This is not an error with vulncheck.
+ ScanModuleOSError = errors.New("scan module OS error")
)
// Wrap adds context to the error and allows
diff --git a/internal/modules/modules.go b/internal/modules/modules.go
new file mode 100644
index 0000000..5bc51ab
--- /dev/null
+++ b/internal/modules/modules.go
@@ -0,0 +1,75 @@
+// Copyright 2022 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 modules assists in working with modules.
+package modules
+
+import (
+ "archive/zip"
+ "context"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "golang.org/x/pkgsite-metrics/internal/derrors"
+ "golang.org/x/pkgsite-metrics/internal/log"
+ "golang.org/x/pkgsite-metrics/internal/proxy"
+)
+
+// Download fetches module at version via proxyClient and writes the modules
+// down to disk at dir.
+func Download(ctx context.Context, module, version, dir string, proxyClient *proxy.Client, stripModulePrefix bool) error {
+ zipr, err := proxyClient.Zip(ctx, module, version)
+ if err != nil {
+ return fmt.Errorf("%v: %w", err, derrors.ProxyError)
+ }
+ log.Debugf(ctx, "writing module zip: %s@%s", module, version)
+ stripPrefix := ""
+ if stripModulePrefix {
+ stripPrefix = module + "@" + version + "/"
+ }
+ if err := writeZip(zipr, dir, stripPrefix); err != nil {
+ return fmt.Errorf("%v: %w", err, derrors.ScanModuleOSError)
+ }
+ return nil
+}
+
+func writeZip(r *zip.Reader, destination, stripPrefix string) error {
+ for _, f := range r.File {
+ name := strings.TrimPrefix(f.Name, stripPrefix)
+ fpath := filepath.Join(destination, name)
+ if !strings.HasPrefix(fpath, filepath.Clean(destination)+string(os.PathSeparator)) {
+ return fmt.Errorf("%s is an illegal filepath", fpath)
+ }
+ if f.FileInfo().IsDir() {
+ if err := os.MkdirAll(fpath, os.ModePerm); err != nil {
+ return err
+ }
+ continue
+ }
+ if err := os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
+ return err
+ }
+ outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
+ if err != nil {
+ return err
+ }
+ rc, err := f.Open()
+ if err != nil {
+ return err
+ }
+ if _, err := io.Copy(outFile, rc); err != nil {
+ return err
+ }
+ if err := outFile.Close(); err != nil {
+ return err
+ }
+ if err := rc.Close(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/internal/modules/modules_test.go b/internal/modules/modules_test.go
new file mode 100644
index 0000000..2e9ccb7
--- /dev/null
+++ b/internal/modules/modules_test.go
@@ -0,0 +1,40 @@
+// Copyright 2022 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 modules
+
+import (
+ "context"
+ "os"
+ "testing"
+
+ "golang.org/x/pkgsite-metrics/internal/proxy"
+)
+
+func TestDownload(t *testing.T) {
+ t.Skip()
+
+ tempDir, err := os.MkdirTemp("", "testModuleDownload")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ defer func() {
+ if err := os.RemoveAll(tempDir); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ proxyClient, err := proxy.New("https://proxy.golang.org")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Use golang.org/x/net@v0.0.0-20221012135044-0b7e1fb9d458
+ module := "golang.org/x/net"
+ version := "v0.0.0-20221012135044-0b7e1fb9d458"
+ if err := Download(context.Background(), module, version, tempDir, proxyClient, true); err != nil {
+ t.Errorf("failed to download %v@%v: %v", module, version, err)
+ }
+}