diff --git a/coderd/autobuild/lifecycle_executor_test.go b/coderd/autobuild/lifecycle_executor_test.go index 3eb779376cc5c..b271fc43d1267 100644 --- a/coderd/autobuild/lifecycle_executor_test.go +++ b/coderd/autobuild/lifecycle_executor_test.go @@ -722,45 +722,17 @@ func TestExecutorWorkspaceAutostopNoWaitChangedMyMind(t *testing.T) { err := client.UpdateWorkspaceTTL(ctx, workspace.ID, codersdk.UpdateWorkspaceTTLRequest{TTLMillis: nil}) require.NoError(t, err) - // Then: the deadline should still be the original value + // Then: the deadline should be set to zero updated := coderdtest.MustWorkspace(t, client, workspace.ID) - assert.WithinDuration(t, workspace.LatestBuild.Deadline.Time, updated.LatestBuild.Deadline.Time, time.Minute) + assert.True(t, !updated.LatestBuild.Deadline.Valid) // When: the autobuild executor ticks after the original deadline go func() { tickCh <- workspace.LatestBuild.Deadline.Time.Add(time.Minute) }() - // Then: the workspace should stop - stats := <-statsCh - assert.Len(t, stats.Errors, 0) - assert.Len(t, stats.Transitions, 1) - assert.Equal(t, stats.Transitions[workspace.ID], database.WorkspaceTransitionStop) - - // Wait for stop to complete - updated = coderdtest.MustWorkspace(t, client, workspace.ID) - _ = coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, updated.LatestBuild.ID) - - // Start the workspace again - workspace = coderdtest.MustTransitionWorkspace(t, client, workspace.ID, database.WorkspaceTransitionStop, database.WorkspaceTransitionStart) - - // Given: the user changes their mind again and wants to enable autostop - newTTL := 8 * time.Hour - err = client.UpdateWorkspaceTTL(ctx, workspace.ID, codersdk.UpdateWorkspaceTTLRequest{TTLMillis: ptr.Ref(newTTL.Milliseconds())}) - require.NoError(t, err) - - // Then: the deadline should remain at the zero value - updated = coderdtest.MustWorkspace(t, client, workspace.ID) - assert.Zero(t, updated.LatestBuild.Deadline) - - // When: the relentless onward march of time continues - go func() { - tickCh <- workspace.LatestBuild.Deadline.Time.Add(newTTL + time.Minute) - close(tickCh) - }() - // Then: the workspace should not stop - stats = <-statsCh + stats := <-statsCh assert.Len(t, stats.Errors, 0) assert.Len(t, stats.Transitions, 0) } diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 19fb1ec1ce810..0e7a4b5972dfd 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -1029,6 +1029,26 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) { return xerrors.Errorf("update workspace time until shutdown: %w", err) } + // If autostop has been disabled, we want to remove the deadline from the + // existing workspace build (if there is one). + if !dbTTL.Valid { + build, err := s.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspace.ID) + if err != nil { + return xerrors.Errorf("get latest workspace build: %w", err) + } + + if build.Transition == database.WorkspaceTransitionStart { + if err = s.UpdateWorkspaceBuildDeadlineByID(ctx, database.UpdateWorkspaceBuildDeadlineByIDParams{ + ID: build.ID, + Deadline: time.Time{}, + MaxDeadline: build.MaxDeadline, + UpdatedAt: dbtime.Time(api.Clock.Now()), + }); err != nil { + return xerrors.Errorf("update workspace build deadline: %w", err) + } + } + } + return nil }, nil) if err != nil { diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index d6e365011b929..115549e28cc2b 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -2394,6 +2394,93 @@ func TestWorkspaceUpdateTTL(t *testing.T) { }) } + t.Run("ModifyAutostopWithRunningWorkspace", func(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + fromTTL *int64 + toTTL *int64 + afterUpdate func(t *testing.T, before, after codersdk.NullTime) + }{ + { + name: "RemoveAutostopRemovesDeadline", + fromTTL: ptr.Ref((8 * time.Hour).Milliseconds()), + toTTL: nil, + afterUpdate: func(t *testing.T, before, after codersdk.NullTime) { + require.NotZero(t, before) + require.Zero(t, after) + }, + }, + { + name: "AddAutostopDoesNotAddDeadline", + fromTTL: nil, + toTTL: ptr.Ref((8 * time.Hour).Milliseconds()), + afterUpdate: func(t *testing.T, before, after codersdk.NullTime) { + require.Zero(t, before) + require.Zero(t, after) + }, + }, + { + name: "IncreaseAutostopDoesNotModifyDeadline", + fromTTL: ptr.Ref((4 * time.Hour).Milliseconds()), + toTTL: ptr.Ref((8 * time.Hour).Milliseconds()), + afterUpdate: func(t *testing.T, before, after codersdk.NullTime) { + require.NotZero(t, before) + require.NotZero(t, after) + require.Equal(t, before, after) + }, + }, + { + name: "DecreaseAutostopDoesNotModifyDeadline", + fromTTL: ptr.Ref((8 * time.Hour).Milliseconds()), + toTTL: ptr.Ref((4 * time.Hour).Milliseconds()), + afterUpdate: func(t *testing.T, before, after codersdk.NullTime) { + require.NotZero(t, before) + require.NotZero(t, after) + require.Equal(t, before, after) + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + var ( + client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + user = coderdtest.CreateFirstUser(t, client) + version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) + _ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) + template = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + workspace = coderdtest.CreateWorkspace(t, client, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) { + cwr.TTLMillis = testCase.fromTTL + }) + build = coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) + ) + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + err := client.UpdateWorkspaceTTL(ctx, workspace.ID, codersdk.UpdateWorkspaceTTLRequest{ + TTLMillis: testCase.toTTL, + }) + require.NoError(t, err) + + deadlineBefore := build.Deadline + + build, err = client.WorkspaceBuild(ctx, build.ID) + require.NoError(t, err) + + deadlineAfter := build.Deadline + + testCase.afterUpdate(t, deadlineBefore, deadlineAfter) + }) + } + }) + t.Run("CustomAutostopDisabledByTemplate", func(t *testing.T) { t.Parallel() var ( diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx index b5e9bfdba8da6..4ee96204dbdd5 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx @@ -118,7 +118,10 @@ export const WorkspaceSchedulePage: FC = () => { await submitScheduleMutation.mutateAsync(data); - if (data.autostopChanged) { + if ( + data.autostopChanged && + getAutostop(workspace).autostopEnabled + ) { setIsConfirmingApply(true); } }}
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: