Skip to content

Commit 912a9ae

Browse files
committed
Add pre-terminate hook
1 parent dcb841b commit 912a9ae

File tree

4 files changed

+60
-5
lines changed

4 files changed

+60
-5
lines changed

docs/_advanced-topics/hooks.md

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ The table below provides an overview of all available hooks.
3333
| post-receive | No | regularly while data is being transmitted. | logging upload progress, stopping running uploads | Yes |
3434
| pre-finish | Yes | after all upload data has been received but before a response is sent. | sending custom data when an upload is finished | No |
3535
| post-finish | No | after all upload data has been received and after a response is sent. | post-processing of upload, logging of upload end | Yes |
36+
| pre-terminate | Yes | before an upload will be terminated. | checking if an upload should be deleted | No |
3637
| post-terminate | No | after an upload has been terminated. | clean up of allocated resources | Yes |
3738

3839
Users should be aware of following things:

pkg/handler/config.go

+7
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ type Config struct {
7575
// If the error is non-nil, the error will be forwarded to the client. Furthermore,
7676
// HTTPResponse will be ignored and the error value can contain values for the HTTP response.
7777
PreFinishResponseCallback func(hook HookEvent) (HTTPResponse, error)
78+
// PreUploadTerminateCallback will be invoked on DELETE requests before an upload is terminated,
79+
// giving the application the opportunity to reject the termination. For example, to ensure resources
80+
// used by other services are not deleted.
81+
// If the callback returns no error, optional values from HTTPResponse will be contained in the HTTP response.
82+
// If the error is non-nil, the error will be forwarded to the client. Furthermore,
83+
// HTTPResponse will be ignored and the error value can contain values for the HTTP response.
84+
PreUploadTerminateCallback func(hook HookEvent) (HTTPResponse, error)
7885
// GracefulRequestCompletionTimeout is the timeout for operations to complete after an HTTP
7986
// request has ended (successfully or by error). For example, if an HTTP request is interrupted,
8087
// instead of stopping immediately, the handler and data store will be given some additional

pkg/handler/unrouted_handler.go

+16-4
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ var (
6060
ErrInvalidUploadDeferLength = NewError("ERR_INVALID_UPLOAD_LENGTH_DEFER", "invalid Upload-Defer-Length header", http.StatusBadRequest)
6161
ErrUploadStoppedByServer = NewError("ERR_UPLOAD_STOPPED", "upload has been stopped by server", http.StatusBadRequest)
6262
ErrUploadRejectedByServer = NewError("ERR_UPLOAD_REJECTED", "upload creation has been rejected by server", http.StatusBadRequest)
63+
ErrUploadTerminationRejected = NewError("ERR_UPLOAD_TERMINATION_REJECTED", "upload termination has been rejected by server", http.StatusBadRequest)
6364
ErrUploadInterrupted = NewError("ERR_UPLOAD_INTERRUPTED", "upload has been interrupted by another request for this upload resource", http.StatusBadRequest)
6465
ErrServerShutdown = NewError("ERR_SERVER_SHUTDOWN", "request has been interrupted because the server is shutting down", http.StatusServiceUnavailable)
6566
ErrOriginNotAllowed = NewError("ERR_ORIGIN_NOT_ALLOWED", "request origin is not allowed", http.StatusForbidden)
@@ -1203,23 +1204,34 @@ func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request)
12031204
}
12041205

12051206
var info FileInfo
1206-
if handler.config.NotifyTerminatedUploads {
1207+
if handler.config.NotifyTerminatedUploads || handler.config.PreUploadTerminateCallback != nil {
12071208
info, err = upload.GetInfo(c)
12081209
if err != nil {
12091210
handler.sendError(c, err)
12101211
return
12111212
}
12121213
}
12131214

1215+
resp := HTTPResponse{
1216+
StatusCode: http.StatusNoContent,
1217+
}
1218+
1219+
if handler.config.PreUploadTerminateCallback != nil {
1220+
resp2, err := handler.config.PreUploadTerminateCallback(newHookEvent(c, info))
1221+
if err != nil {
1222+
handler.sendError(c, err)
1223+
return
1224+
}
1225+
resp = resp.MergeWith(resp2)
1226+
}
1227+
12141228
err = handler.terminateUpload(c, upload, info)
12151229
if err != nil {
12161230
handler.sendError(c, err)
12171231
return
12181232
}
12191233

1220-
handler.sendResp(c, HTTPResponse{
1221-
StatusCode: http.StatusNoContent,
1222-
})
1234+
handler.sendResp(c, resp)
12231235
}
12241236

12251237
// terminateUpload passes a given upload to the DataStore's Terminater,

pkg/hooks/hooks.go

+36-1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ type HookResponse struct {
6868
// to the client.
6969
RejectUpload bool
7070

71+
// RejectTermination will cause the termination of the upload to be rejected, keeping the upload.
72+
// This value is only respected for pre-terminate hooks. For other hooks,
73+
// it is ignored. Use the HTTPResponse field to send details about the rejection
74+
// to the client.
75+
RejectTermination bool
76+
7177
// ChangeFileInfo can be set to change selected properties of an upload before
7278
// it has been created. See the handler.FileInfoChanges type for more details.
7379
// Changes are applied on a per-property basis, meaning that specifying just
@@ -91,10 +97,11 @@ const (
9197
HookPostCreate HookType = "post-create"
9298
HookPreCreate HookType = "pre-create"
9399
HookPreFinish HookType = "pre-finish"
100+
HookPreTerminate HookType = "pre-terminate"
94101
)
95102

96103
// AvailableHooks is a slice of all hooks that are implemented by tusd.
97-
var AvailableHooks []HookType = []HookType{HookPreCreate, HookPostCreate, HookPostReceive, HookPostTerminate, HookPostFinish, HookPreFinish}
104+
var AvailableHooks []HookType = []HookType{HookPreCreate, HookPostCreate, HookPostReceive, HookPreTerminate, HookPostTerminate, HookPostFinish, HookPreFinish}
98105

99106
func preCreateCallback(event handler.HookEvent, hookHandler HookHandler) (handler.HTTPResponse, handler.FileInfoChanges, error) {
100107
ok, hookRes, err := invokeHookSync(HookPreCreate, event, hookHandler)
@@ -128,6 +135,26 @@ func preFinishCallback(event handler.HookEvent, hookHandler HookHandler) (handle
128135
return httpRes, nil
129136
}
130137

138+
func preTerminateCallback(event handler.HookEvent, hookHandler HookHandler) (handler.HTTPResponse, error) {
139+
ok, hookRes, err := invokeHookSync(HookPreTerminate, event, hookHandler)
140+
if !ok || err != nil {
141+
return handler.HTTPResponse{}, err
142+
}
143+
144+
httpRes := hookRes.HTTPResponse
145+
146+
// If the hook response includes the instruction to reject the termination, reuse the error code
147+
// and message from ErrUploadTerminationRejected, but also include custom HTTP response values.
148+
if hookRes.RejectTermination {
149+
err := handler.ErrUploadTerminationRejected
150+
err.HTTPResponse = err.HTTPResponse.MergeWith(httpRes)
151+
152+
return handler.HTTPResponse{}, err
153+
}
154+
155+
return httpRes, nil
156+
}
157+
131158
func postReceiveCallback(event handler.HookEvent, hookHandler HookHandler) {
132159
ok, hookRes, _ := invokeHookSync(HookPostReceive, event, hookHandler)
133160
// invokeHookSync already logs the error, if any occurs. So by checking `ok`, we can ensure
@@ -166,12 +193,14 @@ func SetupHookMetrics() {
166193
MetricsHookErrorsTotal.WithLabelValues(string(HookPostCreate)).Add(0)
167194
MetricsHookErrorsTotal.WithLabelValues(string(HookPreCreate)).Add(0)
168195
MetricsHookErrorsTotal.WithLabelValues(string(HookPreFinish)).Add(0)
196+
MetricsHookErrorsTotal.WithLabelValues(string(HookPreTerminate)).Add(0)
169197
MetricsHookInvocationsTotal.WithLabelValues(string(HookPostFinish)).Add(0)
170198
MetricsHookInvocationsTotal.WithLabelValues(string(HookPostTerminate)).Add(0)
171199
MetricsHookInvocationsTotal.WithLabelValues(string(HookPostReceive)).Add(0)
172200
MetricsHookInvocationsTotal.WithLabelValues(string(HookPostCreate)).Add(0)
173201
MetricsHookInvocationsTotal.WithLabelValues(string(HookPreCreate)).Add(0)
174202
MetricsHookInvocationsTotal.WithLabelValues(string(HookPreFinish)).Add(0)
203+
MetricsHookInvocationsTotal.WithLabelValues(string(HookPreTerminate)).Add(0)
175204
}
176205

177206
func invokeHookAsync(typ HookType, event handler.HookEvent, hookHandler HookHandler) {
@@ -248,6 +277,12 @@ func NewHandlerWithHooks(config *handler.Config, hookHandler HookHandler, enable
248277
}
249278
}
250279

280+
if slices.Contains(enabledHooks, HookPreTerminate) {
281+
config.PreUploadTerminateCallback = func(event handler.HookEvent) (handler.HTTPResponse, error) {
282+
return preTerminateCallback(event, hookHandler)
283+
}
284+
}
285+
251286
// Create handler
252287
handler, err := handler.NewHandler(*config)
253288
if err != nil {

0 commit comments

Comments
 (0)