Temporarily disable the backup command (#3635)
This commit is contained in:
parent
807b4681fa
commit
08fa85b3ab
5 changed files with 2 additions and 235 deletions
|
@ -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)
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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()),
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue