Files
Susana Ferreira 211393a69c fix: exclude prebuilt workspaces from lifecycle executor (#18762)
## Description

This PR updates the lifecycle executor to explicitly exclude prebuilt
workspaces from being considered for lifecycle operations such as
`autostart`, `autostop`, `dormancy`, `default TTL` and `failure TTL`.

Prebuilt workspaces (i.e., those owned by the prebuild system user) are
handled separately by the prebuild reconciliation loop. Including them
in the lifecycle executor could lead to unintended behavior such as
incorrect scheduling or state transitions.

## Changes

* Updated the lifecycle executor query
`GetWorkspacesEligibleForTransition` to exclude workspaces with
`owner_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'` (prebuilds).
* Added tests to verify prebuilt workspaces are not considered in:
  * Autostop
  * Autostart
  * Default TTL
  * Dormancy
  * Failure TTL

Fixes: https://github.com/coder/coder/issues/18740
Related to: https://github.com/coder/coder/issues/18658
2025-07-08 11:35:28 +01:00

53 lines
2.0 KiB
Go

package schedule
import (
"time"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd/schedule/cron"
)
var ErrNoAllowedAutostart = xerrors.New("no allowed autostart")
// NextAutostart takes the workspace and template schedule and returns the next autostart schedule
// after "at". The boolean returned is if the autostart should be allowed to start based on the template
// schedule.
func NextAutostart(at time.Time, wsSchedule string, templateSchedule TemplateScheduleOptions) (time.Time, bool) {
sched, err := cron.Weekly(wsSchedule)
if err != nil {
return time.Time{}, false
}
// Round down to the nearest minute, as this is the finest granularity cron supports.
// Truncate is probably not necessary here, but doing it anyway to be sure.
nextTransition := sched.Next(at).Truncate(time.Minute)
// The nextTransition is when the auto start should kick off. If it lands on a
// forbidden day, do not allow the auto start. We use the time location of the
// schedule to determine the weekday. So if "Saturday" is disallowed, the
// definition of "Saturday" depends on the location of the schedule.
zonedTransition := nextTransition.In(sched.Location())
allowed := templateSchedule.AutostartRequirement.DaysMap()[zonedTransition.Weekday()]
return zonedTransition, allowed
}
// NextAllowedAutostart returns the next valid autostart time after 'at', based on the workspace's
// cron schedule and the template's allowed days. It searches up to 7 days ahead to find a match.
func NextAllowedAutostart(at time.Time, wsSchedule string, templateSchedule TemplateScheduleOptions) (time.Time, error) {
next := at
// Our cron schedules work on a weekly basis, so to ensure we've exhausted all
// possible autostart times we need to check up to 7 days worth of autostarts.
for next.Sub(at) < 7*24*time.Hour {
var valid bool
next, valid = NextAutostart(next, wsSchedule, templateSchedule)
if valid {
return next, nil
}
}
return time.Time{}, ErrNoAllowedAutostart
}