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

Commitd1496ed

Browse files
committed
feat: add endpoint for netstat web socket
1 parente9a28be commitd1496ed

File tree

5 files changed

+149
-0
lines changed

5 files changed

+149
-0
lines changed

‎coderd/coderd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ func New(options *Options) *API {
285285
r.Get("/",api.workspaceAgent)
286286
r.Get("/dial",api.workspaceAgentDial)
287287
r.Get("/turn",api.workspaceAgentTurn)
288+
r.Get("/netstat",api.workspaceAgentNetstat)
288289
r.Get("/pty",api.workspaceAgentPTY)
289290
r.Get("/iceservers",api.workspaceAgentICEServers)
290291
})

‎coderd/coderd_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
139139
"GET:/api/v2/workspaceagents/{workspaceagent}": {NoAuthorize:true},
140140
"GET:/api/v2/workspaceagents/{workspaceagent}/dial": {NoAuthorize:true},
141141
"GET:/api/v2/workspaceagents/{workspaceagent}/iceservers": {NoAuthorize:true},
142+
"GET:/api/v2/workspaceagents/{workspaceagent}/netstat": {NoAuthorize:true},
142143
"GET:/api/v2/workspaceagents/{workspaceagent}/pty": {NoAuthorize:true},
143144
"GET:/api/v2/workspaceagents/{workspaceagent}/turn": {NoAuthorize:true},
144145

‎coderd/workspaceagents.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,3 +501,55 @@ func convertWorkspaceAgent(dbAgent database.WorkspaceAgent, agentUpdateFrequency
501501

502502
returnworkspaceAgent,nil
503503
}
504+
505+
// workspaceAgentNetstat sends listening ports as `agent.NetstatResponse` on an
506+
// interval.
507+
func (api*API)workspaceAgentNetstat(rw http.ResponseWriter,r*http.Request) {
508+
api.websocketWaitMutex.Lock()
509+
api.websocketWaitGroup.Add(1)
510+
api.websocketWaitMutex.Unlock()
511+
deferapi.websocketWaitGroup.Done()
512+
513+
workspaceAgent:=httpmw.WorkspaceAgentParam(r)
514+
apiAgent,err:=convertWorkspaceAgent(workspaceAgent,api.AgentConnectionUpdateFrequency)
515+
iferr!=nil {
516+
httpapi.Write(rw,http.StatusInternalServerError, httpapi.Response{
517+
Message:fmt.Sprintf("convert workspace agent: %s",err),
518+
})
519+
return
520+
}
521+
ifapiAgent.Status!=codersdk.WorkspaceAgentConnected {
522+
httpapi.Write(rw,http.StatusPreconditionRequired, httpapi.Response{
523+
Message:fmt.Sprintf("agent must be in the connected state: %s",apiAgent.Status),
524+
})
525+
return
526+
}
527+
528+
conn,err:=websocket.Accept(rw,r,&websocket.AcceptOptions{
529+
CompressionMode:websocket.CompressionDisabled,
530+
})
531+
iferr!=nil {
532+
httpapi.Write(rw,http.StatusBadRequest, httpapi.Response{
533+
Message:fmt.Sprintf("accept websocket: %s",err),
534+
})
535+
return
536+
}
537+
deferfunc() {
538+
_=conn.Close(websocket.StatusNormalClosure,"ended")
539+
}()
540+
wsNetConn:=websocket.NetConn(r.Context(),conn,websocket.MessageBinary)
541+
agentConn,err:=api.dialWorkspaceAgent(r,workspaceAgent.ID)
542+
iferr!=nil {
543+
_=conn.Close(websocket.StatusInternalError,httpapi.WebsocketCloseSprintf("dial workspace agent: %s",err))
544+
return
545+
}
546+
deferagentConn.Close()
547+
ptNetConn,err:=agentConn.Netstat(r.Context())
548+
iferr!=nil {
549+
_=conn.Close(websocket.StatusInternalError,httpapi.WebsocketCloseSprintf("dial: %s",err))
550+
return
551+
}
552+
deferptNetConn.Close()
553+
554+
agent.Bicopy(r.Context(),wsNetConn,ptNetConn)
555+
}

‎coderd/workspaceagents_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bufio"
55
"context"
66
"encoding/json"
7+
"fmt"
78
"runtime"
89
"strings"
910
"testing"
@@ -264,3 +265,67 @@ func TestWorkspaceAgentPTY(t *testing.T) {
264265
expectLine(matchEchoCommand)
265266
expectLine(matchEchoOutput)
266267
}
268+
269+
funcTestWorkspaceAgentNetstat(t*testing.T) {
270+
t.Parallel()
271+
272+
client,coderAPI:=coderdtest.NewWithAPI(t,nil)
273+
user:=coderdtest.CreateFirstUser(t,client)
274+
daemonCloser:=coderdtest.NewProvisionerDaemon(t,coderAPI)
275+
authToken:=uuid.NewString()
276+
version:=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,&echo.Responses{
277+
Parse:echo.ParseComplete,
278+
ProvisionDryRun:echo.ProvisionComplete,
279+
Provision: []*proto.Provision_Response{{
280+
Type:&proto.Provision_Response_Complete{
281+
Complete:&proto.Provision_Complete{
282+
Resources: []*proto.Resource{{
283+
Name:"example",
284+
Type:"aws_instance",
285+
Agents: []*proto.Agent{{
286+
Id:uuid.NewString(),
287+
Auth:&proto.Agent_Token{
288+
Token:authToken,
289+
},
290+
}},
291+
}},
292+
},
293+
},
294+
}},
295+
})
296+
template:=coderdtest.CreateTemplate(t,client,user.OrganizationID,version.ID)
297+
coderdtest.AwaitTemplateVersionJob(t,client,version.ID)
298+
workspace:=coderdtest.CreateWorkspace(t,client,user.OrganizationID,template.ID)
299+
coderdtest.AwaitWorkspaceBuildJob(t,client,workspace.LatestBuild.ID)
300+
daemonCloser.Close()
301+
302+
agentClient:=codersdk.New(client.URL)
303+
agentClient.SessionToken=authToken
304+
agentCloser:=agent.New(agentClient.ListenWorkspaceAgent,&agent.Options{
305+
Logger:slogtest.Make(t,nil),
306+
})
307+
t.Cleanup(func() {
308+
_=agentCloser.Close()
309+
})
310+
resources:=coderdtest.AwaitWorkspaceAgents(t,client,workspace.LatestBuild.ID)
311+
312+
conn,err:=client.WorkspaceAgentNetstat(context.Background(),resources[0].Agents[0].ID)
313+
require.NoError(t,err)
314+
deferconn.Close()
315+
316+
decoder:=json.NewDecoder(conn)
317+
318+
expectNetstat:=func() {
319+
varres agent.NetstatResponse
320+
err=decoder.Decode(&res)
321+
require.NoError(t,err)
322+
323+
ifruntime.GOOS=="linux"||runtime.GOOS=="windows" {
324+
require.NotNil(t,res.Ports)
325+
}else {
326+
require.Equal(t,fmt.Sprintf("Port scanning is not supported on %s",runtime.GOOS),res.Error)
327+
}
328+
}
329+
330+
expectNetstat()
331+
}

‎codersdk/workspaceagents.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,36 @@ func (c *Client) WorkspaceAgentReconnectingPTY(ctx context.Context, agentID, rec
369369
returnwebsocket.NetConn(ctx,conn,websocket.MessageBinary),nil
370370
}
371371

372+
// WorkspaceAgentNetstat sends listening ports as `agent.NetstatResponse` on an
373+
// interval.
374+
func (c*Client)WorkspaceAgentNetstat(ctx context.Context,agentID uuid.UUID) (net.Conn,error) {
375+
serverURL,err:=c.URL.Parse(fmt.Sprintf("/api/v2/workspaceagents/%s/netstat",agentID))
376+
iferr!=nil {
377+
returnnil,xerrors.Errorf("parse url: %w",err)
378+
}
379+
jar,err:=cookiejar.New(nil)
380+
iferr!=nil {
381+
returnnil,xerrors.Errorf("create cookie jar: %w",err)
382+
}
383+
jar.SetCookies(serverURL, []*http.Cookie{{
384+
Name:httpmw.SessionTokenKey,
385+
Value:c.SessionToken,
386+
}})
387+
httpClient:=&http.Client{
388+
Jar:jar,
389+
}
390+
conn,res,err:=websocket.Dial(ctx,serverURL.String(),&websocket.DialOptions{
391+
HTTPClient:httpClient,
392+
})
393+
iferr!=nil {
394+
ifres==nil {
395+
returnnil,err
396+
}
397+
returnnil,readBodyAsError(res)
398+
}
399+
returnwebsocket.NetConn(ctx,conn,websocket.MessageBinary),nil
400+
}
401+
372402
func (c*Client)turnProxyDialer(ctx context.Context,httpClient*http.Client,pathstring) proxy.Dialer {
373403
returnturnconn.ProxyDialer(func() (net.Conn,error) {
374404
turnURL,err:=c.URL.Parse(path)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp