forked from gomods/athens
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
download: add list from go cli (gomods#336)
* download: add list from go cli * download: include goget tests + hacky hack * download: move dummyMod to pkg/module * Olympus: pass dp and lggr to /list * download: add Version to interface * download: document Protocol
- Loading branch information
1 parent
8ef9d7f
commit 7b590b1
Showing
9 changed files
with
336 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
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. | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.