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

Commiteba753b

Browse files
authored
fix: template: enforce bounds of template max_ttl (#3662)
This PR makes the following changes:- enforces lower and upper limits on template `max_ttl_ms`- adds a migration to enforce 7-day cap on `max_ttl`- allows setting template `max_ttl` to 0- updates template edit CLI help to be clearer
1 parent343d118 commiteba753b

File tree

9 files changed

+207
-25
lines changed

9 files changed

+207
-25
lines changed

‎cli/templateedit.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ func templateEdit() *cobra.Command {
5959
cmd.Flags().StringVarP(&name,"name","","","Edit the template name")
6060
cmd.Flags().StringVarP(&description,"description","","","Edit the template description")
6161
cmd.Flags().StringVarP(&icon,"icon","","","Edit the template icon path")
62-
cmd.Flags().DurationVarP(&maxTTL,"max-ttl","",0,"Edit the template maximum time before shutdown")
63-
cmd.Flags().DurationVarP(&minAutostartInterval,"min-autostart-interval","",0,"Edit the template minimum autostart interval")
62+
cmd.Flags().DurationVarP(&maxTTL,"max-ttl","",0,"Edit the template maximum time before shutdown - workspaces created from this template cannot stay running longer than this.")
63+
cmd.Flags().DurationVarP(&minAutostartInterval,"min-autostart-interval","",0,"Edit the template minimum autostart interval - workspaces created from this template must wait at least this long between autostarts.")
6464
cliui.AllowSkipPrompt(cmd)
6565

6666
returncmd

‎coderd/authorize.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func (api *API) Authorize(r *http.Request, action rbac.Action, object rbac.Objec
4949
// This function will log appropriately, but the caller must return an
5050
// error to the api client.
5151
// Eg:
52+
//
5253
//if !h.Authorize(...) {
5354
//httpapi.Forbidden(rw)
5455
//return

‎coderd/database/databasefake/databasefake.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1564,10 +1564,6 @@ func (q *fakeQuerier) InsertTemplate(_ context.Context, arg database.InsertTempl
15641564
q.mutex.Lock()
15651565
deferq.mutex.Unlock()
15661566

1567-
// default values
1568-
ifarg.MaxTtl==0 {
1569-
arg.MaxTtl=int64(168*time.Hour)
1570-
}
15711567
ifarg.MinAutostartInterval==0 {
15721568
arg.MinAutostartInterval=int64(time.Hour)
15731569
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-- this is a no-op
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- Set a cap of 7 days on template max_ttl
2+
UPDATE templatesSET max_ttl=604800000000000WHERE max_ttl>604800000000000;

‎coderd/templates.go

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,28 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
173173
}
174174

175175
maxTTL:=maxTTLDefault
176-
if!ptr.NilOrZero(createTemplate.MaxTTLMillis) {
176+
ifcreateTemplate.MaxTTLMillis!=nil {
177177
maxTTL=time.Duration(*createTemplate.MaxTTLMillis)*time.Millisecond
178178
}
179+
ifmaxTTL<0 {
180+
httpapi.Write(rw,http.StatusBadRequest, codersdk.Response{
181+
Message:"Invalid create template request.",
182+
Validations: []codersdk.ValidationError{
183+
{Field:"max_ttl_ms",Detail:"Must be a positive integer."},
184+
},
185+
})
186+
return
187+
}
188+
189+
ifmaxTTL>maxTTLDefault {
190+
httpapi.Write(rw,http.StatusBadRequest, codersdk.Response{
191+
Message:"Invalid create template request.",
192+
Validations: []codersdk.ValidationError{
193+
{Field:"max_ttl_ms",Detail:"Cannot be greater than "+maxTTLDefault.String()},
194+
},
195+
})
196+
return
197+
}
179198

180199
minAutostartInterval:=minAutostartIntervalDefault
181200
if!ptr.NilOrZero(createTemplate.MinAutostartIntervalMillis) {
@@ -384,6 +403,15 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
384403
ifreq.MinAutostartIntervalMillis<0 {
385404
validErrs=append(validErrs, codersdk.ValidationError{Field:"min_autostart_interval_ms",Detail:"Must be a positive integer."})
386405
}
406+
ifreq.MaxTTLMillis>maxTTLDefault.Milliseconds() {
407+
httpapi.Write(rw,http.StatusBadRequest, codersdk.Response{
408+
Message:"Invalid create template request.",
409+
Validations: []codersdk.ValidationError{
410+
{Field:"max_ttl_ms",Detail:"Cannot be greater than "+maxTTLDefault.String()},
411+
},
412+
})
413+
return
414+
}
387415

388416
iflen(validErrs)>0 {
389417
httpapi.Write(rw,http.StatusBadRequest, codersdk.Response{
@@ -433,9 +461,6 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
433461
ificon=="" {
434462
icon=template.Icon
435463
}
436-
ifmaxTTL==0 {
437-
maxTTL=time.Duration(template.MaxTtl)
438-
}
439464
ifminAutostartInterval==0 {
440465
minAutostartInterval=time.Duration(template.MinAutostartInterval)
441466
}

‎coderd/templates_test.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,64 @@ func TestPostTemplateByOrganization(t *testing.T) {
108108
require.Equal(t,http.StatusConflict,apiErr.StatusCode())
109109
})
110110

111+
t.Run("MaxTTLTooLow",func(t*testing.T) {
112+
t.Parallel()
113+
client:=coderdtest.New(t,nil)
114+
user:=coderdtest.CreateFirstUser(t,client)
115+
version:=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,nil)
116+
117+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitLong)
118+
defercancel()
119+
120+
_,err:=client.CreateTemplate(ctx,user.OrganizationID, codersdk.CreateTemplateRequest{
121+
Name:"testing",
122+
VersionID:version.ID,
123+
MaxTTLMillis:ptr.Ref(int64(-1)),
124+
})
125+
varapiErr*codersdk.Error
126+
require.ErrorAs(t,err,&apiErr)
127+
require.Equal(t,http.StatusBadRequest,apiErr.StatusCode())
128+
require.Contains(t,err.Error(),"max_ttl_ms: Must be a positive integer")
129+
})
130+
131+
t.Run("MaxTTLTooHigh",func(t*testing.T) {
132+
t.Parallel()
133+
client:=coderdtest.New(t,nil)
134+
user:=coderdtest.CreateFirstUser(t,client)
135+
version:=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,nil)
136+
137+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitLong)
138+
defercancel()
139+
140+
_,err:=client.CreateTemplate(ctx,user.OrganizationID, codersdk.CreateTemplateRequest{
141+
Name:"testing",
142+
VersionID:version.ID,
143+
MaxTTLMillis:ptr.Ref(365*24*time.Hour.Milliseconds()),
144+
})
145+
varapiErr*codersdk.Error
146+
require.ErrorAs(t,err,&apiErr)
147+
require.Equal(t,http.StatusBadRequest,apiErr.StatusCode())
148+
require.Contains(t,err.Error(),"max_ttl_ms: Cannot be greater than")
149+
})
150+
151+
t.Run("NoMaxTTL",func(t*testing.T) {
152+
t.Parallel()
153+
client:=coderdtest.New(t,nil)
154+
user:=coderdtest.CreateFirstUser(t,client)
155+
version:=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,nil)
156+
157+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitLong)
158+
defercancel()
159+
160+
got,err:=client.CreateTemplate(ctx,user.OrganizationID, codersdk.CreateTemplateRequest{
161+
Name:"testing",
162+
VersionID:version.ID,
163+
MaxTTLMillis:ptr.Ref(int64(0)),
164+
})
165+
require.NoError(t,err)
166+
require.Zero(t,got.MaxTTLMillis)
167+
})
168+
111169
t.Run("Unauthorized",func(t*testing.T) {
112170
t.Parallel()
113171
client:=coderdtest.New(t,nil)
@@ -271,6 +329,87 @@ func TestPatchTemplateMeta(t *testing.T) {
271329
assert.Equal(t,req.MinAutostartIntervalMillis,updated.MinAutostartIntervalMillis)
272330
})
273331

332+
t.Run("NoMaxTTL",func(t*testing.T) {
333+
t.Parallel()
334+
335+
client:=coderdtest.New(t,nil)
336+
user:=coderdtest.CreateFirstUser(t,client)
337+
version:=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,nil)
338+
template:=coderdtest.CreateTemplate(t,client,user.OrganizationID,version.ID,func(ctr*codersdk.CreateTemplateRequest) {
339+
ctr.MaxTTLMillis=ptr.Ref(24*time.Hour.Milliseconds())
340+
})
341+
req:= codersdk.UpdateTemplateMeta{
342+
MaxTTLMillis:0,
343+
}
344+
345+
// We're too fast! Sleep so we can be sure that updatedAt is greater
346+
time.Sleep(time.Millisecond*5)
347+
348+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitLong)
349+
defercancel()
350+
351+
_,err:=client.UpdateTemplateMeta(ctx,template.ID,req)
352+
require.NoError(t,err)
353+
354+
// Extra paranoid: did it _really_ happen?
355+
updated,err:=client.Template(ctx,template.ID)
356+
require.NoError(t,err)
357+
assert.Greater(t,updated.UpdatedAt,template.UpdatedAt)
358+
assert.Equal(t,req.MaxTTLMillis,updated.MaxTTLMillis)
359+
})
360+
361+
t.Run("MaxTTLTooLow",func(t*testing.T) {
362+
t.Parallel()
363+
364+
client:=coderdtest.New(t,nil)
365+
user:=coderdtest.CreateFirstUser(t,client)
366+
version:=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,nil)
367+
template:=coderdtest.CreateTemplate(t,client,user.OrganizationID,version.ID,func(ctr*codersdk.CreateTemplateRequest) {
368+
ctr.MaxTTLMillis=ptr.Ref(24*time.Hour.Milliseconds())
369+
})
370+
req:= codersdk.UpdateTemplateMeta{
371+
MaxTTLMillis:-1,
372+
}
373+
374+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitLong)
375+
defercancel()
376+
377+
_,err:=client.UpdateTemplateMeta(ctx,template.ID,req)
378+
require.ErrorContains(t,err,"max_ttl_ms: Must be a positive integer")
379+
380+
// Ensure no update occurred
381+
updated,err:=client.Template(ctx,template.ID)
382+
require.NoError(t,err)
383+
assert.Equal(t,updated.UpdatedAt,template.UpdatedAt)
384+
assert.Equal(t,updated.MaxTTLMillis,template.MaxTTLMillis)
385+
})
386+
387+
t.Run("MaxTTLTooHigh",func(t*testing.T) {
388+
t.Parallel()
389+
390+
client:=coderdtest.New(t,nil)
391+
user:=coderdtest.CreateFirstUser(t,client)
392+
version:=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,nil)
393+
template:=coderdtest.CreateTemplate(t,client,user.OrganizationID,version.ID,func(ctr*codersdk.CreateTemplateRequest) {
394+
ctr.MaxTTLMillis=ptr.Ref(24*time.Hour.Milliseconds())
395+
})
396+
req:= codersdk.UpdateTemplateMeta{
397+
MaxTTLMillis:365*24*time.Hour.Milliseconds(),
398+
}
399+
400+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitLong)
401+
defercancel()
402+
403+
_,err:=client.UpdateTemplateMeta(ctx,template.ID,req)
404+
require.ErrorContains(t,err,"max_ttl_ms: Cannot be greater than")
405+
406+
// Ensure no update occurred
407+
updated,err:=client.Template(ctx,template.ID)
408+
require.NoError(t,err)
409+
assert.Equal(t,updated.UpdatedAt,template.UpdatedAt)
410+
assert.Equal(t,updated.MaxTTLMillis,template.MaxTTLMillis)
411+
})
412+
274413
t.Run("NotModified",func(t*testing.T) {
275414
t.Parallel()
276415

‎coderd/workspaces.go

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ import (
3232
"github.com/coder/coder/codersdk"
3333
)
3434

35-
constworkspaceDefaultTTL=12*time.Hour
36-
3735
var (
3836
ttlMin=time.Minute//nolint:revive // min here means 'minimum' not 'minutes'
3937
ttlMax=7*24*time.Hour
@@ -312,11 +310,6 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
312310
return
313311
}
314312

315-
if!dbTTL.Valid {
316-
// Default to min(12 hours, template maximum). Just defaulting to template maximum can be surprising.
317-
dbTTL= sql.NullInt64{Valid:true,Int64:min(template.MaxTtl,int64(workspaceDefaultTTL))}
318-
}
319-
320313
workspace,err:=api.Database.GetWorkspaceByOwnerIDAndName(r.Context(), database.GetWorkspaceByOwnerIDAndNameParams{
321314
OwnerID:apiKey.UserID,
322315
Name:createWorkspace.Name,
@@ -923,7 +916,8 @@ func validWorkspaceTTLMillis(millis *int64, max time.Duration) (sql.NullInt64, e
923916
return sql.NullInt64{},errTTLMax
924917
}
925918

926-
iftruncated>max {
919+
// template level
920+
ifmax>0&&truncated>max {
927921
return sql.NullInt64{},xerrors.Errorf("time until shutdown must be below template maximum %s",max.String())
928922
}
929923

@@ -1050,10 +1044,3 @@ func splitQueryParameterByDelimiter(query string, delimiter rune, maintainQuotes
10501044

10511045
returnparts
10521046
}
1053-
1054-
funcmin(x,yint64)int64 {
1055-
ifx<y {
1056-
returnx
1057-
}
1058-
returny
1059-
}

‎coderd/workspaces_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,26 @@ func TestPostWorkspacesByOrganization(t *testing.T) {
191191
_=coderdtest.CreateWorkspace(t,client,user.OrganizationID,template.ID)
192192
})
193193

194+
t.Run("TemplateNoTTL",func(t*testing.T) {
195+
t.Parallel()
196+
client:=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerD:true})
197+
user:=coderdtest.CreateFirstUser(t,client)
198+
version:=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,nil)
199+
template:=coderdtest.CreateTemplate(t,client,user.OrganizationID,version.ID,func(ctr*codersdk.CreateTemplateRequest) {
200+
ctr.MaxTTLMillis=ptr.Ref(int64(0))
201+
})
202+
// Given: the template has no max TTL set
203+
require.Zero(t,template.MaxTTLMillis)
204+
coderdtest.AwaitTemplateVersionJob(t,client,version.ID)
205+
206+
// When: we create a workspace with autostop not enabled
207+
workspace:=coderdtest.CreateWorkspace(t,client,user.OrganizationID,template.ID,func(cwr*codersdk.CreateWorkspaceRequest) {
208+
cwr.TTLMillis=ptr.Ref(int64(0))
209+
})
210+
// Then: No TTL should be set by the template
211+
require.Nil(t,workspace.TTLMillis)
212+
})
213+
194214
t.Run("TemplateCustomTTL",func(t*testing.T) {
195215
t.Parallel()
196216
client:=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerD:true})
@@ -1051,6 +1071,17 @@ func TestWorkspaceUpdateTTL(t *testing.T) {
10511071
expectedError:"ttl_ms: time until shutdown must be below template maximum 8h0m0s",
10521072
modifyTemplate:func(ctr*codersdk.CreateTemplateRequest) {ctr.MaxTTLMillis=ptr.Ref((8*time.Hour).Milliseconds()) },
10531073
},
1074+
{
1075+
name:"no template maximum ttl",
1076+
ttlMillis:ptr.Ref((7*24*time.Hour).Milliseconds()),
1077+
modifyTemplate:func(ctr*codersdk.CreateTemplateRequest) {ctr.MaxTTLMillis=ptr.Ref(int64(0)) },
1078+
},
1079+
{
1080+
name:"above maximum ttl even with no template max",
1081+
ttlMillis:ptr.Ref((365*24*time.Hour).Milliseconds()),
1082+
expectedError:"ttl_ms: time until shutdown must be less than 7 days",
1083+
modifyTemplate:func(ctr*codersdk.CreateTemplateRequest) {ctr.MaxTTLMillis=ptr.Ref(int64(0)) },
1084+
},
10541085
}
10551086

10561087
for_,testCase:=rangetestCases {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp