1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-08 19:46:02 +01:00

feat(libstore): support S3 object versioning via versionId parameter

S3 buckets support object versioning to prevent unexpected changes,
but Nix previously lacked the ability to fetch specific versions of
S3 objects. This adds support for a `versionId` query parameter in S3
URLs, enabling users to pin to specific object versions:

```
s3://bucket/key?region=us-east-1&versionId=abc123
```
This commit is contained in:
Bernardo Meurer Costa 2025-10-16 18:38:42 +00:00
parent e213fd64b6
commit e38128b90d
No known key found for this signature in database
5 changed files with 123 additions and 5 deletions

View file

@ -70,6 +70,25 @@ INSTANTIATE_TEST_SUITE_P(
},
"with_profile_and_region",
},
ParsedS3URLTestCase{
"s3://my-bucket/my-key.txt?versionId=abc123xyz",
{
.bucket = "my-bucket",
.key = {"my-key.txt"},
.versionId = "abc123xyz",
},
"with_versionId",
},
ParsedS3URLTestCase{
"s3://bucket/path/to/object?region=eu-west-1&versionId=version456",
{
.bucket = "bucket",
.key = {"path", "to", "object"},
.region = "eu-west-1",
.versionId = "version456",
},
"with_region_and_versionId",
},
ParsedS3URLTestCase{
"s3://bucket/key?endpoint=https://minio.local&scheme=http",
{
@ -222,6 +241,37 @@ INSTANTIATE_TEST_SUITE_P(
},
"https://s3.ap-southeast-2.amazonaws.com/bucket/path/to/file.txt",
"complex_path_and_region",
},
S3ToHttpsConversionTestCase{
ParsedS3URL{
.bucket = "my-bucket",
.key = {"my-key.txt"},
.versionId = "abc123xyz",
},
ParsedURL{
.scheme = "https",
.authority = ParsedURL::Authority{.host = "s3.us-east-1.amazonaws.com"},
.path = {"", "my-bucket", "my-key.txt"},
.query = {{"versionId", "abc123xyz"}},
},
"https://s3.us-east-1.amazonaws.com/my-bucket/my-key.txt?versionId=abc123xyz",
"with_versionId",
},
S3ToHttpsConversionTestCase{
ParsedS3URL{
.bucket = "versioned-bucket",
.key = {"path", "to", "object"},
.region = "eu-west-1",
.versionId = "version456",
},
ParsedURL{
.scheme = "https",
.authority = ParsedURL::Authority{.host = "s3.eu-west-1.amazonaws.com"},
.path = {"", "versioned-bucket", "path", "to", "object"},
.query = {{"versionId", "version456"}},
},
"https://s3.eu-west-1.amazonaws.com/versioned-bucket/path/to/object?versionId=version456",
"with_region_and_versionId",
}),
[](const ::testing::TestParamInfo<S3ToHttpsConversionTestCase> & info) { return info.param.description; });

View file

@ -26,6 +26,7 @@ struct ParsedS3URL
std::optional<std::string> profile;
std::optional<std::string> region;
std::optional<std::string> scheme;
std::optional<std::string> versionId;
/**
* The endpoint can be either missing, be an absolute URI (with a scheme like `http:`)
* or an authority (so an IP address or a registered name).

View file

@ -48,6 +48,7 @@ try {
.profile = getOptionalParam("profile"),
.region = getOptionalParam("region"),
.scheme = getOptionalParam("scheme"),
.versionId = getOptionalParam("versionId"),
.endpoint = [&]() -> decltype(ParsedS3URL::endpoint) {
if (!endpoint)
return std::monostate();
@ -73,6 +74,12 @@ ParsedURL ParsedS3URL::toHttpsUrl() const
auto regionStr = region.transform(toView).value_or("us-east-1");
auto schemeStr = scheme.transform(toView).value_or("https");
// Build query parameters (e.g., versionId if present)
StringMap queryParams;
if (versionId) {
queryParams["versionId"] = *versionId;
}
// Handle endpoint configuration using std::visit
return std::visit(
overloaded{
@ -85,6 +92,7 @@ ParsedURL ParsedS3URL::toHttpsUrl() const
.scheme = std::string{schemeStr},
.authority = ParsedURL::Authority{.host = "s3." + regionStr + ".amazonaws.com"},
.path = std::move(path),
.query = std::move(queryParams),
};
},
[&](const ParsedURL::Authority & auth) {
@ -96,6 +104,7 @@ ParsedURL ParsedS3URL::toHttpsUrl() const
.scheme = std::string{schemeStr},
.authority = auth,
.path = std::move(path),
.query = std::move(queryParams),
};
},
[&](const ParsedURL & endpointUrl) {
@ -107,6 +116,7 @@ ParsedURL ParsedS3URL::toHttpsUrl() const
.scheme = endpointUrl.scheme,
.authority = endpointUrl.authority,
.path = std::move(path),
.query = std::move(queryParams),
};
},
},