@@ -18,6 +18,7 @@ use iroh::{Endpoint, EndpointId, SecretKey};
1818use itertools:: Itertools ;
1919use regex:: Regex ;
2020use reqwest:: Url ;
21+ use sha2:: { Digest , Sha256 } ;
2122use tokio:: {
2223 fs:: File ,
2324 io:: AsyncWriteExt ,
@@ -26,6 +27,7 @@ use tokio::{
2627
2728use super :: api_client:: client:: { EdfSpec , ScriptSpec } ;
2829use crate :: {
30+ cli:: app:: COMAN_VERSION ,
2931 config:: { ComputePlatform , Config , get_data_dir} ,
3032 cscs:: {
3133 api_client:: {
@@ -396,6 +398,82 @@ async fn store_ssh_information(
396398 Ok ( connection_name)
397399}
398400
401+ async fn maybe_download_latest_squash ( current_system : & str , config : & Config ) -> Result < PathBuf , eyre:: Error > {
402+ if let Some ( path) = config. values . coman_squash_path . clone ( ) {
403+ return Ok ( path) ;
404+ }
405+ //download from github for architecture
406+ let system = config
407+ . values
408+ . cscs
409+ . systems
410+ . get ( current_system)
411+ . ok_or ( eyre ! ( "couldn't find architecture for system {}" , current_system) ) ?;
412+ let architecture = system
413+ . architecture
414+ . first ( )
415+ . ok_or ( eyre ! ( "no architecture set for {}" , current_system) ) ?;
416+ let target_path = get_data_dir ( ) . join ( format ! ( "coman_{}.sqsh" , architecture) ) ;
417+ let arch = match architecture. as_str ( ) {
418+ "arm64" => "aarch64" ,
419+ "amd64" => "x86_64" ,
420+ _ => {
421+ return Err ( eyre ! ( "unsupported architecture {}" , architecture) ) ;
422+ }
423+ } ;
424+ if target_path. exists ( ) {
425+ // if file exists, check checksum against newest version and delete if no match
426+ let mut hasher = Sha256 :: new ( ) ;
427+ let mut file = std:: fs:: File :: open ( & target_path) ?;
428+ let _n = std:: io:: copy ( & mut file, & mut hasher) ?;
429+ let hash = hasher. finalize ( ) ;
430+ let hash = format ! ( "{hash:02x}" ) ;
431+
432+ let client = reqwest:: Client :: new ( ) ;
433+ let resp = client
434+ . get ( format ! (
435+ "https://api.github.com/repos/SwissDataScienceCenter/coman/releases/tags/v{}" ,
436+ COMAN_VERSION
437+ ) )
438+ . header ( "Accept" , "application/vnd.github+json" )
439+ . header ( "User-Agent" , format ! ( "coman cli {COMAN_VERSION}" ) )
440+ . send ( )
441+ . await ?;
442+ if resp. status ( ) . is_success ( ) {
443+ let body = resp. text ( ) . await ?;
444+ let body: serde_json:: Value = serde_json:: from_str ( & body) ?;
445+ if let Some ( assets) = body[ "assets" ] . as_array ( ) {
446+ for asset in assets {
447+ if asset[ "name" ] . as_str ( ) == Some ( format ! ( "coman_Linux-{arch}.sqsh" ) . as_str ( ) )
448+ && let Some ( remote_hash) = asset[ "digest" ] . as_str ( )
449+ && remote_hash. strip_prefix ( "sha256:" ) . unwrap ( ) != hash. as_str ( )
450+ {
451+ std:: fs:: remove_file ( & target_path) ?;
452+ }
453+ }
454+ }
455+ }
456+ }
457+ if !target_path. exists ( ) {
458+ let url = format ! (
459+ "https://github.com/SwissDataScienceCenter/coman/releases/download/v{COMAN_VERSION}/coman_Linux-{arch}.sqsh"
460+ ) ;
461+ let mut out = File :: create ( target_path. clone ( ) ) . await ?;
462+ let resp = reqwest:: get ( url) . await ?;
463+ match resp. error_for_status ( ) {
464+ Ok ( resp) => {
465+ let mut stream = resp. bytes_stream ( ) ;
466+ while let Some ( chunk_result) = stream. next ( ) . await {
467+ let chunk = chunk_result?;
468+ out. write_all ( & chunk) . await ?;
469+ }
470+ out. flush ( ) . await ?;
471+ }
472+ Err ( e) => return Err ( eyre ! ( "couldn't download coman squash file: {}" , e) ) ,
473+ }
474+ }
475+ Ok ( target_path)
476+ }
399477async fn inject_coman_squash (
400478 api_client : & CscsApi ,
401479 base_path : & Path ,
@@ -406,50 +484,7 @@ async fn inject_coman_squash(
406484 return Ok ( None ) ;
407485 }
408486 let config = Config :: new ( ) . unwrap ( ) ;
409- let local_squash_path = match config. values . coman_squash_path . clone ( ) {
410- Some ( path) => path,
411- None => {
412- //download from github for architecture
413- let system = config
414- . values
415- . cscs
416- . systems
417- . get ( current_system)
418- . ok_or ( eyre ! ( "couldn't find architecture for system {}" , current_system) ) ?;
419- let architecture = system
420- . architecture
421- . first ( )
422- . ok_or ( eyre ! ( "no architecture set for {}" , current_system) ) ?;
423- let target_path = get_data_dir ( ) . join ( format ! ( "coman_{}.sqsh" , architecture) ) ;
424- if !target_path. exists ( ) {
425- let url = match architecture. as_str ( ) {
426- "arm64" => {
427- "https://github.com/SwissDataScienceCenter/coman/releases/latest/download/coman_Linux-aarch64.sqsh"
428- }
429- "amd64" => {
430- "https://github.com/SwissDataScienceCenter/coman/releases/latest/download/coman_Linux-x86_64.sqsh"
431- }
432- _ => {
433- return Err ( eyre ! ( "unsupported architecture {}" , architecture) ) ;
434- }
435- } ;
436- let mut out = File :: create ( target_path. clone ( ) ) . await ?;
437- let resp = reqwest:: get ( url) . await ?;
438- match resp. error_for_status ( ) {
439- Ok ( resp) => {
440- let mut stream = resp. bytes_stream ( ) ;
441- while let Some ( chunk_result) = stream. next ( ) . await {
442- let chunk = chunk_result?;
443- out. write_all ( & chunk) . await ?;
444- }
445- out. flush ( ) . await ?;
446- }
447- Err ( e) => return Err ( eyre ! ( "couldn't download coman squash file: {}" , e) ) ,
448- }
449- }
450- target_path
451- }
452- } ;
487+ let local_squash_path = maybe_download_latest_squash ( current_system, & config) . await ?;
453488 let target = base_path. join ( "coman.sqsh" ) ;
454489 let file_meta = std:: fs:: metadata ( local_squash_path. clone ( ) ) ?;
455490
@@ -459,13 +494,16 @@ async fn inject_coman_squash(
459494 #[ cfg( target_family = "windows" ) ]
460495 let size = file_meta. file_size ( ) as usize ;
461496
462- let response = api_client. list_path ( current_system, target. clone ( ) ) . await ;
463- if let Ok ( existing) = response
464- && !existing. is_empty ( )
465- {
497+ let mut hasher = Sha256 :: new ( ) ;
498+ let mut file = std:: fs:: File :: open ( & local_squash_path) ?;
499+ let _n = std:: io:: copy ( & mut file, & mut hasher) ?;
500+ let hash = hasher. finalize ( ) ;
501+ let hash = format ! ( "{hash:02x}" ) ;
502+
503+ let response = api_client. checksum ( current_system, target. clone ( ) ) . await ;
504+ if let Ok ( Some ( remote_hash) ) = response {
466505 //squash file already present on remote, don't upload if it's the same
467- let entry = existing. first ( ) . unwrap ( ) ;
468- if entry. size . unwrap_or_default ( ) == size {
506+ if hash == remote_hash {
469507 return Ok ( Some ( target) ) ;
470508 } else {
471509 // remove file before upload
0 commit comments