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

Commit119db78

Browse files
authored
feat: update workspace deadline when workspace ttl updated (#2165)
This commit adds the following changes to workspace scheduling behaviour:* CLI: updating a workspace TTL updates the deadline of the workspace. * If the TTL is being un-set, the workspace deadline is set to zero. * If the TTL is being set, the workspace deadline is updated to be the last updated time of the workspace build plus the requested TTL. Additionally, the user is prompted to confirm interactively (can be bypassed with -y).* UI: updating the workspace schedule behaves similarly to the CLI, showing a message to the user if the updated TTL/time to shutdown would effect changes to the lifetime of the running workspace.
1 parent411d7da commit119db78

File tree

9 files changed

+358
-54
lines changed

9 files changed

+358
-54
lines changed

‎cli/ttl.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package cli
22

33
import (
4+
"errors"
45
"fmt"
56
"time"
67

78
"github.com/spf13/cobra"
89
"golang.org/x/xerrors"
910

11+
"github.com/coder/coder/cli/cliui"
1012
"github.com/coder/coder/codersdk"
1113
)
1214

@@ -89,6 +91,30 @@ func ttlset() *cobra.Command {
8991
_,_=fmt.Fprintf(cmd.OutOrStdout(),"warning: ttl rounded down to %s\n",truncated)
9092
}
9193

94+
ifchanged,newDeadline:=changedNewDeadline(workspace,truncated);changed {
95+
// For the purposes of the user, "less than a minute" is essentially the same as "immediately".
96+
timeRemaining:=time.Until(newDeadline).Truncate(time.Minute)
97+
humanRemaining:="in "+timeRemaining.String()
98+
iftimeRemaining<=0 {
99+
humanRemaining="immediately"
100+
}
101+
_,err=cliui.Prompt(cmd, cliui.PromptOptions{
102+
Text:fmt.Sprintf(
103+
"Workspace %q will be stopped %s. Are you sure?",
104+
workspace.Name,
105+
humanRemaining,
106+
),
107+
Default:"yes",
108+
IsConfirm:true,
109+
})
110+
iferr!=nil {
111+
iferrors.Is(err,cliui.Canceled) {
112+
returnnil
113+
}
114+
returnerr
115+
}
116+
}
117+
92118
millis:=truncated.Milliseconds()
93119
iferr=client.UpdateWorkspaceTTL(cmd.Context(),workspace.ID, codersdk.UpdateWorkspaceTTLRequest{
94120
TTLMillis:&millis,
@@ -131,3 +157,18 @@ func ttlunset() *cobra.Command {
131157
},
132158
}
133159
}
160+
161+
funcchangedNewDeadline(ws codersdk.Workspace,newTTL time.Duration) (changedbool,newDeadline time.Time) {
162+
ifws.LatestBuild.Transition!=codersdk.WorkspaceTransitionStart {
163+
// not running
164+
returnfalse,newDeadline
165+
}
166+
167+
ifws.LatestBuild.Job.CompletedAt==nil {
168+
// still building
169+
returnfalse,newDeadline
170+
}
171+
172+
newDeadline=ws.LatestBuild.Job.CompletedAt.Add(newTTL)
173+
returntrue,newDeadline
174+
}

‎cli/ttl_test.go

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@ package cli_test
33
import (
44
"bytes"
55
"context"
6+
"fmt"
67
"strings"
78
"testing"
89
"time"
910

11+
"github.com/stretchr/testify/assert"
1012
"github.com/stretchr/testify/require"
1113

1214
"github.com/coder/coder/cli/clitest"
1315
"github.com/coder/coder/coderd/coderdtest"
1416
"github.com/coder/coder/coderd/util/ptr"
1517
"github.com/coder/coder/codersdk"
18+
"github.com/coder/coder/pty/ptytest"
1619
)
1720

1821
funcTestTTL(t*testing.T) {
@@ -22,33 +25,29 @@ func TestTTL(t *testing.T) {
2225
t.Parallel()
2326

2427
var (
25-
ctx=context.Background()
2628
client=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerD:true})
2729
user=coderdtest.CreateFirstUser(t,client)
2830
version=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,nil)
2931
_=coderdtest.AwaitTemplateVersionJob(t,client,version.ID)
30-
project=coderdtest.CreateTemplate(t,client,user.OrganizationID,version.ID)
31-
workspace=coderdtest.CreateWorkspace(t,client,user.OrganizationID,project.ID)
32+
template=coderdtest.CreateTemplate(t,client,user.OrganizationID,version.ID)
33+
ttl=7*time.Hour+30*time.Minute+30*time.Second
34+
workspace=coderdtest.CreateWorkspace(t,client,user.OrganizationID,template.ID,func(cwr*codersdk.CreateWorkspaceRequest) {
35+
cwr.TTLMillis=ptr.Ref(ttl.Milliseconds())
36+
})
3237
cmdArgs= []string{"ttl","show",workspace.Name}
33-
ttl=8*time.Hour+30*time.Minute+30*time.Second
3438
stdoutBuf=&bytes.Buffer{}
3539
)
3640

37-
err:=client.UpdateWorkspaceTTL(ctx,workspace.ID, codersdk.UpdateWorkspaceTTLRequest{
38-
TTLMillis:ptr.Ref(ttl.Milliseconds()),
39-
})
40-
require.NoError(t,err)
41-
4241
cmd,root:=clitest.New(t,cmdArgs...)
4342
clitest.SetupConfig(t,client,root)
4443
cmd.SetOut(stdoutBuf)
4544

46-
err=cmd.Execute()
45+
err:=cmd.Execute()
4746
require.NoError(t,err,"unexpected error")
4847
require.Equal(t,ttl.Truncate(time.Minute).String(),strings.TrimSpace(stdoutBuf.String()))
4948
})
5049

51-
t.Run("SetUnsetOK",func(t*testing.T) {
50+
t.Run("UnsetOK",func(t*testing.T) {
5251
t.Parallel()
5352

5453
var (
@@ -58,9 +57,11 @@ func TestTTL(t *testing.T) {
5857
version=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,nil)
5958
_=coderdtest.AwaitTemplateVersionJob(t,client,version.ID)
6059
project=coderdtest.CreateTemplate(t,client,user.OrganizationID,version.ID)
61-
workspace=coderdtest.CreateWorkspace(t,client,user.OrganizationID,project.ID)
6260
ttl=8*time.Hour+30*time.Minute+30*time.Second
63-
cmdArgs= []string{"ttl","set",workspace.Name,ttl.String()}
61+
workspace=coderdtest.CreateWorkspace(t,client,user.OrganizationID,project.ID,func(cwr*codersdk.CreateWorkspaceRequest) {
62+
cwr.TTLMillis=ptr.Ref(ttl.Milliseconds())
63+
})
64+
cmdArgs= []string{"ttl","unset",workspace.Name}
6465
stdoutBuf=&bytes.Buffer{}
6566
)
6667

@@ -71,24 +72,52 @@ func TestTTL(t *testing.T) {
7172
err:=cmd.Execute()
7273
require.NoError(t,err,"unexpected error")
7374

74-
// Ensure ttlupdated
75+
// Ensure ttlunset
7576
updated,err:=client.Workspace(ctx,workspace.ID)
7677
require.NoError(t,err,"fetch updated workspace")
77-
require.Equal(t,ttl.Truncate(time.Minute),time.Duration(*updated.TTLMillis)*time.Millisecond)
78-
require.Contains(t,stdoutBuf.String(),"warning: ttl rounded down")
78+
require.Nil(t,updated.TTLMillis,"expected ttl to not be set")
79+
})
7980

80-
// unset schedule
81-
cmd,root=clitest.New(t,"ttl","unset",workspace.Name)
82-
clitest.SetupConfig(t,client,root)
83-
cmd.SetOut(stdoutBuf)
81+
t.Run("SetOK",func(t*testing.T) {
82+
t.Parallel()
8483

85-
err=cmd.Execute()
86-
require.NoError(t,err,"unexpected error")
84+
var (
85+
ctx=context.Background()
86+
client=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerD:true})
87+
user=coderdtest.CreateFirstUser(t,client)
88+
version=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,nil)
89+
_=coderdtest.AwaitTemplateVersionJob(t,client,version.ID)
90+
project=coderdtest.CreateTemplate(t,client,user.OrganizationID,version.ID)
91+
ttl=8*time.Hour+30*time.Minute+30*time.Second
92+
workspace=coderdtest.CreateWorkspace(t,client,user.OrganizationID,project.ID,func(cwr*codersdk.CreateWorkspaceRequest) {
93+
cwr.TTLMillis=ptr.Ref(ttl.Milliseconds())
94+
})
95+
_=coderdtest.AwaitWorkspaceBuildJob(t,client,workspace.LatestBuild.ID)
96+
cmdArgs= []string{"ttl","set",workspace.Name,ttl.String()}
97+
done=make(chanstruct{})
98+
)
8799

100+
cmd,root:=clitest.New(t,cmdArgs...)
101+
clitest.SetupConfig(t,client,root)
102+
pty:=ptytest.New(t)
103+
cmd.SetIn(pty.Input())
104+
cmd.SetOut(pty.Output())
105+
106+
gofunc() {
107+
deferclose(done)
108+
err:=cmd.Execute()
109+
assert.NoError(t,err,"unexpected error")
110+
}()
111+
112+
pty.ExpectMatch(fmt.Sprintf("warning: ttl rounded down to %s",ttl.Truncate(time.Minute)))
113+
pty.ExpectMatch(fmt.Sprintf("Workspace %q will be stopped in 8h29m0s. Are you sure?",workspace.Name))
114+
pty.WriteLine("yes")
88115
// Ensure ttl updated
89-
updated,err=client.Workspace(ctx,workspace.ID)
116+
updated,err:=client.Workspace(ctx,workspace.ID)
90117
require.NoError(t,err,"fetch updated workspace")
91-
require.Nil(t,updated.TTLMillis,"expected ttl to not be set")
118+
require.Equal(t,ttl.Truncate(time.Minute),time.Duration(*updated.TTLMillis)*time.Millisecond)
119+
120+
<-done
92121
})
93122

94123
t.Run("ZeroInvalid",func(t*testing.T) {

‎coderd/autobuild/executor/lifecycle_executor_test.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -440,18 +440,41 @@ func TestExecutorWorkspaceAutostopNoWaitChangedMyMind(t *testing.T) {
440440
err:=client.UpdateWorkspaceTTL(ctx,workspace.ID, codersdk.UpdateWorkspaceTTLRequest{TTLMillis:nil})
441441
require.NoError(t,err)
442442

443-
// When: the autobuild executor ticks after the deadline
443+
// Then: the deadline should be the zero value
444+
updated:=coderdtest.MustWorkspace(t,client,workspace.ID)
445+
assert.Zero(t,updated.LatestBuild.Deadline)
446+
447+
// When: the autobuild executor ticks after the original deadline
444448
gofunc() {
445449
tickCh<-workspace.LatestBuild.Deadline.Add(time.Minute)
446-
close(tickCh)
447450
}()
448451

449-
// Then: the workspace shouldstill stop - sorry!
452+
// Then: the workspace shouldnot stop
450453
stats:=<-statsCh
451454
assert.NoError(t,stats.Error)
455+
assert.Len(t,stats.Transitions,0)
456+
457+
// Given: the user changes their mind again and wants to enable auto-stop
458+
newTTL:=8*time.Hour
459+
expectedDeadline:=workspace.LatestBuild.UpdatedAt.Add(newTTL)
460+
err=client.UpdateWorkspaceTTL(ctx,workspace.ID, codersdk.UpdateWorkspaceTTLRequest{TTLMillis:ptr.Ref(newTTL.Milliseconds())})
461+
require.NoError(t,err)
462+
463+
// Then: the deadline should be updated based on the TTL
464+
updated=coderdtest.MustWorkspace(t,client,workspace.ID)
465+
assert.WithinDuration(t,expectedDeadline,updated.LatestBuild.Deadline,time.Minute)
466+
467+
// When: the relentless onward march of time continues
468+
gofunc() {
469+
tickCh<-workspace.LatestBuild.Deadline.Add(newTTL+time.Minute)
470+
close(tickCh)
471+
}()
472+
473+
// Then: the workspace should stop
474+
stats=<-statsCh
475+
assert.NoError(t,stats.Error)
452476
assert.Len(t,stats.Transitions,1)
453-
assert.Contains(t,stats.Transitions,workspace.ID)
454-
assert.Equal(t,database.WorkspaceTransitionStop,stats.Transitions[workspace.ID])
477+
assert.Equal(t,stats.Transitions[workspace.ID],database.WorkspaceTransitionStop)
455478
}
456479

457480
funcTestExecutorAutostartMultipleOK(t*testing.T) {

‎coderd/workspaces.go

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -566,17 +566,57 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) {
566566
return
567567
}
568568

569-
err=api.Database.UpdateWorkspaceTTL(r.Context(), database.UpdateWorkspaceTTLParams{
570-
ID:workspace.ID,
571-
Ttl:dbTTL,
569+
err=api.Database.InTx(func(s database.Store)error {
570+
iferr:=s.UpdateWorkspaceTTL(r.Context(), database.UpdateWorkspaceTTLParams{
571+
ID:workspace.ID,
572+
Ttl:dbTTL,
573+
});err!=nil {
574+
returnxerrors.Errorf("update workspace TTL: %w",err)
575+
}
576+
577+
// Also extend the workspace deadline if the workspace is running
578+
latestBuild,err:=s.GetLatestWorkspaceBuildByWorkspaceID(r.Context(),workspace.ID)
579+
iferr!=nil {
580+
returnxerrors.Errorf("get latest workspace build: %w",err)
581+
}
582+
583+
iflatestBuild.Transition!=database.WorkspaceTransitionStart {
584+
returnnil// nothing to do
585+
}
586+
587+
iflatestBuild.UpdatedAt.IsZero() {
588+
// Build in progress; provisionerd should update with the new TTL.
589+
returnnil
590+
}
591+
592+
varnewDeadline time.Time
593+
ifdbTTL.Valid {
594+
newDeadline=latestBuild.UpdatedAt.Add(time.Duration(dbTTL.Int64))
595+
}
596+
597+
iferr:=s.UpdateWorkspaceBuildByID(
598+
r.Context(),
599+
database.UpdateWorkspaceBuildByIDParams{
600+
ID:latestBuild.ID,
601+
UpdatedAt:latestBuild.UpdatedAt,
602+
ProvisionerState:latestBuild.ProvisionerState,
603+
Deadline:newDeadline,
604+
},
605+
);err!=nil {
606+
returnxerrors.Errorf("update workspace deadline: %w",err)
607+
}
608+
returnnil
572609
})
610+
573611
iferr!=nil {
574612
httpapi.Write(rw,http.StatusInternalServerError, httpapi.Response{
575-
Message:"Internal errorupdating workspaceTTL.",
613+
Message:"Errorupdating workspacetime until shutdown!",
576614
Detail:err.Error(),
577615
})
578616
return
579617
}
618+
619+
httpapi.Write(rw,http.StatusOK,nil)
580620
}
581621

582622
func (api*API)putExtendWorkspace(rw http.ResponseWriter,r*http.Request) {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp