Skip to content

Commit bd4751b

Browse files
committed
refactor(libstore/s3-binary-cache-store): implement upload()
This stops us from just delegating to `HttpBinaryCacheStore::upsertFile`, handling compression in our `upsertFile` override, which will be necessary for multipart uploads.
1 parent 00f4a86 commit bd4751b

File tree

3 files changed

+91
-8
lines changed

3 files changed

+91
-8
lines changed

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

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,28 +134,38 @@ bool HttpBinaryCacheStore::fileExists(const std::string & path)
134134
}
135135
}
136136

137+
void HttpBinaryCacheStore::uploadData(
138+
std::string_view path, std::string data, std::string_view mimeType, std::optional<std::string_view> contentEncoding)
139+
{
140+
auto req = makeRequest(path);
141+
req.method = HttpMethod::PUT;
142+
143+
if (contentEncoding) {
144+
req.headers.emplace_back("Content-Encoding", *contentEncoding);
145+
}
146+
147+
req.data = std::move(data);
148+
req.mimeType = mimeType;
149+
150+
getFileTransfer()->upload(req);
151+
}
152+
137153
void HttpBinaryCacheStore::upsertFile(
138154
const std::string & path,
139155
std::shared_ptr<std::basic_iostream<char>> istream,
140156
const std::string & mimeType,
141157
uint64_t sizeHint)
142158
{
143-
auto req = makeRequest(path);
144-
req.method = HttpMethod::PUT;
145159
auto data = StreamToSourceAdapter(istream).drain();
146160

147161
auto compressionMethod = getCompressionMethod(path);
148162

149163
if (compressionMethod) {
150164
data = compress(*compressionMethod, data);
151-
req.headers.emplace_back("Content-Encoding", *compressionMethod);
152165
}
153166

154-
req.data = std::move(data);
155-
req.mimeType = mimeType;
156-
157167
try {
158-
getFileTransfer()->upload(req);
168+
uploadData(path, std::move(data), mimeType, compressionMethod);
159169
} catch (FileTransferError & e) {
160170
UploadToHTTP err(e.message());
161171
err.addTrace({}, "while uploading to HTTP binary cache at '%s'", config->cacheUri.to_string());

src/libstore/include/nix/store/http-binary-cache-store.hh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,20 @@ protected:
8888

8989
FileTransferRequest makeRequest(std::string_view path);
9090

91+
/**
92+
* Uploads data to the binary cache.
93+
*
94+
* @param path The path in the binary cache to upload to
95+
* @param data The data to upload (should already be compressed if needed)
96+
* @param mimeType The MIME type of the content
97+
* @param contentEncoding Optional Content-Encoding header value (e.g., "xz", "br")
98+
*/
99+
void uploadData(
100+
std::string_view path,
101+
std::string data,
102+
std::string_view mimeType,
103+
std::optional<std::string_view> contentEncoding);
104+
91105
void getFile(const std::string & path, Sink & sink) override;
92106

93107
void getFile(const std::string & path, Callback<std::optional<std::string>> callback) noexcept override;

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

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
#include "nix/store/s3-binary-cache-store.hh"
22
#include "nix/store/http-binary-cache-store.hh"
33
#include "nix/store/store-registration.hh"
4+
#include "nix/util/error.hh"
5+
#include "nix/util/logging.hh"
6+
#include "nix/util/compression.hh"
7+
#include "nix/util/serialise.hh"
8+
#include "nix/util/util.hh"
49

510
#include <cassert>
611
#include <ranges>
712
#include <regex>
813

914
namespace nix {
1015

16+
MakeError(UploadToS3, Error);
17+
18+
static constexpr uint64_t AWS_MAX_PART_SIZE = 5ULL * 1024 * 1024 * 1024; // 5GiB
19+
1120
class S3BinaryCacheStore : public virtual HttpBinaryCacheStore
1221
{
1322
public:
@@ -28,6 +37,21 @@ class S3BinaryCacheStore : public virtual HttpBinaryCacheStore
2837
private:
2938
ref<S3BinaryCacheStoreConfig> s3Config;
3039

40+
/**
41+
* Uploads a file to S3 using a regular (non-multipart) upload.
42+
*
43+
* This method is suitable for files up to 5GiB in size. For larger files,
44+
* multipart upload should be used instead.
45+
*
46+
* @see https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html
47+
*/
48+
void upload(
49+
std::string_view path,
50+
std::shared_ptr<std::basic_iostream<char>> istream,
51+
const uint64_t sizeHint,
52+
std::string_view mimeType,
53+
std::optional<std::string_view> contentEncoding);
54+
3155
/**
3256
* Creates a multipart upload for large objects to S3.
3357
*
@@ -61,7 +85,42 @@ void S3BinaryCacheStore::upsertFile(
6185
const std::string & mimeType,
6286
uint64_t sizeHint)
6387
{
64-
HttpBinaryCacheStore::upsertFile(path, istream, mimeType, sizeHint);
88+
auto compressionMethod = getCompressionMethod(path);
89+
std::optional<std::string> contentEncoding = std::nullopt;
90+
91+
if (compressionMethod) {
92+
auto compressedData = compress(*compressionMethod, StreamToSourceAdapter(istream).drain());
93+
sizeHint = compressedData.size();
94+
istream = std::make_shared<std::stringstream>(std::move(compressedData));
95+
contentEncoding = compressionMethod;
96+
}
97+
98+
upload(path, istream, sizeHint, mimeType, contentEncoding);
99+
}
100+
101+
void S3BinaryCacheStore::upload(
102+
std::string_view path,
103+
std::shared_ptr<std::basic_iostream<char>> istream,
104+
const uint64_t sizeHint,
105+
std::string_view mimeType,
106+
std::optional<std::string_view> contentEncoding)
107+
{
108+
debug("using S3 regular upload for '%s' (%d bytes)", path, sizeHint);
109+
if (sizeHint > AWS_MAX_PART_SIZE)
110+
throw Error(
111+
"file too large for S3 upload without multipart: %s would exceed maximum size of %s. Consider enabling multipart-upload.",
112+
renderSize(sizeHint),
113+
renderSize(AWS_MAX_PART_SIZE));
114+
115+
auto data = StreamToSourceAdapter(istream).drain();
116+
117+
try {
118+
uploadData(path, std::move(data), mimeType, contentEncoding);
119+
} catch (FileTransferError & e) {
120+
UploadToS3 err(e.message());
121+
err.addTrace({}, "while uploading to S3 binary cache at '%s'", config->cacheUri.to_string());
122+
throw err;
123+
}
65124
}
66125

67126
std::string S3BinaryCacheStore::createMultipartUpload(

0 commit comments

Comments
 (0)