Temporarily disable the backup command ()

This commit is contained in:
Rushmore Mushambi 2024-03-05 20:38:03 +02:00 committed by GitHub
parent 807b4681fa
commit 08fa85b3ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 2 additions and 235 deletions

View file

@ -1,187 +0,0 @@
use super::abstraction::LevelSelectionArguments;
use crate::cli::abstraction::auth::{CredentialsBuilder, CredentialsLevel};
use crate::cli::abstraction::AuthArguments;
use crate::cnf::SERVER_AGENT;
use crate::err::Error;
use clap::Args;
use futures::TryStreamExt;
use reqwest::header::CONTENT_TYPE;
use reqwest::header::USER_AGENT;
use reqwest::RequestBuilder;
use reqwest::{Body, Client, Response};
use std::io::ErrorKind;
use surrealdb::headers::AUTH_DB;
use surrealdb::headers::AUTH_NS;
use tokio::fs::OpenOptions;
use tokio::io::{copy, stdin, stdout, AsyncWrite, AsyncWriteExt};
use tokio_util::io::{ReaderStream, StreamReader};
const TYPE: &str = "application/octet-stream";
#[derive(Args, Debug)]
pub struct BackupCommandArguments {
#[arg(help = "Path to the remote database or file from which to export")]
#[arg(value_parser = super::validator::into_valid)]
from: String,
#[arg(help = "Path to the remote database or file into which to import")]
#[arg(default_value = "-")]
#[arg(value_parser = super::validator::into_valid)]
into: String,
#[command(flatten)]
auth: AuthArguments,
#[command(flatten)]
level: LevelSelectionArguments,
}
pub async fn init(
BackupCommandArguments {
from,
into,
auth,
level,
}: BackupCommandArguments,
) -> Result<(), Error> {
// Initialize opentelemetry and logging
crate::telemetry::builder().with_log_level("error").init();
// Process the source->destination response
let into_local = into.ends_with(".db");
let from_local = from.ends_with(".db");
match (from.as_str(), into.as_str()) {
// From Stdin -> Into Stdout (are you trying to make an ouroboros?)
("-", "-") => Err(Error::OperationUnsupported),
// From Stdin -> Into File (possible but meaningless)
("-", _) if into_local => Err(Error::OperationUnsupported),
// From File -> Into Stdout (possible but meaningless, could be useful for source validation but not for now)
(_, "-") if from_local => Err(Error::OperationUnsupported),
// From File -> Into File (also possible but meaningless,
// but since the original function had this, I would choose to keep it as of now)
(from, into) if from_local && into_local => {
tokio::fs::copy(from, into).await?;
Ok(())
}
// From File -> Into HTTP
(from, into) if from_local => {
// Copy the data to the destination
let from = OpenOptions::new().read(true).open(from).await?;
post_http_sync_body(from, into, &auth, &level).await
}
// From HTTP -> Into File
(from, into) if into_local => {
// Try to open the output file
let into =
OpenOptions::new().write(true).create(true).truncate(true).open(into).await?;
backup_http_to_file(from, into, &auth, &level).await
}
// From HTTP -> Into Stdout
(from, "-") => backup_http_to_file(from, stdout(), &auth, &level).await,
// From Stdin -> Into File
("-", into) => {
let from = Body::wrap_stream(ReaderStream::new(stdin()));
post_http_sync_body(from, into, &auth, &level).await
}
// From HTTP -> Into HTTP
(from, into) => {
// Copy the data to the destination
let from = get_http_sync_body(from, &auth, &level).await?;
post_http_sync_body(from, into, &auth, &level).await
}
}
}
async fn post_http_sync_body<B: Into<Body>>(
from: B,
into: &str,
auth: &AuthArguments,
level: &LevelSelectionArguments,
) -> Result<(), Error> {
let mut req = Client::new()
.post(format!("{into}/sync"))
.header(USER_AGENT, SERVER_AGENT)
.header(CONTENT_TYPE, TYPE)
.body(from);
// Add authentication if needed
if auth.username.is_some() {
req = req_with_creds(req, auth, level)?;
}
req.send().await?.error_for_status()?;
Ok(())
}
async fn get_http_sync_body(
from: &str,
auth: &AuthArguments,
level: &LevelSelectionArguments,
) -> Result<Response, Error> {
let mut req = Client::new()
.get(format!("{from}/sync"))
.header(USER_AGENT, SERVER_AGENT)
.header(CONTENT_TYPE, TYPE);
// Add authentication if needed
if auth.username.is_some() {
req = req_with_creds(req, auth, level)?;
}
Ok(req.send().await?.error_for_status()?)
}
async fn backup_http_to_file<W: AsyncWrite + Unpin>(
from: &str,
mut into: W,
auth: &AuthArguments,
level: &LevelSelectionArguments,
) -> Result<(), Error> {
let mut from = StreamReader::new(
get_http_sync_body(from, auth, level)
.await?
.bytes_stream()
.map_err(|x| std::io::Error::new(ErrorKind::Other, x)),
);
// Copy the data to the destination
copy(&mut from, &mut into).await?;
into.flush().await?;
// Everything OK
Ok(())
}
fn req_with_creds(
req: RequestBuilder,
AuthArguments {
username,
password,
auth_level,
}: &AuthArguments,
LevelSelectionArguments {
namespace,
database,
}: &LevelSelectionArguments,
) -> Result<RequestBuilder, Error> {
let builder = CredentialsBuilder::default()
.with_username(username.as_deref())
.with_password(password.as_deref())
.with_namespace(namespace.as_deref())
.with_database(database.as_deref());
let req = match auth_level {
CredentialsLevel::Root => {
let creds = builder.root()?;
req.basic_auth(creds.username, Some(creds.password))
}
CredentialsLevel::Namespace => {
let creds = builder.namespace()?;
req.header(&AUTH_NS, creds.namespace).basic_auth(creds.username, Some(creds.password))
}
CredentialsLevel::Database => {
let creds = builder.database()?;
req.header(&AUTH_NS, creds.namespace)
.header(&AUTH_DB, creds.database)
.basic_auth(creds.username, Some(creds.password))
}
};
Ok(req)
}

View file

@ -1,5 +1,4 @@
pub(crate) mod abstraction;
mod backup;
mod config;
mod export;
mod import;
@ -18,7 +17,6 @@ mod version_client;
use crate::cli::version_client::VersionClient;
use crate::cnf::{LOGO, PKG_VERSION};
use crate::env::RELEASE;
use backup::BackupCommandArguments;
use clap::{Parser, Subcommand};
pub use config::CF;
use export::ExportCommandArguments;
@ -66,8 +64,10 @@ struct Cli {
enum Commands {
#[command(about = "Start the database server")]
Start(StartCommandArguments),
/* Not implemented yet
#[command(about = "Backup data to or from an existing database")]
Backup(BackupCommandArguments),
*/
#[command(about = "Import a SurrealQL script into an existing database")]
Import(ImportCommandArguments),
#[command(about = "Export an existing database as a SurrealQL script")]
@ -118,7 +118,6 @@ pub async fn init() -> ExitCode {
// After version warning we can run the respective command
let output = match args.command {
Commands::Start(args) => start::init(args).await,
Commands::Backup(args) => backup::init(args).await,
Commands::Import(args) => import::init(args).await,
Commands::Export(args) => export::init(args).await,
Commands::Version(args) => version::init(args).await,

View file

@ -52,16 +52,6 @@ pub(crate) fn endpoint_valid(v: &str) -> Result<String, String> {
}
}
pub(crate) fn into_valid(v: &str) -> Result<String, String> {
match v {
v if v.ends_with(".db") => Ok(v.to_string()),
v if v.starts_with("http://") => Ok(v.to_string()),
v if v.starts_with("https://") => Ok(v.to_string()),
"-" => Ok(v.to_string()),
_ => Err(String::from("Provide a valid database connection string, or the path to a file")),
}
}
pub(crate) fn key_valid(v: &str) -> Result<String, String> {
match v.len() {
16 => Ok(v.to_string()),

View file

@ -16,9 +16,6 @@ Y88b d88P Y88b 888 888 888 Y8b. 888 888 888 888 .d88P 888 d88P
/// The publicly visible name of the server
pub const PKG_NAME: &str = "surrealdb";
/// The publicly visible user-agent of the command-line tool
pub const SERVER_AGENT: &str = concat!("SurrealDB ", env!("CARGO_PKG_VERSION"));
/// The public endpoint for the administration interface
pub const APP_ENDPOINT: &str = "https://surrealdb.com/app";

View file

@ -6,7 +6,6 @@ mod cli_integration {
use common::Format;
use common::Socket;
use serde_json::json;
use std::fs;
use std::fs::File;
use std::time;
use surrealdb::fflags::FFLAGS;
@ -124,16 +123,6 @@ mod cli_integration {
assert_eq!(rest, "[\n\t{\n\t\tid: thing:one\n\t}\n]\n\n", "failed to send sql: {args}");
}
info!("* Unfinished backup CLI");
{
let file = common::tmp_file("backup.db");
let args = format!("backup {creds} http://{addr} {file}");
common::run(&args).output().expect("failed to run backup: {args}");
// TODO: Once backups are functional, update this test.
assert_eq!(fs::read_to_string(file).unwrap(), "Save");
}
info!("* Advanced uncomputed variable to be computed before saving");
{
let args = format!(
@ -299,15 +288,6 @@ mod cli_integration {
common::run(&args).output().unwrap_or_else(|_| panic!("failed to run import: {args}"));
}
info!("* Root user can do backups");
{
let file = common::tmp_file("backup.db");
let args = format!("backup {creds} http://{addr} {file}");
common::run(&args).output().unwrap_or_else(|_| panic!("failed to run backup: {args}"));
// TODO: Once backups are functional, update this test.
assert_eq!(fs::read_to_string(file).unwrap(), "Save");
}
server.finish()
}
@ -616,18 +596,6 @@ mod cli_integration {
);
}
info!("* Can't do backups");
{
let args = format!("backup {creds} http://{addr}");
let output = common::run(&args).output();
// TODO(sgirones): Once backups are functional, update this test.
// assert!(
// output.unwrap_err().contains("Forbidden"),
// "anonymous user shouldn't be able to backup",
// output
// );
assert!(output.is_ok(), "anonymous user can do backups: {:?}", output);
}
server.finish();
}