Skip to content

Commit 7c595e2

Browse files
feat: allow removing deadline for running workspace (#16085)
Fixes #9775 When a workspace's TTL is removed, and the workspace is running, the deadline is removed from the workspace. This also modifies the frontend to not show a confirmation dialog when the change is to remove autostop.
1 parent 048a10a commit 7c595e2

File tree

4 files changed

+114
-32
lines changed

4 files changed

+114
-32
lines changed

coderd/autobuild/lifecycle_executor_test.go

+3-31
Original file line numberDiff line numberDiff line change
@@ -722,45 +722,17 @@ func TestExecutorWorkspaceAutostopNoWaitChangedMyMind(t *testing.T) {
722722
err := client.UpdateWorkspaceTTL(ctx, workspace.ID, codersdk.UpdateWorkspaceTTLRequest{TTLMillis: nil})
723723
require.NoError(t, err)
724724

725-
// Then: the deadline should still be the original value
725+
// Then: the deadline should be set to zero
726726
updated := coderdtest.MustWorkspace(t, client, workspace.ID)
727-
assert.WithinDuration(t, workspace.LatestBuild.Deadline.Time, updated.LatestBuild.Deadline.Time, time.Minute)
727+
assert.True(t, !updated.LatestBuild.Deadline.Valid)
728728

729729
// When: the autobuild executor ticks after the original deadline
730730
go func() {
731731
tickCh <- workspace.LatestBuild.Deadline.Time.Add(time.Minute)
732732
}()
733733

734-
// Then: the workspace should stop
735-
stats := <-statsCh
736-
assert.Len(t, stats.Errors, 0)
737-
assert.Len(t, stats.Transitions, 1)
738-
assert.Equal(t, stats.Transitions[workspace.ID], database.WorkspaceTransitionStop)
739-
740-
// Wait for stop to complete
741-
updated = coderdtest.MustWorkspace(t, client, workspace.ID)
742-
_ = coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, updated.LatestBuild.ID)
743-
744-
// Start the workspace again
745-
workspace = coderdtest.MustTransitionWorkspace(t, client, workspace.ID, database.WorkspaceTransitionStop, database.WorkspaceTransitionStart)
746-
747-
// Given: the user changes their mind again and wants to enable autostop
748-
newTTL := 8 * time.Hour
749-
err = client.UpdateWorkspaceTTL(ctx, workspace.ID, codersdk.UpdateWorkspaceTTLRequest{TTLMillis: ptr.Ref(newTTL.Milliseconds())})
750-
require.NoError(t, err)
751-
752-
// Then: the deadline should remain at the zero value
753-
updated = coderdtest.MustWorkspace(t, client, workspace.ID)
754-
assert.Zero(t, updated.LatestBuild.Deadline)
755-
756-
// When: the relentless onward march of time continues
757-
go func() {
758-
tickCh <- workspace.LatestBuild.Deadline.Time.Add(newTTL + time.Minute)
759-
close(tickCh)
760-
}()
761-
762734
// Then: the workspace should not stop
763-
stats = <-statsCh
735+
stats := <-statsCh
764736
assert.Len(t, stats.Errors, 0)
765737
assert.Len(t, stats.Transitions, 0)
766738
}

coderd/workspaces.go

+20
Original file line numberDiff line numberDiff line change
@@ -1029,6 +1029,26 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) {
10291029
return xerrors.Errorf("update workspace time until shutdown: %w", err)
10301030
}
10311031

1032+
// If autostop has been disabled, we want to remove the deadline from the
1033+
// existing workspace build (if there is one).
1034+
if !dbTTL.Valid {
1035+
build, err := s.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspace.ID)
1036+
if err != nil {
1037+
return xerrors.Errorf("get latest workspace build: %w", err)
1038+
}
1039+
1040+
if build.Transition == database.WorkspaceTransitionStart {
1041+
if err = s.UpdateWorkspaceBuildDeadlineByID(ctx, database.UpdateWorkspaceBuildDeadlineByIDParams{
1042+
ID: build.ID,
1043+
Deadline: time.Time{},
1044+
MaxDeadline: build.MaxDeadline,
1045+
UpdatedAt: dbtime.Time(api.Clock.Now()),
1046+
}); err != nil {
1047+
return xerrors.Errorf("update workspace build deadline: %w", err)
1048+
}
1049+
}
1050+
}
1051+
10321052
return nil
10331053
}, nil)
10341054
if err != nil {

coderd/workspaces_test.go

+87
Original file line numberDiff line numberDiff line change
@@ -2394,6 +2394,93 @@ func TestWorkspaceUpdateTTL(t *testing.T) {
23942394
})
23952395
}
23962396

2397+
t.Run("ModifyAutostopWithRunningWorkspace", func(t *testing.T) {
2398+
t.Parallel()
2399+
2400+
testCases := []struct {
2401+
name string
2402+
fromTTL *int64
2403+
toTTL *int64
2404+
afterUpdate func(t *testing.T, before, after codersdk.NullTime)
2405+
}{
2406+
{
2407+
name: "RemoveAutostopRemovesDeadline",
2408+
fromTTL: ptr.Ref((8 * time.Hour).Milliseconds()),
2409+
toTTL: nil,
2410+
afterUpdate: func(t *testing.T, before, after codersdk.NullTime) {
2411+
require.NotZero(t, before)
2412+
require.Zero(t, after)
2413+
},
2414+
},
2415+
{
2416+
name: "AddAutostopDoesNotAddDeadline",
2417+
fromTTL: nil,
2418+
toTTL: ptr.Ref((8 * time.Hour).Milliseconds()),
2419+
afterUpdate: func(t *testing.T, before, after codersdk.NullTime) {
2420+
require.Zero(t, before)
2421+
require.Zero(t, after)
2422+
},
2423+
},
2424+
{
2425+
name: "IncreaseAutostopDoesNotModifyDeadline",
2426+
fromTTL: ptr.Ref((4 * time.Hour).Milliseconds()),
2427+
toTTL: ptr.Ref((8 * time.Hour).Milliseconds()),
2428+
afterUpdate: func(t *testing.T, before, after codersdk.NullTime) {
2429+
require.NotZero(t, before)
2430+
require.NotZero(t, after)
2431+
require.Equal(t, before, after)
2432+
},
2433+
},
2434+
{
2435+
name: "DecreaseAutostopDoesNotModifyDeadline",
2436+
fromTTL: ptr.Ref((8 * time.Hour).Milliseconds()),
2437+
toTTL: ptr.Ref((4 * time.Hour).Milliseconds()),
2438+
afterUpdate: func(t *testing.T, before, after codersdk.NullTime) {
2439+
require.NotZero(t, before)
2440+
require.NotZero(t, after)
2441+
require.Equal(t, before, after)
2442+
},
2443+
},
2444+
}
2445+
2446+
for _, testCase := range testCases {
2447+
testCase := testCase
2448+
2449+
t.Run(testCase.name, func(t *testing.T) {
2450+
t.Parallel()
2451+
2452+
var (
2453+
client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
2454+
user = coderdtest.CreateFirstUser(t, client)
2455+
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
2456+
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
2457+
template = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
2458+
workspace = coderdtest.CreateWorkspace(t, client, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
2459+
cwr.TTLMillis = testCase.fromTTL
2460+
})
2461+
build = coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
2462+
)
2463+
2464+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
2465+
defer cancel()
2466+
2467+
err := client.UpdateWorkspaceTTL(ctx, workspace.ID, codersdk.UpdateWorkspaceTTLRequest{
2468+
TTLMillis: testCase.toTTL,
2469+
})
2470+
require.NoError(t, err)
2471+
2472+
deadlineBefore := build.Deadline
2473+
2474+
build, err = client.WorkspaceBuild(ctx, build.ID)
2475+
require.NoError(t, err)
2476+
2477+
deadlineAfter := build.Deadline
2478+
2479+
testCase.afterUpdate(t, deadlineBefore, deadlineAfter)
2480+
})
2481+
}
2482+
})
2483+
23972484
t.Run("CustomAutostopDisabledByTemplate", func(t *testing.T) {
23982485
t.Parallel()
23992486
var (

site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,10 @@ export const WorkspaceSchedulePage: FC = () => {
118118

119119
await submitScheduleMutation.mutateAsync(data);
120120

121-
if (data.autostopChanged) {
121+
if (
122+
data.autostopChanged &&
123+
getAutostop(workspace).autostopEnabled
124+
) {
122125
setIsConfirmingApply(true);
123126
}
124127
}}

0 commit comments

Comments
 (0)