Skip to content

Commit 1816278

Browse files
committed
feat(cli): add --output json/yaml to sandbox get, status, and sandbox create
Add structured output support to three commands that previously only supported human-readable table output: - `sandbox get --output json/yaml`: emits sandbox detail including policy source, revision, and active policy content via a new `sandbox_detail_to_json` converter. - `status --output json/yaml`: emits gateway connection state via a new `status_to_json` converter with conditional fields (auth, version, error, http_status) matching the human output. - `sandbox create --output json/yaml`: emits sandbox metadata after the sandbox reaches Ready phase, suppressing spinners and ANSI chrome from stdout. Uploads and port forwarding still execute before output. Clap rejects `--output` combined with `--editor` or trailing commands. All three commands reuse the existing `OutputFormat` enum and `print_output_single` helper. Default output (no --output flag) is byte-identical to current behavior. Unit tests cover both converters. Closes #1964 Assisted-By: 🤖 Claude Code Signed-off-by: Roland Huß <rhuss@redhat.com>
1 parent 2c54589 commit 1816278

6 files changed

Lines changed: 380 additions & 69 deletions

File tree

crates/openshell-cli/src/main.rs

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,11 @@ enum Commands {
533533

534534
/// Show gateway status and information.
535535
#[command(help_template = LEAF_HELP_TEMPLATE, next_help_heading = "FLAGS")]
536-
Status,
536+
Status {
537+
/// Output format.
538+
#[arg(short = 'o', long = "output", value_enum, default_value_t = OutputFormat::Table)]
539+
output: OutputFormat,
540+
},
537541

538542
/// Manage inference configuration.
539543
#[command(after_help = INFERENCE_EXAMPLES, help_template = SUBCOMMAND_HELP_TEMPLATE)]
@@ -1336,6 +1340,10 @@ enum SandboxCommands {
13361340
#[arg(long, value_parser = ["manual", "auto"], default_value = "manual")]
13371341
approval_mode: String,
13381342

1343+
/// Output format.
1344+
#[arg(short = 'o', long = "output", value_enum, default_value_t = OutputFormat::Table, conflicts_with_all = ["editor", "command"])]
1345+
output: OutputFormat,
1346+
13391347
/// Command to run after "--" (defaults to an interactive shell).
13401348
#[arg(last = true, allow_hyphen_values = true)]
13411349
command: Vec<String>,
@@ -1349,8 +1357,12 @@ enum SandboxCommands {
13491357
name: Option<String>,
13501358

13511359
/// Print only the active policy YAML (same policy as the default view; stdout only).
1352-
#[arg(long)]
1360+
#[arg(long, conflicts_with = "output")]
13531361
policy_only: bool,
1362+
1363+
/// Output format.
1364+
#[arg(short = 'o', long = "output", value_enum, default_value_t = OutputFormat::Table)]
1365+
output: OutputFormat,
13541366
},
13551367

13561368
/// List sandboxes.
@@ -2079,11 +2091,17 @@ async fn main() -> Result<()> {
20792091
// -----------------------------------------------------------
20802092
// Top-level status
20812093
// -----------------------------------------------------------
2082-
Some(Commands::Status) => {
2094+
Some(Commands::Status { output }) => {
20832095
if let Ok(ctx) = resolve_gateway(&cli.gateway, &cli.gateway_endpoint) {
20842096
let mut tls = tls.with_gateway_name(&ctx.name);
20852097
apply_auth(&mut tls, &ctx.name);
2086-
run::gateway_status(&ctx.name, &ctx.endpoint, &tls).await?;
2098+
run::gateway_status(&ctx.name, &ctx.endpoint, output.as_str(), &tls).await?;
2099+
} else if openshell_cli::output::print_output_single(
2100+
output.as_str(),
2101+
&(),
2102+
|()| serde_json::json!({"status": "not_configured"}),
2103+
)? {
2104+
// Structured output handled.
20872105
} else {
20882106
println!("{}", "Gateway Status".cyan().bold());
20892107
println!();
@@ -2612,6 +2630,7 @@ async fn main() -> Result<()> {
26122630
labels,
26132631
envs,
26142632
approval_mode,
2633+
output,
26152634
command,
26162635
} => {
26172636
// Resolve --tty / --no-tty into an Option<bool> override.
@@ -2697,6 +2716,7 @@ async fn main() -> Result<()> {
26972716
&labels_map,
26982717
&env_map,
26992718
&approval_mode,
2719+
output.as_str(),
27002720
&tls,
27012721
))
27022722
.await?;
@@ -2769,9 +2789,14 @@ async fn main() -> Result<()> {
27692789
| SandboxCommands::Download { .. } => {
27702790
unreachable!()
27712791
}
2772-
SandboxCommands::Get { name, policy_only } => {
2792+
SandboxCommands::Get {
2793+
name,
2794+
policy_only,
2795+
output,
2796+
} => {
27732797
let name = resolve_sandbox_name(name, &ctx.name)?;
2774-
run::sandbox_get(endpoint, &name, policy_only, &tls).await?;
2798+
run::sandbox_get(endpoint, &name, policy_only, output.as_str(), &tls)
2799+
.await?;
27752800
}
27762801
SandboxCommands::List {
27772802
limit,
@@ -3455,7 +3480,7 @@ mod tests {
34553480
.expect("global gateway flag should parse with subcommands");
34563481

34573482
assert_eq!(cli.gateway.as_deref(), Some("demo"));
3458-
assert!(matches!(cli.command, Some(Commands::Status)));
3483+
assert!(matches!(cli.command, Some(Commands::Status { .. })));
34593484
}
34603485

34613486
#[test]

0 commit comments

Comments
 (0)