Skip to content
Merged
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
72 changes: 72 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,78 @@ async fn main() -> Result<()> {
- Subcommand pattern adds complexity but improves UX
- Modular structure increases file count but improves testability

#### pdsh Compatibility Mode (Issue #97)

bssh supports pdsh compatibility mode, allowing it to act as a drop-in replacement for pdsh. This enables migration from pdsh without modifying existing scripts.

**Module Structure:**
- `cli/mod.rs` - CLI module exports and pdsh re-exports
- `cli/bssh.rs` - Standard bssh CLI parser
- `cli/pdsh.rs` - pdsh-compatible CLI parser and conversion logic
- `cli/mode_detection_tests.rs` - Tests for mode detection

**Activation Methods:**

1. **Binary name detection**: When bssh is invoked as "pdsh" (via symlink)
```bash
ln -s /usr/bin/bssh /usr/local/bin/pdsh
pdsh -w hosts "uptime" # Uses pdsh compat mode
```

2. **Environment variable**: `BSSH_PDSH_COMPAT=1` or `BSSH_PDSH_COMPAT=true`
```bash
BSSH_PDSH_COMPAT=1 bssh -w hosts "uptime"
```

3. **CLI flag**: `--pdsh-compat`
```bash
bssh --pdsh-compat -w hosts "uptime"
```

**Option Mapping:**

| pdsh option | bssh option | Description |
|-------------|-------------|-------------|
| `-w hosts` | `-H hosts` | Target hosts (comma-separated) |
| `-x hosts` | `--exclude hosts` | Exclude hosts from target list |
| `-f N` | `--parallel N` | Fanout (parallel connections) |
| `-l user` | `-l user` | Remote username |
| `-t N` | `--connect-timeout N` | Connection timeout (seconds) |
| `-u N` | `--timeout N` | Command timeout (seconds) |
| `-N` | `--no-prefix` | Disable hostname prefix in output |
| `-b` | `--batch` | Batch mode (single Ctrl+C terminates) |
| `-k` | `--fail-fast` | Stop on first failure |
| `-q` | (query mode) | Show hosts and exit |
| `-S` | `--any-failure` | Return largest exit code from any node |

**Implementation Details:**

```rust
// Mode detection in main.rs
let pdsh_mode = is_pdsh_compat_mode() || has_pdsh_compat_flag(&args);

if pdsh_mode {
return run_pdsh_mode(&args).await;
}

// pdsh CLI parsing and conversion
let pdsh_cli = PdshCli::parse_from(filtered_args.iter());
let mut cli = pdsh_cli.to_bssh_cli();
```

**Design Decisions:**

1. **Separate parser**: pdsh CLI uses its own clap parser to avoid conflicts with bssh options
2. **Conversion method**: `to_bssh_cli()` converts pdsh options to bssh `Cli` struct
3. **Query mode**: pdsh `-q` shows target hosts without executing commands
4. **Default fanout**: pdsh default is 32, bssh default is 10 - pdsh mode uses 32

**Key Points:**
- Mode detection happens before any argument parsing
- pdsh and bssh modes are mutually exclusive
- Unknown pdsh options produce helpful error messages
- Normal bssh operation is completely unaffected by pdsh compat code

### 2. Configuration Management (`config/*`)

**Module Structure (Refactored 2025-10-17):**
Expand Down
84 changes: 84 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,90 @@ bssh -C production -b "long-running-command"
bssh -H nodes --batch --stream "deployment-script.sh"
```

### pdsh Compatibility Mode

bssh supports pdsh compatibility mode, enabling it to act as a drop-in replacement for pdsh. This allows seamless migration from pdsh without modifying existing scripts.

#### Activation Methods

**1. Binary symlink** (recommended for full compatibility):
```bash
# Create symlink
sudo ln -s /usr/bin/bssh /usr/local/bin/pdsh

# Now pdsh commands use bssh
pdsh -w host1,host2 "uptime"
```

**2. Environment variable**:
```bash
BSSH_PDSH_COMPAT=1 bssh -w host1,host2 "uptime"
```

**3. CLI flag**:
```bash
bssh --pdsh-compat -w host1,host2 "uptime"
```

#### pdsh Option Mapping

| pdsh option | bssh equivalent | Description |
|-------------|-----------------|-------------|
| `-w hosts` | `-H hosts` | Target hosts (comma-separated) |
| `-x hosts` | `--exclude hosts` | Exclude hosts from target list |
| `-f N` | `--parallel N` | Fanout (parallel connections, default: 32) |
| `-l user` | `-l user` | Remote username |
| `-t N` | `--connect-timeout N` | Connection timeout in seconds |
| `-u N` | `--timeout N` | Command timeout in seconds |
| `-N` | `--no-prefix` | Disable hostname prefix in output |
| `-b` | `--batch` | Batch mode (single Ctrl+C terminates) |
| `-k` | `--fail-fast` | Stop on first failure |
| `-q` | (query mode) | Show target hosts and exit |
| `-S` | `--any-failure` | Return largest exit code from any node |

#### pdsh Mode Examples

```bash
# Basic command execution
pdsh -w node1,node2,node3 "uptime"

# With fanout limit
pdsh -w nodes -f 10 "df -h"

# Exclude specific hosts
pdsh -w node[1-5] -x node3 "hostname"

# Query mode: show target hosts without executing
pdsh -w host1,host2,host3 -x host2 -q
# Output:
# host1
# host3

# Combine multiple options
pdsh -w servers -f 20 -l admin -t 30 -N "systemctl status nginx"

# Fail fast mode
pdsh -w nodes -k "critical-operation.sh"
```

#### Query Mode with Glob Patterns

Query mode (`-q`) supports glob pattern matching for exclusions:

```bash
# Exclude hosts matching a pattern
pdsh -w web1,web2,db1,db2 -x "db*" -q
# Output:
# web1
# web2

# Use wildcards in exclusion
pdsh -w node1,node2,backup1,backup2 -x "*backup*" -q
# Output:
# node1
# node2
```

### Built-in Commands
```bash
# Test connectivity to hosts
Expand Down
76 changes: 76 additions & 0 deletions docs/man/bssh.1
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,82 @@ Host legacy.example.com
RequiredRSASize 1024
.fi

.SH PDSH COMPATIBILITY MODE
.B bssh
supports pdsh compatibility mode, enabling it to act as a drop-in replacement for pdsh.
This allows seamless migration from pdsh without modifying existing scripts.

.SS Activation Methods

.TP
.B Binary symlink (recommended)
Create a symlink to use bssh as pdsh:
.nf
sudo ln -s /usr/bin/bssh /usr/local/bin/pdsh
pdsh -w host1,host2 "uptime"
.fi

.TP
.B Environment variable
Set BSSH_PDSH_COMPAT to enable pdsh mode:
.nf
BSSH_PDSH_COMPAT=1 bssh -w host1,host2 "uptime"
.fi

.TP
.B CLI flag
Use --pdsh-compat flag:
.nf
bssh --pdsh-compat -w host1,host2 "uptime"
.fi

.SS pdsh Option Mapping

.TS
l l l.
pdsh option bssh equivalent Description
_
-w hosts -H hosts Target hosts
-x hosts --exclude hosts Exclude hosts
-f N --parallel N Fanout (default: 32)
-l user -l user Remote username
-t N --connect-timeout N Connection timeout
-u N --timeout N Command timeout
-N --no-prefix No hostname prefix
-b --batch Batch mode
-k --fail-fast Stop on first failure
-q (query mode) Show hosts and exit
-S --any-failure Return largest exit code
.TE

.SS pdsh Mode Examples

.TP
Basic command execution:
.B pdsh -w node1,node2,node3 "uptime"

.TP
With fanout limit:
.B pdsh -w nodes -f 10 "df -h"

.TP
Exclude specific hosts:
.B pdsh -w node1,node2,node3 -x node2 "hostname"

.TP
Query mode (show hosts without executing):
.B pdsh -w host1,host2,host3 -x host2 -q
.RS
Shows only host1 and host3
.RE

.TP
Query mode with glob patterns:
.B pdsh -w web1,web2,db1,db2 -x "db*" -q
.RS
Shows web1 and web2 (db* pattern excludes db1 and db2)
.RE

.SH BACKEND.AI INTEGRATION
When running inside a Backend.AI multi-node session, bssh automatically detects cluster configuration
from environment variables:
Expand Down
25 changes: 20 additions & 5 deletions src/cli.rs → src/cli/bssh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,18 @@ pub struct Cli {
)]
pub fail_fast: bool,

#[arg(
long = "any-failure",
help = "Return largest exit code from any node (pdsh -S compatible)\nWhen enabled, returns the maximum exit code from all nodes\nUseful for build/test pipelines where any failure should be reported"
)]
pub any_failure: bool,

#[arg(
long = "pdsh-compat",
help = "Enable pdsh compatibility mode\nAccepts pdsh-style command line arguments (-w, -x, -f, etc.)\nUseful when migrating from pdsh or in mixed environments"
)]
pub pdsh_compat: bool,

#[arg(
trailing_var_arg = true,
help = "Command to execute on remote hosts",
Expand Down Expand Up @@ -412,11 +424,14 @@ pub enum Commands {
impl Cli {
pub fn get_command(&self) -> String {
// In multi-server mode with destination, treat destination as first command arg
if self.is_multi_server_mode() && self.destination.is_some() {
let mut all_args = vec![self.destination.as_ref().unwrap().clone()];
all_args.extend(self.command_args.clone());
all_args.join(" ")
} else if !self.command_args.is_empty() {
if self.is_multi_server_mode() {
if let Some(dest) = &self.destination {
let mut all_args = vec![dest.clone()];
all_args.extend(self.command_args.clone());
return all_args.join(" ");
}
}
if !self.command_args.is_empty() {
self.command_args.join(" ")
} else {
String::new()
Expand Down
49 changes: 49 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2025 Lablup Inc. and Jeongkyu Shin
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! CLI module for bssh
//!
//! This module provides command-line interface parsing for bssh, including:
//! - Standard bssh CLI (`Cli`)
//! - pdsh compatibility layer (`pdsh` submodule)
//!
//! # Architecture
//!
//! The CLI module is structured as follows:
//! - `bssh.rs` - Main bssh CLI parser with all standard options
//! - `pdsh.rs` - pdsh-compatible CLI parser for drop-in replacement mode
//!
//! # pdsh Compatibility Mode
//!
//! bssh can operate in pdsh compatibility mode, activated by:
//! 1. Setting `BSSH_PDSH_COMPAT=1` environment variable
//! 2. Symlinking bssh to "pdsh" and invoking via that name
//! 3. Using the `--pdsh-compat` flag
//!
//! See the `pdsh` module documentation for details on option mapping.

mod bssh;
pub mod pdsh;

#[cfg(test)]
mod mode_detection_tests;

// Re-export main CLI types from bssh module
pub use bssh::{Cli, Commands};

// Re-export pdsh compatibility utilities
pub use pdsh::{
has_pdsh_compat_flag, is_pdsh_compat_mode, remove_pdsh_compat_flag, PdshCli, QueryResult,
PDSH_COMPAT_ENV_VAR,
};
Loading