mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(cli/ssh): allow multiple remote forwards and allow missing local file (#11648)
This commit is contained in:
@@ -883,6 +883,104 @@ func TestSSH(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// Test that we can remote forward multiple sockets, whether or not the
|
||||
// local sockets exists at the time of establishing xthe SSH connection.
|
||||
t.Run("RemoteForwardMultipleUnixSockets", func(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Test not supported on windows")
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
client, workspace, agentToken := setupWorkspaceForAgent(t)
|
||||
|
||||
_ = agenttest.New(t, client.URL, agentToken)
|
||||
coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
|
||||
|
||||
// Wait super long so this doesn't flake on -race test.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitSuperLong)
|
||||
defer cancel()
|
||||
|
||||
tmpdir := tempDirUnixSocket(t)
|
||||
|
||||
type testSocket struct {
|
||||
local string
|
||||
remote string
|
||||
}
|
||||
|
||||
args := []string{"ssh", workspace.Name}
|
||||
var sockets []testSocket
|
||||
for i := 0; i < 2; i++ {
|
||||
localSock := filepath.Join(tmpdir, fmt.Sprintf("local-%d.sock", i))
|
||||
remoteSock := filepath.Join(tmpdir, fmt.Sprintf("remote-%d.sock", i))
|
||||
sockets = append(sockets, testSocket{
|
||||
local: localSock,
|
||||
remote: remoteSock,
|
||||
})
|
||||
args = append(args, "--remote-forward", fmt.Sprintf("%s:%s", remoteSock, localSock))
|
||||
}
|
||||
|
||||
inv, root := clitest.New(t, args...)
|
||||
clitest.SetupConfig(t, client, root)
|
||||
pty := ptytest.New(t).Attach(inv)
|
||||
inv.Stderr = pty.Output()
|
||||
|
||||
w := clitest.StartWithWaiter(t, inv.WithContext(ctx))
|
||||
defer w.Wait() // We don't care about any exit error (exit code 255: SSH connection ended unexpectedly).
|
||||
|
||||
// Since something was output, it should be safe to write input.
|
||||
// This could show a prompt or "running startup scripts", so it's
|
||||
// not indicative of the SSH connection being ready.
|
||||
_ = pty.Peek(ctx, 1)
|
||||
|
||||
// Ensure the SSH connection is ready by testing the shell
|
||||
// input/output.
|
||||
pty.WriteLine("echo ping' 'pong")
|
||||
pty.ExpectMatchContext(ctx, "ping pong")
|
||||
|
||||
for i, sock := range sockets {
|
||||
i := i
|
||||
// Start the listener on the "local machine".
|
||||
l, err := net.Listen("unix", sock.local)
|
||||
require.NoError(t, err)
|
||||
defer l.Close() //nolint:revive // Defer is fine in this loop, we only run it twice.
|
||||
testutil.Go(t, func() {
|
||||
for {
|
||||
fd, err := l.Accept()
|
||||
if err != nil {
|
||||
if !errors.Is(err, net.ErrClosed) {
|
||||
assert.NoError(t, err, "listener accept failed", i)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
testutil.Go(t, func() {
|
||||
defer fd.Close()
|
||||
agentssh.Bicopy(ctx, fd, fd)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Dial the forwarded socket on the "remote machine".
|
||||
d := &net.Dialer{}
|
||||
fd, err := d.DialContext(ctx, "unix", sock.remote)
|
||||
require.NoError(t, err, i)
|
||||
defer fd.Close() //nolint:revive // Defer is fine in this loop, we only run it twice.
|
||||
|
||||
// Ping / pong to ensure the socket is working.
|
||||
_, err = fd.Write([]byte("hello world"))
|
||||
require.NoError(t, err, i)
|
||||
|
||||
buf := make([]byte, 11)
|
||||
_, err = fd.Read(buf)
|
||||
require.NoError(t, err, i)
|
||||
require.Equal(t, "hello world", string(buf), i)
|
||||
}
|
||||
|
||||
// And we're done.
|
||||
pty.WriteLine("exit")
|
||||
})
|
||||
|
||||
t.Run("FileLogging", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user