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- if err != nil {
659- return xerrors .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+ if req .TemplateVersionPresetID != uuid .Nil {
653+ // Try and claim an eligible prebuild, if available.
654+ claimedWorkspace , err = claimPrebuild (ctx , prebuilds , db , api .Logger , req , owner )
655+ if err != nil {
656+ return xerrors .Errorf ("claim prebuild: %w" , err )
657+ }
658+ }
659+
660+ // No prebuild found; regular flow.
661+ if claimedWorkspace == 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+ if err != nil {
681+ return xerrors .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+ if err != 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+ if len (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 if err != nil {
667705 return xerrors .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 if req .TemplateVersionID != uuid .Nil {
677716 builder = builder .VersionID (req .TemplateVersionID )
678717 }
718+ if req .TemplateVersionPresetID != uuid .Nil {
719+ builder = builder .TemplateVersionPresetID (req .TemplateVersionPresetID )
720+ }
721+
722+ if claimedWorkspace != 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 return template , true
840886}
841887
888+ func claimPrebuild (ctx context.Context , claimer prebuilds.Claimer , db database.Store , logger slog.Logger , req codersdk.CreateWorkspaceRequest , owner workspaceOwner ) (* 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+ defer cancel ()
894+
895+ claimedID , err := claimer .Claim (claimCtx , db , owner .ID , req .Name , req .TemplateVersionPresetID )
896+ if err != nil {
897+ // TODO: enhance this by clarifying whether this *specific* prebuild failed or whether there are none to claim.
898+ return nil , xerrors .Errorf ("claim prebuild: %w" , err )
899+ }
900+
901+ // No prebuild available.
902+ if claimedID == nil {
903+ return nil , nil
904+ }
905+
906+ lookup , err := db .GetWorkspaceByID (prebuildsCtx , * claimedID )
907+ if err != nil {
908+ logger .Error (ctx , "unable to find claimed workspace by ID" , slog .Error (err ), slog .F ("claimed_prebuild_id" , (* claimedID ).String ()))
909+ return nil , xerrors .Errorf ("find claimed workspace by ID %q: %w" , (* claimedID ).String (), err )
910+ }
911+ return & lookup , err
912+ }
913+
842914func (api * API ) notifyWorkspaceCreated (
843915 ctx context.Context ,
844916 receiverID uuid.UUID ,
0 commit comments