feat: allow disabling autostart and custom autostop for template (#6933)

API only, frontend in upcoming PR.
This commit is contained in:
Dean Sheather
2023-04-04 22:48:35 +10:00
committed by GitHub
parent 083fc89f93
commit e33941b7c2
65 changed files with 1433 additions and 486 deletions

View File

@ -3,6 +3,7 @@ package executor
import (
"context"
"encoding/json"
"sync/atomic"
"time"
"github.com/google/uuid"
@ -18,11 +19,12 @@ import (
// Executor automatically starts or stops workspaces.
type Executor struct {
ctx context.Context
db database.Store
log slog.Logger
tick <-chan time.Time
statsCh chan<- Stats
ctx context.Context
db database.Store
templateScheduleStore *atomic.Pointer[schedule.TemplateScheduleStore]
log slog.Logger
tick <-chan time.Time
statsCh chan<- Stats
}
// Stats contains information about one run of Executor.
@ -33,13 +35,14 @@ type Stats struct {
}
// New returns a new autobuild executor.
func New(ctx context.Context, db database.Store, log slog.Logger, tick <-chan time.Time) *Executor {
func New(ctx context.Context, db database.Store, tss *atomic.Pointer[schedule.TemplateScheduleStore], log slog.Logger, tick <-chan time.Time) *Executor {
le := &Executor{
//nolint:gocritic // Autostart has a limited set of permissions.
ctx: dbauthz.AsAutostart(ctx),
db: db,
tick: tick,
log: log,
ctx: dbauthz.AsAutostart(ctx),
db: db,
templateScheduleStore: tss,
tick: tick,
log: log,
}
return le
}
@ -102,21 +105,11 @@ func (e *Executor) runOnce(t time.Time) Stats {
// NOTE: If a workspace build is created with a given TTL and then the user either
// changes or unsets the TTL, the deadline for the workspace build will not
// have changed. This behavior is as expected per #2229.
workspaceRows, err := e.db.GetWorkspaces(e.ctx, database.GetWorkspacesParams{
Deleted: false,
})
workspaces, err := e.db.GetWorkspacesEligibleForAutoStartStop(e.ctx, t)
if err != nil {
e.log.Error(e.ctx, "get workspaces for autostart or autostop", slog.Error(err))
return stats
}
workspaces := database.ConvertWorkspaceRows(workspaceRows)
var eligibleWorkspaceIDs []uuid.UUID
for _, ws := range workspaces {
if isEligibleForAutoStartStop(ws) {
eligibleWorkspaceIDs = append(eligibleWorkspaceIDs, ws.ID)
}
}
// We only use errgroup here for convenience of API, not for early
// cancellation. This means we only return nil errors in th eg.Go.
@ -124,8 +117,8 @@ func (e *Executor) runOnce(t time.Time) Stats {
// Limit the concurrency to avoid overloading the database.
eg.SetLimit(10)
for _, wsID := range eligibleWorkspaceIDs {
wsID := wsID
for _, ws := range workspaces {
wsID := ws.ID
log := e.log.With(slog.F("workspace_id", wsID))
eg.Go(func() error {
@ -137,9 +130,6 @@ func (e *Executor) runOnce(t time.Time) Stats {
log.Error(e.ctx, "get workspace autostart failed", slog.Error(err))
return nil
}
if !isEligibleForAutoStartStop(ws) {
return nil
}
// Determine the workspace state based on its latest build.
priorHistory, err := db.GetLatestWorkspaceBuildByWorkspaceID(e.ctx, ws.ID)
@ -148,6 +138,16 @@ func (e *Executor) runOnce(t time.Time) Stats {
return nil
}
templateSchedule, err := (*(e.templateScheduleStore.Load())).GetTemplateScheduleOptions(e.ctx, db, ws.TemplateID)
if err != nil {
log.Warn(e.ctx, "get template schedule options", slog.Error(err))
return nil
}
if !isEligibleForAutoStartStop(ws, priorHistory, templateSchedule) {
return nil
}
priorJob, err := db.GetProvisionerJobByID(e.ctx, priorHistory.JobID)
if err != nil {
log.Warn(e.ctx, "get last provisioner job for workspace %q: %w", slog.Error(err))
@ -198,8 +198,20 @@ func (e *Executor) runOnce(t time.Time) Stats {
return stats
}
func isEligibleForAutoStartStop(ws database.Workspace) bool {
return !ws.Deleted && (ws.AutostartSchedule.String != "" || ws.Ttl.Int64 > 0)
func isEligibleForAutoStartStop(ws database.Workspace, priorHistory database.WorkspaceBuild, templateSchedule schedule.TemplateScheduleOptions) bool {
if ws.Deleted {
return false
}
if templateSchedule.UserAutostartEnabled && ws.AutostartSchedule.Valid && ws.AutostartSchedule.String != "" {
return true
}
// Don't check the template schedule to see whether it allows autostop, this
// is done during the build when determining the deadline.
if priorHistory.Transition == database.WorkspaceTransitionStart && !priorHistory.Deadline.IsZero() {
return true
}
return false
}
func getNextTransition(