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

Commit29f6fce

Browse files
johnstcnkylecarbs
authored andcommitted
feat: enforce template-level constraints for TTL and autostart (#2018)
This PR adds fields to templates that constrain values for workspaces derived from that template.- Autostop: Adds a field max_ttl on the template which limits the maximum value of ttl on all workspaces derived from that template. Defaulting to 168 hours, enforced on edits to workspace metadata. New workspaces will default to the templates's `max_ttl` if not specified.- Autostart: Adds a field min_autostart_duration which limits the minimum duration between successive autostarts of a template, measured from a single reference time. Defaulting to 1 hour, enforced on edits to workspace metadata.
1 parentc0d83bd commit29f6fce

27 files changed

+644
-290
lines changed

‎cli/autostart_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"os"
88
"testing"
9+
"time"
910

1011
"github.com/stretchr/testify/require"
1112

@@ -158,4 +159,26 @@ func TestAutostart(t *testing.T) {
158159
require.NoError(t,err,"fetch updated workspace")
159160
require.Equal(t,expectedSchedule,*updated.AutostartSchedule,"expected default autostart schedule")
160161
})
162+
163+
t.Run("BelowTemplateConstraint",func(t*testing.T) {
164+
t.Parallel()
165+
166+
var (
167+
client=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerD:true})
168+
user=coderdtest.CreateFirstUser(t,client)
169+
version=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,nil)
170+
_=coderdtest.AwaitTemplateVersionJob(t,client,version.ID)
171+
project=coderdtest.CreateTemplate(t,client,user.OrganizationID,version.ID,func(ctr*codersdk.CreateTemplateRequest) {
172+
ctr.MinAutostartIntervalMillis=ptr.Ref(time.Hour.Milliseconds())
173+
})
174+
workspace=coderdtest.CreateWorkspace(t,client,user.OrganizationID,project.ID)
175+
cmdArgs= []string{"autostart","enable",workspace.Name,"--minute","*","--hour","*"}
176+
)
177+
178+
cmd,root:=clitest.New(t,cmdArgs...)
179+
clitest.SetupConfig(t,client,root)
180+
181+
err:=cmd.Execute()
182+
require.ErrorContains(t,err,"schedule: Minimum autostart interval 1m0s below template minimum 1h0m0s")
183+
})
161184
}

‎cli/bump_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/coder/coder/cli/clitest"
1212
"github.com/coder/coder/coderd/coderdtest"
13+
"github.com/coder/coder/coderd/database"
1314
"github.com/coder/coder/codersdk"
1415
)
1516

@@ -152,12 +153,23 @@ func TestBump(t *testing.T) {
152153
cmdArgs= []string{"bump",workspace.Name}
153154
stdoutBuf=&bytes.Buffer{}
154155
)
156+
// Unset the workspace TTL
157+
err=client.UpdateWorkspaceTTL(ctx,workspace.ID, codersdk.UpdateWorkspaceTTLRequest{TTLMillis:nil})
158+
require.NoError(t,err)
159+
workspace,err=client.Workspace(ctx,workspace.ID)
160+
require.NoError(t,err)
161+
require.Nil(t,workspace.TTLMillis)
155162

156163
// Given: we wait for the workspace to build
157164
coderdtest.AwaitWorkspaceBuildJob(t,client,workspace.LatestBuild.ID)
158165
workspace,err=client.Workspace(ctx,workspace.ID)
159166
require.NoError(t,err)
160167

168+
// TODO(cian): need to stop and start the workspace as we do not update the deadline yet
169+
// see: https://github.com/coder/coder/issues/1783
170+
coderdtest.MustTransitionWorkspace(t,client,workspace.ID,database.WorkspaceTransitionStart,database.WorkspaceTransitionStop)
171+
coderdtest.MustTransitionWorkspace(t,client,workspace.ID,database.WorkspaceTransitionStop,database.WorkspaceTransitionStart)
172+
161173
// Assert test invariant: workspace has no TTL set
162174
require.Zero(t,workspace.LatestBuild.Deadline)
163175
require.NoError(t,err)

‎cli/create.go

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -61,20 +61,6 @@ func create() *cobra.Command {
6161
}
6262
}
6363

64-
tz,err:=time.LoadLocation(tzName)
65-
iferr!=nil {
66-
returnxerrors.Errorf("Invalid workspace autostart timezone: %w",err)
67-
}
68-
schedSpec:=fmt.Sprintf("CRON_TZ=%s %s %s * * %s",tz.String(),autostartMinute,autostartHour,autostartDow)
69-
_,err=schedule.Weekly(schedSpec)
70-
iferr!=nil {
71-
returnxerrors.Errorf("invalid workspace autostart schedule: %w",err)
72-
}
73-
74-
ifttl==0 {
75-
returnxerrors.Errorf("TTL must be at least 1 minute")
76-
}
77-
7864
_,err=client.WorkspaceByOwnerAndName(cmd.Context(),codersdk.Me,workspaceName)
7965
iferr==nil {
8066
returnxerrors.Errorf("A workspace already exists named %q!",workspaceName)
@@ -129,6 +115,23 @@ func create() *cobra.Command {
129115
}
130116
}
131117

118+
schedSpec,err:=validSchedule(
119+
autostartMinute,
120+
autostartHour,
121+
autostartDow,
122+
tzName,
123+
time.Duration(template.MinAutostartIntervalMillis)*time.Millisecond,
124+
)
125+
iferr!=nil {
126+
returnxerrors.Errorf("Invalid autostart schedule: %w",err)
127+
}
128+
ifttl<time.Minute {
129+
returnxerrors.Errorf("TTL must be at least 1 minute")
130+
}
131+
ifttlMax:=time.Duration(template.MaxTTLMillis)*time.Millisecond;ttl>ttlMax {
132+
returnxerrors.Errorf("TTL must be below template maximum %s",ttlMax)
133+
}
134+
132135
templateVersion,err:=client.TemplateVersion(cmd.Context(),template.ActiveVersionID)
133136
iferr!=nil {
134137
returnerr
@@ -226,7 +229,7 @@ func create() *cobra.Command {
226229
workspace,err:=client.CreateWorkspace(cmd.Context(),organization.ID, codersdk.CreateWorkspaceRequest{
227230
TemplateID:template.ID,
228231
Name:workspaceName,
229-
AutostartSchedule:&schedSpec,
232+
AutostartSchedule:schedSpec,
230233
TTLMillis:ptr.Ref(ttl.Milliseconds()),
231234
ParameterValues:parameters,
232235
})
@@ -262,7 +265,27 @@ func create() *cobra.Command {
262265
cliflag.StringVarP(cmd.Flags(),&autostartMinute,"autostart-minute","","CODER_WORKSPACE_AUTOSTART_MINUTE","0","Specify the minute(s) at which the workspace should autostart (e.g. 0).")
263266
cliflag.StringVarP(cmd.Flags(),&autostartHour,"autostart-hour","","CODER_WORKSPACE_AUTOSTART_HOUR","9","Specify the hour(s) at which the workspace should autostart (e.g. 9).")
264267
cliflag.StringVarP(cmd.Flags(),&autostartDow,"autostart-day-of-week","","CODER_WORKSPACE_AUTOSTART_DOW","MON-FRI","Specify the days(s) on which the workspace should autostart (e.g. MON,TUE,WED,THU,FRI)")
265-
cliflag.StringVarP(cmd.Flags(),&tzName,"tz","","TZ","","Specify your timezone location for workspace autostart (e.g. US/Central).")
268+
cliflag.StringVarP(cmd.Flags(),&tzName,"tz","","TZ","UTC","Specify your timezone location for workspace autostart (e.g. US/Central).")
266269
cliflag.DurationVarP(cmd.Flags(),&ttl,"ttl","","CODER_WORKSPACE_TTL",8*time.Hour,"Specify a time-to-live (TTL) for the workspace (e.g. 8h).")
267270
returncmd
268271
}
272+
273+
funcvalidSchedule(minute,hour,dow,tzNamestring,min time.Duration) (*string,error) {
274+
_,err:=time.LoadLocation(tzName)
275+
iferr!=nil {
276+
returnnil,xerrors.Errorf("Invalid workspace autostart timezone: %w",err)
277+
}
278+
279+
schedSpec:=fmt.Sprintf("CRON_TZ=%s %s %s * * %s",tzName,minute,hour,dow)
280+
281+
sched,err:=schedule.Weekly(schedSpec)
282+
iferr!=nil {
283+
returnnil,err
284+
}
285+
286+
ifschedMin:=sched.Min();schedMin<min {
287+
returnnil,xerrors.Errorf("minimum autostart interval %s is above template constraint %s",schedMin,min)
288+
}
289+
290+
return&schedSpec,nil
291+
}

‎cli/create_test.go

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/coder/coder/cli/clitest"
1515
"github.com/coder/coder/coderd/coderdtest"
1616
"github.com/coder/coder/coderd/database"
17+
"github.com/coder/coder/coderd/util/ptr"
1718
"github.com/coder/coder/codersdk"
1819
"github.com/coder/coder/provisioner/echo"
1920
"github.com/coder/coder/provisionersdk/proto"
@@ -62,6 +63,57 @@ func TestCreate(t *testing.T) {
6263
<-doneChan
6364
})
6465

66+
t.Run("AboveTemplateMaxTTL",func(t*testing.T) {
67+
t.Parallel()
68+
client:=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerD:true})
69+
user:=coderdtest.CreateFirstUser(t,client)
70+
version:=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,nil)
71+
coderdtest.AwaitTemplateVersionJob(t,client,version.ID)
72+
template:=coderdtest.CreateTemplate(t,client,user.OrganizationID,version.ID,func(ctr*codersdk.CreateTemplateRequest) {
73+
ctr.MaxTTLMillis=ptr.Ref((12*time.Hour).Milliseconds())
74+
})
75+
args:= []string{
76+
"create",
77+
"my-workspace",
78+
"--template",template.Name,
79+
"--ttl","12h1m",
80+
"-y",// don't bother with waiting
81+
}
82+
cmd,root:=clitest.New(t,args...)
83+
clitest.SetupConfig(t,client,root)
84+
pty:=ptytest.New(t)
85+
cmd.SetIn(pty.Input())
86+
cmd.SetOut(pty.Output())
87+
err:=cmd.Execute()
88+
assert.ErrorContains(t,err,"TTL must be below template maximum 12h0m0s")
89+
})
90+
91+
t.Run("BelowTemplateMinAutostartInterval",func(t*testing.T) {
92+
t.Parallel()
93+
client:=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerD:true})
94+
user:=coderdtest.CreateFirstUser(t,client)
95+
version:=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,nil)
96+
coderdtest.AwaitTemplateVersionJob(t,client,version.ID)
97+
template:=coderdtest.CreateTemplate(t,client,user.OrganizationID,version.ID,func(ctr*codersdk.CreateTemplateRequest) {
98+
ctr.MinAutostartIntervalMillis=ptr.Ref(time.Hour.Milliseconds())
99+
})
100+
args:= []string{
101+
"create",
102+
"my-workspace",
103+
"--template",template.Name,
104+
"--autostart-minute","*",// Every minute
105+
"--autostart-hour","*",// Every hour
106+
"-y",// don't bother with waiting
107+
}
108+
cmd,root:=clitest.New(t,args...)
109+
clitest.SetupConfig(t,client,root)
110+
pty:=ptytest.New(t)
111+
cmd.SetIn(pty.Input())
112+
cmd.SetOut(pty.Output())
113+
err:=cmd.Execute()
114+
assert.ErrorContains(t,err,"minimum autostart interval 1m0s is above template constraint 1h0m0s")
115+
})
116+
65117
t.Run("CreateErrInvalidTz",func(t*testing.T) {
66118
t.Parallel()
67119
client:=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerD:true})
@@ -74,19 +126,15 @@ func TestCreate(t *testing.T) {
74126
"my-workspace",
75127
"--template",template.Name,
76128
"--tz","invalid",
129+
"-y",
77130
}
78131
cmd,root:=clitest.New(t,args...)
79132
clitest.SetupConfig(t,client,root)
80-
doneChan:=make(chanstruct{})
81133
pty:=ptytest.New(t)
82134
cmd.SetIn(pty.Input())
83135
cmd.SetOut(pty.Output())
84-
gofunc() {
85-
deferclose(doneChan)
86-
err:=cmd.Execute()
87-
assert.EqualError(t,err,"Invalid workspace autostart timezone: unknown time zone invalid")
88-
}()
89-
<-doneChan
136+
err:=cmd.Execute()
137+
assert.ErrorContains(t,err,"Invalid autostart schedule: Invalid workspace autostart timezone: unknown time zone invalid")
90138
})
91139

92140
t.Run("CreateErrInvalidTTL",func(t*testing.T) {
@@ -101,19 +149,15 @@ func TestCreate(t *testing.T) {
101149
"my-workspace",
102150
"--template",template.Name,
103151
"--ttl","0s",
152+
"-y",
104153
}
105154
cmd,root:=clitest.New(t,args...)
106155
clitest.SetupConfig(t,client,root)
107-
doneChan:=make(chanstruct{})
108156
pty:=ptytest.New(t)
109157
cmd.SetIn(pty.Input())
110158
cmd.SetOut(pty.Output())
111-
gofunc() {
112-
deferclose(doneChan)
113-
err:=cmd.Execute()
114-
assert.EqualError(t,err,"TTL must be at least 1 minute")
115-
}()
116-
<-doneChan
159+
err:=cmd.Execute()
160+
assert.EqualError(t,err,"TTL must be at least 1 minute")
117161
})
118162

119163
t.Run("CreateFromListWithSkip",func(t*testing.T) {

‎cli/templatecreate.go

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,19 @@ import (
1414

1515
"github.com/coder/coder/cli/cliui"
1616
"github.com/coder/coder/coderd/database"
17+
"github.com/coder/coder/coderd/util/ptr"
1718
"github.com/coder/coder/codersdk"
1819
"github.com/coder/coder/provisionerd"
1920
"github.com/coder/coder/provisionersdk"
2021
)
2122

2223
functemplateCreate()*cobra.Command {
2324
var (
24-
directorystring
25-
provisionerstring
26-
parameterFilestring
25+
directorystring
26+
provisionerstring
27+
parameterFilestring
28+
maxTTL time.Duration
29+
minAutostartInterval time.Duration
2730
)
2831
cmd:=&cobra.Command{
2932
Use:"create [name]",
@@ -92,11 +95,15 @@ func templateCreate() *cobra.Command {
9295
returnerr
9396
}
9497

95-
_,err=client.CreateTemplate(cmd.Context(),organization.ID, codersdk.CreateTemplateRequest{
96-
Name:templateName,
97-
VersionID:job.ID,
98-
ParameterValues:parameters,
99-
})
98+
createReq:= codersdk.CreateTemplateRequest{
99+
Name:templateName,
100+
VersionID:job.ID,
101+
ParameterValues:parameters,
102+
MaxTTLMillis:ptr.Ref(maxTTL.Milliseconds()),
103+
MinAutostartIntervalMillis:ptr.Ref(minAutostartInterval.Milliseconds()),
104+
}
105+
106+
_,err=client.CreateTemplate(cmd.Context(),organization.ID,createReq)
100107
iferr!=nil {
101108
returnerr
102109
}
@@ -115,6 +122,8 @@ func templateCreate() *cobra.Command {
115122
cmd.Flags().StringVarP(&directory,"directory","d",currentDirectory,"Specify the directory to create from")
116123
cmd.Flags().StringVarP(&provisioner,"test.provisioner","","terraform","Customize the provisioner backend")
117124
cmd.Flags().StringVarP(&parameterFile,"parameter-file","","","Specify a file path with parameter values.")
125+
cmd.Flags().DurationVarP(&maxTTL,"max-ttl","",168*time.Hour,"Specify a maximum TTL for worksapces created from this template.")
126+
cmd.Flags().DurationVarP(&minAutostartInterval,"min-autostart-interval","",time.Hour,"Specify a minimum autostart interval for workspaces created from this template.")
118127
// This is for testing!
119128
err:=cmd.Flags().MarkHidden("test.provisioner")
120129
iferr!=nil {

‎cli/templatecreate_test.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,16 @@ func TestTemplateCreate(t *testing.T) {
2424
Parse:echo.ParseComplete,
2525
Provision:echo.ProvisionComplete,
2626
})
27-
cmd,root:=clitest.New(t,"templates","create","my-template","--directory",source,"--test.provisioner",string(database.ProvisionerTypeEcho))
27+
args:= []string{
28+
"templates",
29+
"create",
30+
"my-template",
31+
"--directory",source,
32+
"--test.provisioner",string(database.ProvisionerTypeEcho),
33+
"--max-ttl","24h",
34+
"--min-autostart-interval","2h",
35+
}
36+
cmd,root:=clitest.New(t,args...)
2837
clitest.SetupConfig(t,client,root)
2938
pty:=ptytest.New(t)
3039
cmd.SetIn(pty.Input())

‎cli/ttl_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,4 +168,37 @@ func TestTTL(t *testing.T) {
168168
err:=cmd.Execute()
169169
require.ErrorContains(t,err,"status code 403: Forbidden","unexpected error")
170170
})
171+
172+
t.Run("TemplateMaxTTL",func(t*testing.T) {
173+
t.Parallel()
174+
175+
var (
176+
ctx=context.Background()
177+
client=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerD:true})
178+
user=coderdtest.CreateFirstUser(t,client)
179+
version=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,nil)
180+
_=coderdtest.AwaitTemplateVersionJob(t,client,version.ID)
181+
project=coderdtest.CreateTemplate(t,client,user.OrganizationID,version.ID,func(ctr*codersdk.CreateTemplateRequest) {
182+
ctr.MaxTTLMillis=ptr.Ref((8*time.Hour).Milliseconds())
183+
})
184+
workspace=coderdtest.CreateWorkspace(t,client,user.OrganizationID,project.ID,func(cwr*codersdk.CreateWorkspaceRequest) {
185+
cwr.TTLMillis=ptr.Ref((8*time.Hour).Milliseconds())
186+
})
187+
cmdArgs= []string{"ttl","set",workspace.Name,"24h"}
188+
stdoutBuf=&bytes.Buffer{}
189+
)
190+
191+
cmd,root:=clitest.New(t,cmdArgs...)
192+
clitest.SetupConfig(t,client,root)
193+
cmd.SetOut(stdoutBuf)
194+
195+
err:=cmd.Execute()
196+
require.ErrorContains(t,err,"ttl_ms: ttl must be below template maximum 8h0m0s")
197+
198+
// Ensure ttl not updated
199+
updated,err:=client.Workspace(ctx,workspace.ID)
200+
require.NoError(t,err,"fetch updated workspace")
201+
require.NotNil(t,updated.TTLMillis)
202+
require.Equal(t, (8*time.Hour).Milliseconds(),*updated.TTLMillis)
203+
})
171204
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp