Skip to content

Commit 3832f56

Browse files
committed
feat: adds status-table example
1 parent 4ca5b46 commit 3832f56

File tree

7 files changed

+187
-6
lines changed

7 files changed

+187
-6
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ required-features = ["client", "serde"]
5353
name = "service-info"
5454
required-features = ["client", "serde"]
5555

56+
[[example]]
57+
name = "status-table"
58+
required-features = ["client", "serde"]
59+
5660
[[example]]
5761
name = "task-get"
5862
required-features = ["client", "serde"]

examples/service-info.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ async fn main() -> Result<()> {
4949
if let Some(username) = username {
5050
let credentials = format!("{}:{}", username, password.unwrap());
5151
let encoded = BASE64_STANDARD.encode(credentials);
52-
builder = builder.insert_header("Authorization", format!("Basic {}", encoded));
52+
builder = builder.insert_header("Authorization", format!("Basic {encoded}"));
5353
}
5454

5555
let client = builder.try_build().expect("could not build client");

examples/status-table.rs

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
//! Lists the status of each task as a summary.
2+
//!
3+
//! You can run this with the following command:
4+
//!
5+
//! ```bash
6+
//! export USER="<USER>"
7+
//! export PASSWORD="<PASSWORD>"
8+
//! export RUST_LOG="tes=debug"
9+
//!
10+
//! cargo run --release --features=client,serde --example status-table <URL>
11+
//! ```
12+
13+
use std::collections::HashMap;
14+
15+
use base64::prelude::*;
16+
use miette::Context as _;
17+
use miette::IntoDiagnostic;
18+
use miette::Result;
19+
use miette::bail;
20+
use tes::v1::client::Client;
21+
use tes::v1::types::requests::ListTasksParams;
22+
use tes::v1::types::requests::View;
23+
use tes::v1::types::task::State;
24+
use tokio_retry2::strategy::ExponentialFactorBackoff;
25+
use tokio_retry2::strategy::MaxInterval as _;
26+
use tracing_subscriber::EnvFilter;
27+
28+
/// The environment variable for a basic auth username.
29+
const USER_ENV: &str = "USER";
30+
31+
/// The environment variable for a basic auth password.
32+
const PASSWORD_ENV: &str = "PASSWORD";
33+
34+
/// A displayable version of a TES state.
35+
#[derive(Eq, Hash, PartialEq)]
36+
struct DisplayableState(State);
37+
38+
impl DisplayableState {
39+
/// Gets the associated order group for a particular state.
40+
fn ord_group(&self) -> usize {
41+
match self.0 {
42+
State::Unknown => 0,
43+
State::Queued | State::Initializing => 1,
44+
State::Running => 2,
45+
State::Paused => 3,
46+
State::Complete => 4,
47+
State::ExecutorError | State::SystemError => 5,
48+
State::Canceled | State::Canceling => 6,
49+
State::Preempted => 7,
50+
}
51+
}
52+
}
53+
54+
impl std::fmt::Display for DisplayableState {
55+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56+
match self.0 {
57+
State::Unknown => write!(f, "UNKNOWN"),
58+
State::Queued => write!(f, "QUEUED"),
59+
State::Initializing => write!(f, "INITIALIZING"),
60+
State::Running => write!(f, "RUNNING"),
61+
State::Paused => write!(f, "PAUSED"),
62+
State::Complete => write!(f, "COMPLETE"),
63+
State::ExecutorError => write!(f, "EXECUTOR_ERROR"),
64+
State::SystemError => write!(f, "SYSTEM_ERROR"),
65+
State::Canceled => write!(f, "CANCELED"),
66+
State::Canceling => write!(f, "CANCELING"),
67+
State::Preempted => write!(f, "PREEMPTED"),
68+
}
69+
}
70+
}
71+
72+
impl std::cmp::Ord for DisplayableState {
73+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
74+
self.ord_group().cmp(&other.ord_group())
75+
}
76+
}
77+
78+
impl std::cmp::PartialOrd for DisplayableState {
79+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
80+
Some(self.cmp(other))
81+
}
82+
}
83+
84+
/// Prints a status table for the tasks in the server.
85+
async fn print_status_table(client: &Client) -> Result<()> {
86+
let mut last_token = None;
87+
88+
let mut states = HashMap::<Option<DisplayableState>, usize>::new();
89+
90+
loop {
91+
let retries = ExponentialFactorBackoff::from_millis(1000, 2.0)
92+
.max_interval(10000)
93+
.take(3);
94+
95+
let response = client
96+
.list_tasks(
97+
Some(&ListTasksParams {
98+
view: Some(View::Minimal),
99+
page_token: last_token,
100+
..Default::default()
101+
}),
102+
retries,
103+
)
104+
.await
105+
.into_diagnostic()
106+
.context("listing tasks")?;
107+
108+
for state in response
109+
.tasks
110+
.into_iter()
111+
.map(|task| task.into_minimal().unwrap().state.map(DisplayableState))
112+
{
113+
*states.entry(state).or_default() += 1;
114+
}
115+
116+
last_token = response.next_page_token;
117+
if last_token.is_none() {
118+
break;
119+
}
120+
}
121+
122+
let mut states = states.into_iter().collect::<Vec<_>>();
123+
states.sort();
124+
125+
println!("+--------------------+-----------+");
126+
println!("| State | Count | ");
127+
println!("+--------------------+-----------+");
128+
for (state, count) in states {
129+
println!(
130+
"| {state: <18} | {count: >9} |",
131+
state = state
132+
.map(|state| state.to_string())
133+
.unwrap_or(String::from("<unknown>")),
134+
count = count
135+
);
136+
}
137+
println!("+--------------------+-----------+");
138+
139+
Ok(())
140+
}
141+
142+
#[tokio::main]
143+
async fn main() -> Result<()> {
144+
tracing_subscriber::fmt()
145+
.with_env_filter(EnvFilter::from_default_env())
146+
.init();
147+
148+
let url = std::env::args()
149+
.nth(1)
150+
.context("URL argument is required")?;
151+
152+
let mut builder = Client::builder()
153+
.url_from_string(url)
154+
.into_diagnostic()
155+
.context("URL could not be parsed")?;
156+
157+
let username = std::env::var(USER_ENV).ok();
158+
let password = std::env::var(PASSWORD_ENV).ok();
159+
160+
if (username.is_some() && password.is_none()) || (username.is_none() && password.is_some()) {
161+
bail!("${USER_ENV} and ${PASSWORD_ENV} must both be set to use basic auth");
162+
}
163+
164+
if let Some(username) = username {
165+
let credentials = format!("{}:{}", username, password.unwrap());
166+
let encoded = BASE64_STANDARD.encode(credentials);
167+
builder = builder.insert_header("Authorization", format!("Basic {encoded}"));
168+
}
169+
170+
let client = builder
171+
.try_build()
172+
.into_diagnostic()
173+
.context("failed to build TES client")?;
174+
print_status_table(&client).await?;
175+
176+
Ok(())
177+
}

examples/task-get.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ async fn main() -> Result<()> {
5050
if let Some(username) = username {
5151
let credentials = format!("{}:{}", username, password.unwrap());
5252
let encoded = BASE64_STANDARD.encode(credentials);
53-
builder = builder.insert_header("Authorization", format!("Basic {}", encoded));
53+
builder = builder.insert_header("Authorization", format!("Basic {encoded}"));
5454
}
5555

5656
let client = builder.try_build().expect("could not build client");

examples/task-list-all.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ async fn list_all_tasks(client: &Client) -> Result<()> {
4949
.into_diagnostic()
5050
.context("listing tasks")?;
5151

52-
println!("{:#?}", response);
52+
println!("{response:#?}");
5353

5454
last_token = response.next_page_token;
5555
if last_token.is_none() {
@@ -82,7 +82,7 @@ async fn main() -> Result<()> {
8282
if let Some(username) = username {
8383
let credentials = format!("{}:{}", username, password.unwrap());
8484
let encoded = BASE64_STANDARD.encode(credentials);
85-
builder = builder.insert_header("Authorization", format!("Basic {}", encoded));
85+
builder = builder.insert_header("Authorization", format!("Basic {encoded}"));
8686
}
8787

8888
let client = builder.try_build().expect("could not build client");

examples/task-submit.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ async fn main() -> Result<()> {
5050
if let Some(username) = username {
5151
let credentials = format!("{}:{}", username, password.unwrap());
5252
let encoded = BASE64_STANDARD.encode(credentials);
53-
builder = builder.insert_header("Authorization", format!("Basic {}", encoded));
53+
builder = builder.insert_header("Authorization", format!("Basic {encoded}"));
5454
}
5555

5656
let client = builder.try_build().expect("could not build client");

src/v1/types/task.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub enum IoType {
1616
}
1717

1818
/// Task state as defined by the server.
19-
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
19+
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)]
2020
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2121
#[cfg_attr(feature = "serde", serde(rename_all = "SCREAMING_SNAKE_CASE"))]
2222
pub enum State {

0 commit comments

Comments
 (0)