Skip to content

Commit 7d0f53d

Browse files
committed
chore: move autobuild/executor into autobuild
1 parent c3aef93 commit 7d0f53d

File tree

6 files changed

+96
-89
lines changed

6 files changed

+96
-89
lines changed

cli/server.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ import (
6262
"github.com/coder/coder/cli/cliui"
6363
"github.com/coder/coder/cli/config"
6464
"github.com/coder/coder/coderd"
65-
"github.com/coder/coder/coderd/autobuild/executor"
65+
"github.com/coder/coder/coderd/autobuild"
6666
"github.com/coder/coder/coderd/database"
6767
"github.com/coder/coder/coderd/database/dbfake"
6868
"github.com/coder/coder/coderd/database/dbmetrics"
@@ -899,7 +899,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
899899

900900
autobuildPoller := time.NewTicker(cfg.AutobuildPollInterval.Value())
901901
defer autobuildPoller.Stop()
902-
autobuildExecutor := executor.New(ctx, options.Database, coderAPI.TemplateScheduleStore, logger, autobuildPoller.C)
902+
autobuildExecutor := autobuild.NewExecutor(ctx, options.Database, coderAPI.TemplateScheduleStore, logger, autobuildPoller.C)
903903
autobuildExecutor.Run()
904904

905905
// Currently there is no way to ask the server to shut

coderd/autobuild/doc.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Package autobuild contains logic for scheduling workspace
2+
// builds in the background.
3+
package autobuild

coderd/autobuild/executor/lifecycle_executor.go renamed to coderd/autobuild/lifecycle_executor.go

Lines changed: 69 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package executor
1+
package autobuild
22

33
import (
44
"context"
@@ -35,8 +35,8 @@ type Stats struct {
3535
Error error
3636
}
3737

38-
// New returns a new autobuild executor.
39-
func New(ctx context.Context, db database.Store, tss *atomic.Pointer[schedule.TemplateScheduleStore], log slog.Logger, tick <-chan time.Time) *Executor {
38+
// New returns a new wsactions executor.
39+
func NewExecutor(ctx context.Context, db database.Store, tss *atomic.Pointer[schedule.TemplateScheduleStore], log slog.Logger, tick <-chan time.Time) *Executor {
4040
le := &Executor{
4141
//nolint:gocritic // Autostart has a limited set of permissions.
4242
ctx: dbauthz.AsAutostart(ctx),
@@ -125,77 +125,57 @@ func (e *Executor) runOnce(t time.Time) Stats {
125125
log := e.log.With(slog.F("workspace_id", wsID))
126126

127127
eg.Go(func() error {
128-
err := e.db.InTx(func(db database.Store) error {
128+
err := e.db.InTx(func(tx database.Store) error {
129129
// Re-check eligibility since the first check was outside the
130130
// transaction and the workspace settings may have changed.
131-
ws, err := db.GetWorkspaceByID(e.ctx, wsID)
131+
ws, err := tx.GetWorkspaceByID(e.ctx, wsID)
132132
if err != nil {
133133
log.Error(e.ctx, "get workspace autostart failed", slog.Error(err))
134134
return nil
135135
}
136136

137137
// Determine the workspace state based on its latest build.
138-
priorHistory, err := db.GetLatestWorkspaceBuildByWorkspaceID(e.ctx, ws.ID)
138+
latestBuild, err := tx.GetLatestWorkspaceBuildByWorkspaceID(e.ctx, ws.ID)
139139
if err != nil {
140140
log.Warn(e.ctx, "get latest workspace build", slog.Error(err))
141141
return nil
142142
}
143143

144-
templateSchedule, err := (*(e.templateScheduleStore.Load())).GetTemplateScheduleOptions(e.ctx, db, ws.TemplateID)
144+
templateSchedule, err := (*(e.templateScheduleStore.Load())).GetTemplateScheduleOptions(e.ctx, tx, ws.TemplateID)
145145
if err != nil {
146146
log.Warn(e.ctx, "get template schedule options", slog.Error(err))
147147
return nil
148148
}
149149

150-
if !isEligibleForAutoStartStop(ws, priorHistory, templateSchedule) {
151-
return nil
152-
}
153-
154-
priorJob, err := db.GetProvisionerJobByID(e.ctx, priorHistory.JobID)
150+
latestJob, err := tx.GetProvisionerJobByID(e.ctx, latestBuild.JobID)
155151
if err != nil {
156152
log.Warn(e.ctx, "get last provisioner job for workspace %q: %w", slog.Error(err))
157153
return nil
158154
}
159155

160-
validTransition, nextTransition, err := getNextTransition(ws, priorHistory, priorJob)
156+
nextTransition, reason, err := getNextTransition(ws, latestBuild, latestJob, templateSchedule, currentTick)
161157
if err != nil {
162158
log.Debug(e.ctx, "skipping workspace", slog.Error(err))
163159
return nil
164160
}
165161

166-
if currentTick.Before(nextTransition) {
167-
log.Debug(e.ctx, "skipping workspace: too early",
168-
slog.F("next_transition_at", nextTransition),
169-
slog.F("transition", validTransition),
170-
slog.F("current_tick", currentTick),
171-
)
172-
return nil
173-
}
174-
builder := wsbuilder.New(ws, validTransition).
175-
SetLastWorkspaceBuildInTx(&priorHistory).
176-
SetLastWorkspaceBuildJobInTx(&priorJob)
177-
178-
switch validTransition {
179-
case database.WorkspaceTransitionStart:
180-
builder = builder.Reason(database.BuildReasonAutostart)
181-
case database.WorkspaceTransitionStop:
182-
builder = builder.Reason(database.BuildReasonAutostop)
183-
default:
184-
log.Error(e.ctx, "unsupported transition", slog.F("transition", validTransition))
185-
return nil
186-
}
187-
if _, _, err := builder.Build(e.ctx, db, nil); err != nil {
162+
builder := wsbuilder.New(ws, nextTransition).
163+
SetLastWorkspaceBuildInTx(&latestBuild).
164+
SetLastWorkspaceBuildJobInTx(&latestJob).
165+
Reason(reason)
166+
167+
if _, _, err := builder.Build(e.ctx, tx, nil); err != nil {
188168
log.Error(e.ctx, "unable to transition workspace",
189-
slog.F("transition", validTransition),
169+
slog.F("transition", nextTransition),
190170
slog.Error(err),
191171
)
192172
return nil
193173
}
194174
statsMu.Lock()
195-
stats.Transitions[ws.ID] = validTransition
175+
stats.Transitions[ws.ID] = nextTransition
196176
statsMu.Unlock()
197177

198-
log.Info(e.ctx, "scheduling workspace transition", slog.F("transition", validTransition))
178+
log.Info(e.ctx, "scheduling workspace transition", slog.F("transition", nextTransition))
199179

200180
return nil
201181

@@ -218,7 +198,9 @@ func (e *Executor) runOnce(t time.Time) Stats {
218198
return stats
219199
}
220200

221-
func isEligibleForAutoStartStop(ws database.Workspace, priorHistory database.WorkspaceBuild, templateSchedule schedule.TemplateScheduleOptions) bool {
201+
// isEligibleForTransition returns true if the workspace meets basic criteria
202+
// for transitioning to a new state.
203+
func isEligibleForTransition(ws database.Workspace, latestBuild database.WorkspaceBuild, templateSchedule schedule.TemplateScheduleOptions) bool {
222204
if ws.Deleted {
223205
return false
224206
}
@@ -227,7 +209,7 @@ func isEligibleForAutoStartStop(ws database.Workspace, priorHistory database.Wor
227209
}
228210
// Don't check the template schedule to see whether it allows autostop, this
229211
// is done during the build when determining the deadline.
230-
if priorHistory.Transition == database.WorkspaceTransitionStart && !priorHistory.Deadline.IsZero() {
212+
if latestBuild.Transition == database.WorkspaceTransitionStart && !latestBuild.Deadline.IsZero() {
231213
return true
232214
}
233215

@@ -236,35 +218,57 @@ func isEligibleForAutoStartStop(ws database.Workspace, priorHistory database.Wor
236218

237219
func getNextTransition(
238220
ws database.Workspace,
239-
priorHistory database.WorkspaceBuild,
240-
priorJob database.ProvisionerJob,
221+
latestBuild database.WorkspaceBuild,
222+
latestJob database.ProvisionerJob,
223+
templateSchedule schedule.TemplateScheduleOptions,
224+
currentTick time.Time,
241225
) (
242-
validTransition database.WorkspaceTransition,
243-
nextTransition time.Time,
244-
err error,
226+
database.WorkspaceTransition,
227+
database.BuildReason,
228+
error,
245229
) {
246-
if !priorJob.CompletedAt.Valid || priorJob.Error.String != "" {
247-
return "", time.Time{}, xerrors.Errorf("last workspace build did not complete successfully")
230+
if !isEligibleForTransition(ws, latestBuild, templateSchedule) {
231+
return "", "", xerrors.Errorf("workspace ineligible for transition")
248232
}
249233

250-
switch priorHistory.Transition {
251-
case database.WorkspaceTransitionStart:
252-
if priorHistory.Deadline.IsZero() {
253-
return "", time.Time{}, xerrors.Errorf("latest workspace build has zero deadline")
254-
}
255-
// For stopping, do not truncate. This is inconsistent with autostart, but
256-
// it ensures we will not stop too early.
257-
return database.WorkspaceTransitionStop, priorHistory.Deadline, nil
258-
case database.WorkspaceTransitionStop:
259-
sched, err := schedule.Weekly(ws.AutostartSchedule.String)
260-
if err != nil {
261-
return "", time.Time{}, xerrors.Errorf("workspace has invalid autostart schedule: %w", err)
262-
}
263-
// Round down to the nearest minute, as this is the finest granularity cron supports.
264-
// Truncate is probably not necessary here, but doing it anyway to be sure.
265-
nextTransition = sched.Next(priorHistory.CreatedAt).Truncate(time.Minute)
266-
return database.WorkspaceTransitionStart, nextTransition, nil
234+
if !latestJob.CompletedAt.Valid || latestJob.Error.String != "" {
235+
return "", "", xerrors.Errorf("last workspace build did not complete successfully")
236+
}
237+
238+
switch {
239+
case isEligibleForAutostop(latestBuild, currentTick):
240+
return database.WorkspaceTransitionStop, database.BuildReasonAutostop, nil
241+
case isEligibleForAutostart(ws, latestBuild, currentTick):
242+
return database.WorkspaceTransitionStart, database.BuildReasonAutostart, nil
267243
default:
268-
return "", time.Time{}, xerrors.Errorf("last transition not valid for autostart or autostop")
244+
return "", "", xerrors.Errorf("last transition not valid for autostart or autostop")
245+
}
246+
}
247+
248+
// isEligibleForAutostart returns true if the workspace should be autostarted.
249+
func isEligibleForAutostart(ws database.Workspace, build database.WorkspaceBuild, currentTick time.Time) bool {
250+
// If the last transition for the workspace was not 'stop' then the workspace
251+
// cannot be started.
252+
if build.Transition != database.WorkspaceTransitionStop {
253+
return false
254+
}
255+
256+
sched, err := schedule.Weekly(ws.AutostartSchedule.String)
257+
if err != nil {
258+
return false
269259
}
260+
// Round down to the nearest minute, as this is the finest granularity cron supports.
261+
// Truncate is probably not necessary here, but doing it anyway to be sure.
262+
nextTransition := sched.Next(build.CreatedAt).Truncate(time.Minute)
263+
264+
return !currentTick.Before(nextTransition)
265+
}
266+
267+
// isEligibleForAutostart returns true if the workspace should be autostopped.
268+
func isEligibleForAutostop(build database.WorkspaceBuild, currentTick time.Time) bool {
269+
// A workspace must be started in order for it to be auto-stopped.
270+
return build.Transition == database.WorkspaceTransitionStart &&
271+
!build.Deadline.IsZero() &&
272+
// We do not want to stop a workspace prior to it breaching its deadline.
273+
!currentTick.Before(build.Deadline)
270274
}

coderd/autobuild/executor/lifecycle_executor_test.go renamed to coderd/autobuild/lifecycle_executor_test.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package executor_test
1+
package autobuild_test
22

33
import (
44
"context"
@@ -11,7 +11,7 @@ import (
1111
"github.com/stretchr/testify/require"
1212
"go.uber.org/goleak"
1313

14-
"github.com/coder/coder/coderd/autobuild/executor"
14+
"github.com/coder/coder/coderd/autobuild"
1515
"github.com/coder/coder/coderd/coderdtest"
1616
"github.com/coder/coder/coderd/database"
1717
"github.com/coder/coder/coderd/schedule"
@@ -27,7 +27,7 @@ func TestExecutorAutostartOK(t *testing.T) {
2727
var (
2828
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
2929
tickCh = make(chan time.Time)
30-
statsCh = make(chan executor.Stats)
30+
statsCh = make(chan autobuild.Stats)
3131
client = coderdtest.New(t, &coderdtest.Options{
3232
AutobuildTicker: tickCh,
3333
IncludeProvisionerDaemon: true,
@@ -66,7 +66,7 @@ func TestExecutorAutostartTemplateUpdated(t *testing.T) {
6666
ctx = context.Background()
6767
err error
6868
tickCh = make(chan time.Time)
69-
statsCh = make(chan executor.Stats)
69+
statsCh = make(chan autobuild.Stats)
7070
client = coderdtest.New(t, &coderdtest.Options{
7171
AutobuildTicker: tickCh,
7272
IncludeProvisionerDaemon: true,
@@ -113,7 +113,7 @@ func TestExecutorAutostartAlreadyRunning(t *testing.T) {
113113
var (
114114
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
115115
tickCh = make(chan time.Time)
116-
statsCh = make(chan executor.Stats)
116+
statsCh = make(chan autobuild.Stats)
117117
client = coderdtest.New(t, &coderdtest.Options{
118118
AutobuildTicker: tickCh,
119119
IncludeProvisionerDaemon: true,
@@ -145,7 +145,7 @@ func TestExecutorAutostartNotEnabled(t *testing.T) {
145145

146146
var (
147147
tickCh = make(chan time.Time)
148-
statsCh = make(chan executor.Stats)
148+
statsCh = make(chan autobuild.Stats)
149149
client = coderdtest.New(t, &coderdtest.Options{
150150
AutobuildTicker: tickCh,
151151
IncludeProvisionerDaemon: true,
@@ -180,7 +180,7 @@ func TestExecutorAutostopOK(t *testing.T) {
180180

181181
var (
182182
tickCh = make(chan time.Time)
183-
statsCh = make(chan executor.Stats)
183+
statsCh = make(chan autobuild.Stats)
184184
client = coderdtest.New(t, &coderdtest.Options{
185185
AutobuildTicker: tickCh,
186186
IncludeProvisionerDaemon: true,
@@ -216,7 +216,7 @@ func TestExecutorAutostopExtend(t *testing.T) {
216216
var (
217217
ctx = context.Background()
218218
tickCh = make(chan time.Time)
219-
statsCh = make(chan executor.Stats)
219+
statsCh = make(chan autobuild.Stats)
220220
client = coderdtest.New(t, &coderdtest.Options{
221221
AutobuildTicker: tickCh,
222222
IncludeProvisionerDaemon: true,
@@ -266,7 +266,7 @@ func TestExecutorAutostopAlreadyStopped(t *testing.T) {
266266

267267
var (
268268
tickCh = make(chan time.Time)
269-
statsCh = make(chan executor.Stats)
269+
statsCh = make(chan autobuild.Stats)
270270
client = coderdtest.New(t, &coderdtest.Options{
271271
AutobuildTicker: tickCh,
272272
IncludeProvisionerDaemon: true,
@@ -299,7 +299,7 @@ func TestExecutorAutostopNotEnabled(t *testing.T) {
299299
var (
300300
ctx = context.Background()
301301
tickCh = make(chan time.Time)
302-
statsCh = make(chan executor.Stats)
302+
statsCh = make(chan autobuild.Stats)
303303
client = coderdtest.New(t, &coderdtest.Options{
304304
AutobuildTicker: tickCh,
305305
IncludeProvisionerDaemon: true,
@@ -341,7 +341,7 @@ func TestExecutorWorkspaceDeleted(t *testing.T) {
341341
var (
342342
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
343343
tickCh = make(chan time.Time)
344-
statsCh = make(chan executor.Stats)
344+
statsCh = make(chan autobuild.Stats)
345345
client = coderdtest.New(t, &coderdtest.Options{
346346
AutobuildTicker: tickCh,
347347
IncludeProvisionerDaemon: true,
@@ -374,7 +374,7 @@ func TestExecutorWorkspaceAutostartTooEarly(t *testing.T) {
374374
var (
375375
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
376376
tickCh = make(chan time.Time)
377-
statsCh = make(chan executor.Stats)
377+
statsCh = make(chan autobuild.Stats)
378378
client = coderdtest.New(t, &coderdtest.Options{
379379
AutobuildTicker: tickCh,
380380
IncludeProvisionerDaemon: true,
@@ -405,7 +405,7 @@ func TestExecutorWorkspaceAutostopBeforeDeadline(t *testing.T) {
405405

406406
var (
407407
tickCh = make(chan time.Time)
408-
statsCh = make(chan executor.Stats)
408+
statsCh = make(chan autobuild.Stats)
409409
client = coderdtest.New(t, &coderdtest.Options{
410410
AutobuildTicker: tickCh,
411411
IncludeProvisionerDaemon: true,
@@ -433,7 +433,7 @@ func TestExecutorWorkspaceAutostopNoWaitChangedMyMind(t *testing.T) {
433433
var (
434434
ctx = context.Background()
435435
tickCh = make(chan time.Time)
436-
statsCh = make(chan executor.Stats)
436+
statsCh = make(chan autobuild.Stats)
437437
client = coderdtest.New(t, &coderdtest.Options{
438438
AutobuildTicker: tickCh,
439439
IncludeProvisionerDaemon: true,
@@ -501,8 +501,8 @@ func TestExecutorAutostartMultipleOK(t *testing.T) {
501501
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
502502
tickCh = make(chan time.Time)
503503
tickCh2 = make(chan time.Time)
504-
statsCh1 = make(chan executor.Stats)
505-
statsCh2 = make(chan executor.Stats)
504+
statsCh1 = make(chan autobuild.Stats)
505+
statsCh2 = make(chan autobuild.Stats)
506506
client = coderdtest.New(t, &coderdtest.Options{
507507
AutobuildTicker: tickCh,
508508
IncludeProvisionerDaemon: true,
@@ -556,7 +556,7 @@ func TestExecutorAutostartWithParameters(t *testing.T) {
556556
var (
557557
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
558558
tickCh = make(chan time.Time)
559-
statsCh = make(chan executor.Stats)
559+
statsCh = make(chan autobuild.Stats)
560560
client = coderdtest.New(t, &coderdtest.Options{
561561
AutobuildTicker: tickCh,
562562
IncludeProvisionerDaemon: true,
@@ -609,7 +609,7 @@ func TestExecutorAutostartTemplateDisabled(t *testing.T) {
609609
var (
610610
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
611611
tickCh = make(chan time.Time)
612-
statsCh = make(chan executor.Stats)
612+
statsCh = make(chan autobuild.Stats)
613613

614614
client = coderdtest.New(t, &coderdtest.Options{
615615
AutobuildTicker: tickCh,

coderd/autobuild/notify/notifier_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"go.uber.org/atomic"
1010
"go.uber.org/goleak"
1111

12-
"github.com/coder/coder/coderd/autobuild/notify"
12+
"github.com/coder/coder/coderd/wsactions/notify"
1313
)
1414

1515
func TestNotifier(t *testing.T) {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy