fix: Sort workspace by name by created_at (#2214)

* fix: Sort workspace by name by created_at

Fix bug where deleting workspaces with the same name returns the
oldest deleted workspace
This commit is contained in:
Steven Masley
2022-06-10 09:58:42 -05:00
committed by GitHub
parent 953e8c8fe6
commit 6bee180bb3
10 changed files with 60 additions and 29 deletions
+10 -1
View File
@@ -375,7 +375,9 @@ func (q *fakeQuerier) GetWorkspaceByOwnerIDAndName(_ context.Context, arg databa
q.mutex.RLock()
defer q.mutex.RUnlock()
var found *database.Workspace
for _, workspace := range q.workspaces {
workspace := workspace
if workspace.OwnerID != arg.OwnerID {
continue
}
@@ -385,7 +387,14 @@ func (q *fakeQuerier) GetWorkspaceByOwnerIDAndName(_ context.Context, arg databa
if workspace.Deleted != arg.Deleted {
continue
}
return workspace, nil
// Return the most recent workspace with the given name
if found == nil || workspace.CreatedAt.After(found.CreatedAt) {
found = &workspace
}
}
if found != nil {
return *found, nil
}
return database.Workspace{}, sql.ErrNoRows
}
+1
View File
@@ -3480,6 +3480,7 @@ WHERE
owner_id = $1
AND deleted = $2
AND LOWER("name") = LOWER($3)
ORDER BY created_at DESC
`
type GetWorkspaceByOwnerIDAndNameParams struct {
+2 -1
View File
@@ -70,7 +70,8 @@ FROM
WHERE
owner_id = @owner_id
AND deleted = @deleted
AND LOWER("name") = LOWER(@name);
AND LOWER("name") = LOWER(@name)
ORDER BY created_at DESC;
-- name: GetWorkspaceOwnerCountsByTemplateIDs :many
SELECT
+2 -4
View File
@@ -35,10 +35,8 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) {
return
}
// The `deleted` query parameter (which defaults to `false`) MUST match the
// `Deleted` field on the workspace otherwise you will get a 410 Gone.
var (
deletedStr = r.URL.Query().Get("deleted")
deletedStr = r.URL.Query().Get("include_deleted")
showDeleted = false
)
if deletedStr != "" {
@@ -46,7 +44,7 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) {
showDeleted, err = strconv.ParseBool(deletedStr)
if err != nil {
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
Message: fmt.Sprintf("Invalid boolean value %q for \"deleted\" query param.", deletedStr),
Message: fmt.Sprintf("Invalid boolean value %q for \"include_deleted\" query param.", deletedStr),
Validations: []httpapi.Error{
{Field: "deleted", Detail: "Must be a valid boolean"},
},
+35 -4
View File
@@ -258,7 +258,7 @@ func TestWorkspaceByOwnerAndName(t *testing.T) {
t.Run("NotFound", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
_, err := client.WorkspaceByOwnerAndName(context.Background(), codersdk.Me, "something", codersdk.WorkspaceByOwnerAndNameParams{})
_, err := client.WorkspaceByOwnerAndName(context.Background(), codersdk.Me, "something", codersdk.WorkspaceOptions{})
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode())
@@ -271,7 +271,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(), codersdk.Me, workspace.Name, codersdk.WorkspaceByOwnerAndNameParams{})
_, err := client.WorkspaceByOwnerAndName(context.Background(), codersdk.Me, workspace.Name, codersdk.WorkspaceOptions{})
require.NoError(t, err)
})
t.Run("Deleted", func(t *testing.T) {
@@ -294,12 +294,43 @@ func TestWorkspaceByOwnerAndName(t *testing.T) {
// Then:
// When we call without includes_deleted, we don't expect to get the workspace back
_, err = client.WorkspaceByOwnerAndName(context.Background(), workspace.OwnerName, workspace.Name, codersdk.WorkspaceByOwnerAndNameParams{})
_, err = client.WorkspaceByOwnerAndName(context.Background(), workspace.OwnerName, workspace.Name, codersdk.WorkspaceOptions{})
require.ErrorContains(t, err, "403")
// Then:
// When we call with includes_deleted, we should get the workspace back
workspaceNew, err := client.WorkspaceByOwnerAndName(context.Background(), workspace.OwnerName, workspace.Name, codersdk.WorkspaceByOwnerAndNameParams{IncludeDeleted: true})
workspaceNew, err := client.WorkspaceByOwnerAndName(context.Background(), workspace.OwnerName, workspace.Name, codersdk.WorkspaceOptions{IncludeDeleted: true})
require.NoError(t, err)
require.Equal(t, workspace.ID, workspaceNew.ID)
// Given:
// We recreate the workspace with the same name
workspace, err = client.CreateWorkspace(context.Background(), user.OrganizationID, codersdk.CreateWorkspaceRequest{
TemplateID: workspace.TemplateID,
Name: workspace.Name,
AutostartSchedule: workspace.AutostartSchedule,
TTLMillis: workspace.TTLMillis,
})
require.NoError(t, err)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
// Then:
// We can fetch the most recent workspace
workspaceNew, err = client.WorkspaceByOwnerAndName(context.Background(), workspace.OwnerName, workspace.Name, codersdk.WorkspaceOptions{})
require.NoError(t, err)
require.Equal(t, workspace.ID, workspaceNew.ID)
// Given:
// We delete the workspace again
build, err = client.CreateWorkspaceBuild(context.Background(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{
Transition: codersdk.WorkspaceTransitionDelete,
})
require.NoError(t, err, "delete the workspace")
coderdtest.AwaitWorkspaceBuildJob(t, client, build.ID)
// Then:
// When we fetch the deleted workspace, we get the most recently deleted one
workspaceNew, err = client.WorkspaceByOwnerAndName(context.Background(), workspace.OwnerName, workspace.Name, codersdk.WorkspaceOptions{IncludeDeleted: true})
require.NoError(t, err)
require.Equal(t, workspace.ID, workspaceNew.ID)
})