Skip to content

Commit 6415069

Browse files
committed
fix: prebuilds lifecycle parameters on creation and claim
1 parent afb54f6 commit 6415069

File tree

12 files changed

+877
-501
lines changed

12 files changed

+877
-501
lines changed

cli/schedule.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,13 @@ func (r *RootCmd) scheduleStart() *serpent.Command {
157157
return err
158158
}
159159

160+
// Autostart configuration is not supported for prebuilt workspaces.
161+
// Prebuild lifecycle is managed by the reconciliation loop, with scheduling behavior
162+
// defined per preset at the template level, not per workspace.
163+
if workspace.IsPrebuild {
164+
return xerrors.Errorf("autostart configuration is not supported for prebuilt workspaces")
165+
}
166+
160167
var schedStr *string
161168
if inv.Args[1] != "manual" {
162169
sched, err := parseCLISchedule(inv.Args[1:]...)
@@ -205,6 +212,13 @@ func (r *RootCmd) scheduleStop() *serpent.Command {
205212
return err
206213
}
207214

215+
// Autostop configuration is not supported for prebuilt workspaces.
216+
// Prebuild lifecycle is managed by the reconciliation loop, with scheduling behavior
217+
// defined per preset at the template level, not per workspace.
218+
if workspace.IsPrebuild {
219+
return xerrors.Errorf("autostop configuration is not supported for prebuilt workspaces")
220+
}
221+
208222
var durMillis *int64
209223
if inv.Args[1] != "manual" {
210224
dur, err := parseDuration(inv.Args[1])

coderd/database/querier.go

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

Lines changed: 38 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/activitybump.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
--
66
-- Max deadline is respected, and the deadline will never be bumped past it.
77
-- The deadline will never decrease.
8+
-- NOTE: This query should only be called for regular user workspaces.
9+
-- Prebuilds are managed by the reconciliation loop and not subject to activity bumping.
810
-- name: ActivityBumpWorkspace :exec
911
WITH latest AS (
1012
SELECT

coderd/database/queries/prebuilds.sql

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@
22
UPDATE workspaces w
33
SET owner_id = @new_user_id::uuid,
44
name = @new_name::text,
5-
updated_at = NOW()
5+
updated_at = NOW(),
6+
-- Update last_used_at during claim to ensure the claimed workspace is treated as recently used.
7+
-- This avoids unintended dormancy caused by prebuilds having stale usage timestamps.
8+
last_used_at = NOW(),
9+
-- Clear dormant and deletion timestamps as a safeguard to ensure a clean lifecycle state after claim.
10+
-- These fields should not be set on prebuilds, but we defensively reset them here to prevent
11+
-- accidental dormancy or deletion by the lifecycle executor.
12+
dormant_at = NULL,
13+
deleting_at = NULL
614
WHERE w.id IN (
715
SELECT p.id
816
FROM workspace_prebuilds p

coderd/database/queries/workspacebuilds.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ WHERE
135135
id = $1;
136136

137137
-- name: UpdateWorkspaceBuildDeadlineByID :exec
138+
-- NOTE: This query should only be called for regular user workspaces.
139+
-- Prebuilds are managed by the reconciliation loop, not the lifecycle
140+
-- executor which handles deadline and max_deadline.
138141
UPDATE
139142
workspace_builds
140143
SET

coderd/database/queries/workspaces.sql

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,9 @@ WHERE
512512
RETURNING *;
513513

514514
-- name: UpdateWorkspaceAutostart :exec
515+
-- NOTE: This query should only be called for regular user workspaces.
516+
-- Prebuilds are managed by the reconciliation loop, not the lifecycle
517+
-- executor which handles autostart_schedule and next_start_at.
515518
UPDATE
516519
workspaces
517520
SET
@@ -521,6 +524,9 @@ WHERE
521524
id = $1;
522525

523526
-- name: UpdateWorkspaceNextStartAt :exec
527+
-- NOTE: This query should only be called for regular user workspaces.
528+
-- Prebuilds are managed by the reconciliation loop, not the lifecycle
529+
-- executor which handles next_start_at.
524530
UPDATE
525531
workspaces
526532
SET
@@ -545,6 +551,9 @@ WHERE
545551
workspaces.id = batch.id;
546552

547553
-- name: UpdateWorkspaceTTL :exec
554+
-- NOTE: This query should only be called for regular user workspaces.
555+
-- Prebuilds are managed by the reconciliation loop, not the lifecycle
556+
-- executor which handles regular workspace's TTL.
548557
UPDATE
549558
workspaces
550559
SET
@@ -554,11 +563,15 @@ WHERE
554563

555564
-- name: UpdateWorkspacesTTLByTemplateID :exec
556565
UPDATE
557-
workspaces
566+
workspaces
558567
SET
559-
ttl = $2
568+
ttl = $2
560569
WHERE
561-
template_id = $1;
570+
template_id = $1
571+
-- Prebuilt workspaces (identified by having the prebuilds system user as owner_id)
572+
-- should not have their TTL updated, as they are handled by the prebuilds
573+
-- reconciliation loop.
574+
AND workspaces.owner_id != 'c42fdf75-3097-471c-8c33-fb52454d81c0'::UUID;
562575

563576
-- name: UpdateWorkspaceLastUsedAt :exec
564577
UPDATE
@@ -768,6 +781,9 @@ WHERE
768781
AND workspaces.owner_id != 'c42fdf75-3097-471c-8c33-fb52454d81c0'::UUID;
769782

770783
-- name: UpdateWorkspaceDormantDeletingAt :one
784+
-- NOTE: This query should only be called for regular user workspaces.
785+
-- Prebuilds are managed by the reconciliation loop, not the lifecycle
786+
-- executor which handles dormant_at and deleting_at.
771787
UPDATE
772788
workspaces
773789
SET
@@ -805,8 +821,11 @@ SET
805821
dormant_at = CASE WHEN @dormant_at::timestamptz > '0001-01-01 00:00:00+00'::timestamptz THEN @dormant_at::timestamptz ELSE dormant_at END
806822
WHERE
807823
template_id = @template_id
808-
AND
809-
dormant_at IS NOT NULL
824+
AND dormant_at IS NOT NULL
825+
-- Prebuilt workspaces (identified by having the prebuilds system user as owner_id)
826+
-- should not have their dormant or deleting at set, as these are handled by the
827+
-- prebuilds reconciliation loop.
828+
AND workspaces.owner_id != 'c42fdf75-3097-471c-8c33-fb52454d81c0'::UUID
810829
RETURNING *;
811830

812831
-- name: UpdateTemplateWorkspacesLastUsedAt :exec

coderd/provisionerdserver/provisionerdserver.go

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,11 +1183,18 @@ func (s *server) FailJob(ctx context.Context, failJob *proto.FailedJob) (*proto.
11831183
if err != nil {
11841184
return xerrors.Errorf("update workspace build state: %w", err)
11851185
}
1186+
1187+
deadline := build.Deadline
1188+
maxDeadline := build.MaxDeadline
1189+
if workspace.IsPrebuild() {
1190+
deadline = time.Time{}
1191+
maxDeadline = time.Time{}
1192+
}
11861193
err = db.UpdateWorkspaceBuildDeadlineByID(ctx, database.UpdateWorkspaceBuildDeadlineByIDParams{
11871194
ID: input.WorkspaceBuildID,
11881195
UpdatedAt: s.timeNow(),
1189-
Deadline: build.Deadline,
1190-
MaxDeadline: build.MaxDeadline,
1196+
Deadline: deadline,
1197+
MaxDeadline: maxDeadline,
11911198
})
11921199
if err != nil {
11931200
return xerrors.Errorf("update workspace build deadline: %w", err)
@@ -1860,38 +1867,47 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro
18601867
return getWorkspaceError
18611868
}
18621869

1863-
templateScheduleStore := *s.TemplateScheduleStore.Load()
1870+
// Prebuilt workspaces must not have Deadline or MaxDeadline set,
1871+
// as they are managed by the prebuild reconciliation loop, not the lifecycle executor
1872+
deadline := time.Time{}
1873+
maxDeadline := time.Time{}
18641874

1865-
autoStop, err := schedule.CalculateAutostop(ctx, schedule.CalculateAutostopParams{
1866-
Database: db,
1867-
TemplateScheduleStore: templateScheduleStore,
1868-
UserQuietHoursScheduleStore: *s.UserQuietHoursScheduleStore.Load(),
1869-
// `now` is used below to set the build completion time.
1870-
WorkspaceBuildCompletedAt: now,
1871-
Workspace: workspace.WorkspaceTable(),
1872-
// Allowed to be the empty string.
1873-
WorkspaceAutostart: workspace.AutostartSchedule.String,
1874-
})
1875-
if err != nil {
1876-
return xerrors.Errorf("calculate auto stop: %w", err)
1877-
}
1875+
if !workspace.IsPrebuild() {
1876+
templateScheduleStore := *s.TemplateScheduleStore.Load()
18781877

1879-
if workspace.AutostartSchedule.Valid {
1880-
templateScheduleOptions, err := templateScheduleStore.Get(ctx, db, workspace.TemplateID)
1878+
autoStop, err := schedule.CalculateAutostop(ctx, schedule.CalculateAutostopParams{
1879+
Database: db,
1880+
TemplateScheduleStore: templateScheduleStore,
1881+
UserQuietHoursScheduleStore: *s.UserQuietHoursScheduleStore.Load(),
1882+
// `now` is used below to set the build completion time.
1883+
WorkspaceBuildCompletedAt: now,
1884+
Workspace: workspace.WorkspaceTable(),
1885+
// Allowed to be the empty string.
1886+
WorkspaceAutostart: workspace.AutostartSchedule.String,
1887+
})
18811888
if err != nil {
1882-
return xerrors.Errorf("get template schedule options: %w", err)
1889+
return xerrors.Errorf("calculate auto stop: %w", err)
18831890
}
18841891

1885-
nextStartAt, err := schedule.NextAllowedAutostart(now, workspace.AutostartSchedule.String, templateScheduleOptions)
1886-
if err == nil {
1887-
err = db.UpdateWorkspaceNextStartAt(ctx, database.UpdateWorkspaceNextStartAtParams{
1888-
ID: workspace.ID,
1889-
NextStartAt: sql.NullTime{Valid: true, Time: nextStartAt.UTC()},
1890-
})
1892+
if workspace.AutostartSchedule.Valid {
1893+
templateScheduleOptions, err := templateScheduleStore.Get(ctx, db, workspace.TemplateID)
18911894
if err != nil {
1892-
return xerrors.Errorf("update workspace next start at: %w", err)
1895+
return xerrors.Errorf("get template schedule options: %w", err)
1896+
}
1897+
1898+
nextStartAt, err := schedule.NextAllowedAutostart(now, workspace.AutostartSchedule.String, templateScheduleOptions)
1899+
if err == nil {
1900+
err = db.UpdateWorkspaceNextStartAt(ctx, database.UpdateWorkspaceNextStartAtParams{
1901+
ID: workspace.ID,
1902+
NextStartAt: sql.NullTime{Valid: true, Time: nextStartAt.UTC()},
1903+
})
1904+
if err != nil {
1905+
return xerrors.Errorf("update workspace next start at: %w", err)
1906+
}
18931907
}
18941908
}
1909+
deadline = autoStop.Deadline
1910+
maxDeadline = autoStop.MaxDeadline
18951911
}
18961912

18971913
err = db.UpdateProvisionerJobWithCompleteByID(ctx, database.UpdateProvisionerJobWithCompleteByIDParams{
@@ -1917,8 +1933,8 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro
19171933
}
19181934
err = db.UpdateWorkspaceBuildDeadlineByID(ctx, database.UpdateWorkspaceBuildDeadlineByIDParams{
19191935
ID: workspaceBuild.ID,
1920-
Deadline: autoStop.Deadline,
1921-
MaxDeadline: autoStop.MaxDeadline,
1936+
Deadline: deadline,
1937+
MaxDeadline: maxDeadline,
19221938
UpdatedAt: now,
19231939
})
19241940
if err != nil {

0 commit comments

Comments
 (0)