@@ -22,6 +22,7 @@ use clap::Args;
22
22
use clap:: ColorChoice ;
23
23
use clap:: Subcommand ;
24
24
use clap:: ValueEnum ;
25
+ use futures:: StreamExt ;
25
26
use futures:: TryStreamExt ;
26
27
use futures:: future:: try_join;
27
28
use http:: StatusCode ;
@@ -70,6 +71,7 @@ use omicron_uuid_kinds::GenericUuid;
70
71
use omicron_uuid_kinds:: ParseError ;
71
72
use omicron_uuid_kinds:: PhysicalDiskUuid ;
72
73
use omicron_uuid_kinds:: SledUuid ;
74
+ use omicron_uuid_kinds:: SupportBundleUuid ;
73
75
use serde:: Deserialize ;
74
76
use slog_error_chain:: InlineErrorChain ;
75
77
use std:: collections:: BTreeMap ;
@@ -127,6 +129,9 @@ enum NexusCommands {
127
129
Sagas ( SagasArgs ) ,
128
130
/// interact with sleds
129
131
Sleds ( SledsArgs ) ,
132
+ /// interact with support bundles
133
+ #[ command( visible_alias = "sb" ) ]
134
+ SupportBundles ( SupportBundleArgs ) ,
130
135
}
131
136
132
137
#[ derive( Debug , Args ) ]
@@ -475,6 +480,49 @@ struct DiskExpungeArgs {
475
480
physical_disk_id : PhysicalDiskUuid ,
476
481
}
477
482
483
+ #[ derive( Debug , Args ) ]
484
+ struct SupportBundleArgs {
485
+ #[ command( subcommand) ]
486
+ command : SupportBundleCommands ,
487
+ }
488
+
489
+ #[ derive( Debug , Subcommand ) ]
490
+ #[ allow( clippy:: large_enum_variant) ]
491
+ enum SupportBundleCommands {
492
+ /// List all support bundles
493
+ List ,
494
+ /// Create a new support bundle
495
+ Create ,
496
+ /// Delete a support bundle
497
+ Delete ( SupportBundleDeleteArgs ) ,
498
+ /// Download the index of a support bundle
499
+ ///
500
+ /// This is a "list of files", from which individual files can be accessed
501
+ GetIndex ( SupportBundleIndexArgs ) ,
502
+ /// View a file within a support bundle
503
+ GetFile ( SupportBundleFileArgs ) ,
504
+ }
505
+
506
+ #[ derive( Debug , Args ) ]
507
+ struct SupportBundleDeleteArgs {
508
+ id : SupportBundleUuid ,
509
+ }
510
+
511
+ #[ derive( Debug , Args ) ]
512
+ struct SupportBundleIndexArgs {
513
+ id : SupportBundleUuid ,
514
+ }
515
+
516
+ #[ derive( Debug , Args ) ]
517
+ struct SupportBundleFileArgs {
518
+ id : SupportBundleUuid ,
519
+ path : Utf8PathBuf ,
520
+ /// Optional output path where the file should be written,
521
+ /// instead of stdout.
522
+ #[ arg( short, long) ]
523
+ output : Option < Utf8PathBuf > ,
524
+ }
525
+
478
526
impl NexusArgs {
479
527
/// Run a `omdb nexus` subcommand.
480
528
pub ( crate ) async fn run_cmd (
@@ -668,6 +716,27 @@ impl NexusArgs {
668
716
cmd_nexus_sled_expunge_disk ( & client, args, omdb, log, token)
669
717
. await
670
718
}
719
+ NexusCommands :: SupportBundles ( SupportBundleArgs {
720
+ command : SupportBundleCommands :: List ,
721
+ } ) => cmd_nexus_support_bundles_list ( & client) . await ,
722
+ NexusCommands :: SupportBundles ( SupportBundleArgs {
723
+ command : SupportBundleCommands :: Create ,
724
+ } ) => {
725
+ let token = omdb. check_allow_destructive ( ) ?;
726
+ cmd_nexus_support_bundles_create ( & client, token) . await
727
+ }
728
+ NexusCommands :: SupportBundles ( SupportBundleArgs {
729
+ command : SupportBundleCommands :: Delete ( args) ,
730
+ } ) => {
731
+ let token = omdb. check_allow_destructive ( ) ?;
732
+ cmd_nexus_support_bundles_delete ( & client, args, token) . await
733
+ }
734
+ NexusCommands :: SupportBundles ( SupportBundleArgs {
735
+ command : SupportBundleCommands :: GetIndex ( args) ,
736
+ } ) => cmd_nexus_support_bundles_get_index ( & client, args) . await ,
737
+ NexusCommands :: SupportBundles ( SupportBundleArgs {
738
+ command : SupportBundleCommands :: GetFile ( args) ,
739
+ } ) => cmd_nexus_support_bundles_get_file ( & client, args) . await ,
671
740
}
672
741
}
673
742
}
@@ -3682,3 +3751,132 @@ async fn cmd_nexus_sled_expunge_disk_with_datastore(
3682
3751
eprintln ! ( "expunged disk {}" , args. physical_disk_id) ;
3683
3752
Ok ( ( ) )
3684
3753
}
3754
+
3755
+ /// Runs `omdb nexus support-bundles list`
3756
+ async fn cmd_nexus_support_bundles_list (
3757
+ client : & nexus_client:: Client ,
3758
+ ) -> Result < ( ) , anyhow:: Error > {
3759
+ let support_bundle_stream = client. support_bundle_list_stream ( None , None ) ;
3760
+
3761
+ let support_bundles = support_bundle_stream
3762
+ . try_collect :: < Vec < _ > > ( )
3763
+ . await
3764
+ . context ( "listing support bundles" ) ?;
3765
+
3766
+ #[ derive( Tabled ) ]
3767
+ #[ tabled( rename_all = "SCREAMING_SNAKE_CASE" ) ]
3768
+ struct SupportBundleInfo {
3769
+ id : Uuid ,
3770
+ time_created : DateTime < Utc > ,
3771
+ reason_for_creation : String ,
3772
+ reason_for_failure : String ,
3773
+ state : String ,
3774
+ }
3775
+ let rows = support_bundles. into_iter ( ) . map ( |sb| SupportBundleInfo {
3776
+ id : * sb. id ,
3777
+ time_created : sb. time_created ,
3778
+ reason_for_creation : sb. reason_for_creation ,
3779
+ reason_for_failure : sb
3780
+ . reason_for_failure
3781
+ . unwrap_or_else ( || "-" . to_string ( ) ) ,
3782
+ state : format ! ( "{:?}" , sb. state) ,
3783
+ } ) ;
3784
+ let table = tabled:: Table :: new ( rows)
3785
+ . with ( tabled:: settings:: Style :: empty ( ) )
3786
+ . with ( tabled:: settings:: Padding :: new ( 0 , 1 , 0 , 0 ) )
3787
+ . to_string ( ) ;
3788
+ println ! ( "{}" , table) ;
3789
+ Ok ( ( ) )
3790
+ }
3791
+
3792
+ /// Runs `omdb nexus support-bundles create`
3793
+ async fn cmd_nexus_support_bundles_create (
3794
+ client : & nexus_client:: Client ,
3795
+ _destruction_token : DestructiveOperationToken ,
3796
+ ) -> Result < ( ) , anyhow:: Error > {
3797
+ let support_bundle_id = client
3798
+ . support_bundle_create ( )
3799
+ . await
3800
+ . context ( "creating support bundle" ) ?
3801
+ . into_inner ( )
3802
+ . id ;
3803
+ println ! ( "created support bundle: {support_bundle_id}" ) ;
3804
+ Ok ( ( ) )
3805
+ }
3806
+
3807
+ /// Runs `omdb nexus support-bundles delete`
3808
+ async fn cmd_nexus_support_bundles_delete (
3809
+ client : & nexus_client:: Client ,
3810
+ args : & SupportBundleDeleteArgs ,
3811
+ _destruction_token : DestructiveOperationToken ,
3812
+ ) -> Result < ( ) , anyhow:: Error > {
3813
+ let _ = client
3814
+ . support_bundle_delete ( args. id . as_untyped_uuid ( ) )
3815
+ . await
3816
+ . with_context ( || format ! ( "deleting support bundle {}" , args. id) ) ?;
3817
+ println ! ( "support bundle {} deleted" , args. id) ;
3818
+ Ok ( ( ) )
3819
+ }
3820
+
3821
+ async fn write_stream_to_sink (
3822
+ mut stream : impl futures:: Stream < Item = reqwest:: Result < bytes:: Bytes > >
3823
+ + std:: marker:: Unpin ,
3824
+ mut sink : impl std:: io:: Write ,
3825
+ ) -> Result < ( ) , anyhow:: Error > {
3826
+ while let Some ( data) = stream. next ( ) . await {
3827
+ match data {
3828
+ Err ( err) => return Err ( anyhow:: anyhow!( err) ) ,
3829
+ Ok ( data) => sink. write_all ( & data) ?,
3830
+ }
3831
+ }
3832
+ Ok ( ( ) )
3833
+ }
3834
+
3835
+ /// Runs `omdb nexus support-bundles get-index`
3836
+ async fn cmd_nexus_support_bundles_get_index (
3837
+ client : & nexus_client:: Client ,
3838
+ args : & SupportBundleIndexArgs ,
3839
+ ) -> Result < ( ) , anyhow:: Error > {
3840
+ let stream = client
3841
+ . support_bundle_index ( args. id . as_untyped_uuid ( ) )
3842
+ . await
3843
+ . with_context ( || {
3844
+ format ! ( "downloading support bundle index {}" , args. id)
3845
+ } ) ?
3846
+ . into_inner_stream ( ) ;
3847
+
3848
+ write_stream_to_sink ( stream, std:: io:: stdout ( ) ) . await . with_context (
3849
+ || format ! ( "streaming support bundle index {}" , args. id) ,
3850
+ ) ?;
3851
+ Ok ( ( ) )
3852
+ }
3853
+
3854
+ /// Runs `omdb nexus support-bundles get-file`
3855
+ async fn cmd_nexus_support_bundles_get_file (
3856
+ client : & nexus_client:: Client ,
3857
+ args : & SupportBundleFileArgs ,
3858
+ ) -> Result < ( ) , anyhow:: Error > {
3859
+ let stream = client
3860
+ . support_bundle_download_file (
3861
+ args. id . as_untyped_uuid ( ) ,
3862
+ args. path . as_str ( ) ,
3863
+ )
3864
+ . await
3865
+ . with_context ( || {
3866
+ format ! (
3867
+ "downloading support bundle file {}: {}" ,
3868
+ args. id, args. path
3869
+ )
3870
+ } ) ?
3871
+ . into_inner_stream ( ) ;
3872
+
3873
+ let sink: Box < dyn std:: io:: Write > = match & args. output {
3874
+ Some ( path) => Box :: new ( std:: fs:: File :: create ( path) ?) ,
3875
+ None => Box :: new ( std:: io:: stdout ( ) ) ,
3876
+ } ;
3877
+
3878
+ write_stream_to_sink ( stream, sink) . await . with_context ( || {
3879
+ format ! ( "streaming support bundle file {}: {}" , args. id, args. path)
3880
+ } ) ?;
3881
+ Ok ( ( ) )
3882
+ }
0 commit comments