diff --git a/README.md b/README.md index 0491b7d5..02a551d2 100644 --- a/README.md +++ b/README.md @@ -395,12 +395,17 @@ bssh -H nodes --batch --stream "deployment-script.sh" 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. +**📖 Complete Documentation**: +- **[Migration Guide](docs/pdsh-migration.md)** - Step-by-step migration from pdsh to bssh +- **[Options Reference](docs/pdsh-options.md)** - Complete option mapping table +- **[Examples](docs/pdsh-examples.md)** - Real-world usage patterns + #### Activation Methods **1. Binary symlink** (recommended for full compatibility): ```bash -# Create symlink -sudo ln -s /usr/bin/bssh /usr/local/bin/pdsh +# Create symlink (done automatically by Homebrew) +sudo ln -sf "$(which bssh)" /usr/local/bin/pdsh # Now pdsh commands use bssh pdsh -w host1,host2 "uptime" @@ -416,6 +421,12 @@ BSSH_PDSH_COMPAT=1 bssh -w host1,host2 "uptime" bssh --pdsh-compat -w host1,host2 "uptime" ``` +**4. Shell alias**: +```bash +# Add to ~/.bashrc or ~/.zshrc +alias pdsh='bssh --pdsh-compat' +``` + #### pdsh Option Mapping | pdsh option | bssh equivalent | Description | diff --git a/docs/pdsh-examples.md b/docs/pdsh-examples.md new file mode 100644 index 00000000..b0eb37f1 --- /dev/null +++ b/docs/pdsh-examples.md @@ -0,0 +1,779 @@ +# pdsh Compatibility Examples + +Real-world examples of common pdsh usage patterns with bssh. + +## Table of Contents + +- [Basic Operations](#basic-operations) +- [System Administration](#system-administration) +- [Deployment and Configuration](#deployment-and-configuration) +- [Monitoring and Diagnostics](#monitoring-and-diagnostics) +- [Data Collection](#data-collection) +- [Cluster Management](#cluster-management) +- [Advanced Patterns](#advanced-patterns) +- [Scripting Examples](#scripting-examples) + +## Basic Operations + +### Execute Simple Command + +```bash +# Run command on multiple hosts +pdsh -w host1,host2,host3 "uptime" + +# Output: +# [host1] 10:30:45 up 5 days, 2:14, 1 user, load average: 0.15, 0.12, 0.09 +# [host2] 10:30:45 up 3 days, 4:22, 2 users, load average: 0.23, 0.19, 0.17 +# [host3] 10:30:45 up 7 days, 1:45, 1 user, load average: 0.08, 0.11, 0.10 +``` + +### Using Hostlist Expressions + +```bash +# Range expansion +pdsh -w node[1-5] "hostname" + +# Output: +# [node1] node1 +# [node2] node2 +# [node3] node3 +# [node4] node4 +# [node5] node5 + +# Zero-padded ranges +pdsh -w server[01-10] "hostname" +# Creates: server01, server02, ..., server10 + +# Cartesian product +pdsh -w rack[1-2]-node[1-4] "hostname" +# Creates: rack1-node1, rack1-node2, ..., rack2-node4 (8 hosts) +``` + +### Exclude Hosts + +```bash +# Exclude specific hosts +pdsh -w node[1-10] -x node5,node7 "df -h /" + +# Exclude with wildcards +pdsh -w web1,web2,db1,db2,cache1 -x "db*,cache*" "uptime" +# Runs on: web1, web2 + +# Exclude range +pdsh -w compute[01-20] -x "compute[15-20]" "nvidia-smi" +# Runs on: compute01-compute14 +``` + +### Query Mode + +```bash +# Verify host expansion +pdsh -w node[1-5] -q +# Output: +# node1 +# node2 +# node3 +# node4 +# node5 + +# Check exclusions +pdsh -w node[1-10] -x "node[3-5]" -q +# Output: +# node1 +# node2 +# node6 +# node7 +# node8 +# node9 +# node10 +``` + +## System Administration + +### Package Management + +```bash +# Update package lists (Ubuntu/Debian) +pdsh -w servers -l root -S "sudo apt update" + +# Upgrade packages +pdsh -w servers -l admin -S "sudo apt upgrade -y" + +# Install specific package on all hosts +pdsh -w webservers -S "sudo apt install -y nginx" + +# Check package version +pdsh -w servers "dpkg -l | grep nginx" + +# Clean package cache +pdsh -w servers -S "sudo apt clean" +``` + +### Service Management + +```bash +# Restart service on all web servers +pdsh -w web[1-10] -S "sudo systemctl restart nginx" + +# Check service status +pdsh -w app-servers "systemctl status myapp" + +# Enable service on boot +pdsh -w servers -S "sudo systemctl enable docker" + +# Stop service on specific hosts +pdsh -w cache[1-3] -S "sudo systemctl stop redis" + +# Reload configuration +pdsh -w webservers -S "sudo systemctl reload nginx" +``` + +### User Management + +```bash +# Create user on all hosts +pdsh -w servers -S "sudo useradd -m -s /bin/bash deploy" + +# Set password +pdsh -w servers -S "echo 'deploy:newpassword' | sudo chpasswd" + +# Add user to group +pdsh -w servers -S "sudo usermod -aG docker deploy" + +# Check user existence +pdsh -w servers "id deploy" + +# Remove user +pdsh -w servers -S "sudo userdel -r olduser" +``` + +### File System Operations + +```bash +# Check disk usage +pdsh -w servers "df -h | grep -E '^/dev/'" + +# Check specific directory size +pdsh -w servers "du -sh /var/log" + +# Find large files +pdsh -w servers "find /var/log -type f -size +100M" + +# Clean up old logs +pdsh -w servers -S "sudo find /var/log -name '*.log' -mtime +30 -delete" + +# Check mount points +pdsh -w servers "mount | grep -E '^/dev/'" +``` + +### System Information + +```bash +# Kernel version +pdsh -w servers "uname -r" + +# OS version +pdsh -w servers "cat /etc/os-release | grep PRETTY_NAME" + +# CPU information +pdsh -w servers "lscpu | grep 'Model name'" + +# Memory information +pdsh -w servers "free -h | grep 'Mem:'" + +# System uptime +pdsh -w servers "uptime" +``` + +## Deployment and Configuration + +### Application Deployment + +```bash +# Pull latest code +pdsh -w app-servers -l deploy "cd /app && git pull origin main" + +# Build application +pdsh -w app-servers -l deploy "cd /app && npm install && npm run build" + +# Restart application +pdsh -w app-servers -S "sudo systemctl restart myapp" + +# Verify deployment +pdsh -w app-servers "curl -s http://localhost:3000/health | jq .version" + +# Rollback if needed +pdsh -w app-servers -l deploy "cd /app && git checkout v1.2.3 && npm run build" +``` + +### Configuration Management + +```bash +# Copy configuration file to all hosts +for host in $(pdsh -w web[1-5] -q); do + scp nginx.conf $host:/tmp/ + ssh $host "sudo mv /tmp/nginx.conf /etc/nginx/ && sudo systemctl reload nginx" +done + +# Update configuration value +pdsh -w app-servers -S "sudo sed -i 's/^PORT=.*/PORT=8080/' /etc/myapp/config" + +# Validate configuration +pdsh -w webservers "nginx -t" + +# Backup configurations +pdsh -w servers --output-dir ./config-backup "cat /etc/myapp/config" + +# Compare configurations across hosts +pdsh -w web1,web2 "md5sum /etc/nginx/nginx.conf" +``` + +### SSL Certificate Deployment + +```bash +# Copy certificates +for host in $(pdsh -w webservers -q); do + scp cert.pem key.pem $host:/tmp/ + ssh $host "sudo mv /tmp/cert.pem /tmp/key.pem /etc/ssl/ && sudo chmod 600 /etc/ssl/key.pem" +done + +# Update certificate paths in config +pdsh -w webservers -S "sudo systemctl restart nginx" + +# Verify certificates +pdsh -w webservers "sudo openssl x509 -in /etc/ssl/cert.pem -noout -dates" +``` + +## Monitoring and Diagnostics + +### Performance Monitoring + +```bash +# CPU usage +pdsh -w servers "top -bn1 | grep 'Cpu(s)'" + +# Memory usage +pdsh -w servers "free -m | awk 'NR==2{printf \"%.2f%%\", $3*100/$2}'" + +# Disk I/O +pdsh -w servers "iostat -x 1 5" + +# Network statistics +pdsh -w servers "ss -s" + +# Load average +pdsh -w servers "cat /proc/loadavg" +``` + +### Log Analysis + +```bash +# Search for errors in logs +pdsh -w servers "sudo grep -i error /var/log/syslog | tail -20" + +# Count error occurrences +pdsh -w app-servers "sudo grep -c 'ERROR' /var/log/myapp/app.log" + +# Find recent warnings +pdsh -w servers "sudo journalctl -p warning --since '1 hour ago' | tail -10" + +# Monitor active connections +pdsh -w webservers "ss -tan | awk '{print $1}' | sort | uniq -c" + +# Check for specific log patterns +pdsh -w servers "sudo grep '404' /var/log/nginx/access.log | wc -l" +``` + +### Health Checks + +```bash +# HTTP endpoint check +pdsh -w webservers "curl -s -o /dev/null -w '%{http_code}' http://localhost/" + +# Port check +pdsh -w servers "nc -zv localhost 3306" + +# Service status +pdsh -w app-servers "systemctl is-active myapp" + +# Process check +pdsh -w servers "pgrep -c nginx" + +# Disk space check +pdsh -w servers "df -h / | awk 'NR==2 {print $5}' | sed 's/%//'" +``` + +## Data Collection + +### Gathering System Metrics + +```bash +# Collect system info to files +pdsh -w servers --output-dir ./metrics-$(date +%Y%m%d) " + echo '=== System Info ===' && + uname -a && + echo '=== CPU ===' && + lscpu && + echo '=== Memory ===' && + free -h && + echo '=== Disk ===' && + df -h +" + +# Collect network statistics +pdsh -w servers --output-dir ./network-stats " + ss -tan state established | + awk '{print \$5}' | + cut -d: -f1 | + sort | + uniq -c | + sort -rn +" + +# Collect running processes +pdsh -w servers --output-dir ./processes "ps auxf" +``` + +### Inventory Management + +```bash +# Hardware inventory +pdsh -w servers -N " + echo \"Hostname: \$(hostname)\" + echo \"CPU: \$(lscpu | grep 'Model name' | cut -d: -f2 | xargs)\" + echo \"Memory: \$(free -h | awk 'NR==2{print \$2}')\" + echo \"Disk: \$(df -h / | awk 'NR==2{print \$2}')\" + echo '---' +" + +# Software inventory +pdsh -w servers "dpkg -l | grep -E 'nginx|postgresql|redis' | awk '{print \$2,\$3}'" + +# Network configuration +pdsh -w servers "ip -4 addr show | grep inet | awk '{print \$2}'" +``` + +### Log Collection + +```bash +# Collect last 100 lines of logs +pdsh -w app-servers --output-dir ./logs "sudo tail -100 /var/log/myapp/app.log" + +# Collect logs from specific time range +pdsh -w servers --output-dir ./system-logs " + sudo journalctl --since '2025-01-17 10:00' --until '2025-01-17 11:00' +" + +# Collect error logs only +pdsh -w webservers --output-dir ./error-logs " + sudo grep -i error /var/log/nginx/error.log +" +``` + +## Cluster Management + +### Database Cluster + +```bash +# Check database cluster status +pdsh -w db[1-3] "sudo -u postgres psql -c 'SELECT pg_is_in_recovery();'" + +# Perform vacuum +pdsh -w db[1-3] "sudo -u postgres psql -d mydb -c 'VACUUM ANALYZE;'" + +# Check replication lag +pdsh -w db-replica[1-2] " + sudo -u postgres psql -c ' + SELECT EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp())); + ' +" + +# Backup all databases +pdsh -w db-servers " + sudo -u postgres pg_dumpall | gzip > /backup/db-\$(hostname)-\$(date +%Y%m%d).sql.gz +" +``` + +### Web Server Cluster + +```bash +# Rolling restart with fanout=1 (one at a time) +pdsh -w web[1-10] -f 1 -S " + sudo systemctl restart nginx && + sleep 5 && + curl -f http://localhost/health +" + +# Update SSL certificates +pdsh -w web[1-5] -S " + sudo certbot renew && + sudo systemctl reload nginx +" + +# Check upstream health +pdsh -w web[1-5] " + curl -s http://localhost/status | jq .upstreams +" +``` + +### Cache Cluster + +```bash +# Redis cluster info +pdsh -w cache[1-6] "redis-cli INFO replication | grep role" + +# Flush cache on all nodes +pdsh -w cache[1-6] "redis-cli FLUSHALL" + +# Check memory usage +pdsh -w cache[1-6] "redis-cli INFO memory | grep used_memory_human" + +# Memcached stats +pdsh -w cache[1-4] "echo stats | nc localhost 11211 | grep 'STAT curr_items'" +``` + +## Advanced Patterns + +### Conditional Execution + +```bash +# Execute only if file exists +pdsh -w servers " + [ -f /etc/myapp/config ] && + sudo systemctl restart myapp || + echo 'Config file not found' +" + +# Check and install if missing +pdsh -w servers " + dpkg -l nginx >/dev/null 2>&1 || + sudo apt install -y nginx +" + +# Update only if version is old +pdsh -w servers " + current=\$(myapp --version | cut -d' ' -f2) + if [ \"\$current\" != \"2.0.0\" ]; then + sudo /opt/update-myapp.sh + fi +" +``` + +### Parallel File Transfer + +```bash +# Upload file to all hosts +for host in $(pdsh -w servers -q); do + scp localfile.txt $host:/tmp/ & +done +wait + +# Download files from all hosts +mkdir -p downloads +for host in $(pdsh -w servers -q); do + scp $host:/var/log/myapp.log downloads/$host-myapp.log & +done +wait + +# Sync directory to all hosts +for host in $(pdsh -w servers -q); do + rsync -avz ./app/ $host:/opt/app/ & +done +wait +``` + +### Failover and High Availability + +```bash +# Check primary and failover to secondary if down +pdsh -w db-primary "pg_isready" || +pdsh -w db-secondary -S "sudo -u postgres pg_ctl promote -D /var/lib/postgresql/data" + +# Health check with timeout +pdsh -w webservers -u 5 "curl -f -m 3 http://localhost/health" || +echo "Some web servers are unhealthy" + +# Graceful service migration +pdsh -w old-servers -f 1 " + # Drain connections + sudo systemctl stop myapp + sleep 30 +" && +pdsh -w new-servers -f 1 " + # Start service + sudo systemctl start myapp + sleep 5 +" +``` + +### Batch Processing + +```bash +# Process data files in parallel +pdsh -w worker[1-10] -f 5 " + /opt/process-data.sh /data/batch-\$(hostname).csv +" + +# Distributed grep across log files +pdsh -w servers " + zgrep 'ERROR' /var/log/app-\$(date -d yesterday +%Y%m%d).log.gz +" > aggregated-errors.txt + +# Parallel compression +pdsh -w servers " + find /var/log -name '*.log' -mtime +7 -exec gzip {} \\; +" +``` + +## Scripting Examples + +### Health Check Script + +```bash +#!/bin/bash +# health-check.sh - Check cluster health + +CLUSTER="production" +FAILED=0 + +echo "=== Cluster Health Check ===" +echo "Date: $(date)" +echo + +# Check all hosts are reachable +echo "Connectivity Check:" +if pdsh -w $CLUSTER -t 5 "echo ok" >/dev/null 2>&1; then + echo "✓ All hosts reachable" +else + echo "✗ Some hosts unreachable" + FAILED=1 +fi + +# Check disk space +echo +echo "Disk Space Check:" +pdsh -w $CLUSTER " + usage=\$(df -h / | awk 'NR==2 {print \$5}' | sed 's/%//') + if [ \$usage -gt 90 ]; then + echo \"\$(hostname): ✗ Disk usage: \${usage}%\" + exit 1 + else + echo \"\$(hostname): ✓ Disk usage: \${usage}%\" + fi +" || FAILED=1 + +# Check memory +echo +echo "Memory Check:" +pdsh -w $CLUSTER " + mem_available=\$(free -m | awk 'NR==2{printf \"%d\", \$7*100/\$2}') + if [ \$mem_available -lt 10 ]; then + echo \"\$(hostname): ✗ Low memory: \${mem_available}% available\" + exit 1 + else + echo \"\$(hostname): ✓ Memory: \${mem_available}% available\" + fi +" || FAILED=1 + +# Check critical services +echo +echo "Service Check:" +pdsh -w $CLUSTER " + systemctl is-active nginx >/dev/null 2>&1 || { + echo \"\$(hostname): ✗ nginx not running\" + exit 1 + } + echo \"\$(hostname): ✓ nginx running\" +" || FAILED=1 + +echo +if [ $FAILED -eq 0 ]; then + echo "=== All checks passed ===" + exit 0 +else + echo "=== Some checks failed ===" + exit 1 +fi +``` + +### Rolling Deployment Script + +```bash +#!/bin/bash +# rolling-deploy.sh - Deploy application with rolling restart + +CLUSTER="webservers" +APP_PATH="/opt/myapp" +VERSION="$1" + +if [ -z "$VERSION" ]; then + echo "Usage: $0 " + exit 1 +fi + +echo "=== Rolling Deployment ===" +echo "Cluster: $CLUSTER" +echo "Version: $VERSION" +echo + +# Get list of hosts +HOSTS=$(pdsh -w $CLUSTER -q) + +# Deploy to each host sequentially +for host in $HOSTS; do + echo "--- Deploying to $host ---" + + # Deploy new version + ssh $host " + cd $APP_PATH && + git fetch && + git checkout $VERSION && + npm install && + npm run build + " || { + echo "✗ Deployment failed on $host" + exit 1 + } + + # Restart service + ssh $host "sudo systemctl restart myapp" || { + echo "✗ Restart failed on $host" + exit 1 + } + + # Wait for health check + sleep 5 + if ssh $host "curl -f http://localhost:3000/health" >/dev/null 2>&1; then + echo "✓ $host is healthy" + else + echo "✗ Health check failed on $host" + exit 1 + fi + + echo +done + +echo "=== Deployment complete ===" +``` + +### Automated Backup Script + +```bash +#!/bin/bash +# cluster-backup.sh - Backup configurations and data from cluster + +CLUSTER="production" +BACKUP_DIR="/backups/$(date +%Y%m%d)" +mkdir -p "$BACKUP_DIR" + +echo "=== Cluster Backup ===" +echo "Cluster: $CLUSTER" +echo "Backup directory: $BACKUP_DIR" +echo + +# Backup system configurations +echo "Backing up configurations..." +pdsh -w $CLUSTER --output-dir "$BACKUP_DIR/configs" " + tar czf - /etc/nginx /etc/myapp 2>/dev/null +" || echo "Warning: Some config backups failed" + +# Backup application data +echo "Backing up application data..." +for host in $(pdsh -w $CLUSTER -q); do + echo " - $host" + ssh $host "sudo tar czf /tmp/app-data-$(hostname).tar.gz /var/lib/myapp" && + scp $host:/tmp/app-data-$(hostname).tar.gz "$BACKUP_DIR/" && + ssh $host "rm /tmp/app-data-$(hostname).tar.gz" +done + +# Backup databases +echo "Backing up databases..." +pdsh -w db-servers " + sudo -u postgres pg_dump mydb | gzip > /tmp/mydb-\$(hostname).sql.gz +" +for host in $(pdsh -w db-servers -q); do + scp $host:/tmp/mydb-$(hostname).sql.gz "$BACKUP_DIR/" + ssh $host "rm /tmp/mydb-$(hostname).sql.gz" +done + +# Create backup manifest +echo "Creating manifest..." +{ + echo "Backup Date: $(date)" + echo "Cluster: $CLUSTER" + echo "Files:" + ls -lh "$BACKUP_DIR" +} > "$BACKUP_DIR/MANIFEST.txt" + +echo +echo "=== Backup complete ===" +echo "Location: $BACKUP_DIR" +``` + +### Monitoring Script with Alerts + +```bash +#!/bin/bash +# monitor-cluster.sh - Monitor cluster and send alerts + +CLUSTER="production" +ALERT_EMAIL="ops@example.com" +ALERT_THRESHOLD_CPU=80 +ALERT_THRESHOLD_MEM=90 +ALERT_THRESHOLD_DISK=85 + +check_cpu() { + pdsh -w $CLUSTER " + cpu_usage=\$(top -bn1 | grep 'Cpu(s)' | awk '{print \$2}' | cut -d'%' -f1 | cut -d'.' -f1) + if [ \$cpu_usage -gt $ALERT_THRESHOLD_CPU ]; then + echo \"\$(hostname): CPU \${cpu_usage}%\" + fi + " +} + +check_memory() { + pdsh -w $CLUSTER " + mem_usage=\$(free | awk 'NR==2{printf \"%.0f\", \$3*100/\$2}') + if [ \$mem_usage -gt $ALERT_THRESHOLD_MEM ]; then + echo \"\$(hostname): Memory \${mem_usage}%\" + fi + " +} + +check_disk() { + pdsh -w $CLUSTER " + df -h | awk 'NR>1 { + usage=int(\$5) + if (usage > $ALERT_THRESHOLD_DISK) { + print \"\$(hostname): \" \$6 \" \" usage \"%\" + } + }' + " +} + +# Run checks +CPU_ALERTS=$(check_cpu) +MEM_ALERTS=$(check_memory) +DISK_ALERTS=$(check_disk) + +# Send alert if issues found +if [ -n "$CPU_ALERTS" ] || [ -n "$MEM_ALERTS" ] || [ -n "$DISK_ALERTS" ]; then + { + echo "Cluster Alerts - $(date)" + echo + [ -n "$CPU_ALERTS" ] && echo "CPU Alerts:" && echo "$CPU_ALERTS" + [ -n "$MEM_ALERTS" ] && echo "Memory Alerts:" && echo "$MEM_ALERTS" + [ -n "$DISK_ALERTS" ] && echo "Disk Alerts:" && echo "$DISK_ALERTS" + } | mail -s "Cluster Alert: $CLUSTER" "$ALERT_EMAIL" +fi +``` + +## See Also + +- [pdsh Migration Guide](pdsh-migration.md) - Migration instructions +- [pdsh Options Reference](pdsh-options.md) - Complete option mapping +- [bssh README](../README.md) - Full feature documentation + +--- + +**Note**: This document is maintained as part of the bssh project. For the latest version, see https://github.com/lablup/bssh/blob/main/docs/pdsh-examples.md diff --git a/docs/pdsh-migration.md b/docs/pdsh-migration.md new file mode 100644 index 00000000..38a34b42 --- /dev/null +++ b/docs/pdsh-migration.md @@ -0,0 +1,568 @@ +# Migrating from pdsh to bssh + +This guide helps users transition from pdsh to bssh while maintaining existing workflows and scripts. + +## Table of Contents + +- [Why Migrate to bssh?](#why-migrate-to-bssh) +- [Compatibility Overview](#compatibility-overview) +- [Installation](#installation) +- [Enabling pdsh Compatibility Mode](#enabling-pdsh-compatibility-mode) +- [Command Migration](#command-migration) +- [Feature Comparison](#feature-comparison) +- [Known Differences](#known-differences) +- [Migration Checklist](#migration-checklist) +- [Troubleshooting](#troubleshooting) + +## Why Migrate to bssh? + +bssh provides several advantages over pdsh: + +- **Modern Architecture**: Built with Rust for memory safety and performance +- **Enhanced Features**: Interactive TUI, real-time progress tracking, SSH agent support +- **Active Development**: Regular updates and security patches +- **Cross-Platform**: Native support for Linux and macOS +- **Full pdsh Compatibility**: Drop-in replacement with compatibility mode +- **Better Error Handling**: Detailed error messages and graceful failure handling + +## Compatibility Overview + +bssh provides **three ways** to run in pdsh compatibility mode: + +1. **Symlink** (recommended for full compatibility) +2. **Environment variable** +3. **CLI flag** + +All three methods enable the same pdsh-compatible behavior, mapping pdsh options to their bssh equivalents automatically. + +## Installation + +### Homebrew (macOS/Linux) + +```bash +# Install bssh via Homebrew +brew tap lablup/tap +brew install bssh + +# Create pdsh symlink (done automatically by Homebrew) +# The symlink is created at: /usr/local/bin/pdsh -> /usr/local/bin/bssh +``` + +### Ubuntu PPA + +```bash +# Add the PPA and install +sudo add-apt-repository ppa:lablup/backend-ai +sudo apt update +sudo apt install bssh + +# Create pdsh symlink manually +sudo ln -sf $(which bssh) /usr/local/bin/pdsh +``` + +### Debian Package + +```bash +# Download and install .deb package +wget https://github.com/lablup/bssh/releases/download/vVERSION/bssh_VERSION_OS_ARCH.deb +sudo dpkg -i bssh_VERSION_OS_ARCH.deb + +# Create pdsh symlink +sudo ln -sf $(which bssh) /usr/local/bin/pdsh +``` + +### From Source + +```bash +# Build from source +cargo build --release +sudo cp target/release/bssh /usr/local/bin/ + +# Create pdsh symlink +sudo ln -s /usr/local/bin/bssh /usr/local/bin/pdsh +``` + +### Verify Installation + +```bash +# Check that pdsh points to bssh +which pdsh +# Output: /usr/local/bin/pdsh + +ls -l $(which pdsh) +# Output: /usr/local/bin/pdsh -> /usr/local/bin/bssh + +# Verify version +pdsh --version +# Output: bssh X.Y.Z (in pdsh compatibility mode) +``` + +## Enabling pdsh Compatibility Mode + +### Method 1: Symlink (Recommended) + +Create a symlink named `pdsh` pointing to `bssh`. This is the most transparent method and requires no script modifications. + +```bash +# System-wide installation +sudo ln -sf $(which bssh) /usr/local/bin/pdsh + +# User installation (in $HOME/bin or similar) +ln -sf $(which bssh) ~/bin/pdsh +``` + +With the symlink in place, all your existing pdsh commands work unchanged: + +```bash +pdsh -w host1,host2,host3 "uptime" +# Automatically runs in pdsh compatibility mode +``` + +### Method 2: Environment Variable + +Set `BSSH_PDSH_COMPAT=1` to enable pdsh mode without a symlink: + +```bash +# For a single command +BSSH_PDSH_COMPAT=1 bssh -w host1,host2 "uptime" + +# Export for the entire session +export BSSH_PDSH_COMPAT=1 +bssh -w host1,host2 "uptime" + +# Add to shell configuration for permanent enablement +echo 'export BSSH_PDSH_COMPAT=1' >> ~/.bashrc +``` + +### Method 3: CLI Flag + +Use the `--pdsh-compat` flag explicitly: + +```bash +bssh --pdsh-compat -w host1,host2 "uptime" +``` + +### Method 4: Shell Alias + +For users who prefer aliases over symlinks: + +```bash +# Bash/Zsh +echo 'alias pdsh="bssh --pdsh-compat"' >> ~/.bashrc +# or +echo 'alias pdsh="bssh --pdsh-compat"' >> ~/.zshrc + +# Fish +echo 'alias pdsh="bssh --pdsh-compat"' >> ~/.config/fish/config.fish + +# Reload shell configuration +source ~/.bashrc # or ~/.zshrc +``` + +## Command Migration + +### Basic Command Execution + +| pdsh Command | bssh Equivalent | Notes | +|--------------|-----------------|-------| +| `pdsh -w host1,host2 "cmd"` | Same | Works unchanged with symlink | +| `pdsh -w host[1-5] "cmd"` | Same | Hostlist expressions supported | +| `pdsh -w ^/etc/hosts.list "cmd"` | Same | File input supported | + +### Fanout Control + +| pdsh Command | bssh Equivalent | Notes | +|--------------|-----------------|-------| +| `pdsh -w hosts -f 10 "cmd"` | Same | `-f` maps to `--parallel` | +| `pdsh -w hosts -f 1 "cmd"` | Same | Sequential execution | + +### Host Exclusion + +| pdsh Command | bssh Equivalent | Notes | +|--------------|-----------------|-------| +| `pdsh -w host[1-10] -x host5 "cmd"` | Same | Direct mapping | +| `pdsh -w hosts -x "bad*" "cmd"` | Same | Glob patterns supported | + +### User Specification + +| pdsh Command | bssh Equivalent | Notes | +|--------------|-----------------|-------| +| `pdsh -w hosts -l admin "cmd"` | Same | `-l` works identically | +| `pdsh -w user@host "cmd"` | Same | User@host syntax supported | + +### Timeouts + +| pdsh Command | bssh Equivalent | Notes | +|--------------|-----------------|-------| +| `pdsh -w hosts -t 30 "cmd"` | Same | Connect timeout (seconds) | +| `pdsh -w hosts -u 600 "cmd"` | Same | Command timeout (seconds) | + +### Output Control + +| pdsh Command | bssh Equivalent | Notes | +|--------------|-----------------|-------| +| `pdsh -w hosts -N "cmd"` | Same | Disable hostname prefix | +| `pdsh -w hosts "cmd" \| dshbak` | Use `--stream` or TUI | bssh has built-in formatting | + +### Query Mode + +| pdsh Command | bssh Equivalent | Notes | +|--------------|-----------------|-------| +| `pdsh -w hosts -q` | Same | Show target hosts and exit | +| `pdsh -w hosts -x "bad*" -q` | Same | Query with exclusions | + +### Batch Mode + +| pdsh Command | bssh Equivalent | Notes | +|--------------|-----------------|-------| +| `pdsh -w hosts -b "cmd"` | Same | Single Ctrl+C terminates | + +### Fail-Fast Mode + +| pdsh Command | bssh Equivalent | Notes | +|--------------|-----------------|-------| +| `pdsh -w hosts -k "cmd"` | Same | Stop on first failure | + +### Exit Code Handling + +| pdsh Command | bssh Equivalent | Notes | +|--------------|-----------------|-------| +| `pdsh -w hosts -S "cmd"` | Same | Return largest exit code | + +## Feature Comparison + +### Features Available in Both + +| Feature | pdsh | bssh | Notes | +|---------|------|------|-------| +| Basic command execution | ✅ | ✅ | Identical behavior | +| Hostlist expressions | ✅ | ✅ | `host[1-5]`, `rack[1-2]-node[1-3]` | +| Host exclusion | ✅ | ✅ | `-x` with glob patterns | +| Fanout control | ✅ | ✅ | `-f` for parallel connections | +| User specification | ✅ | ✅ | `-l user` option | +| Timeouts | ✅ | ✅ | Connect and command timeouts | +| Query mode | ✅ | ✅ | `-q` to list hosts | +| Batch mode | ✅ | ✅ | `-b` for single Ctrl+C | +| Fail-fast mode | ✅ | ✅ | `-k` to stop on failure | +| No prefix output | ✅ | ✅ | `-N` flag | + +### bssh-Exclusive Features + +| Feature | Description | +|---------|-------------| +| **Interactive TUI** | Real-time multi-node monitoring with Summary/Detail/Split/Diff views | +| **Progress Tracking** | Automatic detection of progress indicators (%, fractions, apt/dpkg) | +| **SSH Agent Support** | Native SSH agent authentication | +| **Jump Hosts** | ProxyJump support for bastion hosts | +| **Port Forwarding** | Local, remote, and dynamic (SOCKS) forwarding | +| **Configuration Files** | YAML-based cluster configuration | +| **Modern Output Modes** | TUI, stream, file, and normal modes with auto-detection | +| **Exit Code Strategies** | Multiple strategies for handling node failures | +| **Sudo Password Injection** | Automatic sudo password handling | +| **Backend.AI Integration** | Auto-detection of Backend.AI multi-node sessions | + +### pdsh Features Not in bssh + +| Feature | Alternative in bssh | +|---------|---------------------| +| RCMD modules (rsh, ssh, mrsh) | bssh uses SSH only (more secure) | +| GENDERS integration | Use YAML config files | +| dshbak output collation | Use `--stream` mode or built-in TUI | +| SLURM/TORQUE integration | Use host lists or config files | + +## Known Differences + +### 1. Default Fanout + +- **pdsh**: Default fanout is 32 +- **bssh**: Default parallel is 10 (native mode), 32 (pdsh mode) + +**Migration**: When using pdsh compatibility mode, bssh automatically uses fanout=32 to match pdsh behavior. + +### 2. Output Formatting + +- **pdsh**: Plain text with optional dshbak post-processing +- **bssh**: Advanced TUI with real-time updates (when running in terminal) + +**Migration**: +- Use `--stream` flag for pdsh-like plain output +- Use `-N` for no hostname prefix (matches pdsh behavior) +- Pipe to file/command to disable TUI auto-detection + +```bash +# Plain output like pdsh +pdsh -w hosts --stream "cmd" + +# Or disable TUI by piping +pdsh -w hosts "cmd" | cat +``` + +### 3. RCMD Modules + +- **pdsh**: Supports multiple RCMD modules (ssh, rsh, mrsh, qsh) +- **bssh**: SSH only (more secure, widely available) + +**Migration**: Ensure all target hosts support SSH. bssh will not work with rsh-based hosts. + +### 4. Exit Code Behavior + +- **pdsh**: Returns 0 on success, 1 if any host fails +- **bssh**: Returns main rank exit code by default (v1.2.0+), supports multiple strategies + +**Migration**: +- Add `--require-all-success` flag for pdsh-like behavior +- Or use `-S` (equivalent to `--any-failure`) to return largest exit code + +```bash +# pdsh-style behavior (fail if any host fails) +pdsh -w hosts --require-all-success "cmd" + +# Return largest exit code from any host +pdsh -w hosts -S "cmd" +``` + +### 5. Cluster Configuration + +- **pdsh**: Uses GENDERS, SLURM, or flat files +- **bssh**: Uses YAML configuration files + +**Migration**: Convert cluster definitions to YAML format: + +```yaml +# ~/.config/bssh/config.yaml +clusters: + production: + nodes: + - web1.example.com + - web2.example.com + - web3.example.com + user: admin + ssh_key: ~/.ssh/prod_key +``` + +Then use with `-C` flag: + +```bash +# Using config file +bssh -C production "uptime" + +# Still works with -w +pdsh -w web[1-3].example.com "uptime" +``` + +## Migration Checklist + +### Pre-Migration + +- [ ] Identify all scripts and tools using pdsh +- [ ] Test bssh with pdsh compatibility mode on non-production hosts +- [ ] Verify all target hosts support SSH (not rsh/mrsh) +- [ ] Check for GENDERS/SLURM integration (migrate to YAML config) +- [ ] Review custom pdsh wrappers and automation + +### Installation + +- [ ] Install bssh via preferred method (Homebrew, apt, cargo) +- [ ] Create pdsh symlink (`ln -sf $(which bssh) /usr/local/bin/pdsh`) +- [ ] Verify symlink: `which pdsh` should point to bssh +- [ ] Test basic command: `pdsh -w localhost "echo test"` + +### Configuration Migration + +- [ ] Convert GENDERS files to bssh YAML format +- [ ] Migrate cluster definitions to `~/.config/bssh/config.yaml` +- [ ] Test cluster access: `bssh -C "uptime"` +- [ ] Configure SSH keys and authentication methods +- [ ] Set up any required environment variables + +### Script Migration + +- [ ] Audit all scripts for pdsh usage +- [ ] Test scripts with pdsh symlink (should work unchanged) +- [ ] Update scripts using dshbak to use `--stream` or TUI +- [ ] Add `--require-all-success` flag where needed +- [ ] Update documentation and comments + +### Testing + +- [ ] Test basic command execution across all clusters +- [ ] Verify fanout/parallel behavior +- [ ] Test host exclusion patterns +- [ ] Validate timeout behavior +- [ ] Check query mode functionality +- [ ] Test error handling and exit codes +- [ ] Verify batch mode and fail-fast behavior + +### Post-Migration + +- [ ] Monitor for any edge cases or unexpected behavior +- [ ] Update team documentation and runbooks +- [ ] Train team members on bssh-specific features (TUI, config files) +- [ ] Consider removing old pdsh installations +- [ ] Document any bssh-specific optimizations + +## Troubleshooting + +### pdsh symlink not working + +**Problem**: Running `pdsh` doesn't invoke bssh + +**Solution**: +```bash +# Check if symlink exists and is correct +ls -l $(which pdsh) + +# Recreate symlink +sudo ln -sf $(which bssh) /usr/local/bin/pdsh + +# Ensure /usr/local/bin is in PATH +echo $PATH | grep -o '/usr/local/bin' +``` + +### pdsh options not recognized + +**Problem**: `pdsh: error: unrecognized option: -w` + +**Solution**: Ensure pdsh compatibility mode is active: +```bash +# Check if running in compatibility mode +pdsh --version +# Should show: "bssh X.Y.Z (pdsh compatibility mode)" or similar + +# Manually enable compatibility mode +BSSH_PDSH_COMPAT=1 bssh -w hosts "cmd" +``` + +### Different output format + +**Problem**: Output looks different from pdsh + +**Solution**: +```bash +# Use stream mode for plain output +pdsh -w hosts --stream "cmd" + +# Disable hostname prefix +pdsh -w hosts -N "cmd" + +# Pipe to disable TUI +pdsh -w hosts "cmd" | cat +``` + +### Hostlist expressions not working + +**Problem**: `host[1-5]` not expanding properly + +**Solution**: Ensure compatibility mode is enabled and check syntax: +```bash +# Query mode to verify expansion +pdsh -w "host[1-5]" -q + +# Should output: +# host1 +# host2 +# host3 +# host4 +# host5 +``` + +### Exit code behavior differs + +**Problem**: Exit codes don't match pdsh expectations + +**Solution**: +```bash +# Add --require-all-success for pdsh-like behavior +pdsh -w hosts --require-all-success "cmd" + +# Or use -S to return largest exit code +pdsh -w hosts -S "cmd" +``` + +### Authentication issues + +**Problem**: SSH authentication fails + +**Solution**: +```bash +# Enable SSH agent +pdsh -A -w hosts "cmd" + +# Use specific SSH key +pdsh -i ~/.ssh/key -w hosts "cmd" + +# Enable verbose logging +pdsh -vv -w hosts "cmd" +``` + +### Performance differences + +**Problem**: bssh seems slower than pdsh + +**Solution**: +```bash +# Increase parallel connections +pdsh -w hosts -f 50 "cmd" + +# Use stream mode instead of TUI +pdsh -w hosts --stream "cmd" + +# Check connection timeout +pdsh -w hosts -t 10 "cmd" +``` + +### File input not working + +**Problem**: `pdsh -w ^/path/to/hosts` doesn't work + +**Solution**: This feature may not be supported in the same way. Use: +```bash +# Alternative: Read hosts and pass directly +HOSTS=$(cat /path/to/hosts | tr '\n' ',' | sed 's/,$//') +pdsh -w "$HOSTS" "cmd" + +# Or use bssh config file +bssh -C cluster-name "cmd" +``` + +## Getting Help + +### Resources + +- **Documentation**: https://github.com/lablup/bssh/blob/main/README.md +- **Issue Tracker**: https://github.com/lablup/bssh/issues +- **Architecture Guide**: https://github.com/lablup/bssh/blob/main/ARCHITECTURE.md +- **Option Mapping**: See [pdsh-options.md](pdsh-options.md) +- **Examples**: See [pdsh-examples.md](pdsh-examples.md) + +### Community Support + +- Open an issue on GitHub for bugs or feature requests +- Check existing issues for known problems and workarounds +- Contribute improvements via pull requests + +### Reporting Issues + +When reporting issues, please include: + +1. **bssh version**: `bssh --version` +2. **Compatibility mode status**: How pdsh mode was enabled (symlink/env/flag) +3. **Command that failed**: Full command line +4. **Expected vs actual behavior** +5. **Error messages**: Include full error output +6. **Environment**: OS, shell, SSH version + +Example bug report: +``` +**Version**: bssh 1.4.0 +**Mode**: pdsh symlink (/usr/local/bin/pdsh -> /usr/local/bin/bssh) +**Command**: pdsh -w host[1-3] -f 10 "uptime" +**Expected**: Output from 3 hosts +**Actual**: Error: "failed to parse hostlist expression" +**Error**: +**Environment**: Ubuntu 22.04, bash 5.1.16, OpenSSH 8.9p1 +``` + +--- + +**Note**: This migration guide is maintained as part of the bssh project. For the latest version, see https://github.com/lablup/bssh/blob/main/docs/pdsh-migration.md diff --git a/docs/pdsh-options.md b/docs/pdsh-options.md new file mode 100644 index 00000000..004a513f --- /dev/null +++ b/docs/pdsh-options.md @@ -0,0 +1,802 @@ +# pdsh Options Mapping Reference + +Complete reference for mapping pdsh command-line options to bssh equivalents. + +## Table of Contents + +- [Quick Reference Table](#quick-reference-table) +- [Host Selection Options](#host-selection-options) +- [Execution Control Options](#execution-control-options) +- [Timeout Options](#timeout-options) +- [Output Control Options](#output-control-options) +- [Authentication Options](#authentication-options) +- [Query and Information Options](#query-and-information-options) +- [Exit Code Options](#exit-code-options) +- [Options Not Supported](#options-not-supported) +- [bssh-Specific Extensions](#bssh-specific-extensions) + +## Quick Reference Table + +| pdsh Option | bssh Equivalent | Compatibility Mode | Notes | +|-------------|-----------------|-------------------|-------| +| `-w ` | `-H ` | `-w ` | Target host specification | +| `-x ` | `--exclude ` | `-x ` | Exclude hosts from target list | +| `-f ` | `--parallel ` | `-f ` | Fanout / parallel connections | +| `-l ` | `-l ` | `-l ` | Remote username | +| `-t ` | `--connect-timeout ` | `-t ` | Connection timeout (seconds) | +| `-u ` | `--timeout ` | `-u ` | Command timeout (seconds) | +| `-N` | `--no-prefix` | `-N` | Disable hostname prefix | +| `-b` | `--batch` | `-b` | Batch mode (immediate Ctrl+C) | +| `-k` | `--fail-fast` | `-k` | Stop on first failure | +| `-q` | (query mode) | `-q` | Show hosts and exit | +| `-S` | `--any-failure` | `-S` | Return largest exit code | + +**Note**: "Compatibility Mode" shows the option syntax when running `bssh --pdsh-compat` or when invoked as `pdsh` via symlink. + +## Host Selection Options + +### `-w ` (--hosts) + +**pdsh**: Specifies target hosts + +**bssh Native**: `-H ` or `--hosts ` + +**pdsh Compat**: `-w ` + +**Syntax**: +```bash +# Simple comma-separated list +pdsh -w host1,host2,host3 "command" + +# Hostlist range expressions +pdsh -w host[1-5] "command" # host1, host2, host3, host4, host5 +pdsh -w node[01-10] "command" # Zero-padded: node01..node10 +pdsh -w rack[1-2]-node[1-3] "command" # Cartesian product: 6 hosts + +# User@host syntax +pdsh -w user1@host1,user2@host2 "command" + +# With port specification +pdsh -w host1:2222,host2:2222 "command" + +# File input (if supported) +pdsh -w ^/path/to/hostfile "command" +``` + +**Examples**: +```bash +# Single host +pdsh -w webserver "uptime" + +# Multiple hosts +pdsh -w web1,web2,db1,db2 "df -h" + +# Range expansion +pdsh -w compute[01-20] "nvidia-smi" + +# Complex expression +pdsh -w cluster[1-3]-node[01-08] "hostname" +# Expands to: cluster1-node01, cluster1-node02, ..., cluster3-node08 +``` + +### `-x ` (--exclude) + +**pdsh**: Exclude hosts from the target list + +**bssh Native**: `--exclude ` + +**pdsh Compat**: `-x ` + +**Syntax**: +```bash +# Exclude specific hosts +pdsh -w host[1-10] -x host5,host7 "command" + +# Exclude with wildcards (glob patterns) +pdsh -w host[1-10] -x "host[3-5]" "command" +pdsh -w web1,web2,db1,db2 -x "db*" "command" + +# Exclude with range expressions +pdsh -w node[1-100] -x "node[50-75]" "command" +``` + +**Examples**: +```bash +# Exclude maintenance hosts +pdsh -w prod[1-20] -x prod15,prod16 "systemctl status nginx" + +# Exclude all database servers +pdsh -w web1,web2,web3,db1,db2,db3 -x "db*" "uptime" + +# Exclude a range +pdsh -w compute[001-100] -x "compute[080-100]" "check-gpu.sh" +``` + +### `-g ` (--group) [Not Supported] + +**pdsh**: Select host group from GENDERS file + +**bssh Alternative**: Use cluster configuration in `~/.config/bssh/config.yaml` + +**Migration**: +```bash +# pdsh with GENDERS +pdsh -g webservers "command" + +# bssh equivalent +bssh -C webservers "command" +``` + +**Config file** (`~/.config/bssh/config.yaml`): +```yaml +clusters: + webservers: + nodes: + - web1.example.com + - web2.example.com + - web3.example.com +``` + +## Execution Control Options + +### `-f ` (--fanout) + +**pdsh**: Set fanout (maximum parallel connections) + +**bssh Native**: `--parallel ` + +**pdsh Compat**: `-f ` + +**Default**: +- pdsh: 32 +- bssh native: 10 +- bssh pdsh mode: 32 + +**Syntax**: +```bash +# Limit to 10 concurrent connections +pdsh -w host[1-100] -f 10 "command" + +# No limit (maximum parallelism) +pdsh -w hosts -f 0 "command" + +# Sequential execution (one at a time) +pdsh -w hosts -f 1 "command" +``` + +**Examples**: +```bash +# Conservative fanout for heavy operations +pdsh -w nodes -f 5 "apt upgrade -y" + +# High fanout for quick checks +pdsh -w servers -f 50 "uptime" + +# Sequential for ordered operations +pdsh -w backup[1-3] -f 1 "rsync-backup.sh" +``` + +**Performance Notes**: +- Higher fanout = faster completion, but more load on local system +- Lower fanout = slower completion, but gentler resource usage +- Optimal fanout depends on: network bandwidth, target load, command type + +### `-b` (--batch) + +**pdsh**: Batch mode - single Ctrl+C terminates all jobs + +**bssh Native**: `--batch` + +**pdsh Compat**: `-b` + +**Behavior**: +- **Without `-b`**: First Ctrl+C shows status, second Ctrl+C terminates (default) +- **With `-b`**: Single Ctrl+C immediately terminates all jobs + +**Syntax**: +```bash +# Batch mode for scripts +pdsh -w hosts -b "long-running-command" +``` + +**Examples**: +```bash +# In CI/CD pipelines +pdsh -w servers -b "deploy-script.sh" + +# For automation where immediate termination is needed +pdsh -w nodes -b --timeout 600 "backup-operation" +``` + +### `-k` (--fail-fast) + +**pdsh**: Stop execution on first failure + +**bssh Native**: `--fail-fast` + +**pdsh Compat**: `-k` + +**Behavior**: +- Cancels remaining commands if any host fails +- Useful for critical operations that require all hosts to succeed + +**Syntax**: +```bash +# Stop if any host fails +pdsh -w hosts -k "critical-deployment.sh" +``` + +**Examples**: +```bash +# Critical security update +pdsh -w production -k "apply-security-patch.sh" + +# Database schema migration (must succeed on all) +pdsh -w db-cluster -k "migrate-schema.sql" + +# Combined with --require-all-success +pdsh -w hosts -k --require-all-success "health-check.sh" +``` + +## Timeout Options + +### `-t ` (--connect-timeout) + +**pdsh**: SSH connection timeout + +**bssh Native**: `--connect-timeout ` + +**pdsh Compat**: `-t ` + +**Default**: 30 seconds (both pdsh and bssh) + +**Syntax**: +```bash +# Short timeout for fast failure detection +pdsh -w hosts -t 5 "command" + +# Longer timeout for slow networks +pdsh -w remote-hosts -t 60 "command" +``` + +**Examples**: +```bash +# Quick connectivity test +pdsh -w datacenter[1-100] -t 3 "echo ok" + +# Reliable connection over WAN +pdsh -w cloud-instances -t 90 "deploy.sh" +``` + +### `-u ` (--command-timeout) + +**pdsh**: Command execution timeout + +**bssh Native**: `--timeout ` + +**pdsh Compat**: `-u ` + +**Default**: 300 seconds (5 minutes) + +**Syntax**: +```bash +# Short timeout for quick commands +pdsh -w hosts -u 10 "uptime" + +# Long timeout for slow operations +pdsh -w hosts -u 3600 "backup-database.sh" + +# No timeout (unlimited execution) +pdsh -w hosts -u 0 "indefinite-process" +``` + +**Examples**: +```bash +# Quick health check with short timeout +pdsh -w webservers -u 5 "curl -s http://localhost/health" + +# Long-running backup operation +pdsh -w databases -u 7200 "pg_dump | gzip > backup.sql.gz" + +# Continuous monitoring (no timeout) +pdsh -w monitors -u 0 "tail -f /var/log/app.log" +``` + +**Combined Timeout Example**: +```bash +# 10s to connect, 600s to run command +pdsh -w hosts -t 10 -u 600 "deploy-application.sh" +``` + +## Output Control Options + +### `-N` (--no-prefix) + +**pdsh**: Disable hostname prefix in output + +**bssh Native**: `--no-prefix` + +**pdsh Compat**: `-N` + +**Behavior**: +- **Without `-N`**: `[hostname] output line` +- **With `-N`**: `output line` (no prefix) + +**Syntax**: +```bash +# No hostname prefix +pdsh -w hosts -N "hostname" +``` + +**Examples**: +```bash +# Default behavior (with prefix) +pdsh -w web[1-2] "echo hello" +# Output: +# [web1] hello +# [web2] hello + +# With -N flag (no prefix) +pdsh -w web[1-2] -N "echo hello" +# Output: +# hello +# hello + +# Useful for parsing output +pdsh -w db-servers -N "SELECT COUNT(*) FROM users;" | awk '{sum+=$1} END {print sum}' +``` + +### `--stream` [bssh Extension] + +**pdsh**: N/A (use dshbak for output processing) + +**bssh Native**: `--stream` + +**pdsh Compat**: `--stream` + +**Behavior**: +- Forces stream mode (real-time output with prefixes) +- Disables TUI mode +- Useful when piping output + +**Syntax**: +```bash +# Stream mode with prefixes +pdsh -w hosts --stream "tail -f /var/log/syslog" + +# Stream mode without prefixes +pdsh -w hosts --stream -N "command" +``` + +**Examples**: +```bash +# Monitor logs in real-time +pdsh -w webservers --stream "tail -f /var/log/nginx/access.log" + +# Pipe to grep +pdsh -w servers --stream "systemctl status nginx" | grep "active (running)" +``` + +### `--output-dir ` [bssh Extension] + +**pdsh**: N/A + +**bssh Native**: `--output-dir ` + +**pdsh Compat**: `--output-dir ` + +**Behavior**: +- Saves each host's output to separate files +- Creates timestamped filenames + +**Syntax**: +```bash +pdsh -w hosts --output-dir ./results "command" +``` + +**Examples**: +```bash +# Save diagnostic outputs +pdsh -w servers --output-dir ./diagnostics-$(date +%Y%m%d) "system-info.sh" + +# Per-host logs +pdsh -w cluster --output-dir ./logs "journalctl -n 100" +``` + +**Output Structure**: +``` +results/ +├── host1_20250117_143022.stdout +├── host1_20250117_143022.stderr +├── host2_20250117_143022.stdout +└── summary_20250117_143022.txt +``` + +## Authentication Options + +### `-l ` (--login) + +**pdsh**: Specify remote username + +**bssh Native**: `-l ` or `--user ` + +**pdsh Compat**: `-l ` + +**Syntax**: +```bash +# Specify username +pdsh -w hosts -l admin "command" + +# Alternative: user@host syntax +pdsh -w admin@host1,admin@host2 "command" +``` + +**Examples**: +```bash +# Admin operations +pdsh -w production -l root "systemctl restart nginx" + +# User-specific commands +pdsh -w devservers -l deploy "cd /app && git pull" +``` + +### `-i ` [bssh Extension] + +**pdsh**: N/A (uses SSH config) + +**bssh Native**: `-i ` or `--identity ` + +**pdsh Compat**: `-i ` + +**Syntax**: +```bash +# Use specific SSH key +pdsh -w hosts -i ~/.ssh/production_key "command" +``` + +**Examples**: +```bash +# Production key +pdsh -w prod-servers -i ~/.ssh/prod_rsa "deploy.sh" + +# Encrypted key (will prompt for passphrase) +pdsh -w servers -i ~/.ssh/encrypted_key "command" +``` + +### `-A` (--use-agent) [bssh Extension] + +**pdsh**: N/A (auto-detects agent) + +**bssh Native**: `-A` or `--use-agent` + +**pdsh Compat**: `-A` + +**Syntax**: +```bash +# Use SSH agent +pdsh -A -w hosts "command" +``` + +**Examples**: +```bash +# Force agent authentication +pdsh -A -w secure-hosts "sensitive-operation.sh" + +# Combined with sudo +pdsh -A -S -w servers "sudo apt update" +``` + +### `-P` (--password) [bssh Extension] + +**pdsh**: N/A + +**bssh Native**: `-P` or `--password` + +**pdsh Compat**: `-P` + +**Behavior**: Prompts for SSH password (not recommended for scripts) + +**Syntax**: +```bash +# Password authentication +pdsh -P -w hosts "command" +# Prompts: "Enter SSH password:" +``` + +### `-S` (--sudo-password) [bssh Extension] + +**pdsh**: N/A + +**bssh Native**: `-S` or `--sudo-password` + +**pdsh Compat**: `-S` + +**Behavior**: Prompts for sudo password and auto-injects it + +**Syntax**: +```bash +# Sudo password injection +pdsh -S -w hosts "sudo apt update" +# Prompts: "Enter sudo password:" +``` + +**Examples**: +```bash +# System updates with sudo +pdsh -S -w servers "sudo systemctl restart nginx" + +# Combined with SSH agent +pdsh -A -S -w hosts "sudo reboot" +``` + +## Query and Information Options + +### `-q` (--query) + +**pdsh**: Show target hosts and exit (do not execute command) + +**bssh Native**: (query mode) + +**pdsh Compat**: `-q` + +**Behavior**: +- Lists all hosts that would be targeted +- Applies exclusions and filters +- Does not execute any commands + +**Syntax**: +```bash +# Query mode +pdsh -w host[1-10] -q + +# Query with exclusions +pdsh -w host[1-10] -x host[3-5] -q +``` + +**Examples**: +```bash +# Verify hostlist expansion +pdsh -w compute[01-20] -q +# Output: +# compute01 +# compute02 +# ... +# compute20 + +# Check exclusion pattern +pdsh -w web1,web2,db1,db2,cache1 -x "db*,cache*" -q +# Output: +# web1 +# web2 + +# Verify final host list before execution +pdsh -w production[1-50] -x "production[10-15]" -q +``` + +**Use Cases**: +- Verify hostlist expressions expand correctly +- Check that exclusion patterns work as expected +- Audit target hosts before running destructive commands +- Debug host selection issues + +### `-V` (--version) + +**pdsh**: Show version information + +**bssh Native**: `-V` or `--version` + +**pdsh Compat**: `-V` + +**Syntax**: +```bash +pdsh --version +``` + +**Example Output** (when running as pdsh): +``` +bssh 1.4.2 (pdsh compatibility mode) +``` + +### `-h` (--help) + +**pdsh**: Show help message + +**bssh Native**: `-h` or `--help` + +**pdsh Compat**: `-h` + +**Syntax**: +```bash +pdsh --help +``` + +## Exit Code Options + +### `-S` (--any-failure) + +**pdsh**: Return exit status of any failing remote command + +**bssh Native**: `--any-failure` + +**pdsh Compat**: `-S` + +**Behavior**: +- Returns the **largest** exit code from any host +- If all succeed (exit 0), returns 0 +- If any fail, returns the highest failure code + +**Syntax**: +```bash +pdsh -w hosts -S "command" +``` + +**Examples**: +```bash +# Capture worst failure +pdsh -w servers -S "health-check.sh" +# Exit codes: host1=0, host2=1, host3=2 +# pdsh returns: 2 + +# Use in scripts +if pdsh -w cluster -S "verify.sh"; then + echo "All hosts OK" +else + echo "At least one host failed (exit code: $?)" +fi +``` + +### `--require-all-success` [bssh Extension] + +**pdsh**: N/A (pdsh default behavior is similar) + +**bssh Native**: `--require-all-success` + +**pdsh Compat**: `--require-all-success` + +**Behavior**: +- Returns 0 only if **all** hosts succeed +- Returns 1 if **any** host fails +- Similar to traditional pdsh behavior + +**Syntax**: +```bash +pdsh -w hosts --require-all-success "command" +``` + +**Examples**: +```bash +# Health check requiring all to pass +pdsh -w production --require-all-success "health-check.sh" + +# Combined with fail-fast +pdsh -w hosts -k --require-all-success "critical-operation.sh" +``` + +### `--check-all-nodes` [bssh Extension] + +**pdsh**: N/A + +**bssh Native**: `--check-all-nodes` + +**pdsh Compat**: `--check-all-nodes` + +**Behavior**: +- Returns main rank's exit code if main rank fails +- Returns 1 if main rank succeeds but others fail +- Useful for MPI-like workloads + +**Syntax**: +```bash +pdsh -w hosts --check-all-nodes "mpirun ./simulation" +``` + +## Options Not Supported + +The following pdsh options are **not supported** in bssh: + +### RCMD Module Options + +| Option | Description | Alternative | +|--------|-------------|-------------| +| `-R ` | Select RCMD module | bssh uses SSH only | +| `-M ,,...` | Load specific modules | N/A | + +**Reason**: bssh uses SSH exclusively for better security and wider compatibility. + +### GENDERS Options + +| Option | Description | Alternative | +|--------|-------------|-------------| +| `-g ` | Target host group | Use `-C ` with YAML config | +| `-a` | Target all hosts | Define "all" cluster in config | +| `-X ` | Exclude host group | Use `--exclude` with hostlist | + +**Migration**: Convert GENDERS files to bssh YAML configuration. + +### Output Module Options + +| Option | Description | Alternative | +|--------|-------------|-------------| +| `-o ` | Select output module | Use `--stream` or built-in TUI | + +**Reason**: bssh has advanced built-in output handling (TUI, stream mode, file output). + +### Other Options + +| Option | Description | Alternative | +|--------|-------------|-------------| +| `-d` | Enable debug output | Use `-v`, `-vv`, or `-vvv` | +| `-r ` | Retry on connection failure | Not supported | +| `-c` | Connect to all hosts first | Automatic in bssh | + +## bssh-Specific Extensions + +These options are available in bssh but not in pdsh: + +### Advanced Features + +| Option | Description | Example | +|--------|-------------|---------| +| `-J ` | Jump host (bastion) | `pdsh -J bastion -w internal-hosts "cmd"` | +| `-L ` | Local port forwarding | `pdsh -L 8080:web:80 -w hosts "cmd"` | +| `-R ` | Remote port forwarding | `pdsh -R 80:localhost:8080 -w hosts "cmd"` | +| `-D ` | Dynamic forwarding (SOCKS) | `pdsh -D 1080 -w hosts "cmd"` | +| `-F ` | SSH config file | `pdsh -F ~/.ssh/custom_config -w hosts "cmd"` | +| `-C ` | Use cluster from config | `pdsh -C production "cmd"` | +| `--filter ` | Filter hosts by pattern | `pdsh -w hosts --filter "web*" "cmd"` | + +### Verbosity Levels + +| Option | Description | +|--------|-------------| +| `-v` | Verbose (INFO level) | +| `-vv` | More verbose (DEBUG level) | +| `-vvv` | Maximum verbosity (TRACE level) | + +**Example**: +```bash +# Debug connection issues +pdsh -vv -w problematic-host "command" +``` + +### Modern Output Modes + +| Option | Description | +|--------|-------------| +| `--stream` | Stream mode with real-time output | +| `--output-dir ` | Save per-host output to files | +| `--no-prefix` | Disable hostname prefix (same as `-N`) | + +### Exit Code Strategies + +| Option | Description | +|--------|-------------| +| `--require-all-success` | Return 0 only if all hosts succeed | +| `--check-all-nodes` | Return main rank code, or 1 if others fail | +| `--any-failure` | Return largest exit code (same as `-S`) | + +## Summary + +### Full Option Compatibility Matrix + +| Category | pdsh Options | bssh Support | Notes | +|----------|--------------|--------------|-------| +| **Host Selection** | `-w`, `-x`, `-g`, `-a`, `-X` | ✅ Partial | `-w`, `-x` supported; use config for groups | +| **Execution** | `-f`, `-b`, `-k` | ✅ Full | Direct mapping | +| **Timeouts** | `-t`, `-u` | ✅ Full | Direct mapping | +| **Output** | `-N`, `-o` | ✅ Partial | `-N` supported; use `--stream` instead of `-o` | +| **Authentication** | `-l` | ✅ Full | Plus additional `-i`, `-A`, `-P`, `-S` | +| **Query** | `-q` | ✅ Full | Direct mapping | +| **Exit Codes** | `-S` | ✅ Full | Plus additional strategies | +| **RCMD Modules** | `-R`, `-M` | ❌ None | SSH-only by design | +| **Debug** | `-d` | ✅ Partial | Use `-v`, `-vv`, `-vvv` | + +### Recommended Reading + +- [pdsh Migration Guide](pdsh-migration.md) - Complete migration instructions +- [pdsh Examples](pdsh-examples.md) - Real-world usage examples +- [bssh README](../README.md) - Full feature documentation + +--- + +**Note**: This document is maintained as part of the bssh project. For the latest version, see https://github.com/lablup/bssh/blob/main/docs/pdsh-options.md diff --git a/docs/shell-config/README.md b/docs/shell-config/README.md new file mode 100644 index 00000000..0b0ddb3b --- /dev/null +++ b/docs/shell-config/README.md @@ -0,0 +1,349 @@ +# Shell Configuration for bssh + +This directory contains shell configuration examples for bssh, including pdsh compatibility setup and useful shortcuts. + +## Available Configurations + +- **[bash.sh](bash.sh)** - Configuration for Bash shell +- **[zsh.sh](zsh.sh)** - Configuration for Zsh shell (with advanced features) +- **[fish.fish](fish.fish)** - Configuration for Fish shell (with abbreviations and interactive features) + +## Quick Setup + +### Bash + +```bash +# Download and source the configuration +curl -o ~/.bssh-config.sh https://raw.githubusercontent.com/lablup/bssh/main/docs/shell-config/bash.sh +echo 'source ~/.bssh-config.sh' >> ~/.bashrc +source ~/.bashrc +``` + +Or manually add to `~/.bashrc`: + +```bash +# bssh pdsh compatibility +alias pdsh='bssh --pdsh-compat' +``` + +### Zsh + +```bash +# Download and source the configuration +curl -o ~/.bssh-config.zsh https://raw.githubusercontent.com/lablup/bssh/main/docs/shell-config/zsh.sh +echo 'source ~/.bssh-config.zsh' >> ~/.zshrc +source ~/.zshrc +``` + +Or manually add to `~/.zshrc`: + +```bash +# bssh pdsh compatibility +alias pdsh='bssh --pdsh-compat' +``` + +### Fish + +```bash +# Download and source the configuration +curl -o ~/.config/fish/conf.d/bssh.fish https://raw.githubusercontent.com/lablup/bssh/main/docs/shell-config/fish.fish + +# Fish will automatically load it on next shell start +# Or reload manually: +source ~/.config/fish/conf.d/bssh.fish +``` + +Or manually add to `~/.config/fish/config.fish`: + +```fish +# bssh pdsh compatibility +alias pdsh='bssh --pdsh-compat' +``` + +## Features + +### All Shells + +- **pdsh compatibility alias**: Makes `pdsh` command use bssh +- **Cluster shortcuts**: Quick access to common clusters +- **Helper functions**: Simplified command execution +- **Environment variables**: Default configuration paths + +### Zsh-Specific + +- **Cluster context**: Set and use current cluster context +- **Associative arrays**: Define cluster groups +- **Right prompt**: Show current cluster in prompt + +### Fish-Specific + +- **Abbreviations**: Auto-expanding shortcuts (e.g., `bsp` → `bssh-prod`) +- **Interactive selection**: Choose cluster interactively +- **Cluster info**: Quick cluster information display +- **Fish-native syntax**: Uses Fish's modern command syntax + +## Common Helper Functions + +All configurations include these helper functions: + +### bssh-all + +Execute command on all nodes in a cluster: + +```bash +bssh-all production "uptime" +``` + +### bssh-hosts + +Execute with hostlist expression: + +```bash +bssh-hosts "web[1-5]" "nginx -t" +``` + +### bssh-health + +Quick cluster health check: + +```bash +bssh-health production +``` + +### pdsh-exec + +Execute in pdsh compatibility mode: + +```bash +pdsh-exec "node[1-10]" "df -h" +``` + +## Advanced Features + +### Cluster Context (Zsh/Fish) + +Set a cluster as current context: + +```bash +# Set context +bssh-context production + +# Execute commands in current context +bssh-ctx "uptime" +bssh-ctx "systemctl status nginx" +``` + +### Cluster Groups (Zsh/Fish) + +Execute on multiple clusters at once: + +```bash +# Zsh +bssh-group all "hostname" # Run on all clusters +bssh-group nonprod "uptime" # Run on non-production clusters + +# Fish +bssh-group all "hostname" +bssh-group prod "df -h" +``` + +### Interactive Selection (Fish) + +Choose cluster interactively: + +```bash +# Select and execute +bssh-select "uptime" + +# Select and set context +bssh-select +``` + +### Cluster Info (Fish) + +Show cluster information: + +```bash +bssh-info production +``` + +## Customization + +### Adding Your Own Shortcuts + +```bash +# Bash/Zsh +alias bssh-web='bssh -C webservers' +alias bssh-db='bssh -C databases' + +# Fish +alias bssh-web='bssh -C webservers' +alias bssh-db='bssh -C databases' +# Or use abbreviations +abbr --add bsw 'bssh -C webservers' +``` + +### Custom Cluster Groups + +```bash +# Zsh +BSSH_CLUSTER_GROUPS[critical]="production database-primary" +BSSH_CLUSTER_GROUPS[monitoring]="monitoring-prod monitoring-staging" + +# Fish - modify bssh-group function in fish.fish +``` + +### Custom Health Checks + +```bash +# Bash/Zsh +bssh-full-health() { + bssh -C "$1" " + echo '=== System Info ===' && + uname -a && + echo '=== Uptime ===' && + uptime && + echo '=== Memory ===' && + free -h && + echo '=== Disk ===' && + df -h / + " +} + +# Fish +function bssh-full-health + bssh -C $argv[1] " + echo '=== System Info ===' && + uname -a && + echo '=== Uptime ===' && + uptime && + echo '=== Memory ===' && + free -h && + echo '=== Disk ===' && + df -h / + " +end +``` + +## Environment Variables + +All configurations support these environment variables: + +| Variable | Description | Default | +|----------|-------------|---------| +| `BSSH_CONFIG` | Configuration file path | `~/.config/bssh/config.yaml` | +| `BSSH_PDSH_COMPAT` | Enable pdsh mode globally | unset (disabled) | +| `BSSH_PARALLEL` | Default parallel connections | 10 | +| `BSSH_CONNECT_TIMEOUT` | Default connection timeout | 30 seconds | +| `BSSH_COMMAND_TIMEOUT` | Default command timeout | 300 seconds | + +Set in your shell configuration: + +```bash +# Bash/Zsh +export BSSH_PDSH_COMPAT=1 +export BSSH_PARALLEL=20 + +# Fish +set -gx BSSH_PDSH_COMPAT 1 +set -gx BSSH_PARALLEL 20 +``` + +## Examples + +### Daily Operations + +```bash +# Check all production servers +bssh-prod "uptime" + +# Update all staging servers +bssh-staging "sudo apt update && sudo apt upgrade -y" + +# Restart service on web servers +bssh-hosts "web[1-10]" "sudo systemctl restart nginx" + +# Health check +bssh-health production +``` + +### Using pdsh Compatibility + +```bash +# All existing pdsh scripts work unchanged +pdsh -w node[1-5] "df -h" +pdsh -w servers -f 10 "uptime" +pdsh -w hosts -x badhost "systemctl status nginx" +``` + +### Cluster Context (Zsh/Fish) + +```bash +# Set context +bssh-context production + +# Execute multiple commands +bssh-ctx "systemctl status nginx" +bssh-ctx "df -h /" +bssh-ctx "free -h" + +# Clear context +unset BSSH_CURRENT_CLUSTER # Zsh +set -e BSSH_CURRENT_CLUSTER # Fish +``` + +## Troubleshooting + +### Alias Not Working + +```bash +# Check if alias is defined +alias pdsh # Should show: pdsh='bssh --pdsh-compat' + +# Re-source configuration +source ~/.bashrc # Bash +source ~/.zshrc # Zsh +source ~/.config/fish/config.fish # Fish +``` + +### Function Not Found + +```bash +# Check if function exists +type bssh-all # Should show function definition + +# Re-source configuration file +``` + +### Environment Variables Not Set + +```bash +# Check if variable is set +echo $BSSH_CONFIG # Should show path + +# Set manually if needed +export BSSH_CONFIG="$HOME/.config/bssh/config.yaml" # Bash/Zsh +set -gx BSSH_CONFIG "$HOME/.config/bssh/config.yaml" # Fish +``` + +## See Also + +- [bssh README](../../README.md) - Main documentation +- [pdsh Migration Guide](../pdsh-migration.md) - Migrating from pdsh +- [pdsh Options](../pdsh-options.md) - Option mapping reference +- [pdsh Examples](../pdsh-examples.md) - Usage examples + +## Contributing + +Feel free to contribute additional shell configurations or improvements: + +1. Add new shell support (e.g., PowerShell, Nushell) +2. Enhance existing configurations with new features +3. Fix bugs or improve documentation +4. Share your custom shortcuts and functions + +Submit pull requests at: https://github.com/lablup/bssh + +## License + +These configurations are part of the bssh project and licensed under Apache 2.0. diff --git a/docs/shell-config/bash.sh b/docs/shell-config/bash.sh new file mode 100644 index 00000000..22400424 --- /dev/null +++ b/docs/shell-config/bash.sh @@ -0,0 +1,111 @@ +# bssh shell configuration for Bash +# Add this to your ~/.bashrc or ~/.bash_profile + +# ============================================ +# Method 1: pdsh Compatibility Alias +# ============================================ +# This makes the 'pdsh' command use bssh in compatibility mode +alias pdsh='bssh --pdsh-compat' + +# Alternatively, if you prefer environment variable: +# export BSSH_PDSH_COMPAT=1 +# alias pdsh='bssh' + +# ============================================ +# Method 2: bssh Cluster Shortcuts +# ============================================ +# Create shortcuts for frequently used clusters + +# Production cluster shortcut +alias bssh-prod='bssh -C production' + +# Staging cluster shortcut +alias bssh-staging='bssh -C staging' + +# Development cluster shortcut +alias bssh-dev='bssh -C development' + +# ============================================ +# Helper Functions +# ============================================ + +# Quick SSH to all nodes in a cluster +bssh-all() { + if [ -z "$1" ]; then + echo "Usage: bssh-all " + return 1 + fi + + local cluster="$1" + shift + bssh -C "$cluster" "$@" +} + +# Execute command with hostlist expansion +bssh-hosts() { + if [ $# -lt 2 ]; then + echo "Usage: bssh-hosts " + return 1 + fi + + local hosts="$1" + shift + bssh -H "$hosts" "$@" +} + +# pdsh-style execution with automatic compatibility mode +pdsh-exec() { + if [ $# -lt 2 ]; then + echo "Usage: pdsh-exec " + return 1 + fi + + local hosts="$1" + shift + bssh --pdsh-compat -w "$hosts" "$@" +} + +# Quick health check across cluster +bssh-health() { + if [ -z "$1" ]; then + echo "Usage: bssh-health " + return 1 + fi + + bssh -C "$1" "uptime; free -h | grep 'Mem:'; df -h /" +} + +# ============================================ +# Completion (Optional) +# ============================================ +# Enable bash completion for bssh if available +if [ -f /usr/share/bash-completion/completions/bssh ]; then + source /usr/share/bash-completion/completions/bssh +fi + +# ============================================ +# Environment Variables +# ============================================ +# Set default bssh configuration file location +export BSSH_CONFIG="${BSSH_CONFIG:-$HOME/.config/bssh/config.yaml}" + +# Enable pdsh compatibility mode globally (if desired) +# export BSSH_PDSH_COMPAT=1 + +# Set default parallel connections +# export BSSH_PARALLEL=10 + +# Set default timeouts +# export BSSH_CONNECT_TIMEOUT=30 +# export BSSH_COMMAND_TIMEOUT=300 + +# ============================================ +# Examples +# ============================================ +# After sourcing this file, you can use: +# +# pdsh -w node[1-5] "uptime" # Using pdsh alias +# bssh-prod "systemctl status nginx" # Production cluster +# bssh-all staging "df -h" # Execute on all staging nodes +# bssh-hosts "web[1-3]" "nginx -t" # Hostlist expansion +# bssh-health production # Quick health check diff --git a/docs/shell-config/fish.fish b/docs/shell-config/fish.fish new file mode 100644 index 00000000..3c076cfd --- /dev/null +++ b/docs/shell-config/fish.fish @@ -0,0 +1,277 @@ +# bssh shell configuration for Fish +# Add this to your ~/.config/fish/config.fish + +# ============================================ +# Method 1: pdsh Compatibility Alias +# ============================================ +# This makes the 'pdsh' command use bssh in compatibility mode +alias pdsh='bssh --pdsh-compat' + +# Alternatively, if you prefer environment variable: +# set -gx BSSH_PDSH_COMPAT 1 +# alias pdsh='bssh' + +# ============================================ +# Method 2: bssh Cluster Shortcuts +# ============================================ +# Create shortcuts for frequently used clusters + +# Production cluster shortcut +alias bssh-prod='bssh -C production' + +# Staging cluster shortcut +alias bssh-staging='bssh -C staging' + +# Development cluster shortcut +alias bssh-dev='bssh -C development' + +# ============================================ +# Helper Functions +# ============================================ + +# Quick SSH to all nodes in a cluster +function bssh-all + if test (count $argv) -lt 2 + echo "Usage: bssh-all " + return 1 + end + + set cluster $argv[1] + set -e argv[1] + bssh -C $cluster $argv +end + +# Execute command with hostlist expansion +function bssh-hosts + if test (count $argv) -lt 2 + echo "Usage: bssh-hosts " + return 1 + end + + set hosts $argv[1] + set -e argv[1] + bssh -H $hosts $argv +end + +# pdsh-style execution with automatic compatibility mode +function pdsh-exec + if test (count $argv) -lt 2 + echo "Usage: pdsh-exec " + return 1 + end + + set hosts $argv[1] + set -e argv[1] + bssh --pdsh-compat -w $hosts $argv +end + +# Quick health check across cluster +function bssh-health + if test (count $argv) -eq 0 + echo "Usage: bssh-health " + return 1 + end + + bssh -C $argv[1] "uptime; free -h | grep 'Mem:'; df -h /" +end + +# Parallel execution with progress tracking +function bssh-parallel + if test (count $argv) -lt 3 + echo "Usage: bssh-parallel " + return 1 + end + + set cluster $argv[1] + set parallel $argv[2] + set -e argv[1..2] + bssh -C $cluster --parallel $parallel $argv +end + +# ============================================ +# Fish Completion (Optional) +# ============================================ +# Fish auto-loads completions from ~/.config/fish/completions/ +# If bssh provides fish completion, it should be at: +# ~/.config/fish/completions/bssh.fish + +# ============================================ +# Environment Variables +# ============================================ +# Set default bssh configuration file location (preserve existing if set) +if not set -q BSSH_CONFIG + set -gx BSSH_CONFIG $HOME/.config/bssh/config.yaml +end + +# Enable pdsh compatibility mode globally (if desired) +# set -gx BSSH_PDSH_COMPAT 1 + +# Set default parallel connections +# set -gx BSSH_PARALLEL 10 + +# Set default timeouts +# set -gx BSSH_CONNECT_TIMEOUT 30 +# set -gx BSSH_COMMAND_TIMEOUT 300 + +# ============================================ +# Fish-Specific Features +# ============================================ + +# Custom prompt indicator when working with clusters +# (Optional: shows current cluster context if set) +function fish_right_prompt + if set -q BSSH_CURRENT_CLUSTER + set_color cyan + echo -n "[cluster: $BSSH_CURRENT_CLUSTER]" + set_color normal + end +end + +# Quick cluster context switching +function bssh-context + if test (count $argv) -eq 0 + if set -q BSSH_CURRENT_CLUSTER + echo "Current cluster: $BSSH_CURRENT_CLUSTER" + else + echo "No cluster context set" + end + return 0 + end + + set -gx BSSH_CURRENT_CLUSTER $argv[1] + echo "Switched to cluster: $BSSH_CURRENT_CLUSTER" +end + +# Use current cluster context +function bssh-ctx + if not set -q BSSH_CURRENT_CLUSTER + echo "No cluster context set. Use: bssh-context " + return 1 + end + + bssh -C $BSSH_CURRENT_CLUSTER $argv +end + +# ============================================ +# Cluster Groups +# ============================================ + +# Define cluster groups (Fish doesn't have associative arrays, use switch) +function bssh-group + if test (count $argv) -lt 2 + echo "Usage: bssh-group " + echo "Available groups: all, prod, nonprod" + return 1 + end + + set group $argv[1] + set -e argv[1] + + switch $group + case all + set clusters production staging development + case prod + set clusters production + case nonprod + set clusters staging development + case '*' + echo "Unknown group: $group" + echo "Available groups: all, prod, nonprod" + return 1 + end + + for cluster in $clusters + echo "===> Running on cluster: $cluster" + bssh -C $cluster $argv + end +end + +# ============================================ +# Enhanced Utilities +# ============================================ + +# Interactive cluster selector +function bssh-select + # Get list of available clusters from config + set clusters (bssh list 2>/dev/null | tail -n +2 | awk '{print $1}') + + if test (count $clusters) -eq 0 + echo "No clusters found in configuration" + return 1 + end + + echo "Select cluster:" + for i in (seq (count $clusters)) + echo " $i) $clusters[$i]" + end + + read -P "Enter number: " selection + + # Validate that selection is a positive integer + if not string match -qr '^[0-9]+$' -- "$selection" + echo "Invalid selection: must be a number" + return 1 + end + + if test "$selection" -ge 1 -a "$selection" -le (count $clusters) + set selected_cluster $clusters[$selection] + echo "Selected: $selected_cluster" + + if test (count $argv) -gt 0 + bssh -C $selected_cluster $argv + else + bssh-context $selected_cluster + end + else + echo "Invalid selection" + return 1 + end +end + +# Quick cluster info +function bssh-info + if test (count $argv) -eq 0 + echo "Usage: bssh-info " + return 1 + end + + echo "Cluster: $argv[1]" + echo "Nodes:" + bssh -C $argv[1] -q 2>/dev/null | while read node + echo " - $node" + end +end + +# ============================================ +# Abbreviations (Fish-specific) +# ============================================ +# These expand automatically as you type + +abbr --add bsp 'bssh-prod' +abbr --add bss 'bssh-staging' +abbr --add bsd 'bssh-dev' +abbr --add bsc 'bssh-context' +abbr --add bsx 'bssh-ctx' +abbr --add bsh 'bssh-health' + +# ============================================ +# Examples +# ============================================ +# After sourcing this file, you can use: +# +# pdsh -w node[1-5] "uptime" # Using pdsh alias +# bssh-prod "systemctl status nginx" # Production cluster +# bssh-all staging "df -h" # Execute on all staging nodes +# bssh-hosts "web[1-3]" "nginx -t" # Hostlist expansion +# bssh-health production # Quick health check +# bssh-context production # Set cluster context +# bssh-ctx "uptime" # Use current context +# bssh-group all "hostname" # Execute on cluster group +# bssh-select "uptime" # Interactive cluster selection +# bssh-info production # Show cluster info +# +# Abbreviations (expand automatically): +# bsp "uptime" -> bssh-prod "uptime" +# bss "df -h" -> bssh-staging "df -h" +# bsc production -> bssh-context production +# bsx "hostname" -> bssh-ctx "hostname" diff --git a/docs/shell-config/zsh.sh b/docs/shell-config/zsh.sh new file mode 100644 index 00000000..01691746 --- /dev/null +++ b/docs/shell-config/zsh.sh @@ -0,0 +1,201 @@ +# bssh shell configuration for Zsh +# Add this to your ~/.zshrc + +# ============================================ +# Method 1: pdsh Compatibility Alias +# ============================================ +# This makes the 'pdsh' command use bssh in compatibility mode +alias pdsh='bssh --pdsh-compat' + +# Alternatively, if you prefer environment variable: +# export BSSH_PDSH_COMPAT=1 +# alias pdsh='bssh' + +# ============================================ +# Method 2: bssh Cluster Shortcuts +# ============================================ +# Create shortcuts for frequently used clusters + +# Production cluster shortcut +alias bssh-prod='bssh -C production' + +# Staging cluster shortcut +alias bssh-staging='bssh -C staging' + +# Development cluster shortcut +alias bssh-dev='bssh -C development' + +# ============================================ +# Helper Functions +# ============================================ + +# Quick SSH to all nodes in a cluster +bssh-all() { + if [[ -z "$1" ]]; then + echo "Usage: bssh-all " + return 1 + fi + + local cluster="$1" + shift + bssh -C "$cluster" "$@" +} + +# Execute command with hostlist expansion +bssh-hosts() { + if [[ $# -lt 2 ]]; then + echo "Usage: bssh-hosts " + return 1 + fi + + local hosts="$1" + shift + bssh -H "$hosts" "$@" +} + +# pdsh-style execution with automatic compatibility mode +pdsh-exec() { + if [[ $# -lt 2 ]]; then + echo "Usage: pdsh-exec " + return 1 + fi + + local hosts="$1" + shift + bssh --pdsh-compat -w "$hosts" "$@" +} + +# Quick health check across cluster +bssh-health() { + if [[ -z "$1" ]]; then + echo "Usage: bssh-health " + return 1 + fi + + bssh -C "$1" "uptime; free -h | grep 'Mem:'; df -h /" +} + +# Parallel execution with progress tracking +bssh-parallel() { + if [[ $# -lt 3 ]]; then + echo "Usage: bssh-parallel " + return 1 + fi + + local cluster="$1" + local parallel="$2" + shift 2 + bssh -C "$cluster" --parallel "$parallel" "$@" +} + +# ============================================ +# Zsh Completion (Optional) +# ============================================ +# Enable completion for bssh +# If bssh provides zsh completion, source it: +# if [ -f /usr/share/zsh/site-functions/_bssh ]; then +# autoload -Uz compinit && compinit +# fi + +# ============================================ +# Environment Variables +# ============================================ +# Set default bssh configuration file location +export BSSH_CONFIG="${BSSH_CONFIG:-$HOME/.config/bssh/config.yaml}" + +# Enable pdsh compatibility mode globally (if desired) +# export BSSH_PDSH_COMPAT=1 + +# Set default parallel connections +# export BSSH_PARALLEL=10 + +# Set default timeouts +# export BSSH_CONNECT_TIMEOUT=30 +# export BSSH_COMMAND_TIMEOUT=300 + +# ============================================ +# Zsh-Specific Features +# ============================================ + +# Custom prompt indicator when working with clusters +# (Optional: shows current cluster context if set) +if [[ -n "$BSSH_CURRENT_CLUSTER" ]]; then + RPROMPT="%F{cyan}[cluster: $BSSH_CURRENT_CLUSTER]%f" +fi + +# Quick cluster context switching +bssh-context() { + if [[ -z "$1" ]]; then + if [[ -n "$BSSH_CURRENT_CLUSTER" ]]; then + echo "Current cluster: $BSSH_CURRENT_CLUSTER" + else + echo "No cluster context set" + fi + return 0 + fi + + export BSSH_CURRENT_CLUSTER="$1" + echo "Switched to cluster: $BSSH_CURRENT_CLUSTER" + + # Update prompt if using the RPROMPT above + RPROMPT="%F{cyan}[cluster: $BSSH_CURRENT_CLUSTER]%f" +} + +# Use current cluster context +bssh-ctx() { + if [[ -z "$BSSH_CURRENT_CLUSTER" ]]; then + echo "No cluster context set. Use: bssh-context " + return 1 + fi + + bssh -C "$BSSH_CURRENT_CLUSTER" "$@" +} + +# ============================================ +# Array/Associative Array Utilities +# ============================================ + +# Define cluster groups (Zsh associative array) +typeset -A BSSH_CLUSTER_GROUPS +BSSH_CLUSTER_GROUPS=( + all "production staging development" + prod "production" + nonprod "staging development" +) + +# Execute command on cluster group +bssh-group() { + if [[ $# -lt 2 ]]; then + echo "Usage: bssh-group " + echo "Available groups: ${(k)BSSH_CLUSTER_GROUPS}" + return 1 + fi + + local group="$1" + shift + + if [[ -z "${BSSH_CLUSTER_GROUPS[$group]}" ]]; then + echo "Unknown group: $group" + echo "Available groups: ${(k)BSSH_CLUSTER_GROUPS}" + return 1 + fi + + for cluster in ${=BSSH_CLUSTER_GROUPS[$group]}; do + echo "===> Running on cluster: $cluster" + bssh -C "$cluster" "$@" + done +} + +# ============================================ +# Examples +# ============================================ +# After sourcing this file, you can use: +# +# pdsh -w node[1-5] "uptime" # Using pdsh alias +# bssh-prod "systemctl status nginx" # Production cluster +# bssh-all staging "df -h" # Execute on all staging nodes +# bssh-hosts "web[1-3]" "nginx -t" # Hostlist expansion +# bssh-health production # Quick health check +# bssh-context production # Set cluster context +# bssh-ctx "uptime" # Use current context +# bssh-group all "hostname" # Execute on cluster group diff --git a/src/cli/bssh.rs b/src/cli/bssh.rs index 53677643..ac126741 100644 --- a/src/cli/bssh.rs +++ b/src/cli/bssh.rs @@ -211,7 +211,7 @@ pub struct Cli { #[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" + help = "Enable pdsh compatibility mode\nAccepts pdsh-style command line arguments (-w, -x, -f, etc.)\nAuto-enabled when invoked as 'pdsh' via symlink or when BSSH_PDSH_COMPAT=1\nUseful when migrating from pdsh or in mixed environments\nSee docs/pdsh-migration.md for complete migration guide" )] pub pdsh_compat: bool, diff --git a/tests/pdsh_compat/README.md b/tests/pdsh_compat/README.md new file mode 100644 index 00000000..c108684a --- /dev/null +++ b/tests/pdsh_compat/README.md @@ -0,0 +1,139 @@ +# pdsh Compatibility Test Suite + +This directory contains integration tests for pdsh compatibility mode. + +## Test Files + +- **test_basic.sh** - Basic command execution tests +- **test_hostlist.sh** - Hostlist expression expansion tests +- **test_options.sh** - Option mapping tests + +## Running Tests + +### Prerequisites + +1. **bssh must be installed** and in PATH +2. **Test hosts must be accessible** via SSH +3. **SSH keys must be configured** for passwordless authentication + +### Setup + +Configure test hosts in environment variables: + +```bash +export BSSH_TEST_HOSTS="localhost" +export BSSH_TEST_USER="$USER" +``` + +For multi-host tests: + +```bash +export BSSH_TEST_HOSTS="host1,host2,host3" +export BSSH_TEST_USER="testuser" +``` + +### Run All Tests + +```bash +cd tests/pdsh_compat +./run_all_tests.sh +``` + +### Run Individual Tests + +```bash +./test_basic.sh +./test_hostlist.sh +./test_options.sh +``` + +## Test Coverage + +### Basic Tests (`test_basic.sh`) +- Simple command execution +- Multiple hosts +- Query mode +- Exit codes + +### Hostlist Tests (`test_hostlist.sh`) +- Range expansion (`host[1-5]`) +- Zero-padded ranges (`node[01-10]`) +- Cartesian products (`rack[1-2]-node[1-3]`) +- Comma-separated lists + +### Options Tests (`test_options.sh`) +- Host selection (`-w`) +- Exclusion (`-x`) +- Fanout (`-f`) +- Timeouts (`-t`, `-u`) +- Output control (`-N`) +- Batch mode (`-b`) +- Fail-fast (`-k`) +- Edge cases (invalid syntax, connection failures, timeouts) + +## CI Integration + +These tests can be run in CI/CD pipelines: + +```yaml +# Example GitHub Actions +- name: Run pdsh compatibility tests + env: + BSSH_TEST_HOSTS: "localhost" + BSSH_TEST_USER: "runner" + run: | + cd tests/pdsh_compat + ./run_all_tests.sh +``` + +## Test Environment + +Tests assume: +- SSH server running on test hosts +- Passwordless SSH authentication configured +- User has permission to execute test commands +- Test hosts have standard Unix utilities (echo, hostname, etc.) + +## Troubleshooting + +### SSH Connection Issues + +```bash +# Test SSH connectivity first +ssh $BSSH_TEST_USER@$BSSH_TEST_HOSTS "echo test" + +# Check SSH keys +ssh-add -l +``` + +### Test Failures + +Run tests with verbose mode: + +```bash +BSSH_TEST_VERBOSE=1 ./test_basic.sh +``` + +### Permission Issues + +Ensure test user has appropriate permissions: + +```bash +# Test as current user +export BSSH_TEST_USER="$USER" +export BSSH_TEST_HOSTS="localhost" +``` + +## Contributing + +When adding new tests: + +1. Follow existing test structure +2. Add descriptive test names +3. Include both positive and negative test cases +4. Update this README with new test coverage +5. Ensure tests can run in CI environment + +## License + +These tests are part of the bssh project and licensed under Apache 2.0. diff --git a/tests/pdsh_compat/run_all_tests.sh b/tests/pdsh_compat/run_all_tests.sh new file mode 100755 index 00000000..7bade712 --- /dev/null +++ b/tests/pdsh_compat/run_all_tests.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +# Run all pdsh compatibility tests + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Get script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Test configuration +export BSSH_TEST_HOST="${BSSH_TEST_HOST:-localhost}" +export BSSH_TEST_USER="${BSSH_TEST_USER:-$USER}" +export PDSH_CMD="${PDSH_CMD:-bssh --pdsh-compat}" +export BSSH_TEST_VERBOSE="${BSSH_TEST_VERBOSE:-0}" + +# Test suite counters +SUITES_RUN=0 +SUITES_PASSED=0 +SUITES_FAILED=0 +TOTAL_TESTS=0 +TOTAL_PASSED=0 +TOTAL_FAILED=0 + +echo -e "${BLUE}=========================================${NC}" +echo -e "${BLUE}pdsh Compatibility Test Suite${NC}" +echo -e "${BLUE}=========================================${NC}" +echo "Test host: $BSSH_TEST_HOST" +echo "Test user: $BSSH_TEST_USER" +echo "Command: $PDSH_CMD" +echo "Verbose: $BSSH_TEST_VERBOSE" +echo + +# Function to run a test suite +run_test_suite() { + local test_script="$1" + local suite_name=$(basename "$test_script" .sh) + + echo -e "${YELLOW}-----------------------------------${NC}" + echo -e "${YELLOW}Running: $suite_name${NC}" + echo -e "${YELLOW}-----------------------------------${NC}" + echo + + ((SUITES_RUN++)) + + if "$test_script"; then + echo -e "${GREEN}✓ $suite_name passed${NC}" + ((SUITES_PASSED++)) + return 0 + else + echo -e "${RED}✗ $suite_name failed${NC}" + ((SUITES_FAILED++)) + return 1 + fi +} + +# Make test scripts executable +chmod +x "$SCRIPT_DIR"/*.sh 2>/dev/null || true + +# Run test suites +run_test_suite "$SCRIPT_DIR/test_basic.sh" +echo + +run_test_suite "$SCRIPT_DIR/test_hostlist.sh" +echo + +run_test_suite "$SCRIPT_DIR/test_options.sh" +echo + +# Final summary +echo +echo -e "${BLUE}=========================================${NC}" +echo -e "${BLUE}Final Test Summary${NC}" +echo -e "${BLUE}=========================================${NC}" +echo "Test suites run: $SUITES_RUN" +echo "Test suites passed: $SUITES_PASSED" +echo "Test suites failed: $SUITES_FAILED" +echo + +if [ "$SUITES_FAILED" -eq 0 ]; then + echo -e "${GREEN}=========================================${NC}" + echo -e "${GREEN}All test suites passed!${NC}" + echo -e "${GREEN}=========================================${NC}" + exit 0 +else + echo -e "${RED}=========================================${NC}" + echo -e "${RED}Some test suites failed.${NC}" + echo -e "${RED}=========================================${NC}" + exit 1 +fi diff --git a/tests/pdsh_compat/test_basic.sh b/tests/pdsh_compat/test_basic.sh new file mode 100755 index 00000000..146664b9 --- /dev/null +++ b/tests/pdsh_compat/test_basic.sh @@ -0,0 +1,169 @@ +#!/usr/bin/env bash +# Basic pdsh compatibility tests + +set -euo pipefail + +# Disable errexit for arithmetic expressions (workaround for (()) returning 1 on 0) +set +e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Test configuration +TEST_HOST="${BSSH_TEST_HOST:-localhost}" +TEST_USER="${BSSH_TEST_USER:-$USER}" +PDSH_CMD="${PDSH_CMD:-bssh --pdsh-compat}" +VERBOSE="${BSSH_TEST_VERBOSE:-0}" + +# Test counters +TESTS_RUN=0 +TESTS_PASSED=0 +TESTS_FAILED=0 + +# Helper functions +log_test() { + echo -e "${YELLOW}[TEST]${NC} $1" +} + +log_pass() { + echo -e "${GREEN}[PASS]${NC} $1" + TESTS_PASSED=$((TESTS_PASSED + 1)) +} + +log_fail() { + echo -e "${RED}[FAIL]${NC} $1" + TESTS_FAILED=$((TESTS_FAILED + 1)) +} + +run_test() { + TESTS_RUN=$((TESTS_RUN + 1)) + local test_name="$1" + shift + local expected_exit="$1" + shift + + log_test "$test_name" + + if [ "$VERBOSE" = "1" ]; then + echo "Command: $*" + fi + + local output + local exit_code + + if output=$("$@" 2>&1); then + exit_code=0 + else + exit_code=$? + fi + + if [ "$VERBOSE" = "1" ]; then + echo "Output: $output" + echo "Exit code: $exit_code" + fi + + if [ "$exit_code" -eq "$expected_exit" ]; then + log_pass "$test_name" + return 0 + else + log_fail "$test_name (expected exit $expected_exit, got $exit_code)" + return 1 + fi +} + +# Test suite +echo "================================" +echo "pdsh Basic Compatibility Tests" +echo "================================" +echo "Test host: $TEST_HOST" +echo "Test user: $TEST_USER" +echo "Command: $PDSH_CMD" +echo + +# Test 1: Basic command execution +run_test "Basic command execution" 0 \ + ${PDSH_CMD} -w "$TEST_HOST" "echo hello" + +# Test 2: Command with output verification +run_test "Output verification" 0 \ + bash -c "$PDSH_CMD -w '$TEST_HOST' 'echo test' | grep -q 'test'" + +# Test 3: Query mode +run_test "Query mode (-q)" 0 \ + ${PDSH_CMD} -w "$TEST_HOST" -q + +# Test 4: Query mode output verification +run_test "Query mode output" 0 \ + bash -c "$PDSH_CMD -w '$TEST_HOST' -q | grep -q '$TEST_HOST'" + +# Test 5: No prefix mode (-N) +run_test "No prefix mode (-N)" 0 \ + bash -c "$PDSH_CMD -w '$TEST_HOST' -N 'echo test' | grep -q '^test$'" + +# Test 6: Command with exit code 0 +run_test "Exit code 0 command" 0 \ + ${PDSH_CMD} -w "$TEST_HOST" "true" + +# Test 7: Command with exit code 1 +run_test "Exit code 1 command" 1 \ + ${PDSH_CMD} -w "$TEST_HOST" "false" + +# Test 8: User specification +if [ "$TEST_USER" != "root" ]; then + run_test "User specification (-l)" 0 \ + ${PDSH_CMD} -w "$TEST_HOST" -l "$TEST_USER" "whoami" +fi + +# Test 9: Multiple commands with semicolon +run_test "Multiple commands" 0 \ + ${PDSH_CMD} -w "$TEST_HOST" "echo first && echo second" + +# Test 10: Command with quotes +run_test "Command with quotes" 0 \ + ${PDSH_CMD} -w "$TEST_HOST" "echo 'quoted string'" + +# Test 11: Command with pipe +run_test "Command with pipe" 0 \ + ${PDSH_CMD} -w "$TEST_HOST" "echo test | grep test" + +# Test 12: Long command +run_test "Long command" 0 \ + ${PDSH_CMD} -w "$TEST_HOST" "echo this is a very long command with many words" + +# Test 13: Empty command (should fail with non-zero exit) +# Note: The exact exit code may vary; we test that it's non-zero +if ${PDSH_CMD} -w "$TEST_HOST" "" >/dev/null 2>&1; then + log_fail "Empty command (should fail) - expected non-zero exit" +else + log_pass "Empty command (should fail)" +fi +TESTS_RUN=$((TESTS_RUN + 1)) + +# Test 14: Hostname output +run_test "Hostname command" 0 \ + ${PDSH_CMD} -w "$TEST_HOST" "hostname" + +# Test 15: Environment variable access +run_test "Environment variable" 0 \ + bash -c "${PDSH_CMD} -w '$TEST_HOST' 'echo \$PATH' | grep -q '/'" + +# Summary +echo +echo "================================" +echo "Test Summary" +echo "================================" +echo "Tests run: $TESTS_RUN" +echo "Tests passed: $TESTS_PASSED" +echo "Tests failed: $TESTS_FAILED" +echo + +if [ "$TESTS_FAILED" -eq 0 ]; then + echo -e "${GREEN}All tests passed!${NC}" + exit 0 +else + echo -e "${RED}Some tests failed.${NC}" + exit 1 +fi diff --git a/tests/pdsh_compat/test_hostlist.sh b/tests/pdsh_compat/test_hostlist.sh new file mode 100755 index 00000000..4f08a6e5 --- /dev/null +++ b/tests/pdsh_compat/test_hostlist.sh @@ -0,0 +1,188 @@ +#!/usr/bin/env bash +# Hostlist expression compatibility tests + +set -euo pipefail + +# Disable errexit for arithmetic expressions (workaround for (()) returning 1 on 0) +set +e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Test configuration +PDSH_CMD="${PDSH_CMD:-bssh --pdsh-compat}" +VERBOSE="${BSSH_TEST_VERBOSE:-0}" + +# Test counters +TESTS_RUN=0 +TESTS_PASSED=0 +TESTS_FAILED=0 + +# Helper functions +log_test() { + echo -e "${YELLOW}[TEST]${NC} $1" +} + +log_pass() { + echo -e "${GREEN}[PASS]${NC} $1" + TESTS_PASSED=$((TESTS_PASSED + 1)) +} + +log_fail() { + echo -e "${RED}[FAIL]${NC} $1" + TESTS_FAILED=$((TESTS_FAILED + 1)) +} + +run_query_test() { + TESTS_RUN=$((TESTS_RUN + 1)) + local test_name="$1" + local hostlist="$2" + shift 2 + local expected_hosts=("$@") + + log_test "$test_name" + + if [ "$VERBOSE" = "1" ]; then + echo "Hostlist: $hostlist" + echo "Expected: ${expected_hosts[*]}" + fi + + local output + if ! output=$($PDSH_CMD -w "$hostlist" -q 2>&1); then + log_fail "$test_name (command failed)" + return 1 + fi + + if [ "$VERBOSE" = "1" ]; then + echo "Output: $output" + fi + + # Convert output to array + local -a actual_hosts + mapfile -t actual_hosts <<< "$output" + + # Check count + if [ "${#actual_hosts[@]}" -ne "${#expected_hosts[@]}" ]; then + log_fail "$test_name (expected ${#expected_hosts[@]} hosts, got ${#actual_hosts[@]})" + return 1 + fi + + # Check each host + for expected_host in "${expected_hosts[@]}"; do + if ! echo "$output" | grep -q "^${expected_host}$"; then + log_fail "$test_name (missing host: $expected_host)" + return 1 + fi + done + + log_pass "$test_name" + return 0 +} + +# Test suite +echo "=====================================" +echo "pdsh Hostlist Expression Tests" +echo "=====================================" +echo "Command: $PDSH_CMD" +echo + +# Test 1: Simple range +run_query_test "Simple range [1-3]" "test[1-3]" \ + "test1" "test2" "test3" + +# Test 2: Single value range +run_query_test "Single value [5]" "test[5]" \ + "test5" + +# Test 3: Zero-padded range +run_query_test "Zero-padded [01-03]" "node[01-03]" \ + "node01" "node02" "node03" + +# Test 4: Comma-separated values +run_query_test "Comma-separated [1,3,5]" "host[1,3,5]" \ + "host1" "host3" "host5" + +# Test 5: Mixed range and values +run_query_test "Mixed [1-2,5,7-8]" "server[1-2,5,7-8]" \ + "server1" "server2" "server5" "server7" "server8" + +# Test 6: Cartesian product (simple) +run_query_test "Cartesian product [1-2]-[a-b]" "node[1-2]-[a-b]" \ + "node1-a" "node1-b" "node2-a" "node2-b" + +# Test 7: Domain suffix +run_query_test "With domain suffix" "web[1-2].example.com" \ + "web1.example.com" "web2.example.com" + +# Test 8: With port number +run_query_test "With port number" "host[1-2]:2222" \ + "host1:2222" "host2:2222" + +# Test 9: With username +run_query_test "With username" "admin@host[1-2]" \ + "admin@host1" "admin@host2" + +# Test 10: Complex expression +run_query_test "Complex expression" "rack[1-2]-node[1-2]" \ + "rack1-node1" "rack1-node2" "rack2-node1" "rack2-node2" + +# Test 11: Comma-separated simple hosts (no expansion) +run_query_test "Comma-separated hosts" "host1,host2,host3" \ + "host1" "host2" "host3" + +# Test 12: Mixed hosts and expressions +run_query_test "Mixed hosts and expressions" "web[1-2],db1,cache" \ + "web1" "web2" "db1" "cache" + +# Test 13: Large range +run_query_test "Large range [1-10]" "node[1-10]" \ + "node1" "node2" "node3" "node4" "node5" \ + "node6" "node7" "node8" "node9" "node10" + +# Test 14: Exclusion with range +((TESTS_RUN++)) +log_test "Exclusion with range" +output=$($PDSH_CMD -w "host[1-5]" -x "host[2-4]" -q 2>&1) +if echo "$output" | grep -q "host1" && \ + echo "$output" | grep -q "host5" && \ + ! echo "$output" | grep -q "host2" && \ + ! echo "$output" | grep -q "host3" && \ + ! echo "$output" | grep -q "host4"; then + log_pass "Exclusion with range" +else + log_fail "Exclusion with range" +fi + +# Test 15: Exclusion with glob pattern +((TESTS_RUN++)) +log_test "Exclusion with glob pattern" +output=$($PDSH_CMD -w "web1,web2,db1,db2" -x "db*" -q 2>&1) +if echo "$output" | grep -q "web1" && \ + echo "$output" | grep -q "web2" && \ + ! echo "$output" | grep -q "db1" && \ + ! echo "$output" | grep -q "db2"; then + log_pass "Exclusion with glob pattern" +else + log_fail "Exclusion with glob pattern" +fi + +# Summary +echo +echo "=====================================" +echo "Test Summary" +echo "=====================================" +echo "Tests run: $TESTS_RUN" +echo "Tests passed: $TESTS_PASSED" +echo "Tests failed: $TESTS_FAILED" +echo + +if [ "$TESTS_FAILED" -eq 0 ]; then + echo -e "${GREEN}All tests passed!${NC}" + exit 0 +else + echo -e "${RED}Some tests failed.${NC}" + exit 1 +fi diff --git a/tests/pdsh_compat/test_options.sh b/tests/pdsh_compat/test_options.sh new file mode 100755 index 00000000..747a7a8b --- /dev/null +++ b/tests/pdsh_compat/test_options.sh @@ -0,0 +1,222 @@ +#!/usr/bin/env bash +# pdsh option mapping tests + +set -euo pipefail + +# Disable errexit for arithmetic expressions (workaround for (()) returning 1 on 0) +set +e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Test configuration +TEST_HOST="${BSSH_TEST_HOST:-localhost}" +TEST_USER="${BSSH_TEST_USER:-$USER}" +PDSH_CMD="${PDSH_CMD:-bssh --pdsh-compat}" +VERBOSE="${BSSH_TEST_VERBOSE:-0}" + +# Test counters +TESTS_RUN=0 +TESTS_PASSED=0 +TESTS_FAILED=0 + +# Helper functions +log_test() { + echo -e "${YELLOW}[TEST]${NC} $1" +} + +log_pass() { + echo -e "${GREEN}[PASS]${NC} $1" + TESTS_PASSED=$((TESTS_PASSED + 1)) +} + +log_fail() { + echo -e "${RED}[FAIL]${NC} $1" + TESTS_FAILED=$((TESTS_FAILED + 1)) +} + +run_test() { + TESTS_RUN=$((TESTS_RUN + 1)) + local test_name="$1" + shift + + log_test "$test_name" + + if [ "$VERBOSE" = "1" ]; then + echo "Command: $*" + fi + + if "$@" >/dev/null 2>&1; then + log_pass "$test_name" + return 0 + else + log_fail "$test_name" + return 1 + fi +} + +# Test suite +echo "=====================================" +echo "pdsh Option Mapping Tests" +echo "=====================================" +echo "Test host: $TEST_HOST" +echo "Command: $PDSH_CMD" +echo + +# Test 1: -w (host specification) +run_test "Option -w (hosts)" \ + $PDSH_CMD -w "$TEST_HOST" "echo test" + +# Test 2: -l (user specification) +run_test "Option -l (user)" \ + $PDSH_CMD -w "$TEST_HOST" -l "$TEST_USER" "whoami" + +# Test 3: -N (no prefix) +((TESTS_RUN++)) +log_test "Option -N (no prefix)" +output=$($PDSH_CMD -w "$TEST_HOST" -N "echo test" 2>&1) +if echo "$output" | grep -q "^\[" ; then + log_fail "Option -N (output still has prefix)" +else + log_pass "Option -N (no prefix)" +fi + +# Test 4: -q (query mode) +run_test "Option -q (query mode)" \ + $PDSH_CMD -w "$TEST_HOST" -q + +# Test 5: -f (fanout/parallel) +run_test "Option -f (fanout)" \ + $PDSH_CMD -w "$TEST_HOST" -f 5 "echo test" + +# Test 6: -t (connect timeout) +run_test "Option -t (connect timeout)" \ + $PDSH_CMD -w "$TEST_HOST" -t 10 "echo test" + +# Test 7: -u (command timeout) +run_test "Option -u (command timeout)" \ + $PDSH_CMD -w "$TEST_HOST" -u 5 "echo test" + +# Test 8: -x (exclude hosts) +((TESTS_RUN++)) +log_test "Option -x (exclude)" +output=$($PDSH_CMD -w "host1,host2,host3" -x "host2" -q 2>&1) +if echo "$output" | grep -q "host1" && \ + echo "$output" | grep -q "host3" && \ + ! echo "$output" | grep -q "host2"; then + log_pass "Option -x (exclude)" +else + log_fail "Option -x (exclude)" +fi + +# Test 9: -b (batch mode) +run_test "Option -b (batch mode)" \ + $PDSH_CMD -w "$TEST_HOST" -b "echo test" + +# Test 10: -k (fail-fast) +run_test "Option -k (fail-fast)" \ + $PDSH_CMD -w "$TEST_HOST" -k "echo test" + +# Test 11: Combined options -w -f -t +run_test "Combined options (-w -f -t)" \ + $PDSH_CMD -w "$TEST_HOST" -f 10 -t 5 "echo test" + +# Test 12: Combined options -w -l -N +run_test "Combined options (-w -l -N)" \ + $PDSH_CMD -w "$TEST_HOST" -l "$TEST_USER" -N "echo test" + +# Test 13: Combined options -w -x -q +((TESTS_RUN++)) +log_test "Combined options (-w -x -q)" +output=$($PDSH_CMD -w "test[1-5]" -x "test[3-4]" -q 2>&1) +expected_count=3 +actual_count=$(echo "$output" | wc -l | tr -d ' ') +if [ "$actual_count" -eq "$expected_count" ]; then + log_pass "Combined options (-w -x -q)" +else + log_fail "Combined options (-w -x -q) - expected $expected_count hosts, got $actual_count" +fi + +# Test 14: All timeout options +run_test "All timeout options (-t -u)" \ + $PDSH_CMD -w "$TEST_HOST" -t 10 -u 60 "echo test" + +# Test 15: Fanout with query +((TESTS_RUN++)) +log_test "Fanout with query (-f -q)" +output=$($PDSH_CMD -w "host[1-3]" -f 2 -q 2>&1) +if [ "$(echo "$output" | wc -l | tr -d ' ')" -eq 3 ]; then + log_pass "Fanout with query (-f -q)" +else + log_fail "Fanout with query (-f -q)" +fi + +# Test 16: Exclusion with glob pattern +((TESTS_RUN++)) +log_test "Exclusion with glob (-x 'pattern*')" +output=$($PDSH_CMD -w "web1,web2,db1,db2" -x "db*" -q 2>&1) +if echo "$output" | grep -q "web1" && \ + echo "$output" | grep -q "web2" && \ + ! echo "$output" | grep -q "db"; then + log_pass "Exclusion with glob (-x 'pattern*')" +else + log_fail "Exclusion with glob (-x 'pattern*')" +fi + +# Test 17: User with hostlist expression +run_test "User with hostlist (-l with -w expression)" \ + $PDSH_CMD -w "localhost" -l "$TEST_USER" "echo test" + +# Test 18: No prefix with multiple outputs +((TESTS_RUN++)) +log_test "No prefix with command output (-N)" +output=$($PDSH_CMD -w "$TEST_HOST" -N "echo line1; echo line2" 2>&1) +line_count=$(echo "$output" | grep -v "^\[" | wc -l | tr -d ' ') +if [ "$line_count" -ge 2 ]; then + log_pass "No prefix with command output (-N)" +else + log_fail "No prefix with command output (-N)" +fi + +# Test 19: Query with username +((TESTS_RUN++)) +log_test "Query with username (-l -q)" +output=$($PDSH_CMD -w "test@host1,test@host2" -q 2>&1) +if echo "$output" | grep -q "test@host1" && \ + echo "$output" | grep -q "test@host2"; then + log_pass "Query with username (-l -q)" +else + log_fail "Query with username (-l -q)" +fi + +# Test 20: Complex exclusion pattern +((TESTS_RUN++)) +log_test "Complex exclusion pattern" +output=$($PDSH_CMD -w "prod[1-10],staging[1-5]" -x "staging*" -q 2>&1) +if echo "$output" | grep -q "prod" && \ + ! echo "$output" | grep -q "staging"; then + log_pass "Complex exclusion pattern" +else + log_fail "Complex exclusion pattern" +fi + +# Summary +echo +echo "=====================================" +echo "Test Summary" +echo "=====================================" +echo "Tests run: $TESTS_RUN" +echo "Tests passed: $TESTS_PASSED" +echo "Tests failed: $TESTS_FAILED" +echo + +if [ "$TESTS_FAILED" -eq 0 ]; then + echo -e "${GREEN}All tests passed!${NC}" + exit 0 +else + echo -e "${RED}Some tests failed.${NC}" + exit 1 +fi