Skip to content

Commit

Permalink
download: add list from go cli
Browse files Browse the repository at this point in the history
  • Loading branch information
marwan-at-work committed Jul 26, 2018
1 parent 1dfbf79 commit 5379fa0
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 10 deletions.
4 changes: 3 additions & 1 deletion cmd/proxy/actions/app_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package actions
import (
"github.com/gobuffalo/buffalo"
"github.com/gomods/athens/pkg/download"
"github.com/gomods/athens/pkg/download/goget"
"github.com/gomods/athens/pkg/log"
"github.com/gomods/athens/pkg/module"
"github.com/gomods/athens/pkg/storage"
Expand All @@ -16,8 +17,9 @@ func addProxyRoutes(
) error {
app.GET("/", proxyHomeHandler)

dp := goget.New()
// Download Protocol
app.GET(download.PathList, download.ListHandler(storage, proxy))
app.GET(download.PathList, download.ListHandler(dp, lggr, proxy))
app.GET(download.PathVersionInfo, cacheMissHandler(download.VersionInfoHandler(storage, proxy), app.Worker, mf, lggr))
app.GET(download.PathVersionModule, cacheMissHandler(download.VersionModuleHandler(storage, proxy), app.Worker, mf, lggr))
app.GET(download.PathVersionZip, cacheMissHandler(download.VersionZipHandler(storage, proxy, lggr), app.Worker, mf, lggr))
Expand Down
6 changes: 6 additions & 0 deletions pkg/config/env/go.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ func GoPath() (string, error) {

return env, nil
}

// GoBinPath returns the path to Go's executable binary
// this binary must have Go Modules enabled.
func GoBinPath() string {
return envy.Get("GO_BIN_PATH", "vgo")
}
17 changes: 17 additions & 0 deletions pkg/download/download.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package download

import (
"context"
"io"

"github.com/gomods/athens/pkg/storage"
)

// Protocol is the download protocol defined by cmd/go
type Protocol interface {
List(ctx context.Context, module string) ([]string, error)
Info(ctx context.Context, module, version string) (*storage.RevInfo, error)
Latest(ctx context.Context, module string) (*storage.RevInfo, error)
GoMod(ctx context.Context, module, version string) ([]byte, error)
Zip(ctx context.Context, module, version string) (io.ReadCloser, error)
}
173 changes: 173 additions & 0 deletions pkg/download/goget/goget.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package goget

import (
"context"
"encoding/json"
"fmt"
"io"
"os/exec"
"path/filepath"
"strings"
"time"

"github.com/gomods/athens/pkg/module"

"github.com/gomods/athens/pkg/config"
"github.com/gomods/athens/pkg/config/env"
"github.com/gomods/athens/pkg/download"
"github.com/gomods/athens/pkg/errors"
"github.com/gomods/athens/pkg/storage"
"github.com/spf13/afero"
)

// New returns a download protocol by using
// go get. You must have a modules supported
// go binary for this to work.
func New() download.Protocol {
return &goget{
goBinPath: env.GoBinPath(),
fs: afero.NewOsFs(),
}
}

type goget struct {
goBinPath string
fs afero.Fs
}

func (gg *goget) List(ctx context.Context, mod string) ([]string, error) {
const op errors.Op = "goget.List"
lr, err := gg.list(op, mod)
if err != nil {
return nil, err
}

return lr.Versions, nil
}

type listResp struct {
Path string
Version string
Versions []string
Time time.Time
}

// dummyMod creates a random go module so that listing
// a depdencny's upstream versions can run from the go cli.
func dummyMod(fs afero.Fs, repoRoot string) error {
const op errors.Op = "goget.dummyMod"
gomodPath := filepath.Join(repoRoot, "go.mod")
gomodContent := []byte("module mod")
if err := afero.WriteFile(fs, gomodPath, gomodContent, 0666); err != nil {
return errors.E(op, err)
}
sourcePath := filepath.Join(repoRoot, "mod.go")
sourceContent := []byte(`package mod`)
if err := afero.WriteFile(fs, sourcePath, sourceContent, 0666); err != nil {
return errors.E(op, err)
}
return nil
}

func (gg *goget) Info(ctx context.Context, mod string, ver string) (*storage.RevInfo, error) {
const op errors.Op = "goget.Info"
fetcher, _ := module.NewGoGetFetcher(gg.goBinPath, gg.fs) // TODO: remove err from func call
ref, err := fetcher.Fetch(mod, ver)
if err != nil {
return nil, errors.E(op, err)
}
defer ref.Clear()
v, err := ref.Read()
if err != nil {
return nil, errors.E(op, err)
}
defer v.Zip.Close()

var ri storage.RevInfo
err = json.Unmarshal(v.Info, &ri)
if err != nil {
return nil, errors.E(op, err)
}

return &ri, nil
}

func (gg *goget) Latest(ctx context.Context, mod string) (*storage.RevInfo, error) {
const op errors.Op = "goget.Latest"
lr, err := gg.list(op, mod)
if err != nil {
return nil, err
}

pseudoInfo := strings.Split(lr.Version, "-")
if len(pseudoInfo) < 3 {
return nil, errors.E(op, fmt.Errorf("malformed pseudoInfo %v", lr.Version))
}
return &storage.RevInfo{
Name: pseudoInfo[2],
Short: pseudoInfo[2],
Time: lr.Time,
Version: lr.Version,
}, nil
}

func (gg *goget) list(op errors.Op, mod string) (*listResp, error) {
hackyPath, err := afero.TempDir(gg.fs, "", "hackymod")
if err != nil {
return nil, errors.E(op, err)
}
defer gg.fs.RemoveAll(hackyPath)
err = dummyMod(gg.fs, hackyPath)
cmd := exec.Command(
gg.goBinPath,
"list", "-m", "-versions", "-json",
config.FmtModVer(mod, "latest"),
)
cmd.Dir = hackyPath

bts, err := cmd.CombinedOutput()
if err != nil {
errFmt := fmt.Errorf("%v: %s", err, bts)
return nil, errors.E(op, errFmt)
}

var lr listResp
err = json.Unmarshal(bts, &lr)
if err != nil {
return nil, errors.E(op, err)
}

return &lr, nil
}

func (gg *goget) GoMod(ctx context.Context, mod string, ver string) ([]byte, error) {
const op errors.Op = "goget.Info"
fetcher, _ := module.NewGoGetFetcher(gg.goBinPath, gg.fs) // TODO: remove err from func call
ref, err := fetcher.Fetch(mod, ver)
if err != nil {
return nil, errors.E(op, err)
}
defer ref.Clear()
v, err := ref.Read()
if err != nil {
return nil, errors.E(op, err)
}
defer v.Zip.Close()

return v.Mod, nil
}

func (gg *goget) Zip(ctx context.Context, mod string, ver string) (io.ReadCloser, error) {
const op errors.Op = "goget.Info"
fetcher, _ := module.NewGoGetFetcher(gg.goBinPath, gg.fs) // TODO: remove err from func call
ref, err := fetcher.Fetch(mod, ver)
if err != nil {
return nil, errors.E(op, err)
}
v, err := ref.Read()
if err != nil {
return nil, errors.E(op, err)
}

return v.Zip, nil
}
20 changes: 11 additions & 9 deletions pkg/download/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,31 @@ import (
"github.com/bketelsen/buffet"
"github.com/gobuffalo/buffalo"
"github.com/gobuffalo/buffalo/render"
"github.com/gomods/athens/pkg/errors"
"github.com/gomods/athens/pkg/log"
"github.com/gomods/athens/pkg/paths"
"github.com/gomods/athens/pkg/storage"
errs "github.com/pkg/errors"
)

// PathList URL.
const PathList = "/{module:.+}/@v/list"

// ListHandler implements GET baseURL/module/@v/list
func ListHandler(lister storage.Lister, eng *render.Engine) func(c buffalo.Context) error {
func ListHandler(dp Protocol, lggr *log.Logger, eng *render.Engine) func(c buffalo.Context) error {
return func(c buffalo.Context) error {
sp := buffet.SpanFromContext(c)
sp.SetOperationName("listHandler")
mod, err := paths.GetModule(c)
if err != nil {
return err
lggr.SystemErr(err)
return c.Render(500, nil)
}
versions, err := lister.List(c, mod)
if storage.IsNotFoundError(err) {
return c.Render(http.StatusNotFound, eng.JSON(err.Error()))
} else if err != nil {
return errs.WithStack(err)

versions, err := dp.List(c, mod)
if err != nil {
lggr.SystemErr(err)
return c.Render(errors.Kind(err), eng.JSON(errors.KindText(err)))
}

return c.Render(http.StatusOK, eng.String(strings.Join(versions, "\n")))
}
}

0 comments on commit 5379fa0

Please sign in to comment.