mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add workspace restart functionality to API (#25757)
This models restart as durable orchestration of existing stop and start workspace builds instead of adding a new restart transition. Keeping restart as two existing transitions preserves the current build/provisioner model. The child start build is created only after the parent stop build succeeds, rather than being inserted immediately in a pending state. That keeps `workspace_builds` aligned with actual provisioner-ready work and avoids introducing a second pending-build lifecycle that the provisioner and build acquisition paths would need to understand. Refs: https://linear.app/codercom/issue/PLAT-143
This commit is contained in:
@@ -482,31 +482,32 @@ var (
|
||||
Identifier: rbac.RoleIdentifier{Name: "system"},
|
||||
DisplayName: "Coder",
|
||||
Site: rbac.Permissions(map[string][]policy.Action{
|
||||
rbac.ResourceWildcard.Type: {policy.ActionRead},
|
||||
rbac.ResourceApiKey.Type: rbac.ResourceApiKey.AvailableActions(),
|
||||
rbac.ResourceGroup.Type: {policy.ActionCreate, policy.ActionUpdate},
|
||||
rbac.ResourceAssignRole.Type: rbac.ResourceAssignRole.AvailableActions(),
|
||||
rbac.ResourceAssignOrgRole.Type: rbac.ResourceAssignOrgRole.AvailableActions(),
|
||||
rbac.ResourceSystem.Type: {policy.WildcardSymbol},
|
||||
rbac.ResourceOrganization.Type: {policy.ActionCreate, policy.ActionRead},
|
||||
rbac.ResourceOrganizationMember.Type: {policy.ActionCreate, policy.ActionDelete, policy.ActionRead},
|
||||
rbac.ResourceProvisionerDaemon.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate},
|
||||
rbac.ResourceUser.Type: rbac.ResourceUser.AvailableActions(),
|
||||
rbac.ResourceWorkspaceDormant.Type: {policy.ActionUpdate, policy.ActionDelete, policy.ActionWorkspaceStop},
|
||||
rbac.ResourceWorkspace.Type: {policy.ActionUpdate, policy.ActionDelete, policy.ActionWorkspaceStart, policy.ActionWorkspaceStop, policy.ActionSSH, policy.ActionCreateAgent, policy.ActionDeleteAgent, policy.ActionUpdateAgent},
|
||||
rbac.ResourceWorkspaceProxy.Type: {policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceDeploymentConfig.Type: {policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceNotificationMessage.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceNotificationPreference.Type: {policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceNotificationTemplate.Type: {policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceCryptoKey.Type: {policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceFile.Type: {policy.ActionCreate, policy.ActionRead},
|
||||
rbac.ResourceProvisionerJobs.Type: {policy.ActionRead, policy.ActionUpdate, policy.ActionCreate},
|
||||
rbac.ResourceOauth2App.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceOauth2AppSecret.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceChat.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceAIProvider.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceAIGatewayKey.Type: {policy.ActionRead, policy.ActionUpdate},
|
||||
rbac.ResourceWildcard.Type: {policy.ActionRead},
|
||||
rbac.ResourceApiKey.Type: rbac.ResourceApiKey.AvailableActions(),
|
||||
rbac.ResourceGroup.Type: {policy.ActionCreate, policy.ActionUpdate},
|
||||
rbac.ResourceAssignRole.Type: rbac.ResourceAssignRole.AvailableActions(),
|
||||
rbac.ResourceAssignOrgRole.Type: rbac.ResourceAssignOrgRole.AvailableActions(),
|
||||
rbac.ResourceSystem.Type: {policy.WildcardSymbol},
|
||||
rbac.ResourceOrganization.Type: {policy.ActionCreate, policy.ActionRead},
|
||||
rbac.ResourceOrganizationMember.Type: {policy.ActionCreate, policy.ActionDelete, policy.ActionRead},
|
||||
rbac.ResourceProvisionerDaemon.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate},
|
||||
rbac.ResourceUser.Type: rbac.ResourceUser.AvailableActions(),
|
||||
rbac.ResourceWorkspaceDormant.Type: {policy.ActionUpdate, policy.ActionDelete, policy.ActionWorkspaceStop},
|
||||
rbac.ResourceWorkspace.Type: {policy.ActionUpdate, policy.ActionDelete, policy.ActionWorkspaceStart, policy.ActionWorkspaceStop, policy.ActionSSH, policy.ActionCreateAgent, policy.ActionDeleteAgent, policy.ActionUpdateAgent},
|
||||
rbac.ResourceWorkspaceProxy.Type: {policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceWorkspaceBuildOrchestration.Type: {policy.ActionUpdate, policy.ActionRead},
|
||||
rbac.ResourceDeploymentConfig.Type: {policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceNotificationMessage.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceNotificationPreference.Type: {policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceNotificationTemplate.Type: {policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceCryptoKey.Type: {policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceFile.Type: {policy.ActionCreate, policy.ActionRead},
|
||||
rbac.ResourceProvisionerJobs.Type: {policy.ActionRead, policy.ActionUpdate, policy.ActionCreate},
|
||||
rbac.ResourceOauth2App.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceOauth2AppSecret.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceChat.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceAIProvider.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceAIGatewayKey.Type: {policy.ActionRead, policy.ActionUpdate},
|
||||
}),
|
||||
User: []rbac.Permission{},
|
||||
ByOrgID: map[string]rbac.OrgPermissions{},
|
||||
@@ -691,10 +692,11 @@ var (
|
||||
Identifier: rbac.RoleIdentifier{Name: "dbpurge"},
|
||||
DisplayName: "DB Purge Daemon",
|
||||
Site: rbac.Permissions(map[string][]policy.Action{
|
||||
rbac.ResourceSystem.Type: {policy.ActionDelete},
|
||||
rbac.ResourceNotificationMessage.Type: {policy.ActionDelete},
|
||||
rbac.ResourceApiKey.Type: {policy.ActionDelete},
|
||||
rbac.ResourceAibridgeInterception.Type: {policy.ActionDelete},
|
||||
rbac.ResourceSystem.Type: {policy.ActionDelete},
|
||||
rbac.ResourceNotificationMessage.Type: {policy.ActionDelete},
|
||||
rbac.ResourceApiKey.Type: {policy.ActionDelete},
|
||||
rbac.ResourceAibridgeInterception.Type: {policy.ActionDelete},
|
||||
rbac.ResourceWorkspaceBuildOrchestration.Type: {policy.ActionDelete},
|
||||
// Chat auto-archive sets archived=true on inactive chats.
|
||||
rbac.ResourceChat.Type: {policy.ActionRead, policy.ActionUpdate},
|
||||
// Purge old boundary logs past the retention period.
|
||||
@@ -2390,6 +2392,13 @@ func (q *querier) DeleteOldWorkspaceAgentStats(ctx context.Context) error {
|
||||
return q.db.DeleteOldWorkspaceAgentStats(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteOldWorkspaceBuildOrchestrations(ctx context.Context, arg database.DeleteOldWorkspaceBuildOrchestrationsParams) (int64, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceWorkspaceBuildOrchestration.AnyOrganization()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return q.db.DeleteOldWorkspaceBuildOrchestrations(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteOrganizationMember(ctx context.Context, arg database.DeleteOrganizationMemberParams) error {
|
||||
return deleteQ[database.OrganizationMember](q.log, q.auth, func(ctx context.Context, arg database.DeleteOrganizationMemberParams) (database.OrganizationMember, error) {
|
||||
member, err := database.ExpectOne(q.OrganizationMembers(ctx, database.OrganizationMembersParams{
|
||||
@@ -3996,6 +4005,13 @@ func (q *querier) GetMCPServerUserTokensByUserID(ctx context.Context, userID uui
|
||||
return q.db.GetMCPServerUserTokensByUserID(ctx, userID)
|
||||
}
|
||||
|
||||
func (q *querier) GetNextPendingWorkspaceBuildOrchestrationForUpdate(ctx context.Context) (database.WorkspaceBuildOrchestration, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceWorkspaceBuildOrchestration.AnyOrganization()); err != nil {
|
||||
return database.WorkspaceBuildOrchestration{}, err
|
||||
}
|
||||
return q.db.GetNextPendingWorkspaceBuildOrchestrationForUpdate(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) GetNotificationMessagesByStatus(ctx context.Context, arg database.GetNotificationMessagesByStatusParams) ([]database.NotificationMessage, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceNotificationMessage); err != nil {
|
||||
return nil, err
|
||||
@@ -6466,6 +6482,66 @@ func (q *querier) InsertWorkspaceBuild(ctx context.Context, arg database.InsertW
|
||||
return q.db.InsertWorkspaceBuild(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertWorkspaceBuildOrchestration(ctx context.Context, arg database.InsertWorkspaceBuildOrchestrationParams) (database.WorkspaceBuildOrchestration, error) {
|
||||
// Read through the raw q.db to fetch the authz context; authorization
|
||||
// happens via q.authorizeContext below, as in InsertWorkspaceBuild.
|
||||
parentBuild, err := q.db.GetWorkspaceBuildByID(ctx, arg.ParentBuildID)
|
||||
if err != nil {
|
||||
return database.WorkspaceBuildOrchestration{}, xerrors.Errorf("get parent workspace build by id: %w", err)
|
||||
}
|
||||
|
||||
workspace, err := q.db.GetWorkspaceByID(ctx, parentBuild.WorkspaceID)
|
||||
if err != nil {
|
||||
return database.WorkspaceBuildOrchestration{}, xerrors.Errorf("get workspace by id: %w", err)
|
||||
}
|
||||
if workspace.IsPrebuild() {
|
||||
return database.WorkspaceBuildOrchestration{}, xerrors.New("cannot orchestrate prebuild workspace builds")
|
||||
}
|
||||
|
||||
// The current API flow inserts this row immediately after
|
||||
// creating the parent build, so the parent transition has already
|
||||
// been authorized. Still, make sure future callers cannot attach
|
||||
// the child intent to a parent build the actor could not initiate.
|
||||
parentAction, err := workspaceTransitionAction(parentBuild.Transition)
|
||||
if err != nil {
|
||||
return database.WorkspaceBuildOrchestration{}, err
|
||||
}
|
||||
if err := q.authorizeContext(ctx, parentAction, workspace); err != nil {
|
||||
return database.WorkspaceBuildOrchestration{}, err
|
||||
}
|
||||
|
||||
// The orchestrator uses system authority to create the child
|
||||
// build after the parent succeeds, so the initiating actor must
|
||||
// be authorized now.
|
||||
childAction, err := workspaceTransitionAction(arg.ChildTransition)
|
||||
if err != nil {
|
||||
return database.WorkspaceBuildOrchestration{}, err
|
||||
}
|
||||
if err := q.authorizeContext(ctx, childAction, workspace); err != nil {
|
||||
return database.WorkspaceBuildOrchestration{}, err
|
||||
}
|
||||
|
||||
if arg.ChildTransition == database.WorkspaceTransitionStart && arg.ChildTemplateVersionID.Valid {
|
||||
// Only template admins may queue child builds with a durable
|
||||
// template version pin, since the active version can change
|
||||
// before the worker creates the child build.
|
||||
template, err := q.db.GetTemplateByID(ctx, workspace.TemplateID)
|
||||
if err != nil {
|
||||
return database.WorkspaceBuildOrchestration{}, xerrors.Errorf("get template by id: %w", err)
|
||||
}
|
||||
|
||||
err = q.authorizeContext(ctx, policy.ActionUpdate, template)
|
||||
var notAuthorized NotAuthorizedError
|
||||
if xerrors.As(err, ¬Authorized) {
|
||||
return database.WorkspaceBuildOrchestration{}, err
|
||||
} else if err != nil {
|
||||
return database.WorkspaceBuildOrchestration{}, xerrors.Errorf("cannot pin template version for child build: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return q.db.InsertWorkspaceBuildOrchestration(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertWorkspaceBuildParameters(ctx context.Context, arg database.InsertWorkspaceBuildParametersParams) error {
|
||||
// TODO: Optimize this. We always have the workspace and build already fetched.
|
||||
build, err := q.db.GetWorkspaceBuildByID(ctx, arg.WorkspaceBuildID)
|
||||
@@ -8457,6 +8533,34 @@ func (q *querier) UpdateWorkspaceBuildNotifiedAutostopDeadline(ctx context.Conte
|
||||
return q.db.UpdateWorkspaceBuildNotifiedAutostopDeadline(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateWorkspaceBuildOrchestrationCanceledByID(ctx context.Context, arg database.UpdateWorkspaceBuildOrchestrationCanceledByIDParams) (database.WorkspaceBuildOrchestration, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceWorkspaceBuildOrchestration.AnyOrganization()); err != nil {
|
||||
return database.WorkspaceBuildOrchestration{}, err
|
||||
}
|
||||
return q.db.UpdateWorkspaceBuildOrchestrationCanceledByID(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateWorkspaceBuildOrchestrationCompletedByID(ctx context.Context, arg database.UpdateWorkspaceBuildOrchestrationCompletedByIDParams) (database.WorkspaceBuildOrchestration, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceWorkspaceBuildOrchestration.AnyOrganization()); err != nil {
|
||||
return database.WorkspaceBuildOrchestration{}, err
|
||||
}
|
||||
return q.db.UpdateWorkspaceBuildOrchestrationCompletedByID(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateWorkspaceBuildOrchestrationFailedByID(ctx context.Context, arg database.UpdateWorkspaceBuildOrchestrationFailedByIDParams) (database.WorkspaceBuildOrchestration, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceWorkspaceBuildOrchestration.AnyOrganization()); err != nil {
|
||||
return database.WorkspaceBuildOrchestration{}, err
|
||||
}
|
||||
return q.db.UpdateWorkspaceBuildOrchestrationFailedByID(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateWorkspaceBuildOrchestrationRetryByID(ctx context.Context, arg database.UpdateWorkspaceBuildOrchestrationRetryByIDParams) (database.WorkspaceBuildOrchestration, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceWorkspaceBuildOrchestration.AnyOrganization()); err != nil {
|
||||
return database.WorkspaceBuildOrchestration{}, err
|
||||
}
|
||||
return q.db.UpdateWorkspaceBuildOrchestrationRetryByID(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateWorkspaceBuildProvisionerStateByID(ctx context.Context, arg database.UpdateWorkspaceBuildProvisionerStateByIDParams) error {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceSystem); err != nil {
|
||||
return err
|
||||
|
||||
@@ -4024,6 +4024,162 @@ func (s *MethodTestSuite) TestWorkspace() {
|
||||
dbm.EXPECT().InsertWorkspaceBuild(gomock.Any(), arg).Return(nil).AnyTimes()
|
||||
check.Args(arg).Asserts(w, policy.ActionDelete)
|
||||
}))
|
||||
s.Run("Start/PinnedVersion/InsertWorkspaceBuildOrchestration", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
activeVersionID := uuid.New()
|
||||
childVersionID := uuid.New()
|
||||
t := testutil.Fake(s.T(), faker, database.Template{ActiveVersionID: activeVersionID})
|
||||
w := testutil.Fake(s.T(), faker, database.Workspace{TemplateID: t.ID})
|
||||
parentBuild := testutil.Fake(s.T(), faker, database.WorkspaceBuild{
|
||||
WorkspaceID: w.ID,
|
||||
TemplateVersionID: activeVersionID,
|
||||
Transition: database.WorkspaceTransitionStop,
|
||||
})
|
||||
arg := database.InsertWorkspaceBuildOrchestrationParams{
|
||||
ParentBuildID: parentBuild.ID,
|
||||
ChildTransition: database.WorkspaceTransitionStart,
|
||||
ChildTemplateVersionID: uuid.NullUUID{
|
||||
UUID: childVersionID,
|
||||
Valid: true,
|
||||
},
|
||||
}
|
||||
orchestration := testutil.Fake(s.T(), faker, database.WorkspaceBuildOrchestration{})
|
||||
dbm.EXPECT().GetWorkspaceBuildByID(gomock.Any(), parentBuild.ID).Return(parentBuild, nil).AnyTimes()
|
||||
dbm.EXPECT().GetWorkspaceByID(gomock.Any(), w.ID).Return(w, nil).AnyTimes()
|
||||
dbm.EXPECT().GetTemplateByID(gomock.Any(), t.ID).Return(t, nil).AnyTimes()
|
||||
// Ensure template admins may queue child builds with a durable
|
||||
// template version pin.
|
||||
dbm.EXPECT().InsertWorkspaceBuildOrchestration(gomock.Any(), arg).Return(orchestration, nil).AnyTimes()
|
||||
check.Args(arg).
|
||||
Asserts(
|
||||
w, policy.ActionWorkspaceStop,
|
||||
w, policy.ActionWorkspaceStart,
|
||||
t, policy.ActionUpdate,
|
||||
).
|
||||
Returns(orchestration)
|
||||
}))
|
||||
s.Run("Start/PinnedVersionWithoutTemplateUpdate/InsertWorkspaceBuildOrchestration", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
activeVersionID := uuid.New()
|
||||
t := testutil.Fake(s.T(), faker, database.Template{ActiveVersionID: activeVersionID})
|
||||
w := testutil.Fake(s.T(), faker, database.Workspace{TemplateID: t.ID})
|
||||
parentBuild := testutil.Fake(s.T(), faker, database.WorkspaceBuild{
|
||||
WorkspaceID: w.ID,
|
||||
TemplateVersionID: activeVersionID,
|
||||
Transition: database.WorkspaceTransitionStop,
|
||||
})
|
||||
arg := database.InsertWorkspaceBuildOrchestrationParams{
|
||||
ParentBuildID: parentBuild.ID,
|
||||
ChildTransition: database.WorkspaceTransitionStart,
|
||||
ChildTemplateVersionID: uuid.NullUUID{
|
||||
UUID: activeVersionID,
|
||||
Valid: true,
|
||||
},
|
||||
}
|
||||
dbm.EXPECT().GetWorkspaceBuildByID(gomock.Any(), parentBuild.ID).Return(parentBuild, nil).AnyTimes()
|
||||
dbm.EXPECT().GetWorkspaceByID(gomock.Any(), w.ID).Return(w, nil).AnyTimes()
|
||||
dbm.EXPECT().GetTemplateByID(gomock.Any(), t.ID).Return(t, nil).AnyTimes()
|
||||
// Ensure non-template admins cannot queue a durable template
|
||||
// version pin for the child build.
|
||||
check.Args(arg).
|
||||
Asserts(
|
||||
w, policy.ActionWorkspaceStop,
|
||||
w, policy.ActionWorkspaceStart,
|
||||
t, policy.ActionUpdate,
|
||||
).
|
||||
Errors(errMatchAny).
|
||||
WithSuccessAuthorizer(func(_ context.Context, _ rbac.Subject, action policy.Action, obj rbac.Object) error {
|
||||
if action == policy.ActionUpdate && obj.Type == rbac.ResourceTemplate.Type {
|
||||
return xerrors.New("not authorized to update template")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}))
|
||||
s.Run("Start/UnpinnedVersion/InsertWorkspaceBuildOrchestration", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
t := testutil.Fake(s.T(), faker, database.Template{})
|
||||
w := testutil.Fake(s.T(), faker, database.Workspace{TemplateID: t.ID})
|
||||
parentBuild := testutil.Fake(s.T(), faker, database.WorkspaceBuild{
|
||||
WorkspaceID: w.ID,
|
||||
Transition: database.WorkspaceTransitionStop,
|
||||
})
|
||||
arg := database.InsertWorkspaceBuildOrchestrationParams{
|
||||
ParentBuildID: parentBuild.ID,
|
||||
ChildTransition: database.WorkspaceTransitionStart,
|
||||
}
|
||||
orchestration := testutil.Fake(s.T(), faker, database.WorkspaceBuildOrchestration{})
|
||||
dbm.EXPECT().GetWorkspaceBuildByID(gomock.Any(), parentBuild.ID).Return(parentBuild, nil).AnyTimes()
|
||||
dbm.EXPECT().GetWorkspaceByID(gomock.Any(), w.ID).Return(w, nil).AnyTimes()
|
||||
// Ensure an unpinned child build does not require template update permission.
|
||||
dbm.EXPECT().InsertWorkspaceBuildOrchestration(gomock.Any(), arg).Return(orchestration, nil).AnyTimes()
|
||||
check.Args(arg).
|
||||
Asserts(
|
||||
w, policy.ActionWorkspaceStop,
|
||||
w, policy.ActionWorkspaceStart,
|
||||
).
|
||||
Returns(orchestration)
|
||||
}))
|
||||
s.Run("GetNextPendingWorkspaceBuildOrchestrationForUpdate", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
orchestration := testutil.Fake(s.T(), faker, database.WorkspaceBuildOrchestration{})
|
||||
dbm.EXPECT().GetNextPendingWorkspaceBuildOrchestrationForUpdate(gomock.Any()).Return(orchestration, nil).AnyTimes()
|
||||
check.Args().
|
||||
Asserts(rbac.ResourceWorkspaceBuildOrchestration.AnyOrganization(), policy.ActionRead).
|
||||
Returns(orchestration)
|
||||
}))
|
||||
s.Run("UpdateWorkspaceBuildOrchestrationCanceledByID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
orchestration := testutil.Fake(s.T(), faker, database.WorkspaceBuildOrchestration{})
|
||||
arg := database.UpdateWorkspaceBuildOrchestrationCanceledByIDParams{
|
||||
UpdatedAt: dbtime.Now(),
|
||||
ID: orchestration.ID,
|
||||
}
|
||||
dbm.EXPECT().UpdateWorkspaceBuildOrchestrationCanceledByID(gomock.Any(), arg).Return(orchestration, nil).AnyTimes()
|
||||
check.Args(arg).
|
||||
Asserts(rbac.ResourceWorkspaceBuildOrchestration.AnyOrganization(), policy.ActionUpdate).
|
||||
Returns(orchestration)
|
||||
}))
|
||||
s.Run("UpdateWorkspaceBuildOrchestrationCompletedByID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
orchestration := testutil.Fake(s.T(), faker, database.WorkspaceBuildOrchestration{})
|
||||
arg := database.UpdateWorkspaceBuildOrchestrationCompletedByIDParams{
|
||||
ChildBuildID: uuid.NullUUID{UUID: uuid.New(), Valid: true},
|
||||
UpdatedAt: dbtime.Now(),
|
||||
ID: orchestration.ID,
|
||||
}
|
||||
dbm.EXPECT().UpdateWorkspaceBuildOrchestrationCompletedByID(gomock.Any(), arg).Return(orchestration, nil).AnyTimes()
|
||||
check.Args(arg).
|
||||
Asserts(rbac.ResourceWorkspaceBuildOrchestration.AnyOrganization(), policy.ActionUpdate).
|
||||
Returns(orchestration)
|
||||
}))
|
||||
s.Run("UpdateWorkspaceBuildOrchestrationFailedByID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
orchestration := testutil.Fake(s.T(), faker, database.WorkspaceBuildOrchestration{})
|
||||
arg := database.UpdateWorkspaceBuildOrchestrationFailedByIDParams{
|
||||
Error: sql.NullString{String: "failed", Valid: true},
|
||||
UpdatedAt: dbtime.Now(),
|
||||
ID: orchestration.ID,
|
||||
}
|
||||
dbm.EXPECT().UpdateWorkspaceBuildOrchestrationFailedByID(gomock.Any(), arg).Return(orchestration, nil).AnyTimes()
|
||||
check.Args(arg).
|
||||
Asserts(rbac.ResourceWorkspaceBuildOrchestration.AnyOrganization(), policy.ActionUpdate).
|
||||
Returns(orchestration)
|
||||
}))
|
||||
s.Run("UpdateWorkspaceBuildOrchestrationRetryByID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
orchestration := testutil.Fake(s.T(), faker, database.WorkspaceBuildOrchestration{})
|
||||
arg := database.UpdateWorkspaceBuildOrchestrationRetryByIDParams{
|
||||
MaxAttemptCount: 3,
|
||||
NextRetryAfter: dbtime.Now(),
|
||||
Error: sql.NullString{String: "retry", Valid: true},
|
||||
UpdatedAt: dbtime.Now(),
|
||||
ID: orchestration.ID,
|
||||
}
|
||||
dbm.EXPECT().UpdateWorkspaceBuildOrchestrationRetryByID(gomock.Any(), arg).Return(orchestration, nil).AnyTimes()
|
||||
check.Args(arg).
|
||||
Asserts(rbac.ResourceWorkspaceBuildOrchestration.AnyOrganization(), policy.ActionUpdate).
|
||||
Returns(orchestration)
|
||||
}))
|
||||
s.Run("DeleteOldWorkspaceBuildOrchestrations", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
|
||||
arg := database.DeleteOldWorkspaceBuildOrchestrationsParams{
|
||||
BeforeTime: dbtime.Now(),
|
||||
LimitCount: 100,
|
||||
}
|
||||
dbm.EXPECT().DeleteOldWorkspaceBuildOrchestrations(gomock.Any(), arg).Return(int64(0), nil).AnyTimes()
|
||||
check.Args(arg).Asserts(rbac.ResourceWorkspaceBuildOrchestration.AnyOrganization(), policy.ActionDelete)
|
||||
}))
|
||||
s.Run("Start/InsertWorkspaceBuildParameters", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
w := testutil.Fake(s.T(), faker, database.Workspace{})
|
||||
b := testutil.Fake(s.T(), faker, database.WorkspaceBuild{
|
||||
|
||||
Reference in New Issue
Block a user