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 <dannykopping@gmail.com> Co-authored-by: Danny Kopping <dannykopping@gmail.com> Co-authored-by: evgeniy-scherbina <evgeniy.shcherbina.es@gmail.com>
1 parent 4aa45a5 commit 99c6f23

22 files changed

+2110
-59
lines changed

coderd/database/dbauthz/dbauthz.go

Lines changed: 111 additions & 2 deletions
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

Lines changed: 90 additions & 0 deletions
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

Lines changed: 23 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy