#include "s3-binary-cache-store.hh" #include #include #include #include #include #include #include namespace nix { /* Helper: given an Outcome, return R in case of success, or throw an exception in case of an error. */ template R && checkAws(Aws::Utils::Outcome && outcome) { if (!outcome.IsSuccess()) throw Error(format("AWS error: %1%") % outcome.GetError().GetMessage()); return outcome.GetResultWithOwnership(); } S3BinaryCacheStore::S3BinaryCacheStore(const StoreFactory & storeFactory, const Path & secretKeyFile, const Path & publicKeyFile, const std::string & bucketName) : BinaryCacheStore(storeFactory, secretKeyFile, publicKeyFile) , bucketName(bucketName) , config(makeConfig()) , client(make_ref(*config)) { } ref S3BinaryCacheStore::makeConfig() { auto res = make_ref(); res->region = Aws::Region::EU_WEST_1; res->requestTimeoutMs = 600 * 1000; return res; } void S3BinaryCacheStore::init() { /* Create the bucket if it doesn't already exists. */ // FIXME: HeadBucket would be more appropriate, but doesn't return // an easily parsed 404 message. auto res = client->GetBucketLocation( Aws::S3::Model::GetBucketLocationRequest().WithBucket(bucketName)); if (!res.IsSuccess()) { if (res.GetError().GetErrorType() != Aws::S3::S3Errors::NO_SUCH_BUCKET) throw Error(format("AWS error: %1%") % res.GetError().GetMessage()); checkAws(client->CreateBucket( Aws::S3::Model::CreateBucketRequest() .WithBucket(bucketName) .WithCreateBucketConfiguration( Aws::S3::Model::CreateBucketConfiguration() .WithLocationConstraint( Aws::S3::Model::BucketLocationConstraint::eu_west_1)))); } BinaryCacheStore::init(); } bool S3BinaryCacheStore::fileExists(const std::string & path) { auto res = client->HeadObject( Aws::S3::Model::HeadObjectRequest() .WithBucket(bucketName) .WithKey(path)); if (!res.IsSuccess()) { auto & error = res.GetError(); if (error.GetErrorType() == Aws::S3::S3Errors::UNKNOWN // FIXME && error.GetMessage().find("404") != std::string::npos) return false; throw Error(format("AWS error: %1%") % error.GetMessage()); } return true; } void S3BinaryCacheStore::upsertFile(const std::string & path, const std::string & data) { auto request = Aws::S3::Model::PutObjectRequest() .WithBucket(bucketName) .WithKey(path); auto stream = std::make_shared(data); request.SetBody(stream); auto now1 = std::chrono::steady_clock::now(); auto result = checkAws(client->PutObject(request)); auto now2 = std::chrono::steady_clock::now(); printMsg(lvlError, format("uploaded ‘s3://%1%/%2%’ (%3% bytes) in %4% ms") % bucketName % path % data.size() % std::chrono::duration_cast(now2 - now1).count()); } std::string S3BinaryCacheStore::getFile(const std::string & path) { auto request = Aws::S3::Model::GetObjectRequest() .WithBucket(bucketName) .WithKey(path); request.SetResponseStreamFactory([&]() { return Aws::New("STRINGSTREAM"); }); auto now1 = std::chrono::steady_clock::now(); auto result = checkAws(client->GetObject(request)); auto now2 = std::chrono::steady_clock::now(); auto res = dynamic_cast(result.GetBody()).str(); printMsg(lvlError, format("downloaded ‘s3://%1%/%2%’ (%3% bytes) in %4% ms") % bucketName % path % res.size() % std::chrono::duration_cast(now2 - now1).count()); return res; } }