Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit8b9e30d

Browse files
feat: implement claiming of prebuilt workspaces
1 parent183146e commit8b9e30d

File tree

11 files changed

+769
-34
lines changed

11 files changed

+769
-34
lines changed

‎coderd/coderd.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"expvar"
1010
"flag"
1111
"fmt"
12+
"github.com/coder/coder/v2/coderd/prebuilds"
1213
"io"
1314
"net/http"
1415
"net/url"
@@ -595,6 +596,7 @@ func New(options *Options) *API {
595596
f:=appearance.NewDefaultFetcher(api.DeploymentValues.DocsURL.String())
596597
api.AppearanceFetcher.Store(&f)
597598
api.PortSharer.Store(&portsharing.DefaultPortSharer)
599+
api.PrebuildsClaimer.Store(&prebuilds.DefaultClaimer)
598600
buildInfo:= codersdk.BuildInfoResponse{
599601
ExternalURL:buildinfo.ExternalURL(),
600602
Version:buildinfo.Version(),
@@ -1562,6 +1564,7 @@ type API struct {
15621564
AccessControlStore*atomic.Pointer[dbauthz.AccessControlStore]
15631565
PortSharer atomic.Pointer[portsharing.PortSharer]
15641566
FileCache files.Cache
1567+
PrebuildsClaimer atomic.Pointer[prebuilds.Claimer]
15651568

15661569
UpdatesProvider tailnet.WorkspaceUpdatesProvider
15671570

‎coderd/prebuilds/api.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ package prebuilds
22

33
import (
44
"context"
5+
6+
"github.com/google/uuid"
7+
8+
"github.com/coder/coder/v2/coderd/database"
59
)
610

711
// ReconciliationOrchestrator manages the lifecycle of prebuild reconciliation.
@@ -19,9 +23,38 @@ type ReconciliationOrchestrator interface {
1923
Stop(ctx context.Context,causeerror)
2024
}
2125

26+
// Reconciler defines the core operations for managing prebuilds.
27+
// It provides both high-level orchestration (ReconcileAll) and lower-level operations
28+
// for more fine-grained control (SnapshotState, ReconcilePreset, CalculateActions).
29+
// All database operations must be performed within repeatable-read transactions
30+
// to ensure consistency.
2231
typeReconcilerinterface {
2332
// ReconcileAll orchestrates the reconciliation of all prebuilds across all templates.
2433
// It takes a global snapshot of the system state and then reconciles each preset
2534
// in parallel, creating or deleting prebuilds as needed to reach their desired states.
35+
// For more fine-grained control, you can use the lower-level methods SnapshotState
36+
// and ReconcilePreset directly.
2637
ReconcileAll(ctx context.Context)error
38+
39+
// SnapshotState captures the current state of all prebuilds across templates.
40+
// It creates a global database snapshot that can be viewed as a collection of PresetSnapshots,
41+
// each representing the state of prebuilds for a specific preset.
42+
// MUST be called inside a repeatable-read transaction.
43+
SnapshotState(ctx context.Context,store database.Store) (*GlobalSnapshot,error)
44+
45+
// ReconcilePreset handles a single PresetSnapshot, determining and executing
46+
// the required actions (creating or deleting prebuilds) based on the current state.
47+
// MUST be called inside a repeatable-read transaction.
48+
ReconcilePreset(ctx context.Context,snapshotPresetSnapshot)error
49+
50+
// CalculateActions determines what actions are needed to reconcile a preset's prebuilds
51+
// to their desired state. This includes creating new prebuilds, deleting excess ones,
52+
// or waiting due to backoff periods.
53+
// MUST be called inside a repeatable-read transaction.
54+
CalculateActions(ctx context.Context,statePresetSnapshot) (*ReconciliationActions,error)
55+
}
56+
57+
typeClaimerinterface {
58+
Claim(ctx context.Context,store database.Store,userID uuid.UUID,namestring,presetID uuid.UUID) (*uuid.UUID,error)
59+
Initiator() uuid.UUID
2760
}

‎coderd/prebuilds/noop.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package prebuilds
33
import (
44
"context"
55

6+
"github.com/google/uuid"
7+
68
"github.com/coder/coder/v2/coderd/database"
79
)
810

@@ -33,3 +35,16 @@ func (NoopReconciler) CalculateActions(context.Context, PresetSnapshot) (*Reconc
3335
}
3436

3537
var_ReconciliationOrchestrator=NoopReconciler{}
38+
39+
typeAGPLPrebuildClaimerstruct{}
40+
41+
func (cAGPLPrebuildClaimer)Claim(context.Context, database.Store, uuid.UUID,string, uuid.UUID) (*uuid.UUID,error) {
42+
// Not entitled to claim prebuilds in AGPL version.
43+
returnnil,nil
44+
}
45+
46+
func (cAGPLPrebuildClaimer)Initiator() uuid.UUID {
47+
returnuuid.Nil
48+
}
49+
50+
varDefaultClaimerClaimer=AGPLPrebuildClaimer{}

‎coderd/provisionerdserver/provisionerdserver.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2462,10 +2462,18 @@ type TemplateVersionImportJob struct {
24622462

24632463
// WorkspaceProvisionJob is the payload for the "workspace_provision" job type.
24642464
typeWorkspaceProvisionJobstruct {
2465-
WorkspaceBuildID uuid.UUID`json:"workspace_build_id"`
2466-
DryRunbool`json:"dry_run"`
2467-
IsPrebuildbool`json:"is_prebuild,omitempty"`
2468-
LogLevelstring`json:"log_level,omitempty"`
2465+
WorkspaceBuildID uuid.UUID`json:"workspace_build_id"`
2466+
DryRunbool`json:"dry_run"`
2467+
IsPrebuildbool`json:"is_prebuild,omitempty"`
2468+
PrebuildClaimedByUser uuid.UUID`json:"prebuild_claimed_by,omitempty"`
2469+
// RunningWorkspaceAgentID is *only* used for prebuilds. We pass it down when we want to rebuild a prebuilt workspace
2470+
// but not generate a new agent token. The provisionerdserver will retrieve this token and push it down to
2471+
// the provisioner (and ultimately to the `coder_agent` resource in the Terraform provider) where it will be
2472+
// reused. Context: the agent token is often used in immutable attributes of workspace resource (e.g. VM/container)
2473+
// to initialize the agent, so if that value changes it will necessitate a replacement of that resource, thus
2474+
// obviating the whole point of the prebuild.
2475+
RunningWorkspaceAgentID uuid.UUID`json:"running_workspace_agent_id"`
2476+
LogLevelstring`json:"log_level,omitempty"`
24692477
}
24702478

24712479
// TemplateVersionDryRunJob is the payload for the "template_version_dry_run" job type.

‎coderd/workspaces.go

Lines changed: 94 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"errors"
88
"fmt"
9+
"github.com/coder/coder/v2/coderd/prebuilds"
910
"net/http"
1011
"slices"
1112
"strconv"
@@ -635,34 +636,71 @@ func createWorkspace(
635636
provisionerJob*database.ProvisionerJob
636637
workspaceBuild*database.WorkspaceBuild
637638
provisionerDaemons []database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow
639+
640+
runningWorkspaceAgentID uuid.UUID
638641
)
642+
643+
prebuilds:= (*api.PrebuildsClaimer.Load()).(prebuilds.Claimer)
644+
639645
err=api.Database.InTx(func(db database.Store)error {
640-
now:=dbtime.Now()
641-
// Workspaces are created without any versions.
642-
minimumWorkspace,err:=db.InsertWorkspace(ctx, database.InsertWorkspaceParams{
643-
ID:uuid.New(),
644-
CreatedAt:now,
645-
UpdatedAt:now,
646-
OwnerID:owner.ID,
647-
OrganizationID:template.OrganizationID,
648-
TemplateID:template.ID,
649-
Name:req.Name,
650-
AutostartSchedule:dbAutostartSchedule,
651-
NextStartAt:nextStartAt,
652-
Ttl:dbTTL,
653-
// The workspaces page will sort by last used at, and it's useful to
654-
// have the newly created workspace at the top of the list!
655-
LastUsedAt:dbtime.Now(),
656-
AutomaticUpdates:dbAU,
657-
})
658-
iferr!=nil {
659-
returnxerrors.Errorf("insert workspace: %w",err)
646+
var (
647+
workspaceID uuid.UUID
648+
claimedWorkspace*database.Workspace
649+
)
650+
651+
// If a template preset was chosen, try claim a prebuild.
652+
ifreq.TemplateVersionPresetID!=uuid.Nil {
653+
// Try and claim an eligible prebuild, if available.
654+
claimedWorkspace,err=claimPrebuild(ctx,prebuilds,db,api.Logger,req,owner)
655+
iferr!=nil {
656+
returnxerrors.Errorf("claim prebuild: %w",err)
657+
}
658+
}
659+
660+
// No prebuild found; regular flow.
661+
ifclaimedWorkspace==nil {
662+
now:=dbtime.Now()
663+
// Workspaces are created without any versions.
664+
minimumWorkspace,err:=db.InsertWorkspace(ctx, database.InsertWorkspaceParams{
665+
ID:uuid.New(),
666+
CreatedAt:now,
667+
UpdatedAt:now,
668+
OwnerID:owner.ID,
669+
OrganizationID:template.OrganizationID,
670+
TemplateID:template.ID,
671+
Name:req.Name,
672+
AutostartSchedule:dbAutostartSchedule,
673+
NextStartAt:nextStartAt,
674+
Ttl:dbTTL,
675+
// The workspaces page will sort by last used at, and it's useful to
676+
// have the newly created workspace at the top of the list!
677+
LastUsedAt:dbtime.Now(),
678+
AutomaticUpdates:dbAU,
679+
})
680+
iferr!=nil {
681+
returnxerrors.Errorf("insert workspace: %w",err)
682+
}
683+
workspaceID=minimumWorkspace.ID
684+
}else {
685+
// Prebuild found!
686+
workspaceID=claimedWorkspace.ID
687+
initiatorID=prebuilds.Initiator()
688+
agents,err:=db.GetWorkspaceAgentsInLatestBuildByWorkspaceID(ctx,claimedWorkspace.ID)
689+
iferr!=nil {
690+
// TODO: comment about best-effort, workspace can be restarted if this fails...
691+
api.Logger.Error(ctx,"failed to retrieve running agents of claimed prebuilt workspace",
692+
slog.F("workspace_id",claimedWorkspace.ID),slog.Error(err))
693+
}
694+
iflen(agents)>=1 {
695+
// TODO: handle multiple agents
696+
runningWorkspaceAgentID=agents[0].ID
697+
}
660698
}
661699

662700
// We have to refetch the workspace for the joined in fields.
663701
// TODO: We can use WorkspaceTable for the builder to not require
664702
// this extra fetch.
665-
workspace,err=db.GetWorkspaceByID(ctx,minimumWorkspace.ID)
703+
workspace,err=db.GetWorkspaceByID(ctx,workspaceID)
666704
iferr!=nil {
667705
returnxerrors.Errorf("get workspace by ID: %w",err)
668706
}
@@ -672,10 +710,18 @@ func createWorkspace(
672710
Initiator(initiatorID).
673711
ActiveVersion().
674712
RichParameterValues(req.RichParameterValues).
675-
TemplateVersionPresetID(req.TemplateVersionPresetID)
713+
TemplateVersionPresetID(req.TemplateVersionPresetID).
714+
RunningWorkspaceAgentID(runningWorkspaceAgentID)
676715
ifreq.TemplateVersionID!=uuid.Nil {
677716
builder=builder.VersionID(req.TemplateVersionID)
678717
}
718+
ifreq.TemplateVersionPresetID!=uuid.Nil {
719+
builder=builder.TemplateVersionPresetID(req.TemplateVersionPresetID)
720+
}
721+
722+
ifclaimedWorkspace!=nil {
723+
builder=builder.MarkPrebuildClaimedBy(owner.ID)
724+
}
679725

680726
workspaceBuild,provisionerJob,provisionerDaemons,err=builder.Build(
681727
ctx,
@@ -839,6 +885,32 @@ func requestTemplate(ctx context.Context, rw http.ResponseWriter, req codersdk.C
839885
returntemplate,true
840886
}
841887

888+
funcclaimPrebuild(ctx context.Context,claimer prebuilds.Claimer,db database.Store,logger slog.Logger,req codersdk.CreateWorkspaceRequest,ownerworkspaceOwner) (*database.Workspace,error) {
889+
prebuildsCtx:=dbauthz.AsPrebuildsOrchestrator(ctx)
890+
891+
// TODO: do we need a timeout here?
892+
claimCtx,cancel:=context.WithTimeout(prebuildsCtx,time.Second*10)
893+
defercancel()
894+
895+
claimedID,err:=claimer.Claim(claimCtx,db,owner.ID,req.Name,req.TemplateVersionPresetID)
896+
iferr!=nil {
897+
// TODO: enhance this by clarifying whether this *specific* prebuild failed or whether there are none to claim.
898+
returnnil,xerrors.Errorf("claim prebuild: %w",err)
899+
}
900+
901+
// No prebuild available.
902+
ifclaimedID==nil {
903+
returnnil,nil
904+
}
905+
906+
lookup,err:=db.GetWorkspaceByID(prebuildsCtx,*claimedID)
907+
iferr!=nil {
908+
logger.Error(ctx,"unable to find claimed workspace by ID",slog.Error(err),slog.F("claimed_prebuild_id", (*claimedID).String()))
909+
returnnil,xerrors.Errorf("find claimed workspace by ID %q: %w", (*claimedID).String(),err)
910+
}
911+
return&lookup,err
912+
}
913+
842914
func (api*API)notifyWorkspaceCreated(
843915
ctx context.Context,
844916
receiverID uuid.UUID,

‎coderd/wsbuilder/wsbuilder.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ type Builder struct {
7575
parameterValues*[]string
7676
templateVersionPresetParameterValues []database.TemplateVersionPresetParameter
7777

78-
prebuildbool
78+
prebuildbool
79+
prebuildClaimedBy uuid.UUID
80+
runningWorkspaceAgentID uuid.UUID
7981

8082
verifyNoLegacyParametersOncebool
8183
}
@@ -178,6 +180,19 @@ func (b Builder) MarkPrebuild() Builder {
178180
returnb
179181
}
180182

183+
func (bBuilder)MarkPrebuildClaimedBy(userID uuid.UUID)Builder {
184+
// nolint: revive
185+
b.prebuildClaimedBy=userID
186+
returnb
187+
}
188+
189+
// RunningWorkspaceAgentID is only used for prebuilds; see the associated field in `provisionerdserver.WorkspaceProvisionJob`.
190+
func (bBuilder)RunningWorkspaceAgentID(id uuid.UUID)Builder {
191+
// nolint: revive
192+
b.runningWorkspaceAgentID=id
193+
returnb
194+
}
195+
181196
// SetLastWorkspaceBuildInTx prepopulates the Builder's cache with the last workspace build. This allows us
182197
// to avoid a repeated database query when the Builder's caller also needs the workspace build, e.g. auto-start &
183198
// auto-stop.
@@ -309,9 +324,11 @@ func (b *Builder) buildTx(authFunc func(action policy.Action, object rbac.Object
309324

310325
workspaceBuildID:=uuid.New()
311326
input,err:=json.Marshal(provisionerdserver.WorkspaceProvisionJob{
312-
WorkspaceBuildID:workspaceBuildID,
313-
LogLevel:b.logLevel,
314-
IsPrebuild:b.prebuild,
327+
WorkspaceBuildID:workspaceBuildID,
328+
LogLevel:b.logLevel,
329+
IsPrebuild:b.prebuild,
330+
PrebuildClaimedByUser:b.prebuildClaimedBy,
331+
RunningWorkspaceAgentID:b.runningWorkspaceAgentID,
315332
})
316333
iferr!=nil {
317334
returnnil,nil,nil,BuildError{
@@ -624,6 +641,11 @@ func (b *Builder) findNewBuildParameterValue(name string) *codersdk.WorkspaceBui
624641
}
625642

626643
func (b*Builder)getLastBuildParameters() ([]database.WorkspaceBuildParameter,error) {
644+
// TODO: exclude preset params from this list instead of returning nothing?
645+
ifb.prebuildClaimedBy!=uuid.Nil {
646+
returnnil,nil
647+
}
648+
627649
ifb.lastBuildParameters!=nil {
628650
return*b.lastBuildParameters,nil
629651
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp