diff --git a/cli/agent_test.go b/cli/agent_test.go index 5d37f2c54c9d4..0a948c0c84e9a 100644 --- a/cli/agent_test.go +++ b/cli/agent_test.go @@ -35,7 +35,7 @@ func TestWorkspaceAgent(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }). @@ -71,7 +71,7 @@ func TestWorkspaceAgent(t *testing.T) { AzureCertificates: certificates, }) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -110,7 +110,7 @@ func TestWorkspaceAgent(t *testing.T) { AWSCertificates: certificates, }) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -151,7 +151,7 @@ func TestWorkspaceAgent(t *testing.T) { }) owner := coderdtest.CreateFirstUser(t, client) member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: owner.OrganizationID, OwnerID: memberUser.ID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -205,7 +205,7 @@ func TestWorkspaceAgent(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -279,7 +279,7 @@ func TestWorkspaceAgent(t *testing.T) { } coderAPI.RootHandler.ServeHTTP(w, r) })) - r := dbfake.WorkspaceBuild(t, coderAPI.Database, database.Workspace{ + r := dbfake.WorkspaceBuild(t, coderAPI.Database, database.WorkspaceTable{ OrganizationID: memberUser.OrganizationIDs[0], OwnerID: memberUser.ID, }).WithAgent().Do() diff --git a/cli/configssh_test.go b/cli/configssh_test.go index feead1e279e96..5bedd18cb27dc 100644 --- a/cli/configssh_test.go +++ b/cli/configssh_test.go @@ -83,7 +83,7 @@ func TestConfigSSH(t *testing.T) { }) owner := coderdtest.CreateFirstUser(t, client) member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: owner.OrganizationID, OwnerID: memberUser.ID, }).WithAgent().Do() @@ -647,7 +647,7 @@ func TestConfigSSH_FileWriteAndOptionsFlow(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) if tt.hasAgent { - _ = dbfake.WorkspaceBuild(t, db, database.Workspace{ + _ = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -767,7 +767,7 @@ func TestConfigSSH_Hostnames(t *testing.T) { owner := coderdtest.CreateFirstUser(t, client) member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: owner.OrganizationID, OwnerID: memberUser.ID, }).Resource(resources...).Do() diff --git a/cli/favorite_test.go b/cli/favorite_test.go index 5cdf5e765c6cf..0668f03361e2d 100644 --- a/cli/favorite_test.go +++ b/cli/favorite_test.go @@ -19,7 +19,7 @@ func TestFavoriteUnfavorite(t *testing.T) { client, db = coderdtest.NewWithDatabase(t, nil) owner = coderdtest.CreateFirstUser(t, client) memberClient, member = coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) - ws = dbfake.WorkspaceBuild(t, db, database.Workspace{OwnerID: member.ID, OrganizationID: owner.OrganizationID}).Do() + ws = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{OwnerID: member.ID, OrganizationID: owner.OrganizationID}).Do() ) inv, root := clitest.New(t, "favorite", ws.Workspace.Name) diff --git a/cli/gitssh_test.go b/cli/gitssh_test.go index 83b873dec914e..6d574ae651aec 100644 --- a/cli/gitssh_test.go +++ b/cli/gitssh_test.go @@ -48,7 +48,7 @@ func prepareTestGitSSH(ctx context.Context, t *testing.T) (*agentsdk.Client, str require.NoError(t, err) // setup template - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() diff --git a/cli/list_test.go b/cli/list_test.go index 82d372bd350aa..37f2f36f79278 100644 --- a/cli/list_test.go +++ b/cli/list_test.go @@ -26,7 +26,7 @@ func TestList(t *testing.T) { owner := coderdtest.CreateFirstUser(t, client) member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) // setup template - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: owner.OrganizationID, OwnerID: memberUser.ID, }).WithAgent().Do() @@ -54,7 +54,7 @@ func TestList(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) owner := coderdtest.CreateFirstUser(t, client) member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) - _ = dbfake.WorkspaceBuild(t, db, database.Workspace{ + _ = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: owner.OrganizationID, OwnerID: memberUser.ID, }).WithAgent().Do() diff --git a/cli/portforward_test.go b/cli/portforward_test.go index edef520c23dc6..29fccafb20ac1 100644 --- a/cli/portforward_test.go +++ b/cli/portforward_test.go @@ -290,12 +290,12 @@ func TestPortForward(t *testing.T) { // runAgent creates a fake workspace and starts an agent locally for that // workspace. The agent will be cleaned up on test completion. // nolint:unused -func runAgent(t *testing.T, client *codersdk.Client, owner uuid.UUID, db database.Store) database.Workspace { +func runAgent(t *testing.T, client *codersdk.Client, owner uuid.UUID, db database.Store) database.WorkspaceTable { user, err := client.User(context.Background(), codersdk.Me) require.NoError(t, err, "specified user does not exist") require.Greater(t, len(user.OrganizationIDs), 0, "user has no organizations") orgID := user.OrganizationIDs[0] - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: orgID, OwnerID: owner, }).WithAgent().Do() diff --git a/cli/schedule_test.go b/cli/schedule_test.go index 11e0171417c04..bf18155be293a 100644 --- a/cli/schedule_test.go +++ b/cli/schedule_test.go @@ -38,7 +38,7 @@ func setupTestSchedule(t *testing.T, sched *cron.Schedule) (ownerClient, memberC memberClient, memberUser := coderdtest.CreateAnotherUserMutators(t, ownerClient, owner.OrganizationID, nil, func(r *codersdk.CreateUserRequestWithOrgs) { r.Username = "testuser2" // ensure deterministic ordering }) - _ = dbfake.WorkspaceBuild(t, db, database.Workspace{ + _ = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ Name: "a-owner", OwnerID: owner.UserID, OrganizationID: owner.OrganizationID, @@ -46,19 +46,19 @@ func setupTestSchedule(t *testing.T, sched *cron.Schedule) (ownerClient, memberC Ttl: sql.NullInt64{Int64: 8 * time.Hour.Nanoseconds(), Valid: true}, }).WithAgent().Do() - _ = dbfake.WorkspaceBuild(t, db, database.Workspace{ + _ = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ Name: "b-owner", OwnerID: owner.UserID, OrganizationID: owner.OrganizationID, AutostartSchedule: sql.NullString{String: sched.String(), Valid: true}, }).WithAgent().Do() - _ = dbfake.WorkspaceBuild(t, db, database.Workspace{ + _ = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ Name: "c-member", OwnerID: memberUser.ID, OrganizationID: owner.OrganizationID, Ttl: sql.NullInt64{Int64: 8 * time.Hour.Nanoseconds(), Valid: true}, }).WithAgent().Do() - _ = dbfake.WorkspaceBuild(t, db, database.Workspace{ + _ = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ Name: "d-member", OwnerID: memberUser.ID, OrganizationID: owner.OrganizationID, diff --git a/cli/ssh_test.go b/cli/ssh_test.go index d000e090a44e4..c2a14c90e39e6 100644 --- a/cli/ssh_test.go +++ b/cli/ssh_test.go @@ -53,14 +53,14 @@ import ( "github.com/coder/coder/v2/testutil" ) -func setupWorkspaceForAgent(t *testing.T, mutations ...func([]*proto.Agent) []*proto.Agent) (*codersdk.Client, database.Workspace, string) { +func setupWorkspaceForAgent(t *testing.T, mutations ...func([]*proto.Agent) []*proto.Agent) (*codersdk.Client, database.WorkspaceTable, string) { t.Helper() client, store := coderdtest.NewWithDatabase(t, nil) client.SetLogger(slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug)) first := coderdtest.CreateFirstUser(t, client) userClient, user := coderdtest.CreateAnotherUser(t, client, first.OrganizationID) - r := dbfake.WorkspaceBuild(t, store, database.Workspace{ + r := dbfake.WorkspaceBuild(t, store, database.WorkspaceTable{ OrganizationID: first.OrganizationID, OwnerID: user.ID, }).WithAgent(mutations...).Do() @@ -260,7 +260,7 @@ func TestSSH(t *testing.T) { client.SetLogger(slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug)) first := coderdtest.CreateFirstUser(t, client) userClient, user := coderdtest.CreateAnotherUser(t, client, first.OrganizationID) - r := dbfake.WorkspaceBuild(t, store, database.Workspace{ + r := dbfake.WorkspaceBuild(t, store, database.WorkspaceTable{ OrganizationID: first.OrganizationID, OwnerID: user.ID, }).WithAgent().Do() @@ -763,7 +763,7 @@ func TestSSH(t *testing.T) { client.SetLogger(slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug)) first := coderdtest.CreateFirstUser(t, client) userClient, user := coderdtest.CreateAnotherUser(t, client, first.OrganizationID) - r := dbfake.WorkspaceBuild(t, store, database.Workspace{ + r := dbfake.WorkspaceBuild(t, store, database.WorkspaceTable{ OrganizationID: first.OrganizationID, OwnerID: user.ID, }).WithAgent().Do() @@ -1370,7 +1370,7 @@ func TestSSH(t *testing.T) { admin.SetLogger(slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug)) first := coderdtest.CreateFirstUser(t, admin) client, user := coderdtest.CreateAnotherUser(t, admin, first.OrganizationID) - r := dbfake.WorkspaceBuild(t, store, database.Workspace{ + r := dbfake.WorkspaceBuild(t, store, database.WorkspaceTable{ OrganizationID: first.OrganizationID, OwnerID: user.ID, }).WithAgent().Do() diff --git a/cli/start_test.go b/cli/start_test.go index e9809ff4bc4ff..da5fb74cacf72 100644 --- a/cli/start_test.go +++ b/cli/start_test.go @@ -390,7 +390,7 @@ func TestStart_AlreadyRunning(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) owner := coderdtest.CreateFirstUser(t, client) memberClient, member := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OwnerID: member.ID, OrganizationID: owner.OrganizationID, }).Do() @@ -417,7 +417,7 @@ func TestStart_Starting(t *testing.T) { client := coderdtest.New(t, &coderdtest.Options{Pubsub: ps, Database: store}) owner := coderdtest.CreateFirstUser(t, client) memberClient, member := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) - r := dbfake.WorkspaceBuild(t, store, database.Workspace{ + r := dbfake.WorkspaceBuild(t, store, database.WorkspaceTable{ OwnerID: member.ID, OrganizationID: owner.OrganizationID, }). diff --git a/cli/state_test.go b/cli/state_test.go index 08f2c96d14f7b..44b92b2c7960d 100644 --- a/cli/state_test.go +++ b/cli/state_test.go @@ -28,7 +28,7 @@ func TestStatePull(t *testing.T) { owner := coderdtest.CreateFirstUser(t, client) templateAdmin, taUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin()) wantState := []byte("some state") - r := dbfake.WorkspaceBuild(t, store, database.Workspace{ + r := dbfake.WorkspaceBuild(t, store, database.WorkspaceTable{ OrganizationID: owner.OrganizationID, OwnerID: taUser.ID, }). @@ -49,7 +49,7 @@ func TestStatePull(t *testing.T) { owner := coderdtest.CreateFirstUser(t, client) templateAdmin, taUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin()) wantState := []byte("some state") - r := dbfake.WorkspaceBuild(t, store, database.Workspace{ + r := dbfake.WorkspaceBuild(t, store, database.WorkspaceTable{ OrganizationID: owner.OrganizationID, OwnerID: taUser.ID, }). @@ -69,7 +69,7 @@ func TestStatePull(t *testing.T) { owner := coderdtest.CreateFirstUser(t, client) _, taUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin()) wantState := []byte("some state") - r := dbfake.WorkspaceBuild(t, store, database.Workspace{ + r := dbfake.WorkspaceBuild(t, store, database.WorkspaceTable{ OrganizationID: owner.OrganizationID, OwnerID: taUser.ID, }). diff --git a/cli/support_test.go b/cli/support_test.go index 6fe8f015c3f2b..274454acb7a48 100644 --- a/cli/support_test.go +++ b/cli/support_test.go @@ -53,7 +53,7 @@ func TestSupportBundle(t *testing.T) { DeploymentValues: dc.Values, }) owner := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: owner.OrganizationID, OwnerID: owner.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -132,7 +132,7 @@ func TestSupportBundle(t *testing.T) { DeploymentValues: dc.Values, }) admin := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: admin.OrganizationID, OwnerID: admin.UserID, }).Do() // without agent! @@ -151,7 +151,7 @@ func TestSupportBundle(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) memberClient, member := coderdtest.CreateAnotherUser(t, client, user.OrganizationID) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: member.ID, }).WithAgent().Do() diff --git a/cli/vscodessh_test.go b/cli/vscodessh_test.go index f80b6b0b6029e..9ef2ab912a206 100644 --- a/cli/vscodessh_test.go +++ b/cli/vscodessh_test.go @@ -41,7 +41,7 @@ func TestVSCodeSSH(t *testing.T) { admin.SetLogger(slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug)) first := coderdtest.CreateFirstUser(t, admin) client, user := coderdtest.CreateAnotherUser(t, admin, first.OrganizationID) - r := dbfake.WorkspaceBuild(t, store, database.Workspace{ + r := dbfake.WorkspaceBuild(t, store, database.WorkspaceTable{ OrganizationID: first.OrganizationID, OwnerID: user.ID, }).WithAgent().Do() diff --git a/coderd/agentapi/api.go b/coderd/agentapi/api.go index bea1fa5d881a3..f69f366b43d4e 100644 --- a/coderd/agentapi/api.go +++ b/coderd/agentapi/api.go @@ -106,7 +106,7 @@ func New(opts Options) *API { if err != nil { return uuid.Nil, err } - return ws.Workspace.ID, nil + return ws.ID, nil }, } @@ -231,9 +231,9 @@ func (a *API) workspaceID(ctx context.Context, agent *database.WorkspaceAgent) ( } a.mu.Lock() - a.cachedWorkspaceID = getWorkspaceAgentByIDRow.Workspace.ID + a.cachedWorkspaceID = getWorkspaceAgentByIDRow.ID a.mu.Unlock() - return getWorkspaceAgentByIDRow.Workspace.ID, nil + return getWorkspaceAgentByIDRow.ID, nil } func (a *API) publishWorkspaceUpdate(ctx context.Context, agent *database.WorkspaceAgent) error { diff --git a/coderd/agentapi/stats.go b/coderd/agentapi/stats.go index 226f06732d4ee..3108d17f75b14 100644 --- a/coderd/agentapi/stats.go +++ b/coderd/agentapi/stats.go @@ -50,7 +50,7 @@ func (a *StatsAPI) UpdateStats(ctx context.Context, req *agentproto.UpdateStatsR if err != nil { return nil, xerrors.Errorf("get workspace by agent ID %q: %w", workspaceAgent.ID, err) } - workspace := getWorkspaceAgentByIDRow.Workspace + workspace := getWorkspaceAgentByIDRow a.Log.Debug(ctx, "read stats report", slog.F("interval", a.AgentStatsRefreshInterval), slog.F("workspace_id", workspace.ID), diff --git a/coderd/agentapi/stats_test.go b/coderd/agentapi/stats_test.go index 57534208be110..d2c8e4f163df5 100644 --- a/coderd/agentapi/stats_test.go +++ b/coderd/agentapi/stats_test.go @@ -40,10 +40,11 @@ func TestUpdateStates(t *testing.T) { Name: "tpl", } workspace = database.Workspace{ - ID: uuid.New(), - OwnerID: user.ID, - TemplateID: template.ID, - Name: "xyz", + ID: uuid.New(), + OwnerID: user.ID, + TemplateID: template.ID, + Name: "xyz", + TemplateName: template.Name, } agent = database.WorkspaceAgent{ ID: uuid.New(), @@ -127,10 +128,7 @@ func TestUpdateStates(t *testing.T) { } // Workspace gets fetched. - dbM.EXPECT().GetWorkspaceByAgentID(gomock.Any(), agent.ID).Return(database.GetWorkspaceByAgentIDRow{ - Workspace: workspace, - TemplateName: template.Name, - }, nil) + dbM.EXPECT().GetWorkspaceByAgentID(gomock.Any(), agent.ID).Return(workspace, nil) // We expect an activity bump because ConnectionCount > 0. dbM.EXPECT().ActivityBumpWorkspace(gomock.Any(), database.ActivityBumpWorkspaceParams{ @@ -225,10 +223,7 @@ func TestUpdateStates(t *testing.T) { } // Workspace gets fetched. - dbM.EXPECT().GetWorkspaceByAgentID(gomock.Any(), agent.ID).Return(database.GetWorkspaceByAgentIDRow{ - Workspace: workspace, - TemplateName: template.Name, - }, nil) + dbM.EXPECT().GetWorkspaceByAgentID(gomock.Any(), agent.ID).Return(workspace, nil) // Workspace last used at gets bumped. dbM.EXPECT().UpdateWorkspaceLastUsedAt(gomock.Any(), database.UpdateWorkspaceLastUsedAtParams{ @@ -350,10 +345,7 @@ func TestUpdateStates(t *testing.T) { } // Workspace gets fetched. - dbM.EXPECT().GetWorkspaceByAgentID(gomock.Any(), agent.ID).Return(database.GetWorkspaceByAgentIDRow{ - Workspace: workspace, - TemplateName: template.Name, - }, nil) + dbM.EXPECT().GetWorkspaceByAgentID(gomock.Any(), agent.ID).Return(workspace, nil) // We expect an activity bump because ConnectionCount > 0. However, the // next autostart time will be set on the bump. @@ -461,10 +453,7 @@ func TestUpdateStates(t *testing.T) { } // Workspace gets fetched. - dbM.EXPECT().GetWorkspaceByAgentID(gomock.Any(), agent.ID).Return(database.GetWorkspaceByAgentIDRow{ - Workspace: workspace, - TemplateName: template.Name, - }, nil) + dbM.EXPECT().GetWorkspaceByAgentID(gomock.Any(), agent.ID).Return(workspace, nil) // We expect an activity bump because ConnectionCount > 0. dbM.EXPECT().ActivityBumpWorkspace(gomock.Any(), database.ActivityBumpWorkspaceParams{ diff --git a/coderd/audit/diff.go b/coderd/audit/diff.go index 04943c760a55e..8d5923d575054 100644 --- a/coderd/audit/diff.go +++ b/coderd/audit/diff.go @@ -12,7 +12,7 @@ type Auditable interface { database.Template | database.TemplateVersion | database.User | - database.Workspace | + database.WorkspaceTable | database.GitSSHKey | database.WorkspaceBuild | database.AuditableGroup | diff --git a/coderd/audit/request.go b/coderd/audit/request.go index adaf3ce1f573c..88b637384eeda 100644 --- a/coderd/audit/request.go +++ b/coderd/audit/request.go @@ -82,7 +82,7 @@ func ResourceTarget[T Auditable](tgt T) string { return typed.Name case database.User: return typed.Username - case database.Workspace: + case database.WorkspaceTable: return typed.Name case database.WorkspaceBuild: // this isn't used @@ -133,7 +133,7 @@ func ResourceID[T Auditable](tgt T) uuid.UUID { return typed.ID case database.User: return typed.ID - case database.Workspace: + case database.WorkspaceTable: return typed.ID case database.WorkspaceBuild: return typed.ID @@ -181,7 +181,7 @@ func ResourceType[T Auditable](tgt T) database.ResourceType { return database.ResourceTypeTemplateVersion case database.User: return database.ResourceTypeUser - case database.Workspace: + case database.WorkspaceTable: return database.ResourceTypeWorkspace case database.WorkspaceBuild: return database.ResourceTypeWorkspaceBuild @@ -225,7 +225,7 @@ func ResourceRequiresOrgID[T Auditable]() bool { switch any(tgt).(type) { case database.Template, database.TemplateVersion: return true - case database.Workspace, database.WorkspaceBuild: + case database.WorkspaceTable, database.WorkspaceBuild: return true case database.AuditableGroup: return true diff --git a/coderd/autobuild/lifecycle_executor.go b/coderd/autobuild/lifecycle_executor.go index 5bd8efe2b9fcf..400f0406aee0e 100644 --- a/coderd/autobuild/lifecycle_executor.go +++ b/coderd/autobuild/lifecycle_executor.go @@ -234,22 +234,24 @@ func (e *Executor) runOnce(t time.Time) Stats { // threshold for inactivity. if reason == database.BuildReasonDormancy { wsOld := ws - ws, err = tx.UpdateWorkspaceDormantDeletingAt(e.ctx, database.UpdateWorkspaceDormantDeletingAtParams{ + wsNew, err := tx.UpdateWorkspaceDormantDeletingAt(e.ctx, database.UpdateWorkspaceDormantDeletingAtParams{ ID: ws.ID, DormantAt: sql.NullTime{ Time: dbtime.Now(), Valid: true, }, }) - - auditLog = &auditParams{ - Old: wsOld, - New: ws, - } if err != nil { return xerrors.Errorf("update workspace dormant deleting at: %w", err) } + auditLog = &auditParams{ + Old: wsOld.WorkspaceTable(), + New: wsNew, + } + // To keep the `ws` accurate without doing a sql fetch + ws.DormantAt = wsNew.DormantAt + shouldNotifyDormancy = true log.Info(e.ctx, "dormant workspace", @@ -510,8 +512,8 @@ func isEligibleForFailedStop(build database.WorkspaceBuild, job database.Provisi } type auditParams struct { - Old database.Workspace - New database.Workspace + Old database.WorkspaceTable + New database.WorkspaceTable Success bool } @@ -521,7 +523,7 @@ func auditBuild(ctx context.Context, log slog.Logger, auditor audit.Auditor, par status = http.StatusOK } - audit.BackgroundAudit(ctx, &audit.BackgroundAuditParams[database.Workspace]{ + audit.BackgroundAudit(ctx, &audit.BackgroundAuditParams[database.WorkspaceTable]{ Audit: auditor, Log: log, UserID: params.New.OwnerID, diff --git a/coderd/coderd_test.go b/coderd/coderd_test.go index f19c8d4c533dd..9e1d9154a07bc 100644 --- a/coderd/coderd_test.go +++ b/coderd/coderd_test.go @@ -355,7 +355,7 @@ func TestCSRFExempt(t *testing.T) { // Create a workspace. const agentSlug = "james" const appSlug = "web" - wrk := dbfake.WorkspaceBuild(t, api.Database, database.Workspace{ + wrk := dbfake.WorkspaceBuild(t, api.Database, database.WorkspaceTable{ OwnerID: owner.ID, OrganizationID: first.OrganizationID, }). diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index a587788791c35..052f25450e6a5 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -2574,7 +2574,7 @@ func (q *querier) GetWorkspaceBuildsCreatedAfter(ctx context.Context, createdAt return q.db.GetWorkspaceBuildsCreatedAfter(ctx, createdAt) } -func (q *querier) GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUID) (database.GetWorkspaceByAgentIDRow, error) { +func (q *querier) GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUID) (database.Workspace, error) { return fetch(q.log, q.auth, q.db.GetWorkspaceByAgentID)(ctx, agentID) } @@ -2719,7 +2719,7 @@ func (q *querier) GetWorkspaces(ctx context.Context, arg database.GetWorkspacesP return q.db.GetAuthorizedWorkspaces(ctx, arg, prep) } -func (q *querier) GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]database.Workspace, error) { +func (q *querier) GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]database.WorkspaceTable, error) { return q.db.GetWorkspacesEligibleForTransition(ctx, now) } @@ -3009,7 +3009,7 @@ func (q *querier) InsertUserLink(ctx context.Context, arg database.InsertUserLin return q.db.InsertUserLink(ctx, arg) } -func (q *querier) InsertWorkspace(ctx context.Context, arg database.InsertWorkspaceParams) (database.Workspace, error) { +func (q *querier) InsertWorkspace(ctx context.Context, arg database.InsertWorkspaceParams) (database.WorkspaceTable, error) { obj := rbac.ResourceWorkspace.WithOwner(arg.OwnerID.String()).InOrg(arg.OrganizationID) return insert(q.log, q.auth, obj, q.db.InsertWorkspace)(ctx, arg) } @@ -3751,9 +3751,13 @@ func (q *querier) UpdateUserStatus(ctx context.Context, arg database.UpdateUserS return updateWithReturn(q.log, q.auth, fetch, q.db.UpdateUserStatus)(ctx, arg) } -func (q *querier) UpdateWorkspace(ctx context.Context, arg database.UpdateWorkspaceParams) (database.Workspace, error) { - fetch := func(ctx context.Context, arg database.UpdateWorkspaceParams) (database.Workspace, error) { - return q.db.GetWorkspaceByID(ctx, arg.ID) +func (q *querier) UpdateWorkspace(ctx context.Context, arg database.UpdateWorkspaceParams) (database.WorkspaceTable, error) { + fetch := func(ctx context.Context, arg database.UpdateWorkspaceParams) (database.WorkspaceTable, error) { + w, err := q.db.GetWorkspaceByID(ctx, arg.ID) + if err != nil { + return database.WorkspaceTable{}, err + } + return w.WorkspaceTable(), nil } return updateWithReturn(q.log, q.auth, fetch, q.db.UpdateWorkspace)(ctx, arg) } @@ -3905,9 +3909,13 @@ func (q *querier) UpdateWorkspaceDeletedByID(ctx context.Context, arg database.U return deleteQ(q.log, q.auth, fetch, q.db.UpdateWorkspaceDeletedByID)(ctx, arg) } -func (q *querier) UpdateWorkspaceDormantDeletingAt(ctx context.Context, arg database.UpdateWorkspaceDormantDeletingAtParams) (database.Workspace, error) { - fetch := func(ctx context.Context, arg database.UpdateWorkspaceDormantDeletingAtParams) (database.Workspace, error) { - return q.db.GetWorkspaceByID(ctx, arg.ID) +func (q *querier) UpdateWorkspaceDormantDeletingAt(ctx context.Context, arg database.UpdateWorkspaceDormantDeletingAtParams) (database.WorkspaceTable, error) { + fetch := func(ctx context.Context, arg database.UpdateWorkspaceDormantDeletingAtParams) (database.WorkspaceTable, error) { + w, err := q.db.GetWorkspaceByID(ctx, arg.ID) + if err != nil { + return database.WorkspaceTable{}, err + } + return w.WorkspaceTable(), nil } return updateWithReturn(q.log, q.auth, fetch, q.db.UpdateWorkspaceDormantDeletingAt)(ctx, arg) } @@ -3940,7 +3948,7 @@ func (q *querier) UpdateWorkspaceTTL(ctx context.Context, arg database.UpdateWor return update(q.log, q.auth, fetch, q.db.UpdateWorkspaceTTL)(ctx, arg) } -func (q *querier) UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.Context, arg database.UpdateWorkspacesDormantDeletingAtByTemplateIDParams) ([]database.Workspace, error) { +func (q *querier) UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.Context, arg database.UpdateWorkspacesDormantDeletingAtByTemplateIDParams) ([]database.WorkspaceTable, error) { template, err := q.db.GetTemplateByID(ctx, arg.TemplateID) if err != nil { return nil, xerrors.Errorf("get template by id: %w", err) diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index f554d709ad4d0..6a34e88104ce1 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -90,7 +90,7 @@ func TestInTX(t *testing.T) { Scope: rbac.ScopeAll, } - w := dbgen.Workspace(t, db, database.Workspace{}) + w := dbgen.Workspace(t, db, database.WorkspaceTable{}) ctx := dbauthz.As(context.Background(), actor) err := q.InTx(func(tx database.Store) error { // The inner tx should use the parent's authz @@ -108,7 +108,7 @@ func TestNew(t *testing.T) { var ( db = dbmem.New() - exp = dbgen.Workspace(t, db, database.Workspace{}) + exp = dbgen.Workspace(t, db, database.WorkspaceTable{}) rec = &coderdtest.RecordingAuthorizer{ Wrapped: &coderdtest.FakeAuthorizer{}, } @@ -123,7 +123,7 @@ func TestNew(t *testing.T) { w, err := az.GetWorkspaceByID(ctx, exp.ID) require.NoError(t, err, "must not error") - require.Equal(t, exp, w, "must be equal") + require.Equal(t, exp, w.WorkspaceTable(), "must be equal") rec.AssertActor(t, subj, rec.Pair(policy.ActionRead, exp)) require.NoError(t, rec.AllAsserted(), "should only be 1 rbac call") @@ -465,7 +465,7 @@ func (s *MethodTestSuite) TestProvisionerJob() { }).Asserts(v.RBACObject(tpl), policy.ActionUpdate) })) s.Run("Build/GetProvisionerJobByID", s.Subtest(func(db database.Store, check *expects) { - w := dbgen.Workspace(s.T(), db, database.Workspace{}) + w := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) j := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{ Type: database.ProvisionerJobTypeWorkspaceBuild, }) @@ -498,7 +498,7 @@ func (s *MethodTestSuite) TestProvisionerJob() { })) s.Run("Build/UpdateProvisionerJobWithCancelByID", s.Subtest(func(db database.Store, check *expects) { tpl := dbgen.Template(s.T(), db, database.Template{AllowUserCancelWorkspaceJobs: true}) - w := dbgen.Workspace(s.T(), db, database.Workspace{TemplateID: tpl.ID}) + w := dbgen.Workspace(s.T(), db, database.WorkspaceTable{TemplateID: tpl.ID}) j := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{ Type: database.ProvisionerJobTypeWorkspaceBuild, }) @@ -507,7 +507,7 @@ func (s *MethodTestSuite) TestProvisionerJob() { })) s.Run("BuildFalseCancel/UpdateProvisionerJobWithCancelByID", s.Subtest(func(db database.Store, check *expects) { tpl := dbgen.Template(s.T(), db, database.Template{AllowUserCancelWorkspaceJobs: false}) - w := dbgen.Workspace(s.T(), db, database.Workspace{TemplateID: tpl.ID}) + w := dbgen.Workspace(s.T(), db, database.WorkspaceTable{TemplateID: tpl.ID}) j := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{ Type: database.ProvisionerJobTypeWorkspaceBuild, }) @@ -557,7 +557,7 @@ func (s *MethodTestSuite) TestProvisionerJob() { check.Args([]uuid.UUID{a.ID, b.ID}).Asserts().Returns(slice.New(a, b)) })) s.Run("GetProvisionerLogsAfterID", s.Subtest(func(db database.Store, check *expects) { - w := dbgen.Workspace(s.T(), db, database.Workspace{}) + w := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) j := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{ Type: database.ProvisionerJobTypeWorkspaceBuild, }) @@ -1455,29 +1455,29 @@ func (s *MethodTestSuite) TestUser() { func (s *MethodTestSuite) TestWorkspace() { s.Run("GetWorkspaceByID", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) check.Args(ws.ID).Asserts(ws, policy.ActionRead) })) s.Run("GetWorkspaces", s.Subtest(func(db database.Store, check *expects) { - _ = dbgen.Workspace(s.T(), db, database.Workspace{}) - _ = dbgen.Workspace(s.T(), db, database.Workspace{}) + _ = dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) + _ = dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) // No asserts here because SQLFilter. check.Args(database.GetWorkspacesParams{}).Asserts() })) s.Run("GetAuthorizedWorkspaces", s.Subtest(func(db database.Store, check *expects) { - _ = dbgen.Workspace(s.T(), db, database.Workspace{}) - _ = dbgen.Workspace(s.T(), db, database.Workspace{}) + _ = dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) + _ = dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) // No asserts here because SQLFilter. check.Args(database.GetWorkspacesParams{}, emptyPreparedAuthorized{}).Asserts() })) s.Run("GetLatestWorkspaceBuildByWorkspaceID", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) b := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID}) check.Args(ws.ID).Asserts(ws, policy.ActionRead).Returns(b) })) s.Run("GetWorkspaceAgentByID", s.Subtest(func(db database.Store, check *expects) { tpl := dbgen.Template(s.T(), db, database.Template{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{ + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{ TemplateID: tpl.ID, }) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) @@ -1487,7 +1487,7 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("GetWorkspaceAgentLifecycleStateByID", s.Subtest(func(db database.Store, check *expects) { tpl := dbgen.Template(s.T(), db, database.Template{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{ + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{ TemplateID: tpl.ID, }) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) @@ -1497,7 +1497,7 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("GetWorkspaceAgentMetadata", s.Subtest(func(db database.Store, check *expects) { tpl := dbgen.Template(s.T(), db, database.Template{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{ + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{ TemplateID: tpl.ID, }) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) @@ -1515,7 +1515,7 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("GetWorkspaceAgentByInstanceID", s.Subtest(func(db database.Store, check *expects) { tpl := dbgen.Template(s.T(), db, database.Template{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{ + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{ TemplateID: tpl.ID, }) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) @@ -1525,7 +1525,7 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("UpdateWorkspaceAgentLifecycleStateByID", s.Subtest(func(db database.Store, check *expects) { tpl := dbgen.Template(s.T(), db, database.Template{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{ + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{ TemplateID: tpl.ID, }) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) @@ -1538,7 +1538,7 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("UpdateWorkspaceAgentMetadata", s.Subtest(func(db database.Store, check *expects) { tpl := dbgen.Template(s.T(), db, database.Template{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{ + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{ TemplateID: tpl.ID, }) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) @@ -1550,7 +1550,7 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("UpdateWorkspaceAgentLogOverflowByID", s.Subtest(func(db database.Store, check *expects) { tpl := dbgen.Template(s.T(), db, database.Template{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{ + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{ TemplateID: tpl.ID, }) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) @@ -1563,7 +1563,7 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("UpdateWorkspaceAgentStartupByID", s.Subtest(func(db database.Store, check *expects) { tpl := dbgen.Template(s.T(), db, database.Template{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{ + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{ TemplateID: tpl.ID, }) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) @@ -1578,7 +1578,7 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("GetWorkspaceAgentLogsAfter", s.Subtest(func(db database.Store, check *expects) { tpl := dbgen.Template(s.T(), db, database.Template{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{ + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{ TemplateID: tpl.ID, }) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) @@ -1590,7 +1590,7 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("GetWorkspaceAppByAgentIDAndSlug", s.Subtest(func(db database.Store, check *expects) { tpl := dbgen.Template(s.T(), db, database.Template{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{ + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{ TemplateID: tpl.ID, }) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) @@ -1605,7 +1605,7 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("GetWorkspaceAppsByAgentID", s.Subtest(func(db database.Store, check *expects) { tpl := dbgen.Template(s.T(), db, database.Template{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{ + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{ TemplateID: tpl.ID, }) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) @@ -1617,17 +1617,17 @@ func (s *MethodTestSuite) TestWorkspace() { check.Args(agt.ID).Asserts(ws, policy.ActionRead).Returns(slice.New(a, b)) })) s.Run("GetWorkspaceBuildByID", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID}) check.Args(build.ID).Asserts(ws, policy.ActionRead).Returns(build) })) s.Run("GetWorkspaceBuildByJobID", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID}) check.Args(build.JobID).Asserts(ws, policy.ActionRead).Returns(build) })) s.Run("GetWorkspaceBuildByWorkspaceIDAndBuildNumber", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, BuildNumber: 10}) check.Args(database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams{ WorkspaceID: ws.ID, @@ -1635,13 +1635,13 @@ func (s *MethodTestSuite) TestWorkspace() { }).Asserts(ws, policy.ActionRead).Returns(build) })) s.Run("GetWorkspaceBuildParameters", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID}) check.Args(build.ID).Asserts(ws, policy.ActionRead). Returns([]database.WorkspaceBuildParameter{}) })) s.Run("GetWorkspaceBuildsByWorkspaceID", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) _ = 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}) @@ -1649,20 +1649,17 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("GetWorkspaceByAgentID", s.Subtest(func(db database.Store, check *expects) { tpl := dbgen.Template(s.T(), db, database.Template{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{ + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{ TemplateID: tpl.ID, }) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) 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, policy.ActionRead).Returns(database.GetWorkspaceByAgentIDRow{ - Workspace: ws, - TemplateName: tpl.Name, - }) + check.Args(agt.ID).Asserts(ws, policy.ActionRead) })) s.Run("GetWorkspaceAgentsInLatestBuildByWorkspaceID", s.Subtest(func(db database.Store, check *expects) { tpl := dbgen.Template(s.T(), db, database.Template{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{ + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{ TemplateID: tpl.ID, }) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) @@ -1671,22 +1668,22 @@ func (s *MethodTestSuite) TestWorkspace() { check.Args(ws.ID).Asserts(ws, policy.ActionRead) })) s.Run("GetWorkspaceByOwnerIDAndName", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) check.Args(database.GetWorkspaceByOwnerIDAndNameParams{ OwnerID: ws.OwnerID, Deleted: ws.Deleted, Name: ws.Name, - }).Asserts(ws, policy.ActionRead).Returns(ws) + }).Asserts(ws, policy.ActionRead) })) s.Run("GetWorkspaceResourceByID", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) _ = dbgen.ProvisionerJob(s.T(), db, nil, 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, policy.ActionRead).Returns(res) })) s.Run("Build/GetWorkspaceResourcesByJobID", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) job := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{ID: build.JobID, Type: database.ProvisionerJobTypeWorkspaceBuild}) check.Args(job.ID).Asserts(ws, policy.ActionRead).Returns([]database.WorkspaceResource{}) @@ -1709,7 +1706,7 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("Start/InsertWorkspaceBuild", s.Subtest(func(db database.Store, check *expects) { t := dbgen.Template(s.T(), db, database.Template{}) - w := dbgen.Workspace(s.T(), db, database.Workspace{ + w := dbgen.Workspace(s.T(), db, database.WorkspaceTable{ TemplateID: t.ID, }) check.Args(database.InsertWorkspaceBuildParams{ @@ -1720,7 +1717,7 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("Stop/InsertWorkspaceBuild", s.Subtest(func(db database.Store, check *expects) { t := dbgen.Template(s.T(), db, database.Template{}) - w := dbgen.Workspace(s.T(), db, database.Workspace{ + w := dbgen.Workspace(s.T(), db, database.WorkspaceTable{ TemplateID: t.ID, }) check.Args(database.InsertWorkspaceBuildParams{ @@ -1740,7 +1737,7 @@ func (s *MethodTestSuite) TestWorkspace() { v := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{ TemplateID: uuid.NullUUID{UUID: t.ID}, }) - w := dbgen.Workspace(s.T(), db, database.Workspace{ + w := dbgen.Workspace(s.T(), db, database.WorkspaceTable{ TemplateID: t.ID, }) check.Args(database.InsertWorkspaceBuildParams{ @@ -1766,7 +1763,7 @@ func (s *MethodTestSuite) TestWorkspace() { }) require.NoError(s.T(), err) - w := dbgen.Workspace(s.T(), db, database.Workspace{ + w := dbgen.Workspace(s.T(), db, database.WorkspaceTable{ TemplateID: t.ID, }) // Assert that we do not check for template update permissions @@ -1781,7 +1778,7 @@ func (s *MethodTestSuite) TestWorkspace() { ) })) s.Run("Delete/InsertWorkspaceBuild", s.Subtest(func(db database.Store, check *expects) { - w := dbgen.Workspace(s.T(), db, database.Workspace{}) + w := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) check.Args(database.InsertWorkspaceBuildParams{ WorkspaceID: w.ID, Transition: database.WorkspaceTransitionDelete, @@ -1789,7 +1786,7 @@ func (s *MethodTestSuite) TestWorkspace() { }).Asserts(w, policy.ActionDelete) })) s.Run("InsertWorkspaceBuildParameters", s.Subtest(func(db database.Store, check *expects) { - w := dbgen.Workspace(s.T(), db, database.Workspace{}) + w := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) b := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: w.ID}) check.Args(database.InsertWorkspaceBuildParametersParams{ WorkspaceBuildID: b.ID, @@ -1798,7 +1795,7 @@ func (s *MethodTestSuite) TestWorkspace() { }).Asserts(w, policy.ActionUpdate) })) s.Run("UpdateWorkspace", s.Subtest(func(db database.Store, check *expects) { - w := dbgen.Workspace(s.T(), db, database.Workspace{}) + w := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) expected := w expected.Name = "" check.Args(database.UpdateWorkspaceParams{ @@ -1806,20 +1803,20 @@ func (s *MethodTestSuite) TestWorkspace() { }).Asserts(w, policy.ActionUpdate).Returns(expected) })) s.Run("UpdateWorkspaceDormantDeletingAt", s.Subtest(func(db database.Store, check *expects) { - w := dbgen.Workspace(s.T(), db, database.Workspace{}) + w := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) check.Args(database.UpdateWorkspaceDormantDeletingAtParams{ ID: w.ID, }).Asserts(w, policy.ActionUpdate) })) s.Run("UpdateWorkspaceAutomaticUpdates", s.Subtest(func(db database.Store, check *expects) { - w := dbgen.Workspace(s.T(), db, database.Workspace{}) + w := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) check.Args(database.UpdateWorkspaceAutomaticUpdatesParams{ ID: w.ID, AutomaticUpdates: database.AutomaticUpdatesAlways, }).Asserts(w, policy.ActionUpdate) })) s.Run("UpdateWorkspaceAppHealthByID", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID}) agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID}) @@ -1830,13 +1827,13 @@ func (s *MethodTestSuite) TestWorkspace() { }).Asserts(ws, policy.ActionUpdate).Returns() })) s.Run("UpdateWorkspaceAutostart", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) check.Args(database.UpdateWorkspaceAutostartParams{ ID: ws.ID, }).Asserts(ws, policy.ActionUpdate).Returns() })) s.Run("UpdateWorkspaceBuildDeadlineByID", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) check.Args(database.UpdateWorkspaceBuildDeadlineByIDParams{ ID: build.ID, @@ -1845,46 +1842,46 @@ func (s *MethodTestSuite) TestWorkspace() { }).Asserts(ws, policy.ActionUpdate) })) s.Run("SoftDeleteWorkspaceByID", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) ws.Deleted = true check.Args(ws.ID).Asserts(ws, policy.ActionDelete).Returns() })) s.Run("UpdateWorkspaceDeletedByID", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{Deleted: true}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{Deleted: true}) check.Args(database.UpdateWorkspaceDeletedByIDParams{ ID: ws.ID, Deleted: true, }).Asserts(ws, policy.ActionDelete).Returns() })) s.Run("UpdateWorkspaceLastUsedAt", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) check.Args(database.UpdateWorkspaceLastUsedAtParams{ ID: ws.ID, }).Asserts(ws, policy.ActionUpdate).Returns() })) s.Run("BatchUpdateWorkspaceLastUsedAt", s.Subtest(func(db database.Store, check *expects) { - ws1 := dbgen.Workspace(s.T(), db, database.Workspace{}) - ws2 := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws1 := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) + ws2 := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) check.Args(database.BatchUpdateWorkspaceLastUsedAtParams{ IDs: []uuid.UUID{ws1.ID, ws2.ID}, }).Asserts(rbac.ResourceWorkspace.All(), policy.ActionUpdate).Returns() })) s.Run("UpdateWorkspaceTTL", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) check.Args(database.UpdateWorkspaceTTLParams{ ID: ws.ID, }).Asserts(ws, policy.ActionUpdate).Returns() })) s.Run("GetWorkspaceByWorkspaceAppID", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) 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}) - check.Args(app.ID).Asserts(ws, policy.ActionRead).Returns(ws) + check.Args(app.ID).Asserts(ws, policy.ActionRead) })) s.Run("ActivityBumpWorkspace", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{ID: build.JobID, Type: database.ProvisionerJobTypeWorkspaceBuild}) check.Args(database.ActivityBumpWorkspaceParams{ @@ -1893,12 +1890,12 @@ func (s *MethodTestSuite) TestWorkspace() { })) s.Run("FavoriteWorkspace", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{OwnerID: u.ID}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{OwnerID: u.ID}) check.Args(ws.ID).Asserts(ws, policy.ActionUpdate).Returns() })) s.Run("UnfavoriteWorkspace", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{OwnerID: u.ID}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{OwnerID: u.ID}) check.Args(ws.ID).Asserts(ws, policy.ActionUpdate).Returns() })) } @@ -1906,7 +1903,7 @@ func (s *MethodTestSuite) TestWorkspace() { func (s *MethodTestSuite) TestWorkspacePortSharing() { s.Run("UpsertWorkspaceAgentPortShare", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{OwnerID: u.ID}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{OwnerID: u.ID}) ps := dbgen.WorkspaceAgentPortShare(s.T(), db, database.WorkspaceAgentPortShare{WorkspaceID: ws.ID}) //nolint:gosimple // casting is not a simplification check.Args(database.UpsertWorkspaceAgentPortShareParams{ @@ -1919,7 +1916,7 @@ func (s *MethodTestSuite) TestWorkspacePortSharing() { })) s.Run("GetWorkspaceAgentPortShare", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{OwnerID: u.ID}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{OwnerID: u.ID}) ps := dbgen.WorkspaceAgentPortShare(s.T(), db, database.WorkspaceAgentPortShare{WorkspaceID: ws.ID}) check.Args(database.GetWorkspaceAgentPortShareParams{ WorkspaceID: ps.WorkspaceID, @@ -1929,13 +1926,13 @@ func (s *MethodTestSuite) TestWorkspacePortSharing() { })) s.Run("ListWorkspaceAgentPortShares", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{OwnerID: u.ID}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{OwnerID: u.ID}) ps := dbgen.WorkspaceAgentPortShare(s.T(), db, database.WorkspaceAgentPortShare{WorkspaceID: ws.ID}) check.Args(ws.ID).Asserts(ws, policy.ActionRead).Returns([]database.WorkspaceAgentPortShare{ps}) })) s.Run("DeleteWorkspaceAgentPortShare", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{OwnerID: u.ID}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{OwnerID: u.ID}) ps := dbgen.WorkspaceAgentPortShare(s.T(), db, database.WorkspaceAgentPortShare{WorkspaceID: ws.ID}) check.Args(database.DeleteWorkspaceAgentPortShareParams{ WorkspaceID: ps.WorkspaceID, @@ -1946,14 +1943,14 @@ func (s *MethodTestSuite) TestWorkspacePortSharing() { s.Run("DeleteWorkspaceAgentPortSharesByTemplate", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) t := dbgen.Template(s.T(), db, database.Template{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{OwnerID: u.ID, TemplateID: t.ID}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{OwnerID: u.ID, TemplateID: t.ID}) _ = dbgen.WorkspaceAgentPortShare(s.T(), db, database.WorkspaceAgentPortShare{WorkspaceID: ws.ID}) check.Args(t.ID).Asserts(t, policy.ActionUpdate).Returns() })) s.Run("ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) t := dbgen.Template(s.T(), db, database.Template{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{OwnerID: u.ID, TemplateID: t.ID}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{OwnerID: u.ID, TemplateID: t.ID}) _ = dbgen.WorkspaceAgentPortShare(s.T(), db, database.WorkspaceAgentPortShare{WorkspaceID: ws.ID}) check.Args(t.ID).Asserts(t, policy.ActionUpdate).Returns() })) @@ -2305,7 +2302,7 @@ func (s *MethodTestSuite) TestSystemFunctions() { }).Asserts(rbac.ResourceSystem, policy.ActionUpdate).Returns(l) })) s.Run("GetLatestWorkspaceBuildsByWorkspaceIDs", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) b := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID}) check.Args([]uuid.UUID{ws.ID}).Asserts(rbac.ResourceSystem, policy.ActionRead).Returns(slice.New(b)) })) @@ -2388,7 +2385,7 @@ func (s *MethodTestSuite) TestSystemFunctions() { }).Asserts(rbac.ResourceSystem, policy.ActionUpdate) })) s.Run("UpdateWorkspaceBuildProvisionerStateByID", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) check.Args(database.UpdateWorkspaceBuildProvisionerStateByIDParams{ ID: build.ID, @@ -2457,13 +2454,13 @@ func (s *MethodTestSuite) TestSystemFunctions() { Asserts(tpl, policy.ActionRead).Errors(sql.ErrNoRows) })) s.Run("GetWorkspaceAppsByAgentIDs", s.Subtest(func(db database.Store, check *expects) { - aWs := dbgen.Workspace(s.T(), db, database.Workspace{}) + aWs := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) aBuild := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: aWs.ID, JobID: uuid.New()}) 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{}) + bWs := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) bBuild := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: bWs.ID, JobID: uuid.New()}) bRes := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: bBuild.JobID}) bAgt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: bRes.ID}) @@ -2478,7 +2475,7 @@ func (s *MethodTestSuite) TestSystemFunctions() { v := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{TemplateID: uuid.NullUUID{UUID: tpl.ID, Valid: true}, JobID: uuid.New()}) tJob := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{ID: v.JobID, Type: database.ProvisionerJobTypeTemplateVersionImport}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) wJob := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{ID: build.JobID, Type: database.ProvisionerJobTypeWorkspaceBuild}) check.Args([]uuid.UUID{tJob.ID, wJob.ID}). @@ -2486,7 +2483,7 @@ func (s *MethodTestSuite) TestSystemFunctions() { Returns([]database.WorkspaceResource{}) })) s.Run("GetWorkspaceResourceMetadataByResourceIDs", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) _ = dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{ID: build.JobID, Type: database.ProvisionerJobTypeWorkspaceBuild}) a := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID}) @@ -2495,7 +2492,7 @@ func (s *MethodTestSuite) TestSystemFunctions() { Asserts(rbac.ResourceSystem, policy.ActionRead) })) s.Run("GetWorkspaceAgentsByResourceIDs", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID}) agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID}) @@ -2529,7 +2526,7 @@ func (s *MethodTestSuite) TestSystemFunctions() { }).Asserts(rbac.ResourceSystem, policy.ActionCreate) })) s.Run("UpdateWorkspaceAgentConnectionByID", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID}) agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID}) @@ -2772,7 +2769,7 @@ func (s *MethodTestSuite) TestSystemFunctions() { check.Args(uuid.New()).Asserts(rbac.ResourceSystem, policy.ActionRead) })) s.Run("GetJFrogXrayScanByWorkspaceAndAgentID", s.Subtest(func(db database.Store, check *expects) { - ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) agent := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{}) err := db.UpsertJFrogXrayScanByWorkspaceAndAgentID(context.Background(), database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams{ @@ -2801,7 +2798,7 @@ func (s *MethodTestSuite) TestSystemFunctions() { })) s.Run("UpsertJFrogXrayScanByWorkspaceAndAgentID", s.Subtest(func(db database.Store, check *expects) { tpl := dbgen.Template(s.T(), db, database.Template{}) - ws := dbgen.Workspace(s.T(), db, database.Workspace{ + ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{ TemplateID: tpl.ID, }) check.Args(database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams{ @@ -2848,7 +2845,7 @@ func (s *MethodTestSuite) TestSystemFunctions() { }).Asserts(rbac.ResourceSystem, policy.ActionCreate) })) s.Run("GetProvisionerJobTimingsByJobID", s.Subtest(func(db database.Store, check *expects) { - w := dbgen.Workspace(s.T(), db, database.Workspace{}) + w := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) j := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{ Type: database.ProvisionerJobTypeWorkspaceBuild, }) @@ -2857,7 +2854,7 @@ func (s *MethodTestSuite) TestSystemFunctions() { check.Args(j.ID).Asserts(w, policy.ActionRead).Returns(t) })) s.Run("GetWorkspaceAgentScriptTimingsByBuildID", s.Subtest(func(db database.Store, check *expects) { - workspace := dbgen.Workspace(s.T(), db, database.Workspace{}) + workspace := dbgen.Workspace(s.T(), db, database.WorkspaceTable{}) job := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{ Type: database.ProvisionerJobTypeWorkspaceBuild, }) diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index 4f9d6ddc5b28c..616dd2afac619 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -32,7 +32,7 @@ var ownerCtx = dbauthz.As(context.Background(), rbac.Subject{ }) type WorkspaceResponse struct { - Workspace database.Workspace + Workspace database.WorkspaceTable Build database.WorkspaceBuild AgentToken string TemplateVersionResponse @@ -44,7 +44,7 @@ type WorkspaceBuildBuilder struct { t testing.TB db database.Store ps pubsub.Pubsub - ws database.Workspace + ws database.WorkspaceTable seed database.WorkspaceBuild resources []*sdkproto.Resource params []database.WorkspaceBuildParameter @@ -60,7 +60,7 @@ type workspaceBuildDisposition struct { // Pass a database.Workspace{} with a nil ID to also generate a new workspace. // Omitting the template ID on a workspace will also generate a new template // with a template version. -func WorkspaceBuild(t testing.TB, db database.Store, ws database.Workspace) WorkspaceBuildBuilder { +func WorkspaceBuild(t testing.TB, db database.Store, ws database.WorkspaceTable) WorkspaceBuildBuilder { return WorkspaceBuildBuilder{t: t, db: db, ws: ws} } diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index a8ecabe752011..255c62f82aef2 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -232,7 +232,7 @@ func WorkspaceAgentScriptTiming(t testing.TB, db database.Store, orig database.W return timing } -func Workspace(t testing.TB, db database.Store, orig database.Workspace) database.Workspace { +func Workspace(t testing.TB, db database.Store, orig database.WorkspaceTable) database.WorkspaceTable { t.Helper() workspace, err := db.InsertWorkspace(genCtx, database.InsertWorkspaceParams{ diff --git a/coderd/database/dbgen/dbgen_test.go b/coderd/database/dbgen/dbgen_test.go index 04f6d38d70d00..eec6e90d5904a 100644 --- a/coderd/database/dbgen/dbgen_test.go +++ b/coderd/database/dbgen/dbgen_test.go @@ -128,8 +128,8 @@ func TestGenerator(t *testing.T) { t.Run("Workspace", func(t *testing.T) { t.Parallel() db := dbmem.New() - exp := dbgen.Workspace(t, db, database.Workspace{}) - require.Equal(t, exp, must(db.GetWorkspaceByID(context.Background(), exp.ID))) + exp := dbgen.Workspace(t, db, database.WorkspaceTable{}) + require.Equal(t, exp, must(db.GetWorkspaceByID(context.Background(), exp.ID)).WorkspaceTable()) }) t.Run("WorkspaceAgent", func(t *testing.T) { diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index ef7a2e63f0b5f..24498d88c9dbc 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -81,7 +81,7 @@ func New() database.Store { workspaceAgentLogs: make([]database.WorkspaceAgentLog, 0), workspaceBuilds: make([]database.WorkspaceBuild, 0), workspaceApps: make([]database.WorkspaceApp, 0), - workspaces: make([]database.Workspace, 0), + workspaces: make([]database.WorkspaceTable, 0), licenses: make([]database.License, 0), workspaceProxies: make([]database.WorkspaceProxy, 0), customRoles: make([]database.CustomRole, 0), @@ -232,7 +232,7 @@ type data struct { workspaceBuildParameters []database.WorkspaceBuildParameter workspaceResourceMetadata []database.WorkspaceResourceMetadatum workspaceResources []database.WorkspaceResource - workspaces []database.Workspace + workspaces []database.WorkspaceTable workspaceProxies []database.WorkspaceProxy customRoles []database.CustomRole provisionerJobTimings []database.ProvisionerJobTiming @@ -445,9 +445,11 @@ func mapAgentStatus(dbAgent database.WorkspaceAgent, agentInactiveDisconnectTime return status } -func (q *FakeQuerier) convertToWorkspaceRowsNoLock(ctx context.Context, workspaces []database.Workspace, count int64, withSummary bool) []database.GetWorkspacesRow { //nolint:revive // withSummary flag ensures the extra technical row +func (q *FakeQuerier) convertToWorkspaceRowsNoLock(ctx context.Context, workspaces []database.WorkspaceTable, count int64, withSummary bool) []database.GetWorkspacesRow { //nolint:revive // withSummary flag ensures the extra technical row rows := make([]database.GetWorkspacesRow, 0, len(workspaces)) for _, w := range workspaces { + extended := q.extendWorkspace(w) + wr := database.GetWorkspacesRow{ ID: w.ID, CreatedAt: w.CreatedAt, @@ -462,16 +464,33 @@ func (q *FakeQuerier) convertToWorkspaceRowsNoLock(ctx context.Context, workspac LastUsedAt: w.LastUsedAt, DormantAt: w.DormantAt, DeletingAt: w.DeletingAt, - Count: count, AutomaticUpdates: w.AutomaticUpdates, Favorite: w.Favorite, - } - for _, t := range q.templates { - if t.ID == w.TemplateID { - wr.TemplateName = t.Name - break - } + OwnerAvatarUrl: extended.OwnerAvatarUrl, + OwnerUsername: extended.OwnerUsername, + + OrganizationName: extended.OrganizationName, + OrganizationDisplayName: extended.OrganizationDisplayName, + OrganizationIcon: extended.OrganizationIcon, + OrganizationDescription: extended.OrganizationDescription, + + TemplateName: extended.TemplateName, + TemplateDisplayName: extended.TemplateDisplayName, + TemplateIcon: extended.TemplateIcon, + TemplateDescription: extended.TemplateDescription, + + Count: count, + + // These fields are missing! + // Try to resolve them below + TemplateVersionID: uuid.UUID{}, + TemplateVersionName: sql.NullString{}, + LatestBuildCompletedAt: sql.NullTime{}, + LatestBuildCanceledAt: sql.NullTime{}, + LatestBuildError: sql.NullString{}, + LatestBuildTransition: "", + LatestBuildStatus: "", } if build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, w.ID); err == nil { @@ -488,15 +507,14 @@ func (q *FakeQuerier) convertToWorkspaceRowsNoLock(ctx context.Context, workspac if pj, err := q.getProvisionerJobByIDNoLock(ctx, build.JobID); err == nil { wr.LatestBuildStatus = pj.JobStatus + wr.LatestBuildCanceledAt = pj.CanceledAt + wr.LatestBuildCompletedAt = pj.CompletedAt + wr.LatestBuildError = pj.Error } wr.LatestBuildTransition = build.Transition } - if u, err := q.getUserByIDNoLock(w.OwnerID); err == nil { - wr.Username = u.Username - } - rows = append(rows, wr) } if withSummary { @@ -509,14 +527,50 @@ func (q *FakeQuerier) convertToWorkspaceRowsNoLock(ctx context.Context, workspac } func (q *FakeQuerier) getWorkspaceByIDNoLock(_ context.Context, id uuid.UUID) (database.Workspace, error) { - for _, workspace := range q.workspaces { - if workspace.ID == id { - return workspace, nil - } + return q.getWorkspaceNoLock(func(w database.WorkspaceTable) bool { + return w.ID == id + }) +} + +func (q *FakeQuerier) getWorkspaceNoLock(find func(w database.WorkspaceTable) bool) (database.Workspace, error) { + w, found := slice.Find(q.workspaces, find) + if found { + return q.extendWorkspace(w), nil } return database.Workspace{}, sql.ErrNoRows } +func (q *FakeQuerier) extendWorkspace(w database.WorkspaceTable) database.Workspace { + var extended database.Workspace + // This is a cheeky way to copy the fields over without explicitly listing them all. + d, _ := json.Marshal(w) + _ = json.Unmarshal(d, &extended) + + org, _ := slice.Find(q.organizations, func(o database.Organization) bool { + return o.ID == w.OrganizationID + }) + extended.OrganizationName = org.Name + extended.OrganizationDescription = org.Description + extended.OrganizationDisplayName = org.DisplayName + extended.OrganizationIcon = org.Icon + + tpl, _ := slice.Find(q.templates, func(t database.TemplateTable) bool { + return t.ID == w.TemplateID + }) + extended.TemplateName = tpl.Name + extended.TemplateDisplayName = tpl.DisplayName + extended.TemplateDescription = tpl.Description + extended.TemplateIcon = tpl.Icon + + owner, _ := slice.Find(q.users, func(u database.User) bool { + return u.ID == w.OwnerID + }) + extended.OwnerUsername = owner.Username + extended.OwnerAvatarUrl = owner.AvatarURL + + return extended +} + func (q *FakeQuerier) getWorkspaceByAgentIDNoLock(_ context.Context, agentID uuid.UUID) (database.Workspace, error) { var agent database.WorkspaceAgent for _, _agent := range q.workspaceAgents { @@ -551,13 +605,9 @@ func (q *FakeQuerier) getWorkspaceByAgentIDNoLock(_ context.Context, agentID uui return database.Workspace{}, sql.ErrNoRows } - for _, workspace := range q.workspaces { - if workspace.ID == build.WorkspaceID { - return workspace, nil - } - } - - return database.Workspace{}, sql.ErrNoRows + return q.getWorkspaceNoLock(func(w database.WorkspaceTable) bool { + return w.ID == build.WorkspaceID + }) } func (q *FakeQuerier) getWorkspaceBuildByIDNoLock(_ context.Context, id uuid.UUID) (database.WorkspaceBuild, error) { @@ -986,14 +1036,14 @@ func (q *FakeQuerier) getLatestWorkspaceAppByTemplateIDUserIDSlugNoLock(ctx cont LIMIT 1 */ - var workspaces []database.Workspace + var workspaces []database.WorkspaceTable for _, w := range q.workspaces { if w.TemplateID != templateID || w.OwnerID != userID { continue } workspaces = append(workspaces, w) } - slices.SortFunc(workspaces, func(a, b database.Workspace) int { + slices.SortFunc(workspaces, func(a, b database.WorkspaceTable) int { if a.CreatedAt.Before(b.CreatedAt) { return 1 } else if a.CreatedAt.Equal(b.CreatedAt) { @@ -5644,7 +5694,7 @@ func (q *FakeQuerier) GetWorkspaceAgentAndLatestBuildByAuthToken(_ context.Conte continue } row := database.GetWorkspaceAgentAndLatestBuildByAuthTokenRow{ - Workspace: database.Workspace{ + WorkspaceTable: database.WorkspaceTable{ ID: ws.ID, TemplateID: ws.TemplateID, }, @@ -5655,7 +5705,7 @@ func (q *FakeQuerier) GetWorkspaceAgentAndLatestBuildByAuthToken(_ context.Conte if err != nil { return database.GetWorkspaceAgentAndLatestBuildByAuthTokenRow{}, sql.ErrNoRows } - row.Workspace.OwnerID = usr.ID + row.WorkspaceTable.OwnerID = usr.ID // Keep track of the latest build number rows = append(rows, row) @@ -5672,7 +5722,7 @@ func (q *FakeQuerier) GetWorkspaceAgentAndLatestBuildByAuthToken(_ context.Conte continue } - if rows[i].WorkspaceBuild.BuildNumber != latestBuildNumber[rows[i].Workspace.ID] { + if rows[i].WorkspaceBuild.BuildNumber != latestBuildNumber[rows[i].WorkspaceTable.ID] { continue } @@ -6514,24 +6564,16 @@ func (q *FakeQuerier) GetWorkspaceBuildsCreatedAfter(_ context.Context, after ti return workspaceBuilds, nil } -func (q *FakeQuerier) GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUID) (database.GetWorkspaceByAgentIDRow, error) { +func (q *FakeQuerier) GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUID) (database.Workspace, error) { q.mutex.RLock() defer q.mutex.RUnlock() w, err := q.getWorkspaceByAgentIDNoLock(ctx, agentID) if err != nil { - return database.GetWorkspaceByAgentIDRow{}, err - } - - tpl, err := q.getTemplateByIDNoLock(ctx, w.TemplateID) - if err != nil { - return database.GetWorkspaceByAgentIDRow{}, err + return database.Workspace{}, err } - return database.GetWorkspaceByAgentIDRow{ - Workspace: w, - TemplateName: tpl.Name, - }, nil + return w, nil } func (q *FakeQuerier) GetWorkspaceByID(ctx context.Context, id uuid.UUID) (database.Workspace, error) { @@ -6549,7 +6591,7 @@ func (q *FakeQuerier) GetWorkspaceByOwnerIDAndName(_ context.Context, arg databa q.mutex.RLock() defer q.mutex.RUnlock() - var found *database.Workspace + var found *database.WorkspaceTable for _, workspace := range q.workspaces { workspace := workspace if workspace.OwnerID != arg.OwnerID { @@ -6568,7 +6610,7 @@ func (q *FakeQuerier) GetWorkspaceByOwnerIDAndName(_ context.Context, arg databa } } if found != nil { - return *found, nil + return q.extendWorkspace(*found), nil } return database.Workspace{}, sql.ErrNoRows } @@ -6794,11 +6836,11 @@ func (q *FakeQuerier) GetWorkspaces(ctx context.Context, arg database.GetWorkspa return workspaceRows, err } -func (q *FakeQuerier) GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]database.Workspace, error) { +func (q *FakeQuerier) GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]database.WorkspaceTable, error) { q.mutex.RLock() defer q.mutex.RUnlock() - workspaces := []database.Workspace{} + workspaces := []database.WorkspaceTable{} for _, workspace := range q.workspaces { build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspace.ID) if err != nil { @@ -7759,16 +7801,16 @@ func (q *FakeQuerier) InsertUserLink(_ context.Context, args database.InsertUser return link, nil } -func (q *FakeQuerier) InsertWorkspace(_ context.Context, arg database.InsertWorkspaceParams) (database.Workspace, error) { +func (q *FakeQuerier) InsertWorkspace(_ context.Context, arg database.InsertWorkspaceParams) (database.WorkspaceTable, error) { if err := validateDatabaseType(arg); err != nil { - return database.Workspace{}, err + return database.WorkspaceTable{}, err } q.mutex.Lock() defer q.mutex.Unlock() //nolint:gosimple - workspace := database.Workspace{ + workspace := database.WorkspaceTable{ ID: arg.ID, CreatedAt: arg.CreatedAt, UpdatedAt: arg.UpdatedAt, @@ -9408,9 +9450,9 @@ func (q *FakeQuerier) UpdateUserStatus(_ context.Context, arg database.UpdateUse return database.User{}, sql.ErrNoRows } -func (q *FakeQuerier) UpdateWorkspace(_ context.Context, arg database.UpdateWorkspaceParams) (database.Workspace, error) { +func (q *FakeQuerier) UpdateWorkspace(_ context.Context, arg database.UpdateWorkspaceParams) (database.WorkspaceTable, error) { if err := validateDatabaseType(arg); err != nil { - return database.Workspace{}, err + return database.WorkspaceTable{}, err } q.mutex.Lock() @@ -9425,7 +9467,7 @@ func (q *FakeQuerier) UpdateWorkspace(_ context.Context, arg database.UpdateWork continue } if other.Name == arg.Name { - return database.Workspace{}, errUniqueConstraint + return database.WorkspaceTable{}, errUniqueConstraint } } @@ -9435,7 +9477,7 @@ func (q *FakeQuerier) UpdateWorkspace(_ context.Context, arg database.UpdateWork return workspace, nil } - return database.Workspace{}, sql.ErrNoRows + return database.WorkspaceTable{}, sql.ErrNoRows } func (q *FakeQuerier) UpdateWorkspaceAgentConnectionByID(_ context.Context, arg database.UpdateWorkspaceAgentConnectionByIDParams) error { @@ -9700,9 +9742,9 @@ func (q *FakeQuerier) UpdateWorkspaceDeletedByID(_ context.Context, arg database return sql.ErrNoRows } -func (q *FakeQuerier) UpdateWorkspaceDormantDeletingAt(_ context.Context, arg database.UpdateWorkspaceDormantDeletingAtParams) (database.Workspace, error) { +func (q *FakeQuerier) UpdateWorkspaceDormantDeletingAt(_ context.Context, arg database.UpdateWorkspaceDormantDeletingAtParams) (database.WorkspaceTable, error) { if err := validateDatabaseType(arg); err != nil { - return database.Workspace{}, err + return database.WorkspaceTable{}, err } q.mutex.Lock() defer q.mutex.Unlock() @@ -9724,7 +9766,7 @@ func (q *FakeQuerier) UpdateWorkspaceDormantDeletingAt(_ context.Context, arg da } } if template.ID == uuid.Nil { - return database.Workspace{}, xerrors.Errorf("unable to find workspace template") + return database.WorkspaceTable{}, xerrors.Errorf("unable to find workspace template") } if template.TimeTilDormantAutoDelete > 0 { workspace.DeletingAt = sql.NullTime{ @@ -9736,7 +9778,7 @@ func (q *FakeQuerier) UpdateWorkspaceDormantDeletingAt(_ context.Context, arg da q.workspaces[index] = workspace return workspace, nil } - return database.Workspace{}, sql.ErrNoRows + return database.WorkspaceTable{}, sql.ErrNoRows } func (q *FakeQuerier) UpdateWorkspaceLastUsedAt(_ context.Context, arg database.UpdateWorkspaceLastUsedAtParams) error { @@ -9819,7 +9861,7 @@ func (q *FakeQuerier) UpdateWorkspaceTTL(_ context.Context, arg database.UpdateW return sql.ErrNoRows } -func (q *FakeQuerier) UpdateWorkspacesDormantDeletingAtByTemplateID(_ context.Context, arg database.UpdateWorkspacesDormantDeletingAtByTemplateIDParams) ([]database.Workspace, error) { +func (q *FakeQuerier) UpdateWorkspacesDormantDeletingAtByTemplateID(_ context.Context, arg database.UpdateWorkspacesDormantDeletingAtByTemplateIDParams) ([]database.WorkspaceTable, error) { q.mutex.Lock() defer q.mutex.Unlock() @@ -9828,7 +9870,7 @@ func (q *FakeQuerier) UpdateWorkspacesDormantDeletingAtByTemplateID(_ context.Co return nil, err } - affectedRows := []database.Workspace{} + affectedRows := []database.WorkspaceTable{} for i, ws := range q.workspaces { if ws.TemplateID != arg.TemplateID { continue @@ -10863,7 +10905,7 @@ func (q *FakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database. } } - workspaces := make([]database.Workspace, 0) + workspaces := make([]database.WorkspaceTable, 0) for _, workspace := range q.workspaces { if arg.OwnerID != uuid.Nil && workspace.OwnerID != arg.OwnerID { continue @@ -11159,7 +11201,7 @@ func (q *FakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database. if arg.Offset > 0 { if int(arg.Offset) > len(workspaces) { - return q.convertToWorkspaceRowsNoLock(ctx, []database.Workspace{}, int64(beforePageCount), arg.WithSummary), nil + return q.convertToWorkspaceRowsNoLock(ctx, []database.WorkspaceTable{}, int64(beforePageCount), arg.WithSummary), nil } workspaces = workspaces[arg.Offset:] } diff --git a/coderd/database/dbmetrics/dbmetrics.go b/coderd/database/dbmetrics/dbmetrics.go index b543d27c5f833..c3e9de22fb0d8 100644 --- a/coderd/database/dbmetrics/dbmetrics.go +++ b/coderd/database/dbmetrics/dbmetrics.go @@ -1544,7 +1544,7 @@ func (m metricsStore) GetWorkspaceBuildsCreatedAfter(ctx context.Context, create return builds, err } -func (m metricsStore) GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUID) (database.GetWorkspaceByAgentIDRow, error) { +func (m metricsStore) GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUID) (database.Workspace, error) { start := time.Now() workspace, err := m.s.GetWorkspaceByAgentID(ctx, agentID) m.queryLatencies.WithLabelValues("GetWorkspaceByAgentID").Observe(time.Since(start).Seconds()) @@ -1656,7 +1656,7 @@ func (m metricsStore) GetWorkspaces(ctx context.Context, arg database.GetWorkspa return workspaces, err } -func (m metricsStore) GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]database.Workspace, error) { +func (m metricsStore) GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]database.WorkspaceTable, error) { start := time.Now() workspaces, err := m.s.GetWorkspacesEligibleForTransition(ctx, now) m.queryLatencies.WithLabelValues("GetWorkspacesEligibleForAutoStartStop").Observe(time.Since(start).Seconds()) @@ -1908,7 +1908,7 @@ func (m metricsStore) InsertUserLink(ctx context.Context, arg database.InsertUse return link, err } -func (m metricsStore) InsertWorkspace(ctx context.Context, arg database.InsertWorkspaceParams) (database.Workspace, error) { +func (m metricsStore) InsertWorkspace(ctx context.Context, arg database.InsertWorkspaceParams) (database.WorkspaceTable, error) { start := time.Now() workspace, err := m.s.InsertWorkspace(ctx, arg) m.queryLatencies.WithLabelValues("InsertWorkspace").Observe(time.Since(start).Seconds()) @@ -2391,7 +2391,7 @@ func (m metricsStore) UpdateUserStatus(ctx context.Context, arg database.UpdateU return user, err } -func (m metricsStore) UpdateWorkspace(ctx context.Context, arg database.UpdateWorkspaceParams) (database.Workspace, error) { +func (m metricsStore) UpdateWorkspace(ctx context.Context, arg database.UpdateWorkspaceParams) (database.WorkspaceTable, error) { start := time.Now() workspace, err := m.s.UpdateWorkspace(ctx, arg) m.queryLatencies.WithLabelValues("UpdateWorkspace").Observe(time.Since(start).Seconds()) @@ -2482,7 +2482,7 @@ func (m metricsStore) UpdateWorkspaceDeletedByID(ctx context.Context, arg databa return err } -func (m metricsStore) UpdateWorkspaceDormantDeletingAt(ctx context.Context, arg database.UpdateWorkspaceDormantDeletingAtParams) (database.Workspace, error) { +func (m metricsStore) UpdateWorkspaceDormantDeletingAt(ctx context.Context, arg database.UpdateWorkspaceDormantDeletingAtParams) (database.WorkspaceTable, error) { start := time.Now() ws, r0 := m.s.UpdateWorkspaceDormantDeletingAt(ctx, arg) m.queryLatencies.WithLabelValues("UpdateWorkspaceDormantDeletingAt").Observe(time.Since(start).Seconds()) @@ -2517,7 +2517,7 @@ func (m metricsStore) UpdateWorkspaceTTL(ctx context.Context, arg database.Updat return r0 } -func (m metricsStore) UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.Context, arg database.UpdateWorkspacesDormantDeletingAtByTemplateIDParams) ([]database.Workspace, error) { +func (m metricsStore) UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.Context, arg database.UpdateWorkspacesDormantDeletingAtByTemplateIDParams) ([]database.WorkspaceTable, error) { start := time.Now() r0, r1 := m.s.UpdateWorkspacesDormantDeletingAtByTemplateID(ctx, arg) m.queryLatencies.WithLabelValues("UpdateWorkspacesDormantDeletingAtByTemplateID").Observe(time.Since(start).Seconds()) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index fb8bb1a55e00d..b3c7b9e7615d3 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -3234,10 +3234,10 @@ func (mr *MockStoreMockRecorder) GetWorkspaceBuildsCreatedAfter(arg0, arg1 any) } // GetWorkspaceByAgentID mocks base method. -func (m *MockStore) GetWorkspaceByAgentID(arg0 context.Context, arg1 uuid.UUID) (database.GetWorkspaceByAgentIDRow, error) { +func (m *MockStore) GetWorkspaceByAgentID(arg0 context.Context, arg1 uuid.UUID) (database.Workspace, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetWorkspaceByAgentID", arg0, arg1) - ret0, _ := ret[0].(database.GetWorkspaceByAgentIDRow) + ret0, _ := ret[0].(database.Workspace) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -3474,10 +3474,10 @@ func (mr *MockStoreMockRecorder) GetWorkspaces(arg0, arg1 any) *gomock.Call { } // GetWorkspacesEligibleForTransition mocks base method. -func (m *MockStore) GetWorkspacesEligibleForTransition(arg0 context.Context, arg1 time.Time) ([]database.Workspace, error) { +func (m *MockStore) GetWorkspacesEligibleForTransition(arg0 context.Context, arg1 time.Time) ([]database.WorkspaceTable, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetWorkspacesEligibleForTransition", arg0, arg1) - ret0, _ := ret[0].([]database.Workspace) + ret0, _ := ret[0].([]database.WorkspaceTable) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -4021,10 +4021,10 @@ func (mr *MockStoreMockRecorder) InsertUserLink(arg0, arg1 any) *gomock.Call { } // InsertWorkspace mocks base method. -func (m *MockStore) InsertWorkspace(arg0 context.Context, arg1 database.InsertWorkspaceParams) (database.Workspace, error) { +func (m *MockStore) InsertWorkspace(arg0 context.Context, arg1 database.InsertWorkspaceParams) (database.WorkspaceTable, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "InsertWorkspace", arg0, arg1) - ret0, _ := ret[0].(database.Workspace) + ret0, _ := ret[0].(database.WorkspaceTable) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -5041,10 +5041,10 @@ func (mr *MockStoreMockRecorder) UpdateUserStatus(arg0, arg1 any) *gomock.Call { } // UpdateWorkspace mocks base method. -func (m *MockStore) UpdateWorkspace(arg0 context.Context, arg1 database.UpdateWorkspaceParams) (database.Workspace, error) { +func (m *MockStore) UpdateWorkspace(arg0 context.Context, arg1 database.UpdateWorkspaceParams) (database.WorkspaceTable, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdateWorkspace", arg0, arg1) - ret0, _ := ret[0].(database.Workspace) + ret0, _ := ret[0].(database.WorkspaceTable) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -5224,10 +5224,10 @@ func (mr *MockStoreMockRecorder) UpdateWorkspaceDeletedByID(arg0, arg1 any) *gom } // UpdateWorkspaceDormantDeletingAt mocks base method. -func (m *MockStore) UpdateWorkspaceDormantDeletingAt(arg0 context.Context, arg1 database.UpdateWorkspaceDormantDeletingAtParams) (database.Workspace, error) { +func (m *MockStore) UpdateWorkspaceDormantDeletingAt(arg0 context.Context, arg1 database.UpdateWorkspaceDormantDeletingAtParams) (database.WorkspaceTable, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdateWorkspaceDormantDeletingAt", arg0, arg1) - ret0, _ := ret[0].(database.Workspace) + ret0, _ := ret[0].(database.WorkspaceTable) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -5296,10 +5296,10 @@ func (mr *MockStoreMockRecorder) UpdateWorkspaceTTL(arg0, arg1 any) *gomock.Call } // UpdateWorkspacesDormantDeletingAtByTemplateID mocks base method. -func (m *MockStore) UpdateWorkspacesDormantDeletingAtByTemplateID(arg0 context.Context, arg1 database.UpdateWorkspacesDormantDeletingAtByTemplateIDParams) ([]database.Workspace, error) { +func (m *MockStore) UpdateWorkspacesDormantDeletingAtByTemplateID(arg0 context.Context, arg1 database.UpdateWorkspacesDormantDeletingAtByTemplateIDParams) ([]database.WorkspaceTable, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdateWorkspacesDormantDeletingAtByTemplateID", arg0, arg1) - ret0, _ := ret[0].([]database.Workspace) + ret0, _ := ret[0].([]database.WorkspaceTable) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/coderd/database/dbpurge/dbpurge_test.go b/coderd/database/dbpurge/dbpurge_test.go index 8353a1cbdcd1b..75c73700d1e4f 100644 --- a/coderd/database/dbpurge/dbpurge_test.go +++ b/coderd/database/dbpurge/dbpurge_test.go @@ -195,7 +195,7 @@ func TestDeleteOldWorkspaceAgentLogs(t *testing.T) { // Workspace A was built twice before the threshold, and never connected on // either attempt. - wsA := dbgen.Workspace(t, db, database.Workspace{Name: "a", OwnerID: user.ID, OrganizationID: org.ID, TemplateID: tmpl.ID}) + wsA := dbgen.Workspace(t, db, database.WorkspaceTable{Name: "a", OwnerID: user.ID, OrganizationID: org.ID, TemplateID: tmpl.ID}) wbA1 := mustCreateWorkspaceBuild(t, db, org, tv, wsA.ID, beforeThreshold, 1) wbA2 := mustCreateWorkspaceBuild(t, db, org, tv, wsA.ID, beforeThreshold, 2) agentA1 := mustCreateAgent(t, db, wbA1) @@ -204,7 +204,7 @@ func TestDeleteOldWorkspaceAgentLogs(t *testing.T) { mustCreateAgentLogs(ctx, t, db, agentA2, nil, "agent a2 logs should be retained") // Workspace B was built twice before the threshold. - wsB := dbgen.Workspace(t, db, database.Workspace{Name: "b", OwnerID: user.ID, OrganizationID: org.ID, TemplateID: tmpl.ID}) + wsB := dbgen.Workspace(t, db, database.WorkspaceTable{Name: "b", OwnerID: user.ID, OrganizationID: org.ID, TemplateID: tmpl.ID}) wbB1 := mustCreateWorkspaceBuild(t, db, org, tv, wsB.ID, beforeThreshold, 1) wbB2 := mustCreateWorkspaceBuild(t, db, org, tv, wsB.ID, beforeThreshold, 2) agentB1 := mustCreateAgent(t, db, wbB1) @@ -213,7 +213,7 @@ func TestDeleteOldWorkspaceAgentLogs(t *testing.T) { mustCreateAgentLogs(ctx, t, db, agentB2, &beforeThreshold, "agent b2 logs should be retained") // Workspace C was built once before the threshold, and once after. - wsC := dbgen.Workspace(t, db, database.Workspace{Name: "c", OwnerID: user.ID, OrganizationID: org.ID, TemplateID: tmpl.ID}) + wsC := dbgen.Workspace(t, db, database.WorkspaceTable{Name: "c", OwnerID: user.ID, OrganizationID: org.ID, TemplateID: tmpl.ID}) wbC1 := mustCreateWorkspaceBuild(t, db, org, tv, wsC.ID, beforeThreshold, 1) wbC2 := mustCreateWorkspaceBuild(t, db, org, tv, wsC.ID, afterThreshold, 2) agentC1 := mustCreateAgent(t, db, wbC1) @@ -222,7 +222,7 @@ func TestDeleteOldWorkspaceAgentLogs(t *testing.T) { mustCreateAgentLogs(ctx, t, db, agentC2, &afterThreshold, "agent c2 logs should be retained") // Workspace D was built twice after the threshold. - wsD := dbgen.Workspace(t, db, database.Workspace{Name: "d", OwnerID: user.ID, OrganizationID: org.ID, TemplateID: tmpl.ID}) + wsD := dbgen.Workspace(t, db, database.WorkspaceTable{Name: "d", OwnerID: user.ID, OrganizationID: org.ID, TemplateID: tmpl.ID}) wbD1 := mustCreateWorkspaceBuild(t, db, org, tv, wsD.ID, afterThreshold, 1) wbD2 := mustCreateWorkspaceBuild(t, db, org, tv, wsD.ID, afterThreshold, 2) agentD1 := mustCreateAgent(t, db, wbD1) @@ -231,7 +231,7 @@ func TestDeleteOldWorkspaceAgentLogs(t *testing.T) { mustCreateAgentLogs(ctx, t, db, agentD2, &afterThreshold, "agent d2 logs should be retained") // Workspace E was build once after threshold but never connected. - wsE := dbgen.Workspace(t, db, database.Workspace{Name: "e", OwnerID: user.ID, OrganizationID: org.ID, TemplateID: tmpl.ID}) + wsE := dbgen.Workspace(t, db, database.WorkspaceTable{Name: "e", OwnerID: user.ID, OrganizationID: org.ID, TemplateID: tmpl.ID}) wbE1 := mustCreateWorkspaceBuild(t, db, org, tv, wsE.ID, beforeThreshold, 1) agentE1 := mustCreateAgent(t, db, wbE1) mustCreateAgentLogs(ctx, t, db, agentE1, nil, "agent e1 logs should be retained") diff --git a/coderd/database/dbrollup/dbrollup_test.go b/coderd/database/dbrollup/dbrollup_test.go index 6c8e96b847b80..0c32ddc9a9c9a 100644 --- a/coderd/database/dbrollup/dbrollup_test.go +++ b/coderd/database/dbrollup/dbrollup_test.go @@ -64,7 +64,7 @@ func TestRollup_TwoInstancesUseLocking(t *testing.T) { user = dbgen.User(t, db, database.User{Name: "user1"}) tpl = dbgen.Template(t, db, database.Template{OrganizationID: org.ID, CreatedBy: user.ID}) ver = dbgen.TemplateVersion(t, db, database.TemplateVersion{OrganizationID: org.ID, TemplateID: uuid.NullUUID{UUID: tpl.ID, Valid: true}, CreatedBy: user.ID}) - ws = dbgen.Workspace(t, db, database.Workspace{OrganizationID: org.ID, TemplateID: tpl.ID, OwnerID: user.ID}) + ws = dbgen.Workspace(t, db, database.WorkspaceTable{OrganizationID: org.ID, TemplateID: tpl.ID, OwnerID: user.ID}) job = dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{OrganizationID: org.ID}) build = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: job.ID, TemplateVersionID: ver.ID}) res = dbgen.WorkspaceResource(t, db, database.WorkspaceResource{JobID: build.JobID}) @@ -151,7 +151,7 @@ func TestRollupTemplateUsageStats(t *testing.T) { user = dbgen.User(t, db, database.User{Name: "user1"}) tpl = dbgen.Template(t, db, database.Template{OrganizationID: org.ID, CreatedBy: user.ID}) ver = dbgen.TemplateVersion(t, db, database.TemplateVersion{OrganizationID: org.ID, TemplateID: uuid.NullUUID{UUID: tpl.ID, Valid: true}, CreatedBy: user.ID}) - ws = dbgen.Workspace(t, db, database.Workspace{OrganizationID: org.ID, TemplateID: tpl.ID, OwnerID: user.ID}) + ws = dbgen.Workspace(t, db, database.WorkspaceTable{OrganizationID: org.ID, TemplateID: tpl.ID, OwnerID: user.ID}) job = dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{OrganizationID: org.ID}) build = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: job.ID, TemplateVersionID: ver.ID}) res = dbgen.WorkspaceResource(t, db, database.WorkspaceResource{JobID: build.JobID}) diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 382cab743fb39..3a9a5a7a2d8f6 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -1701,6 +1701,39 @@ CREATE TABLE workspaces ( COMMENT ON COLUMN workspaces.favorite IS 'Favorite is true if the workspace owner has favorited the workspace.'; +CREATE VIEW workspaces_expanded AS + SELECT workspaces.id, + workspaces.created_at, + workspaces.updated_at, + workspaces.owner_id, + workspaces.organization_id, + workspaces.template_id, + workspaces.deleted, + workspaces.name, + workspaces.autostart_schedule, + workspaces.ttl, + workspaces.last_used_at, + workspaces.dormant_at, + workspaces.deleting_at, + workspaces.automatic_updates, + workspaces.favorite, + visible_users.avatar_url AS owner_avatar_url, + visible_users.username AS owner_username, + organizations.name AS organization_name, + organizations.display_name AS organization_display_name, + organizations.icon AS organization_icon, + organizations.description AS organization_description, + templates.name AS template_name, + templates.display_name AS template_display_name, + templates.icon AS template_icon, + templates.description AS template_description + FROM (((workspaces + JOIN visible_users ON ((workspaces.owner_id = visible_users.id))) + JOIN organizations ON ((workspaces.organization_id = organizations.id))) + JOIN templates ON ((workspaces.template_id = templates.id))); + +COMMENT ON VIEW workspaces_expanded IS 'Joins in the display name information such as username, avatar, and organization name.'; + ALTER TABLE ONLY licenses ALTER COLUMN id SET DEFAULT nextval('licenses_id_seq'::regclass); ALTER TABLE ONLY provisioner_job_logs ALTER COLUMN id SET DEFAULT nextval('provisioner_job_logs_id_seq'::regclass); diff --git a/coderd/database/gentest/models_test.go b/coderd/database/gentest/models_test.go index c1d2ea4999668..7cd54224cfaf2 100644 --- a/coderd/database/gentest/models_test.go +++ b/coderd/database/gentest/models_test.go @@ -65,6 +65,20 @@ func TestViewSubsetWorkspaceBuild(t *testing.T) { } } +// TestViewSubsetWorkspace ensures WorkspaceTable is a subset of Workspace +func TestViewSubsetWorkspace(t *testing.T) { + t.Parallel() + table := reflect.TypeOf(database.WorkspaceTable{}) + joined := reflect.TypeOf(database.Workspace{}) + + tableFields := allFields(table) + joinedFields := allFields(joined) + if !assert.Subset(t, fieldNames(joinedFields), fieldNames(tableFields), "table is not subset") { + t.Log("Some fields were added to the Workspace Table without updating the 'workspaces_expanded' view.") + t.Log("See migration 000262_workspace_with_names.up.sql to create the view.") + } +} + func fieldNames(fields []reflect.StructField) []string { names := make([]string, len(fields)) for i, field := range fields { diff --git a/coderd/database/migrations/000269_workspace_with_names.down.sql b/coderd/database/migrations/000269_workspace_with_names.down.sql new file mode 100644 index 0000000000000..dd9c23c2f36c5 --- /dev/null +++ b/coderd/database/migrations/000269_workspace_with_names.down.sql @@ -0,0 +1 @@ +DROP VIEW workspaces_expanded; diff --git a/coderd/database/migrations/000269_workspace_with_names.up.sql b/coderd/database/migrations/000269_workspace_with_names.up.sql new file mode 100644 index 0000000000000..8264b17d8bbc1 --- /dev/null +++ b/coderd/database/migrations/000269_workspace_with_names.up.sql @@ -0,0 +1,33 @@ +CREATE VIEW + workspaces_expanded +AS +SELECT + workspaces.*, + -- Owner + visible_users.avatar_url AS owner_avatar_url, + visible_users.username AS owner_username, + -- Organization + organizations.name AS organization_name, + organizations.display_name AS organization_display_name, + organizations.icon AS organization_icon, + organizations.description AS organization_description, + -- Template + templates.name AS template_name, + templates.display_name AS template_display_name, + templates.icon AS template_icon, + templates.description AS template_description +FROM + workspaces + INNER JOIN + visible_users + ON + workspaces.owner_id = visible_users.id + INNER JOIN + organizations + ON workspaces.organization_id = organizations.id + INNER JOIN + templates + ON workspaces.template_id = templates.id +; + +COMMENT ON VIEW workspaces_expanded IS 'Joins in the display name information such as username, avatar, and organization name.'; diff --git a/coderd/database/modelmethods.go b/coderd/database/modelmethods.go index 846de6e36aa47..a74ddf29bfcf9 100644 --- a/coderd/database/modelmethods.go +++ b/coderd/database/modelmethods.go @@ -192,12 +192,36 @@ func (gm GroupMember) RBACObject() rbac.Object { return rbac.ResourceGroupMember.WithID(gm.UserID).InOrg(gm.OrganizationID).WithOwner(gm.UserID.String()) } -func (w GetWorkspaceByAgentIDRow) RBACObject() rbac.Object { - return w.Workspace.RBACObject() +// WorkspaceTable converts a Workspace to it's reduced version. +// A more generalized solution is to use json marshaling to +// consistently keep these two structs in sync. +// That would be a lot of overhead, and a more costly unit test is +// written to make sure these match up. +func (w Workspace) WorkspaceTable() WorkspaceTable { + return WorkspaceTable{ + ID: w.ID, + CreatedAt: w.CreatedAt, + UpdatedAt: w.UpdatedAt, + OwnerID: w.OwnerID, + OrganizationID: w.OrganizationID, + TemplateID: w.TemplateID, + Deleted: w.Deleted, + Name: w.Name, + AutostartSchedule: w.AutostartSchedule, + Ttl: w.Ttl, + LastUsedAt: w.LastUsedAt, + DormantAt: w.DormantAt, + DeletingAt: w.DeletingAt, + AutomaticUpdates: w.AutomaticUpdates, + Favorite: w.Favorite, + } } func (w Workspace) RBACObject() rbac.Object { - // If a workspace is locked it cannot be accessed. + return w.WorkspaceTable().RBACObject() +} + +func (w WorkspaceTable) RBACObject() rbac.Object { if w.DormantAt.Valid { return w.DormantRBAC() } @@ -207,7 +231,7 @@ func (w Workspace) RBACObject() rbac.Object { WithOwner(w.OwnerID.String()) } -func (w Workspace) DormantRBAC() rbac.Object { +func (w WorkspaceTable) DormantRBAC() rbac.Object { return rbac.ResourceWorkspaceDormant. WithID(w.ID). InOrg(w.OrganizationID). @@ -389,21 +413,31 @@ func ConvertWorkspaceRows(rows []GetWorkspacesRow) []Workspace { workspaces := make([]Workspace, len(rows)) for i, r := range rows { workspaces[i] = Workspace{ - ID: r.ID, - CreatedAt: r.CreatedAt, - UpdatedAt: r.UpdatedAt, - OwnerID: r.OwnerID, - OrganizationID: r.OrganizationID, - TemplateID: r.TemplateID, - Deleted: r.Deleted, - Name: r.Name, - AutostartSchedule: r.AutostartSchedule, - Ttl: r.Ttl, - LastUsedAt: r.LastUsedAt, - DormantAt: r.DormantAt, - DeletingAt: r.DeletingAt, - AutomaticUpdates: r.AutomaticUpdates, - Favorite: r.Favorite, + ID: r.ID, + CreatedAt: r.CreatedAt, + UpdatedAt: r.UpdatedAt, + OwnerID: r.OwnerID, + OrganizationID: r.OrganizationID, + TemplateID: r.TemplateID, + Deleted: r.Deleted, + Name: r.Name, + AutostartSchedule: r.AutostartSchedule, + Ttl: r.Ttl, + LastUsedAt: r.LastUsedAt, + DormantAt: r.DormantAt, + DeletingAt: r.DeletingAt, + AutomaticUpdates: r.AutomaticUpdates, + Favorite: r.Favorite, + OwnerAvatarUrl: r.OwnerAvatarUrl, + OwnerUsername: r.OwnerUsername, + OrganizationName: r.OrganizationName, + OrganizationDisplayName: r.OrganizationDisplayName, + OrganizationIcon: r.OrganizationIcon, + OrganizationDescription: r.OrganizationDescription, + TemplateName: r.TemplateName, + TemplateDisplayName: r.TemplateDisplayName, + TemplateIcon: r.TemplateIcon, + TemplateDescription: r.TemplateDescription, } } diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index 1274608a7d276..9888027e01559 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -288,10 +288,18 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa &i.DeletingAt, &i.AutomaticUpdates, &i.Favorite, + &i.OwnerAvatarUrl, + &i.OwnerUsername, + &i.OrganizationName, + &i.OrganizationDisplayName, + &i.OrganizationIcon, + &i.OrganizationDescription, &i.TemplateName, + &i.TemplateDisplayName, + &i.TemplateIcon, + &i.TemplateDescription, &i.TemplateVersionID, &i.TemplateVersionName, - &i.Username, &i.LatestBuildCompletedAt, &i.LatestBuildCanceledAt, &i.LatestBuildError, diff --git a/coderd/database/modelqueries_internal_test.go b/coderd/database/modelqueries_internal_test.go index 4977120e88135..992eb269ddc14 100644 --- a/coderd/database/modelqueries_internal_test.go +++ b/coderd/database/modelqueries_internal_test.go @@ -2,8 +2,11 @@ package database import ( "testing" + "time" "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/testutil" ) func TestIsAuthorizedQuery(t *testing.T) { @@ -13,3 +16,41 @@ func TestIsAuthorizedQuery(t *testing.T) { _, err := insertAuthorizedFilter(query, "") require.ErrorContains(t, err, "does not contain authorized replace string", "ensure replace string") } + +// TestWorkspaceTableConvert verifies all workspace fields are converted +// when reducing a `Workspace` to a `WorkspaceTable`. +// This test is a guard rail to prevent developer oversight mistakes. +func TestWorkspaceTableConvert(t *testing.T) { + t.Parallel() + + staticRandoms := &testutil.Random{ + String: func() string { return "foo" }, + Bool: func() bool { return true }, + Int: func() int64 { return 500 }, + Uint: func() uint64 { return 126 }, + Float: func() float64 { return 3.14 }, + Complex: func() complex128 { return 6.24 }, + Time: func() time.Time { + return time.Date(2020, 5, 2, 5, 19, 21, 30, time.UTC) + }, + } + + // This feels a bit janky, but it works. + // If you use 'PopulateStruct' to create 2 workspaces, using the same + // "random" values for each type. Then they should be identical. + // + // So if 'workspace.WorkspaceTable()' was missing any fields in its + // conversion, the comparison would fail. + + var workspace Workspace + err := testutil.PopulateStruct(&workspace, staticRandoms) + require.NoError(t, err) + + var subset WorkspaceTable + err = testutil.PopulateStruct(&subset, staticRandoms) + require.NoError(t, err) + + require.Equal(t, workspace.WorkspaceTable(), subset, + "'workspace.WorkspaceTable()' is not missing at least 1 field when converting to 'WorkspaceTable'. "+ + "To resolve this, go to the 'func (w Workspace) WorkspaceTable()' and ensure all fields are converted.") +} diff --git a/coderd/database/models.go b/coderd/database/models.go index c44aa6011bc22..1207587d46529 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -2902,23 +2902,33 @@ type VisibleUser struct { AvatarURL string `db:"avatar_url" json:"avatar_url"` } +// Joins in the display name information such as username, avatar, and organization name. type Workspace struct { - ID uuid.UUID `db:"id" json:"id"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - UpdatedAt time.Time `db:"updated_at" json:"updated_at"` - OwnerID uuid.UUID `db:"owner_id" json:"owner_id"` - OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` - TemplateID uuid.UUID `db:"template_id" json:"template_id"` - Deleted bool `db:"deleted" json:"deleted"` - Name string `db:"name" json:"name"` - AutostartSchedule sql.NullString `db:"autostart_schedule" json:"autostart_schedule"` - Ttl sql.NullInt64 `db:"ttl" json:"ttl"` - LastUsedAt time.Time `db:"last_used_at" json:"last_used_at"` - DormantAt sql.NullTime `db:"dormant_at" json:"dormant_at"` - DeletingAt sql.NullTime `db:"deleting_at" json:"deleting_at"` - AutomaticUpdates AutomaticUpdates `db:"automatic_updates" json:"automatic_updates"` - // Favorite is true if the workspace owner has favorited the workspace. - Favorite bool `db:"favorite" json:"favorite"` + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + OwnerID uuid.UUID `db:"owner_id" json:"owner_id"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + TemplateID uuid.UUID `db:"template_id" json:"template_id"` + Deleted bool `db:"deleted" json:"deleted"` + Name string `db:"name" json:"name"` + AutostartSchedule sql.NullString `db:"autostart_schedule" json:"autostart_schedule"` + Ttl sql.NullInt64 `db:"ttl" json:"ttl"` + LastUsedAt time.Time `db:"last_used_at" json:"last_used_at"` + DormantAt sql.NullTime `db:"dormant_at" json:"dormant_at"` + DeletingAt sql.NullTime `db:"deleting_at" json:"deleting_at"` + AutomaticUpdates AutomaticUpdates `db:"automatic_updates" json:"automatic_updates"` + Favorite bool `db:"favorite" json:"favorite"` + OwnerAvatarUrl string `db:"owner_avatar_url" json:"owner_avatar_url"` + OwnerUsername string `db:"owner_username" json:"owner_username"` + OrganizationName string `db:"organization_name" json:"organization_name"` + OrganizationDisplayName string `db:"organization_display_name" json:"organization_display_name"` + OrganizationIcon string `db:"organization_icon" json:"organization_icon"` + OrganizationDescription string `db:"organization_description" json:"organization_description"` + TemplateName string `db:"template_name" json:"template_name"` + TemplateDisplayName string `db:"template_display_name" json:"template_display_name"` + TemplateIcon string `db:"template_icon" json:"template_icon"` + TemplateDescription string `db:"template_description" json:"template_description"` } type WorkspaceAgent struct { @@ -3184,3 +3194,22 @@ type WorkspaceResourceMetadatum struct { Sensitive bool `db:"sensitive" json:"sensitive"` ID int64 `db:"id" json:"id"` } + +type WorkspaceTable struct { + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + OwnerID uuid.UUID `db:"owner_id" json:"owner_id"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + TemplateID uuid.UUID `db:"template_id" json:"template_id"` + Deleted bool `db:"deleted" json:"deleted"` + Name string `db:"name" json:"name"` + AutostartSchedule sql.NullString `db:"autostart_schedule" json:"autostart_schedule"` + Ttl sql.NullInt64 `db:"ttl" json:"ttl"` + LastUsedAt time.Time `db:"last_used_at" json:"last_used_at"` + DormantAt sql.NullTime `db:"dormant_at" json:"dormant_at"` + DeletingAt sql.NullTime `db:"deleting_at" json:"deleting_at"` + AutomaticUpdates AutomaticUpdates `db:"automatic_updates" json:"automatic_updates"` + // Favorite is true if the workspace owner has favorited the workspace. + Favorite bool `db:"favorite" json:"favorite"` +} diff --git a/coderd/database/querier.go b/coderd/database/querier.go index cb126f83af32f..fcb58a7d6e305 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -319,7 +319,7 @@ type sqlcQuerier interface { GetWorkspaceBuildStatsByTemplates(ctx context.Context, since time.Time) ([]GetWorkspaceBuildStatsByTemplatesRow, 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) (GetWorkspaceByAgentIDRow, 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) GetWorkspaceByWorkspaceAppID(ctx context.Context, workspaceAppID uuid.UUID) (Workspace, error) @@ -345,7 +345,7 @@ type sqlcQuerier interface { // It has to be a CTE because the set returning function 'unnest' cannot // be used in a WHERE clause. GetWorkspaces(ctx context.Context, arg GetWorkspacesParams) ([]GetWorkspacesRow, error) - GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]Workspace, error) + GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]WorkspaceTable, error) InsertAPIKey(ctx context.Context, arg InsertAPIKeyParams) (APIKey, error) // We use the organization_id as the id // for simplicity since all users is @@ -391,7 +391,7 @@ type sqlcQuerier interface { // InsertUserGroupsByName adds a user to all provided groups, if they exist. InsertUserGroupsByName(ctx context.Context, arg InsertUserGroupsByNameParams) error InsertUserLink(ctx context.Context, arg InsertUserLinkParams) (UserLink, error) - InsertWorkspace(ctx context.Context, arg InsertWorkspaceParams) (Workspace, error) + InsertWorkspace(ctx context.Context, arg InsertWorkspaceParams) (WorkspaceTable, error) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspaceAgentParams) (WorkspaceAgent, error) InsertWorkspaceAgentLogSources(ctx context.Context, arg InsertWorkspaceAgentLogSourcesParams) ([]WorkspaceAgentLogSource, error) InsertWorkspaceAgentLogs(ctx context.Context, arg InsertWorkspaceAgentLogsParams) ([]WorkspaceAgentLog, error) @@ -469,7 +469,7 @@ type sqlcQuerier interface { UpdateUserQuietHoursSchedule(ctx context.Context, arg UpdateUserQuietHoursScheduleParams) (User, error) UpdateUserRoles(ctx context.Context, arg UpdateUserRolesParams) (User, error) UpdateUserStatus(ctx context.Context, arg UpdateUserStatusParams) (User, error) - UpdateWorkspace(ctx context.Context, arg UpdateWorkspaceParams) (Workspace, error) + UpdateWorkspace(ctx context.Context, arg UpdateWorkspaceParams) (WorkspaceTable, error) UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg UpdateWorkspaceAgentConnectionByIDParams) error UpdateWorkspaceAgentLifecycleStateByID(ctx context.Context, arg UpdateWorkspaceAgentLifecycleStateByIDParams) error UpdateWorkspaceAgentLogOverflowByID(ctx context.Context, arg UpdateWorkspaceAgentLogOverflowByIDParams) error @@ -482,13 +482,13 @@ type sqlcQuerier interface { UpdateWorkspaceBuildDeadlineByID(ctx context.Context, arg UpdateWorkspaceBuildDeadlineByIDParams) error UpdateWorkspaceBuildProvisionerStateByID(ctx context.Context, arg UpdateWorkspaceBuildProvisionerStateByIDParams) error UpdateWorkspaceDeletedByID(ctx context.Context, arg UpdateWorkspaceDeletedByIDParams) error - UpdateWorkspaceDormantDeletingAt(ctx context.Context, arg UpdateWorkspaceDormantDeletingAtParams) (Workspace, error) + UpdateWorkspaceDormantDeletingAt(ctx context.Context, arg UpdateWorkspaceDormantDeletingAtParams) (WorkspaceTable, error) UpdateWorkspaceLastUsedAt(ctx context.Context, arg UpdateWorkspaceLastUsedAtParams) error // This allows editing the properties of a workspace proxy. UpdateWorkspaceProxy(ctx context.Context, arg UpdateWorkspaceProxyParams) (WorkspaceProxy, error) UpdateWorkspaceProxyDeleted(ctx context.Context, arg UpdateWorkspaceProxyDeletedParams) error UpdateWorkspaceTTL(ctx context.Context, arg UpdateWorkspaceTTLParams) error - UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.Context, arg UpdateWorkspacesDormantDeletingAtByTemplateIDParams) ([]Workspace, error) + UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.Context, arg UpdateWorkspacesDormantDeletingAtByTemplateIDParams) ([]WorkspaceTable, error) UpsertAnnouncementBanners(ctx context.Context, value string) error UpsertAppSecurityKey(ctx context.Context, value string) error UpsertApplicationName(ctx context.Context, value string) error diff --git a/coderd/database/querier_test.go b/coderd/database/querier_test.go index dfa024464de9b..58c9626f2c9bf 100644 --- a/coderd/database/querier_test.go +++ b/coderd/database/querier_test.go @@ -416,7 +416,7 @@ func TestGetWorkspaceAgentUsageStatsAndLabels(t *testing.T) { OrganizationID: org.ID, CreatedBy: user1.ID, }) - workspace1 := dbgen.Workspace(t, db, database.Workspace{ + workspace1 := dbgen.Workspace(t, db, database.WorkspaceTable{ OwnerID: user1.ID, OrganizationID: org.ID, TemplateID: template1.ID, @@ -435,7 +435,7 @@ func TestGetWorkspaceAgentUsageStatsAndLabels(t *testing.T) { CreatedBy: user1.ID, OrganizationID: org.ID, }) - workspace2 := dbgen.Workspace(t, db, database.Workspace{ + workspace2 := dbgen.Workspace(t, db, database.WorkspaceTable{ OwnerID: user2.ID, OrganizationID: org.ID, TemplateID: template2.ID, @@ -577,7 +577,7 @@ func TestGetWorkspaceAgentUsageStatsAndLabels(t *testing.T) { OrganizationID: org.ID, CreatedBy: user.ID, }) - workspace := dbgen.Workspace(t, db, database.Workspace{ + workspace := dbgen.Workspace(t, db, database.WorkspaceTable{ OwnerID: user.ID, OrganizationID: org.ID, TemplateID: template.ID, @@ -1596,7 +1596,7 @@ func createTemplateVersion(t testing.TB, db database.Store, tpl database.Templat dbgen.ProvisionerJob(t, db, nil, j) if args.CreateWorkspace { - wrk := dbgen.Workspace(t, db, database.Workspace{ + wrk := dbgen.Workspace(t, db, database.WorkspaceTable{ CreatedAt: time.Time{}, UpdatedAt: time.Time{}, OwnerID: tpl.CreatedBy, diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 913d3a040e8b8..45cbef3f5e1d8 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -11117,7 +11117,7 @@ WHERE ` type GetWorkspaceAgentAndLatestBuildByAuthTokenRow struct { - Workspace Workspace `db:"workspace" json:"workspace"` + WorkspaceTable WorkspaceTable `db:"workspace_table" json:"workspace_table"` WorkspaceAgent WorkspaceAgent `db:"workspace_agent" json:"workspace_agent"` WorkspaceBuild WorkspaceBuild `db:"workspace_build" json:"workspace_build"` } @@ -11126,21 +11126,21 @@ func (q *sqlQuerier) GetWorkspaceAgentAndLatestBuildByAuthToken(ctx context.Cont row := q.db.QueryRowContext(ctx, getWorkspaceAgentAndLatestBuildByAuthToken, authToken) var i GetWorkspaceAgentAndLatestBuildByAuthTokenRow err := row.Scan( - &i.Workspace.ID, - &i.Workspace.CreatedAt, - &i.Workspace.UpdatedAt, - &i.Workspace.OwnerID, - &i.Workspace.OrganizationID, - &i.Workspace.TemplateID, - &i.Workspace.Deleted, - &i.Workspace.Name, - &i.Workspace.AutostartSchedule, - &i.Workspace.Ttl, - &i.Workspace.LastUsedAt, - &i.Workspace.DormantAt, - &i.Workspace.DeletingAt, - &i.Workspace.AutomaticUpdates, - &i.Workspace.Favorite, + &i.WorkspaceTable.ID, + &i.WorkspaceTable.CreatedAt, + &i.WorkspaceTable.UpdatedAt, + &i.WorkspaceTable.OwnerID, + &i.WorkspaceTable.OrganizationID, + &i.WorkspaceTable.TemplateID, + &i.WorkspaceTable.Deleted, + &i.WorkspaceTable.Name, + &i.WorkspaceTable.AutostartSchedule, + &i.WorkspaceTable.Ttl, + &i.WorkspaceTable.LastUsedAt, + &i.WorkspaceTable.DormantAt, + &i.WorkspaceTable.DeletingAt, + &i.WorkspaceTable.AutomaticUpdates, + &i.WorkspaceTable.Favorite, &i.WorkspaceAgent.ID, &i.WorkspaceAgent.CreatedAt, &i.WorkspaceAgent.UpdatedAt, @@ -14539,12 +14539,9 @@ func (q *sqlQuerier) GetDeploymentWorkspaceStats(ctx context.Context) (GetDeploy const getWorkspaceByAgentID = `-- name: GetWorkspaceByAgentID :one SELECT - workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, - templates.name as template_name + id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, owner_avatar_url, owner_username, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description FROM - workspaces -INNER JOIN - templates ON workspaces.template_id = templates.id + workspaces_expanded as workspaces WHERE workspaces.id = ( SELECT @@ -14570,40 +14567,44 @@ WHERE ) ` -type GetWorkspaceByAgentIDRow struct { - Workspace Workspace `db:"workspace" json:"workspace"` - TemplateName string `db:"template_name" json:"template_name"` -} - -func (q *sqlQuerier) GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUID) (GetWorkspaceByAgentIDRow, error) { +func (q *sqlQuerier) GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUID) (Workspace, error) { row := q.db.QueryRowContext(ctx, getWorkspaceByAgentID, agentID) - var i GetWorkspaceByAgentIDRow + var i Workspace err := row.Scan( - &i.Workspace.ID, - &i.Workspace.CreatedAt, - &i.Workspace.UpdatedAt, - &i.Workspace.OwnerID, - &i.Workspace.OrganizationID, - &i.Workspace.TemplateID, - &i.Workspace.Deleted, - &i.Workspace.Name, - &i.Workspace.AutostartSchedule, - &i.Workspace.Ttl, - &i.Workspace.LastUsedAt, - &i.Workspace.DormantAt, - &i.Workspace.DeletingAt, - &i.Workspace.AutomaticUpdates, - &i.Workspace.Favorite, + &i.ID, + &i.CreatedAt, + &i.UpdatedAt, + &i.OwnerID, + &i.OrganizationID, + &i.TemplateID, + &i.Deleted, + &i.Name, + &i.AutostartSchedule, + &i.Ttl, + &i.LastUsedAt, + &i.DormantAt, + &i.DeletingAt, + &i.AutomaticUpdates, + &i.Favorite, + &i.OwnerAvatarUrl, + &i.OwnerUsername, + &i.OrganizationName, + &i.OrganizationDisplayName, + &i.OrganizationIcon, + &i.OrganizationDescription, &i.TemplateName, + &i.TemplateDisplayName, + &i.TemplateIcon, + &i.TemplateDescription, ) return i, err } const getWorkspaceByID = `-- name: GetWorkspaceByID :one SELECT - id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite + id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, owner_avatar_url, owner_username, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description FROM - workspaces + workspaces_expanded WHERE id = $1 LIMIT @@ -14629,15 +14630,25 @@ func (q *sqlQuerier) GetWorkspaceByID(ctx context.Context, id uuid.UUID) (Worksp &i.DeletingAt, &i.AutomaticUpdates, &i.Favorite, + &i.OwnerAvatarUrl, + &i.OwnerUsername, + &i.OrganizationName, + &i.OrganizationDisplayName, + &i.OrganizationIcon, + &i.OrganizationDescription, + &i.TemplateName, + &i.TemplateDisplayName, + &i.TemplateIcon, + &i.TemplateDescription, ) return i, err } const getWorkspaceByOwnerIDAndName = `-- name: GetWorkspaceByOwnerIDAndName :one SELECT - id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite + id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, owner_avatar_url, owner_username, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description FROM - workspaces + workspaces_expanded as workspaces WHERE owner_id = $1 AND deleted = $2 @@ -14670,15 +14681,25 @@ func (q *sqlQuerier) GetWorkspaceByOwnerIDAndName(ctx context.Context, arg GetWo &i.DeletingAt, &i.AutomaticUpdates, &i.Favorite, + &i.OwnerAvatarUrl, + &i.OwnerUsername, + &i.OrganizationName, + &i.OrganizationDisplayName, + &i.OrganizationIcon, + &i.OrganizationDescription, + &i.TemplateName, + &i.TemplateDisplayName, + &i.TemplateIcon, + &i.TemplateDescription, ) return i, err } const getWorkspaceByWorkspaceAppID = `-- name: GetWorkspaceByWorkspaceAppID :one SELECT - id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite + id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, owner_avatar_url, owner_username, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description FROM - workspaces + workspaces_expanded as workspaces WHERE workspaces.id = ( SELECT @@ -14730,6 +14751,16 @@ func (q *sqlQuerier) GetWorkspaceByWorkspaceAppID(ctx context.Context, workspace &i.DeletingAt, &i.AutomaticUpdates, &i.Favorite, + &i.OwnerAvatarUrl, + &i.OwnerUsername, + &i.OrganizationName, + &i.OrganizationDisplayName, + &i.OrganizationIcon, + &i.OrganizationDescription, + &i.TemplateName, + &i.TemplateDisplayName, + &i.TemplateIcon, + &i.TemplateDescription, ) return i, err } @@ -14781,18 +14812,16 @@ SELECT ), filtered_workspaces AS ( SELECT - workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, - COALESCE(template.name, 'unknown') as template_name, + workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, workspaces.owner_avatar_url, workspaces.owner_username, workspaces.organization_name, workspaces.organization_display_name, workspaces.organization_icon, workspaces.organization_description, workspaces.template_name, workspaces.template_display_name, workspaces.template_icon, workspaces.template_description, latest_build.template_version_id, latest_build.template_version_name, - users.username as username, latest_build.completed_at as latest_build_completed_at, latest_build.canceled_at as latest_build_canceled_at, latest_build.error as latest_build_error, latest_build.transition as latest_build_transition, latest_build.job_status as latest_build_status FROM - workspaces + workspaces_expanded as workspaces JOIN users ON @@ -14931,7 +14960,7 @@ WHERE -- Filter by owner_name AND CASE WHEN $8 :: text != '' THEN - workspaces.owner_id = (SELECT id FROM users WHERE lower(username) = lower($8) AND deleted = false) + workspaces.owner_id = (SELECT id FROM users WHERE lower(owner_username) = lower($8) AND deleted = false) ELSE true END -- Filter by template_name @@ -15023,7 +15052,7 @@ WHERE -- @authorize_filter ), filtered_workspaces_order AS ( SELECT - fw.id, fw.created_at, fw.updated_at, fw.owner_id, fw.organization_id, fw.template_id, fw.deleted, fw.name, fw.autostart_schedule, fw.ttl, fw.last_used_at, fw.dormant_at, fw.deleting_at, fw.automatic_updates, fw.favorite, fw.template_name, fw.template_version_id, fw.template_version_name, fw.username, fw.latest_build_completed_at, fw.latest_build_canceled_at, fw.latest_build_error, fw.latest_build_transition, fw.latest_build_status + fw.id, fw.created_at, fw.updated_at, fw.owner_id, fw.organization_id, fw.template_id, fw.deleted, fw.name, fw.autostart_schedule, fw.ttl, fw.last_used_at, fw.dormant_at, fw.deleting_at, fw.automatic_updates, fw.favorite, fw.owner_avatar_url, fw.owner_username, fw.organization_name, fw.organization_display_name, fw.organization_icon, fw.organization_description, fw.template_name, fw.template_display_name, fw.template_icon, fw.template_description, fw.template_version_id, fw.template_version_name, fw.latest_build_completed_at, fw.latest_build_canceled_at, fw.latest_build_error, fw.latest_build_transition, fw.latest_build_status FROM filtered_workspaces fw ORDER BY @@ -15033,7 +15062,7 @@ WHERE latest_build_canceled_at IS NULL AND latest_build_error IS NULL AND latest_build_transition = 'start'::workspace_transition) DESC, - LOWER(username) ASC, + LOWER(owner_username) ASC, LOWER(name) ASC LIMIT CASE @@ -15044,7 +15073,7 @@ WHERE $20 ), filtered_workspaces_order_with_summary AS ( SELECT - fwo.id, fwo.created_at, fwo.updated_at, fwo.owner_id, fwo.organization_id, fwo.template_id, fwo.deleted, fwo.name, fwo.autostart_schedule, fwo.ttl, fwo.last_used_at, fwo.dormant_at, fwo.deleting_at, fwo.automatic_updates, fwo.favorite, fwo.template_name, fwo.template_version_id, fwo.template_version_name, fwo.username, fwo.latest_build_completed_at, fwo.latest_build_canceled_at, fwo.latest_build_error, fwo.latest_build_transition, fwo.latest_build_status + fwo.id, fwo.created_at, fwo.updated_at, fwo.owner_id, fwo.organization_id, fwo.template_id, fwo.deleted, fwo.name, fwo.autostart_schedule, fwo.ttl, fwo.last_used_at, fwo.dormant_at, fwo.deleting_at, fwo.automatic_updates, fwo.favorite, fwo.owner_avatar_url, fwo.owner_username, fwo.organization_name, fwo.organization_display_name, fwo.organization_icon, fwo.organization_description, fwo.template_name, fwo.template_display_name, fwo.template_icon, fwo.template_description, fwo.template_version_id, fwo.template_version_name, fwo.latest_build_completed_at, fwo.latest_build_canceled_at, fwo.latest_build_error, fwo.latest_build_transition, fwo.latest_build_status FROM filtered_workspaces_order fwo -- Return a technical summary row with total count of workspaces. @@ -15066,11 +15095,19 @@ WHERE '0001-01-01 00:00:00+00'::timestamptz, -- deleting_at 'never'::automatic_updates, -- automatic_updates false, -- favorite - -- Extra columns added to ` + "`" + `filtered_workspaces` + "`" + ` + '', -- owner_avatar_url + '', -- owner_username + '', -- organization_name + '', -- organization_display_name + '', -- organization_icon + '', -- organization_description '', -- template_name + '', -- template_display_name + '', -- template_icon + '', -- template_description + -- Extra columns added to ` + "`" + `filtered_workspaces` + "`" + ` '00000000-0000-0000-0000-000000000000'::uuid, -- template_version_id '', -- template_version_name - '', -- username '0001-01-01 00:00:00+00'::timestamptz, -- latest_build_completed_at, '0001-01-01 00:00:00+00'::timestamptz, -- latest_build_canceled_at, '', -- latest_build_error @@ -15085,7 +15122,7 @@ WHERE filtered_workspaces ) SELECT - fwos.id, fwos.created_at, fwos.updated_at, fwos.owner_id, fwos.organization_id, fwos.template_id, fwos.deleted, fwos.name, fwos.autostart_schedule, fwos.ttl, fwos.last_used_at, fwos.dormant_at, fwos.deleting_at, fwos.automatic_updates, fwos.favorite, fwos.template_name, fwos.template_version_id, fwos.template_version_name, fwos.username, fwos.latest_build_completed_at, fwos.latest_build_canceled_at, fwos.latest_build_error, fwos.latest_build_transition, fwos.latest_build_status, + fwos.id, fwos.created_at, fwos.updated_at, fwos.owner_id, fwos.organization_id, fwos.template_id, fwos.deleted, fwos.name, fwos.autostart_schedule, fwos.ttl, fwos.last_used_at, fwos.dormant_at, fwos.deleting_at, fwos.automatic_updates, fwos.favorite, fwos.owner_avatar_url, fwos.owner_username, fwos.organization_name, fwos.organization_display_name, fwos.organization_icon, fwos.organization_description, fwos.template_name, fwos.template_display_name, fwos.template_icon, fwos.template_description, fwos.template_version_id, fwos.template_version_name, fwos.latest_build_completed_at, fwos.latest_build_canceled_at, fwos.latest_build_error, fwos.latest_build_transition, fwos.latest_build_status, tc.count FROM filtered_workspaces_order_with_summary fwos @@ -15119,31 +15156,39 @@ type GetWorkspacesParams struct { } type GetWorkspacesRow struct { - ID uuid.UUID `db:"id" json:"id"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - UpdatedAt time.Time `db:"updated_at" json:"updated_at"` - OwnerID uuid.UUID `db:"owner_id" json:"owner_id"` - OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` - TemplateID uuid.UUID `db:"template_id" json:"template_id"` - Deleted bool `db:"deleted" json:"deleted"` - Name string `db:"name" json:"name"` - AutostartSchedule sql.NullString `db:"autostart_schedule" json:"autostart_schedule"` - Ttl sql.NullInt64 `db:"ttl" json:"ttl"` - LastUsedAt time.Time `db:"last_used_at" json:"last_used_at"` - DormantAt sql.NullTime `db:"dormant_at" json:"dormant_at"` - DeletingAt sql.NullTime `db:"deleting_at" json:"deleting_at"` - AutomaticUpdates AutomaticUpdates `db:"automatic_updates" json:"automatic_updates"` - Favorite bool `db:"favorite" json:"favorite"` - TemplateName string `db:"template_name" json:"template_name"` - TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"` - TemplateVersionName sql.NullString `db:"template_version_name" json:"template_version_name"` - Username string `db:"username" json:"username"` - LatestBuildCompletedAt sql.NullTime `db:"latest_build_completed_at" json:"latest_build_completed_at"` - LatestBuildCanceledAt sql.NullTime `db:"latest_build_canceled_at" json:"latest_build_canceled_at"` - LatestBuildError sql.NullString `db:"latest_build_error" json:"latest_build_error"` - LatestBuildTransition WorkspaceTransition `db:"latest_build_transition" json:"latest_build_transition"` - LatestBuildStatus ProvisionerJobStatus `db:"latest_build_status" json:"latest_build_status"` - Count int64 `db:"count" json:"count"` + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + OwnerID uuid.UUID `db:"owner_id" json:"owner_id"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + TemplateID uuid.UUID `db:"template_id" json:"template_id"` + Deleted bool `db:"deleted" json:"deleted"` + Name string `db:"name" json:"name"` + AutostartSchedule sql.NullString `db:"autostart_schedule" json:"autostart_schedule"` + Ttl sql.NullInt64 `db:"ttl" json:"ttl"` + LastUsedAt time.Time `db:"last_used_at" json:"last_used_at"` + DormantAt sql.NullTime `db:"dormant_at" json:"dormant_at"` + DeletingAt sql.NullTime `db:"deleting_at" json:"deleting_at"` + AutomaticUpdates AutomaticUpdates `db:"automatic_updates" json:"automatic_updates"` + Favorite bool `db:"favorite" json:"favorite"` + OwnerAvatarUrl string `db:"owner_avatar_url" json:"owner_avatar_url"` + OwnerUsername string `db:"owner_username" json:"owner_username"` + OrganizationName string `db:"organization_name" json:"organization_name"` + OrganizationDisplayName string `db:"organization_display_name" json:"organization_display_name"` + OrganizationIcon string `db:"organization_icon" json:"organization_icon"` + OrganizationDescription string `db:"organization_description" json:"organization_description"` + TemplateName string `db:"template_name" json:"template_name"` + TemplateDisplayName string `db:"template_display_name" json:"template_display_name"` + TemplateIcon string `db:"template_icon" json:"template_icon"` + TemplateDescription string `db:"template_description" json:"template_description"` + TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"` + TemplateVersionName sql.NullString `db:"template_version_name" json:"template_version_name"` + LatestBuildCompletedAt sql.NullTime `db:"latest_build_completed_at" json:"latest_build_completed_at"` + LatestBuildCanceledAt sql.NullTime `db:"latest_build_canceled_at" json:"latest_build_canceled_at"` + LatestBuildError sql.NullString `db:"latest_build_error" json:"latest_build_error"` + LatestBuildTransition WorkspaceTransition `db:"latest_build_transition" json:"latest_build_transition"` + LatestBuildStatus ProvisionerJobStatus `db:"latest_build_status" json:"latest_build_status"` + Count int64 `db:"count" json:"count"` } // build_params is used to filter by build parameters if present. @@ -15197,10 +15242,18 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams) &i.DeletingAt, &i.AutomaticUpdates, &i.Favorite, + &i.OwnerAvatarUrl, + &i.OwnerUsername, + &i.OrganizationName, + &i.OrganizationDisplayName, + &i.OrganizationIcon, + &i.OrganizationDescription, &i.TemplateName, + &i.TemplateDisplayName, + &i.TemplateIcon, + &i.TemplateDescription, &i.TemplateVersionID, &i.TemplateVersionName, - &i.Username, &i.LatestBuildCompletedAt, &i.LatestBuildCanceledAt, &i.LatestBuildError, @@ -15295,15 +15348,15 @@ WHERE ) AND workspaces.deleted = 'false' ` -func (q *sqlQuerier) GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]Workspace, error) { +func (q *sqlQuerier) GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]WorkspaceTable, error) { rows, err := q.db.QueryContext(ctx, getWorkspacesEligibleForTransition, now) if err != nil { return nil, err } defer rows.Close() - var items []Workspace + var items []WorkspaceTable for rows.Next() { - var i Workspace + var i WorkspaceTable if err := rows.Scan( &i.ID, &i.CreatedAt, @@ -15367,7 +15420,7 @@ type InsertWorkspaceParams struct { AutomaticUpdates AutomaticUpdates `db:"automatic_updates" json:"automatic_updates"` } -func (q *sqlQuerier) InsertWorkspace(ctx context.Context, arg InsertWorkspaceParams) (Workspace, error) { +func (q *sqlQuerier) InsertWorkspace(ctx context.Context, arg InsertWorkspaceParams) (WorkspaceTable, error) { row := q.db.QueryRowContext(ctx, insertWorkspace, arg.ID, arg.CreatedAt, @@ -15381,7 +15434,7 @@ func (q *sqlQuerier) InsertWorkspace(ctx context.Context, arg InsertWorkspacePar arg.LastUsedAt, arg.AutomaticUpdates, ) - var i Workspace + var i WorkspaceTable err := row.Scan( &i.ID, &i.CreatedAt, @@ -15445,9 +15498,9 @@ type UpdateWorkspaceParams struct { Name string `db:"name" json:"name"` } -func (q *sqlQuerier) UpdateWorkspace(ctx context.Context, arg UpdateWorkspaceParams) (Workspace, error) { +func (q *sqlQuerier) UpdateWorkspace(ctx context.Context, arg UpdateWorkspaceParams) (WorkspaceTable, error) { row := q.db.QueryRowContext(ctx, updateWorkspace, arg.ID, arg.Name) - var i Workspace + var i WorkspaceTable err := row.Scan( &i.ID, &i.CreatedAt, @@ -15558,9 +15611,9 @@ type UpdateWorkspaceDormantDeletingAtParams struct { DormantAt sql.NullTime `db:"dormant_at" json:"dormant_at"` } -func (q *sqlQuerier) UpdateWorkspaceDormantDeletingAt(ctx context.Context, arg UpdateWorkspaceDormantDeletingAtParams) (Workspace, error) { +func (q *sqlQuerier) UpdateWorkspaceDormantDeletingAt(ctx context.Context, arg UpdateWorkspaceDormantDeletingAtParams) (WorkspaceTable, error) { row := q.db.QueryRowContext(ctx, updateWorkspaceDormantDeletingAt, arg.ID, arg.DormantAt) - var i Workspace + var i WorkspaceTable err := row.Scan( &i.ID, &i.CreatedAt, @@ -15641,15 +15694,15 @@ type UpdateWorkspacesDormantDeletingAtByTemplateIDParams struct { TemplateID uuid.UUID `db:"template_id" json:"template_id"` } -func (q *sqlQuerier) UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.Context, arg UpdateWorkspacesDormantDeletingAtByTemplateIDParams) ([]Workspace, error) { +func (q *sqlQuerier) UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.Context, arg UpdateWorkspacesDormantDeletingAtByTemplateIDParams) ([]WorkspaceTable, error) { rows, err := q.db.QueryContext(ctx, updateWorkspacesDormantDeletingAtByTemplateID, arg.TimeTilDormantAutodeleteMs, arg.DormantAt, arg.TemplateID) if err != nil { return nil, err } defer rows.Close() - var items []Workspace + var items []WorkspaceTable for rows.Next() { - var i Workspace + var i WorkspaceTable if err := rows.Scan( &i.ID, &i.CreatedAt, diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index 42d7a5247f1b5..08e795d7a2402 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -2,7 +2,7 @@ SELECT * FROM - workspaces + workspaces_expanded WHERE id = $1 LIMIT @@ -12,7 +12,7 @@ LIMIT SELECT * FROM - workspaces + workspaces_expanded as workspaces WHERE workspaces.id = ( SELECT @@ -46,12 +46,9 @@ WHERE -- name: GetWorkspaceByAgentID :one SELECT - sqlc.embed(workspaces), - templates.name as template_name + * FROM - workspaces -INNER JOIN - templates ON workspaces.template_id = templates.id + workspaces_expanded as workspaces WHERE workspaces.id = ( SELECT @@ -89,17 +86,15 @@ SELECT filtered_workspaces AS ( SELECT workspaces.*, - COALESCE(template.name, 'unknown') as template_name, latest_build.template_version_id, latest_build.template_version_name, - users.username as username, latest_build.completed_at as latest_build_completed_at, latest_build.canceled_at as latest_build_canceled_at, latest_build.error as latest_build_error, latest_build.transition as latest_build_transition, latest_build.job_status as latest_build_status FROM - workspaces + workspaces_expanded as workspaces JOIN users ON @@ -238,7 +233,7 @@ WHERE -- Filter by owner_name AND CASE WHEN @owner_username :: text != '' THEN - workspaces.owner_id = (SELECT id FROM users WHERE lower(username) = lower(@owner_username) AND deleted = false) + workspaces.owner_id = (SELECT id FROM users WHERE lower(owner_username) = lower(@owner_username) AND deleted = false) ELSE true END -- Filter by template_name @@ -340,7 +335,7 @@ WHERE latest_build_canceled_at IS NULL AND latest_build_error IS NULL AND latest_build_transition = 'start'::workspace_transition) DESC, - LOWER(username) ASC, + LOWER(owner_username) ASC, LOWER(name) ASC LIMIT CASE @@ -373,11 +368,19 @@ WHERE '0001-01-01 00:00:00+00'::timestamptz, -- deleting_at 'never'::automatic_updates, -- automatic_updates false, -- favorite - -- Extra columns added to `filtered_workspaces` + '', -- owner_avatar_url + '', -- owner_username + '', -- organization_name + '', -- organization_display_name + '', -- organization_icon + '', -- organization_description '', -- template_name + '', -- template_display_name + '', -- template_icon + '', -- template_description + -- Extra columns added to `filtered_workspaces` '00000000-0000-0000-0000-000000000000'::uuid, -- template_version_id '', -- template_version_name - '', -- username '0001-01-01 00:00:00+00'::timestamptz, -- latest_build_completed_at, '0001-01-01 00:00:00+00'::timestamptz, -- latest_build_canceled_at, '', -- latest_build_error @@ -403,7 +406,7 @@ CROSS JOIN SELECT * FROM - workspaces + workspaces_expanded as workspaces WHERE owner_id = @owner_id AND deleted = @deleted diff --git a/coderd/database/sqlc.yaml b/coderd/database/sqlc.yaml index 7ef860e0b36ce..a70e45a522989 100644 --- a/coderd/database/sqlc.yaml +++ b/coderd/database/sqlc.yaml @@ -83,6 +83,8 @@ sql: template_with_name: Template workspace_build: WorkspaceBuildTable workspace_build_with_user: WorkspaceBuild + workspace: WorkspaceTable + workspaces_expanded: Workspace template_version: TemplateVersionTable template_version_with_user: TemplateVersion api_key: APIKey diff --git a/coderd/httpmw/workspaceagent.go b/coderd/httpmw/workspaceagent.go index 99889c0bae5fc..b27af7d0093a0 100644 --- a/coderd/httpmw/workspaceagent.go +++ b/coderd/httpmw/workspaceagent.go @@ -110,7 +110,7 @@ func ExtractWorkspaceAgentAndLatestBuild(opts ExtractWorkspaceAgentAndLatestBuil } //nolint:gocritic // System needs to be able to get owner roles. - roles, err := opts.DB.GetAuthorizationUserRoles(dbauthz.AsSystemRestricted(ctx), row.Workspace.OwnerID) + roles, err := opts.DB.GetAuthorizationUserRoles(dbauthz.AsSystemRestricted(ctx), row.WorkspaceTable.OwnerID) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error checking workspace agent authorization.", @@ -129,13 +129,13 @@ func ExtractWorkspaceAgentAndLatestBuild(opts ExtractWorkspaceAgentAndLatestBuil } subject := rbac.Subject{ - ID: row.Workspace.OwnerID.String(), + ID: row.WorkspaceTable.OwnerID.String(), Roles: rbac.RoleIdentifiers(roleNames), Groups: roles.Groups, Scope: rbac.WorkspaceAgentScope(rbac.WorkspaceAgentScopeParams{ - WorkspaceID: row.Workspace.ID, - OwnerID: row.Workspace.OwnerID, - TemplateID: row.Workspace.TemplateID, + WorkspaceID: row.WorkspaceTable.ID, + OwnerID: row.WorkspaceTable.OwnerID, + TemplateID: row.WorkspaceTable.TemplateID, VersionID: row.WorkspaceBuild.TemplateVersionID, }), }.WithCachedASTValue() diff --git a/coderd/httpmw/workspaceagent_test.go b/coderd/httpmw/workspaceagent_test.go index 0bc4b04a3589d..8d79b6ddbdbb9 100644 --- a/coderd/httpmw/workspaceagent_test.go +++ b/coderd/httpmw/workspaceagent_test.go @@ -97,7 +97,7 @@ func TestWorkspaceAgent(t *testing.T) { }) } -func setup(t testing.TB, db database.Store, authToken uuid.UUID, mw func(http.Handler) http.Handler) (*http.Request, http.Handler, database.Workspace, database.TemplateVersion) { +func setup(t testing.TB, db database.Store, authToken uuid.UUID, mw func(http.Handler) http.Handler) (*http.Request, http.Handler, database.WorkspaceTable, database.TemplateVersion) { t.Helper() org := dbgen.Organization(t, db, database.Organization{}) user := dbgen.User(t, db, database.User{ @@ -116,7 +116,7 @@ func setup(t testing.TB, db database.Store, authToken uuid.UUID, mw func(http.Ha ActiveVersionID: templateVersion.ID, CreatedBy: user.ID, }) - workspace := dbgen.Workspace(t, db, database.Workspace{ + workspace := dbgen.Workspace(t, db, database.WorkspaceTable{ OwnerID: user.ID, OrganizationID: org.ID, TemplateID: template.ID, diff --git a/coderd/httpmw/workspaceagentparam_test.go b/coderd/httpmw/workspaceagentparam_test.go index b27c80ba94710..51e55b81e20a7 100644 --- a/coderd/httpmw/workspaceagentparam_test.go +++ b/coderd/httpmw/workspaceagentparam_test.go @@ -31,7 +31,7 @@ func TestWorkspaceAgentParam(t *testing.T) { UserID: user.ID, }) tpl = dbgen.Template(t, db, database.Template{}) - workspace = dbgen.Workspace(t, db, database.Workspace{ + workspace = dbgen.Workspace(t, db, database.WorkspaceTable{ OwnerID: user.ID, TemplateID: tpl.ID, }) diff --git a/coderd/httpmw/workspacebuildparam_test.go b/coderd/httpmw/workspacebuildparam_test.go index fb2d2f044f77f..e4bd4d10dafb2 100644 --- a/coderd/httpmw/workspacebuildparam_test.go +++ b/coderd/httpmw/workspacebuildparam_test.go @@ -20,13 +20,13 @@ import ( func TestWorkspaceBuildParam(t *testing.T) { t.Parallel() - setupAuthentication := func(db database.Store) (*http.Request, database.Workspace) { + setupAuthentication := func(db database.Store) (*http.Request, database.WorkspaceTable) { var ( user = dbgen.User(t, db, database.User{}) _, token = dbgen.APIKey(t, db, database.APIKey{ UserID: user.ID, }) - workspace = dbgen.Workspace(t, db, database.Workspace{ + workspace = dbgen.Workspace(t, db, database.WorkspaceTable{ OwnerID: user.ID, }) ) diff --git a/coderd/httpmw/workspaceparam_test.go b/coderd/httpmw/workspaceparam_test.go index 54daf661c39c8..81f47d135f6ee 100644 --- a/coderd/httpmw/workspaceparam_test.go +++ b/coderd/httpmw/workspaceparam_test.go @@ -355,7 +355,7 @@ func setupWorkspaceWithAgents(t testing.TB, cfg setupConfig) (database.Store, *h _, token = dbgen.APIKey(t, db, database.APIKey{ UserID: user.ID, }) - workspace = dbgen.Workspace(t, db, database.Workspace{ + workspace = dbgen.Workspace(t, db, database.WorkspaceTable{ OwnerID: user.ID, Name: cfg.WorkspaceName, }) diff --git a/coderd/metricscache/metricscache_test.go b/coderd/metricscache/metricscache_test.go index 891a66738c803..f854d21e777b0 100644 --- a/coderd/metricscache/metricscache_test.go +++ b/coderd/metricscache/metricscache_test.go @@ -49,7 +49,7 @@ func TestCache_TemplateWorkspaceOwners(t *testing.T) { "TemplateWorkspaceOwners never populated 0 owners", ) - dbgen.Workspace(t, db, database.Workspace{ + dbgen.Workspace(t, db, database.WorkspaceTable{ TemplateID: template.ID, OwnerID: user1.ID, }) @@ -61,7 +61,7 @@ func TestCache_TemplateWorkspaceOwners(t *testing.T) { "TemplateWorkspaceOwners never populated 1 owner", ) - workspace2 := dbgen.Workspace(t, db, database.Workspace{ + workspace2 := dbgen.Workspace(t, db, database.WorkspaceTable{ TemplateID: template.ID, OwnerID: user2.ID, }) @@ -74,7 +74,7 @@ func TestCache_TemplateWorkspaceOwners(t *testing.T) { ) // 3rd workspace should not be counted since we have the same owner as workspace2. - dbgen.Workspace(t, db, database.Workspace{ + dbgen.Workspace(t, db, database.WorkspaceTable{ TemplateID: template.ID, OwnerID: user1.ID, }) diff --git a/coderd/notifications/reports/generator_internal_test.go b/coderd/notifications/reports/generator_internal_test.go index a6a7f66f725cf..fcf22d80d80f9 100644 --- a/coderd/notifications/reports/generator_internal_test.go +++ b/coderd/notifications/reports/generator_internal_test.go @@ -90,7 +90,7 @@ func TestReportFailedWorkspaceBuilds(t *testing.T) { t1v1 := dbgen.TemplateVersion(t, db, database.TemplateVersion{Name: "template-1-version-1", CreatedBy: templateAdmin1.ID, OrganizationID: org.ID, TemplateID: uuid.NullUUID{UUID: t1.ID, Valid: true}, JobID: uuid.New()}) // Workspaces - w1 := dbgen.Workspace(t, db, database.Workspace{TemplateID: t1.ID, OwnerID: user1.ID, OrganizationID: org.ID}) + w1 := dbgen.Workspace(t, db, database.WorkspaceTable{TemplateID: t1.ID, OwnerID: user1.ID, OrganizationID: org.ID}) w1wb1pj := dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{OrganizationID: org.ID, Error: jobError, ErrorCode: jobErrorCode, CompletedAt: sql.NullTime{Time: now.Add(-6 * dayDuration), Valid: true}}) _ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{WorkspaceID: w1.ID, BuildNumber: 1, TemplateVersionID: t1v1.ID, JobID: w1wb1pj.ID, CreatedAt: now.Add(-2 * dayDuration), Transition: database.WorkspaceTransitionStart, Reason: database.BuildReasonInitiator}) @@ -164,10 +164,10 @@ func TestReportFailedWorkspaceBuilds(t *testing.T) { t2v2 := dbgen.TemplateVersion(t, db, database.TemplateVersion{Name: "template-2-version-2", CreatedBy: templateAdmin1.ID, OrganizationID: org.ID, TemplateID: uuid.NullUUID{UUID: t2.ID, Valid: true}, JobID: uuid.New()}) // Workspaces - w1 := dbgen.Workspace(t, db, database.Workspace{TemplateID: t1.ID, OwnerID: user1.ID, OrganizationID: org.ID}) - w2 := dbgen.Workspace(t, db, database.Workspace{TemplateID: t2.ID, OwnerID: user2.ID, OrganizationID: org.ID}) - w3 := dbgen.Workspace(t, db, database.Workspace{TemplateID: t1.ID, OwnerID: user1.ID, OrganizationID: org.ID}) - w4 := dbgen.Workspace(t, db, database.Workspace{TemplateID: t2.ID, OwnerID: user2.ID, OrganizationID: org.ID}) + w1 := dbgen.Workspace(t, db, database.WorkspaceTable{TemplateID: t1.ID, OwnerID: user1.ID, OrganizationID: org.ID}) + w2 := dbgen.Workspace(t, db, database.WorkspaceTable{TemplateID: t2.ID, OwnerID: user2.ID, OrganizationID: org.ID}) + w3 := dbgen.Workspace(t, db, database.WorkspaceTable{TemplateID: t1.ID, OwnerID: user1.ID, OrganizationID: org.ID}) + w4 := dbgen.Workspace(t, db, database.WorkspaceTable{TemplateID: t2.ID, OwnerID: user2.ID, OrganizationID: org.ID}) // When: first run notifEnq.Clear() @@ -330,7 +330,7 @@ func TestReportFailedWorkspaceBuilds(t *testing.T) { t1v2 := dbgen.TemplateVersion(t, db, database.TemplateVersion{Name: "template-1-version-2", CreatedBy: templateAdmin1.ID, OrganizationID: org.ID, TemplateID: uuid.NullUUID{UUID: t1.ID, Valid: true}, JobID: uuid.New()}) // Workspaces - w1 := dbgen.Workspace(t, db, database.Workspace{TemplateID: t1.ID, OwnerID: user1.ID, OrganizationID: org.ID}) + w1 := dbgen.Workspace(t, db, database.WorkspaceTable{TemplateID: t1.ID, OwnerID: user1.ID, OrganizationID: org.ID}) // When: first run notifEnq.Clear() @@ -427,7 +427,7 @@ func TestReportFailedWorkspaceBuilds(t *testing.T) { t1v1 := dbgen.TemplateVersion(t, db, database.TemplateVersion{Name: "template-1-version-1", CreatedBy: templateAdmin1.ID, OrganizationID: org.ID, TemplateID: uuid.NullUUID{UUID: t1.ID, Valid: true}, JobID: uuid.New()}) // Workspaces - w1 := dbgen.Workspace(t, db, database.Workspace{TemplateID: t1.ID, OwnerID: user1.ID, OrganizationID: org.ID}) + w1 := dbgen.Workspace(t, db, database.WorkspaceTable{TemplateID: t1.ID, OwnerID: user1.ID, OrganizationID: org.ID}) // When: first run notifEnq.Clear() diff --git a/coderd/prometheusmetrics/insights/metricscollector_test.go b/coderd/prometheusmetrics/insights/metricscollector_test.go index 9179c9896235d..9382fa5013525 100644 --- a/coderd/prometheusmetrics/insights/metricscollector_test.go +++ b/coderd/prometheusmetrics/insights/metricscollector_test.go @@ -63,8 +63,8 @@ func TestCollectInsights(t *testing.T) { param1 = dbgen.TemplateVersionParameter(t, db, database.TemplateVersionParameter{TemplateVersionID: ver.ID, Name: "first_parameter"}) param2 = dbgen.TemplateVersionParameter(t, db, database.TemplateVersionParameter{TemplateVersionID: ver.ID, Name: "second_parameter", Type: "bool"}) param3 = dbgen.TemplateVersionParameter(t, db, database.TemplateVersionParameter{TemplateVersionID: ver.ID, Name: "third_parameter", Type: "number"}) - workspace1 = dbgen.Workspace(t, db, database.Workspace{OrganizationID: orgID, TemplateID: tpl.ID, OwnerID: user.ID}) - workspace2 = dbgen.Workspace(t, db, database.Workspace{OrganizationID: orgID, TemplateID: tpl.ID, OwnerID: user.ID}) + workspace1 = dbgen.Workspace(t, db, database.WorkspaceTable{OrganizationID: orgID, TemplateID: tpl.ID, OwnerID: user.ID}) + workspace2 = dbgen.Workspace(t, db, database.WorkspaceTable{OrganizationID: orgID, TemplateID: tpl.ID, OwnerID: user.ID}) job1 = dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{OrganizationID: orgID}) job2 = dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{OrganizationID: orgID}) build1 = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{TemplateVersionID: ver.ID, WorkspaceID: workspace1.ID, JobID: job1.ID}) diff --git a/coderd/prometheusmetrics/prometheusmetrics.go b/coderd/prometheusmetrics/prometheusmetrics.go index a6aec430a6b08..ebd50ff0f42ce 100644 --- a/coderd/prometheusmetrics/prometheusmetrics.go +++ b/coderd/prometheusmetrics/prometheusmetrics.go @@ -166,7 +166,7 @@ func Workspaces(ctx context.Context, logger slog.Logger, registerer prometheus.R workspaceLatestBuildStatuses.Reset() for _, w := range ws { - workspaceLatestBuildStatuses.WithLabelValues(string(w.LatestBuildStatus), w.TemplateName, w.TemplateVersionName.String, w.Username, string(w.LatestBuildTransition)).Add(1) + workspaceLatestBuildStatuses.WithLabelValues(string(w.LatestBuildStatus), w.TemplateName, w.TemplateVersionName.String, w.OwnerUsername, string(w.LatestBuildTransition)).Add(1) } } diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index e81aa02f0c264..0a4198423e403 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -1406,7 +1406,7 @@ func (s *server) CompleteJob(ctx context.Context, completed *proto.CompletedJob) TemplateScheduleStore: *s.TemplateScheduleStore.Load(), UserQuietHoursScheduleStore: *s.UserQuietHoursScheduleStore.Load(), Now: now, - Workspace: workspace, + Workspace: workspace.WorkspaceTable(), // Allowed to be the empty string. WorkspaceAutostart: workspace.AutostartSchedule.String, }) diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index c6c5613f97a35..baa53b92d74e2 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -267,7 +267,7 @@ func TestAcquireJob(t *testing.T) { Required: true, Sensitive: false, }) - workspace := dbgen.Workspace(t, db, database.Workspace{ + workspace := dbgen.Workspace(t, db, database.WorkspaceTable{ TemplateID: template.ID, OwnerID: user.ID, OrganizationID: pd.OrganizationID, @@ -1263,7 +1263,7 @@ func TestCompleteJob(t *testing.T) { Valid: true, } } - workspace := dbgen.Workspace(t, db, database.Workspace{ + workspaceTable := dbgen.Workspace(t, db, database.WorkspaceTable{ TemplateID: template.ID, Ttl: workspaceTTL, OwnerID: user.ID, @@ -1278,7 +1278,7 @@ func TestCompleteJob(t *testing.T) { JobID: uuid.New(), }) build := dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{ - WorkspaceID: workspace.ID, + WorkspaceID: workspaceTable.ID, TemplateVersionID: version.ID, Transition: c.transition, Reason: database.BuildReasonInitiator, @@ -1331,7 +1331,7 @@ func TestCompleteJob(t *testing.T) { <-publishedWorkspace <-publishedLogs - workspace, err = db.GetWorkspaceByID(ctx, workspace.ID) + workspace, err := db.GetWorkspaceByID(ctx, workspaceTable.ID) require.NoError(t, err) require.Equal(t, c.transition == database.WorkspaceTransitionDelete, workspace.Deleted) @@ -1622,7 +1622,7 @@ func TestNotifications(t *testing.T) { template, err := db.GetTemplateByID(ctx, template.ID) require.NoError(t, err) file := dbgen.File(t, db, database.File{CreatedBy: user.ID}) - workspace := dbgen.Workspace(t, db, database.Workspace{ + workspaceTable := dbgen.Workspace(t, db, database.WorkspaceTable{ TemplateID: template.ID, OwnerID: user.ID, OrganizationID: pd.OrganizationID, @@ -1636,7 +1636,7 @@ func TestNotifications(t *testing.T) { JobID: uuid.New(), }) build := dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{ - WorkspaceID: workspace.ID, + WorkspaceID: workspaceTable.ID, TemplateVersionID: version.ID, InitiatorID: initiator.ID, Transition: database.WorkspaceTransitionDelete, @@ -1674,7 +1674,7 @@ func TestNotifications(t *testing.T) { }) require.NoError(t, err) - workspace, err = db.GetWorkspaceByID(ctx, workspace.ID) + workspace, err := db.GetWorkspaceByID(ctx, workspaceTable.ID) require.NoError(t, err) require.True(t, workspace.Deleted) @@ -1740,7 +1740,7 @@ func TestNotifications(t *testing.T) { OrganizationID: pd.OrganizationID, }) file := dbgen.File(t, db, database.File{CreatedBy: user.ID}) - workspace := dbgen.Workspace(t, db, database.Workspace{ + workspace := dbgen.Workspace(t, db, database.WorkspaceTable{ TemplateID: template.ID, OwnerID: user.ID, OrganizationID: pd.OrganizationID, @@ -1822,7 +1822,7 @@ func TestNotifications(t *testing.T) { template := dbgen.Template(t, db, database.Template{ Name: "template", DisplayName: "William's Template", Provisioner: database.ProvisionerTypeEcho, OrganizationID: pd.OrganizationID, }) - workspace := dbgen.Workspace(t, db, database.Workspace{ + workspace := dbgen.Workspace(t, db, database.WorkspaceTable{ TemplateID: template.ID, OwnerID: user.ID, OrganizationID: pd.OrganizationID, }) version := dbgen.TemplateVersion(t, db, database.TemplateVersion{ diff --git a/coderd/schedule/autostop.go b/coderd/schedule/autostop.go index 1651b3f64aa9c..88529d26b3b78 100644 --- a/coderd/schedule/autostop.go +++ b/coderd/schedule/autostop.go @@ -51,7 +51,7 @@ type CalculateAutostopParams struct { WorkspaceAutostart string Now time.Time - Workspace database.Workspace + Workspace database.WorkspaceTable } type AutostopTime struct { diff --git a/coderd/schedule/autostop_test.go b/coderd/schedule/autostop_test.go index 0c4c072438537..e28ce3579cd4c 100644 --- a/coderd/schedule/autostop_test.go +++ b/coderd/schedule/autostop_test.go @@ -561,7 +561,7 @@ func TestCalculateAutoStop(t *testing.T) { Valid: true, } } - workspace := dbgen.Workspace(t, db, database.Workspace{ + workspace := dbgen.Workspace(t, db, database.WorkspaceTable{ TemplateID: template.ID, OrganizationID: org.ID, OwnerID: user.ID, diff --git a/coderd/telemetry/telemetry_test.go b/coderd/telemetry/telemetry_test.go index fd9f4752bff51..908bcd657ee4f 100644 --- a/coderd/telemetry/telemetry_test.go +++ b/coderd/telemetry/telemetry_test.go @@ -50,7 +50,7 @@ func TestTelemetry(t *testing.T) { }) _ = dbgen.TemplateVersion(t, db, database.TemplateVersion{}) user := dbgen.User(t, db, database.User{}) - _ = dbgen.Workspace(t, db, database.Workspace{}) + _ = dbgen.Workspace(t, db, database.WorkspaceTable{}) _ = dbgen.WorkspaceApp(t, db, database.WorkspaceApp{ SharingLevel: database.AppSharingLevelOwner, Health: database.WorkspaceAppHealthDisabled, diff --git a/coderd/unhanger/detector_test.go b/coderd/unhanger/detector_test.go index 28bb2575b9ee7..b1bf374881d37 100644 --- a/coderd/unhanger/detector_test.go +++ b/coderd/unhanger/detector_test.go @@ -133,7 +133,7 @@ func TestDetectorHungWorkspaceBuild(t *testing.T) { }, CreatedBy: user.ID, }) - workspace = dbgen.Workspace(t, db, database.Workspace{ + workspace = dbgen.Workspace(t, db, database.WorkspaceTable{ OwnerID: user.ID, OrganizationID: org.ID, TemplateID: template.ID, @@ -255,7 +255,7 @@ func TestDetectorHungWorkspaceBuildNoOverrideState(t *testing.T) { }, CreatedBy: user.ID, }) - workspace = dbgen.Workspace(t, db, database.Workspace{ + workspace = dbgen.Workspace(t, db, database.WorkspaceTable{ OwnerID: user.ID, OrganizationID: org.ID, TemplateID: template.ID, @@ -377,7 +377,7 @@ func TestDetectorHungWorkspaceBuildNoOverrideStateIfNoExistingBuild(t *testing.T }, CreatedBy: user.ID, }) - workspace = dbgen.Workspace(t, db, database.Workspace{ + workspace = dbgen.Workspace(t, db, database.WorkspaceTable{ OwnerID: user.ID, OrganizationID: org.ID, TemplateID: template.ID, diff --git a/coderd/users.go b/coderd/users.go index 78b88ba892426..73ac5c4212a42 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -1461,15 +1461,6 @@ func userOrganizationIDs(ctx context.Context, api *API, user database.User) ([]u return member.OrganizationIDs, nil } -func userByID(id uuid.UUID, users []database.User) (database.User, bool) { - for _, user := range users { - if id == user.ID { - return user, true - } - } - return database.User{}, false -} - func convertAPIKey(k database.APIKey) codersdk.APIKey { return codersdk.APIKey{ ID: k.ID, diff --git a/coderd/users_test.go b/coderd/users_test.go index ff7c63e63bebc..bd66bdb1d9a09 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -1936,7 +1936,7 @@ func TestUserAutofillParameters(t *testing.T) { }, ).Do() - dbfake.WorkspaceBuild(t, db, database.Workspace{ + dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OwnerID: u2.ID, TemplateID: version.Template.ID, OrganizationID: u1.OrganizationID, @@ -1969,7 +1969,7 @@ func TestUserAutofillParameters(t *testing.T) { require.Equal(t, "foo", params[0].Value) // Verify that latest parameter value is returned. - dbfake.WorkspaceBuild(t, db, database.Workspace{ + dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: u1.OrganizationID, OwnerID: u2.ID, TemplateID: version.Template.ID, diff --git a/coderd/util/slice/slice.go b/coderd/util/slice/slice.go index 78d5e7fe61928..7317a801a089f 100644 --- a/coderd/util/slice/slice.go +++ b/coderd/util/slice/slice.go @@ -55,6 +55,17 @@ func Contains[T comparable](haystack []T, needle T) bool { }) } +// Find returns the first element that satisfies the condition. +func Find[T any](haystack []T, cond func(T) bool) (T, bool) { + for _, hay := range haystack { + if cond(hay) { + return hay, true + } + } + var empty T + return empty, false +} + // Overlap returns if the 2 sets have any overlap (element(s) in common) func Overlap[T comparable](a []T, b []T) bool { return OverlapCompare(a, b, func(a, b T) bool { diff --git a/coderd/workspaceagentportshare_test.go b/coderd/workspaceagentportshare_test.go index f767aed933562..201ba68f3d6c5 100644 --- a/coderd/workspaceagentportshare_test.go +++ b/coderd/workspaceagentportshare_test.go @@ -24,7 +24,7 @@ func TestPostWorkspaceAgentPortShare(t *testing.T) { client, user := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) tmpDir := t.TempDir() - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: owner.OrganizationID, OwnerID: user.ID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -141,7 +141,7 @@ func TestGetWorkspaceAgentPortShares(t *testing.T) { client, user := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) tmpDir := t.TempDir() - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: owner.OrganizationID, OwnerID: user.ID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -177,7 +177,7 @@ func TestDeleteWorkspaceAgentPortShare(t *testing.T) { client, user := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) tmpDir := t.TempDir() - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: owner.OrganizationID, OwnerID: user.ID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 4b1af869cc007..6ea631f2e7d0c 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -366,7 +366,7 @@ func (api *API) workspaceAgentLogs(rw http.ResponseWriter, r *http.Request) { return } - row, err := api.Database.GetWorkspaceByAgentID(ctx, workspaceAgent.ID) + workspace, err := api.Database.GetWorkspaceByAgentID(ctx, workspaceAgent.ID) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace by agent id.", @@ -374,7 +374,6 @@ func (api *API) workspaceAgentLogs(rw http.ResponseWriter, r *http.Request) { }) return } - workspace := row.Workspace api.WebsocketWaitMutex.Lock() api.WebsocketWaitGroup.Add(1) diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 906333456ae70..8c0801a914d61 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -57,7 +57,7 @@ func TestWorkspaceAgent(t *testing.T) { tmpDir := t.TempDir() anotherClient, anotherUser := coderdtest.CreateAnotherUser(t, client, user.OrganizationID) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: anotherUser.ID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -79,7 +79,7 @@ func TestWorkspaceAgent(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) tmpDir := t.TempDir() - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -107,7 +107,7 @@ func TestWorkspaceAgent(t *testing.T) { wantTroubleshootingURL := "https://example.com/troubleshoot" - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -148,7 +148,7 @@ func TestWorkspaceAgent(t *testing.T) { PortForwardingHelper: true, SshHelper: true, } - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -181,7 +181,7 @@ func TestWorkspaceAgent(t *testing.T) { apps.WebTerminal = false // Creating another workspace is easier - r = dbfake.WorkspaceBuild(t, db, database.Workspace{ + r = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -205,7 +205,7 @@ func TestWorkspaceAgentLogs(t *testing.T) { ctx := testutil.Context(t, testutil.WaitMedium) client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -247,7 +247,7 @@ func TestWorkspaceAgentLogs(t *testing.T) { ctx := testutil.Context(t, testutil.WaitMedium) client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -289,7 +289,7 @@ func TestWorkspaceAgentLogs(t *testing.T) { ctx := testutil.Context(t, testutil.WaitMedium) client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -332,7 +332,7 @@ func TestWorkspaceAgentConnectRPC(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -420,7 +420,7 @@ func TestWorkspaceAgentConnectRPC(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) // Given: a workspace exists - seed := database.Workspace{OrganizationID: user.OrganizationID, OwnerID: user.UserID} + seed := database.WorkspaceTable{OrganizationID: user.OrganizationID, OwnerID: user.UserID} wsb := dbfake.WorkspaceBuild(t, db, seed).WithAgent().Do() // When: the workspace is marked as soft-deleted // nolint:gocritic // this is a test @@ -446,7 +446,7 @@ func TestWorkspaceAgentTailnet(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -486,7 +486,7 @@ func TestWorkspaceAgentClientCoordinate_BadVersion(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -571,7 +571,7 @@ func TestWorkspaceAgentClientCoordinate_ResumeToken(t *testing.T) { // Create a workspace with an agent. No need to connect it since clients can // still connect to the coordinator while the agent isn't connected. - r := dbfake.WorkspaceBuild(t, api.Database, database.Workspace{ + r := dbfake.WorkspaceBuild(t, api.Database, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -679,7 +679,7 @@ func TestWorkspaceAgentTailnetDirectDisabled(t *testing.T) { DeploymentValues: dv, }) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -750,7 +750,7 @@ func TestWorkspaceAgentListeningPorts(t *testing.T) { require.NoError(t, err) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -1006,7 +1006,7 @@ func TestWorkspaceAgentAppHealth(t *testing.T) { }, }, } - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -1088,7 +1088,7 @@ func TestWorkspaceAgentPostLogSource(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) ctx := testutil.Context(t, testutil.WaitShort) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -1130,7 +1130,7 @@ func TestWorkspaceAgent_LifecycleState(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -1203,7 +1203,7 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -1368,7 +1368,7 @@ func TestWorkspaceAgent_Metadata_DisplayOrder(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -1475,7 +1475,7 @@ func TestWorkspaceAgent_Metadata_CatchMemoryLeak(t *testing.T) { Logger: &logger, }) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -1607,7 +1607,7 @@ func TestWorkspaceAgent_Startup(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -1653,7 +1653,7 @@ func TestWorkspaceAgent_Startup(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -1698,7 +1698,7 @@ func TestWorkspaceAgent_UpdatedDERP(t *testing.T) { api.DERPMapper.Store(&derpMapFn) // Start workspace a workspace agent. - r := dbfake.WorkspaceBuild(t, api.Database, database.Workspace{ + r := dbfake.WorkspaceBuild(t, api.Database, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -1815,7 +1815,7 @@ func TestWorkspaceAgentExternalAuthListen(t *testing.T) { tmpDir := t.TempDir() client, user := coderdtest.CreateAnotherUser(t, ownerClient, first.OrganizationID) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: first.OrganizationID, OwnerID: user.ID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { diff --git a/coderd/workspaceagentsrpc_test.go b/coderd/workspaceagentsrpc_test.go index ca8f334d4e766..817aa11c4c292 100644 --- a/coderd/workspaceagentsrpc_test.go +++ b/coderd/workspaceagentsrpc_test.go @@ -22,7 +22,7 @@ func TestWorkspaceAgentReportStats(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -72,7 +72,7 @@ func TestAgentAPI_LargeManifest(t *testing.T) { for i := range longScript { longScript[i] = 'q' } - r := dbfake.WorkspaceBuild(t, store, database.Workspace{ + r := dbfake.WorkspaceBuild(t, store, database.WorkspaceTable{ OrganizationID: adminUser.OrganizationID, OwnerID: adminUser.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index 92e21b78e0756..3515bc4a944b5 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -46,7 +46,7 @@ func (api *API) workspaceBuild(rw http.ResponseWriter, r *http.Request) { workspaceBuild := httpmw.WorkspaceBuildParam(r) workspace := httpmw.WorkspaceParam(r) - data, err := api.workspaceBuildsData(ctx, []database.Workspace{workspace}, []database.WorkspaceBuild{workspaceBuild}) + data, err := api.workspaceBuildsData(ctx, []database.WorkspaceBuild{workspaceBuild}) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error getting workspace build data.", @@ -72,21 +72,11 @@ func (api *API) workspaceBuild(rw http.ResponseWriter, r *http.Request) { }) return } - owner, ok := userByID(workspace.OwnerID, data.users) - if !ok { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error converting workspace build.", - Detail: "owner not found for workspace", - }) - return - } apiBuild, err := api.convertWorkspaceBuild( workspaceBuild, workspace, data.jobs[0], - owner.Username, - owner.AvatarURL, data.resources, data.metadata, data.agents, @@ -189,7 +179,7 @@ func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) { return } - data, err := api.workspaceBuildsData(ctx, []database.Workspace{workspace}, workspaceBuilds) + data, err := api.workspaceBuildsData(ctx, workspaceBuilds) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error getting workspace build data.", @@ -202,7 +192,6 @@ func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) { workspaceBuilds, []database.Workspace{workspace}, data.jobs, - data.users, data.resources, data.metadata, data.agents, @@ -279,7 +268,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.WorkspaceBuild{workspaceBuild}) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error getting workspace build data.", @@ -287,21 +276,11 @@ func (api *API) workspaceBuildByBuildNumber(rw http.ResponseWriter, r *http.Requ }) return } - owner, ok := userByID(workspace.OwnerID, data.users) - if !ok { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error converting workspace build.", - Detail: "owner not found for workspace", - }) - return - } apiBuild, err := api.convertWorkspaceBuild( workspaceBuild, workspace, data.jobs[0], - owner.Username, - owner.AvatarURL, data.resources, data.metadata, data.agents, @@ -410,26 +389,6 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { api.Logger.Error(ctx, "failed to post provisioner job to pubsub", slog.Error(err)) } - users, err := api.Database.GetUsersByIDs(ctx, []uuid.UUID{ - workspace.OwnerID, - workspaceBuild.InitiatorID, - }) - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error getting user.", - Detail: err.Error(), - }) - return - } - owner, exists := userByID(workspace.OwnerID, users) - if !exists { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error converting workspace build.", - Detail: "owner not found for workspace", - }) - return - } - apiBuild, err := api.convertWorkspaceBuild( *workspaceBuild, workspace, @@ -437,8 +396,6 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { ProvisionerJob: *provisionerJob, QueuePosition: 0, }, - owner.Username, - owner.AvatarURL, []database.WorkspaceResource{}, []database.WorkspaceResourceMetadatum{}, []database.WorkspaceAgent{}, @@ -674,7 +631,6 @@ func (api *API) workspaceBuildTimings(rw http.ResponseWriter, r *http.Request) { } type workspaceBuildsData struct { - users []database.User jobs []database.GetProvisionerJobsByIDsWithQueuePositionRow templateVersions []database.TemplateVersion resources []database.WorkspaceResource @@ -685,16 +641,7 @@ type workspaceBuildsData struct { logSources []database.WorkspaceAgentLogSource } -func (api *API) workspaceBuildsData(ctx context.Context, workspaces []database.Workspace, workspaceBuilds []database.WorkspaceBuild) (workspaceBuildsData, error) { - userIDs := make([]uuid.UUID, 0, len(workspaceBuilds)) - for _, workspace := range workspaces { - userIDs = append(userIDs, workspace.OwnerID) - } - users, err := api.Database.GetUsersByIDs(ctx, userIDs) - if err != nil { - return workspaceBuildsData{}, xerrors.Errorf("get users: %w", err) - } - +func (api *API) workspaceBuildsData(ctx context.Context, workspaceBuilds []database.WorkspaceBuild) (workspaceBuildsData, error) { jobIDs := make([]uuid.UUID, 0, len(workspaceBuilds)) for _, build := range workspaceBuilds { jobIDs = append(jobIDs, build.JobID) @@ -723,7 +670,6 @@ func (api *API) workspaceBuildsData(ctx context.Context, workspaces []database.W if len(resources) == 0 { return workspaceBuildsData{ - users: users, jobs: jobs, templateVersions: templateVersions, }, nil @@ -748,7 +694,6 @@ func (api *API) workspaceBuildsData(ctx context.Context, workspaces []database.W if len(resources) == 0 { return workspaceBuildsData{ - users: users, jobs: jobs, templateVersions: templateVersions, resources: resources, @@ -789,7 +734,6 @@ func (api *API) workspaceBuildsData(ctx context.Context, workspaces []database.W } return workspaceBuildsData{ - users: users, jobs: jobs, templateVersions: templateVersions, resources: resources, @@ -805,7 +749,6 @@ func (api *API) convertWorkspaceBuilds( workspaceBuilds []database.WorkspaceBuild, workspaces []database.Workspace, jobs []database.GetProvisionerJobsByIDsWithQueuePositionRow, - users []database.User, workspaceResources []database.WorkspaceResource, resourceMetadata []database.WorkspaceResourceMetadatum, resourceAgents []database.WorkspaceAgent, @@ -842,17 +785,11 @@ func (api *API) convertWorkspaceBuilds( if !exists { return nil, xerrors.New("template version not found") } - owner, exists := userByID(workspace.OwnerID, users) - if !exists { - return nil, xerrors.Errorf("owner not found for workspace: %q", workspace.Name) - } apiBuild, err := api.convertWorkspaceBuild( build, workspace, job, - owner.Username, - owner.AvatarURL, workspaceResources, resourceMetadata, resourceAgents, @@ -875,7 +812,6 @@ func (api *API) convertWorkspaceBuild( build database.WorkspaceBuild, workspace database.Workspace, job database.GetProvisionerJobsByIDsWithQueuePositionRow, - username, avatarURL string, workspaceResources []database.WorkspaceResource, resourceMetadata []database.WorkspaceResourceMetadatum, resourceAgents []database.WorkspaceAgent, @@ -931,7 +867,7 @@ func (api *API) convertWorkspaceBuild( scripts := scriptsByAgentID[agent.ID] logSources := logSourcesByAgentID[agent.ID] apiAgent, err := db2sdk.WorkspaceAgent( - api.DERPMap(), *api.TailnetCoordinator.Load(), agent, db2sdk.Apps(apps, agent, username, workspace), convertScripts(scripts), convertLogSources(logSources), api.AgentInactiveDisconnectTimeout, + api.DERPMap(), *api.TailnetCoordinator.Load(), agent, db2sdk.Apps(apps, agent, workspace.OwnerUsername, workspace), convertScripts(scripts), convertLogSources(logSources), api.AgentInactiveDisconnectTimeout, api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), ) if err != nil { @@ -958,8 +894,8 @@ func (api *API) convertWorkspaceBuild( CreatedAt: build.CreatedAt, UpdatedAt: build.UpdatedAt, WorkspaceOwnerID: workspace.OwnerID, - WorkspaceOwnerName: username, - WorkspaceOwnerAvatarURL: avatarURL, + WorkspaceOwnerName: workspace.OwnerUsername, + WorkspaceOwnerAvatarURL: workspace.OwnerAvatarUrl, WorkspaceID: build.WorkspaceID, WorkspaceName: workspace.Name, TemplateVersionID: build.TemplateVersionID, diff --git a/coderd/workspacebuilds_test.go b/coderd/workspacebuilds_test.go index ec20556a7b2ed..e8eeca0f49d66 100644 --- a/coderd/workspacebuilds_test.go +++ b/coderd/workspacebuilds_test.go @@ -1217,7 +1217,7 @@ func TestWorkspaceBuildTimings(t *testing.T) { // Tests will run in parallel. To avoid conflicts and race conditions on the // build number, each test will have its own workspace and build. makeBuild := func() database.WorkspaceBuild { - ws := dbgen.Workspace(t, db, database.Workspace{ + ws := dbgen.Workspace(t, db, database.WorkspaceTable{ OwnerID: owner.UserID, OrganizationID: owner.OrganizationID, TemplateID: template.ID, diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 2407130ea38e4..394a728472b0d 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -99,22 +99,12 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) { httpapi.Forbidden(rw) return } - owner, ok := userByID(workspace.OwnerID, data.users) - if !ok { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching workspace resources.", - Detail: "unable to find workspace owner's username", - }) - return - } w, err := convertWorkspace( apiKey.UserID, workspace, data.builds[0], data.templates[0], - owner.Username, - owner.AvatarURL, api.Options.AllowWorkspaceRenames, ) if err != nil { @@ -307,21 +297,12 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request) httpapi.ResourceNotFound(rw) return } - owner, ok := userByID(workspace.OwnerID, data.users) - if !ok { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching workspace resources.", - Detail: "unable to find workspace owner's username", - }) - return - } + w, err := convertWorkspace( apiKey.UserID, workspace, data.builds[0], data.templates[0], - owner.Username, - owner.AvatarURL, api.Options.AllowWorkspaceRenames, ) if err != nil { @@ -364,7 +345,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req } ) - aReq, commitAudit := audit.InitRequest[database.Workspace](rw, &audit.RequestParams{ + aReq, commitAudit := audit.InitRequest[database.WorkspaceTable](rw, &audit.RequestParams{ Audit: *auditor, Log: api.Logger, Request: r, @@ -413,7 +394,7 @@ func (api *API) postUserWorkspaces(rw http.ResponseWriter, r *http.Request) { user = httpmw.UserParam(r) ) - aReq, commitAudit := audit.InitRequest[database.Workspace](rw, &audit.RequestParams{ + aReq, commitAudit := audit.InitRequest[database.WorkspaceTable](rw, &audit.RequestParams{ Audit: *auditor, Log: api.Logger, Request: r, @@ -446,7 +427,7 @@ type workspaceOwner struct { func createWorkspace( ctx context.Context, - auditReq *audit.Request[database.Workspace], + auditReq *audit.Request[database.WorkspaceTable], initiatorID uuid.UUID, api *API, owner workspaceOwner, @@ -627,7 +608,7 @@ func createWorkspace( err = api.Database.InTx(func(db database.Store) error { now := dbtime.Now() // Workspaces are created without any versions. - workspace, err = db.InsertWorkspace(ctx, database.InsertWorkspaceParams{ + minimumWorkspace, err := db.InsertWorkspace(ctx, database.InsertWorkspaceParams{ ID: uuid.New(), CreatedAt: now, UpdatedAt: now, @@ -646,6 +627,14 @@ func createWorkspace( return xerrors.Errorf("insert workspace: %w", err) } + // We have to refetch the workspace for the joined in fields. + // TODO: We can use WorkspaceTable for the builder to not require + // this extra fetch. + workspace, err = db.GetWorkspaceByID(ctx, minimumWorkspace.ID) + if err != nil { + return xerrors.Errorf("get workspace by ID: %w", err) + } + builder := wsbuilder.New(workspace, database.WorkspaceTransitionStart). Reason(database.BuildReasonInitiator). Initiator(initiatorID). @@ -685,7 +674,7 @@ func createWorkspace( // Client probably doesn't care about this error, so just log it. api.Logger.Error(ctx, "failed to post provisioner job to pubsub", slog.Error(err)) } - auditReq.New = workspace + auditReq.New = workspace.WorkspaceTable() api.Telemetry.Report(&telemetry.Snapshot{ Workspaces: []telemetry.Workspace{telemetry.ConvertWorkspace(workspace)}, @@ -699,8 +688,6 @@ func createWorkspace( ProvisionerJob: *provisionerJob, QueuePosition: 0, }, - owner.Username, - owner.AvatarURL, []database.WorkspaceResource{}, []database.WorkspaceResourceMetadatum{}, []database.WorkspaceAgent{}, @@ -722,8 +709,6 @@ func createWorkspace( workspace, apiBuild, template, - owner.Username, - owner.AvatarURL, api.Options.AllowWorkspaceRenames, ) if err != nil { @@ -750,7 +735,7 @@ func (api *API) patchWorkspace(rw http.ResponseWriter, r *http.Request) { ctx = r.Context() workspace = httpmw.WorkspaceParam(r) auditor = api.Auditor.Load() - aReq, commitAudit = audit.InitRequest[database.Workspace](rw, &audit.RequestParams{ + aReq, commitAudit = audit.InitRequest[database.WorkspaceTable](rw, &audit.RequestParams{ Audit: *auditor, Log: api.Logger, Request: r, @@ -759,7 +744,7 @@ func (api *API) patchWorkspace(rw http.ResponseWriter, r *http.Request) { }) ) defer commitAudit() - aReq.Old = workspace + aReq.Old = workspace.WorkspaceTable() var req codersdk.UpdateWorkspaceRequest if !httpapi.Read(ctx, rw, r, &req) { @@ -767,7 +752,7 @@ func (api *API) patchWorkspace(rw http.ResponseWriter, r *http.Request) { } if req.Name == "" || req.Name == workspace.Name { - aReq.New = workspace + aReq.New = workspace.WorkspaceTable() // Nothing changed, optionally this could be an error. rw.WriteHeader(http.StatusNoContent) return @@ -822,8 +807,8 @@ func (api *API) patchWorkspace(rw http.ResponseWriter, r *http.Request) { } api.publishWorkspaceUpdate(ctx, workspace.ID) - aReq.New = newWorkspace + rw.WriteHeader(http.StatusNoContent) } @@ -841,7 +826,7 @@ func (api *API) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) { ctx = r.Context() workspace = httpmw.WorkspaceParam(r) auditor = api.Auditor.Load() - aReq, commitAudit = audit.InitRequest[database.Workspace](rw, &audit.RequestParams{ + aReq, commitAudit = audit.InitRequest[database.WorkspaceTable](rw, &audit.RequestParams{ Audit: *auditor, Log: api.Logger, Request: r, @@ -850,7 +835,7 @@ func (api *API) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) { }) ) defer commitAudit() - aReq.Old = workspace + aReq.Old = workspace.WorkspaceTable() var req codersdk.UpdateWorkspaceAutostartRequest if !httpapi.Read(ctx, rw, r, &req) { @@ -897,7 +882,7 @@ func (api *API) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) { newWorkspace := workspace newWorkspace.AutostartSchedule = dbSched - aReq.New = newWorkspace + aReq.New = newWorkspace.WorkspaceTable() rw.WriteHeader(http.StatusNoContent) } @@ -916,7 +901,7 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) { ctx = r.Context() workspace = httpmw.WorkspaceParam(r) auditor = api.Auditor.Load() - aReq, commitAudit = audit.InitRequest[database.Workspace](rw, &audit.RequestParams{ + aReq, commitAudit = audit.InitRequest[database.WorkspaceTable](rw, &audit.RequestParams{ Audit: *auditor, Log: api.Logger, Request: r, @@ -925,7 +910,7 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) { }) ) defer commitAudit() - aReq.Old = workspace + aReq.Old = workspace.WorkspaceTable() var req codersdk.UpdateWorkspaceTTLRequest if !httpapi.Read(ctx, rw, r, &req) { @@ -977,7 +962,7 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) { newWorkspace := workspace newWorkspace.Ttl = dbTTL - aReq.New = newWorkspace + aReq.New = newWorkspace.WorkspaceTable() rw.WriteHeader(http.StatusNoContent) } @@ -995,19 +980,18 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) { func (api *API) putWorkspaceDormant(rw http.ResponseWriter, r *http.Request) { var ( ctx = r.Context() - workspace = httpmw.WorkspaceParam(r) + oldWorkspace = httpmw.WorkspaceParam(r) apiKey = httpmw.APIKey(r) - oldWorkspace = workspace auditor = api.Auditor.Load() - aReq, commitAudit = audit.InitRequest[database.Workspace](rw, &audit.RequestParams{ + aReq, commitAudit = audit.InitRequest[database.WorkspaceTable](rw, &audit.RequestParams{ Audit: *auditor, Log: api.Logger, Request: r, Action: database.AuditActionWrite, - OrganizationID: workspace.OrganizationID, + OrganizationID: oldWorkspace.OrganizationID, }) ) - aReq.Old = oldWorkspace + aReq.Old = oldWorkspace.WorkspaceTable() defer commitAudit() var req codersdk.UpdateWorkspaceDormancy @@ -1016,7 +1000,7 @@ func (api *API) putWorkspaceDormant(rw http.ResponseWriter, r *http.Request) { } // If the workspace is already in the desired state do nothing! - if workspace.DormantAt.Valid == req.Dormant { + if oldWorkspace.DormantAt.Valid == req.Dormant { rw.WriteHeader(http.StatusNotModified) return } @@ -1028,8 +1012,8 @@ func (api *API) putWorkspaceDormant(rw http.ResponseWriter, r *http.Request) { dormantAt.Time = dbtime.Now() } - workspace, err := api.Database.UpdateWorkspaceDormantDeletingAt(ctx, database.UpdateWorkspaceDormantDeletingAtParams{ - ID: workspace.ID, + newWorkspace, err := api.Database.UpdateWorkspaceDormantDeletingAt(ctx, database.UpdateWorkspaceDormantDeletingAtParams{ + ID: oldWorkspace.ID, DormantAt: dormantAt, }) if err != nil { @@ -1041,26 +1025,26 @@ func (api *API) putWorkspaceDormant(rw http.ResponseWriter, r *http.Request) { } // We don't need to notify the owner if they are the one making the request. - if req.Dormant && apiKey.UserID != workspace.OwnerID { + if req.Dormant && apiKey.UserID != newWorkspace.OwnerID { initiator, initiatorErr := api.Database.GetUserByID(ctx, apiKey.UserID) if initiatorErr != nil { api.Logger.Warn( ctx, "failed to fetch the user that marked the workspace as dormant", slog.Error(err), - slog.F("workspace_id", workspace.ID), + slog.F("workspace_id", newWorkspace.ID), slog.F("user_id", apiKey.UserID), ) } - tmpl, tmplErr := api.Database.GetTemplateByID(ctx, workspace.TemplateID) + tmpl, tmplErr := api.Database.GetTemplateByID(ctx, newWorkspace.TemplateID) if tmplErr != nil { api.Logger.Warn( ctx, "failed to fetch the template of the workspace marked as dormant", slog.Error(err), - slog.F("workspace_id", workspace.ID), - slog.F("template_id", workspace.TemplateID), + slog.F("workspace_id", newWorkspace.ID), + slog.F("template_id", newWorkspace.TemplateID), ) } @@ -1068,18 +1052,18 @@ func (api *API) putWorkspaceDormant(rw http.ResponseWriter, r *http.Request) { dormantTime := dbtime.Now().Add(time.Duration(tmpl.TimeTilDormant)) _, err = api.NotificationsEnqueuer.Enqueue( ctx, - workspace.OwnerID, + newWorkspace.OwnerID, notifications.TemplateWorkspaceDormant, map[string]string{ - "name": workspace.Name, + "name": newWorkspace.Name, "reason": "a " + initiator.Username + " request", "timeTilDormant": humanize.Time(dormantTime), }, "api", - workspace.ID, - workspace.OwnerID, - workspace.TemplateID, - workspace.OrganizationID, + newWorkspace.ID, + newWorkspace.OwnerID, + newWorkspace.TemplateID, + newWorkspace.OrganizationID, ) if err != nil { api.Logger.Warn(ctx, "failed to notify of workspace marked as dormant", slog.Error(err)) @@ -1087,37 +1071,40 @@ func (api *API) putWorkspaceDormant(rw http.ResponseWriter, r *http.Request) { } } - data, err := api.workspaceData(ctx, []database.Workspace{workspace}) + // We have to refetch the workspace to get the joined in fields. + workspace, err := api.Database.GetWorkspaceByID(ctx, newWorkspace.ID) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching workspace resources.", + Message: "Internal error fetching workspace.", Detail: err.Error(), }) return } - owner, ok := userByID(workspace.OwnerID, data.users) - if !ok { + + data, err := api.workspaceData(ctx, []database.Workspace{workspace}) + if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace resources.", - Detail: "unable to find workspace owner's username", + Detail: err.Error(), }) return } + // TODO: This is a strange error since it occurs after the mutatation. + // An example of why we should join in fields to prevent this forbidden error + // from being sent, when the action did succeed. if len(data.templates) == 0 { httpapi.Forbidden(rw) return } - aReq.New = workspace + aReq.New = newWorkspace w, err := convertWorkspace( apiKey.UserID, workspace, data.builds[0], data.templates[0], - owner.Username, - owner.AvatarURL, api.Options.AllowWorkspaceRenames, ) if err != nil { @@ -1371,7 +1358,7 @@ func (api *API) putFavoriteWorkspace(rw http.ResponseWriter, r *http.Request) { return } - aReq, commitAudit := audit.InitRequest[database.Workspace](rw, &audit.RequestParams{ + aReq, commitAudit := audit.InitRequest[database.WorkspaceTable](rw, &audit.RequestParams{ Audit: *auditor, Log: api.Logger, Request: r, @@ -1379,7 +1366,7 @@ func (api *API) putFavoriteWorkspace(rw http.ResponseWriter, r *http.Request) { OrganizationID: workspace.OrganizationID, }) defer commitAudit() - aReq.Old = workspace + aReq.Old = workspace.WorkspaceTable() err := api.Database.FavoriteWorkspace(ctx, workspace.ID) if err != nil { @@ -1390,7 +1377,7 @@ func (api *API) putFavoriteWorkspace(rw http.ResponseWriter, r *http.Request) { return } - aReq.New = workspace + aReq.New = workspace.WorkspaceTable() aReq.New.Favorite = true rw.WriteHeader(http.StatusNoContent) @@ -1418,7 +1405,7 @@ func (api *API) deleteFavoriteWorkspace(rw http.ResponseWriter, r *http.Request) return } - aReq, commitAudit := audit.InitRequest[database.Workspace](rw, &audit.RequestParams{ + aReq, commitAudit := audit.InitRequest[database.WorkspaceTable](rw, &audit.RequestParams{ Audit: *auditor, Log: api.Logger, Request: r, @@ -1427,7 +1414,7 @@ func (api *API) deleteFavoriteWorkspace(rw http.ResponseWriter, r *http.Request) }) defer commitAudit() - aReq.Old = workspace + aReq.Old = workspace.WorkspaceTable() err := api.Database.UnfavoriteWorkspace(ctx, workspace.ID) if err != nil { @@ -1437,7 +1424,7 @@ func (api *API) deleteFavoriteWorkspace(rw http.ResponseWriter, r *http.Request) }) return } - aReq.New = workspace + aReq.New = workspace.WorkspaceTable() aReq.New.Favorite = false rw.WriteHeader(http.StatusNoContent) @@ -1457,7 +1444,7 @@ func (api *API) putWorkspaceAutoupdates(rw http.ResponseWriter, r *http.Request) ctx = r.Context() workspace = httpmw.WorkspaceParam(r) auditor = api.Auditor.Load() - aReq, commitAudit = audit.InitRequest[database.Workspace](rw, &audit.RequestParams{ + aReq, commitAudit = audit.InitRequest[database.WorkspaceTable](rw, &audit.RequestParams{ Audit: *auditor, Log: api.Logger, Request: r, @@ -1466,7 +1453,7 @@ func (api *API) putWorkspaceAutoupdates(rw http.ResponseWriter, r *http.Request) }) ) defer commitAudit() - aReq.Old = workspace + aReq.Old = workspace.WorkspaceTable() var req codersdk.UpdateWorkspaceAutomaticUpdatesRequest if !httpapi.Read(ctx, rw, r, &req) { @@ -1499,7 +1486,7 @@ func (api *API) putWorkspaceAutoupdates(rw http.ResponseWriter, r *http.Request) newWorkspace := workspace newWorkspace.AutomaticUpdates = database.AutomaticUpdates(req.AutomaticUpdates) - aReq.New = newWorkspace + aReq.New = newWorkspace.WorkspaceTable() rw.WriteHeader(http.StatusNoContent) } @@ -1658,25 +1645,11 @@ func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) { return } - owner, ok := userByID(workspace.OwnerID, data.users) - if !ok { - _ = sendEvent(ctx, codersdk.ServerSentEvent{ - Type: codersdk.ServerSentEventTypeError, - Data: codersdk.Response{ - Message: "Internal error fetching workspace resources.", - Detail: "unable to find workspace owner's username", - }, - }) - return - } - w, err := convertWorkspace( apiKey.UserID, workspace, data.builds[0], data.templates[0], - owner.Username, - owner.AvatarURL, api.Options.AllowWorkspaceRenames, ) if err != nil { @@ -1778,7 +1751,6 @@ func (api *API) workspaceTimings(rw http.ResponseWriter, r *http.Request) { type workspaceData struct { templates []database.Template builds []codersdk.WorkspaceBuild - users []database.User allowRenames bool } @@ -1808,7 +1780,7 @@ func (api *API) workspaceData(ctx context.Context, workspaces []database.Workspa return workspaceData{}, xerrors.Errorf("get workspace builds: %w", err) } - data, err := api.workspaceBuildsData(ctx, workspaces, builds) + data, err := api.workspaceBuildsData(ctx, builds) if err != nil { return workspaceData{}, xerrors.Errorf("get workspace builds data: %w", err) } @@ -1817,7 +1789,6 @@ func (api *API) workspaceData(ctx context.Context, workspaces []database.Workspa builds, workspaces, data.jobs, - data.users, data.resources, data.metadata, data.agents, @@ -1833,7 +1804,6 @@ func (api *API) workspaceData(ctx context.Context, workspaces []database.Workspa return workspaceData{ templates: templates, builds: apiBuilds, - users: data.users, allowRenames: api.Options.AllowWorkspaceRenames, }, nil } @@ -1847,10 +1817,6 @@ func convertWorkspaces(requesterID uuid.UUID, workspaces []database.Workspace, d for _, template := range data.templates { templateByID[template.ID] = template } - userByID := map[uuid.UUID]database.User{} - for _, user := range data.users { - userByID[user.ID] = user - } apiWorkspaces := make([]codersdk.Workspace, 0, len(workspaces)) for _, workspace := range workspaces { @@ -1867,18 +1833,12 @@ func convertWorkspaces(requesterID uuid.UUID, workspaces []database.Workspace, d if !exists { continue } - owner, exists := userByID[workspace.OwnerID] - if !exists { - continue - } w, err := convertWorkspace( requesterID, workspace, build, template, - owner.Username, - owner.AvatarURL, data.allowRenames, ) if err != nil { @@ -1895,8 +1855,6 @@ func convertWorkspace( workspace database.Workspace, workspaceBuild codersdk.WorkspaceBuild, template database.Template, - username string, - avatarURL string, allowRenames bool, ) (codersdk.Workspace, error) { if requesterID == uuid.Nil { @@ -1941,15 +1899,15 @@ func convertWorkspace( CreatedAt: workspace.CreatedAt, UpdatedAt: workspace.UpdatedAt, OwnerID: workspace.OwnerID, - OwnerName: username, - OwnerAvatarURL: avatarURL, + OwnerName: workspace.OwnerUsername, + OwnerAvatarURL: workspace.OwnerAvatarUrl, OrganizationID: workspace.OrganizationID, - OrganizationName: template.OrganizationName, + OrganizationName: workspace.OrganizationName, TemplateID: workspace.TemplateID, LatestBuild: workspaceBuild, - TemplateName: template.Name, - TemplateIcon: template.Icon, - TemplateDisplayName: template.DisplayName, + TemplateName: workspace.TemplateName, + TemplateIcon: workspace.TemplateIcon, + TemplateDisplayName: workspace.TemplateDisplayName, TemplateAllowUserCancelWorkspaceJobs: template.AllowUserCancelWorkspaceJobs, TemplateActiveVersionID: template.ActiveVersionID, TemplateRequireActiveVersion: template.RequireActiveVersion, diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index dc83289340059..c24afc67de8ba 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -392,7 +392,7 @@ func TestResolveAutostart(t *testing.T) { defer cancel() client, member := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) - resp := dbfake.WorkspaceBuild(t, db, database.Workspace{ + resp := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OwnerID: member.ID, OrganizationID: owner.OrganizationID, AutomaticUpdates: database.AutomaticUpdatesAlways, @@ -456,22 +456,22 @@ func TestWorkspacesSortOrder(t *testing.T) { }) // c-workspace should be running - wsbC := dbfake.WorkspaceBuild(t, db, database.Workspace{Name: "c-workspace", OwnerID: firstUser.UserID, OrganizationID: firstUser.OrganizationID}).Do() + wsbC := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{Name: "c-workspace", OwnerID: firstUser.UserID, OrganizationID: firstUser.OrganizationID}).Do() // b-workspace should be stopped - wsbB := dbfake.WorkspaceBuild(t, db, database.Workspace{Name: "b-workspace", OwnerID: firstUser.UserID, OrganizationID: firstUser.OrganizationID}).Seed(database.WorkspaceBuild{Transition: database.WorkspaceTransitionStop}).Do() + wsbB := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{Name: "b-workspace", OwnerID: firstUser.UserID, OrganizationID: firstUser.OrganizationID}).Seed(database.WorkspaceBuild{Transition: database.WorkspaceTransitionStop}).Do() // a-workspace should be running - wsbA := dbfake.WorkspaceBuild(t, db, database.Workspace{Name: "a-workspace", OwnerID: firstUser.UserID, OrganizationID: firstUser.OrganizationID}).Do() + wsbA := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{Name: "a-workspace", OwnerID: firstUser.UserID, OrganizationID: firstUser.OrganizationID}).Do() // d-workspace should be stopped - wsbD := dbfake.WorkspaceBuild(t, db, database.Workspace{Name: "d-workspace", OwnerID: secondUser.ID, OrganizationID: firstUser.OrganizationID}).Seed(database.WorkspaceBuild{Transition: database.WorkspaceTransitionStop}).Do() + wsbD := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{Name: "d-workspace", OwnerID: secondUser.ID, OrganizationID: firstUser.OrganizationID}).Seed(database.WorkspaceBuild{Transition: database.WorkspaceTransitionStop}).Do() // e-workspace should also be stopped - wsbE := dbfake.WorkspaceBuild(t, db, database.Workspace{Name: "e-workspace", OwnerID: secondUser.ID, OrganizationID: firstUser.OrganizationID}).Seed(database.WorkspaceBuild{Transition: database.WorkspaceTransitionStop}).Do() + wsbE := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{Name: "e-workspace", OwnerID: secondUser.ID, OrganizationID: firstUser.OrganizationID}).Seed(database.WorkspaceBuild{Transition: database.WorkspaceTransitionStop}).Do() // f-workspace is also stopped, but is marked as favorite - wsbF := dbfake.WorkspaceBuild(t, db, database.Workspace{Name: "f-workspace", OwnerID: firstUser.UserID, OrganizationID: firstUser.OrganizationID}).Seed(database.WorkspaceBuild{Transition: database.WorkspaceTransitionStop}).Do() + wsbF := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{Name: "f-workspace", OwnerID: firstUser.UserID, OrganizationID: firstUser.OrganizationID}).Seed(database.WorkspaceBuild{Transition: database.WorkspaceTransitionStop}).Do() ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() @@ -905,7 +905,7 @@ func TestWorkspaceFilterAllStatus(t *testing.T) { CreatedBy: owner.UserID, }) - makeWorkspace := func(workspace database.Workspace, job database.ProvisionerJob, transition database.WorkspaceTransition) (database.Workspace, database.WorkspaceBuild, database.ProvisionerJob) { + makeWorkspace := func(workspace database.WorkspaceTable, job database.ProvisionerJob, transition database.WorkspaceTransition) (database.WorkspaceTable, database.WorkspaceBuild, database.ProvisionerJob) { db := db workspace.OwnerID = owner.UserID @@ -940,21 +940,21 @@ func TestWorkspaceFilterAllStatus(t *testing.T) { } // pending - makeWorkspace(database.Workspace{ + makeWorkspace(database.WorkspaceTable{ Name: string(database.WorkspaceStatusPending), }, database.ProvisionerJob{ StartedAt: sql.NullTime{Valid: false}, }, database.WorkspaceTransitionStart) // starting - makeWorkspace(database.Workspace{ + makeWorkspace(database.WorkspaceTable{ Name: string(database.WorkspaceStatusStarting), }, database.ProvisionerJob{ StartedAt: sql.NullTime{Time: time.Now().Add(time.Second * -2), Valid: true}, }, database.WorkspaceTransitionStart) // running - makeWorkspace(database.Workspace{ + makeWorkspace(database.WorkspaceTable{ Name: string(database.WorkspaceStatusRunning), }, database.ProvisionerJob{ CompletedAt: sql.NullTime{Time: time.Now(), Valid: true}, @@ -962,14 +962,14 @@ func TestWorkspaceFilterAllStatus(t *testing.T) { }, database.WorkspaceTransitionStart) // stopping - makeWorkspace(database.Workspace{ + makeWorkspace(database.WorkspaceTable{ Name: string(database.WorkspaceStatusStopping), }, database.ProvisionerJob{ StartedAt: sql.NullTime{Time: time.Now().Add(time.Second * -2), Valid: true}, }, database.WorkspaceTransitionStop) // stopped - makeWorkspace(database.Workspace{ + makeWorkspace(database.WorkspaceTable{ Name: string(database.WorkspaceStatusStopped), }, database.ProvisionerJob{ StartedAt: sql.NullTime{Time: time.Now().Add(time.Second * -2), Valid: true}, @@ -977,7 +977,7 @@ func TestWorkspaceFilterAllStatus(t *testing.T) { }, database.WorkspaceTransitionStop) // failed -- delete - makeWorkspace(database.Workspace{ + makeWorkspace(database.WorkspaceTable{ Name: string(database.WorkspaceStatusFailed) + "-deleted", }, database.ProvisionerJob{ StartedAt: sql.NullTime{Time: time.Now().Add(time.Second * -2), Valid: true}, @@ -986,7 +986,7 @@ func TestWorkspaceFilterAllStatus(t *testing.T) { }, database.WorkspaceTransitionDelete) // failed -- stop - makeWorkspace(database.Workspace{ + makeWorkspace(database.WorkspaceTable{ Name: string(database.WorkspaceStatusFailed) + "-stopped", }, database.ProvisionerJob{ StartedAt: sql.NullTime{Time: time.Now().Add(time.Second * -2), Valid: true}, @@ -995,7 +995,7 @@ func TestWorkspaceFilterAllStatus(t *testing.T) { }, database.WorkspaceTransitionStop) // canceling - makeWorkspace(database.Workspace{ + makeWorkspace(database.WorkspaceTable{ Name: string(database.WorkspaceStatusCanceling), }, database.ProvisionerJob{ StartedAt: sql.NullTime{Time: time.Now().Add(time.Second * -2), Valid: true}, @@ -1003,7 +1003,7 @@ func TestWorkspaceFilterAllStatus(t *testing.T) { }, database.WorkspaceTransitionStart) // canceled - makeWorkspace(database.Workspace{ + makeWorkspace(database.WorkspaceTable{ Name: string(database.WorkspaceStatusCanceled), }, database.ProvisionerJob{ StartedAt: sql.NullTime{Time: time.Now().Add(time.Second * -2), Valid: true}, @@ -1012,14 +1012,14 @@ func TestWorkspaceFilterAllStatus(t *testing.T) { }, database.WorkspaceTransitionStart) // deleting - makeWorkspace(database.Workspace{ + makeWorkspace(database.WorkspaceTable{ Name: string(database.WorkspaceStatusDeleting), }, database.ProvisionerJob{ StartedAt: sql.NullTime{Time: time.Now().Add(time.Second * -2), Valid: true}, }, database.WorkspaceTransitionDelete) // deleted - makeWorkspace(database.Workspace{ + makeWorkspace(database.WorkspaceTable{ Name: string(database.WorkspaceStatusDeleted), }, database.ProvisionerJob{ StartedAt: sql.NullTime{Time: time.Now().Add(time.Second * -2), Valid: true}, @@ -1567,14 +1567,14 @@ func TestWorkspaceFilterManual(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() - dormantWorkspace := dbfake.WorkspaceBuild(t, db, database.Workspace{ + dormantWorkspace := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ TemplateID: template.ID, OwnerID: user.UserID, OrganizationID: user.OrganizationID, }).Do().Workspace // Create another workspace to validate that we do not return active workspaces. - _ = dbfake.WorkspaceBuild(t, db, database.Workspace{ + _ = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ TemplateID: template.ID, OwnerID: user.UserID, OrganizationID: user.OrganizationID, @@ -3246,8 +3246,8 @@ func TestWorkspaceFavoriteUnfavorite(t *testing.T) { owner = coderdtest.CreateFirstUser(t, client) memberClient, member = coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) // This will be our 'favorite' workspace - wsb1 = dbfake.WorkspaceBuild(t, db, database.Workspace{OwnerID: member.ID, OrganizationID: owner.OrganizationID}).Do() - wsb2 = dbfake.WorkspaceBuild(t, db, database.Workspace{OwnerID: owner.UserID, OrganizationID: owner.OrganizationID}).Do() + wsb1 = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{OwnerID: member.ID, OrganizationID: owner.OrganizationID}).Do() + wsb2 = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{OwnerID: owner.UserID, OrganizationID: owner.OrganizationID}).Do() ) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) @@ -3324,7 +3324,7 @@ func TestWorkspaceUsageTracking(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) tmpDir := t.TempDir() - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -3371,7 +3371,7 @@ func TestWorkspaceUsageTracking(t *testing.T) { ActivityBumpMillis: 8 * time.Hour.Milliseconds(), }) require.NoError(t, err) - r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, TemplateID: template.ID, @@ -3598,7 +3598,7 @@ func TestWorkspaceTimings(t *testing.T) { ActiveVersionID: version.ID, CreatedBy: owner.UserID, }) - ws := dbgen.Workspace(t, db, database.Workspace{ + ws := dbgen.Workspace(t, db, database.WorkspaceTable{ OwnerID: owner.UserID, OrganizationID: owner.OrganizationID, TemplateID: template.ID, diff --git a/coderd/workspacestats/activitybump_test.go b/coderd/workspacestats/activitybump_test.go index 3abb46b7ab343..50c22042d6491 100644 --- a/coderd/workspacestats/activitybump_test.go +++ b/coderd/workspacestats/activitybump_test.go @@ -191,7 +191,7 @@ func Test_ActivityBumpWorkspace(t *testing.T) { ActiveVersionID: templateVersion.ID, CreatedBy: user.ID, }) - ws = dbgen.Workspace(t, db, database.Workspace{ + ws = dbgen.Workspace(t, db, database.WorkspaceTable{ OwnerID: user.ID, OrganizationID: org.ID, TemplateID: template.ID, diff --git a/coderd/workspacestats/batcher_internal_test.go b/coderd/workspacestats/batcher_internal_test.go index 2f7a25b152127..3e106f07e4e2f 100644 --- a/coderd/workspacestats/batcher_internal_test.go +++ b/coderd/workspacestats/batcher_internal_test.go @@ -162,7 +162,7 @@ type deps struct { Agent database.WorkspaceAgent Template database.Template User database.User - Workspace database.Workspace + Workspace database.WorkspaceTable } // setupDeps sets up a set of test dependencies. @@ -189,7 +189,7 @@ func setupDeps(t *testing.T, store database.Store, ps pubsub.Pubsub) deps { OrganizationID: org.ID, ActiveVersionID: tv.ID, }) - ws := dbgen.Workspace(t, store, database.Workspace{ + ws := dbgen.Workspace(t, store, database.WorkspaceTable{ TemplateID: tpl.ID, OwnerID: user.ID, OrganizationID: org.ID, diff --git a/coderd/workspacestats/tracker_test.go b/coderd/workspacestats/tracker_test.go index 99e9f9503b645..4b5115fd143e9 100644 --- a/coderd/workspacestats/tracker_test.go +++ b/coderd/workspacestats/tracker_test.go @@ -149,7 +149,7 @@ func TestTracker_MultipleInstances(t *testing.T) { numWorkspaces := 10 w := make([]dbfake.WorkspaceResponse, numWorkspaces) for i := 0; i < numWorkspaces; i++ { - wr := dbfake.WorkspaceBuild(t, db, database.Workspace{ + wr := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OwnerID: owner.UserID, OrganizationID: owner.OrganizationID, LastUsedAt: now, diff --git a/docs/admin/security/audit-logs.md b/docs/admin/security/audit-logs.md index b22055ff18b5a..602710289261f 100644 --- a/docs/admin/security/audit-logs.md +++ b/docs/admin/security/audit-logs.md @@ -26,9 +26,9 @@ We track the following resources: | Template
write, delete |
FieldTracked
active_version_idtrue
activity_bumptrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
autostart_block_days_of_weektrue
autostop_requirement_days_of_weektrue
autostop_requirement_weekstrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
default_ttltrue
deletedfalse
deprecatedtrue
descriptiontrue
display_nametrue
failure_ttltrue
group_acltrue
icontrue
idtrue
max_port_sharing_leveltrue
nametrue
organization_display_namefalse
organization_iconfalse
organization_idfalse
organization_namefalse
provisionertrue
require_active_versiontrue
time_til_dormanttrue
time_til_dormant_autodeletetrue
updated_atfalse
user_acltrue
| | TemplateVersion
create, write |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
external_auth_providersfalse
idtrue
job_idfalse
messagefalse
nametrue
organization_idfalse
readmetrue
template_idtrue
updated_atfalse
| | User
create, write, delete |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
github_com_user_idfalse
hashed_one_time_passcodefalse
hashed_passwordtrue
idtrue
last_seen_atfalse
login_typetrue
must_reset_passwordtrue
nametrue
one_time_passcode_expires_attrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
theme_preferencefalse
updated_atfalse
usernametrue
| -| Workspace
create, write, delete |
FieldTracked
automatic_updatestrue
autostart_scheduletrue
created_atfalse
deletedfalse
deleting_attrue
dormant_attrue
favoritetrue
idtrue
last_used_atfalse
nametrue
organization_idfalse
owner_idtrue
template_idtrue
ttltrue
updated_atfalse
| | WorkspaceBuild
start, stop |
FieldTracked
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_usernamefalse
initiator_idfalse
job_idfalse
max_deadlinefalse
provisioner_statefalse
reasonfalse
template_version_idtrue
transitionfalse
updated_atfalse
workspace_idfalse
| | WorkspaceProxy
|
FieldTracked
created_attrue
deletedfalse
derp_enabledtrue
derp_onlytrue
display_nametrue
icontrue
idtrue
nametrue
region_idtrue
token_hashed_secrettrue
updated_atfalse
urltrue
versiontrue
wildcard_hostnametrue
| +| WorkspaceTable
|
FieldTracked
automatic_updatestrue
autostart_scheduletrue
created_atfalse
deletedfalse
deleting_attrue
dormant_attrue
favoritetrue
idtrue
last_used_atfalse
nametrue
organization_idfalse
owner_idtrue
template_idtrue
ttltrue
updated_atfalse
| diff --git a/enterprise/audit/diff_internal_test.go b/enterprise/audit/diff_internal_test.go index f98d16138cf1f..d5c191c8907fa 100644 --- a/enterprise/audit/diff_internal_test.go +++ b/enterprise/audit/diff_internal_test.go @@ -370,8 +370,8 @@ func Test_diff(t *testing.T) { runDiffTests(t, []diffTest{ { name: "Create", - left: audit.Empty[database.Workspace](), - right: database.Workspace{ + left: audit.Empty[database.WorkspaceTable](), + right: database.WorkspaceTable{ ID: uuid.UUID{1}, CreatedAt: time.Now(), UpdatedAt: time.Now(), @@ -392,8 +392,8 @@ func Test_diff(t *testing.T) { }, { name: "NullSchedules", - left: audit.Empty[database.Workspace](), - right: database.Workspace{ + left: audit.Empty[database.WorkspaceTable](), + right: database.WorkspaceTable{ ID: uuid.UUID{1}, CreatedAt: time.Now(), UpdatedAt: time.Now(), diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go index baa9f33b18786..2de2d918dc0aa 100644 --- a/enterprise/audit/table.go +++ b/enterprise/audit/table.go @@ -149,7 +149,7 @@ var auditableResourcesTypes = map[any]map[string]Action{ "one_time_passcode_expires_at": ActionTrack, "must_reset_password": ActionTrack, }, - &database.Workspace{}: { + &database.WorkspaceTable{}: { "id": ActionTrack, "created_at": ActionIgnore, // Never changes. "updated_at": ActionIgnore, // Changes, but is implicit and not helpful in a diff. diff --git a/enterprise/coderd/appearance_test.go b/enterprise/coderd/appearance_test.go index e3563aa882e5a..8550f13904e2d 100644 --- a/enterprise/coderd/appearance_test.go +++ b/enterprise/coderd/appearance_test.go @@ -148,7 +148,7 @@ func TestAnnouncementBanners(t *testing.T) { err := client.UpdateAppearance(ctx, cfg) require.NoError(t, err) - r := dbfake.WorkspaceBuild(t, store, database.Workspace{ + r := dbfake.WorkspaceBuild(t, store, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() diff --git a/enterprise/coderd/jfrog_test.go b/enterprise/coderd/jfrog_test.go index fd47f80b3ee92..a9841a6d92067 100644 --- a/enterprise/coderd/jfrog_test.go +++ b/enterprise/coderd/jfrog_test.go @@ -29,7 +29,7 @@ func TestJFrogXrayScan(t *testing.T) { tac, ta := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin()) - wsResp := dbfake.WorkspaceBuild(t, db, database.Workspace{ + wsResp := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: owner.OrganizationID, OwnerID: ta.ID, }).WithAgent().Do() @@ -85,7 +85,7 @@ func TestJFrogXrayScan(t *testing.T) { memberClient, member := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) - wsResp := dbfake.WorkspaceBuild(t, db, database.Workspace{ + wsResp := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: owner.OrganizationID, OwnerID: member.ID, }).WithAgent().Do() diff --git a/enterprise/coderd/schedule/template.go b/enterprise/coderd/schedule/template.go index 6b148e8ef4708..626e296d6a3e8 100644 --- a/enterprise/coderd/schedule/template.go +++ b/enterprise/coderd/schedule/template.go @@ -136,7 +136,7 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S var ( template database.Template - markedForDeletion []database.Workspace + markedForDeletion []database.WorkspaceTable ) err = db.InTx(func(tx database.Store) error { ctx, span := tracing.StartSpanWithName(ctx, "(*schedule.EnterpriseTemplateScheduleStore).Set()-InTx()") @@ -296,7 +296,7 @@ func (s *EnterpriseTemplateScheduleStore) updateWorkspaceBuild(ctx context.Conte UserQuietHoursScheduleStore: *s.UserQuietHoursScheduleStore.Load(), // Use the job completion time as the time we calculate autostop from. Now: job.CompletedAt.Time, - Workspace: workspace, + Workspace: workspace.WorkspaceTable(), WorkspaceAutostart: workspace.AutostartSchedule.String, }) if err != nil { diff --git a/enterprise/coderd/schedule/template_test.go b/enterprise/coderd/schedule/template_test.go index bce5ffbec930e..c85c2c6ea1b0e 100644 --- a/enterprise/coderd/schedule/template_test.go +++ b/enterprise/coderd/schedule/template_test.go @@ -211,7 +211,7 @@ func TestTemplateUpdateBuildDeadlines(t *testing.T) { ActiveVersionID: templateVersion.ID, CreatedBy: user.ID, }) - ws = dbgen.Workspace(t, db, database.Workspace{ + ws = dbgen.Workspace(t, db, database.WorkspaceTable{ OrganizationID: organizationID, OwnerID: user.ID, TemplateID: template.ID, @@ -357,7 +357,7 @@ func TestTemplateUpdateBuildDeadlinesSkip(t *testing.T) { ) // Create a workspace that will be shared by two builds. - ws := dbgen.Workspace(t, db, database.Workspace{ + ws := dbgen.Workspace(t, db, database.WorkspaceTable{ OwnerID: user.ID, TemplateID: template.ID, OrganizationID: templateJob.OrganizationID, @@ -474,7 +474,7 @@ func TestTemplateUpdateBuildDeadlinesSkip(t *testing.T) { for i, b := range builds { wsID := b.workspaceID if wsID == uuid.Nil { - ws := dbgen.Workspace(t, db, database.Workspace{ + ws := dbgen.Workspace(t, db, database.WorkspaceTable{ OwnerID: user.ID, TemplateID: b.templateID, OrganizationID: templateJob.OrganizationID, @@ -642,21 +642,21 @@ func TestNotifications(t *testing.T) { ) // Add two dormant workspaces and one active workspace. - dormantWorkspaces := []database.Workspace{ - dbgen.Workspace(t, db, database.Workspace{ + dormantWorkspaces := []database.WorkspaceTable{ + dbgen.Workspace(t, db, database.WorkspaceTable{ OwnerID: user.ID, TemplateID: template.ID, OrganizationID: templateJob.OrganizationID, LastUsedAt: time.Now().Add(-time.Hour), }), - dbgen.Workspace(t, db, database.Workspace{ + dbgen.Workspace(t, db, database.WorkspaceTable{ OwnerID: user.ID, TemplateID: template.ID, OrganizationID: templateJob.OrganizationID, LastUsedAt: time.Now().Add(-time.Hour), }), } - dbgen.Workspace(t, db, database.Workspace{ + dbgen.Workspace(t, db, database.WorkspaceTable{ OwnerID: user.ID, TemplateID: template.ID, OrganizationID: templateJob.OrganizationID, diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index dc685c46cec41..239c7ae377102 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -449,7 +449,7 @@ func TestWorkspaceAutobuild(t *testing.T) { TimeTilDormantMillis: inactiveTTL.Milliseconds(), }) - resp := dbfake.WorkspaceBuild(t, db, database.Workspace{ + resp := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, TemplateID: template.ID, @@ -1260,18 +1260,18 @@ func TestWorkspacesFiltering(t *testing.T) { CreatedBy: owner.UserID, }).Do() - dormantWS1 := dbfake.WorkspaceBuild(t, db, database.Workspace{ + dormantWS1 := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OwnerID: templateAdmin.ID, OrganizationID: owner.OrganizationID, }).Do().Workspace - dormantWS2 := dbfake.WorkspaceBuild(t, db, database.Workspace{ + dormantWS2 := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OwnerID: templateAdmin.ID, OrganizationID: owner.OrganizationID, TemplateID: resp.Template.ID, }).Do().Workspace - _ = dbfake.WorkspaceBuild(t, db, database.Workspace{ + _ = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OwnerID: templateAdmin.ID, OrganizationID: owner.OrganizationID, TemplateID: resp.Template.ID, @@ -1448,7 +1448,7 @@ func TestResolveAutostart(t *testing.T) { client, member := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) - workspace := dbfake.WorkspaceBuild(t, db, database.Workspace{ + workspace := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OwnerID: member.ID, OrganizationID: owner.OrganizationID, TemplateID: version1.Template.ID, diff --git a/support/support_test.go b/support/support_test.go index cdd62ceeb8f9b..1a088eb734185 100644 --- a/support/support_test.go +++ b/support/support_test.go @@ -199,7 +199,7 @@ func setupWorkspaceAndAgent(ctx context.Context, t *testing.T, client *codersdk. CreatedBy: user.UserID, }). Do() - wbr := dbfake.WorkspaceBuild(t, db, database.Workspace{ + wbr := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, TemplateID: tv.Template.ID, diff --git a/testutil/reflect.go b/testutil/reflect.go new file mode 100644 index 0000000000000..a776ac98e20a0 --- /dev/null +++ b/testutil/reflect.go @@ -0,0 +1,144 @@ +package testutil + +import ( + "reflect" + "time" + + "golang.org/x/xerrors" +) + +type Random struct { + String func() string + Bool func() bool + Int func() int64 + Uint func() uint64 + Float func() float64 + Complex func() complex128 + Time func() time.Time +} + +func NewRandom() *Random { + // Guaranteed to be random... + return &Random{ + String: func() string { return "foo" }, + Bool: func() bool { return true }, + Int: func() int64 { return 500 }, + Uint: func() uint64 { return 126 }, + Float: func() float64 { return 3.14 }, + Complex: func() complex128 { return 6.24 }, + Time: func() time.Time { return time.Date(2020, 5, 2, 5, 19, 21, 30, time.UTC) }, + } +} + +// PopulateStruct does a best effort to populate a struct with random values. +func PopulateStruct(s interface{}, r *Random) error { + if r == nil { + r = NewRandom() + } + + v := reflect.ValueOf(s) + if v.Kind() != reflect.Ptr || v.IsNil() { + return xerrors.Errorf("s must be a non-nil pointer") + } + + v = v.Elem() + if v.Kind() != reflect.Struct { + return xerrors.Errorf("s must be a pointer to a struct") + } + + t := v.Type() + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + fieldName := field.Name + + fieldValue := v.Field(i) + if !fieldValue.CanSet() { + continue // Skip if field is unexported + } + + nv, err := populateValue(fieldValue, r) + if err != nil { + return xerrors.Errorf("%s : %w", fieldName, err) + } + v.Field(i).Set(nv) + } + + return nil +} + +func populateValue(v reflect.Value, r *Random) (reflect.Value, error) { + var err error + + // Handle some special cases + switch v.Type() { + case reflect.TypeOf(time.Time{}): + v.Set(reflect.ValueOf(r.Time())) + return v, nil + default: + // Go to Kind instead + } + + switch v.Kind() { + case reflect.Struct: + if err := PopulateStruct(v.Addr().Interface(), r); err != nil { + return v, err + } + case reflect.String: + v.SetString(r.String()) + case reflect.Bool: + v.SetBool(true) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v.SetInt(r.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + v.SetUint(r.Uint()) + case reflect.Float32, reflect.Float64: + v.SetFloat(r.Float()) + case reflect.Complex64, reflect.Complex128: + v.SetComplex(r.Complex()) + case reflect.Array: + for i := 0; i < v.Len(); i++ { + nv, err := populateValue(v.Index(i), r) + if err != nil { + return v, xerrors.Errorf("array index %d : %w", i, err) + } + v.Index(i).Set(nv) + } + case reflect.Map: + m := reflect.MakeMap(v.Type()) + + // Set a value in the map + k := reflect.New(v.Type().Key()) + kv := reflect.New(v.Type().Elem()) + k, err = populateValue(k, r) + if err != nil { + return v, xerrors.Errorf("map key : %w", err) + } + kv, err = populateValue(kv, r) + if err != nil { + return v, xerrors.Errorf("map value : %w", err) + } + + m.SetMapIndex(k, kv) + return m, nil + case reflect.Pointer: + return populateValue(v.Elem(), r) + case reflect.Slice: + s := reflect.MakeSlice(v.Type(), 2, 2) + sv, err := populateValue(reflect.New(v.Type().Elem()), r) + if err != nil { + return v, xerrors.Errorf("slice value : %w", err) + } + + s.Index(0).Set(sv) + s.Index(1).Set(sv) + // reflect.AppendSlice(s, sv) + + return s, nil + case reflect.Uintptr, reflect.UnsafePointer, reflect.Chan, reflect.Func, reflect.Interface: + // Unsupported + return v, xerrors.Errorf("%s is not supported", v.Kind()) + default: + return v, xerrors.Errorf("unsupported kind %s", v.Kind()) + } + return v, nil +}