Add support for sync to AWS (#3723)

This is closely modeled on support for sync to GCP (#3223), but with
different authentication options to mirror typical usage of AWS.
This commit is contained in:
Dustin J. Mitchell
2024-12-16 20:08:50 -05:00
committed by GitHub
parent ff325bc19e
commit 758ac8f850
7 changed files with 506 additions and 150 deletions

View File

@@ -318,6 +318,12 @@ std::string configurationDefaults =
"#sync.server.client_id # Client ID for sync to a server\n"
"#sync.server.url # URL of the sync server\n"
"#sync.local.server_dir # Directory for local sync\n"
"#sync.aws.region # region for AWS sync\n"
"#sync.aws.bucket # bucket for AWS sync\n"
"#sync.aws.access_key_id # access_key_id for AWS sync\n"
"#sync.aws.secret_access_key # secret_access_key for AWS sync\n"
"#sync.aws.profile # profile name for AWS sync\n"
"#sync.aws.default_credentials # use default credentials for AWS sync\n"
"#sync.gcp.credential_path # Path to JSON file containing credentials to "
"authenticate GCP Sync\n"
"#sync.gcp.bucket # Bucket for sync to GCP\n"

View File

@@ -194,9 +194,15 @@ int CmdShow::execute(std::string& output) {
" search.case.sensitive"
" sugar"
" summary.all.projects"
" sync.local.server_dir"
" sync.aws.access_key_id"
" sync.aws.bucket"
" sync.aws.default_credentials"
" sync.aws.profile"
" sync.aws.region"
" sync.aws.secret_access_key"
" sync.gcp.credential_path"
" sync.gcp.bucket"
" sync.local.server_dir"
" sync.server.client_id"
" sync.encryption_secret"
" sync.server.url"

View File

@@ -38,6 +38,7 @@
#include <taskchampion-cpp/lib.h>
#include <util.h>
#include <iostream>
#include <sstream>
////////////////////////////////////////////////////////////////////////////////
@@ -65,12 +66,11 @@ int CmdSync::execute(std::string& output) {
bool avoid_snapshots = false;
bool verbose = Context::getContext().verbose("sync");
// If no server is set up, quit.
std::string origin = Context::getContext().config.get("sync.server.origin");
std::string url = Context::getContext().config.get("sync.server.url");
std::string server_dir = Context::getContext().config.get("sync.local.server_dir");
std::string client_id = Context::getContext().config.get("sync.server.client_id");
std::string gcp_credential_path = Context::getContext().config.get("sync.gcp.credential_path");
std::string aws_bucket = Context::getContext().config.get("sync.aws.bucket");
std::string gcp_bucket = Context::getContext().config.get("sync.gcp.bucket");
std::string encryption_secret = Context::getContext().config.get("sync.encryption_secret");
@@ -85,7 +85,55 @@ int CmdSync::execute(std::string& output) {
out << format("Syncing with {1}", server_dir) << '\n';
}
replica->sync_to_local(server_dir, avoid_snapshots);
} else if (aws_bucket != "") {
std::string aws_region = Context::getContext().config.get("sync.aws.region");
std::string aws_profile = Context::getContext().config.get("sync.aws.profile");
std::string aws_access_key_id = Context::getContext().config.get("sync.aws.access_key_id");
std::string aws_secret_access_key =
Context::getContext().config.get("sync.aws.secret_access_key");
std::string aws_default_credentials =
Context::getContext().config.get("sync.aws.default_credentials");
if (aws_region == "") {
throw std::string("sync.aws.region is required");
}
if (encryption_secret == "") {
throw std::string("sync.encryption_secret is required");
}
bool using_profile = false;
bool using_creds = false;
bool using_default = false;
if (aws_profile != "") {
using_profile = true;
}
if (aws_access_key_id != "" || aws_secret_access_key != "") {
using_creds = true;
}
if (aws_default_credentials != "") {
using_default = true;
}
if (using_profile + using_creds + using_default != 1) {
throw std::string("exactly one method of specifying AWS credentials is required");
}
if (verbose) {
out << format("Syncing with AWS bucket {1}", aws_bucket) << '\n';
}
if (using_profile) {
replica->sync_to_aws_with_profile(aws_region, aws_bucket, aws_profile, encryption_secret,
avoid_snapshots);
} else if (using_creds) {
replica->sync_to_aws_with_access_key(aws_region, aws_bucket, aws_access_key_id,
aws_secret_access_key, encryption_secret,
avoid_snapshots);
} else {
replica->sync_to_aws_with_default_creds(aws_region, aws_bucket, encryption_secret,
avoid_snapshots);
}
} else if (gcp_bucket != "") {
std::string gcp_credential_path = Context::getContext().config.get("sync.gcp.credential_path");
if (encryption_secret == "") {
throw std::string("sync.encryption_secret is required");
}

View File

@@ -9,7 +9,7 @@ rust-version = "1.78.0" # MSRV
crate-type = ["staticlib"]
[dependencies]
taskchampion = "1.0.0"
taskchampion = "=1.0.2"
cxx = "1.0.133"
[features]

View File

@@ -164,6 +164,36 @@ mod ffi {
avoid_snapshots: bool,
) -> Result<()>;
/// Sync with a server created from `ServerConfig::Aws` using `AwsCredentials::Profile`.
fn sync_to_aws_with_profile(
&mut self,
region: String,
bucket: String,
profile_name: String,
encryption_secret: &CxxString,
avoid_snapshots: bool,
) -> Result<()>;
/// Sync with a server created from `ServerConfig::Aws` using `AwsCredentials::AccessKey`.
fn sync_to_aws_with_access_key(
&mut self,
region: String,
bucket: String,
access_key_id: String,
secret_access_key: String,
encryption_secret: &CxxString,
avoid_snapshots: bool,
) -> Result<()>;
/// Sync with a server created from `ServerConfig::Aws` using `AwsCredentials::Default`.
fn sync_to_aws_with_default_creds(
&mut self,
region: String,
bucket: String,
encryption_secret: &CxxString,
avoid_snapshots: bool,
) -> Result<()>;
/// Sync with a server created from `ServerConfig::Gcp`.
///
/// An empty value for `credential_path` is converted to `Option::None`.
@@ -580,6 +610,63 @@ impl Replica {
Ok(self.0.sync(&mut server, avoid_snapshots)?)
}
fn sync_to_aws_with_profile(
&mut self,
region: String,
bucket: String,
profile_name: String,
encryption_secret: &CxxString,
avoid_snapshots: bool,
) -> Result<(), CppError> {
let mut server = tc::server::ServerConfig::Aws {
region,
bucket,
credentials: tc::server::AwsCredentials::Profile { profile_name },
encryption_secret: encryption_secret.as_bytes().to_vec(),
}
.into_server()?;
Ok(self.0.sync(&mut server, avoid_snapshots)?)
}
fn sync_to_aws_with_access_key(
&mut self,
region: String,
bucket: String,
access_key_id: String,
secret_access_key: String,
encryption_secret: &CxxString,
avoid_snapshots: bool,
) -> Result<(), CppError> {
let mut server = tc::server::ServerConfig::Aws {
region,
bucket,
credentials: tc::server::AwsCredentials::AccessKey {
access_key_id,
secret_access_key,
},
encryption_secret: encryption_secret.as_bytes().to_vec(),
}
.into_server()?;
Ok(self.0.sync(&mut server, avoid_snapshots)?)
}
fn sync_to_aws_with_default_creds(
&mut self,
region: String,
bucket: String,
encryption_secret: &CxxString,
avoid_snapshots: bool,
) -> Result<(), CppError> {
let mut server = tc::server::ServerConfig::Aws {
region,
bucket,
credentials: tc::server::AwsCredentials::Default,
encryption_secret: encryption_secret.as_bytes().to_vec(),
}
.into_server()?;
Ok(self.0.sync(&mut server, avoid_snapshots)?)
}
fn sync_to_gcp(
&mut self,
bucket: String,