Skip to content

Commit 71765b6

Browse files
authored
hooks: Allow pre-create to change upload ID, meta data and storage details (#962)
* hooks: Allow pre-create to change upload ID, meta data and storage details * fixup! hooks: Allow pre-create to change upload ID, meta data and storage details * hooks: Add support for ChangeFileInfo in gRPC
1 parent bbf9e60 commit 71765b6

File tree

15 files changed

+965
-497
lines changed

15 files changed

+965
-497
lines changed

cmd/tusd/cli/hooks.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ func hookTypeInSlice(a hooks.HookType, list []hooks.HookType) bool {
1818
return false
1919
}
2020

21-
func preCreateCallback(event handler.HookEvent) (handler.HTTPResponse, error) {
21+
func preCreateCallback(event handler.HookEvent) (handler.HTTPResponse, handler.FileInfoChanges, error) {
2222
ok, hookRes, err := invokeHookSync(hooks.HookPreCreate, event)
2323
if !ok || err != nil {
24-
return handler.HTTPResponse{}, err
24+
return handler.HTTPResponse{}, handler.FileInfoChanges{}, err
2525
}
2626

2727
httpRes := hookRes.HTTPResponse
@@ -32,10 +32,12 @@ func preCreateCallback(event handler.HookEvent) (handler.HTTPResponse, error) {
3232
err := handler.ErrUploadRejectedByServer
3333
err.HTTPResponse = err.HTTPResponse.MergeWith(httpRes)
3434

35-
return handler.HTTPResponse{}, err
35+
return handler.HTTPResponse{}, handler.FileInfoChanges{}, err
3636
}
3737

38-
return httpRes, nil
38+
// Pass any changes regarding file info from the hook to the handler.
39+
changes := hookRes.ChangeFileInfo
40+
return httpRes, changes, nil
3941
}
4042

4143
func preFinishCallback(event handler.HookEvent) (handler.HTTPResponse, error) {

cmd/tusd/cli/hooks/grpc.go

+7
Original file line numberDiff line numberDiff line change
@@ -94,5 +94,12 @@ func unmarshal(res *pb.HookResponse) (hookRes HookResponse) {
9494
hookRes.HTTPResponse.Body = httpRes.Body
9595
}
9696

97+
changes := res.ChangeFileInfo
98+
if changes != nil {
99+
hookRes.ChangeFileInfo.ID = changes.Id
100+
hookRes.ChangeFileInfo.MetaData = changes.MetaData
101+
hookRes.ChangeFileInfo.Storage = changes.Storage
102+
}
103+
97104
return hookRes
98105
}

cmd/tusd/cli/hooks/hooks.go

+7
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ type HookResponse struct {
4848
// to the client.
4949
RejectUpload bool
5050

51+
// ChangeFileInfo can be set to change selected properties of an upload before
52+
// it has been created. See the handler.FileInfoChanges type for more details.
53+
// Changes are applied on a per-property basis, meaning that specifying just
54+
// one property leaves all others unchanged.
55+
// This value is only respected for pre-create hooks.
56+
ChangeFileInfo handler.FileInfoChanges
57+
5158
// StopUpload will cause the upload to be stopped during a PATCH request.
5259
// This value is only respected for post-receive hooks. For other hooks,
5360
// it is ignored. Use the HTTPResponse field to send details about the stop

cmd/tusd/cli/hooks/proto/v2/hook.proto

+41-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
// If this file gets changed, you must recompile the generate package in pkg/proto.
22
// To do this, install the Go protobuf toolchain as mentioned in
3-
// https://github.com/golang/protobuf#installation.
4-
// Then use following command to recompile it with gRPC support:
5-
// protoc --go_out=plugins=grpc:../../../../../pkg/proto/ v2/hook.proto
3+
// https://grpc.io/docs/languages/go/quickstart/#prerequisites.
4+
// Then use following command from the repository's root to recompile it with gRPC support:
5+
// protoc --go-grpc_out=./pkg/ --go_out=./pkg/ ./cmd/tusd/cli/hooks/proto/v2/hook.proto
66
// In addition, it may be necessary to update the protobuf or gRPC dependencies as well.
77

88
syntax = "proto3";
99
package v2;
1010

11+
option go_package = "proto/v2";
12+
1113
// HookRequest contains the information about the hook type, the involved upload,
1214
// and causing HTTP request.
1315
message HookRequest {
@@ -56,6 +58,35 @@ message FileInfo {
5658
map <string, string> storage = 9;
5759
}
5860

61+
// FileInfoChanges collects changes the should be made to a FileInfo object. This
62+
// can be done using the PreUploadCreateCallback to modify certain properties before
63+
// an upload is created. Properties which should not be modified (e.g. Size or Offset)
64+
// are intentionally left out here.
65+
message FileInfoChanges {
66+
// If ID is not empty, it will be passed to the data store, allowing
67+
// hooks to influence the upload ID. Be aware that a data store is not required to
68+
// respect a pre-defined upload ID and might overwrite or modify it. However,
69+
// all data stores in the github.com/tus/tusd package do respect pre-defined IDs.
70+
string id = 1;
71+
72+
// If MetaData is not nil, it replaces the entire user-defined meta data from
73+
// the upload creation request. You can add custom meta data fields this way
74+
// or ensure that only certain fields from the user-defined meta data are saved.
75+
// If you want to retain only specific entries from the user-defined meta data, you must
76+
// manually copy them into this MetaData field.
77+
// If you do not want to store any meta data, set this field to an empty map (`MetaData{}`).
78+
// If you want to keep the entire user-defined meta data, set this field to nil.
79+
map <string, string> metaData = 2;
80+
81+
// If Storage is not nil, it is passed to the data store to allow for minor adjustments
82+
// to the upload storage (e.g. destination file name). The details are specific for each
83+
// data store and should be looked up in their respective documentation.
84+
// Please be aware that this behavior is currently not supported by any data store in
85+
// the github.com/tus/tusd package.
86+
map <string, string> storage = 3;
87+
}
88+
89+
5990
// HTTPRequest contains basic details of an incoming HTTP request.
6091
message HTTPRequest {
6192
// Method is the HTTP method, e.g. POST or PATCH.
@@ -87,6 +118,13 @@ message HookResponse {
87118
// to the client.
88119
bool rejectUpload = 2;
89120

121+
// ChangeFileInfo can be set to change selected properties of an upload before
122+
// it has been created. See the handler.FileInfoChanges type for more details.
123+
// Changes are applied on a per-property basis, meaning that specifying just
124+
// one property leaves all others unchanged.
125+
// This value is only respected for pre-create hooks.
126+
FileInfoChanges changeFileInfo = 4;
127+
90128
// StopUpload will cause the upload to be stopped during a PATCH request.
91129
// This value is only respected for post-receive hooks. For other hooks,
92130
// it is ignored. Use the HTTPResponse field to send details about the stop
@@ -104,7 +142,6 @@ message HTTPResponse {
104142
string body = 3;
105143
}
106144

107-
108145
// The hook service definition.
109146
service HookHandler {
110147
// InvokeHook is invoked for every hook that is executed. HookRequest contains the

examples/hooks/grpc/Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
hook_pb2.py:
2-
python -m grpc_tools.protoc --proto_path=../../../cmd/tusd/cli/hooks/proto/v2/ hook.proto --python_out=. --grpc_python_out=.
1+
hook_pb2.py: ../../../cmd/tusd/cli/hooks/proto/v2/hook.proto
2+
python3 -m grpc_tools.protoc --proto_path=../../../cmd/tusd/cli/hooks/proto/v2/ hook.proto --python_out=. --grpc_python_out=.

examples/hooks/grpc/hook_pb2.py

+26-102
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/hooks/grpc/hook_pb2_grpc.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ class HookHandlerServicer(object):
2727
"""
2828

2929
def InvokeHook(self, request, context):
30-
"""Sends a hook
30+
"""InvokeHook is invoked for every hook that is executed. HookRequest contains the
31+
corresponding information about the hook type, the involved upload, and
32+
causing HTTP request.
33+
The return value HookResponse allows to stop or reject an upload, as well as modifying
34+
the HTTP response. See the documentation for HookResponse for more details.
3135
"""
3236
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
3337
context.set_details('Method not implemented!')

examples/hooks/grpc/server.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import grpc
22
from concurrent import futures
33
import time
4+
import uuid
45
import hook_pb2_grpc as pb2_grpc
56
import hook_pb2 as pb2
67

@@ -19,13 +20,22 @@ def InvokeHook(self, hook_request, context):
1920

2021
# Example: Use the pre-create hook to check if a filename has been supplied
2122
# using metadata. If not, the upload is rejected with a custom HTTP response.
23+
# In addition, a custom upload ID with a choosable prefix is supplied.
24+
# Metadata is configured, so that it only retains the filename meta data
25+
# and the creation time.
2226
if hook_request.type == 'pre-create':
23-
filename = hook_request.event.upload.metaData['filename']
24-
if filename == "":
27+
metaData = hook_request.event.upload.metaData
28+
isValid = 'filename' in metaData
29+
if not isValid:
2530
hook_response.rejectUpload = True
2631
hook_response.httpResponse.statusCode = 400
2732
hook_response.httpResponse.body = 'no filename provided'
2833
hook_response.httpResponse.headers['X-Some-Header'] = 'yes'
34+
else:
35+
hook_response.changeFileInfo.id = f'prefix-{uuid.uuid4()}'
36+
hook_response.changeFileInfo.metaData
37+
hook_response.changeFileInfo.metaData['filename'] = metaData['filename']
38+
hook_response.changeFileInfo.metaData['creation_time'] = time.ctime()
2939

3040
# Example: Use the post-finish hook to print information about a completed upload,
3141
# including its storage location.

0 commit comments

Comments
 (0)