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
+17 -1
View File
@@ -96,6 +96,7 @@ import (
"github.com/coder/coder/v2/coderd/workspaceconnwatcher"
"github.com/coder/coder/v2/coderd/workspacestats"
"github.com/coder/coder/v2/coderd/wsbuilder"
"github.com/coder/coder/v2/coderd/wsbuildorchestrator"
"github.com/coder/coder/v2/coderd/x/chatd"
"github.com/coder/coder/v2/coderd/x/chatd/chatprovider"
"github.com/coder/coder/v2/coderd/x/chatd/mcpclient"
@@ -968,6 +969,19 @@ func New(options *Options) *API {
api.workspaceAgentConnWatcher = workspaceconnwatcher.New(api.ctx, options.Logger, options.Pubsub, options.Database)
api.workspaceBuildOrchestrator = wsbuildorchestrator.New(wsbuildorchestrator.Options{
Logger: options.Logger,
Database: options.Database,
Pubsub: options.Pubsub,
FileCache: api.FileCache,
BuildUsageChecker: api.BuildUsageChecker,
DeploymentValues: options.DeploymentValues,
Experiments: api.Experiments,
BuilderMetrics: options.WorkspaceBuilderMetrics,
Clock: quartz.NewReal(),
})
api.workspaceBuildOrchestrator.Start(api.ctx)
apiKeyMiddleware := httpmw.ExtractAPIKeyMW(httpmw.ExtractAPIKeyConfig{
DB: options.Database,
ActivateDormantUser: ActivateDormantUser(options.Logger, &api.Auditor, options.Database),
@@ -2316,7 +2330,8 @@ type API struct {
// profiler is process-global, so concurrent collections would fail.
ProfileCollecting atomic.Bool
workspaceAgentConnWatcher *workspaceconnwatcher.Watcher
workspaceAgentConnWatcher *workspaceconnwatcher.Watcher
workspaceBuildOrchestrator *wsbuildorchestrator.Orchestrator
}
// chatDaemonPublishDiffStatusChangeFunc returns chatDaemon's
@@ -2400,6 +2415,7 @@ func (api *API) Close() error {
_ = api.AppEncryptionKeyCache.Close()
_ = api.UpdatesProvider.Close()
api.workspaceAgentConnWatcher.Close()
api.workspaceBuildOrchestrator.Close()
if current := api.PrebuildsReconciler.Load(); current != nil {
ctx, giveUp := context.WithTimeoutCause(context.Background(), time.Second*30, xerrors.New("gave up waiting for reconciler to stop before shutdown"))