feat: claim prebuilds based on workspace parameters instead of preset id (#19279)

Closes https://github.com/coder/coder/issues/18356.

This change finds and selects a matching preset if one was not chosen
during workspace creation. This solidifies the relationship between
presets and parameters.

When a workspace is created without in explicitly chosen preset, it will
now still be eligible to claim a prebuilt workspace if one is available.
This commit is contained in:
Sas Swart
2025-08-20 11:02:53 +02:00
committed by GitHub
parent 5e84d257b7
commit f9a6adc704
15 changed files with 736 additions and 37 deletions
+15 -6
View File
@@ -15,6 +15,7 @@ import (
"github.com/coder/coder/v2/coderd/dynamicparameters"
"github.com/coder/coder/v2/coderd/files"
"github.com/coder/coder/v2/coderd/prebuilds"
"github.com/coder/coder/v2/coderd/rbac/policy"
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/provisioner/terraform/tfparse"
@@ -442,6 +443,20 @@ func (b *Builder) buildTx(authFunc func(action policy.Action, object rbac.Object
var workspaceBuild database.WorkspaceBuild
err = b.store.InTx(func(store database.Store) error {
names, values, err := b.getParameters()
if err != nil {
// getParameters already wraps errors in BuildError
return err
}
if b.templateVersionPresetID == uuid.Nil {
presetID, err := prebuilds.FindMatchingPresetID(b.ctx, b.store, templateVersionID, names, values)
if err != nil {
return BuildError{http.StatusInternalServerError, "find matching preset", err}
}
b.templateVersionPresetID = presetID
}
err = store.InsertWorkspaceBuild(b.ctx, database.InsertWorkspaceBuildParams{
ID: workspaceBuildID,
CreatedAt: now,
@@ -473,12 +488,6 @@ func (b *Builder) buildTx(authFunc func(action policy.Action, object rbac.Object
return BuildError{code, "insert workspace build", err}
}
names, values, err := b.getParameters()
if err != nil {
// getParameters already wraps errors in BuildError
return err
}
err = store.InsertWorkspaceBuildParameters(b.ctx, database.InsertWorkspaceBuildParametersParams{
WorkspaceBuildID: workspaceBuildID,
Name: names,
+22
View File
@@ -82,6 +82,7 @@ func TestBuilder_NoOptions(t *testing.T) {
}),
withInTx,
expectFindMatchingPresetID(uuid.Nil, sql.ErrNoRows),
expectBuild(func(bld database.InsertWorkspaceBuildParams) {
asrt.Equal(inactiveVersionID, bld.TemplateVersionID)
asrt.Equal(workspaceID, bld.WorkspaceID)
@@ -132,6 +133,7 @@ func TestBuilder_Initiator(t *testing.T) {
asrt.Equal(otherUserID, job.InitiatorID)
}),
withInTx,
expectFindMatchingPresetID(uuid.Nil, sql.ErrNoRows),
expectBuild(func(bld database.InsertWorkspaceBuildParams) {
asrt.Equal(otherUserID, bld.InitiatorID)
}),
@@ -180,6 +182,7 @@ func TestBuilder_Baggage(t *testing.T) {
asrt.Contains(string(job.TraceMetadata.RawMessage), "ip=127.0.0.1")
}),
withInTx,
expectFindMatchingPresetID(uuid.Nil, sql.ErrNoRows),
expectBuild(func(bld database.InsertWorkspaceBuildParams) {
}),
expectBuildParameters(func(params database.InsertWorkspaceBuildParametersParams) {
@@ -219,6 +222,7 @@ func TestBuilder_Reason(t *testing.T) {
expectProvisionerJob(func(_ database.InsertProvisionerJobParams) {
}),
withInTx,
expectFindMatchingPresetID(uuid.Nil, sql.ErrNoRows),
expectBuild(func(bld database.InsertWorkspaceBuildParams) {
asrt.Equal(database.BuildReasonAutostart, bld.Reason)
}),
@@ -261,6 +265,7 @@ func TestBuilder_ActiveVersion(t *testing.T) {
}),
withInTx,
expectFindMatchingPresetID(uuid.Nil, sql.ErrNoRows),
expectBuild(func(bld database.InsertWorkspaceBuildParams) {
asrt.Equal(activeVersionID, bld.TemplateVersionID)
// no previous build...
@@ -386,6 +391,7 @@ func TestWorkspaceBuildWithTags(t *testing.T) {
expectBuildParameters(func(_ database.InsertWorkspaceBuildParametersParams) {
}),
withBuild,
expectFindMatchingPresetID(uuid.Nil, sql.ErrNoRows),
)
fc := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
@@ -470,6 +476,7 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
}
}),
withBuild,
expectFindMatchingPresetID(uuid.Nil, sql.ErrNoRows),
)
fc := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
@@ -519,6 +526,7 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
}
}),
withBuild,
expectFindMatchingPresetID(uuid.Nil, sql.ErrNoRows),
)
fc := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
@@ -661,6 +669,7 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
}
}),
withBuild,
expectFindMatchingPresetID(uuid.Nil, sql.ErrNoRows),
)
fc := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
@@ -713,6 +722,7 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
withProvisionerDaemons([]database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow{}),
// Outputs
expectFindMatchingPresetID(uuid.Nil, sql.ErrNoRows),
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {}),
withInTx,
expectBuild(func(bld database.InsertWorkspaceBuildParams) {}),
@@ -775,6 +785,7 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
withProvisionerDaemons([]database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow{}),
// Outputs
expectFindMatchingPresetID(uuid.Nil, sql.ErrNoRows),
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {}),
withInTx,
expectBuild(func(bld database.InsertWorkspaceBuildParams) {}),
@@ -906,6 +917,7 @@ func TestWorkspaceBuildDeleteOrphan(t *testing.T) {
}),
withInTx,
expectFindMatchingPresetID(uuid.Nil, sql.ErrNoRows),
expectBuild(func(bld database.InsertWorkspaceBuildParams) {
asrt.Equal(inactiveVersionID, bld.TemplateVersionID)
asrt.Equal(workspaceID, bld.WorkspaceID)
@@ -968,6 +980,7 @@ func TestWorkspaceBuildDeleteOrphan(t *testing.T) {
}),
withInTx,
expectFindMatchingPresetID(uuid.Nil, sql.ErrNoRows),
expectBuild(func(bld database.InsertWorkspaceBuildParams) {
asrt.Equal(inactiveVersionID, bld.TemplateVersionID)
asrt.Equal(workspaceID, bld.WorkspaceID)
@@ -1041,6 +1054,7 @@ func TestWorkspaceBuildUsageChecker(t *testing.T) {
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {}),
withInTx,
expectFindMatchingPresetID(uuid.Nil, sql.ErrNoRows),
expectBuild(func(bld database.InsertWorkspaceBuildParams) {}),
withBuild,
expectBuildParameters(func(params database.InsertWorkspaceBuildParametersParams) {}),
@@ -1485,6 +1499,14 @@ func withProvisionerDaemons(provisionerDaemons []database.GetEligibleProvisioner
}
}
func expectFindMatchingPresetID(id uuid.UUID, err error) func(mTx *dbmock.MockStore) {
return func(mTx *dbmock.MockStore) {
mTx.EXPECT().FindMatchingPresetID(gomock.Any(), gomock.Any()).
Times(1).
Return(id, err)
}
}
type fakeUsageChecker struct {
checkBuildUsageFunc func(ctx context.Context, store database.Store, templateVersion *database.TemplateVersion) (wsbuilder.UsageCheckResponse, error)
}