@@ -18,6 +18,7 @@ import (
18
18
"golang.org/x/xerrors"
19
19
20
20
"cdr.dev/slog"
21
+
21
22
"github.com/coder/coder/v2/agent/proto"
22
23
"github.com/coder/coder/v2/coderd/audit"
23
24
"github.com/coder/coder/v2/coderd/database"
@@ -28,6 +29,7 @@ import (
28
29
"github.com/coder/coder/v2/coderd/httpapi"
29
30
"github.com/coder/coder/v2/coderd/httpmw"
30
31
"github.com/coder/coder/v2/coderd/notifications"
32
+ "github.com/coder/coder/v2/coderd/prebuilds"
31
33
"github.com/coder/coder/v2/coderd/rbac"
32
34
"github.com/coder/coder/v2/coderd/rbac/policy"
33
35
"github.com/coder/coder/v2/coderd/schedule"
@@ -636,33 +638,57 @@ func createWorkspace(
636
638
workspaceBuild * database.WorkspaceBuild
637
639
provisionerDaemons []database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow
638
640
)
641
+
639
642
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 ()
660
686
}
661
687
662
688
// We have to refetch the workspace for the joined in fields.
663
689
// TODO: We can use WorkspaceTable for the builder to not require
664
690
// this extra fetch.
665
- workspace , err = db .GetWorkspaceByID (ctx , minimumWorkspace . ID )
691
+ workspace , err = db .GetWorkspaceByID (ctx , workspaceID )
666
692
if err != nil {
667
693
return xerrors .Errorf ("get workspace by ID: %w" , err )
668
694
}
@@ -676,6 +702,13 @@ func createWorkspace(
676
702
if req .TemplateVersionID != uuid .Nil {
677
703
builder = builder .VersionID (req .TemplateVersionID )
678
704
}
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
+
679
712
if req .EnableDynamicParameters && api .Experiments .Enabled (codersdk .ExperimentDynamicParameters ) {
680
713
builder = builder .UsingDynamicParameters ()
681
714
}
@@ -842,6 +875,21 @@ func requestTemplate(ctx context.Context, rw http.ResponseWriter, req codersdk.C
842
875
return template , true
843
876
}
844
877
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
+
845
893
func (api * API ) notifyWorkspaceCreated (
846
894
ctx context.Context ,
847
895
receiverID uuid.UUID ,
0 commit comments