mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: Remove unused workspace routes in favor of list with filter (#2038)
* fix: Remove unused workspace routes in favor of list with filter This consolidates the workspace routes into a single place. It allows users to fetch a workspace by their username and workspace name, which will be used by the frontend for routing. * Fix RBAC * Fix CLI usages
This commit is contained in:
+3
-15
@@ -41,12 +41,8 @@ func autostartShow() *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
organization, err := currentOrganization(cmd, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
|
||||
workspace, err := namedWorkspace(cmd, client, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -93,10 +89,6 @@ func autostartEnable() *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
organization, err := currentOrganization(cmd, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
spec := fmt.Sprintf("CRON_TZ=%s %s %s * * %s", autostartTimezone, autostartMinute, autostartHour, autostartDayOfWeek)
|
||||
validSchedule, err := schedule.Weekly(spec)
|
||||
@@ -104,7 +96,7 @@ func autostartEnable() *cobra.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
|
||||
workspace, err := namedWorkspace(cmd, client, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -142,12 +134,8 @@ func autostartDisable() *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
organization, err := currentOrganization(cmd, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
|
||||
workspace, err := namedWorkspace(cmd, client, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+1
-6
@@ -43,12 +43,7 @@ func bump() *cobra.Command {
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create client: %w", err)
|
||||
}
|
||||
organization, err := currentOrganization(cmd, client)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get current org: %w", err)
|
||||
}
|
||||
|
||||
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
|
||||
workspace, err := namedWorkspace(cmd, client, args[0])
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get workspace: %w", err)
|
||||
}
|
||||
|
||||
+3
-5
@@ -48,10 +48,6 @@ func configSSH() *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
organization, err := currentOrganization(cmd, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.HasPrefix(sshConfigFile, "~/") {
|
||||
dirname, _ := os.UserHomeDir()
|
||||
sshConfigFile = filepath.Join(dirname, sshConfigFile[2:])
|
||||
@@ -65,7 +61,9 @@ func configSSH() *cobra.Command {
|
||||
sshConfigContent = sshConfigContent[:startIndex-1] + sshConfigContent[endIndex+len(sshEndToken):]
|
||||
}
|
||||
|
||||
workspaces, err := client.WorkspacesByOwner(cmd.Context(), organization.ID, codersdk.Me)
|
||||
workspaces, err := client.Workspaces(cmd.Context(), codersdk.WorkspaceFilter{
|
||||
Owner: codersdk.Me,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+2
-2
@@ -49,7 +49,7 @@ func create() *cobra.Command {
|
||||
workspaceName, err = cliui.Prompt(cmd, cliui.PromptOptions{
|
||||
Text: "Specify a name for your workspace:",
|
||||
Validate: func(workspaceName string) error {
|
||||
_, err = client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, workspaceName)
|
||||
_, err = client.WorkspaceByOwnerAndName(cmd.Context(), codersdk.Me, workspaceName)
|
||||
if err == nil {
|
||||
return xerrors.Errorf("A workspace already exists named %q!", workspaceName)
|
||||
}
|
||||
@@ -75,7 +75,7 @@ func create() *cobra.Command {
|
||||
return xerrors.Errorf("TTL must be at least 1 minute")
|
||||
}
|
||||
|
||||
_, err = client.WorkspaceByOwnerAndName(cmd.Context(), organization.ID, codersdk.Me, workspaceName)
|
||||
_, err = client.WorkspaceByOwnerAndName(cmd.Context(), codersdk.Me, workspaceName)
|
||||
if err == nil {
|
||||
return xerrors.Errorf("A workspace already exists named %q!", workspaceName)
|
||||
}
|
||||
|
||||
+1
-5
@@ -30,11 +30,7 @@ func delete() *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
organization, err := currentOrganization(cmd, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
|
||||
workspace, err := namedWorkspace(cmd, client, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+1
-5
@@ -70,12 +70,8 @@ func portForward() *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
organization, err := currentOrganization(cmd, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
workspace, agent, err := getWorkspaceAndAgent(cmd, client, organization.ID, codersdk.Me, args[0], false)
|
||||
workspace, agent, err := getWorkspaceAndAgent(cmd, client, codersdk.Me, args[0], false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+2
-3
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/kirsle/configdir"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -180,7 +179,7 @@ func currentOrganization(cmd *cobra.Command, client *codersdk.Client) (codersdk.
|
||||
// namedWorkspace fetches and returns a workspace by an identifier, which may be either
|
||||
// a bare name (for a workspace owned by the current user) or a "user/workspace" combination,
|
||||
// where user is either a username or UUID.
|
||||
func namedWorkspace(cmd *cobra.Command, client *codersdk.Client, orgID uuid.UUID, identifier string) (codersdk.Workspace, error) {
|
||||
func namedWorkspace(cmd *cobra.Command, client *codersdk.Client, identifier string) (codersdk.Workspace, error) {
|
||||
parts := strings.Split(identifier, "/")
|
||||
|
||||
var owner, name string
|
||||
@@ -195,7 +194,7 @@ func namedWorkspace(cmd *cobra.Command, client *codersdk.Client, orgID uuid.UUID
|
||||
return codersdk.Workspace{}, xerrors.Errorf("invalid workspace name: %q", identifier)
|
||||
}
|
||||
|
||||
return client.WorkspaceByOwnerAndName(cmd.Context(), orgID, owner, name)
|
||||
return client.WorkspaceByOwnerAndName(cmd.Context(), owner, name)
|
||||
}
|
||||
|
||||
// createConfig consumes the global configuration flag to produce a config root.
|
||||
|
||||
+3
-5
@@ -412,11 +412,9 @@ func server() *cobra.Command {
|
||||
"Interrupt caught, gracefully exiting. Use ctrl+\\ to force quit"))
|
||||
|
||||
if dev {
|
||||
organizations, err := client.OrganizationsByUser(cmd.Context(), codersdk.Me)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get organizations: %w", err)
|
||||
}
|
||||
workspaces, err := client.WorkspacesByOwner(cmd.Context(), organizations[0].ID, codersdk.Me)
|
||||
workspaces, err := client.Workspaces(cmd.Context(), codersdk.WorkspaceFilter{
|
||||
Owner: codersdk.Me,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get workspaces: %w", err)
|
||||
}
|
||||
|
||||
+1
-5
@@ -18,11 +18,7 @@ func show() *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
organization, err := currentOrganization(cmd, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
|
||||
workspace, err := namedWorkspace(cmd, client, args[0])
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get workspace: %w", err)
|
||||
}
|
||||
|
||||
+6
-8
@@ -48,10 +48,6 @@ func ssh() *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
organization, err := currentOrganization(cmd, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if shuffle {
|
||||
err := cobra.ExactArgs(0)(cmd, args)
|
||||
@@ -65,7 +61,7 @@ func ssh() *cobra.Command {
|
||||
}
|
||||
}
|
||||
|
||||
workspace, agent, err := getWorkspaceAndAgent(cmd, client, organization.ID, codersdk.Me, args[0], shuffle)
|
||||
workspace, agent, err := getWorkspaceAndAgent(cmd, client, codersdk.Me, args[0], shuffle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -185,7 +181,7 @@ func ssh() *cobra.Command {
|
||||
// getWorkspaceAgent returns the workspace and agent selected using either the
|
||||
// `<workspace>[.<agent>]` syntax via `in` or picks a random workspace and agent
|
||||
// if `shuffle` is true.
|
||||
func getWorkspaceAndAgent(cmd *cobra.Command, client *codersdk.Client, orgID uuid.UUID, userID string, in string, shuffle bool) (codersdk.Workspace, codersdk.WorkspaceAgent, error) { //nolint:revive
|
||||
func getWorkspaceAndAgent(cmd *cobra.Command, client *codersdk.Client, userID string, in string, shuffle bool) (codersdk.Workspace, codersdk.WorkspaceAgent, error) { //nolint:revive
|
||||
ctx := cmd.Context()
|
||||
|
||||
var (
|
||||
@@ -194,7 +190,9 @@ func getWorkspaceAndAgent(cmd *cobra.Command, client *codersdk.Client, orgID uui
|
||||
err error
|
||||
)
|
||||
if shuffle {
|
||||
workspaces, err := client.WorkspacesByOwner(cmd.Context(), orgID, userID)
|
||||
workspaces, err := client.Workspaces(cmd.Context(), codersdk.WorkspaceFilter{
|
||||
Owner: codersdk.Me,
|
||||
})
|
||||
if err != nil {
|
||||
return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, err
|
||||
}
|
||||
@@ -207,7 +205,7 @@ func getWorkspaceAndAgent(cmd *cobra.Command, client *codersdk.Client, orgID uui
|
||||
return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, err
|
||||
}
|
||||
} else {
|
||||
workspace, err = namedWorkspace(cmd, client, orgID, workspaceParts[0])
|
||||
workspace, err = namedWorkspace(cmd, client, workspaceParts[0])
|
||||
if err != nil {
|
||||
return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, err
|
||||
}
|
||||
|
||||
+1
-5
@@ -28,11 +28,7 @@ func start() *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
organization, err := currentOrganization(cmd, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
|
||||
workspace, err := namedWorkspace(cmd, client, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+2
-12
@@ -30,12 +30,7 @@ func statePull() *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
organization, err := currentOrganization(cmd, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
|
||||
workspace, err := namedWorkspace(cmd, client, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -76,12 +71,7 @@ func statePush() *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
organization, err := currentOrganization(cmd, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
|
||||
workspace, err := namedWorkspace(cmd, client, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+1
-5
@@ -28,11 +28,7 @@ func stop() *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
organization, err := currentOrganization(cmd, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
|
||||
workspace, err := namedWorkspace(cmd, client, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+3
-15
@@ -39,12 +39,8 @@ func ttlShow() *cobra.Command {
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create client: %w", err)
|
||||
}
|
||||
organization, err := currentOrganization(cmd, client)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get current org: %w", err)
|
||||
}
|
||||
|
||||
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
|
||||
workspace, err := namedWorkspace(cmd, client, args[0])
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get workspace: %w", err)
|
||||
}
|
||||
@@ -72,12 +68,8 @@ func ttlset() *cobra.Command {
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create client: %w", err)
|
||||
}
|
||||
organization, err := currentOrganization(cmd, client)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get current org: %w", err)
|
||||
}
|
||||
|
||||
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
|
||||
workspace, err := namedWorkspace(cmd, client, args[0])
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get workspace: %w", err)
|
||||
}
|
||||
@@ -120,12 +112,8 @@ func ttlunset() *cobra.Command {
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create client: %w", err)
|
||||
}
|
||||
organization, err := currentOrganization(cmd, client)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get current org: %w", err)
|
||||
}
|
||||
|
||||
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
|
||||
workspace, err := namedWorkspace(cmd, client, args[0])
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get workspace: %w", err)
|
||||
}
|
||||
|
||||
+1
-5
@@ -19,11 +19,7 @@ func update() *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
organization, err := currentOrganization(cmd, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
workspace, err := namedWorkspace(cmd, client, organization.ID, args[0])
|
||||
workspace, err := namedWorkspace(cmd, client, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+2
-9
@@ -152,15 +152,7 @@ func New(options *Options) *API {
|
||||
r.Get("/", api.templatesByOrganization)
|
||||
r.Get("/{templatename}", api.templateByOrganizationAndName)
|
||||
})
|
||||
r.Route("/workspaces", func(r chi.Router) {
|
||||
r.Post("/", api.postWorkspacesByOrganization)
|
||||
r.Get("/", api.workspacesByOrganization)
|
||||
r.Route("/{user}", func(r chi.Router) {
|
||||
r.Use(httpmw.ExtractUserParam(options.Database))
|
||||
r.Get("/{workspacename}", api.workspaceByOwnerAndName)
|
||||
r.Get("/", api.workspacesByOwner)
|
||||
})
|
||||
})
|
||||
r.Post("/workspaces", api.postWorkspacesByOrganization)
|
||||
r.Route("/members", func(r chi.Router) {
|
||||
r.Get("/roles", api.assignableOrgRoles)
|
||||
r.Route("/{user}", func(r chi.Router) {
|
||||
@@ -259,6 +251,7 @@ func New(options *Options) *API {
|
||||
r.Get("/", api.organizationsByUser)
|
||||
r.Get("/{organizationname}", api.organizationByUserAndName)
|
||||
})
|
||||
r.Get("/workspace/{workspacename}", api.workspaceByOwnerAndName)
|
||||
r.Get("/gitsshkey", api.gitSSHKey)
|
||||
r.Put("/gitsshkey", api.regenerateGitSSHKey)
|
||||
})
|
||||
|
||||
+5
-11
@@ -147,22 +147,16 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
|
||||
"GET:/api/v2/workspaceagents/{workspaceagent}/turn": {NoAuthorize: true},
|
||||
|
||||
// These endpoints have more assertions. This is good, add more endpoints to assert if you can!
|
||||
"GET:/api/v2/organizations/{organization}": {AssertObject: rbac.ResourceOrganization.InOrg(admin.OrganizationID)},
|
||||
"GET:/api/v2/users/{user}/organizations": {StatusCode: http.StatusOK, AssertObject: rbac.ResourceOrganization},
|
||||
"GET:/api/v2/users/{user}/workspaces": {StatusCode: http.StatusOK, AssertObject: rbac.ResourceWorkspace},
|
||||
"GET:/api/v2/organizations/{organization}/workspaces/{user}": {StatusCode: http.StatusOK, AssertObject: rbac.ResourceWorkspace},
|
||||
"GET:/api/v2/organizations/{organization}/workspaces/{user}/{workspace}": {
|
||||
AssertObject: rbac.ResourceWorkspace.InOrg(organization.ID).WithID(workspace.ID.String()).WithOwner(workspace.OwnerID.String()),
|
||||
"GET:/api/v2/organizations/{organization}": {AssertObject: rbac.ResourceOrganization.InOrg(admin.OrganizationID)},
|
||||
"GET:/api/v2/users/{user}/organizations": {StatusCode: http.StatusOK, AssertObject: rbac.ResourceOrganization},
|
||||
"GET:/api/v2/users/{user}/workspace/{workspacename}": {
|
||||
AssertObject: rbac.ResourceWorkspace,
|
||||
AssertAction: rbac.ActionRead,
|
||||
},
|
||||
"GET:/api/v2/workspaces/{workspace}/builds/{workspacebuildname}": {
|
||||
AssertAction: rbac.ActionRead,
|
||||
AssertObject: workspaceRBACObj,
|
||||
},
|
||||
"GET:/api/v2/organizations/{organization}/workspaces/{user}/{workspacename}": {
|
||||
AssertAction: rbac.ActionRead,
|
||||
AssertObject: workspaceRBACObj,
|
||||
},
|
||||
"GET:/api/v2/organizations/{organization}/workspaces": {StatusCode: http.StatusOK, AssertObject: rbac.ResourceWorkspace},
|
||||
"GET:/api/v2/workspacebuilds/{workspacebuild}": {
|
||||
AssertAction: rbac.ActionRead,
|
||||
AssertObject: workspaceRBACObj,
|
||||
|
||||
@@ -100,35 +100,6 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) {
|
||||
httpapi.Write(rw, http.StatusOK, convertWorkspace(workspace, build, job, template, owner))
|
||||
}
|
||||
|
||||
func (api *API) workspacesByOrganization(rw http.ResponseWriter, r *http.Request) {
|
||||
organization := httpmw.OrganizationParam(r)
|
||||
workspaces, err := api.Database.GetWorkspacesWithFilter(r.Context(), database.GetWorkspacesWithFilterParams{
|
||||
OrganizationID: organization.ID,
|
||||
Deleted: false,
|
||||
})
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("get workspaces: %s", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Rbac filter
|
||||
workspaces = AuthorizeFilter(api, r, rbac.ActionRead, workspaces)
|
||||
|
||||
apiWorkspaces, err := convertWorkspaces(r.Context(), api.Database, workspaces)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("convert workspaces: %s", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
httpapi.Write(rw, http.StatusOK, apiWorkspaces)
|
||||
}
|
||||
|
||||
// workspaces returns all workspaces a user can read.
|
||||
// Optional filters with query params
|
||||
func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) {
|
||||
@@ -189,38 +160,8 @@ func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) {
|
||||
httpapi.Write(rw, http.StatusOK, apiWorkspaces)
|
||||
}
|
||||
|
||||
func (api *API) workspacesByOwner(rw http.ResponseWriter, r *http.Request) {
|
||||
owner := httpmw.UserParam(r)
|
||||
workspaces, err := api.Database.GetWorkspacesWithFilter(r.Context(), database.GetWorkspacesWithFilterParams{
|
||||
OwnerID: owner.ID,
|
||||
Deleted: false,
|
||||
})
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("get workspaces: %s", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Only return workspaces the user can read
|
||||
workspaces = AuthorizeFilter(api, r, rbac.ActionRead, workspaces)
|
||||
|
||||
apiWorkspaces, err := convertWorkspaces(r.Context(), api.Database, workspaces)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("convert workspaces: %s", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
httpapi.Write(rw, http.StatusOK, apiWorkspaces)
|
||||
}
|
||||
|
||||
func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request) {
|
||||
owner := httpmw.UserParam(r)
|
||||
organization := httpmw.OrganizationParam(r)
|
||||
workspaceName := chi.URLParam(r, "workspacename")
|
||||
|
||||
workspace, err := api.Database.GetWorkspaceByOwnerIDAndName(r.Context(), database.GetWorkspaceByOwnerIDAndNameParams{
|
||||
@@ -238,14 +179,6 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if workspace.OrganizationID != organization.ID {
|
||||
httpapi.Write(rw, http.StatusUnauthorized, httpapi.Response{
|
||||
Message: fmt.Sprintf("workspace is not owned by organization %q", organization.Name),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if !api.Authorize(rw, r, rbac.ActionRead, workspace) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -210,107 +210,15 @@ func TestPostWorkspacesByOrganization(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkspacesByOrganization(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("ListEmpty", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
_, err := client.WorkspacesByOrganization(context.Background(), user.OrganizationID)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("List", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
ws := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, ws.LatestBuild.ID)
|
||||
workspaces, err := client.WorkspacesByOrganization(context.Background(), user.OrganizationID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, workspaces, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkspacesByOwner(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("ListEmpty", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
_, err := client.WorkspacesByOwner(context.Background(), user.OrganizationID, codersdk.Me)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("ListMine", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
me, err := client.User(context.Background(), codersdk.Me)
|
||||
require.NoError(t, err)
|
||||
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
_ = coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||
|
||||
// Create noise workspace that should be filtered out
|
||||
other := coderdtest.CreateAnotherUser(t, client, user.OrganizationID)
|
||||
_ = coderdtest.CreateWorkspace(t, other, user.OrganizationID, template.ID)
|
||||
|
||||
// Use a username
|
||||
workspaces, err := client.Workspaces(context.Background(), codersdk.WorkspaceFilter{
|
||||
OrganizationID: user.OrganizationID,
|
||||
Owner: me.Username,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, workspaces, 1)
|
||||
})
|
||||
|
||||
t.Run("ListName", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
w := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||
|
||||
// Create noise workspace that should be filtered out
|
||||
_ = coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||
|
||||
// Use name filter
|
||||
workspaces, err := client.Workspaces(context.Background(), codersdk.WorkspaceFilter{
|
||||
Name: w.Name,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, workspaces, 1)
|
||||
|
||||
// Create same name workspace that should be included
|
||||
other := coderdtest.CreateAnotherUser(t, client, user.OrganizationID)
|
||||
_ = coderdtest.CreateWorkspace(t, other, user.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) { cwr.Name = w.Name })
|
||||
|
||||
workspaces, err = client.Workspaces(context.Background(), codersdk.WorkspaceFilter{
|
||||
Name: w.Name,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, workspaces, 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkspaceByOwnerAndName(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
_, err := client.WorkspaceByOwnerAndName(context.Background(), user.OrganizationID, codersdk.Me, "something")
|
||||
_, err := client.WorkspaceByOwnerAndName(context.Background(), codersdk.Me, "something")
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, http.StatusForbidden, apiErr.StatusCode())
|
||||
require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode())
|
||||
})
|
||||
t.Run("Get", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
@@ -320,7 +228,7 @@ func TestWorkspaceByOwnerAndName(t *testing.T) {
|
||||
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||
_, err := client.WorkspaceByOwnerAndName(context.Background(), user.OrganizationID, codersdk.Me, workspace.Name)
|
||||
_, err := client.WorkspaceByOwnerAndName(context.Background(), codersdk.Me, workspace.Name)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
@@ -440,7 +348,9 @@ func TestPostWorkspaceBuild(t *testing.T) {
|
||||
require.Equal(t, workspace.LatestBuild.BuildNumber+1, build.BuildNumber)
|
||||
coderdtest.AwaitWorkspaceBuildJob(t, client, build.ID)
|
||||
|
||||
workspaces, err := client.WorkspacesByOwner(context.Background(), user.OrganizationID, user.UserID.String())
|
||||
workspaces, err := client.Workspaces(context.Background(), codersdk.WorkspaceFilter{
|
||||
Owner: user.UserID.String(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, workspaces, 0)
|
||||
})
|
||||
|
||||
@@ -200,51 +200,3 @@ func (c *Client) CreateWorkspace(ctx context.Context, organizationID uuid.UUID,
|
||||
var workspace Workspace
|
||||
return workspace, json.NewDecoder(res.Body).Decode(&workspace)
|
||||
}
|
||||
|
||||
// WorkspacesByOrganization returns all workspaces in the specified organization.
|
||||
func (c *Client) WorkspacesByOrganization(ctx context.Context, organizationID uuid.UUID) ([]Workspace, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/workspaces", organizationID), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, readBodyAsError(res)
|
||||
}
|
||||
|
||||
var workspaces []Workspace
|
||||
return workspaces, json.NewDecoder(res.Body).Decode(&workspaces)
|
||||
}
|
||||
|
||||
// WorkspacesByOwner returns all workspaces contained in the organization owned by the user.
|
||||
func (c *Client) WorkspacesByOwner(ctx context.Context, organizationID uuid.UUID, user string) ([]Workspace, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/workspaces/%s", organizationID, user), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, readBodyAsError(res)
|
||||
}
|
||||
|
||||
var workspaces []Workspace
|
||||
return workspaces, json.NewDecoder(res.Body).Decode(&workspaces)
|
||||
}
|
||||
|
||||
// WorkspaceByOwnerAndName returns a workspace by the owner's UUID and the workspace's name.
|
||||
func (c *Client) WorkspaceByOwnerAndName(ctx context.Context, organization uuid.UUID, owner string, name string) (Workspace, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/workspaces/%s/%s", organization, owner, name), nil)
|
||||
if err != nil {
|
||||
return Workspace{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return Workspace{}, readBodyAsError(res)
|
||||
}
|
||||
|
||||
var workspace Workspace
|
||||
return workspace, json.NewDecoder(res.Body).Decode(&workspace)
|
||||
}
|
||||
|
||||
@@ -238,3 +238,19 @@ func (c *Client) Workspaces(ctx context.Context, filter WorkspaceFilter) ([]Work
|
||||
var workspaces []Workspace
|
||||
return workspaces, json.NewDecoder(res.Body).Decode(&workspaces)
|
||||
}
|
||||
|
||||
// WorkspaceByOwnerAndName returns a workspace by the owner's UUID and the workspace's name.
|
||||
func (c *Client) WorkspaceByOwnerAndName(ctx context.Context, owner string, name string) (Workspace, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/workspace/%s", owner, name), nil)
|
||||
if err != nil {
|
||||
return Workspace{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return Workspace{}, readBodyAsError(res)
|
||||
}
|
||||
|
||||
var workspace Workspace
|
||||
return workspace, json.NewDecoder(res.Body).Decode(&workspace)
|
||||
}
|
||||
|
||||
+1
-4
@@ -139,13 +139,10 @@ export const getWorkspaces = async (filter?: TypesGen.WorkspaceFilter): Promise<
|
||||
}
|
||||
|
||||
export const getWorkspaceByOwnerAndName = async (
|
||||
organizationID: string,
|
||||
username = "me",
|
||||
workspaceName: string,
|
||||
): Promise<TypesGen.Workspace> => {
|
||||
const response = await axios.get<TypesGen.Workspace>(
|
||||
`/api/v2/organizations/${organizationID}/workspaces/${username}/${workspaceName}`,
|
||||
)
|
||||
const response = await axios.get<TypesGen.Workspace>(`/api/v2/users/${username}/workspace/${workspaceName}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
|
||||
@@ -54,25 +54,10 @@ describe("TerminalPage", () => {
|
||||
history.push(`/some-user/${MockWorkspace.name}/terminal`)
|
||||
})
|
||||
|
||||
it("shows an error if fetching organizations fails", async () => {
|
||||
// Given
|
||||
server.use(
|
||||
rest.get("/api/v2/users/me/organizations", async (req, res, ctx) => {
|
||||
return res(ctx.status(500), ctx.json({ message: "nope" }))
|
||||
}),
|
||||
)
|
||||
|
||||
// When
|
||||
const { container } = renderTerminal()
|
||||
|
||||
// Then
|
||||
await expectTerminalText(container, Language.organizationsErrorMessagePrefix)
|
||||
})
|
||||
|
||||
it("shows an error if fetching workspace fails", async () => {
|
||||
// Given
|
||||
server.use(
|
||||
rest.get("/api/v2/organizations/:organizationId/workspaces/:userName/:workspaceName", (req, res, ctx) => {
|
||||
rest.get("/api/v2/users/:userId/workspace/:workspaceName", (req, res, ctx) => {
|
||||
return res(ctx.status(500), ctx.json({ id: "workspace-id" }))
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -11,7 +11,6 @@ import { MONOSPACE_FONT_FAMILY } from "../../theme/constants"
|
||||
import { terminalMachine } from "../../xServices/terminal/terminalXService"
|
||||
|
||||
export const Language = {
|
||||
organizationsErrorMessagePrefix: "Unable to fetch organizations: ",
|
||||
workspaceErrorMessagePrefix: "Unable to fetch workspace: ",
|
||||
workspaceAgentErrorMessagePrefix: "Unable to fetch workspace agent: ",
|
||||
websocketErrorMessagePrefix: "WebSocket failed: ",
|
||||
@@ -58,8 +57,7 @@ const TerminalPage: FC<{
|
||||
})
|
||||
const isConnected = terminalState.matches("connected")
|
||||
const isDisconnected = terminalState.matches("disconnected")
|
||||
const { organizationsError, workspaceError, workspaceAgentError, workspaceAgent, websocketError } =
|
||||
terminalState.context
|
||||
const { workspaceError, workspaceAgentError, workspaceAgent, websocketError } = terminalState.context
|
||||
|
||||
// Create the terminal!
|
||||
useEffect(() => {
|
||||
@@ -145,9 +143,6 @@ const TerminalPage: FC<{
|
||||
terminal.options = {
|
||||
disableStdin: true,
|
||||
}
|
||||
if (organizationsError instanceof Error) {
|
||||
terminal.writeln(Language.organizationsErrorMessagePrefix + organizationsError.message)
|
||||
}
|
||||
if (workspaceError instanceof Error) {
|
||||
terminal.writeln(Language.workspaceErrorMessagePrefix + workspaceError.message)
|
||||
}
|
||||
@@ -180,17 +175,7 @@ const TerminalPage: FC<{
|
||||
width: terminal.cols,
|
||||
},
|
||||
})
|
||||
}, [
|
||||
workspaceError,
|
||||
organizationsError,
|
||||
workspaceAgentError,
|
||||
websocketError,
|
||||
workspaceAgent,
|
||||
terminal,
|
||||
fitAddon,
|
||||
isConnected,
|
||||
sendEvent,
|
||||
])
|
||||
}, [workspaceError, workspaceAgentError, websocketError, workspaceAgent, terminal, fitAddon, isConnected, sendEvent])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -42,9 +42,6 @@ export const handlers = [
|
||||
rest.post("/api/v2/users", async (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json(M.MockUser))
|
||||
}),
|
||||
rest.post("/api/v2/users/me/workspaces", async (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json(M.MockWorkspace))
|
||||
}),
|
||||
rest.get("/api/v2/users/me/organizations", (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json([M.MockOrganization]))
|
||||
}),
|
||||
@@ -83,23 +80,14 @@ export const handlers = [
|
||||
rest.get("/api/v2/users/:userId/gitsshkey", async (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json(M.MockGitSSHKey))
|
||||
}),
|
||||
rest.get("/api/v2/users/:userId/workspace/:workspaceName", async (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json(M.MockWorkspace))
|
||||
}),
|
||||
|
||||
// workspaces
|
||||
rest.get("/api/v2/workspaces", async (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json([M.MockWorkspace]))
|
||||
}),
|
||||
rest.get("/api/v2/organizations/:organizationId/workspaces/:userName/:workspaceName", (req, res, ctx) => {
|
||||
if (req.params.workspaceName !== M.MockWorkspace.name) {
|
||||
return res(
|
||||
ctx.status(404),
|
||||
ctx.json({
|
||||
message: "workspace not found",
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
return res(ctx.status(200), ctx.json(M.MockWorkspace))
|
||||
}
|
||||
}),
|
||||
rest.get("/api/v2/workspaces/:workspaceId", async (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json(M.MockWorkspace))
|
||||
}),
|
||||
|
||||
@@ -4,8 +4,6 @@ import * as Types from "../../api/types"
|
||||
import * as TypesGen from "../../api/typesGenerated"
|
||||
|
||||
export interface TerminalContext {
|
||||
organizationsError?: Error | unknown
|
||||
organizations?: TypesGen.Organization[]
|
||||
workspaceError?: Error | unknown
|
||||
workspace?: TypesGen.Workspace
|
||||
workspaceAgent?: TypesGen.WorkspaceAgent
|
||||
@@ -37,9 +35,6 @@ export const terminalMachine =
|
||||
context: {} as TerminalContext,
|
||||
events: {} as TerminalEvent,
|
||||
services: {} as {
|
||||
getOrganizations: {
|
||||
data: TypesGen.Organization[]
|
||||
}
|
||||
getWorkspace: {
|
||||
data: TypesGen.Workspace
|
||||
}
|
||||
@@ -52,27 +47,8 @@ export const terminalMachine =
|
||||
},
|
||||
},
|
||||
id: "terminalState",
|
||||
initial: "gettingOrganizations",
|
||||
initial: "gettingWorkspace",
|
||||
states: {
|
||||
gettingOrganizations: {
|
||||
invoke: {
|
||||
src: "getOrganizations",
|
||||
id: "getOrganizations",
|
||||
onDone: [
|
||||
{
|
||||
actions: ["assignOrganizations", "clearOrganizationsError"],
|
||||
target: "gettingWorkspace",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: "assignOrganizationsError",
|
||||
target: "disconnected",
|
||||
},
|
||||
],
|
||||
},
|
||||
tags: "loading",
|
||||
},
|
||||
gettingWorkspace: {
|
||||
invoke: {
|
||||
src: "getWorkspace",
|
||||
@@ -145,7 +121,7 @@ export const terminalMachine =
|
||||
on: {
|
||||
CONNECT: {
|
||||
actions: "assignConnection",
|
||||
target: "gettingOrganizations",
|
||||
target: "gettingWorkspace",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -153,12 +129,11 @@ export const terminalMachine =
|
||||
},
|
||||
{
|
||||
services: {
|
||||
getOrganizations: API.getOrganizations,
|
||||
getWorkspace: async (context) => {
|
||||
if (!context.organizations || !context.workspaceName) {
|
||||
throw new Error("organizations or workspace name not set")
|
||||
if (!context.workspaceName) {
|
||||
throw new Error("workspace name not set")
|
||||
}
|
||||
return API.getWorkspaceByOwnerAndName(context.organizations[0].id, context.username, context.workspaceName)
|
||||
return API.getWorkspaceByOwnerAndName(context.username, context.workspaceName)
|
||||
},
|
||||
getWorkspaceAgent: async (context) => {
|
||||
if (!context.workspace || !context.workspaceName) {
|
||||
@@ -220,16 +195,6 @@ export const terminalMachine =
|
||||
reconnection: event.reconnection ?? context.reconnection,
|
||||
workspaceName: event.workspaceName ?? context.workspaceName,
|
||||
})),
|
||||
assignOrganizations: assign({
|
||||
organizations: (_, event) => event.data,
|
||||
}),
|
||||
assignOrganizationsError: assign({
|
||||
organizationsError: (_, event) => event.data,
|
||||
}),
|
||||
clearOrganizationsError: assign((context) => ({
|
||||
...context,
|
||||
organizationsError: undefined,
|
||||
})),
|
||||
assignWorkspace: assign({
|
||||
workspace: (_, event) => event.data,
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user