mirror of
https://github.com/coder/coder.git
synced 2025-07-08 11:39:50 +00:00
132 lines
5.1 KiB
Go
132 lines
5.1 KiB
Go
package schedule
|
|
|
|
import (
|
|
"context"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/coder/coder/coderd/database"
|
|
agpl "github.com/coder/coder/coderd/schedule"
|
|
)
|
|
|
|
// EnterpriseTemplateScheduleStore provides an agpl.TemplateScheduleStore that
|
|
// has all fields implemented for enterprise customers.
|
|
type EnterpriseTemplateScheduleStore struct {
|
|
// UseRestartRequirement decides whether the RestartRequirement field should
|
|
// be used instead of the MaxTTL field for determining the max deadline of a
|
|
// workspace build. This value is determined by a feature flag, licensing,
|
|
// and whether a default user quiet hours schedule is set.
|
|
UseRestartRequirement atomic.Bool
|
|
}
|
|
|
|
var _ agpl.TemplateScheduleStore = &EnterpriseTemplateScheduleStore{}
|
|
|
|
func NewEnterpriseTemplateScheduleStore() *EnterpriseTemplateScheduleStore {
|
|
return &EnterpriseTemplateScheduleStore{}
|
|
}
|
|
|
|
// Get implements agpl.TemplateScheduleStore.
|
|
func (s *EnterpriseTemplateScheduleStore) Get(ctx context.Context, db database.Store, templateID uuid.UUID) (agpl.TemplateScheduleOptions, error) {
|
|
tpl, err := db.GetTemplateByID(ctx, templateID)
|
|
if err != nil {
|
|
return agpl.TemplateScheduleOptions{}, err
|
|
}
|
|
|
|
// These extra checks have to be done before the conversion because we lose
|
|
// precision and signs when converting to the agpl types from the database.
|
|
if tpl.RestartRequirementDaysOfWeek < 0 {
|
|
return agpl.TemplateScheduleOptions{}, xerrors.New("invalid restart requirement days, negative")
|
|
}
|
|
if tpl.RestartRequirementDaysOfWeek > 0b11111111 {
|
|
return agpl.TemplateScheduleOptions{}, xerrors.New("invalid restart requirement days, too large")
|
|
}
|
|
err = agpl.VerifyTemplateRestartRequirement(uint8(tpl.RestartRequirementDaysOfWeek), tpl.RestartRequirementWeeks)
|
|
if err != nil {
|
|
return agpl.TemplateScheduleOptions{}, err
|
|
}
|
|
|
|
return agpl.TemplateScheduleOptions{
|
|
UserAutostartEnabled: tpl.AllowUserAutostart,
|
|
UserAutostopEnabled: tpl.AllowUserAutostop,
|
|
DefaultTTL: time.Duration(tpl.DefaultTTL),
|
|
MaxTTL: time.Duration(tpl.MaxTTL),
|
|
UseRestartRequirement: s.UseRestartRequirement.Load(),
|
|
RestartRequirement: agpl.TemplateRestartRequirement{
|
|
DaysOfWeek: uint8(tpl.RestartRequirementDaysOfWeek),
|
|
Weeks: tpl.RestartRequirementWeeks,
|
|
},
|
|
FailureTTL: time.Duration(tpl.FailureTTL),
|
|
InactivityTTL: time.Duration(tpl.InactivityTTL),
|
|
LockedTTL: time.Duration(tpl.LockedTTL),
|
|
}, nil
|
|
}
|
|
|
|
// Set implements agpl.TemplateScheduleStore.
|
|
func (*EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.Store, tpl database.Template, opts agpl.TemplateScheduleOptions) (database.Template, error) {
|
|
if int64(opts.DefaultTTL) == tpl.DefaultTTL &&
|
|
int64(opts.MaxTTL) == tpl.MaxTTL &&
|
|
int16(opts.RestartRequirement.DaysOfWeek) == tpl.RestartRequirementDaysOfWeek &&
|
|
opts.RestartRequirement.Weeks == tpl.RestartRequirementWeeks &&
|
|
int64(opts.FailureTTL) == tpl.FailureTTL &&
|
|
int64(opts.InactivityTTL) == tpl.InactivityTTL &&
|
|
int64(opts.LockedTTL) == tpl.LockedTTL &&
|
|
opts.UserAutostartEnabled == tpl.AllowUserAutostart &&
|
|
opts.UserAutostopEnabled == tpl.AllowUserAutostop {
|
|
// Avoid updating the UpdatedAt timestamp if nothing will be changed.
|
|
return tpl, nil
|
|
}
|
|
|
|
err := agpl.VerifyTemplateRestartRequirement(opts.RestartRequirement.DaysOfWeek, opts.RestartRequirement.Weeks)
|
|
if err != nil {
|
|
return database.Template{}, err
|
|
}
|
|
|
|
var template database.Template
|
|
err = db.InTx(func(db database.Store) error {
|
|
err := db.UpdateTemplateScheduleByID(ctx, database.UpdateTemplateScheduleByIDParams{
|
|
ID: tpl.ID,
|
|
UpdatedAt: database.Now(),
|
|
AllowUserAutostart: opts.UserAutostartEnabled,
|
|
AllowUserAutostop: opts.UserAutostopEnabled,
|
|
DefaultTTL: int64(opts.DefaultTTL),
|
|
MaxTTL: int64(opts.MaxTTL),
|
|
RestartRequirementDaysOfWeek: int16(opts.RestartRequirement.DaysOfWeek),
|
|
RestartRequirementWeeks: opts.RestartRequirement.Weeks,
|
|
FailureTTL: int64(opts.FailureTTL),
|
|
InactivityTTL: int64(opts.InactivityTTL),
|
|
LockedTTL: int64(opts.LockedTTL),
|
|
})
|
|
if err != nil {
|
|
return xerrors.Errorf("update template schedule: %w", err)
|
|
}
|
|
|
|
// If we updated the locked_ttl we need to update all the workspaces deleting_at
|
|
// to ensure workspaces are being cleaned up correctly. Similarly if we are
|
|
// disabling it (by passing 0), then we want to delete nullify the deleting_at
|
|
// fields of all the template workspaces.
|
|
err = db.UpdateWorkspacesDeletingAtByTemplateID(ctx, database.UpdateWorkspacesDeletingAtByTemplateIDParams{
|
|
TemplateID: tpl.ID,
|
|
LockedTtlMs: opts.LockedTTL.Milliseconds(),
|
|
})
|
|
if err != nil {
|
|
return xerrors.Errorf("update deleting_at of all workspaces for new locked_ttl %q: %w", opts.LockedTTL, err)
|
|
}
|
|
|
|
// TODO: update all workspace max_deadlines to be within new bounds
|
|
template, err = db.GetTemplateByID(ctx, tpl.ID)
|
|
if err != nil {
|
|
return xerrors.Errorf("get updated template schedule: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}, nil)
|
|
if err != nil {
|
|
return database.Template{}, err
|
|
}
|
|
|
|
return template, nil
|
|
}
|