* merge list from storage with the one from go list

* fix and rename

* move stuff to semver pkg

* fix gofmt

* move union out of semver pkg

* add tests

* fix err msg

* fix tests

* error handling

* error handling, go list refactoring

* fix list

* cleanup

* fix gofmt
This commit is contained in:
marpio
2018-09-13 23:12:17 +02:00
committed by Aaron Schlesinger
parent d745dfbae4
commit 6af44242fb
13 changed files with 322 additions and 109 deletions
+49 -77
View File
@@ -1,21 +1,13 @@
package download
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"os/exec"
"time"
"github.com/gomods/athens/pkg/config"
"github.com/gomods/athens/pkg/errors"
"github.com/gomods/athens/pkg/module"
"github.com/gomods/athens/pkg/observ"
"github.com/gomods/athens/pkg/stash"
"github.com/gomods/athens/pkg/storage"
"github.com/spf13/afero"
)
// Protocol is the download protocol which mirrors
@@ -42,10 +34,9 @@ type Wrapper func(Protocol) Protocol
// Opts specifies download protocol options to avoid long func signature.
type Opts struct {
Storage storage.Backend
Stasher stash.Stasher
GoBinPath string
Fs afero.Fs
Storage storage.Backend
Stasher stash.Stasher
Lister UpstreamLister
}
// New returns a full implementation of the download.Protocol
@@ -54,7 +45,7 @@ type Opts struct {
// The wrappers are applied in order, meaning the last wrapper
// passed is the Protocol that gets hit first.
func New(opts *Opts, wrappers ...Wrapper) Protocol {
var p Protocol = &protocol{opts.Storage, opts.Stasher, opts.GoBinPath, opts.Fs}
var p Protocol = &protocol{opts.Storage, opts.Stasher, opts.Lister}
for _, w := range wrappers {
p = w(p)
}
@@ -63,88 +54,49 @@ func New(opts *Opts, wrappers ...Wrapper) Protocol {
}
type protocol struct {
s storage.Backend
stasher stash.Stasher
goBinPath string
fs afero.Fs
s storage.Backend
stasher stash.Stasher
lister UpstreamLister
}
func (p *protocol) List(ctx context.Context, mod string) ([]string, error) {
const op errors.Op = "protocol.List"
ctx, span := observ.StartSpan(ctx, op.String())
defer span.End()
lr, err := p.list(op, mod)
if err != nil {
return nil, err
strList, sErr := p.s.List(ctx, mod)
// if we got an unexpected storage err then we can not guarantee that the end result contains all versions
// a tag or repo could have been deleted
if sErr != nil {
return nil, errors.E(op, sErr)
}
_, goList, goErr := p.lister.List(mod)
isUnexpGoErr := goErr != nil && !errors.IsRepoNotFoundErr(goErr)
// if i.e. github is unavailable we should fail as well so that the behavior of the proxy is stable.
// otherwise we will get different results the next time because i.e. GH is up again
if isUnexpGoErr {
return nil, errors.E(op, goErr)
}
return lr.Versions, nil
isRepoNotFoundErr := goErr != nil && errors.IsRepoNotFoundErr(goErr)
storageEmpty := len(strList) == 0
if isRepoNotFoundErr && storageEmpty {
return nil, errors.E(op, errors.M(mod), errors.KindNotFound, goErr)
}
return union(goList, strList), nil
}
func (p *protocol) Latest(ctx context.Context, mod string) (*storage.RevInfo, error) {
const op errors.Op = "protocol.Latest"
ctx, span := observ.StartSpan(ctx, op.String())
defer span.End()
lr, err := p.list(op, mod)
lr, _, err := p.lister.List(mod)
if err != nil {
return nil, errors.E(op, err)
}
return &storage.RevInfo{
Time: lr.Time,
Version: lr.Version,
}, nil
}
type listResp struct {
Path string
Version string
Versions []string `json:",omitempty"`
Time time.Time
}
func (p *protocol) list(op errors.Op, mod string) (*listResp, error) {
hackyPath, err := afero.TempDir(p.fs, "", "hackymod")
if err != nil {
return nil, errors.E(op, err)
}
defer p.fs.RemoveAll(hackyPath)
err = module.Dummy(p.fs, hackyPath)
if err != nil {
return nil, errors.E(op, err)
}
cmd := exec.Command(
p.goBinPath,
"list", "-m", "-versions", "-json",
config.FmtModVer(mod, "latest"),
)
cmd.Dir = hackyPath
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
cmd.Stdout = stdout
cmd.Stderr = stderr
gopath, err := afero.TempDir(p.fs, "", "athens")
if err != nil {
return nil, errors.E(op, err)
}
defer module.ClearFiles(p.fs, gopath)
cmd.Env = module.PrepareEnv(gopath)
err = cmd.Run()
if err != nil {
err = fmt.Errorf("%v: %s", err, stderr)
return nil, errors.E(op, err)
}
var lr listResp
err = json.NewDecoder(stdout).Decode(&lr)
if err != nil {
return nil, errors.E(op, err)
}
return &lr, nil
return lr, nil
}
func (p *protocol) Info(ctx context.Context, mod, ver string) ([]byte, error) {
@@ -203,3 +155,23 @@ func (p *protocol) Zip(ctx context.Context, mod, ver string) (io.ReadCloser, err
return zip, nil
}
// union concatenates two version lists and removes duplicates
func union(list1, list2 []string) []string {
if list1 == nil {
list1 = []string{}
}
if list2 == nil {
list2 = []string{}
}
list := append(list1, list2...)
unique := []string{}
m := make(map[string]struct{})
for _, v := range list {
if _, ok := m[v]; !ok {
unique = append(unique, v)
m[v] = struct{}{}
}
}
return unique
}