@@ -18,6 +18,7 @@ import (
1818 "golang.org/x/xerrors"
1919
2020 "cdr.dev/slog"
21+
2122 "github.com/coder/coder/v2/agent/proto"
2223 "github.com/coder/coder/v2/coderd/audit"
2324 "github.com/coder/coder/v2/coderd/database"
@@ -28,6 +29,7 @@ import (
2829 "github.com/coder/coder/v2/coderd/httpapi"
2930 "github.com/coder/coder/v2/coderd/httpmw"
3031 "github.com/coder/coder/v2/coderd/notifications"
32+ "github.com/coder/coder/v2/coderd/prebuilds"
3133 "github.com/coder/coder/v2/coderd/rbac"
3234 "github.com/coder/coder/v2/coderd/rbac/policy"
3335 "github.com/coder/coder/v2/coderd/schedule"
@@ -636,33 +638,57 @@ func createWorkspace(
636638 workspaceBuild * database.WorkspaceBuild
637639 provisionerDaemons []database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow
638640 )
641+
639642 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 )
643+ var (
644+ workspaceID uuid.UUID
645+ claimedWorkspace * database.Workspace
646+ prebuildsClaimer = * api .PrebuildsClaimer .Load ()
647+ )
648+
649+ // If a template preset was chosen, try claim a prebuilt workspace.
650+ if req .TemplateVersionPresetID != uuid .Nil {
651+ // Try and claim an eligible prebuild, if available.
652+ claimedWorkspace , err = claimPrebuild (ctx , prebuildsClaimer , db , api .Logger , req , owner )
653+ if err != nil && ! errors .Is (err , prebuilds .ErrNoClaimablePrebuiltWorkspaces ) {
654+ return xerrors .Errorf ("claim prebuild: %w" , err )
655+ }
656+ }
657+
658+ // No prebuild found; regular flow.
659+ if claimedWorkspace == nil {
660+ now := dbtime .Now ()
661+ // Workspaces are created without any versions.
662+ minimumWorkspace , err := db .InsertWorkspace (ctx , database.InsertWorkspaceParams {
663+ ID : uuid .New (),
664+ CreatedAt : now ,
665+ UpdatedAt : now ,
666+ OwnerID : owner .ID ,
667+ OrganizationID : template .OrganizationID ,
668+ TemplateID : template .ID ,
669+ Name : req .Name ,
670+ AutostartSchedule : dbAutostartSchedule ,
671+ NextStartAt : nextStartAt ,
672+ Ttl : dbTTL ,
673+ // The workspaces page will sort by last used at, and it's useful to
674+ // have the newly created workspace at the top of the list!
675+ LastUsedAt : dbtime .Now (),
676+ AutomaticUpdates : dbAU ,
677+ })
678+ if err != nil {
679+ return xerrors .Errorf ("insert workspace: %w" , err )
680+ }
681+ workspaceID = minimumWorkspace .ID
682+ } else {
683+ // Prebuild found!
684+ workspaceID = claimedWorkspace .ID
685+ initiatorID = prebuildsClaimer .Initiator ()
660686 }
661687
662688 // We have to refetch the workspace for the joined in fields.
663689 // TODO: We can use WorkspaceTable for the builder to not require
664690 // this extra fetch.
665- workspace , err = db .GetWorkspaceByID (ctx , minimumWorkspace . ID )
691+ workspace , err = db .GetWorkspaceByID (ctx , workspaceID )
666692 if err != nil {
667693 return xerrors .Errorf ("get workspace by ID: %w" , err )
668694 }
@@ -676,6 +702,13 @@ func createWorkspace(
676702 if req .TemplateVersionID != uuid .Nil {
677703 builder = builder .VersionID (req .TemplateVersionID )
678704 }
705+ if req .TemplateVersionPresetID != uuid .Nil {
706+ builder = builder .TemplateVersionPresetID (req .TemplateVersionPresetID )
707+ }
708+ if claimedWorkspace != nil {
709+ builder = builder .MarkPrebuildClaimedBy (owner .ID )
710+ }
711+
679712 if req .EnableDynamicParameters && api .Experiments .Enabled (codersdk .ExperimentDynamicParameters ) {
680713 builder = builder .UsingDynamicParameters ()
681714 }
@@ -842,6 +875,21 @@ func requestTemplate(ctx context.Context, rw http.ResponseWriter, req codersdk.C
842875 return template , true
843876}
844877
878+ func claimPrebuild (ctx context.Context , claimer prebuilds.Claimer , db database.Store , logger slog.Logger , req codersdk.CreateWorkspaceRequest , owner workspaceOwner ) (* database.Workspace , error ) {
879+ claimedID , err := claimer .Claim (ctx , owner .ID , req .Name , req .TemplateVersionPresetID )
880+ if err != nil {
881+ // TODO: enhance this by clarifying whether this *specific* prebuild failed or whether there are none to claim.
882+ return nil , xerrors .Errorf ("claim prebuild: %w" , err )
883+ }
884+
885+ lookup , err := db .GetWorkspaceByID (ctx , * claimedID )
886+ if err != nil {
887+ logger .Error (ctx , "unable to find claimed workspace by ID" , slog .Error (err ), slog .F ("claimed_prebuild_id" , claimedID .String ()))
888+ return nil , xerrors .Errorf ("find claimed workspace by ID %q: %w" , claimedID .String (), err )
889+ }
890+ return & lookup , nil
891+ }
892+
845893func (api * API ) notifyWorkspaceCreated (
846894 ctx context.Context ,
847895 receiverID uuid.UUID ,
0 commit comments