1- use std:: { error:: Error , path:: PathBuf , str:: FromStr , thread, time :: Duration } ;
1+ use std:: { error:: Error , path:: PathBuf , str:: FromStr , thread} ;
22
3- use base64:: prelude:: * ;
43use clap:: { Args , Command , Parser , Subcommand , ValueHint , builder:: TypedValueParser } ;
54use clap_complete:: { ArgValueCompleter , CompletionCandidate , Generator , Shell , generate} ;
6- use color_eyre:: { Report , Result , eyre:: eyre} ;
7- use iroh_ssh:: IrohSsh ;
5+ use color_eyre:: { Report , Result } ;
86use itertools:: Itertools ;
9- use pid1:: Pid1Settings ;
10- use rust_supervisor:: { ChildType , Supervisor , SupervisorConfig } ;
117use strum:: VariantNames ;
128use tokio:: sync:: mpsc;
139
@@ -16,9 +12,9 @@ use crate::{
1612 cscs:: {
1713 api_client:: {
1814 client:: { EdfSpec as EdfSpecEnum , ScriptSpec as ScriptSpecEnum } ,
19- types:: { JobStatus , PathType } ,
15+ types:: PathType ,
2016 } ,
21- handlers:: { cscs_file_list, cscs_job_details , cscs_job_list, file_system_roots} ,
17+ handlers:: { cscs_file_list, cscs_job_list, file_system_roots} ,
2218 } ,
2319 util:: types:: DockerImageUrl ,
2420} ;
@@ -133,6 +129,18 @@ pub enum CscsCommands {
133129 #[ command( subcommand) ]
134130 command : CscsSystemCommands ,
135131 } ,
132+ #[ clap(
133+ alias( "pf" ) ,
134+ about = "Forward a local port to a remote port for a job. Note that the port needs to have been exposed with the -P flag on job submission [aliases: pf]"
135+ ) ]
136+ PortForward {
137+ #[ arg( short, long, help = "Local port to forward from" ) ]
138+ source_port : u16 ,
139+ #[ arg( short, long, help = "Remote port to forward to" ) ]
140+ destination_port : u16 ,
141+ #[ arg( help="id or name of the job (name uses newest job of that name)" , add = ArgValueCompleter :: new( job_id_or_name_completer) ) ]
142+ job : JobIdOrName ,
143+ } ,
136144}
137145
138146#[ derive( Args , Clone , Debug ) ]
@@ -257,6 +265,11 @@ pub enum CscsJobCommands {
257265 help="Environment variables to set in the container" ,
258266 value_hint=ValueHint :: Other ) ]
259267 env : Vec < ( String , String ) > ,
268+ #[ clap( short='P' ,
269+ value_name="TARGET" ,
270+ help="Ports to forward from the container" ,
271+ value_hint=ValueHint :: Other ) ]
272+ port_forward : Vec < u16 > ,
260273 #[ clap( short='M' ,
261274 value_name="PATH:CONTAINER_PATH" ,
262275 value_parser=parse_key_val_colon:: <String , String >,
@@ -543,79 +556,3 @@ fn is_bare_string(value_str: &str) -> bool {
543556pub fn print_completions < G : Generator > ( generator : G , cmd : & mut Command ) {
544557 generate ( generator, cmd, cmd. get_name ( ) . to_string ( ) , & mut std:: io:: stdout ( ) ) ;
545558}
546-
547- /// Runs a wrapped command in a container-safe way and potentially runs background processes like iroh-ssh
548- pub ( crate ) async fn cli_exec_command ( command : Vec < String > ) -> Result < ( ) > {
549- // Pid1 takes care of proper terminating of processes and signal handling when running in a container
550- Pid1Settings :: new ( )
551- . enable_log ( true )
552- . timeout ( Duration :: from_secs ( 2 ) )
553- . launch ( )
554- . expect ( "Launch failed" ) ;
555-
556- let mut supervisor = Supervisor :: new ( SupervisorConfig :: default ( ) ) ;
557- supervisor. add_process ( "iroh-ssh" , ChildType :: Permanent , || {
558- thread:: spawn ( || {
559- let rt = tokio:: runtime:: Builder :: new_current_thread ( )
560- . enable_all ( )
561- . build ( )
562- . expect ( "couldn't start tokio" ) ;
563-
564- // Call the asynchronous connect method using the runtime.
565- rt. block_on ( async move {
566- let mut builder = IrohSsh :: builder ( ) . accept_incoming ( true ) . accept_port ( 15263 ) ;
567- if let Ok ( secret) = std:: env:: var ( "COMAN_IROH_SECRET" ) {
568- let secret_key = BASE64_STANDARD . decode ( secret) . unwrap ( ) ;
569- let secret_key: & [ u8 ; 32 ] = secret_key[ 0 ..32 ] . try_into ( ) . unwrap ( ) ;
570- builder = builder. secret_key ( secret_key) ;
571- }
572-
573- let server = builder. build ( ) . await . expect ( "couldn't create iroh server" ) ;
574- println ! ( "{}@{}" , whoami:: username( ) , server. node_id( ) ) ;
575- loop {
576- tokio:: time:: sleep ( Duration :: from_secs ( 60 ) ) . await ;
577- }
578- } ) ;
579- } )
580- } ) ;
581- supervisor. add_process ( "main-process" , ChildType :: Temporary , move || {
582- let command = command. clone ( ) ;
583- thread:: spawn ( move || {
584- let mut child = std:: process:: Command :: new ( command[ 0 ] . clone ( ) )
585- . args ( & command[ 1 ..] )
586- . spawn ( )
587- . expect ( "Failed to start compute job" ) ;
588- child. wait ( ) . expect ( "Failed to wait on compute job" ) ;
589- } )
590- } ) ;
591-
592- let supervisor = supervisor. start_monitoring ( ) ;
593- loop {
594- thread:: sleep ( Duration :: from_secs ( 1 ) ) ;
595-
596- if let Some ( rust_supervisor:: ProcessState :: Failed | rust_supervisor:: ProcessState :: Stopped ) =
597- supervisor. get_process_state ( "main-process" )
598- {
599- break ;
600- }
601- }
602- Ok ( ( ) )
603- }
604-
605- /// Thin wrapper around iroh proxy
606- pub ( crate ) async fn cli_proxy_command ( system : String , job_id : i64 ) -> Result < ( ) > {
607- let data_dir = get_data_dir ( ) ;
608- let job_info = cscs_job_details ( job_id, Some ( system. clone ( ) ) , None ) . await ?;
609- if job_info. is_none ( ) {
610- return Err ( eyre ! ( "remote job does not exist!" ) ) ;
611- } else if let Some ( job_info) = job_info
612- && job_info. status != JobStatus :: Running
613- {
614- return Err ( eyre ! ( "remote job is not in running state, connection not available" ) ) ;
615- }
616- let endpoint_id = std:: fs:: read_to_string ( data_dir. join ( format ! ( "{}_{}.endpoint" , system, job_id) ) ) ?;
617- println ! ( "{}" , endpoint_id) ;
618- iroh_ssh:: api:: proxy_mode ( iroh_ssh:: ProxyArgs { node_id : endpoint_id } )
619- . await
620- . map_err ( |e| eyre ! ( "couldn't proxy ssh connection: {:?}" , e) )
621- }
0 commit comments