mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-21 21:26:16 +01:00
- Introduce `Transport` interface to abstract WebSocket and SSH communication - Add generic `Data` field to `AgentResponse` for streamlined future endpoints - Maintain backward compatibility with legacy hubs and agents using typed fields - Unify fetch operations (SMART, systemd, containers) under a single `request` method - Improve `RequestManager` with deadline awareness and legacy response support - Refactor agent response routing into dedicated `agent/response.go` - Update version to 0.18.0-beta.2
113 lines
3.5 KiB
Go
113 lines
3.5 KiB
Go
// Package transport provides a unified abstraction for hub-agent communication
|
|
// over different transports (WebSocket, SSH).
|
|
package transport
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/fxamacker/cbor/v2"
|
|
"github.com/henrygd/beszel/internal/common"
|
|
"github.com/henrygd/beszel/internal/entities/smart"
|
|
"github.com/henrygd/beszel/internal/entities/system"
|
|
"github.com/henrygd/beszel/internal/entities/systemd"
|
|
)
|
|
|
|
// Transport defines the interface for hub-agent communication.
|
|
// Both WebSocket and SSH transports implement this interface.
|
|
type Transport interface {
|
|
// Request sends a request to the agent and unmarshals the response into dest.
|
|
// The dest parameter should be a pointer to the expected response type.
|
|
Request(ctx context.Context, action common.WebSocketAction, req any, dest any) error
|
|
// IsConnected returns true if the transport connection is active.
|
|
IsConnected() bool
|
|
// Close terminates the transport connection.
|
|
Close()
|
|
}
|
|
|
|
// UnmarshalResponse unmarshals an AgentResponse into the destination type.
|
|
// It first checks the generic Data field (0.19+ agents), then falls back
|
|
// to legacy typed fields for backward compatibility with 0.18.0 agents.
|
|
func UnmarshalResponse(resp common.AgentResponse, action common.WebSocketAction, dest any) error {
|
|
if dest == nil {
|
|
return errors.New("nil destination")
|
|
}
|
|
// Try generic Data field first (0.19+)
|
|
if len(resp.Data) > 0 {
|
|
if err := cbor.Unmarshal(resp.Data, dest); err != nil {
|
|
return fmt.Errorf("failed to unmarshal generic response data: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
// Fall back to legacy typed fields for older agents/hubs.
|
|
return unmarshalLegacyResponse(resp, action, dest)
|
|
}
|
|
|
|
// unmarshalLegacyResponse handles legacy responses that use typed fields.
|
|
func unmarshalLegacyResponse(resp common.AgentResponse, action common.WebSocketAction, dest any) error {
|
|
switch action {
|
|
case common.GetData:
|
|
d, ok := dest.(*system.CombinedData)
|
|
if !ok {
|
|
return fmt.Errorf("unexpected dest type for GetData: %T", dest)
|
|
}
|
|
if resp.SystemData == nil {
|
|
return errors.New("no system data in response")
|
|
}
|
|
*d = *resp.SystemData
|
|
return nil
|
|
case common.CheckFingerprint:
|
|
d, ok := dest.(*common.FingerprintResponse)
|
|
if !ok {
|
|
return fmt.Errorf("unexpected dest type for CheckFingerprint: %T", dest)
|
|
}
|
|
if resp.Fingerprint == nil {
|
|
return errors.New("no fingerprint in response")
|
|
}
|
|
*d = *resp.Fingerprint
|
|
return nil
|
|
case common.GetContainerLogs:
|
|
d, ok := dest.(*string)
|
|
if !ok {
|
|
return fmt.Errorf("unexpected dest type for GetContainerLogs: %T", dest)
|
|
}
|
|
if resp.String == nil {
|
|
return errors.New("no logs in response")
|
|
}
|
|
*d = *resp.String
|
|
return nil
|
|
case common.GetContainerInfo:
|
|
d, ok := dest.(*string)
|
|
if !ok {
|
|
return fmt.Errorf("unexpected dest type for GetContainerInfo: %T", dest)
|
|
}
|
|
if resp.String == nil {
|
|
return errors.New("no info in response")
|
|
}
|
|
*d = *resp.String
|
|
return nil
|
|
case common.GetSmartData:
|
|
d, ok := dest.(*map[string]smart.SmartData)
|
|
if !ok {
|
|
return fmt.Errorf("unexpected dest type for GetSmartData: %T", dest)
|
|
}
|
|
if resp.SmartData == nil {
|
|
return errors.New("no SMART data in response")
|
|
}
|
|
*d = resp.SmartData
|
|
return nil
|
|
case common.GetSystemdInfo:
|
|
d, ok := dest.(*systemd.ServiceDetails)
|
|
if !ok {
|
|
return fmt.Errorf("unexpected dest type for GetSystemdInfo: %T", dest)
|
|
}
|
|
if resp.ServiceInfo == nil {
|
|
return errors.New("no systemd info in response")
|
|
}
|
|
*d = resp.ServiceInfo
|
|
return nil
|
|
}
|
|
return fmt.Errorf("unsupported action: %d", action)
|
|
}
|