Skip to content

Commit ea2027e

Browse files
committed
fixup! feat: add endpoint to get listening ports in agent
1 parent 0f7b8dc commit ea2027e

File tree

4 files changed

+144
-91
lines changed

4 files changed

+144
-91
lines changed

agent/ports_supported.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//go:build linux || windows
2+
// +build linux windows
3+
4+
package agent
5+
6+
import (
7+
"runtime"
8+
"time"
9+
10+
"github.com/cakturk/go-netstat/netstat"
11+
"golang.org/x/xerrors"
12+
13+
"github.com/coder/coder/codersdk"
14+
)
15+
16+
func (lp *listeningPortsHandler) getListeningPorts() ([]codersdk.ListeningPort, error) {
17+
lp.mut.Lock()
18+
defer lp.mut.Unlock()
19+
20+
if runtime.GOOS != "linux" && runtime.GOOS != "windows" {
21+
// Can't scan for ports on non-linux or non-windows systems at the
22+
// moment. The UI will not show any "no ports found" message to the
23+
// user, so the user won't suspect a thing.
24+
return []codersdk.ListeningPort{}, nil
25+
}
26+
27+
if time.Since(lp.mtime) < time.Second {
28+
// copy
29+
ports := make([]codersdk.ListeningPort, len(lp.ports))
30+
copy(ports, lp.ports)
31+
return ports, nil
32+
}
33+
34+
tabs, err := netstat.TCPSocks(func(s *netstat.SockTabEntry) bool {
35+
return s.State == netstat.Listen
36+
})
37+
if err != nil {
38+
return nil, xerrors.Errorf("scan listening ports: %w", err)
39+
}
40+
41+
ports := []codersdk.ListeningPort{}
42+
for _, tab := range tabs {
43+
if tab.LocalAddr.Port < uint16(codersdk.MinimumListeningPort) {
44+
continue
45+
}
46+
47+
ports = append(ports, codersdk.ListeningPort{
48+
ProcessName: tab.Process.Name,
49+
Network: codersdk.ListeningPortNetworkTCP,
50+
Port: tab.LocalAddr.Port,
51+
})
52+
}
53+
54+
lp.ports = ports
55+
lp.mtime = time.Now()
56+
57+
// copy
58+
ports = make([]codersdk.ListeningPort, len(lp.ports))
59+
copy(ports, lp.ports)
60+
return ports, nil
61+
}

agent/ports_unsupported.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//go:build !linux && !windows
2+
// +build !linux,!windows
3+
4+
package agent
5+
6+
import "github.com/coder/coder/codersdk"
7+
8+
func (lp *listeningPortsHandler) getListeningPorts() ([]codersdk.ListeningPort, error) {
9+
// Can't scan for ports on non-linux or non-windows systems at the moment.
10+
// The UI will not show any "no ports found" message to the user, so the
11+
// user won't suspect a thing.
12+
return []codersdk.ListeningPort{}, nil
13+
}

agent/statsendpoint.go

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,10 @@ package agent
22

33
import (
44
"net/http"
5-
"runtime"
65
"sync"
76
"time"
87

9-
"github.com/cakturk/go-netstat/netstat"
108
"github.com/go-chi/chi"
11-
"golang.org/x/xerrors"
129

1310
"github.com/coder/coder/coderd/httpapi"
1411
"github.com/coder/coder/codersdk"
@@ -34,53 +31,6 @@ type listeningPortsHandler struct {
3431
mtime time.Time
3532
}
3633

37-
func (lp *listeningPortsHandler) getListeningPorts() ([]codersdk.ListeningPort, error) {
38-
lp.mut.Lock()
39-
defer lp.mut.Unlock()
40-
41-
if runtime.GOOS != "linux" && runtime.GOOS != "windows" {
42-
// Can't scan for ports on non-linux or non-windows systems at the
43-
// moment. The UI will not show any "no ports found" message to the
44-
// user, so the user won't suspect a thing.
45-
return []codersdk.ListeningPort{}, nil
46-
}
47-
48-
if time.Since(lp.mtime) < time.Second {
49-
// copy
50-
ports := make([]codersdk.ListeningPort, len(lp.ports))
51-
copy(ports, lp.ports)
52-
return ports, nil
53-
}
54-
55-
tabs, err := netstat.TCPSocks(func(s *netstat.SockTabEntry) bool {
56-
return s.State == netstat.Listen
57-
})
58-
if err != nil {
59-
return nil, xerrors.Errorf("scan listening ports: %w", err)
60-
}
61-
62-
ports := []codersdk.ListeningPort{}
63-
for _, tab := range tabs {
64-
if tab.LocalAddr.Port < uint16(codersdk.MinimumListeningPort) {
65-
continue
66-
}
67-
68-
ports = append(ports, codersdk.ListeningPort{
69-
ProcessName: tab.Process.Name,
70-
Network: codersdk.ListeningPortNetworkTCP,
71-
Port: tab.LocalAddr.Port,
72-
})
73-
}
74-
75-
lp.ports = ports
76-
lp.mtime = time.Now()
77-
78-
// copy
79-
ports = make([]codersdk.ListeningPort, len(lp.ports))
80-
copy(ports, lp.ports)
81-
return ports, nil
82-
}
83-
8434
// handler returns a list of listening ports. This is tested by coderd's
8535
// TestWorkspaceAgentListeningPorts test.
8636
func (lp *listeningPortsHandler) handler(rw http.ResponseWriter, r *http.Request) {

coderd/workspaceagents_test.go

Lines changed: 70 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -408,60 +408,89 @@ func TestWorkspaceAgentListeningPorts(t *testing.T) {
408408
CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet,
409409
Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelDebug),
410410
})
411-
defer func() {
411+
t.Cleanup(func() {
412412
_ = agentCloser.Close()
413-
}()
413+
})
414414
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID)
415415

416-
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
417-
defer cancel()
416+
t.Run("LinuxAndWindows", func(t *testing.T) {
417+
t.Parallel()
418+
if runtime.GOOS != "linux" && runtime.GOOS != "windows" {
419+
t.Skip("only runs on linux and windows")
420+
return
421+
}
418422

419-
// Create a TCP listener on a random port that we expect to see in the
420-
// response.
421-
l, err := net.Listen("tcp", "localhost:0")
422-
require.NoError(t, err)
423-
defer l.Close()
424-
tcpAddr, _ := l.Addr().(*net.TCPAddr)
423+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
424+
defer cancel()
425425

426-
// List ports and ensure that the port we expect to see is there.
427-
res, err := client.WorkspaceAgentListeningPorts(ctx, resources[0].Agents[0].ID)
428-
require.NoError(t, err)
426+
// Create a TCP listener on a random port that we expect to see in the
427+
// response.
428+
l, err := net.Listen("tcp", "localhost:0")
429+
require.NoError(t, err)
430+
defer l.Close()
431+
tcpAddr, _ := l.Addr().(*net.TCPAddr)
429432

430-
var (
431-
expected = map[uint16]bool{
432-
// expect the listener we made
433-
uint16(tcpAddr.Port): false,
434-
// expect the coderdtest server
435-
uint16(coderdPort): false,
436-
}
437-
)
438-
for _, port := range res.Ports {
439-
if port.Network == codersdk.ListeningPortNetworkTCP {
440-
if val, ok := expected[port.Port]; ok {
441-
if val {
442-
t.Fatalf("expected to find TCP port %d only once in response", port.Port)
433+
// List ports and ensure that the port we expect to see is there.
434+
res, err := client.WorkspaceAgentListeningPorts(ctx, resources[0].Agents[0].ID)
435+
require.NoError(t, err)
436+
437+
var (
438+
expected = map[uint16]bool{
439+
// expect the listener we made
440+
uint16(tcpAddr.Port): false,
441+
// expect the coderdtest server
442+
uint16(coderdPort): false,
443+
}
444+
)
445+
for _, port := range res.Ports {
446+
if port.Network == codersdk.ListeningPortNetworkTCP {
447+
if val, ok := expected[port.Port]; ok {
448+
if val {
449+
t.Fatalf("expected to find TCP port %d only once in response", port.Port)
450+
}
443451
}
452+
expected[port.Port] = true
444453
}
445-
expected[port.Port] = true
446454
}
447-
}
448-
for port, found := range expected {
449-
if !found {
450-
t.Fatalf("expected to find TCP port %d in response", port)
455+
for port, found := range expected {
456+
if !found {
457+
t.Fatalf("expected to find TCP port %d in response", port)
458+
}
451459
}
452-
}
453460

454-
// Close the listener and check that the port is no longer in the response.
455-
require.NoError(t, l.Close())
456-
time.Sleep(2 * time.Second) // avoid cache
457-
res, err = client.WorkspaceAgentListeningPorts(ctx, resources[0].Agents[0].ID)
458-
require.NoError(t, err)
461+
// Close the listener and check that the port is no longer in the response.
462+
require.NoError(t, l.Close())
463+
time.Sleep(2 * time.Second) // avoid cache
464+
res, err = client.WorkspaceAgentListeningPorts(ctx, resources[0].Agents[0].ID)
465+
require.NoError(t, err)
459466

460-
for _, port := range res.Ports {
461-
if port.Network == codersdk.ListeningPortNetworkTCP && port.Port == uint16(tcpAddr.Port) {
462-
t.Fatalf("expected to not find TCP port %d in response", tcpAddr.Port)
467+
for _, port := range res.Ports {
468+
if port.Network == codersdk.ListeningPortNetworkTCP && port.Port == uint16(tcpAddr.Port) {
469+
t.Fatalf("expected to not find TCP port %d in response", tcpAddr.Port)
470+
}
463471
}
464-
}
472+
})
473+
474+
t.Run("Darwin", func(t *testing.T) {
475+
t.Parallel()
476+
if runtime.GOOS != "darwin" {
477+
t.Skip("only runs on darwin")
478+
return
479+
}
480+
481+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
482+
defer cancel()
483+
484+
// Create a TCP listener on a random port.
485+
l, err := net.Listen("tcp", "localhost:0")
486+
require.NoError(t, err)
487+
defer l.Close()
488+
489+
// List ports and ensure that the list is empty because we're on darwin.
490+
res, err := client.WorkspaceAgentListeningPorts(ctx, resources[0].Agents[0].ID)
491+
require.NoError(t, err)
492+
require.Len(t, res.Ports, 0)
493+
})
465494
}
466495

467496
func TestWorkspaceAgentAppHealth(t *testing.T) {

0 commit comments

Comments
 (0)