Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitb1a095e

Browse files
feat: show listening ports in port forward popup (#4389)
* feat: show listening ports in port forward popup* Move fetch logic to a machine* feat: don't show app ports and common non-HTTP portsCo-authored-by: Bruno Quaresma <bruno@coder.com>
1 parenta64731e commitb1a095e

File tree

7 files changed

+456
-94
lines changed

7 files changed

+456
-94
lines changed

‎coderd/workspaceagents.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net"
1010
"net/http"
1111
"net/netip"
12+
"net/url"
1213
"reflect"
1314
"strconv"
1415
"strings"
@@ -262,6 +263,59 @@ func (api *API) workspaceAgentListeningPorts(rw http.ResponseWriter, r *http.Req
262263
return
263264
}
264265

266+
// Get a list of ports that are in-use by applications.
267+
apps,err:=api.Database.GetWorkspaceAppsByAgentID(ctx,workspaceAgent.ID)
268+
ifxerrors.Is(err,sql.ErrNoRows) {
269+
apps= []database.WorkspaceApp{}
270+
err=nil
271+
}
272+
iferr!=nil {
273+
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
274+
Message:"Internal error fetching workspace apps.",
275+
Detail:err.Error(),
276+
})
277+
return
278+
}
279+
appPorts:=make(map[uint16]struct{},len(apps))
280+
for_,app:=rangeapps {
281+
if!app.Url.Valid||app.Url.String=="" {
282+
continue
283+
}
284+
u,err:=url.Parse(app.Url.String)
285+
iferr!=nil {
286+
continue
287+
}
288+
port:=u.Port()
289+
ifport=="" {
290+
continue
291+
}
292+
portNum,err:=strconv.Atoi(port)
293+
iferr!=nil {
294+
continue
295+
}
296+
ifportNum<1||portNum>65535 {
297+
continue
298+
}
299+
appPorts[uint16(portNum)]=struct{}{}
300+
}
301+
302+
// Filter out ports that are globally blocked, in-use by applications, or
303+
// common non-HTTP ports such as databases, FTP, SSH, etc.
304+
filteredPorts:=make([]codersdk.ListeningPort,0,len(portsResponse.Ports))
305+
for_,port:=rangeportsResponse.Ports {
306+
ifport.Port<uint16(codersdk.MinimumListeningPort) {
307+
continue
308+
}
309+
if_,ok:=appPorts[port.Port];ok {
310+
continue
311+
}
312+
if_,ok:=codersdk.IgnoredListeningPorts[port.Port];ok {
313+
continue
314+
}
315+
filteredPorts=append(filteredPorts,port)
316+
}
317+
318+
portsResponse.Ports=filteredPorts
265319
httpapi.Write(ctx,rw,http.StatusOK,portsResponse)
266320
}
267321

‎coderd/workspaceagents_test.go

Lines changed: 200 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bufio"
55
"context"
66
"encoding/json"
7+
"fmt"
78
"net"
89
"runtime"
910
"strconv"
@@ -367,50 +368,124 @@ func TestWorkspaceAgentPTY(t *testing.T) {
367368

368369
funcTestWorkspaceAgentListeningPorts(t*testing.T) {
369370
t.Parallel()
370-
client:=coderdtest.New(t,&coderdtest.Options{
371-
IncludeProvisionerDaemon:true,
372-
})
373-
coderdPort,err:=strconv.Atoi(client.URL.Port())
374-
require.NoError(t,err)
375371

376-
user:=coderdtest.CreateFirstUser(t,client)
377-
authToken:=uuid.NewString()
378-
version:=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,&echo.Responses{
379-
Parse:echo.ParseComplete,
380-
ProvisionDryRun:echo.ProvisionComplete,
381-
Provision: []*proto.Provision_Response{{
382-
Type:&proto.Provision_Response_Complete{
383-
Complete:&proto.Provision_Complete{
384-
Resources: []*proto.Resource{{
385-
Name:"example",
386-
Type:"aws_instance",
387-
Agents: []*proto.Agent{{
388-
Id:uuid.NewString(),
389-
Auth:&proto.Agent_Token{
390-
Token:authToken,
391-
},
372+
setup:=func(t*testing.T,apps []*proto.App) (*codersdk.Client,uint16, uuid.UUID) {
373+
client:=coderdtest.New(t,&coderdtest.Options{
374+
IncludeProvisionerDaemon:true,
375+
})
376+
coderdPort,err:=strconv.Atoi(client.URL.Port())
377+
require.NoError(t,err)
378+
379+
user:=coderdtest.CreateFirstUser(t,client)
380+
authToken:=uuid.NewString()
381+
version:=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,&echo.Responses{
382+
Parse:echo.ParseComplete,
383+
ProvisionDryRun:echo.ProvisionComplete,
384+
Provision: []*proto.Provision_Response{{
385+
Type:&proto.Provision_Response_Complete{
386+
Complete:&proto.Provision_Complete{
387+
Resources: []*proto.Resource{{
388+
Name:"example",
389+
Type:"aws_instance",
390+
Agents: []*proto.Agent{{
391+
Id:uuid.NewString(),
392+
Auth:&proto.Agent_Token{
393+
Token:authToken,
394+
},
395+
Apps:apps,
396+
}},
392397
}},
393-
}},
398+
},
394399
},
395-
},
396-
}},
397-
})
398-
template:=coderdtest.CreateTemplate(t,client,user.OrganizationID,version.ID)
399-
coderdtest.AwaitTemplateVersionJob(t,client,version.ID)
400-
workspace:=coderdtest.CreateWorkspace(t,client,user.OrganizationID,template.ID)
401-
coderdtest.AwaitWorkspaceBuildJob(t,client,workspace.LatestBuild.ID)
400+
}},
401+
})
402+
template:=coderdtest.CreateTemplate(t,client,user.OrganizationID,version.ID)
403+
coderdtest.AwaitTemplateVersionJob(t,client,version.ID)
404+
workspace:=coderdtest.CreateWorkspace(t,client,user.OrganizationID,template.ID)
405+
coderdtest.AwaitWorkspaceBuildJob(t,client,workspace.LatestBuild.ID)
402406

403-
agentClient:=codersdk.New(client.URL)
404-
agentClient.SessionToken=authToken
405-
agentCloser:=agent.New(agent.Options{
406-
FetchMetadata:agentClient.WorkspaceAgentMetadata,
407-
CoordinatorDialer:agentClient.ListenWorkspaceAgentTailnet,
408-
Logger:slogtest.Make(t,nil).Named("agent").Leveled(slog.LevelDebug),
409-
})
410-
t.Cleanup(func() {
411-
_=agentCloser.Close()
412-
})
413-
resources:=coderdtest.AwaitWorkspaceAgents(t,client,workspace.ID)
407+
agentClient:=codersdk.New(client.URL)
408+
agentClient.SessionToken=authToken
409+
agentCloser:=agent.New(agent.Options{
410+
FetchMetadata:agentClient.WorkspaceAgentMetadata,
411+
CoordinatorDialer:agentClient.ListenWorkspaceAgentTailnet,
412+
Logger:slogtest.Make(t,nil).Named("agent").Leveled(slog.LevelDebug),
413+
})
414+
t.Cleanup(func() {
415+
_=agentCloser.Close()
416+
})
417+
resources:=coderdtest.AwaitWorkspaceAgents(t,client,workspace.ID)
418+
419+
returnclient,uint16(coderdPort),resources[0].Agents[0].ID
420+
}
421+
422+
willFilterPort:=func(portint)bool {
423+
ifport<codersdk.MinimumListeningPort||port>65535 {
424+
returntrue
425+
}
426+
if_,ok:=codersdk.IgnoredListeningPorts[uint16(port)];ok {
427+
returntrue
428+
}
429+
430+
returnfalse
431+
}
432+
433+
generateUnfilteredPort:=func(t*testing.T) (net.Listener,uint16) {
434+
var (
435+
l net.Listener
436+
portuint16
437+
)
438+
require.Eventually(t,func()bool {
439+
varerrerror
440+
l,err=net.Listen("tcp","localhost:0")
441+
iferr!=nil {
442+
returnfalse
443+
}
444+
tcpAddr,_:=l.Addr().(*net.TCPAddr)
445+
ifwillFilterPort(tcpAddr.Port) {
446+
_=l.Close()
447+
returnfalse
448+
}
449+
t.Cleanup(func() {
450+
_=l.Close()
451+
})
452+
453+
port=uint16(tcpAddr.Port)
454+
returntrue
455+
},testutil.WaitShort,testutil.IntervalFast)
456+
457+
returnl,port
458+
}
459+
460+
generateFilteredPort:=func(t*testing.T) (net.Listener,uint16) {
461+
var (
462+
l net.Listener
463+
portuint16
464+
)
465+
require.Eventually(t,func()bool {
466+
forignoredPort:=rangecodersdk.IgnoredListeningPorts {
467+
ifignoredPort<1024||ignoredPort==5432 {
468+
continue
469+
}
470+
471+
varerrerror
472+
l,err=net.Listen("tcp",fmt.Sprintf("localhost:%d",ignoredPort))
473+
iferr!=nil {
474+
continue
475+
}
476+
t.Cleanup(func() {
477+
_=l.Close()
478+
})
479+
480+
port=ignoredPort
481+
returntrue
482+
}
483+
484+
returnfalse
485+
},testutil.WaitShort,testutil.IntervalFast)
486+
487+
returnl,port
488+
}
414489

415490
t.Run("LinuxAndWindows",func(t*testing.T) {
416491
t.Parallel()
@@ -419,55 +494,98 @@ func TestWorkspaceAgentListeningPorts(t *testing.T) {
419494
return
420495
}
421496

422-
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitLong)
423-
defercancel()
497+
t.Run("OK",func(t*testing.T) {
498+
t.Parallel()
424499

425-
// Create a TCP listener on a random port that we expect to see in the
426-
// response.
427-
l,err:=net.Listen("tcp","localhost:0")
428-
require.NoError(t,err)
429-
deferl.Close()
430-
tcpAddr,_:=l.Addr().(*net.TCPAddr)
500+
client,coderdPort,agentID:=setup(t,nil)
431501

432-
// List ports and ensure that the port we expect to see is there.
433-
res,err:=client.WorkspaceAgentListeningPorts(ctx,resources[0].Agents[0].ID)
434-
require.NoError(t,err)
502+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitLong)
503+
defercancel()
435504

436-
var (
437-
expected=map[uint16]bool{
438-
// expect the listener we made
439-
uint16(tcpAddr.Port):false,
440-
// expect the coderdtest server
441-
uint16(coderdPort):false,
442-
}
443-
)
444-
for_,port:=rangeres.Ports {
445-
ifport.Network==codersdk.ListeningPortNetworkTCP {
446-
ifval,ok:=expected[port.Port];ok {
447-
ifval {
448-
t.Fatalf("expected to find TCP port %d only once in response",port.Port)
505+
// Generate a random unfiltered port.
506+
l,lPort:=generateUnfilteredPort(t)
507+
508+
// List ports and ensure that the port we expect to see is there.
509+
res,err:=client.WorkspaceAgentListeningPorts(ctx,agentID)
510+
require.NoError(t,err)
511+
512+
var (
513+
expected=map[uint16]bool{
514+
// expect the listener we made
515+
lPort:false,
516+
// expect the coderdtest server
517+
coderdPort:false,
518+
}
519+
)
520+
for_,port:=rangeres.Ports {
521+
ifport.Network==codersdk.ListeningPortNetworkTCP {
522+
ifval,ok:=expected[port.Port];ok {
523+
ifval {
524+
t.Fatalf("expected to find TCP port %d only once in response",port.Port)
525+
}
449526
}
527+
expected[port.Port]=true
450528
}
451-
expected[port.Port]=true
452529
}
453-
}
454-
forport,found:=rangeexpected {
455-
if!found {
456-
t.Fatalf("expected to find TCP port %d in response",port)
530+
forport,found:=rangeexpected {
531+
if!found {
532+
t.Fatalf("expected to find TCP port %d in response",port)
533+
}
457534
}
458-
}
459535

460-
// Close the listener and check that the port is no longer in the response.
461-
require.NoError(t,l.Close())
462-
time.Sleep(2*time.Second)// avoid cache
463-
res,err=client.WorkspaceAgentListeningPorts(ctx,resources[0].Agents[0].ID)
464-
require.NoError(t,err)
536+
// Close the listener and check that the port is no longer in the response.
537+
require.NoError(t,l.Close())
538+
time.Sleep(2*time.Second)// avoid cache
539+
res,err=client.WorkspaceAgentListeningPorts(ctx,agentID)
540+
require.NoError(t,err)
465541

466-
for_,port:=rangeres.Ports {
467-
ifport.Network==codersdk.ListeningPortNetworkTCP&&port.Port==uint16(tcpAddr.Port) {
468-
t.Fatalf("expected to not find TCP port %d in response",tcpAddr.Port)
542+
for_,port:=rangeres.Ports {
543+
ifport.Network==codersdk.ListeningPortNetworkTCP&&port.Port==lPort {
544+
t.Fatalf("expected to not find TCP port %d in response",lPort)
545+
}
469546
}
470-
}
547+
})
548+
549+
t.Run("Filter",func(t*testing.T) {
550+
t.Parallel()
551+
552+
// Generate an unfiltered port that we will create an app for and
553+
// should not exist in the response.
554+
_,appLPort:=generateUnfilteredPort(t)
555+
app:=&proto.App{
556+
Name:"test-app",
557+
Url:fmt.Sprintf("http://localhost:%d",appLPort),
558+
}
559+
560+
// Generate a filtered port that should not exist in the response.
561+
_,filteredLPort:=generateFilteredPort(t)
562+
563+
client,coderdPort,agentID:=setup(t, []*proto.App{app})
564+
565+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitLong)
566+
defercancel()
567+
568+
res,err:=client.WorkspaceAgentListeningPorts(ctx,agentID)
569+
require.NoError(t,err)
570+
571+
sawCoderdPort:=false
572+
for_,port:=rangeres.Ports {
573+
ifport.Network==codersdk.ListeningPortNetworkTCP {
574+
ifport.Port==appLPort {
575+
t.Fatalf("expected to not find TCP port (app port) %d in response",appLPort)
576+
}
577+
ifport.Port==filteredLPort {
578+
t.Fatalf("expected to not find TCP port (filtered port) %d in response",filteredLPort)
579+
}
580+
ifport.Port==coderdPort {
581+
sawCoderdPort=true
582+
}
583+
}
584+
}
585+
if!sawCoderdPort {
586+
t.Fatalf("expected to find TCP port (coderd port) %d in response",coderdPort)
587+
}
588+
})
471589
})
472590

473591
t.Run("Darwin",func(t*testing.T) {
@@ -477,6 +595,8 @@ func TestWorkspaceAgentListeningPorts(t *testing.T) {
477595
return
478596
}
479597

598+
client,_,agentID:=setup(t,nil)
599+
480600
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitLong)
481601
defercancel()
482602

@@ -486,7 +606,7 @@ func TestWorkspaceAgentListeningPorts(t *testing.T) {
486606
deferl.Close()
487607

488608
// List ports and ensure that the list is empty because we're on darwin.
489-
res,err:=client.WorkspaceAgentListeningPorts(ctx,resources[0].Agents[0].ID)
609+
res,err:=client.WorkspaceAgentListeningPorts(ctx,agentID)
490610
require.NoError(t,err)
491611
require.Len(t,res.Ports,0)
492612
})

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp