Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

fix: delete related task when deleting workspace (#20567)#20585

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
johnstcn merged 1 commit intorelease/2.28fromcj/2.28/task-deleted-workspace
Nov 3, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 46 additions & 6 deletionscoderd/aitasks_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -478,10 +478,10 @@ func TestTasks(t *testing.T) {
}
})

t.Run("NoWorkspace", func(t *testing.T) {
t.Run("DeletedWorkspace", func(t *testing.T) {
t.Parallel()

client:= coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
client, db:= coderdtest.NewWithDatabase(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
template := createAITemplate(t, client, user)
ctx := testutil.Context(t, testutil.WaitLong)
Expand All@@ -495,14 +495,54 @@ func TestTasks(t *testing.T) {
ws, err := client.Workspace(ctx, task.WorkspaceID.UUID)
require.NoError(t, err)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID)
// Delete the task workspace
coderdtest.MustTransitionWorkspace(t, client, ws.ID, codersdk.WorkspaceTransitionStart, codersdk.WorkspaceTransitionDelete)
// We should still be able to fetch the task after deleting its workspace

// Mark the workspace as deleted directly in the database, bypassing provisionerd.
require.NoError(t, db.UpdateWorkspaceDeletedByID(dbauthz.AsProvisionerd(ctx), database.UpdateWorkspaceDeletedByIDParams{
ID: ws.ID,
Deleted: true,
}))
// We should still be able to fetch the task if its workspace was deleted.
// Provisionerdserver will attempt delete the related task when deleting a workspace.
// This test ensures that we can still handle the case where, for some reason, the
// task has not been marked as deleted, but the workspace has.
task, err = exp.TaskByID(ctx, task.ID)
require.NoError(t, err, "fetching a task should still workafter deletingits related workspace")
require.NoError(t, err, "fetching a task should still workifits related workspace is deleted")
err = exp.DeleteTask(ctx, task.OwnerID.String(), task.ID)
require.NoError(t, err, "should be possible to delete a task with no workspace")
})

t.Run("DeletingTaskWorkspaceDeletesTask", func(t *testing.T) {
t.Parallel()

client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
template := createAITemplate(t, client, user)

ctx := testutil.Context(t, testutil.WaitLong)

exp := codersdk.NewExperimentalClient(client)
task, err := exp.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "delete me",
})
require.NoError(t, err)
require.True(t, task.WorkspaceID.Valid, "task should have a workspace ID")
ws, err := client.Workspace(ctx, task.WorkspaceID.UUID)
require.NoError(t, err)
if assert.True(t, ws.TaskID.Valid, "task id should be set on workspace") {
assert.Equal(t, task.ID, ws.TaskID.UUID, "workspace task id should match")
}
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID)

// When; the task workspace is deleted
coderdtest.MustTransitionWorkspace(t, client, ws.ID, codersdk.WorkspaceTransitionStart, codersdk.WorkspaceTransitionDelete)
// Then: the task associated with the workspace is also deleted
_, err = exp.TaskByID(ctx, task.ID)
require.Error(t, err, "expected an error fetching the task")
var sdkErr *codersdk.Error
require.ErrorAs(t, err, &sdkErr, "expected a codersdk.Error")
require.Equal(t, http.StatusNotFound, sdkErr.StatusCode())
})
})

t.Run("Send", func(t *testing.T) {
Expand Down
4 changes: 2 additions & 2 deletionscoderd/database/dbauthz/dbauthz.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -219,8 +219,8 @@ var (
rbac.ResourceUser.Type: {policy.ActionRead, policy.ActionReadPersonal, policy.ActionUpdatePersonal},
rbac.ResourceWorkspaceDormant.Type: {policy.ActionDelete, policy.ActionRead, policy.ActionUpdate, policy.ActionWorkspaceStop},
rbac.ResourceWorkspace.Type: {policy.ActionDelete, policy.ActionRead, policy.ActionUpdate, policy.ActionWorkspaceStart, policy.ActionWorkspaceStop, policy.ActionCreateAgent},
// Provisionerd needs to readandupdate tasks associated with workspaces.
rbac.ResourceTask.Type: {policy.ActionRead, policy.ActionUpdate},
// Provisionerd needs to read, update,anddelete tasks associated with workspaces.
rbac.ResourceTask.Type: {policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
rbac.ResourceApiKey.Type: {policy.WildcardSymbol},
// When org scoped provisioner credentials are implemented,
// this can be reduced to read a specific org.
Expand Down
8 changes: 8 additions & 0 deletionscoderd/provisionerdserver/provisionerdserver.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2278,6 +2278,14 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro
if err != nil {
return xerrors.Errorf("update workspace deleted: %w", err)
}
if workspace.TaskID.Valid {
if _, err := db.DeleteTask(ctx, database.DeleteTaskParams{
ID: workspace.TaskID.UUID,
DeletedAt: dbtime.Now(),
}); err != nil && !errors.Is(err, sql.ErrNoRows) {
return xerrors.Errorf("delete task related to workspace: %w", err)
}
}

return nil
}, nil)
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
import { MockFailedWorkspace, MockWorkspace } from "testHelpers/entities";
import {
MockFailedWorkspace,
MockTaskWorkspace,
MockWorkspace,
} from "testHelpers/entities";
import type { Meta, StoryObj } from "@storybook/react-vite";
import { daysAgo } from "utils/time";
import { WorkspaceDeleteDialog } from "./WorkspaceDeleteDialog";
Expand DownExpand Up@@ -45,3 +49,9 @@ export const UnhealthyAdminView: Story = {
canDeleteFailedWorkspace: true,
},
};

export const WithTask: Story = {
args: {
workspace: MockTaskWorkspace,
},
};
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,6 +56,8 @@ export const WorkspaceDeleteDialog: FC<WorkspaceDeleteDialogProps> = ({
(workspace.latest_build.status === "failed" ||
workspace.latest_build.status === "canceled");

const hasTask = !!workspace.task_id;

return (
<ConfirmDialog
type="delete"
Expand DownExpand Up@@ -109,8 +111,24 @@ export const WorkspaceDeleteDialog: FC<WorkspaceDeleteDialogProps> = ({
"data-testid": "delete-dialog-name-confirmation",
}}
/>
{hasTask && (
<div css={styles.warnContainer}>
<div css={{ flexDirection: "column" }}>
<p className="info">This workspace is related to a task</p>
<span css={{ fontSize: 12, marginTop: 4, display: "block" }}>
Deleting this workspace will also delete{" "}
<Link
href={`/tasks/${workspace.owner_name}/${workspace.task_id}`}
>
this task
</Link>
.
</span>
</div>
</div>
)}
{canOrphan && (
<div css={styles.orphanContainer}>
<div css={styles.warnContainer}>
<div css={{ flexDirection: "column" }}>
<Checkbox
id="orphan_resources"
Expand DownExpand Up@@ -178,7 +196,7 @@ const styles = {
color: theme.palette.text.primary,
},
}),
orphanContainer: (theme) => ({
warnContainer: (theme) => ({
marginTop: 24,
display: "flex",
backgroundColor: theme.roles.danger.background,
Expand Down
10 changes: 10 additions & 0 deletionssite/src/pages/TaskPage/TaskPage.stories.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
import {
MockDeletedWorkspace,
MockFailedWorkspace,
MockStartingWorkspace,
MockStoppedWorkspace,
Expand DownExpand Up@@ -169,6 +170,15 @@ export const TerminatedBuildWithStatus: Story = {
},
};

export const DeletedWorkspace: Story = {
beforeEach: () => {
spyOn(API.experimental, "getTask").mockResolvedValue(MockTask);
spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(
MockDeletedWorkspace,
);
},
};

export const WaitingStartupScripts: Story = {
beforeEach: () => {
spyOn(API.experimental, "getTask").mockResolvedValue(MockTask);
Expand Down
22 changes: 21 additions & 1 deletionsite/src/pages/TaskPage/TaskPage.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -221,7 +221,27 @@ const WorkspaceNotRunning: FC<WorkspaceNotRunningProps> = ({ workspace }) => {
? mutateStartWorkspace.error
: undefined;

return (
const deleted = workspace.latest_build?.transition === ("delete" as const);

return deleted ? (
<Margins>
<div className="w-full min-h-80 flex items-center justify-center">
<div className="flex flex-col items-center">
<h3 className="m-0 font-medium text-content-primary text-base">
Task workspace was deleted.
</h3>
<span className="text-content-secondary text-sm">
This task cannot be resumed. Delete this task and create a new one.
</span>
<Button size="sm" variant="outline" asChild className="mt-4">
<RouterLink to="/tasks" data-testid="task-create-new">
Create a new task
</RouterLink>
</Button>
</div>
</div>
</Margins>
) : (
<Margins>
<div className="w-full min-h-80 flex items-center justify-center">
<div className="flex flex-col items-center">
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp