mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-13 04:51:00 +08:00
feat(cli): add JSON/YAML output format to provider list command
Closes #1745 Add -o/--output flag support (json, yaml, table) to openshell provider list by migrating it to use the existing generic output formatter from #1750. This follows the established pattern used by sandbox list, gateway list, and provider list-profiles commands. Changes: - crates/openshell-cli/src/run.rs: Add provider_to_json() helper that maps Provider proto fields to JSON, including only credential keys (not values) for security. Add output parameter to provider_list() and insert early-return for structured formats. - crates/openshell-cli/src/main.rs: Add output field to ProviderCommands::List with conflicts_with constraints between names and output flags. Update invocation to pass output parameter. - crates/openshell-cli/tests/provider_commands_integration.rs: Update provider_list() call to include output parameter. Security: The provider_to_json() helper only exposes credential keys, never credential values, preventing CWE-532 (insertion of sensitive information into output).
This commit is contained in:
@@ -777,8 +777,12 @@ enum ProviderCommands {
|
||||
offset: u32,
|
||||
|
||||
/// Print only provider names, one per line.
|
||||
#[arg(long)]
|
||||
#[arg(long, conflicts_with = "output")]
|
||||
names: bool,
|
||||
|
||||
/// Output format.
|
||||
#[arg(short = 'o', long = "output", value_enum, default_value_t = OutputFormat::Table, conflicts_with = "names")]
|
||||
output: OutputFormat,
|
||||
},
|
||||
|
||||
/// List available provider profiles.
|
||||
@@ -2902,8 +2906,10 @@ async fn main() -> Result<()> {
|
||||
limit,
|
||||
offset,
|
||||
names,
|
||||
output,
|
||||
} => {
|
||||
run::provider_list(endpoint, limit, offset, names, &tls).await?;
|
||||
run::provider_list(endpoint, limit, offset, names, output.as_str(), &tls)
|
||||
.await?;
|
||||
}
|
||||
ProviderCommands::ListProfiles { output } => {
|
||||
run::provider_list_profiles(endpoint, output.as_str(), &tls).await?;
|
||||
|
||||
@@ -4778,11 +4778,65 @@ pub async fn provider_get(server: &str, name: &str, tls: &TlsOptions) -> Result<
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn provider_to_json(provider: &Provider) -> serde_json::Value {
|
||||
let mut obj = serde_json::Map::new();
|
||||
|
||||
// Core fields
|
||||
obj.insert("id".to_string(), serde_json::json!(provider.object_id()));
|
||||
obj.insert(
|
||||
"name".to_string(),
|
||||
serde_json::json!(provider.object_name()),
|
||||
);
|
||||
obj.insert("type".to_string(), serde_json::json!(provider.r#type));
|
||||
|
||||
// Credential keys (NEVER values - security)
|
||||
let credential_keys: Vec<String> = provider.credentials.keys().cloned().collect();
|
||||
obj.insert(
|
||||
"credential_keys".to_string(),
|
||||
serde_json::json!(credential_keys),
|
||||
);
|
||||
|
||||
// Config (non-secret configuration)
|
||||
if !provider.config.is_empty() {
|
||||
obj.insert("config".to_string(), serde_json::json!(provider.config));
|
||||
}
|
||||
|
||||
// Metadata fields (only if metadata exists)
|
||||
if let Some(meta) = &provider.metadata {
|
||||
if !meta.labels.is_empty() {
|
||||
obj.insert("labels".to_string(), serde_json::json!(meta.labels));
|
||||
}
|
||||
if meta.resource_version != 0 {
|
||||
obj.insert(
|
||||
"resource_version".to_string(),
|
||||
serde_json::json!(meta.resource_version),
|
||||
);
|
||||
}
|
||||
if meta.created_at_ms != 0 {
|
||||
obj.insert(
|
||||
"created_at_ms".to_string(),
|
||||
serde_json::json!(meta.created_at_ms),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Credential expiration times (only if present)
|
||||
if !provider.credential_expires_at_ms.is_empty() {
|
||||
obj.insert(
|
||||
"credential_expires_at_ms".to_string(),
|
||||
serde_json::json!(provider.credential_expires_at_ms),
|
||||
);
|
||||
}
|
||||
|
||||
serde_json::Value::Object(obj)
|
||||
}
|
||||
|
||||
pub async fn provider_list(
|
||||
server: &str,
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
names_only: bool,
|
||||
output: &str,
|
||||
tls: &TlsOptions,
|
||||
) -> Result<()> {
|
||||
let mut client = grpc_client(server, tls).await?;
|
||||
@@ -4792,6 +4846,11 @@ pub async fn provider_list(
|
||||
.into_diagnostic()?;
|
||||
let providers = response.into_inner().providers;
|
||||
|
||||
// Handle structured output formats (json, yaml)
|
||||
if crate::output::print_output_collection(output, &providers, provider_to_json)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if providers.is_empty() {
|
||||
if !names_only {
|
||||
println!("No providers found.");
|
||||
|
||||
@@ -997,7 +997,7 @@ async fn provider_cli_run_functions_support_full_crud_flow() {
|
||||
run::provider_get(&ts.endpoint, "my-claude", &ts.tls)
|
||||
.await
|
||||
.expect("provider get");
|
||||
run::provider_list(&ts.endpoint, 100, 0, false, &ts.tls)
|
||||
run::provider_list(&ts.endpoint, 100, 0, false, "table", &ts.tls)
|
||||
.await
|
||||
.expect("provider list");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user