Skip to content

Commit 686b24d

Browse files
mjamivjohntmyers
andauthored
fix(cli): add json output for policy get (#1410)
* fix(cli): add json output for policy get * test(cli): cover policy get full json output * fix(cli): address policy get json clippy --------- Co-authored-by: John Myers <9696606+johntmyers@users.noreply.github.com>
1 parent 48333e5 commit 686b24d

6 files changed

Lines changed: 343 additions & 23 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/openshell-cli/src/main.rs

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,21 @@ impl OutputFormat {
680680
}
681681
}
682682

683+
#[derive(Clone, Debug, ValueEnum)]
684+
enum PolicyGetOutput {
685+
Table,
686+
Json,
687+
}
688+
689+
impl PolicyGetOutput {
690+
fn as_str(&self) -> &'static str {
691+
match self {
692+
Self::Table => "table",
693+
Self::Json => "json",
694+
}
695+
}
696+
}
697+
683698
#[derive(Clone, Debug, ValueEnum)]
684699
enum CliEditor {
685700
Vscode,
@@ -1591,10 +1606,14 @@ enum PolicyCommands {
15911606
#[arg(long = "rev", default_value_t = 0)]
15921607
rev: u32,
15931608

1594-
/// Print the full policy as YAML.
1609+
/// Include the full policy payload.
15951610
#[arg(long)]
15961611
full: bool,
15971612

1613+
/// Output format.
1614+
#[arg(short = 'o', long = "output", value_enum, default_value_t = PolicyGetOutput::Table)]
1615+
output: PolicyGetOutput,
1616+
15981617
/// Show the global policy revision.
15991618
#[arg(long)]
16001619
global: bool,
@@ -2271,13 +2290,29 @@ async fn main() -> Result<()> {
22712290
name,
22722291
rev,
22732292
full,
2293+
output,
22742294
global,
22752295
} => {
22762296
if global {
2277-
run::sandbox_policy_get_global(&ctx.endpoint, rev, full, &tls).await?;
2297+
run::sandbox_policy_get_global(
2298+
&ctx.endpoint,
2299+
rev,
2300+
full,
2301+
output.as_str(),
2302+
&tls,
2303+
)
2304+
.await?;
22782305
} else {
22792306
let name = resolve_sandbox_name(name, &ctx.name)?;
2280-
run::sandbox_policy_get(&ctx.endpoint, &name, rev, full, &tls).await?;
2307+
run::sandbox_policy_get(
2308+
&ctx.endpoint,
2309+
&name,
2310+
rev,
2311+
full,
2312+
output.as_str(),
2313+
&tls,
2314+
)
2315+
.await?;
22812316
}
22822317
}
22832318
PolicyCommands::List {
@@ -3968,6 +4003,34 @@ mod tests {
39684003
}
39694004
}
39704005

4006+
#[test]
4007+
fn policy_get_json_output_parses() {
4008+
let cli = Cli::try_parse_from([
4009+
"openshell",
4010+
"policy",
4011+
"get",
4012+
"my-sandbox",
4013+
"--full",
4014+
"-o",
4015+
"json",
4016+
])
4017+
.expect("policy get -o json should parse");
4018+
4019+
match cli.command {
4020+
Some(Commands::Policy {
4021+
command:
4022+
Some(PolicyCommands::Get {
4023+
name, full, output, ..
4024+
}),
4025+
}) => {
4026+
assert_eq!(name.as_deref(), Some("my-sandbox"));
4027+
assert!(full);
4028+
assert!(matches!(output, PolicyGetOutput::Json));
4029+
}
4030+
other => panic!("expected policy get command, got: {other:?}"),
4031+
}
4032+
}
4033+
39714034
#[test]
39724035
fn policy_delete_global_parses() {
39734036
let cli = Cli::try_parse_from(["openshell", "policy", "delete", "--global", "--yes"])

crates/openshell-cli/src/run.rs

Lines changed: 146 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6125,8 +6125,49 @@ pub async fn sandbox_policy_get(
61256125
name: &str,
61266126
version: u32,
61276127
full: bool,
6128+
output: &str,
61286129
tls: &TlsOptions,
61296130
) -> Result<()> {
6131+
let mut stdout = Vec::new();
6132+
let mut stderr = Vec::new();
6133+
sandbox_policy_get_to_writer(
6134+
server,
6135+
name,
6136+
version,
6137+
full,
6138+
output,
6139+
tls,
6140+
(&mut stdout, &mut stderr),
6141+
)
6142+
.await?;
6143+
6144+
{
6145+
let mut terminal_stdout = std::io::stdout().lock();
6146+
terminal_stdout.write_all(&stdout).into_diagnostic()?;
6147+
}
6148+
{
6149+
let mut terminal_stderr = std::io::stderr().lock();
6150+
terminal_stderr.write_all(&stderr).into_diagnostic()?;
6151+
}
6152+
6153+
Ok(())
6154+
}
6155+
6156+
#[doc(hidden)]
6157+
pub async fn sandbox_policy_get_to_writer<W, E>(
6158+
server: &str,
6159+
name: &str,
6160+
version: u32,
6161+
full: bool,
6162+
output: &str,
6163+
tls: &TlsOptions,
6164+
writers: (&mut W, &mut E),
6165+
) -> Result<()>
6166+
where
6167+
W: Write + Send,
6168+
E: Write + Send,
6169+
{
6170+
let (stdout, stderr) = writers;
61306171
let mut client = grpc_client(server, tls).await?;
61316172

61326173
let status_resp = client
@@ -6141,32 +6182,55 @@ pub async fn sandbox_policy_get(
61416182
let inner = status_resp.into_inner();
61426183
if let Some(rev) = inner.revision {
61436184
let status = PolicyStatus::try_from(rev.status).unwrap_or(PolicyStatus::Unspecified);
6144-
println!("Version: {}", rev.version);
6145-
println!("Hash: {}", rev.policy_hash);
6146-
println!("Status: {status:?}");
6147-
println!("Active: {}", inner.active_version);
6185+
match output {
6186+
"json" => {
6187+
let obj = policy_revision_to_json(
6188+
"sandbox",
6189+
Some(name),
6190+
Some(inner.active_version),
6191+
&rev,
6192+
status,
6193+
full,
6194+
)?;
6195+
writeln!(
6196+
stdout,
6197+
"{}",
6198+
serde_json::to_string_pretty(&obj).into_diagnostic()?
6199+
)
6200+
.into_diagnostic()?;
6201+
return Ok(());
6202+
}
6203+
"table" => {}
6204+
_ => return Err(miette!("unsupported output format: {output}")),
6205+
}
6206+
6207+
writeln!(stdout, "Version: {}", rev.version).into_diagnostic()?;
6208+
writeln!(stdout, "Hash: {}", rev.policy_hash).into_diagnostic()?;
6209+
writeln!(stdout, "Status: {status:?}").into_diagnostic()?;
6210+
writeln!(stdout, "Active: {}", inner.active_version).into_diagnostic()?;
61486211
if rev.created_at_ms > 0 {
6149-
println!("Created: {} ms", rev.created_at_ms);
6212+
writeln!(stdout, "Created: {} ms", rev.created_at_ms).into_diagnostic()?;
61506213
}
61516214
if rev.loaded_at_ms > 0 {
6152-
println!("Loaded: {} ms", rev.loaded_at_ms);
6215+
writeln!(stdout, "Loaded: {} ms", rev.loaded_at_ms).into_diagnostic()?;
61536216
}
61546217
if !rev.load_error.is_empty() {
6155-
println!("Error: {}", rev.load_error);
6218+
writeln!(stdout, "Error: {}", rev.load_error).into_diagnostic()?;
61566219
}
61576220

61586221
if full {
61596222
if let Some(ref policy) = rev.policy {
6160-
println!("---");
6223+
writeln!(stdout, "---").into_diagnostic()?;
61616224
let yaml_str = openshell_policy::serialize_sandbox_policy(policy)
61626225
.wrap_err("failed to serialize policy to YAML")?;
6163-
print!("{yaml_str}");
6226+
write!(stdout, "{yaml_str}").into_diagnostic()?;
61646227
} else {
6165-
eprintln!("Policy payload not available for this version");
6228+
writeln!(stderr, "Policy payload not available for this version")
6229+
.into_diagnostic()?;
61666230
}
61676231
}
61686232
} else {
6169-
eprintln!("No policy history found for sandbox '{name}'");
6233+
writeln!(stderr, "No policy history found for sandbox '{name}'").into_diagnostic()?;
61706234
}
61716235

61726236
Ok(())
@@ -6176,6 +6240,7 @@ pub async fn sandbox_policy_get_global(
61766240
server: &str,
61776241
version: u32,
61786242
full: bool,
6243+
output: &str,
61796244
tls: &TlsOptions,
61806245
) -> Result<()> {
61816246
let mut client = grpc_client(server, tls).await?;
@@ -6192,6 +6257,16 @@ pub async fn sandbox_policy_get_global(
61926257
let inner = status_resp.into_inner();
61936258
if let Some(rev) = inner.revision {
61946259
let status = PolicyStatus::try_from(rev.status).unwrap_or(PolicyStatus::Unspecified);
6260+
match output {
6261+
"json" => {
6262+
let obj = policy_revision_to_json("global", None, None, &rev, status, full)?;
6263+
println!("{}", serde_json::to_string_pretty(&obj).into_diagnostic()?);
6264+
return Ok(());
6265+
}
6266+
"table" => {}
6267+
_ => return Err(miette!("unsupported output format: {output}")),
6268+
}
6269+
61956270
println!("Scope: global");
61966271
println!("Version: {}", rev.version);
61976272
println!("Hash: {}", rev.policy_hash);
@@ -6220,6 +6295,66 @@ pub async fn sandbox_policy_get_global(
62206295
Ok(())
62216296
}
62226297

6298+
fn policy_status_json_name(status: PolicyStatus) -> &'static str {
6299+
match status {
6300+
PolicyStatus::Unspecified => "unspecified",
6301+
PolicyStatus::Pending => "pending",
6302+
PolicyStatus::Loaded => "loaded",
6303+
PolicyStatus::Failed => "failed",
6304+
PolicyStatus::Superseded => "superseded",
6305+
}
6306+
}
6307+
6308+
fn policy_revision_to_json(
6309+
scope: &str,
6310+
sandbox: Option<&str>,
6311+
active_version: Option<u32>,
6312+
rev: &openshell_core::proto::SandboxPolicyRevision,
6313+
status: PolicyStatus,
6314+
full: bool,
6315+
) -> Result<serde_json::Value> {
6316+
let mut obj = serde_json::Map::new();
6317+
obj.insert("scope".to_string(), serde_json::json!(scope));
6318+
if let Some(sandbox) = sandbox {
6319+
obj.insert("sandbox".to_string(), serde_json::json!(sandbox));
6320+
}
6321+
obj.insert("version".to_string(), serde_json::json!(rev.version));
6322+
obj.insert("hash".to_string(), serde_json::json!(rev.policy_hash));
6323+
obj.insert(
6324+
"status".to_string(),
6325+
serde_json::json!(policy_status_json_name(status)),
6326+
);
6327+
if let Some(active_version) = active_version {
6328+
obj.insert(
6329+
"active_version".to_string(),
6330+
serde_json::json!(active_version),
6331+
);
6332+
}
6333+
if rev.created_at_ms > 0 {
6334+
obj.insert(
6335+
"created_at_ms".to_string(),
6336+
serde_json::json!(rev.created_at_ms),
6337+
);
6338+
}
6339+
if rev.loaded_at_ms > 0 {
6340+
obj.insert(
6341+
"loaded_at_ms".to_string(),
6342+
serde_json::json!(rev.loaded_at_ms),
6343+
);
6344+
}
6345+
if !rev.load_error.is_empty() {
6346+
obj.insert("load_error".to_string(), serde_json::json!(rev.load_error));
6347+
}
6348+
if full {
6349+
let policy = match rev.policy.as_ref() {
6350+
Some(policy) => openshell_policy::sandbox_policy_to_json_value(policy)?,
6351+
None => serde_json::Value::Null,
6352+
};
6353+
obj.insert("policy".to_string(), policy);
6354+
}
6355+
Ok(serde_json::Value::Object(obj))
6356+
}
6357+
62236358
pub async fn sandbox_policy_list(
62246359
server: &str,
62256360
name: &str,

0 commit comments

Comments
 (0)