Skip to content

Commit dd0d006

Browse files
authored
Merge pull request #14375 from lovesegfault/nix-s3-upload-part
feat(libstore/s3-binary-cache-store): implement `uploadPart()`
2 parents 1d3f0ca + c592090 commit dd0d006

File tree

1 file changed

+95
-0
lines changed

1 file changed

+95
-0
lines changed

src/libstore/s3-binary-cache-store.cc

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include <cassert>
66
#include <ranges>
7+
#include <regex>
78

89
namespace nix {
910

@@ -26,6 +27,32 @@ class S3BinaryCacheStore : public virtual HttpBinaryCacheStore
2627

2728
private:
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

3158
void 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+
40135
StringSet S3BinaryCacheStoreConfig::uriSchemes()
41136
{
42137
return {"s3"};

0 commit comments

Comments
 (0)