Skip to content
Permalink
Browse files

(module): Separate methods and add tests for Serve

  • Loading branch information...
erbesharat committed Sep 6, 2019
1 parent 4e4b6b1 commit 5a4c896acf51d4df29a5370177e43ddd4485ce56
Showing with 187 additions and 112 deletions.
  1. +3 −112 gomods.go
  2. +62 −0 gomods_test.go
  3. +122 −0 module.go
115 gomods.go
@@ -8,33 +8,15 @@ import (
"os"
"regexp"
"strings"

"github.com/gomods/athens/pkg/download"
"github.com/gomods/athens/pkg/download/addons"
"github.com/gomods/athens/pkg/module"
"github.com/gomods/athens/pkg/paths"
"github.com/gomods/athens/pkg/stash"
"github.com/gomods/athens/pkg/storage"
"github.com/gomods/athens/pkg/storage/fs"
"github.com/spf13/afero"
)

type Module struct {
Name string
Version string
FileExt string
}

type ModuleHandler interface {
fetch(r *http.Request, c Config) (*storage.Version, error)
storage(c Config) (storage.Backend, error)
dp(fetcher module.Fetcher, s storage.Backend, fs afero.Fs) download.Protocol
}

var gomodsRegex = regexp.MustCompile("(list|info|mod|zip)")
var modVersionRegex = regexp.MustCompile("(.*)\\.(info|mod|zip)")

// DefaultGoBinaryPath is the default Golang binary installed on the machine
var DefaultGoBinaryPath = os.Getenv("GOROOT") + "/bin/go"

// Serve handles the incoming requests and serves the module files like .mod and etc
func (conf *Config) Serve(w http.ResponseWriter, r *http.Request) error {
m := Module{}
if err := m.ParseImportPath(r.URL.Path); err != nil {
@@ -107,94 +89,3 @@ func (conf *Config) Serve(w http.ResponseWriter, r *http.Request) error {
return fmt.Errorf("the requested file's extension is not supported")
}
}

func (m Module) fetch(r *http.Request, c Config) (download.Protocol, error) {
fetcher, err := module.NewGoGetFetcher(c.GoBinary, c.Fs)
if err != nil {
return nil, err
}
s, err := m.storage(c)
if err != nil {
return nil, err
}
dp := m.dp(fetcher, s, c)
return dp, nil
}

func (m Module) storage(c Config) (storage.Backend, error) {
switch c.Cache.Type {
case "local":
// Check if cache storage path exists, if not create it
if _, err := os.Stat(c.Cache.Path); os.IsNotExist(err) {
if err = os.MkdirAll(c.Cache.Path, os.ModePerm); err != nil {
return nil, fmt.Errorf("couldn't create the cache storage directory on %s: %s", c.Cache.Path, err.Error())
}
}
s, err := fs.NewStorage(c.Cache.Path, afero.NewOsFs())
if err != nil {
return nil, fmt.Errorf("could not create new storage from os fs (%s)", err)
}
return s, nil
case "tmp":
s, err := fs.NewStorage(c.Cache.Path, afero.NewOsFs())
if err != nil {
return nil, fmt.Errorf("could not create new storage from os fs (%s)", err)
}
return s, nil
}
return nil, fmt.Errorf("Invalid storage config for gomods")
}

func (m Module) dp(fetcher module.Fetcher, s storage.Backend, c Config) download.Protocol {
lister := download.NewVCSLister(c.GoBinary, c.Fs)
st := stash.New(fetcher, s, stash.WithPool(c.Workers), stash.WithSingleflight)
dpOpts := &download.Opts{
Storage: s,
Stasher: st,
Lister: lister,
}
dp := download.New(dpOpts, addons.WithPool(c.Workers))
return dp
}

// ParseImportPath parses the request path and exports the
// module's import path, module's version and file extension
func (m *Module) ParseImportPath(path string) error {
if strings.Contains(path, "@latest") {
pathLatest := strings.Split(path, "/@")
m.Name, m.Version, m.FileExt = pathLatest[0][1:], "", pathLatest[1]
if err := m.DecodeImportPath(); err != nil {
return err
}
return nil
}

// First item in array is modules import path and the secondd item is version+extension
pathSlice := strings.Split(path, "/@v/")
if pathSlice[1] == "list" {
m.Name, m.Version, m.FileExt = pathSlice[0][1:], "", "list"
if err := m.DecodeImportPath(); err != nil {
return err
}
return nil
}

versionExt := modVersionRegex.FindAllStringSubmatch(pathSlice[1], -1)[0]
m.Name, m.Version, m.FileExt = pathSlice[0][1:], versionExt[1], versionExt[2]
if err := m.DecodeImportPath(); err != nil {
return err
}

return nil
}

// DecodeImportPath decodes the module's import path. For more information check
// https://github.com/golang/go/blob/master/src/cmd/go/internal/module/module.go#L375-L433
func (m *Module) DecodeImportPath() error {
decoded, err := paths.DecodePath(m.Name)
if err != nil {
return err
}
m.Name = decoded
return nil
}
@@ -0,0 +1,62 @@
/*
Copyright 2019 - The TXTDirect Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package gomods

import (
"fmt"
"net/http/httptest"
"os"
"testing"
)

func Test_gomods(t *testing.T) {
tests := []struct {
host string
path string
expected string
}{
{
path: "/github.com/okkur/reposeed-server/@v/list",
},
{
path: "/github.com/okkur/reposeed-server/@v/v0.1.0.info",
},
{
path: "/github.com/okkur/reposeed-server/@v/v0.1.0.mod",
},
{
path: "/github.com/okkur/reposeed-server/@v/v0.1.0.zip",
},
}
for _, test := range tests {
if err := os.MkdirAll("/tmp/gomods", os.ModePerm); err != nil {
t.Fatal("Couldn't create storage directory (/tmp/gomods)")
}
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", fmt.Sprintf("https://example.com%s", test.path), nil)
c := Config{
Workers: DefaultGomodsWorkers,
GoBinary: DefaultGoBinaryPath,
Cache: Cache{
Type: "local",
Path: "/tmp/gomods",
},
}
c.SetDefaults()
err := c.Serve(w, r)
if err != nil {
t.Errorf("Unexpected error: %s", err.Error())
}
}
}
122 module.go
@@ -0,0 +1,122 @@
package gomods

import (
"fmt"
"net/http"
"os"
"strings"

"github.com/gomods/athens/pkg/download"
"github.com/gomods/athens/pkg/download/addons"
"github.com/gomods/athens/pkg/module"
"github.com/gomods/athens/pkg/paths"
"github.com/gomods/athens/pkg/stash"
"github.com/gomods/athens/pkg/storage"
"github.com/gomods/athens/pkg/storage/fs"
"github.com/spf13/afero"
)

// Module is the struct that keeps a Go module's data
type Module struct {
Name string
Version string
FileExt string
}

// ModuleHandler is the interface that keeps the module handlers for fething and caching
type ModuleHandler interface {
fetch(r *http.Request, c Config) (*storage.Version, error)
storage(c Config) (storage.Backend, error)
dp(fetcher module.Fetcher, s storage.Backend, fs afero.Fs) download.Protocol
}

func (m Module) fetch(r *http.Request, c Config) (download.Protocol, error) {
fetcher, err := module.NewGoGetFetcher(c.GoBinary, c.Fs)
if err != nil {
return nil, err
}
s, err := m.storage(c)
if err != nil {
return nil, err
}
dp := m.dp(fetcher, s, c)
return dp, nil
}

func (m Module) storage(c Config) (storage.Backend, error) {
switch c.Cache.Type {
case "local":
// Check if cache storage path exists, if not create it
if _, err := os.Stat(c.Cache.Path); os.IsNotExist(err) {
if err = os.MkdirAll(c.Cache.Path, os.ModePerm); err != nil {
return nil, fmt.Errorf("couldn't create the cache storage directory on %s: %s", c.Cache.Path, err.Error())
}
}
s, err := fs.NewStorage(c.Cache.Path, afero.NewOsFs())
if err != nil {
return nil, fmt.Errorf("could not create new storage from os fs (%s)", err)
}
return s, nil
case "tmp":
s, err := fs.NewStorage(c.Cache.Path, afero.NewOsFs())
if err != nil {
return nil, fmt.Errorf("could not create new storage from os fs (%s)", err)
}
return s, nil
}
return nil, fmt.Errorf("Invalid storage config for gomods")
}

func (m Module) dp(fetcher module.Fetcher, s storage.Backend, c Config) download.Protocol {
lister := download.NewVCSLister(c.GoBinary, c.Fs)
st := stash.New(fetcher, s, stash.WithPool(c.Workers), stash.WithSingleflight)
dpOpts := &download.Opts{
Storage: s,
Stasher: st,
Lister: lister,
}
dp := download.New(dpOpts, addons.WithPool(c.Workers))
return dp
}

// ParseImportPath parses the request path and exports the
// module's import path, module's version and file extension
func (m *Module) ParseImportPath(path string) error {
if strings.Contains(path, "@latest") {
pathLatest := strings.Split(path, "/@")
m.Name, m.Version, m.FileExt = pathLatest[0][1:], "", pathLatest[1]
if err := m.DecodeImportPath(); err != nil {
return err
}
return nil
}

// First item in array is modules import path and the secondd item is version+extension
pathSlice := strings.Split(path, "/@v/")
if pathSlice[1] == "list" {
m.Name, m.Version, m.FileExt = pathSlice[0][1:], "", "list"
if err := m.DecodeImportPath(); err != nil {
return err
}
return nil
}

versionExt := modVersionRegex.FindAllStringSubmatch(pathSlice[1], -1)[0]
m.Name, m.Version, m.FileExt = pathSlice[0][1:], versionExt[1], versionExt[2]
if err := m.DecodeImportPath(); err != nil {
return err
}

return nil
}

// DecodeImportPath decodes the module's import path. For more information check
// https://github.com/golang/go/blob/master/src/cmd/go/internal/module/module.go#L375-L433
func (m *Module) DecodeImportPath() error {
decoded, err := paths.DecodePath(m.Name)
if err != nil {
return err
}
m.Name = decoded
return nil
}

0 comments on commit 5a4c896

Please sign in to comment.
You can’t perform that action at this time.