mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add user quiet hours schedule and restart requirement feature flag (#8115)
This commit is contained in:
+109
-24
@@ -4,35 +4,113 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/coderd/database"
|
||||
)
|
||||
|
||||
const MaxTemplateRestartRequirementWeeks = 16
|
||||
|
||||
func TemplateRestartRequirementEpoch(loc *time.Location) time.Time {
|
||||
// The "first week" starts on January 2nd, 2023, which is the first Monday
|
||||
// of 2023. All other weeks are counted using modulo arithmetic from that
|
||||
// date.
|
||||
return time.Date(2023, time.January, 2, 0, 0, 0, 0, loc)
|
||||
}
|
||||
|
||||
// DaysOfWeek intentionally starts on Monday as opposed to Sunday so the weekend
|
||||
// days are contiguous in the bitmap. This matters greatly when doing restarts
|
||||
// every second week or more to avoid workspaces restarting "at the start" of
|
||||
// the week rather than "at the end" of the week.
|
||||
var DaysOfWeek = []time.Weekday{
|
||||
time.Monday,
|
||||
time.Tuesday,
|
||||
time.Wednesday,
|
||||
time.Thursday,
|
||||
time.Friday,
|
||||
time.Saturday,
|
||||
time.Sunday,
|
||||
}
|
||||
|
||||
type TemplateRestartRequirement struct {
|
||||
// DaysOfWeek is a bitmap of which days of the week the workspace must be
|
||||
// restarted. If fully zero, the workspace is not required to be restarted
|
||||
// ever.
|
||||
//
|
||||
// First bit is Monday, ..., seventh bit is Sunday, eighth bit is unused.
|
||||
DaysOfWeek uint8
|
||||
// Weeks is the amount of weeks between restarts. If 0 or 1, the workspace
|
||||
// is restarted weekly in accordance with DaysOfWeek. If 2, the workspace is
|
||||
// restarted every other week. And so forth.
|
||||
//
|
||||
// The limit for this value is 16, which is roughly 4 months.
|
||||
//
|
||||
// The "first week" starts on January 2nd, 2023, which is the first Monday
|
||||
// of 2023. All other weeks are counted using modulo arithmetic from that
|
||||
// date.
|
||||
Weeks int64
|
||||
}
|
||||
|
||||
// DaysMap returns a map of the days of the week that the workspace must be
|
||||
// restarted.
|
||||
func (r TemplateRestartRequirement) DaysMap() map[time.Weekday]bool {
|
||||
days := make(map[time.Weekday]bool)
|
||||
for i, day := range DaysOfWeek {
|
||||
days[day] = r.DaysOfWeek&(1<<uint(i)) != 0
|
||||
}
|
||||
return days
|
||||
}
|
||||
|
||||
// VerifyTemplateRestartRequirement returns an error if the restart requirement
|
||||
// is invalid.
|
||||
func VerifyTemplateRestartRequirement(days uint8, weeks int64) error {
|
||||
if days&0b10000000 != 0 {
|
||||
return xerrors.New("invalid restart requirement days, last bit is set")
|
||||
}
|
||||
if days > 0b11111111 {
|
||||
return xerrors.New("invalid restart requirement days, too large")
|
||||
}
|
||||
if weeks < 0 {
|
||||
return xerrors.New("invalid restart requirement weeks, negative")
|
||||
}
|
||||
if weeks > MaxTemplateRestartRequirementWeeks {
|
||||
return xerrors.New("invalid restart requirement weeks, too large")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type TemplateScheduleOptions struct {
|
||||
UserAutostartEnabled bool `json:"user_autostart_enabled"`
|
||||
UserAutostopEnabled bool `json:"user_autostop_enabled"`
|
||||
DefaultTTL time.Duration `json:"default_ttl"`
|
||||
// If MaxTTL is set, the workspace must be stopped before this time or it
|
||||
// will be stopped automatically.
|
||||
//
|
||||
// If set, users cannot disable automatic workspace shutdown.
|
||||
// TODO(@dean): remove MaxTTL once restart_requirement is matured and the
|
||||
// default
|
||||
MaxTTL time.Duration `json:"max_ttl"`
|
||||
// FailureTTL dictates the duration after which failed workspaces will be stopped automatically.
|
||||
// UseRestartRequirement dictates whether the restart requirement should be
|
||||
// used instead of MaxTTL. This is governed by the feature flag and
|
||||
// licensing.
|
||||
// TODO(@dean): remove this when we remove max_tll
|
||||
UseRestartRequirement bool
|
||||
// RestartRequirement dictates when the workspace must be restarted. This
|
||||
// used to be handled by MaxTTL.
|
||||
RestartRequirement TemplateRestartRequirement `json:"restart_requirement"`
|
||||
// FailureTTL dictates the duration after which failed workspaces will be
|
||||
// stopped automatically.
|
||||
FailureTTL time.Duration `json:"failure_ttl"`
|
||||
// InactivityTTL dictates the duration after which inactive workspaces will be locked.
|
||||
// InactivityTTL dictates the duration after which inactive workspaces will
|
||||
// be locked.
|
||||
InactivityTTL time.Duration `json:"inactivity_ttl"`
|
||||
// LockedTTL dictates the duration after which locked workspaces will be permanently deleted.
|
||||
// LockedTTL dictates the duration after which locked workspaces will be
|
||||
// permanently deleted.
|
||||
LockedTTL time.Duration `json:"locked_ttl"`
|
||||
}
|
||||
|
||||
// TemplateScheduleStore provides an interface for retrieving template
|
||||
// scheduling options set by the template/site admin.
|
||||
type TemplateScheduleStore interface {
|
||||
GetTemplateScheduleOptions(ctx context.Context, db database.Store, templateID uuid.UUID) (TemplateScheduleOptions, error)
|
||||
SetTemplateScheduleOptions(ctx context.Context, db database.Store, template database.Template, opts TemplateScheduleOptions) (database.Template, error)
|
||||
Get(ctx context.Context, db database.Store, templateID uuid.UUID) (TemplateScheduleOptions, error)
|
||||
Set(ctx context.Context, db database.Store, template database.Template, opts TemplateScheduleOptions) (database.Template, error)
|
||||
}
|
||||
|
||||
type agplTemplateScheduleStore struct{}
|
||||
@@ -43,7 +121,7 @@ func NewAGPLTemplateScheduleStore() TemplateScheduleStore {
|
||||
return &agplTemplateScheduleStore{}
|
||||
}
|
||||
|
||||
func (*agplTemplateScheduleStore) GetTemplateScheduleOptions(ctx context.Context, db database.Store, templateID uuid.UUID) (TemplateScheduleOptions, error) {
|
||||
func (*agplTemplateScheduleStore) Get(ctx context.Context, db database.Store, templateID uuid.UUID) (TemplateScheduleOptions, error) {
|
||||
tpl, err := db.GetTemplateByID(ctx, templateID)
|
||||
if err != nil {
|
||||
return TemplateScheduleOptions{}, err
|
||||
@@ -55,16 +133,21 @@ func (*agplTemplateScheduleStore) GetTemplateScheduleOptions(ctx context.Context
|
||||
UserAutostartEnabled: true,
|
||||
UserAutostopEnabled: true,
|
||||
DefaultTTL: time.Duration(tpl.DefaultTTL),
|
||||
// Disregard the values in the database, since MaxTTL, FailureTTL, InactivityTTL, and LockedTTL are enterprise
|
||||
// features.
|
||||
MaxTTL: 0,
|
||||
// Disregard the values in the database, since RestartRequirement,
|
||||
// FailureTTL, InactivityTTL, and LockedTTL are enterprise features.
|
||||
UseRestartRequirement: false,
|
||||
MaxTTL: 0,
|
||||
RestartRequirement: TemplateRestartRequirement{
|
||||
DaysOfWeek: 0,
|
||||
Weeks: 0,
|
||||
},
|
||||
FailureTTL: 0,
|
||||
InactivityTTL: 0,
|
||||
LockedTTL: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (*agplTemplateScheduleStore) SetTemplateScheduleOptions(ctx context.Context, db database.Store, tpl database.Template, opts TemplateScheduleOptions) (database.Template, error) {
|
||||
func (*agplTemplateScheduleStore) Set(ctx context.Context, db database.Store, tpl database.Template, opts TemplateScheduleOptions) (database.Template, error) {
|
||||
if int64(opts.DefaultTTL) == tpl.DefaultTTL {
|
||||
// Avoid updating the UpdatedAt timestamp if nothing will be changed.
|
||||
return tpl, nil
|
||||
@@ -76,14 +159,16 @@ func (*agplTemplateScheduleStore) SetTemplateScheduleOptions(ctx context.Context
|
||||
ID: tpl.ID,
|
||||
UpdatedAt: database.Now(),
|
||||
DefaultTTL: int64(opts.DefaultTTL),
|
||||
// Don't allow changing it, but keep the value in the DB (to avoid
|
||||
// clearing settings if the license has an issue).
|
||||
AllowUserAutostart: tpl.AllowUserAutostart,
|
||||
AllowUserAutostop: tpl.AllowUserAutostop,
|
||||
MaxTTL: tpl.MaxTTL,
|
||||
FailureTTL: tpl.FailureTTL,
|
||||
InactivityTTL: tpl.InactivityTTL,
|
||||
LockedTTL: tpl.LockedTTL,
|
||||
// Don't allow changing these settings, but keep the value in the DB (to
|
||||
// avoid clearing settings if the license has an issue).
|
||||
MaxTTL: tpl.MaxTTL,
|
||||
RestartRequirementDaysOfWeek: tpl.RestartRequirementDaysOfWeek,
|
||||
RestartRequirementWeeks: tpl.RestartRequirementWeeks,
|
||||
AllowUserAutostart: tpl.AllowUserAutostart,
|
||||
AllowUserAutostop: tpl.AllowUserAutostop,
|
||||
FailureTTL: tpl.FailureTTL,
|
||||
InactivityTTL: tpl.InactivityTTL,
|
||||
LockedTTL: tpl.LockedTTL,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("update template schedule: %w", err)
|
||||
|
||||
Reference in New Issue
Block a user