diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 8c8cf6d4c1..5c3add4a46 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -3253,6 +3253,10 @@ func (q *querier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, work return q.db.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspaceID) } +func (q *querier) GetLatestWorkspaceBuildWithStatusByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (database.GetLatestWorkspaceBuildWithStatusByWorkspaceIDRow, error) { + return fetch(q.log, q.auth, q.db.GetLatestWorkspaceBuildWithStatusByWorkspaceID)(ctx, workspaceID) +} + func (q *querier) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]database.WorkspaceBuild, error) { // This function is a system function until we implement a join for workspace builds. if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil { diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 7826990ff2..f712234a0d 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -2991,6 +2991,11 @@ func (s *MethodTestSuite) TestWorkspace() { dbm.EXPECT().GetLatestWorkspaceBuildByWorkspaceID(gomock.Any(), w.ID).Return(b, nil).AnyTimes() check.Args(w.ID).Asserts(w, policy.ActionRead).Returns(b) })) + s.Run("GetLatestWorkspaceBuildWithStatusByWorkspaceID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) { + r := testutil.Fake(s.T(), faker, database.GetLatestWorkspaceBuildWithStatusByWorkspaceIDRow{}) + dbm.EXPECT().GetLatestWorkspaceBuildWithStatusByWorkspaceID(gomock.Any(), r.WorkspaceTable.ID).Return(r, nil).AnyTimes() + check.Args(r.WorkspaceTable.ID).Asserts(r.WorkspaceTable, policy.ActionRead).Returns(r) + })) s.Run("GetWorkspaceAgentByID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) { w := testutil.Fake(s.T(), faker, database.Workspace{}) agt := testutil.Fake(s.T(), faker, database.WorkspaceAgent{}) diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index 396209fba3..ee67537183 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -1800,6 +1800,14 @@ func (m queryMetricsStore) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Cont return r0, r1 } +func (m queryMetricsStore) GetLatestWorkspaceBuildWithStatusByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (database.GetLatestWorkspaceBuildWithStatusByWorkspaceIDRow, error) { + start := time.Now() + r0, r1 := m.s.GetLatestWorkspaceBuildWithStatusByWorkspaceID(ctx, workspaceID) + m.queryLatencies.WithLabelValues("GetLatestWorkspaceBuildWithStatusByWorkspaceID").Observe(time.Since(start).Seconds()) + m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetLatestWorkspaceBuildWithStatusByWorkspaceID").Inc() + return r0, r1 +} + func (m queryMetricsStore) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]database.WorkspaceBuild, error) { start := time.Now() r0, r1 := m.s.GetLatestWorkspaceBuildsByWorkspaceIDs(ctx, ids) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 4d66dd5907..7a1537000a 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -3332,6 +3332,21 @@ func (mr *MockStoreMockRecorder) GetLatestWorkspaceBuildByWorkspaceID(ctx, works return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestWorkspaceBuildByWorkspaceID", reflect.TypeOf((*MockStore)(nil).GetLatestWorkspaceBuildByWorkspaceID), ctx, workspaceID) } +// GetLatestWorkspaceBuildWithStatusByWorkspaceID mocks base method. +func (m *MockStore) GetLatestWorkspaceBuildWithStatusByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (database.GetLatestWorkspaceBuildWithStatusByWorkspaceIDRow, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLatestWorkspaceBuildWithStatusByWorkspaceID", ctx, workspaceID) + ret0, _ := ret[0].(database.GetLatestWorkspaceBuildWithStatusByWorkspaceIDRow) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLatestWorkspaceBuildWithStatusByWorkspaceID indicates an expected call of GetLatestWorkspaceBuildWithStatusByWorkspaceID. +func (mr *MockStoreMockRecorder) GetLatestWorkspaceBuildWithStatusByWorkspaceID(ctx, workspaceID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestWorkspaceBuildWithStatusByWorkspaceID", reflect.TypeOf((*MockStore)(nil).GetLatestWorkspaceBuildWithStatusByWorkspaceID), ctx, workspaceID) +} + // GetLatestWorkspaceBuildsByWorkspaceIDs mocks base method. func (m *MockStore) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]database.WorkspaceBuild, error) { m.ctrl.T.Helper() diff --git a/coderd/database/modelmethods.go b/coderd/database/modelmethods.go index cf2b0e542a..2f558eb803 100644 --- a/coderd/database/modelmethods.go +++ b/coderd/database/modelmethods.go @@ -952,3 +952,7 @@ type UpsertConnectionLogParams struct { Time time.Time `db:"time" json:"time"` ConnectionStatus ConnectionStatus `db:"connection_status" json:"connection_status"` } + +func (r GetLatestWorkspaceBuildWithStatusByWorkspaceIDRow) RBACObject() rbac.Object { + return r.WorkspaceTable.RBACObject() +} diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 94144a494a..18441173e4 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -410,6 +410,7 @@ type sqlcQuerier interface { GetLatestWorkspaceAppStatusByAppID(ctx context.Context, appID uuid.UUID) (WorkspaceAppStatus, error) GetLatestWorkspaceAppStatusesByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAppStatus, error) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (WorkspaceBuild, error) + GetLatestWorkspaceBuildWithStatusByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (GetLatestWorkspaceBuildWithStatusByWorkspaceIDRow, error) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceBuild, error) GetLicenseByID(ctx context.Context, id int32) (License, error) GetLicenses(ctx context.Context) ([]License, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 8f663acb29..4f284699c5 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -29660,6 +29660,61 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, w return i, err } +const getLatestWorkspaceBuildWithStatusByWorkspaceID = `-- name: GetLatestWorkspaceBuildWithStatusByWorkspaceID :one +SELECT + workspace_builds.transition, workspace_builds.build_number, provisioner_jobs.job_status, + workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, workspaces.next_start_at, workspaces.group_acl, workspaces.user_acl -- Used for dbauthz fetch() checks +FROM + workspace_builds +INNER JOIN + provisioner_jobs ON workspace_builds.job_id = provisioner_jobs.id +INNER JOIN + workspaces ON workspace_builds.workspace_id = workspaces.id +WHERE + workspace_builds.workspace_id = $1 AND + workspaces.deleted = false +ORDER BY + workspace_builds.build_number desc + LIMIT + 1 +` + +type GetLatestWorkspaceBuildWithStatusByWorkspaceIDRow struct { + Transition WorkspaceTransition `db:"transition" json:"transition"` + BuildNumber int32 `db:"build_number" json:"build_number"` + JobStatus ProvisionerJobStatus `db:"job_status" json:"job_status"` + WorkspaceTable WorkspaceTable `db:"workspace_table" json:"workspace_table"` +} + +func (q *sqlQuerier) GetLatestWorkspaceBuildWithStatusByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (GetLatestWorkspaceBuildWithStatusByWorkspaceIDRow, error) { + row := q.db.QueryRowContext(ctx, getLatestWorkspaceBuildWithStatusByWorkspaceID, workspaceID) + var i GetLatestWorkspaceBuildWithStatusByWorkspaceIDRow + err := row.Scan( + &i.Transition, + &i.BuildNumber, + &i.JobStatus, + &i.WorkspaceTable.ID, + &i.WorkspaceTable.CreatedAt, + &i.WorkspaceTable.UpdatedAt, + &i.WorkspaceTable.OwnerID, + &i.WorkspaceTable.OrganizationID, + &i.WorkspaceTable.TemplateID, + &i.WorkspaceTable.Deleted, + &i.WorkspaceTable.Name, + &i.WorkspaceTable.AutostartSchedule, + &i.WorkspaceTable.Ttl, + &i.WorkspaceTable.LastUsedAt, + &i.WorkspaceTable.DormantAt, + &i.WorkspaceTable.DeletingAt, + &i.WorkspaceTable.AutomaticUpdates, + &i.WorkspaceTable.Favorite, + &i.WorkspaceTable.NextStartAt, + &i.WorkspaceTable.GroupACL, + &i.WorkspaceTable.UserACL, + ) + return i, err +} + const getLatestWorkspaceBuildsByWorkspaceIDs = `-- name: GetLatestWorkspaceBuildsByWorkspaceIDs :many SELECT DISTINCT ON (workspace_id) diff --git a/coderd/database/queries/workspacebuilds.sql b/coderd/database/queries/workspacebuilds.sql index 775e9da0ab..7767cd0b6f 100644 --- a/coderd/database/queries/workspacebuilds.sql +++ b/coderd/database/queries/workspacebuilds.sql @@ -291,3 +291,21 @@ INNER JOIN templates ON templates.id = workspaces.template_id WHERE workspace_builds.id = @workspace_build_id; + +-- name: GetLatestWorkspaceBuildWithStatusByWorkspaceID :one +SELECT + workspace_builds.transition, workspace_builds.build_number, provisioner_jobs.job_status, + sqlc.embed(workspaces) -- Used for dbauthz fetch() checks +FROM + workspace_builds +INNER JOIN + provisioner_jobs ON workspace_builds.job_id = provisioner_jobs.id +INNER JOIN + workspaces ON workspace_builds.workspace_id = workspaces.id +WHERE + workspace_builds.workspace_id = $1 AND + workspaces.deleted = false +ORDER BY + workspace_builds.build_number desc + LIMIT + 1;