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
76 changes: 76 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,82 @@ let mut cli = pdsh_cli.to_bssh_cli();
- Unknown pdsh options produce helpful error messages
- Normal bssh operation is completely unaffected by pdsh compat code

### 1.5 Hostlist Expression Support (`hostlist/*`)

**Module Structure (Added 2025-12-17, Issue #98):**
- `hostlist/mod.rs` - Module exports and comma-separated pattern handling (130 lines)
- `hostlist/parser.rs` - Range expression parser (570 lines)
- `hostlist/expander.rs` - Range expansion and cartesian product (270 lines)
- `hostlist/error.rs` - Error types with thiserror (80 lines)

**Design Decisions:**
- pdsh-compatible hostlist expression syntax
- Zero-cost abstraction for non-range patterns (pass-through)
- Efficient cartesian product expansion for multiple ranges
- Distinguishes hostlist expressions from glob patterns

**Hostlist Expression Syntax:**
```
hostlist = host_term (',' host_term)*
host_term = prefix range_expr suffix
range_expr = '[' range_list ']'
range_list = range_item (',' range_item)*
range_item = NUMBER | NUMBER '-' NUMBER
prefix = STRING (any characters before '[')
suffix = STRING (any characters after ']', may include nested ranges)
```

**Supported Features:**
- Simple range: `node[1-5]` -> `node1, node2, node3, node4, node5`
- Zero-padded: `node[01-05]` -> `node01, node02, node03, node04, node05`
- Comma-separated: `node[1,3,5]` -> `node1, node3, node5`
- Mixed: `node[1-3,7,9-10]` -> 7 hosts
- Cartesian product: `rack[1-2]-node[1-3]` -> 6 hosts
- With domain: `web[1-3].example.com` -> 3 hosts
- With user/port: `admin@db[01-03]:5432` -> 3 hosts with user and port
- File input: `^/path/to/file` -> read hosts from file

**Integration Points:**
- `-H` option in native bssh mode (all patterns automatically expanded)
- `-w` option in pdsh compatibility mode
- `--filter` option (supports both glob and hostlist patterns)
- `--exclude` option (supports both glob and hostlist patterns)
- pdsh query mode (`-q`) with full expansion support

**Pattern Detection Heuristics:**
```rust
// Distinguishes hostlist expressions from glob patterns
// Hostlist: [1-5], [01-05], [1,2,3], [1-3,5-7] (numeric content)
// Glob: [abc], [a-z], [!xyz] (alphabetic content)

fn is_hostlist_expression(pattern: &str) -> bool {
// Check if brackets contain numeric ranges
// Numeric: 1-5, 01-05, 1,2,3
// Non-numeric (glob): abc, a-z, !xyz
}
```

**Safety Limits:**
- Maximum expansion size: 100,000 hosts (prevents DoS)
- Validates range direction (start <= end)
- Error on empty brackets, unclosed brackets, nested brackets
- IPv6 literal bracket disambiguation

**Data Flow:**
```
Input: "admin@web[1-3].example.com:22"
Parse user prefix: "admin@"
Parse hostname with range: "web[1-3].example.com"
Expand range: ["web1.example.com", "web2.example.com", "web3.example.com"]
Parse port suffix: ":22"
Output: ["admin@web1.example.com:22", "admin@web2.example.com:22", "admin@web3.example.com:22"]
```

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

**Module Structure (Refactored 2025-10-17):**
Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ A high-performance SSH client with **SSH-compatible syntax** for both single-hos
- **Port Forwarding**: Full support for local (-L), remote (-R), and dynamic (-D) SSH port forwarding
- **Jump Host Support**: Connect through bastion hosts using OpenSSH ProxyJump syntax (`-J`)
- **Parallel Execution**: Execute commands across multiple nodes simultaneously
- **Hostlist Expressions**: pdsh-style range expansion (`node[1-5]`, `rack[1-2]-node[1-3]`) for compact host specification
- **Fail-Fast Mode**: Stop immediately on first failure with `-k` flag (pdsh compatible)
- **Interactive Terminal UI (TUI)**: Real-time monitoring with 4 view modes (Summary/Detail/Split/Diff) for multi-node operations
- **Cluster Management**: Define and manage node clusters via configuration files
Expand Down Expand Up @@ -179,14 +180,24 @@ bssh -H "user1@host1.com,user2@host2.com:2222" "uptime"
# Using cluster from config
bssh -C production "df -h"

# Hostlist expressions (pdsh-style range expansion)
bssh -H "node[1-5]" "uptime" # node1, node2, node3, node4, node5
bssh -H "node[01-05]" "df -h" # Zero-padded: node01, node02, ...
bssh -H "node[1,3,5]" "ps aux" # Specific values: node1, node3, node5
bssh -H "rack[1-2]-node[1-3]" "uptime" # Cartesian product: 6 hosts
bssh -H "web[1-3].example.com" "nginx -v" # With domain suffix
bssh -H "admin@db[01-03]:5432" "psql --version" # With user and port
bssh -H "^/etc/hosts.cluster" "uptime" # Read hosts from file

# Filter specific hosts with pattern matching
bssh -H "web1,web2,db1,db2" -f "web*" "systemctl status nginx"
bssh -C production -f "db*" "pg_dump --version"
bssh -H "node[1-10]" -f "node[1-5]" "uptime" # Filter with hostlist expression

# Exclude specific hosts from execution
bssh -H "node1,node2,node3" --exclude "node2" "uptime"
bssh -C production --exclude "db*" "systemctl restart nginx"
bssh -C production --exclude "web1,web2" "apt update"
bssh -H "node[1-10]" --exclude "node[3-5]" "uptime" # Exclude with hostlist expression

# With custom SSH key
bssh -C staging -i ~/.ssh/custom_key "systemctl status nginx"
Expand Down
135 changes: 124 additions & 11 deletions docs/man/bssh.1
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,29 @@ Example: -D 1080 (SOCKS5 proxy on localhost:1080), -D *:1080/4 (SOCKS4 on all in
.TP
.BR \-H ", " \-\-hosts " " \fIHOSTS\fR
Comma-separated list of hosts in [user@]hostname[:port] format.
Example: user1@host1:2222,user2@host2
Supports pdsh-style hostlist expressions for range expansion.
.RS
.PP
Simple host list:
.IP \[bu] 2
-H "user1@host1:2222,user2@host2"
.PP
Hostlist expressions (range expansion):
.IP \[bu] 2
-H "node[1-5]" \[->] node1, node2, node3, node4, node5
.IP \[bu] 2
-H "node[01-05]" \[->] node01, node02, ... (zero-padded)
.IP \[bu] 2
-H "node[1,3,5]" \[->] node1, node3, node5 (specific values)
.IP \[bu] 2
-H "rack[1-2]-node[1-3]" \[->] 6 hosts (cartesian product)
.IP \[bu] 2
-H "web[1-3].example.com" \[->] web1.example.com, web2.example.com, ...
.IP \[bu] 2
-H "admin@web[1-3]:22" \[->] expands with user and port preserved
.IP \[bu] 2
-H "^/path/to/hostfile" \[->] read hosts from file
.RE

.TP
.BR \-C ", " \-\-cluster " " \fICLUSTER\fR
Expand Down Expand Up @@ -166,29 +188,47 @@ Password is never logged or printed in any output

.TP
.BR \-f ", " \-\-filter " " \fIPATTERN\fR
Filter hosts by pattern (supports wildcards like 'web*').
Filter hosts by pattern. Supports both wildcards and hostlist expressions.
Use with -H or -C to execute on a subset of hosts.
Example: -f "web*" matches web01, web02, etc.
.RS
.PP
Examples:
.IP \[bu] 2
-f "web*" \[->] matches web01, web02, etc. (glob pattern)
.IP \[bu] 2
-f "node[1-5]" \[->] matches node1 through node5 (hostlist expression)
.IP \[bu] 2
-f "node[1,3,5]" \[->] matches node1, node3, node5 (specific values)
.RE

.TP
.BR \-\-exclude " " \fIHOSTS\fR
Exclude hosts from target list (comma-separated).
Supports wildcard patterns: '*' (any chars), '?' (single char), '[abc]' (char set).
Patterns with wildcards use glob matching; plain patterns use substring matching.
Supports wildcards, glob patterns, and hostlist expressions.
Applied after --filter option.
.RS
.PP
Examples:
Glob patterns:
.IP \[bu] 2
--exclude "db*" \[->] exclude hosts starting with 'db'
.IP \[bu] 2
--exclude "node2" - Exclude single host
--exclude "*-backup" \[->] exclude backup nodes
.IP \[bu] 2
--exclude "web1,web2" - Exclude multiple hosts
--exclude "web[12]" \[->] exclude web1 and web2 (glob character class)
.PP
Hostlist expressions:
.IP \[bu] 2
--exclude "node[3-5]" \[->] exclude node3, node4, node5 (range)
.IP \[bu] 2
--exclude "db*" - Exclude hosts starting with 'db'
--exclude "node[1,3,5]" \[->] exclude node1, node3, node5 (specific values)
.IP \[bu] 2
--exclude "*-backup" - Exclude backup nodes
--exclude "rack[1-2]-node[1-3]" \[->] exclude 6 hosts (cartesian product)
.PP
Simple patterns:
.IP \[bu] 2
--exclude "web[12]" - Exclude web1 and web2
--exclude "node2" \[->] exclude single host
.IP \[bu] 2
--exclude "web1,web2" \[->] exclude multiple hosts
.RE

.TP
Expand Down Expand Up @@ -1094,6 +1134,79 @@ Current node's role (main or sub)

Note: Backend.AI multi-node clusters use SSH port 2200 by default, which is automatically configured.

.SH HOSTLIST EXPRESSIONS
Hostlist expressions provide a compact way to specify multiple hosts using range notation,
compatible with pdsh syntax. This allows efficient targeting of large numbers of hosts
without listing each one individually.

.SS Basic Syntax
.TP
.B Simple range
.B node[1-5]
expands to node1, node2, node3, node4, node5
.TP
.B Zero-padded range
.B node[01-05]
expands to node01, node02, node03, node04, node05
.TP
.B Comma-separated values
.B node[1,3,5]
expands to node1, node3, node5
.TP
.B Mixed ranges and values
.B node[1-3,7,9-10]
expands to node1, node2, node3, node7, node9, node10

.SS Advanced Patterns
.TP
.B Multiple ranges (cartesian product)
.B rack[1-2]-node[1-3]
expands to rack1-node1, rack1-node2, rack1-node3, rack2-node1, rack2-node2, rack2-node3
.TP
.B Domain suffix
.B web[1-3].example.com
expands to web1.example.com, web2.example.com, web3.example.com
.TP
.B With user and port
.B admin@server[1-3]:2222
expands to admin@server1:2222, admin@server2:2222, admin@server3:2222

.SS File Input
.TP
.B ^/path/to/hostfile
Reads hosts from file, one per line. Lines starting with # are comments.
Maximum file size: 1MB, maximum lines: 100,000.

.SS Using with Options
Hostlist expressions can be used with:
.TP
.B -H, --hosts
Specify target hosts:
.B bssh -H "node[1-10]" "uptime"
.TP
.B --filter
Include only matching hosts:
.B bssh -c cluster --filter "web[1-5]" "systemctl status nginx"
.TP
.B --exclude
Exclude matching hosts:
.B bssh -c cluster --exclude "node[1,3,5]" "df -h"

.SS Examples
.nf
# Execute on 100 nodes
bssh -H "compute[001-100]" "hostname"

# Target specific racks
bssh -H "rack[A-C]-node[1-8]" "uptime"

# Use hosts from file
bssh -H "^/etc/cluster/hosts" "date"

# Combine with exclusions
bssh -H "node[1-20]" --exclude "node[5,10,15]" "ps aux"
.fi

.SH EXAMPLES

.SS SSH Compatibility Mode (Single Host)
Expand Down
Loading
Loading