internal/worker: /clean endpoint supports a single module
The /clean endpoint can be used to clean all versions of a given
module.
For golang/go#45852
Change-Id: Ie39d142c9c1049213eb5319d31196d77e3b7052f
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/315829
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Julie Qiu <julie@golang.org>
diff --git a/internal/postgres/clean.go b/internal/postgres/clean.go
index 16e9e22..f25cda1 100644
--- a/internal/postgres/clean.go
+++ b/internal/postgres/clean.go
@@ -73,12 +73,12 @@
// CleanModuleVersions deletes each module version from the DB and marks it as cleaned
// in module_version_states.
-func (db *DB) CleanModuleVersions(ctx context.Context, mvs []ModuleVersion) (err error) {
+func (db *DB) CleanModuleVersions(ctx context.Context, mvs []ModuleVersion, reason string) (err error) {
defer derrors.Wrap(&err, "CleanModuleVersions(%d modules)", len(mvs))
status := derrors.ToStatus(derrors.Cleaned)
for _, mv := range mvs {
- if err := db.UpdateModuleVersionStatus(ctx, mv.ModulePath, mv.Version, status, ""); err != nil {
+ if err := db.UpdateModuleVersionStatus(ctx, mv.ModulePath, mv.Version, status, reason); err != nil {
return err
}
if err := db.DeleteModule(ctx, mv.ModulePath, mv.Version); err != nil {
@@ -87,3 +87,27 @@
}
return nil
}
+
+// CleanModule deletes all versions of the given module path from the DB and marks them
+// as cleaned in module_version_states.
+func (db *DB) CleanModule(ctx context.Context, modulePath, reason string) (err error) {
+ defer derrors.Wrap(&err, "CleanModule(%q)", modulePath)
+
+ var mvs []ModuleVersion
+ err = db.db.RunQuery(ctx, `
+ SELECT version
+ FROM modules
+ WHERE module_path = $1
+ `, func(rows *sql.Rows) error {
+ var v string
+ if err := rows.Scan(&v); err != nil {
+ return err
+ }
+ mvs = append(mvs, ModuleVersion{modulePath, v})
+ return nil
+ }, modulePath)
+ if err != nil {
+ return err
+ }
+ return db.CleanModuleVersions(ctx, mvs, reason)
+}
diff --git a/internal/postgres/clean_test.go b/internal/postgres/clean_test.go
index e5aac90..6c65182 100644
--- a/internal/postgres/clean_test.go
+++ b/internal/postgres/clean_test.go
@@ -18,7 +18,7 @@
"golang.org/x/pkgsite/internal/testing/sample"
)
-func TestClean(t *testing.T) {
+func TestCleanBulk(t *testing.T) {
t.Parallel()
ctx := context.Background()
testDB, release := acquire(t)
@@ -61,7 +61,7 @@
t.Errorf("got %v\nwant %v", got, want)
}
- if err := testDB.CleanModuleVersions(ctx, mvs); err != nil {
+ if err := testDB.CleanModuleVersions(ctx, mvs, "test"); err != nil {
t.Fatal(err)
}
@@ -72,3 +72,27 @@
}
}
}
+
+func TestCleanModule(t *testing.T) {
+ t.Parallel()
+ ctx := context.Background()
+ testDB, release := acquire(t)
+ defer release()
+
+ const modulePath = "m.com"
+ versions := []string{"v1.0.0", "v1.2.3"}
+ for _, v := range versions {
+ m := sample.Module(modulePath, v, "")
+ MustInsertModule(ctx, t, testDB, m)
+ }
+ if err := testDB.CleanModule(ctx, modulePath, ""); err != nil {
+ t.Fatal(err)
+ }
+
+ for _, v := range versions {
+ _, err := testDB.GetModuleInfo(ctx, modulePath, v)
+ if !errors.Is(err, derrors.NotFound) {
+ t.Errorf("%s: got %v, want NotFound", v, err)
+ }
+ }
+}
diff --git a/internal/worker/server.go b/internal/worker/server.go
index 6e8a973..745abea 100644
--- a/internal/worker/server.go
+++ b/internal/worker/server.go
@@ -204,7 +204,8 @@
// manual: delete the specified module version.
handle("/delete/", http.StripPrefix("/delete", rmw(s.errorHandler(s.handleDelete))))
- // scheduled: clean some module versions.
+ // scheduled ("limit" query param): clean some eligible module versions selected from the DB
+ // manual ("module" query param): clean all versions of a given module.
handle("/clean", rmw(s.errorHandler(s.handleClean)))
handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(s.staticPath.String()))))
@@ -585,20 +586,48 @@
// Consider a module version for cleaning only if it is older than this.
const cleanDays = 7
+// handleClean handles a request to clean module versions.
+//
+// If the request has a 'limit' query parameter, then up to that many module versions
+// are selected from the DB among those eligible for cleaning, and they are cleaned.
+//
+// If the request has a 'module' query parameter, all versions of that module path
+// are cleaned.
+//
+// It is an error if neither or both query parameters are provided.
func (s *Server) handleClean(w http.ResponseWriter, r *http.Request) (err error) {
defer derrors.Wrap(&err, "handleClean")
ctx := r.Context()
- limit := parseLimitParam(r, 1000)
- mvs, err := s.db.GetModuleVersionsToClean(ctx, cleanDays, limit)
- if err != nil {
- return err
+
+ limit := r.FormValue("limit")
+ module := r.FormValue("module")
+ switch {
+ case limit == "" && module == "":
+ return errors.New("need 'limit' or 'module' query param")
+
+ case limit != "" && module != "":
+ return errors.New("need exactly one of 'limit' or 'module' query param")
+
+ case limit != "":
+ mvs, err := s.db.GetModuleVersionsToClean(ctx, cleanDays, parseLimitParam(r, 1000))
+ if err != nil {
+ return err
+ }
+ log.Infof(ctx, "cleaning %d modules", len(mvs))
+ if err := s.db.CleanModuleVersions(ctx, mvs, "Bulk deleted via /clean endpoint"); err != nil {
+ return err
+ }
+ fmt.Fprintf(w, "Cleaned %d module versions.\n", len(mvs))
+ return nil
+
+ default: // module != ""
+ log.Infof(ctx, "cleaning module %q", module)
+ if err := s.db.CleanModule(ctx, module, "Manually deleted via /clean endpoint"); err != nil {
+ return err
+ }
+ fmt.Fprintf(w, "Cleaned module %q\n", module)
+ return nil
}
- log.Infof(ctx, "cleaning %d modules", len(mvs))
- if err := s.db.CleanModuleVersions(ctx, mvs); err != nil {
- return err
- }
- fmt.Fprintf(w, "Cleaned %d module versions.\n", len(mvs))
- return nil
}
func (s *Server) handleHealthCheck(w http.ResponseWriter, r *http.Request) {