44
55#include < cassert>
66#include < ranges>
7+ #include < regex>
78
89namespace nix {
910
@@ -26,6 +27,32 @@ class S3BinaryCacheStore : public virtual HttpBinaryCacheStore
2627
2728private:
2829 ref<S3BinaryCacheStoreConfig> s3Config;
30+
31+ /* *
32+ * Creates a multipart upload for large objects to S3.
33+ *
34+ * @see
35+ * https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateMultipartUpload.html#API_CreateMultipartUpload_RequestSyntax
36+ */
37+ std::string createMultipartUpload (
38+ std::string_view key, std::string_view mimeType, std::optional<std::string_view> contentEncoding);
39+
40+ /* *
41+ * Uploads a single part of a multipart upload
42+ *
43+ * @see https://docs.aws.amazon.com/AmazonS3/latest/API/API_UploadPart.html#API_UploadPart_RequestSyntax
44+ *
45+ * @returns the [ETag](https://en.wikipedia.org/wiki/HTTP_ETag)
46+ */
47+ std::string uploadPart (std::string_view key, std::string_view uploadId, uint64_t partNumber, std::string data);
48+
49+ /* *
50+ * Abort a multipart upload
51+ *
52+ * @see
53+ * https://docs.aws.amazon.com/AmazonS3/latest/API/API_AbortMultipartUpload.html#API_AbortMultipartUpload_RequestSyntax
54+ */
55+ void abortMultipartUpload (std::string_view key, std::string_view uploadId);
2956};
3057
3158void S3BinaryCacheStore::upsertFile (
@@ -37,6 +64,74 @@ void S3BinaryCacheStore::upsertFile(
3764 HttpBinaryCacheStore::upsertFile (path, istream, mimeType, sizeHint);
3865}
3966
67+ std::string S3BinaryCacheStore::createMultipartUpload (
68+ std::string_view key, std::string_view mimeType, std::optional<std::string_view> contentEncoding)
69+ {
70+ auto req = makeRequest (key);
71+
72+ // setupForS3() converts s3:// to https:// but strips query parameters
73+ // So we call it first, then add our multipart parameters
74+ req.setupForS3 ();
75+
76+ auto url = req.uri .parsed ();
77+ url.query [" uploads" ] = " " ;
78+ req.uri = VerbatimURL (url);
79+
80+ req.method = HttpMethod::POST;
81+ req.data = " " ;
82+ req.mimeType = mimeType;
83+
84+ if (contentEncoding) {
85+ req.headers .emplace_back (" Content-Encoding" , *contentEncoding);
86+ }
87+
88+ auto result = getFileTransfer ()->enqueueFileTransfer (req).get ();
89+
90+ std::regex uploadIdRegex (" <UploadId>([^<]+)</UploadId>" );
91+ std::smatch match;
92+
93+ if (std::regex_search (result.data , match, uploadIdRegex)) {
94+ return match[1 ];
95+ }
96+
97+ throw Error (" S3 CreateMultipartUpload response missing <UploadId>" );
98+ }
99+
100+ std::string
101+ S3BinaryCacheStore::uploadPart (std::string_view key, std::string_view uploadId, uint64_t partNumber, std::string data)
102+ {
103+ auto req = makeRequest (key);
104+ req.setupForS3 ();
105+
106+ auto url = req.uri .parsed ();
107+ url.query [" partNumber" ] = std::to_string (partNumber);
108+ url.query [" uploadId" ] = uploadId;
109+ req.uri = VerbatimURL (url);
110+ req.data = std::move (data);
111+ req.mimeType = " application/octet-stream" ;
112+
113+ auto result = getFileTransfer ()->enqueueFileTransfer (req).get ();
114+
115+ if (result.etag .empty ()) {
116+ throw Error (" S3 UploadPart response missing ETag for part %d" , partNumber);
117+ }
118+
119+ return std::move (result.etag );
120+ }
121+
122+ void S3BinaryCacheStore::abortMultipartUpload (std::string_view key, std::string_view uploadId)
123+ {
124+ auto req = makeRequest (key);
125+ req.setupForS3 ();
126+
127+ auto url = req.uri .parsed ();
128+ url.query [" uploadId" ] = uploadId;
129+ req.uri = VerbatimURL (url);
130+ req.method = HttpMethod::DELETE;
131+
132+ getFileTransfer ()->enqueueFileTransfer (req).get ();
133+ }
134+
40135StringSet S3BinaryCacheStoreConfig::uriSchemes ()
41136{
42137 return {" s3" };
0 commit comments