diff --git a/coderd/httpmw/workspaceparam.go b/coderd/httpmw/workspaceparam.go index 49810206e7..25b07aa669 100644 --- a/coderd/httpmw/workspaceparam.go +++ b/coderd/httpmw/workspaceparam.go @@ -2,12 +2,7 @@ package httpmw import ( "context" - "fmt" "net/http" - "strings" - - "github.com/go-chi/chi/v5" - "github.com/google/uuid" "cdr.dev/slog/v3" "github.com/coder/coder/v2/coderd/database" @@ -59,116 +54,3 @@ func ExtractWorkspaceParam(db database.Store) func(http.Handler) http.Handler { }) } } - -// ExtractWorkspaceAndAgentParam grabs a workspace and an agent from the -// "workspace_and_agent" URL parameter. `ExtractUserParam` must be called -// before this. -// This can be in the form of: -// - ".[workspace-agent]" : If multiple agents exist -// - "" : If one agent exists -func ExtractWorkspaceAndAgentParam(db database.Store) func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - ctx := r.Context() - user := UserParam(r) - workspaceWithAgent := chi.URLParam(r, "workspace_and_agent") - workspaceParts := strings.Split(workspaceWithAgent, ".") - - workspace, err := db.GetWorkspaceByOwnerIDAndName(ctx, database.GetWorkspaceByOwnerIDAndNameParams{ - OwnerID: user.ID, - Name: workspaceParts[0], - }) - if err != nil { - if httpapi.Is404Error(err) { - httpapi.ResourceNotFound(rw) - return - } - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching workspace.", - Detail: err.Error(), - }) - return - } - - build, err := db.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspace.ID) - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching workspace build.", - Detail: err.Error(), - }) - return - } - - resources, err := db.GetWorkspaceResourcesByJobID(ctx, build.JobID) - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching workspace resources.", - Detail: err.Error(), - }) - return - } - resourceIDs := make([]uuid.UUID, 0) - for _, resource := range resources { - resourceIDs = append(resourceIDs, resource.ID) - } - - agents, err := db.GetWorkspaceAgentsByResourceIDs(ctx, resourceIDs) - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching workspace agents.", - Detail: err.Error(), - }) - return - } - - if len(agents) == 0 { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "No agents exist for this workspace", - }) - return - } - - // If we have more than 1 workspace agent, we need to specify which one to use. - if len(agents) > 1 && len(workspaceParts) <= 1 { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "More than one agent exists, but no agent specified.", - }) - return - } - - var agent database.WorkspaceAgent - var found bool - // If we have more than 1 workspace agent, we need to specify which one to use. - // If the user specified an agent, we need to make sure that agent - // actually exists. - if len(workspaceParts) > 1 || len(agents) > 1 { - for _, otherAgent := range agents { - if otherAgent.Name == workspaceParts[1] { - agent = otherAgent - found = true - break - } - } - if !found { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: fmt.Sprintf("No agent exists with the name %q", workspaceParts[1]), - }) - return - } - } else { - agent = agents[0] - } - - ctx = context.WithValue(ctx, workspaceParamContextKey{}, workspace) - ctx = context.WithValue(ctx, workspaceAgentParamContextKey{}, agent) - - if rlogger := loggermw.RequestLoggerFromContext(ctx); rlogger != nil { - rlogger.WithFields( - slog.F("workspace_name", workspace.Name), - slog.F("agent_name", agent.Name), - ) - } - next.ServeHTTP(rw, r.WithContext(ctx)) - }) - } -} diff --git a/coderd/httpmw/workspaceparam_test.go b/coderd/httpmw/workspaceparam_test.go index e83cbe437e..0579708290 100644 --- a/coderd/httpmw/workspaceparam_test.go +++ b/coderd/httpmw/workspaceparam_test.go @@ -2,7 +2,6 @@ package httpmw_test import ( "context" - "encoding/json" "fmt" "net" "net/http" @@ -13,7 +12,6 @@ import ( "github.com/go-chi/chi/v5" "github.com/google/uuid" "github.com/sqlc-dev/pqtype" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/coder/coder/v2/coderd/database" @@ -153,279 +151,3 @@ func TestWorkspaceParam(t *testing.T) { require.Equal(t, http.StatusOK, res.StatusCode) }) } - -func TestWorkspaceAgentByNameParam(t *testing.T) { - t.Parallel() - - testCases := []struct { - Name string - // Agents are mapped to a resource - Agents map[string][]string - URLParam string - WorkspaceName string - ExpectedAgent string - ExpectedStatusCode int - ExpectedError string - }{ - { - Name: "NoAgents", - WorkspaceName: "dev", - Agents: map[string][]string{}, - URLParam: "dev", - ExpectedError: "No agents exist", - ExpectedStatusCode: http.StatusBadRequest, - }, - { - Name: "NoAgentsSpecify", - WorkspaceName: "dev", - Agents: map[string][]string{}, - URLParam: "dev.agent", - ExpectedError: "No agents exist", - ExpectedStatusCode: http.StatusBadRequest, - }, - { - Name: "MultipleAgents", - WorkspaceName: "dev", - Agents: map[string][]string{ - "resource-a": { - "agent-one", - "agent-two", - }, - }, - URLParam: "dev", - ExpectedStatusCode: http.StatusBadRequest, - ExpectedError: "More than one agent exists, but no agent specified", - }, - { - Name: "MultipleResources", - WorkspaceName: "dev", - Agents: map[string][]string{ - "resource-a": { - "agent-one", - }, - "resource-b": { - "agent-two", - }, - }, - URLParam: "dev", - ExpectedStatusCode: http.StatusBadRequest, - ExpectedError: "More than one agent exists, but no agent specified", - }, - { - Name: "NotExistsOneAgent", - WorkspaceName: "dev", - Agents: map[string][]string{ - "resource-a": { - "agent-one", - }, - }, - URLParam: "dev.not-exists", - ExpectedStatusCode: http.StatusBadRequest, - ExpectedError: "No agent exists with the name", - }, - { - Name: "NotExistsMultipleAgents", - WorkspaceName: "dev", - Agents: map[string][]string{ - "resource-a": { - "agent-one", - }, - "resource-b": { - "agent-two", - }, - "resource-c": { - "agent-three", - }, - }, - URLParam: "dev.not-exists", - ExpectedStatusCode: http.StatusBadRequest, - ExpectedError: "No agent exists with the name", - }, - - // OKs - { - Name: "MultipleResourcesOneAgent", - WorkspaceName: "dev", - Agents: map[string][]string{ - "resource-a": {}, - "resource-b": { - "agent-one", - }, - }, - URLParam: "dev", - ExpectedAgent: "agent-one", - ExpectedStatusCode: http.StatusOK, - }, - { - Name: "OneAgent", - WorkspaceName: "dev", - Agents: map[string][]string{ - "resource-a": { - "agent-one", - }, - }, - URLParam: "dev", - ExpectedAgent: "agent-one", - ExpectedStatusCode: http.StatusOK, - }, - { - Name: "OneAgentSelected", - WorkspaceName: "dev", - Agents: map[string][]string{ - "resource-a": { - "agent-one", - }, - }, - URLParam: "dev.agent-one", - ExpectedAgent: "agent-one", - ExpectedStatusCode: http.StatusOK, - }, - { - Name: "MultipleAgentSelectOne", - WorkspaceName: "dev", - Agents: map[string][]string{ - "resource-a": { - "agent-one", - "agent-two", - "agent-selected", - }, - }, - URLParam: "dev.agent-selected", - ExpectedAgent: "agent-selected", - ExpectedStatusCode: http.StatusOK, - }, - { - Name: "MultipleResourcesSelectOne", - WorkspaceName: "dev", - Agents: map[string][]string{ - "resource-a": { - "agent-one", - }, - "resource-b": { - "agent-two", - }, - "resource-c": { - "agent-selected", - "agent-three", - }, - }, - URLParam: "dev.agent-selected", - ExpectedAgent: "agent-selected", - ExpectedStatusCode: http.StatusOK, - }, - } - - for _, c := range testCases { - t.Run(c.Name, func(t *testing.T) { - t.Parallel() - db, r := setupWorkspaceWithAgents(t, setupConfig{ - WorkspaceName: c.WorkspaceName, - Agents: c.Agents, - }) - - chi.RouteContext(r.Context()).URLParams.Add("workspace_and_agent", c.URLParam) - - rtr := chi.NewRouter() - rtr.Use( - httpmw.ExtractAPIKeyMW(httpmw.ExtractAPIKeyConfig{ - DB: db, - RedirectToLogin: true, - }), - httpmw.ExtractUserParam(db), - httpmw.ExtractWorkspaceAndAgentParam(db), - ) - rtr.Get("/", func(w http.ResponseWriter, r *http.Request) { - workspace := httpmw.WorkspaceParam(r) - agent := httpmw.WorkspaceAgentParam(r) - - assert.Equal(t, c.ExpectedAgent, agent.Name, "expected agent name") - assert.Equal(t, c.WorkspaceName, workspace.Name, "expected workspace name") - }) - - rw := httptest.NewRecorder() - rtr.ServeHTTP(rw, r) - res := rw.Result() - var coderResp codersdk.Response - _ = json.NewDecoder(res.Body).Decode(&coderResp) - res.Body.Close() - require.Equal(t, c.ExpectedStatusCode, res.StatusCode) - if c.ExpectedError != "" { - require.Contains(t, coderResp.Message, c.ExpectedError) - } - }) - } -} - -type setupConfig struct { - WorkspaceName string - // Agents are mapped to a resource - Agents map[string][]string -} - -func setupWorkspaceWithAgents(t testing.TB, cfg setupConfig) (database.Store, *http.Request) { - t.Helper() - db, _ := dbtestutil.NewDB(t) - - var ( - user = dbgen.User(t, db, database.User{}) - _, token = dbgen.APIKey(t, db, database.APIKey{ - UserID: user.ID, - }) - org = dbgen.Organization(t, db, database.Organization{}) - tpl = dbgen.Template(t, db, database.Template{ - OrganizationID: org.ID, - CreatedBy: user.ID, - }) - workspace = dbgen.Workspace(t, db, database.WorkspaceTable{ - OwnerID: user.ID, - OrganizationID: org.ID, - TemplateID: tpl.ID, - Name: cfg.WorkspaceName, - }) - job = dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{ - Type: database.ProvisionerJobTypeWorkspaceBuild, - Provisioner: database.ProvisionerTypeEcho, - StorageMethod: database.ProvisionerStorageMethodFile, - }) - tv = dbgen.TemplateVersion(t, db, database.TemplateVersion{ - TemplateID: uuid.NullUUID{ - UUID: tpl.ID, - Valid: true, - }, - JobID: job.ID, - OrganizationID: org.ID, - CreatedBy: user.ID, - }) - _ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{ - JobID: job.ID, - WorkspaceID: workspace.ID, - Transition: database.WorkspaceTransitionStart, - Reason: database.BuildReasonInitiator, - TemplateVersionID: tv.ID, - }) - ) - - r := httptest.NewRequest("GET", "/", nil) - r.Header.Set(codersdk.SessionTokenHeader, token) - - for resourceName, agentNames := range cfg.Agents { - resource := dbgen.WorkspaceResource(t, db, database.WorkspaceResource{ - JobID: job.ID, - Name: resourceName, - Transition: database.WorkspaceTransitionStart, - }) - - for _, name := range agentNames { - _ = dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{ - ResourceID: resource.ID, - Name: name, - }) - } - } - - ctx := chi.NewRouteContext() - ctx.URLParams.Add("user", codersdk.Me) - r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, ctx)) - - return db, r -}