Add sysctl alter user

We will now also only output server logs to the terminal in CI, iff
there is an error. Otherwise, it plagues output.
next
Sayan Nandan 10 months ago
parent 9e1ce8c3e2
commit bbcb7acb95
No known key found for this signature in database
GPG Key ID: 42EEDF4AE9D96B54

@ -14,15 +14,15 @@ All changes in this project will be noted in this file.
- `space`s are the equivalent of the `keyspace` from previous versions - `space`s are the equivalent of the `keyspace` from previous versions
- `model`s are the equivalent of `table`s from previous version - `model`s are the equivalent of `table`s from previous version
- The following queries were added: - The following queries were added:
- `CREATE SPACE ...` - `CREATE SPACE [IF NOT EXISTS] ...`
- `CREATE MODEL ...` - `CREATE MODEL [IF NOT EXISTS] ...`
- Nested lists are now supported - Nested lists are now supported
- Type definitions are now supported - Type definitions are now supported
- Multiple fields are now supported - Multiple fields are now supported
- `ALTER SPACE ...` - `ALTER SPACE ...`
- `ALTER MODEL ...` - `ALTER MODEL ...`
- `DROP SPACE ...` - `DROP SPACE [IF EXISTS] ...`
- `DROP MODEL ...` - `DROP MODEL [IF EXISTS] ...`
- `USE <space>`: - `USE <space>`:
- works just like SQL - works just like SQL
- **does not work with DDL queries**: the reason it works in this way is to prevent accidental deletes - **does not work with DDL queries**: the reason it works in this way is to prevent accidental deletes
@ -56,6 +56,7 @@ All changes in this project will be noted in this file.
- `DELETE FROM <space>.<model> WHERE <primary_key_column> = <value>` - `DELETE FROM <space>.<model> WHERE <primary_key_column> = <value>`
- DCL: - DCL:
- `SYSCTL CREATE USER <name> WITH { password: <password> }` - `SYSCTL CREATE USER <name> WITH { password: <password> }`
- `SYSCTL ALTER USER <name> WITH { password: <new password> }`
- `SYSCTL DROP USER <name>` - `SYSCTL DROP USER <name>`
#### Fractal engine #### Fractal engine

@ -52,10 +52,13 @@ fn main() {
Builder::new() Builder::new()
.parse_filters(&env::var("SKYHARNESS_LOG").unwrap_or_else(|_| "info".to_owned())) .parse_filters(&env::var("SKYHARNESS_LOG").unwrap_or_else(|_| "info".to_owned()))
.init(); .init();
// avoid verbose logging env::set_var("SKY_LOG", "trace");
env::set_var("SKY_LOG", "error");
if let Err(e) = runner() { if let Err(e) = runner() {
error!("harness failed with: {}", e); error!("harness failed with: {}", e);
error!("fetching logs from server processes");
for ret in test::get_children() {
ret.print_logs();
}
process::exit(0x01); process::exit(0x01);
} }
} }

@ -46,6 +46,7 @@ use {
std::{fs, io::Write}, std::{fs, io::Write},
}; };
mod svc; mod svc;
pub use svc::get_children;
/// Run the test suite /// Run the test suite
pub fn run_test() -> HarnessResult<()> { pub fn run_test() -> HarnessResult<()> {

@ -33,12 +33,73 @@ use {
HarnessError, HarnessResult, ROOT_DIR, HarnessError, HarnessResult, ROOT_DIR,
}, },
std::{ std::{
cell::RefCell,
io::ErrorKind, io::ErrorKind,
path::Path, path::Path,
process::{Child, Command}, process::{Child, Command, Output, Stdio},
}, },
}; };
thread_local! {
static CHILDREN: RefCell<Vec<(&'static str, Child)>> = RefCell::default();
}
pub struct ChildStatus {
id: &'static str,
stdout: String,
stderr: String,
exit_code: i32,
}
impl ChildStatus {
pub fn new(id: &'static str, stdout: String, stderr: String, exit_code: i32) -> Self {
Self {
id,
stdout,
stderr,
exit_code,
}
}
pub fn print_logs(&self) {
println!(
"######################### LOGS FROM {} #########################",
self.id
);
println!("-> exit code: `{}`", self.exit_code);
if !self.stdout.is_empty() {
println!("+++++++++++++++++++++ STDOUT +++++++++++++++++++++");
println!("{}", self.stdout);
println!("++++++++++++++++++++++++++++++++++++++++++++++++++");
}
if !self.stderr.is_empty() {
println!("+++++++++++++++++++++ STDERR +++++++++++++++++++++");
println!("{}", self.stderr);
println!("++++++++++++++++++++++++++++++++++++++++++++++++++");
}
println!("######################### ############ #########################");
}
}
pub fn get_children() -> Vec<ChildStatus> {
CHILDREN.with(|c| {
let mut ret = vec![];
for (name, child) in c.borrow_mut().drain(..) {
let Output {
status,
stdout,
stderr,
} = child.wait_with_output().unwrap();
ret.push(ChildStatus::new(
name,
String::from_utf8(stdout).unwrap(),
String::from_utf8(stderr).unwrap(),
status.code().unwrap(),
))
}
ret
})
}
#[cfg(windows)] #[cfg(windows)]
/// The powershell script hack to send CTRL+C using kernel32 /// The powershell script hack to send CTRL+C using kernel32
const POWERSHELL_SCRIPT: &str = include_str!("../../../ci/windows/stop.ps1"); const POWERSHELL_SCRIPT: &str = include_str!("../../../ci/windows/stop.ps1");
@ -69,6 +130,8 @@ pub fn get_run_server_cmd(server_id: &'static str, target_folder: impl AsRef<Pat
]; ];
let mut cmd = util::assemble_command_from_slice(&args); let mut cmd = util::assemble_command_from_slice(&args);
cmd.current_dir(server_id); cmd.current_dir(server_id);
cmd.stdout(Stdio::null());
cmd.stderr(Stdio::piped());
#[cfg(windows)] #[cfg(windows)]
cmd.creation_flags(CREATE_NEW_CONSOLE); cmd.creation_flags(CREATE_NEW_CONSOLE);
cmd cmd
@ -174,15 +237,15 @@ fn wait_for_shutdown() -> HarnessResult<()> {
} }
/// Start the servers returning handles to the child processes /// Start the servers returning handles to the child processes
fn start_servers(target_folder: impl AsRef<Path>) -> HarnessResult<Vec<Child>> { fn start_servers(target_folder: impl AsRef<Path>) -> HarnessResult<()> {
let mut ret = Vec::with_capacity(SERVERS.len());
for (server_id, _ports) in SERVERS { for (server_id, _ports) in SERVERS {
let cmd = get_run_server_cmd(server_id, target_folder.as_ref()); let cmd = get_run_server_cmd(server_id, target_folder.as_ref());
info!("Starting {server_id} ..."); info!("Starting {server_id} ...");
ret.push(util::get_child(format!("start {server_id}"), cmd)?); let child = util::get_child(format!("start {server_id}"), cmd)?;
CHILDREN.with(|c| c.borrow_mut().push((server_id, child)));
} }
wait_for_startup()?; wait_for_startup()?;
Ok(ret) Ok(())
} }
pub(super) fn run_with_servers( pub(super) fn run_with_servers(
@ -191,14 +254,12 @@ pub(super) fn run_with_servers(
run_what: impl FnOnce() -> HarnessResult<()>, run_what: impl FnOnce() -> HarnessResult<()>,
) -> HarnessResult<()> { ) -> HarnessResult<()> {
info!("Starting servers ..."); info!("Starting servers ...");
let children = start_servers(target_folder.as_ref())?; start_servers(target_folder.as_ref())?;
run_what()?; run_what()?;
if kill_servers_when_done { if kill_servers_when_done {
kill_servers()?; kill_servers()?;
wait_for_shutdown()?; wait_for_shutdown()?;
} }
// just use this to avoid ignoring the children vector
assert_eq!(children.len(), SERVERS.len());
Ok(()) Ok(())
} }

@ -29,7 +29,7 @@ use crate::engine::{
error::{QueryError, QueryResult}, error::{QueryError, QueryResult},
fractal::GlobalInstanceLike, fractal::GlobalInstanceLike,
net::protocol::ClientLocalState, net::protocol::ClientLocalState,
ql::dcl::{SysctlCommand, UserAdd, UserDel}, ql::dcl::{SysctlCommand, UserDecl, UserDel},
}; };
const KEY_PASSWORD: &str = "password"; const KEY_PASSWORD: &str = "password";
@ -45,22 +45,41 @@ pub fn exec<G: GlobalInstanceLike>(
match cmd { match cmd {
SysctlCommand::CreateUser(new) => create_user(&g, new), SysctlCommand::CreateUser(new) => create_user(&g, new),
SysctlCommand::DropUser(drop) => drop_user(&g, current_user, drop), SysctlCommand::DropUser(drop) => drop_user(&g, current_user, drop),
SysctlCommand::AlterUser(usermod) => alter_user(&g, current_user, usermod),
SysctlCommand::ReportStatus => Ok(()), SysctlCommand::ReportStatus => Ok(()),
} }
} }
fn create_user(global: &impl GlobalInstanceLike, mut user_add: UserAdd<'_>) -> QueryResult<()> { fn alter_user(
let username = user_add.username().to_owned(); global: &impl GlobalInstanceLike,
let password = match user_add.options_mut().remove(KEY_PASSWORD) { cstate: &ClientLocalState,
user: UserDecl,
) -> QueryResult<()> {
if cstate.is_root() {
// the root password can only be changed by shutting down the server
return Err(QueryError::SysAuthError);
}
let (username, password) = get_user_data(user)?;
global.sys_store().alter_user(username, password)
}
fn create_user(global: &impl GlobalInstanceLike, user: UserDecl) -> QueryResult<()> {
let (username, password) = get_user_data(user)?;
global.sys_store().create_new_user(username, password)
}
fn get_user_data(mut user: UserDecl) -> Result<(String, String), QueryError> {
let username = user.username().to_owned();
let password = match user.options_mut().remove(KEY_PASSWORD) {
Some(DictEntryGeneric::Data(d)) Some(DictEntryGeneric::Data(d))
if d.kind() == TagClass::Str && user_add.options().is_empty() => if d.kind() == TagClass::Str && user.options().is_empty() =>
unsafe { d.into_str().unwrap_unchecked() }, unsafe { d.into_str().unwrap_unchecked() },
None | Some(_) => { None | Some(_) => {
// invalid properties // invalid properties
return Err(QueryError::QExecDdlInvalidProperties); return Err(QueryError::QExecDdlInvalidProperties);
} }
}; };
global.sys_store().create_new_user(username, password) Ok((username, password))
} }
fn drop_user( fn drop_user(

@ -183,6 +183,23 @@ impl<Fs: RawFSInterface> SystemStore<Fs> {
Entry::Occupied(_) => Err(QueryError::SysAuthError), Entry::Occupied(_) => Err(QueryError::SysAuthError),
} }
} }
pub fn alter_user(&self, username: String, password: String) -> QueryResult<()> {
let mut auth = self.system_store().auth_data().write();
match auth.users.get_mut(username.as_str()) {
Some(user) => {
let last_pass_hash = core::mem::replace(
&mut user.key,
rcrypt::hash(password, rcrypt::DEFAULT_COST)
.unwrap()
.into_boxed_slice(),
);
self._try_sync_or(&mut auth, |auth| {
auth.users.get_mut(username.as_str()).unwrap().key = last_pass_hash;
})
}
None => Err(QueryError::SysAuthError),
}
}
pub fn drop_user(&self, username: &str) -> QueryResult<()> { pub fn drop_user(&self, username: &str) -> QueryResult<()> {
let mut auth = self.system_store().auth_data().write(); let mut auth = self.system_store().auth_data().write();
if username == SysAuthUser::USER_ROOT { if username == SysAuthUser::USER_ROOT {

@ -25,23 +25,23 @@
*/ */
use crate::engine::{ use crate::engine::{
data::{ data::DictGeneric,
tag::{DataTag, TagClass},
DictGeneric,
},
error::{QueryError, QueryResult}, error::{QueryError, QueryResult},
ql::{ ql::{
ast::{traits, QueryData, State}, ast::{traits, QueryData, State},
ddl::syn, ddl::syn,
lex::Ident,
}, },
}; };
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum SysctlCommand<'a> { pub enum SysctlCommand<'a> {
/// `sysctl create user ...` /// `sysctl create user ...`
CreateUser(UserAdd<'a>), CreateUser(UserDecl<'a>),
/// `sysctl drop user ...` /// `sysctl drop user ...`
DropUser(UserDel<'a>), DropUser(UserDel<'a>),
/// `systcl alter user ...`
AlterUser(UserDecl<'a>),
/// `sysctl status` /// `sysctl status`
ReportStatus, ReportStatus,
} }
@ -62,16 +62,19 @@ impl<'a> traits::ASTNode<'a> for SysctlCommand<'a> {
return Err(QueryError::QLUnexpectedEndOfStatement); return Err(QueryError::QLUnexpectedEndOfStatement);
} }
let (a, b) = (state.fw_read(), state.fw_read()); let (a, b) = (state.fw_read(), state.fw_read());
let alter = Token![alter].eq(a) & b.ident_eq("user");
let create = Token![create].eq(a) & b.ident_eq("user"); let create = Token![create].eq(a) & b.ident_eq("user");
let drop = Token![drop].eq(a) & b.ident_eq("user"); let drop = Token![drop].eq(a) & b.ident_eq("user");
let status = a.ident_eq("report") & b.ident_eq("status"); let status = a.ident_eq("report") & b.ident_eq("status");
if !(create | drop | status) { if !(create | drop | status | alter) {
return Err(QueryError::QLUnknownStatement); return Err(QueryError::QLUnknownStatement);
} }
if create { if create {
UserAdd::parse(state).map(SysctlCommand::CreateUser) UserDecl::parse(state).map(SysctlCommand::CreateUser)
} else if drop { } else if drop {
UserDel::parse(state).map(SysctlCommand::DropUser) UserDel::parse(state).map(SysctlCommand::DropUser)
} else if alter {
UserDecl::parse(state).map(SysctlCommand::AlterUser)
} else { } else {
Ok(SysctlCommand::ReportStatus) Ok(SysctlCommand::ReportStatus)
} }
@ -89,7 +92,7 @@ fn parse<'a, Qd: QueryData<'a>>(state: &mut State<'a, Qd>) -> QueryResult<UserMe
} }
let token_buffer = state.current(); let token_buffer = state.current();
// initial sig // initial sig
let signature_okay = token_buffer[0].is_lit() let signature_okay = token_buffer[0].is_ident()
& token_buffer[1].eq(&Token![with]) & token_buffer[1].eq(&Token![with])
& token_buffer[2].eq(&Token![open {}]); & token_buffer[2].eq(&Token![open {}]);
// get props // get props
@ -100,45 +103,38 @@ fn parse<'a, Qd: QueryData<'a>>(state: &mut State<'a, Qd>) -> QueryResult<UserMe
}; };
let maybe_username = unsafe { let maybe_username = unsafe {
// UNSAFE(@ohsayan): the dict parse ensures state correctness // UNSAFE(@ohsayan): the dict parse ensures state correctness
token_buffer[0].uck_read_lit() token_buffer[0].uck_read_ident()
}; };
state.poison_if_not(maybe_username.kind().tag_class() == TagClass::Str);
if state.not_exhausted() | !state.okay() { if state.not_exhausted() | !state.okay() {
// we shouldn't have more tokens // we shouldn't have more tokens
return Err(QueryError::QLInvalidSyntax); return Err(QueryError::QLInvalidSyntax);
} }
Ok(UserMeta { Ok(UserMeta {
username: unsafe { username: maybe_username,
// UNSAFE(@ohsayan): +tagck in state
maybe_username.str()
},
options: dict, options: dict,
}) })
} }
struct UserMeta<'a> { struct UserMeta<'a> {
username: &'a str, username: Ident<'a>,
options: DictGeneric, options: DictGeneric,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct UserAdd<'a> { pub struct UserDecl<'a> {
username: &'a str, username: Ident<'a>,
options: DictGeneric, options: DictGeneric,
} }
impl<'a> UserAdd<'a> { impl<'a> UserDecl<'a> {
pub(in crate::engine::ql) fn new(username: &'a str, options: DictGeneric) -> Self { pub(in crate::engine::ql) fn new(username: Ident<'a>, options: DictGeneric) -> Self {
Self { username, options } Self { username, options }
} }
/// Parse a `user add` DCL command
///
/// MUSTENDSTREAM: YES
pub fn parse<Qd: QueryData<'a>>(state: &mut State<'a, Qd>) -> QueryResult<Self> { pub fn parse<Qd: QueryData<'a>>(state: &mut State<'a, Qd>) -> QueryResult<Self> {
parse(state).map(|UserMeta { username, options }: UserMeta| Self::new(username, options)) parse(state).map(|UserMeta { username, options }: UserMeta| Self::new(username, options))
} }
pub fn username(&self) -> &str { pub fn username(&self) -> &str {
self.username self.username.as_str()
} }
pub fn options_mut(&mut self) -> &mut DictGeneric { pub fn options_mut(&mut self) -> &mut DictGeneric {
&mut self.options &mut self.options
@ -150,33 +146,28 @@ impl<'a> UserAdd<'a> {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct UserDel<'a> { pub struct UserDel<'a> {
username: &'a str, username: Ident<'a>,
} }
impl<'a> UserDel<'a> { impl<'a> UserDel<'a> {
pub(in crate::engine::ql) fn new(username: &'a str) -> Self { pub(in crate::engine::ql) fn new(username: Ident<'a>) -> Self {
Self { username } Self { username }
} }
/// Parse a `user del` DCL command /// Parse a `user del` DCL command
/// ///
/// MUSTENDSTREAM: YES /// MUSTENDSTREAM: YES
pub fn parse<Qd: QueryData<'a>>(state: &mut State<'a, Qd>) -> QueryResult<Self> { pub fn parse<Qd: QueryData<'a>>(state: &mut State<'a, Qd>) -> QueryResult<Self> {
if state.can_read_lit_rounded() & (state.remaining() == 1) { if state.cursor_has_ident_rounded() & (state.remaining() == 1) {
let lit = unsafe { let username = unsafe {
// UNSAFE(@ohsayan): +boundck // UNSAFE(@ohsayan): +boundck
state.read_cursor_lit_unchecked() state.read().uck_read_ident()
}; };
state.cursor_ahead(); state.cursor_ahead();
if lit.kind().tag_class() == TagClass::Str { return Ok(Self::new(username));
return Ok(Self::new(unsafe {
// UNSAFE(@ohsayan): +tagck
lit.str()
}));
}
} }
Err(QueryError::QLInvalidSyntax) Err(QueryError::QLInvalidSyntax)
} }
pub fn username(&self) -> &str { pub fn username(&self) -> &str {
self.username self.username.as_str()
} }
} }

@ -39,12 +39,25 @@ fn report_status_simple() {
#[test] #[test]
fn create_user_simple() { fn create_user_simple() {
let query = lex_insecure(b"sysctl create user 'sayan' with { password: 'mypass123' }").unwrap(); let query = lex_insecure(b"sysctl create user sayan with { password: 'mypass123' }").unwrap();
let q = ast::parse_ast_node_full::<dcl::SysctlCommand>(&query[1..]).unwrap(); let q = ast::parse_ast_node_full::<dcl::SysctlCommand>(&query[1..]).unwrap();
assert_eq!( assert_eq!(
q, q,
SysctlCommand::CreateUser(dcl::UserAdd::new( SysctlCommand::CreateUser(dcl::UserDecl::new(
"sayan", "sayan".into(),
into_dict!("password" => lit!("mypass123"))
))
)
}
#[test]
fn alter_user_simple() {
let query = lex_insecure(b"sysctl alter user sayan with { password: 'mypass123' }").unwrap();
let q = ast::parse_ast_node_full::<dcl::SysctlCommand>(&query[1..]).unwrap();
assert_eq!(
q,
SysctlCommand::AlterUser(dcl::UserDecl::new(
"sayan".into(),
into_dict!("password" => lit!("mypass123")) into_dict!("password" => lit!("mypass123"))
)) ))
) )
@ -52,7 +65,10 @@ fn create_user_simple() {
#[test] #[test]
fn delete_user_simple() { fn delete_user_simple() {
let query = lex_insecure(b"sysctl drop user 'monster'").unwrap(); let query = lex_insecure(b"sysctl drop user monster").unwrap();
let q = ast::parse_ast_node_full::<dcl::SysctlCommand>(&query[1..]).unwrap(); let q = ast::parse_ast_node_full::<dcl::SysctlCommand>(&query[1..]).unwrap();
assert_eq!(q, SysctlCommand::DropUser(dcl::UserDel::new("monster"))); assert_eq!(
q,
SysctlCommand::DropUser(dcl::UserDel::new("monster".into()))
);
} }

@ -57,12 +57,9 @@ fn ensure_sysctl_status_end_of_tokens() {
#[dbtest] #[dbtest]
fn ensure_sysctl_create_user() { fn ensure_sysctl_create_user() {
let mut db = db!(); let mut db = db!();
let query = format!("sysctl create user myuser with {{ password: ? }} blah");
assert_err_eq!( assert_err_eq!(
db.query_parse::<()>(&query!( db.query_parse::<()>(&query!(query, "mypass")),
"sysctl create user ? with { password: ? } blah",
"myuser",
"mypass"
)),
Error::ServerError(INVALID_SYNTAX_ERR) Error::ServerError(INVALID_SYNTAX_ERR)
); );
} }

@ -230,10 +230,9 @@ pub fn dbtest(attrs: TokenStream, item: TokenStream) -> TokenStream {
/// password set by [`sky_macros::dbtest`] (relogin) /// password set by [`sky_macros::dbtest`] (relogin)
const __DBTEST_PASS: &str = #new_password; const __DBTEST_PASS: &str = #new_password;
{ {
let query_assembled = format!("sysctl create user {} with {{ password: ? }}", #new_username);
let mut db = skytable::Config::new(#host, #port, #login_username, #login_password).connect().unwrap(); let mut db = skytable::Config::new(#host, #port, #login_username, #login_password).connect().unwrap();
db.query_parse::<()>( db.query_parse::<()>(&skytable::query!(query_assembled, #new_password)).unwrap();
&skytable::query!("sysctl create user ? with { password: ? }", #new_username, #new_password)
).unwrap();
} }
}; };
} }
@ -272,10 +271,9 @@ pub fn dbtest(attrs: TokenStream, item: TokenStream) -> TokenStream {
ret_block = quote! { ret_block = quote! {
#ret_block #ret_block
{ {
let query_assembled = format!("sysctl drop user {}", #new_username);
let mut db = skytable::Config::new(#host, #port, #login_username, #login_password).connect().unwrap(); let mut db = skytable::Config::new(#host, #port, #login_username, #login_password).connect().unwrap();
db.query_parse::<()>( db.query_parse::<()>(&skytable::query!(query_assembled)).unwrap();
&skytable::query!("sysctl drop user ?", #new_username)
).unwrap();
} }
}; };
} }

Loading…
Cancel
Save