Skip to content

Commit ad074c8

Browse files
authored
hooks: Move into pkgs/hooks (#976)
* hooks: Move implementations in `pkgs/hooks` * misc: Upgrade go.mod to Go 1.20 * hooks: Use built-in function for testing slice elements * Move setup logic into hooks package * Implement tests * Remove duplicate import * Documentation * Minor cleanups
1 parent 7e8b77b commit ad074c8

File tree

18 files changed

+821
-2083
lines changed

18 files changed

+821
-2083
lines changed

cmd/tusd/cli/flags.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import (
55
"path/filepath"
66
"strings"
77

8-
"github.com/tus/tusd/v2/cmd/tusd/cli/hooks"
8+
"github.com/tus/tusd/v2/pkg/hooks"
9+
"golang.org/x/exp/slices"
910
)
1011

1112
var Flags struct {
@@ -134,7 +135,7 @@ func SetEnabledHooks() {
134135
for i, h := range slc {
135136
slc[i] = strings.TrimSpace(h)
136137

137-
if !hookTypeInSlice(hooks.HookType(h), hooks.AvailableHooks) {
138+
if !slices.Contains(hooks.AvailableHooks, hooks.HookType(h)) {
138139
stderr.Fatalf("Unknown hook event type in -hooks-enabled-events flag: %s", h)
139140
}
140141

cmd/tusd/cli/hooks.go

Lines changed: 10 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -3,95 +3,25 @@ package cli
33
import (
44
"strings"
55

6-
"github.com/tus/tusd/v2/cmd/tusd/cli/hooks"
76
"github.com/tus/tusd/v2/pkg/handler"
7+
"github.com/tus/tusd/v2/pkg/hooks"
8+
"github.com/tus/tusd/v2/pkg/hooks/file"
9+
"github.com/tus/tusd/v2/pkg/hooks/grpc"
10+
"github.com/tus/tusd/v2/pkg/hooks/http"
11+
"github.com/tus/tusd/v2/pkg/hooks/plugin"
812
)
913

10-
var hookHandler hooks.HookHandler = nil
11-
12-
func hookTypeInSlice(a hooks.HookType, list []hooks.HookType) bool {
13-
for _, b := range list {
14-
if b == a {
15-
return true
16-
}
17-
}
18-
return false
19-
}
20-
21-
func preCreateCallback(event handler.HookEvent) (handler.HTTPResponse, handler.FileInfoChanges, error) {
22-
ok, hookRes, err := invokeHookSync(hooks.HookPreCreate, event)
23-
if !ok || err != nil {
24-
return handler.HTTPResponse{}, handler.FileInfoChanges{}, err
25-
}
26-
27-
httpRes := hookRes.HTTPResponse
28-
29-
// If the hook response includes the instruction to reject the upload, reuse the error code
30-
// and message from ErrUploadRejectedByServer, but also include custom HTTP response values.
31-
if hookRes.RejectUpload {
32-
err := handler.ErrUploadRejectedByServer
33-
err.HTTPResponse = err.HTTPResponse.MergeWith(httpRes)
34-
35-
return handler.HTTPResponse{}, handler.FileInfoChanges{}, err
36-
}
37-
38-
// Pass any changes regarding file info from the hook to the handler.
39-
changes := hookRes.ChangeFileInfo
40-
return httpRes, changes, nil
41-
}
42-
43-
func preFinishCallback(event handler.HookEvent) (handler.HTTPResponse, error) {
44-
ok, hookRes, err := invokeHookSync(hooks.HookPreFinish, event)
45-
if !ok || err != nil {
46-
return handler.HTTPResponse{}, err
47-
}
48-
49-
httpRes := hookRes.HTTPResponse
50-
return httpRes, nil
51-
}
52-
53-
func postReceiveCallback(event handler.HookEvent) {
54-
ok, hookRes, _ := invokeHookSync(hooks.HookPostReceive, event)
55-
// invokeHookSync already logs the error, if any occurs. So by checking `ok`, we can ensure
56-
// that the hook finished successfully
57-
if !ok {
58-
return
59-
}
60-
61-
if hookRes.StopUpload {
62-
logEv(stdout, "HookStopUpload", "id", event.Upload.ID)
63-
64-
// TODO: Control response for PATCH request
65-
event.Upload.StopUpload()
66-
}
67-
}
68-
69-
func SetupHookMetrics() {
70-
MetricsHookErrorsTotal.WithLabelValues(string(hooks.HookPostFinish)).Add(0)
71-
MetricsHookErrorsTotal.WithLabelValues(string(hooks.HookPostTerminate)).Add(0)
72-
MetricsHookErrorsTotal.WithLabelValues(string(hooks.HookPostReceive)).Add(0)
73-
MetricsHookErrorsTotal.WithLabelValues(string(hooks.HookPostCreate)).Add(0)
74-
MetricsHookErrorsTotal.WithLabelValues(string(hooks.HookPreCreate)).Add(0)
75-
MetricsHookErrorsTotal.WithLabelValues(string(hooks.HookPreFinish)).Add(0)
76-
MetricsHookInvocationsTotal.WithLabelValues(string(hooks.HookPostFinish)).Add(0)
77-
MetricsHookInvocationsTotal.WithLabelValues(string(hooks.HookPostTerminate)).Add(0)
78-
MetricsHookInvocationsTotal.WithLabelValues(string(hooks.HookPostReceive)).Add(0)
79-
MetricsHookInvocationsTotal.WithLabelValues(string(hooks.HookPostCreate)).Add(0)
80-
MetricsHookInvocationsTotal.WithLabelValues(string(hooks.HookPreCreate)).Add(0)
81-
MetricsHookInvocationsTotal.WithLabelValues(string(hooks.HookPreFinish)).Add(0)
82-
}
83-
84-
func SetupPreHooks(config *handler.Config) error {
14+
func getHookHandler(config *handler.Config) hooks.HookHandler {
8515
if Flags.FileHooksDir != "" {
8616
stdout.Printf("Using '%s' for hooks", Flags.FileHooksDir)
8717

88-
hookHandler = &hooks.FileHook{
18+
return &file.FileHook{
8919
Directory: Flags.FileHooksDir,
9020
}
9121
} else if Flags.HttpHooksEndpoint != "" {
9222
stdout.Printf("Using '%s' as the endpoint for hooks", Flags.HttpHooksEndpoint)
9323

94-
hookHandler = &hooks.HttpHook{
24+
return &http.HttpHook{
9525
Endpoint: Flags.HttpHooksEndpoint,
9626
MaxRetries: Flags.HttpHooksRetry,
9727
Backoff: Flags.HttpHooksBackoff,
@@ -100,97 +30,18 @@ func SetupPreHooks(config *handler.Config) error {
10030
} else if Flags.GrpcHooksEndpoint != "" {
10131
stdout.Printf("Using '%s' as the endpoint for gRPC hooks", Flags.GrpcHooksEndpoint)
10232

103-
hookHandler = &hooks.GrpcHook{
33+
return &grpc.GrpcHook{
10434
Endpoint: Flags.GrpcHooksEndpoint,
10535
MaxRetries: Flags.GrpcHooksRetry,
10636
Backoff: Flags.GrpcHooksBackoff,
10737
}
10838
} else if Flags.PluginHookPath != "" {
10939
stdout.Printf("Using '%s' to load plugin for hooks", Flags.PluginHookPath)
11040

111-
hookHandler = &hooks.PluginHook{
41+
return &plugin.PluginHook{
11242
Path: Flags.PluginHookPath,
11343
}
11444
} else {
11545
return nil
11646
}
117-
118-
var enabledHooksString []string
119-
for _, h := range Flags.EnabledHooks {
120-
enabledHooksString = append(enabledHooksString, string(h))
121-
}
122-
123-
stdout.Printf("Enabled hook events: %s", strings.Join(enabledHooksString, ", "))
124-
125-
if err := hookHandler.Setup(); err != nil {
126-
return err
127-
}
128-
129-
config.PreUploadCreateCallback = preCreateCallback
130-
config.PreFinishResponseCallback = preFinishCallback
131-
132-
return nil
133-
}
134-
135-
func SetupPostHooks(handler *handler.Handler) {
136-
go func() {
137-
for {
138-
select {
139-
case event := <-handler.CompleteUploads:
140-
invokeHookAsync(hooks.HookPostFinish, event)
141-
case event := <-handler.TerminatedUploads:
142-
invokeHookAsync(hooks.HookPostTerminate, event)
143-
case event := <-handler.CreatedUploads:
144-
invokeHookAsync(hooks.HookPostCreate, event)
145-
case event := <-handler.UploadProgress:
146-
go postReceiveCallback(event)
147-
}
148-
}
149-
}()
150-
}
151-
152-
func invokeHookAsync(typ hooks.HookType, event handler.HookEvent) {
153-
go func() {
154-
// Error handling is taken care by the function.
155-
_, _, _ = invokeHookSync(typ, event)
156-
}()
157-
}
158-
159-
// invokeHookSync executes a hook of the given type with the given event data. If
160-
// the hook was not executed properly (e.g. an error occurred or not handler is installed),
161-
// `ok` will be false and `res` is not filled. `err` can contain the underlying error.
162-
// If `ok` is true, `res` contains the response as retrieved from the hook.
163-
// Therefore, a caller should always check `ok` and `err` before assuming that the
164-
// hook completed successfully.
165-
func invokeHookSync(typ hooks.HookType, event handler.HookEvent) (ok bool, res hooks.HookResponse, err error) {
166-
// Stop, if no hook handler is installed or this hook event is not enabled
167-
if hookHandler == nil || !hookTypeInSlice(typ, Flags.EnabledHooks) {
168-
return false, hooks.HookResponse{}, nil
169-
}
170-
171-
MetricsHookInvocationsTotal.WithLabelValues(string(typ)).Add(1)
172-
173-
id := event.Upload.ID
174-
175-
if Flags.VerboseOutput {
176-
logEv(stdout, "HookInvocationStart", "type", string(typ), "id", id)
177-
}
178-
179-
res, err = hookHandler.InvokeHook(hooks.HookRequest{
180-
Type: typ,
181-
Event: event,
182-
})
183-
if err != nil {
184-
// If an error occurs during the hook execution, we log and track the error, but do not
185-
// return a hook response.
186-
logEv(stderr, "HookInvocationError", "type", string(typ), "id", id, "error", err.Error())
187-
MetricsHookErrorsTotal.WithLabelValues(string(typ)).Add(1)
188-
return false, hooks.HookResponse{}, err
189-
}
190-
191-
if Flags.VerboseOutput {
192-
logEv(stdout, "HookInvocationFinish", "type", string(typ), "id", id)
193-
}
194-
195-
return true, res, nil
19647
}

cmd/tusd/cli/hooks/hooks.go

Lines changed: 0 additions & 76 deletions
This file was deleted.

cmd/tusd/cli/metrics.go

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"net/http"
55

66
"github.com/tus/tusd/v2/pkg/handler"
7+
"github.com/tus/tusd/v2/pkg/hooks"
78
"github.com/tus/tusd/v2/pkg/prometheuscollector"
89

910
"github.com/prometheus/client_golang/prometheus"
@@ -15,26 +16,10 @@ var MetricsOpenConnections = prometheus.NewGauge(prometheus.GaugeOpts{
1516
Help: "Current number of open connections.",
1617
})
1718

18-
var MetricsHookErrorsTotal = prometheus.NewCounterVec(
19-
prometheus.CounterOpts{
20-
Name: "tusd_hook_errors_total",
21-
Help: "Total number of execution errors per hook type.",
22-
},
23-
[]string{"hooktype"},
24-
)
25-
26-
var MetricsHookInvocationsTotal = prometheus.NewCounterVec(
27-
prometheus.CounterOpts{
28-
Name: "tusd_hook_invocations_total",
29-
Help: "Total number of invocations per hook type.",
30-
},
31-
[]string{"hooktype"},
32-
)
33-
3419
func SetupMetrics(mux *http.ServeMux, handler *handler.Handler) {
3520
prometheus.MustRegister(MetricsOpenConnections)
36-
prometheus.MustRegister(MetricsHookErrorsTotal)
37-
prometheus.MustRegister(MetricsHookInvocationsTotal)
21+
prometheus.MustRegister(hooks.MetricsHookErrorsTotal)
22+
prometheus.MustRegister(hooks.MetricsHookInvocationsTotal)
3823
prometheus.MustRegister(prometheuscollector.New(handler.Metrics))
3924

4025
stdout.Printf("Using %s as the metrics path.\n", Flags.MetricsPath)

0 commit comments

Comments
 (0)