-
Notifications
You must be signed in to change notification settings - Fork 43
[omdb] Basic commands to access support bundles #7972
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: sb-internal-api
Are you sure you want to change the base?
Changes from all commits
c079c3f
df47341
e39785a
3dfd8ab
4bf9d9a
600a537
192e255
6374a7b
8278c09
b9b94d5
53c0a76
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -21,6 +21,7 @@ use clap::Args; | |||||||
use clap::ColorChoice; | ||||||||
use clap::Subcommand; | ||||||||
use clap::ValueEnum; | ||||||||
use futures::StreamExt; | ||||||||
use futures::TryStreamExt; | ||||||||
use futures::future::try_join; | ||||||||
use http::StatusCode; | ||||||||
|
@@ -69,6 +70,7 @@ use omicron_uuid_kinds::GenericUuid; | |||||||
use omicron_uuid_kinds::ParseError; | ||||||||
use omicron_uuid_kinds::PhysicalDiskUuid; | ||||||||
use omicron_uuid_kinds::SledUuid; | ||||||||
use omicron_uuid_kinds::SupportBundleUuid; | ||||||||
use serde::Deserialize; | ||||||||
use slog_error_chain::InlineErrorChain; | ||||||||
use std::collections::BTreeMap; | ||||||||
|
@@ -126,6 +128,9 @@ enum NexusCommands { | |||||||
Sagas(SagasArgs), | ||||||||
/// interact with sleds | ||||||||
Sleds(SledsArgs), | ||||||||
/// interact with support bundles | ||||||||
#[command(visible_alias = "sb")] | ||||||||
SupportBundles(SupportBundleArgs), | ||||||||
} | ||||||||
|
||||||||
#[derive(Debug, Args)] | ||||||||
|
@@ -474,6 +479,49 @@ struct DiskExpungeArgs { | |||||||
physical_disk_id: PhysicalDiskUuid, | ||||||||
} | ||||||||
|
||||||||
#[derive(Debug, Args)] | ||||||||
struct SupportBundleArgs { | ||||||||
#[command(subcommand)] | ||||||||
command: SupportBundleCommands, | ||||||||
} | ||||||||
|
||||||||
#[derive(Debug, Subcommand)] | ||||||||
#[allow(clippy::large_enum_variant)] | ||||||||
enum SupportBundleCommands { | ||||||||
/// List all support bundles | ||||||||
List, | ||||||||
/// Create a new support bundle | ||||||||
Create, | ||||||||
/// Delete a support bundle | ||||||||
Delete(SupportBundleDeleteArgs), | ||||||||
/// Download the index of a support bundle | ||||||||
/// | ||||||||
/// This is a "list of files", from which individual files can be accessed | ||||||||
GetIndex(SupportBundleIndexArgs), | ||||||||
/// View a file within a support bundle | ||||||||
GetFile(SupportBundleFileArgs), | ||||||||
} | ||||||||
|
||||||||
#[derive(Debug, Args)] | ||||||||
struct SupportBundleDeleteArgs { | ||||||||
id: SupportBundleUuid, | ||||||||
} | ||||||||
|
||||||||
#[derive(Debug, Args)] | ||||||||
struct SupportBundleIndexArgs { | ||||||||
id: SupportBundleUuid, | ||||||||
} | ||||||||
|
||||||||
#[derive(Debug, Args)] | ||||||||
struct SupportBundleFileArgs { | ||||||||
id: SupportBundleUuid, | ||||||||
path: Utf8PathBuf, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'll eventually have non-text files in bundles, e.g. core dumps, SP dumps. An option to directly save the file would be useful for these.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added, also, removed utf8 parsing so the core dump cases can be passed through as regular binary files. |
||||||||
/// Optional output path where the file should be written, | ||||||||
/// instead of stdout. | ||||||||
#[arg(short, long)] | ||||||||
output: Option<Utf8PathBuf>, | ||||||||
} | ||||||||
|
||||||||
impl NexusArgs { | ||||||||
/// Run a `omdb nexus` subcommand. | ||||||||
pub(crate) async fn run_cmd( | ||||||||
|
@@ -667,6 +715,27 @@ impl NexusArgs { | |||||||
cmd_nexus_sled_expunge_disk(&client, args, omdb, log, token) | ||||||||
.await | ||||||||
} | ||||||||
NexusCommands::SupportBundles(SupportBundleArgs { | ||||||||
command: SupportBundleCommands::List, | ||||||||
}) => cmd_nexus_support_bundles_list(&client).await, | ||||||||
NexusCommands::SupportBundles(SupportBundleArgs { | ||||||||
command: SupportBundleCommands::Create, | ||||||||
}) => { | ||||||||
let token = omdb.check_allow_destructive()?; | ||||||||
cmd_nexus_support_bundles_create(&client, token).await | ||||||||
} | ||||||||
NexusCommands::SupportBundles(SupportBundleArgs { | ||||||||
command: SupportBundleCommands::Delete(args), | ||||||||
}) => { | ||||||||
let token = omdb.check_allow_destructive()?; | ||||||||
cmd_nexus_support_bundles_delete(&client, args, token).await | ||||||||
} | ||||||||
NexusCommands::SupportBundles(SupportBundleArgs { | ||||||||
command: SupportBundleCommands::GetIndex(args), | ||||||||
}) => cmd_nexus_support_bundles_get_index(&client, args).await, | ||||||||
NexusCommands::SupportBundles(SupportBundleArgs { | ||||||||
command: SupportBundleCommands::GetFile(args), | ||||||||
}) => cmd_nexus_support_bundles_get_file(&client, args).await, | ||||||||
} | ||||||||
} | ||||||||
} | ||||||||
|
@@ -3385,3 +3454,132 @@ async fn cmd_nexus_sled_expunge_disk_with_datastore( | |||||||
eprintln!("expunged disk {}", args.physical_disk_id); | ||||||||
Ok(()) | ||||||||
} | ||||||||
|
||||||||
/// Runs `omdb nexus support-bundles list` | ||||||||
async fn cmd_nexus_support_bundles_list( | ||||||||
client: &nexus_client::Client, | ||||||||
) -> Result<(), anyhow::Error> { | ||||||||
let support_bundle_stream = client.support_bundle_list_stream(None, None); | ||||||||
|
||||||||
let support_bundles = support_bundle_stream | ||||||||
.try_collect::<Vec<_>>() | ||||||||
.await | ||||||||
.context("listing support bundles")?; | ||||||||
|
||||||||
#[derive(Tabled)] | ||||||||
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")] | ||||||||
struct SupportBundleInfo { | ||||||||
id: Uuid, | ||||||||
time_created: DateTime<Utc>, | ||||||||
reason_for_creation: String, | ||||||||
reason_for_failure: String, | ||||||||
state: String, | ||||||||
} | ||||||||
let rows = support_bundles.into_iter().map(|sb| SupportBundleInfo { | ||||||||
id: *sb.id, | ||||||||
time_created: sb.time_created, | ||||||||
reason_for_creation: sb.reason_for_creation, | ||||||||
reason_for_failure: sb | ||||||||
.reason_for_failure | ||||||||
.unwrap_or_else(|| "-".to_string()), | ||||||||
state: format!("{:?}", sb.state), | ||||||||
}); | ||||||||
let table = tabled::Table::new(rows) | ||||||||
.with(tabled::settings::Style::empty()) | ||||||||
.with(tabled::settings::Padding::new(0, 1, 0, 0)) | ||||||||
.to_string(); | ||||||||
println!("{}", table); | ||||||||
Ok(()) | ||||||||
} | ||||||||
|
||||||||
/// Runs `omdb nexus support-bundles create` | ||||||||
async fn cmd_nexus_support_bundles_create( | ||||||||
client: &nexus_client::Client, | ||||||||
_destruction_token: DestructiveOperationToken, | ||||||||
) -> Result<(), anyhow::Error> { | ||||||||
let support_bundle_id = client | ||||||||
.support_bundle_create() | ||||||||
.await | ||||||||
.context("creating support bundle")? | ||||||||
.into_inner() | ||||||||
.id; | ||||||||
println!("created support bundle: {support_bundle_id}"); | ||||||||
Ok(()) | ||||||||
} | ||||||||
|
||||||||
/// Runs `omdb nexus support-bundles delete` | ||||||||
async fn cmd_nexus_support_bundles_delete( | ||||||||
client: &nexus_client::Client, | ||||||||
args: &SupportBundleDeleteArgs, | ||||||||
_destruction_token: DestructiveOperationToken, | ||||||||
) -> Result<(), anyhow::Error> { | ||||||||
let _ = client | ||||||||
.support_bundle_delete(args.id.as_untyped_uuid()) | ||||||||
.await | ||||||||
.with_context(|| format!("deleting support bundle {}", args.id))?; | ||||||||
println!("support bundle {} deleted", args.id); | ||||||||
Ok(()) | ||||||||
} | ||||||||
|
||||||||
async fn write_stream_to_sink( | ||||||||
mut stream: impl futures::Stream<Item = reqwest::Result<bytes::Bytes>> | ||||||||
+ std::marker::Unpin, | ||||||||
mut sink: impl std::io::Write, | ||||||||
) -> Result<(), anyhow::Error> { | ||||||||
while let Some(data) = stream.next().await { | ||||||||
match data { | ||||||||
Err(err) => return Err(anyhow::anyhow!(err)), | ||||||||
Ok(data) => sink.write_all(&data)?, | ||||||||
} | ||||||||
} | ||||||||
Ok(()) | ||||||||
} | ||||||||
|
||||||||
/// Runs `omdb nexus support-bundles get-index` | ||||||||
async fn cmd_nexus_support_bundles_get_index( | ||||||||
client: &nexus_client::Client, | ||||||||
args: &SupportBundleIndexArgs, | ||||||||
) -> Result<(), anyhow::Error> { | ||||||||
let stream = client | ||||||||
.support_bundle_index(args.id.as_untyped_uuid()) | ||||||||
.await | ||||||||
.with_context(|| { | ||||||||
format!("downloading support bundle index {}", args.id) | ||||||||
})? | ||||||||
.into_inner_stream(); | ||||||||
|
||||||||
write_stream_to_sink(stream, std::io::stdout()).await.with_context( | ||||||||
|| format!("streaming support bundle index {}", args.id), | ||||||||
)?; | ||||||||
Ok(()) | ||||||||
} | ||||||||
|
||||||||
/// Runs `omdb nexus support-bundles get-file` | ||||||||
async fn cmd_nexus_support_bundles_get_file( | ||||||||
client: &nexus_client::Client, | ||||||||
args: &SupportBundleFileArgs, | ||||||||
) -> Result<(), anyhow::Error> { | ||||||||
let stream = client | ||||||||
.support_bundle_download_file( | ||||||||
args.id.as_untyped_uuid(), | ||||||||
args.path.as_str(), | ||||||||
) | ||||||||
.await | ||||||||
.with_context(|| { | ||||||||
format!( | ||||||||
"downloading support bundle file {}: {}", | ||||||||
args.id, args.path | ||||||||
) | ||||||||
})? | ||||||||
.into_inner_stream(); | ||||||||
|
||||||||
let sink: Box<dyn std::io::Write> = match &args.output { | ||||||||
Some(path) => Box::new(std::fs::File::create(path)?), | ||||||||
None => Box::new(std::io::stdout()), | ||||||||
}; | ||||||||
|
||||||||
write_stream_to_sink(stream, sink).await.with_context(|| { | ||||||||
format!("streaming support bundle file {}: {}", args.id, args.path) | ||||||||
})?; | ||||||||
Ok(()) | ||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
support-bundles
is a bit of a mouthful, WDYT about adding an alias?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added