use crate::command::{CommandExecutor, CommandOutput, DockerCommand};
use crate::error::Result;
use async_trait::async_trait;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SwarmNodeRole {
Worker,
Manager,
}
impl SwarmNodeRole {
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Self::Worker => "worker",
Self::Manager => "manager",
}
}
}
impl std::fmt::Display for SwarmNodeRole {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone)]
pub struct SwarmJoinTokenResult {
pub token: Option<String>,
pub join_command: Option<String>,
pub role: SwarmNodeRole,
pub output: String,
}
impl SwarmJoinTokenResult {
fn parse(output: &CommandOutput, role: SwarmNodeRole, quiet: bool) -> Self {
let stdout = output.stdout.trim();
if quiet {
Self {
token: Some(stdout.to_string()),
join_command: None,
role,
output: stdout.to_string(),
}
} else {
let mut token = None;
let mut join_command = None;
for line in stdout.lines() {
let trimmed = line.trim();
if trimmed.starts_with("docker swarm join") {
join_command = Some(trimmed.to_string());
let parts: Vec<&str> = trimmed.split_whitespace().collect();
for (i, part) in parts.iter().enumerate() {
if *part == "--token" {
if let Some(t) = parts.get(i + 1) {
token = Some((*t).to_string());
}
}
}
}
}
Self {
token,
join_command,
role,
output: stdout.to_string(),
}
}
}
}
#[derive(Debug, Clone)]
pub struct SwarmJoinTokenCommand {
role: SwarmNodeRole,
quiet: bool,
rotate: bool,
pub executor: CommandExecutor,
}
impl SwarmJoinTokenCommand {
#[must_use]
pub fn new(role: SwarmNodeRole) -> Self {
Self {
role,
quiet: false,
rotate: false,
executor: CommandExecutor::default(),
}
}
#[must_use]
pub fn worker() -> Self {
Self::new(SwarmNodeRole::Worker)
}
#[must_use]
pub fn manager() -> Self {
Self::new(SwarmNodeRole::Manager)
}
#[must_use]
pub fn quiet(mut self) -> Self {
self.quiet = true;
self
}
#[must_use]
pub fn rotate(mut self) -> Self {
self.rotate = true;
self
}
fn build_args(&self) -> Vec<String> {
let mut args = vec!["swarm".to_string(), "join-token".to_string()];
if self.quiet {
args.push("--quiet".to_string());
}
if self.rotate {
args.push("--rotate".to_string());
}
args.push(self.role.as_str().to_string());
args
}
}
impl Default for SwarmJoinTokenCommand {
fn default() -> Self {
Self::worker()
}
}
#[async_trait]
impl DockerCommand for SwarmJoinTokenCommand {
type Output = SwarmJoinTokenResult;
fn get_executor(&self) -> &CommandExecutor {
&self.executor
}
fn get_executor_mut(&mut self) -> &mut CommandExecutor {
&mut self.executor
}
fn build_command_args(&self) -> Vec<String> {
self.build_args()
}
async fn execute(&self) -> Result<Self::Output> {
let args = self.build_args();
let output = self.execute_command(args).await?;
Ok(SwarmJoinTokenResult::parse(&output, self.role, self.quiet))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_join_token_worker() {
let cmd = SwarmJoinTokenCommand::worker();
let args = cmd.build_args();
assert_eq!(args, vec!["swarm", "join-token", "worker"]);
}
#[test]
fn test_join_token_manager() {
let cmd = SwarmJoinTokenCommand::manager();
let args = cmd.build_args();
assert_eq!(args, vec!["swarm", "join-token", "manager"]);
}
#[test]
fn test_join_token_quiet() {
let cmd = SwarmJoinTokenCommand::worker().quiet();
let args = cmd.build_args();
assert!(args.contains(&"--quiet".to_string()));
assert!(args.contains(&"worker".to_string()));
}
#[test]
fn test_join_token_rotate() {
let cmd = SwarmJoinTokenCommand::manager().rotate();
let args = cmd.build_args();
assert!(args.contains(&"--rotate".to_string()));
assert!(args.contains(&"manager".to_string()));
}
#[test]
fn test_join_token_all_options() {
let cmd = SwarmJoinTokenCommand::worker().quiet().rotate();
let args = cmd.build_args();
assert_eq!(
args,
vec!["swarm", "join-token", "--quiet", "--rotate", "worker"]
);
}
#[test]
fn test_node_role_display() {
assert_eq!(SwarmNodeRole::Worker.to_string(), "worker");
assert_eq!(SwarmNodeRole::Manager.to_string(), "manager");
}
}