mirror of
https://github.com/Gu1llaum-3/sshm.git
synced 2026-03-13 19:31:44 +01:00
refactor: update NewPingManager to accept a config file parameter
- Modified the NewPingManager function to include a configFile argument for better SSH configuration management. - Updated all relevant tests to reflect the new function signature. - Enhanced ping functionality to support ProxyJump and ProxyCommand using an external SSH command. - Adjusted UI initialization to pass the config file to the PingManager. This change improves flexibility in managing SSH connections and enhances the overall functionality of the ping manager.
This commit is contained in:
@@ -2,12 +2,15 @@ package connectivity
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"github.com/Gu1llaum-3/sshm/internal/config"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Gu1llaum-3/sshm/internal/config"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -45,16 +48,18 @@ type HostPingResult struct {
|
|||||||
|
|
||||||
// PingManager manages SSH connectivity checks for multiple hosts
|
// PingManager manages SSH connectivity checks for multiple hosts
|
||||||
type PingManager struct {
|
type PingManager struct {
|
||||||
results map[string]*HostPingResult
|
results map[string]*HostPingResult
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
|
configFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPingManager creates a new ping manager with the specified timeout
|
// NewPingManager creates a new ping manager with the specified timeout
|
||||||
func NewPingManager(timeout time.Duration) *PingManager {
|
func NewPingManager(timeout time.Duration, configFile string) *PingManager {
|
||||||
return &PingManager{
|
return &PingManager{
|
||||||
results: make(map[string]*HostPingResult),
|
results: make(map[string]*HostPingResult),
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
|
configFile: configFile,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +103,14 @@ func (pm *PingManager) PingHost(ctx context.Context, host config.SSHHost) *HostP
|
|||||||
// Mark as connecting
|
// Mark as connecting
|
||||||
pm.updateStatus(host.Name, StatusConnecting, nil, 0)
|
pm.updateStatus(host.Name, StatusConnecting, nil, 0)
|
||||||
|
|
||||||
|
// If the host uses a ProxyJump or ProxyCommand, we need to use the external SSH command
|
||||||
|
// because implementing jump host support with pure Go ssh library requires
|
||||||
|
// handling authentication for the jump host, which is complex and requires
|
||||||
|
// access to the user's SSH agent or keys.
|
||||||
|
if host.ProxyJump != "" || host.ProxyCommand != "" {
|
||||||
|
return pm.pingWithExternalCommand(ctx, host, start)
|
||||||
|
}
|
||||||
|
|
||||||
// Determine the actual hostname and port
|
// Determine the actual hostname and port
|
||||||
hostname := host.Hostname
|
hostname := host.Hostname
|
||||||
if hostname == "" {
|
if hostname == "" {
|
||||||
@@ -159,6 +172,53 @@ func (pm *PingManager) PingHost(ctx context.Context, host config.SSHHost) *HostP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pingWithExternalCommand pings a host using the external SSH command
|
||||||
|
func (pm *PingManager) pingWithExternalCommand(ctx context.Context, host config.SSHHost, start time.Time) *HostPingResult {
|
||||||
|
// Construct the SSH command
|
||||||
|
// ssh -q -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=5 host exit
|
||||||
|
args := []string{"-q", "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=no"}
|
||||||
|
|
||||||
|
// Set timeout matching the manager's timeout
|
||||||
|
// Convert duration to seconds (rounding up to ensure we don't timeout too early in the command)
|
||||||
|
timeoutSec := int(pm.timeout.Seconds())
|
||||||
|
if timeoutSec < 1 {
|
||||||
|
timeoutSec = 1
|
||||||
|
}
|
||||||
|
args = append(args, "-o", fmt.Sprintf("ConnectTimeout=%d", timeoutSec))
|
||||||
|
|
||||||
|
// If we have a specific config file, use it
|
||||||
|
if pm.configFile != "" {
|
||||||
|
args = append(args, "-F", pm.configFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the host name and the command to run (exit)
|
||||||
|
args = append(args, host.Name, "exit")
|
||||||
|
|
||||||
|
// Create command with context for timeout cancellation
|
||||||
|
// Note: We used pm.timeout for the ssh command option, but we also respect the context deadline
|
||||||
|
cmd := exec.CommandContext(ctx, "ssh", args...)
|
||||||
|
|
||||||
|
// Run the command
|
||||||
|
err := cmd.Run()
|
||||||
|
duration := time.Since(start)
|
||||||
|
|
||||||
|
var status PingStatus
|
||||||
|
if err != nil {
|
||||||
|
// SSH returns non-zero exit code on connection failure
|
||||||
|
status = StatusOffline
|
||||||
|
} else {
|
||||||
|
status = StatusOnline
|
||||||
|
}
|
||||||
|
|
||||||
|
pm.updateStatus(host.Name, status, err, duration)
|
||||||
|
return &HostPingResult{
|
||||||
|
HostName: host.Name,
|
||||||
|
Status: status,
|
||||||
|
Error: err,
|
||||||
|
Duration: duration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// PingAllHosts pings all hosts concurrently and returns a channel of results
|
// PingAllHosts pings all hosts concurrently and returns a channel of results
|
||||||
func (pm *PingManager) PingAllHosts(ctx context.Context, hosts []config.SSHHost) <-chan *HostPingResult {
|
func (pm *PingManager) PingAllHosts(ctx context.Context, hosts []config.SSHHost) <-chan *HostPingResult {
|
||||||
resultChan := make(chan *HostPingResult, len(hosts))
|
resultChan := make(chan *HostPingResult, len(hosts))
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestNewPingManager(t *testing.T) {
|
func TestNewPingManager(t *testing.T) {
|
||||||
pm := NewPingManager(5 * time.Second)
|
pm := NewPingManager(5*time.Second, "")
|
||||||
if pm == nil {
|
if pm == nil {
|
||||||
t.Error("NewPingManager() returned nil")
|
t.Error("NewPingManager() returned nil")
|
||||||
}
|
}
|
||||||
@@ -19,16 +19,16 @@ func TestNewPingManager(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPingManager_PingHost(t *testing.T) {
|
func TestPingManager_PingHost(t *testing.T) {
|
||||||
pm := NewPingManager(1 * time.Second)
|
pm := NewPingManager(1*time.Second, "")
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// Test ping method exists and doesn't panic
|
// Test ping method exists and doesn't panic
|
||||||
host := config.SSHHost{Name: "test", Hostname: "127.0.0.1", Port: "22"}
|
host := config.SSHHost{Name: "test", Hostname: "127.0.0.1", Port: "22"}
|
||||||
result := pm.PingHost(ctx, host)
|
result := pm.PingHost(ctx, host)
|
||||||
if result == nil {
|
if result == nil {
|
||||||
t.Error("Expected ping result to be returned")
|
t.Error("Expected ping result to be returned")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test with invalid host
|
// Test with invalid host
|
||||||
invalidHost := config.SSHHost{Name: "invalid", Hostname: "invalid.host.12345", Port: "22"}
|
invalidHost := config.SSHHost{Name: "invalid", Hostname: "invalid.host.12345", Port: "22"}
|
||||||
result = pm.PingHost(ctx, invalidHost)
|
result = pm.PingHost(ctx, invalidHost)
|
||||||
@@ -38,14 +38,14 @@ func TestPingManager_PingHost(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPingManager_GetStatus(t *testing.T) {
|
func TestPingManager_GetStatus(t *testing.T) {
|
||||||
pm := NewPingManager(1 * time.Second)
|
pm := NewPingManager(1*time.Second, "")
|
||||||
|
|
||||||
// Test unknown host
|
// Test unknown host
|
||||||
status := pm.GetStatus("unknown.host")
|
status := pm.GetStatus("unknown.host")
|
||||||
if status != StatusUnknown {
|
if status != StatusUnknown {
|
||||||
t.Errorf("Expected StatusUnknown for unknown host, got %v", status)
|
t.Errorf("Expected StatusUnknown for unknown host, got %v", status)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test after ping
|
// Test after ping
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
host := config.SSHHost{Name: "test", Hostname: "127.0.0.1", Port: "22"}
|
host := config.SSHHost{Name: "test", Hostname: "127.0.0.1", Port: "22"}
|
||||||
@@ -57,21 +57,21 @@ func TestPingManager_GetStatus(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPingManager_PingMultipleHosts(t *testing.T) {
|
func TestPingManager_PingMultipleHosts(t *testing.T) {
|
||||||
pm := NewPingManager(1 * time.Second)
|
pm := NewPingManager(1*time.Second, "")
|
||||||
hosts := []config.SSHHost{
|
hosts := []config.SSHHost{
|
||||||
{Name: "localhost", Hostname: "127.0.0.1", Port: "22"},
|
{Name: "localhost", Hostname: "127.0.0.1", Port: "22"},
|
||||||
{Name: "invalid", Hostname: "invalid.host.12345", Port: "22"},
|
{Name: "invalid", Hostname: "invalid.host.12345", Port: "22"},
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// Ping each host individually
|
// Ping each host individually
|
||||||
for _, host := range hosts {
|
for _, host := range hosts {
|
||||||
result := pm.PingHost(ctx, host)
|
result := pm.PingHost(ctx, host)
|
||||||
if result == nil {
|
if result == nil {
|
||||||
t.Errorf("Expected ping result for host %s", host.Name)
|
t.Errorf("Expected ping result for host %s", host.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that status was set
|
// Check that status was set
|
||||||
status := pm.GetStatus(host.Name)
|
status := pm.GetStatus(host.Name)
|
||||||
if status == StatusUnknown {
|
if status == StatusUnknown {
|
||||||
@@ -81,19 +81,19 @@ func TestPingManager_PingMultipleHosts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPingManager_GetResult(t *testing.T) {
|
func TestPingManager_GetResult(t *testing.T) {
|
||||||
pm := NewPingManager(1 * time.Second)
|
pm := NewPingManager(1*time.Second, "")
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// Test getting result for unknown host
|
// Test getting result for unknown host
|
||||||
result, exists := pm.GetResult("unknown")
|
result, exists := pm.GetResult("unknown")
|
||||||
if exists || result != nil {
|
if exists || result != nil {
|
||||||
t.Error("Expected no result for unknown host")
|
t.Error("Expected no result for unknown host")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test after ping
|
// Test after ping
|
||||||
host := config.SSHHost{Name: "test", Hostname: "127.0.0.1", Port: "22"}
|
host := config.SSHHost{Name: "test", Hostname: "127.0.0.1", Port: "22"}
|
||||||
pm.PingHost(ctx, host)
|
pm.PingHost(ctx, host)
|
||||||
|
|
||||||
result, exists = pm.GetResult("test")
|
result, exists = pm.GetResult("test")
|
||||||
if !exists || result == nil {
|
if !exists || result == nil {
|
||||||
t.Error("Expected result to exist after ping")
|
t.Error("Expected result to exist after ping")
|
||||||
@@ -114,7 +114,7 @@ func TestPingStatus_String(t *testing.T) {
|
|||||||
{StatusOffline, "offline"},
|
{StatusOffline, "offline"},
|
||||||
{PingStatus(999), "unknown"}, // Invalid status
|
{PingStatus(999), "unknown"}, // Invalid status
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.expected, func(t *testing.T) {
|
t.Run(tt.expected, func(t *testing.T) {
|
||||||
if got := tt.status.String(); got != tt.expected {
|
if got := tt.status.String(); got != tt.expected {
|
||||||
@@ -126,19 +126,19 @@ func TestPingStatus_String(t *testing.T) {
|
|||||||
|
|
||||||
func TestPingHost_Basic(t *testing.T) {
|
func TestPingHost_Basic(t *testing.T) {
|
||||||
// Test that the ping functionality exists
|
// Test that the ping functionality exists
|
||||||
pm := NewPingManager(1 * time.Second)
|
pm := NewPingManager(1*time.Second, "")
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
host := config.SSHHost{Name: "test", Hostname: "127.0.0.1", Port: "22"}
|
host := config.SSHHost{Name: "test", Hostname: "127.0.0.1", Port: "22"}
|
||||||
|
|
||||||
// Just ensure the function doesn't panic
|
// Just ensure the function doesn't panic
|
||||||
result := pm.PingHost(ctx, host)
|
result := pm.PingHost(ctx, host)
|
||||||
if result == nil {
|
if result == nil {
|
||||||
t.Error("Expected ping result to be returned")
|
t.Error("Expected ping result to be returned")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that status is set
|
// Test that status is set
|
||||||
status := pm.GetStatus("test")
|
status := pm.GetStatus("test")
|
||||||
if status == StatusUnknown {
|
if status == StatusUnknown {
|
||||||
t.Error("Expected status to be set after ping attempt")
|
t.Error("Expected status to be set after ping attempt")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func NewModel(hosts []config.SSHHost, configFile string, searchMode bool, curren
|
|||||||
styles := NewStyles(80) // Default width
|
styles := NewStyles(80) // Default width
|
||||||
|
|
||||||
// Initialize ping manager with 5 second timeout
|
// Initialize ping manager with 5 second timeout
|
||||||
pingManager := connectivity.NewPingManager(5 * time.Second)
|
pingManager := connectivity.NewPingManager(5*time.Second, configFile)
|
||||||
|
|
||||||
// Create the model with default sorting by name
|
// Create the model with default sorting by name
|
||||||
m := Model{
|
m := Model{
|
||||||
|
|||||||
Reference in New Issue
Block a user