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

Skip to content

Commit 4eb0bb6

Browse files
authored
feat: don't return 200 for deleted workspaces (#1556)
1 parent eb8f371 commit 4eb0bb6

File tree

5 files changed

+109
-1
lines changed

5 files changed

+109
-1
lines changed

coderd/autobuild/executor/lifecycle_executor_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"os"
7+
"strings"
78
"testing"
89
"time"
910

@@ -470,6 +471,9 @@ func mustTransitionWorkspace(t *testing.T, client *codersdk.Client, workspaceID
470471
func mustWorkspace(t *testing.T, client *codersdk.Client, workspaceID uuid.UUID) codersdk.Workspace {
471472
ctx := context.Background()
472473
ws, err := client.Workspace(ctx, workspaceID)
474+
if err != nil && strings.Contains(err.Error(), "status code 410") {
475+
ws, err = client.DeletedWorkspace(ctx, workspaceID)
476+
}
473477
require.NoError(t, err, "no workspace found with id %s", workspaceID)
474478
return ws
475479
}

coderd/workspaces.go

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

1213
"github.com/go-chi/chi/v5"
@@ -34,6 +35,40 @@ func (api *api) workspace(rw http.ResponseWriter, r *http.Request) {
3435
return
3536
}
3637

38+
if !api.Authorize(rw, r, rbac.ActionRead,
39+
rbac.ResourceWorkspace.InOrg(workspace.OrganizationID).WithOwner(workspace.OwnerID.String()).WithID(workspace.ID.String())) {
40+
return
41+
}
42+
43+
// The `deleted` query parameter (which defaults to `false`) MUST match the
44+
// `Deleted` field on the workspace otherwise you will get a 410 Gone.
45+
var (
46+
deletedStr = r.URL.Query().Get("deleted")
47+
showDeleted = false
48+
)
49+
if deletedStr != "" {
50+
var err error
51+
showDeleted, err = strconv.ParseBool(deletedStr)
52+
if err != nil {
53+
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
54+
Message: fmt.Sprintf("invalid bool for 'deleted' query param: %s", err),
55+
})
56+
return
57+
}
58+
}
59+
if workspace.Deleted && !showDeleted {
60+
httpapi.Write(rw, http.StatusGone, httpapi.Response{
61+
Message: fmt.Sprintf("workspace %q was deleted, you can view this workspace by specifying '?deleted=true' and trying again", workspace.ID.String()),
62+
})
63+
return
64+
}
65+
if !workspace.Deleted && showDeleted {
66+
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
67+
Message: fmt.Sprintf("workspace %q is not deleted, please remove '?deleted=true' and try again", workspace.ID.String()),
68+
})
69+
return
70+
}
71+
3772
build, err := api.Database.GetLatestWorkspaceBuildByWorkspaceID(r.Context(), workspace.ID)
3873
if err != nil {
3974
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{

coderd/workspaces_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,58 @@ import (
2020
"github.com/coder/coder/provisionersdk/proto"
2121
)
2222

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

codersdk/client.go

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

3737
type requestOption func(*http.Request)
3838

39+
func queryParam(k, v string) requestOption {
40+
return func(r *http.Request) {
41+
q := r.URL.Query()
42+
q.Set(k, v)
43+
r.URL.RawQuery = q.Encode()
44+
}
45+
}
46+
3947
// Request performs an HTTP request with the body provided.
4048
// The caller is responsible for closing the response body.
4149
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
@@ -42,7 +42,16 @@ type CreateWorkspaceBuildRequest struct {
4242

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

0 commit comments

Comments
 (0)