Skip to content

feat(coderd): add workspace timings endpoint #14648

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 71 additions & 0 deletions coderd/apidoc/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -1157,6 +1157,7 @@ func New(options *Options) *API {
r.Post("/", api.postWorkspaceAgentPortShare)
r.Delete("/", api.deleteWorkspaceAgentPortShare)
})
r.Get("/timings", api.workspaceTimings)
})
})
r.Route("/workspacebuilds/{workspacebuild}", func(r chi.Router) {
Expand Down
4 changes: 4 additions & 0 deletions coderd/database/dbauthz/dbauthz.go
Original file line number Diff line number Diff line change
Expand Up @@ -1791,6 +1791,10 @@ func (q *querier) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (data
return job, nil
}

func (q *querier) GetProvisionerJobTimingsByJobID(ctx context.Context, jobID uuid.UUID) ([]database.ProvisionerJobTiming, error) {
return q.db.GetProvisionerJobTimingsByJobID(ctx, jobID)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BrunoQuaresma I missed this yesterday; we need to ensure the user can read the workspace build in order to read the timings, otherwise we might leak some information.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any RBAC resource related to the workspace build. The closest that I see is the rbac.ResourceWorkspace, does it work?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can take a look at GetProvisionerLogsAfterID. As you can see, it is assumed that if you can fetch Job from the database, you can also read Logs

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mtojek even better, ty

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

// TODO: we need to add a provisioner job resource
func (q *querier) GetProvisionerJobsByIDs(ctx context.Context, ids []uuid.UUID) ([]database.ProvisionerJob, error) {
// if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
Expand Down
17 changes: 17 additions & 0 deletions coderd/database/dbauthz/dbauthz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,23 @@ func (s *MethodTestSuite) TestProvisionerJob() {
check.Args(database.UpdateProvisionerJobWithCancelByIDParams{ID: j.ID}).
Asserts(v.RBACObject(tpl), []policy.Action{policy.ActionRead, policy.ActionUpdate}).Returns()
}))
s.Run("GetProvisionerJobTimingsByJobID", s.Subtest(func(db database.Store, check *expects) {
jobID := uuid.New()
j := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{ID: jobID})
t := dbgen.ProvisionerJobTimings(s.T(), db, database.InsertProvisionerJobTimingsParams{
JobID: jobID,
StartedAt: []time.Time{dbtime.Now(), dbtime.Now()},
EndedAt: []time.Time{dbtime.Now(), dbtime.Now()},
Stage: []database.ProvisionerJobTimingStage{
database.ProvisionerJobTimingStageInit,
database.ProvisionerJobTimingStagePlan,
},
Source: []string{"source1", "source2"},
Action: []string{"action1", "action2"},
Resource: []string{"resource1", "resource2"},
})
check.Args(j.ID).Asserts().Returns(t)
}))
s.Run("GetProvisionerJobsByIDs", s.Subtest(func(db database.Store, check *expects) {
a := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{})
b := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{})
Expand Down
6 changes: 6 additions & 0 deletions coderd/database/dbgen/dbgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,12 @@ func CustomRole(t testing.TB, db database.Store, seed database.CustomRole) datab
return role
}

func ProvisionerJobTimings(t testing.TB, db database.Store, seed database.InsertProvisionerJobTimingsParams) []database.ProvisionerJobTiming {
timings, err := db.InsertProvisionerJobTimings(genCtx, seed)
require.NoError(t, err, "insert provisioner job timings")
return timings
}

func must[V any](v V, err error) V {
if err != nil {
panic(err)
Expand Down
43 changes: 41 additions & 2 deletions coderd/database/dbmem/dbmem.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ type data struct {
workspaces []database.Workspace
workspaceProxies []database.WorkspaceProxy
customRoles []database.CustomRole
provisionerJobTimings []database.ProvisionerJobTiming
runtimeConfig map[string]string
// Locks is a map of lock names. Any keys within the map are currently
// locked.
Expand Down Expand Up @@ -3284,6 +3285,26 @@ func (q *FakeQuerier) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (
return q.getProvisionerJobByIDNoLock(ctx, id)
}

func (q *FakeQuerier) GetProvisionerJobTimingsByJobID(_ context.Context, jobID uuid.UUID) ([]database.ProvisionerJobTiming, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()

timings := make([]database.ProvisionerJobTiming, 0)
for _, timing := range q.provisionerJobTimings {
if timing.JobID == jobID {
timings = append(timings, timing)
}
}
if len(timings) == 0 {
return nil, sql.ErrNoRows
}
sort.Slice(timings, func(i, j int) bool {
return timings[i].StartedAt.Before(timings[j].StartedAt)
})

return timings, nil
}

func (q *FakeQuerier) GetProvisionerJobsByIDs(_ context.Context, ids []uuid.UUID) ([]database.ProvisionerJob, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
Expand Down Expand Up @@ -6777,13 +6798,31 @@ func (q *FakeQuerier) InsertProvisionerJobLogs(_ context.Context, arg database.I
return logs, nil
}

func (*FakeQuerier) InsertProvisionerJobTimings(_ context.Context, arg database.InsertProvisionerJobTimingsParams) ([]database.ProvisionerJobTiming, error) {
func (q *FakeQuerier) InsertProvisionerJobTimings(_ context.Context, arg database.InsertProvisionerJobTimingsParams) ([]database.ProvisionerJobTiming, error) {
err := validateDatabaseType(arg)
if err != nil {
return nil, err
}

return nil, nil
q.mutex.Lock()
defer q.mutex.Unlock()

insertedTimings := make([]database.ProvisionerJobTiming, 0, len(arg.StartedAt))
for i := range arg.StartedAt {
timing := database.ProvisionerJobTiming{
JobID: arg.JobID,
StartedAt: arg.StartedAt[i],
EndedAt: arg.EndedAt[i],
Stage: arg.Stage[i],
Source: arg.Source[i],
Action: arg.Action[i],
Resource: arg.Resource[i],
}
q.provisionerJobTimings = append(q.provisionerJobTimings, timing)
insertedTimings = append(insertedTimings, timing)
}

return insertedTimings, nil
}

func (q *FakeQuerier) InsertProvisionerKey(_ context.Context, arg database.InsertProvisionerKeyParams) (database.ProvisionerKey, error) {
Expand Down
7 changes: 7 additions & 0 deletions coderd/database/dbmetrics/dbmetrics.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions coderd/database/dbmock/dbmock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions coderd/database/querier.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading