Skip to content

Commit 359a9e0

Browse files
Merge remote-tracking branch 'origin/main' into 15843-queue-position
2 parents 4f77f67 + 04c3396 commit 359a9e0

File tree

97 files changed

+1958
-508
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+1958
-508
lines changed

agent/agent.go

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"os"
1515
"os/user"
1616
"path/filepath"
17+
"slices"
1718
"sort"
1819
"strconv"
1920
"strings"
@@ -26,7 +27,6 @@ import (
2627
"github.com/prometheus/common/expfmt"
2728
"github.com/spf13/afero"
2829
"go.uber.org/atomic"
29-
"golang.org/x/exp/slices"
3030
"golang.org/x/sync/errgroup"
3131
"golang.org/x/xerrors"
3232
"google.golang.org/protobuf/types/known/timestamppb"
@@ -1362,19 +1362,22 @@ func (a *agent) createTailnet(
13621362
return nil, xerrors.Errorf("update host signer: %w", err)
13631363
}
13641364

1365-
sshListener, err := network.Listen("tcp", ":"+strconv.Itoa(workspacesdk.AgentSSHPort))
1366-
if err != nil {
1367-
return nil, xerrors.Errorf("listen on the ssh port: %w", err)
1368-
}
1369-
defer func() {
1365+
for _, port := range []int{workspacesdk.AgentSSHPort, workspacesdk.AgentStandardSSHPort} {
1366+
sshListener, err := network.Listen("tcp", ":"+strconv.Itoa(port))
13701367
if err != nil {
1371-
_ = sshListener.Close()
1368+
return nil, xerrors.Errorf("listen on the ssh port (%v): %w", port, err)
1369+
}
1370+
// nolint:revive // We do want to run the deferred functions when createTailnet returns.
1371+
defer func() {
1372+
if err != nil {
1373+
_ = sshListener.Close()
1374+
}
1375+
}()
1376+
if err = a.trackGoroutine(func() {
1377+
_ = a.sshServer.Serve(sshListener)
1378+
}); err != nil {
1379+
return nil, err
13721380
}
1373-
}()
1374-
if err = a.trackGoroutine(func() {
1375-
_ = a.sshServer.Serve(sshListener)
1376-
}); err != nil {
1377-
return nil, err
13781381
}
13791382

13801383
reconnectingPTYListener, err := network.Listen("tcp", ":"+strconv.Itoa(workspacesdk.AgentReconnectingPTYPort))

agent/agent_test.go

Lines changed: 121 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"path/filepath"
2020
"regexp"
2121
"runtime"
22+
"slices"
2223
"strconv"
2324
"strings"
2425
"sync/atomic"
@@ -41,7 +42,6 @@ import (
4142
"github.com/stretchr/testify/assert"
4243
"github.com/stretchr/testify/require"
4344
"golang.org/x/crypto/ssh"
44-
"golang.org/x/exp/slices"
4545
"golang.org/x/xerrors"
4646

4747
"cdr.dev/slog"
@@ -65,38 +65,48 @@ func TestMain(m *testing.M) {
6565
goleak.VerifyTestMain(m, testutil.GoleakOptions...)
6666
}
6767

68+
var sshPorts = []uint16{workspacesdk.AgentSSHPort, workspacesdk.AgentStandardSSHPort}
69+
6870
// NOTE: These tests only work when your default shell is bash for some reason.
6971

7072
func TestAgent_Stats_SSH(t *testing.T) {
7173
t.Parallel()
72-
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
73-
defer cancel()
7474

75-
//nolint:dogsled
76-
conn, _, stats, _, _ := setupAgent(t, agentsdk.Manifest{}, 0)
75+
for _, port := range sshPorts {
76+
port := port
77+
t.Run(fmt.Sprintf("(:%d)", port), func(t *testing.T) {
78+
t.Parallel()
7779

78-
sshClient, err := conn.SSHClient(ctx)
79-
require.NoError(t, err)
80-
defer sshClient.Close()
81-
session, err := sshClient.NewSession()
82-
require.NoError(t, err)
83-
defer session.Close()
84-
stdin, err := session.StdinPipe()
85-
require.NoError(t, err)
86-
err = session.Shell()
87-
require.NoError(t, err)
80+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
81+
defer cancel()
8882

89-
var s *proto.Stats
90-
require.Eventuallyf(t, func() bool {
91-
var ok bool
92-
s, ok = <-stats
93-
return ok && s.ConnectionCount > 0 && s.RxBytes > 0 && s.TxBytes > 0 && s.SessionCountSsh == 1
94-
}, testutil.WaitLong, testutil.IntervalFast,
95-
"never saw stats: %+v", s,
96-
)
97-
_ = stdin.Close()
98-
err = session.Wait()
99-
require.NoError(t, err)
83+
//nolint:dogsled
84+
conn, _, stats, _, _ := setupAgent(t, agentsdk.Manifest{}, 0)
85+
86+
sshClient, err := conn.SSHClientOnPort(ctx, port)
87+
require.NoError(t, err)
88+
defer sshClient.Close()
89+
session, err := sshClient.NewSession()
90+
require.NoError(t, err)
91+
defer session.Close()
92+
stdin, err := session.StdinPipe()
93+
require.NoError(t, err)
94+
err = session.Shell()
95+
require.NoError(t, err)
96+
97+
var s *proto.Stats
98+
require.Eventuallyf(t, func() bool {
99+
var ok bool
100+
s, ok = <-stats
101+
return ok && s.ConnectionCount > 0 && s.RxBytes > 0 && s.TxBytes > 0 && s.SessionCountSsh == 1
102+
}, testutil.WaitLong, testutil.IntervalFast,
103+
"never saw stats: %+v", s,
104+
)
105+
_ = stdin.Close()
106+
err = session.Wait()
107+
require.NoError(t, err)
108+
})
109+
}
100110
}
101111

102112
func TestAgent_Stats_ReconnectingPTY(t *testing.T) {
@@ -278,15 +288,23 @@ func TestAgent_Stats_Magic(t *testing.T) {
278288

279289
func TestAgent_SessionExec(t *testing.T) {
280290
t.Parallel()
281-
session := setupSSHSession(t, agentsdk.Manifest{}, codersdk.ServiceBannerConfig{}, nil)
282291

283-
command := "echo test"
284-
if runtime.GOOS == "windows" {
285-
command = "cmd.exe /c echo test"
292+
for _, port := range sshPorts {
293+
port := port
294+
t.Run(fmt.Sprintf("(:%d)", port), func(t *testing.T) {
295+
t.Parallel()
296+
297+
session := setupSSHSessionOnPort(t, agentsdk.Manifest{}, codersdk.ServiceBannerConfig{}, nil, port)
298+
299+
command := "echo test"
300+
if runtime.GOOS == "windows" {
301+
command = "cmd.exe /c echo test"
302+
}
303+
output, err := session.Output(command)
304+
require.NoError(t, err)
305+
require.Equal(t, "test", strings.TrimSpace(string(output)))
306+
})
286307
}
287-
output, err := session.Output(command)
288-
require.NoError(t, err)
289-
require.Equal(t, "test", strings.TrimSpace(string(output)))
290308
}
291309

292310
//nolint:tparallel // Sub tests need to run sequentially.
@@ -396,25 +414,33 @@ func TestAgent_SessionTTYShell(t *testing.T) {
396414
// it seems like it could be either.
397415
t.Skip("ConPTY appears to be inconsistent on Windows.")
398416
}
399-
session := setupSSHSession(t, agentsdk.Manifest{}, codersdk.ServiceBannerConfig{}, nil)
400-
command := "sh"
401-
if runtime.GOOS == "windows" {
402-
command = "cmd.exe"
417+
418+
for _, port := range sshPorts {
419+
port := port
420+
t.Run(fmt.Sprintf("(%d)", port), func(t *testing.T) {
421+
t.Parallel()
422+
423+
session := setupSSHSessionOnPort(t, agentsdk.Manifest{}, codersdk.ServiceBannerConfig{}, nil, port)
424+
command := "sh"
425+
if runtime.GOOS == "windows" {
426+
command = "cmd.exe"
427+
}
428+
err := session.RequestPty("xterm", 128, 128, ssh.TerminalModes{})
429+
require.NoError(t, err)
430+
ptty := ptytest.New(t)
431+
session.Stdout = ptty.Output()
432+
session.Stderr = ptty.Output()
433+
session.Stdin = ptty.Input()
434+
err = session.Start(command)
435+
require.NoError(t, err)
436+
_ = ptty.Peek(ctx, 1) // wait for the prompt
437+
ptty.WriteLine("echo test")
438+
ptty.ExpectMatch("test")
439+
ptty.WriteLine("exit")
440+
err = session.Wait()
441+
require.NoError(t, err)
442+
})
403443
}
404-
err := session.RequestPty("xterm", 128, 128, ssh.TerminalModes{})
405-
require.NoError(t, err)
406-
ptty := ptytest.New(t)
407-
session.Stdout = ptty.Output()
408-
session.Stderr = ptty.Output()
409-
session.Stdin = ptty.Input()
410-
err = session.Start(command)
411-
require.NoError(t, err)
412-
_ = ptty.Peek(ctx, 1) // wait for the prompt
413-
ptty.WriteLine("echo test")
414-
ptty.ExpectMatch("test")
415-
ptty.WriteLine("exit")
416-
err = session.Wait()
417-
require.NoError(t, err)
418444
}
419445

420446
func TestAgent_SessionTTYExitCode(t *testing.T) {
@@ -608,37 +634,41 @@ func TestAgent_Session_TTY_MOTD_Update(t *testing.T) {
608634
//nolint:dogsled // Allow the blank identifiers.
609635
conn, client, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0, setSBInterval)
610636

611-
sshClient, err := conn.SSHClient(ctx)
612-
require.NoError(t, err)
613-
t.Cleanup(func() {
614-
_ = sshClient.Close()
615-
})
616-
617637
//nolint:paralleltest // These tests need to swap the banner func.
618-
for i, test := range tests {
619-
test := test
620-
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
621-
// Set new banner func and wait for the agent to call it to update the
622-
// banner.
623-
ready := make(chan struct{}, 2)
624-
client.SetAnnouncementBannersFunc(func() ([]codersdk.BannerConfig, error) {
625-
select {
626-
case ready <- struct{}{}:
627-
default:
628-
}
629-
return []codersdk.BannerConfig{test.banner}, nil
630-
})
631-
<-ready
632-
<-ready // Wait for two updates to ensure the value has propagated.
633-
634-
session, err := sshClient.NewSession()
635-
require.NoError(t, err)
636-
t.Cleanup(func() {
637-
_ = session.Close()
638-
})
638+
for _, port := range sshPorts {
639+
port := port
639640

640-
testSessionOutput(t, session, test.expected, test.unexpected, nil)
641+
sshClient, err := conn.SSHClientOnPort(ctx, port)
642+
require.NoError(t, err)
643+
t.Cleanup(func() {
644+
_ = sshClient.Close()
641645
})
646+
647+
for i, test := range tests {
648+
test := test
649+
t.Run(fmt.Sprintf("(:%d)/%d", port, i), func(t *testing.T) {
650+
// Set new banner func and wait for the agent to call it to update the
651+
// banner.
652+
ready := make(chan struct{}, 2)
653+
client.SetAnnouncementBannersFunc(func() ([]codersdk.BannerConfig, error) {
654+
select {
655+
case ready <- struct{}{}:
656+
default:
657+
}
658+
return []codersdk.BannerConfig{test.banner}, nil
659+
})
660+
<-ready
661+
<-ready // Wait for two updates to ensure the value has propagated.
662+
663+
session, err := sshClient.NewSession()
664+
require.NoError(t, err)
665+
t.Cleanup(func() {
666+
_ = session.Close()
667+
})
668+
669+
testSessionOutput(t, session, test.expected, test.unexpected, nil)
670+
})
671+
}
642672
}
643673
}
644674

@@ -2424,6 +2454,17 @@ func setupSSHSession(
24242454
banner codersdk.BannerConfig,
24252455
prepareFS func(fs afero.Fs),
24262456
opts ...func(*agenttest.Client, *agent.Options),
2457+
) *ssh.Session {
2458+
return setupSSHSessionOnPort(t, manifest, banner, prepareFS, workspacesdk.AgentSSHPort, opts...)
2459+
}
2460+
2461+
func setupSSHSessionOnPort(
2462+
t *testing.T,
2463+
manifest agentsdk.Manifest,
2464+
banner codersdk.BannerConfig,
2465+
prepareFS func(fs afero.Fs),
2466+
port uint16,
2467+
opts ...func(*agenttest.Client, *agent.Options),
24272468
) *ssh.Session {
24282469
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
24292470
defer cancel()
@@ -2437,7 +2478,7 @@ func setupSSHSession(
24372478
if prepareFS != nil {
24382479
prepareFS(fs)
24392480
}
2440-
sshClient, err := conn.SSHClient(ctx)
2481+
sshClient, err := conn.SSHClientOnPort(ctx, port)
24412482
require.NoError(t, err)
24422483
t.Cleanup(func() {
24432484
_ = sshClient.Close()

agent/agentssh/agentssh.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"os/user"
1313
"path/filepath"
1414
"runtime"
15+
"slices"
1516
"strings"
1617
"sync"
1718
"time"
@@ -24,7 +25,6 @@ import (
2425
"github.com/spf13/afero"
2526
"go.uber.org/atomic"
2627
gossh "golang.org/x/crypto/ssh"
27-
"golang.org/x/exp/slices"
2828
"golang.org/x/xerrors"
2929

3030
"cdr.dev/slog"

agent/agenttest/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package agenttest
33
import (
44
"context"
55
"io"
6+
"slices"
67
"sync"
78
"sync/atomic"
89
"testing"
@@ -12,7 +13,6 @@ import (
1213
"github.com/stretchr/testify/assert"
1314
"github.com/stretchr/testify/require"
1415
"golang.org/x/exp/maps"
15-
"golang.org/x/exp/slices"
1616
"golang.org/x/xerrors"
1717
"google.golang.org/protobuf/types/known/durationpb"
1818
"google.golang.org/protobuf/types/known/emptypb"

agent/reconnectingpty/buffered.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import (
55
"errors"
66
"io"
77
"net"
8+
"slices"
89
"time"
910

1011
"github.com/armon/circbuf"
1112
"github.com/prometheus/client_golang/prometheus"
12-
"golang.org/x/exp/slices"
1313
"golang.org/x/xerrors"
1414

1515
"cdr.dev/slog"

agent/usershell/usershell_darwin.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func Get(username string) (string, error) {
1818
return "", xerrors.Errorf("username is nonlocal path: %s", username)
1919
}
2020
//nolint: gosec // input checked above
21-
out, _ := exec.Command("dscl", ".", "-read", filepath.Join("/Users", username), "UserShell").Output()
21+
out, _ := exec.Command("dscl", ".", "-read", filepath.Join("/Users", username), "UserShell").Output() //nolint:gocritic
2222
s, ok := strings.CutPrefix(string(out), "UserShell: ")
2323
if ok {
2424
return strings.TrimSpace(s), nil

cli/configssh.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"os"
1212
"path/filepath"
1313
"runtime"
14+
"slices"
1415
"strconv"
1516
"strings"
1617

@@ -19,7 +20,6 @@ import (
1920
"github.com/pkg/diff"
2021
"github.com/pkg/diff/write"
2122
"golang.org/x/exp/constraints"
22-
"golang.org/x/exp/slices"
2323
"golang.org/x/xerrors"
2424

2525
"github.com/coder/coder/v2/cli/cliui"

0 commit comments

Comments
 (0)