Add support for ephemeral tags.
This commit is contained in:
@@ -27,6 +27,8 @@ type api struct {
|
||||
vcsPaths []vcsPath
|
||||
stores []store.Store
|
||||
semc chan struct{}
|
||||
|
||||
ephemeralTagStorage *vcs.EphemeralTagStorage
|
||||
}
|
||||
|
||||
type vcsPath struct {
|
||||
@@ -42,6 +44,7 @@ var (
|
||||
apiInfo = regexp.MustCompile(`^/(?P<module>.*)/@v/(?P<version>.*).info$`)
|
||||
apiMod = regexp.MustCompile(`^/(?P<module>.*)/@v/(?P<version>.*).mod$`)
|
||||
apiZip = regexp.MustCompile(`^/(?P<module>.*)/@v/(?P<version>.*).zip$`)
|
||||
apiTag = regexp.MustCompile(`^/tags/(?P<module>.*)/@v/(?P<version>.*)$`)
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -86,6 +89,28 @@ func Git(prefix string, auth string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// GitWithEphemeralTags configures API to use a specific git client when trying
|
||||
// to download a repository with the given prefix. Auth string can be a path to
|
||||
// the SSK key, or a colon-separated username:password string.
|
||||
func GitWithEphemeralTags(prefix string, auth string) Option {
|
||||
|
||||
storage := vcs.NewEphemeralTagStorage()
|
||||
|
||||
a := vcs.Key(auth)
|
||||
if creds := strings.SplitN(auth, ":", 2); len(creds) == 2 {
|
||||
a = vcs.Password(creds[0], creds[1])
|
||||
}
|
||||
return func(api *api) {
|
||||
api.ephemeralTagStorage = storage
|
||||
api.vcsPaths = append(api.vcsPaths, vcsPath{
|
||||
prefix: prefix,
|
||||
vcs: func(module string) vcs.VCS {
|
||||
return vcs.NewGitWithEphemeralTags(api.log, api.gitdir, module, a, storage)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func CustomVCS(prefix string, cmd string) Option {
|
||||
return func(api *api) {
|
||||
api.vcsPaths = append(api.vcsPaths, vcsPath{
|
||||
@@ -151,6 +176,7 @@ func (api *api) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
{"info", apiInfo, api.info},
|
||||
{"api", apiMod, api.mod},
|
||||
{"zip", apiZip, api.zip},
|
||||
{"tag", apiTag, api.tag},
|
||||
} {
|
||||
if m := route.regexp.FindStringSubmatch(r.URL.Path); m != nil {
|
||||
module, version := m[1], ""
|
||||
@@ -301,3 +327,32 @@ func (api *api) delete(w http.ResponseWriter, r *http.Request, module, version s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (api *api) tag(w http.ResponseWriter, r *http.Request, module, version string) {
|
||||
api.log("api.tag", "module", module, "version", version)
|
||||
|
||||
taggable, ok := api.vcs(r.Context(), module).(vcs.Taggable)
|
||||
if !ok {
|
||||
err := fmt.Errorf("repository for module %v is not taggable", module)
|
||||
api.log("api.tag", "module", module, "version", version, "error", err)
|
||||
httpErrors.Add(module, 1)
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
req := struct {
|
||||
Short string `json:"short"`
|
||||
}{}
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
api.log("api.tag", "module", module, "version", version, "error", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
taggable.Tag(vcs.Version(version), req.Short)
|
||||
|
||||
// TODO(bilus): Response
|
||||
}
|
||||
|
||||
@@ -143,6 +143,11 @@ func isVendoredPackage(name string) bool {
|
||||
}
|
||||
|
||||
func (g *gitVCS) Zip(ctx context.Context, version Version) (io.ReadCloser, error) {
|
||||
dirName := g.module + "@" + string(version)
|
||||
return g.zipUnder(ctx, version, dirName)
|
||||
}
|
||||
|
||||
func (g *gitVCS) zipUnder(ctx context.Context, version Version, dirName string) (io.ReadCloser, error) {
|
||||
g.log("gitVCS.Zip", "module", g.module, "version", version)
|
||||
ci, err := g.commit(ctx, version)
|
||||
if err != nil {
|
||||
@@ -203,7 +208,7 @@ func (g *gitVCS) Zip(ctx context.Context, version Version) (io.ReadCloser, error
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
w, err := zw.Create(filepath.Join(g.module+"@"+string(version), name))
|
||||
w, err := zw.Create(filepath.Join(dirName, name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
130
pkg/vcs/tags.go
Normal file
130
pkg/vcs/tags.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package vcs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ephemeralTag struct {
|
||||
semVer Version
|
||||
short string
|
||||
}
|
||||
|
||||
type EphemeralTagStorage struct {
|
||||
tagsByModule map[moduleName][]ephemeralTag
|
||||
}
|
||||
|
||||
func NewEphemeralTagStorage() *EphemeralTagStorage {
|
||||
return &EphemeralTagStorage{
|
||||
tagsByModule: make(map[moduleName][]ephemeralTag),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *EphemeralTagStorage) Tag(module string, semVer Version, short string) {
|
||||
tags := s.tagsByModule[module]
|
||||
tmp := tags[:0]
|
||||
for _, t := range tags {
|
||||
if t.semVer != semVer {
|
||||
tmp = append(tmp, t)
|
||||
}
|
||||
}
|
||||
s.tagsByModule[module] = append(tmp, ephemeralTag{semVer, short})
|
||||
}
|
||||
|
||||
func (s *EphemeralTagStorage) tags(module string) []ephemeralTag {
|
||||
return s.tagsByModule[module]
|
||||
}
|
||||
|
||||
type moduleName = string
|
||||
|
||||
type taggableVCS struct {
|
||||
wrapped *gitVCS
|
||||
module string
|
||||
storage *EphemeralTagStorage
|
||||
}
|
||||
|
||||
type Taggable interface {
|
||||
Tag(semVer Version, short string)
|
||||
}
|
||||
|
||||
// NewGitWithEphemeralTags return a go-git VCS client implementation that
|
||||
// provides information about the specific module using the given
|
||||
// authentication mechanism while adding support to ephemeral tags.
|
||||
func NewGitWithEphemeralTags(l logger, dir string, module string, auth Auth, storage *EphemeralTagStorage) VCS {
|
||||
git := NewGit(l, dir, module, auth).(*gitVCS)
|
||||
return &taggableVCS{
|
||||
wrapped: git,
|
||||
module: module,
|
||||
storage: storage,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *taggableVCS) Tag(semVer Version, short string) {
|
||||
v.storage.Tag(v.module, semVer, short)
|
||||
}
|
||||
|
||||
func (v *taggableVCS) List(ctx context.Context) ([]Version, error) {
|
||||
remoteVersions, err := v.wrapped.List(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tags := v.storage.tags(v.module)
|
||||
// Remote versions win.
|
||||
return appendEphemeralVersion(remoteVersions, tags...), nil
|
||||
}
|
||||
|
||||
func appendEphemeralVersion(versions []Version, tags ...ephemeralTag) []Version {
|
||||
ephemeral := make([]Version, 0)
|
||||
for _, tag := range tags {
|
||||
if !versionExists(versions, tag.semVer) {
|
||||
ephemeral = append(ephemeral, tag.semVer)
|
||||
}
|
||||
}
|
||||
return append(versions, ephemeral...)
|
||||
}
|
||||
|
||||
func versionExists(versions []Version, v Version) bool {
|
||||
for _, v2 := range versions {
|
||||
if v == v2 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (v *taggableVCS) Timestamp(ctx context.Context, version Version) (time.Time, error) {
|
||||
version2, err := v.resolveVersion(ctx, version)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return v.wrapped.Timestamp(ctx, version2)
|
||||
}
|
||||
|
||||
func (v *taggableVCS) Zip(ctx context.Context, version Version) (io.ReadCloser, error) {
|
||||
version2, err := v.resolveVersion(ctx, version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Zip must contain the ephemeral version.
|
||||
dirName := v.module + "@" + string(version)
|
||||
return v.wrapped.zipUnder(ctx, version2, dirName)
|
||||
}
|
||||
|
||||
func (v *taggableVCS) resolveVersion(ctx context.Context, version Version) (Version, error) {
|
||||
for _, tag := range v.storage.tags(v.module) {
|
||||
if tag.semVer == version {
|
||||
// TODO(bilus): Duplicated in git.go.
|
||||
t, err := v.wrapped.Timestamp(ctx, Version("v0.0.0-20060102150405-"+tag.short))
|
||||
if err != nil {
|
||||
return Version(""), err
|
||||
}
|
||||
version2 := Version(fmt.Sprintf("v0.0.0-%s-%s", t.Format("20060102150405"), tag.short))
|
||||
|
||||
return version2, nil
|
||||
}
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
Reference in New Issue
Block a user