diff --git a/coderd/database/dbauthz/querier.go b/coderd/database/dbauthz/querier.go index 6aecd87baf35c..6515f9a791228 100644 --- a/coderd/database/dbauthz/querier.go +++ b/coderd/database/dbauthz/querier.go @@ -1139,24 +1139,11 @@ func (q *querier) GetWorkspaces(ctx context.Context, arg database.GetWorkspacesP } func (q *querier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (database.WorkspaceBuild, error) { - if _, err := q.GetWorkspaceByID(ctx, workspaceID); err != nil { - return database.WorkspaceBuild{}, err - } - return q.db.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspaceID) + return fetch(q.log, q.auth, q.db.GetLatestWorkspaceBuildByWorkspaceID)(ctx, workspaceID) } func (q *querier) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]database.WorkspaceBuild, error) { - // This is not ideal as not all builds will be returned if the workspace cannot be read. - // This should probably be handled differently? Maybe join workspace builds with workspace - // ownership properties and filter on that. - for _, id := range ids { - _, err := q.GetWorkspaceByID(ctx, id) - if err != nil { - return nil, err - } - } - - return q.db.GetLatestWorkspaceBuildsByWorkspaceIDs(ctx, ids) + return fetchWithPostFilter(q.auth, q.db.GetLatestWorkspaceBuildsByWorkspaceIDs)(ctx, ids) } func (q *querier) GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (database.WorkspaceAgent, error) { @@ -1235,34 +1222,15 @@ func (q *querier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid.UU } func (q *querier) GetWorkspaceBuildByID(ctx context.Context, buildID uuid.UUID) (database.WorkspaceBuild, error) { - build, err := q.db.GetWorkspaceBuildByID(ctx, buildID) - if err != nil { - return database.WorkspaceBuild{}, err - } - if _, err := q.GetWorkspaceByID(ctx, build.WorkspaceID); err != nil { - return database.WorkspaceBuild{}, err - } - return build, nil + return fetch(q.log, q.auth, q.db.GetWorkspaceBuildByID)(ctx, buildID) } func (q *querier) GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UUID) (database.WorkspaceBuild, error) { - build, err := q.db.GetWorkspaceBuildByJobID(ctx, jobID) - if err != nil { - return database.WorkspaceBuild{}, err - } - // Authorized fetch - _, err = q.GetWorkspaceByID(ctx, build.WorkspaceID) - if err != nil { - return database.WorkspaceBuild{}, err - } - return build, nil + return fetch(q.log, q.auth, q.db.GetWorkspaceBuildByJobID)(ctx, jobID) } func (q *querier) GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx context.Context, arg database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams) (database.WorkspaceBuild, error) { - if _, err := q.GetWorkspaceByID(ctx, arg.WorkspaceID); err != nil { - return database.WorkspaceBuild{}, err - } - return q.db.GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx, arg) + return fetch(q.log, q.auth, q.db.GetWorkspaceBuildByWorkspaceIDAndBuildNumber)(ctx, arg) } func (q *querier) GetWorkspaceBuildParameters(ctx context.Context, workspaceBuildID uuid.UUID) ([]database.WorkspaceBuildParameter, error) { @@ -1277,10 +1245,19 @@ func (q *querier) GetWorkspaceBuildParameters(ctx context.Context, workspaceBuil } func (q *querier) GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg database.GetWorkspaceBuildsByWorkspaceIDParams) ([]database.WorkspaceBuild, error) { - if _, err := q.GetWorkspaceByID(ctx, arg.WorkspaceID); err != nil { + builds, err := q.db.GetWorkspaceBuildsByWorkspaceID(ctx, arg) + if err != nil { return nil, err } - return q.db.GetWorkspaceBuildsByWorkspaceID(ctx, arg) + if len(builds) == 0 { + return nil, sql.ErrNoRows + } + // All builds come from the same workspace, so we only need to check the first one. + err = q.authorizeContext(ctx, rbac.ActionRead, builds[0]) + if err != nil { + return nil, err + } + return builds, nil } func (q *querier) GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUID) (database.Workspace, error) { @@ -1340,11 +1317,7 @@ func (q *querier) GetWorkspaceResourcesByJobID(ctx context.Context, jobID uuid.U if err != nil { return nil, err } - workspace, err := q.db.GetWorkspaceByID(ctx, build.WorkspaceID) - if err != nil { - return nil, err - } - obj = workspace + obj = build default: return nil, xerrors.Errorf("unknown job type: %s", job.Type) } @@ -1360,10 +1333,10 @@ func (q *querier) InsertWorkspace(ctx context.Context, arg database.InsertWorksp return insert(q.log, q.auth, obj, q.db.InsertWorkspace)(ctx, arg) } -func (q *querier) InsertWorkspaceBuild(ctx context.Context, arg database.InsertWorkspaceBuildParams) (database.WorkspaceBuild, error) { +func (q *querier) InsertWorkspaceBuild(ctx context.Context, arg database.InsertWorkspaceBuildParams) (database.WorkspaceBuildThin, error) { w, err := q.db.GetWorkspaceByID(ctx, arg.WorkspaceID) if err != nil { - return database.WorkspaceBuild{}, err + return database.WorkspaceBuildThin{}, err } var action rbac.Action = rbac.ActionUpdate @@ -1372,7 +1345,7 @@ func (q *querier) InsertWorkspaceBuild(ctx context.Context, arg database.InsertW } if err = q.authorizeContext(ctx, action, w); err != nil { - return database.WorkspaceBuild{}, err + return database.WorkspaceBuildThin{}, err } return q.db.InsertWorkspaceBuild(ctx, arg) @@ -1385,12 +1358,7 @@ func (q *querier) InsertWorkspaceBuildParameters(ctx context.Context, arg databa return err } - workspace, err := q.db.GetWorkspaceByID(ctx, build.WorkspaceID) - if err != nil { - return err - } - - err = q.authorizeContext(ctx, rbac.ActionUpdate, workspace) + err = q.authorizeContext(ctx, rbac.ActionUpdate, build) if err != nil { return err } @@ -1448,19 +1416,15 @@ func (q *querier) UpdateWorkspaceAutostart(ctx context.Context, arg database.Upd return update(q.log, q.auth, fetch, q.db.UpdateWorkspaceAutostart)(ctx, arg) } -func (q *querier) UpdateWorkspaceBuildByID(ctx context.Context, arg database.UpdateWorkspaceBuildByIDParams) (database.WorkspaceBuild, error) { +func (q *querier) UpdateWorkspaceBuildByID(ctx context.Context, arg database.UpdateWorkspaceBuildByIDParams) (database.WorkspaceBuildThin, error) { build, err := q.db.GetWorkspaceBuildByID(ctx, arg.ID) if err != nil { - return database.WorkspaceBuild{}, err + return database.WorkspaceBuildThin{}, err } - workspace, err := q.db.GetWorkspaceByID(ctx, build.WorkspaceID) - if err != nil { - return database.WorkspaceBuild{}, err - } - err = q.authorizeContext(ctx, rbac.ActionUpdate, workspace.RBACObject()) + err = q.authorizeContext(ctx, rbac.ActionUpdate, build) if err != nil { - return database.WorkspaceBuild{}, err + return database.WorkspaceBuildThin{}, err } return q.db.UpdateWorkspaceBuildByID(ctx, arg) diff --git a/coderd/database/dbauthz/querier_test.go b/coderd/database/dbauthz/querier_test.go index b1b21c78ae9de..a446247f64c72 100644 --- a/coderd/database/dbauthz/querier_test.go +++ b/coderd/database/dbauthz/querier_test.go @@ -946,16 +946,16 @@ func (s *MethodTestSuite) TestWorkspace() { s.Run("GetLatestWorkspaceBuildByWorkspaceID", s.Subtest(func(db database.Store, check *expects) { ws := dbgen.Workspace(s.T(), db, database.Workspace{}) b := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID}) - check.Args(ws.ID).Asserts(ws, rbac.ActionRead).Returns(b) + check.Args(ws.ID).Asserts(ws, rbac.ActionRead).Returns(b.WithWorkspace(ws)) })) s.Run("GetLatestWorkspaceBuildsByWorkspaceIDs", s.Subtest(func(db database.Store, check *expects) { ws := dbgen.Workspace(s.T(), db, database.Workspace{}) - b := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID}) + b := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID}).WithWorkspace(ws) check.Args([]uuid.UUID{ws.ID}).Asserts(ws, rbac.ActionRead).Returns(slice.New(b)) })) s.Run("GetWorkspaceAgentByID", s.Subtest(func(db database.Store, check *expects) { ws := dbgen.Workspace(s.T(), db, database.Workspace{}) - build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) + build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}).WithWorkspace(ws) res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID}) agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID}) check.Args(agt.ID).Asserts(ws, rbac.ActionRead).Returns(agt) @@ -969,7 +969,7 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("GetWorkspaceAgentsByResourceIDs", s.Subtest(func(db database.Store, check *expects) { ws := dbgen.Workspace(s.T(), db, database.Workspace{}) - build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) + build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}).WithWorkspace(ws) res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID}) agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID}) check.Args([]uuid.UUID{res.ID}).Asserts( /*ws, rbac.ActionRead*/ ). @@ -977,7 +977,7 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("UpdateWorkspaceAgentLifecycleStateByID", s.Subtest(func(db database.Store, check *expects) { ws := dbgen.Workspace(s.T(), db, database.Workspace{}) - build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) + build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}).WithWorkspace(ws) res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID}) agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID}) check.Args(database.UpdateWorkspaceAgentLifecycleStateByIDParams{ @@ -987,7 +987,7 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("UpdateWorkspaceAgentStartupByID", s.Subtest(func(db database.Store, check *expects) { ws := dbgen.Workspace(s.T(), db, database.Workspace{}) - build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) + build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}).WithWorkspace(ws) res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID}) agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID}) check.Args(database.UpdateWorkspaceAgentStartupByIDParams{ @@ -996,7 +996,7 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("GetWorkspaceAppByAgentIDAndSlug", s.Subtest(func(db database.Store, check *expects) { ws := dbgen.Workspace(s.T(), db, database.Workspace{}) - build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) + build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}).WithWorkspace(ws) res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID}) agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID}) app := dbgen.WorkspaceApp(s.T(), db, database.WorkspaceApp{AgentID: agt.ID}) @@ -1008,7 +1008,7 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("GetWorkspaceAppsByAgentID", s.Subtest(func(db database.Store, check *expects) { ws := dbgen.Workspace(s.T(), db, database.Workspace{}) - build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) + build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}).WithWorkspace(ws) res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID}) agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID}) a := dbgen.WorkspaceApp(s.T(), db, database.WorkspaceApp{AgentID: agt.ID}) @@ -1018,13 +1018,13 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("GetWorkspaceAppsByAgentIDs", s.Subtest(func(db database.Store, check *expects) { aWs := dbgen.Workspace(s.T(), db, database.Workspace{}) - aBuild := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: aWs.ID, JobID: uuid.New()}) + aBuild := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: aWs.ID, JobID: uuid.New()}).WithWorkspace(aWs) aRes := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: aBuild.JobID}) aAgt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: aRes.ID}) a := dbgen.WorkspaceApp(s.T(), db, database.WorkspaceApp{AgentID: aAgt.ID}) bWs := dbgen.Workspace(s.T(), db, database.Workspace{}) - bBuild := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: bWs.ID, JobID: uuid.New()}) + bBuild := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: bWs.ID, JobID: uuid.New()}).WithWorkspace(bWs) bRes := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: bBuild.JobID}) bAgt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: bRes.ID}) b := dbgen.WorkspaceApp(s.T(), db, database.WorkspaceApp{AgentID: bAgt.ID}) @@ -1035,17 +1035,17 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("GetWorkspaceBuildByID", s.Subtest(func(db database.Store, check *expects) { ws := dbgen.Workspace(s.T(), db, database.Workspace{}) - build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID}) + build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID}).WithWorkspace(ws) check.Args(build.ID).Asserts(ws, rbac.ActionRead).Returns(build) })) s.Run("GetWorkspaceBuildByJobID", s.Subtest(func(db database.Store, check *expects) { ws := dbgen.Workspace(s.T(), db, database.Workspace{}) - build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID}) + build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID}).WithWorkspace(ws) check.Args(build.JobID).Asserts(ws, rbac.ActionRead).Returns(build) })) s.Run("GetWorkspaceBuildByWorkspaceIDAndBuildNumber", s.Subtest(func(db database.Store, check *expects) { ws := dbgen.Workspace(s.T(), db, database.Workspace{}) - build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, BuildNumber: 10}) + build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, BuildNumber: 10}).WithWorkspace(ws) check.Args(database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams{ WorkspaceID: ws.ID, BuildNumber: build.BuildNumber, @@ -1059,14 +1059,14 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("GetWorkspaceBuildsByWorkspaceID", s.Subtest(func(db database.Store, check *expects) { ws := dbgen.Workspace(s.T(), db, database.Workspace{}) - _ = dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, BuildNumber: 1}) - _ = dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, BuildNumber: 2}) - _ = dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, BuildNumber: 3}) + _ = dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, BuildNumber: 1}).WithWorkspace(ws) + _ = dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, BuildNumber: 2}).WithWorkspace(ws) + _ = dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, BuildNumber: 3}).WithWorkspace(ws) check.Args(database.GetWorkspaceBuildsByWorkspaceIDParams{WorkspaceID: ws.ID}).Asserts(ws, rbac.ActionRead) // ordering })) s.Run("GetWorkspaceByAgentID", s.Subtest(func(db database.Store, check *expects) { ws := dbgen.Workspace(s.T(), db, database.Workspace{}) - build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) + build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}).WithWorkspace(ws) res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID}) agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID}) check.Args(agt.ID).Asserts(ws, rbac.ActionRead).Returns(ws) @@ -1081,14 +1081,14 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("GetWorkspaceResourceByID", s.Subtest(func(db database.Store, check *expects) { ws := dbgen.Workspace(s.T(), db, database.Workspace{}) - build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) + build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}).WithWorkspace(ws) _ = dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{ID: build.JobID, Type: database.ProvisionerJobTypeWorkspaceBuild}) res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID}) check.Args(res.ID).Asserts(ws, rbac.ActionRead).Returns(res) })) s.Run("GetWorkspaceResourceMetadataByResourceIDs", s.Subtest(func(db database.Store, check *expects) { ws := dbgen.Workspace(s.T(), db, database.Workspace{}) - build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) + build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}).WithWorkspace(ws) _ = dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{ID: build.JobID, Type: database.ProvisionerJobTypeWorkspaceBuild}) a := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID}) b := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID}) @@ -1097,7 +1097,7 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("Build/GetWorkspaceResourcesByJobID", s.Subtest(func(db database.Store, check *expects) { ws := dbgen.Workspace(s.T(), db, database.Workspace{}) - build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) + build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}).WithWorkspace(ws) job := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{ID: build.JobID, Type: database.ProvisionerJobTypeWorkspaceBuild}) check.Args(job.ID).Asserts(ws, rbac.ActionRead).Returns([]database.WorkspaceResource{}) })) @@ -1113,7 +1113,7 @@ func (s *MethodTestSuite) TestWorkspace() { tJob := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{ID: v.JobID, Type: database.ProvisionerJobTypeTemplateVersionImport}) ws := dbgen.Workspace(s.T(), db, database.Workspace{}) - build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) + build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}).WithWorkspace(ws) wJob := dbgen.ProvisionerJob(s.T(), db, database.ProvisionerJob{ID: build.JobID, Type: database.ProvisionerJobTypeWorkspaceBuild}) check.Args([]uuid.UUID{tJob.ID, wJob.ID}). Asserts( /*v.RBACObject(tpl), rbac.ActionRead, ws, rbac.ActionRead*/ ). diff --git a/coderd/database/dbauthz/system.go b/coderd/database/dbauthz/system.go index b290f68bc8428..e1c8ba9b2b7d3 100644 --- a/coderd/database/dbauthz/system.go +++ b/coderd/database/dbauthz/system.go @@ -162,7 +162,7 @@ func (q *querier) GetDeploymentDAUs(ctx context.Context) ([]database.GetDeployme } // UpdateWorkspaceBuildCostByID is used by the provisioning system to update the cost of a workspace build. -func (q *querier) UpdateWorkspaceBuildCostByID(ctx context.Context, arg database.UpdateWorkspaceBuildCostByIDParams) (database.WorkspaceBuild, error) { +func (q *querier) UpdateWorkspaceBuildCostByID(ctx context.Context, arg database.UpdateWorkspaceBuildCostByIDParams) (database.WorkspaceBuildThin, error) { return q.db.UpdateWorkspaceBuildCostByID(ctx, arg) } diff --git a/coderd/database/dbfake/databasefake.go b/coderd/database/dbfake/databasefake.go index a4693643bd433..93ed5d0f73746 100644 --- a/coderd/database/dbfake/databasefake.go +++ b/coderd/database/dbfake/databasefake.go @@ -60,7 +60,7 @@ func New() database.Store { templateVersions: make([]database.TemplateVersion, 0), templates: make([]database.Template, 0), workspaceAgentStats: make([]database.WorkspaceAgentStat, 0), - workspaceBuilds: make([]database.WorkspaceBuild, 0), + workspaceBuilds: make([]database.WorkspaceBuildThin, 0), workspaceApps: make([]database.WorkspaceApp, 0), workspaces: make([]database.Workspace, 0), licenses: make([]database.License, 0), @@ -118,7 +118,7 @@ type data struct { templates []database.Template workspaceAgents []database.WorkspaceAgent workspaceApps []database.WorkspaceApp - workspaceBuilds []database.WorkspaceBuild + workspaceBuilds []database.WorkspaceBuildThin workspaceBuildParameters []database.WorkspaceBuildParameter workspaceResourceMetadata []database.WorkspaceResourceMetadatum workspaceResources []database.WorkspaceResource @@ -1213,7 +1213,7 @@ func (q *fakeQuerier) GetWorkspaceByAgentID(_ context.Context, agentID uuid.UUID return database.Workspace{}, sql.ErrNoRows } - var build database.WorkspaceBuild + var build database.WorkspaceBuildThin for _, _build := range q.workspaceBuilds { if _build.JobID == resource.JobID { build = _build @@ -1333,7 +1333,7 @@ func (q *fakeQuerier) GetWorkspaceBuildByID(_ context.Context, id uuid.UUID) (da for _, history := range q.workspaceBuilds { if history.ID == id { - return history, nil + return q.expandWorkspaceThin(history), nil } } return database.WorkspaceBuild{}, sql.ErrNoRows @@ -1345,7 +1345,7 @@ func (q *fakeQuerier) GetWorkspaceBuildByJobID(_ context.Context, jobID uuid.UUI for _, build := range q.workspaceBuilds { if build.JobID == jobID { - return build, nil + return q.expandWorkspaceThin(build), nil } } return database.WorkspaceBuild{}, sql.ErrNoRows @@ -1359,7 +1359,7 @@ func (q *fakeQuerier) GetLatestWorkspaceBuildByWorkspaceID(_ context.Context, wo var buildNum int32 = -1 for _, workspaceBuild := range q.workspaceBuilds { if workspaceBuild.WorkspaceID == workspaceID && workspaceBuild.BuildNumber > buildNum { - row = workspaceBuild + row = q.expandWorkspaceThin(workspaceBuild) buildNum = workspaceBuild.BuildNumber } } @@ -1378,7 +1378,7 @@ func (q *fakeQuerier) GetLatestWorkspaceBuilds(_ context.Context) ([]database.Wo for _, workspaceBuild := range q.workspaceBuilds { id := workspaceBuild.WorkspaceID if workspaceBuild.BuildNumber > buildNumbers[id] { - builds[id] = workspaceBuild + builds[id] = q.expandWorkspaceThin(workspaceBuild) buildNumbers[id] = workspaceBuild.BuildNumber } } @@ -1404,7 +1404,7 @@ func (q *fakeQuerier) GetLatestWorkspaceBuildsByWorkspaceIDs(_ context.Context, for _, workspaceBuild := range q.workspaceBuilds { for _, id := range ids { if id == workspaceBuild.WorkspaceID && workspaceBuild.BuildNumber > buildNumbers[id] { - builds[id] = workspaceBuild + builds[id] = q.expandWorkspaceThin(workspaceBuild) buildNumbers[id] = workspaceBuild.BuildNumber } } @@ -1432,7 +1432,7 @@ func (q *fakeQuerier) GetWorkspaceBuildsByWorkspaceID(_ context.Context, q.mutex.RLock() defer q.mutex.RUnlock() - history := make([]database.WorkspaceBuild, 0) + history := make([]database.WorkspaceBuildThin, 0) for _, workspaceBuild := range q.workspaceBuilds { if workspaceBuild.CreatedAt.Before(params.Since) { continue @@ -1443,7 +1443,7 @@ func (q *fakeQuerier) GetWorkspaceBuildsByWorkspaceID(_ context.Context, } // Order by build_number - slices.SortFunc(history, func(a, b database.WorkspaceBuild) bool { + slices.SortFunc(history, func(a, b database.WorkspaceBuildThin) bool { // use greater than since we want descending order return a.BuildNumber > b.BuildNumber }) @@ -1482,7 +1482,7 @@ func (q *fakeQuerier) GetWorkspaceBuildsByWorkspaceID(_ context.Context, if len(history) == 0 { return nil, sql.ErrNoRows } - return history, nil + return q.expandWorkspaceThins(history), nil } func (q *fakeQuerier) GetWorkspaceBuildByWorkspaceIDAndBuildNumber(_ context.Context, arg database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams) (database.WorkspaceBuild, error) { @@ -1500,7 +1500,7 @@ func (q *fakeQuerier) GetWorkspaceBuildByWorkspaceIDAndBuildNumber(_ context.Con if workspaceBuild.BuildNumber != arg.BuildNumber { continue } - return workspaceBuild, nil + return q.expandWorkspaceThin(workspaceBuild), nil } return database.WorkspaceBuild{}, sql.ErrNoRows } @@ -1526,7 +1526,7 @@ func (q *fakeQuerier) GetWorkspaceBuildsCreatedAfter(_ context.Context, after ti workspaceBuilds := make([]database.WorkspaceBuild, 0) for _, workspaceBuild := range q.workspaceBuilds { if workspaceBuild.CreatedAt.After(after) { - workspaceBuilds = append(workspaceBuilds, workspaceBuild) + workspaceBuilds = append(workspaceBuilds, q.expandWorkspaceThin(workspaceBuild)) } } return workspaceBuilds, nil @@ -3063,15 +3063,15 @@ func (q *fakeQuerier) InsertWorkspace(_ context.Context, arg database.InsertWork return workspace, nil } -func (q *fakeQuerier) InsertWorkspaceBuild(_ context.Context, arg database.InsertWorkspaceBuildParams) (database.WorkspaceBuild, error) { +func (q *fakeQuerier) InsertWorkspaceBuild(_ context.Context, arg database.InsertWorkspaceBuildParams) (database.WorkspaceBuildThin, error) { if err := validateDatabaseType(arg); err != nil { - return database.WorkspaceBuild{}, err + return database.WorkspaceBuildThin{}, err } q.mutex.Lock() defer q.mutex.Unlock() - workspaceBuild := database.WorkspaceBuild{ + workspaceBuild := database.WorkspaceBuildThin{ ID: arg.ID, CreatedAt: arg.CreatedAt, UpdatedAt: arg.UpdatedAt, @@ -3495,9 +3495,9 @@ func (q *fakeQuerier) UpdateWorkspaceLastUsedAt(_ context.Context, arg database. return sql.ErrNoRows } -func (q *fakeQuerier) UpdateWorkspaceBuildByID(_ context.Context, arg database.UpdateWorkspaceBuildByIDParams) (database.WorkspaceBuild, error) { +func (q *fakeQuerier) UpdateWorkspaceBuildByID(_ context.Context, arg database.UpdateWorkspaceBuildByIDParams) (database.WorkspaceBuildThin, error) { if err := validateDatabaseType(arg); err != nil { - return database.WorkspaceBuild{}, err + return database.WorkspaceBuildThin{}, err } q.mutex.Lock() @@ -3513,12 +3513,12 @@ func (q *fakeQuerier) UpdateWorkspaceBuildByID(_ context.Context, arg database.U q.workspaceBuilds[index] = workspaceBuild return workspaceBuild, nil } - return database.WorkspaceBuild{}, sql.ErrNoRows + return database.WorkspaceBuildThin{}, sql.ErrNoRows } -func (q *fakeQuerier) UpdateWorkspaceBuildCostByID(_ context.Context, arg database.UpdateWorkspaceBuildCostByIDParams) (database.WorkspaceBuild, error) { +func (q *fakeQuerier) UpdateWorkspaceBuildCostByID(_ context.Context, arg database.UpdateWorkspaceBuildCostByIDParams) (database.WorkspaceBuildThin, error) { if err := validateDatabaseType(arg); err != nil { - return database.WorkspaceBuild{}, err + return database.WorkspaceBuildThin{}, err } q.mutex.Lock() @@ -3532,7 +3532,7 @@ func (q *fakeQuerier) UpdateWorkspaceBuildCostByID(_ context.Context, arg databa q.workspaceBuilds[index] = workspaceBuild return workspaceBuild, nil } - return database.WorkspaceBuild{}, sql.ErrNoRows + return database.WorkspaceBuildThin{}, sql.ErrNoRows } func (q *fakeQuerier) UpdateWorkspaceDeletedByID(_ context.Context, arg database.UpdateWorkspaceDeletedByIDParams) error { @@ -4370,7 +4370,7 @@ func (q *fakeQuerier) GetQuotaConsumedForUser(_ context.Context, userID uuid.UUI continue } - var lastBuild database.WorkspaceBuild + var lastBuild database.WorkspaceBuildThin for _, build := range q.workspaceBuilds { if build.WorkspaceID != workspace.ID { continue @@ -4400,3 +4400,18 @@ func (q *fakeQuerier) UpdateWorkspaceAgentLifecycleStateByID(_ context.Context, } return sql.ErrNoRows } + +// expandWorkspaceThins must be called from a locked context. +func (q *fakeQuerier) expandWorkspaceThins(thins []database.WorkspaceBuildThin) []database.WorkspaceBuild { + cpy := make([]database.WorkspaceBuild, 0, len(thins)) + for _, thin := range thins { + cpy = append(cpy, q.expandWorkspaceThin(thin)) + } + return cpy +} + +// expandWorkspaceThin must be called from a locked context. +func (q *fakeQuerier) expandWorkspaceThin(thin database.WorkspaceBuildThin) database.WorkspaceBuild { + w, _ := q.GetWorkspaceByID(context.Background(), thin.WorkspaceID) + return thin.Expand(w.OrganizationID, w.OwnerID) +} diff --git a/coderd/database/dbgen/generator.go b/coderd/database/dbgen/generator.go index cb8f52c06529a..0ec2bcb025eba 100644 --- a/coderd/database/dbgen/generator.go +++ b/coderd/database/dbgen/generator.go @@ -152,7 +152,7 @@ func Workspace(t testing.TB, db database.Store, orig database.Workspace) databas return workspace } -func WorkspaceBuild(t testing.TB, db database.Store, orig database.WorkspaceBuild) database.WorkspaceBuild { +func WorkspaceBuild(t testing.TB, db database.Store, orig database.WorkspaceBuild) database.WorkspaceBuildThin { build, err := db.InsertWorkspaceBuild(context.Background(), database.InsertWorkspaceBuildParams{ ID: takeFirst(orig.ID, uuid.New()), CreatedAt: takeFirst(orig.CreatedAt, database.Now()), diff --git a/coderd/database/dbgen/generator_test.go b/coderd/database/dbgen/generator_test.go index c09cc6df8a466..6082d1d33addb 100644 --- a/coderd/database/dbgen/generator_test.go +++ b/coderd/database/dbgen/generator_test.go @@ -167,7 +167,7 @@ func TestGenerator(t *testing.T) { t.Parallel() db := dbfake.New() exp := dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{}) - require.Equal(t, exp, must(db.GetWorkspaceBuildByID(context.Background(), exp.ID))) + require.Equal(t, exp, must(db.GetWorkspaceBuildByID(context.Background(), exp.ID)).ToThin()) }) t.Run("User", func(t *testing.T) { diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index e182ad382370e..fc16b3f4af880 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -566,6 +566,39 @@ CREATE TABLE workspace_builds ( daily_cost integer DEFAULT 0 NOT NULL ); +CREATE TABLE workspaces ( + id uuid NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + owner_id uuid NOT NULL, + organization_id uuid NOT NULL, + template_id uuid NOT NULL, + deleted boolean DEFAULT false NOT NULL, + name character varying(64) NOT NULL, + autostart_schedule text, + ttl bigint, + last_used_at timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone NOT NULL +); + +CREATE VIEW workspace_builds_rbac AS + SELECT workspace_builds.id, + workspace_builds.created_at, + workspace_builds.updated_at, + workspace_builds.workspace_id, + workspace_builds.template_version_id, + workspace_builds.build_number, + workspace_builds.transition, + workspace_builds.initiator_id, + workspace_builds.provisioner_state, + workspace_builds.job_id, + workspace_builds.deadline, + workspace_builds.reason, + workspace_builds.daily_cost, + workspaces.organization_id, + workspaces.owner_id AS workspace_owner_id + FROM (public.workspace_builds + JOIN workspaces ON ((workspace_builds.workspace_id = workspaces.id))); + CREATE TABLE workspace_resource_metadata ( workspace_resource_id uuid NOT NULL, key character varying(1024) NOT NULL, @@ -596,20 +629,6 @@ CREATE TABLE workspace_resources ( daily_cost integer DEFAULT 0 NOT NULL ); -CREATE TABLE workspaces ( - id uuid NOT NULL, - created_at timestamp with time zone NOT NULL, - updated_at timestamp with time zone NOT NULL, - owner_id uuid NOT NULL, - organization_id uuid NOT NULL, - template_id uuid NOT NULL, - deleted boolean DEFAULT false NOT NULL, - name character varying(64) NOT NULL, - autostart_schedule text, - ttl bigint, - last_used_at timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone NOT NULL -); - ALTER TABLE ONLY licenses ALTER COLUMN id SET DEFAULT nextval('licenses_id_seq'::regclass); ALTER TABLE ONLY provisioner_job_logs ALTER COLUMN id SET DEFAULT nextval('provisioner_job_logs_id_seq'::regclass); diff --git a/coderd/database/migrations/000102_workspace_build_view.down.sql b/coderd/database/migrations/000102_workspace_build_view.down.sql new file mode 100644 index 0000000000000..a8ce5434bc705 --- /dev/null +++ b/coderd/database/migrations/000102_workspace_build_view.down.sql @@ -0,0 +1 @@ +DROP VIEW workspace_builds_rbac; diff --git a/coderd/database/migrations/000102_workspace_build_view.up.sql b/coderd/database/migrations/000102_workspace_build_view.up.sql new file mode 100644 index 0000000000000..5c7a580730d52 --- /dev/null +++ b/coderd/database/migrations/000102_workspace_build_view.up.sql @@ -0,0 +1,14 @@ +BEGIN; +-- workspace_builds_rbac includes the linked workspace information +-- required to perform RBAC checks on workspace builds without needing +-- to fetch the workspace. +CREATE VIEW workspace_builds_rbac AS +SELECT + workspace_builds.*, + workspaces.organization_id AS organization_id, + workspaces.owner_id AS workspace_owner_id +FROM + workspace_builds +INNER JOIN + workspaces ON workspace_builds.workspace_id = workspaces.id; +COMMIT; diff --git a/coderd/database/modelmethods.go b/coderd/database/modelmethods.go index 44c598697ef8b..6aa9610998178 100644 --- a/coderd/database/modelmethods.go +++ b/coderd/database/modelmethods.go @@ -4,6 +4,8 @@ import ( "sort" "strconv" + "github.com/google/uuid" + "github.com/coder/coder/coderd/rbac" ) @@ -74,6 +76,12 @@ func (g Group) RBACObject() rbac.Object { InOrg(g.OrganizationID) } +func (b WorkspaceBuild) RBACObject() rbac.Object { + return rbac.ResourceWorkspace.WithID(b.WorkspaceID). + InOrg(b.OrganizationID). + WithOwner(b.WorkspaceOwnerID.String()) +} + func (w Workspace) RBACObject() rbac.Object { return rbac.ResourceWorkspace.WithID(w.ID). InOrg(w.OrganizationID). @@ -156,6 +164,48 @@ func (l License) RBACObject() rbac.Object { return rbac.ResourceLicense.WithIDString(strconv.FormatInt(int64(l.ID), 10)) } +func (b WorkspaceBuild) ToThin() WorkspaceBuildThin { + return WorkspaceBuildThin{ + ID: b.ID, + CreatedAt: b.CreatedAt, + UpdatedAt: b.UpdatedAt, + WorkspaceID: b.WorkspaceID, + TemplateVersionID: b.TemplateVersionID, + BuildNumber: b.BuildNumber, + Transition: b.Transition, + InitiatorID: b.InitiatorID, + ProvisionerState: b.ProvisionerState, + JobID: b.JobID, + Deadline: b.Deadline, + Reason: b.Reason, + DailyCost: b.DailyCost, + } +} + +func (b WorkspaceBuildThin) WithWorkspace(workspace Workspace) WorkspaceBuild { + return b.Expand(workspace.OrganizationID, workspace.OwnerID) +} + +func (b WorkspaceBuildThin) Expand(orgID, ownerID uuid.UUID) WorkspaceBuild { + return WorkspaceBuild{ + ID: b.ID, + CreatedAt: b.CreatedAt, + UpdatedAt: b.UpdatedAt, + WorkspaceID: b.WorkspaceID, + TemplateVersionID: b.TemplateVersionID, + BuildNumber: b.BuildNumber, + Transition: b.Transition, + InitiatorID: b.InitiatorID, + ProvisionerState: b.ProvisionerState, + JobID: b.JobID, + Deadline: b.Deadline, + Reason: b.Reason, + DailyCost: b.DailyCost, + OrganizationID: orgID, + WorkspaceOwnerID: ownerID, + } +} + func ConvertUserRows(rows []GetUsersRow) []User { users := make([]User, len(rows)) for i, r := range rows { diff --git a/coderd/database/models.go b/coderd/database/models.go index 1467b95ef1e0e..4ab0096ee1718 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1595,6 +1595,8 @@ type WorkspaceBuild struct { Deadline time.Time `db:"deadline" json:"deadline"` Reason BuildReason `db:"reason" json:"reason"` DailyCost int32 `db:"daily_cost" json:"daily_cost"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + WorkspaceOwnerID uuid.UUID `db:"workspace_owner_id" json:"workspace_owner_id"` } type WorkspaceBuildParameter struct { @@ -1605,6 +1607,22 @@ type WorkspaceBuildParameter struct { Value string `db:"value" json:"value"` } +type WorkspaceBuildThin struct { + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` + TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"` + BuildNumber int32 `db:"build_number" json:"build_number"` + Transition WorkspaceTransition `db:"transition" json:"transition"` + InitiatorID uuid.UUID `db:"initiator_id" json:"initiator_id"` + ProvisionerState []byte `db:"provisioner_state" json:"provisioner_state"` + JobID uuid.UUID `db:"job_id" json:"job_id"` + Deadline time.Time `db:"deadline" json:"deadline"` + Reason BuildReason `db:"reason" json:"reason"` + DailyCost int32 `db:"daily_cost" json:"daily_cost"` +} + type WorkspaceResource struct { ID uuid.UUID `db:"id" json:"id"` CreatedAt time.Time `db:"created_at" json:"created_at"` diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 5082f1e9dfa23..a78bef2976b3b 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -169,7 +169,7 @@ type sqlcQuerier interface { InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspaceAgentParams) (WorkspaceAgent, error) InsertWorkspaceAgentStat(ctx context.Context, arg InsertWorkspaceAgentStatParams) (WorkspaceAgentStat, error) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspaceAppParams) (WorkspaceApp, error) - InsertWorkspaceBuild(ctx context.Context, arg InsertWorkspaceBuildParams) (WorkspaceBuild, error) + InsertWorkspaceBuild(ctx context.Context, arg InsertWorkspaceBuildParams) (WorkspaceBuildThin, error) InsertWorkspaceBuildParameters(ctx context.Context, arg InsertWorkspaceBuildParametersParams) error InsertWorkspaceResource(ctx context.Context, arg InsertWorkspaceResourceParams) (WorkspaceResource, error) InsertWorkspaceResourceMetadata(ctx context.Context, arg InsertWorkspaceResourceMetadataParams) ([]WorkspaceResourceMetadatum, error) @@ -205,8 +205,8 @@ type sqlcQuerier interface { UpdateWorkspaceAgentStartupByID(ctx context.Context, arg UpdateWorkspaceAgentStartupByIDParams) error UpdateWorkspaceAppHealthByID(ctx context.Context, arg UpdateWorkspaceAppHealthByIDParams) error UpdateWorkspaceAutostart(ctx context.Context, arg UpdateWorkspaceAutostartParams) error - UpdateWorkspaceBuildByID(ctx context.Context, arg UpdateWorkspaceBuildByIDParams) (WorkspaceBuild, error) - UpdateWorkspaceBuildCostByID(ctx context.Context, arg UpdateWorkspaceBuildCostByIDParams) (WorkspaceBuild, error) + UpdateWorkspaceBuildByID(ctx context.Context, arg UpdateWorkspaceBuildByIDParams) (WorkspaceBuildThin, error) + UpdateWorkspaceBuildCostByID(ctx context.Context, arg UpdateWorkspaceBuildCostByIDParams) (WorkspaceBuildThin, error) UpdateWorkspaceDeletedByID(ctx context.Context, arg UpdateWorkspaceDeletedByIDParams) error UpdateWorkspaceLastUsedAt(ctx context.Context, arg UpdateWorkspaceLastUsedAtParams) error UpdateWorkspaceTTL(ctx context.Context, arg UpdateWorkspaceTTLParams) error diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index c05b97727a368..c4e109b830678 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -5785,15 +5785,15 @@ func (q *sqlQuerier) InsertWorkspaceBuildParameters(ctx context.Context, arg Ins const getLatestWorkspaceBuildByWorkspaceID = `-- name: GetLatestWorkspaceBuildByWorkspaceID :one SELECT - id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost + id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, organization_id, workspace_owner_id FROM - workspace_builds + workspace_builds_rbac WHERE - workspace_id = $1 + workspace_id = $1 ORDER BY build_number desc LIMIT - 1 + 1 ` func (q *sqlQuerier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (WorkspaceBuild, error) { @@ -5813,12 +5813,14 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, w &i.Deadline, &i.Reason, &i.DailyCost, + &i.OrganizationID, + &i.WorkspaceOwnerID, ) return i, err } const getLatestWorkspaceBuilds = `-- name: GetLatestWorkspaceBuilds :many -SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost +SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.organization_id, wb.workspace_owner_id FROM ( SELECT workspace_id, MAX(build_number) as max_build_number @@ -5828,7 +5830,7 @@ FROM ( workspace_id ) m JOIN - workspace_builds wb + workspace_builds_rbac wb ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number ` @@ -5855,6 +5857,8 @@ func (q *sqlQuerier) GetLatestWorkspaceBuilds(ctx context.Context) ([]WorkspaceB &i.Deadline, &i.Reason, &i.DailyCost, + &i.OrganizationID, + &i.WorkspaceOwnerID, ); err != nil { return nil, err } @@ -5870,7 +5874,7 @@ func (q *sqlQuerier) GetLatestWorkspaceBuilds(ctx context.Context) ([]WorkspaceB } const getLatestWorkspaceBuildsByWorkspaceIDs = `-- name: GetLatestWorkspaceBuildsByWorkspaceIDs :many -SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost +SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.organization_id, wb.workspace_owner_id FROM ( SELECT workspace_id, MAX(build_number) as max_build_number @@ -5882,7 +5886,7 @@ FROM ( workspace_id ) m JOIN - workspace_builds wb + workspace_builds_rbac wb ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number ` @@ -5909,6 +5913,8 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, &i.Deadline, &i.Reason, &i.DailyCost, + &i.OrganizationID, + &i.WorkspaceOwnerID, ); err != nil { return nil, err } @@ -5925,9 +5931,9 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, const getWorkspaceBuildByID = `-- name: GetWorkspaceBuildByID :one SELECT - id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost + id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, organization_id, workspace_owner_id FROM - workspace_builds + workspace_builds_rbac WHERE id = $1 LIMIT @@ -5951,15 +5957,17 @@ func (q *sqlQuerier) GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (W &i.Deadline, &i.Reason, &i.DailyCost, + &i.OrganizationID, + &i.WorkspaceOwnerID, ) return i, err } const getWorkspaceBuildByJobID = `-- name: GetWorkspaceBuildByJobID :one SELECT - id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost + id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, organization_id, workspace_owner_id FROM - workspace_builds + workspace_builds_rbac WHERE job_id = $1 LIMIT @@ -5983,15 +5991,17 @@ func (q *sqlQuerier) GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UU &i.Deadline, &i.Reason, &i.DailyCost, + &i.OrganizationID, + &i.WorkspaceOwnerID, ) return i, err } const getWorkspaceBuildByWorkspaceIDAndBuildNumber = `-- name: GetWorkspaceBuildByWorkspaceIDAndBuildNumber :one SELECT - id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost + id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, organization_id, workspace_owner_id FROM - workspace_builds + workspace_builds_rbac WHERE workspace_id = $1 AND build_number = $2 @@ -6019,19 +6029,21 @@ func (q *sqlQuerier) GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx context.Co &i.Deadline, &i.Reason, &i.DailyCost, + &i.OrganizationID, + &i.WorkspaceOwnerID, ) return i, err } const getWorkspaceBuildsByWorkspaceID = `-- name: GetWorkspaceBuildsByWorkspaceID :many SELECT - id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost + id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, organization_id, workspace_owner_id FROM - workspace_builds + workspace_builds_rbac WHERE - workspace_builds.workspace_id = $1 - AND workspace_builds.created_at > $2 - AND CASE + workspace_builds_rbac.workspace_id = $1 + AND workspace_builds_rbac.created_at > $2 + AND CASE -- This allows using the last element on a page as effectively a cursor. -- This is an important option for scripts that need to paginate without -- duplicating or missing data. @@ -6094,6 +6106,8 @@ func (q *sqlQuerier) GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg Ge &i.Deadline, &i.Reason, &i.DailyCost, + &i.OrganizationID, + &i.WorkspaceOwnerID, ); err != nil { return nil, err } @@ -6109,7 +6123,7 @@ func (q *sqlQuerier) GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg Ge } const getWorkspaceBuildsCreatedAfter = `-- name: GetWorkspaceBuildsCreatedAfter :many -SELECT id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost FROM workspace_builds WHERE created_at > $1 +SELECT id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, organization_id, workspace_owner_id FROM workspace_builds_rbac WHERE created_at > $1 ` func (q *sqlQuerier) GetWorkspaceBuildsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceBuild, error) { @@ -6135,6 +6149,8 @@ func (q *sqlQuerier) GetWorkspaceBuildsCreatedAfter(ctx context.Context, created &i.Deadline, &i.Reason, &i.DailyCost, + &i.OrganizationID, + &i.WorkspaceOwnerID, ); err != nil { return nil, err } @@ -6184,7 +6200,7 @@ type InsertWorkspaceBuildParams struct { Reason BuildReason `db:"reason" json:"reason"` } -func (q *sqlQuerier) InsertWorkspaceBuild(ctx context.Context, arg InsertWorkspaceBuildParams) (WorkspaceBuild, error) { +func (q *sqlQuerier) InsertWorkspaceBuild(ctx context.Context, arg InsertWorkspaceBuildParams) (WorkspaceBuildThin, error) { row := q.db.QueryRowContext(ctx, insertWorkspaceBuild, arg.ID, arg.CreatedAt, @@ -6199,7 +6215,7 @@ func (q *sqlQuerier) InsertWorkspaceBuild(ctx context.Context, arg InsertWorkspa arg.Deadline, arg.Reason, ) - var i WorkspaceBuild + var i WorkspaceBuildThin err := row.Scan( &i.ID, &i.CreatedAt, @@ -6236,14 +6252,14 @@ type UpdateWorkspaceBuildByIDParams struct { Deadline time.Time `db:"deadline" json:"deadline"` } -func (q *sqlQuerier) UpdateWorkspaceBuildByID(ctx context.Context, arg UpdateWorkspaceBuildByIDParams) (WorkspaceBuild, error) { +func (q *sqlQuerier) UpdateWorkspaceBuildByID(ctx context.Context, arg UpdateWorkspaceBuildByIDParams) (WorkspaceBuildThin, error) { row := q.db.QueryRowContext(ctx, updateWorkspaceBuildByID, arg.ID, arg.UpdatedAt, arg.ProvisionerState, arg.Deadline, ) - var i WorkspaceBuild + var i WorkspaceBuildThin err := row.Scan( &i.ID, &i.CreatedAt, @@ -6276,9 +6292,9 @@ type UpdateWorkspaceBuildCostByIDParams struct { DailyCost int32 `db:"daily_cost" json:"daily_cost"` } -func (q *sqlQuerier) UpdateWorkspaceBuildCostByID(ctx context.Context, arg UpdateWorkspaceBuildCostByIDParams) (WorkspaceBuild, error) { +func (q *sqlQuerier) UpdateWorkspaceBuildCostByID(ctx context.Context, arg UpdateWorkspaceBuildCostByIDParams) (WorkspaceBuildThin, error) { row := q.db.QueryRowContext(ctx, updateWorkspaceBuildCostByID, arg.ID, arg.DailyCost) - var i WorkspaceBuild + var i WorkspaceBuildThin err := row.Scan( &i.ID, &i.CreatedAt, diff --git a/coderd/database/queries/workspacebuilds.sql b/coderd/database/queries/workspacebuilds.sql index 30658634da4e0..da1a9d5356a22 100644 --- a/coderd/database/queries/workspacebuilds.sql +++ b/coderd/database/queries/workspacebuilds.sql @@ -2,7 +2,7 @@ SELECT * FROM - workspace_builds + workspace_builds_rbac WHERE id = $1 LIMIT @@ -12,20 +12,20 @@ LIMIT SELECT * FROM - workspace_builds + workspace_builds_rbac WHERE job_id = $1 LIMIT 1; -- name: GetWorkspaceBuildsCreatedAfter :many -SELECT * FROM workspace_builds WHERE created_at > $1; +SELECT * FROM workspace_builds_rbac WHERE created_at > $1; -- name: GetWorkspaceBuildByWorkspaceIDAndBuildNumber :one SELECT * FROM - workspace_builds + workspace_builds_rbac WHERE workspace_id = $1 AND build_number = $2; @@ -34,11 +34,11 @@ WHERE SELECT * FROM - workspace_builds + workspace_builds_rbac WHERE - workspace_builds.workspace_id = $1 - AND workspace_builds.created_at > @since - AND CASE + workspace_builds_rbac.workspace_id = $1 + AND workspace_builds_rbac.created_at > @since + AND CASE -- This allows using the last element on a page as effectively a cursor. -- This is an important option for scripts that need to paginate without -- duplicating or missing data. @@ -65,15 +65,15 @@ LIMIT -- name: GetLatestWorkspaceBuildByWorkspaceID :one SELECT - * + * FROM - workspace_builds + workspace_builds_rbac WHERE - workspace_id = $1 + workspace_id = $1 ORDER BY build_number desc LIMIT - 1; + 1; -- name: GetLatestWorkspaceBuildsByWorkspaceIDs :many SELECT wb.* @@ -88,7 +88,7 @@ FROM ( workspace_id ) m JOIN - workspace_builds wb + workspace_builds_rbac wb ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number; -- name: GetLatestWorkspaceBuilds :many @@ -102,7 +102,7 @@ FROM ( workspace_id ) m JOIN - workspace_builds wb + workspace_builds_rbac wb ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number; -- name: InsertWorkspaceBuild :one diff --git a/coderd/database/sqlc.yaml b/coderd/database/sqlc.yaml index e708a6c4ca065..98c4308da584b 100644 --- a/coderd/database/sqlc.yaml +++ b/coderd/database/sqlc.yaml @@ -20,6 +20,8 @@ overrides: go_type: type: "TemplateACL" rename: + workspace_builds_rbac: WorkspaceBuild + workspace_build: WorkspaceBuildThin api_key: APIKey api_key_scope: APIKeyScope api_key_scope_all: APIKeyScopeAll diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index bcc0d6b0ad93a..34ee3403fbdf7 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -635,13 +635,14 @@ func TestFailJob(t *testing.T) { ID: uuid.New(), }) require.NoError(t, err) - build, err := srv.Database.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{ + buildThin, err := srv.Database.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{ ID: uuid.New(), WorkspaceID: workspace.ID, Transition: database.WorkspaceTransitionStart, Reason: database.BuildReasonInitiator, }) require.NoError(t, err) + build := buildThin.WithWorkspace(workspace) input, err := json.Marshal(provisionerdserver.WorkspaceProvisionJob{ WorkspaceBuildID: build.ID, }) diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index 26176f5a0c93e..32195f60c238b 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -584,7 +584,7 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { return xerrors.Errorf("insert provisioner job: %w", err) } - workspaceBuild, err = db.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{ + thinBuild, err := db.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{ ID: workspaceBuildID, CreatedAt: database.Now(), UpdatedAt: database.Now(), @@ -601,6 +601,9 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { return xerrors.Errorf("insert workspace build: %w", err) } + // Assign owning fields. + workspaceBuild = thinBuild.WithWorkspace(workspace) + names := make([]string, 0, len(parameters)) values := make([]string, 0, len(parameters)) for _, param := range parameters { diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 4bafa506b42aa..aad1b84b033b1 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -528,7 +528,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req if err != nil { return xerrors.Errorf("insert provisioner job: %w", err) } - workspaceBuild, err = db.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{ + workspaceBuildThin, err := db.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{ ID: workspaceBuildID, CreatedAt: now, UpdatedAt: now, @@ -544,6 +544,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req if err != nil { return xerrors.Errorf("insert workspace build: %w", err) } + workspaceBuild = workspaceBuildThin.WithWorkspace(workspace) names := make([]string, 0, len(createWorkspace.RichParameterValues)) values := make([]string, 0, len(createWorkspace.RichParameterValues))