Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions cli/src/clients/datafusion_helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ use chrono::{DateTime, Duration, Local};
use clap::ValueEnum;
use restate_types::journal_v2::Entry;
use restate_types::{identifiers::AwakeableIdentifier, invocation::ServiceType};
use serde::Deserialize;
use serde_with::{DeserializeAs, serde_as};
use serde::{Deserialize, Serialize};
use serde_with::{DeserializeAs, SerializeAs, serde_as};

mod v2;

Expand All @@ -36,7 +36,16 @@ pub struct SimpleInvocation {
}

#[derive(
ValueEnum, Copy, Clone, Eq, Hash, PartialEq, Debug, Default, serde_with::DeserializeFromStr,
ValueEnum,
Copy,
Clone,
Eq,
Hash,
PartialEq,
Debug,
Default,
serde_with::DeserializeFromStr,
serde_with::SerializeDisplay,
)]
pub enum InvocationState {
#[default]
Expand Down Expand Up @@ -86,7 +95,7 @@ impl Display for InvocationState {
}

#[serde_as]
#[derive(Debug, Clone, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Invocation {
pub id: String,
pub target: String,
Expand Down Expand Up @@ -152,6 +161,15 @@ impl<'de> DeserializeAs<'de, ServiceType> for DatafusionServiceType {
}
}

impl SerializeAs<ServiceType> for DatafusionServiceType {
fn serialize_as<S>(source: &ServiceType, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
source.serialize(serializer)
}
}

impl FromStr for DatafusionServiceType {
type Err = String;

Expand Down
77 changes: 61 additions & 16 deletions cli/src/commands/invocations/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ use cling::prelude::*;
use indicatif::ProgressBar;
use itertools::Itertools;

use restate_cli_util::c_eprintln;
use restate_cli_util::ui::console::Styled;
use restate_cli_util::ui::console::{Styled, StyledTable};
use restate_cli_util::ui::stylesheet::Style;
use restate_cli_util::ui::watcher::Watch;
use restate_cli_util::{CliContext, OutputFormat, c_eprintln, c_println};

use crate::cli_env::CliEnv;
use crate::clients::datafusion_helpers::{InvocationState, find_and_count_active_invocations};
Expand Down Expand Up @@ -191,24 +191,69 @@ async fn list(env: &CliEnv, opts: &List) -> Result<()> {
find_and_count_active_invocations(&sql_client, &active_filter_str, order_by, opts.limit)
.await?;

// Render Output UI
progress.finish_and_clear();

// Sample of active invocations
if !results.is_empty() {
// Truncate the output to fit the requested limit
results.truncate(opts.limit);
for inv in &results {
render_invocation_compact(inv);
match CliContext::get().output_format() {
OutputFormat::Human => {
// Sample of active invocations
if !results.is_empty() {
// Truncate the output to fit the requested limit
results.truncate(opts.limit);
for inv in &results {
render_invocation_compact(inv);
}
}
c_eprintln!(
"Showing {}/{} invocations. Query took {:?}",
results.len(),
count_estimate,
Styled(Style::Notice, start_time.elapsed())
);
}
OutputFormat::Table => {
if !results.is_empty() {
results.truncate(opts.limit);
let mut table = comfy_table::Table::new_styled();
table.set_styled_header(vec!["ID", "TARGET", "STATUS", "DEPLOYMENT", "CREATED AT"]);
for inv in &results {
let deployment = inv
.pinned_deployment_id
.as_deref()
.or(inv.last_attempt_deployment_id.as_deref())
.unwrap_or("-");
table.add_row(vec![
inv.id.as_str(),
inv.target.as_str(),
&inv.status.to_string(),
deployment,
&inv.created_at.to_rfc3339(),
]);
}
c_println!("{}", table);
}
c_eprintln!(
"Showing {}/{} invocations. Query took {:?}",
results.len(),
count_estimate,
Styled(Style::Notice, start_time.elapsed())
);
}
OutputFormat::Json => {
results.truncate(opts.limit);
serde_json::to_writer(std::io::stdout(), &results)?;
println!();
}
OutputFormat::Jsonl => {
results.truncate(opts.limit);
let stdout = std::io::stdout();
let mut out = stdout.lock();
for inv in &results {
serde_json::to_writer(&mut out, inv)?;
use std::io::Write;
writeln!(out)?;
}
}
}

c_eprintln!(
"Showing {}/{} invocations. Query took {:?}",
results.len(),
count_estimate,
Styled(Style::Notice, start_time.elapsed())
);

Ok(())
}
1 change: 1 addition & 0 deletions crates/cli-util/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,5 +280,6 @@ These options are available to all commands via `CommonOpts`:
| `-y`, `--yes` | Auto-confirm prompts |
| `--table-style` | `compact` (default) or `borders` |
| `--time-format` | `human` (default), `iso8601`, or `rfc2822` |
| `--output` | `human` (default), `table`, `json`, or `jsonl` |
| `--connect-timeout` | Connection timeout in ms |
| `--request-timeout` | Request timeout in ms |
9 changes: 8 additions & 1 deletion crates/cli-util/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ use dotenvy::dotenv;
use tracing::{info, warn};
use tracing_log::AsTrace;

use crate::opts::{CommonOpts, ConfirmMode, NetworkOpts, TableStyle, TimeFormat, UiOpts};
use crate::opts::{
CommonOpts, ConfirmMode, NetworkOpts, OutputFormat, TableStyle, TimeFormat, UiOpts,
};
use crate::os_env::OsEnv;

static GLOBAL_CLI_CONTEXT: OnceLock<ArcSwap<CliContext>> = OnceLock::new();
Expand Down Expand Up @@ -229,6 +231,11 @@ impl CliContext {
self.ui.time_format
}

/// Get the user's preferred output format for list and describe commands.
pub fn output_format(&self) -> OutputFormat {
self.ui.output
}

/// Whether colors and styling should be used in output.
///
/// This is determined by color detection at context creation time.
Expand Down
2 changes: 1 addition & 1 deletion crates/cli-util/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ mod os_env;
pub mod ui;

pub use context::CliContext;
pub use opts::CommonOpts;
pub use opts::{CommonOpts, OutputFormat};
pub use os_env::OsEnv;

// Re-export comfy-table for console c_* macros (used internally by macros)
Expand Down
18 changes: 18 additions & 0 deletions crates/cli-util/src/opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@ pub enum TimeFormat {
Rfc2822,
}

#[derive(ValueEnum, Clone, Copy, Eq, PartialEq, Default, Debug)]
#[clap(rename_all = "kebab-case")]
pub enum OutputFormat {
/// Human-friendly output with colors and icons (default)
#[default]
Human,
/// Plain table suitable for shell pipelines
Table,
/// JSON array
Json,
/// Newline-delimited JSON
Jsonl,
}

/// Silent (no) logging by default in CLI
#[derive(Clone, Default)]
pub(crate) struct Quiet;
Expand Down Expand Up @@ -73,6 +87,10 @@ pub(crate) struct UiOpts {

#[arg(long, default_value = "human", global = true)]
pub time_format: TimeFormat,

/// Output format for list and describe commands
#[arg(long, default_value = "human", global = true)]
pub output: OutputFormat,
}

#[derive(Args, Clone, Default)]
Expand Down
33 changes: 33 additions & 0 deletions release-notes/unreleased/3872-cli-output-flag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Release Notes for Issue #3872: Universal --output flag for list commands

## New Feature

### What Changed

`restate inv list` (and `restate inv ls`) now supports a `--output` flag for
machine-readable output, making it easy to pipe invocation IDs into other commands.

```
--output <FORMAT> human (default) | table | json | jsonl
```

- **`human`** — existing pretty output with colors and icons (unchanged default)
- **`table`** — plain columns: ID, TARGET, STATUS, DEPLOYMENT, CREATED AT
- **`json`** — JSON array of all results
- **`jsonl`** — one JSON object per line (newline-delimited JSON)

### Examples

```bash
# Cancel all backing-off invocations
restate inv list --status backing-off --output jsonl | jq -r .id | xargs restate inv cancel

# Pipe into grep then cancel
restate inv list --output table | grep MyService | awk '{print $1}' | xargs restate inv cancel

# Pretty-print full JSON
restate inv list --all --output json | jq .
```

### Related Issues
- Issue #3872: Universal --output option for all get/list/describe commands
Loading