-
Notifications
You must be signed in to change notification settings - Fork 508
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
download: add list from go cli #336
Changes from all commits
c604da2
bbb569b
a629644
82f60c9
79a5d67
4379bcd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package download | ||
|
||
import ( | ||
"context" | ||
"io" | ||
|
||
"github.com/gomods/athens/pkg/errors" | ||
"github.com/gomods/athens/pkg/storage" | ||
) | ||
|
||
// Protocol is the download protocol which mirrors | ||
// the http requests that cmd/go makes to the proxy. | ||
type Protocol interface { | ||
// List implements GET /{module}/@v/list | ||
List(ctx context.Context, mod string) ([]string, error) | ||
|
||
// Info implements GET /{module}/@v/{version}.info | ||
Info(ctx context.Context, mod, ver string) ([]byte, error) | ||
|
||
// Latest implements GET /{module}/@latest | ||
Latest(ctx context.Context, mod string) (*storage.RevInfo, error) | ||
|
||
// GoMod implements GET /{module}/@v/{version}.mod | ||
GoMod(ctx context.Context, mod, ver string) ([]byte, error) | ||
|
||
// Zip implements GET /{module}/@v/{version}.zip | ||
Zip(ctx context.Context, mod, ver string) (io.ReadCloser, error) | ||
|
||
// Version is a helper method to get Info, GoMod, and Zip together. | ||
Version(ctx context.Context, mod, ver string) (*storage.Version, error) | ||
} | ||
|
||
type protocol struct { | ||
s storage.Backend | ||
dp Protocol | ||
} | ||
|
||
// New takes an upstream Protocol and storage | ||
// it always prefers storage, otherwise it goes to upstream | ||
// and fills the storage with the results. | ||
func New(dp Protocol, s storage.Backend) Protocol { | ||
return &protocol{dp: dp, s: s} | ||
} | ||
|
||
func (p *protocol) List(ctx context.Context, mod string) ([]string, error) { | ||
return p.dp.List(ctx, mod) | ||
} | ||
|
||
func (p *protocol) Info(ctx context.Context, mod, ver string) ([]byte, error) { | ||
const op errors.Op = "protocol.Info" | ||
v, err := p.s.Get(mod, ver) | ||
if errors.ErrNotFound(err) { | ||
v, err = p.fillCache(ctx, mod, ver) | ||
} | ||
if err != nil { | ||
return nil, errors.E(op, err) | ||
} | ||
|
||
return v.Info, nil | ||
} | ||
|
||
func (p *protocol) fillCache(ctx context.Context, mod, ver string) (*storage.Version, error) { | ||
const op errors.Op = "protocol.fillCache" | ||
v, err := p.dp.Version(ctx, mod, ver) | ||
if err != nil { | ||
return nil, errors.E(op, err) | ||
} | ||
err = p.s.Save(ctx, mod, ver, v.Mod, v.Zip, v.Info) | ||
if err != nil { | ||
return nil, errors.E(op, err) | ||
} | ||
|
||
return v, nil | ||
} | ||
|
||
func (p *protocol) Latest(ctx context.Context, mod string) (*storage.RevInfo, error) { | ||
return p.dp.Latest(ctx, mod) | ||
} | ||
|
||
func (p *protocol) GoMod(ctx context.Context, mod, ver string) ([]byte, error) { | ||
const op errors.Op = "protocol.GoMod" | ||
v, err := p.s.Get(mod, ver) | ||
if errors.ErrNotFound(err) { | ||
v, err = p.fillCache(ctx, mod, ver) | ||
} | ||
if err != nil { | ||
return nil, errors.E(op, err) | ||
} | ||
|
||
return v.Mod, nil | ||
} | ||
|
||
func (p *protocol) Zip(ctx context.Context, mod, ver string) (io.ReadCloser, error) { | ||
const op errors.Op = "protocol.Zip" | ||
v, err := p.s.Get(mod, ver) | ||
if errors.ErrNotFound(err) { | ||
v, err = p.fillCache(ctx, mod, ver) | ||
} | ||
if err != nil { | ||
return nil, errors.E(op, err) | ||
} | ||
|
||
return v.Zip, nil | ||
} | ||
|
||
func (p *protocol) Version(ctx context.Context, mod, ver string) (*storage.Version, error) { | ||
return p.dp.Version(ctx, mod, ver) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
package goget | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"os/exec" | ||
"strings" | ||
"time" | ||
|
||
"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/module" | ||
"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 `json:"omitempty"` | ||
Time time.Time | ||
} | ||
|
||
func (gg *goget) Info(ctx context.Context, mod string, ver string) ([]byte, error) { | ||
const op errors.Op = "goget.Info" | ||
v, err := gg.Version(ctx, mod, ver) | ||
if err != nil { | ||
return nil, errors.E(op) | ||
} | ||
v.Zip.Close() | ||
|
||
return v.Info, 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 = module.Dummy(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) | ||
} | ||
|
||
// ugly hack until go cli implements -quiet flag. | ||
// https://github.com/golang/go/issues/26628 | ||
if bytes.HasPrefix(bts, []byte("go: finding")) { | ||
bts = bts[bytes.Index(bts, []byte{'\n'}):] | ||
} | ||
|
||
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" | ||
v, err := gg.Version(ctx, mod, ver) | ||
if err != nil { | ||
return nil, errors.E(op) | ||
} | ||
v.Zip.Close() | ||
|
||
return v.Mod, nil | ||
} | ||
|
||
func (gg *goget) Zip(ctx context.Context, mod, ver string) (io.ReadCloser, error) { | ||
const op errors.Op = "goget.Info" | ||
v, err := gg.Version(ctx, mod, ver) | ||
if err != nil { | ||
return nil, errors.E(op) | ||
} | ||
|
||
return v.Zip, nil | ||
} | ||
|
||
func (gg *goget) Version(ctx context.Context, mod, ver string) (*storage.Version, error) { | ||
const op errors.Op = "goget.Version" | ||
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, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package goget | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
) | ||
|
||
type testCase struct { | ||
name string | ||
mod string | ||
version string | ||
} | ||
|
||
// TODO(marwan): we should create Test Repos under github.com/gomods | ||
// so we can get reproducible results from live VCS repos. | ||
// For now, I cannot test that github.com/pkg/errors returns v0.8.0 | ||
// from goget.Latest, because they could very well introduce a new tag | ||
// in the near future. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sounds like a great idea! mind creating an issue for it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yep There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we update the comment to ref: #340 |
||
var tt = []testCase{ | ||
{"basic list", "github.com/pkg/errors", "latest"}, | ||
{"list non tagged", "github.com/marwan-at-work/gowatch", "latest"}, | ||
{"list vanity", "golang.org/x/tools", "latest"}, | ||
} | ||
|
||
func TestList(t *testing.T) { | ||
dp := New() | ||
ctx := context.Background() | ||
|
||
for _, tc := range tt { | ||
t.Run(tc.name, func(t *testing.T) { | ||
_, err := dp.List(ctx, tc.mod) // TODO ensure list is correct per TODO above. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will this be in this PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably after |
||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package errors | ||
|
||
// ErrNotFound helper function for KindNotFound | ||
func ErrNotFound(err error) bool { | ||
return Kind(err) == KindNotFound | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the name and the short name be the same? I'm not familiar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@robjloranger the name is meant to be the full sha. But it should work with the short version. I'm currently testing that.