55 */
66
77use std:: {
8+ collections:: HashMap ,
89 env,
910 env:: temp_dir,
1011 error:: Error ,
@@ -22,15 +23,16 @@ use clap::Parser;
2223use home:: home_dir;
2324use rustyline:: error:: ReadlineError ;
2425use sentry:: ClientOptions ;
25- use typedb_driver:: { Credentials , DriverOptions , Transaction , TransactionType , TypeDBDriver } ;
26+ use typedb_driver:: { Addresses , Credentials , DriverOptions , Transaction , TransactionType , TypeDBDriver } ;
2627
2728use crate :: {
2829 cli:: { Args , ADDRESS_VALUE_NAME , USERNAME_VALUE_NAME } ,
2930 completions:: { database_name_completer_fn, file_completer} ,
3031 operations:: {
3132 database_create, database_create_init, database_delete, database_export, database_import, database_list,
32- database_schema, transaction_close, transaction_commit, transaction_query, transaction_read,
33- transaction_rollback, transaction_schema, transaction_source, transaction_write, user_create, user_delete,
33+ database_schema, replica_deregister, replica_list, replica_primary, replica_register, server_version,
34+ transaction_close, transaction_commit, transaction_query, transaction_read, transaction_rollback,
35+ transaction_schema, transaction_source, transaction_write, user_create, user_delete,
3436 user_list, user_update_password,
3537 } ,
3638 repl:: {
@@ -126,13 +128,19 @@ fn main() {
126128 println ! ( "{}" , VERSION ) ;
127129 exit ( ExitCode :: Success as i32 ) ;
128130 }
129- let address = match args. address {
130- Some ( address) => address,
131- None => {
132- println_error ! ( "missing server address ('{}')." , format_argument!( "--address <{ADDRESS_VALUE_NAME}>" ) ) ;
133- exit ( ExitCode :: UserInputError as i32 ) ;
134- }
135- } ;
131+ let address_info = parse_addresses ( & args) ;
132+ if !args. tls_disabled && !address_info. only_https {
133+ println_error ! (
134+ "\
135+ TLS connections can only be enabled when connecting to HTTPS endpoints. \
136+ For example, using 'https://<ip>:port'.\n \
137+ Please modify the address or disable TLS ('{}'). {}\
138+ ",
139+ format_argument!( "--tls-disabled" ) ,
140+ format_warning!( "WARNING: this will send passwords over plaintext!" ) ,
141+ ) ;
142+ exit ( ExitCode :: UserInputError as i32 ) ;
143+ }
136144 let username = match args. username {
137145 Some ( username) => username,
138146 None => {
@@ -146,34 +154,28 @@ fn main() {
146154 if args. password . is_none ( ) {
147155 args. password = Some ( LineReaderHidden :: new ( ) . readline ( & format ! ( "password for '{username}': " ) ) ) ;
148156 }
149- if !args. diagnostics_disable {
157+ if !args. diagnostics_disabled {
150158 init_diagnostics ( )
151159 }
152- if !args. tls_disabled && !address. starts_with ( "https:" ) {
153- println_error ! (
154- "\
155- TLS connections can only be enabled when connecting to HTTPS endpoints. \
156- For example, using 'https://<ip>:port'.\n \
157- Please modify the address or disable TLS ('{}'). {}\
158- ",
159- format_argument!( "--tls-disabled" ) ,
160- format_warning!( "WARNING: this will send passwords over plaintext!" ) ,
161- ) ;
162- exit ( ExitCode :: UserInputError as i32 ) ;
163- }
164160 let tls_root_ca_path = args. tls_root_ca . as_ref ( ) . map ( |value| Path :: new ( value) ) ;
165-
166161 let runtime = BackgroundRuntime :: new ( ) ;
162+ let driver_options = DriverOptions :: new ( )
163+ . use_replication ( !args. replication_disabled )
164+ . is_tls_enabled ( !args. tls_disabled )
165+ . tls_root_ca ( tls_root_ca_path)
166+ . unwrap ( ) ;
167167 let driver = match runtime. run ( TypeDBDriver :: new (
168- address ,
168+ address_info . addresses ,
169169 Credentials :: new ( & username, args. password . as_ref ( ) . unwrap ( ) ) ,
170- DriverOptions :: new ( !args . tls_disabled , tls_root_ca_path ) . unwrap ( ) ,
170+ driver_options ,
171171 ) ) {
172172 Ok ( driver) => Arc :: new ( driver) ,
173173 Err ( err) => {
174174 let tls_error =
175175 if args. tls_disabled { "" } else { "\n Verify that the server is also configured with TLS encryption." } ;
176- println_error ! ( "Failed to create driver connection to server. {err}{tls_error}" ) ;
176+ let replication_error =
177+ if args. replication_disabled { "\n Verify that the connection address is **exactly** the same as the server address specified in its config." } else { "" } ;
178+ println_error ! ( "Failed to create driver connection to server. {err}{tls_error}{replication_error}" ) ;
177179 exit ( ExitCode :: ConnectionError as i32 ) ;
178180 }
179181 } ;
@@ -332,6 +334,28 @@ fn execute_commands(context: &mut ConsoleContext, mut input: &str, must_log_comm
332334}
333335
334336fn entry_repl ( driver : Arc < TypeDBDriver > , runtime : BackgroundRuntime ) -> Repl < ConsoleContext > {
337+ let server_commands =
338+ Subcommand :: new ( "server" ) . add ( CommandLeaf :: new ( "version" , "Retrieve server version." , server_version) ) ;
339+
340+ let replica_commands = Subcommand :: new ( "replica" )
341+ . add ( CommandLeaf :: new ( "list" , "List replicas." , replica_list) )
342+ . add ( CommandLeaf :: new ( "primary" , "Get current primary replica." , replica_primary) )
343+ . add ( CommandLeaf :: new_with_inputs (
344+ "register" ,
345+ "Register new replica. Requires a clustering address, not a connection address." ,
346+ vec ! [
347+ CommandInput :: new_required( "replica id" , get_word, None ) ,
348+ CommandInput :: new_required( "clustering address" , get_word, None ) ,
349+ ] ,
350+ replica_register,
351+ ) )
352+ . add ( CommandLeaf :: new_with_input (
353+ "deregister" ,
354+ "Deregister existing replica." ,
355+ CommandInput :: new_required ( "replica id" , get_word, None ) ,
356+ replica_deregister,
357+ ) ) ;
358+
335359 let database_commands = Subcommand :: new ( "database" )
336360 . add ( CommandLeaf :: new ( "list" , "List databases on the server." , database_list) )
337361 . add ( CommandLeaf :: new_with_input (
@@ -451,8 +475,10 @@ fn entry_repl(driver: Arc<TypeDBDriver>, runtime: BackgroundRuntime) -> Repl<Con
451475 let history_path = home_dir ( ) . unwrap_or_else ( || temp_dir ( ) ) . join ( ENTRY_REPL_HISTORY ) ;
452476
453477 let repl = Repl :: new ( PROMPT . to_owned ( ) , history_path, false , None )
478+ . add ( server_commands)
454479 . add ( database_commands)
455480 . add ( user_commands)
481+ . add ( replica_commands)
456482 . add ( transaction_commands) ;
457483
458484 repl
@@ -508,6 +534,42 @@ fn transaction_type_str(transaction_type: TransactionType) -> &'static str {
508534 }
509535}
510536
537+ struct AddressInfo {
538+ only_https : bool ,
539+ addresses : Addresses ,
540+ }
541+
542+ fn parse_addresses ( args : & Args ) -> AddressInfo {
543+ if let Some ( address) = & args. address {
544+ AddressInfo {
545+ only_https : is_https_address ( address) ,
546+ addresses : Addresses :: try_from_address_str ( address) . unwrap ( ) ,
547+ }
548+ } else if let Some ( addresses) = & args. addresses {
549+ let split = addresses. split ( ',' ) . map ( str:: to_string) . collect :: < Vec < _ > > ( ) ;
550+ let only_https = split. iter ( ) . all ( |address| is_https_address ( address) ) ;
551+ AddressInfo { only_https, addresses : Addresses :: try_from_addresses_str ( split) . unwrap ( ) }
552+ } else if let Some ( translation) = & args. address_translation {
553+ let mut map = HashMap :: new ( ) ;
554+ let mut only_https = true ;
555+ for pair in translation. split ( ',' ) {
556+ let ( public_address, private_address) = pair
557+ . split_once ( '=' )
558+ . unwrap_or_else ( || panic ! ( "Invalid address pair: {pair}. Must be of form public=private" ) ) ;
559+ only_https = only_https && is_https_address ( public_address) ;
560+ map. insert ( public_address. to_string ( ) , private_address. to_string ( ) ) ;
561+ }
562+ println ! ( "Translation map:: {map:?}" ) ; // TODO: Remove
563+ AddressInfo { only_https, addresses : Addresses :: try_from_translation_str ( map) . unwrap ( ) }
564+ } else {
565+ panic ! ( "At least one of --address, --addresses, or --address-translation must be provided." ) ;
566+ }
567+ }
568+
569+ fn is_https_address ( address : & str ) -> bool {
570+ address. starts_with ( "https:" )
571+ }
572+
511573fn init_diagnostics ( ) {
512574 let _ = sentry:: init ( (
513575 DIAGNOSTICS_REPORTING_URI ,
0 commit comments