mirror of
https://github.com/gomods/athens
synced 2026-02-03 08:40:31 +00:00
index: gracefully handle duplicate module indexes (#1645)
* index: gracefully handle duplicate module indexes * fix memory impl
This commit is contained in:
@@ -6,12 +6,16 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gomods/athens/pkg/errors"
|
||||
"github.com/gomods/athens/pkg/index"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/technosophos/moniker"
|
||||
)
|
||||
|
||||
// RunTests runs compliance tests for the given Indexer implementation.
|
||||
// clearIndex is a function that must clear the entire storage so that
|
||||
// tests can assume a clean state.
|
||||
func RunTests(t *testing.T, indexer index.Indexer, clearIndex func() error) {
|
||||
if err := clearIndex(); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -80,6 +84,23 @@ func RunTests(t *testing.T, indexer index.Indexer, clearIndex func() error) {
|
||||
},
|
||||
limit: 0,
|
||||
},
|
||||
{
|
||||
name: "duplicate module version",
|
||||
desc: "if we try to index a module that already exists, a KindAlreadyExists must be returned",
|
||||
preTest: func(t *testing.T) ([]*index.Line, time.Time) {
|
||||
m := &index.Line{Path: "gomods.io/tobeduplicated", Version: "v0.1.0"}
|
||||
err := indexer.Index(context.Background(), m.Path, m.Version)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = indexer.Index(context.Background(), m.Path, m.Version)
|
||||
if !errors.Is(err, errors.KindAlreadyExists) {
|
||||
t.Fatalf("expected an error of kind AlreadyExists but got %s", errors.KindText(err))
|
||||
}
|
||||
return []*index.Line{m}, time.Time{}
|
||||
},
|
||||
limit: 2000,
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package mem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -22,12 +23,17 @@ type indexer struct {
|
||||
func (i *indexer) Index(ctx context.Context, mod, ver string) error {
|
||||
const op errors.Op = "mem.Index"
|
||||
i.mu.Lock()
|
||||
defer i.mu.Unlock()
|
||||
for _, l := range i.lines {
|
||||
if l.Path == mod && l.Version == ver {
|
||||
return errors.E(op, fmt.Sprintf("%s@%s already indexed", mod, ver), errors.KindAlreadyExists)
|
||||
}
|
||||
}
|
||||
i.lines = append(i.lines, &index.Line{
|
||||
Path: mod,
|
||||
Version: ver,
|
||||
Timestamp: time.Now(),
|
||||
})
|
||||
i.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@ import (
|
||||
"github.com/gomods/athens/pkg/index"
|
||||
)
|
||||
|
||||
// New returns a new Indexer with a MySQL implementation.
|
||||
// It attempts to connect to the DB and create the index table
|
||||
// if it doesn ot already exist.
|
||||
func New(cfg *config.MySQL) (index.Indexer, error) {
|
||||
dataSource := getMySQLSource(cfg)
|
||||
db, err := sql.Open("mysql", dataSource)
|
||||
@@ -65,7 +68,7 @@ func (i *indexer) Index(ctx context.Context, mod, ver string) error {
|
||||
time.Now().Format(time.RFC3339Nano),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.E(op, err)
|
||||
return errors.E(op, err, getKind(err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -103,3 +106,15 @@ func getMySQLSource(cfg *config.MySQL) string {
|
||||
c.Params = cfg.Params
|
||||
return c.FormatDSN()
|
||||
}
|
||||
|
||||
func getKind(err error) int {
|
||||
mysqlErr, ok := err.(*mysql.MySQLError)
|
||||
if !ok {
|
||||
return errors.KindUnexpected
|
||||
}
|
||||
switch mysqlErr.Number {
|
||||
case 1062:
|
||||
return errors.KindAlreadyExists
|
||||
}
|
||||
return errors.KindUnexpected
|
||||
}
|
||||
|
||||
@@ -8,13 +8,16 @@ import (
|
||||
"time"
|
||||
|
||||
// register the driver with database/sql
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/lib/pq"
|
||||
|
||||
"github.com/gomods/athens/pkg/config"
|
||||
"github.com/gomods/athens/pkg/errors"
|
||||
"github.com/gomods/athens/pkg/index"
|
||||
)
|
||||
|
||||
// New returns a new Indexer with a PostgreSQL implementation.
|
||||
// It attempts to connect to the DB and create the index table
|
||||
// if it doesn ot already exist.
|
||||
func New(cfg *config.Postgres) (index.Indexer, error) {
|
||||
dataSource := getPostgresSource(cfg)
|
||||
db, err := sql.Open("postgres", dataSource)
|
||||
@@ -64,7 +67,7 @@ func (i *indexer) Index(ctx context.Context, mod, ver string) error {
|
||||
time.Now().Format(time.RFC3339Nano),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.E(op, err)
|
||||
return errors.E(op, err, getKind(err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -104,3 +107,15 @@ func getPostgresSource(cfg *config.Postgres) string {
|
||||
}
|
||||
return strings.Join(args, " ")
|
||||
}
|
||||
|
||||
func getKind(err error) int {
|
||||
pqerr, ok := err.(*pq.Error)
|
||||
if !ok {
|
||||
return errors.KindUnexpected
|
||||
}
|
||||
switch pqerr.Code {
|
||||
case "23505":
|
||||
return errors.KindAlreadyExists
|
||||
}
|
||||
return errors.KindUnexpected
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ func (s *stasher) Stash(ctx context.Context, mod, ver string) (string, error) {
|
||||
return "", errors.E(op, err)
|
||||
}
|
||||
err = s.indexer.Index(ctx, mod, v.Semver)
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, errors.KindAlreadyExists) {
|
||||
return "", errors.E(op, err)
|
||||
}
|
||||
return v.Semver, nil
|
||||
|
||||
Reference in New Issue
Block a user