Skip to content

Commit 7d0bc56

Browse files
committed
chore: Make workspace and agent extract a middleware
1 parent 433705d commit 7d0bc56

File tree

4 files changed

+112
-87
lines changed

4 files changed

+112
-87
lines changed

coderd/coderd.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,15 @@ func New(options *Options) *API {
138138
httpmw.RateLimitPerMinute(options.APIRateLimit),
139139
httpmw.ExtractAPIKey(options.Database, oauthConfigs, true),
140140
httpmw.ExtractUserParam(api.Database),
141+
httpmw.ExtractWorkspaceAndAgentParam(api.Database),
141142
)
142143
r.HandleFunc("/*", api.workspaceAppsProxyPath)
143144
}
144145
// %40 is the encoded character of the @ symbol. VS Code Web does
145146
// not handle character encoding properly, so it's safe to assume
146147
// other applications might not as well.
147-
r.Route("/%40{user}/{workspacename}/apps/{workspaceapp}", apps)
148-
r.Route("/@{user}/{workspacename}/apps/{workspaceapp}", apps)
148+
r.Route("/%40{user}/{workspacename_and_agent}/apps/{workspaceapp}", apps)
149+
r.Route("/@{user}/{workspacename_and_agent}/apps/{workspaceapp}", apps)
149150

150151
r.Route("/api/v2", func(r chi.Router) {
151152
r.NotFound(func(rw http.ResponseWriter, r *http.Request) {

coderd/httpmw/chiparams.go

Lines changed: 0 additions & 1 deletion
This file was deleted.

coderd/httpmw/workspaceparam.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ import (
44
"context"
55
"database/sql"
66
"errors"
7+
"fmt"
78
"net/http"
9+
"strings"
810

911
"github.com/coder/coder/coderd/database"
1012
"github.com/coder/coder/coderd/httpapi"
1113
"github.com/coder/coder/codersdk"
14+
"github.com/go-chi/chi/v5"
15+
"github.com/google/uuid"
1216
)
1317

1418
type workspaceParamContextKey struct{}
@@ -48,3 +52,106 @@ func ExtractWorkspaceParam(db database.Store) func(http.Handler) http.Handler {
4852
})
4953
}
5054
}
55+
56+
// ExtractWorkspaceAndAgentParam grabs a workspace and an agent from the
57+
// "workspacename_and_agent" URL parameter. `ExtractUserParam` must be called
58+
// before this.
59+
// This can be in the form of:
60+
// - "<workspace-name>.[workspace-agent]" : If multiple agents exist
61+
// - "<workspace-name>" : If one agent exists
62+
func ExtractWorkspaceAndAgentParam(db database.Store) func(http.Handler) http.Handler {
63+
return func(next http.Handler) http.Handler {
64+
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
65+
user := UserParam(r)
66+
workspaceWithAgent := chi.URLParam(r, "workspacename_and_agent")
67+
workspaceParts := strings.Split(workspaceWithAgent, ".")
68+
69+
workspace, err := db.GetWorkspaceByOwnerIDAndName(r.Context(), database.GetWorkspaceByOwnerIDAndNameParams{
70+
OwnerID: user.ID,
71+
Name: workspaceParts[0],
72+
})
73+
if err != nil {
74+
if errors.Is(err, sql.ErrNoRows) {
75+
httpapi.ResourceNotFound(rw)
76+
return
77+
}
78+
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
79+
Message: "Internal error fetching workspace.",
80+
Detail: err.Error(),
81+
})
82+
return
83+
}
84+
85+
build, err := db.GetLatestWorkspaceBuildByWorkspaceID(r.Context(), workspace.ID)
86+
if err != nil {
87+
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
88+
Message: "Internal error fetching workspace build.",
89+
Detail: err.Error(),
90+
})
91+
return
92+
}
93+
94+
resources, err := db.GetWorkspaceResourcesByJobID(r.Context(), build.JobID)
95+
if err != nil {
96+
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
97+
Message: "Internal error fetching workspace resources.",
98+
Detail: err.Error(),
99+
})
100+
return
101+
}
102+
resourceIDs := make([]uuid.UUID, 0)
103+
for _, resource := range resources {
104+
resourceIDs = append(resourceIDs, resource.ID)
105+
}
106+
107+
agents, err := db.GetWorkspaceAgentsByResourceIDs(r.Context(), resourceIDs)
108+
if err != nil {
109+
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
110+
Message: "Internal error fetching workspace agents.",
111+
Detail: err.Error(),
112+
})
113+
return
114+
}
115+
116+
if len(agents) == 0 {
117+
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
118+
Message: "No agents exist for this workspace",
119+
})
120+
return
121+
}
122+
123+
// If we have more than 1 workspace agent, we need to specify which one to use.
124+
if len(agents) > 1 && len(workspaceParts) <= 1 {
125+
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
126+
Message: "More than one agent exists, but no agent specified.",
127+
})
128+
return
129+
}
130+
131+
// If we have more than 1 workspace agent, we need to specify which one to use.
132+
var agent database.WorkspaceAgent
133+
var found bool
134+
if len(agents) > 1 {
135+
for _, otherAgent := range agents {
136+
if otherAgent.Name == workspaceParts[1] {
137+
agent = otherAgent
138+
found = true
139+
break
140+
}
141+
}
142+
if !found {
143+
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
144+
Message: fmt.Sprintf("No agent exists with the name %s", workspaceParts[1]),
145+
})
146+
return
147+
}
148+
} else {
149+
agent = agents[0]
150+
}
151+
152+
ctx := context.WithValue(r.Context(), workspaceParamContextKey{}, workspace)
153+
ctx = context.WithValue(r.Context(), workspaceAgentContextKey{}, agent)
154+
next.ServeHTTP(rw, r.WithContext(ctx))
155+
})
156+
}
157+
}

coderd/workspaceapps.go

Lines changed: 2 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"strings"
1111

1212
"github.com/go-chi/chi/v5"
13-
"github.com/google/uuid"
1413

1514
"github.com/coder/coder/coderd/database"
1615
"github.com/coder/coder/coderd/httpapi"
@@ -23,95 +22,14 @@ import (
2322
// workspaceAppsProxyPath proxies requests to a workspace application
2423
// through a relative URL path.
2524
func (api *API) workspaceAppsProxyPath(rw http.ResponseWriter, r *http.Request) {
26-
user := httpmw.UserParam(r)
27-
// This can be in the form of: "<workspace-name>.[workspace-agent]" or "<workspace-name>"
28-
workspaceWithAgent := chi.URLParam(r, "workspacename")
29-
workspaceParts := strings.Split(workspaceWithAgent, ".")
30-
31-
workspace, err := api.Database.GetWorkspaceByOwnerIDAndName(r.Context(), database.GetWorkspaceByOwnerIDAndNameParams{
32-
OwnerID: user.ID,
33-
Name: workspaceParts[0],
34-
})
35-
if errors.Is(err, sql.ErrNoRows) {
36-
httpapi.ResourceNotFound(rw)
37-
return
38-
}
39-
if err != nil {
40-
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
41-
Message: "Internal error fetching workspace.",
42-
Detail: err.Error(),
43-
})
44-
return
45-
}
25+
workspace := httpmw.WorkspaceParam(r)
26+
agent := httpmw.WorkspaceAgentParam(r)
4627

4728
if !api.Authorize(r, rbac.ActionCreate, workspace.ExecutionRBAC()) {
4829
httpapi.ResourceNotFound(rw)
4930
return
5031
}
5132

52-
build, err := api.Database.GetLatestWorkspaceBuildByWorkspaceID(r.Context(), workspace.ID)
53-
if err != nil {
54-
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
55-
Message: "Internal error fetching workspace build.",
56-
Detail: err.Error(),
57-
})
58-
return
59-
}
60-
61-
resources, err := api.Database.GetWorkspaceResourcesByJobID(r.Context(), build.JobID)
62-
if err != nil {
63-
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
64-
Message: "Internal error fetching workspace resources.",
65-
Detail: err.Error(),
66-
})
67-
return
68-
}
69-
resourceIDs := make([]uuid.UUID, 0)
70-
for _, resource := range resources {
71-
resourceIDs = append(resourceIDs, resource.ID)
72-
}
73-
agents, err := api.Database.GetWorkspaceAgentsByResourceIDs(r.Context(), resourceIDs)
74-
if err != nil {
75-
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
76-
Message: "Internal error fetching workspace agents.",
77-
Detail: err.Error(),
78-
})
79-
return
80-
}
81-
if len(agents) == 0 {
82-
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
83-
Message: "No agents exist.",
84-
})
85-
return
86-
}
87-
88-
// If we have more than 1 workspace agent, we need to specify which one to use.
89-
if len(agents) > 1 && len(workspaceParts) <= 1 {
90-
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
91-
Message: "More than one agent exists, but no agent specified.",
92-
})
93-
return
94-
}
95-
96-
// If we have more than 1 workspace agent, we need to specify which one to use.
97-
var agent *database.WorkspaceAgent
98-
if len(agents) > 1 {
99-
for _, otherAgent := range agents {
100-
if otherAgent.Name == workspaceParts[1] {
101-
agent = &otherAgent
102-
break
103-
}
104-
}
105-
if agent == nil {
106-
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
107-
Message: fmt.Sprintf("No agent exists with the name %s", workspaceParts[1]),
108-
})
109-
return
110-
}
111-
} else {
112-
agent = &agents[0]
113-
}
114-
11533
app, err := api.Database.GetWorkspaceAppByAgentIDAndName(r.Context(), database.GetWorkspaceAppByAgentIDAndNameParams{
11634
AgentID: agent.ID,
11735
Name: chi.URLParam(r, "workspaceapp"),

0 commit comments

Comments
 (0)