summaryrefslogtreecommitdiffhomepage
path: root/tools/unitctl
diff options
context:
space:
mode:
authorAva Hahn <a.hahn@f5.com>2024-07-03 12:44:56 -0700
committerAva Hahn <110854134+avahahn@users.noreply.github.com>2024-07-08 11:58:09 -0700
commit706ea1a6890ac243d0aded8cfbcbd152affc8701 (patch)
treedcdf33182863012e30a8a306dd0e509a070c660a /tools/unitctl
parentb5fe3eaf1a0bcbc98d274902f5e38f2c2ad71dbc (diff)
downloadunit-706ea1a6890ac243d0aded8cfbcbd152affc8701.tar.gz
unit-706ea1a6890ac243d0aded8cfbcbd152affc8701.tar.bz2
tools/unitctl: Enable Multi Socket Support
This commit refactors the CLI code to accept multiple instances of the control socket flag. All subcommands except for edit and save now support being run against multiple specified instances of unitd. * control_socket_addresses CLI field is now a vector * centralize error related logic into the error module * wait_for_socket now returns a vector of sockets. all sockets in vector are waited upon and validated * extraneous code is removed * applications, execute, import, listeners, and status commands all run against N control sockets now * edit and save commands return error when run against a single control socket Signed-off-by: Ava Hahn <a.hahn@f5.com>
Diffstat (limited to 'tools/unitctl')
-rw-r--r--tools/unitctl/README.md33
-rw-r--r--tools/unitctl/unitctl/src/cmd/applications.rs62
-rw-r--r--tools/unitctl/unitctl/src/cmd/edit.rs13
-rw-r--r--tools/unitctl/unitctl/src/cmd/execute.rs25
-rw-r--r--tools/unitctl/unitctl/src/cmd/import.rs12
-rw-r--r--tools/unitctl/unitctl/src/cmd/listeners.rs25
-rw-r--r--tools/unitctl/unitctl/src/cmd/save.rs15
-rw-r--r--tools/unitctl/unitctl/src/cmd/status.rs25
-rw-r--r--tools/unitctl/unitctl/src/main.rs57
-rw-r--r--tools/unitctl/unitctl/src/unitctl.rs2
-rw-r--r--tools/unitctl/unitctl/src/unitctl_error.rs53
-rw-r--r--tools/unitctl/unitctl/src/wait.rs162
12 files changed, 282 insertions, 202 deletions
diff --git a/tools/unitctl/README.md b/tools/unitctl/README.md
index 74366007..4aa6068c 100644
--- a/tools/unitctl/README.md
+++ b/tools/unitctl/README.md
@@ -111,6 +111,14 @@ $ unitctl app reload wasm
}
```
+*Note:* Both of the above commands support operating on multiple instances
+of Unit at once. To do this, pass multiple values for the `-s` flag as
+shown below:
+
+```
+$ unitctl -s '127.0.0.1:8001' -s /run/nginx-unit.control.sock app list
+```
+
### Lists active listeners from running Unit processes
```
unitctl listeners
@@ -122,6 +130,13 @@ No socket path provided - attempting to detect from running instance
}
```
+*Note:* This command supports operating on multiple instances of Unit at once.
+To do this, pass multiple values for the `-s` flag as shown below:
+
+```
+$ unitctl -s '127.0.0.1:8001' -s /run/nginx-unit.control.sock listeners
+```
+
### Get the current status of NGINX Unit processes
```
$ unitctl status -t yaml
@@ -136,6 +151,13 @@ requests:
applications: {}
```
+*Note:* This command supports operating on multiple instances of Unit at once.
+To do this, pass multiple values for the `-s` flag as shown below:
+
+```
+$ unitctl -s '127.0.0.1:8001' -s /run/nginx-unit.control.sock status
+```
+
### Send arbitrary configuration payloads to Unit
```
$ echo '{
@@ -158,6 +180,13 @@ $ echo '{
}
```
+*Note:* This command supports operating on multiple instances of Unit at once.
+To do this, pass multiple values for the `-s` flag as shown below:
+
+```
+$ unitctl -s '127.0.0.1:8001' -s /run/nginx-unit.control.sock execute ...
+```
+
### Edit current configuration in your favorite editor
```
$ unitctl edit
@@ -168,6 +197,8 @@ $ unitctl edit
}
```
+*Note:* This command does not support operating on multiple instances of Unit at once.
+
### Import configuration, certificates, and NJS modules from directory
```
$ unitctl import /opt/unit/config
@@ -191,6 +222,8 @@ $ unitctl export -f - > config.tar
*Note:* The exported configuration omits certificates.
+*Note:* This command does not support operating on multiple instances of Unit at once.
+
### Wait for socket to become available
```
$ unitctl --wait-timeout-seconds=3 --wait-max-tries=4 import /opt/unit/config`
diff --git a/tools/unitctl/unitctl/src/cmd/applications.rs b/tools/unitctl/unitctl/src/cmd/applications.rs
index f4c44105..41af679e 100644
--- a/tools/unitctl/unitctl/src/cmd/applications.rs
+++ b/tools/unitctl/unitctl/src/cmd/applications.rs
@@ -1,36 +1,46 @@
use crate::unitctl::{ApplicationArgs, ApplicationCommands, UnitCtl};
-use crate::{wait, UnitctlError};
+use crate::{wait, UnitctlError, eprint_error};
use crate::requests::send_empty_body_deserialize_response;
use unit_client_rs::unit_client::UnitClient;
pub(crate) async fn cmd(cli: &UnitCtl, args: &ApplicationArgs) -> Result<(), UnitctlError> {
- let control_socket = wait::wait_for_socket(cli).await?;
- let client = UnitClient::new(control_socket);
+ let clients: Vec<UnitClient> = wait::wait_for_sockets(cli)
+ .await?
+ .into_iter()
+ .map(|sock| UnitClient::new(sock))
+ .collect();
- match &args.command {
- ApplicationCommands::Reload { ref name } => client
- .restart_application(name)
- .await
- .map_err(|e| UnitctlError::UnitClientError { source: *e })
- .and_then(|r| args.output_format.write_to_stdout(&r)),
+ for client in clients {
+ let _ = match &args.command {
+ ApplicationCommands::Reload { ref name } => client
+ .restart_application(name)
+ .await
+ .map_err(|e| UnitctlError::UnitClientError { source: *e })
+ .and_then(|r| args.output_format.write_to_stdout(&r)),
- /* we should be able to use this but the openapi generator library
- * is fundamentally incorrect and provides a broken API for the
- * applications endpoint.
- ApplicationCommands::List {} => client
- .applications()
- .await
- .map_err(|e| UnitctlError::UnitClientError { source: *e })
- .and_then(|response| args.output_format.write_to_stdout(&response)),*/
+ /* we should be able to use this but the openapi generator library
+ * is fundamentally incorrect and provides a broken API for the
+ * applications endpoint.
+ ApplicationCommands::List {} => client
+ .applications()
+ .await
+ .map_err(|e| UnitctlError::UnitClientError { source: *e })
+ .and_then(|response| args.output_format.write_to_stdout(&response)),*/
- ApplicationCommands::List {} => {
- args.output_format.write_to_stdout(
- &send_empty_body_deserialize_response(
- &client,
- "GET",
- "/config/applications",
- ).await?
- )
- },
+ ApplicationCommands::List {} => {
+ args.output_format.write_to_stdout(
+ &send_empty_body_deserialize_response(
+ &client,
+ "GET",
+ "/config/applications",
+ ).await?
+ )
+ },
+ }.map_err(|error| {
+ eprint_error(&error);
+ std::process::exit(error.exit_code());
+ });
}
+
+ Ok(())
}
diff --git a/tools/unitctl/unitctl/src/cmd/edit.rs b/tools/unitctl/unitctl/src/cmd/edit.rs
index 21bba519..34c1e7a3 100644
--- a/tools/unitctl/unitctl/src/cmd/edit.rs
+++ b/tools/unitctl/unitctl/src/cmd/edit.rs
@@ -1,6 +1,7 @@
use crate::inputfile::{InputFile, InputFormat};
use crate::requests::{send_and_validate_config_deserialize_response, send_empty_body_deserialize_response};
use crate::unitctl::UnitCtl;
+use crate::unitctl_error::ControlSocketErrorKind;
use crate::{wait, OutputFormat, UnitctlError};
use std::path::{Path, PathBuf};
use unit_client_rs::unit_client::UnitClient;
@@ -19,8 +20,16 @@ const EDITOR_KNOWN_LIST: [&str; 8] = [
];
pub(crate) async fn cmd(cli: &UnitCtl, output_format: OutputFormat) -> Result<(), UnitctlError> {
- let control_socket = wait::wait_for_socket(cli).await?;
- let client = UnitClient::new(control_socket);
+ if cli.control_socket_addresses.is_some() &&
+ cli.control_socket_addresses.clone().unwrap().len() > 1 {
+ return Err(UnitctlError::ControlSocketError{
+ kind: ControlSocketErrorKind::General,
+ message: "too many control sockets. specify at most one.".to_string(),
+ });
+ }
+
+ let mut control_sockets = wait::wait_for_sockets(cli).await?;
+ let client = UnitClient::new(control_sockets.pop().unwrap());
// Get latest configuration
let current_config = send_empty_body_deserialize_response(&client, "GET", "/config").await?;
diff --git a/tools/unitctl/unitctl/src/cmd/execute.rs b/tools/unitctl/unitctl/src/cmd/execute.rs
index 1bde437d..85aea404 100644
--- a/tools/unitctl/unitctl/src/cmd/execute.rs
+++ b/tools/unitctl/unitctl/src/cmd/execute.rs
@@ -5,7 +5,7 @@ use crate::requests::{
};
use crate::unitctl::UnitCtl;
use crate::wait;
-use crate::{OutputFormat, UnitctlError};
+use crate::{OutputFormat, UnitctlError, eprint_error};
use unit_client_rs::unit_client::UnitClient;
pub(crate) async fn cmd(
@@ -15,8 +15,11 @@ pub(crate) async fn cmd(
method: &str,
path: &str,
) -> Result<(), UnitctlError> {
- let control_socket = wait::wait_for_socket(cli).await?;
- let client = UnitClient::new(control_socket);
+ let clients: Vec<_> = wait::wait_for_sockets(cli)
+ .await?
+ .into_iter()
+ .map(|sock| UnitClient::new(sock))
+ .collect();
let path_trimmed = path.trim();
let method_upper = method.to_uppercase();
@@ -28,7 +31,21 @@ pub(crate) async fn cmd(
eprintln!("Cannot use GET method with input file - ignoring input file");
}
- send_and_deserialize(client, method_upper, input_file_arg, path_trimmed, output_format).await
+ for client in clients {
+ let _ = send_and_deserialize(
+ client,
+ method_upper.clone(),
+ input_file_arg.clone(),
+ path_trimmed,
+ output_format
+ ).await
+ .map_err(|e| {
+ eprint_error(&e);
+ std::process::exit(e.exit_code());
+ });
+ }
+
+ Ok(())
}
async fn send_and_deserialize(
diff --git a/tools/unitctl/unitctl/src/cmd/import.rs b/tools/unitctl/unitctl/src/cmd/import.rs
index 81f925bc..956832f3 100644
--- a/tools/unitctl/unitctl/src/cmd/import.rs
+++ b/tools/unitctl/unitctl/src/cmd/import.rs
@@ -50,8 +50,12 @@ pub async fn cmd(cli: &UnitCtl, directory: &PathBuf) -> Result<(), UnitctlError>
});
}
- let control_socket = wait::wait_for_socket(cli).await?;
- let client = UnitClient::new(control_socket);
+ let clients: Vec<_> = wait::wait_for_sockets(cli)
+ .await?
+ .into_iter()
+ .map(|sock| UnitClient::new(sock))
+ .collect();
+
let mut results = vec![];
for i in WalkDir::new(directory)
.follow_links(true)
@@ -60,7 +64,9 @@ pub async fn cmd(cli: &UnitCtl, directory: &PathBuf) -> Result<(), UnitctlError>
.filter_map(Result::ok)
.filter(|e| !e.path().is_dir())
{
- results.push(process_entry(i, &client).await);
+ for client in &clients {
+ results.push(process_entry(i.clone(), client).await);
+ }
}
if results.iter().filter(|r| r.is_err()).count() == results.len() {
diff --git a/tools/unitctl/unitctl/src/cmd/listeners.rs b/tools/unitctl/unitctl/src/cmd/listeners.rs
index 4eb48355..05fbec07 100644
--- a/tools/unitctl/unitctl/src/cmd/listeners.rs
+++ b/tools/unitctl/unitctl/src/cmd/listeners.rs
@@ -1,14 +1,23 @@
use crate::unitctl::UnitCtl;
use crate::wait;
-use crate::{OutputFormat, UnitctlError};
+use crate::{OutputFormat, UnitctlError, eprint_error};
use unit_client_rs::unit_client::UnitClient;
pub async fn cmd(cli: &UnitCtl, output_format: OutputFormat) -> Result<(), UnitctlError> {
- let control_socket = wait::wait_for_socket(cli).await?;
- let client = UnitClient::new(control_socket);
- client
- .listeners()
- .await
- .map_err(|e| UnitctlError::UnitClientError { source: *e })
- .and_then(|response| output_format.write_to_stdout(&response))
+ let socks = wait::wait_for_sockets(cli)
+ .await?;
+ let clients = socks.iter()
+ .map(|sock| UnitClient::new(sock.clone()));
+
+ for client in clients {
+ let _ = client.listeners()
+ .await
+ .map_err(|e| {
+ let err = UnitctlError::UnitClientError { source: *e };
+ eprint_error(&err);
+ std::process::exit(err.exit_code());
+ })
+ .and_then(|response| output_format.write_to_stdout(&response));
+ }
+ Ok(())
}
diff --git a/tools/unitctl/unitctl/src/cmd/save.rs b/tools/unitctl/unitctl/src/cmd/save.rs
index bce8fdb9..d93ce221 100644
--- a/tools/unitctl/unitctl/src/cmd/save.rs
+++ b/tools/unitctl/unitctl/src/cmd/save.rs
@@ -2,6 +2,7 @@ use crate::unitctl::UnitCtl;
use crate::wait;
use crate::UnitctlError;
use crate::requests::send_empty_body_deserialize_response;
+use crate::unitctl_error::ControlSocketErrorKind;
use unit_client_rs::unit_client::UnitClient;
use tar::{Builder, Header};
use std::fs::File;
@@ -12,13 +13,21 @@ pub async fn cmd(
cli: &UnitCtl,
filename: &String
) -> Result<(), UnitctlError> {
+ if cli.control_socket_addresses.is_some() &&
+ cli.control_socket_addresses.clone().unwrap().len() > 1 {
+ return Err(UnitctlError::ControlSocketError{
+ kind: ControlSocketErrorKind::General,
+ message: "too many control sockets. specify at most one.".to_string(),
+ });
+ }
+
+ let mut control_sockets = wait::wait_for_sockets(cli).await?;
+ let client = UnitClient::new(control_sockets.pop().unwrap());
+
if !filename.ends_with(".tar") {
eprintln!("Warning: writing uncompressed tarball to {}", filename);
}
- let control_socket = wait::wait_for_socket(cli).await?;
- let client = UnitClient::new(control_socket);
-
let config_res = serde_json::to_string_pretty(
&send_empty_body_deserialize_response(&client, "GET", "/config").await?
);
diff --git a/tools/unitctl/unitctl/src/cmd/status.rs b/tools/unitctl/unitctl/src/cmd/status.rs
index 2cac5714..6d5eb00a 100644
--- a/tools/unitctl/unitctl/src/cmd/status.rs
+++ b/tools/unitctl/unitctl/src/cmd/status.rs
@@ -1,14 +1,23 @@
use crate::unitctl::UnitCtl;
use crate::wait;
-use crate::{OutputFormat, UnitctlError};
+use crate::{OutputFormat, UnitctlError, eprint_error};
use unit_client_rs::unit_client::UnitClient;
pub async fn cmd(cli: &UnitCtl, output_format: OutputFormat) -> Result<(), UnitctlError> {
- let control_socket = wait::wait_for_socket(cli).await?;
- let client = UnitClient::new(control_socket);
- client
- .status()
- .await
- .map_err(|e| UnitctlError::UnitClientError { source: *e })
- .and_then(|response| output_format.write_to_stdout(&response))
+ let socks = wait::wait_for_sockets(cli)
+ .await?;
+ let clients = socks.iter()
+ .map(|sock| UnitClient::new(sock.clone()));
+
+ for client in clients {
+ let _ = client.status()
+ .await
+ .map_err(|e| {
+ let err = UnitctlError::UnitClientError { source: *e };
+ eprint_error(&err);
+ std::process::exit(err.exit_code());
+ })
+ .and_then(|response| output_format.write_to_stdout(&response));
+ }
+ Ok(())
}
diff --git a/tools/unitctl/unitctl/src/main.rs b/tools/unitctl/unitctl/src/main.rs
index 8f33fc16..822b2ae7 100644
--- a/tools/unitctl/unitctl/src/main.rs
+++ b/tools/unitctl/unitctl/src/main.rs
@@ -15,8 +15,8 @@ use crate::cmd::{
};
use crate::output_format::OutputFormat;
use crate::unitctl::{Commands, UnitCtl};
-use crate::unitctl_error::UnitctlError;
-use unit_client_rs::unit_client::{UnitClient, UnitClientError, UnitSerializableMap};
+use crate::unitctl_error::{UnitctlError, eprint_error};
+use unit_client_rs::unit_client::{UnitClient, UnitSerializableMap};
mod cmd;
mod inputfile;
@@ -58,56 +58,3 @@ async fn main() -> Result<(), UnitctlError> {
std::process::exit(error.exit_code());
})
}
-
-fn eprint_error(error: &UnitctlError) {
- match error {
- UnitctlError::NoUnitInstancesError => {
- eprintln!("No running unit instances found");
- }
- UnitctlError::MultipleUnitInstancesError { ref suggestion } => {
- eprintln!("{}", suggestion);
- }
- UnitctlError::NoSocketPathError => {
- eprintln!("Unable to detect socket path from running instance");
- }
- UnitctlError::UnitClientError { source } => match source {
- UnitClientError::SocketPermissionsError { .. } => {
- eprintln!("{}", source);
- eprintln!("Try running again with the same permissions as the unit control socket");
- }
- UnitClientError::OpenAPIError { source } => {
- eprintln!("OpenAPI Error: {}", source);
- }
- _ => {
- eprintln!("Unit client error: {}", source);
- }
- },
- UnitctlError::SerializationError { message } => {
- eprintln!("Serialization error: {}", message);
- }
- UnitctlError::DeserializationError { message } => {
- eprintln!("Deserialization error: {}", message);
- }
- UnitctlError::IoError { ref source } => {
- eprintln!("IO error: {}", source);
- }
- UnitctlError::PathNotFound { path } => {
- eprintln!("Path not found: {}", path);
- }
- UnitctlError::EditorError { message } => {
- eprintln!("Error opening editor: {}", message);
- }
- UnitctlError::CertificateError { message } => {
- eprintln!("Certificate error: {}", message);
- }
- UnitctlError::NoInputFileError => {
- eprintln!("No input file specified when required");
- }
- UnitctlError::UiServerError { ref message } => {
- eprintln!("UI server error: {}", message);
- }
- _ => {
- eprintln!("{}", error);
- }
- }
-}
diff --git a/tools/unitctl/unitctl/src/unitctl.rs b/tools/unitctl/unitctl/src/unitctl.rs
index e567116b..a36f006c 100644
--- a/tools/unitctl/unitctl/src/unitctl.rs
+++ b/tools/unitctl/unitctl/src/unitctl.rs
@@ -16,7 +16,7 @@ pub(crate) struct UnitCtl {
value_parser = parse_control_socket_address,
help = "Path (unix:/var/run/unit/control.sock), tcp address with port (127.0.0.1:80), or URL"
)]
- pub(crate) control_socket_address: Option<ControlSocket>,
+ pub(crate) control_socket_addresses: Option<Vec<ControlSocket>>,
#[arg(
required = false,
default_missing_value = "1",
diff --git a/tools/unitctl/unitctl/src/unitctl_error.rs b/tools/unitctl/unitctl/src/unitctl_error.rs
index 1cf4fe48..83b2da46 100644
--- a/tools/unitctl/unitctl/src/unitctl_error.rs
+++ b/tools/unitctl/unitctl/src/unitctl_error.rs
@@ -70,3 +70,56 @@ impl Termination for UnitctlError {
ExitCode::from(self.exit_code() as u8)
}
}
+
+pub fn eprint_error(error: &UnitctlError) {
+ match error {
+ UnitctlError::NoUnitInstancesError => {
+ eprintln!("No running unit instances found");
+ }
+ UnitctlError::MultipleUnitInstancesError { ref suggestion } => {
+ eprintln!("{}", suggestion);
+ }
+ UnitctlError::NoSocketPathError => {
+ eprintln!("Unable to detect socket path from running instance");
+ }
+ UnitctlError::UnitClientError { source } => match source {
+ UnitClientError::SocketPermissionsError { .. } => {
+ eprintln!("{}", source);
+ eprintln!("Try running again with the same permissions as the unit control socket");
+ }
+ UnitClientError::OpenAPIError { source } => {
+ eprintln!("OpenAPI Error: {}", source);
+ }
+ _ => {
+ eprintln!("Unit client error: {}", source);
+ }
+ },
+ UnitctlError::SerializationError { message } => {
+ eprintln!("Serialization error: {}", message);
+ }
+ UnitctlError::DeserializationError { message } => {
+ eprintln!("Deserialization error: {}", message);
+ }
+ UnitctlError::IoError { ref source } => {
+ eprintln!("IO error: {}", source);
+ }
+ UnitctlError::PathNotFound { path } => {
+ eprintln!("Path not found: {}", path);
+ }
+ UnitctlError::EditorError { message } => {
+ eprintln!("Error opening editor: {}", message);
+ }
+ UnitctlError::CertificateError { message } => {
+ eprintln!("Certificate error: {}", message);
+ }
+ UnitctlError::NoInputFileError => {
+ eprintln!("No input file specified when required");
+ }
+ UnitctlError::UiServerError { ref message } => {
+ eprintln!("UI server error: {}", message);
+ }
+ _ => {
+ eprintln!("{}", error);
+ }
+ }
+}
diff --git a/tools/unitctl/unitctl/src/wait.rs b/tools/unitctl/unitctl/src/wait.rs
index 313403a8..860fb0b5 100644
--- a/tools/unitctl/unitctl/src/wait.rs
+++ b/tools/unitctl/unitctl/src/wait.rs
@@ -8,105 +8,83 @@ use unit_client_rs::unitd_instance::UnitdInstance;
/// Waits for a socket to become available. Availability is tested by attempting to access the
/// status endpoint via the control socket. When socket is available, ControlSocket instance
/// is returned.
-pub async fn wait_for_socket(cli: &UnitCtl) -> Result<ControlSocket, UnitctlError> {
- // Don't wait, if wait_time is not specified
- if cli.wait_time_seconds.is_none() {
- return cli.control_socket_address.instance_value_if_none().await.and_validate();
+pub async fn wait_for_sockets(cli: &UnitCtl) -> Result<Vec<ControlSocket>, UnitctlError> {
+ let socks: Vec<ControlSocket>;
+ match &cli.control_socket_addresses {
+ None => {
+ socks = vec![find_socket_address_from_instance().await?];
+ },
+ Some(s) => socks = s.clone(),
}
- let wait_time =
- Duration::from_secs(cli.wait_time_seconds.expect("wait_time_option default was not applied") as u64);
- let max_tries = cli.wait_max_tries.expect("max_tries_option default was not applied");
-
- let mut attempt: u8 = 0;
- let mut control_socket: ControlSocket;
- while attempt < max_tries {
- if attempt > 0 {
- eprintln!(
- "Waiting for {}s control socket to be available try {}/{}...",
- wait_time.as_secs(),
- attempt + 1,
- max_tries
- );
- std::thread::sleep(wait_time);
+ let mut mapped = vec![];
+ for addr in socks {
+ if cli.wait_time_seconds.is_none() {
+ mapped.push(addr.to_owned().validate()?);
+ continue;
}
- attempt += 1;
-
- let result = cli.control_socket_address.instance_value_if_none().await.and_validate();
+ let wait_time =
+ Duration::from_secs(cli.wait_time_seconds.expect("wait_time_option default was not applied") as u64);
+ let max_tries = cli.wait_max_tries.expect("max_tries_option default was not applied");
+
+ let mut attempt = 0;
+ while attempt < max_tries {
+ if attempt > 0 {
+ eprintln!(
+ "Waiting for {}s control socket to be available try {}/{}...",
+ wait_time.as_secs(),
+ attempt + 1,
+ max_tries
+ );
+ std::thread::sleep(wait_time);
+ }
- if let Err(error) = result {
- if error.retryable() {
- continue;
+ attempt += 1;
+
+ let res = addr.to_owned().validate();
+ if res.is_err() {
+ let err = res.map_err(|error| match error {
+ UnitClientError::UnixSocketNotFound { .. } => UnitctlError::ControlSocketError {
+ kind: ControlSocketErrorKind::NotFound,
+ message: format!("{}", error),
+ },
+ UnitClientError::SocketPermissionsError { .. } => UnitctlError::ControlSocketError {
+ kind: ControlSocketErrorKind::Permissions,
+ message: format!("{}", error),
+ },
+ UnitClientError::TcpSocketAddressUriError { .. }
+ | UnitClientError::TcpSocketAddressNoPortError { .. }
+ | UnitClientError::TcpSocketAddressParseError { .. } => UnitctlError::ControlSocketError {
+ kind: ControlSocketErrorKind::Parse,
+ message: format!("{}", error),
+ },
+ _ => UnitctlError::ControlSocketError {
+ kind: ControlSocketErrorKind::General,
+ message: format!("{}", error),
+ },
+ });
+ if err.as_ref().is_err_and(|e| e.retryable()) {
+ continue;
+ } else {
+ return Err(err.expect_err("impossible error condition"));
+ }
} else {
- return Err(error);
+ let sock = res.unwrap();
+ if let Err(e) = UnitClient::new(sock.clone()).status().await {
+ eprintln!("Unable to access status endpoint: {}", *e);
+ continue;
+ }
+ mapped.push(sock);
}
}
- control_socket = result.unwrap();
- let client = UnitClient::new(control_socket.clone());
-
- match client.status().await {
- Ok(_) => {
- return Ok(control_socket.to_owned());
- }
- Err(error) => {
- eprintln!("Unable to access status endpoint: {}", *error);
- continue;
- }
+ if attempt >= max_tries {
+ return Err(UnitctlError::WaitTimeoutError);
}
}
- if attempt >= max_tries {
- Err(UnitctlError::WaitTimeoutError)
- } else {
- panic!("Unexpected state - this should never happen");
- }
-}
-
-trait OptionControlSocket {
- async fn instance_value_if_none(&self) -> Result<ControlSocket, UnitctlError>;
-}
-
-impl OptionControlSocket for Option<ControlSocket> {
- async fn instance_value_if_none(&self) -> Result<ControlSocket, UnitctlError> {
- if let Some(control_socket) = self {
- Ok(control_socket.to_owned())
- } else {
- find_socket_address_from_instance().await
- }
- }
-}
-
-trait ResultControlSocket<T, E> {
- fn and_validate(self) -> Result<ControlSocket, UnitctlError>;
-}
-
-impl ResultControlSocket<ControlSocket, UnitctlError> for Result<ControlSocket, UnitctlError> {
- fn and_validate(self) -> Result<ControlSocket, UnitctlError> {
- self.and_then(|control_socket| {
- control_socket.validate().map_err(|error| match error {
- UnitClientError::UnixSocketNotFound { .. } => UnitctlError::ControlSocketError {
- kind: ControlSocketErrorKind::NotFound,
- message: format!("{}", error),
- },
- UnitClientError::SocketPermissionsError { .. } => UnitctlError::ControlSocketError {
- kind: ControlSocketErrorKind::Permissions,
- message: format!("{}", error),
- },
- UnitClientError::TcpSocketAddressUriError { .. }
- | UnitClientError::TcpSocketAddressNoPortError { .. }
- | UnitClientError::TcpSocketAddressParseError { .. } => UnitctlError::ControlSocketError {
- kind: ControlSocketErrorKind::Parse,
- message: format!("{}", error),
- },
- _ => UnitctlError::ControlSocketError {
- kind: ControlSocketErrorKind::General,
- message: format!("{}", error),
- },
- })
- })
- }
+ return Ok(mapped);
}
async fn find_socket_address_from_instance() -> Result<ControlSocket, UnitctlError> {
@@ -114,7 +92,7 @@ async fn find_socket_address_from_instance() -> Result<ControlSocket, UnitctlErr
if instances.is_empty() {
return Err(UnitctlError::NoUnitInstancesError);
} else if instances.len() > 1 {
- let suggestion: String = "Multiple unit instances found. Specify the socket address to the instance you wish \
+ let suggestion: String = "Multiple unit instances found. Specify the socket address(es) to the instance you wish \
to control using the `--control-socket-address` flag"
.to_string();
return Err(UnitctlError::MultipleUnitInstancesError { suggestion });
@@ -131,14 +109,14 @@ async fn find_socket_address_from_instance() -> Result<ControlSocket, UnitctlErr
async fn wait_for_unavailable_unix_socket() {
let control_socket = ControlSocket::try_from("unix:/tmp/this_socket_does_not_exist.sock");
let cli = UnitCtl {
- control_socket_address: Some(control_socket.unwrap()),
+ control_socket_addresses: Some(vec![control_socket.unwrap()]),
wait_time_seconds: Some(1u8),
wait_max_tries: Some(3u8),
command: crate::unitctl::Commands::Status {
output_format: crate::output_format::OutputFormat::JsonPretty,
},
};
- let error = wait_for_socket(&cli)
+ let error = wait_for_sockets(&cli)
.await
.expect_err("Expected error, but no error received");
match error {
@@ -151,7 +129,7 @@ async fn wait_for_unavailable_unix_socket() {
async fn wait_for_unavailable_tcp_socket() {
let control_socket = ControlSocket::try_from("http://127.0.0.1:9783456");
let cli = UnitCtl {
- control_socket_address: Some(control_socket.unwrap()),
+ control_socket_addresses: Some(vec![control_socket.unwrap()]),
wait_time_seconds: Some(1u8),
wait_max_tries: Some(3u8),
command: crate::unitctl::Commands::Status {
@@ -159,7 +137,7 @@ async fn wait_for_unavailable_tcp_socket() {
},
};
- let error = wait_for_socket(&cli)
+ let error = wait_for_sockets(&cli)
.await
.expect_err("Expected error, but no error received");
match error {