feat(connection): Add Connection interface for local/remote ops (gt-f9x.7)
Define the Connection interface that abstracts file operations, command execution, and tmux management for both local and remote (SSH) contexts. Includes: - Connection interface with file, exec, and tmux operations - FileInfo interface for remote stat results - BasicFileInfo implementation with JSON serialization - Error types for connection failures This enables Gas Town to manage rigs on remote machines using the same interface as local operations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
170
internal/connection/connection.go
Normal file
170
internal/connection/connection.go
Normal file
@@ -0,0 +1,170 @@
|
||||
// Package connection provides an abstraction for local and remote operations.
|
||||
// This allows Gas Town to manage rigs on remote machines via SSH using
|
||||
// the same interface as local operations.
|
||||
package connection
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Connection abstracts file operations, command execution, and tmux management
|
||||
// for both local and remote (SSH) execution contexts.
|
||||
type Connection interface {
|
||||
// Identification
|
||||
|
||||
// Name returns a human-readable name for this connection.
|
||||
Name() string
|
||||
|
||||
// IsLocal returns true if this is a local connection.
|
||||
IsLocal() bool
|
||||
|
||||
// File operations
|
||||
|
||||
// ReadFile reads the named file and returns its contents.
|
||||
ReadFile(path string) ([]byte, error)
|
||||
|
||||
// WriteFile writes data to the named file with the given permissions.
|
||||
WriteFile(path string, data []byte, perm fs.FileMode) error
|
||||
|
||||
// MkdirAll creates a directory and all parent directories.
|
||||
MkdirAll(path string, perm fs.FileMode) error
|
||||
|
||||
// Remove removes the named file or empty directory.
|
||||
Remove(path string) error
|
||||
|
||||
// RemoveAll removes the named file or directory and any children.
|
||||
RemoveAll(path string) error
|
||||
|
||||
// Stat returns file info for the named file.
|
||||
Stat(path string) (FileInfo, error)
|
||||
|
||||
// Glob returns the names of all files matching the pattern.
|
||||
Glob(pattern string) ([]string, error)
|
||||
|
||||
// Exists returns true if the path exists.
|
||||
Exists(path string) (bool, error)
|
||||
|
||||
// Command execution
|
||||
|
||||
// Exec runs a command and returns its combined output.
|
||||
Exec(cmd string, args ...string) ([]byte, error)
|
||||
|
||||
// ExecDir runs a command in the specified directory.
|
||||
ExecDir(dir, cmd string, args ...string) ([]byte, error)
|
||||
|
||||
// ExecEnv runs a command with additional environment variables.
|
||||
ExecEnv(env map[string]string, cmd string, args ...string) ([]byte, error)
|
||||
|
||||
// Tmux operations
|
||||
|
||||
// TmuxNewSession creates a new tmux session with the given name.
|
||||
TmuxNewSession(name, dir string) error
|
||||
|
||||
// TmuxKillSession terminates the named tmux session.
|
||||
TmuxKillSession(name string) error
|
||||
|
||||
// TmuxSendKeys sends keys to the named tmux session.
|
||||
TmuxSendKeys(session, keys string) error
|
||||
|
||||
// TmuxCapturePane captures the last N lines from a tmux pane.
|
||||
TmuxCapturePane(session string, lines int) (string, error)
|
||||
|
||||
// TmuxHasSession returns true if the named tmux session exists.
|
||||
TmuxHasSession(name string) (bool, error)
|
||||
|
||||
// TmuxListSessions returns a list of all tmux session names.
|
||||
TmuxListSessions() ([]string, error)
|
||||
}
|
||||
|
||||
// FileInfo abstracts fs.FileInfo for use over remote connections.
|
||||
// This is needed because fs.FileInfo contains methods that can't be
|
||||
// easily serialized over SSH.
|
||||
type FileInfo interface {
|
||||
// Name returns the base name of the file.
|
||||
Name() string
|
||||
|
||||
// Size returns the length in bytes.
|
||||
Size() int64
|
||||
|
||||
// Mode returns the file mode bits.
|
||||
Mode() fs.FileMode
|
||||
|
||||
// ModTime returns the modification time.
|
||||
ModTime() time.Time
|
||||
|
||||
// IsDir returns true if this is a directory.
|
||||
IsDir() bool
|
||||
}
|
||||
|
||||
// BasicFileInfo is a simple implementation of FileInfo.
|
||||
type BasicFileInfo struct {
|
||||
FileName string `json:"name"`
|
||||
FileSize int64 `json:"size"`
|
||||
FileMode fs.FileMode `json:"mode"`
|
||||
FileModTime time.Time `json:"mod_time"`
|
||||
FileIsDir bool `json:"is_dir"`
|
||||
}
|
||||
|
||||
// Name implements FileInfo.
|
||||
func (f BasicFileInfo) Name() string { return f.FileName }
|
||||
|
||||
// Size implements FileInfo.
|
||||
func (f BasicFileInfo) Size() int64 { return f.FileSize }
|
||||
|
||||
// Mode implements FileInfo.
|
||||
func (f BasicFileInfo) Mode() fs.FileMode { return f.FileMode }
|
||||
|
||||
// ModTime implements FileInfo.
|
||||
func (f BasicFileInfo) ModTime() time.Time { return f.FileModTime }
|
||||
|
||||
// IsDir implements FileInfo.
|
||||
func (f BasicFileInfo) IsDir() bool { return f.FileIsDir }
|
||||
|
||||
// FromOSFileInfo creates a BasicFileInfo from an os.FileInfo.
|
||||
func FromOSFileInfo(fi fs.FileInfo) BasicFileInfo {
|
||||
return BasicFileInfo{
|
||||
FileName: fi.Name(),
|
||||
FileSize: fi.Size(),
|
||||
FileMode: fi.Mode(),
|
||||
FileModTime: fi.ModTime(),
|
||||
FileIsDir: fi.IsDir(),
|
||||
}
|
||||
}
|
||||
|
||||
// Error types for connection operations.
|
||||
type (
|
||||
// ConnectionError indicates a connection-level failure.
|
||||
ConnectionError struct {
|
||||
Op string // Operation that failed (e.g., "connect", "exec")
|
||||
Machine string // Machine name or address
|
||||
Err error // Underlying error
|
||||
}
|
||||
|
||||
// NotFoundError indicates a file or resource was not found.
|
||||
NotFoundError struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
// PermissionError indicates an access permission failure.
|
||||
PermissionError struct {
|
||||
Path string
|
||||
Op string
|
||||
}
|
||||
)
|
||||
|
||||
func (e *ConnectionError) Error() string {
|
||||
return "connection " + e.Op + " on " + e.Machine + ": " + e.Err.Error()
|
||||
}
|
||||
|
||||
func (e *ConnectionError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
func (e *NotFoundError) Error() string {
|
||||
return "not found: " + e.Path
|
||||
}
|
||||
|
||||
func (e *PermissionError) Error() string {
|
||||
return "permission denied: " + e.Op + " " + e.Path
|
||||
}
|
||||
Reference in New Issue
Block a user