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)