Skip to content

Commit 71e9fe8

Browse files
committed
feat: add API for setting template max_ttl
1 parent 3a8cb3a commit 71e9fe8

File tree

16 files changed

+253
-80
lines changed

16 files changed

+253
-80
lines changed

coderd/apidoc/docs.go

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/dbauthz/querier.go

+7
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,13 @@ func (q *querier) UpdateTemplateMetaByID(ctx context.Context, arg database.Updat
852852
return updateWithReturn(q.log, q.auth, fetch, q.db.UpdateTemplateMetaByID)(ctx, arg)
853853
}
854854

855+
func (q *querier) UpdateTemplateScheduleByID(ctx context.Context, arg database.UpdateTemplateScheduleByIDParams) (database.Template, error) {
856+
fetch := func(ctx context.Context, arg database.UpdateTemplateScheduleByIDParams) (database.Template, error) {
857+
return q.db.GetTemplateByID(ctx, arg.ID)
858+
}
859+
return updateWithReturn(q.log, q.auth, fetch, q.db.UpdateTemplateScheduleByID)(ctx, arg)
860+
}
861+
855862
func (q *querier) UpdateTemplateVersionByID(ctx context.Context, arg database.UpdateTemplateVersionByIDParams) error {
856863
template, err := q.db.GetTemplateByID(ctx, arg.TemplateID.UUID)
857864
if err != nil {

coderd/database/dbfake/databasefake.go

+23-2
Original file line numberDiff line numberDiff line change
@@ -1644,8 +1644,8 @@ func (q *fakeQuerier) UpdateTemplateMetaByID(_ context.Context, arg database.Upd
16441644
return database.Template{}, err
16451645
}
16461646

1647-
q.mutex.RLock()
1648-
defer q.mutex.RUnlock()
1647+
q.mutex.Lock()
1648+
defer q.mutex.Unlock()
16491649

16501650
for idx, tpl := range q.templates {
16511651
if tpl.ID != arg.ID {
@@ -1656,7 +1656,28 @@ func (q *fakeQuerier) UpdateTemplateMetaByID(_ context.Context, arg database.Upd
16561656
tpl.DisplayName = arg.DisplayName
16571657
tpl.Description = arg.Description
16581658
tpl.Icon = arg.Icon
1659+
q.templates[idx] = tpl
1660+
return tpl, nil
1661+
}
1662+
1663+
return database.Template{}, sql.ErrNoRows
1664+
}
1665+
1666+
func (q *fakeQuerier) UpdateTemplateScheduleByID(_ context.Context, arg database.UpdateTemplateScheduleByIDParams) (database.Template, error) {
1667+
if err := validateDatabaseType(arg); err != nil {
1668+
return database.Template{}, err
1669+
}
1670+
1671+
q.mutex.Lock()
1672+
defer q.mutex.Unlock()
1673+
1674+
for idx, tpl := range q.templates {
1675+
if tpl.ID != arg.ID {
1676+
continue
1677+
}
1678+
tpl.UpdatedAt = database.Now()
16591679
tpl.DefaultTTL = arg.DefaultTTL
1680+
tpl.MaxTTL = arg.MaxTTL
16601681
q.templates[idx] = tpl
16611682
return tpl, nil
16621683
}

coderd/database/querier.go

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

+55-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/templates.sql

+16-5
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,22 @@ UPDATE
102102
SET
103103
updated_at = $2,
104104
description = $3,
105-
default_ttl = $4,
106-
name = $5,
107-
icon = $6,
108-
display_name = $7,
109-
allow_user_cancel_workspace_jobs = $8
105+
name = $4,
106+
icon = $5,
107+
display_name = $6,
108+
allow_user_cancel_workspace_jobs = $7
109+
WHERE
110+
id = $1
111+
RETURNING
112+
*;
113+
114+
-- name: UpdateTemplateScheduleByID :one
115+
UPDATE
116+
templates
117+
SET
118+
updated_at = $2,
119+
default_ttl = $3,
120+
max_ttl = $4
110121
WHERE
111122
id = $1
112123
RETURNING

coderd/provisionerdserver/templatescheduleoptions.go

+16-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type TemplateScheduleOptions struct {
2323
// scheduling options set by the template/site admin.
2424
type TemplateScheduleStore interface {
2525
GetTemplateScheduleOptions(ctx context.Context, db database.Store, templateID uuid.UUID) (TemplateScheduleOptions, error)
26+
SetTemplateScheduleOptions(ctx context.Context, db database.Store, template database.Template, opts TemplateScheduleOptions) (database.Template, error)
2627
}
2728

2829
type agplTemplateScheduleStore struct{}
@@ -33,7 +34,7 @@ func NewAGPLTemplateScheduleStore() TemplateScheduleStore {
3334
return &agplTemplateScheduleStore{}
3435
}
3536

36-
func (s *agplTemplateScheduleStore) GetTemplateScheduleOptions(ctx context.Context, db database.Store, templateID uuid.UUID) (TemplateScheduleOptions, error) {
37+
func (*agplTemplateScheduleStore) GetTemplateScheduleOptions(ctx context.Context, db database.Store, templateID uuid.UUID) (TemplateScheduleOptions, error) {
3738
tpl, err := db.GetTemplateByID(ctx, templateID)
3839
if err != nil {
3940
return TemplateScheduleOptions{}, err
@@ -42,6 +43,19 @@ func (s *agplTemplateScheduleStore) GetTemplateScheduleOptions(ctx context.Conte
4243
return TemplateScheduleOptions{
4344
UserSchedulingEnabled: true,
4445
DefaultTTL: time.Duration(tpl.DefaultTTL),
45-
MaxTTL: 0,
46+
// Disregard the value in the database, since MaxTTL is an enterprise
47+
// feature.
48+
MaxTTL: 0,
4649
}, nil
4750
}
51+
52+
func (*agplTemplateScheduleStore) SetTemplateScheduleOptions(ctx context.Context, db database.Store, tpl database.Template, opts TemplateScheduleOptions) (database.Template, error) {
53+
return db.UpdateTemplateScheduleByID(ctx, database.UpdateTemplateScheduleByIDParams{
54+
ID: tpl.ID,
55+
UpdatedAt: database.Now(),
56+
DefaultTTL: int64(opts.DefaultTTL),
57+
// Don't allow changing it, but keep the value in the DB (to avoid
58+
// clearing settings if the license has an issue).
59+
MaxTTL: tpl.MaxTTL,
60+
})
61+
}

coderd/templates.go

+15-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/coder/coder/coderd/database"
1818
"github.com/coder/coder/coderd/httpapi"
1919
"github.com/coder/coder/coderd/httpmw"
20+
"github.com/coder/coder/coderd/provisionerdserver"
2021
"github.com/coder/coder/coderd/rbac"
2122
"github.com/coder/coder/coderd/telemetry"
2223
"github.com/coder/coder/codersdk"
@@ -479,7 +480,6 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
479480
displayName := req.DisplayName
480481
desc := req.Description
481482
icon := req.Icon
482-
defaultTTL := time.Duration(req.DefaultTTLMillis) * time.Millisecond
483483
allowUserCancelWorkspaceJobs := req.AllowUserCancelWorkspaceJobs
484484

485485
if name == "" {
@@ -497,11 +497,23 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
497497
DisplayName: displayName,
498498
Description: desc,
499499
Icon: icon,
500-
DefaultTTL: int64(defaultTTL),
501500
AllowUserCancelWorkspaceJobs: allowUserCancelWorkspaceJobs,
502501
})
503502
if err != nil {
504-
return err
503+
return xerrors.Errorf("update template metadata: %w", err)
504+
}
505+
506+
defaultTTL := time.Duration(req.DefaultTTLMillis) * time.Millisecond
507+
maxTTL := time.Duration(req.MaxTTLMillis) * time.Millisecond
508+
if defaultTTL != time.Duration(template.DefaultTTL) || maxTTL != time.Duration(template.MaxTTL) {
509+
updated, err = (*api.TemplateScheduleStore.Load()).SetTemplateScheduleOptions(ctx, tx, updated, provisionerdserver.TemplateScheduleOptions{
510+
UserSchedulingEnabled: true,
511+
DefaultTTL: defaultTTL,
512+
MaxTTL: maxTTL,
513+
})
514+
if err != nil {
515+
return xerrors.Errorf("set template schedule options: %w", err)
516+
}
505517
}
506518

507519
return nil

coderd/templates_test.go

+24
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,30 @@ func TestPatchTemplateMeta(t *testing.T) {
430430
require.NoError(t, err)
431431
assert.Equal(t, updated.Icon, "")
432432
})
433+
434+
t.Run("MaxTTLEnterpriseOnly", func(t *testing.T) {
435+
t.Parallel()
436+
437+
client := coderdtest.New(t, nil)
438+
user := coderdtest.CreateFirstUser(t, client)
439+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
440+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
441+
require.EqualValues(t, 0, template.MaxTTLMillis)
442+
req := codersdk.UpdateTemplateMeta{
443+
MaxTTLMillis: time.Hour.Milliseconds(),
444+
}
445+
446+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
447+
defer cancel()
448+
449+
updated, err := client.UpdateTemplateMeta(ctx, template.ID, req)
450+
require.NoError(t, err)
451+
require.EqualValues(t, 0, updated.MaxTTLMillis)
452+
453+
template, err = client.Template(ctx, template.ID)
454+
require.NoError(t, err)
455+
require.EqualValues(t, 0, template.MaxTTLMillis)
456+
})
433457
}
434458

435459
func TestDeleteTemplate(t *testing.T) {

codersdk/templates.go

+15-8
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,11 @@ type Template struct {
2828
Description string `json:"description"`
2929
Icon string `json:"icon"`
3030
DefaultTTLMillis int64 `json:"default_ttl_ms"`
31-
CreatedByID uuid.UUID `json:"created_by_id" format:"uuid"`
32-
CreatedByName string `json:"created_by_name"`
31+
// MaxTTLMillis is an enterprise feature. It's value is only used if your
32+
// license is entitled to use the advanced template scheduling feature.
33+
MaxTTLMillis int64 `json:"max_ttl_ms"`
34+
CreatedByID uuid.UUID `json:"created_by_id" format:"uuid"`
35+
CreatedByName string `json:"created_by_name"`
3336

3437
AllowUserCancelWorkspaceJobs bool `json:"allow_user_cancel_workspace_jobs"`
3538
}
@@ -73,12 +76,16 @@ type UpdateTemplateACL struct {
7376
}
7477

7578
type UpdateTemplateMeta struct {
76-
Name string `json:"name,omitempty" validate:"omitempty,template_name"`
77-
DisplayName string `json:"display_name,omitempty" validate:"omitempty,template_display_name"`
78-
Description string `json:"description,omitempty"`
79-
Icon string `json:"icon,omitempty"`
80-
DefaultTTLMillis int64 `json:"default_ttl_ms,omitempty"`
81-
AllowUserCancelWorkspaceJobs bool `json:"allow_user_cancel_workspace_jobs,omitempty"`
79+
Name string `json:"name,omitempty" validate:"omitempty,template_name"`
80+
DisplayName string `json:"display_name,omitempty" validate:"omitempty,template_display_name"`
81+
Description string `json:"description,omitempty"`
82+
Icon string `json:"icon,omitempty"`
83+
DefaultTTLMillis int64 `json:"default_ttl_ms,omitempty"`
84+
// MaxTTLMillis can only be set if your license includes the advanced
85+
// template scheduling feature. If you attempt to set this value while
86+
// unlicensed, it will be ignored.
87+
MaxTTLMillis int64 `json:"max_ttl_ms,omitempty"`
88+
AllowUserCancelWorkspaceJobs bool `json:"allow_user_cancel_workspace_jobs,omitempty"`
8289
}
8390

8491
type TemplateExample struct {

0 commit comments

Comments
 (0)