fix(remote): clarify udp reachability
- commit
- d737ef0a758eb5dffd931026ef4f739f7a9eee26
- author
- brice · · 2026-07-06 21:28 UTC
- parent
- c38f440
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +89 -78
M README.md +2 -2
| @@ -120,9 +120,9 @@ vev attach user@host | |||
| 120 | 120 | vev attach user@host:session | |
| 121 | 121 | ``` | |
| 122 | 122 | ||
| 123 | - | Remote attach uses SSH to bootstrap a UDP transport by default, so `vev` must be installed on the remote host and reachable through your normal SSH config. Omitting `:session` opens an ephemeral remote session. Named sessions can resume after temporary network disconnects, such as sleep or Wi-Fi changes. | |
| 123 | + | Remote attach uses SSH only to start a UDP proxy on the remote host; the session itself then talks directly to the remote host over UDP. `vev` must be installed on the remote host, and the host name you pass to `vev attach` must be reachable both by SSH for bootstrap and by UDP for the session transport. Omitting `:session` opens an ephemeral remote session. Named sessions can resume after temporary network disconnects, such as sleep or Wi-Fi changes. | |
| 124 | 124 | ||
| 125 | - | Set `VEV_REMOTE_TRANSPORT=stdio` to use SSH stdio compatibility mode on networks where UDP is blocked. Stdio mode can work through SSH-only paths, but it may not notice sleep or Wi-Fi loss promptly. | |
| 125 | + | Set `VEV_REMOTE_TRANSPORT=stdio` to use SSH stdio compatibility mode on networks where direct UDP is blocked or the SSH target is only reachable through jump-host or SSH-config-only routing. Stdio mode keeps all traffic inside SSH, but it may not notice sleep or Wi-Fi loss promptly. | |
| 126 | 126 | ||
| 127 | 127 | ## Terminal color | |
| 128 | 128 | ||
| @@ -120,9 +120,9 @@ vev attach user@host | |||||
| 120 | vev attach user@host:session | 120 | vev attach user@host:session | ||
| 121 | ``` | 121 | ``` | ||
| 122 | 122 | ||||
| 123 | - | Remote attach uses SSH to bootstrap a UDP transport by default, so `vev` must be installed on the remote host and reachable through your normal SSH config. Omitting `:session` opens an ephemeral remote session. Named sessions can resume after temporary network disconnects, such as sleep or Wi-Fi changes. | 123 | + | Remote attach uses SSH only to start a UDP proxy on the remote host; the session itself then talks directly to the remote host over UDP. `vev` must be installed on the remote host, and the host name you pass to `vev attach` must be reachable both by SSH for bootstrap and by UDP for the session transport. Omitting `:session` opens an ephemeral remote session. Named sessions can resume after temporary network disconnects, such as sleep or Wi-Fi changes. |
| 124 | 124 | ||||
| 125 | - | Set `VEV_REMOTE_TRANSPORT=stdio` to use SSH stdio compatibility mode on networks where UDP is blocked. Stdio mode can work through SSH-only paths, but it may not notice sleep or Wi-Fi loss promptly. | 125 | + | Set `VEV_REMOTE_TRANSPORT=stdio` to use SSH stdio compatibility mode on networks where direct UDP is blocked or the SSH target is only reachable through jump-host or SSH-config-only routing. Stdio mode keeps all traffic inside SSH, but it may not notice sleep or Wi-Fi loss promptly. |
| 126 | 126 | ||||
| 127 | ## Terminal color | 127 | ## Terminal color | ||
| 128 | 128 | ||||
M internal/adapters/dgram/ssh_dialer_test.go +0 -69
| @@ -10,78 +10,9 @@ import ( | |||
| 10 | 10 | "testing" | |
| 11 | 11 | "time" | |
| 12 | 12 | ||
| 13 | - | "github.com/bnema/vev/internal/ports" | |
| 14 | 13 | pdgram "github.com/bnema/vev/pkg/dgram" | |
| 15 | 14 | ) | |
| 16 | 15 | ||
| 17 | - | func TestRemoteDialerFactorySelectsExplicitModes(t *testing.T) { | |
| 18 | - | factory := NewRemoteDialerFactory() | |
| 19 | - | ||
| 20 | - | tests := []struct { | |
| 21 | - | name string | |
| 22 | - | mode ports.RemoteTransportMode | |
| 23 | - | wantType any | |
| 24 | - | }{ | |
| 25 | - | {name: "udp", mode: ports.RemoteTransportUDP, wantType: RemoteDialer{}}, | |
| 26 | - | {name: "stdio", mode: ports.RemoteTransportStdio, wantType: stdioDialer{}}, | |
| 27 | - | } | |
| 28 | - | for _, tt := range tests { | |
| 29 | - | t.Run(tt.name, func(t *testing.T) { | |
| 30 | - | dialer, err := factory.DialerForRemote("remote.example", "work", tt.mode, nil) | |
| 31 | - | if err != nil { | |
| 32 | - | t.Fatalf("DialerForRemote() error = %v", err) | |
| 33 | - | } | |
| 34 | - | switch tt.wantType.(type) { | |
| 35 | - | case RemoteDialer: | |
| 36 | - | got, ok := dialer.(RemoteDialer) | |
| 37 | - | if !ok { | |
| 38 | - | t.Fatalf("dialer type = %T, want %T", dialer, RemoteDialer{}) | |
| 39 | - | } | |
| 40 | - | if got.Target != "remote.example" || got.Session != "work" { | |
| 41 | - | t.Fatalf("dialer = %+v, want target/session copied", got) | |
| 42 | - | } | |
| 43 | - | case stdioDialer: | |
| 44 | - | got, ok := dialer.(stdioDialer) | |
| 45 | - | if !ok { | |
| 46 | - | t.Fatalf("dialer type = %T, want %T", dialer, stdioDialer{}) | |
| 47 | - | } | |
| 48 | - | if got.target != "remote.example" || got.session != "work" { | |
| 49 | - | t.Fatalf("dialer = %+v, want target/session copied", got) | |
| 50 | - | } | |
| 51 | - | } | |
| 52 | - | }) | |
| 53 | - | } | |
| 54 | - | } | |
| 55 | - | ||
| 56 | - | func TestRemoteDialerFactoryRejectsUnsupportedMode(t *testing.T) { | |
| 57 | - | factory := NewRemoteDialerFactory() | |
| 58 | - | ||
| 59 | - | dialer, err := factory.DialerForRemote("remote.example", "work", ports.RemoteTransportMode("serial"), nil) | |
| 60 | - | if err == nil { | |
| 61 | - | t.Fatal("DialerForRemote() error = nil, want unsupported mode error") | |
| 62 | - | } | |
| 63 | - | if dialer != nil { | |
| 64 | - | t.Fatalf("DialerForRemote() dialer = %T, want nil", dialer) | |
| 65 | - | } | |
| 66 | - | if got, want := err.Error(), "vev: unsupported remote transport \"serial\""; got != want { | |
| 67 | - | t.Fatalf("error = %q, want %q", got, want) | |
| 68 | - | } | |
| 69 | - | } | |
| 70 | - | ||
| 71 | - | func TestStdioDialerUsesContextBeforeStartingSSH(t *testing.T) { | |
| 72 | - | dialer := stdioDialer{target: "remote.example", session: "work"} | |
| 73 | - | ctx, cancel := context.WithCancel(context.Background()) | |
| 74 | - | cancel() | |
| 75 | - | ||
| 76 | - | transport, err := dialer.Dial(ctx) | |
| 77 | - | if !errors.Is(err, context.Canceled) { | |
| 78 | - | t.Fatalf("Dial() error = %v, want context canceled", err) | |
| 79 | - | } | |
| 80 | - | if transport != nil { | |
| 81 | - | t.Fatalf("Dial() transport = %T, want nil", transport) | |
| 82 | - | } | |
| 83 | - | } | |
| 84 | - | ||
| 85 | 16 | type fakeBootstrapProcess struct { | |
| 86 | 17 | stdout io.ReadCloser | |
| 87 | 18 | startErr error | |
| @@ -10,78 +10,9 @@ import ( | |||||
| 10 | "testing" | 10 | "testing" | ||
| 11 | "time" | 11 | "time" | ||
| 12 | 12 | ||||
| 13 | - | "github.com/bnema/vev/internal/ports" | |||
| 14 | pdgram "github.com/bnema/vev/pkg/dgram" | 13 | pdgram "github.com/bnema/vev/pkg/dgram" | ||
| 15 | ) | 14 | ) | ||
| 16 | 15 | ||||
| 17 | - | func TestRemoteDialerFactorySelectsExplicitModes(t *testing.T) { | |||
| 18 | - | factory := NewRemoteDialerFactory() | |||
| 19 | - | ||||
| 20 | - | tests := []struct { | |||
| 21 | - | name string | |||
| 22 | - | mode ports.RemoteTransportMode | |||
| 23 | - | wantType any | |||
| 24 | - | }{ | |||
| 25 | - | {name: "udp", mode: ports.RemoteTransportUDP, wantType: RemoteDialer{}}, | |||
| 26 | - | {name: "stdio", mode: ports.RemoteTransportStdio, wantType: stdioDialer{}}, | |||
| 27 | - | } | |||
| 28 | - | for _, tt := range tests { | |||
| 29 | - | t.Run(tt.name, func(t *testing.T) { | |||
| 30 | - | dialer, err := factory.DialerForRemote("remote.example", "work", tt.mode, nil) | |||
| 31 | - | if err != nil { | |||
| 32 | - | t.Fatalf("DialerForRemote() error = %v", err) | |||
| 33 | - | } | |||
| 34 | - | switch tt.wantType.(type) { | |||
| 35 | - | case RemoteDialer: | |||
| 36 | - | got, ok := dialer.(RemoteDialer) | |||
| 37 | - | if !ok { | |||
| 38 | - | t.Fatalf("dialer type = %T, want %T", dialer, RemoteDialer{}) | |||
| 39 | - | } | |||
| 40 | - | if got.Target != "remote.example" || got.Session != "work" { | |||
| 41 | - | t.Fatalf("dialer = %+v, want target/session copied", got) | |||
| 42 | - | } | |||
| 43 | - | case stdioDialer: | |||
| 44 | - | got, ok := dialer.(stdioDialer) | |||
| 45 | - | if !ok { | |||
| 46 | - | t.Fatalf("dialer type = %T, want %T", dialer, stdioDialer{}) | |||
| 47 | - | } | |||
| 48 | - | if got.target != "remote.example" || got.session != "work" { | |||
| 49 | - | t.Fatalf("dialer = %+v, want target/session copied", got) | |||
| 50 | - | } | |||
| 51 | - | } | |||
| 52 | - | }) | |||
| 53 | - | } | |||
| 54 | - | } | |||
| 55 | - | ||||
| 56 | - | func TestRemoteDialerFactoryRejectsUnsupportedMode(t *testing.T) { | |||
| 57 | - | factory := NewRemoteDialerFactory() | |||
| 58 | - | ||||
| 59 | - | dialer, err := factory.DialerForRemote("remote.example", "work", ports.RemoteTransportMode("serial"), nil) | |||
| 60 | - | if err == nil { | |||
| 61 | - | t.Fatal("DialerForRemote() error = nil, want unsupported mode error") | |||
| 62 | - | } | |||
| 63 | - | if dialer != nil { | |||
| 64 | - | t.Fatalf("DialerForRemote() dialer = %T, want nil", dialer) | |||
| 65 | - | } | |||
| 66 | - | if got, want := err.Error(), "vev: unsupported remote transport \"serial\""; got != want { | |||
| 67 | - | t.Fatalf("error = %q, want %q", got, want) | |||
| 68 | - | } | |||
| 69 | - | } | |||
| 70 | - | ||||
| 71 | - | func TestStdioDialerUsesContextBeforeStartingSSH(t *testing.T) { | |||
| 72 | - | dialer := stdioDialer{target: "remote.example", session: "work"} | |||
| 73 | - | ctx, cancel := context.WithCancel(context.Background()) | |||
| 74 | - | cancel() | |||
| 75 | - | ||||
| 76 | - | transport, err := dialer.Dial(ctx) | |||
| 77 | - | if !errors.Is(err, context.Canceled) { | |||
| 78 | - | t.Fatalf("Dial() error = %v, want context canceled", err) | |||
| 79 | - | } | |||
| 80 | - | if transport != nil { | |||
| 81 | - | t.Fatalf("Dial() transport = %T, want nil", transport) | |||
| 82 | - | } | |||
| 83 | - | } | |||
| 84 | - | ||||
| 85 | type fakeBootstrapProcess struct { | 16 | type fakeBootstrapProcess struct { | ||
| 86 | stdout io.ReadCloser | 17 | stdout io.ReadCloser | ||
| 87 | startErr error | 18 | startErr error | ||
R internal/adapters/dgram/remote_factory.go → internal/adapters/remote/remote_factory.go +7 -6 renamed, 65% similar
| @@ -1,26 +1,27 @@ | |||
| 1 | - | package dgram | |
| 1 | + | package remote | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | 5 | "fmt" | |
| 6 | 6 | "log/slog" | |
| 7 | 7 | ||
| 8 | + | "github.com/bnema/vev/internal/adapters/dgram" | |
| 8 | 9 | "github.com/bnema/vev/internal/adapters/sshstdio" | |
| 9 | 10 | "github.com/bnema/vev/internal/ports" | |
| 10 | 11 | ) | |
| 11 | 12 | ||
| 12 | - | // RemoteDialerFactory selects the requested remote transport adapter. | |
| 13 | - | type RemoteDialerFactory struct{} | |
| 13 | + | // DialerFactory selects the requested remote transport adapter. | |
| 14 | + | type DialerFactory struct{} | |
| 14 | 15 | ||
| 15 | - | func NewRemoteDialerFactory() RemoteDialerFactory { return RemoteDialerFactory{} } | |
| 16 | + | func NewDialerFactory() DialerFactory { return DialerFactory{} } | |
| 16 | 17 | ||
| 17 | - | func (RemoteDialerFactory) DialerForRemote(target string, session string, mode ports.RemoteTransportMode, log *slog.Logger) (ports.Dialer, error) { | |
| 18 | + | func (DialerFactory) DialerForRemote(target string, session string, mode ports.RemoteTransportMode, log *slog.Logger) (ports.Dialer, error) { | |
| 18 | 19 | switch mode { | |
| 19 | 20 | case ports.RemoteTransportUDP: | |
| 20 | 21 | if log != nil { | |
| 21 | 22 | log.Info("remote transport selected", "mode", mode, "target", target, "session", session) | |
| 22 | 23 | } | |
| 23 | - | return NewRemoteDialerWithLogger(target, session, log), nil | |
| 24 | + | return dgram.NewRemoteDialerWithLogger(target, session, log), nil | |
| 24 | 25 | case ports.RemoteTransportStdio: | |
| 25 | 26 | if log != nil { | |
| 26 | 27 | log.Info("remote transport selected", "mode", mode, "target", target, "session", session) | |
| @@ -1,26 +1,27 @@ | |||||
| 1 | - | package dgram | 1 | + | package remote |
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | "fmt" | 5 | "fmt" | ||
| 6 | "log/slog" | 6 | "log/slog" | ||
| 7 | 7 | ||||
| 8 | + | "github.com/bnema/vev/internal/adapters/dgram" | |||
| 8 | "github.com/bnema/vev/internal/adapters/sshstdio" | 9 | "github.com/bnema/vev/internal/adapters/sshstdio" | ||
| 9 | "github.com/bnema/vev/internal/ports" | 10 | "github.com/bnema/vev/internal/ports" | ||
| 10 | ) | 11 | ) | ||
| 11 | 12 | ||||
| 12 | - | // RemoteDialerFactory selects the requested remote transport adapter. | 13 | + | // DialerFactory selects the requested remote transport adapter. |
| 13 | - | type RemoteDialerFactory struct{} | 14 | + | type DialerFactory struct{} |
| 14 | 15 | ||||
| 15 | - | func NewRemoteDialerFactory() RemoteDialerFactory { return RemoteDialerFactory{} } | 16 | + | func NewDialerFactory() DialerFactory { return DialerFactory{} } |
| 16 | 17 | ||||
| 17 | - | func (RemoteDialerFactory) DialerForRemote(target string, session string, mode ports.RemoteTransportMode, log *slog.Logger) (ports.Dialer, error) { | 18 | + | func (DialerFactory) DialerForRemote(target string, session string, mode ports.RemoteTransportMode, log *slog.Logger) (ports.Dialer, error) { |
| 18 | switch mode { | 19 | switch mode { | ||
| 19 | case ports.RemoteTransportUDP: | 20 | case ports.RemoteTransportUDP: | ||
| 20 | if log != nil { | 21 | if log != nil { | ||
| 21 | log.Info("remote transport selected", "mode", mode, "target", target, "session", session) | 22 | log.Info("remote transport selected", "mode", mode, "target", target, "session", session) | ||
| 22 | } | 23 | } | ||
| 23 | - | return NewRemoteDialerWithLogger(target, session, log), nil | 24 | + | return dgram.NewRemoteDialerWithLogger(target, session, log), nil |
| 24 | case ports.RemoteTransportStdio: | 25 | case ports.RemoteTransportStdio: | ||
| 25 | if log != nil { | 26 | if log != nil { | ||
| 26 | log.Info("remote transport selected", "mode", mode, "target", target, "session", session) | 27 | log.Info("remote transport selected", "mode", mode, "target", target, "session", session) | ||
A internal/adapters/remote/remote_factory_test.go +78 -0
| @@ -0,0 +1,78 @@ | |||
| 1 | + | package remote | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "errors" | |
| 6 | + | "testing" | |
| 7 | + | ||
| 8 | + | "github.com/bnema/vev/internal/adapters/dgram" | |
| 9 | + | "github.com/bnema/vev/internal/ports" | |
| 10 | + | ) | |
| 11 | + | ||
| 12 | + | func TestDialerFactorySelectsExplicitModes(t *testing.T) { | |
| 13 | + | factory := NewDialerFactory() | |
| 14 | + | ||
| 15 | + | tests := []struct { | |
| 16 | + | name string | |
| 17 | + | mode ports.RemoteTransportMode | |
| 18 | + | wantType any | |
| 19 | + | }{ | |
| 20 | + | {name: "udp", mode: ports.RemoteTransportUDP, wantType: dgram.RemoteDialer{}}, | |
| 21 | + | {name: "stdio", mode: ports.RemoteTransportStdio, wantType: stdioDialer{}}, | |
| 22 | + | } | |
| 23 | + | for _, tt := range tests { | |
| 24 | + | t.Run(tt.name, func(t *testing.T) { | |
| 25 | + | dialer, err := factory.DialerForRemote("remote.example", "work", tt.mode, nil) | |
| 26 | + | if err != nil { | |
| 27 | + | t.Fatalf("DialerForRemote() error = %v", err) | |
| 28 | + | } | |
| 29 | + | switch tt.wantType.(type) { | |
| 30 | + | case dgram.RemoteDialer: | |
| 31 | + | got, ok := dialer.(dgram.RemoteDialer) | |
| 32 | + | if !ok { | |
| 33 | + | t.Fatalf("dialer type = %T, want %T", dialer, dgram.RemoteDialer{}) | |
| 34 | + | } | |
| 35 | + | if got.Target != "remote.example" || got.Session != "work" { | |
| 36 | + | t.Fatalf("dialer = %+v, want target/session copied", got) | |
| 37 | + | } | |
| 38 | + | case stdioDialer: | |
| 39 | + | got, ok := dialer.(stdioDialer) | |
| 40 | + | if !ok { | |
| 41 | + | t.Fatalf("dialer type = %T, want %T", dialer, stdioDialer{}) | |
| 42 | + | } | |
| 43 | + | if got.target != "remote.example" || got.session != "work" { | |
| 44 | + | t.Fatalf("dialer = %+v, want target/session copied", got) | |
| 45 | + | } | |
| 46 | + | } | |
| 47 | + | }) | |
| 48 | + | } | |
| 49 | + | } | |
| 50 | + | ||
| 51 | + | func TestDialerFactoryRejectsUnsupportedMode(t *testing.T) { | |
| 52 | + | factory := NewDialerFactory() | |
| 53 | + | ||
| 54 | + | dialer, err := factory.DialerForRemote("remote.example", "work", ports.RemoteTransportMode("serial"), nil) | |
| 55 | + | if err == nil { | |
| 56 | + | t.Fatal("DialerForRemote() error = nil, want unsupported mode error") | |
| 57 | + | } | |
| 58 | + | if dialer != nil { | |
| 59 | + | t.Fatalf("DialerForRemote() dialer = %T, want nil", dialer) | |
| 60 | + | } | |
| 61 | + | if got, want := err.Error(), "vev: unsupported remote transport \"serial\""; got != want { | |
| 62 | + | t.Fatalf("error = %q, want %q", got, want) | |
| 63 | + | } | |
| 64 | + | } | |
| 65 | + | ||
| 66 | + | func TestStdioDialerUsesContextBeforeStartingSSH(t *testing.T) { | |
| 67 | + | dialer := stdioDialer{target: "remote.example", session: "work"} | |
| 68 | + | ctx, cancel := context.WithCancel(context.Background()) | |
| 69 | + | cancel() | |
| 70 | + | ||
| 71 | + | transport, err := dialer.Dial(ctx) | |
| 72 | + | if !errors.Is(err, context.Canceled) { | |
| 73 | + | t.Fatalf("Dial() error = %v, want context canceled", err) | |
| 74 | + | } | |
| 75 | + | if transport != nil { | |
| 76 | + | t.Fatalf("Dial() transport = %T, want nil", transport) | |
| 77 | + | } | |
| 78 | + | } | |
| @@ -0,0 +1,78 @@ | |||||
| 1 | + | package remote | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "errors" | |||
| 6 | + | "testing" | |||
| 7 | + | ||||
| 8 | + | "github.com/bnema/vev/internal/adapters/dgram" | |||
| 9 | + | "github.com/bnema/vev/internal/ports" | |||
| 10 | + | ) | |||
| 11 | + | ||||
| 12 | + | func TestDialerFactorySelectsExplicitModes(t *testing.T) { | |||
| 13 | + | factory := NewDialerFactory() | |||
| 14 | + | ||||
| 15 | + | tests := []struct { | |||
| 16 | + | name string | |||
| 17 | + | mode ports.RemoteTransportMode | |||
| 18 | + | wantType any | |||
| 19 | + | }{ | |||
| 20 | + | {name: "udp", mode: ports.RemoteTransportUDP, wantType: dgram.RemoteDialer{}}, | |||
| 21 | + | {name: "stdio", mode: ports.RemoteTransportStdio, wantType: stdioDialer{}}, | |||
| 22 | + | } | |||
| 23 | + | for _, tt := range tests { | |||
| 24 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 25 | + | dialer, err := factory.DialerForRemote("remote.example", "work", tt.mode, nil) | |||
| 26 | + | if err != nil { | |||
| 27 | + | t.Fatalf("DialerForRemote() error = %v", err) | |||
| 28 | + | } | |||
| 29 | + | switch tt.wantType.(type) { | |||
| 30 | + | case dgram.RemoteDialer: | |||
| 31 | + | got, ok := dialer.(dgram.RemoteDialer) | |||
| 32 | + | if !ok { | |||
| 33 | + | t.Fatalf("dialer type = %T, want %T", dialer, dgram.RemoteDialer{}) | |||
| 34 | + | } | |||
| 35 | + | if got.Target != "remote.example" || got.Session != "work" { | |||
| 36 | + | t.Fatalf("dialer = %+v, want target/session copied", got) | |||
| 37 | + | } | |||
| 38 | + | case stdioDialer: | |||
| 39 | + | got, ok := dialer.(stdioDialer) | |||
| 40 | + | if !ok { | |||
| 41 | + | t.Fatalf("dialer type = %T, want %T", dialer, stdioDialer{}) | |||
| 42 | + | } | |||
| 43 | + | if got.target != "remote.example" || got.session != "work" { | |||
| 44 | + | t.Fatalf("dialer = %+v, want target/session copied", got) | |||
| 45 | + | } | |||
| 46 | + | } | |||
| 47 | + | }) | |||
| 48 | + | } | |||
| 49 | + | } | |||
| 50 | + | ||||
| 51 | + | func TestDialerFactoryRejectsUnsupportedMode(t *testing.T) { | |||
| 52 | + | factory := NewDialerFactory() | |||
| 53 | + | ||||
| 54 | + | dialer, err := factory.DialerForRemote("remote.example", "work", ports.RemoteTransportMode("serial"), nil) | |||
| 55 | + | if err == nil { | |||
| 56 | + | t.Fatal("DialerForRemote() error = nil, want unsupported mode error") | |||
| 57 | + | } | |||
| 58 | + | if dialer != nil { | |||
| 59 | + | t.Fatalf("DialerForRemote() dialer = %T, want nil", dialer) | |||
| 60 | + | } | |||
| 61 | + | if got, want := err.Error(), "vev: unsupported remote transport \"serial\""; got != want { | |||
| 62 | + | t.Fatalf("error = %q, want %q", got, want) | |||
| 63 | + | } | |||
| 64 | + | } | |||
| 65 | + | ||||
| 66 | + | func TestStdioDialerUsesContextBeforeStartingSSH(t *testing.T) { | |||
| 67 | + | dialer := stdioDialer{target: "remote.example", session: "work"} | |||
| 68 | + | ctx, cancel := context.WithCancel(context.Background()) | |||
| 69 | + | cancel() | |||
| 70 | + | ||||
| 71 | + | transport, err := dialer.Dial(ctx) | |||
| 72 | + | if !errors.Is(err, context.Canceled) { | |||
| 73 | + | t.Fatalf("Dial() error = %v, want context canceled", err) | |||
| 74 | + | } | |||
| 75 | + | if transport != nil { | |||
| 76 | + | t.Fatalf("Dial() transport = %T, want nil", transport) | |||
| 77 | + | } | |||
| 78 | + | } | |||
M internal/app/run.go +2 -1
| @@ -30,6 +30,7 @@ import ( | |||
| 30 | 30 | "github.com/bnema/vev/internal/adapters/dgram" | |
| 31 | 31 | "github.com/bnema/vev/internal/adapters/ipc" | |
| 32 | 32 | "github.com/bnema/vev/internal/adapters/pty" | |
| 33 | + | remoteadapter "github.com/bnema/vev/internal/adapters/remote" | |
| 33 | 34 | "github.com/bnema/vev/internal/adapters/shellcmd" | |
| 34 | 35 | snapshotadapter "github.com/bnema/vev/internal/adapters/snapshot" | |
| 35 | 36 | "github.com/bnema/vev/internal/adapters/sshstdio" | |
| @@ -354,7 +355,7 @@ const envRemoteTransport = "VEV_REMOTE_TRANSPORT" | |||
| 354 | 355 | func defaultLocalDialer() ports.Dialer { return localDaemonDialer{dir: ipc.SocketDir()} } | |
| 355 | 356 | ||
| 356 | 357 | func defaultRemoteDialerFactory() ports.RemoteDialerFactory { | |
| 357 | - | return dgram.NewRemoteDialerFactory() | |
| 358 | + | return remoteadapter.NewDialerFactory() | |
| 358 | 359 | } | |
| 359 | 360 | ||
| 360 | 361 | type runAttachDeps struct { | |
| @@ -30,6 +30,7 @@ import ( | |||||
| 30 | "github.com/bnema/vev/internal/adapters/dgram" | 30 | "github.com/bnema/vev/internal/adapters/dgram" | ||
| 31 | "github.com/bnema/vev/internal/adapters/ipc" | 31 | "github.com/bnema/vev/internal/adapters/ipc" | ||
| 32 | "github.com/bnema/vev/internal/adapters/pty" | 32 | "github.com/bnema/vev/internal/adapters/pty" | ||
| 33 | + | remoteadapter "github.com/bnema/vev/internal/adapters/remote" | |||
| 33 | "github.com/bnema/vev/internal/adapters/shellcmd" | 34 | "github.com/bnema/vev/internal/adapters/shellcmd" | ||
| 34 | snapshotadapter "github.com/bnema/vev/internal/adapters/snapshot" | 35 | snapshotadapter "github.com/bnema/vev/internal/adapters/snapshot" | ||
| 35 | "github.com/bnema/vev/internal/adapters/sshstdio" | 36 | "github.com/bnema/vev/internal/adapters/sshstdio" | ||
| @@ -354,7 +355,7 @@ const envRemoteTransport = "VEV_REMOTE_TRANSPORT" | |||||
| 354 | func defaultLocalDialer() ports.Dialer { return localDaemonDialer{dir: ipc.SocketDir()} } | 355 | func defaultLocalDialer() ports.Dialer { return localDaemonDialer{dir: ipc.SocketDir()} } | ||
| 355 | 356 | ||||
| 356 | func defaultRemoteDialerFactory() ports.RemoteDialerFactory { | 357 | func defaultRemoteDialerFactory() ports.RemoteDialerFactory { | ||
| 357 | - | return dgram.NewRemoteDialerFactory() | 358 | + | return remoteadapter.NewDialerFactory() |
| 358 | } | 359 | } | ||
| 359 | 360 | ||||
| 360 | type runAttachDeps struct { | 361 | type runAttachDeps struct { | ||