refactor: move api, client, compose packages to pkg

This commit is contained in:
Pavel Sviderski
2025-03-22 18:43:00 +10:00
parent 3757813b20
commit b1abd07dde
42 changed files with 82 additions and 82 deletions
+88
View File
@@ -0,0 +1,88 @@
package connector
import (
"context"
"fmt"
"github.com/psviderski/uncloud/internal/machine"
"github.com/psviderski/uncloud/internal/sshexec"
"golang.org/x/crypto/ssh"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"net"
"strings"
)
type SSHConnectorConfig struct {
User string
Host string
Port int
KeyPath string
SockPath string
}
// SSHConnector establishes a connection to the machine API through an SSH tunnel to the machine.
type SSHConnector struct {
config SSHConnectorConfig
client *ssh.Client
}
func NewSSHConnector(cfg *SSHConnectorConfig) *SSHConnector {
return &SSHConnector{config: *cfg}
}
func NewSSHConnectorFromClient(client *ssh.Client) *SSHConnector {
return &SSHConnector{client: client}
}
// TODO: handle context cancelation.
func (c *SSHConnector) Connect(ctx context.Context) (*grpc.ClientConn, error) {
if c.client == nil {
// Establish an SSH connection if the SSH client is not provided.
if c.config == (SSHConnectorConfig{}) {
return nil, fmt.Errorf("SSH connector not configured")
}
var err error
c.client, err = sshexec.Connect(c.config.User, c.config.Host, c.config.Port, c.config.KeyPath)
if err != nil {
return nil, fmt.Errorf("SSH login to %s@%s:%d: %w", c.config.User, c.config.Host, c.config.Port, err)
}
}
sockPath := c.config.SockPath
if sockPath == "" {
sockPath = machine.DefaultUncloudSockPath
}
conn, err := grpc.NewClient(
"unix://"+sockPath,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(
func(ctx context.Context, addr string) (net.Conn, error) {
addr = strings.TrimPrefix(addr, "unix://")
conn, dErr := c.client.DialContext(ctx, "unix", addr)
if dErr != nil {
return nil, fmt.Errorf(
"connect to machine API socket '%s' through SSH tunnel (is the Uncloud daemon running "+
"on the remote machine and does the SSH user '%s' have permissions to access the socket?):"+
" %w",
addr, c.client.User(), dErr,
)
}
return conn, nil
},
),
)
if err != nil {
return nil, fmt.Errorf("create machine API client: %w", err)
}
return conn, nil
}
func (c *SSHConnector) Close() error {
if c.client != nil {
err := c.client.Close()
c.client = nil
return err
}
return nil
}
+33
View File
@@ -0,0 +1,33 @@
package connector
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"net/netip"
)
// TCPConnector establishes a connection to the machine API through a direct TCP connection to an API endpoint.
type TCPConnector struct {
apiAddr netip.AddrPort
}
func NewTCPConnector(apiAddr netip.AddrPort) *TCPConnector {
return &TCPConnector{apiAddr: apiAddr}
}
func (c *TCPConnector) Connect(_ context.Context) (*grpc.ClientConn, error) {
conn, err := grpc.NewClient(
c.apiAddr.String(),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
return nil, fmt.Errorf("create machine API client: %w", err)
}
return conn, nil
}
func (c *TCPConnector) Close() error {
return nil
}
+83
View File
@@ -0,0 +1,83 @@
package connector
import (
"context"
"fmt"
"github.com/psviderski/uncloud/internal/cli/config"
machine2 "github.com/psviderski/uncloud/internal/machine"
"github.com/psviderski/uncloud/internal/machine/network"
"github.com/psviderski/uncloud/internal/machine/network/tunnel"
"github.com/psviderski/uncloud/pkg/client"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"net"
"net/netip"
"strconv"
)
// WireGuardConnector establishes a connection to the cluster API through a WireGuard tunnel
// to one of the cluster machines.
type WireGuardConnector struct {
user *client.User
machines []config.MachineConnection
tun *tunnel.Tunnel
}
func NewWireGuardConnector(user *client.User, machines []config.MachineConnection) *WireGuardConnector {
return &WireGuardConnector{
user: user,
machines: machines,
}
}
// TODO: handle context cancelation.
func (c *WireGuardConnector) Connect(ctx context.Context) (*grpc.ClientConn, error) {
if len(c.machines) == 0 {
return nil, fmt.Errorf("no machines to connect to")
}
// TODO: iterate over machines and try to connect to each one until successful.
// For now, try to connect to only the first machine.
machine := c.machines[0]
endpointIPs, err := net.LookupIP(machine.Host)
if err != nil {
return nil, fmt.Errorf("resolve IP for %q: %w", machine.Host, err)
}
endpointAddr, err := netip.ParseAddr(endpointIPs[0].String())
if err != nil {
return nil, fmt.Errorf("parse IP address %q: %w", endpointIPs[0].String(), err)
}
endpoint := netip.AddrPortFrom(endpointAddr, tunnel.DefaultEndpointPort)
machineManagementIP := network.ManagementIP(machine.PublicKey)
machineAPIAddr := net.JoinHostPort(machineManagementIP.String(), strconv.Itoa(machine2.APIPort))
tunCfg := &tunnel.Config{
LocalAddress: c.user.ManagementIP(),
LocalPrivateKey: c.user.PrivateKey(),
RemotePublicKey: machine.PublicKey,
RemoteNetwork: netip.PrefixFrom(machineManagementIP, 128),
Endpoint: endpoint,
}
if c.tun, err = tunnel.Connect(tunCfg); err != nil {
return nil, fmt.Errorf("establish WireGuard tunnel to %q: %w", endpoint, err)
}
conn, err := grpc.NewClient(
machineAPIAddr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
return c.tun.DialContext(ctx, "tcp", addr)
}),
)
if err != nil {
return nil, fmt.Errorf("connect to machine API through WireGuard tunnel: %w", err)
}
return conn, nil
}
func (c *WireGuardConnector) Close() error {
if c.tun != nil {
c.tun.Close()
c.tun = nil
}
return nil
}