Skip to content

Commit 5bfdf74

Browse files
committed
admin/gc: purge old events
Add event garbage collection to the gc command. Events older than a configurable maximum age are deleted. The age limit defaults to 30 days and can be set via [database].event-max-age in the config file or overridden with --event-max-age on the command line. Introduce a custom Duration type that extends Go's time.Duration with support for "d" (days) and "w" (weeks) suffixes. Signed-off-by: Robin Jarry <robin@jarry.cc>
1 parent 954f8f5 commit 5bfdf74

6 files changed

Lines changed: 115 additions & 4 deletions

File tree

‎cmd/pw/admin/gc.go‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package admin
77

88
import (
99
"fmt"
10+
"time"
1011

1112
"github.com/getpatchwork/patchwork/cmd/pw/pw"
1213
"github.com/getpatchwork/patchwork/pkg/db"
@@ -41,5 +42,16 @@ func (c *GcCmd) Run(ctx *pw.Context) error {
4142
fmt.Printf("deleted %d inactive user(s)\n", n)
4243
}
4344

45+
if ctx.Config.Database.EventMaxAge > 0 {
46+
cutoff := time.Now().Add(-ctx.Config.Database.EventMaxAge.Duration())
47+
n, err = q.CleanOldEvents(cutoff)
48+
if err != nil {
49+
return fmt.Errorf("events: %w", err)
50+
}
51+
if n > 0 {
52+
fmt.Printf("deleted %d old event(s)\n", n)
53+
}
54+
}
55+
4456
return nil
4557
}

‎docs/deployment/configuration.rst‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ Settings Reference
6565

6666
Automatically run pending migrations when the HTTP server starts.
6767

68+
.. confval:: [database].event-max-age
69+
:type: duration
70+
:default: `30d`
71+
72+
Maximum age of events to keep. Events older than this are deleted when
73+
running :program:`pw admin gc`. Accepts duration suffixes: `s`, `m`, `h`,
74+
`d` (days) and `w` (weeks). Set to `0s` to disable event purging.
75+
6876
.. confval:: [http].listen
6977
:type: string
7078
:default: `"127.0.0.1:8080"`

‎docs/deployment/management.rst‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -529,8 +529,13 @@ Garbage Collection
529529

530530
.. program:: pw admin gc
531531

532-
Clean up expired sessions, stale email confirmations, and inactive users with
533-
no pending confirmation. Run this periodically via cron or a systemd timer.
532+
Clean up expired sessions, stale email confirmations, inactive users with no
533+
pending confirmation, and old events. Run this periodically via cron or
534+
a systemd timer.
535+
536+
.. option:: --event-max-age <duration>
537+
538+
Override :confval:`[database].event-max-age` for this run.
534539

535540

536541
Email Ingress

‎pkg/config/config.go‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ type Config struct {
2121
}
2222

2323
type DatabaseConfig struct {
24-
URL string `help:"Database connection URL." completion:"sqlite://,postgres://,mysql://"`
25-
AutoSync bool `help:"Automatically run pending migrations at startup."`
24+
URL string `help:"Database connection URL." completion:"sqlite://,postgres://,mysql://"`
25+
AutoSync bool `help:"Automatically run pending migrations at startup."`
26+
EventMaxAge Duration `help:"Maximum age of events. Used by pw admin gc." default:"30d"`
2627
}
2728

2829
type IngressConfig struct {

‎pkg/config/duration.go‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Patchwork - automated patch tracking system
2+
// Copyright (C) The Patchwork Contributors (see CONTRIBUTORS)
3+
//
4+
// SPDX-License-Identifier: GPL-2.0-or-later
5+
6+
package config
7+
8+
import (
9+
"fmt"
10+
"regexp"
11+
"strconv"
12+
"time"
13+
)
14+
15+
type Duration time.Duration
16+
17+
var durationRe = regexp.MustCompile(`^(\d+)(w|d|h|m|s|ms|us|ns)$`)
18+
19+
func ParseDuration(s string) (Duration, error) {
20+
m := durationRe.FindStringSubmatch(s)
21+
if m != nil {
22+
n, err := strconv.ParseInt(m[1], 10, 64)
23+
if err != nil {
24+
return 0, err
25+
}
26+
switch m[2] {
27+
case "w":
28+
return Duration(time.Duration(n) * 7 * 24 * time.Hour), nil
29+
case "d":
30+
return Duration(time.Duration(n) * 24 * time.Hour), nil
31+
}
32+
}
33+
d, err := time.ParseDuration(s)
34+
if err != nil {
35+
return 0, fmt.Errorf("invalid duration %q", s)
36+
}
37+
return Duration(d), nil
38+
}
39+
40+
func (d Duration) Duration() time.Duration {
41+
return time.Duration(d)
42+
}
43+
44+
func (d Duration) String() string {
45+
td := time.Duration(d)
46+
if td == 0 {
47+
return "0s"
48+
}
49+
week := 7 * 24 * time.Hour
50+
day := 24 * time.Hour
51+
if td%week == 0 {
52+
return fmt.Sprintf("%dw", td/week)
53+
}
54+
if td%day == 0 {
55+
return fmt.Sprintf("%dd", td/day)
56+
}
57+
return td.String()
58+
}
59+
60+
func (d *Duration) UnmarshalText(b []byte) error {
61+
v, err := ParseDuration(string(b))
62+
if err != nil {
63+
return err
64+
}
65+
*d = v
66+
return nil
67+
}

‎pkg/db/event.go‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Patchwork - automated patch tracking system
2+
// Copyright (C) The Patchwork Contributors (see CONTRIBUTORS)
3+
//
4+
// SPDX-License-Identifier: GPL-2.0-or-later
5+
6+
package db
7+
8+
import "time"
9+
10+
func (q *Queries) CleanOldEvents(cutoff time.Time) (int64, error) {
11+
res, err := q.DB.NewDelete().Model((*Event)(nil)).
12+
Where("date < ?", cutoff).Exec(q.Ctx)
13+
if err != nil {
14+
return 0, err
15+
}
16+
n, _ := res.RowsAffected()
17+
return n, nil
18+
}

0 commit comments

Comments
 (0)