Skip to content

Commit 99c6f23

Browse files
SasSwartdannykoppingevgeniy-scherbina
authored
feat: add migrations and queries to support prebuilds (#16891)
Depends on #16916 _(change base to `main` once it is merged)_ Closes coder/internal#514 _This is one of several PRs to decompose the `dk/prebuilds` feature branch into separate PRs to merge into `main`._ --------- Signed-off-by: Danny Kopping <[email protected]> Co-authored-by: Danny Kopping <[email protected]> Co-authored-by: evgeniy-scherbina <[email protected]>
1 parent 4aa45a5 commit 99c6f23

22 files changed

+2110
-59
lines changed

coderd/database/dbauthz/dbauthz.go

+111-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818

1919
"cdr.dev/slog"
2020

21+
"github.com/coder/coder/v2/coderd/prebuilds"
2122
"github.com/coder/coder/v2/coderd/rbac/policy"
2223
"github.com/coder/coder/v2/coderd/rbac/rolestore"
2324

@@ -361,6 +362,27 @@ var (
361362
}),
362363
Scope: rbac.ScopeAll,
363364
}.WithCachedASTValue()
365+
366+
subjectPrebuildsOrchestrator = rbac.Subject{
367+
FriendlyName: "Prebuilds Orchestrator",
368+
ID: prebuilds.SystemUserID.String(),
369+
Roles: rbac.Roles([]rbac.Role{
370+
{
371+
Identifier: rbac.RoleIdentifier{Name: "prebuilds-orchestrator"},
372+
DisplayName: "Coder",
373+
Site: rbac.Permissions(map[string][]policy.Action{
374+
// May use template, read template-related info, & insert template-related resources (preset prebuilds).
375+
rbac.ResourceTemplate.Type: {policy.ActionRead, policy.ActionUpdate, policy.ActionUse, policy.ActionViewInsights},
376+
// May CRUD workspaces, and start/stop them.
377+
rbac.ResourceWorkspace.Type: {
378+
policy.ActionCreate, policy.ActionDelete, policy.ActionRead, policy.ActionUpdate,
379+
policy.ActionWorkspaceStart, policy.ActionWorkspaceStop,
380+
},
381+
}),
382+
},
383+
}),
384+
Scope: rbac.ScopeAll,
385+
}.WithCachedASTValue()
364386
)
365387

366388
// AsProvisionerd returns a context with an actor that has permissions required
@@ -415,6 +437,12 @@ func AsSystemReadProvisionerDaemons(ctx context.Context) context.Context {
415437
return context.WithValue(ctx, authContextKey{}, subjectSystemReadProvisionerDaemons)
416438
}
417439

440+
// AsPrebuildsOrchestrator returns a context with an actor that has permissions
441+
// to read orchestrator workspace prebuilds.
442+
func AsPrebuildsOrchestrator(ctx context.Context) context.Context {
443+
return context.WithValue(ctx, authContextKey{}, subjectPrebuildsOrchestrator)
444+
}
445+
418446
var AsRemoveActor = rbac.Subject{
419447
ID: "remove-actor",
420448
}
@@ -1109,6 +1137,31 @@ func (q *querier) BulkMarkNotificationMessagesSent(ctx context.Context, arg data
11091137
return q.db.BulkMarkNotificationMessagesSent(ctx, arg)
11101138
}
11111139

1140+
func (q *querier) ClaimPrebuiltWorkspace(ctx context.Context, arg database.ClaimPrebuiltWorkspaceParams) (database.ClaimPrebuiltWorkspaceRow, error) {
1141+
empty := database.ClaimPrebuiltWorkspaceRow{}
1142+
1143+
preset, err := q.db.GetPresetByID(ctx, arg.PresetID)
1144+
if err != nil {
1145+
return empty, err
1146+
}
1147+
1148+
workspaceObject := rbac.ResourceWorkspace.WithOwner(arg.NewUserID.String()).InOrg(preset.OrganizationID)
1149+
err = q.authorizeContext(ctx, policy.ActionCreate, workspaceObject.RBACObject())
1150+
if err != nil {
1151+
return empty, err
1152+
}
1153+
1154+
tpl, err := q.GetTemplateByID(ctx, preset.TemplateID.UUID)
1155+
if err != nil {
1156+
return empty, xerrors.Errorf("verify template by id: %w", err)
1157+
}
1158+
if err := q.authorizeContext(ctx, policy.ActionUse, tpl); err != nil {
1159+
return empty, xerrors.Errorf("use template for workspace: %w", err)
1160+
}
1161+
1162+
return q.db.ClaimPrebuiltWorkspace(ctx, arg)
1163+
}
1164+
11121165
func (q *querier) CleanTailnetCoordinators(ctx context.Context) error {
11131166
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceTailnetCoordinator); err != nil {
11141167
return err
@@ -1130,6 +1183,13 @@ func (q *querier) CleanTailnetTunnels(ctx context.Context) error {
11301183
return q.db.CleanTailnetTunnels(ctx)
11311184
}
11321185

1186+
func (q *querier) CountInProgressPrebuilds(ctx context.Context) ([]database.CountInProgressPrebuildsRow, error) {
1187+
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceWorkspace.All()); err != nil {
1188+
return nil, err
1189+
}
1190+
return q.db.CountInProgressPrebuilds(ctx)
1191+
}
1192+
11331193
func (q *querier) CountUnreadInboxNotificationsByUserID(ctx context.Context, userID uuid.UUID) (int64, error) {
11341194
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceInboxNotification.WithOwner(userID.String())); err != nil {
11351195
return 0, err
@@ -2096,6 +2156,30 @@ func (q *querier) GetParameterSchemasByJobID(ctx context.Context, jobID uuid.UUI
20962156
return q.db.GetParameterSchemasByJobID(ctx, jobID)
20972157
}
20982158

2159+
func (q *querier) GetPrebuildMetrics(ctx context.Context) ([]database.GetPrebuildMetricsRow, error) {
2160+
// GetPrebuildMetrics returns metrics related to prebuilt workspaces,
2161+
// such as the number of created and failed prebuilt workspaces.
2162+
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceWorkspace.All()); err != nil {
2163+
return nil, err
2164+
}
2165+
return q.db.GetPrebuildMetrics(ctx)
2166+
}
2167+
2168+
func (q *querier) GetPresetByID(ctx context.Context, presetID uuid.UUID) (database.GetPresetByIDRow, error) {
2169+
empty := database.GetPresetByIDRow{}
2170+
2171+
preset, err := q.db.GetPresetByID(ctx, presetID)
2172+
if err != nil {
2173+
return empty, err
2174+
}
2175+
_, err = q.GetTemplateByID(ctx, preset.TemplateID.UUID)
2176+
if err != nil {
2177+
return empty, err
2178+
}
2179+
2180+
return preset, nil
2181+
}
2182+
20992183
func (q *querier) GetPresetByWorkspaceBuildID(ctx context.Context, workspaceID uuid.UUID) (database.TemplateVersionPreset, error) {
21002184
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceTemplate); err != nil {
21012185
return database.TemplateVersionPreset{}, err
@@ -2113,6 +2197,14 @@ func (q *querier) GetPresetParametersByTemplateVersionID(ctx context.Context, te
21132197
return q.db.GetPresetParametersByTemplateVersionID(ctx, templateVersionID)
21142198
}
21152199

2200+
func (q *querier) GetPresetsBackoff(ctx context.Context, lookback time.Time) ([]database.GetPresetsBackoffRow, error) {
2201+
// GetPresetsBackoff returns a list of template version presets along with metadata such as the number of failed prebuilds.
2202+
if err := q.authorizeContext(ctx, policy.ActionViewInsights, rbac.ResourceTemplate.All()); err != nil {
2203+
return nil, err
2204+
}
2205+
return q.db.GetPresetsBackoff(ctx, lookback)
2206+
}
2207+
21162208
func (q *querier) GetPresetsByTemplateVersionID(ctx context.Context, templateVersionID uuid.UUID) ([]database.TemplateVersionPreset, error) {
21172209
// An actor can read template version presets if they can read the related template version.
21182210
_, err := q.GetTemplateVersionByID(ctx, templateVersionID)
@@ -2164,13 +2256,13 @@ func (q *querier) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (data
21642256
// can read the job.
21652257
_, err := q.GetWorkspaceBuildByJobID(ctx, id)
21662258
if err != nil {
2167-
return database.ProvisionerJob{}, err
2259+
return database.ProvisionerJob{}, xerrors.Errorf("fetch related workspace build: %w", err)
21682260
}
21692261
case database.ProvisionerJobTypeTemplateVersionDryRun, database.ProvisionerJobTypeTemplateVersionImport:
21702262
// Authorized call to get template version.
21712263
_, err := authorizedTemplateVersionFromJob(ctx, q, job)
21722264
if err != nil {
2173-
return database.ProvisionerJob{}, err
2265+
return database.ProvisionerJob{}, xerrors.Errorf("fetch related template version: %w", err)
21742266
}
21752267
default:
21762268
return database.ProvisionerJob{}, xerrors.Errorf("unknown job type: %q", job.Type)
@@ -2263,6 +2355,14 @@ func (q *querier) GetReplicasUpdatedAfter(ctx context.Context, updatedAt time.Ti
22632355
return q.db.GetReplicasUpdatedAfter(ctx, updatedAt)
22642356
}
22652357

2358+
func (q *querier) GetRunningPrebuiltWorkspaces(ctx context.Context) ([]database.GetRunningPrebuiltWorkspacesRow, error) {
2359+
// This query returns only prebuilt workspaces, but we decided to require permissions for all workspaces.
2360+
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceWorkspace.All()); err != nil {
2361+
return nil, err
2362+
}
2363+
return q.db.GetRunningPrebuiltWorkspaces(ctx)
2364+
}
2365+
22662366
func (q *querier) GetRuntimeConfig(ctx context.Context, key string) (string, error) {
22672367
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
22682368
return "", err
@@ -2387,6 +2487,15 @@ func (q *querier) GetTemplateParameterInsights(ctx context.Context, arg database
23872487
return q.db.GetTemplateParameterInsights(ctx, arg)
23882488
}
23892489

2490+
func (q *querier) GetTemplatePresetsWithPrebuilds(ctx context.Context, templateID uuid.NullUUID) ([]database.GetTemplatePresetsWithPrebuildsRow, error) {
2491+
// GetTemplatePresetsWithPrebuilds retrieves template versions with configured presets and prebuilds.
2492+
// Presets and prebuilds are part of the template, so if you can access templates - you can access them as well.
2493+
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceTemplate.All()); err != nil {
2494+
return nil, err
2495+
}
2496+
return q.db.GetTemplatePresetsWithPrebuilds(ctx, templateID)
2497+
}
2498+
23902499
func (q *querier) GetTemplateUsageStats(ctx context.Context, arg database.GetTemplateUsageStatsParams) ([]database.TemplateUsageStat, error) {
23912500
if err := q.authorizeTemplateInsights(ctx, arg.TemplateIDs); err != nil {
23922501
return nil, err

coderd/database/dbauthz/dbauthz_test.go

+90
Original file line numberDiff line numberDiff line change
@@ -4838,6 +4838,96 @@ func (s *MethodTestSuite) TestNotifications() {
48384838
}))
48394839
}
48404840

4841+
func (s *MethodTestSuite) TestPrebuilds() {
4842+
s.Run("ClaimPrebuiltWorkspace", s.Subtest(func(db database.Store, check *expects) {
4843+
org := dbgen.Organization(s.T(), db, database.Organization{})
4844+
user := dbgen.User(s.T(), db, database.User{})
4845+
template := dbgen.Template(s.T(), db, database.Template{
4846+
OrganizationID: org.ID,
4847+
CreatedBy: user.ID,
4848+
})
4849+
templateVersion := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{
4850+
TemplateID: uuid.NullUUID{
4851+
UUID: template.ID,
4852+
Valid: true,
4853+
},
4854+
OrganizationID: org.ID,
4855+
CreatedBy: user.ID,
4856+
})
4857+
preset := dbgen.Preset(s.T(), db, database.InsertPresetParams{
4858+
TemplateVersionID: templateVersion.ID,
4859+
})
4860+
check.Args(database.ClaimPrebuiltWorkspaceParams{
4861+
NewUserID: user.ID,
4862+
NewName: "",
4863+
PresetID: preset.ID,
4864+
}).Asserts(
4865+
rbac.ResourceWorkspace.WithOwner(user.ID.String()).InOrg(org.ID), policy.ActionCreate,
4866+
template, policy.ActionRead,
4867+
template, policy.ActionUse,
4868+
).ErrorsWithInMemDB(dbmem.ErrUnimplemented).
4869+
ErrorsWithPG(sql.ErrNoRows)
4870+
}))
4871+
s.Run("GetPrebuildMetrics", s.Subtest(func(_ database.Store, check *expects) {
4872+
check.Args().
4873+
Asserts(rbac.ResourceWorkspace.All(), policy.ActionRead).
4874+
ErrorsWithInMemDB(dbmem.ErrUnimplemented)
4875+
}))
4876+
s.Run("CountInProgressPrebuilds", s.Subtest(func(_ database.Store, check *expects) {
4877+
check.Args().
4878+
Asserts(rbac.ResourceWorkspace.All(), policy.ActionRead).
4879+
ErrorsWithInMemDB(dbmem.ErrUnimplemented)
4880+
}))
4881+
s.Run("GetPresetsBackoff", s.Subtest(func(_ database.Store, check *expects) {
4882+
check.Args(time.Time{}).
4883+
Asserts(rbac.ResourceTemplate.All(), policy.ActionViewInsights).
4884+
ErrorsWithInMemDB(dbmem.ErrUnimplemented)
4885+
}))
4886+
s.Run("GetRunningPrebuiltWorkspaces", s.Subtest(func(_ database.Store, check *expects) {
4887+
check.Args().
4888+
Asserts(rbac.ResourceWorkspace.All(), policy.ActionRead).
4889+
ErrorsWithInMemDB(dbmem.ErrUnimplemented)
4890+
}))
4891+
s.Run("GetTemplatePresetsWithPrebuilds", s.Subtest(func(db database.Store, check *expects) {
4892+
user := dbgen.User(s.T(), db, database.User{})
4893+
check.Args(uuid.NullUUID{UUID: user.ID, Valid: true}).
4894+
Asserts(rbac.ResourceTemplate.All(), policy.ActionRead).
4895+
ErrorsWithInMemDB(dbmem.ErrUnimplemented)
4896+
}))
4897+
s.Run("GetPresetByID", s.Subtest(func(db database.Store, check *expects) {
4898+
org := dbgen.Organization(s.T(), db, database.Organization{})
4899+
user := dbgen.User(s.T(), db, database.User{})
4900+
template := dbgen.Template(s.T(), db, database.Template{
4901+
OrganizationID: org.ID,
4902+
CreatedBy: user.ID,
4903+
})
4904+
templateVersion := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{
4905+
TemplateID: uuid.NullUUID{
4906+
UUID: template.ID,
4907+
Valid: true,
4908+
},
4909+
OrganizationID: org.ID,
4910+
CreatedBy: user.ID,
4911+
})
4912+
preset := dbgen.Preset(s.T(), db, database.InsertPresetParams{
4913+
TemplateVersionID: templateVersion.ID,
4914+
})
4915+
check.Args(preset.ID).
4916+
Asserts(template, policy.ActionRead).
4917+
Returns(database.GetPresetByIDRow{
4918+
ID: preset.ID,
4919+
TemplateVersionID: preset.TemplateVersionID,
4920+
Name: preset.Name,
4921+
CreatedAt: preset.CreatedAt,
4922+
TemplateID: uuid.NullUUID{
4923+
UUID: template.ID,
4924+
Valid: true,
4925+
},
4926+
OrganizationID: org.ID,
4927+
})
4928+
}))
4929+
}
4930+
48414931
func (s *MethodTestSuite) TestOAuth2ProviderApps() {
48424932
s.Run("GetOAuth2ProviderApps", s.Subtest(func(db database.Store, check *expects) {
48434933
apps := []database.OAuth2ProviderApp{

coderd/database/dbgen/dbgen.go

+23
Original file line numberDiff line numberDiff line change
@@ -1196,6 +1196,29 @@ func TelemetryItem(t testing.TB, db database.Store, seed database.TelemetryItem)
11961196
return item
11971197
}
11981198

1199+
func Preset(t testing.TB, db database.Store, seed database.InsertPresetParams) database.TemplateVersionPreset {
1200+
preset, err := db.InsertPreset(genCtx, database.InsertPresetParams{
1201+
TemplateVersionID: takeFirst(seed.TemplateVersionID, uuid.New()),
1202+
Name: takeFirst(seed.Name, testutil.GetRandomName(t)),
1203+
CreatedAt: takeFirst(seed.CreatedAt, dbtime.Now()),
1204+
DesiredInstances: seed.DesiredInstances,
1205+
InvalidateAfterSecs: seed.InvalidateAfterSecs,
1206+
})
1207+
require.NoError(t, err, "insert preset")
1208+
return preset
1209+
}
1210+
1211+
func PresetParameter(t testing.TB, db database.Store, seed database.InsertPresetParametersParams) []database.TemplateVersionPresetParameter {
1212+
parameters, err := db.InsertPresetParameters(genCtx, database.InsertPresetParametersParams{
1213+
TemplateVersionPresetID: takeFirst(seed.TemplateVersionPresetID, uuid.New()),
1214+
Names: takeFirstSlice(seed.Names, []string{testutil.GetRandomName(t)}),
1215+
Values: takeFirstSlice(seed.Values, []string{testutil.GetRandomName(t)}),
1216+
})
1217+
1218+
require.NoError(t, err, "insert preset parameters")
1219+
return parameters
1220+
}
1221+
11991222
func provisionerJobTiming(t testing.TB, db database.Store, seed database.ProvisionerJobTiming) database.ProvisionerJobTiming {
12001223
timing, err := db.InsertProvisionerJobTimings(genCtx, database.InsertProvisionerJobTimingsParams{
12011224
JobID: takeFirst(seed.JobID, uuid.New()),

0 commit comments

Comments
 (0)