Skip to content

Commit a7e5bd6

Browse files
committed
Add basic router for applications using same codepath
1 parent 35ee5d6 commit a7e5bd6

File tree

3 files changed

+119
-46
lines changed

3 files changed

+119
-46
lines changed

coderd/subdomain.go

+67-15
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
package coderd
22

33
import (
4+
"database/sql"
45
"fmt"
56
"net/http"
67
"regexp"
78
"strings"
89

10+
"github.com/coder/coder/coderd/httpapi"
11+
"github.com/coder/coder/codersdk"
12+
13+
"github.com/coder/coder/coderd/database"
14+
915
"golang.org/x/xerrors"
1016
)
1117

@@ -17,20 +23,66 @@ const (
1723
)
1824

1925
type Application struct {
20-
AppURL string
21-
AppName string
22-
Workspace string
23-
Agent string
24-
User string
25-
Path string
26+
AppURL string
27+
AppName string
28+
WorkspaceName string
29+
Agent string
30+
Username string
31+
Path string
2632

2733
// Domain is used to output the url to reach the app.
2834
Domain string
2935
}
3036

3137
func (api *API) handleSubdomain(next http.Handler) http.Handler {
32-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
33-
38+
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
39+
ctx := r.Context()
40+
app, err := ParseSubdomainAppURL(r)
41+
if err != nil {
42+
// Not a Dev URL, proceed as usual.
43+
// TODO: @emyrk we should probably catch invalid subdomains. Meaning
44+
// an invalid devurl should not route to the coderd.
45+
next.ServeHTTP(rw, r)
46+
return
47+
}
48+
49+
user, err := api.Database.GetUserByEmailOrUsername(ctx, database.GetUserByEmailOrUsernameParams{
50+
Username: app.Username,
51+
})
52+
if err != nil {
53+
if xerrors.Is(err, sql.ErrNoRows) {
54+
httpapi.ResourceNotFound(rw)
55+
return
56+
}
57+
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
58+
Message: "Internal error fetching user.",
59+
Detail: err.Error(),
60+
})
61+
return
62+
}
63+
64+
workspace, err := api.Database.GetWorkspaceByOwnerIDAndName(ctx, database.GetWorkspaceByOwnerIDAndNameParams{
65+
OwnerID: user.ID,
66+
Name: app.WorkspaceName,
67+
})
68+
if err != nil {
69+
if xerrors.Is(err, sql.ErrNoRows) {
70+
httpapi.ResourceNotFound(rw)
71+
return
72+
}
73+
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
74+
Message: "Internal error fetching workspace.",
75+
Detail: err.Error(),
76+
})
77+
return
78+
}
79+
80+
api.proxyWorkspaceApplication(proxyApplication{
81+
Workspace: workspace,
82+
// TODO: Fetch workspace agent
83+
Agent: database.WorkspaceAgent{},
84+
AppName: app.AppName,
85+
}, rw, r)
3486
})
3587
}
3688

@@ -64,13 +116,13 @@ func ParseSubdomainAppURL(r *http.Request) (Application, error) {
64116
matchGroup := matches[0]
65117

66118
return Application{
67-
AppURL: "",
68-
AppName: matchGroup[appURL.SubexpIndex("AppName")],
69-
Workspace: matchGroup[appURL.SubexpIndex("WorkspaceName")],
70-
Agent: matchGroup[appURL.SubexpIndex("AgentName")],
71-
User: matchGroup[appURL.SubexpIndex("UserName")],
72-
Path: r.URL.Path,
73-
Domain: domain,
119+
AppURL: "",
120+
AppName: matchGroup[appURL.SubexpIndex("AppName")],
121+
WorkspaceName: matchGroup[appURL.SubexpIndex("WorkspaceName")],
122+
Agent: matchGroup[appURL.SubexpIndex("AgentName")],
123+
Username: matchGroup[appURL.SubexpIndex("UserName")],
124+
Path: r.URL.Path,
125+
Domain: domain,
74126
}, nil
75127
}
76128

coderd/subdomain_test.go

+28-28
Original file line numberDiff line numberDiff line change
@@ -40,52 +40,52 @@ func TestParseSubdomainAppURL(t *testing.T) {
4040
Name: "User+Workspace+App",
4141
URL: "https://user--workspace--app.coder.com",
4242
Expected: coderd.Application{
43-
AppURL: "",
44-
AppName: "app",
45-
Workspace: "workspace",
46-
Agent: "",
47-
User: "user",
48-
Path: "",
49-
Domain: "coder.com",
43+
AppURL: "",
44+
AppName: "app",
45+
WorkspaceName: "workspace",
46+
Agent: "",
47+
User: "user",
48+
Path: "",
49+
Domain: "coder.com",
5050
},
5151
},
5252
{
5353
Name: "User+Workspace+Port",
5454
URL: "https://user--workspace--8080.coder.com",
5555
Expected: coderd.Application{
56-
AppURL: "",
57-
AppName: "8080",
58-
Workspace: "workspace",
59-
Agent: "",
60-
User: "user",
61-
Path: "",
62-
Domain: "coder.com",
56+
AppURL: "",
57+
AppName: "8080",
58+
WorkspaceName: "workspace",
59+
Agent: "",
60+
User: "user",
61+
Path: "",
62+
Domain: "coder.com",
6363
},
6464
},
6565
{
6666
Name: "User+Workspace.Agent+App",
6767
URL: "https://user--workspace--agent--app.coder.com",
6868
Expected: coderd.Application{
69-
AppURL: "",
70-
AppName: "app",
71-
Workspace: "workspace",
72-
Agent: "agent",
73-
User: "user",
74-
Path: "",
75-
Domain: "coder.com",
69+
AppURL: "",
70+
AppName: "app",
71+
WorkspaceName: "workspace",
72+
Agent: "agent",
73+
User: "user",
74+
Path: "",
75+
Domain: "coder.com",
7676
},
7777
},
7878
{
7979
Name: "User+Workspace.Agent+Port",
8080
URL: "https://user--workspace--agent--8080.coder.com",
8181
Expected: coderd.Application{
82-
AppURL: "",
83-
AppName: "8080",
84-
Workspace: "workspace",
85-
Agent: "agent",
86-
User: "user",
87-
Path: "",
88-
Domain: "coder.com",
82+
AppURL: "",
83+
AppName: "8080",
84+
WorkspaceName: "workspace",
85+
Agent: "agent",
86+
User: "user",
87+
Path: "",
88+
Domain: "coder.com",
8989
},
9090
},
9191
}

coderd/workspaceapps.go

+24-3
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,30 @@ func (api *API) workspaceAppsProxyPath(rw http.ResponseWriter, r *http.Request)
9696
}
9797
}
9898

99+
api.proxyWorkspaceApplication(proxyApplication{
100+
Workspace: workspace,
101+
Agent: agent,
102+
AppName: chi.URLParam(r, "workspaceapp"),
103+
}, rw, r)
104+
}
105+
106+
// proxyApplication are the required fields to proxy a workspace application.
107+
type proxyApplication struct {
108+
Workspace database.Workspace
109+
Agent database.WorkspaceAgent
110+
111+
AppName string
112+
}
113+
114+
func (api *API) proxyWorkspaceApplication(proxyApp proxyApplication, rw http.ResponseWriter, r *http.Request) {
115+
if !api.Authorize(r, rbac.ActionCreate, proxyApp.Workspace.ExecutionRBAC()) {
116+
httpapi.ResourceNotFound(rw)
117+
return
118+
}
119+
99120
app, err := api.Database.GetWorkspaceAppByAgentIDAndName(r.Context(), database.GetWorkspaceAppByAgentIDAndNameParams{
100-
AgentID: agent.ID,
101-
Name: chi.URLParam(r, "workspaceapp"),
121+
AgentID: proxyApp.Agent.ID,
122+
Name: proxyApp.AppName,
102123
})
103124
if errors.Is(err, sql.ErrNoRows) {
104125
httpapi.Write(rw, http.StatusNotFound, codersdk.Response{
@@ -161,7 +182,7 @@ func (api *API) workspaceAppsProxyPath(rw http.ResponseWriter, r *http.Request)
161182
}
162183
r.URL.Path = path
163184

164-
conn, release, err := api.workspaceAgentCache.Acquire(r, agent.ID)
185+
conn, release, err := api.workspaceAgentCache.Acquire(r, proxyApp.Agent.ID)
165186
if err != nil {
166187
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
167188
Message: "Failed to dial workspace agent.",

0 commit comments

Comments
 (0)