diff --git a/Cargo.lock b/Cargo.lock index 6600071d4..7ab87ad4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3212,6 +3212,7 @@ dependencies = [ "serde_json", "serde_yaml", "shutdown_hooks", + "snafu", "strum", "strum_macros", "tokio", diff --git a/control-plane/plugin/Cargo.toml b/control-plane/plugin/Cargo.toml index fb0573ab6..44bdfcfd8 100644 --- a/control-plane/plugin/Cargo.toml +++ b/control-plane/plugin/Cargo.toml @@ -35,6 +35,7 @@ serde_json = "1.0.107" serde_yaml = "0.9.25" humantime = "2.1.0" chrono = "0.4.31" +snafu = "0.7.5" [dev-dependencies] # Test dependencies diff --git a/control-plane/plugin/src/bin/rest-plugin/main.rs b/control-plane/plugin/src/bin/rest-plugin/main.rs index 8790bb4cd..b71e5cfeb 100644 --- a/control-plane/plugin/src/bin/rest-plugin/main.rs +++ b/control-plane/plugin/src/bin/rest-plugin/main.rs @@ -58,7 +58,7 @@ async fn execute(cli_args: CliArgs) { } // Perform the operations based on the subcommand, with proper output format. - match &cli_args.operations { + let result = match &cli_args.operations { Operations::Drain(resource) => match resource { DrainResources::Node(drain_node_args) => { node::Node::drain( @@ -133,4 +133,9 @@ async fn execute(cli_args: CliArgs) { } }, }; + + if let Err(error) = result { + eprintln!("{error}"); + std::process::exit(1); + }; } diff --git a/control-plane/plugin/src/operations.rs b/control-plane/plugin/src/operations.rs index 4e73586bd..7ef61b0cd 100644 --- a/control-plane/plugin/src/operations.rs +++ b/control-plane/plugin/src/operations.rs @@ -1,6 +1,11 @@ -use crate::resources::{utils, CordonResources, DrainResources, GetResources, ScaleResources}; +use crate::resources::{ + error::Error, utils, CordonResources, DrainResources, GetResources, ScaleResources, +}; use async_trait::async_trait; +/// Result wrapper for plugin commands. +pub type PluginResult = Result<(), Error>; + /// The types of operations that are supported. #[derive(clap::Subcommand, Debug)] pub enum Operations { @@ -31,14 +36,14 @@ pub trait Drain { label: String, drain_timeout: Option, output: &utils::OutputFormat, - ); + ) -> PluginResult; } /// List trait. /// To be implemented by resources which support the 'list' operation. #[async_trait(?Send)] pub trait List { - async fn list(output: &utils::OutputFormat); + async fn list(output: &utils::OutputFormat) -> PluginResult; } /// List trait. @@ -46,7 +51,7 @@ pub trait List { #[async_trait(?Send)] pub trait ListExt { type Context; - async fn list(output: &utils::OutputFormat, context: &Self::Context); + async fn list(output: &utils::OutputFormat, context: &Self::Context) -> PluginResult; } /// Get trait. @@ -54,7 +59,7 @@ pub trait ListExt { #[async_trait(?Send)] pub trait Get { type ID; - async fn get(id: &Self::ID, output: &utils::OutputFormat); + async fn get(id: &Self::ID, output: &utils::OutputFormat) -> PluginResult; } /// Scale trait. @@ -62,7 +67,7 @@ pub trait Get { #[async_trait(?Send)] pub trait Scale { type ID; - async fn scale(id: &Self::ID, replica_count: u8, output: &utils::OutputFormat); + async fn scale(id: &Self::ID, replica_count: u8, output: &utils::OutputFormat) -> PluginResult; } /// Replica topology trait. @@ -71,8 +76,8 @@ pub trait Scale { pub trait ReplicaTopology { type ID; type Context; - async fn topologies(output: &utils::OutputFormat, context: &Self::Context); - async fn topology(id: &Self::ID, output: &utils::OutputFormat); + async fn topologies(output: &utils::OutputFormat, context: &Self::Context) -> PluginResult; + async fn topology(id: &Self::ID, output: &utils::OutputFormat) -> PluginResult; } /// Rebuild trait. @@ -80,7 +85,7 @@ pub trait ReplicaTopology { #[async_trait(?Send)] pub trait RebuildHistory { type ID; - async fn rebuild_history(id: &Self::ID, output: &utils::OutputFormat); + async fn rebuild_history(id: &Self::ID, output: &utils::OutputFormat) -> PluginResult; } /// GetBlockDevices trait. @@ -88,7 +93,11 @@ pub trait RebuildHistory { #[async_trait(?Send)] pub trait GetBlockDevices { type ID; - async fn get_blockdevices(id: &Self::ID, all: &bool, output: &utils::OutputFormat); + async fn get_blockdevices( + id: &Self::ID, + all: &bool, + output: &utils::OutputFormat, + ) -> PluginResult; } /// GetSnapshots trait. @@ -103,7 +112,7 @@ pub trait GetSnapshots { volid: &Self::SourceID, snapid: &Self::ResourceID, output: &utils::OutputFormat, - ); + ) -> PluginResult; } /// Cordon trait. @@ -111,6 +120,6 @@ pub trait GetSnapshots { #[async_trait(?Send)] pub trait Cordoning { type ID; - async fn cordon(id: &Self::ID, label: &str, output: &utils::OutputFormat); - async fn uncordon(id: &Self::ID, label: &str, output: &utils::OutputFormat); + async fn cordon(id: &Self::ID, label: &str, output: &utils::OutputFormat) -> PluginResult; + async fn uncordon(id: &Self::ID, label: &str, output: &utils::OutputFormat) -> PluginResult; } diff --git a/control-plane/plugin/src/resources/blockdevice.rs b/control-plane/plugin/src/resources/blockdevice.rs index 3cb2afa79..061137954 100644 --- a/control-plane/plugin/src/resources/blockdevice.rs +++ b/control-plane/plugin/src/resources/blockdevice.rs @@ -1,6 +1,7 @@ use crate::{ operations::GetBlockDevices, resources::{ + error::Error, utils::{ optional_cell, print_table, CreateRow, GetHeaderRow, OutputFormat, BLOCKDEVICE_HEADERS_ALL, BLOCKDEVICE_HEADERS_USABLE, @@ -124,7 +125,11 @@ impl GetHeaderRow for BlockDeviceUsable { #[async_trait(?Send)] impl GetBlockDevices for BlockDevice { type ID = NodeId; - async fn get_blockdevices(id: &Self::ID, all: &bool, output: &OutputFormat) { + async fn get_blockdevices( + id: &Self::ID, + all: &bool, + output: &OutputFormat, + ) -> Result<(), Error> { let mut used_disks: Vec = vec![]; match RestClient::client().pools_api().get_node_pools(id).await { Ok(pools) => { @@ -140,8 +145,10 @@ impl GetBlockDevices for BlockDevice { } } Err(e) => { - println!("Failed to list blockdevices for node {id} . Error {e}"); - return; + return Err(Error::GetBlockDevicesError { + id: id.to_string(), + source: e, + }); } } @@ -169,9 +176,13 @@ impl GetBlockDevices for BlockDevice { }; } Err(e) => { - println!("Failed to list blockdevices for node {id} . Error {e}") + return Err(Error::GetBlockDevicesError { + id: id.to_string(), + source: e, + }); } } + Ok(()) } } diff --git a/control-plane/plugin/src/resources/cordon.rs b/control-plane/plugin/src/resources/cordon.rs index 594c76128..fe8638af4 100644 --- a/control-plane/plugin/src/resources/cordon.rs +++ b/control-plane/plugin/src/resources/cordon.rs @@ -5,8 +5,9 @@ use async_trait::async_trait; use openapi::models::CordonDrainState; use crate::{ - operations::{Get, List}, + operations::{Get, List, PluginResult}, resources::{ + error::Error, node::{node_display_print, node_display_print_one, NodeDisplayFormat}, utils::OutputFormat, NodeId, @@ -17,21 +18,25 @@ use crate::{ #[async_trait(?Send)] impl Get for NodeCordon { type ID = NodeId; - async fn get(id: &Self::ID, output: &OutputFormat) { + async fn get(id: &Self::ID, output: &OutputFormat) -> PluginResult { match RestClient::client().nodes_api().get_node(id).await { Ok(node) => { node_display_print_one(node.into_body(), output, NodeDisplayFormat::CordonLabels) } Err(e) => { - println!("Failed to get node {id}. Error {e}") + return Err(Error::GetNodeError { + id: id.to_string(), + source: e, + }); } } + Ok(()) } } #[async_trait(?Send)] impl List for NodeCordons { - async fn list(output: &OutputFormat) { + async fn list(output: &OutputFormat) -> PluginResult { match RestClient::client().nodes_api().get_nodes(None).await { Ok(nodes) => { // iterate through the nodes and filter for only those that have cordon or drain @@ -53,8 +58,9 @@ impl List for NodeCordons { node_display_print(filteredlist, output, NodeDisplayFormat::CordonLabels) } Err(e) => { - println!("Failed to list nodes. Error {e}") + return Err(Error::ListNodesError { source: e }); } } + Ok(()) } } diff --git a/control-plane/plugin/src/resources/drain.rs b/control-plane/plugin/src/resources/drain.rs index 4836e2205..a08593fa9 100644 --- a/control-plane/plugin/src/resources/drain.rs +++ b/control-plane/plugin/src/resources/drain.rs @@ -5,8 +5,9 @@ use async_trait::async_trait; use openapi::models::CordonDrainState; use crate::{ - operations::{Get, List}, + operations::{Get, List, PluginResult}, resources::{ + error::Error, node::{node_display_print, node_display_print_one, NodeDisplayFormat}, utils::OutputFormat, NodeId, @@ -17,19 +18,23 @@ use crate::{ #[async_trait(?Send)] impl Get for NodeDrain { type ID = NodeId; - async fn get(id: &Self::ID, output: &OutputFormat) { + async fn get(id: &Self::ID, output: &OutputFormat) -> PluginResult { match RestClient::client().nodes_api().get_node(id).await { Ok(node) => node_display_print_one(node.into_body(), output, NodeDisplayFormat::Drain), Err(e) => { - println!("Failed to get node {id}. Error {e}") + return Err(Error::GetNodeError { + id: id.to_string(), + source: e, + }); } } + Ok(()) } } #[async_trait(?Send)] impl List for NodeDrains { - async fn list(output: &OutputFormat) { + async fn list(output: &OutputFormat) -> PluginResult { match RestClient::client().nodes_api().get_nodes(None).await { Ok(nodes) => { // iterate through the nodes and filter for only those that have drain labels @@ -51,8 +56,9 @@ impl List for NodeDrains { node_display_print(filteredlist, output, NodeDisplayFormat::Drain); } Err(e) => { - println!("Failed to list nodes. Error {e}") + return Err(Error::ListNodesError { source: e }); } } + Ok(()) } } diff --git a/control-plane/plugin/src/resources/error.rs b/control-plane/plugin/src/resources/error.rs new file mode 100644 index 000000000..6add2fc5b --- /dev/null +++ b/control-plane/plugin/src/resources/error.rs @@ -0,0 +1,77 @@ +use snafu::Snafu; + +/// All errors returned when plugin command fails +#[derive(Debug, Snafu)] +#[snafu(visibility(pub))] +#[allow(clippy::enum_variant_names)] +pub enum Error { + /// Error when listing block devices fails. + #[snafu(display("Failed to list blockdevices for node {id} . Error {source}"))] + GetBlockDevicesError { + id: String, + source: openapi::tower::client::Error, + }, + /// Error when get node request fails. + #[snafu(display("Failed to get node {id}. Error {source}"))] + GetNodeError { + id: String, + source: openapi::tower::client::Error, + }, + /// Error when node cordon request fails. + #[snafu(display("Failed to get node {id}. Error {source}"))] + NodeCordonError { + id: String, + source: openapi::tower::client::Error, + }, + /// Error when node uncordon request fails. + #[snafu(display("Failed to uncordon node {id}. Error {source}"))] + NodeUncordonError { + id: String, + source: openapi::tower::client::Error, + }, + /// Error when put node drain request fails. + #[snafu(display("Failed to put node drain {id}. Error {source}"))] + PutNodeDrainError { + id: String, + source: openapi::tower::client::Error, + }, + /// Error when list nodes request fails. + #[snafu(display("Failed to list nodes. Error {source}"))] + ListNodesError { + source: openapi::tower::client::Error, + }, + /// Error when get pool request fails. + #[snafu(display("Failed to get pool {id}. Error {source}"))] + GetPoolError { + id: String, + source: openapi::tower::client::Error, + }, + /// Error when list pools request fails. + #[snafu(display("Failed to list pools. Error {source}"))] + ListPoolsError { + source: openapi::tower::client::Error, + }, + /// Error when get volume request fails. + #[snafu(display("Failed to get volume {id}. Error {source}"))] + GetVolumeError { + id: String, + source: openapi::tower::client::Error, + }, + /// Error when get rebuild history for volume request fails. + #[snafu(display("Failed to get rebuild history for volume {id}. Error {source}"))] + GetRebuildHistory { + id: String, + source: openapi::tower::client::Error, + }, + /// Error when scale volume request fails. + #[snafu(display("Failed to scale volume {id}. Error {source}"))] + ScaleVolumeError { + id: String, + source: openapi::tower::client::Error, + }, + /// Error when list snapshots request fails. + #[snafu(display("Failed to list volume snapshots. Error {source}"))] + ListSnapshotsError { + source: openapi::tower::client::Error, + }, +} diff --git a/control-plane/plugin/src/resources/mod.rs b/control-plane/plugin/src/resources/mod.rs index 5784629dd..f811de54a 100644 --- a/control-plane/plugin/src/resources/mod.rs +++ b/control-plane/plugin/src/resources/mod.rs @@ -8,6 +8,7 @@ use crate::resources::{ pub mod blockdevice; pub mod cordon; pub mod drain; +pub mod error; pub mod node; pub mod pool; pub mod snapshot; diff --git a/control-plane/plugin/src/resources/node.rs b/control-plane/plugin/src/resources/node.rs index e9d6452ef..8b828f2bc 100644 --- a/control-plane/plugin/src/resources/node.rs +++ b/control-plane/plugin/src/resources/node.rs @@ -1,6 +1,7 @@ use crate::{ - operations::{Cordoning, Drain, Get, List}, + operations::{Cordoning, Drain, Get, List, PluginResult}, resources::{ + error::Error, utils, utils::{print_table, CreateRow, CreateRows, GetHeaderRow, OutputFormat}, NodeId, @@ -89,16 +90,17 @@ impl GetHeaderRow for openapi::models::Node { #[async_trait(?Send)] impl List for Nodes { - async fn list(output: &utils::OutputFormat) { + async fn list(output: &utils::OutputFormat) -> PluginResult { match RestClient::client().nodes_api().get_nodes(None).await { Ok(nodes) => { // Print table, json or yaml based on output format. utils::print_table(output, nodes.into_body()); } Err(e) => { - println!("Failed to list nodes. Error {e}") + return Err(Error::ListNodesError { source: e }); } } + Ok(()) } } @@ -109,16 +111,20 @@ pub struct Node {} #[async_trait(?Send)] impl Get for Node { type ID = NodeId; - async fn get(id: &Self::ID, output: &utils::OutputFormat) { + async fn get(id: &Self::ID, output: &utils::OutputFormat) -> PluginResult { match RestClient::client().nodes_api().get_node(id).await { Ok(node) => { // Print table, json or yaml based on output format. utils::print_table(output, node.into_body()); } Err(e) => { - println!("Failed to get node {id}. Error {e}") + return Err(Error::GetNodeError { + id: id.to_string(), + source: e, + }); } } + Ok(()) } } @@ -142,7 +148,7 @@ fn drain_labels_from_state(ds: &CordonDrainState) -> Vec { #[async_trait(?Send)] impl Cordoning for Node { type ID = NodeId; - async fn cordon(id: &Self::ID, label: &str, output: &OutputFormat) { + async fn cordon(id: &Self::ID, label: &str, output: &OutputFormat) -> PluginResult { // is node already cordoned with the label? let already_has_cordon_label: bool = match RestClient::client().nodes_api().get_node(id).await { @@ -155,13 +161,15 @@ impl Cordoning for Node { }, None => { println!("Node {id} is not registered"); - return; + return Ok(()); } } } Err(e) => { - println!("Failed to get node {id}. Error {e}"); - return; + return Err(Error::GetNodeError { + id: id.to_string(), + source: e, + }); } }; let result = match already_has_cordon_label { @@ -185,12 +193,16 @@ impl Cordoning for Node { } }, Err(e) => { - println!("Failed to cordon node {id}. Error {e}") + return Err(Error::NodeCordonError { + id: id.to_string(), + source: e, + }); } } + Ok(()) } - async fn uncordon(id: &Self::ID, label: &str, output: &OutputFormat) { + async fn uncordon(id: &Self::ID, label: &str, output: &OutputFormat) -> PluginResult { match RestClient::client() .nodes_api() .delete_node_cordon(id, label) @@ -228,9 +240,13 @@ impl Cordoning for Node { } }, Err(e) => { - println!("Failed to uncordon node {id}. Error {e}") + return Err(Error::NodeUncordonError { + id: id.to_string(), + source: e, + }); } } + Ok(()) } } @@ -427,7 +443,7 @@ impl Drain for Node { label: String, drain_timeout: Option, output: &utils::OutputFormat, - ) { + ) -> PluginResult { let mut timeout_instant: Option = None; if let Some(dt) = drain_timeout { timeout_instant = time::Instant::now().checked_add(dt.into()); @@ -443,13 +459,15 @@ impl Drain for Node { }, None => { println!("Node {id} is not registered"); - return; + return Ok(()); } } } Err(e) => { - println!("Failed to get node {id}. Error {e}"); - return; + return Err(Error::GetNodeError { + id: id.to_string(), + source: e, + }); } }; if !already_has_drain_label { @@ -458,8 +476,10 @@ impl Drain for Node { .put_node_drain(id, &label) .await { - println!("Failed to put node drain {id}. Error {error}"); - return; + return Err(Error::PutNodeDrainError { + id: id.to_string(), + source: error, + }); } } // loop this call until no longer draining @@ -518,8 +538,10 @@ impl Drain for Node { } } Err(e) => { - println!("Failed to get node {id}. Error {e}"); - break; + return Err(Error::GetNodeError { + id: id.to_string(), + source: e, + }); } } if timeout_instant.is_some() && time::Instant::now() > timeout_instant.unwrap() { @@ -529,5 +551,6 @@ impl Drain for Node { let sleep = Duration::from_secs(2); tokio::time::sleep(sleep).await; } + Ok(()) } } diff --git a/control-plane/plugin/src/resources/pool.rs b/control-plane/plugin/src/resources/pool.rs index cddadf74e..60c8cacdf 100644 --- a/control-plane/plugin/src/resources/pool.rs +++ b/control-plane/plugin/src/resources/pool.rs @@ -1,6 +1,7 @@ use crate::{ - operations::{Get, List}, + operations::{Get, List, PluginResult}, resources::{ + error::Error, utils, utils::{CreateRow, GetHeaderRow}, PoolId, @@ -61,16 +62,17 @@ impl GetHeaderRow for openapi::models::Pool { #[async_trait(?Send)] impl List for Pools { - async fn list(output: &utils::OutputFormat) { + async fn list(output: &utils::OutputFormat) -> PluginResult { match RestClient::client().pools_api().get_pools().await { Ok(pools) => { // Print table, json or yaml based on output format. utils::print_table(output, pools.into_body()); } Err(e) => { - println!("Failed to list pools. Error {e}") + return Err(Error::ListPoolsError { source: e }); } } + Ok(()) } } @@ -81,15 +83,19 @@ pub struct Pool {} #[async_trait(?Send)] impl Get for Pool { type ID = PoolId; - async fn get(id: &Self::ID, output: &utils::OutputFormat) { + async fn get(id: &Self::ID, output: &utils::OutputFormat) -> PluginResult { match RestClient::client().pools_api().get_pool(id).await { Ok(pool) => { // Print table, json or yaml based on output format. utils::print_table(output, pool.into_body()); } Err(e) => { - println!("Failed to get pool {id}. Error {e}") + return Err(Error::GetPoolError { + id: id.to_string(), + source: e, + }); } } + Ok(()) } } diff --git a/control-plane/plugin/src/resources/snapshot.rs b/control-plane/plugin/src/resources/snapshot.rs index 328b6d0c8..5ba52026c 100644 --- a/control-plane/plugin/src/resources/snapshot.rs +++ b/control-plane/plugin/src/resources/snapshot.rs @@ -1,6 +1,6 @@ use crate::{ - operations::GetSnapshots, - resources::{utils, utils::optional_cell, SnapshotId, VolumeId}, + operations::{GetSnapshots, PluginResult}, + resources::{error::Error, utils, utils::optional_cell, SnapshotId, VolumeId}, rest_wrapper::RestClient, }; use async_trait::async_trait; @@ -70,11 +70,15 @@ impl GetSnapshots for VolumeSnapshots { volid: &Self::SourceID, snapid: &Self::ResourceID, output: &utils::OutputFormat, - ) { - if let Some(snapshots) = get_snapshots(volid, snapid).await { - // Print table, json or yaml based on output format. - utils::print_table(output, snapshots); + ) -> PluginResult { + match get_snapshots(volid, snapid).await { + Ok(snapshots) => { + // Print table, json or yaml based on output format. + utils::print_table(output, snapshots); + } + Err(e) => return Err(e), } + Ok(()) } } @@ -87,7 +91,7 @@ impl GetHeaderRow for openapi::models::VolumeSnapshot { async fn get_snapshots( volid: &Option, snapid: &Option, -) -> Option> { +) -> Result, Error> { let max_entries = 100; let mut starting_token = Some(0); let mut snapshots = Vec::with_capacity(max_entries as usize); @@ -105,11 +109,10 @@ async fn get_snapshots( starting_token = s.next_token; } Err(e) => { - println!("Failed to list volume snapshots. Error {e}"); - return None; + return Err(Error::ListSnapshotsError { source: e }); } } } - Some(snapshots) + Ok(snapshots) } diff --git a/control-plane/plugin/src/resources/volume.rs b/control-plane/plugin/src/resources/volume.rs index 6d78b367b..8bc9cf1ac 100644 --- a/control-plane/plugin/src/resources/volume.rs +++ b/control-plane/plugin/src/resources/volume.rs @@ -1,6 +1,7 @@ use crate::{ - operations::{Get, ListExt, RebuildHistory, ReplicaTopology, Scale}, + operations::{Get, ListExt, PluginResult, RebuildHistory, ReplicaTopology, Scale}, resources::{ + error::Error, utils, utils::{optional_cell, CreateRow, CreateRows, GetHeaderRow, OutputFormat}, VolumeId, @@ -81,11 +82,12 @@ impl GetHeaderRow for openapi::models::Volume { #[async_trait(?Send)] impl ListExt for Volumes { type Context = VolumesArgs; - async fn list(output: &OutputFormat, context: &Self::Context) { + async fn list(output: &OutputFormat, context: &Self::Context) -> PluginResult { if let Some(volumes) = get_paginated_volumes(context).await { // Print table, json or yaml based on output format. utils::print_table(output, volumes); - } + }; + Ok(()) } } @@ -143,23 +145,27 @@ pub struct Volume {} #[async_trait(?Send)] impl Get for Volume { type ID = VolumeId; - async fn get(id: &Self::ID, output: &utils::OutputFormat) { + async fn get(id: &Self::ID, output: &utils::OutputFormat) -> PluginResult { match RestClient::client().volumes_api().get_volume(id).await { Ok(volume) => { // Print table, json or yaml based on output format. utils::print_table(output, volume.into_body()); } Err(e) => { - println!("Failed to get volume {id}. Error {e}") + return Err(Error::GetVolumeError { + id: id.to_string(), + source: e, + }); } } + Ok(()) } } #[async_trait(?Send)] impl Scale for Volume { type ID = VolumeId; - async fn scale(id: &Self::ID, replica_count: u8, output: &utils::OutputFormat) { + async fn scale(id: &Self::ID, replica_count: u8, output: &utils::OutputFormat) -> PluginResult { match RestClient::client() .volumes_api() .put_volume_replica_count(id, replica_count) @@ -176,9 +182,13 @@ impl Scale for Volume { } }, Err(e) => { - println!("Failed to scale volume {id}. Error {e}") + return Err(Error::ScaleVolumeError { + id: id.to_string(), + source: e, + }); } } + Ok(()) } } @@ -186,27 +196,32 @@ impl Scale for Volume { impl ReplicaTopology for Volume { type ID = VolumeId; type Context = VolumesArgs; - async fn topologies(output: &OutputFormat, context: &Self::Context) { + async fn topologies(output: &OutputFormat, context: &Self::Context) -> PluginResult { let volumes = VolumeTopologies(get_paginated_volumes(context).await.unwrap_or_default()); utils::print_table(output, volumes); + Ok(()) } - async fn topology(id: &Self::ID, output: &OutputFormat) { + async fn topology(id: &Self::ID, output: &OutputFormat) -> PluginResult { match RestClient::client().volumes_api().get_volume(id).await { Ok(volume) => { // Print table, json or yaml based on output format. utils::print_table(output, volume.into_body().state.replica_topology); } Err(e) => { - println!("Failed to get volume {id}. Error {e}") + return Err(Error::GetVolumeError { + id: id.to_string(), + source: e, + }); } } + Ok(()) } } #[async_trait(?Send)] impl RebuildHistory for Volume { type ID = VolumeId; - async fn rebuild_history(id: &Self::ID, output: &OutputFormat) { + async fn rebuild_history(id: &Self::ID, output: &OutputFormat) -> PluginResult { match RestClient::client() .volumes_api() .get_rebuild_history(id) @@ -216,9 +231,13 @@ impl RebuildHistory for Volume { utils::print_table(output, history.into_body()); } Err(e) => { - println!("Failed to get rebuild history for volume {id}. Error {e}") + return Err(Error::GetRebuildHistory { + id: id.to_string(), + source: e, + }); } } + Ok(()) } }