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:
George K
2026-07-07 09:18:30 -07:00
committed by GitHub
parent ba094c5706
commit 6af0f4d698
45 changed files with 4412 additions and 144 deletions
+30
View File
@@ -17,6 +17,10 @@ import (
// creating N separate subscriptions.
const AllWorkspaceEventChannel = "workspace_updates:all"
// WorkspaceBuildOrchestrationWakeChannel wakes the internal worker that
// processes pending workspace build orchestration rows.
const WorkspaceBuildOrchestrationWakeChannel = "workspace_build_orchestrations:wake"
// HandleWorkspaceBuildUpdate wraps a callback to parse WorkspaceBuildUpdate
// messages from the pubsub.
func HandleWorkspaceBuildUpdate(cb func(ctx context.Context, payload codersdk.WorkspaceBuildUpdate, err error)) func(ctx context.Context, message []byte, err error) {
@@ -48,12 +52,38 @@ func PublishWorkspaceBuildUpdate(_ context.Context, ps pubsub.Pubsub, update cod
return nil
}
// PublishWorkspaceBuildOrchestrationWake wakes coderd instances that can
// process pending workspace build orchestration rows. Call this after any
// workspace build reaches a terminal state: succeeded, failed, or canceled.
func PublishWorkspaceBuildOrchestrationWake(_ context.Context, ps pubsub.Pubsub) error {
if err := ps.Publish(WorkspaceBuildOrchestrationWakeChannel, []byte("{}")); err != nil {
return xerrors.Errorf("publish workspace build orchestration wake: %w", err)
}
return nil
}
// WorkspaceEventChannel can be used to subscribe to events for
// workspaces owned by the provided user ID.
func WorkspaceEventChannel(ownerID uuid.UUID) string {
return fmt.Sprintf("workspace_owner:%s", ownerID)
}
// PublishWorkspaceEvent validates and publishes a workspace event to
// the owner's event channel.
func PublishWorkspaceEvent(_ context.Context, ps pubsub.Pubsub, ownerID uuid.UUID, event WorkspaceEvent) error {
if err := event.Validate(); err != nil {
return xerrors.Errorf("validate workspace event: %w", err)
}
msg, err := json.Marshal(event)
if err != nil {
return xerrors.Errorf("marshal workspace event: %w", err)
}
if err := ps.Publish(WorkspaceEventChannel(ownerID), msg); err != nil {
return xerrors.Errorf("publish workspace event: %w", err)
}
return nil
}
func HandleWorkspaceEvent(cb func(ctx context.Context, payload WorkspaceEvent, err error)) func(ctx context.Context, message []byte, err error) {
return func(ctx context.Context, message []byte, err error) {
if err != nil {