diff --git a/cmd/gomodproxy/main.go b/cmd/gomodproxy/main.go index f3cd2a7..cca2a24 100644 --- a/cmd/gomodproxy/main.go +++ b/cmd/gomodproxy/main.go @@ -97,6 +97,7 @@ func (f *listFlag) Set(s string) error { *f = append(*f, s); return nil } func main() { gitPaths := listFlag{} + vcsPaths := listFlag{} addr := flag.String("addr", ":0", "http server address") verbose := flag.Bool("v", false, "verbose logging") @@ -108,6 +109,7 @@ func main() { memLimit := flag.Int64("mem", 256, "in-memory cache size in MB") workers := flag.Int("workers", 1, "number of parallel VCS workers") flag.Var(&gitPaths, "git", "list of git settings") + flag.Var(&vcsPaths, "vcs", "list of custom VCS handlers") flag.Parse() @@ -130,6 +132,14 @@ func main() { } options = append(options, api.Log(logger)) + for _, path := range vcsPaths { + kv := strings.SplitN(path, ":", 2) + if len(kv) != 2 { + log.Fatal("bad VCS syntax:", path) + } + options = append(options, api.CustomVCS(kv[0], kv[1])) + } + for _, path := range gitPaths { kv := strings.SplitN(path, ":", 2) if len(kv) != 2 { diff --git a/go.sum b/go.sum index c80e604..9926502 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,7 @@ github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnG github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= diff --git a/pkg/api/api.go b/pkg/api/api.go index b533cd8..d65054f 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -86,6 +86,17 @@ func Git(prefix string, auth string) Option { } } +func CustomVCS(prefix string, cmd string) Option { + return func(api *api) { + api.vcsPaths = append(api.vcsPaths, vcsPath{ + prefix: prefix, + vcs: func(module string) vcs.VCS { + return vcs.NewCommand(api.log, cmd, module) + }, + }) + } +} + // Memory configures API to use in-memory cache for downloaded modules. func Memory(log logger, limit int64) Option { return func(api *api) { diff --git a/pkg/vcs/cmd.go b/pkg/vcs/cmd.go new file mode 100644 index 0000000..07b90d6 --- /dev/null +++ b/pkg/vcs/cmd.go @@ -0,0 +1,75 @@ +package vcs + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io" + "io/ioutil" + "os" + "os/exec" + "strconv" + "strings" + "time" +) + +type cmdVCS struct { + log logger + module string + cmd string +} + +func NewCommand(l logger, cmd string, module string) VCS { + return &cmdVCS{log: l, cmd: cmd, module: module} +} + +func (c *cmdVCS) List(ctx context.Context) ([]Version, error) { + b, err := c.exec(ctx, "MODULE="+c.module, "ACTION=list", "VERSION=latest", "FILEPATH="+c.module+"/@v/list") + if err != nil { + return nil, err + } + versions := []Version{} + for _, line := range strings.Split(string(b), "\n") { + versions = append(versions, Version(line)) + } + return versions, nil +} + +func (c *cmdVCS) Timestamp(ctx context.Context, version Version) (time.Time, error) { + b, err := c.exec(ctx, "MODULE="+c.module, "ACTION=timestamp", "VERSION="+version.String(), + "FILEPATH="+c.module+"/@v/"+version.String()+".info") + if err != nil { + return time.Time{}, err + } + info := struct { + Version string + Time time.Time + }{} + if json.Unmarshal(b, &info) == nil { + return info.Time, nil + } + if t, err := time.Parse(time.RFC3339, string(b)); err == nil { + return t, nil + } + if sec, err := strconv.ParseInt(string(b), 10, 64); err == nil { + return time.Unix(sec, 0), nil + } + return time.Time{}, errors.New("unknown time format") +} + +func (c *cmdVCS) Zip(ctx context.Context, version Version) (io.ReadCloser, error) { + b, err := c.exec(ctx, "MODULE="+c.module, "ACTION=zip", "VERSION="+version.String(), + "FILEPATH="+c.module+"/@v/"+version.String()+".zip") + if err != nil { + return nil, err + } + return ioutil.NopCloser(bytes.NewReader(b)), nil +} + +func (c *cmdVCS) exec(ctx context.Context, env ...string) ([]byte, error) { + cmd := exec.Command("sh", "-c", c.cmd) + cmd.Env = append(os.Environ(), env...) + cmd.Stderr = os.Stderr + return cmd.Output() +} diff --git a/pkg/vcs/vcs.go b/pkg/vcs/vcs.go index 0ce8eae..dd2ef8f 100644 --- a/pkg/vcs/vcs.go +++ b/pkg/vcs/vcs.go @@ -27,6 +27,11 @@ func (v Version) Hash() string { return fields[2] } +// String returns a string representation of a version +func (v Version) String() string { + return string(v) +} + // Module is a source code snapshot for which one can get the commit timestamp // or the actual ZIP with the source code in it. type Module interface {