Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f7984cd

Browse files
committed
feat: don't return 200 for deleted workspaces
1 parent 9141be3 commit f7984cd

File tree

4 files changed

+102
-15
lines changed

4 files changed

+102
-15
lines changed

coderd/workspaces.go

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"errors"
88
"fmt"
99
"net/http"
10+
"strconv"
1011

1112
"github.com/go-chi/chi/v5"
1213
"github.com/google/uuid"
@@ -25,6 +26,41 @@ import (
2526
func (api *api) workspace(rw http.ResponseWriter, r *http.Request) {
2627
workspace := httpmw.WorkspaceParam(r)
2728

29+
if !api.Authorize(rw, r, rbac.ActionRead,
30+
rbac.ResourceWorkspace.InOrg(workspace.OrganizationID).WithOwner(workspace.OwnerID.String()).WithID(workspace.ID.String())) {
31+
return
32+
}
33+
34+
// The `deleted` query parameter (which defaults to `false`) MUST match the
35+
// `deleted` field on the workspace otherwise you will get a 410 Gone.
36+
var (
37+
deletedStr = r.URL.Query().Get("deleted")
38+
deleted = false
39+
)
40+
if deletedStr != "" {
41+
var err error
42+
deleted, err = strconv.ParseBool(deletedStr)
43+
if err != nil {
44+
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
45+
Message: fmt.Sprintf("invalid bool for 'deleted' query param: %s", err),
46+
})
47+
return
48+
}
49+
}
50+
if workspace.Deleted != deleted {
51+
if workspace.Deleted {
52+
httpapi.Write(rw, http.StatusGone, httpapi.Response{
53+
Message: fmt.Sprintf("workspace %q was deleted, you can view this workspace by specifying '?deleted=true' and trying again", workspace.ID.String()),
54+
})
55+
return
56+
}
57+
58+
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
59+
Message: fmt.Sprintf("workspace %q is not deleted, please remove '?deleted=true' and try again", workspace.ID.String()),
60+
})
61+
return
62+
}
63+
2864
build, err := api.Database.GetWorkspaceBuildByWorkspaceIDWithoutAfter(r.Context(), workspace.ID)
2965
if err != nil {
3066
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
@@ -58,11 +94,6 @@ func (api *api) workspace(rw http.ResponseWriter, r *http.Request) {
5894
return
5995
}
6096

61-
if !api.Authorize(rw, r, rbac.ActionRead,
62-
rbac.ResourceWorkspace.InOrg(workspace.OrganizationID).WithOwner(workspace.OwnerID.String()).WithID(workspace.ID.String())) {
63-
return
64-
}
65-
6697
httpapi.Write(rw, http.StatusOK,
6798
convertWorkspace(workspace, convertWorkspaceBuild(build, convertProvisionerJob(job)), template, owner))
6899
}

coderd/workspaces_test.go

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,54 @@ import (
2020

2121
func TestWorkspace(t *testing.T) {
2222
t.Parallel()
23-
client := coderdtest.New(t, nil)
24-
user := coderdtest.CreateFirstUser(t, client)
25-
coderdtest.NewProvisionerDaemon(t, client)
26-
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
27-
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
28-
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
29-
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
30-
_, err := client.Workspace(context.Background(), workspace.ID)
31-
require.NoError(t, err)
23+
24+
t.Run("OK", func(t *testing.T) {
25+
t.Parallel()
26+
client := coderdtest.New(t, nil)
27+
user := coderdtest.CreateFirstUser(t, client)
28+
coderdtest.NewProvisionerDaemon(t, client)
29+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
30+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
31+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
32+
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
33+
34+
_, err := client.Workspace(context.Background(), workspace.ID)
35+
require.NoError(t, err)
36+
})
37+
38+
t.Run("Deleted", func(t *testing.T) {
39+
t.Parallel()
40+
client := coderdtest.New(t, nil)
41+
user := coderdtest.CreateFirstUser(t, client)
42+
coderdtest.NewProvisionerDaemon(t, client)
43+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
44+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
45+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
46+
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
47+
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
48+
49+
// Getting with deleted=true should fail.
50+
_, err := client.DeletedWorkspace(context.Background(), workspace.ID)
51+
require.Error(t, err)
52+
require.ErrorContains(t, err, "400") // bad request
53+
54+
// Delete the workspace
55+
build, err := client.CreateWorkspaceBuild(context.Background(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{
56+
Transition: database.WorkspaceTransitionDelete,
57+
})
58+
require.NoError(t, err, "delete the workspace")
59+
coderdtest.AwaitWorkspaceBuildJob(t, client, build.ID)
60+
61+
// Getting with deleted=true should work.
62+
workspaceNew, err := client.DeletedWorkspace(context.Background(), workspace.ID)
63+
require.NoError(t, err)
64+
require.Equal(t, workspace.ID, workspaceNew.ID)
65+
66+
// Getting with deleted=false should not work.
67+
_, err = client.Workspace(context.Background(), workspace.ID)
68+
require.Error(t, err)
69+
require.ErrorContains(t, err, "410") // gone
70+
})
3271
}
3372

3473
func TestPostWorkspacesByOrganization(t *testing.T) {

codersdk/client.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ type Client struct {
3535

3636
type requestOption func(*http.Request)
3737

38+
func queryParam(k, v string) requestOption {
39+
return func(r *http.Request) {
40+
q := r.URL.Query()
41+
q.Set(k, v)
42+
r.URL.RawQuery = q.Encode()
43+
}
44+
}
45+
3846
// Request performs an HTTP request with the body provided.
3947
// The caller is responsible for closing the response body.
4048
func (c *Client) Request(ctx context.Context, method, path string, body interface{}, opts ...requestOption) (*http.Response, error) {

codersdk/workspaces.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,16 @@ type CreateWorkspaceBuildRequest struct {
4040

4141
// Workspace returns a single workspace.
4242
func (c *Client) Workspace(ctx context.Context, id uuid.UUID) (Workspace, error) {
43-
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaces/%s", id), nil)
43+
return c.getWorkspace(ctx, id)
44+
}
45+
46+
// DeletedWorkspace returns a single workspace that was deleted.
47+
func (c *Client) DeletedWorkspace(ctx context.Context, id uuid.UUID) (Workspace, error) {
48+
return c.getWorkspace(ctx, id, queryParam("deleted", "true"))
49+
}
50+
51+
func (c *Client) getWorkspace(ctx context.Context, id uuid.UUID, opts ...requestOption) (Workspace, error) {
52+
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaces/%s", id), nil, opts...)
4453
if err != nil {
4554
return Workspace{}, err
4655
}

0 commit comments

Comments
 (0)