Skip to content

Commit 35c5928

Browse files
committed
feat(janitor): add a ticker
1 parent e391620 commit 35c5928

File tree

2 files changed

+97
-10
lines changed

2 files changed

+97
-10
lines changed

cmd/chall-manager-janitor/main.go

Lines changed: 79 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"context"
55
"io"
66
"os"
7+
"os/signal"
78
"sync"
9+
"syscall"
810
"time"
911

1012
"github.com/ctfer-io/chall-manager/api/v1/challenge"
@@ -68,6 +70,14 @@ func main() {
6870
Destination: &serviceName,
6971
Usage: "Override the service name. Useful when deploying multiple instances to filter signals.",
7072
},
73+
&cli.DurationFlag{
74+
Name: "ticker",
75+
EnvVars: []string{"TICKER"},
76+
Usage: `If set, define the tick between 2 run of the janitor. ` +
77+
`This mode is not recommended and should be preferred to a cron or an equivalent.`,
78+
// Not recommended because the janitor was not made to be a long-running software.
79+
// It was not optimised in this way, despite it should work fine.
80+
},
7181
},
7282
Action: run,
7383
Authors: []*cli.Author{
@@ -93,41 +103,97 @@ func main() {
93103
}
94104
}
95105

96-
func run(ctx *cli.Context) error {
106+
func run(c *cli.Context) error {
97107
opts := []grpc.DialOption{
98108
grpc.WithTransportCredentials(insecure.NewCredentials()),
99109
}
100110
if tracing {
101111
opts = append(opts, grpc.WithStatsHandler(otelgrpc.NewClientHandler()))
102112

103113
// Set up OpenTelemetry.
104-
otelShutdown, err := setupOtelSDK(ctx.Context)
114+
otelShutdown, err := setupOtelSDK(c.Context)
105115
if err != nil {
106116
return err
107117
}
108118
// Handle shutdown properly so nothing leaks.
109119
defer func() {
110-
err = multierr.Append(err, otelShutdown(ctx.Context))
120+
err = multierr.Append(err, otelShutdown(c.Context))
111121
}()
112122
}
113123

114124
logger := Log()
115125

116-
cli, err := grpc.NewClient(ctx.String("url"), opts...)
126+
// Create context that listens for the interrupt signal from the OS
127+
ctx, stop := signal.NotifyContext(c.Context, syscall.SIGINT, syscall.SIGTERM)
128+
defer stop()
129+
130+
cli, err := grpc.NewClient(c.String("url"), opts...)
117131
if err != nil {
118132
return err
119133
}
120-
store := challenge.NewChallengeStoreClient(cli)
121-
manager := instance.NewInstanceManagerClient(cli)
122134
defer func(cli *grpc.ClientConn) {
123135
if err := cli.Close(); err != nil {
124-
logger.Error(ctx.Context, "closing gRPC connection", zap.Error(err))
136+
logger.Error(ctx, "closing gRPC connection", zap.Error(err))
125137
}
126138
}(cli)
127139

128-
span := trace.SpanFromContext(ctx.Context)
140+
if c.IsSet("ticker") {
141+
if err := janitorWithTicker(ctx, cli, c.Duration("ticker")); err != nil {
142+
return err
143+
}
144+
} else {
145+
if err := janitor(ctx, cli); err != nil {
146+
return err
147+
}
148+
}
149+
150+
// Listen for the interrupt signal
151+
<-ctx.Done()
152+
153+
// Restore default behavior on the interrupt signal
154+
stop()
155+
logger.Info(ctx, "shutting down gracefully")
156+
157+
return nil
158+
}
159+
160+
func janitorWithTicker(ctx context.Context, cli *grpc.ClientConn, d time.Duration) error {
161+
logger := Log()
162+
ticker := time.NewTicker(d)
163+
wg := sync.WaitGroup{}
164+
165+
run := true
166+
for run {
167+
select {
168+
case <-ticker.C:
169+
wg.Add(1)
170+
go func(ctx context.Context, cli *grpc.ClientConn) {
171+
defer wg.Done()
172+
173+
if err := janitor(ctx, cli); err != nil {
174+
logger.Error(ctx, "janitoring did not succeed", zap.Error(err))
175+
}
176+
}(ctx, cli)
177+
178+
case <-ctx.Done():
179+
ticker.Stop()
180+
run = false
181+
}
182+
}
183+
wg.Wait()
184+
return nil
185+
}
186+
187+
func janitor(ctx context.Context, cli *grpc.ClientConn) error {
188+
logger := Log()
189+
logger.Info(ctx, "starting janitoring")
190+
191+
store := challenge.NewChallengeStoreClient(cli)
192+
manager := instance.NewInstanceManagerClient(cli)
193+
194+
span := trace.SpanFromContext(ctx)
129195
span.AddEvent("querying challenges")
130-
challs, err := store.QueryChallenge(ctx.Context, nil)
196+
challs, err := store.QueryChallenge(ctx, nil)
131197
if err != nil {
132198
return err
133199
}
@@ -139,7 +205,7 @@ func run(ctx *cli.Context) error {
139205
}
140206
return err
141207
}
142-
ctx := WithChallengeID(ctx.Context, chall.Id)
208+
ctx := WithChallengeID(ctx, chall.Id)
143209

144210
// Don't janitor if the challenge has no dates configured
145211
if chall.Timeout == nil && chall.Until == nil {
@@ -172,6 +238,9 @@ func run(ctx *cli.Context) error {
172238
}
173239
wg.Wait()
174240
}
241+
242+
logger.Info(ctx, "completed janitoring")
243+
175244
return nil
176245
}
177246

webdocs/ops-guides/deployment/index.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,21 @@ slsa-verifier slsa-verifier verify-image "ctferio/chall-manager-janitor:<tag>@sh
100100
We let the reader deploy it as (s)he needs it, but recommend you take a look at how we use systemd services and timers in the [binary `setup.sh`](https://github.com/ctfer-io/chall-manager/blob/main/hack/setup.sh) script.
101101

102102
Additionally, we recommend you create a specific network to isolate the docker images from other adjacent services.
103+
104+
For instance, the following `docker-compose.yml` may fit your development needs, with the support of the janitor.
105+
106+
```yaml
107+
version: '3.8'
108+
109+
services:
110+
chall-manager:
111+
image: ctferio/chall-manager:v0.2.0
112+
ports:
113+
- "8080:8080"
114+
115+
chall-manager-janitor:
116+
image: ctferio/chall-manager-janitor:v0.2.0
117+
environment:
118+
URL: chall-manager:8080
119+
TICKER: 1m
120+
```

0 commit comments

Comments
 (0)