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 +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy