package config import ( "errors" "fmt" "net" "net/netip" "strconv" "strings" "github.com/psviderski/uncloud/internal/secret" ) type MachineConnection struct { // SSH uses the system ssh CLI command to connect. This is the default SSH connection method. SSH SSHDestination `yaml:"ssh,omitempty"` // SSHCLI is a backward-compatible alias for SSH. SSHCLI SSHDestination `yaml:"ssh_cli,omitempty"` // SSHGo uses Go's built-in SSH library to connect. SSHGo SSHDestination `yaml:"ssh_go,omitempty"` SSHKeyFile string `yaml:"ssh_key_file,omitempty"` // TCP is the address and port of the machine's API server. // The pointer is used to omit the field when not set. Otherwise, yaml marshalling includes an empty object. TCP *netip.AddrPort `yaml:"tcp,omitempty"` // Unix is the path to the machine's API unix socket. Unix string `yaml:"unix,omitempty"` Host string `yaml:"host,omitempty"` PublicKey secret.Secret `yaml:"public_key,omitempty"` MachineID string `yaml:"machine_id,omitempty"` } func (c *MachineConnection) String() string { if c.SSH != "" { return "ssh://" + string(c.SSH) } else if c.SSHCLI != "" { return "ssh://" + string(c.SSHCLI) } else if c.SSHGo != "" { return "ssh+go://" + string(c.SSHGo) } else if c.TCP != nil && c.TCP.IsValid() { return fmt.Sprintf("tcp://%s", c.TCP) } else if c.Unix != "" { return fmt.Sprintf("unix://%s", c.Unix) } return "unknown connection" } func (c *MachineConnection) Validate() error { setCount := 0 if c.SSH != "" { setCount++ } if c.SSHCLI != "" { setCount++ } if c.SSHGo != "" { setCount++ } if c.TCP != nil && c.TCP.IsValid() { setCount++ } if c.Unix != "" { setCount++ } if setCount == 0 { return errors.New("no connection method specified (ssh, ssh_go, tcp, or unix required)") } if setCount > 1 { return errors.New("only one connection method allowed per connection (ssh, ssh_go, tcp, or unix)") } return nil } // SSHDestination represents an SSH destination string in the canonical form of "user@host:port". // Empty user or port components are omitted. type SSHDestination string // NewSSHDestination constructs an SSHDestination from user, host, and port components. // If user is empty, it is omitted. // If port is 0, it is omitted. func NewSSHDestination(user, host string, port int) SSHDestination { dst := host if port != 0 { dst = net.JoinHostPort(host, strconv.Itoa(port)) } if user != "" { dst = fmt.Sprintf("%s@%s", user, dst) } return SSHDestination(dst) } // Parse parses the SSH destination string into user, host, and port components. // If user is not specified, it returns an empty string. // If port is not specified, it returns 0. func (d SSHDestination) Parse() (user string, host string, port int, err error) { host = string(d) if strings.Contains(host, "@") { user, host, _ = strings.Cut(host, "@") } h, p, sErr := net.SplitHostPort(host) if sErr == nil { host = h port, err = strconv.Atoi(p) } return }