mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
fix(coderd): use BuildReasonTaskAutoPause for task workspaces (#22126)
Relates to https://github.com/coder/internal/issues/1252 When a workspace with a TaskID hits its deadline, use BuildReasonTaskAutoPause instead of BuildReasonAutostop. This allows downstream systems to distinguish between regular autostop and task workspace pauses. Created by Mux using Opus 4.5.
This commit is contained in:
Generated
+8
-2
@@ -13553,7 +13553,10 @@ const docTemplate = `{
|
||||
"cli",
|
||||
"ssh_connection",
|
||||
"vscode_connection",
|
||||
"jetbrains_connection"
|
||||
"jetbrains_connection",
|
||||
"task_auto_pause",
|
||||
"task_manual_pause",
|
||||
"task_resume"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"BuildReasonInitiator",
|
||||
@@ -13564,7 +13567,10 @@ const docTemplate = `{
|
||||
"BuildReasonCLI",
|
||||
"BuildReasonSSHConnection",
|
||||
"BuildReasonVSCodeConnection",
|
||||
"BuildReasonJetbrainsConnection"
|
||||
"BuildReasonJetbrainsConnection",
|
||||
"BuildReasonTaskAutoPause",
|
||||
"BuildReasonTaskManualPause",
|
||||
"BuildReasonTaskResume"
|
||||
]
|
||||
},
|
||||
"codersdk.CORSBehavior": {
|
||||
|
||||
Generated
+8
-2
@@ -12148,7 +12148,10 @@
|
||||
"cli",
|
||||
"ssh_connection",
|
||||
"vscode_connection",
|
||||
"jetbrains_connection"
|
||||
"jetbrains_connection",
|
||||
"task_auto_pause",
|
||||
"task_manual_pause",
|
||||
"task_resume"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"BuildReasonInitiator",
|
||||
@@ -12159,7 +12162,10 @@
|
||||
"BuildReasonCLI",
|
||||
"BuildReasonSSHConnection",
|
||||
"BuildReasonVSCodeConnection",
|
||||
"BuildReasonJetbrainsConnection"
|
||||
"BuildReasonJetbrainsConnection",
|
||||
"BuildReasonTaskAutoPause",
|
||||
"BuildReasonTaskManualPause",
|
||||
"BuildReasonTaskResume"
|
||||
]
|
||||
},
|
||||
"codersdk.CORSBehavior": {
|
||||
|
||||
@@ -525,10 +525,18 @@ func getNextTransition(
|
||||
) {
|
||||
switch {
|
||||
case isEligibleForAutostop(user, ws, latestBuild, latestJob, currentTick):
|
||||
// Use task-specific reason for AI task workspaces.
|
||||
if ws.TaskID.Valid {
|
||||
return database.WorkspaceTransitionStop, database.BuildReasonTaskAutoPause, nil
|
||||
}
|
||||
return database.WorkspaceTransitionStop, database.BuildReasonAutostop, nil
|
||||
case isEligibleForAutostart(user, ws, latestBuild, latestJob, templateSchedule, currentTick):
|
||||
return database.WorkspaceTransitionStart, database.BuildReasonAutostart, nil
|
||||
case isEligibleForFailedStop(latestBuild, latestJob, templateSchedule, currentTick):
|
||||
// Use task-specific reason for AI task workspaces.
|
||||
if ws.TaskID.Valid {
|
||||
return database.WorkspaceTransitionStop, database.BuildReasonTaskAutoPause, nil
|
||||
}
|
||||
return database.WorkspaceTransitionStop, database.BuildReasonAutostop, nil
|
||||
case isEligibleForDormantStop(ws, templateSchedule, currentTick):
|
||||
// Only stop started workspaces.
|
||||
|
||||
@@ -5,12 +5,113 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/schedule"
|
||||
)
|
||||
|
||||
func Test_getNextTransition_TaskAutoPause(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Set up a workspace that is eligible for autostop (past deadline).
|
||||
now := time.Now()
|
||||
pastDeadline := now.Add(-time.Hour)
|
||||
|
||||
okUser := database.User{Status: database.UserStatusActive}
|
||||
okBuild := database.WorkspaceBuild{
|
||||
Transition: database.WorkspaceTransitionStart,
|
||||
Deadline: pastDeadline,
|
||||
}
|
||||
okJob := database.ProvisionerJob{
|
||||
JobStatus: database.ProvisionerJobStatusSucceeded,
|
||||
}
|
||||
okTemplateSchedule := schedule.TemplateScheduleOptions{}
|
||||
|
||||
// Failed build setup for failedstop tests.
|
||||
failedBuild := database.WorkspaceBuild{
|
||||
Transition: database.WorkspaceTransitionStart,
|
||||
}
|
||||
failedJob := database.ProvisionerJob{
|
||||
JobStatus: database.ProvisionerJobStatusFailed,
|
||||
CompletedAt: sql.NullTime{Time: now.Add(-time.Hour), Valid: true},
|
||||
}
|
||||
failedTemplateSchedule := schedule.TemplateScheduleOptions{
|
||||
FailureTTL: time.Minute, // TTL already elapsed since job completed an hour ago.
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
Name string
|
||||
Workspace database.Workspace
|
||||
Build database.WorkspaceBuild
|
||||
Job database.ProvisionerJob
|
||||
TemplateSchedule schedule.TemplateScheduleOptions
|
||||
ExpectedReason database.BuildReason
|
||||
}{
|
||||
{
|
||||
Name: "RegularWorkspace_Autostop",
|
||||
Workspace: database.Workspace{
|
||||
DormantAt: sql.NullTime{Valid: false},
|
||||
},
|
||||
Build: okBuild,
|
||||
Job: okJob,
|
||||
TemplateSchedule: okTemplateSchedule,
|
||||
ExpectedReason: database.BuildReasonAutostop,
|
||||
},
|
||||
{
|
||||
Name: "TaskWorkspace_Autostop_UsesTaskAutoPause",
|
||||
Workspace: database.Workspace{
|
||||
DormantAt: sql.NullTime{Valid: false},
|
||||
TaskID: uuid.NullUUID{UUID: uuid.New(), Valid: true},
|
||||
},
|
||||
Build: okBuild,
|
||||
Job: okJob,
|
||||
TemplateSchedule: okTemplateSchedule,
|
||||
ExpectedReason: database.BuildReasonTaskAutoPause,
|
||||
},
|
||||
{
|
||||
Name: "RegularWorkspace_FailedStop",
|
||||
Workspace: database.Workspace{
|
||||
DormantAt: sql.NullTime{Valid: false},
|
||||
},
|
||||
Build: failedBuild,
|
||||
Job: failedJob,
|
||||
TemplateSchedule: failedTemplateSchedule,
|
||||
ExpectedReason: database.BuildReasonAutostop,
|
||||
},
|
||||
{
|
||||
Name: "TaskWorkspace_FailedStop_UsesTaskAutoPause",
|
||||
Workspace: database.Workspace{
|
||||
DormantAt: sql.NullTime{Valid: false},
|
||||
TaskID: uuid.NullUUID{UUID: uuid.New(), Valid: true},
|
||||
},
|
||||
Build: failedBuild,
|
||||
Job: failedJob,
|
||||
TemplateSchedule: failedTemplateSchedule,
|
||||
ExpectedReason: database.BuildReasonTaskAutoPause,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
transition, reason, err := getNextTransition(
|
||||
okUser,
|
||||
tc.Workspace,
|
||||
tc.Build,
|
||||
tc.Job,
|
||||
tc.TemplateSchedule,
|
||||
now,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, database.WorkspaceTransitionStop, transition)
|
||||
require.Equal(t, tc.ExpectedReason, reason)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_isEligibleForAutostart(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -2019,5 +2019,11 @@ func TestExecutorTaskWorkspace(t *testing.T) {
|
||||
assert.Contains(t, stats.Transitions, workspace.ID, "task workspace should be in transitions")
|
||||
assert.Equal(t, database.WorkspaceTransitionStop, stats.Transitions[workspace.ID], "should autostop the workspace")
|
||||
require.Empty(t, stats.Errors, "should have no errors when managing task workspaces")
|
||||
|
||||
// Then: The build reason should be TaskAutoPause (not regular Autostop)
|
||||
workspace = coderdtest.MustWorkspace(t, client, workspace.ID)
|
||||
_ = coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
|
||||
workspace = coderdtest.MustWorkspace(t, client, workspace.ID)
|
||||
assert.Equal(t, codersdk.BuildReasonTaskAutoPause, workspace.LatestBuild.Reason, "task workspace should use TaskAutoPause build reason")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -59,6 +59,15 @@ const (
|
||||
BuildReasonVSCodeConnection BuildReason = "vscode_connection"
|
||||
// BuildReasonJetbrainsConnection "jetbrains_connection" is used when a build to start a workspace is triggered by a JetBrains connection.
|
||||
BuildReasonJetbrainsConnection BuildReason = "jetbrains_connection"
|
||||
// BuildReasonTaskAutoPause "task_auto_pause" is used when a build to stop
|
||||
// a task workspace is triggered by the lifecycle executor.
|
||||
BuildReasonTaskAutoPause BuildReason = "task_auto_pause"
|
||||
// BuildReasonTaskManualPause "task_manual_pause" is used when a build to
|
||||
// stop a task workspace is triggered by a user.
|
||||
BuildReasonTaskManualPause BuildReason = "task_manual_pause"
|
||||
// BuildReasonTaskResume "task_resume" is used when a build to
|
||||
// start a task workspace is triggered by a user.
|
||||
BuildReasonTaskResume BuildReason = "task_resume"
|
||||
)
|
||||
|
||||
// WorkspaceBuild is an at-point representation of a workspace state.
|
||||
|
||||
Generated
+3
-3
@@ -1511,9 +1511,9 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Value(s) |
|
||||
|-------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `autostart`, `autostop`, `cli`, `dashboard`, `dormancy`, `initiator`, `jetbrains_connection`, `ssh_connection`, `vscode_connection` |
|
||||
| Value(s) |
|
||||
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `autostart`, `autostop`, `cli`, `dashboard`, `dormancy`, `initiator`, `jetbrains_connection`, `ssh_connection`, `task_auto_pause`, `task_manual_pause`, `task_resume`, `vscode_connection` |
|
||||
|
||||
## codersdk.CORSBehavior
|
||||
|
||||
|
||||
Generated
+6
@@ -963,6 +963,9 @@ export type BuildReason =
|
||||
| "initiator"
|
||||
| "jetbrains_connection"
|
||||
| "ssh_connection"
|
||||
| "task_auto_pause"
|
||||
| "task_manual_pause"
|
||||
| "task_resume"
|
||||
| "vscode_connection";
|
||||
|
||||
export const BuildReasons: BuildReason[] = [
|
||||
@@ -974,6 +977,9 @@ export const BuildReasons: BuildReason[] = [
|
||||
"initiator",
|
||||
"jetbrains_connection",
|
||||
"ssh_connection",
|
||||
"task_auto_pause",
|
||||
"task_manual_pause",
|
||||
"task_resume",
|
||||
"vscode_connection",
|
||||
];
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ export const TaskPausedTimeout: Story = {
|
||||
latest_build: {
|
||||
...MockWorkspaceBuildStop,
|
||||
status: "stopped",
|
||||
reason: "autostop",
|
||||
reason: "task_auto_pause",
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -93,6 +93,13 @@ describe("util > workspace", () => {
|
||||
},
|
||||
"Coder",
|
||||
],
|
||||
[
|
||||
{
|
||||
...Mocks.MockWorkspaceBuild,
|
||||
reason: "task_auto_pause",
|
||||
},
|
||||
"Coder",
|
||||
],
|
||||
])(
|
||||
"getDisplayWorkspaceBuildInitiatedBy(%p) returns %p",
|
||||
(build, initiatedBy) => {
|
||||
|
||||
@@ -87,16 +87,26 @@ export const getDisplayWorkspaceBuildInitiatedBy = (
|
||||
case "ssh_connection":
|
||||
case "vscode_connection":
|
||||
case "jetbrains_connection":
|
||||
case "task_manual_pause":
|
||||
case "task_resume":
|
||||
return build.initiator_name;
|
||||
case "autostart":
|
||||
case "autostop":
|
||||
case "dormancy":
|
||||
case "task_auto_pause":
|
||||
return "Coder";
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const systemBuildReasons = ["autostart", "autostop", "dormancy"];
|
||||
export const systemBuildReasons = [
|
||||
"autostart",
|
||||
"autostop",
|
||||
"dormancy",
|
||||
"task_auto_pause",
|
||||
"task_manual_pause",
|
||||
"task_resume",
|
||||
];
|
||||
|
||||
export const buildReasonLabels: Record<TypesGen.BuildReason, string> = {
|
||||
// User build reasons
|
||||
@@ -111,6 +121,9 @@ export const buildReasonLabels: Record<TypesGen.BuildReason, string> = {
|
||||
autostart: "Autostart",
|
||||
autostop: "Autostop",
|
||||
dormancy: "Dormancy",
|
||||
task_auto_pause: "Task Auto-Pause",
|
||||
task_manual_pause: "Task Manual Pause",
|
||||
task_resume: "Task Resume",
|
||||
};
|
||||
|
||||
const getWorkspaceBuildDurationInSeconds = (
|
||||
|
||||
Reference in New Issue
Block a user