fix: allow users to extend their running workspace's deadline (#15895)

Fixes https://github.com/coder/coder/issues/15515

This change effectively reverts the changes introduced by
https://github.com/coder/coder/pull/13182 (for
https://github.com/coder/coder/issues/13078).

We also rename the `override-stop` command name to `extend` to match the
API endpoint's name (keeping an alias to allow `override-stop` to be used).
This commit is contained in:
Danielle Maywood
2024-12-18 10:58:33 +00:00
committed by GitHub
parent 4c5b737368
commit 91875c2b47
11 changed files with 82 additions and 123 deletions

View File

@ -46,7 +46,7 @@ When enabling scheduled stop, enter a duration in one of the following formats:
* 2m (2 minutes)
* 2 (2 minutes)
`
scheduleOverrideDescriptionLong = `
scheduleExtendDescriptionLong = `
* The new stop time is calculated from *now*.
* The new stop time must be at least 30 minutes in the future.
* The workspace template may restrict the maximum workspace runtime.
@ -56,7 +56,7 @@ When enabling scheduled stop, enter a duration in one of the following formats:
func (r *RootCmd) schedules() *serpent.Command {
scheduleCmd := &serpent.Command{
Annotations: workspaceCommand,
Use: "schedule { show | start | stop | override } <workspace>",
Use: "schedule { show | start | stop | extend } <workspace>",
Short: "Schedule automated start and stop times for workspaces",
Handler: func(inv *serpent.Invocation) error {
return inv.Command.HelpHandler(inv)
@ -65,7 +65,7 @@ func (r *RootCmd) schedules() *serpent.Command {
r.scheduleShow(),
r.scheduleStart(),
r.scheduleStop(),
r.scheduleOverride(),
r.scheduleExtend(),
},
}
@ -229,14 +229,15 @@ func (r *RootCmd) scheduleStop() *serpent.Command {
}
}
func (r *RootCmd) scheduleOverride() *serpent.Command {
func (r *RootCmd) scheduleExtend() *serpent.Command {
client := new(codersdk.Client)
overrideCmd := &serpent.Command{
Use: "override-stop <workspace-name> <duration from now>",
Short: "Override the stop time of a currently running workspace instance.",
Long: scheduleOverrideDescriptionLong + "\n" + FormatExamples(
extendCmd := &serpent.Command{
Use: "extend <workspace-name> <duration from now>",
Aliases: []string{"override-stop"},
Short: "Extend the stop time of a currently running workspace instance.",
Long: scheduleExtendDescriptionLong + "\n" + FormatExamples(
Example{
Command: "coder schedule override-stop my-workspace 90m",
Command: "coder schedule extend my-workspace 90m",
},
),
Middleware: serpent.Chain(
@ -244,7 +245,7 @@ func (r *RootCmd) scheduleOverride() *serpent.Command {
r.InitClient(client),
),
Handler: func(inv *serpent.Invocation) error {
overrideDuration, err := parseDuration(inv.Args[1])
extendDuration, err := parseDuration(inv.Args[1])
if err != nil {
return err
}
@ -259,7 +260,7 @@ func (r *RootCmd) scheduleOverride() *serpent.Command {
loc = time.UTC // best effort
}
if overrideDuration < 29*time.Minute {
if extendDuration < 29*time.Minute {
_, _ = fmt.Fprintf(
inv.Stdout,
"Please specify a duration of at least 30 minutes.\n",
@ -267,7 +268,7 @@ func (r *RootCmd) scheduleOverride() *serpent.Command {
return nil
}
newDeadline := time.Now().In(loc).Add(overrideDuration)
newDeadline := time.Now().In(loc).Add(extendDuration)
if err := client.PutExtendWorkspace(inv.Context(), workspace.ID, codersdk.PutExtendWorkspaceRequest{
Deadline: newDeadline,
}); err != nil {
@ -281,7 +282,7 @@ func (r *RootCmd) scheduleOverride() *serpent.Command {
return displaySchedule(updated, inv.Stdout)
},
}
return overrideCmd
return extendCmd
}
func displaySchedule(ws codersdk.Workspace, out io.Writer) error {

View File

@ -332,6 +332,18 @@ func TestScheduleModify(t *testing.T) {
//nolint:paralleltest // t.Setenv
func TestScheduleOverride(t *testing.T) {
tests := []struct {
command string
}{
{command: "extend"},
// test for backwards compatibility
{command: "override-stop"},
}
for _, tt := range tests {
tt := tt
t.Run(tt.command, func(t *testing.T) {
// Given
// Set timezone to Asia/Kolkata to surface any timezone-related bugs.
t.Setenv("TZ", "Asia/Kolkata")
@ -347,7 +359,7 @@ func TestScheduleOverride(t *testing.T) {
// When: we override the stop schedule
inv, root := clitest.New(t,
"schedule", "override-stop", ws[0].OwnerName+"/"+ws[0].Name, "10h",
"schedule", tt.command, ws[0].OwnerName+"/"+ws[0].Name, "10h",
)
clitest.SetupConfig(t, ownerClient, root)
@ -360,4 +372,6 @@ func TestScheduleOverride(t *testing.T) {
pty.ExpectMatch(sched.Next(now).In(loc).Format(time.RFC3339))
pty.ExpectMatch("8h")
pty.ExpectMatch(expectedDeadline)
})
}
}

View File

@ -1,13 +1,12 @@
coder v0.0.0-devel
USAGE:
coder schedule { show | start | stop | override } <workspace>
coder schedule { show | start | stop | extend } <workspace>
Schedule automated start and stop times for workspaces
SUBCOMMANDS:
override-stop Override the stop time of a currently running workspace
instance.
extend Extend the stop time of a currently running workspace instance.
show Show workspace schedules
start Edit workspace start schedule
stop Edit workspace stop schedule

View File

@ -1,15 +1,17 @@
coder v0.0.0-devel
USAGE:
coder schedule override-stop <workspace-name> <duration from now>
coder schedule extend <workspace-name> <duration from now>
Override the stop time of a currently running workspace instance.
Extend the stop time of a currently running workspace instance.
Aliases: override-stop
* The new stop time is calculated from *now*.
* The new stop time must be at least 30 minutes in the future.
* The workspace template may restrict the maximum workspace runtime.
$ coder schedule override-stop my-workspace 90m
$ coder schedule extend my-workspace 90m
———
Run `coder --help` for a list of global options.

View File

@ -1200,18 +1200,6 @@ func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) {
return xerrors.Errorf("workspace shutdown is manual")
}
tmpl, err := s.GetTemplateByID(ctx, workspace.TemplateID)
if err != nil {
code = http.StatusInternalServerError
resp.Message = "Error fetching template."
return xerrors.Errorf("get template: %w", err)
}
if !tmpl.AllowUserAutostop {
code = http.StatusBadRequest
resp.Message = "Cannot extend workspace: template does not allow user autostop."
return xerrors.New("cannot extend workspace: template does not allow user autostop")
}
newDeadline := req.Deadline.UTC()
if err := validWorkspaceDeadline(job.CompletedAt.Time, newDeadline); err != nil {
// NOTE(Cian): Putting the error in the Message field on request from the FE folks.

View File

@ -1194,9 +1194,9 @@
"path": "reference/cli/schedule.md"
},
{
"title": "schedule override-stop",
"description": "Override the stop time of a currently running workspace instance.",
"path": "reference/cli/schedule_override-stop.md"
"title": "schedule extend",
"description": "Extend the stop time of a currently running workspace instance.",
"path": "reference/cli/schedule_extend.md"
},
{
"title": "schedule show",

View File

@ -7,14 +7,14 @@ Schedule automated start and stop times for workspaces
## Usage
```console
coder schedule { show | start | stop | override } <workspace>
coder schedule { show | start | stop | extend } <workspace>
```
## Subcommands
| Name | Purpose |
| --------------------------------------------------------- | ----------------------------------------------------------------- |
| ------------------------------------------- | --------------------------------------------------------------- |
| [<code>show</code>](./schedule_show.md) | Show workspace schedules |
| [<code>start</code>](./schedule_start.md) | Edit workspace start schedule |
| [<code>stop</code>](./schedule_stop.md) | Edit workspace stop schedule |
| [<code>override-stop</code>](./schedule_override-stop.md) | Override the stop time of a currently running workspace instance. |
| [<code>extend</code>](./schedule_extend.md) | Extend the stop time of a currently running workspace instance. |

View File

@ -1,13 +1,17 @@
<!-- DO NOT EDIT | GENERATED CONTENT -->
# schedule override-stop
# schedule extend
Override the stop time of a currently running workspace instance.
Extend the stop time of a currently running workspace instance.
Aliases:
- override-stop
## Usage
```console
coder schedule override-stop <workspace-name> <duration from now>
coder schedule extend <workspace-name> <duration from now>
```
## Description
@ -18,5 +22,5 @@ coder schedule override-stop <workspace-name> <duration from now>
* The new stop time must be at least 30 minutes in the future.
* The workspace template may restrict the maximum workspace runtime.
$ coder schedule override-stop my-workspace 90m
$ coder schedule extend my-workspace 90m
```

View File

@ -1391,35 +1391,6 @@ func TestTemplateDoesNotAllowUserAutostop(t *testing.T) {
require.Equal(t, templateTTL, template.DefaultTTLMillis)
require.Equal(t, templateTTL, *workspace.TTLMillis)
})
t.Run("ExtendIsNotEnabledByTemplate", func(t *testing.T) {
t.Parallel()
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
client := coderdtest.New(t, &coderdtest.Options{
IncludeProvisionerDaemon: true,
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger, nil),
})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.AllowUserAutostop = ptr.Ref(false)
})
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, template.ID)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
require.Equal(t, false, template.AllowUserAutostop, "template should have AllowUserAutostop as false")
ctx := testutil.Context(t, testutil.WaitShort)
ttl := 8 * time.Hour
newDeadline := time.Now().Add(ttl + time.Hour).UTC()
err := client.PutExtendWorkspace(ctx, workspace.ID, codersdk.PutExtendWorkspaceRequest{
Deadline: newDeadline,
})
require.ErrorContains(t, err, "template does not allow user autostop")
})
}
// TestWorkspaceTagsTerraform tests that a workspace can be created with tags.

View File

@ -303,24 +303,6 @@ export const WithQuotaWithOrgs: Story = {
},
};
export const TemplateDoesNotAllowAutostop: Story = {
args: {
workspace: {
...MockWorkspace,
latest_build: {
...MockWorkspace.latest_build,
get deadline() {
return addHours(new Date(), 8).toISOString();
},
},
},
template: {
...MockTemplate,
allow_user_autostop: false,
},
},
};
export const TemplateInfoPopover: Story = {
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);

View File

@ -234,9 +234,7 @@ export const WorkspaceTopbar: FC<WorkspaceProps> = ({
<WorkspaceScheduleControls
workspace={workspace}
template={template}
canUpdateSchedule={
canUpdateWorkspace && template.allow_user_autostop
}
canUpdateSchedule={canUpdateWorkspace}
/>
<WorkspaceNotifications
workspace={workspace}