@@ -76,6 +76,24 @@ pub fn apps_cmd() -> Command {
7676 )
7777 . about ( "Delete an app in Tower" ) ,
7878 )
79+ . subcommand (
80+ Command :: new ( "cancel" )
81+ . arg (
82+ Arg :: new ( "app_name" )
83+ . value_parser ( value_parser ! ( String ) )
84+ . index ( 1 )
85+ . required ( true )
86+ . help ( "Name of the app" ) ,
87+ )
88+ . arg (
89+ Arg :: new ( "run_number" )
90+ . value_parser ( value_parser ! ( i64 ) )
91+ . index ( 2 )
92+ . required ( true )
93+ . help ( "Run number to cancel" ) ,
94+ )
95+ . about ( "Cancel a running app run" ) ,
96+ )
7997}
8098
8199pub async fn do_logs ( config : Config , cmd : & ArgMatches ) {
@@ -223,6 +241,26 @@ pub async fn do_delete(config: Config, cmd: &ArgMatches) {
223241 output:: with_spinner ( "Deleting app" , api:: delete_app ( & config, name) ) . await ;
224242}
225243
244+ pub async fn do_cancel ( config : Config , cmd : & ArgMatches ) {
245+ let name = cmd
246+ . get_one :: < String > ( "app_name" )
247+ . expect ( "app_name should be required" ) ;
248+ let seq = cmd
249+ . get_one :: < i64 > ( "run_number" )
250+ . copied ( )
251+ . expect ( "run_number should be required" ) ;
252+
253+ let response =
254+ output:: with_spinner ( "Cancelling run" , api:: cancel_run ( & config, name, seq) ) . await ;
255+
256+ let run = & response. run ;
257+ let status = format ! ( "{:?}" , run. status) ;
258+ output:: success_with_data (
259+ & format ! ( "Run #{} for '{}' cancelled (status: {})" , seq, name, status) ,
260+ Some ( response) ,
261+ ) ;
262+ }
263+
226264async fn latest_run_number ( config : & Config , name : & str ) -> i64 {
227265 match api:: describe_app ( config, name) . await {
228266 Ok ( resp) => resp. runs
@@ -715,4 +753,30 @@ mod tests {
715753 assert ! ( should_emit_line( & mut last_line_num, 4 ) ) ;
716754 assert_eq ! ( last_line_num, Some ( 4 ) ) ;
717755 }
756+
757+ #[ test]
758+ fn test_cancel_args_parsing ( ) {
759+ let matches = apps_cmd ( )
760+ . try_get_matches_from ( [ "apps" , "cancel" , "my-app" , "42" ] )
761+ . unwrap ( ) ;
762+ let ( cmd, sub_matches) = matches. subcommand ( ) . unwrap ( ) ;
763+
764+ assert_eq ! ( cmd, "cancel" ) ;
765+ assert_eq ! (
766+ sub_matches
767+ . get_one:: <String >( "app_name" )
768+ . map( |s| s. as_str( ) ) ,
769+ Some ( "my-app" )
770+ ) ;
771+ assert_eq ! ( sub_matches. get_one:: <i64 >( "run_number" ) , Some ( & 42 ) ) ;
772+ }
773+
774+ #[ test]
775+ fn test_cancel_requires_both_args ( ) {
776+ let result = apps_cmd ( ) . try_get_matches_from ( [ "apps" , "cancel" , "my-app" ] ) ;
777+ assert ! ( result. is_err( ) ) ;
778+
779+ let result = apps_cmd ( ) . try_get_matches_from ( [ "apps" , "cancel" ] ) ;
780+ assert ! ( result. is_err( ) ) ;
781+ }
718782}
0 commit comments