Skip to content

Commit 50f6300

Browse files
authored
Merge branch 'main' into kevinh-add-workspace-timing-metrics
2 parents 2f77a60 + 943dfc7 commit 50f6300

File tree

25 files changed

+770
-52
lines changed

25 files changed

+770
-52
lines changed

.github/workflows/ci.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -630,11 +630,8 @@ jobs:
630630
working-directory: site
631631

632632
test-e2e:
633-
# test-e2e fails on 2-core 8GB runners, so we use the 4-core 16GB runner
634633
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-4' || 'ubuntu-latest' }}
635634
needs: changes
636-
if: needs.changes.outputs.go == 'true' || needs.changes.outputs.ts == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
637-
timeout-minutes: 20
638635
strategy:
639636
fail-fast: false
640637
matrix:
@@ -643,6 +640,9 @@ jobs:
643640
name: test-e2e
644641
- premium: true
645642
name: test-e2e-premium
643+
# Skip test-e2e on forks as they don't have access to CI secrets
644+
if: (needs.changes.outputs.go == 'true' || needs.changes.outputs.ts == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main') && !(github.event.pull_request.head.repo.fork)
645+
timeout-minutes: 20
646646
name: ${{ matrix.variant.name }}
647647
steps:
648648
- name: Harden Runner
@@ -749,7 +749,7 @@ jobs:
749749
# Prevent excessive build runs on minor version changes
750750
skip: "@(renovate/**|dependabot/**)"
751751
# Run TurboSnap to trace file dependencies to related stories
752-
# and tell chromatic to only take snapshots of relevent stories
752+
# and tell chromatic to only take snapshots of relevant stories
753753
onlyChanged: true
754754
# Avoid uploading single files, because that's very slow
755755
zip: true
@@ -776,7 +776,7 @@ jobs:
776776
workingDir: "./site"
777777
storybookBaseDir: "./site"
778778
# Run TurboSnap to trace file dependencies to related stories
779-
# and tell chromatic to only take snapshots of relevent stories
779+
# and tell chromatic to only take snapshots of relevant stories
780780
onlyChanged: true
781781
# Avoid uploading single files, because that's very slow
782782
zip: true

.github/workflows/contrib.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ jobs:
5353
env:
5454
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
5555
# the below token should have repo scope and must be manually added by you in the repository's secret
56-
PERSONAL_ACCESS_TOKEN: ${{ secrets.CDRCOMMUNITY_GITHUB_TOKEN }}
56+
PERSONAL_ACCESS_TOKEN: ${{ secrets.CDRCI2_GITHUB_TOKEN }}
5757
with:
5858
remote-organization-name: "coder"
5959
remote-repository-name: "cla"

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: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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+
tests := []struct {
22+
name string
23+
fromTTL time.Duration
24+
toTTL time.Duration
25+
expected sql.NullInt64
26+
}{
27+
{
28+
name: "ModifyTTLDurationDown",
29+
fromTTL: 24 * time.Hour,
30+
toTTL: 1 * time.Hour,
31+
expected: sql.NullInt64{Valid: true, Int64: int64(1 * time.Hour)},
32+
},
33+
{
34+
name: "ModifyTTLDurationUp",
35+
fromTTL: 24 * time.Hour,
36+
toTTL: 36 * time.Hour,
37+
expected: sql.NullInt64{Valid: true, Int64: int64(36 * time.Hour)},
38+
},
39+
{
40+
name: "ModifyTTLDurationSame",
41+
fromTTL: 24 * time.Hour,
42+
toTTL: 24 * time.Hour,
43+
expected: sql.NullInt64{Valid: true, Int64: int64(24 * time.Hour)},
44+
},
45+
{
46+
name: "DisableTTL",
47+
fromTTL: 24 * time.Hour,
48+
toTTL: 0,
49+
expected: sql.NullInt64{},
50+
},
51+
}
52+
53+
for _, tt := range tests {
54+
tt := tt
55+
56+
t.Run(tt.name, func(t *testing.T) {
57+
t.Parallel()
58+
59+
var (
60+
db, _ = dbtestutil.NewDB(t)
61+
ctx = testutil.Context(t, testutil.WaitLong)
62+
user = dbgen.User(t, db, database.User{})
63+
file = dbgen.File(t, db, database.File{CreatedBy: user.ID})
64+
// Create first template
65+
templateJob = dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
66+
FileID: file.ID,
67+
InitiatorID: user.ID,
68+
Tags: database.StringMap{"foo": "bar"},
69+
})
70+
templateVersion = dbgen.TemplateVersion(t, db, database.TemplateVersion{
71+
CreatedBy: user.ID,
72+
JobID: templateJob.ID,
73+
OrganizationID: templateJob.OrganizationID,
74+
})
75+
template = dbgen.Template(t, db, database.Template{
76+
ActiveVersionID: templateVersion.ID,
77+
CreatedBy: user.ID,
78+
OrganizationID: templateJob.OrganizationID,
79+
})
80+
// Create second template
81+
otherTTL = tt.fromTTL + 6*time.Hour
82+
otherTemplateJob = dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
83+
FileID: file.ID,
84+
InitiatorID: user.ID,
85+
Tags: database.StringMap{"foo": "bar"},
86+
})
87+
otherTemplateVersion = dbgen.TemplateVersion(t, db, database.TemplateVersion{
88+
CreatedBy: user.ID,
89+
JobID: otherTemplateJob.ID,
90+
OrganizationID: otherTemplateJob.OrganizationID,
91+
})
92+
otherTemplate = dbgen.Template(t, db, database.Template{
93+
ActiveVersionID: otherTemplateVersion.ID,
94+
CreatedBy: user.ID,
95+
OrganizationID: otherTemplateJob.OrganizationID,
96+
})
97+
)
98+
99+
templateScheduleStore := schedule.NewAGPLTemplateScheduleStore()
100+
101+
// Set both template's default TTL
102+
template, err := templateScheduleStore.Set(ctx, db, template, schedule.TemplateScheduleOptions{
103+
DefaultTTL: tt.fromTTL,
104+
})
105+
require.NoError(t, err)
106+
otherTemplate, err = templateScheduleStore.Set(ctx, db, otherTemplate, schedule.TemplateScheduleOptions{
107+
DefaultTTL: otherTTL,
108+
})
109+
require.NoError(t, err)
110+
111+
// We create two workspaces here, one with the template we're modifying, the
112+
// other with a different template. We want to ensure we only modify one
113+
// of the workspaces.
114+
workspace := dbgen.Workspace(t, db, database.WorkspaceTable{
115+
OwnerID: user.ID,
116+
TemplateID: template.ID,
117+
OrganizationID: templateJob.OrganizationID,
118+
LastUsedAt: dbtime.Now(),
119+
Ttl: sql.NullInt64{Valid: true, Int64: int64(tt.fromTTL)},
120+
})
121+
otherWorkspace := dbgen.Workspace(t, db, database.WorkspaceTable{
122+
OwnerID: user.ID,
123+
TemplateID: otherTemplate.ID,
124+
OrganizationID: otherTemplateJob.OrganizationID,
125+
LastUsedAt: dbtime.Now(),
126+
Ttl: sql.NullInt64{Valid: true, Int64: int64(otherTTL)},
127+
})
128+
129+
// Ensure the workspace's start with the correct TTLs
130+
require.Equal(t, sql.NullInt64{Valid: true, Int64: int64(tt.fromTTL)}, workspace.Ttl)
131+
require.Equal(t, sql.NullInt64{Valid: true, Int64: int64(otherTTL)}, otherWorkspace.Ttl)
132+
133+
// Update _only_ the primary template's TTL
134+
_, err = templateScheduleStore.Set(ctx, db, template, schedule.TemplateScheduleOptions{
135+
DefaultTTL: tt.toTTL,
136+
})
137+
require.NoError(t, err)
138+
139+
// Verify the primary workspace's TTL has been updated.
140+
ws, err := db.GetWorkspaceByID(ctx, workspace.ID)
141+
require.NoError(t, err)
142+
require.Equal(t, tt.expected, ws.Ttl)
143+
144+
// Verify that the other workspace's TTL has not been touched.
145+
ws, err = db.GetWorkspaceByID(ctx, otherWorkspace.ID)
146+
require.NoError(t, err)
147+
require.Equal(t, sql.NullInt64{Valid: true, Int64: int64(otherTTL)}, ws.Ttl)
148+
})
149+
}
150+
}

enterprise/coderd/schedule/template.go

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

198+
// Update all workspace's TTL using this template if either of the following:
199+
// - The template's AllowUserAutostop has just been disabled
200+
// - The template's TTL has been modified and AllowUserAutostop is disabled
201+
if !opts.UserAutostopEnabled && (tpl.AllowUserAutostop || int64(opts.DefaultTTL) != tpl.DefaultTTL) {
202+
var ttl sql.NullInt64
203+
if opts.DefaultTTL != 0 {
204+
ttl = sql.NullInt64{Valid: true, Int64: int64(opts.DefaultTTL)}
205+
}
206+
207+
if err = tx.UpdateWorkspacesTTLByTemplateID(ctx, database.UpdateWorkspacesTTLByTemplateIDParams{
208+
TemplateID: template.ID,
209+
Ttl: ttl,
210+
}); err != nil {
211+
return xerrors.Errorf("update workspaces ttl by template id %q: %w", template.ID, err)
212+
}
213+
}
214+
198215
// Recalculate max_deadline and deadline for all running workspace
199216
// builds on this template.
200217
err = s.updateWorkspaceBuilds(ctx, tx, template)

0 commit comments

Comments
 (0)