Skip to content

Commit 8c45183

Browse files
committed
Wrap prebuild creation and destruction in transactions
If a prebuild fails to provision due to (e.g.) an invalid preset param value, it will create the workspace but not the build, leading to an error sql: Scan error on column index 31, name "latest_build_transition": unsupported scan type for WorkspaceTransition: <nil> Signed-off-by: Danny Kopping <dannykopping@gmail.com>
1 parent e3cecab commit 8c45183

File tree

1 file changed

+52
-42
lines changed

1 file changed

+52
-42
lines changed

enterprise/coderd/prebuilds/reconcile.go

+52-42
Original file line numberDiff line numberDiff line change
@@ -323,59 +323,69 @@ func (c *storeReconciler) createPrebuild(ctx context.Context, prebuildID uuid.UU
323323
return xerrors.Errorf("failed to generate unique prebuild ID: %w", err)
324324
}
325325

326-
template, err := c.store.GetTemplateByID(ctx, templateID)
327-
if err != nil {
328-
return xerrors.Errorf("failed to get template: %w", err)
329-
}
326+
return c.store.InTx(func(db database.Store) error {
327+
template, err := db.GetTemplateByID(ctx, templateID)
328+
if err != nil {
329+
return xerrors.Errorf("failed to get template: %w", err)
330+
}
330331

331-
now := dbtime.Now()
332-
// Workspaces are created without any versions.
333-
minimumWorkspace, err := c.store.InsertWorkspace(ctx, database.InsertWorkspaceParams{
334-
ID: prebuildID,
335-
CreatedAt: now,
336-
UpdatedAt: now,
337-
OwnerID: OwnerID,
338-
OrganizationID: template.OrganizationID,
339-
TemplateID: template.ID,
340-
Name: name,
341-
LastUsedAt: dbtime.Now(),
342-
AutomaticUpdates: database.AutomaticUpdatesNever,
343-
})
344-
if err != nil {
345-
return xerrors.Errorf("insert workspace: %w", err)
346-
}
332+
now := dbtime.Now()
333+
334+
minimumWorkspace, err := db.InsertWorkspace(ctx, database.InsertWorkspaceParams{
335+
ID: prebuildID,
336+
CreatedAt: now,
337+
UpdatedAt: now,
338+
OwnerID: OwnerID,
339+
OrganizationID: template.OrganizationID,
340+
TemplateID: template.ID,
341+
Name: name,
342+
LastUsedAt: dbtime.Now(),
343+
AutomaticUpdates: database.AutomaticUpdatesNever,
344+
})
345+
if err != nil {
346+
return xerrors.Errorf("insert workspace: %w", err)
347+
}
347348

348-
// We have to refetch the workspace for the joined in fields.
349-
workspace, err := c.store.GetWorkspaceByID(ctx, minimumWorkspace.ID)
350-
if err != nil {
351-
return xerrors.Errorf("get workspace by ID: %w", err)
352-
}
349+
// We have to refetch the workspace for the joined in fields.
350+
workspace, err := db.GetWorkspaceByID(ctx, minimumWorkspace.ID)
351+
if err != nil {
352+
return xerrors.Errorf("get workspace by ID: %w", err)
353+
}
353354

354-
c.logger.Info(ctx, "attempting to create prebuild", slog.F("name", name),
355-
slog.F("workspace_id", prebuildID.String()), slog.F("preset_id", presetID.String()))
355+
c.logger.Info(ctx, "attempting to create prebuild", slog.F("name", name),
356+
slog.F("workspace_id", prebuildID.String()), slog.F("preset_id", presetID.String()))
356357

357-
return c.provision(ctx, prebuildID, template, presetID, database.WorkspaceTransitionStart, workspace)
358+
return c.provision(ctx, db, prebuildID, template, presetID, database.WorkspaceTransitionStart, workspace)
359+
}, &database.TxOptions{
360+
Isolation: sql.LevelRepeatableRead,
361+
ReadOnly: false,
362+
})
358363
}
359364

360365
func (c *storeReconciler) deletePrebuild(ctx context.Context, prebuildID uuid.UUID, templateID uuid.UUID, presetID uuid.UUID) error {
361-
workspace, err := c.store.GetWorkspaceByID(ctx, prebuildID)
362-
if err != nil {
363-
return xerrors.Errorf("get workspace by ID: %w", err)
364-
}
366+
return c.store.InTx(func(db database.Store) error {
367+
workspace, err := db.GetWorkspaceByID(ctx, prebuildID)
368+
if err != nil {
369+
return xerrors.Errorf("get workspace by ID: %w", err)
370+
}
365371

366-
template, err := c.store.GetTemplateByID(ctx, templateID)
367-
if err != nil {
368-
return xerrors.Errorf("failed to get template: %w", err)
369-
}
372+
template, err := db.GetTemplateByID(ctx, templateID)
373+
if err != nil {
374+
return xerrors.Errorf("failed to get template: %w", err)
375+
}
370376

371-
c.logger.Info(ctx, "attempting to delete prebuild",
372-
slog.F("workspace_id", prebuildID.String()), slog.F("preset_id", presetID.String()))
377+
c.logger.Info(ctx, "attempting to delete prebuild",
378+
slog.F("workspace_id", prebuildID.String()), slog.F("preset_id", presetID.String()))
373379

374-
return c.provision(ctx, prebuildID, template, presetID, database.WorkspaceTransitionDelete, workspace)
380+
return c.provision(ctx, db, prebuildID, template, presetID, database.WorkspaceTransitionDelete, workspace)
381+
}, &database.TxOptions{
382+
Isolation: sql.LevelRepeatableRead,
383+
ReadOnly: false,
384+
})
375385
}
376386

377-
func (c *storeReconciler) provision(ctx context.Context, prebuildID uuid.UUID, template database.Template, presetID uuid.UUID, transition database.WorkspaceTransition, workspace database.Workspace) error {
378-
tvp, err := c.store.GetPresetParametersByTemplateVersionID(ctx, template.ActiveVersionID)
387+
func (c *storeReconciler) provision(ctx context.Context, db database.Store, prebuildID uuid.UUID, template database.Template, presetID uuid.UUID, transition database.WorkspaceTransition, workspace database.Workspace) error {
388+
tvp, err := db.GetPresetParametersByTemplateVersionID(ctx, template.ActiveVersionID)
379389
if err != nil {
380390
return xerrors.Errorf("fetch preset details: %w", err)
381391
}
@@ -409,7 +419,7 @@ func (c *storeReconciler) provision(ctx context.Context, prebuildID uuid.UUID, t
409419

410420
_, provisionerJob, _, err := builder.Build(
411421
ctx,
412-
c.store,
422+
db,
413423
func(action policy.Action, object rbac.Objecter) bool {
414424
return true // TODO: harden?
415425
},

0 commit comments

Comments
 (0)