Skip to content

Commit fabcf47

Browse files
authored
Merge pull request #3 from systemli/configurable-expiration-duration
make expiration duration for pads configurable
2 parents 0d66ec7 + 20b4e48 commit fabcf47

File tree

10 files changed

+408
-85
lines changed

10 files changed

+408
-85
lines changed

README.md

+19-9
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ Usage:
7373
7474
Flags:
7575
-h, --help help for metrics
76-
--listen.addr string (default ":9012")
76+
--listen.addr string Address on which to expose metrics. (default ":9012")
77+
--suffixes string Suffixes to group the pads. (default "keep,temp")
7778
```
7879

7980
### Move Pad
@@ -91,19 +92,28 @@ Flags:
9192

9293
### Purge
9394

94-
The command checks every Pad if the last edited date is older than the defined limit. Older Pads will be deleted.
95+
The command checks every Pad for it’s last edited date. If it is older than the defined limit, the pad will be deleted.
9596

96-
Pads without any changes (revisions) will be deleted.
97-
Pads without a suffix will be deleted after 30 days of inactivity.
98-
Pads with the suffix "-temp" will be deleted after 24 hours of inactivity.
99-
Pads with the suffix "-keep" will be deleted after 365 days of inactivity.
97+
Pads without any changes (revisions) will be deleted. This can happen when no content was changed in the pad
98+
(e.g. a person misspelles a pad).
99+
Pads will grouped by the pre-defined suffixes. Every suffix has a defined expiration time. If the pad is older than the
100+
defined expiration time, the pad will be deleted.
101+
102+
Example:
103+
104+
`etherpad-toolkit purge --expiration "default:720h,temp:24h,keep:8760h"`
105+
106+
This configuration will group the pads in three clusters: default (expiration: 30 days, suffix is required!),
107+
temp (expiration: 24 hours), keep (expiration: 365 days). If pads in the clusters older than the given expiration the
108+
pads will be deleted.
100109

101110
```
102111
Usage:
103112
etherpad-toolkit purge [flags]
104113
105114
Flags:
106-
--concurrency int Concurrency for the purge process (default 4)
107-
--dry-run Enable dry-run
108-
-h, --help help for purge
115+
--concurrency int Concurrency for the purge process (default 4)
116+
--dry-run Enable dry-run
117+
--expiration string Configuration for pad expiration duration. Example: "default:720h,temp:24h,keep:8760h"
118+
-h, --help help for purge
109119
```

cmd/metrics.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cmd
22

33
import (
44
"net/http"
5+
"strings"
56

67
"github.com/prometheus/client_golang/prometheus"
78
"github.com/prometheus/client_golang/prometheus/promhttp"
@@ -13,6 +14,7 @@ import (
1314

1415
var (
1516
listenAddr string
17+
suffixes string
1618

1719
metricsCmd = &cobra.Command{
1820
Use: "metrics",
@@ -23,14 +25,15 @@ var (
2325
)
2426

2527
func init() {
26-
metricsCmd.Flags().StringVar(&listenAddr, "listen.addr", ":9012", "")
28+
metricsCmd.Flags().StringVar(&listenAddr, "listen.addr", ":9012", "Address on which to expose metrics.")
29+
metricsCmd.Flags().StringVar(&suffixes, "suffixes", "keep,temp", "Suffixes to group the pads.")
2730

2831
rootCmd.AddCommand(metricsCmd)
2932
}
3033

3134
func runMetrics(cmd *cobra.Command, args []string) {
3235
etherpad := pkg.NewEtherpadClient(etherpadUrl, etherpadApiKey)
33-
prometheus.MustRegister(metrics.NewPadCollector(etherpad))
36+
prometheus.MustRegister(metrics.NewPadCollector(etherpad, strings.Split(suffixes, ",")))
3437

3538
http.Handle("/metrics", promhttp.Handler())
3639
log.Fatal(http.ListenAndServe(listenAddr, nil))

cmd/purge.go

+19-12
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,23 @@ import (
1111
var (
1212
concurrency int
1313
dryRun bool
14+
expiration string
1415

1516
longDescription = `
16-
The command checks every Pad if the last edited date is older than the defined limit. Older Pads will be deleted.
17+
The command checks every Pad for it’s last edited date. If it is older than the defined limit, the pad will be deleted.
1718
18-
Pads without any changes (revisions) will be deleted.
19-
Pads without a suffix will be deleted after 30 days of inactivity.
20-
Pads with the suffix "-temp" will be deleted after 24 hours of inactivity.
21-
Pads with the suffix "-keep" will be deleted after 365 days of inactivity.
19+
Pads without any changes (revisions) will be deleted. This can happen when no content was changed in the pad
20+
(e.g. a person misspelles a pad).
21+
Pads will grouped by the pre-defined suffixes. Every suffix has a defined expiration time. If the pad is older than the
22+
defined expiration time, the pad will be deleted.
23+
24+
Example:
25+
26+
etherpad-toolkit purge --expiration "default:720h,temp:24h,keep:8760h"
27+
28+
This configuration will group the pads in three clusters: default (expiration: 30 days, suffix is required!),
29+
temp (expiration: 24 hours), keep (expiration: 365 days). If pads in the clusters older than the given expiration the
30+
pads will be deleted.
2231
`
2332

2433
purgeCmd = &cobra.Command{
@@ -30,6 +39,7 @@ Pads with the suffix "-keep" will be deleted after 365 days of inactivity.
3039
)
3140

3241
func init() {
42+
purgeCmd.Flags().StringVar(&expiration, "expiration", "", "Configuration for pad expiration duration. Example: \"default:720h,temp:24h,keep:8760h\"")
3343
purgeCmd.Flags().IntVar(&concurrency, "concurrency", 4, "Concurrency for the purge process")
3444
purgeCmd.Flags().BoolVar(&dryRun, "dry-run", false, "Enable dry-run")
3545

@@ -38,14 +48,11 @@ func init() {
3848

3949
func runPurger(cmd *cobra.Command, args []string) {
4050
etherpad := pkg.NewEtherpadClient(etherpadUrl, etherpadApiKey)
41-
purger := purge.NewPurger(etherpad, dryRun)
42-
43-
pads, err := etherpad.ListAllPads()
51+
exp, err := helper.ParsePadExpiration(expiration)
4452
if err != nil {
45-
log.WithError(err).Error("failed to fetch pads")
53+
log.WithError(err).Error("failed to parse expiration string")
4654
return
4755
}
48-
sorted := helper.SortPads(pads)
49-
50-
purger.PurgePads(sorted, concurrency)
56+
purger := purge.NewPurger(etherpad, exp, dryRun)
57+
purger.PurgePads(concurrency)
5158
}

pkg/helper/expiration.go

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package helper
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"strings"
7+
"time"
8+
9+
log "github.com/sirupsen/logrus"
10+
)
11+
12+
const DefaultSuffix = "default"
13+
14+
type PadExpiration map[string]time.Duration
15+
16+
// ParsePadExpiration splits a string with format "default:30d,temp:24h,keep:365d" and returns a PadExpiration type.
17+
// The key "default:<duration>" is mandatory in the input string.
18+
func ParsePadExpiration(s string) (PadExpiration, error) {
19+
exp := make(PadExpiration)
20+
21+
if s == "" {
22+
return exp, errors.New("input string is empty")
23+
}
24+
25+
for _, str := range strings.Split(s, ",") {
26+
split := strings.Split(str, ":")
27+
if len(split) != 2 {
28+
log.WithField("string", str).Error("string is not valid")
29+
continue
30+
}
31+
duration, err := time.ParseDuration(split[1])
32+
if err != nil {
33+
log.WithError(err).WithField("duration", split[1]).Error("unable to parse the duration")
34+
continue
35+
}
36+
37+
exp[split[0]] = duration
38+
}
39+
40+
if _, ok := exp[DefaultSuffix]; !ok {
41+
return exp, errors.New("missing default expiration duration")
42+
}
43+
44+
return exp, nil
45+
}
46+
47+
// GetDuration tries to get the Duration by pad name, returns the default duration if no suffix matches.
48+
func (pe *PadExpiration) GetDuration(pad string) time.Duration {
49+
for suffix, duration := range *pe {
50+
if strings.HasSuffix(pad, fmt.Sprintf("-%s", suffix)) {
51+
return -duration
52+
}
53+
}
54+
55+
return -(*pe)[DefaultSuffix]
56+
}
57+
58+
// GroupPadsByExpiration sorts pads for the given expiration and returns a map with string keys and string slices.
59+
func GroupPadsByExpiration(pads []string, expiration PadExpiration) map[string][]string {
60+
var suffixes []string
61+
for suffix := range expiration {
62+
if suffix == DefaultSuffix {
63+
continue
64+
}
65+
suffixes = append(suffixes, suffix)
66+
}
67+
68+
return GroupPadsBySuffixes(pads, suffixes)
69+
}
70+
71+
// GroupPadsBySuffixes sorts pads for the given suffixes and returns a map with string keys and string slices.
72+
func GroupPadsBySuffixes(pads, suffixes []string) map[string][]string {
73+
sorted := make(map[string][]string)
74+
75+
for _, pad := range pads {
76+
found := false
77+
for _, suffix := range suffixes {
78+
if strings.HasSuffix(pad, fmt.Sprintf("-%s", suffix)) {
79+
sorted[suffix] = append(sorted[suffix], pad)
80+
found = true
81+
}
82+
}
83+
if !found {
84+
sorted[DefaultSuffix] = append(sorted[DefaultSuffix], pad)
85+
}
86+
}
87+
88+
return sorted
89+
}

pkg/helper/expiration_test.go

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package helper
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestParsePadExpiration(t *testing.T) {
11+
s := "default:720h,temp:24h,keep:262800h"
12+
exp, err := ParsePadExpiration(s)
13+
assert.Nil(t, err)
14+
assert.Equal(t, time.Duration(2592000000000000), exp[DefaultSuffix])
15+
assert.Equal(t, time.Duration(946080000000000000), exp["keep"])
16+
assert.Equal(t, time.Duration(86400000000000), exp["temp"])
17+
18+
s = "wrong:1d"
19+
20+
_, err = ParsePadExpiration(s)
21+
assert.Error(t, err)
22+
assert.Equal(t, "missing default expiration duration", err.Error())
23+
24+
s = "wrong:1h:2h"
25+
_, err = ParsePadExpiration(s)
26+
assert.Error(t, err)
27+
assert.Equal(t, "missing default expiration duration", err.Error())
28+
29+
s = ""
30+
_, err = ParsePadExpiration(s)
31+
assert.Error(t, err)
32+
assert.Equal(t, "input string is empty", err.Error())
33+
}
34+
35+
func TestPadExpiration_GetDuration(t *testing.T) {
36+
s := "default:24h"
37+
exp, err := ParsePadExpiration(s)
38+
assert.Nil(t, err)
39+
40+
dur := exp.GetDuration("pad")
41+
assert.Equal(t, "-24h0m0s", dur.String())
42+
43+
s = "default:24h,temp:10m"
44+
exp, err = ParsePadExpiration(s)
45+
assert.Nil(t, err)
46+
47+
dur = exp.GetDuration("pad")
48+
assert.Equal(t, "-24h0m0s", dur.String())
49+
50+
dur = exp.GetDuration("pad-temp")
51+
assert.Equal(t, "-10m0s", dur.String())
52+
}
53+
54+
func TestGroupPadsByExpiration(t *testing.T) {
55+
s := "default:720h,temp:24h,keep:262800h"
56+
pads := []string{"pad", "pad2", "pad-keep", "pad-temp"}
57+
exp, err := ParsePadExpiration(s)
58+
assert.Nil(t, err)
59+
60+
sorted := GroupPadsByExpiration(pads, exp)
61+
62+
if _, ok := sorted[DefaultSuffix]; !ok {
63+
t.Fail()
64+
}
65+
66+
if _, ok := sorted["keep"]; !ok {
67+
t.Fail()
68+
}
69+
70+
if _, ok := sorted["temp"]; !ok {
71+
t.Fail()
72+
}
73+
74+
assert.Equal(t, []string{"pad", "pad2"}, sorted[DefaultSuffix])
75+
assert.Equal(t, []string{"pad-keep"}, sorted["keep"])
76+
assert.Equal(t, []string{"pad-temp"}, sorted["temp"])
77+
}

pkg/helper/sort.go

-20
This file was deleted.

pkg/helper/sort_test.go

-16
This file was deleted.

pkg/metrics/collector.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ import (
99

1010
type PadCollector struct {
1111
etherpad *pkg.Etherpad
12+
suffixes []string
1213
PadGaugeDesc *prometheus.Desc
1314
}
1415

15-
func NewPadCollector(etherpad *pkg.Etherpad) *PadCollector {
16+
func NewPadCollector(etherpad *pkg.Etherpad, suffixes []string) *PadCollector {
1617
return &PadCollector{
1718
etherpad: etherpad,
19+
suffixes: suffixes,
1820
PadGaugeDesc: prometheus.NewDesc("etherpad_toolkit_pads", "The current number of pads", []string{"suffix"}, nil),
1921
}
2022
}
@@ -30,7 +32,7 @@ func (pc *PadCollector) Collect(ch chan<- prometheus.Metric) {
3032
return
3133
}
3234

33-
sorted := helper.SortPads(allPads)
35+
sorted := helper.GroupPadsBySuffixes(allPads, pc.suffixes)
3436

3537
for suffix, pads := range sorted {
3638
ch <- prometheus.MustNewConstMetric(

0 commit comments

Comments
 (0)