Skip to content

Commit 104d660

Browse files
authored
feat: Add VSCODE_PROXY_URI to surface code-server ports (#4798)
* feat: Add `VSCODE_PROXY_URI` to surface code-server ports Fixes #4776. * Check if app host is provided
1 parent e83e6dc commit 104d660

File tree

7 files changed

+80
-19
lines changed

7 files changed

+80
-19
lines changed

agent/agent.go

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -221,12 +221,18 @@ func (a *agent) run(ctx context.Context) error {
221221
}
222222

223223
func (a *agent) createTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) (*tailnet.Conn, error) {
224+
a.closeMutex.Lock()
225+
if a.isClosed() {
226+
a.closeMutex.Unlock()
227+
return nil, xerrors.New("closed")
228+
}
224229
network, err := tailnet.NewConn(&tailnet.Options{
225230
Addresses: []netip.Prefix{netip.PrefixFrom(codersdk.TailnetIP, 128)},
226231
DERPMap: derpMap,
227232
Logger: a.logger.Named("tailnet"),
228233
})
229234
if err != nil {
235+
a.closeMutex.Unlock()
230236
return nil, xerrors.Errorf("create tailnet: %w", err)
231237
}
232238
a.network = network
@@ -237,14 +243,13 @@ func (a *agent) createTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) (*t
237243
}
238244
return a.stats.wrapConn(conn)
239245
})
246+
a.connCloseWait.Add(4)
247+
a.closeMutex.Unlock()
240248

241249
sshListener, err := network.Listen("tcp", ":"+strconv.Itoa(codersdk.TailnetSSHPort))
242250
if err != nil {
243251
return nil, xerrors.Errorf("listen on the ssh port: %w", err)
244252
}
245-
a.closeMutex.Lock()
246-
a.connCloseWait.Add(1)
247-
a.closeMutex.Unlock()
248253
go func() {
249254
defer a.connCloseWait.Done()
250255
for {
@@ -260,9 +265,6 @@ func (a *agent) createTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) (*t
260265
if err != nil {
261266
return nil, xerrors.Errorf("listen for reconnecting pty: %w", err)
262267
}
263-
a.closeMutex.Lock()
264-
a.connCloseWait.Add(1)
265-
a.closeMutex.Unlock()
266268
go func() {
267269
defer a.connCloseWait.Done()
268270
for {
@@ -298,9 +300,6 @@ func (a *agent) createTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) (*t
298300
if err != nil {
299301
return nil, xerrors.Errorf("listen for speedtest: %w", err)
300302
}
301-
a.closeMutex.Lock()
302-
a.connCloseWait.Add(1)
303-
a.closeMutex.Unlock()
304303
go func() {
305304
defer a.connCloseWait.Done()
306305
for {
@@ -323,9 +322,6 @@ func (a *agent) createTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) (*t
323322
if err != nil {
324323
return nil, xerrors.Errorf("listen for statistics: %w", err)
325324
}
326-
a.closeMutex.Lock()
327-
a.connCloseWait.Add(1)
328-
a.closeMutex.Unlock()
329325
go func() {
330326
defer a.connCloseWait.Done()
331327
defer statisticsListener.Close()
@@ -569,7 +565,6 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
569565
// Set environment variables reliable detection of being inside a
570566
// Coder workspace.
571567
cmd.Env = append(cmd.Env, "CODER=true")
572-
573568
cmd.Env = append(cmd.Env, fmt.Sprintf("USER=%s", username))
574569
// Git on Windows resolves with UNIX-style paths.
575570
// If using backslashes, it's unable to find the executable.
@@ -585,6 +580,10 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
585580
cmd.Env = append(cmd.Env, fmt.Sprintf("SSH_CLIENT=%s %s %s", srcAddr, srcPort, dstPort))
586581
cmd.Env = append(cmd.Env, fmt.Sprintf("SSH_CONNECTION=%s %s %s %s", srcAddr, srcPort, dstAddr, dstPort))
587582

583+
// This adds the ports dialog to code-server that enables
584+
// proxying a port dynamically.
585+
cmd.Env = append(cmd.Env, fmt.Sprintf("VSCODE_PROXY_URI=%s", metadata.VSCodePortProxyURI))
586+
588587
// Hide Coder message on code-server's "Getting Started" page
589588
cmd.Env = append(cmd.Env, "CS_DISABLE_GETTING_STARTED_OVERRIDE=true")
590589

agent/agent_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323

2424
"golang.org/x/xerrors"
2525
"tailscale.com/net/speedtest"
26+
"tailscale.com/tailcfg"
2627

2728
scp "github.com/bramvdbogaerde/go-scp"
2829
"github.com/google/uuid"
@@ -559,6 +560,7 @@ func TestAgent(t *testing.T) {
559560
agentID: uuid.New(),
560561
metadata: codersdk.WorkspaceAgentMetadata{
561562
GitAuthConfigs: 1,
563+
DERPMap: &tailcfg.DERPMap{},
562564
},
563565
statsChan: make(chan *codersdk.AgentStats),
564566
coordinator: tailnet.NewCoordinator(),

coderd/activitybump_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func TestWorkspaceActivityBump(t *testing.T) {
3131
})
3232
user := coderdtest.CreateFirstUser(t, client)
3333

34-
workspace = createWorkspaceWithApps(t, client, user.OrganizationID, 1234, func(cwr *codersdk.CreateWorkspaceRequest) {
34+
workspace = createWorkspaceWithApps(t, client, user.OrganizationID, "", 1234, func(cwr *codersdk.CreateWorkspaceRequest) {
3535
cwr.TTLMillis = &ttlMillis
3636
})
3737

coderd/workspaceagents.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,46 @@ func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request)
8383
})
8484
return
8585
}
86+
resource, err := api.Database.GetWorkspaceResourceByID(r.Context(), workspaceAgent.ResourceID)
87+
if err != nil {
88+
httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{
89+
Message: "Internal error fetching workspace resource.",
90+
Detail: err.Error(),
91+
})
92+
return
93+
}
94+
build, err := api.Database.GetWorkspaceBuildByJobID(r.Context(), resource.JobID)
95+
if err != nil {
96+
httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{
97+
Message: "Internal error fetching workspace build.",
98+
Detail: err.Error(),
99+
})
100+
return
101+
}
102+
workspace, err := api.Database.GetWorkspaceByID(r.Context(), build.WorkspaceID)
103+
if err != nil {
104+
httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{
105+
Message: "Internal error fetching workspace.",
106+
Detail: err.Error(),
107+
})
108+
return
109+
}
110+
owner, err := api.Database.GetUserByID(r.Context(), workspace.OwnerID)
111+
if err != nil {
112+
httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{
113+
Message: "Internal error fetching workspace owner.",
114+
Detail: err.Error(),
115+
})
116+
return
117+
}
118+
119+
vscodeProxyURI := strings.ReplaceAll(api.AppHostname, "*",
120+
fmt.Sprintf("%s://{{port}}--%s--%s--%s",
121+
api.AccessURL.Scheme,
122+
workspaceAgent.Name,
123+
workspace.Name,
124+
owner.Username,
125+
))
86126

87127
httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspaceAgentMetadata{
88128
Apps: convertApps(dbApps),
@@ -91,6 +131,7 @@ func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request)
91131
EnvironmentVariables: apiAgent.EnvironmentVariables,
92132
StartupScript: apiAgent.StartupScript,
93133
Directory: apiAgent.Directory,
134+
VSCodePortProxyURI: vscodeProxyURI,
94135
})
95136
}
96137

coderd/workspaceapps_test.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ func setupProxyTest(t *testing.T, customAppHost ...string) (*codersdk.Client, co
121121
})
122122
user := coderdtest.CreateFirstUser(t, client)
123123

124-
workspace := createWorkspaceWithApps(t, client, user.OrganizationID, uint16(tcpAddr.Port))
124+
workspace := createWorkspaceWithApps(t, client, user.OrganizationID, appHost, uint16(tcpAddr.Port))
125125

126126
// Configure the HTTP client to not follow redirects and to route all
127127
// requests regardless of hostname to the coderd test server.
@@ -139,7 +139,7 @@ func setupProxyTest(t *testing.T, customAppHost ...string) (*codersdk.Client, co
139139
return client, user, workspace, uint16(tcpAddr.Port)
140140
}
141141

142-
func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.UUID, port uint16, workspaceMutators ...func(*codersdk.CreateWorkspaceRequest)) codersdk.Workspace {
142+
func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.UUID, appHost string, port uint16, workspaceMutators ...func(*codersdk.CreateWorkspaceRequest)) codersdk.Workspace {
143143
authToken := uuid.NewString()
144144

145145
appURL := fmt.Sprintf("http://127.0.0.1:%d?%s", port, proxyTestAppQuery)
@@ -198,6 +198,17 @@ func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.U
198198

199199
agentClient := codersdk.New(client.URL)
200200
agentClient.SessionToken = authToken
201+
if appHost != "" {
202+
metadata, err := agentClient.WorkspaceAgentMetadata(context.Background())
203+
require.NoError(t, err)
204+
require.Equal(t, fmt.Sprintf(
205+
"http://{{port}}--%s--%s--%s%s",
206+
proxyTestAgentName,
207+
workspace.Name,
208+
"testuser",
209+
strings.ReplaceAll(appHost, "*", ""),
210+
), metadata.VSCodePortProxyURI)
211+
}
201212
agentCloser := agent.New(agent.Options{
202213
Client: agentClient,
203214
Logger: slogtest.Make(t, nil).Named("agent"),

coderd/wsconncache/wsconncache.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,18 @@ func (c *Cache) Acquire(r *http.Request, id uuid.UUID) (*Conn, func(), error) {
8686
// A singleflight group is used to allow for concurrent requests to the
8787
// same identifier to resolve.
8888
rawConn, err, _ = c.connGroup.Do(id.String(), func() (interface{}, error) {
89+
c.closeMutex.Lock()
90+
select {
91+
case <-c.closed:
92+
c.closeMutex.Unlock()
93+
return nil, xerrors.New("closed")
94+
default:
95+
}
96+
c.closeGroup.Add(1)
97+
c.closeMutex.Unlock()
8998
agentConn, err := c.dialer(r, id)
9099
if err != nil {
100+
c.closeGroup.Done()
91101
return nil, xerrors.Errorf("dial: %w", err)
92102
}
93103
timeoutCtx, timeoutCancelFunc := context.WithCancel(context.Background())
@@ -102,9 +112,6 @@ func (c *Cache) Acquire(r *http.Request, id uuid.UUID) (*Conn, func(), error) {
102112
timeoutCancel: timeoutCancelFunc,
103113
transport: transport,
104114
}
105-
c.closeMutex.Lock()
106-
c.closeGroup.Add(1)
107-
c.closeMutex.Unlock()
108115
go func() {
109116
defer c.closeGroup.Done()
110117
var err error

codersdk/workspaceagents.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ type WorkspaceAgentMetadata struct {
123123
// the Coder deployment has. If this number is >0, we
124124
// set up special configuration in the workspace.
125125
GitAuthConfigs int `json:"git_auth_configs"`
126+
VSCodePortProxyURI string `json:"vscode_port_proxy_uri"`
126127
Apps []WorkspaceApp `json:"apps"`
127128
DERPMap *tailcfg.DERPMap `json:"derpmap"`
128129
EnvironmentVariables map[string]string `json:"environment_variables"`

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy