mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(coderd): add task log snapshot storage endpoint (#21644)
This change adds a POST /workspaceagents/me/tasks/{task}/log-snapshot
endpoint for agents to upload task conversation history during
workspace shutdown. This allows users to view task logs even when the
workspace is stopped.
The endpoint accepts agentapi format payloads (typically last 10
messages, max 64KB), wraps them in a format envelope, and upserts to the
task_snapshots table. Uses agent token auth and validates the task
belongs to the agent's workspace.
Closes coder/internal#1253
This commit is contained in:
@@ -3055,6 +3055,25 @@ func (q *querier) GetTaskByWorkspaceID(ctx context.Context, workspaceID uuid.UUI
|
||||
return fetch(q.log, q.auth, q.db.GetTaskByWorkspaceID)(ctx, workspaceID)
|
||||
}
|
||||
|
||||
func (q *querier) GetTaskSnapshot(ctx context.Context, taskID uuid.UUID) (database.TaskSnapshot, error) {
|
||||
// Fetch task to build RBAC object for authorization.
|
||||
task, err := q.GetTaskByID(ctx, taskID)
|
||||
if err != nil {
|
||||
return database.TaskSnapshot{}, err
|
||||
}
|
||||
|
||||
obj := rbac.ResourceTask.
|
||||
WithID(task.ID).
|
||||
WithOwner(task.OwnerID.String()).
|
||||
InOrg(task.OrganizationID)
|
||||
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, obj); err != nil {
|
||||
return database.TaskSnapshot{}, err
|
||||
}
|
||||
|
||||
return q.db.GetTaskSnapshot(ctx, taskID)
|
||||
}
|
||||
|
||||
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
|
||||
@@ -6024,6 +6043,25 @@ func (q *querier) UpsertTailnetTunnel(ctx context.Context, arg database.UpsertTa
|
||||
return q.db.UpsertTailnetTunnel(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpsertTaskSnapshot(ctx context.Context, arg database.UpsertTaskSnapshotParams) error {
|
||||
// Fetch task to build RBAC object for authorization.
|
||||
task, err := q.GetTaskByID(ctx, arg.TaskID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
obj := rbac.ResourceTask.
|
||||
WithID(task.ID).
|
||||
WithOwner(task.OwnerID.String()).
|
||||
InOrg(task.OrganizationID)
|
||||
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, obj); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return q.db.UpsertTaskSnapshot(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)
|
||||
|
||||
@@ -2547,6 +2547,24 @@ func (s *MethodTestSuite) TestTasks() {
|
||||
dbm.EXPECT().ListTasks(gomock.Any(), gomock.Any()).Return([]database.Task{t1, t2}, nil).AnyTimes()
|
||||
check.Args(database.ListTasksParams{}).Asserts(t1, policy.ActionRead, t2, policy.ActionRead).Returns([]database.Task{t1, t2})
|
||||
}))
|
||||
s.Run("GetTaskSnapshot", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
task := testutil.Fake(s.T(), faker, database.Task{})
|
||||
snapshot := testutil.Fake(s.T(), faker, database.TaskSnapshot{TaskID: task.ID})
|
||||
dbm.EXPECT().GetTaskByID(gomock.Any(), task.ID).Return(task, nil).AnyTimes()
|
||||
dbm.EXPECT().GetTaskSnapshot(gomock.Any(), task.ID).Return(snapshot, nil).AnyTimes()
|
||||
check.Args(task.ID).Asserts(task, policy.ActionRead, task, policy.ActionRead).Returns(snapshot)
|
||||
}))
|
||||
s.Run("UpsertTaskSnapshot", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
task := testutil.Fake(s.T(), faker, database.Task{})
|
||||
arg := database.UpsertTaskSnapshotParams{
|
||||
TaskID: task.ID,
|
||||
LogSnapshot: []byte(`{"format":"agentapi","data":[]}`),
|
||||
LogSnapshotCreatedAt: dbtime.Now(),
|
||||
}
|
||||
dbm.EXPECT().GetTaskByID(gomock.Any(), task.ID).Return(task, nil).AnyTimes()
|
||||
dbm.EXPECT().UpsertTaskSnapshot(gomock.Any(), arg).Return(nil).AnyTimes()
|
||||
check.Args(arg).Asserts(task, policy.ActionRead, task, policy.ActionUpdate).Returns()
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *MethodTestSuite) TestProvisionerKeys() {
|
||||
|
||||
Reference in New Issue
Block a user