feat(coderd/database): add task status and status view (#20235)

This change updates the `task_workspace_apps` table structure for
improved linking to workspace builds and adds queries to manage tasks
and a view to expose task status.

Updates coder/internal#948
Supersedes coder/coder#20212
Supersedes coder/coder#19773
This commit is contained in:
Mathias Fredriksson
2025-10-13 12:25:58 +03:00
committed by GitHub
parent 299a54a99b
commit 952c69f412
19 changed files with 1249 additions and 130 deletions
+31
View File
@@ -2885,6 +2885,14 @@ func (q *querier) GetTailnetTunnelPeerIDs(ctx context.Context, srcID uuid.UUID)
return q.db.GetTailnetTunnelPeerIDs(ctx, srcID)
}
func (q *querier) GetTaskByID(ctx context.Context, id uuid.UUID) (database.Task, error) {
return fetch(q.log, q.auth, q.db.GetTaskByID)(ctx, id)
}
func (q *querier) GetTaskByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (database.Task, error) {
return fetch(q.log, q.auth, q.db.GetTaskByWorkspaceID)(ctx, workspaceID)
}
func (q *querier) GetTelemetryItem(ctx context.Context, key string) (database.TelemetryItem, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
return database.TelemetryItem{}, err
@@ -4110,6 +4118,17 @@ func (q *querier) InsertReplica(ctx context.Context, arg database.InsertReplicaP
return q.db.InsertReplica(ctx, arg)
}
func (q *querier) InsertTask(ctx context.Context, arg database.InsertTaskParams) (database.TaskTable, error) {
// Ensure the actor can access the specified template version (and thus its template).
if _, err := q.GetTemplateVersionByID(ctx, arg.TemplateVersionID); err != nil {
return database.TaskTable{}, err
}
obj := rbac.ResourceTask.WithOwner(arg.OwnerID.String()).InOrg(arg.OrganizationID)
return insert(q.log, q.auth, obj, q.db.InsertTask)(ctx, arg)
}
func (q *querier) InsertTelemetryItemIfNotExists(ctx context.Context, arg database.InsertTelemetryItemIfNotExistsParams) error {
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceSystem); err != nil {
return err
@@ -5668,6 +5687,18 @@ func (q *querier) UpsertTailnetTunnel(ctx context.Context, arg database.UpsertTa
return q.db.UpsertTailnetTunnel(ctx, arg)
}
func (q *querier) UpsertTaskWorkspaceApp(ctx context.Context, arg database.UpsertTaskWorkspaceAppParams) (database.TaskWorkspaceApp, error) {
// Fetch the task to derive the RBAC object and authorize update on it.
task, err := q.db.GetTaskByID(ctx, arg.TaskID)
if err != nil {
return database.TaskWorkspaceApp{}, err
}
if err := q.authorizeContext(ctx, policy.ActionUpdate, task); err != nil {
return database.TaskWorkspaceApp{}, err
}
return q.db.UpsertTaskWorkspaceApp(ctx, arg)
}
func (q *querier) UpsertTelemetryItem(ctx context.Context, arg database.UpsertTelemetryItemParams) error {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceSystem); err != nil {
return err
+47
View File
@@ -2347,6 +2347,53 @@ func (s *MethodTestSuite) TestWorkspacePortSharing() {
}))
}
func (s *MethodTestSuite) TestTasks() {
s.Run("GetTaskByID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
task := testutil.Fake(s.T(), faker, database.Task{})
dbm.EXPECT().GetTaskByID(gomock.Any(), task.ID).Return(task, nil).AnyTimes()
check.Args(task.ID).Asserts(task, policy.ActionRead).Returns(task)
}))
s.Run("InsertTask", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
tpl := testutil.Fake(s.T(), faker, database.Template{})
tv := testutil.Fake(s.T(), faker, database.TemplateVersion{
TemplateID: uuid.NullUUID{UUID: tpl.ID, Valid: true},
OrganizationID: tpl.OrganizationID,
})
arg := testutil.Fake(s.T(), faker, database.InsertTaskParams{
OrganizationID: tpl.OrganizationID,
TemplateVersionID: tv.ID,
})
dbm.EXPECT().GetTemplateVersionByID(gomock.Any(), tv.ID).Return(tv, nil).AnyTimes()
dbm.EXPECT().GetTemplateByID(gomock.Any(), tpl.ID).Return(tpl, nil).AnyTimes()
dbm.EXPECT().InsertTask(gomock.Any(), arg).Return(database.TaskTable{}, nil).AnyTimes()
check.Args(arg).Asserts(
tpl, policy.ActionRead,
rbac.ResourceTask.InOrg(arg.OrganizationID).WithOwner(arg.OwnerID.String()), policy.ActionCreate,
).Returns(database.TaskTable{})
}))
s.Run("UpsertTaskWorkspaceApp", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
task := testutil.Fake(s.T(), faker, database.Task{})
arg := database.UpsertTaskWorkspaceAppParams{
TaskID: task.ID,
WorkspaceBuildNumber: 1,
}
dbm.EXPECT().GetTaskByID(gomock.Any(), task.ID).Return(task, nil).AnyTimes()
dbm.EXPECT().UpsertTaskWorkspaceApp(gomock.Any(), arg).Return(database.TaskWorkspaceApp{}, nil).AnyTimes()
check.Args(arg).Asserts(task, policy.ActionUpdate).Returns(database.TaskWorkspaceApp{})
}))
s.Run("GetTaskByWorkspaceID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
task := testutil.Fake(s.T(), faker, database.Task{})
task.WorkspaceID = uuid.NullUUID{UUID: uuid.New(), Valid: true}
dbm.EXPECT().GetTaskByWorkspaceID(gomock.Any(), task.WorkspaceID.UUID).Return(task, nil).AnyTimes()
check.Args(task.WorkspaceID.UUID).Asserts(task, policy.ActionRead).Returns(task)
}))
}
func (s *MethodTestSuite) TestProvisionerKeys() {
s.Run("InsertProvisionerKey", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
org := testutil.Fake(s.T(), faker, database.Organization{})