mirror of https://github.com/coder/coder.git
chore: Implement joins with golang templates (#6429)
* feat: Implement view for workspace builds to include rbac info * Removes the need to fetch the workspace to run an rbac check. * chore: Use workspace build as RBAC object * chore: Use golang templates instead of sqlc files
This commit is contained in:
parent
a666539bfa
commit
8b125d6c5d
|
@ -14,7 +14,7 @@ type Auditable interface {
|
|||
database.User |
|
||||
database.Workspace |
|
||||
database.GitSSHKey |
|
||||
database.WorkspaceBuild |
|
||||
database.WorkspaceBuildRBAC |
|
||||
database.AuditableGroup |
|
||||
database.License
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ func ResourceTarget[T Auditable](tgt T) string {
|
|||
return typed.Username
|
||||
case database.Workspace:
|
||||
return typed.Name
|
||||
case database.WorkspaceBuild:
|
||||
case database.WorkspaceBuildRBAC:
|
||||
// this isn't used
|
||||
return ""
|
||||
case database.GitSSHKey:
|
||||
|
@ -89,7 +89,7 @@ func ResourceID[T Auditable](tgt T) uuid.UUID {
|
|||
return typed.ID
|
||||
case database.Workspace:
|
||||
return typed.ID
|
||||
case database.WorkspaceBuild:
|
||||
case database.WorkspaceBuildRBAC:
|
||||
return typed.ID
|
||||
case database.GitSSHKey:
|
||||
return typed.UserID
|
||||
|
@ -114,7 +114,7 @@ func ResourceType[T Auditable](tgt T) database.ResourceType {
|
|||
return database.ResourceTypeUser
|
||||
case database.Workspace:
|
||||
return database.ResourceTypeWorkspace
|
||||
case database.WorkspaceBuild:
|
||||
case database.WorkspaceBuildRBAC:
|
||||
return database.ResourceTypeWorkspaceBuild
|
||||
case database.GitSSHKey:
|
||||
return database.ResourceTypeGitSshKey
|
||||
|
|
|
@ -204,7 +204,7 @@ func isEligibleForAutoStartStop(ws database.Workspace) bool {
|
|||
|
||||
func getNextTransition(
|
||||
ws database.Workspace,
|
||||
priorHistory database.WorkspaceBuild,
|
||||
priorHistory database.WorkspaceBuildRBAC,
|
||||
priorJob database.ProvisionerJob,
|
||||
) (
|
||||
validTransition database.WorkspaceTransition,
|
||||
|
@ -239,7 +239,7 @@ func getNextTransition(
|
|||
|
||||
// TODO(cian): this function duplicates most of api.postWorkspaceBuilds. Refactor.
|
||||
// See: https://github.com/coder/coder/issues/1401
|
||||
func build(ctx context.Context, store database.Store, workspace database.Workspace, trans database.WorkspaceTransition, priorHistory database.WorkspaceBuild, priorJob database.ProvisionerJob) error {
|
||||
func build(ctx context.Context, store database.Store, workspace database.Workspace, trans database.WorkspaceTransition, priorHistory database.WorkspaceBuildRBAC, priorJob database.ProvisionerJob) error {
|
||||
template, err := store.GetTemplateByID(ctx, workspace.TemplateID)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get workspace template: %w", err)
|
||||
|
|
|
@ -2,17 +2,16 @@ package executor_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/goleak"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/goleak"
|
||||
|
||||
"github.com/coder/coder/coderd/autobuild/executor"
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/database/dbtestutil"
|
||||
"github.com/coder/coder/coderd/schedule"
|
||||
"github.com/coder/coder/coderd/util/ptr"
|
||||
"github.com/coder/coder/codersdk"
|
||||
|
@ -493,7 +492,7 @@ func TestExecutorWorkspaceAutostopNoWaitChangedMyMind(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestExecutorAutostartMultipleOK(t *testing.T) {
|
||||
if os.Getenv("DB") == "" {
|
||||
if !dbtestutil.UsingRealDatabase() {
|
||||
t.Skip(`This test only really works when using a "real" database, similar to a HA setup`)
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@ import (
|
|||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/coderd/database/sqlxqueries"
|
||||
)
|
||||
|
||||
// Store contains all queryable database functions.
|
||||
|
@ -37,11 +39,21 @@ type DBTX interface {
|
|||
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
|
||||
SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error
|
||||
GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error
|
||||
|
||||
// Extends the sqlx interface
|
||||
sqlx.QueryerContext
|
||||
}
|
||||
|
||||
// New creates a new database store using a SQL database connection.
|
||||
func New(sdb *sql.DB) Store {
|
||||
dbx := sqlx.NewDb(sdb, "postgres")
|
||||
// Load the embedded queries. If this fails, some of our queries
|
||||
// will never work. This is a fatal developer error that should never
|
||||
// happen.
|
||||
_, err := sqlxqueries.LoadQueries()
|
||||
if err != nil {
|
||||
panic(xerrors.Errorf("load queries: %w", err))
|
||||
}
|
||||
return &sqlQuerier{
|
||||
db: dbx,
|
||||
sdb: dbx,
|
||||
|
|
|
@ -1167,25 +1167,12 @@ func (q *querier) GetWorkspaces(ctx context.Context, arg database.GetWorkspacesP
|
|||
return q.db.GetAuthorizedWorkspaces(ctx, arg, prep)
|
||||
}
|
||||
|
||||
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)
|
||||
func (q *querier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (database.WorkspaceBuildRBAC, error) {
|
||||
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)
|
||||
func (q *querier) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]database.WorkspaceBuildRBAC, error) {
|
||||
return fetchWithPostFilter(q.auth, q.db.GetLatestWorkspaceBuildsByWorkspaceIDs)(ctx, ids)
|
||||
}
|
||||
|
||||
func (q *querier) GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (database.WorkspaceAgent, error) {
|
||||
|
@ -1263,35 +1250,16 @@ func (q *querier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid.UU
|
|||
return q.db.GetWorkspaceAppsByAgentID(ctx, agentID)
|
||||
}
|
||||
|
||||
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
|
||||
func (q *querier) GetWorkspaceBuildByID(ctx context.Context, buildID uuid.UUID) (database.WorkspaceBuildRBAC, error) {
|
||||
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
|
||||
func (q *querier) GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UUID) (database.WorkspaceBuildRBAC, error) {
|
||||
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)
|
||||
func (q *querier) GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx context.Context, arg database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams) (database.WorkspaceBuildRBAC, error) {
|
||||
return fetch(q.log, q.auth, q.db.GetWorkspaceBuildByWorkspaceIDAndBuildNumber)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) GetWorkspaceBuildParameters(ctx context.Context, workspaceBuildID uuid.UUID) ([]database.WorkspaceBuildParameter, error) {
|
||||
|
@ -1305,11 +1273,20 @@ func (q *querier) GetWorkspaceBuildParameters(ctx context.Context, workspaceBuil
|
|||
return q.db.GetWorkspaceBuildParameters(ctx, workspaceBuildID)
|
||||
}
|
||||
|
||||
func (q *querier) GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg database.GetWorkspaceBuildsByWorkspaceIDParams) ([]database.WorkspaceBuild, error) {
|
||||
if _, err := q.GetWorkspaceByID(ctx, arg.WorkspaceID); err != nil {
|
||||
func (q *querier) GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg database.GetWorkspaceBuildsByWorkspaceIDParams) ([]database.WorkspaceBuildRBAC, error) {
|
||||
builds, err := q.db.GetWorkspaceBuildsByWorkspaceID(ctx, arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetWorkspaceBuildsByWorkspaceID(ctx, arg)
|
||||
if len(builds) == 0 {
|
||||
return []database.WorkspaceBuildRBAC{}, nil
|
||||
}
|
||||
// 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) {
|
||||
|
@ -1369,11 +1346,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)
|
||||
}
|
||||
|
@ -1414,12 +1387,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
|
||||
}
|
||||
|
@ -1483,11 +1451,7 @@ func (q *querier) UpdateWorkspaceBuildByID(ctx context.Context, arg database.Upd
|
|||
return database.WorkspaceBuild{}, 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
|
||||
}
|
||||
|
|
|
@ -956,16 +956,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)
|
||||
|
@ -979,7 +979,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*/ ).
|
||||
|
@ -987,7 +987,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{
|
||||
|
@ -997,7 +997,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{
|
||||
|
@ -1006,7 +1006,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})
|
||||
|
@ -1018,7 +1018,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})
|
||||
|
@ -1028,13 +1028,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})
|
||||
|
@ -1045,17 +1045,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,
|
||||
|
@ -1069,14 +1069,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)
|
||||
|
@ -1091,14 +1091,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})
|
||||
|
@ -1107,7 +1107,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{})
|
||||
}))
|
||||
|
@ -1123,7 +1123,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*/ ).
|
||||
|
@ -1207,9 +1207,11 @@ func (s *MethodTestSuite) TestWorkspace() {
|
|||
ws := dbgen.Workspace(s.T(), db, database.Workspace{})
|
||||
build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()})
|
||||
check.Args(database.UpdateWorkspaceBuildByIDParams{
|
||||
ID: build.ID,
|
||||
UpdatedAt: build.UpdatedAt,
|
||||
Deadline: build.Deadline,
|
||||
ID: build.ID,
|
||||
UpdatedAt: build.UpdatedAt,
|
||||
Deadline: build.Deadline,
|
||||
ProvisionerState: build.ProvisionerState,
|
||||
MaxDeadline: build.MaxDeadline,
|
||||
}).Asserts(ws, rbac.ActionUpdate).Returns(build)
|
||||
}))
|
||||
s.Run("SoftDeleteWorkspaceByID", s.Subtest(func(db database.Store, check *expects) {
|
||||
|
|
|
@ -76,7 +76,7 @@ func (q *querier) GetUserLinkByUserIDLoginType(ctx context.Context, arg database
|
|||
return q.db.GetUserLinkByUserIDLoginType(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) GetLatestWorkspaceBuilds(ctx context.Context) ([]database.WorkspaceBuild, error) {
|
||||
func (q *querier) GetLatestWorkspaceBuilds(ctx context.Context) ([]database.WorkspaceBuildRBAC, error) {
|
||||
// This function is a system function until we implement a join for workspace builds.
|
||||
// This is because we need to query for all related workspaces to the returned builds.
|
||||
// This is a very inefficient method of fetching the latest workspace builds.
|
||||
|
@ -177,7 +177,7 @@ func (q *querier) GetLastUpdateCheck(ctx context.Context) (string, error) {
|
|||
// Telemetry related functions. These functions are system functions for returning
|
||||
// telemetry data. Never called by a user.
|
||||
|
||||
func (q *querier) GetWorkspaceBuildsCreatedAfter(ctx context.Context, createdAt time.Time) ([]database.WorkspaceBuild, error) {
|
||||
func (q *querier) GetWorkspaceBuildsCreatedAfter(ctx context.Context, createdAt time.Time) ([]database.WorkspaceBuildRBAC, error) {
|
||||
return q.db.GetWorkspaceBuildsCreatedAfter(ctx, createdAt)
|
||||
}
|
||||
|
||||
|
|
|
@ -34,8 +34,8 @@ func (s *MethodTestSuite) TestSystemFunctions() {
|
|||
}).Asserts().Returns(l)
|
||||
}))
|
||||
s.Run("GetLatestWorkspaceBuilds", s.Subtest(func(db database.Store, check *expects) {
|
||||
dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{})
|
||||
dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{})
|
||||
dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuildRBAC{})
|
||||
dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuildRBAC{})
|
||||
check.Args().Asserts()
|
||||
}))
|
||||
s.Run("GetWorkspaceAgentByAuthToken", s.Subtest(func(db database.Store, check *expects) {
|
||||
|
@ -92,7 +92,7 @@ func (s *MethodTestSuite) TestSystemFunctions() {
|
|||
check.Args().Asserts()
|
||||
}))
|
||||
s.Run("UpdateWorkspaceBuildCostByID", s.Subtest(func(db database.Store, check *expects) {
|
||||
b := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{})
|
||||
b := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuildRBAC{})
|
||||
o := b
|
||||
o.DailyCost = 10
|
||||
check.Args(database.UpdateWorkspaceBuildCostByIDParams{
|
||||
|
|
|
@ -1452,66 +1452,66 @@ func (q *fakeQuerier) GetWorkspaceAppsByAgentIDs(_ context.Context, ids []uuid.U
|
|||
return apps, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetWorkspaceBuildByID(_ context.Context, id uuid.UUID) (database.WorkspaceBuild, error) {
|
||||
func (q *fakeQuerier) GetWorkspaceBuildByID(_ context.Context, id uuid.UUID) (database.WorkspaceBuildRBAC, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
for _, history := range q.workspaceBuilds {
|
||||
if history.ID == id {
|
||||
return history, nil
|
||||
return q.expandWorkspaceThin(history), nil
|
||||
}
|
||||
}
|
||||
return database.WorkspaceBuild{}, sql.ErrNoRows
|
||||
return database.WorkspaceBuildRBAC{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetWorkspaceBuildByJobID(_ context.Context, jobID uuid.UUID) (database.WorkspaceBuild, error) {
|
||||
func (q *fakeQuerier) GetWorkspaceBuildByJobID(_ context.Context, jobID uuid.UUID) (database.WorkspaceBuildRBAC, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
for _, build := range q.workspaceBuilds {
|
||||
if build.JobID == jobID {
|
||||
return build, nil
|
||||
return q.expandWorkspaceThin(build), nil
|
||||
}
|
||||
}
|
||||
return database.WorkspaceBuild{}, sql.ErrNoRows
|
||||
return database.WorkspaceBuildRBAC{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (database.WorkspaceBuild, error) {
|
||||
func (q *fakeQuerier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (database.WorkspaceBuildRBAC, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
return q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspaceID)
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) getLatestWorkspaceBuildByWorkspaceIDNoLock(_ context.Context, workspaceID uuid.UUID) (database.WorkspaceBuild, error) {
|
||||
var row database.WorkspaceBuild
|
||||
func (q *fakeQuerier) getLatestWorkspaceBuildByWorkspaceIDNoLock(_ context.Context, workspaceID uuid.UUID) (database.WorkspaceBuildRBAC, error) {
|
||||
var row database.WorkspaceBuildRBAC
|
||||
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
|
||||
}
|
||||
}
|
||||
if buildNum == -1 {
|
||||
return database.WorkspaceBuild{}, sql.ErrNoRows
|
||||
return database.WorkspaceBuildRBAC{}, sql.ErrNoRows
|
||||
}
|
||||
return row, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetLatestWorkspaceBuilds(_ context.Context) ([]database.WorkspaceBuild, error) {
|
||||
func (q *fakeQuerier) GetLatestWorkspaceBuilds(_ context.Context) ([]database.WorkspaceBuildRBAC, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
builds := make(map[uuid.UUID]database.WorkspaceBuild)
|
||||
builds := make(map[uuid.UUID]database.WorkspaceBuildRBAC)
|
||||
buildNumbers := make(map[uuid.UUID]int32)
|
||||
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
|
||||
}
|
||||
}
|
||||
var returnBuilds []database.WorkspaceBuild
|
||||
var returnBuilds []database.WorkspaceBuildRBAC
|
||||
for i, n := range buildNumbers {
|
||||
if n > 0 {
|
||||
b := builds[i]
|
||||
|
@ -1524,21 +1524,21 @@ func (q *fakeQuerier) GetLatestWorkspaceBuilds(_ context.Context) ([]database.Wo
|
|||
return returnBuilds, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetLatestWorkspaceBuildsByWorkspaceIDs(_ context.Context, ids []uuid.UUID) ([]database.WorkspaceBuild, error) {
|
||||
func (q *fakeQuerier) GetLatestWorkspaceBuildsByWorkspaceIDs(_ context.Context, ids []uuid.UUID) ([]database.WorkspaceBuildRBAC, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
builds := make(map[uuid.UUID]database.WorkspaceBuild)
|
||||
builds := make(map[uuid.UUID]database.WorkspaceBuildRBAC)
|
||||
buildNumbers := make(map[uuid.UUID]int32)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
var returnBuilds []database.WorkspaceBuild
|
||||
var returnBuilds []database.WorkspaceBuildRBAC
|
||||
for i, n := range buildNumbers {
|
||||
if n > 0 {
|
||||
b := builds[i]
|
||||
|
@ -1553,7 +1553,7 @@ func (q *fakeQuerier) GetLatestWorkspaceBuildsByWorkspaceIDs(_ context.Context,
|
|||
|
||||
func (q *fakeQuerier) GetWorkspaceBuildsByWorkspaceID(_ context.Context,
|
||||
params database.GetWorkspaceBuildsByWorkspaceIDParams,
|
||||
) ([]database.WorkspaceBuild, error) {
|
||||
) ([]database.WorkspaceBuildRBAC, error) {
|
||||
if err := validateDatabaseType(params); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -1561,18 +1561,18 @@ func (q *fakeQuerier) GetWorkspaceBuildsByWorkspaceID(_ context.Context,
|
|||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
history := make([]database.WorkspaceBuild, 0)
|
||||
history := make([]database.WorkspaceBuildRBAC, 0)
|
||||
for _, workspaceBuild := range q.workspaceBuilds {
|
||||
if workspaceBuild.CreatedAt.Before(params.Since) {
|
||||
continue
|
||||
}
|
||||
if workspaceBuild.WorkspaceID == params.WorkspaceID {
|
||||
history = append(history, workspaceBuild)
|
||||
history = append(history, q.expandWorkspaceThin(workspaceBuild))
|
||||
}
|
||||
}
|
||||
|
||||
// Order by build_number
|
||||
slices.SortFunc(history, func(a, b database.WorkspaceBuild) bool {
|
||||
slices.SortFunc(history, func(a, b database.WorkspaceBuildRBAC) bool {
|
||||
// use greater than since we want descending order
|
||||
return a.BuildNumber > b.BuildNumber
|
||||
})
|
||||
|
@ -1614,9 +1614,9 @@ func (q *fakeQuerier) GetWorkspaceBuildsByWorkspaceID(_ context.Context,
|
|||
return history, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetWorkspaceBuildByWorkspaceIDAndBuildNumber(_ context.Context, arg database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams) (database.WorkspaceBuild, error) {
|
||||
func (q *fakeQuerier) GetWorkspaceBuildByWorkspaceIDAndBuildNumber(_ context.Context, arg database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams) (database.WorkspaceBuildRBAC, error) {
|
||||
if err := validateDatabaseType(arg); err != nil {
|
||||
return database.WorkspaceBuild{}, err
|
||||
return database.WorkspaceBuildRBAC{}, err
|
||||
}
|
||||
|
||||
q.mutex.RLock()
|
||||
|
@ -1629,9 +1629,9 @@ 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
|
||||
return database.WorkspaceBuildRBAC{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetWorkspaceBuildParameters(_ context.Context, workspaceBuildID uuid.UUID) ([]database.WorkspaceBuildParameter, error) {
|
||||
|
@ -1648,14 +1648,14 @@ func (q *fakeQuerier) GetWorkspaceBuildParameters(_ context.Context, workspaceBu
|
|||
return params, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetWorkspaceBuildsCreatedAfter(_ context.Context, after time.Time) ([]database.WorkspaceBuild, error) {
|
||||
func (q *fakeQuerier) GetWorkspaceBuildsCreatedAfter(_ context.Context, after time.Time) ([]database.WorkspaceBuildRBAC, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
workspaceBuilds := make([]database.WorkspaceBuild, 0)
|
||||
workspaceBuilds := make([]database.WorkspaceBuildRBAC, 0)
|
||||
for _, workspaceBuild := range q.workspaceBuilds {
|
||||
if workspaceBuild.CreatedAt.After(after) {
|
||||
workspaceBuilds = append(workspaceBuilds, workspaceBuild)
|
||||
workspaceBuilds = append(workspaceBuilds, q.expandWorkspaceThin(workspaceBuild))
|
||||
}
|
||||
}
|
||||
return workspaceBuilds, nil
|
||||
|
@ -3250,6 +3250,7 @@ func (q *fakeQuerier) InsertWorkspaceBuild(_ context.Context, arg database.Inser
|
|||
JobID: arg.JobID,
|
||||
ProvisionerState: arg.ProvisionerState,
|
||||
Deadline: arg.Deadline,
|
||||
MaxDeadline: arg.MaxDeadline,
|
||||
Reason: arg.Reason,
|
||||
}
|
||||
q.workspaceBuilds = append(q.workspaceBuilds, workspaceBuild)
|
||||
|
@ -4627,13 +4628,13 @@ func (q *fakeQuerier) GetQuotaConsumedForUser(_ context.Context, userID uuid.UUI
|
|||
continue
|
||||
}
|
||||
|
||||
var lastBuild database.WorkspaceBuild
|
||||
var lastBuild database.WorkspaceBuildRBAC
|
||||
for _, build := range q.workspaceBuilds {
|
||||
if build.WorkspaceID != workspace.ID {
|
||||
continue
|
||||
}
|
||||
if build.CreatedAt.After(lastBuild.CreatedAt) {
|
||||
lastBuild = build
|
||||
lastBuild = q.expandWorkspaceThin(build)
|
||||
}
|
||||
}
|
||||
sum += int64(lastBuild.DailyCost)
|
||||
|
@ -4657,3 +4658,16 @@ func (q *fakeQuerier) UpdateWorkspaceAgentLifecycleStateByID(_ context.Context,
|
|||
}
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
// expandWorkspaceThin must be called from a locked context.
|
||||
func (q *fakeQuerier) expandWorkspaceThin(thin database.WorkspaceBuild) database.WorkspaceBuildRBAC {
|
||||
for _, workspace := range q.workspaces {
|
||||
if workspace.ID == thin.WorkspaceID {
|
||||
return thin.WithWorkspace(workspace)
|
||||
}
|
||||
}
|
||||
|
||||
return database.WorkspaceBuildRBAC{
|
||||
WorkspaceBuild: thin,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -153,20 +153,34 @@ 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 {
|
||||
type AnyWorkspaceBuild interface {
|
||||
database.WorkspaceBuildRBAC | database.WorkspaceBuild
|
||||
}
|
||||
|
||||
func WorkspaceBuild[B AnyWorkspaceBuild](t testing.TB, db database.Store, orig B) database.WorkspaceBuild {
|
||||
var thin database.WorkspaceBuild
|
||||
switch v := any(orig).(type) {
|
||||
case database.WorkspaceBuildRBAC:
|
||||
thin = v.WorkspaceBuild
|
||||
case database.WorkspaceBuild:
|
||||
thin = v
|
||||
default:
|
||||
panic(fmt.Sprintf("developer error: invalid type %T", v))
|
||||
}
|
||||
build, err := db.InsertWorkspaceBuild(context.Background(), database.InsertWorkspaceBuildParams{
|
||||
ID: takeFirst(orig.ID, uuid.New()),
|
||||
CreatedAt: takeFirst(orig.CreatedAt, database.Now()),
|
||||
UpdatedAt: takeFirst(orig.UpdatedAt, database.Now()),
|
||||
WorkspaceID: takeFirst(orig.WorkspaceID, uuid.New()),
|
||||
TemplateVersionID: takeFirst(orig.TemplateVersionID, uuid.New()),
|
||||
BuildNumber: takeFirst(orig.BuildNumber, 1),
|
||||
Transition: takeFirst(orig.Transition, database.WorkspaceTransitionStart),
|
||||
InitiatorID: takeFirst(orig.InitiatorID, uuid.New()),
|
||||
JobID: takeFirst(orig.JobID, uuid.New()),
|
||||
ProvisionerState: takeFirstSlice(orig.ProvisionerState, []byte{}),
|
||||
Deadline: takeFirst(orig.Deadline, database.Now().Add(time.Hour)),
|
||||
Reason: takeFirst(orig.Reason, database.BuildReasonInitiator),
|
||||
ID: takeFirst(thin.ID, uuid.New()),
|
||||
CreatedAt: takeFirst(thin.CreatedAt, database.Now()),
|
||||
UpdatedAt: takeFirst(thin.UpdatedAt, database.Now()),
|
||||
WorkspaceID: takeFirst(thin.WorkspaceID, uuid.New()),
|
||||
TemplateVersionID: takeFirst(thin.TemplateVersionID, uuid.New()),
|
||||
BuildNumber: takeFirst(thin.BuildNumber, 1),
|
||||
Transition: takeFirst(thin.Transition, database.WorkspaceTransitionStart),
|
||||
InitiatorID: takeFirst(thin.InitiatorID, uuid.New()),
|
||||
JobID: takeFirst(thin.JobID, uuid.New()),
|
||||
ProvisionerState: takeFirstSlice(thin.ProvisionerState, []byte{}),
|
||||
Deadline: takeFirst(thin.Deadline, database.Now().Add(time.Hour)),
|
||||
MaxDeadline: takeFirst(thin.MaxDeadline, database.Now().Add(time.Hour*24*7)),
|
||||
Reason: takeFirst(thin.Reason, database.BuildReasonInitiator),
|
||||
})
|
||||
require.NoError(t, err, "insert workspace build")
|
||||
return build
|
||||
|
@ -219,7 +233,7 @@ func OrganizationMember(t testing.TB, db database.Store, orig database.Organizat
|
|||
UpdatedAt: takeFirst(orig.UpdatedAt, database.Now()),
|
||||
Roles: takeFirstSlice(orig.Roles, []string{}),
|
||||
})
|
||||
require.NoError(t, err, "insert organization")
|
||||
require.NoError(t, err, "insert organization member")
|
||||
return mem
|
||||
}
|
||||
|
||||
|
|
|
@ -166,8 +166,8 @@ func TestGenerator(t *testing.T) {
|
|||
t.Run("WorkspaceBuild", func(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)))
|
||||
exp := dbgen.WorkspaceBuild(t, db, database.WorkspaceBuildRBAC{})
|
||||
require.Equal(t, exp, must(db.GetWorkspaceBuildByID(context.Background(), exp.ID)).WorkspaceBuild)
|
||||
})
|
||||
|
||||
t.Run("User", func(t *testing.T) {
|
||||
|
|
|
@ -13,9 +13,14 @@ func takeFirstIP(values ...net.IPNet) net.IPNet {
|
|||
// takeFirstSlice implements takeFirst for []any.
|
||||
// []any is not a comparable type.
|
||||
func takeFirstSlice[T any](values ...[]T) []T {
|
||||
return takeFirstF(values, func(v []T) bool {
|
||||
out := takeFirstF(values, func(v []T) bool {
|
||||
return len(v) != 0
|
||||
})
|
||||
// Prevent nil slices
|
||||
if out == nil {
|
||||
return []T{}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// takeFirstF takes the first value that returns true
|
||||
|
|
|
@ -13,12 +13,16 @@ import (
|
|||
"github.com/coder/coder/coderd/database/postgres"
|
||||
)
|
||||
|
||||
func UsingRealDatabase() bool {
|
||||
return os.Getenv("DB") != ""
|
||||
}
|
||||
|
||||
func NewDB(t *testing.T) (database.Store, database.Pubsub) {
|
||||
t.Helper()
|
||||
|
||||
db := dbfake.New()
|
||||
pubsub := database.NewPubsubInMemory()
|
||||
if os.Getenv("DB") != "" {
|
||||
if UsingRealDatabase() {
|
||||
connectionURL := os.Getenv("CODER_PG_CONNECTION_URL")
|
||||
if connectionURL == "" {
|
||||
var (
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/coder/coder/coderd/rbac"
|
||||
)
|
||||
|
||||
|
@ -101,6 +103,12 @@ func (g Group) RBACObject() rbac.Object {
|
|||
InOrg(g.OrganizationID)
|
||||
}
|
||||
|
||||
func (b WorkspaceBuildRBAC) 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).
|
||||
|
@ -183,6 +191,18 @@ func (l License) RBACObject() rbac.Object {
|
|||
return rbac.ResourceLicense.WithIDString(strconv.FormatInt(int64(l.ID), 10))
|
||||
}
|
||||
|
||||
func (b WorkspaceBuild) WithWorkspace(workspace Workspace) WorkspaceBuildRBAC {
|
||||
return b.Expand(workspace.OrganizationID, workspace.OwnerID)
|
||||
}
|
||||
|
||||
func (b WorkspaceBuild) Expand(orgID, ownerID uuid.UUID) WorkspaceBuildRBAC {
|
||||
return WorkspaceBuildRBAC{
|
||||
WorkspaceBuild: b,
|
||||
OrganizationID: orgID,
|
||||
WorkspaceOwnerID: ownerID,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertUserRows(rows []GetUsersRow) []User {
|
||||
users := make([]User, len(rows))
|
||||
for i, r := range rows {
|
||||
|
|
|
@ -4,11 +4,13 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/lib/pq"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/coderd/database/sqlxqueries"
|
||||
"github.com/coder/coder/coderd/rbac"
|
||||
"github.com/coder/coder/coderd/rbac/regosql"
|
||||
)
|
||||
|
@ -178,6 +180,95 @@ func (q *sqlQuerier) GetTemplateGroupRoles(ctx context.Context, id uuid.UUID) ([
|
|||
|
||||
type workspaceQuerier interface {
|
||||
GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspacesParams, prepared rbac.PreparedAuthorized) ([]GetWorkspacesRow, error)
|
||||
GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (WorkspaceBuildRBAC, error)
|
||||
GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UUID) (WorkspaceBuildRBAC, error)
|
||||
GetWorkspaceBuildsCreatedAfter(ctx context.Context, after time.Time) ([]WorkspaceBuildRBAC, error)
|
||||
GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx context.Context, arg GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams) (WorkspaceBuildRBAC, error)
|
||||
GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg GetWorkspaceBuildsByWorkspaceIDParams) ([]WorkspaceBuildRBAC, error)
|
||||
GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceBuildRBAC, error)
|
||||
GetLatestWorkspaceBuilds(ctx context.Context) ([]WorkspaceBuildRBAC, error)
|
||||
GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspacedID uuid.UUID) (WorkspaceBuildRBAC, error)
|
||||
}
|
||||
|
||||
// WorkspaceBuildRBAC extends WorkspaceBuild with fields that are used for RBAC.
|
||||
// This allows WorkspaceBuild to be used in Authorize() calls.
|
||||
type WorkspaceBuildRBAC struct {
|
||||
WorkspaceBuild
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||
WorkspaceOwnerID uuid.UUID `db:"workspace_owner_id" json:"workspace_owner_id"`
|
||||
}
|
||||
|
||||
type getWorkspaceBuildParams struct {
|
||||
BuildID uuid.UUID `db:"build_id"`
|
||||
JobID uuid.UUID `db:"job_id"`
|
||||
CreatedAfter time.Time `db:"created_after"`
|
||||
WorkspaceID uuid.UUID `db:"workspace_id"`
|
||||
BuildNumber int32 `db:"build_number"`
|
||||
LimitOpt int32 `db:"limit_opt"`
|
||||
Latest bool `db:"-"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) getWorkspaceBuild(ctx context.Context, arg getWorkspaceBuildParams) (WorkspaceBuildRBAC, error) {
|
||||
var res WorkspaceBuildRBAC
|
||||
arg.LimitOpt = 1
|
||||
return res, sqlxqueries.GetContext(ctx, q.db, "GetWorkspaceBuild", arg, &res)
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) selectWorkspaceBuild(ctx context.Context, arg getWorkspaceBuildParams) ([]WorkspaceBuildRBAC, error) {
|
||||
var res []WorkspaceBuildRBAC
|
||||
arg.LimitOpt = -1
|
||||
return res, sqlxqueries.SelectContext(ctx, q.db, "GetWorkspaceBuild", arg, &res)
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (WorkspaceBuildRBAC, error) {
|
||||
return q.getWorkspaceBuild(ctx, getWorkspaceBuildParams{BuildID: id})
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UUID) (WorkspaceBuildRBAC, error) {
|
||||
return q.getWorkspaceBuild(ctx, getWorkspaceBuildParams{JobID: jobID})
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceBuildsCreatedAfter(ctx context.Context, after time.Time) ([]WorkspaceBuildRBAC, error) {
|
||||
return q.selectWorkspaceBuild(ctx, getWorkspaceBuildParams{CreatedAfter: after})
|
||||
}
|
||||
|
||||
type GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams struct {
|
||||
BuildNumber int32
|
||||
WorkspaceID uuid.UUID
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx context.Context, arg GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams) (WorkspaceBuildRBAC, error) {
|
||||
return q.getWorkspaceBuild(ctx, getWorkspaceBuildParams{
|
||||
BuildNumber: arg.BuildNumber,
|
||||
WorkspaceID: arg.WorkspaceID,
|
||||
})
|
||||
}
|
||||
|
||||
type GetWorkspaceBuildsByWorkspaceIDParams struct {
|
||||
WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"`
|
||||
Since time.Time `db:"since" json:"since"`
|
||||
AfterID uuid.UUID `db:"after_id" json:"after_id"`
|
||||
OffsetOpt int32 `db:"offset_opt" json:"offset_opt"`
|
||||
LimitOpt int32 `db:"limit_opt" json:"limit_opt"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg GetWorkspaceBuildsByWorkspaceIDParams) ([]WorkspaceBuildRBAC, error) {
|
||||
var res []WorkspaceBuildRBAC
|
||||
return res, sqlxqueries.SelectContext(ctx, q.db, "GetWorkspaceBuildsByWorkspaceID", arg, &res)
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspacedID uuid.UUID) (WorkspaceBuildRBAC, error) {
|
||||
return q.getWorkspaceBuild(ctx, getWorkspaceBuildParams{WorkspaceID: workspacedID, Latest: true})
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceBuildRBAC, error) {
|
||||
var res []WorkspaceBuildRBAC
|
||||
return res, sqlxqueries.SelectContext(ctx, q.db, "GetLatestWorkspaceBuildsByWorkspaceIDs", ids, &res)
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetLatestWorkspaceBuilds(ctx context.Context) ([]WorkspaceBuildRBAC, error) {
|
||||
var res []WorkspaceBuildRBAC
|
||||
return res, sqlxqueries.SelectContext(ctx, q.db, "GetLatestWorkspaceBuilds", nil, &res)
|
||||
}
|
||||
|
||||
// GetAuthorizedWorkspaces returns all workspaces that the user is authorized to access.
|
||||
|
|
|
@ -0,0 +1,189 @@
|
|||
package database_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/database/dbgen"
|
||||
"github.com/coder/coder/coderd/database/dbtestutil"
|
||||
"github.com/coder/coder/coderd/rbac"
|
||||
)
|
||||
|
||||
func TestGetWorkspaceBuild(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !dbtestutil.UsingRealDatabase() {
|
||||
t.Skip("Test only runs against a real database")
|
||||
}
|
||||
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
|
||||
// Seed the database with some workspace builds.
|
||||
var (
|
||||
now = database.Now()
|
||||
org = dbgen.Organization(t, db, database.Organization{})
|
||||
user = dbgen.User(t, db, database.User{
|
||||
RBACRoles: []string{rbac.RoleOwner()},
|
||||
})
|
||||
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
||||
UserID: user.ID,
|
||||
OrganizationID: org.ID,
|
||||
})
|
||||
template = dbgen.Template(t, db, database.Template{
|
||||
OrganizationID: org.ID,
|
||||
CreatedBy: user.ID,
|
||||
})
|
||||
version = dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
||||
TemplateID: uuid.NullUUID{
|
||||
UUID: template.ID,
|
||||
Valid: true,
|
||||
},
|
||||
OrganizationID: org.ID,
|
||||
CreatedBy: user.ID,
|
||||
})
|
||||
workspace = dbgen.Workspace(t, db, database.Workspace{
|
||||
OwnerID: user.ID,
|
||||
OrganizationID: org.ID,
|
||||
TemplateID: template.ID,
|
||||
})
|
||||
jobs = []database.ProvisionerJob{
|
||||
dbgen.ProvisionerJob(t, db, database.ProvisionerJob{
|
||||
OrganizationID: org.ID,
|
||||
InitiatorID: user.ID,
|
||||
}),
|
||||
dbgen.ProvisionerJob(t, db, database.ProvisionerJob{
|
||||
OrganizationID: org.ID,
|
||||
InitiatorID: user.ID,
|
||||
}),
|
||||
}
|
||||
builds = []database.WorkspaceBuild{
|
||||
dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
||||
WorkspaceID: workspace.ID,
|
||||
TemplateVersionID: version.ID,
|
||||
BuildNumber: 1,
|
||||
Transition: database.WorkspaceTransitionStart,
|
||||
InitiatorID: user.ID,
|
||||
JobID: jobs[0].ID,
|
||||
Reason: database.BuildReasonInitiator,
|
||||
CreatedAt: now,
|
||||
}),
|
||||
dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
||||
WorkspaceID: workspace.ID,
|
||||
TemplateVersionID: version.ID,
|
||||
BuildNumber: 2,
|
||||
Transition: database.WorkspaceTransitionStart,
|
||||
InitiatorID: user.ID,
|
||||
JobID: jobs[1].ID,
|
||||
Reason: database.BuildReasonInitiator,
|
||||
CreatedAt: now.Add(time.Hour),
|
||||
}),
|
||||
}
|
||||
orderBuilds = []database.WorkspaceBuild{
|
||||
builds[1],
|
||||
builds[0],
|
||||
}
|
||||
ctx = context.Background()
|
||||
)
|
||||
|
||||
t.Run("GetWorkspaceBuildByID", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, expected := range builds {
|
||||
build, err := db.GetWorkspaceBuildByID(ctx, expected.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Equal(t, expected, build.WorkspaceBuild, "builds should be equal")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetWorkspaceBuildByJobID", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
for i, job := range jobs {
|
||||
build, err := db.GetWorkspaceBuildByJobID(ctx, job.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := builds[i]
|
||||
require.Equal(t, expected, build.WorkspaceBuild, "builds should be equal")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetWorkspaceBuildsCreatedAfter", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
found, err := db.GetWorkspaceBuildsCreatedAfter(ctx, builds[0].CreatedAt.Add(time.Second))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := builds[1]
|
||||
require.Len(t, found, 1, "should only be one build")
|
||||
require.Equal(t, expected, found[0].WorkspaceBuild, "builds should be equal")
|
||||
})
|
||||
|
||||
t.Run("GetWorkspaceBuildByWorkspaceIDAndBuildNumber", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, expected := range builds {
|
||||
build, err := db.GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx, database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams{
|
||||
BuildNumber: expected.BuildNumber,
|
||||
WorkspaceID: expected.WorkspaceID,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Equal(t, expected, build.WorkspaceBuild, "builds should be equal")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetWorkspaceBuildsByWorkspaceID", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
found, err := db.GetWorkspaceBuildsByWorkspaceID(ctx, database.GetWorkspaceBuildsByWorkspaceIDParams{
|
||||
WorkspaceID: workspace.ID,
|
||||
Since: builds[0].CreatedAt.Add(-1 * time.Hour),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Len(t, found, 2, "should be two builds")
|
||||
require.Equal(t, orderBuilds, toThins(found), "builds should be equal")
|
||||
})
|
||||
|
||||
t.Run("GetLatestWorkspaceBuildsByWorkspaceIDs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
found, err := db.GetLatestWorkspaceBuildsByWorkspaceIDs(ctx, []uuid.UUID{workspace.ID})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Len(t, found, 1, "should be only one build")
|
||||
require.Equal(t, builds[1], found[0].WorkspaceBuild, "builds should be equal")
|
||||
})
|
||||
|
||||
t.Run("GetLatestWorkspaceBuilds", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
found, err := db.GetLatestWorkspaceBuilds(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Len(t, found, 1, "should be only 1 build")
|
||||
require.Equal(t, builds[1], found[0].WorkspaceBuild, "builds should be equal")
|
||||
})
|
||||
|
||||
t.Run("GetLatestWorkspaceBuildByWorkspaceID", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
found, err := db.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspace.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Equal(t, builds[1], found.WorkspaceBuild, "builds should be equal")
|
||||
})
|
||||
}
|
||||
|
||||
func toThins(builds []database.WorkspaceBuildRBAC) []database.WorkspaceBuild {
|
||||
thins := make([]database.WorkspaceBuild, len(builds))
|
||||
for i, build := range builds {
|
||||
thins[i] = build.WorkspaceBuild
|
||||
}
|
||||
return thins
|
||||
}
|
|
@ -66,9 +66,6 @@ type sqlcQuerier interface {
|
|||
GetGroupMembers(ctx context.Context, groupID uuid.UUID) ([]User, error)
|
||||
GetGroupsByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]Group, error)
|
||||
GetLastUpdateCheck(ctx context.Context) (string, error)
|
||||
GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (WorkspaceBuild, error)
|
||||
GetLatestWorkspaceBuilds(ctx context.Context) ([]WorkspaceBuild, error)
|
||||
GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceBuild, error)
|
||||
GetLicenseByID(ctx context.Context, id int32) (License, error)
|
||||
GetLicenses(ctx context.Context) ([]License, error)
|
||||
GetLogoURL(ctx context.Context) (string, error)
|
||||
|
@ -127,12 +124,7 @@ type sqlcQuerier interface {
|
|||
GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceApp, error)
|
||||
GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceApp, error)
|
||||
GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceApp, error)
|
||||
GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (WorkspaceBuild, error)
|
||||
GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UUID) (WorkspaceBuild, error)
|
||||
GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx context.Context, arg GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams) (WorkspaceBuild, error)
|
||||
GetWorkspaceBuildParameters(ctx context.Context, workspaceBuildID uuid.UUID) ([]WorkspaceBuildParameter, error)
|
||||
GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg GetWorkspaceBuildsByWorkspaceIDParams) ([]WorkspaceBuild, error)
|
||||
GetWorkspaceBuildsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceBuild, error)
|
||||
GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUID) (Workspace, error)
|
||||
GetWorkspaceByID(ctx context.Context, id uuid.UUID) (Workspace, error)
|
||||
GetWorkspaceByOwnerIDAndName(ctx context.Context, arg GetWorkspaceByOwnerIDAndNameParams) (Workspace, error)
|
||||
|
|
|
@ -6033,380 +6033,6 @@ func (q *sqlQuerier) InsertWorkspaceBuildParameters(ctx context.Context, arg Ins
|
|||
return err
|
||||
}
|
||||
|
||||
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, max_deadline
|
||||
FROM
|
||||
workspace_builds
|
||||
WHERE
|
||||
workspace_id = $1
|
||||
ORDER BY
|
||||
build_number desc
|
||||
LIMIT
|
||||
1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (WorkspaceBuild, error) {
|
||||
row := q.db.QueryRowContext(ctx, getLatestWorkspaceBuildByWorkspaceID, workspaceID)
|
||||
var i WorkspaceBuild
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.WorkspaceID,
|
||||
&i.TemplateVersionID,
|
||||
&i.BuildNumber,
|
||||
&i.Transition,
|
||||
&i.InitiatorID,
|
||||
&i.ProvisionerState,
|
||||
&i.JobID,
|
||||
&i.Deadline,
|
||||
&i.Reason,
|
||||
&i.DailyCost,
|
||||
&i.MaxDeadline,
|
||||
)
|
||||
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, wb.max_deadline
|
||||
FROM (
|
||||
SELECT
|
||||
workspace_id, MAX(build_number) as max_build_number
|
||||
FROM
|
||||
workspace_builds
|
||||
GROUP BY
|
||||
workspace_id
|
||||
) m
|
||||
JOIN
|
||||
workspace_builds wb
|
||||
ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetLatestWorkspaceBuilds(ctx context.Context) ([]WorkspaceBuild, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getLatestWorkspaceBuilds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []WorkspaceBuild
|
||||
for rows.Next() {
|
||||
var i WorkspaceBuild
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.WorkspaceID,
|
||||
&i.TemplateVersionID,
|
||||
&i.BuildNumber,
|
||||
&i.Transition,
|
||||
&i.InitiatorID,
|
||||
&i.ProvisionerState,
|
||||
&i.JobID,
|
||||
&i.Deadline,
|
||||
&i.Reason,
|
||||
&i.DailyCost,
|
||||
&i.MaxDeadline,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
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, wb.max_deadline
|
||||
FROM (
|
||||
SELECT
|
||||
workspace_id, MAX(build_number) as max_build_number
|
||||
FROM
|
||||
workspace_builds
|
||||
WHERE
|
||||
workspace_id = ANY($1 :: uuid [ ])
|
||||
GROUP BY
|
||||
workspace_id
|
||||
) m
|
||||
JOIN
|
||||
workspace_builds wb
|
||||
ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceBuild, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getLatestWorkspaceBuildsByWorkspaceIDs, pq.Array(ids))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []WorkspaceBuild
|
||||
for rows.Next() {
|
||||
var i WorkspaceBuild
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.WorkspaceID,
|
||||
&i.TemplateVersionID,
|
||||
&i.BuildNumber,
|
||||
&i.Transition,
|
||||
&i.InitiatorID,
|
||||
&i.ProvisionerState,
|
||||
&i.JobID,
|
||||
&i.Deadline,
|
||||
&i.Reason,
|
||||
&i.DailyCost,
|
||||
&i.MaxDeadline,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
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, max_deadline
|
||||
FROM
|
||||
workspace_builds
|
||||
WHERE
|
||||
id = $1
|
||||
LIMIT
|
||||
1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (WorkspaceBuild, error) {
|
||||
row := q.db.QueryRowContext(ctx, getWorkspaceBuildByID, id)
|
||||
var i WorkspaceBuild
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.WorkspaceID,
|
||||
&i.TemplateVersionID,
|
||||
&i.BuildNumber,
|
||||
&i.Transition,
|
||||
&i.InitiatorID,
|
||||
&i.ProvisionerState,
|
||||
&i.JobID,
|
||||
&i.Deadline,
|
||||
&i.Reason,
|
||||
&i.DailyCost,
|
||||
&i.MaxDeadline,
|
||||
)
|
||||
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, max_deadline
|
||||
FROM
|
||||
workspace_builds
|
||||
WHERE
|
||||
job_id = $1
|
||||
LIMIT
|
||||
1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UUID) (WorkspaceBuild, error) {
|
||||
row := q.db.QueryRowContext(ctx, getWorkspaceBuildByJobID, jobID)
|
||||
var i WorkspaceBuild
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.WorkspaceID,
|
||||
&i.TemplateVersionID,
|
||||
&i.BuildNumber,
|
||||
&i.Transition,
|
||||
&i.InitiatorID,
|
||||
&i.ProvisionerState,
|
||||
&i.JobID,
|
||||
&i.Deadline,
|
||||
&i.Reason,
|
||||
&i.DailyCost,
|
||||
&i.MaxDeadline,
|
||||
)
|
||||
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, max_deadline
|
||||
FROM
|
||||
workspace_builds
|
||||
WHERE
|
||||
workspace_id = $1
|
||||
AND build_number = $2
|
||||
`
|
||||
|
||||
type GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams struct {
|
||||
WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"`
|
||||
BuildNumber int32 `db:"build_number" json:"build_number"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx context.Context, arg GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams) (WorkspaceBuild, error) {
|
||||
row := q.db.QueryRowContext(ctx, getWorkspaceBuildByWorkspaceIDAndBuildNumber, arg.WorkspaceID, arg.BuildNumber)
|
||||
var i WorkspaceBuild
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.WorkspaceID,
|
||||
&i.TemplateVersionID,
|
||||
&i.BuildNumber,
|
||||
&i.Transition,
|
||||
&i.InitiatorID,
|
||||
&i.ProvisionerState,
|
||||
&i.JobID,
|
||||
&i.Deadline,
|
||||
&i.Reason,
|
||||
&i.DailyCost,
|
||||
&i.MaxDeadline,
|
||||
)
|
||||
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, max_deadline
|
||||
FROM
|
||||
workspace_builds
|
||||
WHERE
|
||||
workspace_builds.workspace_id = $1
|
||||
AND workspace_builds.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.
|
||||
WHEN $3 :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN (
|
||||
-- The pagination cursor is the last ID of the previous page.
|
||||
-- The query is ordered by the build_number field, so select all
|
||||
-- rows after the cursor.
|
||||
build_number > (
|
||||
SELECT
|
||||
build_number
|
||||
FROM
|
||||
workspace_builds
|
||||
WHERE
|
||||
id = $3
|
||||
)
|
||||
)
|
||||
ELSE true
|
||||
END
|
||||
ORDER BY
|
||||
build_number desc OFFSET $4
|
||||
LIMIT
|
||||
-- A null limit means "no limit", so 0 means return all
|
||||
NULLIF($5 :: int, 0)
|
||||
`
|
||||
|
||||
type GetWorkspaceBuildsByWorkspaceIDParams struct {
|
||||
WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"`
|
||||
Since time.Time `db:"since" json:"since"`
|
||||
AfterID uuid.UUID `db:"after_id" json:"after_id"`
|
||||
OffsetOpt int32 `db:"offset_opt" json:"offset_opt"`
|
||||
LimitOpt int32 `db:"limit_opt" json:"limit_opt"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg GetWorkspaceBuildsByWorkspaceIDParams) ([]WorkspaceBuild, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getWorkspaceBuildsByWorkspaceID,
|
||||
arg.WorkspaceID,
|
||||
arg.Since,
|
||||
arg.AfterID,
|
||||
arg.OffsetOpt,
|
||||
arg.LimitOpt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []WorkspaceBuild
|
||||
for rows.Next() {
|
||||
var i WorkspaceBuild
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.WorkspaceID,
|
||||
&i.TemplateVersionID,
|
||||
&i.BuildNumber,
|
||||
&i.Transition,
|
||||
&i.InitiatorID,
|
||||
&i.ProvisionerState,
|
||||
&i.JobID,
|
||||
&i.Deadline,
|
||||
&i.Reason,
|
||||
&i.DailyCost,
|
||||
&i.MaxDeadline,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
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, max_deadline FROM workspace_builds WHERE created_at > $1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceBuildsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceBuild, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getWorkspaceBuildsCreatedAfter, createdAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []WorkspaceBuild
|
||||
for rows.Next() {
|
||||
var i WorkspaceBuild
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.WorkspaceID,
|
||||
&i.TemplateVersionID,
|
||||
&i.BuildNumber,
|
||||
&i.Transition,
|
||||
&i.InitiatorID,
|
||||
&i.ProvisionerState,
|
||||
&i.JobID,
|
||||
&i.Deadline,
|
||||
&i.Reason,
|
||||
&i.DailyCost,
|
||||
&i.MaxDeadline,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const insertWorkspaceBuild = `-- name: InsertWorkspaceBuild :one
|
||||
INSERT INTO
|
||||
workspace_builds (
|
||||
|
|
|
@ -1,110 +1,3 @@
|
|||
-- name: GetWorkspaceBuildByID :one
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
workspace_builds
|
||||
WHERE
|
||||
id = $1
|
||||
LIMIT
|
||||
1;
|
||||
|
||||
-- name: GetWorkspaceBuildByJobID :one
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
workspace_builds
|
||||
WHERE
|
||||
job_id = $1
|
||||
LIMIT
|
||||
1;
|
||||
|
||||
-- name: GetWorkspaceBuildsCreatedAfter :many
|
||||
SELECT * FROM workspace_builds WHERE created_at > $1;
|
||||
|
||||
-- name: GetWorkspaceBuildByWorkspaceIDAndBuildNumber :one
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
workspace_builds
|
||||
WHERE
|
||||
workspace_id = $1
|
||||
AND build_number = $2;
|
||||
|
||||
-- name: GetWorkspaceBuildsByWorkspaceID :many
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
workspace_builds
|
||||
WHERE
|
||||
workspace_builds.workspace_id = $1
|
||||
AND workspace_builds.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.
|
||||
WHEN @after_id :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN (
|
||||
-- The pagination cursor is the last ID of the previous page.
|
||||
-- The query is ordered by the build_number field, so select all
|
||||
-- rows after the cursor.
|
||||
build_number > (
|
||||
SELECT
|
||||
build_number
|
||||
FROM
|
||||
workspace_builds
|
||||
WHERE
|
||||
id = @after_id
|
||||
)
|
||||
)
|
||||
ELSE true
|
||||
END
|
||||
ORDER BY
|
||||
build_number desc OFFSET @offset_opt
|
||||
LIMIT
|
||||
-- A null limit means "no limit", so 0 means return all
|
||||
NULLIF(@limit_opt :: int, 0);
|
||||
|
||||
-- name: GetLatestWorkspaceBuildByWorkspaceID :one
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
workspace_builds
|
||||
WHERE
|
||||
workspace_id = $1
|
||||
ORDER BY
|
||||
build_number desc
|
||||
LIMIT
|
||||
1;
|
||||
|
||||
-- name: GetLatestWorkspaceBuildsByWorkspaceIDs :many
|
||||
SELECT wb.*
|
||||
FROM (
|
||||
SELECT
|
||||
workspace_id, MAX(build_number) as max_build_number
|
||||
FROM
|
||||
workspace_builds
|
||||
WHERE
|
||||
workspace_id = ANY(@ids :: uuid [ ])
|
||||
GROUP BY
|
||||
workspace_id
|
||||
) m
|
||||
JOIN
|
||||
workspace_builds wb
|
||||
ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number;
|
||||
|
||||
-- name: GetLatestWorkspaceBuilds :many
|
||||
SELECT wb.*
|
||||
FROM (
|
||||
SELECT
|
||||
workspace_id, MAX(build_number) as max_build_number
|
||||
FROM
|
||||
workspace_builds
|
||||
GROUP BY
|
||||
workspace_id
|
||||
) m
|
||||
JOIN
|
||||
workspace_builds wb
|
||||
ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number;
|
||||
|
||||
-- name: InsertWorkspaceBuild :one
|
||||
INSERT INTO
|
||||
workspace_builds (
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
# Editor/IDE config
|
||||
|
||||
To edit template files, it is best to configure your IDE to work with go template files. VSCode gives better highlighting support, as the Goland highlighting tends to recognize the sql as invalid and shows many sql errors in the template file.
|
||||
|
||||
## VSCode
|
||||
|
||||
Required extension (Default Golang Extension): https://marketplace.visualstudio.com/items?itemName=golang.Go
|
||||
|
||||
The default extension [supports syntax highlighting](https://github.com/golang/vscode-go/wiki/features#go-template-syntax-highlighting), but requires a configuration change. You must add this section to your golang extension settings:
|
||||
|
||||
```json
|
||||
"gopls": {
|
||||
"ui.semanticTokens": true
|
||||
},
|
||||
```
|
||||
|
||||
The VSCode extension does not support both go template and postgres highlighting. I suggest you use Postgres highlighting, as it is much easier to work with. You can switch between the two with:
|
||||
|
||||
1. `ctl + shift + p`
|
||||
1. "Change language Mode"
|
||||
1. "Postgres" or "Go Template File"
|
||||
|
||||
- Feel free to create a permanent file association with `*.gosql` files.
|
||||
|
||||
## Goland
|
||||
|
||||
Goland supports [template highlighting](https://www.jetbrains.com/help/go/integration-with-go-templates.html) out of the box. To associate sql files, add a new file type in **Editor** settings. Select "Go template files". Add a new filename of `*.gosql` and select "postgres" as the "Template Data Language".
|
||||
|
||||
![Goland language configuration](./imgs/goland-gosql.png)
|
||||
|
||||
It also helps to support the sqlc type variables. You can do this by adding ["User Parameters"](https://www.jetbrains.com/help/datagrip/settings-tools-database-user-parameters.html) in database queries.
|
||||
|
||||
![Goland language configuration](./imgs/goland-user-params.png)
|
||||
|
||||
You can also add `dump.sql` as a DDL data source for proper table column recognition.
|
|
@ -0,0 +1,95 @@
|
|||
package sqlxqueries
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jmoiron/sqlx/reflectx"
|
||||
"github.com/lib/pq"
|
||||
|
||||
"github.com/coder/coder/coderd/util/slice"
|
||||
)
|
||||
|
||||
var nameRegex = regexp.MustCompile(`@([a-zA-Z0-9_]+)`)
|
||||
|
||||
// dbmapper grabs struct 'db' tags.
|
||||
var dbmapper = reflectx.NewMapper("db")
|
||||
|
||||
// bindNamed is an implementation that improves on the SQLx implementation. This
|
||||
// adjusts the query to use "$#" syntax for arguments instead of "@argument". The
|
||||
// returned args are the values of the struct fields that match the names in the
|
||||
// correct order and indexing.
|
||||
//
|
||||
// 1. SQLx does not reuse arguments, so "@arg, @arg" will result in two arguments
|
||||
// "$1, $2" instead of "$1, $1".
|
||||
// 2. SQLx does not handle uuid arrays.
|
||||
// 3. SQLx only supports ":name" style arguments and breaks "::" type casting.
|
||||
func bindNamed(query string, arg interface{}) (newQuery string, args []interface{}, err error) {
|
||||
// We do not need to implement a sql parser to extract and replace the variable names.
|
||||
// All names follow a simple regex.
|
||||
names := nameRegex.FindAllString(query, -1)
|
||||
// Get all unique names
|
||||
names = slice.Unique(names)
|
||||
|
||||
// Replace all names with the correct index
|
||||
for i, name := range names {
|
||||
rpl := fmt.Sprintf("$%d", i+1)
|
||||
if strings.Contains(query, rpl) {
|
||||
return "", nil,
|
||||
xerrors.Errorf("query contains both named params %q, and unnamed %q: choose one", name, rpl)
|
||||
}
|
||||
query = strings.ReplaceAll(query, name, rpl)
|
||||
// Remove the "@" prefix to match to the "db" struct tag.
|
||||
names[i] = strings.TrimPrefix(name, "@")
|
||||
}
|
||||
|
||||
arglist := make([]interface{}, 0, len(names))
|
||||
|
||||
// This comes straight from SQLx's implementation to get the values
|
||||
// of the struct fields.
|
||||
var v reflect.Value
|
||||
for v = reflect.ValueOf(arg); v.Kind() == reflect.Ptr; {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
// If there is only 1 argument, and the argument is not a struct, then
|
||||
// the only argument is the value passed in. This is a nice shortcut
|
||||
// for simple queries with 1 param like "id".
|
||||
if v.Type().Kind() != reflect.Struct && len(names) == 1 {
|
||||
arglist = append(arglist, pqValue(v))
|
||||
return query, arglist, nil
|
||||
}
|
||||
|
||||
err = dbmapper.TraversalsByNameFunc(v.Type(), names, func(i int, t []int) error {
|
||||
if len(t) == 0 {
|
||||
return xerrors.Errorf("could not find name %s in %#v", names[i], arg)
|
||||
}
|
||||
|
||||
val := reflectx.FieldByIndexesReadOnly(v, t)
|
||||
arglist = append(arglist, pqValue(val))
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return query, arglist, nil
|
||||
}
|
||||
|
||||
func pqValue(val reflect.Value) interface{} {
|
||||
valI := val.Interface()
|
||||
// Handle some custom types to make arguments easier to use.
|
||||
switch valI.(type) {
|
||||
// Feel free to add more types here as needed.
|
||||
case []uuid.UUID:
|
||||
return pq.Array(valI)
|
||||
default:
|
||||
return valI
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
|
@ -0,0 +1,72 @@
|
|||
package sqlxqueries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// constructQuery will return a SQL query by the given template name.
|
||||
// It will also return the arguments in order for the query based on the input
|
||||
// argument.
|
||||
func constructQuery(queryName string, argument any) (string, []any, error) {
|
||||
// No argument was given, use an empty struct.
|
||||
if argument == nil {
|
||||
argument = struct{}{}
|
||||
}
|
||||
|
||||
query, err := query(queryName, argument)
|
||||
if err != nil {
|
||||
return "", nil, xerrors.Errorf("get query: %w", err)
|
||||
}
|
||||
|
||||
query, args, err := bindNamed(query, argument)
|
||||
if err != nil {
|
||||
return "", nil, xerrors.Errorf("bind named: %w", err)
|
||||
}
|
||||
return query, args, nil
|
||||
}
|
||||
|
||||
// SelectContext runs the named query on the given database.
|
||||
// If the query returns no rows, an empty slice is returned.
|
||||
func SelectContext(ctx context.Context, q sqlx.QueryerContext, queryName string, argument any, res any) error {
|
||||
if q == nil {
|
||||
return xerrors.New("queryer is nil")
|
||||
}
|
||||
|
||||
query, args, err := constructQuery(queryName, argument)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get query: %w", err)
|
||||
}
|
||||
|
||||
err = sqlx.SelectContext(ctx, q, res, query, args...)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("%s: %w", queryName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetContext runs the named query on the given database.
|
||||
// If the query returns no rows, sql.ErrNoRows is returned.
|
||||
func GetContext(ctx context.Context, q sqlx.QueryerContext, queryName string, argument interface{}, res any) error {
|
||||
if q == nil {
|
||||
return xerrors.New("queryer is nil")
|
||||
}
|
||||
|
||||
query, args, err := constructQuery(queryName, argument)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get query: %w", err)
|
||||
}
|
||||
|
||||
// GetContext maps the results of the query to the items slice by struct
|
||||
// db tags.
|
||||
err = sqlx.GetContext(ctx, q, res, query, args...)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("%s: %w", queryName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package sqlxqueries
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"sync"
|
||||
"text/template"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
//go:embed *.gosql
|
||||
var sqlxQueries embed.FS
|
||||
|
||||
var (
|
||||
// Only parse the queries once.
|
||||
once sync.Once
|
||||
cached *template.Template
|
||||
//nolint:errname
|
||||
cachedError error
|
||||
)
|
||||
|
||||
// LoadQueries parses the embedded queries and returns the template.
|
||||
// Results are cached.
|
||||
func LoadQueries() (*template.Template, error) {
|
||||
once.Do(func() {
|
||||
tpls, err := template.New("").
|
||||
Funcs(template.FuncMap{
|
||||
"int32": func(i int) int32 { return int32(i) },
|
||||
}).ParseFS(sqlxQueries, "*.gosql")
|
||||
if err != nil {
|
||||
cachedError = xerrors.Errorf("developer error parse sqlx queries: %w", err)
|
||||
return
|
||||
}
|
||||
cached = tpls
|
||||
})
|
||||
|
||||
return cached, cachedError
|
||||
}
|
||||
|
||||
// query executes the named template with the given data and returns the result.
|
||||
// The returned query string is SQL.
|
||||
func query(name string, data interface{}) (string, error) {
|
||||
tpls, err := LoadQueries()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
err = tpls.ExecuteTemplate(&out, name, data)
|
||||
if err != nil {
|
||||
return "", xerrors.Errorf("execute template %s: %w", name, err)
|
||||
}
|
||||
return out.String(), nil
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package sqlxqueries_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/coderd/database/sqlxqueries"
|
||||
)
|
||||
|
||||
func Test_loadQueries(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := sqlxqueries.LoadQueries()
|
||||
require.NoError(t, err)
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
{{ define "workspace_builds_rbac" }}
|
||||
(
|
||||
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
|
||||
)
|
||||
{{ end }};
|
||||
|
||||
|
||||
{{ define "GetWorkspaceBuild" }}
|
||||
-- name: GetWorkspaceBuild :one
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
{{ template "workspace_builds_rbac" }} workspace_builds
|
||||
WHERE
|
||||
CASE
|
||||
WHEN @build_id :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN
|
||||
id = @build_id
|
||||
ELSE true
|
||||
END
|
||||
AND CASE
|
||||
WHEN @job_id :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN
|
||||
job_id = @job_id
|
||||
ELSE true
|
||||
END
|
||||
AND CASE
|
||||
WHEN @job_id :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN
|
||||
job_id = @job_id
|
||||
ELSE true
|
||||
END
|
||||
AND CASE
|
||||
WHEN @created_after :: timestamp with time zone != '0001-01-01 00:00:00Z' THEN
|
||||
created_at > @created_after
|
||||
ELSE true
|
||||
END
|
||||
AND CASE
|
||||
WHEN @workspace_id :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN
|
||||
workspace_id = @workspace_id
|
||||
ELSE true
|
||||
END
|
||||
AND CASE
|
||||
WHEN @build_number :: integer != 0 THEN
|
||||
build_number = @build_number
|
||||
ELSE true
|
||||
END
|
||||
{{ if .Latest }}
|
||||
ORDER BY
|
||||
build_number desc
|
||||
{{ end }}
|
||||
{{ if gt .LimitOpt 0 }} LIMIT @limit_opt {{ end }}
|
||||
;
|
||||
{{ end }}
|
||||
|
||||
|
||||
{{ define "GetWorkspaceBuildsByWorkspaceID" }}
|
||||
-- name: GetWorkspaceBuildsByWorkspaceID :many
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
{{ template "workspace_builds_rbac" }} workspace_builds
|
||||
WHERE
|
||||
workspace_builds.workspace_id = @workspace_id
|
||||
AND workspace_builds.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.
|
||||
WHEN @after_id :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN (
|
||||
-- The pagination cursor is the last ID of the previous page.
|
||||
-- The query is ordered by the build_number field, so select all
|
||||
-- rows after the cursor.
|
||||
build_number > (
|
||||
SELECT
|
||||
build_number
|
||||
FROM
|
||||
workspace_builds
|
||||
WHERE
|
||||
id = @after_id
|
||||
)
|
||||
)
|
||||
ELSE true
|
||||
END
|
||||
ORDER BY
|
||||
build_number desc OFFSET @offset_opt
|
||||
LIMIT
|
||||
-- A null limit means "no limit", so 0 means return all
|
||||
NULLIF(@limit_opt :: int, 0);
|
||||
{{ end }}
|
||||
|
||||
{{ define "GetLatestWorkspaceBuildsByWorkspaceIDs" }}
|
||||
-- name: GetLatestWorkspaceBuildsByWorkspaceIDs :many
|
||||
SELECT wb.*
|
||||
FROM (
|
||||
SELECT
|
||||
workspace_id, MAX(build_number) as max_build_number
|
||||
FROM
|
||||
workspace_builds
|
||||
WHERE
|
||||
workspace_id = ANY(@ids :: uuid [ ])
|
||||
GROUP BY
|
||||
workspace_id
|
||||
) m
|
||||
JOIN
|
||||
{{ template "workspace_builds_rbac" }} wb
|
||||
ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number;
|
||||
{{ end }}
|
||||
|
||||
{{ define "GetLatestWorkspaceBuilds" }}
|
||||
-- name: GetLatestWorkspaceBuilds :many
|
||||
SELECT wb.*
|
||||
FROM (
|
||||
SELECT
|
||||
workspace_id, MAX(build_number) as max_build_number
|
||||
FROM
|
||||
workspace_builds
|
||||
GROUP BY
|
||||
workspace_id
|
||||
) m
|
||||
JOIN
|
||||
{{ template "workspace_builds_rbac" }} wb
|
||||
ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number;
|
||||
{{ end }}
|
|
@ -16,8 +16,8 @@ import (
|
|||
type workspaceBuildParamContextKey struct{}
|
||||
|
||||
// WorkspaceBuildParam returns the workspace build from the ExtractWorkspaceBuildParam handler.
|
||||
func WorkspaceBuildParam(r *http.Request) database.WorkspaceBuild {
|
||||
workspaceBuild, ok := r.Context().Value(workspaceBuildParamContextKey{}).(database.WorkspaceBuild)
|
||||
func WorkspaceBuildParam(r *http.Request) database.WorkspaceBuildRBAC {
|
||||
workspaceBuild, ok := r.Context().Value(workspaceBuildParamContextKey{}).(database.WorkspaceBuildRBAC)
|
||||
if !ok {
|
||||
panic("developer error: workspace build param middleware not provided")
|
||||
}
|
||||
|
|
|
@ -670,14 +670,14 @@ func (server *Server) FailJob(ctx context.Context, failJob *proto.FailedJob) (*p
|
|||
return nil, xerrors.Errorf("unmarshal workspace provision input: %w", err)
|
||||
}
|
||||
|
||||
var build database.WorkspaceBuild
|
||||
var build database.WorkspaceBuildRBAC
|
||||
err := server.Database.InTx(func(db database.Store) error {
|
||||
workspaceBuild, err := db.GetWorkspaceBuildByID(ctx, input.WorkspaceBuildID)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get workspace build: %w", err)
|
||||
}
|
||||
|
||||
build, err = db.UpdateWorkspaceBuildByID(ctx, database.UpdateWorkspaceBuildByIDParams{
|
||||
thinBuild, err := db.UpdateWorkspaceBuildByID(ctx, database.UpdateWorkspaceBuildByIDParams{
|
||||
ID: input.WorkspaceBuildID,
|
||||
UpdatedAt: database.Now(),
|
||||
ProvisionerState: jobType.WorkspaceBuild.State,
|
||||
|
@ -687,6 +687,8 @@ func (server *Server) FailJob(ctx context.Context, failJob *proto.FailedJob) (*p
|
|||
if err != nil {
|
||||
return xerrors.Errorf("update workspace build state: %w", err)
|
||||
}
|
||||
// Keep the same owner args as the original build.
|
||||
build = thinBuild.Expand(workspaceBuild.OrganizationID, workspaceBuild.WorkspaceOwnerID)
|
||||
|
||||
return nil
|
||||
}, nil)
|
||||
|
@ -719,7 +721,7 @@ func (server *Server) FailJob(ctx context.Context, failJob *proto.FailedJob) (*p
|
|||
BuildNumber: previousBuildNumber,
|
||||
})
|
||||
if prevBuildErr != nil {
|
||||
previousBuild = database.WorkspaceBuild{}
|
||||
previousBuild = database.WorkspaceBuildRBAC{}
|
||||
}
|
||||
|
||||
// We pass the below information to the Auditor so that it
|
||||
|
@ -735,7 +737,7 @@ func (server *Server) FailJob(ctx context.Context, failJob *proto.FailedJob) (*p
|
|||
server.Logger.Error(ctx, "marshal workspace resource info for failed job", slog.Error(err))
|
||||
}
|
||||
|
||||
audit.BuildAudit(ctx, &audit.BuildAuditParams[database.WorkspaceBuild]{
|
||||
audit.BuildAudit(ctx, &audit.BuildAuditParams[database.WorkspaceBuildRBAC]{
|
||||
Audit: *auditor,
|
||||
Log: server.Logger,
|
||||
UserID: job.InitiatorID,
|
||||
|
@ -1039,7 +1041,7 @@ func (server *Server) CompleteJob(ctx context.Context, completed *proto.Complete
|
|||
BuildNumber: previousBuildNumber,
|
||||
})
|
||||
if prevBuildErr != nil {
|
||||
previousBuild = database.WorkspaceBuild{}
|
||||
previousBuild = database.WorkspaceBuildRBAC{}
|
||||
}
|
||||
|
||||
// We pass the below information to the Auditor so that it
|
||||
|
@ -1055,7 +1057,7 @@ func (server *Server) CompleteJob(ctx context.Context, completed *proto.Complete
|
|||
server.Logger.Error(ctx, "marshal resource info for successful job", slog.Error(err))
|
||||
}
|
||||
|
||||
audit.BuildAudit(ctx, &audit.BuildAuditParams[database.WorkspaceBuild]{
|
||||
audit.BuildAudit(ctx, &audit.BuildAuditParams[database.WorkspaceBuildRBAC]{
|
||||
Audit: *auditor,
|
||||
Log: server.Logger,
|
||||
UserID: job.InitiatorID,
|
||||
|
|
|
@ -644,13 +644,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,
|
||||
})
|
||||
|
|
|
@ -503,7 +503,7 @@ func ConvertWorkspace(workspace database.Workspace) Workspace {
|
|||
}
|
||||
|
||||
// ConvertWorkspaceBuild anonymizes a workspace build.
|
||||
func ConvertWorkspaceBuild(build database.WorkspaceBuild) WorkspaceBuild {
|
||||
func ConvertWorkspaceBuild(build database.WorkspaceBuildRBAC) WorkspaceBuild {
|
||||
return WorkspaceBuild{
|
||||
ID: build.ID,
|
||||
CreatedAt: build.CreatedAt,
|
||||
|
|
|
@ -41,7 +41,7 @@ func (api *API) workspaceBuild(rw http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
data, err := api.workspaceBuildsData(ctx, []database.Workspace{workspace}, []database.WorkspaceBuild{workspaceBuild})
|
||||
data, err := api.workspaceBuildsData(ctx, []database.Workspace{workspace}, []database.WorkspaceBuildRBAC{workspaceBuild})
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error getting workspace build data.",
|
||||
|
@ -113,7 +113,7 @@ func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
var workspaceBuilds []database.WorkspaceBuild
|
||||
var workspaceBuilds []database.WorkspaceBuildRBAC
|
||||
// Ensure all db calls happen in the same tx
|
||||
err := api.Database.InTx(func(store database.Store) error {
|
||||
var err error
|
||||
|
@ -253,7 +253,7 @@ func (api *API) workspaceBuildByBuildNumber(rw http.ResponseWriter, r *http.Requ
|
|||
return
|
||||
}
|
||||
|
||||
data, err := api.workspaceBuildsData(ctx, []database.Workspace{workspace}, []database.WorkspaceBuild{workspaceBuild})
|
||||
data, err := api.workspaceBuildsData(ctx, []database.Workspace{workspace}, []database.WorkspaceBuildRBAC{workspaceBuild})
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error getting workspace build data.",
|
||||
|
@ -526,7 +526,7 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
var workspaceBuild database.WorkspaceBuild
|
||||
var workspaceBuild database.WorkspaceBuildRBAC
|
||||
var provisionerJob database.ProvisionerJob
|
||||
// This must happen in a transaction to ensure history can be inserted, and
|
||||
// the prior history can update it's "after" column to point at the new.
|
||||
|
@ -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 {
|
||||
|
@ -923,7 +926,7 @@ type workspaceBuildsData struct {
|
|||
apps []database.WorkspaceApp
|
||||
}
|
||||
|
||||
func (api *API) workspaceBuildsData(ctx context.Context, workspaces []database.Workspace, workspaceBuilds []database.WorkspaceBuild) (workspaceBuildsData, error) {
|
||||
func (api *API) workspaceBuildsData(ctx context.Context, workspaces []database.Workspace, workspaceBuilds []database.WorkspaceBuildRBAC) (workspaceBuildsData, error) {
|
||||
userIDs := make([]uuid.UUID, 0, len(workspaceBuilds))
|
||||
for _, build := range workspaceBuilds {
|
||||
userIDs = append(userIDs, build.InitiatorID)
|
||||
|
@ -1014,7 +1017,7 @@ func (api *API) workspaceBuildsData(ctx context.Context, workspaces []database.W
|
|||
}
|
||||
|
||||
func (api *API) convertWorkspaceBuilds(
|
||||
workspaceBuilds []database.WorkspaceBuild,
|
||||
workspaceBuilds []database.WorkspaceBuildRBAC,
|
||||
workspaces []database.Workspace,
|
||||
jobs []database.ProvisionerJob,
|
||||
users []database.User,
|
||||
|
@ -1075,7 +1078,7 @@ func (api *API) convertWorkspaceBuilds(
|
|||
}
|
||||
|
||||
func (api *API) convertWorkspaceBuild(
|
||||
build database.WorkspaceBuild,
|
||||
build database.WorkspaceBuildRBAC,
|
||||
workspace database.Workspace,
|
||||
job database.ProvisionerJob,
|
||||
users []database.User,
|
||||
|
|
|
@ -475,7 +475,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
|
|||
tags := provisionerdserver.MutateTags(user.ID, templateVersionJob.Tags)
|
||||
var (
|
||||
provisionerJob database.ProvisionerJob
|
||||
workspaceBuild database.WorkspaceBuild
|
||||
workspaceBuild database.WorkspaceBuildRBAC
|
||||
)
|
||||
err = api.Database.InTx(func(db database.Store) error {
|
||||
now := database.Now()
|
||||
|
@ -540,7 +540,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,
|
||||
|
@ -556,6 +556,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))
|
||||
|
|
|
@ -19,7 +19,7 @@ We track the following resources:
|
|||
| TemplateVersion<br><i>create, write</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>false</td></tr><tr><td>created_by</td><td>true</td></tr><tr><td>git_auth_providers</td><td>false</td></tr><tr><td>id</td><td>true</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>readme</td><td>true</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
|
||||
| User<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>avatar_url</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>true</td></tr><tr><td>email</td><td>true</td></tr><tr><td>hashed_password</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_seen_at</td><td>false</td></tr><tr><td>login_type</td><td>false</td></tr><tr><td>rbac_roles</td><td>true</td></tr><tr><td>status</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>username</td><td>true</td></tr></tbody></table> |
|
||||
| Workspace<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>autostart_schedule</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_used_at</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>owner_id</td><td>true</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>ttl</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
|
||||
| WorkspaceBuild<br><i>start, stop</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>build_number</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>daily_cost</td><td>false</td></tr><tr><td>deadline</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>initiator_id</td><td>false</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>max_deadline</td><td>false</td></tr><tr><td>provisioner_state</td><td>false</td></tr><tr><td>reason</td><td>false</td></tr><tr><td>template_version_id</td><td>true</td></tr><tr><td>transition</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>workspace_id</td><td>false</td></tr></tbody></table> |
|
||||
| WorkspaceBuild<br><i>start, stop</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>build_number</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>daily_cost</td><td>false</td></tr><tr><td>deadline</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>initiator_id</td><td>false</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>max_deadline</td><td>false</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>provisioner_state</td><td>false</td></tr><tr><td>reason</td><td>false</td></tr><tr><td>template_version_id</td><td>true</td></tr><tr><td>transition</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>workspace_id</td><td>false</td></tr><tr><td>workspace_owner_id</td><td>false</td></tr></tbody></table> |
|
||||
|
||||
<!-- End generated by 'make docs/admin/audit-logs.md'. -->
|
||||
|
||||
|
|
|
@ -126,6 +126,8 @@ var AuditableResources = auditMap(map[any]map[string]Action{
|
|||
"reason": ActionIgnore,
|
||||
"daily_cost": ActionIgnore,
|
||||
"max_deadline": ActionIgnore,
|
||||
"organization_id": ActionIgnore,
|
||||
"workspace_owner_id": ActionIgnore,
|
||||
},
|
||||
&database.AuditableGroup{}: {
|
||||
"id": ActionTrack,
|
||||
|
|
Loading…
Reference in New Issue