Skip to content

Commit f99d45d

Browse files
fix: update workspace TTL on template TTL change
1 parent ca81097 commit f99d45d

File tree

12 files changed

+324
-0
lines changed

12 files changed

+324
-0
lines changed

coderd/database/dbauthz/dbauthz.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4138,6 +4138,17 @@ func (q *querier) UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.Cont
41384138
return q.db.UpdateWorkspacesDormantDeletingAtByTemplateID(ctx, arg)
41394139
}
41404140

4141+
func (q *querier) UpdateWorkspacesTTLByTemplateID(ctx context.Context, arg database.UpdateWorkspacesTTLByTemplateIDParams) error {
4142+
template, err := q.db.GetTemplateByID(ctx, arg.TemplateID)
4143+
if err != nil {
4144+
return xerrors.Errorf("get template by id: %w", err)
4145+
}
4146+
if err := q.authorizeContext(ctx, policy.ActionUpdate, template); err != nil {
4147+
return err
4148+
}
4149+
return q.db.UpdateWorkspacesTTLByTemplateID(ctx, arg)
4150+
}
4151+
41414152
func (q *querier) UpsertAnnouncementBanners(ctx context.Context, value string) error {
41424153
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceDeploymentConfig); err != nil {
41434154
return err

coderd/database/dbauthz/dbauthz_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,12 @@ func (s *MethodTestSuite) TestTemplate() {
10171017
TemplateID: t1.ID,
10181018
}).Asserts(t1, policy.ActionUpdate)
10191019
}))
1020+
s.Run("UpdateWorkspacesTTLByTemplateID", s.Subtest(func(db database.Store, check *expects) {
1021+
t1 := dbgen.Template(s.T(), db, database.Template{})
1022+
check.Args(database.UpdateWorkspacesTTLByTemplateIDParams{
1023+
TemplateID: t1.ID,
1024+
}).Asserts(t1, policy.ActionUpdate)
1025+
}))
10201026
s.Run("UpdateTemplateActiveVersionByID", s.Subtest(func(db database.Store, check *expects) {
10211027
t1 := dbgen.Template(s.T(), db, database.Template{
10221028
ActiveVersionID: uuid.New(),

coderd/database/dbmem/dbmem.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10192,6 +10192,26 @@ func (q *FakeQuerier) UpdateWorkspacesDormantDeletingAtByTemplateID(_ context.Co
1019210192
return affectedRows, nil
1019310193
}
1019410194

10195+
func (q *FakeQuerier) UpdateWorkspacesTTLByTemplateID(_ context.Context, arg database.UpdateWorkspacesTTLByTemplateIDParams) error {
10196+
err := validateDatabaseType(arg)
10197+
if err != nil {
10198+
return err
10199+
}
10200+
10201+
q.mutex.Lock()
10202+
defer q.mutex.Unlock()
10203+
10204+
for i, ws := range q.workspaces {
10205+
if ws.TemplateID != arg.TemplateID {
10206+
continue
10207+
}
10208+
10209+
q.workspaces[i].Ttl = arg.Ttl
10210+
}
10211+
10212+
return nil
10213+
}
10214+
1019510215
func (q *FakeQuerier) UpsertAnnouncementBanners(_ context.Context, data string) error {
1019610216
q.mutex.RLock()
1019710217
defer q.mutex.RUnlock()

coderd/database/dbmetrics/querymetrics.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/dbmock/dbmock.go

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/querier.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/workspaces.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,14 @@ SET
501501
WHERE
502502
id = $1;
503503

504+
-- name: UpdateWorkspacesTTLByTemplateID :exec
505+
UPDATE
506+
workspaces
507+
SET
508+
ttl = $2
509+
WHERE
510+
template_id = $1;
511+
504512
-- name: UpdateWorkspaceLastUsedAt :exec
505513
UPDATE
506514
workspaces

coderd/schedule/template.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package schedule
22

33
import (
44
"context"
5+
"database/sql"
56
"time"
67

78
"github.com/google/uuid"
@@ -228,6 +229,23 @@ func (*agplTemplateScheduleStore) Set(ctx context.Context, db database.Store, tp
228229
return xerrors.Errorf("update template schedule: %w", err)
229230
}
230231

232+
// Users running the AGPL version are unable to customize their workspaces
233+
// autostop, so we want to keep their workspaces in track with any template
234+
// TTL changes.
235+
if tpl.DefaultTTL != int64(opts.DefaultTTL) {
236+
var ttl sql.NullInt64
237+
if opts.DefaultTTL != 0 {
238+
ttl = sql.NullInt64{Valid: true, Int64: int64(opts.DefaultTTL)}
239+
}
240+
241+
if err = db.UpdateWorkspacesTTLByTemplateID(ctx, database.UpdateWorkspacesTTLByTemplateIDParams{
242+
TemplateID: tpl.ID,
243+
Ttl: ttl,
244+
}); err != nil {
245+
return xerrors.Errorf("update workspace ttl by template id %q: %w", tpl.ID, err)
246+
}
247+
}
248+
231249
template, err = db.GetTemplateByID(ctx, tpl.ID)
232250
if err != nil {
233251
return xerrors.Errorf("fetch updated template: %w", err)

coderd/schedule/template_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package schedule_test
2+
3+
import (
4+
"database/sql"
5+
"testing"
6+
"time"
7+
8+
"github.com/stretchr/testify/require"
9+
10+
"github.com/coder/coder/v2/coderd/database"
11+
"github.com/coder/coder/v2/coderd/database/dbgen"
12+
"github.com/coder/coder/v2/coderd/database/dbtestutil"
13+
"github.com/coder/coder/v2/coderd/database/dbtime"
14+
"github.com/coder/coder/v2/coderd/schedule"
15+
"github.com/coder/coder/v2/testutil"
16+
)
17+
18+
func TestTemplateTTL(t *testing.T) {
19+
t.Parallel()
20+
21+
t.Run("ModifiesWorkspaceTTL", func(t *testing.T) {
22+
t.Parallel()
23+
24+
var (
25+
db, _ = dbtestutil.NewDB(t)
26+
ctx = testutil.Context(t, testutil.WaitLong)
27+
user = dbgen.User(t, db, database.User{})
28+
file = dbgen.File(t, db, database.File{CreatedBy: user.ID})
29+
templateJob = dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
30+
FileID: file.ID,
31+
InitiatorID: user.ID,
32+
Tags: database.StringMap{"foo": "bar"},
33+
})
34+
defaultTTL = 24 * time.Hour
35+
templateVersion = dbgen.TemplateVersion(t, db, database.TemplateVersion{
36+
CreatedBy: user.ID,
37+
JobID: templateJob.ID,
38+
OrganizationID: templateJob.OrganizationID,
39+
})
40+
template = dbgen.Template(t, db, database.Template{
41+
ActiveVersionID: templateVersion.ID,
42+
CreatedBy: user.ID,
43+
OrganizationID: templateJob.OrganizationID,
44+
DefaultTTL: int64(defaultTTL),
45+
})
46+
workspace = dbgen.Workspace(t, db, database.WorkspaceTable{
47+
OwnerID: user.ID,
48+
TemplateID: template.ID,
49+
OrganizationID: templateJob.OrganizationID,
50+
LastUsedAt: dbtime.Now(),
51+
Ttl: sql.NullInt64{Valid: true, Int64: int64(defaultTTL)},
52+
})
53+
)
54+
55+
templateScheduleStore := schedule.NewAGPLTemplateScheduleStore()
56+
57+
// We've created a template with a TTL of 24 hours, so we expect our
58+
// workspace to have a TTL of 24 hours.
59+
require.Equal(t, sql.NullInt64{Valid: true, Int64: int64(defaultTTL)}, workspace.Ttl)
60+
61+
// We expect an AGPL template schedule store to always update
62+
// the TTL of existing workspaces.
63+
_, err := templateScheduleStore.Set(ctx, db, template, schedule.TemplateScheduleOptions{
64+
DefaultTTL: 1 * time.Hour,
65+
})
66+
require.NoError(t, err)
67+
68+
// Verify that the workspace's TTL has been updated.
69+
ws, err := db.GetWorkspaceByID(ctx, workspace.ID)
70+
require.NoError(t, err)
71+
require.Equal(t, sql.NullInt64{Valid: true, Int64: int64(1 * time.Hour)}, ws.Ttl)
72+
})
73+
}

enterprise/coderd/schedule/template.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,22 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S
195195
return xerrors.Errorf("get updated template schedule: %w", err)
196196
}
197197

198+
// If this template disallows users from customizing their own autostop, then
199+
// we want to keep their workspaces in track with any template TTL changes.
200+
if !template.AllowUserAutostop && int64(opts.DefaultTTL) != tpl.DefaultTTL {
201+
var ttl sql.NullInt64
202+
if opts.DefaultTTL != 0 {
203+
ttl = sql.NullInt64{Valid: true, Int64: int64(opts.DefaultTTL)}
204+
}
205+
206+
if err = tx.UpdateWorkspacesTTLByTemplateID(ctx, database.UpdateWorkspacesTTLByTemplateIDParams{
207+
TemplateID: template.ID,
208+
Ttl: ttl,
209+
}); err != nil {
210+
return xerrors.Errorf("update workspaces ttl by template id %q: %w", template.ID, err)
211+
}
212+
}
213+
198214
// Recalculate max_deadline and deadline for all running workspace
199215
// builds on this template.
200216
err = s.updateWorkspaceBuilds(ctx, tx, template)

0 commit comments

Comments
 (0)